const defaultMessages = {
403: "Forbidden",
404: "Not Found"
};
const createError = (status, message) =>
Object.assign(new Error(message ?? defaultMessages[status]), { status });
const parseCookies = (req, res, next) => {
req.cookies = (req.headers.cookie ?? "")
.split(";")
.map(pair => pair.trim().split("="))
.filter(pair => pair[0])
.reduce((jar, [key, ...rest]) => {
try {
jar[key] = decodeURIComponent(rest.join("="));
} catch {
jar[key] = rest.join("=");
}
return jar;
}, {});
next();
};
const speedLimiter = ({ windowMs, limit, skip }) => {
const hits = new Map();
setInterval(() => {
const now = Date.now();
hits.forEach((timestamps, key) => {
const fresh = timestamps.filter(t => now - t < windowMs);
fresh.length ? hits.set(key, fresh) : hits.delete(key);
});
}, windowMs).unref();
return (req, res, next) => {
if (skip?.(req, res)) return next();
const key = req.headers["x-real-ip"] ?? req.ip;
const now = Date.now();
const timestamps = (hits.get(key) ?? []).filter(
t => now - t < windowMs
);
if (timestamps.length >= limit) {
return res
.status(429)
.send("Too many requests, please try again later.");
}
hits.set(key, [...timestamps, now]);
next();
};
};
const accessLogger =
({ skip, stream }) =>
(req, res, next) => {
res.on("finish", () => {
if (skip?.(req, res)) return;
const date = new Date().toISOString();
const url = req.originalUrl ?? req.url;
const referrer = req.headers.referer ?? "-";
const userAgent = req.headers["user-agent"] ?? "-";
stream.write(`${date} ${url} ${referrer} /// ${userAgent}\n`);
});
next();
};
export { createError, parseCookies, speedLimiter, accessLogger };