/** * A small in-memory sliding-window limiter. * * Deliberately per-process and not backed by Redis: this application runs as a * single container serving one shop and a handful of devices. The job here is * to stop a firmware stuck in a retry loop from hammering the renderer, not to * survive a distributed attack. */ const windows = new Map(); export type RateLimitResult = { allowed: boolean; /** Seconds until the caller may try again; 0 when allowed. */ retryAfter: number; }; export function rateLimit(key: string, limit: number, windowMs: number): RateLimitResult { const now = Date.now(); const cutoff = now - windowMs; const hits = (windows.get(key) ?? []).filter((timestamp) => timestamp > cutoff); if (hits.length >= limit) { windows.set(key, hits); const oldest = hits[0] ?? now; return { allowed: false, retryAfter: Math.ceil((oldest + windowMs - now) / 1000) }; } hits.push(now); windows.set(key, hits); // Opportunistic sweep: without it a long-running process would hold a key // for every IP that ever called, forever. if (windows.size > 1000) { for (const [existing, timestamps] of windows) { if (timestamps.every((timestamp) => timestamp <= cutoff)) { windows.delete(existing); } } } return { allowed: true, retryAfter: 0 }; } /** Test seam: forget every window. */ export function resetRateLimits(): void { windows.clear(); }