From 039e75586dd755c67e76bd6eccddd34e300d35f3 Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 18 Jan 2026 06:53:17 +0300 Subject: [PATCH] Add files via upload --- src/utils/rateLimit.ts | 117 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/utils/rateLimit.ts diff --git a/src/utils/rateLimit.ts b/src/utils/rateLimit.ts new file mode 100644 index 0000000..199e09e --- /dev/null +++ b/src/utils/rateLimit.ts @@ -0,0 +1,117 @@ +/** + * 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