import type { IncomingMessage, ServerResponse } from 'node:http'; import { isDeepStrictEqual } from 'node:util'; import { assertLiveTrafficSnapshot, type LiveTrafficSnapshot, } from '../../../shared/liveTraffic.js'; import type { StoredState } from '../../../shared/contracts/state.js'; import { HarborError } from '../../../shared/errors.js'; import { normalizeTrafficSettings, type TrafficSettings, } from '../../../shared/trafficSettings.js'; import { sendJson } from '../response.js'; interface LiveTrafficReader { snapshot(): unknown | Promise; } interface DeviceInventoryReader { snapshot(): unknown; } interface TrafficSettingsState { read(): { revision?: unknown; traffic?: TrafficSettings }; update(mutator: (state: StoredState) => Record): 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, settingsState = null, readBody = null, sendState = null, }: { traffic: LiveTrafficReader | null; deviceInventory?: DeviceInventoryReader | null; settingsState?: TrafficSettingsState | null; readBody?: ((req: IncomingMessage) => Promise>) | null; sendState?: ((res: ServerResponse) => Promise) | null; }) { return { async handle(req: IncomingMessage, res: ServerResponse) { const pathname = new URL(req.url || '/', 'http://localhost').pathname; if (pathname === '/api/traffic/live') { 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; } if (pathname === '/api/traffic/settings') { if (req.method !== 'PUT' || !settingsState || !readBody || !sendState) { throw new HarborError('ENDPOINT_NOT_FOUND'); } const body = await readBody(req); const expectedRevision = body.expectedRevision; if (!Number.isSafeInteger(expectedRevision) || Number(expectedRevision) < 0) { throw new HarborError('REQUEST_INVALID'); } let settings: TrafficSettings; try { settings = normalizeTrafficSettings(body.settings, { strict: true }); } catch (cause) { throw new HarborError('REQUEST_INVALID', { cause }); } const current = settingsState.read(); if (current.revision !== expectedRevision) throw new HarborError('STATE_CONFLICT'); if (!isDeepStrictEqual(current.traffic, settings)) { settingsState.update((state) => ({ ...state, traffic: settings })); } await sendState(res); return true; } return false; }, }; }