import type { Context, Next } from "hono"; interface RateLimitEntry { count: number; resetAt: number; } const store = new Map(); function getClientIp(c: Context): string { return c.req.header("x-forwarded-for")?.split(",")[0]?.trim() || "unknown"; } function cleanup() { const now = Date.now(); for (const [key, entry] of store) { if (now >= entry.resetAt) { store.delete(key); } } } export function createRateLimit(maxAttempts: number, windowMs: number) { return async function rateLimitMiddleware(c: Context, next: Next) { cleanup(); const ip = getClientIp(c); const key = `${ip}:${c.req.path}`; const now = Date.now(); const entry = store.get(key); if (!entry || now >= entry.resetAt) { store.set(key, { count: 1, resetAt: now + windowMs }); return next(); } if (entry.count >= maxAttempts) { const retryAfter = Math.ceil((entry.resetAt - now) / 1000); c.header("Retry-After", String(retryAfter)); return c.json({ error: "Too many attempts. Try again later." }, 429); } entry.count++; return next(); }; } export const rateLimit = createRateLimit(5, 15 * 60 * 1000); /** @internal — only for testing */ export function _resetForTesting() { store.clear(); }