import { env } from '$env/dynamic/private'; // In-memory store: Map const publishStore = new Map(); const readStore = new Map(); const PUBLISH_LIMIT = parseInt(env.RATE_LIMIT_PUBLISH ?? '5'); const READ_LIMIT = parseInt(env.RATE_LIMIT_READ ?? '100'); const PUBLISH_WINDOW_MS = 60 * 60 * 1000; // 1 hour const READ_WINDOW_MS = 60 * 1000; // 1 minute function check(store, ip, limit, windowMs) { const now = Date.now(); const entry = store.get(ip); if (!entry || now - entry.windowStart > windowMs) { store.set(ip, { count: 1, windowStart: now }); return { allowed: true, remaining: limit - 1 }; } if (entry.count >= limit) { const retryAfter = Math.ceil((entry.windowStart + windowMs - now) / 1000); return { allowed: false, remaining: 0, retryAfter }; } entry.count += 1; return { allowed: true, remaining: limit - entry.count }; } export function checkPublishRate(ip) { return check(publishStore, ip, PUBLISH_LIMIT, PUBLISH_WINDOW_MS); } export function checkReadRate(ip) { return check(readStore, ip, READ_LIMIT, READ_WINDOW_MS); } export function getClientIp(request) { return ( request.headers.get('x-forwarded-for')?.split(',')[0].trim() ?? request.headers.get('x-real-ip') ?? '127.0.0.1' ); } // Cleanup old entries every 10 minutes to prevent memory leak setInterval(() => { const now = Date.now(); for (const [ip, entry] of publishStore) { if (now - entry.windowStart > PUBLISH_WINDOW_MS) publishStore.delete(ip); } for (const [ip, entry] of readStore) { if (now - entry.windowStart > READ_WINDOW_MS) readStore.delete(ip); } }, 10 * 60 * 1000);