import { withSuperAdmin } from "../db"; import { gpsStorage } from "./storage"; import { notifyOffline } from "./geozone.service"; import { eventBus } from "../routes/shared"; import { logger } from "../utils/logger"; /** * Воркер офлайн-детекта GPS-объектов. * Раз в минуту ищет активные онлайн-объекты, от которых не было точек * более 15 минут, переводит их в offline и уведомляет подписчиков (on_offline). * Обратный переход в online происходит в processPosition при новой точке. * Работает независимо от Traccar (PWA-трекинг тоже покрывается). */ const log = logger("GPS Worker"); const TICK_MS = 60_000; const OFFLINE_THRESHOLD_MS = 15 * 60_000; let isRunning = false; async function tick(): Promise { const cutoff = new Date(Date.now() - OFFLINE_THRESHOLD_MS); const staleAssets = await withSuperAdmin(() => gpsStorage.getStaleOnlineAssets(cutoff)); for (const asset of staleAssets) { try { await withSuperAdmin(async () => { await gpsStorage.setAssetOnline(asset.id, false); await notifyOffline(asset); }); // SSE — чтобы открытые карты сразу «погасили» маркер eventBus.publishEvent({ type: "gps_asset_offline", data: { assetId: asset.id }, organizationId: asset.organizationId, }); log.info(`Объект ${asset.id} ('${asset.name}', org ${asset.organizationId}) переведён в offline`); } catch (err) { log.error(`Ошибка обработки объекта ${asset.id}:`, err); } } } export function startGpsWorker(): void { if (isRunning) return; isRunning = true; setInterval(() => { tick().catch((err) => log.error("Tick error:", err)); }, TICK_MS); log.info("started"); }