const WINDOW_MS = 15 * 60 * 1000; // 15 minutes const MAX_ATTEMPTS = 5; interface RateLimitEntry { count: number; resetAt: number; } const store = new Map(); function cleanup() { const now = Date.now(); for (const [key, entry] of store) { if (now >= entry.resetAt) { store.delete(key); } } } let cleanupInterval: ReturnType | null = null; function ensureCleanupRunning() { if (cleanupInterval) return; cleanupInterval = setInterval(cleanup, 5 * 60 * 1000); if (typeof cleanupInterval === "object" && "unref" in cleanupInterval) { cleanupInterval.unref(); } } export function checkRateLimit(key: string): { allowed: boolean; remaining: number; retryAfterMs: number; } { ensureCleanupRunning(); const now = Date.now(); const entry = store.get(key); if (!entry || now >= entry.resetAt) { store.set(key, { count: 1, resetAt: now + WINDOW_MS }); return { allowed: true, remaining: MAX_ATTEMPTS - 1, retryAfterMs: 0 }; } if (entry.count >= MAX_ATTEMPTS) { return { allowed: false, remaining: 0, retryAfterMs: entry.resetAt - now, }; } entry.count += 1; return { allowed: true, remaining: MAX_ATTEMPTS - entry.count, retryAfterMs: 0, }; } export function getClientIp(request: Request): string { const forwarded = request.headers.get("x-forwarded-for"); if (forwarded) { return forwarded.split(",")[0].trim(); } return "unknown"; }