/** * In-memory throttle для SSE-событий gps_asset_moved. * Трекеры присылают точки ~1 раз в секунду; без ограничения каждый открытый * клиент организации получает broadcast каждую секунду и инвалидирует запросы * позиций/объектов, что исчерпывает per-user бакет apiLimiter (429). * Запись позиции в БД не затрагивается — ограничивается только broadcast. */ const GPS_MOVED_SSE_MIN_INTERVAL_MS = 3_000; const MAX_TRACKED_ASSETS = 1_000; const lastPublishedAt = new Map(); /** true — событие публиковать; false — пропустить (с последней публикации прошло меньше интервала) */ export function shouldPublishGpsAssetMoved(assetId: number, now = Date.now()): boolean { const last = lastPublishedAt.get(assetId); if (last !== undefined && now - last < GPS_MOVED_SSE_MIN_INTERVAL_MS) return false; lastPublishedAt.set(assetId, now); // Защита от неограниченного роста карты: чистим устаревшие записи при превышении лимита if (lastPublishedAt.size > MAX_TRACKED_ASSETS) { for (const [id, ts] of lastPublishedAt) { if (now - ts >= GPS_MOVED_SSE_MIN_INTERVAL_MS) lastPublishedAt.delete(id); } } return true; }