type Bucket = { count: number; resetAt: number }; const buckets = new Map(); const MAX_BUCKETS = 10_000; export function requestIp(req: Request): string { // The deployment binds Next.js to localhost behind a reverse proxy. The // proxy must replace client-supplied forwarding headers for this to be safe. return ( req.headers.get("x-real-ip") || req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown" ); } export function rateLimit(key: string, max: number, windowMs: number): boolean { const now = Date.now(); const current = buckets.get(key); if (!current || current.resetAt <= now) { if (buckets.size >= MAX_BUCKETS) { for (const [bucketKey, bucket] of buckets) { if (bucket.resetAt <= now) buckets.delete(bucketKey); } if (buckets.size >= MAX_BUCKETS) buckets.delete(buckets.keys().next().value as string); } buckets.set(key, { count: 1, resetAt: now + windowMs }); return true; } if (current.count >= max) return false; current.count += 1; return true; }