import type { IncomingMessage, ServerResponse } from 'node:http'; import { assertLiveTrafficSnapshot, type LiveTrafficSnapshot, } from '../../../shared/liveTraffic.js'; import { HarborError } from '../../../shared/errors.js'; import { sendJson } from '../response.js'; interface LiveTrafficReader { snapshot(): unknown | Promise; } interface DeviceInventoryReader { snapshot(): unknown; } function record(value: unknown): Record { return value && typeof value === 'object' && !Array.isArray(value) ? value as Record : {}; } export function enrichLiveTrafficDeviceLabels( snapshot: LiveTrafficSnapshot, inventory: unknown, ): LiveTrafficSnapshot { const devices = Array.isArray(record(inventory).devices) ? (record(inventory).devices as unknown[]).map(record) : []; const labels = new Map(devices.flatMap((device) => { const id = String(device.id || ''); if (!/^dev_[a-f0-9]{16}$/.test(id)) return []; const label = [device.alias, device.hostname, device.ip] .find((value) => typeof value === 'string' && value.trim()); return label ? [[id, String(label).trim()] as const] : []; })); if (!labels.size) return snapshot; return { ...snapshot, connections: snapshot.connections.map((connection) => { const label = connection.origin.kind === 'device' && connection.origin.id ? labels.get(connection.origin.id) : null; return label ? { ...connection, origin: { ...connection.origin, label }, } : connection; }), }; } export function createLiveTrafficRoute({ traffic, deviceInventory = null, }: { traffic: LiveTrafficReader | null; deviceInventory?: DeviceInventoryReader | null; }) { return { async handle(req: IncomingMessage, res: ServerResponse) { const pathname = new URL(req.url || '/', 'http://localhost').pathname; if (pathname !== '/api/traffic/live') return false; if (req.method !== 'GET' || !traffic) throw new HarborError('ENDPOINT_NOT_FOUND'); const snapshot = assertLiveTrafficSnapshot(await traffic.snapshot()); const enriched = deviceInventory ? enrichLiveTrafficDeviceLabels(snapshot, deviceInventory.snapshot()) : snapshot; sendJson(res, 200, enriched); return true; }, }; }