/** * Client-side rate limiting utilities to prevent spam requests */ interface RateLimitEntry { count: number; resetTime: number; } const rateLimitStore = new Map(); /** * Check if action is rate limited * @param key Unique key for the action (e.g., 'payment', 'ticket-create') * @param maxRequests Maximum requests allowed in the time window * @param windowMs Time window in milliseconds * @returns true if action is allowed, false if rate limited */ export function checkRateLimit( key: string, maxRequests: number = 3, windowMs: number = 10000, ): boolean { const now = Date.now(); const entry = rateLimitStore.get(key); // Clean up expired entry if (entry && now >= entry.resetTime) { rateLimitStore.delete(key); } const currentEntry = rateLimitStore.get(key); if (!currentEntry) { // First request - allow and start tracking rateLimitStore.set(key, { count: 1, resetTime: now + windowMs, }); return true; } if (currentEntry.count >= maxRequests) { // Rate limit exceeded return false; } // Increment counter and allow currentEntry.count++; return true; } /** * Get remaining time until rate limit resets (in seconds) */ export function getRateLimitResetTime(key: string): number { const entry = rateLimitStore.get(key); if (!entry) return 0; const remaining = entry.resetTime - Date.now(); return remaining > 0 ? Math.ceil(remaining / 1000) : 0; } /** * Reset rate limit for a key (e.g., after successful action) */ export function resetRateLimit(key: string): void { rateLimitStore.delete(key); } /** * Debounce function - delays execution until after wait ms have elapsed */ export function debounce unknown>( func: T, wait: number, ): (...args: Parameters) => void { let timeoutId: ReturnType | null = null; return (...args: Parameters) => { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { func(...args); timeoutId = null; }, wait); }; } /** * Throttle function - ensures function is called at most once per limit ms */ export function throttle unknown>( func: T, limit: number, ): (...args: Parameters) => void { let inThrottle = false; return (...args: Parameters) => { if (!inThrottle) { func(...args); inThrottle = true; setTimeout(() => { inThrottle = false; }, limit); } }; } // Rate limit keys for different actions export const RATE_LIMIT_KEYS = { PAYMENT: 'payment', TICKET_CREATE: 'ticket-create', TICKET_REPLY: 'ticket-reply', PROMO_ACTIVATE: 'promo-activate', WHEEL_SPIN: 'wheel-spin', } as const;