import type { Context, Next } from "hono"; interface RateLimitEntry { count: number; resetAt: number; } const store = new Map(); const MAX_ATTEMPTS = 5; const WINDOW_MS = 15 * 60 * 1000; // 15 minutes 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 async function rateLimit(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 + WINDOW_MS }); return next(); } if (entry.count >= MAX_ATTEMPTS) { 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(); } /** @internal — only for testing */ export function _resetForTesting() { store.clear(); }