UNPKG

restifyx.js

Version:

Advanced API endpoint handler system with automatic documentation

96 lines 3.6 kB
"use strict"; 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