interface RateLimitEntry { count: number; resetTime: number; } const rateLimitStore = new Map(); // Clean up old entries every 5 minutes setInterval(() => { const now = Date.now(); for (const [key, entry] of rateLimitStore.entries()) { if (entry.resetTime < now) { rateLimitStore.delete(key); } } }, 5 * 60 * 1000); export interface RateLimitResult { success: boolean; limit: number; remaining: number; reset: number; } export function rateLimit( identifier: string, limit: number = 10, windowMs: number = 60 * 1000 // 1 minute ): RateLimitResult { const now = Date.now(); const entry = rateLimitStore.get(identifier); // No existing entry or expired if (!entry || entry.resetTime < now) { const resetTime = now + windowMs; rateLimitStore.set(identifier, { count: 1, resetTime, }); return { success: true, limit, remaining: limit - 1, reset: resetTime, }; } // Check if limit exceeded if (entry.count >= limit) { return { success: false, limit, remaining: 0, reset: entry.resetTime, }; } // Increment count entry.count++; rateLimitStore.set(identifier, entry); return { success: true, limit, remaining: limit - entry.count, reset: entry.resetTime, }; } export function getClientIP(request: Request): string { // Try to get IP from various headers (Vercel, Cloudflare, etc.) const forwarded = request.headers.get("x-forwarded-for"); if (forwarded) { return forwarded.split(",")[0].trim(); } const real = request.headers.get("x-real-ip"); if (real) { return real; } // Fallback to a default identifier return "unknown"; }