restifyx.js
Version:
Advanced API endpoint handler system with automatic documentation
96 lines • 3.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.memoryRateLimiter = void 0;
exports.rateLimitMiddleware = rateLimitMiddleware;
const ApiError_1 = require("../core/errors/ApiError");
const logger_1 = require("./logger");
class MemoryRateLimiter {
constructor(cleanupIntervalMs = 60000) {
this.limits = new Map();
this.logger = (0, logger_1.getLogger)();
this.interval = setInterval(() => this.cleanup(), cleanupIntervalMs);
}
check(key, options) {
const now = Date.now();
const resetTime = now + options.windowMs;
let info = this.limits.get(key);
if (!info || now > info.resetTime) {
info = { count: 0, resetTime, lastAttempt: now };
}
info.count++;
info.lastAttempt = now;
this.limits.set(key, info);
const limited = info.count > options.max;
if (limited) {
this.logger.debug(`Rate limit exceeded for key: ${key}, count: ${info.count}, max: ${options.max}`);
}
return {
limited,
info,
};
}
cleanup() {
const now = Date.now();
let expiredCount = 0;
for (const [key, info] of this.limits.entries()) {
if (now > info.resetTime) {
this.limits.delete(key);
expiredCount++;
}
}
if (expiredCount > 0) {
this.logger.debug(`Rate limiter cleanup: removed ${expiredCount} expired entries`);
}
}
stop() {
clearInterval(this.interval);
}
getKeys() {
return Array.from(this.limits.keys());
}
reset(key) {
return this.limits.delete(key);
}
resetAll() {
this.limits.clear();
}
getInfo(key) {
return this.limits.get(key);
}
}
const memoryRateLimiter = new MemoryRateLimiter();
exports.memoryRateLimiter = memoryRateLimiter;
function rateLimitMiddleware(options) {
return (req, res, next) => {
if (options.skip && options.skip(req)) {
return next();
}
const key = options.keyGenerator
? options.keyGenerator(req)
: req.ip || req.headers["x-forwarded-for"] || "unknown";
const { limited, info } = memoryRateLimiter.check(key, options);
if (options.headers !== false) {
if (options.standardHeaders !== false) {
res.setHeader("RateLimit-Limit", options.max.toString());
res.setHeader("RateLimit-Remaining", Math.max(0, options.max - info.count).toString());
res.setHeader("RateLimit-Reset", Math.ceil(info.resetTime / 1000).toString());
}
if (options.legacyHeaders !== false) {
res.setHeader("X-RateLimit-Limit", options.max.toString());
res.setHeader("X-RateLimit-Remaining", Math.max(0, options.max - info.count).toString());
res.setHeader("X-RateLimit-Reset", Math.ceil(info.resetTime / 1000).toString());
}
if (options.draft_polli_ratelimit_headers) {
res.setHeader("RateLimit-Policy", `${options.max};w=${Math.floor(options.windowMs / 1000)}`);
}
}
if (limited) {
const message = options.message || "Too many requests, please try again later.";
throw new ApiError_1.ApiError(429, message, {
retryAfter: Math.ceil((info.resetTime - Date.now()) / 1000),
}, "RATE_LIMIT_EXCEEDED");
}
next();
};
}
//# sourceMappingURL=rate-limiter.js.map