@aeroswarm/client 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/context/index.tsx","../../src/sdk/websocket/index.ts","../../src/sdk/websocket/replay.ts","../../src/sdk/api/trpc.ts","../../src/sdk/api/rest.ts","../../src/sdk/api/graphql.ts","../../src/sdk/cache/index.ts","../../src/shared/label/index.ts","../../src/sdk/index.ts","../../src/react/hooks/use-track-motion.ts","../../src/shared/motion/index.ts","../../src/react/hooks/use-track-history.ts","../../src/react/hooks/index.ts","../../src/react/store/index.ts","../../src/shared/utils/index.ts"],"sourcesContent":["import React, { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { AeroSwarm, type AeroSwarmOptions } from \"../../sdk/index.js\";\n\ninterface AeroSwarmContextValue {\n client: AeroSwarm;\n}\n\nconst AeroSwarmContext = createContext<AeroSwarmContextValue | null>(null);\n\nexport function useAeroSwarmContext(): AeroSwarmContextValue {\n const ctx = useContext(AeroSwarmContext);\n if (!ctx) throw new Error(\"useAeroSwarmContext must be used inside <AeroSwarmProvider>\");\n return ctx;\n}\n\nexport interface AeroSwarmProviderProps extends AeroSwarmOptions {\n children: ReactNode;\n queryClient?: QueryClient;\n}\n\nexport function AeroSwarmProvider({\n children,\n queryClient: externalQc,\n ...sdkOpts\n}: AeroSwarmProviderProps): React.ReactElement {\n const client = useMemo(\n () => new AeroSwarm(sdkOpts),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [sdkOpts.endpoint, sdkOpts.token],\n );\n\n const qc = useMemo(\n () => externalQc ?? new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 10_000,\n refetchInterval: 30_000,\n },\n },\n }),\n [externalQc],\n );\n\n return (\n <QueryClientProvider client={qc}>\n <AeroSwarmContext.Provider value={{ client }}>\n {children}\n </AeroSwarmContext.Provider>\n </QueryClientProvider>\n );\n}\n","\r\n\r\nimport { streamEnvelopeSchema, currentTrackSchema } from \"@aeroswarm/core/validation\";\r\nimport type { CurrentTrack, AeroConnectionStatus, AeroRecoverableError, SourceStatus } from \"@aeroswarm/core/types\";\r\n\r\nexport type WsEncoding = \"json\" | \"msgpack\";\r\n\r\nexport interface SubscribeOptions {\r\n bbox?: [number, number, number, number];\r\n /** Scope the stream to tracks stamped with this ICAO airport code, e.g. \"WARR\" */\r\n airportId?: string;\r\n source?: \"fused\" | \"normalized\";\r\n minConfidence?: number;\r\n maxTracks?: number;\r\n mode?: \"delta\" | \"full\";\r\n maxUpdateHz?: number;\r\n encoding?: WsEncoding;\r\n staleAfterMs?: number;\r\n removeAfterMs?: number;\r\n}\r\n\r\n/** Envelope fields that ride along with a track message. */\r\nexport interface StreamMeta {\r\n /** The server's clock when it sent the message (ms). Lets a client align its own clock with `lastSeenAt`. */\r\n serverTime: number;\r\n seq: number;\r\n}\r\n\r\nexport interface TrackCallbacks {\r\n onTrackUpdate?: (track: CurrentTrack, meta?: StreamMeta) => void;\r\n onTrackRemoved?: (event: { trackId: string; reason: string; }) => void;\r\n onSnapshot?: (tracks: CurrentTrack[], meta?: StreamMeta) => void;\r\n onSourceStatus?: (event: { sourceId: string; status: SourceStatus; }) => void;\r\n onStatusChange?: (status: AeroConnectionStatus) => void;\r\n onError?: (err: AeroRecoverableError) => void;\r\n onDegraded?: (reason: string) => void;\r\n}\r\n\r\nexport class AeroWsClient {\r\n private ws: WebSocket | null = null;\r\n private attempt = 0;\r\n private stopped = false;\r\n private lastSubscribe: SubscribeOptions | null = null;\r\n private callbacks: TrackCallbacks = {};\r\n private lastSeq = -1;\r\n private lastHeartbeat = Date.now();\r\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\r\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n private status: AeroConnectionStatus = \"idle\";\r\n\r\n constructor(\r\n private readonly url: string,\r\n private readonly token: string,\r\n private readonly maxReconnectDelay = 30_000,\r\n private readonly heartbeatTimeoutMs = 35_000,\r\n ) { }\r\n\r\n connect(opts: SubscribeOptions, callbacks: TrackCallbacks): void {\r\n this.lastSubscribe = opts;\r\n this.callbacks = callbacks;\r\n this.stopped = false;\r\n this._clearReconnectTimer();\r\n this._setStatus(\"connecting\");\r\n this._connect();\r\n }\r\n\r\n updateFilter(opts: Partial<Pick<SubscribeOptions, 'bbox' | 'minConfidence' | 'maxUpdateHz' | 'maxTracks'>>): void {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\r\n this.ws.send(JSON.stringify({ type: \"update-filter\", ...opts }));\r\n }\r\n\r\n disconnect(): void {\r\n this.stopped = true;\r\n this._clearHeartbeatTimer();\r\n this._clearReconnectTimer();\r\n this._dropSocket();\r\n this._setStatus(\"offline\");\r\n }\r\n\r\n /**\r\n * Force a fresh connection now, skipping any backoff wait. Re-subscribes with\r\n * the last `connect()` options (the server then sends a new snapshot).\r\n * Also resumes after `disconnect()`. No-op if `connect()` was never called.\r\n * Returns false in that no-op case.\r\n */\r\n reconnect(): boolean {\r\n if (!this.lastSubscribe) return false;\r\n this.stopped = false;\r\n this.attempt = 0;\r\n this._clearHeartbeatTimer();\r\n this._clearReconnectTimer();\r\n this._dropSocket();\r\n this._setStatus(\"reconnecting\");\r\n this._connect();\r\n return true;\r\n }\r\n\r\n getStatus(): AeroConnectionStatus { return this.status; }\r\n\r\n private _setStatus(s: AeroConnectionStatus): void {\r\n if (this.status === s) return;\r\n this.status = s;\r\n this.callbacks.onStatusChange?.(s);\r\n }\r\n\r\n /** Close the current socket without letting its onclose schedule a reconnect. */\r\n private _dropSocket(): void {\r\n const ws = this.ws;\r\n this.ws = null;\r\n if (!ws) return;\r\n ws.onopen = ws.onmessage = ws.onclose = ws.onerror = null;\r\n ws.close();\r\n }\r\n\r\n private _connect(): void {\r\n const wsUrl = `${this.url}?token=${encodeURIComponent(this.token)}`;\r\n this._dropSocket();\r\n try {\r\n this.ws = new WebSocket(wsUrl);\r\n this.ws.binaryType = \"arraybuffer\";\r\n } catch {\r\n this._scheduleReconnect();\r\n return;\r\n }\r\n\r\n this.ws.onopen = () => {\r\n this.attempt = 0;\r\n this.lastSeq = -1;\r\n this._setStatus(\"connected\");\r\n this._resetHeartbeatTimer();\r\n\r\n if (this.lastSubscribe) {\r\n this.ws!.send(JSON.stringify({\r\n type: \"subscribe\",\r\n encoding: \"json\",\r\n ...this.lastSubscribe,\r\n }));\r\n }\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n this._handleRaw(event.data as string | ArrayBuffer);\r\n };\r\n\r\n this.ws.onclose = () => {\r\n this._clearHeartbeatTimer();\r\n if (!this.stopped) {\r\n this._setStatus(\"reconnecting\");\r\n this._scheduleReconnect();\r\n }\r\n };\r\n\r\n this.ws.onerror = () => {\r\n this.callbacks.onError?.({\r\n code: \"WS_ERROR\",\r\n message: \"WebSocket connection error\",\r\n recoverable: true,\r\n });\r\n };\r\n }\r\n\r\n private _handleRaw(raw: string | ArrayBuffer): void {\r\n\r\n this._resetHeartbeatTimer();\r\n\r\n let parsed: unknown;\r\n try {\r\n const text = typeof raw === \"string\" ? raw : new TextDecoder().decode(raw);\r\n parsed = JSON.parse(text);\r\n } catch {\r\n\r\n return;\r\n }\r\n\r\n let msg: ReturnType<typeof streamEnvelopeSchema.parse>;\r\n const result = streamEnvelopeSchema.safeParse(parsed);\r\n if (!result.success) {\r\n\r\n const raw = parsed as Record<string, unknown>;\r\n if (raw[\"type\"] === \"snapshot\" && Array.isArray((raw[\"payload\"] as Record<string, unknown>)?.[\"tracks\"])) {\r\n const rawTracks = ((raw[\"payload\"] as Record<string, unknown>)[\"tracks\"]) as unknown[];\r\n const goodTracks = rawTracks\r\n .map((t) => currentTrackSchema.safeParse(t))\r\n .filter((r): r is { success: true; data: CurrentTrack } => r.success)\r\n .map((r) => r.data);\r\n if (goodTracks.length > 0) this.callbacks.onSnapshot?.(goodTracks);\r\n }\r\n return;\r\n }\r\n msg = result.data;\r\n\r\n if (msg.seq <= this.lastSeq && this.lastSeq > 0) return;\r\n\r\n if (msg.seq > this.lastSeq + 10 && this.lastSeq > 0) {\r\n this._requestSnapshot();\r\n }\r\n this.lastSeq = msg.seq;\r\n\r\n const meta: StreamMeta = { serverTime: msg.serverTime, seq: msg.seq };\r\n switch (msg.type) {\r\n case \"snapshot\":\r\n this.callbacks.onSnapshot?.(msg.payload.tracks, meta);\r\n break;\r\n case \"track.updated\":\r\n this.callbacks.onTrackUpdate?.(msg.payload.track, meta);\r\n break;\r\n case \"track.removed\":\r\n this.callbacks.onTrackRemoved?.(msg.payload);\r\n break;\r\n case \"source.status\":\r\n this.callbacks.onSourceStatus?.(msg.payload);\r\n break;\r\n case \"stream.heartbeat\":\r\n this.lastHeartbeat = Date.now();\r\n break;\r\n case \"stream.degraded\":\r\n this._setStatus(\"degraded\");\r\n this.callbacks.onDegraded?.(msg.payload.reason);\r\n break;\r\n case \"stream.error\":\r\n this.callbacks.onError?.({\r\n code: msg.payload.code,\r\n message: msg.payload.message ?? msg.payload.code,\r\n recoverable: msg.payload.recoverable,\r\n });\r\n break;\r\n }\r\n }\r\n\r\n private _requestSnapshot(): void {\r\n if (!this.lastSubscribe || !this.ws || this.ws.readyState !== WebSocket.OPEN) return;\r\n this.ws.send(JSON.stringify({ type: \"subscribe\", ...this.lastSubscribe }));\r\n }\r\n\r\n private _resetHeartbeatTimer(): void {\r\n this._clearHeartbeatTimer();\r\n this.heartbeatTimer = setInterval(() => {\r\n if (Date.now() - this.lastHeartbeat > this.heartbeatTimeoutMs) {\r\n this._setStatus(\"degraded\");\r\n this.callbacks.onDegraded?.(\"heartbeat_timeout\");\r\n }\r\n }, this.heartbeatTimeoutMs / 2);\r\n }\r\n\r\n private _clearHeartbeatTimer(): void {\r\n if (this.heartbeatTimer) {\r\n clearInterval(this.heartbeatTimer);\r\n this.heartbeatTimer = null;\r\n }\r\n }\r\n\r\n private _scheduleReconnect(): void {\r\n const delay = Math.min(1000 * 2 ** this.attempt, this.maxReconnectDelay);\r\n this.attempt++;\r\n this._clearReconnectTimer();\r\n this.reconnectTimer = setTimeout(() => {\r\n this.reconnectTimer = null;\r\n if (!this.stopped) this._connect();\r\n }, delay);\r\n }\r\n\r\n private _clearReconnectTimer(): void {\r\n if (this.reconnectTimer) {\r\n clearTimeout(this.reconnectTimer);\r\n this.reconnectTimer = null;\r\n }\r\n }\r\n}\r\n\r\nfunction isRenderableCurrent(t: CurrentTrack): boolean {\r\n return (\r\n typeof t.lat === \"number\" &&\r\n typeof t.lon === \"number\" &&\r\n Number.isFinite(t.lat) &&\r\n Number.isFinite(t.lon) &&\r\n t.lat >= -90 && t.lat <= 90 &&\r\n t.lon >= -180 && t.lon <= 180\r\n );\r\n}\r\n","import type { CurrentTrack } from \"@aeroswarm/core/types\";\n\n/**\n * Server-paced WS replay client for `/ws/tracks/replay`.\n *\n * The server streams historical `fused_tracks` events at `speed`× real\n * time (paced server-side against a wall-clock anchor), so the client\n * receives replay frames exactly like live traffic: a `snapshot`\n * envelope with the state at `from`, then `track.updated` /\n * `track.removed` envelopes, plus `replay.progress` heartbeats and a\n * final `replay.done`.\n *\n * The wire protocol is plain JSON, so consumers that don't use this SDK\n * can speak it manually:\n *\n * → connect wss://host/ws/tracks/replay?token=TOKEN[&airportId=WARR][&sessionId=ID]\n * → send { \"type\": \"replay.start\", \"from\": ISO, \"to\": ISO, \"speed\": 2 }\n * → send { \"type\": \"replay.pause\" } · { \"type\": \"replay.resume\" }\n * → send { \"type\": \"replay.seek\", \"at\": ISO }\n * → send { \"type\": \"replay.stop\" }\n * ← recv { \"type\": \"snapshot\", \"payload\": { \"tracks\": [...], \"sessionId\": ID } }\n * ← recv { \"type\": \"track.updated\", \"payload\": { \"track\": {...} } }\n * ← recv { \"type\": \"track.removed\", \"payload\": { \"trackId\": ID, \"reason\": \"dropped\" } }\n * ← recv { \"type\": \"replay.progress\", \"payload\": { \"at\": ISO, \"pct\": 0-100, \"sessionId\": ID } }\n * ← recv { \"type\": \"replay.done\", \"payload\": { \"sessionId\": ID } }\n *\n * Reconnecting with the same `sessionId` (within 1 h) resumes a replay\n * from its last saved position.\n */\n\nexport interface ReplayStreamOptions {\n from: Date | string;\n to: Date | string;\n /** Playback speed multiplier (1 = realtime). Default 1. */\n speed?: number;\n /** Which airport to replay. Federated airports (with a historyEndpoint)\n * are resolved by AeroSwarm.replay.stream to their own backend; against\n * AeroSwarm's own endpoint this is a row filter on the local DB. */\n airportId?: string;\n /** Resume a previous session instead of starting fresh. */\n sessionId?: string;\n}\n\nexport interface ReplayProgress {\n /** Replay-time position (ISO timestamp) */\n at: string;\n /** 0–100 within the requested range */\n pct: number;\n sessionId: string;\n}\n\nexport interface ReplayCallbacks {\n /** State at `from` — arrives once per start/seek. */\n onSnapshot?: (tracks: CurrentTrack[], sessionId: string) => void;\n onTrackUpdate?: (track: CurrentTrack) => void;\n onTrackRemoved?: (event: { trackId: string; reason: string }) => void;\n onProgress?: (progress: ReplayProgress) => void;\n onDone?: (sessionId: string) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface ReplayHandle {\n readonly sessionId: string | null;\n pause(): void;\n resume(): void;\n /** Jump to a replay-time position; the server restarts the paced stream there. */\n seek(at: Date | string): void;\n /** End the replay and close the socket. */\n stop(): void;\n}\n\nconst toIso = (v: Date | string): string => (v instanceof Date ? v.toISOString() : v);\n\nexport class AeroReplayClient {\n constructor(\n private readonly wsBase: string,\n private readonly token: string,\n /** Default \"/ws/tracks/replay\" (AeroSwarm's own endpoint). A federated\n * airport backend running @aeroswarm/replay-server's default mount\n * uses \"/replay\" instead — see AeroSwarm.replay.stream's endpoint\n * resolution. */\n private readonly wsPath: string = \"/ws/tracks/replay\",\n ) {}\n\n /** Open a replay stream. Returns a handle for transport control. */\n stream(opts: ReplayStreamOptions, callbacks: ReplayCallbacks): ReplayHandle {\n const params = new URLSearchParams({ token: this.token });\n if (opts.airportId) params.set(\"airportId\", opts.airportId);\n if (opts.sessionId) params.set(\"sessionId\", opts.sessionId);\n\n const ws = new WebSocket(`${this.wsBase}${this.wsPath}?${params.toString()}`);\n let sessionId: string | null = opts.sessionId ?? null;\n let closedByUser = false;\n\n ws.onopen = () => {\n // A fresh start message is only needed when not resuming a session.\n if (!opts.sessionId) {\n ws.send(JSON.stringify({\n type: \"replay.start\",\n from: toIso(opts.from),\n to: toIso(opts.to),\n speed: opts.speed ?? 1,\n }));\n }\n };\n\n ws.onmessage = (ev) => {\n let msg: { type?: string; payload?: Record<string, unknown> };\n try { msg = JSON.parse(String(ev.data)); } catch { return; }\n const payload = msg.payload ?? {};\n\n switch (msg.type) {\n case \"snapshot\": {\n if (typeof payload[\"sessionId\"] === \"string\") sessionId = payload[\"sessionId\"];\n callbacks.onSnapshot?.((payload[\"tracks\"] as CurrentTrack[]) ?? [], sessionId ?? \"\");\n break;\n }\n case \"track.updated\":\n callbacks.onTrackUpdate?.(payload[\"track\"] as CurrentTrack);\n break;\n case \"track.removed\":\n callbacks.onTrackRemoved?.({\n trackId: String(payload[\"trackId\"] ?? \"\"),\n reason: String(payload[\"reason\"] ?? \"\"),\n });\n break;\n case \"replay.progress\": {\n const progress = payload as unknown as ReplayProgress;\n sessionId = progress.sessionId ?? sessionId;\n callbacks.onProgress?.(progress);\n break;\n }\n case \"replay.done\":\n callbacks.onDone?.(String(payload[\"sessionId\"] ?? sessionId ?? \"\"));\n break;\n }\n };\n\n ws.onerror = () => {\n if (!closedByUser) callbacks.onError?.(new Error(\"replay websocket error\"));\n };\n ws.onclose = (ev) => {\n if (!closedByUser && ev.code >= 4000) {\n callbacks.onError?.(new Error(`replay websocket closed: ${ev.reason || ev.code}`));\n }\n };\n\n const send = (body: Record<string, unknown>) => {\n if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(body));\n };\n\n return {\n get sessionId() { return sessionId; },\n pause: () => send({ type: \"replay.pause\" }),\n resume: () => send({ type: \"replay.resume\" }),\n seek: (at) => send({ type: \"replay.seek\", at: toIso(at) }),\n stop: () => {\n closedByUser = true;\n send({ type: \"replay.stop\" });\n try { ws.close(); } catch { /* already closed */ }\n },\n };\n }\n}\n","import { createTRPCProxyClient, httpBatchLink } from \"@trpc/client\";\n\nexport function createAeroTrpcClient(endpoint: string, token: string) {\n const base = endpoint.replace(/\\/$/, \"\");\n return createTRPCProxyClient<any>({\n links: [\n httpBatchLink({\n url: `${base}/trpc`,\n headers: () => ({ \"x-aero-token\": token }),\n methodOverride: \"POST\",\n }),\n ],\n });\n}\n\nexport type AeroTrpcClient = ReturnType<typeof createAeroTrpcClient>;\n","export interface SourceRow {\r\n id: string;\r\n name: string;\r\n type: string;\r\n sourceToken: string;\r\n createdAt: string;\r\n /** Airport this source's data belongs to. When set, anything that\r\n * authenticates with this source's token (WS live/replay, tRPC calls)\r\n * is scoped to this airport automatically. */\r\n airportId?: string | null;\r\n}\r\n\r\nimport type { FlightPlan } from \"../../shared/types/index.js\";\r\n\r\n/** A flight plan plus the live track it currently correlates with (null = not on radar). */\r\nexport type FlightPlanRow = FlightPlan & { trackId: string | null };\r\n\r\n/** One raw AFTN message from the live log (newest first, capped by the service). */\r\nexport interface AftnMessageRow {\r\n id: string;\r\n receivedAt: number;\r\n airportId: string | null;\r\n feedId: string | null;\r\n callsign: string | null;\r\n type: string;\r\n adep?: string;\r\n ades?: string;\r\n raw: string;\r\n}\r\n\r\nexport class AeroRestClient {\r\n constructor(\r\n private readonly endpoint: string,\r\n private readonly token: string,\r\n ) { }\r\n\r\n private async req<T>(method: string, path: string, body?: unknown): Promise<T> {\r\n const res = await fetch(`${this.endpoint}${path}`, {\r\n method,\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n \"x-aero-token\": this.token,\r\n },\r\n body: body ? JSON.stringify(body) : undefined,\r\n });\r\n if (!res.ok) throw new Error(`${method} ${path} → ${res.status}`);\r\n return res.json() as Promise<T>;\r\n }\r\n\r\n createSource(body: Record<string, unknown>) {\r\n return this.req<{ ok: boolean; data: { sourceId: string; sourceToken: string; airportId?: string | null; } }>(\"POST\", \"/v1/sources\", body);\r\n }\r\n\r\n listSources() {\r\n return this.req<{ ok: boolean; data: { sources: SourceRow[]; } }>(\"GET\", \"/v1/sources\");\r\n }\r\n\r\n deleteSource(id: string) {\r\n return this.req<{ ok: boolean; }>(\"DELETE\", `/v1/sources/${id}`);\r\n }\r\n\r\n createWebhook(body: { url: string; events: string[]; secret?: string; }) {\r\n return this.req<{ webhookId: string; }>(\"POST\", \"/v1/webhooks\", body);\r\n }\r\n\r\n getTracksCurrent(params?: { bbox?: [number, number, number, number]; minConfidence?: number; }) {\r\n const search = new URLSearchParams();\r\n if (params?.bbox) search.set(\"bbox\", params.bbox.join(\",\"));\r\n if (params?.minConfidence != null) search.set(\"minConfidence\", String(params.minConfidence));\r\n const qs = search.size ? `?${search.toString()}` : \"\";\r\n return this.req<{ tracks: unknown[]; count: number; }>(\"GET\", `/v1/tracks/current${qs}`);\r\n }\r\n\r\n /** Flight plans received from AFTN feeds (FPL/CHG/DLA/DEP/ARR/CNL folded per flight). */\r\n listFlightPlans(params?: { airportId?: string; status?: string; callsign?: string; limit?: number; }) {\r\n const search = new URLSearchParams();\r\n if (params?.airportId) search.set(\"airportId\", params.airportId);\r\n if (params?.status) search.set(\"status\", params.status);\r\n if (params?.callsign) search.set(\"callsign\", params.callsign);\r\n if (params?.limit != null) search.set(\"limit\", String(params.limit));\r\n const qs = search.size ? `?${search.toString()}` : \"\";\r\n return this.req<{ ok: boolean; data: { flightPlans: FlightPlanRow[]; count: number; } }>(\"GET\", `/v1/flight-plans${qs}`);\r\n }\r\n\r\n getFlightPlan(id: string) {\r\n return this.req<{ ok: boolean; data: { flightPlan: FlightPlan; } }>(\"GET\", `/v1/flight-plans/${encodeURIComponent(id)}`);\r\n }\r\n\r\n /** Raw AFTN messages (every parsed ATS message, newest first — the input\r\n * side of the flight-plan pipeline). */\r\n listAftnMessages(params?: { airportId?: string; limit?: number; }) {\r\n const search = new URLSearchParams();\r\n if (params?.airportId) search.set(\"airportId\", params.airportId);\r\n if (params?.limit != null) search.set(\"limit\", String(params.limit));\r\n const qs = search.size ? `?${search.toString()}` : \"\";\r\n return this.req<{ ok: boolean; data: { messages: AftnMessageRow[]; count: number; } }>(\"GET\", `/v1/aftn/messages${qs}`);\r\n }\r\n\r\n health() {\r\n return this.req<{ status: string; }>(\"GET\", \"/health\");\r\n }\r\n}\r\n","export class AeroGraphQLClient {\n constructor(\n private readonly endpoint: string,\n private readonly token: string,\n ) { }\n\n async query<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T> {\n const res = await fetch(`${this.endpoint}/graphql`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-aero-token\": this.token,\n },\n body: JSON.stringify({ query, variables }),\n });\n if (!res.ok) throw new Error(`GraphQL ${res.status}: ${await res.text().catch(() => \"\")}`);\n const json = await res.json() as { data?: T; errors?: unknown[]; };\n if (json.errors?.length) throw new Error(JSON.stringify(json.errors));\n return json.data as T;\n }\n}\n","\r\n\r\nimport type { CurrentTrack, AeroConnectionStatus, AeroRecoverableError, SourceStatus } from \"@aeroswarm/core/types\";\r\nimport { isRenderableTrack } from \"@aeroswarm/core/utils\";\r\nimport { withLabel, type LabeledTrack } from \"../../shared/label/index.js\";\r\n\r\ntype Listener<T> = (payload: T) => void;\r\n\r\nexport class TypedEmitter<T extends Record<string, unknown>> {\r\n private listeners = new Map<keyof T, Set<Listener<unknown>>>();\r\n\r\n on<K extends keyof T>(event: K, fn: Listener<T[K]>): () => void {\r\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\r\n this.listeners.get(event)!.add(fn as Listener<unknown>);\r\n return () => this.listeners.get(event)?.delete(fn as Listener<unknown>);\r\n }\r\n\r\n emit<K extends keyof T>(event: K, payload: T[K]): void {\r\n this.listeners.get(event)?.forEach((fn) => fn(payload));\r\n }\r\n}\r\n\r\nexport class TrackCache extends TypedEmitter<{\r\n update: LabeledTrack;\r\n removed: { trackId: string; };\r\n snapshot: LabeledTrack[];\r\n status: AeroConnectionStatus;\r\n degraded: { reason: string; };\r\n error: AeroRecoverableError;\r\n \"source.status\": { sourceId: string; status: SourceStatus; };\r\n}> {\r\n private tracks = new Map<string, LabeledTrack>();\r\n\r\n /** Merges into the cached track and returns the labeled result. A \"lost\"\r\n * track is evicted from the cache but still returned (labelCategory LOST). */\r\n applyUpdate(track: CurrentTrack): LabeledTrack {\r\n const merged = withLabel({ ...this.tracks.get(track.trackId), ...track });\r\n if (track.status === \"lost\") {\r\n this.tracks.delete(track.trackId);\r\n this.emit(\"removed\", { trackId: track.trackId });\r\n return merged;\r\n }\r\n this.tracks.set(track.trackId, merged);\r\n this.emit(\"update\", merged);\r\n return merged;\r\n }\r\n\r\n applyRemoved(ev: { trackId: string; }): void {\r\n this.tracks.delete(ev.trackId);\r\n this.emit(\"removed\", ev);\r\n }\r\n\r\n applySnapshot(tracks: CurrentTrack[]): LabeledTrack[] {\r\n this.tracks.clear();\r\n for (const t of tracks) {\r\n if (isRenderableTrack(t)) this.tracks.set(t.trackId, withLabel(t));\r\n }\r\n const all = Array.from(this.tracks.values());\r\n this.emit(\"snapshot\", all);\r\n return all;\r\n }\r\n\r\n getAll(): LabeledTrack[] { return Array.from(this.tracks.values()); }\r\n get(id: string): LabeledTrack | undefined { return this.tracks.get(id); }\r\n get size(): number { return this.tracks.size; }\r\n clear(): void { this.tracks.clear(); }\r\n\r\n getRenderable(): (LabeledTrack & { lat: number; lon: number; })[] {\r\n return this.getAll().filter(isRenderableTrack) as (LabeledTrack & { lat: number; lon: number; })[];\r\n }\r\n}\r\n","import type { CurrentTrack } from \"../types/index.js\";\n\n/**\n * Callsign-label category of a track, in priority order:\n * LOST (track status \"lost\") > GROUND_VEHICLE > ARRIVAL / DEPARTURE > UNKNOWN.\n * ARRIVAL/DEPARTURE come from the correlated flight plan, relative to the\n * track's airport (`track.flightPlan.phase`).\n */\nexport type LabelCategory = \"ARRIVAL\" | \"DEPARTURE\" | \"GROUND_VEHICLE\" | \"LOST\" | \"UNKNOWN\";\n\nexport const LABEL_COLORS: Readonly<Record<LabelCategory, string>> = {\n ARRIVAL: \"#FACC15\", // yellow\n DEPARTURE: \"#22C55E\", // green\n GROUND_VEHICLE: \"#3B82F6\", // blue\n LOST: \"#FFFFFF\", // white\n UNKNOWN: \"#FFFFFF\", // white — no flight plan\n};\n\n/** A track as the client exposes it: the server's track plus derived label fields. */\nexport type LabeledTrack = CurrentTrack & {\n labelCategory: LabelCategory;\n labelColor: string;\n};\n\nexport function getLabelCategory(\n track: Pick<CurrentTrack, \"status\" | \"targetType\" | \"flightPlan\">,\n): LabelCategory {\n if (track.status === \"lost\") return \"LOST\";\n if (track.targetType === \"vehicle\") return \"GROUND_VEHICLE\";\n switch (track.flightPlan?.phase) {\n case \"ARRIVAL\": return \"ARRIVAL\";\n case \"DEPARTURE\": return \"DEPARTURE\";\n default: return \"UNKNOWN\";\n }\n}\n\n/**\n * Attach `labelCategory` / `labelColor`. Always call this on the *merged*\n * track: a delta without `flightPlan` must not downgrade a known category\n * (that is what makes labels flicker).\n */\nexport function withLabel(track: CurrentTrack): LabeledTrack {\n const labelCategory = getLabelCategory(track);\n return { ...track, labelCategory, labelColor: LABEL_COLORS[labelCategory] };\n}\n","import { AeroWsClient, type StreamMeta, type SubscribeOptions, type TrackCallbacks } from \"./websocket/index.js\";\r\nimport { AeroReplayClient, type ReplayStreamOptions, type ReplayCallbacks, type ReplayHandle } from \"./websocket/replay.js\";\r\nimport { createAeroTrpcClient, type AeroTrpcClient } from \"./api/trpc.js\";\r\nimport { AeroRestClient } from \"./api/rest.js\";\r\nimport { AeroGraphQLClient } from \"./api/graphql.js\";\r\nimport { TrackCache } from \"./cache/index.js\";\r\nimport type { LabeledTrack } from \"../shared/label/index.js\";\r\nimport type { Bbox, AeroConnectionStatus, AeroRecoverableError, CurrentTrack } from \"../shared/types/index.js\";\r\n\r\nexport { TrackCache } from \"./cache/index.js\";\r\nexport { AeroWsClient } from \"./websocket/index.js\";\r\nexport { AeroReplayClient } from \"./websocket/replay.js\";\r\nexport type { SubscribeOptions, TrackCallbacks, StreamMeta } from \"./websocket/index.js\";\r\nexport type { ReplayStreamOptions, ReplayCallbacks, ReplayHandle, ReplayProgress } from \"./websocket/replay.js\";\r\nexport type { SourceRow, FlightPlanRow, AftnMessageRow } from \"./api/rest.js\";\r\n\r\nexport interface AeroSwarmOptions {\r\n endpoint: string;\r\n token: string;\r\n wsEndpoint?: string;\r\n reconnect?: boolean;\r\n maxReconnectDelay?: number;\r\n encoding?: \"json\" | \"msgpack\";\r\n staleAfterMs?: number;\r\n removeAfterMs?: number;\r\n onTokenExpired?: () => Promise<string>;\r\n}\r\n\r\n/** Live callbacks: tracks arrive merged with previous state and carry\r\n * `status`, `labelCategory` and `labelColor`. */\r\nexport interface LiveTrackCallbacks extends Omit<TrackCallbacks, \"onTrackUpdate\" | \"onSnapshot\"> {\r\n /** `meta.serverTime` is the server's clock; pass it to `createTrackMotion().update()`. */\r\n onTrackUpdate?: (track: LabeledTrack, meta?: StreamMeta) => void;\r\n onSnapshot?: (tracks: LabeledTrack[], meta?: StreamMeta) => void;\r\n}\r\n\r\nexport interface UseAeroTracksResult {\r\n tracks: CurrentTrack[];\r\n status: AeroConnectionStatus;\r\n sourceStatus: Record<string, string>;\r\n lastUpdateAt: number | null;\r\n error: AeroRecoverableError | null;\r\n}\r\n\r\nexport interface TrackHistoryPoint {\r\n t: number;\r\n lat: number | null;\r\n lon: number | null;\r\n altFt: number | null;\r\n speedKt: number | null;\r\n headingDeg: number | null;\r\n}\r\n\r\nexport interface DayAvailability {\r\n day: string; // \"YYYY-MM-DD\"\r\n eventCount: number;\r\n trackCount: number;\r\n firstAt: string | null; // first/last event that day (ISO)\r\n lastAt: string | null;\r\n}\r\n\r\nexport class AeroSwarm {\r\n readonly trpc: AeroTrpcClient;\r\n readonly rest: AeroRestClient;\r\n readonly graphql: AeroGraphQLClient;\r\n readonly cache: TrackCache;\r\n\r\n private readonly wsClient: AeroWsClient;\r\n private _status: AeroConnectionStatus = \"idle\";\r\n private _lastUpdateAt: number | null = null;\r\n private _error: AeroRecoverableError | null = null;\r\n private _sourceStatus: Record<string, string> = {};\r\n\r\n // ── Federated history resolution ──────────────────────────────────────\r\n // An airport can serve its own replay/history through a backend mounting\r\n // @aeroswarm/replay-server instead of AeroSwarm's own (see\r\n // docs/development/federation/). `airports.list()`'s `historyEndpoint`\r\n // field drives this automatically; every federatable call also accepts\r\n // an explicit `endpoint` to bypass the registry entirely (\"connect\r\n // manual\"). Priority: explicit endpoint > registry > AeroSwarm itself.\r\n private airportsCache: { byId: Map<string, string | null>; fetchedAt: number; } | null = null;\r\n private static readonly AIRPORTS_CACHE_TTL_MS = 60_000;\r\n\r\n /** Resolves which base URL (if any) should serve a federated call.\r\n * Returns null to mean \"use AeroSwarm's own endpoint\" — the normal,\r\n * unfederated path, which is also what a lookup failure falls back to\r\n * (a broken airports.list() call must never brick replay/history for\r\n * everyone else). */\r\n private async resolveHistoryEndpoint(airportId?: string, override?: string): Promise<string | null> {\r\n if (override) return override.replace(/\\/$/, \"\");\r\n if (!airportId) return null;\r\n try {\r\n const now = Date.now();\r\n if (!this.airportsCache || now - this.airportsCache.fetchedAt > AeroSwarm.AIRPORTS_CACHE_TTL_MS) {\r\n const { airports } = await this.airports.list();\r\n this.airportsCache = {\r\n byId: new Map(airports.map((a) => [a.id, a.historyEndpoint])),\r\n fetchedAt: now,\r\n };\r\n }\r\n const endpoint = this.airportsCache.byId.get(airportId) ?? null;\r\n return endpoint ? endpoint.replace(/\\/$/, \"\") : null;\r\n } catch {\r\n return null; // fail open to AeroSwarm's own endpoint\r\n }\r\n }\r\n\r\n private async federatedFetch<T>(base: string, path: string, query?: Record<string, string | undefined>): Promise<T> {\r\n const url = new URL(`${base}${path}`);\r\n for (const [k, v] of Object.entries(query ?? {})) if (v !== undefined) url.searchParams.set(k, v);\r\n const res = await fetch(url, { headers: { Authorization: `Bearer ${this.opts.token}` } });\r\n if (!res.ok) throw new Error(`Federated request failed: ${res.status} ${res.statusText} (${url.pathname})`);\r\n return res.json() as Promise<T>;\r\n }\r\n\r\n constructor(private readonly opts: AeroSwarmOptions) {\r\n this.trpc = createAeroTrpcClient(opts.endpoint, opts.token);\r\n this.rest = new AeroRestClient(opts.endpoint, opts.token);\r\n this.graphql = new AeroGraphQLClient(opts.endpoint, opts.token);\r\n this.cache = new TrackCache();\r\n\r\n const wsBase = (opts.wsEndpoint ?? opts.endpoint).replace(/^http/, \"ws\");\r\n this.wsClient = new AeroWsClient(\r\n `${wsBase}/ws/tracks/live`,\r\n opts.token,\r\n opts.maxReconnectDelay ?? 30_000,\r\n );\r\n }\r\n\r\n readonly tracks = {\r\n subscribe: (opts: SubscribeOptions, callbacks: LiveTrackCallbacks): (() => void) => {\r\n this.wsClient.connect(\r\n { encoding: this.opts.encoding ?? \"json\", ...opts },\r\n {\r\n ...callbacks,\r\n onStatusChange: (s) => {\r\n this._status = s;\r\n callbacks.onStatusChange?.(s);\r\n },\r\n onSnapshot: (tracks, meta) => {\r\n const labeled = this.cache.applySnapshot(tracks);\r\n this._lastUpdateAt = Date.now();\r\n callbacks.onSnapshot?.(labeled, meta);\r\n },\r\n onTrackUpdate: (track, meta) => {\r\n const labeled = this.cache.applyUpdate(track);\r\n this._lastUpdateAt = Date.now();\r\n callbacks.onTrackUpdate?.(labeled, meta);\r\n },\r\n onTrackRemoved: (ev) => {\r\n this.cache.applyRemoved(ev);\r\n callbacks.onTrackRemoved?.(ev);\r\n },\r\n onSourceStatus: (ev) => {\r\n this._sourceStatus[ev.sourceId] = ev.status;\r\n callbacks.onSourceStatus?.(ev);\r\n },\r\n onError: (err) => {\r\n this._error = err;\r\n callbacks.onError?.(err);\r\n },\r\n }\r\n );\r\n return () => this.wsClient.disconnect();\r\n },\r\n\r\n updateFilter: (opts: Partial<SubscribeOptions>) => this.wsClient.updateFilter(opts),\r\n\r\n /** Drop the live socket and reconnect immediately (no backoff wait),\r\n * re-subscribing with the last options. Returns false if nothing has\r\n * been subscribed yet. */\r\n reconnect: (): boolean => this.wsClient.reconnect(),\r\n\r\n stream: (opts: SubscribeOptions): AsyncIterable<LabeledTrack> => {\r\n const queue: LabeledTrack[] = [];\r\n let resolve: ((v: IteratorResult<LabeledTrack>) => void) | null = null;\r\n let done = false;\r\n\r\n this.wsClient.connect({ encoding: \"json\", ...opts }, {\r\n onSnapshot: (tracks) => { this.cache.applySnapshot(tracks); },\r\n onTrackUpdate: (raw) => {\r\n const t = this.cache.applyUpdate(raw);\r\n if (resolve) { resolve({ value: t, done: false }); resolve = null; }\r\n else queue.push(t);\r\n },\r\n onStatusChange: (s) => { if (s === \"offline\") done = true; },\r\n });\r\n\r\n return {\r\n [Symbol.asyncIterator]: () => ({\r\n next: (): Promise<IteratorResult<LabeledTrack>> => {\r\n if (queue.length) return Promise.resolve({ value: queue.shift()!, done: false });\r\n if (done) return Promise.resolve({ value: undefined as never, done: true });\r\n return new Promise((r) => { resolve = r; });\r\n },\r\n return: async () => { this.wsClient.disconnect(); return { value: undefined as never, done: true }; },\r\n }),\r\n };\r\n },\r\n\r\n snapshot: (input?: { airportId?: string; bbox?: Bbox; source?: \"fused\" | \"normalized\"; minConfidence?: number; limit?: number; offset?: number; }) =>\r\n (this.trpc as any).tracks.snapshot.query(input ?? {}),\r\n\r\n /** Persisted position history for one track. If the airport has a\r\n * `historyEndpoint` (or `endpoint` is passed explicitly), this calls\r\n * that backend's `@aeroswarm/replay-server` instead of AeroSwarm's\r\n * own local database — see docs/development/federation/003-*.md. */\r\n history: async (input: { trackId: string; airportId?: string; limit?: number; endpoint?: string; }) => {\r\n const base = await this.resolveHistoryEndpoint(input.airportId, input.endpoint);\r\n if (base) {\r\n return this.federatedFetch<TrackHistoryPoint[]>(base, `/tracks/${encodeURIComponent(input.trackId)}/history`, {\r\n limit: input.limit?.toString(),\r\n });\r\n }\r\n return (this.trpc as any).tracks.history.query(input) as Promise<TrackHistoryPoint[]>;\r\n },\r\n };\r\n\r\n /** Flight plans received from AFTN feeds. Live tracks that correlate with a\r\n * plan (Mode-S address, else callsign) also carry `track.flightPlan`. */\r\n readonly flightPlans = {\r\n list: (input?: { airportId?: string; status?: string; callsign?: string; limit?: number; }) =>\r\n this.rest.listFlightPlans(input).then((r) => r.data.flightPlans),\r\n get: (id: string) => this.rest.getFlightPlan(id).then((r) => r.data.flightPlan),\r\n };\r\n\r\n /** Raw AFTN/ATS messages received by AFTN feeds — the input side of the\r\n * flight-plan pipeline (newest first, capped by the service). */\r\n readonly aftn = {\r\n messages: (input?: { airportId?: string; limit?: number; }) =>\r\n this.rest.listAftnMessages(input).then((r) => r.data.messages),\r\n };\r\n\r\n readonly sources = {\r\n /** Registers a source and returns its token, tied to `airportId` — a\r\n * source is a sensor physically at one airport, so this is required.\r\n * Anything that later authenticates with the returned token (WS\r\n * live/replay, tRPC calls) is scoped to that airport automatically,\r\n * with no need to keep passing `airportId` on every subsequent call.\r\n * For access spanning multiple airports, use the viewer/dashboard\r\n * token instead — it's never auto-scoped. */\r\n create: (body: { name: string; airportId: string; sourceType?: string; location?: { lat: number; lon: number; }; config?: Record<string, unknown>; }) =>\r\n this.rest.createSource(body),\r\n health: (sourceId: string) => (this.trpc as any).sources.health.query({ sourceId }),\r\n list: (input?: { airportId?: string; }) => (this.trpc as any).sources.list.query(input ?? {}),\r\n /** Reassigning `airportId` (including to `null` to clear it) re-caches\r\n * the source's token immediately, so the new scoping takes effect\r\n * without waiting out the token cache's TTL. */\r\n update: (input: { id: string; name?: string; airportId?: string | null; location?: { lat: number; lon: number; }; config?: Record<string, unknown>; }) =>\r\n (this.trpc as any).sources.update.mutate(input),\r\n delete: (id: string) => (this.trpc as any).sources.delete.mutate({ id }),\r\n };\r\n\r\n /** Airport registry (multi-airport tenancy). */\r\n readonly airports = {\r\n list: () =>\r\n (this.trpc as any).airports.list.query() as Promise<{\r\n airports: {\r\n id: string; name: string; city: string | null;\r\n lat: number; lon: number;\r\n bbox: [number, number, number, number]; timezone: string;\r\n /** Set = this airport serves its own replay/history (federated —\r\n * see docs/development/federation/003-*.md); `replay.*`/\r\n * `tracks.history` calls for it connect there directly instead of\r\n * AeroSwarm's own endpoint. null = AeroSwarm serves it (default). */\r\n historyEndpoint: string | null;\r\n }[];\r\n }>,\r\n create: (input: {\r\n id: string; name: string; city?: string;\r\n lat: number; lon: number;\r\n bbox: [number, number, number, number]; timezone?: string;\r\n historyEndpoint?: string;\r\n }) => (this.trpc as any).airports.create.mutate(input) as Promise<{ ok: boolean; id: string; }>,\r\n update: (input: {\r\n id: string; name?: string; city?: string;\r\n lat?: number; lon?: number;\r\n bbox?: [number, number, number, number]; timezone?: string;\r\n /** null clears it (reverts to AeroSwarm's own replay); omit to leave unchanged. */\r\n historyEndpoint?: string | null;\r\n }) => (this.trpc as any).airports.update.mutate(input) as Promise<{ ok: boolean; }>,\r\n delete: (input: { id: string; }) =>\r\n (this.trpc as any).airports.delete.mutate(input) as Promise<{ ok: boolean; }>,\r\n /** Surface geometry (runway/taxiway/apron GeoJSON) for the map. */\r\n surface: (input: { id: string; }) =>\r\n (this.trpc as any).airports.surface.query(input) as Promise<{ surface: unknown | null; }>,\r\n setSurface: (input: { id: string; surface: Record<string, unknown>; }) =>\r\n (this.trpc as any).airports.setSurface.mutate(input) as Promise<{ ok: boolean; }>,\r\n };\r\n\r\n readonly replay = {\r\n /** Data-availability calendar: which days have recorded tracks.\r\n * Use this to populate/validate replay date pickers — days not in\r\n * the list have no data. Federates to the airport's own backend when\r\n * it has a `historyEndpoint` (or `endpoint` is passed explicitly) —\r\n * see docs/development/federation/003-*.md. */\r\n days: async (input?: { airportId?: string; lookbackDays?: number; endpoint?: string; }) => {\r\n const base = await this.resolveHistoryEndpoint(input?.airportId, input?.endpoint);\r\n if (base) {\r\n return this.federatedFetch<{ days: DayAvailability[]; }>(base, \"/replay/days\", {\r\n lookbackDays: input?.lookbackDays?.toString(),\r\n });\r\n }\r\n return (this.trpc as any).replay.days.query(input ?? {}) as Promise<{ days: DayAvailability[]; }>;\r\n },\r\n range: async (input: { from: string; to: string; airportId?: string; endpoint?: string; }) => {\r\n const base = await this.resolveHistoryEndpoint(input.airportId, input.endpoint);\r\n if (base) {\r\n const summary = await this.federatedFetch<{ earliest: string | null; latest: string | null; eventCount: number; trackCount: number; }>(\r\n base, \"/replay/range\", { from: input.from, to: input.to },\r\n );\r\n return { from: input.from, to: input.to, ...summary };\r\n }\r\n return (this.trpc as any).replay.range.query(input) as Promise<{\r\n from: string; to: string;\r\n earliest: string | null; latest: string | null;\r\n eventCount: number; trackCount: number;\r\n }>;\r\n },\r\n // replay.window / replay.seek (bulk sample download for client-side\r\n // interpolation) were removed — they were never part of the federation\r\n // contract, and `replay.stream`'s server-paced WS (with server-side\r\n // seek/re-snapshot) covers every use they had at a fraction of the\r\n // payload. See docs/development/federation/005-*.md.\r\n /** Server-paced WS playback: streams historical tracks at `speed`× in\r\n * the same envelope format as the live stream. Returns a transport\r\n * handle (pause/resume/seek/stop) — a Promise, since resolving which\r\n * backend serves this airport (see docs/development/federation/003-*.md)\r\n * is async. See `AeroReplayClient` for the raw wire protocol if you\r\n * want to speak WS manually instead. */\r\n stream: async (opts: ReplayStreamOptions & { endpoint?: string; }, callbacks: ReplayCallbacks): Promise<ReplayHandle> => {\r\n const federatedBase = await this.resolveHistoryEndpoint(opts.airportId, opts.endpoint);\r\n if (federatedBase) {\r\n const wsBase = federatedBase.replace(/^http/, \"ws\");\r\n // A federated backend is single-airport — no airportId param needed\r\n // (and its own auth is source-token-based, not airport-scoped).\r\n const { airportId: _drop, ...rest } = opts;\r\n return new AeroReplayClient(wsBase, this.opts.token, \"/replay\").stream(rest, callbacks);\r\n }\r\n const wsBase = (this.opts.wsEndpoint ?? this.opts.endpoint).replace(/^http/, \"ws\");\r\n return new AeroReplayClient(wsBase, this.opts.token).stream(opts, callbacks);\r\n },\r\n };\r\n\r\n /** Movement analytics: takeoffs/landings per hour + peak hour, recorded\r\n * by the pipeline's movement detector. */\r\n readonly analytics = {\r\n movements: (input?: { airportId?: string; day?: string; }) =>\r\n (this.trpc as any).analytics.movements.query(input ?? {}) as Promise<{\r\n day: string;\r\n hours: { hour: string; takeoffs: number; landings: number; }[];\r\n totals: { takeoffs: number; landings: number; };\r\n peak: { hour: string; count: number; } | null;\r\n }>,\r\n };\r\n\r\n /** Route conformance: pre-defined ground routes + live track clearances.\r\n * Assign a route to a track and the safety engine can detect when it\r\n * strays beyond the route's corridor. */\r\n readonly routes = {\r\n list: (input?: { airportId?: string; }) =>\r\n (this.trpc as any).routes.list.query(input ?? {}) as Promise<{\r\n routes: {\r\n routeId: string; airportId: string; name: string; kind: string;\r\n points: [number, number][]; maxDeviationM: number; enabled: boolean;\r\n }[];\r\n }>,\r\n create: (input: { airportId: string; name: string; kind?: \"taxi\" | \"departure\" | \"arrival\"; points: [number, number][]; maxDeviationM?: number; }) =>\r\n (this.trpc as any).routes.create.mutate(input) as Promise<{ id: string; }>,\r\n delete: (input: { id: string; }) =>\r\n (this.trpc as any).routes.delete.mutate(input) as Promise<{ ok: boolean; }>,\r\n assign: (input: { trackId: string; routeId: string; }) =>\r\n (this.trpc as any).routes.assign.mutate(input) as Promise<{ ok: boolean; }>,\r\n unassign: (input: { trackId: string; }) =>\r\n (this.trpc as any).routes.unassign.mutate(input) as Promise<{ ok: boolean; }>,\r\n assignments: () =>\r\n (this.trpc as any).routes.assignments.query() as Promise<{\r\n assignments: { trackId: string; routeId: string; assignedAt: number; }[];\r\n }>,\r\n };\r\n\r\n readonly webhooks = {\r\n list: () => (this.trpc as any).webhooks.list.query(),\r\n create: (body: { url: string; events: string[]; secret?: string; }) =>\r\n (this.trpc as any).webhooks.create.mutate(body) as Promise<{ ok: boolean; webhookId: string; }>,\r\n delete: (input: { id: string; }) => (this.trpc as any).webhooks.delete.mutate(input),\r\n };\r\n\r\n readonly system = {\r\n stats: () => (this.trpc as any).system.stats.query(),\r\n infraHealth: () => (this.trpc as any).system.infraHealth.query(),\r\n ingestHistory: () => (this.trpc as any).system.ingestHistory.query(),\r\n getConfig: () => (this.trpc as any).system.getConfig.query() as Promise<{ key: string; value: string; updatedAt: string; }[]>,\r\n setConfig: (input: { key: string; value: string; }) =>\r\n (this.trpc as any).system.setConfig.mutate(input) as Promise<{ ok: boolean; }>,\r\n clearTable: (input: {\r\n table: \"fused_tracks\" | \"source_health\";\r\n mode: \"all\" | \"range\";\r\n from?: string;\r\n to?: string;\r\n }) => (this.trpc as any).system.clearTable.mutate(input),\r\n };\r\n\r\n getStatus(): AeroConnectionStatus { return this._status; }\r\n getLastUpdateAt(): number | null { return this._lastUpdateAt; }\r\n getError(): AeroRecoverableError | null { return this._error; }\r\n getSourceStatus(): Record<string, string> { return { ...this._sourceStatus }; }\r\n\r\n disconnect(): void { this.wsClient.disconnect(); }\r\n reconnect(): boolean { return this.wsClient.reconnect(); }\r\n}\r\n","import { useEffect, useMemo, useRef } from \"react\";\nimport {\n createTrackMotion,\n type MotionFrame,\n type MotionOptions,\n type TrackMotion,\n} from \"../../shared/motion/index.js\";\n\nexport interface UseTrackMotionOptions extends MotionOptions {\n /** Called ~30 times a second while something moves, with only the tracks that changed. */\n onFrame: (frame: MotionFrame) => void;\n}\n\n/**\n * Smooth motion for live tracks. Feed it from the live subscription and draw\n * from `onFrame`; nothing here causes a React render.\n *\n * const motion = useTrackMotion({ onFrame: ({ changed }) => layer.update(changed) });\n * client.tracks.subscribe(opts, { onTrackUpdate: (t, meta) => motion.update(t, meta) });\n *\n * The engine is created once: options are read on the first render only.\n */\nexport function useTrackMotion(\n options: UseTrackMotionOptions,\n): Pick<TrackMotion, \"update\" | \"remove\" | \"clear\" | \"sample\"> {\n const { onFrame, ...motionOptions } = options;\n const onFrameRef = useRef(onFrame);\n onFrameRef.current = onFrame;\n\n const motion = useMemo(() => createTrackMotion(motionOptions), []);\n\n useEffect(() => {\n const stop = motion.start((frame) => onFrameRef.current(frame));\n return () => {\n stop();\n motion.clear();\n };\n }, [motion]);\n\n return motion;\n}\n","// Smooth motion for live tracks, independent of any UI framework.\n//\n// A feed gives a position every second (MLAT, ADS-B) or every antenna sweep\n// (radar, ~7.5 s). Drawn as they arrive, icons jump. This engine shows where the\n// aircraft PROBABLY is right now and eases corrections in, the way flight\n// trackers do:\n//\n// motion.update(track, { serverTime }); // every WS update\n// const stop = motion.start(({ changed }) => { // ~30 frames/s, only tracks that moved\n// for (const [id, s] of changed) layer.put(id, s.lat, s.lon, s.headingDeg);\n// });\n//\n// How the picture stays smooth:\n// * dead reckoning: from the last fix, speed and heading carry the position forward;\n// * a new fix does NOT snap the icon. The gap between where it was shown and where the\n// new fix says it should be becomes an offset that decays (smoothstep) to zero. The\n// icon keeps moving at its own speed meanwhile, so there is no stop-and-go at\n// each update (a plain linear blend between two positions slows the icon to a halt\n// at the start of every blend);\n// * heading eases along the shortest arc, so icons never spin the long way round;\n// * time is the SERVER's: lastSeenAt is the server's clock, so the engine learns the\n// offset to the browser clock from the serverTime of each message;\n// * extrapolation stops at a limit, and freezes once the track is \"lost\".\nimport type { CurrentTrack } from \"../types/index.js\";\n\n/** What the engine reads from a track. */\nexport type MotionInput = Pick<\n CurrentTrack,\n \"trackId\" | \"lat\" | \"lon\" | \"groundSpeedKt\" | \"headingDeg\" | \"altitudeFt\" | \"status\" | \"lastSeenAt\"\n> & { targetType?: CurrentTrack[\"targetType\"] };\n\nexport interface MotionSample {\n lat: number;\n lon: number;\n /** Degrees 0..360, eased; null until a valid heading has been seen. */\n headingDeg: number | null;\n altitudeFt: number | null;\n /** Speed is above `minMovingKt`. */\n moving: boolean;\n /** Position currently comes from dead reckoning (not frozen, limit not reached). */\n extrapolating: boolean;\n}\n\nexport interface MotionFrame {\n /** Server-aligned time of this frame (ms). */\n at: number;\n /** Only tracks whose picture changed since the previous frame. */\n changed: ReadonlyMap<string, MotionSample>;\n}\n\nexport interface MotionScheduler {\n request(callback: (timestamp: number) => void): unknown;\n cancel(handle: unknown): void;\n}\n\nexport interface MotionOptions {\n /** How long a correction takes to fade out. Default 1500 ms. */\n blendMs?: number;\n /** How long the icon takes to turn to a new heading. Default 800 ms. */\n headingSmoothMs?: number;\n /** Dead reckoning stops after this long without a new fix. Default 20 s in the air, 8 s on the surface. */\n maxExtrapolateMs?: { air: number; surface: number };\n /** Below this speed a track is treated as standing still. Default 5 kt. */\n minMovingKt?: number;\n /** A correction larger than this is a glitch or a sensor change far away: jump instead of easing. Default 5000 m. */\n snapDistanceM?: number;\n /** Frame rate limit for `start()`. Default 30. */\n frameHz?: number;\n /** Changes smaller than this are not reported. Default 0.5 m (and 0.5 degrees of heading). */\n epsilonM?: number;\n /** false = show raw positions (for comparing against another source). Default true. */\n extrapolate?: boolean;\n /** Decides what counts as \"on the surface\". Default: vehicles, or below 150 ft. */\n isSurface?: (track: MotionInput) => boolean;\n /** Browser clock. Default Date.now. Inject the replay clock to play back at another speed. */\n clock?: () => number;\n /** Frame timer. Default requestAnimationFrame, else setTimeout. Inject in tests. */\n scheduler?: MotionScheduler;\n}\n\nexport interface TrackMotion {\n /** `meta.serverTime` (from the WS envelope) keeps the server/browser clock offset current. */\n update(track: MotionInput, meta?: { serverTime?: number }): void;\n remove(trackId: string): void;\n clear(): void;\n /** Where the track is shown at `at` (browser time; default now). Pure: does not touch frame state. */\n sample(trackId: string, at?: number): MotionSample | undefined;\n /** One frame: the tracks whose picture changed since the previous frame. `start()` calls this for you. */\n tick(at?: number): ReadonlyMap<string, MotionSample>;\n /** Runs frames until the returned function is called. Idles (no timer) while nothing moves. */\n start(onFrame: (frame: MotionFrame) => void): () => void;\n /** Tracks currently held. */\n readonly size: number;\n /** Current server minus browser clock, ms. */\n offsetMs(): number;\n}\n\nconst KT_TO_MS = 0.514444;\nconst M_PER_DEG = 111_320;\nconst MAX_PLAUSIBLE_KT = 1_500;\nconst OFFSET_WINDOW = 20;\nconst ALT_EPSILON_FT = 10;\nconst HEADING_EPSILON_DEG = 0.5;\nconst SURFACE_ALT_FT = 150;\n\nconst clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\nconst smoothstep = (x: number): number => {\n const c = clamp01(x);\n return c * c * (3 - 2 * c);\n};\n/** -180..180 */\nconst wrapDeg = (d: number): number => ((((d + 180) % 360) + 360) % 360) - 180;\nconst norm360 = (d: number): number => ((d % 360) + 360) % 360;\nconst cosLat = (lat: number): number => Math.max(0.01, Math.cos((lat * Math.PI) / 180));\n\nfunction validHeading(h: number | null): number | null {\n return h != null && Number.isFinite(h) && h >= 0 && h <= 360 ? h % 360 : null;\n}\n\nfunction move(lat: number, lon: number, bearingDeg: number, distM: number): { lat: number; lon: number } {\n const rad = (bearingDeg * Math.PI) / 180;\n const dLat = (distM * Math.cos(rad)) / M_PER_DEG;\n const dLon = (distM * Math.sin(rad)) / (M_PER_DEG * cosLat(lat + dLat / 2));\n return { lat: lat + dLat, lon: lon + dLon };\n}\n\n/** Flat-earth distance: exact enough over the few kilometres involved here. */\nfunction distanceM(aLat: number, aLon: number, bLat: number, bLon: number): number {\n const dy = (bLat - aLat) * M_PER_DEG;\n const dx = (bLon - aLon) * M_PER_DEG * cosLat((aLat + bLat) / 2);\n return Math.hypot(dx, dy);\n}\n\nconst headingChanged = (a: number | null, b: number | null): boolean =>\n a === null || b === null ? a !== b : Math.abs(wrapDeg(a - b)) > HEADING_EPSILON_DEG;\n\nconst altitudeChanged = (a: number | null, b: number | null): boolean =>\n a === null || b === null ? a !== b : Math.abs(a - b) >= ALT_EPSILON_FT;\n\ninterface Entry {\n id: string;\n /** Last accepted position and the server time it belongs to. */\n fixLat: number;\n fixLon: number;\n fixAt: number;\n speedMs: number;\n course: number | null;\n surface: boolean;\n /** Elapsed ms at which a lost track stopped moving; null while it is alive. */\n frozen: number | null;\n /** Offset (degrees) between the shown and the predicted position at `blendStart`; fades to 0. */\n errLat: number;\n errLon: number;\n blendStart: number;\n hdgFrom: number | null;\n hdgTo: number | null;\n hdgStart: number;\n altFrom: number | null;\n altTo: number | null;\n altStart: number;\n /** From this server time on, `sample()` no longer changes. */\n settleAt: number;\n emitted: MotionSample | null;\n}\n\ntype Resolved = Required<Omit<MotionOptions, \"scheduler\" | \"isSurface\" | \"clock\">> &\n Pick<MotionOptions, \"scheduler\"> & {\n isSurface: (t: MotionInput) => boolean;\n clock: () => number;\n };\n\nconst defaultIsSurface = (t: MotionInput): boolean =>\n t.targetType === \"vehicle\" || (t.altitudeFt != null && t.altitudeFt <= SURFACE_ALT_FT);\n\nfunction resolve(o: MotionOptions): Resolved {\n return {\n blendMs: o.blendMs ?? 1_500,\n headingSmoothMs: o.headingSmoothMs ?? 800,\n maxExtrapolateMs: o.maxExtrapolateMs ?? { air: 20_000, surface: 8_000 },\n minMovingKt: o.minMovingKt ?? 5,\n snapDistanceM: o.snapDistanceM ?? 5_000,\n frameHz: o.frameHz ?? 30,\n epsilonM: o.epsilonM ?? 0.5,\n extrapolate: o.extrapolate ?? true,\n isSurface: o.isSurface ?? defaultIsSurface,\n clock: o.clock ?? Date.now,\n scheduler: o.scheduler,\n };\n}\n\nfunction defaultScheduler(): MotionScheduler {\n const g = globalThis as {\n requestAnimationFrame?: (cb: (t: number) => void) => number;\n cancelAnimationFrame?: (h: number) => void;\n };\n if (typeof g.requestAnimationFrame === \"function\" && typeof g.cancelAnimationFrame === \"function\") {\n return {\n request: (cb) => g.requestAnimationFrame!(cb),\n cancel: (h) => g.cancelAnimationFrame!(h as number),\n };\n }\n return {\n request: (cb) => setTimeout(() => cb(Date.now()), 16),\n cancel: (h) => clearTimeout(h as ReturnType<typeof setTimeout>),\n };\n}\n\nclass Motion implements TrackMotion {\n private readonly o: Resolved;\n private readonly entries = new Map<string, Entry>();\n /** Tracks whose picture can still change. Everything else costs nothing per frame. */\n private readonly active = new Set<string>();\n private readonly listeners = new Set<(f: MotionFrame) => void>();\n private offsets: number[] = [];\n private offset = 0;\n private handle: unknown = null;\n private lastFrameAt = Number.NEGATIVE_INFINITY;\n\n constructor(options: MotionOptions) {\n this.o = resolve(options);\n }\n\n get size(): number {\n return this.entries.size;\n }\n\n offsetMs(): number {\n return this.offset;\n }\n\n // ── time ────────────────────────────────────────────────────────────────\n\n /** Browser clock + learned offset = the server's clock. */\n private serverNow(at?: number): number {\n return (at ?? this.o.clock()) + this.offset;\n }\n\n /**\n * `serverTime - browserNow` is the true offset MINUS the network delay, so the\n * largest recent value is the one with the least delay, i.e. the best estimate.\n */\n private observe(serverTime: number): void {\n if (!Number.isFinite(serverTime)) return;\n this.offsets.push(serverTime - this.o.clock());\n if (this.offsets.length > OFFSET_WINDOW) this.offsets.shift();\n this.offset = Math.max(...this.offsets);\n }\n\n private cap(e: Entry): number {\n const m = this.o.maxExtrapolateMs;\n return e.surface ? m.surface : m.air;\n }\n\n // ── model ───────────────────────────────────────────────────────────────\n\n private posAt(e: Entry, s: number): { lat: number; lon: number; extrapolating: boolean } {\n const cap = this.cap(e);\n let elapsed = Math.min(Math.max(0, s - e.fixAt), cap);\n if (e.frozen !== null) elapsed = Math.min(elapsed, e.frozen);\n if (!this.o.extrapolate || e.course === null || e.speedMs <= 0) {\n return { lat: e.fixLat, lon: e.fixLon, extrapolating: false };\n }\n const p = move(e.fixLat, e.fixLon, e.course, (e.speedMs * elapsed) / 1000);\n return { ...p, extrapolating: e.frozen === null && elapsed < cap };\n }\n\n private sampleAt(e: Entry, s: number): MotionSample {\n const p = this.posAt(e, s);\n const rest = 1 - smoothstep((s - e.blendStart) / this.o.blendMs);\n\n let headingDeg: number | null = null;\n if (e.hdgTo !== null) {\n const from = e.hdgFrom ?? e.hdgTo;\n const left = 1 - smoothstep((s - e.hdgStart) / this.o.headingSmoothMs);\n headingDeg = norm360(e.hdgTo + wrapDeg(from - e.hdgTo) * left);\n }\n\n let altitudeFt: number | null = null;\n if (e.altTo !== null) {\n const k = smoothstep((s - e.altStart) / this.o.blendMs);\n altitudeFt = e.altFrom === null ? e.altTo : e.altFrom + (e.altTo - e.altFrom) * k;\n }\n\n return {\n lat: p.lat + e.errLat * rest,\n lon: p.lon + e.errLon * rest,\n headingDeg,\n altitudeFt,\n moving: e.speedMs > 0,\n extrapolating: p.extrapolating,\n };\n }\n\n // ── input ───────────────────────────────────────────────────────────────\n\n update(t: MotionInput, meta?: { serverTime?: number }): void {\n if (meta?.serverTime !== undefined) this.observe(meta.serverTime);\n if (t.lat == null || t.lon == null || !Number.isFinite(t.lat) || !Number.isFinite(t.lon)) {\n this.remove(t.trackId);\n return;\n }\n const s = this.serverNow();\n const e = this.entries.get(t.trackId);\n if (!e) {\n this.create(t, s);\n return;\n }\n this.refresh(e, t, s);\n }\n\n private moving(t: MotionInput, course: number | null): boolean {\n const kt = t.groundSpeedKt;\n return course !== null && kt != null && Number.isFinite(kt) && kt >= this.o.minMovingKt && kt <= MAX_PLAUSIBLE_KT;\n }\n\n private create(t: MotionInput, s: number): void {\n const course = validHeading(t.headingDeg);\n const moving = this.moving(t, course);\n const e: Entry = {\n id: t.trackId,\n fixLat: t.lat as number,\n fixLon: t.lon as number,\n fixAt: t.lastSeenAt,\n speedMs: moving ? (t.groundSpeedKt as number) * KT_TO_MS : 0,\n course,\n surface: this.o.isSurface(t),\n frozen: null,\n errLat: 0,\n errLon: 0,\n blendStart: s,\n hdgFrom: course,\n hdgTo: course,\n hdgStart: s,\n altFrom: null,\n altTo: t.altitudeFt,\n altStart: s,\n settleAt: 0,\n emitted: null,\n };\n if (t.status === \"lost\") e.frozen = this.frozenAt(e, s);\n this.entries.set(e.id, e);\n this.settle(e);\n this.active.add(e.id);\n this.ensureLoop();\n }\n\n private frozenAt(e: Entry, s: number): number {\n return Math.min(Math.max(0, s - e.fixAt), this.cap(e));\n }\n\n private refresh(e: Entry, t: MotionInput, s: number): void {\n e.surface = this.o.isSurface(t);\n if (t.status === \"lost\") e.frozen ??= this.frozenAt(e, s);\n else e.frozen = null;\n\n const lat = t.lat as number;\n const lon = t.lon as number;\n const older = t.lastSeenAt < e.fixAt;\n const same = t.lastSeenAt === e.fixAt && lat === e.fixLat && lon === e.fixLon;\n if (older || same) {\n // A repeat or a late arrival: nothing to ease, and restarting the blend would jerk the icon.\n this.settle(e);\n this.touch(e);\n return;\n }\n\n const shown = this.sampleAt(e, s);\n const course = validHeading(t.headingDeg) ?? e.course;\n const moving = this.moving(t, course);\n e.fixLat = lat;\n e.fixLon = lon;\n e.fixAt = t.lastSeenAt;\n e.speedMs = moving ? (t.groundSpeedKt as number) * KT_TO_MS : 0;\n e.course = course;\n\n const predicted = this.posAt(e, s);\n const jump = distanceM(shown.lat, shown.lon, predicted.lat, predicted.lon);\n const snap = jump > this.o.snapDistanceM;\n e.errLat = snap ? 0 : shown.lat - predicted.lat;\n e.errLon = snap ? 0 : shown.lon - predicted.lon;\n e.blendStart = s;\n\n e.hdgFrom = snap ? course : (shown.headingDeg ?? course);\n e.hdgTo = course;\n e.hdgStart = s;\n\n e.altFrom = snap ? t.altitudeFt : shown.altitudeFt;\n e.altTo = t.altitudeFt;\n e.altStart = s;\n\n this.settle(e);\n this.touch(e);\n }\n\n /** The server time after which nothing about this track changes on its own. */\n private settle(e: Entry): void {\n const ends = [e.blendStart + this.o.blendMs, e.hdgStart + this.o.headingSmoothMs, e.altStart + this.o.blendMs];\n if (this.o.extrapolate && e.speedMs > 0 && e.frozen === null) ends.push(e.fixAt + this.cap(e));\n e.settleAt = Math.max(...ends);\n }\n\n private touch(e: Entry): void {\n this.active.add(e.id);\n this.ensureLoop();\n }\n\n remove(trackId: string): void {\n this.entries.delete(trackId);\n this.active.delete(trackId);\n }\n\n clear(): void {\n this.entries.clear();\n this.active.clear();\n }\n\n // ── output ──────────────────────────────────────────────────────────────\n\n sample(trackId: string, at?: number): MotionSample | undefined {\n const e = this.entries.get(trackId);\n return e ? this.sampleAt(e, this.serverNow(at)) : undefined;\n }\n\n private differs(a: MotionSample | null, b: MotionSample): boolean {\n if (a === null || a.moving !== b.moving || a.extrapolating !== b.extrapolating) return true;\n if (distanceM(a.lat, a.lon, b.lat, b.lon) > this.o.epsilonM) return true;\n return headingChanged(a.headingDeg, b.headingDeg) || altitudeChanged(a.altitudeFt, b.altitudeFt);\n }\n\n tick(at?: number): ReadonlyMap<string, MotionSample> {\n const s = this.serverNow(at);\n const changed = new Map<string, MotionSample>();\n for (const id of [...this.active]) {\n const e = this.entries.get(id);\n if (!e) {\n this.active.delete(id);\n continue;\n }\n const sample = this.sampleAt(e, s);\n if (this.differs(e.emitted, sample)) {\n e.emitted = sample;\n changed.set(id, sample);\n }\n if (s >= e.settleAt) this.active.delete(id);\n }\n return changed;\n }\n\n // ── frame loop ──────────────────────────────────────────────────────────\n\n start(onFrame: (frame: MotionFrame) => void): () => void {\n this.listeners.add(onFrame);\n this.ensureLoop();\n return () => {\n this.listeners.delete(onFrame);\n if (this.listeners.size === 0) this.cancelLoop();\n };\n }\n\n private ensureLoop(): void {\n if (this.handle !== null || this.listeners.size === 0 || this.active.size === 0) return;\n const scheduler = this.o.scheduler ?? (this.o.scheduler = defaultScheduler());\n this.handle = scheduler.request((ts) => this.frame(ts));\n }\n\n private cancelLoop(): void {\n if (this.handle === null) return;\n (this.o.scheduler ?? defaultScheduler()).cancel(this.handle);\n this.handle = null;\n }\n\n private frame(ts: number): void {\n this.handle = null;\n if (this.listeners.size === 0) return;\n if (ts - this.lastFrameAt >= 1000 / this.o.frameHz - 1) {\n this.lastFrameAt = ts;\n const changed = this.tick();\n if (changed.size > 0) {\n const frame: MotionFrame = { at: this.serverNow(), changed };\n for (const fn of [...this.listeners]) fn(frame);\n }\n }\n // Nothing left to animate: stop asking for frames until the next update.\n this.ensureLoop();\n }\n}\n\nexport function createTrackMotion(options: MotionOptions = {}): TrackMotion {\n return new Motion(options);\n}\n","import { useCallback, useEffect, useRef } from \"react\";\nimport type { CurrentTrack } from \"../../shared/types/index.js\";\n\ninterface InterpState {\n fromLat: number; fromLon: number;\n toLat: number; toLon: number;\n startedAt: number;\n durationMs: number;\n}\n\ninterface VelocityState {\n speedKt: number;\n headingDeg: number;\n}\n\ninterface DrState {\n baseLat: number;\n baseLon: number;\n baseTime: number; // performance.now() when DR started\n}\n\nexport interface TrackPosition {\n lat: number;\n lon: number;\n}\n\nexport interface TrackHistoryFrame {\n /** Animated (interpolated + dead-reckoned) current position per trackId */\n currentPositions: Map<string, TrackPosition>;\n /** Full position history per trackId as [lon, lat] pairs */\n history: Map<string, [number, number][]>;\n /** Last wall-clock update timestamp (ms) per trackId */\n lastSeen: Map<string, number>;\n /** Set of trackIds currently mid-tween */\n isMoving: Set<string>;\n}\n\nexport interface UseTrackHistoryOptions {\n /** Max history points to retain per track (default 500) */\n maxHistoryPoints?: number;\n /** Minimum position delta in degrees to record a new history point (default 0.0001) */\n minDeltaDeg?: number;\n /**\n * Maximum allowed position jump in degrees per update (default 5.0 ≈ 555 km).\n * Updates that exceed this are silently discarded as bad transponder data.\n */\n maxJumpDeg?: number;\n /** Called every animation frame while tweens/DR are active, and once on every data update */\n onFrame: (frame: TrackHistoryFrame) => void;\n}\n\nexport interface UseTrackHistoryResult {\n /** Feed a single track update directly into the animation engine (no React render cycle) */\n processUpdate: (track: CurrentTrack) => void;\n /** Remove a track from the animation engine */\n processRemove: (trackId: string) => void;\n /** Force a one-shot frame emission with current state */\n reemit: () => void;\n}\n\n// Minimum speed (kt) for dead reckoning to kick in\nconst DR_MIN_SPEED_KT = 10;\n// Maximum physically plausible speed (kt) — anything faster is bad data\n// (misdecode, or a position delta divided by a compressed replay clock)\n// and must never drive dead reckoning, or icons get flung off the map.\nconst MAX_SPEED_KT = 1200;\n\n// An update gap longer than this restarts the track's trail instead of\n// connecting the two positions with a line across the map.\nconst GAP_RESET_MS = 30_000;\n// Max dead-reckoning duration before we stop extrapolating (ms)\nconst DR_MAX_MS = 180_000; // 3 minutes\n\nfunction deadReckon(\n baseLat: number, baseLon: number,\n speedKt: number, headingDeg: number,\n elapsedMs: number,\n): TrackPosition {\n const elapsedSec = elapsedMs / 1000;\n const distanceNm = speedKt * elapsedSec / 3600;\n const headingRad = headingDeg * (Math.PI / 180);\n const dlat = (distanceNm / 60) * Math.cos(headingRad);\n const dlon = (distanceNm / (60 * Math.cos(baseLat * (Math.PI / 180)))) * Math.sin(headingRad);\n return { lat: baseLat + dlat, lon: baseLon + dlon };\n}\n\n/**\n * @deprecated Use `useTrackMotion` (or `createTrackMotion` outside React). This hook times\n * fixes by when they ARRIVE, so network jitter ends up in the prediction; the new engine uses\n * the server's clock, keeps the icon moving while a correction fades (no stop-and-go), eases the\n * heading, and only reports tracks that changed. Server-side trails come from `tracks.trail`.\n */\nexport function useTrackHistory(\n options: UseTrackHistoryOptions,\n): UseTrackHistoryResult {\n const {\n maxHistoryPoints = 500,\n minDeltaDeg = 0.0001,\n maxJumpDeg = 5.0,\n onFrame,\n } = options;\n\n const currentPositions = useRef<Map<string, TrackPosition>>(new Map());\n const lastActualPos = useRef<Map<string, TrackPosition>>(new Map());\n const history = useRef<Map<string, [number, number][]>>(new Map());\n const lastSeen = useRef<Map<string, number>>(new Map());\n const lastUpdPerf = useRef<Map<string, number>>(new Map());\n const interp = useRef<Map<string, InterpState>>(new Map());\n const velocity = useRef<Map<string, VelocityState>>(new Map());\n const drState = useRef<Map<string, DrState>>(new Map());\n const rafRef = useRef<number | null>(null);\n const onFrameRef = useRef(onFrame);\n onFrameRef.current = onFrame;\n\n // Options via refs so processUpdate is stable\n const maxHistoryPointsRef = useRef(maxHistoryPoints);\n maxHistoryPointsRef.current = maxHistoryPoints;\n const minDeltaDegRef = useRef(minDeltaDeg);\n minDeltaDegRef.current = minDeltaDeg;\n const maxJumpDegRef = useRef(maxJumpDeg);\n maxJumpDegRef.current = maxJumpDeg;\n\n function emitFrame() {\n onFrameRef.current({\n currentPositions: currentPositions.current,\n history: history.current,\n lastSeen: lastSeen.current,\n isMoving: new Set([...interp.current.keys(), ...drState.current.keys()]),\n });\n }\n\n // Returns true if any track needs continuous animation (tween or DR)\n function needsRaf(): boolean {\n if (interp.current.size > 0) return true;\n for (const [id, dr] of drState.current) {\n if (interp.current.has(id)) continue;\n const vel = velocity.current.get(id);\n if (vel && vel.speedKt >= DR_MIN_SPEED_KT) return true;\n // clean up stale DR\n if (performance.now() - dr.baseTime > DR_MAX_MS) drState.current.delete(id);\n }\n return false;\n }\n\n function ensureRaf() {\n if (rafRef.current !== null || !needsRaf()) return;\n const tick = (perfNow: number) => {\n for (const [id, tw] of interp.current) {\n const p = Math.min((perfNow - tw.startedAt) / tw.durationMs, 1);\n const ease = p < 1 ? p * (2 - p) : 1; // ease-out quad\n const lat = tw.fromLat + (tw.toLat - tw.fromLat) * ease;\n const lon = tw.fromLon + (tw.toLon - tw.fromLon) * ease;\n currentPositions.current.set(id, { lat, lon });\n if (p >= 1) {\n interp.current.delete(id);\n // Hand off to dead reckoning\n const vel = velocity.current.get(id);\n if (vel && vel.speedKt >= DR_MIN_SPEED_KT) {\n drState.current.set(id, { baseLat: lat, baseLon: lon, baseTime: perfNow });\n }\n }\n }\n\n // Dead reckoning for non-tweening tracks\n for (const [id, dr] of drState.current) {\n if (interp.current.has(id)) continue;\n const vel = velocity.current.get(id);\n if (!vel || vel.speedKt < DR_MIN_SPEED_KT) continue;\n const elapsed = perfNow - dr.baseTime;\n if (elapsed > DR_MAX_MS) { drState.current.delete(id); continue; }\n currentPositions.current.set(id, deadReckon(\n dr.baseLat, dr.baseLon, vel.speedKt, vel.headingDeg, elapsed,\n ));\n }\n\n emitFrame();\n rafRef.current = needsRaf() ? requestAnimationFrame(tick) : null;\n };\n rafRef.current = requestAnimationFrame(tick);\n }\n\n // Imperative: process a single track update without going through React state\n const processUpdate = useCallback((t: CurrentTrack) => {\n if (t.lat == null || t.lon == null) return;\n if (t.lat < -90 || t.lat > 90 || t.lon < -180 || t.lon > 180) return;\n const perfNow = performance.now();\n const wallNow = Date.now();\n const id = t.trackId;\n const lon = t.lon as number;\n const lat = t.lat as number;\n\n lastSeen.current.set(id, wallNow);\n\n // Update velocity from explicit data first (rejecting implausible speeds)\n if (t.groundSpeedKt != null && t.headingDeg != null) {\n const spd = t.groundSpeedKt as number;\n const hdg = t.headingDeg as number;\n if (spd >= 0 && spd <= MAX_SPEED_KT) {\n if (spd >= DR_MIN_SPEED_KT || !velocity.current.has(id)) {\n velocity.current.set(id, { speedKt: spd, headingDeg: hdg });\n }\n }\n }\n\n // Derive velocity from position delta if no explicit velocity (e.g. CAT48 radar tracks)\n const prevActual = lastActualPos.current.get(id);\n const prevUpdPerf = lastUpdPerf.current.get(id);\n if (prevActual && prevUpdPerf && perfNow > prevUpdPerf) {\n const dtSec = (perfNow - prevUpdPerf) / 1000;\n const dlatDeg = lat - prevActual.lat;\n const dlonDeg = lon - prevActual.lon;\n const dlatNm = dlatDeg * 60;\n const dlonNm = dlonDeg * 60 * Math.cos(lat * Math.PI / 180);\n const distNm = Math.sqrt(dlatNm * dlatNm + dlonNm * dlonNm);\n const derivedSpd = (distNm / dtSec) * 3600; // kt\n // Only trust the derivation over a sane wall-clock interval and a\n // physically plausible result — back-to-back packets (fast replay,\n // batched delivery) make dtSec tiny and the quotient absurd.\n if (dtSec >= 0.25 && derivedSpd >= DR_MIN_SPEED_KT && derivedSpd <= MAX_SPEED_KT) {\n const derivedHdg = (Math.atan2(dlonNm, dlatNm) * 180 / Math.PI + 360) % 360;\n const existing = velocity.current.get(id);\n if (!existing || existing.speedKt < DR_MIN_SPEED_KT) {\n velocity.current.set(id, { speedKt: derivedSpd, headingDeg: derivedHdg });\n }\n }\n }\n\n const cur = currentPositions.current.get(id);\n const lastActual = lastActualPos.current.get(id);\n\n if (!cur) {\n // First appearance — place immediately, no tween\n currentPositions.current.set(id, { lat, lon });\n lastActualPos.current.set(id, { lat, lon });\n history.current.set(id, []);\n lastUpdPerf.current.set(id, perfNow);\n const vel = velocity.current.get(id);\n if (vel && vel.speedKt >= DR_MIN_SPEED_KT) {\n drState.current.set(id, { baseLat: lat, baseLon: lon, baseTime: perfNow });\n }\n emitFrame();\n ensureRaf();\n return;\n }\n\n // Long silence (lost coverage, or the socket was offline) — restart the\n // trail and place the marker directly. Connecting across the gap draws a\n // straight line across the map, and the implausible-jump guard below would\n // otherwise discard the far-away reappearance forever.\n if (prevUpdPerf !== undefined && perfNow - prevUpdPerf > GAP_RESET_MS) {\n history.current.set(id, []);\n currentPositions.current.set(id, { lat, lon });\n lastActualPos.current.set(id, { lat, lon });\n lastUpdPerf.current.set(id, perfNow);\n interp.current.delete(id);\n drState.current.delete(id);\n emitFrame();\n ensureRaf();\n return;\n }\n\n const minD = minDeltaDegRef.current;\n const maxJ = maxJumpDegRef.current;\n\n // Compare against last ACTUAL position\n const checkLat = lastActual?.lat ?? cur.lat;\n const checkLon = lastActual?.lon ?? cur.lon;\n\n if (\n Math.abs(checkLat - lat) < minD &&\n Math.abs(checkLon - lon) < minD\n ) return; // no meaningful change\n\n // Discard implausible jumps relative to last ACTUAL position\n if (lastActual && (\n Math.abs(lat - lastActual.lat) > maxJ ||\n Math.abs(lon - lastActual.lon) > maxJ\n )) return;\n\n // Record actual position\n lastActualPos.current.set(id, { lat, lon });\n\n // Detect actual update interval to set tween duration\n const prevPerf = lastUpdPerf.current.get(id) ?? perfNow;\n const interval = Math.max(100, Math.min(perfNow - prevPerf, 5000));\n lastUpdPerf.current.set(id, perfNow);\n\n // Record current animated position into history\n const maxHP = maxHistoryPointsRef.current;\n const hist = history.current.get(id) ?? [];\n const last = hist.at(-1);\n const dupThreshold = minD * 0.5;\n if (\n !last ||\n Math.abs(last[0] - cur.lon) > dupThreshold ||\n Math.abs(last[1] - cur.lat) > dupThreshold\n ) {\n hist.push([cur.lon, cur.lat]);\n if (hist.length > maxHP) hist.shift();\n history.current.set(id, hist);\n }\n\n // Cancel any in-progress DR — tween takes over from current (possibly DR) position\n drState.current.delete(id);\n\n interp.current.set(id, {\n fromLat: cur.lat, fromLon: cur.lon,\n toLat: lat, toLon: lon,\n startedAt: perfNow,\n durationMs: Math.min(interval * 0.95, 3000),\n });\n\n emitFrame();\n ensureRaf();\n }, []); // stable — all state in refs\n\n // Imperative: remove a track from the engine\n const processRemove = useCallback((trackId: string) => {\n currentPositions.current.delete(trackId);\n lastActualPos.current.delete(trackId);\n history.current.delete(trackId);\n lastSeen.current.delete(trackId);\n lastUpdPerf.current.delete(trackId);\n interp.current.delete(trackId);\n velocity.current.delete(trackId);\n drState.current.delete(trackId);\n emitFrame();\n }, []); // stable\n\n useEffect(() => {\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n };\n }, []);\n\n const reemit = useCallback(() => {\n emitFrame();\n }, []);\n\n return { processUpdate, processRemove, reemit };\n}\n","export { useTrackMotion } from \"./use-track-motion.js\";\r\nexport type { UseTrackMotionOptions } from \"./use-track-motion.js\";\r\nexport { useTrackHistory } from \"./use-track-history.js\";\r\nexport type { UseTrackHistoryOptions, UseTrackHistoryResult, TrackHistoryFrame, TrackPosition } from \"./use-track-history.js\";\r\n\r\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\r\nimport { useQuery } from \"@tanstack/react-query\";\r\nimport { useShallow } from \"zustand/react/shallow\";\r\nimport { type LabeledTrack } from \"../../shared/label/index.js\";\r\nimport type { CurrentTrack, Bbox, AeroConnectionStatus, AeroRecoverableError } from \"../../shared/types/index.js\";\r\nimport { useAeroSwarmContext } from \"../context/index.js\";\r\nimport {\r\n useTrackStore,\r\n selectAllTracks,\r\n selectRenderableTracks,\r\n selectTrack,\r\n selectTrackCount,\r\n selectStatus,\r\n selectIsDegraded,\r\n selectLastUpdateAt,\r\n selectError,\r\n} from \"../store/index.js\";\r\n\r\nexport interface UseAeroTracksOptions {\r\n bbox?: Bbox;\r\n source?: \"fused\" | \"normalized\";\r\n minConfidence?: number;\r\n maxUpdateHz?: number;\r\n enabled?: boolean;\r\n staleAfterMs?: number;\r\n removeAfterMs?: number;\r\n}\r\n\r\nexport interface UseAeroTracksResult {\r\n\r\n tracks: (LabeledTrack & { lat: number; lon: number; })[];\r\n\r\n allTracks: LabeledTrack[];\r\n\r\n status: AeroConnectionStatus;\r\n isConnected: boolean;\r\n isLoading: boolean;\r\n isDegraded: boolean;\r\n\r\n lastUpdateAt: number | null;\r\n\r\n error: AeroRecoverableError | null;\r\n trackCount: number;\r\n\r\n /** Force an immediate reconnect of the live stream. Returns false if not subscribed. */\r\n reconnect: () => boolean;\r\n}\r\n\r\nexport function useAeroTracks(opts: UseAeroTracksOptions = {}): UseAeroTracksResult {\r\n const { client } = useAeroSwarmContext();\r\n\r\n const tracks = useTrackStore(useShallow(selectRenderableTracks));\r\n const allTracks = useTrackStore(useShallow(selectAllTracks));\r\n const status = useTrackStore(selectStatus);\r\n const isDegraded = useTrackStore(selectIsDegraded);\r\n const lastUpdateAt = useTrackStore(selectLastUpdateAt);\r\n const error = useTrackStore(selectError);\r\n const unsubRef = useRef<(() => void) | null>(null);\r\n\r\n const { isLoading } = useQuery({\r\n queryKey: [\"aero-snapshot\", opts.bbox?.join(\",\"), opts.source, opts.minConfidence],\r\n enabled: opts.enabled !== false,\r\n staleTime: 5_000,\r\n queryFn: async () => {\r\n const result = await client.tracks.snapshot({\r\n bbox: opts.bbox,\r\n minConfidence: opts.minConfidence,\r\n }) as { tracks: CurrentTrack[]; };\r\n\r\n useTrackStore.getState().applySnapshot(result.tracks ?? []);\r\n return result;\r\n },\r\n });\r\n\r\n useEffect(() => {\r\n if (opts.enabled === false) return;\r\n\r\n const actions = useTrackStore.getState();\r\n\r\n unsubRef.current = client.tracks.subscribe(\r\n {\r\n bbox: opts.bbox,\r\n source: opts.source ?? \"fused\",\r\n minConfidence: opts.minConfidence,\r\n maxUpdateHz: opts.maxUpdateHz ?? 20,\r\n mode: \"delta\",\r\n },\r\n {\r\n onStatusChange: (s) => actions.setStatus(s),\r\n onSnapshot: (t) => actions.applySnapshot(t),\r\n onTrackUpdate: (t) => actions.applyUpdate(t),\r\n onTrackRemoved: (ev) => actions.applyRemoved(ev),\r\n onSourceStatus: (ev) => actions.setSourceStatus(ev.sourceId, ev.status as never),\r\n onDegraded: (r) => actions.setDegraded(r),\r\n onError: (e) => actions.setError(e),\r\n }\r\n );\r\n\r\n return () => {\r\n unsubRef.current?.();\r\n useTrackStore.getState().setStatus(\"idle\");\r\n };\r\n\r\n }, [opts.bbox?.join(\",\"), opts.source, opts.minConfidence, opts.maxUpdateHz, opts.enabled]);\r\n\r\n const reconnect = useCallback(() => client.tracks.reconnect(), [client]);\r\n\r\n return {\r\n tracks,\r\n allTracks,\r\n status,\r\n isConnected: status === \"connected\",\r\n isLoading,\r\n isDegraded,\r\n lastUpdateAt,\r\n error,\r\n trackCount: tracks.length,\r\n reconnect,\r\n };\r\n}\r\n\r\nexport const useTracks = useAeroTracks;\r\n\r\nexport function useAeroTrack(trackId: string): { track: LabeledTrack | undefined; } {\r\n const track = useTrackStore(useMemo(() => selectTrack(trackId), [trackId]));\r\n return { track };\r\n}\r\n\r\nexport function useAeroSnapshot(opts: UseAeroTracksOptions = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-snapshot-only\", opts.bbox?.join(\",\"), opts.source],\r\n enabled: opts.enabled !== false,\r\n queryFn: () => client.tracks.snapshot({ bbox: opts.bbox, minConfidence: opts.minConfidence }),\r\n });\r\n}\r\n\r\nexport function useSourceHealth(sourceId?: string) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-source-health\", sourceId],\r\n refetchInterval: 15_000,\r\n queryFn: () => sourceId ? client.sources.health(sourceId) : client.sources.list(),\r\n });\r\n}\r\n\r\nexport function useTrackCount(): number {\r\n return useTrackStore(selectTrackCount);\r\n}\r\n\r\nexport function useAeroStatus() {\r\n return {\r\n status: useTrackStore(selectStatus),\r\n isDegraded: useTrackStore(selectIsDegraded),\r\n lastUpdateAt: useTrackStore(selectLastUpdateAt),\r\n error: useTrackStore(selectError),\r\n };\r\n}\r\n\r\nexport interface UseReplayOptions {\r\n enabled?: boolean;\r\n refetchInterval?: number;\r\n}\r\n\r\nexport function useReplayStatus(sessionId: string, opts: UseReplayOptions = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-replay-status\", sessionId],\r\n enabled: opts.enabled !== false,\r\n refetchInterval: opts.refetchInterval ?? 1000,\r\n queryFn: () => (client as any).replay?.status?.query?.({ sessionId }) ?? Promise.resolve({ status: \"idle\" }),\r\n });\r\n}\r\n\r\nexport function useReplayStop() {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-replay-stop\"],\r\n enabled: false,\r\n queryFn: () => (client as any).replay?.stop?.query?.({ sessionId: \"\" }) ?? Promise.resolve({ success: true }),\r\n });\r\n}\r\n\r\nexport function useWebhooks(opts: { enabled?: boolean; } = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-webhooks\"],\r\n enabled: opts.enabled !== false,\r\n queryFn: () => (client as any).webhooks?.list?.query?.() ?? Promise.resolve({ webhooks: [] }),\r\n });\r\n}\r\n\r\nexport function useLineage(eventId: string, opts: { enabled?: boolean; } = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-lineage\", eventId],\r\n enabled: opts.enabled !== false && !!eventId,\r\n queryFn: () => (client as any).lineage?.get?.query?.({ eventId }) ?? Promise.resolve({ lineage: null }),\r\n });\r\n}\r\n\r\nexport function useLineageTrace(trackId: string, opts: { enabled?: boolean; } = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-lineage-trace\", trackId],\r\n enabled: opts.enabled !== false && !!trackId,\r\n queryFn: () => (client as any).lineage?.trace?.query?.({ trackId }) ?? Promise.resolve({ trace: [] }),\r\n });\r\n}\r\n","import { create } from \"zustand\";\r\nimport type { CurrentTrack, AeroConnectionStatus, AeroRecoverableError, SourceStatus } from \"../../shared/types/index.js\";\r\nimport { isRenderableTrack } from \"../../shared/utils/index.js\";\r\nimport { withLabel, type LabeledTrack } from \"../../shared/label/index.js\";\r\n\r\ninterface TrackStoreState {\r\n tracks: Map<string, LabeledTrack>;\r\n status: AeroConnectionStatus;\r\n sourceStatus: Record<string, SourceStatus>;\r\n lastUpdateAt: number | null;\r\n error: AeroRecoverableError | null;\r\n isDegraded: boolean;\r\n degradedReason: string | null;\r\n\r\n applyUpdate: (track: CurrentTrack) => void;\r\n applyBatch: (updates: CurrentTrack[], removedIds: string[]) => void;\r\n applyRemoved: (ev: { trackId: string; }) => void;\r\n applySnapshot: (tracks: CurrentTrack[]) => void;\r\n setStatus: (s: AeroConnectionStatus) => void;\r\n setSourceStatus: (sourceId: string, status: SourceStatus) => void;\r\n setError: (err: AeroRecoverableError | null) => void;\r\n setDegraded: (reason: string | null) => void;\r\n /** Remove tracks not updated within maxAgeMs; returns the removed ids.\r\n * Without this, tracks whose id churned (feed restart, replay restart)\r\n * linger on the map forever as ghosts — they never get an explicit\r\n * \"removed\" event because the server no longer knows that id. */\r\n pruneStale: (maxAgeMs: number) => string[];\r\n clear: () => void;\r\n}\r\n\r\nexport const useTrackStore = create<TrackStoreState>()((set, get) => ({\r\n tracks: new Map(),\r\n status: \"idle\",\r\n sourceStatus: {},\r\n lastUpdateAt: null,\r\n error: null,\r\n isDegraded: false,\r\n degradedReason: null,\r\n\r\n applyUpdate(track) {\r\n const next = new Map(get().tracks);\r\n next.set(track.trackId, withLabel({ ...next.get(track.trackId), ...track }));\r\n set({ tracks: next, lastUpdateAt: Date.now() });\r\n },\r\n\r\n applyBatch(updates, removedIds) {\r\n if (updates.length === 0 && removedIds.length === 0) return;\r\n const next = new Map(get().tracks);\r\n for (const track of updates) {\r\n next.set(track.trackId, withLabel({ ...next.get(track.trackId), ...track }));\r\n }\r\n for (const id of removedIds) next.delete(id);\r\n set({ tracks: next, lastUpdateAt: Date.now() });\r\n },\r\n\r\n applyRemoved({ trackId }) {\r\n const next = new Map(get().tracks);\r\n next.delete(trackId);\r\n set({ tracks: next });\r\n },\r\n\r\n applySnapshot(tracks) {\r\n // A snapshot is the server's full current state for this subscription —\r\n // treat it as authoritative and drop everything else. Merging instead\r\n // leaves ghost tracks behind after a reconnect: aircraft removed while\r\n // the socket was down would stay on the map forever.\r\n const next = new Map<string, LabeledTrack>();\r\n for (const t of tracks) {\r\n if (isRenderableTrack(t)) next.set(t.trackId, withLabel(t));\r\n }\r\n set({ tracks: next, lastUpdateAt: Date.now() });\r\n },\r\n\r\n setStatus(s) {\r\n set({\r\n status: s,\r\n isDegraded: s === \"degraded\",\r\n\r\n degradedReason: s === \"connected\" ? null : get().degradedReason,\r\n });\r\n },\r\n\r\n setSourceStatus(sourceId, status) {\r\n set({ sourceStatus: { ...get().sourceStatus, [sourceId]: status } });\r\n },\r\n\r\n setError(err) { set({ error: err }); },\r\n\r\n setDegraded(reason) {\r\n set({\r\n isDegraded: reason !== null,\r\n degradedReason: reason,\r\n status: reason !== null ? \"degraded\" : get().status,\r\n });\r\n },\r\n\r\n pruneStale(maxAgeMs) {\r\n const cutoff = Date.now() - maxAgeMs;\r\n const removed: string[] = [];\r\n const next = new Map(get().tracks);\r\n for (const [id, t] of next) {\r\n if ((t.lastSeenAt ?? 0) < cutoff) {\r\n next.delete(id);\r\n removed.push(id);\r\n }\r\n }\r\n if (removed.length > 0) set({ tracks: next });\r\n return removed;\r\n },\r\n\r\n clear() { set({ tracks: new Map(), lastUpdateAt: null }); },\r\n}));\r\n\r\nexport const selectAllTracks = (s: TrackStoreState) => Array.from(s.tracks.values());\r\nexport const selectTrack = (id: string) => (s: TrackStoreState) => s.tracks.get(id);\r\nexport const selectTrackCount = (s: TrackStoreState) => s.tracks.size;\r\nexport const selectStatus = (s: TrackStoreState) => s.status;\r\nexport const selectIsDegraded = (s: TrackStoreState) => s.isDegraded;\r\nexport const selectLastUpdateAt = (s: TrackStoreState) => s.lastUpdateAt;\r\nexport const selectError = (s: TrackStoreState) => s.error;\r\n\r\nexport const selectRenderableTracks = (s: TrackStoreState) =>\r\n Array.from(s.tracks.values()).filter(isRenderableTrack) as\r\n (LabeledTrack & { lat: number; lon: number; })[];\r\n","export { isRenderableTrack, haversineKm, isInsideBbox, toCurrentTrack, clamp } from \"@aeroswarm/core/utils\";\n"],"mappings":"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","names":["raw","resolve","wsBase","useMemo","useMemo","useEffect","useRef","useCallback","useEffect","useMemo","useRef","isRenderableTrack","isRenderableTrack","useRef","useEffect","useCallback","useMemo"]}
1
+ {"version":3,"sources":["../../src/react/context/index.tsx","../../src/sdk/websocket/index.ts","../../src/sdk/websocket/replay.ts","../../src/sdk/api/trpc.ts","../../src/sdk/api/response.ts","../../src/sdk/api/rest.ts","../../src/sdk/api/graphql.ts","../../src/sdk/cache/index.ts","../../src/shared/label/index.ts","../../src/sdk/index.ts","../../src/react/hooks/use-track-trail.ts","../../src/shared/trail/index.ts","../../src/react/hooks/use-track-motion.ts","../../src/shared/motion/index.ts","../../src/react/hooks/use-track-history.ts","../../src/react/hooks/index.ts","../../src/react/store/index.ts","../../src/shared/utils/index.ts"],"sourcesContent":["import React, { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { AeroSwarm, type AeroSwarmOptions } from \"../../sdk/index.js\";\n\ninterface AeroSwarmContextValue {\n client: AeroSwarm;\n}\n\nconst AeroSwarmContext = createContext<AeroSwarmContextValue | null>(null);\n\nexport function useAeroSwarmContext(): AeroSwarmContextValue {\n const ctx = useContext(AeroSwarmContext);\n if (!ctx) throw new Error(\"useAeroSwarmContext must be used inside <AeroSwarmProvider>\");\n return ctx;\n}\n\nexport interface AeroSwarmProviderProps extends AeroSwarmOptions {\n children: ReactNode;\n queryClient?: QueryClient;\n}\n\nexport function AeroSwarmProvider({\n children,\n queryClient: externalQc,\n ...sdkOpts\n}: AeroSwarmProviderProps): React.ReactElement {\n const client = useMemo(\n () => new AeroSwarm(sdkOpts),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [sdkOpts.endpoint, sdkOpts.token],\n );\n\n const qc = useMemo(\n () => externalQc ?? new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 10_000,\n refetchInterval: 30_000,\n },\n },\n }),\n [externalQc],\n );\n\n return (\n <QueryClientProvider client={qc}>\n <AeroSwarmContext.Provider value={{ client }}>\n {children}\n </AeroSwarmContext.Provider>\n </QueryClientProvider>\n );\n}\n","\r\n\r\nimport { streamEnvelopeSchema, currentTrackSchema } from \"@aeroswarm/core/validation\";\r\nimport type { CurrentTrack, AeroConnectionStatus, AeroRecoverableError, SourceStatus } from \"@aeroswarm/core/types\";\r\n\r\nexport type WsEncoding = \"json\" | \"msgpack\";\r\n\r\nexport interface SubscribeOptions {\r\n bbox?: [number, number, number, number];\r\n /** Scope the stream to tracks stamped with this ICAO airport code, e.g. \"WARR\" */\r\n airportId?: string;\r\n source?: \"fused\" | \"normalized\";\r\n minConfidence?: number;\r\n maxTracks?: number;\r\n mode?: \"delta\" | \"full\";\r\n maxUpdateHz?: number;\r\n encoding?: WsEncoding;\r\n staleAfterMs?: number;\r\n removeAfterMs?: number;\r\n}\r\n\r\n/** Envelope fields that ride along with a track message. */\r\nexport interface StreamMeta {\r\n /** The server's clock when it sent the message (ms). Lets a client align its own clock with `lastSeenAt`. */\r\n serverTime: number;\r\n seq: number;\r\n}\r\n\r\nexport interface TrackCallbacks {\r\n onTrackUpdate?: (track: CurrentTrack, meta?: StreamMeta) => void;\r\n onTrackRemoved?: (event: { trackId: string; reason: string; }) => void;\r\n onSnapshot?: (tracks: CurrentTrack[], meta?: StreamMeta) => void;\r\n onSourceStatus?: (event: { sourceId: string; status: SourceStatus; }) => void;\r\n onStatusChange?: (status: AeroConnectionStatus) => void;\r\n onError?: (err: AeroRecoverableError) => void;\r\n onDegraded?: (reason: string) => void;\r\n}\r\n\r\nexport class AeroWsClient {\r\n private ws: WebSocket | null = null;\r\n private attempt = 0;\r\n private stopped = false;\r\n private lastSubscribe: SubscribeOptions | null = null;\r\n private callbacks: TrackCallbacks = {};\r\n private lastSeq = -1;\r\n private lastHeartbeat = Date.now();\r\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\r\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n private status: AeroConnectionStatus = \"idle\";\r\n\r\n constructor(\r\n private readonly url: string,\r\n private readonly token: string,\r\n private readonly maxReconnectDelay = 30_000,\r\n private readonly heartbeatTimeoutMs = 35_000,\r\n ) { }\r\n\r\n connect(opts: SubscribeOptions, callbacks: TrackCallbacks): void {\r\n this.lastSubscribe = opts;\r\n this.callbacks = callbacks;\r\n this.stopped = false;\r\n this._clearReconnectTimer();\r\n this._setStatus(\"connecting\");\r\n this._connect();\r\n }\r\n\r\n updateFilter(opts: Partial<Pick<SubscribeOptions, 'bbox' | 'minConfidence' | 'maxUpdateHz' | 'maxTracks'>>): void {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\r\n this.ws.send(JSON.stringify({ type: \"update-filter\", ...opts }));\r\n }\r\n\r\n disconnect(): void {\r\n this.stopped = true;\r\n this._clearHeartbeatTimer();\r\n this._clearReconnectTimer();\r\n this._dropSocket();\r\n this._setStatus(\"offline\");\r\n }\r\n\r\n /**\r\n * Force a fresh connection now, skipping any backoff wait. Re-subscribes with\r\n * the last `connect()` options (the server then sends a new snapshot).\r\n * Also resumes after `disconnect()`. No-op if `connect()` was never called.\r\n * Returns false in that no-op case.\r\n */\r\n reconnect(): boolean {\r\n if (!this.lastSubscribe) return false;\r\n this.stopped = false;\r\n this.attempt = 0;\r\n this._clearHeartbeatTimer();\r\n this._clearReconnectTimer();\r\n this._dropSocket();\r\n this._setStatus(\"reconnecting\");\r\n this._connect();\r\n return true;\r\n }\r\n\r\n getStatus(): AeroConnectionStatus { return this.status; }\r\n\r\n private _setStatus(s: AeroConnectionStatus): void {\r\n if (this.status === s) return;\r\n this.status = s;\r\n this.callbacks.onStatusChange?.(s);\r\n }\r\n\r\n /** Close the current socket without letting its onclose schedule a reconnect. */\r\n private _dropSocket(): void {\r\n const ws = this.ws;\r\n this.ws = null;\r\n if (!ws) return;\r\n ws.onopen = ws.onmessage = ws.onclose = ws.onerror = null;\r\n ws.close();\r\n }\r\n\r\n private _connect(): void {\r\n const wsUrl = `${this.url}?token=${encodeURIComponent(this.token)}`;\r\n this._dropSocket();\r\n try {\r\n this.ws = new WebSocket(wsUrl);\r\n this.ws.binaryType = \"arraybuffer\";\r\n } catch {\r\n this._scheduleReconnect();\r\n return;\r\n }\r\n\r\n this.ws.onopen = () => {\r\n this.attempt = 0;\r\n this.lastSeq = -1;\r\n this._setStatus(\"connected\");\r\n this._resetHeartbeatTimer();\r\n\r\n if (this.lastSubscribe) {\r\n this.ws!.send(JSON.stringify({\r\n type: \"subscribe\",\r\n encoding: \"json\",\r\n ...this.lastSubscribe,\r\n }));\r\n }\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n this._handleRaw(event.data as string | ArrayBuffer);\r\n };\r\n\r\n this.ws.onclose = () => {\r\n this._clearHeartbeatTimer();\r\n if (!this.stopped) {\r\n this._setStatus(\"reconnecting\");\r\n this._scheduleReconnect();\r\n }\r\n };\r\n\r\n this.ws.onerror = () => {\r\n this.callbacks.onError?.({\r\n code: \"WS_ERROR\",\r\n message: \"WebSocket connection error\",\r\n recoverable: true,\r\n });\r\n };\r\n }\r\n\r\n private _handleRaw(raw: string | ArrayBuffer): void {\r\n\r\n this._resetHeartbeatTimer();\r\n\r\n let parsed: unknown;\r\n try {\r\n const text = typeof raw === \"string\" ? raw : new TextDecoder().decode(raw);\r\n parsed = JSON.parse(text);\r\n } catch {\r\n\r\n return;\r\n }\r\n\r\n let msg: ReturnType<typeof streamEnvelopeSchema.parse>;\r\n const result = streamEnvelopeSchema.safeParse(parsed);\r\n if (!result.success) {\r\n\r\n const raw = parsed as Record<string, unknown>;\r\n if (raw[\"type\"] === \"snapshot\" && Array.isArray((raw[\"payload\"] as Record<string, unknown>)?.[\"tracks\"])) {\r\n const rawTracks = ((raw[\"payload\"] as Record<string, unknown>)[\"tracks\"]) as unknown[];\r\n const goodTracks = rawTracks\r\n .map((t) => currentTrackSchema.safeParse(t))\r\n .filter((r): r is { success: true; data: CurrentTrack } => r.success)\r\n .map((r) => r.data);\r\n if (goodTracks.length > 0) this.callbacks.onSnapshot?.(goodTracks);\r\n }\r\n return;\r\n }\r\n msg = result.data;\r\n\r\n if (msg.seq <= this.lastSeq && this.lastSeq > 0) return;\r\n\r\n if (msg.seq > this.lastSeq + 10 && this.lastSeq > 0) {\r\n this._requestSnapshot();\r\n }\r\n this.lastSeq = msg.seq;\r\n\r\n const meta: StreamMeta = { serverTime: msg.serverTime, seq: msg.seq };\r\n switch (msg.type) {\r\n case \"snapshot\":\r\n this.callbacks.onSnapshot?.(msg.payload.tracks, meta);\r\n break;\r\n case \"track.updated\":\r\n this.callbacks.onTrackUpdate?.(msg.payload.track, meta);\r\n break;\r\n case \"track.removed\":\r\n this.callbacks.onTrackRemoved?.(msg.payload);\r\n break;\r\n case \"source.status\":\r\n this.callbacks.onSourceStatus?.(msg.payload);\r\n break;\r\n case \"stream.heartbeat\":\r\n this.lastHeartbeat = Date.now();\r\n break;\r\n case \"stream.degraded\":\r\n this._setStatus(\"degraded\");\r\n this.callbacks.onDegraded?.(msg.payload.reason);\r\n break;\r\n case \"stream.error\":\r\n this.callbacks.onError?.({\r\n code: msg.payload.code,\r\n message: msg.payload.message ?? msg.payload.code,\r\n recoverable: msg.payload.recoverable,\r\n });\r\n break;\r\n }\r\n }\r\n\r\n private _requestSnapshot(): void {\r\n if (!this.lastSubscribe || !this.ws || this.ws.readyState !== WebSocket.OPEN) return;\r\n this.ws.send(JSON.stringify({ type: \"subscribe\", ...this.lastSubscribe }));\r\n }\r\n\r\n private _resetHeartbeatTimer(): void {\r\n this._clearHeartbeatTimer();\r\n this.heartbeatTimer = setInterval(() => {\r\n if (Date.now() - this.lastHeartbeat > this.heartbeatTimeoutMs) {\r\n this._setStatus(\"degraded\");\r\n this.callbacks.onDegraded?.(\"heartbeat_timeout\");\r\n }\r\n }, this.heartbeatTimeoutMs / 2);\r\n }\r\n\r\n private _clearHeartbeatTimer(): void {\r\n if (this.heartbeatTimer) {\r\n clearInterval(this.heartbeatTimer);\r\n this.heartbeatTimer = null;\r\n }\r\n }\r\n\r\n private _scheduleReconnect(): void {\r\n const delay = Math.min(1000 * 2 ** this.attempt, this.maxReconnectDelay);\r\n this.attempt++;\r\n this._clearReconnectTimer();\r\n this.reconnectTimer = setTimeout(() => {\r\n this.reconnectTimer = null;\r\n if (!this.stopped) this._connect();\r\n }, delay);\r\n }\r\n\r\n private _clearReconnectTimer(): void {\r\n if (this.reconnectTimer) {\r\n clearTimeout(this.reconnectTimer);\r\n this.reconnectTimer = null;\r\n }\r\n }\r\n}\r\n\r\nfunction isRenderableCurrent(t: CurrentTrack): boolean {\r\n return (\r\n typeof t.lat === \"number\" &&\r\n typeof t.lon === \"number\" &&\r\n Number.isFinite(t.lat) &&\r\n Number.isFinite(t.lon) &&\r\n t.lat >= -90 && t.lat <= 90 &&\r\n t.lon >= -180 && t.lon <= 180\r\n );\r\n}\r\n","import type { CurrentTrack } from \"@aeroswarm/core/types\";\n\n/**\n * Server-paced WS replay client for `/ws/tracks/replay`.\n *\n * The server streams historical `fused_tracks` events at `speed`× real\n * time (paced server-side against a wall-clock anchor), so the client\n * receives replay frames exactly like live traffic: a `snapshot`\n * envelope with the state at `from`, then `track.updated` /\n * `track.removed` envelopes, plus `replay.progress` heartbeats and a\n * final `replay.done`.\n *\n * The wire protocol is plain JSON, so consumers that don't use this SDK\n * can speak it manually:\n *\n * → connect wss://host/ws/tracks/replay?token=TOKEN[&airportId=WARR][&sessionId=ID]\n * → send { \"type\": \"replay.start\", \"from\": ISO, \"to\": ISO, \"speed\": 2 }\n * → send { \"type\": \"replay.pause\" } · { \"type\": \"replay.resume\" }\n * → send { \"type\": \"replay.seek\", \"at\": ISO }\n * → send { \"type\": \"replay.stop\" }\n * ← recv { \"type\": \"snapshot\", \"payload\": { \"tracks\": [...], \"sessionId\": ID } }\n * ← recv { \"type\": \"track.updated\", \"payload\": { \"track\": {...} } }\n * ← recv { \"type\": \"track.removed\", \"payload\": { \"trackId\": ID, \"reason\": \"dropped\" } }\n * ← recv { \"type\": \"replay.progress\", \"payload\": { \"at\": ISO, \"pct\": 0-100, \"sessionId\": ID } }\n * ← recv { \"type\": \"replay.done\", \"payload\": { \"sessionId\": ID } }\n *\n * Reconnecting with the same `sessionId` (within 1 h) resumes a replay\n * from its last saved position.\n */\n\nexport interface ReplayStreamOptions {\n from: Date | string;\n to: Date | string;\n /** Playback speed multiplier (1 = realtime). Default 1. */\n speed?: number;\n /** Which airport to replay. Federated airports (with a historyEndpoint)\n * are resolved by AeroSwarm.replay.stream to their own backend; against\n * AeroSwarm's own endpoint this is a row filter on the local DB. */\n airportId?: string;\n /** Resume a previous session instead of starting fresh. */\n sessionId?: string;\n}\n\nexport interface ReplayProgress {\n /** Replay-time position (ISO timestamp) */\n at: string;\n /** 0–100 within the requested range */\n pct: number;\n sessionId: string;\n}\n\nexport interface ReplayCallbacks {\n /** State at `from` — arrives once per start/seek. */\n onSnapshot?: (tracks: CurrentTrack[], sessionId: string) => void;\n onTrackUpdate?: (track: CurrentTrack) => void;\n onTrackRemoved?: (event: { trackId: string; reason: string }) => void;\n onProgress?: (progress: ReplayProgress) => void;\n onDone?: (sessionId: string) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface ReplayHandle {\n readonly sessionId: string | null;\n pause(): void;\n resume(): void;\n /** Jump to a replay-time position; the server restarts the paced stream there. */\n seek(at: Date | string): void;\n /** End the replay and close the socket. */\n stop(): void;\n}\n\nconst toIso = (v: Date | string): string => (v instanceof Date ? v.toISOString() : v);\n\nexport class AeroReplayClient {\n constructor(\n private readonly wsBase: string,\n private readonly token: string,\n /** Default \"/ws/tracks/replay\" (AeroSwarm's own endpoint). A federated\n * airport backend running @aeroswarm/replay-server's default mount\n * uses \"/replay\" instead — see AeroSwarm.replay.stream's endpoint\n * resolution. */\n private readonly wsPath: string = \"/ws/tracks/replay\",\n ) {}\n\n /** Open a replay stream. Returns a handle for transport control. */\n stream(opts: ReplayStreamOptions, callbacks: ReplayCallbacks): ReplayHandle {\n const params = new URLSearchParams({ token: this.token });\n if (opts.airportId) params.set(\"airportId\", opts.airportId);\n if (opts.sessionId) params.set(\"sessionId\", opts.sessionId);\n\n const ws = new WebSocket(`${this.wsBase}${this.wsPath}?${params.toString()}`);\n let sessionId: string | null = opts.sessionId ?? null;\n let closedByUser = false;\n\n ws.onopen = () => {\n // A fresh start message is only needed when not resuming a session.\n if (!opts.sessionId) {\n ws.send(JSON.stringify({\n type: \"replay.start\",\n from: toIso(opts.from),\n to: toIso(opts.to),\n speed: opts.speed ?? 1,\n }));\n }\n };\n\n ws.onmessage = (ev) => {\n let msg: { type?: string; payload?: Record<string, unknown> };\n try { msg = JSON.parse(String(ev.data)); } catch { return; }\n const payload = msg.payload ?? {};\n\n switch (msg.type) {\n case \"snapshot\": {\n if (typeof payload[\"sessionId\"] === \"string\") sessionId = payload[\"sessionId\"];\n callbacks.onSnapshot?.((payload[\"tracks\"] as CurrentTrack[]) ?? [], sessionId ?? \"\");\n break;\n }\n case \"track.updated\":\n callbacks.onTrackUpdate?.(payload[\"track\"] as CurrentTrack);\n break;\n case \"track.removed\":\n callbacks.onTrackRemoved?.({\n trackId: String(payload[\"trackId\"] ?? \"\"),\n reason: String(payload[\"reason\"] ?? \"\"),\n });\n break;\n case \"replay.progress\": {\n const progress = payload as unknown as ReplayProgress;\n sessionId = progress.sessionId ?? sessionId;\n callbacks.onProgress?.(progress);\n break;\n }\n case \"replay.done\":\n callbacks.onDone?.(String(payload[\"sessionId\"] ?? sessionId ?? \"\"));\n break;\n }\n };\n\n ws.onerror = () => {\n if (!closedByUser) callbacks.onError?.(new Error(\"replay websocket error\"));\n };\n ws.onclose = (ev) => {\n if (!closedByUser && ev.code >= 4000) {\n callbacks.onError?.(new Error(`replay websocket closed: ${ev.reason || ev.code}`));\n }\n };\n\n const send = (body: Record<string, unknown>) => {\n if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(body));\n };\n\n return {\n get sessionId() { return sessionId; },\n pause: () => send({ type: \"replay.pause\" }),\n resume: () => send({ type: \"replay.resume\" }),\n seek: (at) => send({ type: \"replay.seek\", at: toIso(at) }),\n stop: () => {\n closedByUser = true;\n send({ type: \"replay.stop\" });\n try { ws.close(); } catch { /* already closed */ }\n },\n };\n }\n}\n","import { createTRPCProxyClient, httpBatchLink } from \"@trpc/client\";\n\nexport function createAeroTrpcClient(endpoint: string, token: string) {\n const base = endpoint.replace(/\\/$/, \"\");\n return createTRPCProxyClient<any>({\n links: [\n httpBatchLink({\n url: `${base}/trpc`,\n headers: () => ({ \"x-aero-token\": token }),\n methodOverride: \"POST\",\n }),\n ],\n });\n}\n\nexport type AeroTrpcClient = ReturnType<typeof createAeroTrpcClient>;\n","// Reading REST responses. Servers answer in one of two shapes:\n//\n// standard { ok: true, data, meta? } / { ok: false, error: { code, message, recoverable, details? } }\n// original bare data (e.g. an array) / { ok: false, error: \"text\" } (+ optional \"detail\")\n//\n// Both are understood, and every failure becomes an `AeroApiError` that keeps what the server\n// said (code, message, details) instead of just \"GET /x -> 500\".\n//\n// This file does not import @aeroswarm/core/api on purpose: that subpath only exists in newer\n// versions of core, and a client released before the app upgrades core would break. The shared\n// names are mirrored here and a test checks they have not drifted.\n\n/** Mirrors `ENVELOPE_HEADER` in @aeroswarm/core/api. */\nexport const ENVELOPE_HEADER = \"x-aero-envelope\";\n\n/** Mirrors `API_ERROR_CODES` in @aeroswarm/core/api. A server may send codes this client does not know. */\nexport type ApiErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"BAD_REQUEST\"\n | \"CONFLICT\"\n | \"RATE_LIMITED\"\n | \"UNAVAILABLE\"\n | \"INTERNAL\";\n\nconst STATUS_CODES: ReadonlyArray<[number, ApiErrorCode]> = [\n [401, \"UNAUTHORIZED\"],\n [403, \"FORBIDDEN\"],\n [404, \"NOT_FOUND\"],\n [400, \"BAD_REQUEST\"],\n [409, \"CONFLICT\"],\n [429, \"RATE_LIMITED\"],\n [503, \"UNAVAILABLE\"],\n [500, \"INTERNAL\"],\n];\n\n/** The code for an HTTP status when the server sent none. */\nexport function codeForStatus(status: number): ApiErrorCode {\n const hit = STATUS_CODES.find(([s]) => s === status);\n if (hit) return hit[1];\n return status >= 500 ? \"INTERNAL\" : \"BAD_REQUEST\";\n}\n\nexport interface ApiErrorInit {\n status: number;\n code: string;\n message: string;\n recoverable: boolean;\n details?: unknown;\n}\n\n/** A REST call failed. `code` is stable and meant for `switch`; `message` is for people. */\nexport class AeroApiError extends Error {\n readonly status: number;\n readonly code: string;\n /** Retrying later may succeed (rate limit, server busy or failing). */\n readonly recoverable: boolean;\n readonly details?: unknown;\n\n constructor(init: ApiErrorInit) {\n super(init.message);\n this.name = \"AeroApiError\";\n this.status = init.status;\n this.code = init.code;\n this.recoverable = init.recoverable;\n this.details = init.details;\n }\n}\n\ntype Json = Record<string, unknown>;\nconst isObject = (v: unknown): v is Json => typeof v === \"object\" && v !== null && !Array.isArray(v);\n\n/** `{ ok: true, data }`, with or without `meta`. */\nfunction isEnvelope(body: unknown): body is { ok: true; data: unknown } {\n return isObject(body) && body.ok === true && \"data\" in body;\n}\n\nfunction isFailure(body: unknown): body is { ok: false; error?: unknown; detail?: unknown } {\n return isObject(body) && body.ok === false;\n}\n\nfunction errorFrom(res: Response, body: unknown, text: string, label: string | undefined): AeroApiError {\n const prefix = label ? `${label} → ${res.status}` : `HTTP ${res.status}`;\n const failure = isFailure(body) ? body : undefined;\n const e = failure?.error;\n\n if (isObject(e)) {\n const code = typeof e.code === \"string\" ? e.code : codeForStatus(res.status);\n const message = typeof e.message === \"string\" ? e.message : res.statusText || prefix;\n return new AeroApiError({\n status: res.status,\n code,\n message: `${prefix}: ${message}`,\n recoverable: typeof e.recoverable === \"boolean\" ? e.recoverable : res.status >= 500,\n details: e.details,\n });\n }\n\n const message = typeof e === \"string\" ? e : text.slice(0, 200) || res.statusText;\n return new AeroApiError({\n status: res.status,\n code: codeForStatus(res.status),\n message: message ? `${prefix}: ${message}` : prefix,\n recoverable: res.status >= 500 || res.status === 429,\n details: failure?.detail,\n });\n}\n\n/**\n * The parsed JSON of a response, untouched. Throws `AeroApiError` for a non-2xx status\n * and for a 2xx that carries `{ ok: false }`. For endpoints whose callers read the\n * envelope themselves (`r.data.x`).\n */\nexport async function readApiBody(res: Response, label?: string): Promise<unknown> {\n const text = await res.text();\n let body: unknown;\n try {\n body = text ? JSON.parse(text) : undefined;\n } catch {\n body = undefined; // not JSON: an HTML error page from a proxy, say\n }\n if (!res.ok || isFailure(body)) throw errorFrom(res, body, text, label);\n return body;\n}\n\n/** What a standard response says about itself (see `meta` in @aeroswarm/core/api). Absent for servers that answer with bare data. */\nexport interface ApiMetaLike {\n serverTime?: number;\n count?: number;\n nextCursor?: string;\n truncated?: boolean;\n}\n\n/**\n * The data of a response together with its `meta`: unwrapped from `{ ok, data, meta }`, or the body\n * as it is (and no meta) for servers that answer with bare data. Throws `AeroApiError` on failure.\n */\nexport async function parseApiEnvelope<T>(res: Response, label?: string): Promise<{ data: T; meta?: ApiMetaLike }> {\n const body = await readApiBody(res, label);\n if (!isEnvelope(body)) return { data: body as T };\n const meta = (body as Json).meta;\n return { data: body.data as T, ...(isObject(meta) && { meta: meta as ApiMetaLike }) };\n}\n\n/**\n * The data of a response: unwrapped from `{ ok, data }`, or the body as it is for servers\n * that answer with bare data. Throws `AeroApiError` on failure.\n */\nexport async function parseApiResponse<T>(res: Response, label?: string): Promise<T> {\n return (await parseApiEnvelope<T>(res, label)).data;\n}\n","import { readApiBody } from \"./response.js\";\r\n\r\nexport interface SourceRow {\r\n id: string;\r\n name: string;\r\n type: string;\r\n sourceToken: string;\r\n createdAt: string;\r\n /** Airport this source's data belongs to. When set, anything that\r\n * authenticates with this source's token (WS live/replay, tRPC calls)\r\n * is scoped to this airport automatically. */\r\n airportId?: string | null;\r\n}\r\n\r\nimport type { FlightPlan } from \"../../shared/types/index.js\";\r\n\r\n/** A flight plan plus the live track it currently correlates with (null = not on radar). */\r\nexport type FlightPlanRow = FlightPlan & { trackId: string | null };\r\n\r\n/** One raw AFTN message from the live log (newest first, capped by the service). */\r\nexport interface AftnMessageRow {\r\n id: string;\r\n receivedAt: number;\r\n airportId: string | null;\r\n feedId: string | null;\r\n callsign: string | null;\r\n type: string;\r\n adep?: string;\r\n ades?: string;\r\n raw: string;\r\n}\r\n\r\nexport class AeroRestClient {\r\n constructor(\r\n private readonly endpoint: string,\r\n private readonly token: string,\r\n ) { }\r\n\r\n private async req<T>(method: string, path: string, body?: unknown): Promise<T> {\r\n const res = await fetch(`${this.endpoint}${path}`, {\r\n method,\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n \"x-aero-token\": this.token,\r\n },\r\n body: body ? JSON.stringify(body) : undefined,\r\n });\r\n return (await readApiBody(res, `${method} ${path}`)) as T;\r\n }\r\n\r\n createSource(body: Record<string, unknown>) {\r\n return this.req<{ ok: boolean; data: { sourceId: string; sourceToken: string; airportId?: string | null; } }>(\"POST\", \"/v1/sources\", body);\r\n }\r\n\r\n listSources() {\r\n return this.req<{ ok: boolean; data: { sources: SourceRow[]; } }>(\"GET\", \"/v1/sources\");\r\n }\r\n\r\n deleteSource(id: string) {\r\n return this.req<{ ok: boolean; }>(\"DELETE\", `/v1/sources/${id}`);\r\n }\r\n\r\n createWebhook(body: { url: string; events: string[]; secret?: string; }) {\r\n return this.req<{ webhookId: string; }>(\"POST\", \"/v1/webhooks\", body);\r\n }\r\n\r\n getTracksCurrent(params?: { bbox?: [number, number, number, number]; minConfidence?: number; }) {\r\n const search = new URLSearchParams();\r\n if (params?.bbox) search.set(\"bbox\", params.bbox.join(\",\"));\r\n if (params?.minConfidence != null) search.set(\"minConfidence\", String(params.minConfidence));\r\n const qs = search.size ? `?${search.toString()}` : \"\";\r\n return this.req<{ tracks: unknown[]; count: number; }>(\"GET\", `/v1/tracks/current${qs}`);\r\n }\r\n\r\n /** Flight plans received from AFTN feeds (FPL/CHG/DLA/DEP/ARR/CNL folded per flight). */\r\n listFlightPlans(params?: { airportId?: string; status?: string; callsign?: string; limit?: number; }) {\r\n const search = new URLSearchParams();\r\n if (params?.airportId) search.set(\"airportId\", params.airportId);\r\n if (params?.status) search.set(\"status\", params.status);\r\n if (params?.callsign) search.set(\"callsign\", params.callsign);\r\n if (params?.limit != null) search.set(\"limit\", String(params.limit));\r\n const qs = search.size ? `?${search.toString()}` : \"\";\r\n return this.req<{ ok: boolean; data: { flightPlans: FlightPlanRow[]; count: number; } }>(\"GET\", `/v1/flight-plans${qs}`);\r\n }\r\n\r\n getFlightPlan(id: string) {\r\n return this.req<{ ok: boolean; data: { flightPlan: FlightPlan; } }>(\"GET\", `/v1/flight-plans/${encodeURIComponent(id)}`);\r\n }\r\n\r\n /** Raw AFTN messages (every parsed ATS message, newest first — the input\r\n * side of the flight-plan pipeline). */\r\n listAftnMessages(params?: { airportId?: string; limit?: number; }) {\r\n const search = new URLSearchParams();\r\n if (params?.airportId) search.set(\"airportId\", params.airportId);\r\n if (params?.limit != null) search.set(\"limit\", String(params.limit));\r\n const qs = search.size ? `?${search.toString()}` : \"\";\r\n return this.req<{ ok: boolean; data: { messages: AftnMessageRow[]; count: number; } }>(\"GET\", `/v1/aftn/messages${qs}`);\r\n }\r\n\r\n health() {\r\n return this.req<{ status: string; }>(\"GET\", \"/health\");\r\n }\r\n}\r\n","export class AeroGraphQLClient {\n constructor(\n private readonly endpoint: string,\n private readonly token: string,\n ) { }\n\n async query<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T> {\n const res = await fetch(`${this.endpoint}/graphql`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-aero-token\": this.token,\n },\n body: JSON.stringify({ query, variables }),\n });\n if (!res.ok) throw new Error(`GraphQL ${res.status}: ${await res.text().catch(() => \"\")}`);\n const json = await res.json() as { data?: T; errors?: unknown[]; };\n if (json.errors?.length) throw new Error(JSON.stringify(json.errors));\n return json.data as T;\n }\n}\n","\r\n\r\nimport type { CurrentTrack, AeroConnectionStatus, AeroRecoverableError, SourceStatus } from \"@aeroswarm/core/types\";\r\nimport { isRenderableTrack } from \"@aeroswarm/core/utils\";\r\nimport { withLabel, type LabeledTrack } from \"../../shared/label/index.js\";\r\n\r\ntype Listener<T> = (payload: T) => void;\r\n\r\nexport class TypedEmitter<T extends Record<string, unknown>> {\r\n private listeners = new Map<keyof T, Set<Listener<unknown>>>();\r\n\r\n on<K extends keyof T>(event: K, fn: Listener<T[K]>): () => void {\r\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\r\n this.listeners.get(event)!.add(fn as Listener<unknown>);\r\n return () => this.listeners.get(event)?.delete(fn as Listener<unknown>);\r\n }\r\n\r\n emit<K extends keyof T>(event: K, payload: T[K]): void {\r\n this.listeners.get(event)?.forEach((fn) => fn(payload));\r\n }\r\n}\r\n\r\nexport class TrackCache extends TypedEmitter<{\r\n update: LabeledTrack;\r\n removed: { trackId: string; };\r\n snapshot: LabeledTrack[];\r\n status: AeroConnectionStatus;\r\n degraded: { reason: string; };\r\n error: AeroRecoverableError;\r\n \"source.status\": { sourceId: string; status: SourceStatus; };\r\n}> {\r\n private tracks = new Map<string, LabeledTrack>();\r\n\r\n /** Merges into the cached track and returns the labeled result. A \"lost\"\r\n * track is evicted from the cache but still returned (labelCategory LOST). */\r\n applyUpdate(track: CurrentTrack): LabeledTrack {\r\n const merged = withLabel({ ...this.tracks.get(track.trackId), ...track });\r\n if (track.status === \"lost\") {\r\n this.tracks.delete(track.trackId);\r\n this.emit(\"removed\", { trackId: track.trackId });\r\n return merged;\r\n }\r\n this.tracks.set(track.trackId, merged);\r\n this.emit(\"update\", merged);\r\n return merged;\r\n }\r\n\r\n applyRemoved(ev: { trackId: string; }): void {\r\n this.tracks.delete(ev.trackId);\r\n this.emit(\"removed\", ev);\r\n }\r\n\r\n applySnapshot(tracks: CurrentTrack[]): LabeledTrack[] {\r\n this.tracks.clear();\r\n for (const t of tracks) {\r\n if (isRenderableTrack(t)) this.tracks.set(t.trackId, withLabel(t));\r\n }\r\n const all = Array.from(this.tracks.values());\r\n this.emit(\"snapshot\", all);\r\n return all;\r\n }\r\n\r\n getAll(): LabeledTrack[] { return Array.from(this.tracks.values()); }\r\n get(id: string): LabeledTrack | undefined { return this.tracks.get(id); }\r\n get size(): number { return this.tracks.size; }\r\n clear(): void { this.tracks.clear(); }\r\n\r\n getRenderable(): (LabeledTrack & { lat: number; lon: number; })[] {\r\n return this.getAll().filter(isRenderableTrack) as (LabeledTrack & { lat: number; lon: number; })[];\r\n }\r\n}\r\n","import type { CurrentTrack } from \"../types/index.js\";\n\n/**\n * Callsign-label category of a track, in priority order:\n * LOST (track status \"lost\") > GROUND_VEHICLE > ARRIVAL / DEPARTURE > UNKNOWN.\n * ARRIVAL/DEPARTURE come from the correlated flight plan, relative to the\n * track's airport (`track.flightPlan.phase`).\n */\nexport type LabelCategory = \"ARRIVAL\" | \"DEPARTURE\" | \"GROUND_VEHICLE\" | \"LOST\" | \"UNKNOWN\";\n\nexport const LABEL_COLORS: Readonly<Record<LabelCategory, string>> = {\n ARRIVAL: \"#FACC15\", // yellow\n DEPARTURE: \"#22C55E\", // green\n GROUND_VEHICLE: \"#3B82F6\", // blue\n LOST: \"#FFFFFF\", // white\n UNKNOWN: \"#FFFFFF\", // white — no flight plan\n};\n\n/** A track as the client exposes it: the server's track plus derived label fields. */\nexport type LabeledTrack = CurrentTrack & {\n labelCategory: LabelCategory;\n labelColor: string;\n};\n\nexport function getLabelCategory(\n track: Pick<CurrentTrack, \"status\" | \"targetType\" | \"flightPlan\">,\n): LabelCategory {\n if (track.status === \"lost\") return \"LOST\";\n if (track.targetType === \"vehicle\") return \"GROUND_VEHICLE\";\n switch (track.flightPlan?.phase) {\n case \"ARRIVAL\": return \"ARRIVAL\";\n case \"DEPARTURE\": return \"DEPARTURE\";\n default: return \"UNKNOWN\";\n }\n}\n\n/**\n * Attach `labelCategory` / `labelColor`. Always call this on the *merged*\n * track: a delta without `flightPlan` must not downgrade a known category\n * (that is what makes labels flicker).\n */\nexport function withLabel(track: CurrentTrack): LabeledTrack {\n const labelCategory = getLabelCategory(track);\n return { ...track, labelCategory, labelColor: LABEL_COLORS[labelCategory] };\n}\n","import { AeroWsClient, type StreamMeta, type SubscribeOptions, type TrackCallbacks } from \"./websocket/index.js\";\r\nimport { AeroReplayClient, type ReplayStreamOptions, type ReplayCallbacks, type ReplayHandle } from \"./websocket/replay.js\";\r\nimport { createAeroTrpcClient, type AeroTrpcClient } from \"./api/trpc.js\";\r\nimport { AeroRestClient } from \"./api/rest.js\";\r\nimport { AeroApiError, ENVELOPE_HEADER, parseApiEnvelope, type ApiMetaLike } from \"./api/response.js\";\r\nimport type { TrailFeatureCollection, TrailRequest, TrailResult } from \"../shared/trail/index.js\";\r\nimport { AeroGraphQLClient } from \"./api/graphql.js\";\r\nimport { TrackCache } from \"./cache/index.js\";\r\nimport type { LabeledTrack } from \"../shared/label/index.js\";\r\nimport type { Bbox, AeroConnectionStatus, AeroRecoverableError, CurrentTrack } from \"../shared/types/index.js\";\r\n\r\nexport { TrackCache } from \"./cache/index.js\";\r\nexport { AeroWsClient } from \"./websocket/index.js\";\r\nexport { AeroReplayClient } from \"./websocket/replay.js\";\r\nexport type { SubscribeOptions, TrackCallbacks, StreamMeta } from \"./websocket/index.js\";\r\nexport type { ReplayStreamOptions, ReplayCallbacks, ReplayHandle, ReplayProgress } from \"./websocket/replay.js\";\r\nexport type { SourceRow, FlightPlanRow, AftnMessageRow } from \"./api/rest.js\";\r\nexport { AeroApiError, parseApiResponse, parseApiEnvelope, readApiBody, codeForStatus } from \"./api/response.js\";\r\nexport type { ApiErrorCode, ApiErrorInit, ApiMetaLike } from \"./api/response.js\";\r\n\r\nexport interface AeroSwarmOptions {\r\n endpoint: string;\r\n token: string;\r\n wsEndpoint?: string;\r\n reconnect?: boolean;\r\n maxReconnectDelay?: number;\r\n encoding?: \"json\" | \"msgpack\";\r\n staleAfterMs?: number;\r\n removeAfterMs?: number;\r\n onTokenExpired?: () => Promise<string>;\r\n}\r\n\r\n/** Live callbacks: tracks arrive merged with previous state and carry\r\n * `status`, `labelCategory` and `labelColor`. */\r\nexport interface LiveTrackCallbacks extends Omit<TrackCallbacks, \"onTrackUpdate\" | \"onSnapshot\"> {\r\n /** `meta.serverTime` is the server's clock; pass it to `createTrackMotion().update()`. */\r\n onTrackUpdate?: (track: LabeledTrack, meta?: StreamMeta) => void;\r\n onSnapshot?: (tracks: LabeledTrack[], meta?: StreamMeta) => void;\r\n}\r\n\r\nexport interface UseAeroTracksResult {\r\n tracks: CurrentTrack[];\r\n status: AeroConnectionStatus;\r\n sourceStatus: Record<string, string>;\r\n lastUpdateAt: number | null;\r\n error: AeroRecoverableError | null;\r\n}\r\n\r\nexport interface TrackHistoryPoint {\r\n t: number;\r\n lat: number | null;\r\n lon: number | null;\r\n altFt: number | null;\r\n speedKt: number | null;\r\n headingDeg: number | null;\r\n}\r\n\r\nexport interface DayAvailability {\r\n day: string; // \"YYYY-MM-DD\"\r\n eventCount: number;\r\n trackCount: number;\r\n firstAt: string | null; // first/last event that day (ISO)\r\n lastAt: string | null;\r\n}\r\n\r\nexport class AeroSwarm {\r\n readonly trpc: AeroTrpcClient;\r\n readonly rest: AeroRestClient;\r\n readonly graphql: AeroGraphQLClient;\r\n readonly cache: TrackCache;\r\n\r\n private readonly wsClient: AeroWsClient;\r\n private _status: AeroConnectionStatus = \"idle\";\r\n private _lastUpdateAt: number | null = null;\r\n private _error: AeroRecoverableError | null = null;\r\n private _sourceStatus: Record<string, string> = {};\r\n\r\n // ── Federated history resolution ──────────────────────────────────────\r\n // An airport can serve its own replay/history through a backend mounting\r\n // @aeroswarm/replay-server instead of AeroSwarm's own (see\r\n // docs/development/federation/). `airports.list()`'s `historyEndpoint`\r\n // field drives this automatically; every federatable call also accepts\r\n // an explicit `endpoint` to bypass the registry entirely (\"connect\r\n // manual\"). Priority: explicit endpoint > registry > AeroSwarm itself.\r\n private airportsCache: { byId: Map<string, string | null>; fetchedAt: number; } | null = null;\r\n private static readonly AIRPORTS_CACHE_TTL_MS = 60_000;\r\n\r\n /** Resolves which base URL (if any) should serve a federated call.\r\n * Returns null to mean \"use AeroSwarm's own endpoint\" — the normal,\r\n * unfederated path, which is also what a lookup failure falls back to\r\n * (a broken airports.list() call must never brick replay/history for\r\n * everyone else). */\r\n private async resolveHistoryEndpoint(airportId?: string, override?: string): Promise<string | null> {\r\n if (override) return override.replace(/\\/$/, \"\");\r\n if (!airportId) return null;\r\n try {\r\n const now = Date.now();\r\n if (!this.airportsCache || now - this.airportsCache.fetchedAt > AeroSwarm.AIRPORTS_CACHE_TTL_MS) {\r\n const { airports } = await this.airports.list();\r\n this.airportsCache = {\r\n byId: new Map(airports.map((a) => [a.id, a.historyEndpoint])),\r\n fetchedAt: now,\r\n };\r\n }\r\n const endpoint = this.airportsCache.byId.get(airportId) ?? null;\r\n return endpoint ? endpoint.replace(/\\/$/, \"\") : null;\r\n } catch {\r\n return null; // fail open to AeroSwarm's own endpoint\r\n }\r\n }\r\n\r\n private async federatedRequest<T>(\r\n base: string,\r\n path: string,\r\n query?: Record<string, string | undefined>,\r\n ): Promise<{ data: T; meta?: ApiMetaLike }> {\r\n const url = new URL(`${base}${path}`);\r\n for (const [k, v] of Object.entries(query ?? {})) if (v !== undefined) url.searchParams.set(k, v);\r\n // Ask for the standard envelope. Servers that predate it ignore the header and answer\r\n // with bare data, which parseApiEnvelope also accepts.\r\n const res = await fetch(url, {\r\n headers: { Authorization: `Bearer ${this.opts.token}`, [ENVELOPE_HEADER]: \"1\" },\r\n });\r\n return parseApiEnvelope<T>(res, `Federated request ${url.pathname}`);\r\n }\r\n\r\n private async federatedFetch<T>(base: string, path: string, query?: Record<string, string | undefined>): Promise<T> {\r\n return (await this.federatedRequest<T>(base, path, query)).data;\r\n }\r\n\r\n constructor(private readonly opts: AeroSwarmOptions) {\r\n this.trpc = createAeroTrpcClient(opts.endpoint, opts.token);\r\n this.rest = new AeroRestClient(opts.endpoint, opts.token);\r\n this.graphql = new AeroGraphQLClient(opts.endpoint, opts.token);\r\n this.cache = new TrackCache();\r\n\r\n const wsBase = (opts.wsEndpoint ?? opts.endpoint).replace(/^http/, \"ws\");\r\n this.wsClient = new AeroWsClient(\r\n `${wsBase}/ws/tracks/live`,\r\n opts.token,\r\n opts.maxReconnectDelay ?? 30_000,\r\n );\r\n }\r\n\r\n readonly tracks = {\r\n subscribe: (opts: SubscribeOptions, callbacks: LiveTrackCallbacks): (() => void) => {\r\n this.wsClient.connect(\r\n { encoding: this.opts.encoding ?? \"json\", ...opts },\r\n {\r\n ...callbacks,\r\n onStatusChange: (s) => {\r\n this._status = s;\r\n callbacks.onStatusChange?.(s);\r\n },\r\n onSnapshot: (tracks, meta) => {\r\n const labeled = this.cache.applySnapshot(tracks);\r\n this._lastUpdateAt = Date.now();\r\n callbacks.onSnapshot?.(labeled, meta);\r\n },\r\n onTrackUpdate: (track, meta) => {\r\n const labeled = this.cache.applyUpdate(track);\r\n this._lastUpdateAt = Date.now();\r\n callbacks.onTrackUpdate?.(labeled, meta);\r\n },\r\n onTrackRemoved: (ev) => {\r\n this.cache.applyRemoved(ev);\r\n callbacks.onTrackRemoved?.(ev);\r\n },\r\n onSourceStatus: (ev) => {\r\n this._sourceStatus[ev.sourceId] = ev.status;\r\n callbacks.onSourceStatus?.(ev);\r\n },\r\n onError: (err) => {\r\n this._error = err;\r\n callbacks.onError?.(err);\r\n },\r\n }\r\n );\r\n return () => this.wsClient.disconnect();\r\n },\r\n\r\n updateFilter: (opts: Partial<SubscribeOptions>) => this.wsClient.updateFilter(opts),\r\n\r\n /** Drop the live socket and reconnect immediately (no backoff wait),\r\n * re-subscribing with the last options. Returns false if nothing has\r\n * been subscribed yet. */\r\n reconnect: (): boolean => this.wsClient.reconnect(),\r\n\r\n stream: (opts: SubscribeOptions): AsyncIterable<LabeledTrack> => {\r\n const queue: LabeledTrack[] = [];\r\n let resolve: ((v: IteratorResult<LabeledTrack>) => void) | null = null;\r\n let done = false;\r\n\r\n this.wsClient.connect({ encoding: \"json\", ...opts }, {\r\n onSnapshot: (tracks) => { this.cache.applySnapshot(tracks); },\r\n onTrackUpdate: (raw) => {\r\n const t = this.cache.applyUpdate(raw);\r\n if (resolve) { resolve({ value: t, done: false }); resolve = null; }\r\n else queue.push(t);\r\n },\r\n onStatusChange: (s) => { if (s === \"offline\") done = true; },\r\n });\r\n\r\n return {\r\n [Symbol.asyncIterator]: () => ({\r\n next: (): Promise<IteratorResult<LabeledTrack>> => {\r\n if (queue.length) return Promise.resolve({ value: queue.shift()!, done: false });\r\n if (done) return Promise.resolve({ value: undefined as never, done: true });\r\n return new Promise((r) => { resolve = r; });\r\n },\r\n return: async () => { this.wsClient.disconnect(); return { value: undefined as never, done: true }; },\r\n }),\r\n };\r\n },\r\n\r\n snapshot: (input?: { airportId?: string; bbox?: Bbox; source?: \"fused\" | \"normalized\"; minConfidence?: number; limit?: number; offset?: number; }) =>\r\n (this.trpc as any).tracks.snapshot.query(input ?? {}),\r\n\r\n /** Persisted position history for one track. If the airport has a\r\n * `historyEndpoint` (or `endpoint` is passed explicitly), this calls\r\n * that backend's `@aeroswarm/replay-server` instead of AeroSwarm's\r\n * own local database — see docs/development/federation/003-*.md. */\r\n history: async (input: { trackId: string; airportId?: string; limit?: number; endpoint?: string; }) => {\r\n const base = await this.resolveHistoryEndpoint(input.airportId, input.endpoint);\r\n if (base) {\r\n return this.federatedFetch<TrackHistoryPoint[]>(base, `/tracks/${encodeURIComponent(input.trackId)}/history`, {\r\n limit: input.limit?.toString(),\r\n });\r\n }\r\n return (this.trpc as any).tracks.history.query(input) as Promise<TrackHistoryPoint[]>;\r\n },\r\n\r\n /**\r\n * The path an aircraft has flown, ready to draw: a GeoJSON line plus a point for every time\r\n * mark, with the label already written in `tz`. By default the whole flight so far; `fromMs`\r\n * and `toMs` choose a period, `sinceMs` fetches only what is new (see `createTrailPoller`),\r\n * `stepM` asks for a coarser line. The airport's backend builds it (memory first, history\r\n * as a fallback), so nothing is computed here. There is no AeroSwarm-side equivalent:\r\n * pass `endpoint` or an `airportId` with a registered `historyEndpoint`.\r\n */\r\n trail: async (input: TrailRequest): Promise<TrailResult> => {\r\n const base = await this.resolveHistoryEndpoint(input.airportId, input.endpoint);\r\n if (!base) {\r\n throw new AeroApiError({\r\n status: 400,\r\n code: \"BAD_REQUEST\",\r\n recoverable: false,\r\n message: \"tracks.trail needs the airport's backend: pass `endpoint`, or an `airportId` that has a historyEndpoint\",\r\n });\r\n }\r\n const num = (v: number | undefined): string | undefined => (v === undefined ? undefined : String(v));\r\n const { data, meta } = await this.federatedRequest<TrailFeatureCollection>(\r\n base,\r\n `/tracks/${encodeURIComponent(input.trackId)}/trail`,\r\n {\r\n fromMs: num(input.fromMs),\r\n toMs: num(input.toMs),\r\n sinceMs: num(input.sinceMs),\r\n stepM: num(input.stepM),\r\n limit: num(input.limit),\r\n labelEveryMs: num(input.labelEveryMs),\r\n tz: input.tz,\r\n labelFormat: input.labelFormat,\r\n format: \"geojson\",\r\n },\r\n );\r\n return { trail: data, ...(meta && { meta }) };\r\n },\r\n };\r\n\r\n /** Flight plans received from AFTN feeds. Live tracks that correlate with a\r\n * plan (Mode-S address, else callsign) also carry `track.flightPlan`. */\r\n readonly flightPlans = {\r\n list: (input?: { airportId?: string; status?: string; callsign?: string; limit?: number; }) =>\r\n this.rest.listFlightPlans(input).then((r) => r.data.flightPlans),\r\n get: (id: string) => this.rest.getFlightPlan(id).then((r) => r.data.flightPlan),\r\n };\r\n\r\n /** Raw AFTN/ATS messages received by AFTN feeds — the input side of the\r\n * flight-plan pipeline (newest first, capped by the service). */\r\n readonly aftn = {\r\n messages: (input?: { airportId?: string; limit?: number; }) =>\r\n this.rest.listAftnMessages(input).then((r) => r.data.messages),\r\n };\r\n\r\n readonly sources = {\r\n /** Registers a source and returns its token, tied to `airportId` — a\r\n * source is a sensor physically at one airport, so this is required.\r\n * Anything that later authenticates with the returned token (WS\r\n * live/replay, tRPC calls) is scoped to that airport automatically,\r\n * with no need to keep passing `airportId` on every subsequent call.\r\n * For access spanning multiple airports, use the viewer/dashboard\r\n * token instead — it's never auto-scoped. */\r\n create: (body: { name: string; airportId: string; sourceType?: string; location?: { lat: number; lon: number; }; config?: Record<string, unknown>; }) =>\r\n this.rest.createSource(body),\r\n health: (sourceId: string) => (this.trpc as any).sources.health.query({ sourceId }),\r\n list: (input?: { airportId?: string; }) => (this.trpc as any).sources.list.query(input ?? {}),\r\n /** Reassigning `airportId` (including to `null` to clear it) re-caches\r\n * the source's token immediately, so the new scoping takes effect\r\n * without waiting out the token cache's TTL. */\r\n update: (input: { id: string; name?: string; airportId?: string | null; location?: { lat: number; lon: number; }; config?: Record<string, unknown>; }) =>\r\n (this.trpc as any).sources.update.mutate(input),\r\n delete: (id: string) => (this.trpc as any).sources.delete.mutate({ id }),\r\n };\r\n\r\n /** Airport registry (multi-airport tenancy). */\r\n readonly airports = {\r\n list: () =>\r\n (this.trpc as any).airports.list.query() as Promise<{\r\n airports: {\r\n id: string; name: string; city: string | null;\r\n lat: number; lon: number;\r\n bbox: [number, number, number, number]; timezone: string;\r\n /** Set = this airport serves its own replay/history (federated —\r\n * see docs/development/federation/003-*.md); `replay.*`/\r\n * `tracks.history` calls for it connect there directly instead of\r\n * AeroSwarm's own endpoint. null = AeroSwarm serves it (default). */\r\n historyEndpoint: string | null;\r\n }[];\r\n }>,\r\n create: (input: {\r\n id: string; name: string; city?: string;\r\n lat: number; lon: number;\r\n bbox: [number, number, number, number]; timezone?: string;\r\n historyEndpoint?: string;\r\n }) => (this.trpc as any).airports.create.mutate(input) as Promise<{ ok: boolean; id: string; }>,\r\n update: (input: {\r\n id: string; name?: string; city?: string;\r\n lat?: number; lon?: number;\r\n bbox?: [number, number, number, number]; timezone?: string;\r\n /** null clears it (reverts to AeroSwarm's own replay); omit to leave unchanged. */\r\n historyEndpoint?: string | null;\r\n }) => (this.trpc as any).airports.update.mutate(input) as Promise<{ ok: boolean; }>,\r\n delete: (input: { id: string; }) =>\r\n (this.trpc as any).airports.delete.mutate(input) as Promise<{ ok: boolean; }>,\r\n /** Surface geometry (runway/taxiway/apron GeoJSON) for the map. */\r\n surface: (input: { id: string; }) =>\r\n (this.trpc as any).airports.surface.query(input) as Promise<{ surface: unknown | null; }>,\r\n setSurface: (input: { id: string; surface: Record<string, unknown>; }) =>\r\n (this.trpc as any).airports.setSurface.mutate(input) as Promise<{ ok: boolean; }>,\r\n };\r\n\r\n readonly replay = {\r\n /** Data-availability calendar: which days have recorded tracks.\r\n * Use this to populate/validate replay date pickers — days not in\r\n * the list have no data. Federates to the airport's own backend when\r\n * it has a `historyEndpoint` (or `endpoint` is passed explicitly) —\r\n * see docs/development/federation/003-*.md. */\r\n days: async (input?: { airportId?: string; lookbackDays?: number; endpoint?: string; }) => {\r\n const base = await this.resolveHistoryEndpoint(input?.airportId, input?.endpoint);\r\n if (base) {\r\n return this.federatedFetch<{ days: DayAvailability[]; }>(base, \"/replay/days\", {\r\n lookbackDays: input?.lookbackDays?.toString(),\r\n });\r\n }\r\n return (this.trpc as any).replay.days.query(input ?? {}) as Promise<{ days: DayAvailability[]; }>;\r\n },\r\n range: async (input: { from: string; to: string; airportId?: string; endpoint?: string; }) => {\r\n const base = await this.resolveHistoryEndpoint(input.airportId, input.endpoint);\r\n if (base) {\r\n const summary = await this.federatedFetch<{ earliest: string | null; latest: string | null; eventCount: number; trackCount: number; }>(\r\n base, \"/replay/range\", { from: input.from, to: input.to },\r\n );\r\n return { from: input.from, to: input.to, ...summary };\r\n }\r\n return (this.trpc as any).replay.range.query(input) as Promise<{\r\n from: string; to: string;\r\n earliest: string | null; latest: string | null;\r\n eventCount: number; trackCount: number;\r\n }>;\r\n },\r\n // replay.window / replay.seek (bulk sample download for client-side\r\n // interpolation) were removed — they were never part of the federation\r\n // contract, and `replay.stream`'s server-paced WS (with server-side\r\n // seek/re-snapshot) covers every use they had at a fraction of the\r\n // payload. See docs/development/federation/005-*.md.\r\n /** Server-paced WS playback: streams historical tracks at `speed`× in\r\n * the same envelope format as the live stream. Returns a transport\r\n * handle (pause/resume/seek/stop) — a Promise, since resolving which\r\n * backend serves this airport (see docs/development/federation/003-*.md)\r\n * is async. See `AeroReplayClient` for the raw wire protocol if you\r\n * want to speak WS manually instead. */\r\n stream: async (opts: ReplayStreamOptions & { endpoint?: string; }, callbacks: ReplayCallbacks): Promise<ReplayHandle> => {\r\n const federatedBase = await this.resolveHistoryEndpoint(opts.airportId, opts.endpoint);\r\n if (federatedBase) {\r\n const wsBase = federatedBase.replace(/^http/, \"ws\");\r\n // A federated backend is single-airport — no airportId param needed\r\n // (and its own auth is source-token-based, not airport-scoped).\r\n const { airportId: _drop, ...rest } = opts;\r\n return new AeroReplayClient(wsBase, this.opts.token, \"/replay\").stream(rest, callbacks);\r\n }\r\n const wsBase = (this.opts.wsEndpoint ?? this.opts.endpoint).replace(/^http/, \"ws\");\r\n return new AeroReplayClient(wsBase, this.opts.token).stream(opts, callbacks);\r\n },\r\n };\r\n\r\n /** Movement analytics: takeoffs/landings per hour + peak hour, recorded\r\n * by the pipeline's movement detector. */\r\n readonly analytics = {\r\n movements: (input?: { airportId?: string; day?: string; }) =>\r\n (this.trpc as any).analytics.movements.query(input ?? {}) as Promise<{\r\n day: string;\r\n hours: { hour: string; takeoffs: number; landings: number; }[];\r\n totals: { takeoffs: number; landings: number; };\r\n peak: { hour: string; count: number; } | null;\r\n }>,\r\n };\r\n\r\n /** Route conformance: pre-defined ground routes + live track clearances.\r\n * Assign a route to a track and the safety engine can detect when it\r\n * strays beyond the route's corridor. */\r\n readonly routes = {\r\n list: (input?: { airportId?: string; }) =>\r\n (this.trpc as any).routes.list.query(input ?? {}) as Promise<{\r\n routes: {\r\n routeId: string; airportId: string; name: string; kind: string;\r\n points: [number, number][]; maxDeviationM: number; enabled: boolean;\r\n }[];\r\n }>,\r\n create: (input: { airportId: string; name: string; kind?: \"taxi\" | \"departure\" | \"arrival\"; points: [number, number][]; maxDeviationM?: number; }) =>\r\n (this.trpc as any).routes.create.mutate(input) as Promise<{ id: string; }>,\r\n delete: (input: { id: string; }) =>\r\n (this.trpc as any).routes.delete.mutate(input) as Promise<{ ok: boolean; }>,\r\n assign: (input: { trackId: string; routeId: string; }) =>\r\n (this.trpc as any).routes.assign.mutate(input) as Promise<{ ok: boolean; }>,\r\n unassign: (input: { trackId: string; }) =>\r\n (this.trpc as any).routes.unassign.mutate(input) as Promise<{ ok: boolean; }>,\r\n assignments: () =>\r\n (this.trpc as any).routes.assignments.query() as Promise<{\r\n assignments: { trackId: string; routeId: string; assignedAt: number; }[];\r\n }>,\r\n };\r\n\r\n readonly webhooks = {\r\n list: () => (this.trpc as any).webhooks.list.query(),\r\n create: (body: { url: string; events: string[]; secret?: string; }) =>\r\n (this.trpc as any).webhooks.create.mutate(body) as Promise<{ ok: boolean; webhookId: string; }>,\r\n delete: (input: { id: string; }) => (this.trpc as any).webhooks.delete.mutate(input),\r\n };\r\n\r\n readonly system = {\r\n stats: () => (this.trpc as any).system.stats.query(),\r\n infraHealth: () => (this.trpc as any).system.infraHealth.query(),\r\n ingestHistory: () => (this.trpc as any).system.ingestHistory.query(),\r\n getConfig: () => (this.trpc as any).system.getConfig.query() as Promise<{ key: string; value: string; updatedAt: string; }[]>,\r\n setConfig: (input: { key: string; value: string; }) =>\r\n (this.trpc as any).system.setConfig.mutate(input) as Promise<{ ok: boolean; }>,\r\n clearTable: (input: {\r\n table: \"fused_tracks\" | \"source_health\";\r\n mode: \"all\" | \"range\";\r\n from?: string;\r\n to?: string;\r\n }) => (this.trpc as any).system.clearTable.mutate(input),\r\n };\r\n\r\n getStatus(): AeroConnectionStatus { return this._status; }\r\n getLastUpdateAt(): number | null { return this._lastUpdateAt; }\r\n getError(): AeroRecoverableError | null { return this._error; }\r\n getSourceStatus(): Record<string, string> { return { ...this._sourceStatus }; }\r\n\r\n disconnect(): void { this.wsClient.disconnect(); }\r\n reconnect(): boolean { return this.wsClient.reconnect(); }\r\n}\r\n","import { useEffect, useRef, useState } from \"react\";\nimport { createTrailPoller, type TrailFeatureCollection, type TrailRequest } from \"../../shared/trail/index.js\";\nimport { useAeroSwarmContext } from \"../context/index.js\";\n\nexport interface UseTrackTrailOptions extends Omit<TrailRequest, \"trackId\" | \"sinceMs\"> {\n /** How often to ask for what is new. Default 5000. A fixed period (`toMs`) is fetched once. */\n pollMs?: number;\n /** false = do nothing (e.g. while no aircraft is selected). Default true. */\n enabled?: boolean;\n}\n\nexport interface UseTrackTrailResult {\n /** The line and its time marks, ready to draw; undefined until the first answer. */\n trail: TrailFeatureCollection | undefined;\n /** First request in flight. */\n loading: boolean;\n /** The last failure, if any. A trail already shown stays. */\n error: unknown;\n}\n\n/**\n * The trail of one aircraft (the one that is selected), kept current: fetched in full once, then\n * only what is new every `pollMs`. Nothing is computed here: the backend draws the line and writes\n * the time labels. Pass `null` while nothing is selected.\n */\nexport function useTrackTrail(\n trackId: string | null | undefined,\n options: UseTrackTrailOptions = {},\n): UseTrackTrailResult {\n const { client } = useAeroSwarmContext();\n const [trail, setTrail] = useState<TrailFeatureCollection | undefined>(undefined);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<unknown>(undefined);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const enabled = options.enabled !== false && !!trackId;\n // The request changes when any of these does; the rest is read from the latest options.\n const key = JSON.stringify([\n trackId, options.endpoint, options.airportId, options.fromMs, options.toMs,\n options.stepM, options.limit, options.labelEveryMs, options.tz, options.labelFormat, options.pollMs,\n ]);\n\n useEffect(() => {\n setTrail(undefined);\n setError(undefined);\n if (!enabled || !trackId) {\n setLoading(false);\n return;\n }\n setLoading(true);\n const { enabled: _enabled, pollMs, ...request } = optionsRef.current;\n const poller = createTrailPoller(\n (sinceMs) => client.tracks.trail({ ...request, trackId, sinceMs }),\n {\n // A fixed period in the past does not grow: ask once.\n pollMs: request.toMs !== undefined ? 0 : pollMs,\n onUpdate: (t) => {\n setTrail(t);\n setLoading(false);\n setError(undefined);\n },\n onError: (e) => {\n setError(e);\n setLoading(false);\n },\n },\n );\n return () => poller.stop();\n }, [client, enabled, key]); // eslint-disable-line react-hooks/exhaustive-deps\n\n return { trail, loading, error };\n}\n","// The path an aircraft has flown, as the server draws it: a GeoJSON line plus a point for\n// every time mark, with the label already written in the time zone you asked for\n// (`@aeroswarm/live-server` formatTrail). The client only fetches, keeps it current and draws.\n//\n// const trail = client.tracks.trail({ trackId, endpoint, tz: \"Asia/Jakarta\" }); // once\n// const poller = createTrailPoller((since) => client.tracks.trail({ trackId, endpoint, sinceMs: since }),\n// { onUpdate: draw }); // keeps it current\n// poller.stop(); // when deselected\n//\n// After the first (full) request only what is NEW is fetched (`sinceMs`), so polling every few\n// seconds is a few hundred bytes.\n\nexport interface TrailLine {\n type: \"Feature\";\n properties: {\n kind: \"trail\";\n trackId: string;\n /** epoch ms of the first and the last point */\n from: number;\n to: number;\n /** spacing of the points, metres */\n stepM: number;\n /** the line starts at the beginning of the flight (nothing older exists) */\n fromStart: boolean;\n };\n /** [lon, lat, altitude in metres] */\n geometry: { type: \"LineString\"; coordinates: Array<[number, number, number]> };\n}\n\nexport interface TrailMark {\n type: \"Feature\";\n /** `label` is ready to display, e.g. \"01-11-26 11:16\" */\n properties: { kind: \"mark\"; t: number; label: string };\n geometry: { type: \"Point\"; coordinates: [number, number] };\n}\n\nexport interface TrailFeatureCollection {\n type: \"FeatureCollection\";\n features: Array<TrailLine | TrailMark>;\n}\n\nexport interface TrailMeta {\n serverTime?: number;\n count?: number;\n /** the server dropped points to honour `limit` */\n truncated?: boolean;\n}\n\nexport interface TrailResult {\n trail: TrailFeatureCollection;\n meta?: TrailMeta;\n}\n\nexport interface TrailRequest {\n trackId: string;\n /** The airport backend's base URL (its `/aero` mount). Without it `airportId` must have a registered historyEndpoint. */\n endpoint?: string;\n airportId?: string;\n /** A period instead of the whole flight (epoch ms). */\n fromMs?: number;\n toMs?: number;\n /** Only what is newer than this: for keeping a trail current. */\n sinceMs?: number;\n /** Coarser spacing than the server stores, metres. */\n stepM?: number;\n /** At most this many points. */\n limit?: number;\n /** A time mark every this many ms (default 120 000), aligned to the clock. 0 = none. */\n labelEveryMs?: number;\n /** Time zone of the labels, e.g. \"Asia/Jakarta\" (default UTC). */\n tz?: string;\n labelFormat?: \"datetime\" | \"time\";\n}\n\nconst lineOf = (fc: TrailFeatureCollection): TrailLine | undefined =>\n fc.features.find((f): f is TrailLine => f.properties.kind === \"trail\");\n\nconst marksOf = (fc: TrailFeatureCollection): TrailMark[] =>\n fc.features.filter((f): f is TrailMark => f.properties.kind === \"mark\");\n\n/** Time of the last point of a trail; undefined when it has none. What to pass as `sinceMs`. */\nexport function trailEnd(trail: TrailFeatureCollection): number | undefined {\n const line = lineOf(trail);\n return line && line.geometry.coordinates.length > 0 ? line.properties.to : undefined;\n}\n\n/**\n * Appends a delta (a request with `sinceMs`) to the trail you already have. Returns `prev`\n * itself when the delta adds nothing, so a caller can tell nothing changed.\n */\nexport function mergeTrail(prev: TrailFeatureCollection | undefined, delta: TrailFeatureCollection): TrailFeatureCollection {\n if (!prev) return delta;\n const before = lineOf(prev);\n const added = lineOf(delta);\n if (!before || !added) return prev;\n if (added.geometry.coordinates.length === 0) return prev;\n\n const seen = new Set(marksOf(prev).map((m) => m.properties.t));\n const newMarks = marksOf(delta).filter((m) => !seen.has(m.properties.t));\n\n const line: TrailLine = {\n ...before,\n properties: { ...before.properties, to: added.properties.to, stepM: Math.max(before.properties.stepM, added.properties.stepM) },\n geometry: { type: \"LineString\", coordinates: [...before.geometry.coordinates, ...added.geometry.coordinates] },\n };\n return { type: \"FeatureCollection\", features: [line, ...marksOf(prev), ...newMarks] };\n}\n\nexport interface TrailPollerOptions {\n /** How often to ask for what is new. Default 5000. 0 or less: fetch once and stop. */\n pollMs?: number;\n /** Called with the whole trail, first when it arrives, then each time it grows. */\n onUpdate: (trail: TrailFeatureCollection) => void;\n onError?: (err: unknown) => void;\n /** Stop for good when the server says the track is unknown (it left and its retention ended). Default true. */\n stopWhenGone?: boolean;\n}\n\nexport interface TrailPoller {\n stop(): void;\n /** Ask now instead of waiting for the next tick. */\n refresh(): Promise<void>;\n /** The trail as it stands. */\n current(): TrailFeatureCollection | undefined;\n}\n\nconst isGone = (err: unknown): boolean =>\n typeof err === \"object\" && err !== null && (err as { code?: unknown }).code === \"NOT_FOUND\";\n\n/**\n * Fetches a trail once and then keeps it current by asking only for what is new.\n * Framework-free: `useTrackTrail` is a thin wrapper around it.\n */\nexport function createTrailPoller(\n fetchTrail: (sinceMs?: number) => Promise<TrailResult>,\n options: TrailPollerOptions,\n): TrailPoller {\n const pollMs = options.pollMs ?? 5_000;\n const stopWhenGone = options.stopWhenGone ?? true;\n let current: TrailFeatureCollection | undefined;\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let running: Promise<void> | undefined;\n\n const schedule = (): void => {\n if (stopped || pollMs <= 0) return;\n timer = setTimeout(() => void tick(), pollMs);\n };\n\n async function once(): Promise<void> {\n try {\n const since = current ? trailEnd(current) : undefined;\n const { trail } = await fetchTrail(since);\n if (stopped) return;\n const next = mergeTrail(current, trail);\n if (next !== current) {\n current = next;\n options.onUpdate(next);\n }\n } catch (err) {\n if (stopped) return;\n options.onError?.(err);\n if (stopWhenGone && isGone(err)) stopped = true;\n }\n }\n\n function tick(): Promise<void> {\n if (stopped) return Promise.resolve();\n // One request at a time: a slow answer must not be overtaken by the next tick.\n running ??= once().finally(() => {\n running = undefined;\n schedule();\n });\n return running;\n }\n\n void tick();\n\n return {\n stop(): void {\n stopped = true;\n if (timer) clearTimeout(timer);\n },\n async refresh(): Promise<void> {\n if (timer) clearTimeout(timer);\n await (running ?? tick());\n },\n current: () => current,\n };\n}\n","import { useEffect, useMemo, useRef } from \"react\";\nimport {\n createTrackMotion,\n type MotionFrame,\n type MotionOptions,\n type TrackMotion,\n} from \"../../shared/motion/index.js\";\n\nexport interface UseTrackMotionOptions extends MotionOptions {\n /** Called ~30 times a second while something moves, with only the tracks that changed. */\n onFrame: (frame: MotionFrame) => void;\n}\n\n/**\n * Smooth motion for live tracks. Feed it from the live subscription and draw\n * from `onFrame`; nothing here causes a React render.\n *\n * const motion = useTrackMotion({ onFrame: ({ changed }) => layer.update(changed) });\n * client.tracks.subscribe(opts, { onTrackUpdate: (t, meta) => motion.update(t, meta) });\n *\n * The engine is created once: options are read on the first render only.\n */\nexport function useTrackMotion(\n options: UseTrackMotionOptions,\n): Pick<TrackMotion, \"update\" | \"remove\" | \"clear\" | \"sample\"> {\n const { onFrame, ...motionOptions } = options;\n const onFrameRef = useRef(onFrame);\n onFrameRef.current = onFrame;\n\n const motion = useMemo(() => createTrackMotion(motionOptions), []);\n\n useEffect(() => {\n const stop = motion.start((frame) => onFrameRef.current(frame));\n return () => {\n stop();\n motion.clear();\n };\n }, [motion]);\n\n return motion;\n}\n","// Smooth motion for live tracks, independent of any UI framework.\n//\n// A feed gives a position every second (MLAT, ADS-B) or every antenna sweep\n// (radar, ~7.5 s). Drawn as they arrive, icons jump. This engine shows where the\n// aircraft PROBABLY is right now and eases corrections in, the way flight\n// trackers do:\n//\n// motion.update(track, { serverTime }); // every WS update\n// const stop = motion.start(({ changed }) => { // ~30 frames/s, only tracks that moved\n// for (const [id, s] of changed) layer.put(id, s.lat, s.lon, s.headingDeg);\n// });\n//\n// How the picture stays smooth:\n// * dead reckoning: from the last fix, speed and heading carry the position forward;\n// * a new fix does NOT snap the icon. The gap between where it was shown and where the\n// new fix says it should be becomes an offset that decays (smoothstep) to zero. The\n// icon keeps moving at its own speed meanwhile, so there is no stop-and-go at\n// each update (a plain linear blend between two positions slows the icon to a halt\n// at the start of every blend);\n// * heading eases along the shortest arc, so icons never spin the long way round;\n// * time is the SERVER's: lastSeenAt is the server's clock, so the engine learns the\n// offset to the browser clock from the serverTime of each message;\n// * extrapolation stops at a limit, and freezes once the track is \"lost\".\nimport type { CurrentTrack } from \"../types/index.js\";\n\n/** What the engine reads from a track. */\nexport type MotionInput = Pick<\n CurrentTrack,\n \"trackId\" | \"lat\" | \"lon\" | \"groundSpeedKt\" | \"headingDeg\" | \"altitudeFt\" | \"status\" | \"lastSeenAt\"\n> & { targetType?: CurrentTrack[\"targetType\"] };\n\nexport interface MotionSample {\n lat: number;\n lon: number;\n /** Degrees 0..360, eased; null until a valid heading has been seen. */\n headingDeg: number | null;\n altitudeFt: number | null;\n /** Speed is above `minMovingKt`. */\n moving: boolean;\n /** Position currently comes from dead reckoning (not frozen, limit not reached). */\n extrapolating: boolean;\n}\n\nexport interface MotionFrame {\n /** Server-aligned time of this frame (ms). */\n at: number;\n /** Only tracks whose picture changed since the previous frame. */\n changed: ReadonlyMap<string, MotionSample>;\n}\n\nexport interface MotionScheduler {\n request(callback: (timestamp: number) => void): unknown;\n cancel(handle: unknown): void;\n}\n\nexport interface MotionOptions {\n /** How long a correction takes to fade out. Default 1500 ms. */\n blendMs?: number;\n /** How long the icon takes to turn to a new heading. Default 800 ms. */\n headingSmoothMs?: number;\n /** Dead reckoning stops after this long without a new fix. Default 20 s in the air, 8 s on the surface. */\n maxExtrapolateMs?: { air: number; surface: number };\n /** Below this speed a track is treated as standing still. Default 5 kt. */\n minMovingKt?: number;\n /** A correction larger than this is a glitch or a sensor change far away: jump instead of easing. Default 5000 m. */\n snapDistanceM?: number;\n /** Frame rate limit for `start()`. Default 30. */\n frameHz?: number;\n /** Changes smaller than this are not reported. Default 0.5 m (and 0.5 degrees of heading). */\n epsilonM?: number;\n /** false = show raw positions (for comparing against another source). Default true. */\n extrapolate?: boolean;\n /** Decides what counts as \"on the surface\". Default: vehicles, or below 150 ft. */\n isSurface?: (track: MotionInput) => boolean;\n /** Browser clock. Default Date.now. Inject the replay clock to play back at another speed. */\n clock?: () => number;\n /** Frame timer. Default requestAnimationFrame, else setTimeout. Inject in tests. */\n scheduler?: MotionScheduler;\n}\n\nexport interface TrackMotion {\n /** `meta.serverTime` (from the WS envelope) keeps the server/browser clock offset current. */\n update(track: MotionInput, meta?: { serverTime?: number }): void;\n remove(trackId: string): void;\n clear(): void;\n /** Where the track is shown at `at` (browser time; default now). Pure: does not touch frame state. */\n sample(trackId: string, at?: number): MotionSample | undefined;\n /** One frame: the tracks whose picture changed since the previous frame. `start()` calls this for you. */\n tick(at?: number): ReadonlyMap<string, MotionSample>;\n /** Runs frames until the returned function is called. Idles (no timer) while nothing moves. */\n start(onFrame: (frame: MotionFrame) => void): () => void;\n /** Tracks currently held. */\n readonly size: number;\n /** Current server minus browser clock, ms. */\n offsetMs(): number;\n}\n\nconst KT_TO_MS = 0.514444;\nconst M_PER_DEG = 111_320;\nconst MAX_PLAUSIBLE_KT = 1_500;\nconst OFFSET_WINDOW = 20;\nconst ALT_EPSILON_FT = 10;\nconst HEADING_EPSILON_DEG = 0.5;\nconst SURFACE_ALT_FT = 150;\n\nconst clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\nconst smoothstep = (x: number): number => {\n const c = clamp01(x);\n return c * c * (3 - 2 * c);\n};\n/** -180..180 */\nconst wrapDeg = (d: number): number => ((((d + 180) % 360) + 360) % 360) - 180;\nconst norm360 = (d: number): number => ((d % 360) + 360) % 360;\nconst cosLat = (lat: number): number => Math.max(0.01, Math.cos((lat * Math.PI) / 180));\n\nfunction validHeading(h: number | null): number | null {\n return h != null && Number.isFinite(h) && h >= 0 && h <= 360 ? h % 360 : null;\n}\n\nfunction move(lat: number, lon: number, bearingDeg: number, distM: number): { lat: number; lon: number } {\n const rad = (bearingDeg * Math.PI) / 180;\n const dLat = (distM * Math.cos(rad)) / M_PER_DEG;\n const dLon = (distM * Math.sin(rad)) / (M_PER_DEG * cosLat(lat + dLat / 2));\n return { lat: lat + dLat, lon: lon + dLon };\n}\n\n/** Flat-earth distance: exact enough over the few kilometres involved here. */\nfunction distanceM(aLat: number, aLon: number, bLat: number, bLon: number): number {\n const dy = (bLat - aLat) * M_PER_DEG;\n const dx = (bLon - aLon) * M_PER_DEG * cosLat((aLat + bLat) / 2);\n return Math.hypot(dx, dy);\n}\n\nconst headingChanged = (a: number | null, b: number | null): boolean =>\n a === null || b === null ? a !== b : Math.abs(wrapDeg(a - b)) > HEADING_EPSILON_DEG;\n\nconst altitudeChanged = (a: number | null, b: number | null): boolean =>\n a === null || b === null ? a !== b : Math.abs(a - b) >= ALT_EPSILON_FT;\n\ninterface Entry {\n id: string;\n /** Last accepted position and the server time it belongs to. */\n fixLat: number;\n fixLon: number;\n fixAt: number;\n speedMs: number;\n course: number | null;\n surface: boolean;\n /** Elapsed ms at which a lost track stopped moving; null while it is alive. */\n frozen: number | null;\n /** Offset (degrees) between the shown and the predicted position at `blendStart`; fades to 0. */\n errLat: number;\n errLon: number;\n blendStart: number;\n hdgFrom: number | null;\n hdgTo: number | null;\n hdgStart: number;\n altFrom: number | null;\n altTo: number | null;\n altStart: number;\n /** From this server time on, `sample()` no longer changes. */\n settleAt: number;\n emitted: MotionSample | null;\n}\n\ntype Resolved = Required<Omit<MotionOptions, \"scheduler\" | \"isSurface\" | \"clock\">> &\n Pick<MotionOptions, \"scheduler\"> & {\n isSurface: (t: MotionInput) => boolean;\n clock: () => number;\n };\n\nconst defaultIsSurface = (t: MotionInput): boolean =>\n t.targetType === \"vehicle\" || (t.altitudeFt != null && t.altitudeFt <= SURFACE_ALT_FT);\n\nfunction resolve(o: MotionOptions): Resolved {\n return {\n blendMs: o.blendMs ?? 1_500,\n headingSmoothMs: o.headingSmoothMs ?? 800,\n maxExtrapolateMs: o.maxExtrapolateMs ?? { air: 20_000, surface: 8_000 },\n minMovingKt: o.minMovingKt ?? 5,\n snapDistanceM: o.snapDistanceM ?? 5_000,\n frameHz: o.frameHz ?? 30,\n epsilonM: o.epsilonM ?? 0.5,\n extrapolate: o.extrapolate ?? true,\n isSurface: o.isSurface ?? defaultIsSurface,\n clock: o.clock ?? Date.now,\n scheduler: o.scheduler,\n };\n}\n\nfunction defaultScheduler(): MotionScheduler {\n const g = globalThis as {\n requestAnimationFrame?: (cb: (t: number) => void) => number;\n cancelAnimationFrame?: (h: number) => void;\n };\n if (typeof g.requestAnimationFrame === \"function\" && typeof g.cancelAnimationFrame === \"function\") {\n return {\n request: (cb) => g.requestAnimationFrame!(cb),\n cancel: (h) => g.cancelAnimationFrame!(h as number),\n };\n }\n return {\n request: (cb) => setTimeout(() => cb(Date.now()), 16),\n cancel: (h) => clearTimeout(h as ReturnType<typeof setTimeout>),\n };\n}\n\nclass Motion implements TrackMotion {\n private readonly o: Resolved;\n private readonly entries = new Map<string, Entry>();\n /** Tracks whose picture can still change. Everything else costs nothing per frame. */\n private readonly active = new Set<string>();\n private readonly listeners = new Set<(f: MotionFrame) => void>();\n private offsets: number[] = [];\n private offset = 0;\n private handle: unknown = null;\n private lastFrameAt = Number.NEGATIVE_INFINITY;\n\n constructor(options: MotionOptions) {\n this.o = resolve(options);\n }\n\n get size(): number {\n return this.entries.size;\n }\n\n offsetMs(): number {\n return this.offset;\n }\n\n // ── time ────────────────────────────────────────────────────────────────\n\n /** Browser clock + learned offset = the server's clock. */\n private serverNow(at?: number): number {\n return (at ?? this.o.clock()) + this.offset;\n }\n\n /**\n * `serverTime - browserNow` is the true offset MINUS the network delay, so the\n * largest recent value is the one with the least delay, i.e. the best estimate.\n */\n private observe(serverTime: number): void {\n if (!Number.isFinite(serverTime)) return;\n this.offsets.push(serverTime - this.o.clock());\n if (this.offsets.length > OFFSET_WINDOW) this.offsets.shift();\n this.offset = Math.max(...this.offsets);\n }\n\n private cap(e: Entry): number {\n const m = this.o.maxExtrapolateMs;\n return e.surface ? m.surface : m.air;\n }\n\n // ── model ───────────────────────────────────────────────────────────────\n\n private posAt(e: Entry, s: number): { lat: number; lon: number; extrapolating: boolean } {\n const cap = this.cap(e);\n let elapsed = Math.min(Math.max(0, s - e.fixAt), cap);\n if (e.frozen !== null) elapsed = Math.min(elapsed, e.frozen);\n if (!this.o.extrapolate || e.course === null || e.speedMs <= 0) {\n return { lat: e.fixLat, lon: e.fixLon, extrapolating: false };\n }\n const p = move(e.fixLat, e.fixLon, e.course, (e.speedMs * elapsed) / 1000);\n return { ...p, extrapolating: e.frozen === null && elapsed < cap };\n }\n\n private sampleAt(e: Entry, s: number): MotionSample {\n const p = this.posAt(e, s);\n const rest = 1 - smoothstep((s - e.blendStart) / this.o.blendMs);\n\n let headingDeg: number | null = null;\n if (e.hdgTo !== null) {\n const from = e.hdgFrom ?? e.hdgTo;\n const left = 1 - smoothstep((s - e.hdgStart) / this.o.headingSmoothMs);\n headingDeg = norm360(e.hdgTo + wrapDeg(from - e.hdgTo) * left);\n }\n\n let altitudeFt: number | null = null;\n if (e.altTo !== null) {\n const k = smoothstep((s - e.altStart) / this.o.blendMs);\n altitudeFt = e.altFrom === null ? e.altTo : e.altFrom + (e.altTo - e.altFrom) * k;\n }\n\n return {\n lat: p.lat + e.errLat * rest,\n lon: p.lon + e.errLon * rest,\n headingDeg,\n altitudeFt,\n moving: e.speedMs > 0,\n extrapolating: p.extrapolating,\n };\n }\n\n // ── input ───────────────────────────────────────────────────────────────\n\n update(t: MotionInput, meta?: { serverTime?: number }): void {\n if (meta?.serverTime !== undefined) this.observe(meta.serverTime);\n if (t.lat == null || t.lon == null || !Number.isFinite(t.lat) || !Number.isFinite(t.lon)) {\n this.remove(t.trackId);\n return;\n }\n const s = this.serverNow();\n const e = this.entries.get(t.trackId);\n if (!e) {\n this.create(t, s);\n return;\n }\n this.refresh(e, t, s);\n }\n\n private moving(t: MotionInput, course: number | null): boolean {\n const kt = t.groundSpeedKt;\n return course !== null && kt != null && Number.isFinite(kt) && kt >= this.o.minMovingKt && kt <= MAX_PLAUSIBLE_KT;\n }\n\n private create(t: MotionInput, s: number): void {\n const course = validHeading(t.headingDeg);\n const moving = this.moving(t, course);\n const e: Entry = {\n id: t.trackId,\n fixLat: t.lat as number,\n fixLon: t.lon as number,\n fixAt: t.lastSeenAt,\n speedMs: moving ? (t.groundSpeedKt as number) * KT_TO_MS : 0,\n course,\n surface: this.o.isSurface(t),\n frozen: null,\n errLat: 0,\n errLon: 0,\n blendStart: s,\n hdgFrom: course,\n hdgTo: course,\n hdgStart: s,\n altFrom: null,\n altTo: t.altitudeFt,\n altStart: s,\n settleAt: 0,\n emitted: null,\n };\n if (t.status === \"lost\") e.frozen = this.frozenAt(e, s);\n this.entries.set(e.id, e);\n this.settle(e);\n this.active.add(e.id);\n this.ensureLoop();\n }\n\n private frozenAt(e: Entry, s: number): number {\n return Math.min(Math.max(0, s - e.fixAt), this.cap(e));\n }\n\n private refresh(e: Entry, t: MotionInput, s: number): void {\n e.surface = this.o.isSurface(t);\n if (t.status === \"lost\") e.frozen ??= this.frozenAt(e, s);\n else e.frozen = null;\n\n const lat = t.lat as number;\n const lon = t.lon as number;\n const older = t.lastSeenAt < e.fixAt;\n const same = t.lastSeenAt === e.fixAt && lat === e.fixLat && lon === e.fixLon;\n if (older || same) {\n // A repeat or a late arrival: nothing to ease, and restarting the blend would jerk the icon.\n this.settle(e);\n this.touch(e);\n return;\n }\n\n const shown = this.sampleAt(e, s);\n const course = validHeading(t.headingDeg) ?? e.course;\n const moving = this.moving(t, course);\n e.fixLat = lat;\n e.fixLon = lon;\n e.fixAt = t.lastSeenAt;\n e.speedMs = moving ? (t.groundSpeedKt as number) * KT_TO_MS : 0;\n e.course = course;\n\n const predicted = this.posAt(e, s);\n const jump = distanceM(shown.lat, shown.lon, predicted.lat, predicted.lon);\n const snap = jump > this.o.snapDistanceM;\n e.errLat = snap ? 0 : shown.lat - predicted.lat;\n e.errLon = snap ? 0 : shown.lon - predicted.lon;\n e.blendStart = s;\n\n e.hdgFrom = snap ? course : (shown.headingDeg ?? course);\n e.hdgTo = course;\n e.hdgStart = s;\n\n e.altFrom = snap ? t.altitudeFt : shown.altitudeFt;\n e.altTo = t.altitudeFt;\n e.altStart = s;\n\n this.settle(e);\n this.touch(e);\n }\n\n /** The server time after which nothing about this track changes on its own. */\n private settle(e: Entry): void {\n const ends = [e.blendStart + this.o.blendMs, e.hdgStart + this.o.headingSmoothMs, e.altStart + this.o.blendMs];\n if (this.o.extrapolate && e.speedMs > 0 && e.frozen === null) ends.push(e.fixAt + this.cap(e));\n e.settleAt = Math.max(...ends);\n }\n\n private touch(e: Entry): void {\n this.active.add(e.id);\n this.ensureLoop();\n }\n\n remove(trackId: string): void {\n this.entries.delete(trackId);\n this.active.delete(trackId);\n }\n\n clear(): void {\n this.entries.clear();\n this.active.clear();\n }\n\n // ── output ──────────────────────────────────────────────────────────────\n\n sample(trackId: string, at?: number): MotionSample | undefined {\n const e = this.entries.get(trackId);\n return e ? this.sampleAt(e, this.serverNow(at)) : undefined;\n }\n\n private differs(a: MotionSample | null, b: MotionSample): boolean {\n if (a === null || a.moving !== b.moving || a.extrapolating !== b.extrapolating) return true;\n if (distanceM(a.lat, a.lon, b.lat, b.lon) > this.o.epsilonM) return true;\n return headingChanged(a.headingDeg, b.headingDeg) || altitudeChanged(a.altitudeFt, b.altitudeFt);\n }\n\n tick(at?: number): ReadonlyMap<string, MotionSample> {\n const s = this.serverNow(at);\n const changed = new Map<string, MotionSample>();\n for (const id of [...this.active]) {\n const e = this.entries.get(id);\n if (!e) {\n this.active.delete(id);\n continue;\n }\n const sample = this.sampleAt(e, s);\n if (this.differs(e.emitted, sample)) {\n e.emitted = sample;\n changed.set(id, sample);\n }\n if (s >= e.settleAt) this.active.delete(id);\n }\n return changed;\n }\n\n // ── frame loop ──────────────────────────────────────────────────────────\n\n start(onFrame: (frame: MotionFrame) => void): () => void {\n this.listeners.add(onFrame);\n this.ensureLoop();\n return () => {\n this.listeners.delete(onFrame);\n if (this.listeners.size === 0) this.cancelLoop();\n };\n }\n\n private ensureLoop(): void {\n if (this.handle !== null || this.listeners.size === 0 || this.active.size === 0) return;\n const scheduler = this.o.scheduler ?? (this.o.scheduler = defaultScheduler());\n this.handle = scheduler.request((ts) => this.frame(ts));\n }\n\n private cancelLoop(): void {\n if (this.handle === null) return;\n (this.o.scheduler ?? defaultScheduler()).cancel(this.handle);\n this.handle = null;\n }\n\n private frame(ts: number): void {\n this.handle = null;\n if (this.listeners.size === 0) return;\n if (ts - this.lastFrameAt >= 1000 / this.o.frameHz - 1) {\n this.lastFrameAt = ts;\n const changed = this.tick();\n if (changed.size > 0) {\n const frame: MotionFrame = { at: this.serverNow(), changed };\n for (const fn of [...this.listeners]) fn(frame);\n }\n }\n // Nothing left to animate: stop asking for frames until the next update.\n this.ensureLoop();\n }\n}\n\nexport function createTrackMotion(options: MotionOptions = {}): TrackMotion {\n return new Motion(options);\n}\n","import { useCallback, useEffect, useRef } from \"react\";\nimport type { CurrentTrack } from \"../../shared/types/index.js\";\n\ninterface InterpState {\n fromLat: number; fromLon: number;\n toLat: number; toLon: number;\n startedAt: number;\n durationMs: number;\n}\n\ninterface VelocityState {\n speedKt: number;\n headingDeg: number;\n}\n\ninterface DrState {\n baseLat: number;\n baseLon: number;\n baseTime: number; // performance.now() when DR started\n}\n\nexport interface TrackPosition {\n lat: number;\n lon: number;\n}\n\nexport interface TrackHistoryFrame {\n /** Animated (interpolated + dead-reckoned) current position per trackId */\n currentPositions: Map<string, TrackPosition>;\n /** Full position history per trackId as [lon, lat] pairs */\n history: Map<string, [number, number][]>;\n /** Last wall-clock update timestamp (ms) per trackId */\n lastSeen: Map<string, number>;\n /** Set of trackIds currently mid-tween */\n isMoving: Set<string>;\n}\n\nexport interface UseTrackHistoryOptions {\n /** Max history points to retain per track (default 500) */\n maxHistoryPoints?: number;\n /** Minimum position delta in degrees to record a new history point (default 0.0001) */\n minDeltaDeg?: number;\n /**\n * Maximum allowed position jump in degrees per update (default 5.0 ≈ 555 km).\n * Updates that exceed this are silently discarded as bad transponder data.\n */\n maxJumpDeg?: number;\n /** Called every animation frame while tweens/DR are active, and once on every data update */\n onFrame: (frame: TrackHistoryFrame) => void;\n}\n\nexport interface UseTrackHistoryResult {\n /** Feed a single track update directly into the animation engine (no React render cycle) */\n processUpdate: (track: CurrentTrack) => void;\n /** Remove a track from the animation engine */\n processRemove: (trackId: string) => void;\n /** Force a one-shot frame emission with current state */\n reemit: () => void;\n}\n\n// Minimum speed (kt) for dead reckoning to kick in\nconst DR_MIN_SPEED_KT = 10;\n// Maximum physically plausible speed (kt) — anything faster is bad data\n// (misdecode, or a position delta divided by a compressed replay clock)\n// and must never drive dead reckoning, or icons get flung off the map.\nconst MAX_SPEED_KT = 1200;\n\n// An update gap longer than this restarts the track's trail instead of\n// connecting the two positions with a line across the map.\nconst GAP_RESET_MS = 30_000;\n// Max dead-reckoning duration before we stop extrapolating (ms)\nconst DR_MAX_MS = 180_000; // 3 minutes\n\nfunction deadReckon(\n baseLat: number, baseLon: number,\n speedKt: number, headingDeg: number,\n elapsedMs: number,\n): TrackPosition {\n const elapsedSec = elapsedMs / 1000;\n const distanceNm = speedKt * elapsedSec / 3600;\n const headingRad = headingDeg * (Math.PI / 180);\n const dlat = (distanceNm / 60) * Math.cos(headingRad);\n const dlon = (distanceNm / (60 * Math.cos(baseLat * (Math.PI / 180)))) * Math.sin(headingRad);\n return { lat: baseLat + dlat, lon: baseLon + dlon };\n}\n\n/**\n * @deprecated Use `useTrackMotion` (or `createTrackMotion` outside React). This hook times\n * fixes by when they ARRIVE, so network jitter ends up in the prediction; the new engine uses\n * the server's clock, keeps the icon moving while a correction fades (no stop-and-go), eases the\n * heading, and only reports tracks that changed. Server-side trails come from `tracks.trail`.\n */\nexport function useTrackHistory(\n options: UseTrackHistoryOptions,\n): UseTrackHistoryResult {\n const {\n maxHistoryPoints = 500,\n minDeltaDeg = 0.0001,\n maxJumpDeg = 5.0,\n onFrame,\n } = options;\n\n const currentPositions = useRef<Map<string, TrackPosition>>(new Map());\n const lastActualPos = useRef<Map<string, TrackPosition>>(new Map());\n const history = useRef<Map<string, [number, number][]>>(new Map());\n const lastSeen = useRef<Map<string, number>>(new Map());\n const lastUpdPerf = useRef<Map<string, number>>(new Map());\n const interp = useRef<Map<string, InterpState>>(new Map());\n const velocity = useRef<Map<string, VelocityState>>(new Map());\n const drState = useRef<Map<string, DrState>>(new Map());\n const rafRef = useRef<number | null>(null);\n const onFrameRef = useRef(onFrame);\n onFrameRef.current = onFrame;\n\n // Options via refs so processUpdate is stable\n const maxHistoryPointsRef = useRef(maxHistoryPoints);\n maxHistoryPointsRef.current = maxHistoryPoints;\n const minDeltaDegRef = useRef(minDeltaDeg);\n minDeltaDegRef.current = minDeltaDeg;\n const maxJumpDegRef = useRef(maxJumpDeg);\n maxJumpDegRef.current = maxJumpDeg;\n\n function emitFrame() {\n onFrameRef.current({\n currentPositions: currentPositions.current,\n history: history.current,\n lastSeen: lastSeen.current,\n isMoving: new Set([...interp.current.keys(), ...drState.current.keys()]),\n });\n }\n\n // Returns true if any track needs continuous animation (tween or DR)\n function needsRaf(): boolean {\n if (interp.current.size > 0) return true;\n for (const [id, dr] of drState.current) {\n if (interp.current.has(id)) continue;\n const vel = velocity.current.get(id);\n if (vel && vel.speedKt >= DR_MIN_SPEED_KT) return true;\n // clean up stale DR\n if (performance.now() - dr.baseTime > DR_MAX_MS) drState.current.delete(id);\n }\n return false;\n }\n\n function ensureRaf() {\n if (rafRef.current !== null || !needsRaf()) return;\n const tick = (perfNow: number) => {\n for (const [id, tw] of interp.current) {\n const p = Math.min((perfNow - tw.startedAt) / tw.durationMs, 1);\n const ease = p < 1 ? p * (2 - p) : 1; // ease-out quad\n const lat = tw.fromLat + (tw.toLat - tw.fromLat) * ease;\n const lon = tw.fromLon + (tw.toLon - tw.fromLon) * ease;\n currentPositions.current.set(id, { lat, lon });\n if (p >= 1) {\n interp.current.delete(id);\n // Hand off to dead reckoning\n const vel = velocity.current.get(id);\n if (vel && vel.speedKt >= DR_MIN_SPEED_KT) {\n drState.current.set(id, { baseLat: lat, baseLon: lon, baseTime: perfNow });\n }\n }\n }\n\n // Dead reckoning for non-tweening tracks\n for (const [id, dr] of drState.current) {\n if (interp.current.has(id)) continue;\n const vel = velocity.current.get(id);\n if (!vel || vel.speedKt < DR_MIN_SPEED_KT) continue;\n const elapsed = perfNow - dr.baseTime;\n if (elapsed > DR_MAX_MS) { drState.current.delete(id); continue; }\n currentPositions.current.set(id, deadReckon(\n dr.baseLat, dr.baseLon, vel.speedKt, vel.headingDeg, elapsed,\n ));\n }\n\n emitFrame();\n rafRef.current = needsRaf() ? requestAnimationFrame(tick) : null;\n };\n rafRef.current = requestAnimationFrame(tick);\n }\n\n // Imperative: process a single track update without going through React state\n const processUpdate = useCallback((t: CurrentTrack) => {\n if (t.lat == null || t.lon == null) return;\n if (t.lat < -90 || t.lat > 90 || t.lon < -180 || t.lon > 180) return;\n const perfNow = performance.now();\n const wallNow = Date.now();\n const id = t.trackId;\n const lon = t.lon as number;\n const lat = t.lat as number;\n\n lastSeen.current.set(id, wallNow);\n\n // Update velocity from explicit data first (rejecting implausible speeds)\n if (t.groundSpeedKt != null && t.headingDeg != null) {\n const spd = t.groundSpeedKt as number;\n const hdg = t.headingDeg as number;\n if (spd >= 0 && spd <= MAX_SPEED_KT) {\n if (spd >= DR_MIN_SPEED_KT || !velocity.current.has(id)) {\n velocity.current.set(id, { speedKt: spd, headingDeg: hdg });\n }\n }\n }\n\n // Derive velocity from position delta if no explicit velocity (e.g. CAT48 radar tracks)\n const prevActual = lastActualPos.current.get(id);\n const prevUpdPerf = lastUpdPerf.current.get(id);\n if (prevActual && prevUpdPerf && perfNow > prevUpdPerf) {\n const dtSec = (perfNow - prevUpdPerf) / 1000;\n const dlatDeg = lat - prevActual.lat;\n const dlonDeg = lon - prevActual.lon;\n const dlatNm = dlatDeg * 60;\n const dlonNm = dlonDeg * 60 * Math.cos(lat * Math.PI / 180);\n const distNm = Math.sqrt(dlatNm * dlatNm + dlonNm * dlonNm);\n const derivedSpd = (distNm / dtSec) * 3600; // kt\n // Only trust the derivation over a sane wall-clock interval and a\n // physically plausible result — back-to-back packets (fast replay,\n // batched delivery) make dtSec tiny and the quotient absurd.\n if (dtSec >= 0.25 && derivedSpd >= DR_MIN_SPEED_KT && derivedSpd <= MAX_SPEED_KT) {\n const derivedHdg = (Math.atan2(dlonNm, dlatNm) * 180 / Math.PI + 360) % 360;\n const existing = velocity.current.get(id);\n if (!existing || existing.speedKt < DR_MIN_SPEED_KT) {\n velocity.current.set(id, { speedKt: derivedSpd, headingDeg: derivedHdg });\n }\n }\n }\n\n const cur = currentPositions.current.get(id);\n const lastActual = lastActualPos.current.get(id);\n\n if (!cur) {\n // First appearance — place immediately, no tween\n currentPositions.current.set(id, { lat, lon });\n lastActualPos.current.set(id, { lat, lon });\n history.current.set(id, []);\n lastUpdPerf.current.set(id, perfNow);\n const vel = velocity.current.get(id);\n if (vel && vel.speedKt >= DR_MIN_SPEED_KT) {\n drState.current.set(id, { baseLat: lat, baseLon: lon, baseTime: perfNow });\n }\n emitFrame();\n ensureRaf();\n return;\n }\n\n // Long silence (lost coverage, or the socket was offline) — restart the\n // trail and place the marker directly. Connecting across the gap draws a\n // straight line across the map, and the implausible-jump guard below would\n // otherwise discard the far-away reappearance forever.\n if (prevUpdPerf !== undefined && perfNow - prevUpdPerf > GAP_RESET_MS) {\n history.current.set(id, []);\n currentPositions.current.set(id, { lat, lon });\n lastActualPos.current.set(id, { lat, lon });\n lastUpdPerf.current.set(id, perfNow);\n interp.current.delete(id);\n drState.current.delete(id);\n emitFrame();\n ensureRaf();\n return;\n }\n\n const minD = minDeltaDegRef.current;\n const maxJ = maxJumpDegRef.current;\n\n // Compare against last ACTUAL position\n const checkLat = lastActual?.lat ?? cur.lat;\n const checkLon = lastActual?.lon ?? cur.lon;\n\n if (\n Math.abs(checkLat - lat) < minD &&\n Math.abs(checkLon - lon) < minD\n ) return; // no meaningful change\n\n // Discard implausible jumps relative to last ACTUAL position\n if (lastActual && (\n Math.abs(lat - lastActual.lat) > maxJ ||\n Math.abs(lon - lastActual.lon) > maxJ\n )) return;\n\n // Record actual position\n lastActualPos.current.set(id, { lat, lon });\n\n // Detect actual update interval to set tween duration\n const prevPerf = lastUpdPerf.current.get(id) ?? perfNow;\n const interval = Math.max(100, Math.min(perfNow - prevPerf, 5000));\n lastUpdPerf.current.set(id, perfNow);\n\n // Record current animated position into history\n const maxHP = maxHistoryPointsRef.current;\n const hist = history.current.get(id) ?? [];\n const last = hist.at(-1);\n const dupThreshold = minD * 0.5;\n if (\n !last ||\n Math.abs(last[0] - cur.lon) > dupThreshold ||\n Math.abs(last[1] - cur.lat) > dupThreshold\n ) {\n hist.push([cur.lon, cur.lat]);\n if (hist.length > maxHP) hist.shift();\n history.current.set(id, hist);\n }\n\n // Cancel any in-progress DR — tween takes over from current (possibly DR) position\n drState.current.delete(id);\n\n interp.current.set(id, {\n fromLat: cur.lat, fromLon: cur.lon,\n toLat: lat, toLon: lon,\n startedAt: perfNow,\n durationMs: Math.min(interval * 0.95, 3000),\n });\n\n emitFrame();\n ensureRaf();\n }, []); // stable — all state in refs\n\n // Imperative: remove a track from the engine\n const processRemove = useCallback((trackId: string) => {\n currentPositions.current.delete(trackId);\n lastActualPos.current.delete(trackId);\n history.current.delete(trackId);\n lastSeen.current.delete(trackId);\n lastUpdPerf.current.delete(trackId);\n interp.current.delete(trackId);\n velocity.current.delete(trackId);\n drState.current.delete(trackId);\n emitFrame();\n }, []); // stable\n\n useEffect(() => {\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n };\n }, []);\n\n const reemit = useCallback(() => {\n emitFrame();\n }, []);\n\n return { processUpdate, processRemove, reemit };\n}\n","export { useTrackTrail } from \"./use-track-trail.js\";\r\nexport type { UseTrackTrailOptions, UseTrackTrailResult } from \"./use-track-trail.js\";\r\nexport { useTrackMotion } from \"./use-track-motion.js\";\r\nexport type { UseTrackMotionOptions } from \"./use-track-motion.js\";\r\nexport { useTrackHistory } from \"./use-track-history.js\";\r\nexport type { UseTrackHistoryOptions, UseTrackHistoryResult, TrackHistoryFrame, TrackPosition } from \"./use-track-history.js\";\r\n\r\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\r\nimport { useQuery } from \"@tanstack/react-query\";\r\nimport { useShallow } from \"zustand/react/shallow\";\r\nimport { type LabeledTrack } from \"../../shared/label/index.js\";\r\nimport type { CurrentTrack, Bbox, AeroConnectionStatus, AeroRecoverableError } from \"../../shared/types/index.js\";\r\nimport { useAeroSwarmContext } from \"../context/index.js\";\r\nimport {\r\n useTrackStore,\r\n selectAllTracks,\r\n selectRenderableTracks,\r\n selectTrack,\r\n selectTrackCount,\r\n selectStatus,\r\n selectIsDegraded,\r\n selectLastUpdateAt,\r\n selectError,\r\n} from \"../store/index.js\";\r\n\r\nexport interface UseAeroTracksOptions {\r\n bbox?: Bbox;\r\n source?: \"fused\" | \"normalized\";\r\n minConfidence?: number;\r\n maxUpdateHz?: number;\r\n enabled?: boolean;\r\n staleAfterMs?: number;\r\n removeAfterMs?: number;\r\n}\r\n\r\nexport interface UseAeroTracksResult {\r\n\r\n tracks: (LabeledTrack & { lat: number; lon: number; })[];\r\n\r\n allTracks: LabeledTrack[];\r\n\r\n status: AeroConnectionStatus;\r\n isConnected: boolean;\r\n isLoading: boolean;\r\n isDegraded: boolean;\r\n\r\n lastUpdateAt: number | null;\r\n\r\n error: AeroRecoverableError | null;\r\n trackCount: number;\r\n\r\n /** Force an immediate reconnect of the live stream. Returns false if not subscribed. */\r\n reconnect: () => boolean;\r\n}\r\n\r\nexport function useAeroTracks(opts: UseAeroTracksOptions = {}): UseAeroTracksResult {\r\n const { client } = useAeroSwarmContext();\r\n\r\n const tracks = useTrackStore(useShallow(selectRenderableTracks));\r\n const allTracks = useTrackStore(useShallow(selectAllTracks));\r\n const status = useTrackStore(selectStatus);\r\n const isDegraded = useTrackStore(selectIsDegraded);\r\n const lastUpdateAt = useTrackStore(selectLastUpdateAt);\r\n const error = useTrackStore(selectError);\r\n const unsubRef = useRef<(() => void) | null>(null);\r\n\r\n const { isLoading } = useQuery({\r\n queryKey: [\"aero-snapshot\", opts.bbox?.join(\",\"), opts.source, opts.minConfidence],\r\n enabled: opts.enabled !== false,\r\n staleTime: 5_000,\r\n queryFn: async () => {\r\n const result = await client.tracks.snapshot({\r\n bbox: opts.bbox,\r\n minConfidence: opts.minConfidence,\r\n }) as { tracks: CurrentTrack[]; };\r\n\r\n useTrackStore.getState().applySnapshot(result.tracks ?? []);\r\n return result;\r\n },\r\n });\r\n\r\n useEffect(() => {\r\n if (opts.enabled === false) return;\r\n\r\n const actions = useTrackStore.getState();\r\n\r\n unsubRef.current = client.tracks.subscribe(\r\n {\r\n bbox: opts.bbox,\r\n source: opts.source ?? \"fused\",\r\n minConfidence: opts.minConfidence,\r\n maxUpdateHz: opts.maxUpdateHz ?? 20,\r\n mode: \"delta\",\r\n },\r\n {\r\n onStatusChange: (s) => actions.setStatus(s),\r\n onSnapshot: (t) => actions.applySnapshot(t),\r\n onTrackUpdate: (t) => actions.applyUpdate(t),\r\n onTrackRemoved: (ev) => actions.applyRemoved(ev),\r\n onSourceStatus: (ev) => actions.setSourceStatus(ev.sourceId, ev.status as never),\r\n onDegraded: (r) => actions.setDegraded(r),\r\n onError: (e) => actions.setError(e),\r\n }\r\n );\r\n\r\n return () => {\r\n unsubRef.current?.();\r\n useTrackStore.getState().setStatus(\"idle\");\r\n };\r\n\r\n }, [opts.bbox?.join(\",\"), opts.source, opts.minConfidence, opts.maxUpdateHz, opts.enabled]);\r\n\r\n const reconnect = useCallback(() => client.tracks.reconnect(), [client]);\r\n\r\n return {\r\n tracks,\r\n allTracks,\r\n status,\r\n isConnected: status === \"connected\",\r\n isLoading,\r\n isDegraded,\r\n lastUpdateAt,\r\n error,\r\n trackCount: tracks.length,\r\n reconnect,\r\n };\r\n}\r\n\r\nexport const useTracks = useAeroTracks;\r\n\r\nexport function useAeroTrack(trackId: string): { track: LabeledTrack | undefined; } {\r\n const track = useTrackStore(useMemo(() => selectTrack(trackId), [trackId]));\r\n return { track };\r\n}\r\n\r\nexport function useAeroSnapshot(opts: UseAeroTracksOptions = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-snapshot-only\", opts.bbox?.join(\",\"), opts.source],\r\n enabled: opts.enabled !== false,\r\n queryFn: () => client.tracks.snapshot({ bbox: opts.bbox, minConfidence: opts.minConfidence }),\r\n });\r\n}\r\n\r\nexport function useSourceHealth(sourceId?: string) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-source-health\", sourceId],\r\n refetchInterval: 15_000,\r\n queryFn: () => sourceId ? client.sources.health(sourceId) : client.sources.list(),\r\n });\r\n}\r\n\r\nexport function useTrackCount(): number {\r\n return useTrackStore(selectTrackCount);\r\n}\r\n\r\nexport function useAeroStatus() {\r\n return {\r\n status: useTrackStore(selectStatus),\r\n isDegraded: useTrackStore(selectIsDegraded),\r\n lastUpdateAt: useTrackStore(selectLastUpdateAt),\r\n error: useTrackStore(selectError),\r\n };\r\n}\r\n\r\nexport interface UseReplayOptions {\r\n enabled?: boolean;\r\n refetchInterval?: number;\r\n}\r\n\r\nexport function useReplayStatus(sessionId: string, opts: UseReplayOptions = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-replay-status\", sessionId],\r\n enabled: opts.enabled !== false,\r\n refetchInterval: opts.refetchInterval ?? 1000,\r\n queryFn: () => (client as any).replay?.status?.query?.({ sessionId }) ?? Promise.resolve({ status: \"idle\" }),\r\n });\r\n}\r\n\r\nexport function useReplayStop() {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-replay-stop\"],\r\n enabled: false,\r\n queryFn: () => (client as any).replay?.stop?.query?.({ sessionId: \"\" }) ?? Promise.resolve({ success: true }),\r\n });\r\n}\r\n\r\nexport function useWebhooks(opts: { enabled?: boolean; } = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-webhooks\"],\r\n enabled: opts.enabled !== false,\r\n queryFn: () => (client as any).webhooks?.list?.query?.() ?? Promise.resolve({ webhooks: [] }),\r\n });\r\n}\r\n\r\nexport function useLineage(eventId: string, opts: { enabled?: boolean; } = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-lineage\", eventId],\r\n enabled: opts.enabled !== false && !!eventId,\r\n queryFn: () => (client as any).lineage?.get?.query?.({ eventId }) ?? Promise.resolve({ lineage: null }),\r\n });\r\n}\r\n\r\nexport function useLineageTrace(trackId: string, opts: { enabled?: boolean; } = {}) {\r\n const { client } = useAeroSwarmContext();\r\n return useQuery({\r\n queryKey: [\"aero-lineage-trace\", trackId],\r\n enabled: opts.enabled !== false && !!trackId,\r\n queryFn: () => (client as any).lineage?.trace?.query?.({ trackId }) ?? Promise.resolve({ trace: [] }),\r\n });\r\n}\r\n","import { create } from \"zustand\";\r\nimport type { CurrentTrack, AeroConnectionStatus, AeroRecoverableError, SourceStatus } from \"../../shared/types/index.js\";\r\nimport { isRenderableTrack } from \"../../shared/utils/index.js\";\r\nimport { withLabel, type LabeledTrack } from \"../../shared/label/index.js\";\r\n\r\ninterface TrackStoreState {\r\n tracks: Map<string, LabeledTrack>;\r\n status: AeroConnectionStatus;\r\n sourceStatus: Record<string, SourceStatus>;\r\n lastUpdateAt: number | null;\r\n error: AeroRecoverableError | null;\r\n isDegraded: boolean;\r\n degradedReason: string | null;\r\n\r\n applyUpdate: (track: CurrentTrack) => void;\r\n applyBatch: (updates: CurrentTrack[], removedIds: string[]) => void;\r\n applyRemoved: (ev: { trackId: string; }) => void;\r\n applySnapshot: (tracks: CurrentTrack[]) => void;\r\n setStatus: (s: AeroConnectionStatus) => void;\r\n setSourceStatus: (sourceId: string, status: SourceStatus) => void;\r\n setError: (err: AeroRecoverableError | null) => void;\r\n setDegraded: (reason: string | null) => void;\r\n /** Remove tracks not updated within maxAgeMs; returns the removed ids.\r\n * Without this, tracks whose id churned (feed restart, replay restart)\r\n * linger on the map forever as ghosts — they never get an explicit\r\n * \"removed\" event because the server no longer knows that id. */\r\n pruneStale: (maxAgeMs: number) => string[];\r\n clear: () => void;\r\n}\r\n\r\nexport const useTrackStore = create<TrackStoreState>()((set, get) => ({\r\n tracks: new Map(),\r\n status: \"idle\",\r\n sourceStatus: {},\r\n lastUpdateAt: null,\r\n error: null,\r\n isDegraded: false,\r\n degradedReason: null,\r\n\r\n applyUpdate(track) {\r\n const next = new Map(get().tracks);\r\n next.set(track.trackId, withLabel({ ...next.get(track.trackId), ...track }));\r\n set({ tracks: next, lastUpdateAt: Date.now() });\r\n },\r\n\r\n applyBatch(updates, removedIds) {\r\n if (updates.length === 0 && removedIds.length === 0) return;\r\n const next = new Map(get().tracks);\r\n for (const track of updates) {\r\n next.set(track.trackId, withLabel({ ...next.get(track.trackId), ...track }));\r\n }\r\n for (const id of removedIds) next.delete(id);\r\n set({ tracks: next, lastUpdateAt: Date.now() });\r\n },\r\n\r\n applyRemoved({ trackId }) {\r\n const next = new Map(get().tracks);\r\n next.delete(trackId);\r\n set({ tracks: next });\r\n },\r\n\r\n applySnapshot(tracks) {\r\n // A snapshot is the server's full current state for this subscription —\r\n // treat it as authoritative and drop everything else. Merging instead\r\n // leaves ghost tracks behind after a reconnect: aircraft removed while\r\n // the socket was down would stay on the map forever.\r\n const next = new Map<string, LabeledTrack>();\r\n for (const t of tracks) {\r\n if (isRenderableTrack(t)) next.set(t.trackId, withLabel(t));\r\n }\r\n set({ tracks: next, lastUpdateAt: Date.now() });\r\n },\r\n\r\n setStatus(s) {\r\n set({\r\n status: s,\r\n isDegraded: s === \"degraded\",\r\n\r\n degradedReason: s === \"connected\" ? null : get().degradedReason,\r\n });\r\n },\r\n\r\n setSourceStatus(sourceId, status) {\r\n set({ sourceStatus: { ...get().sourceStatus, [sourceId]: status } });\r\n },\r\n\r\n setError(err) { set({ error: err }); },\r\n\r\n setDegraded(reason) {\r\n set({\r\n isDegraded: reason !== null,\r\n degradedReason: reason,\r\n status: reason !== null ? \"degraded\" : get().status,\r\n });\r\n },\r\n\r\n pruneStale(maxAgeMs) {\r\n const cutoff = Date.now() - maxAgeMs;\r\n const removed: string[] = [];\r\n const next = new Map(get().tracks);\r\n for (const [id, t] of next) {\r\n if ((t.lastSeenAt ?? 0) < cutoff) {\r\n next.delete(id);\r\n removed.push(id);\r\n }\r\n }\r\n if (removed.length > 0) set({ tracks: next });\r\n return removed;\r\n },\r\n\r\n clear() { set({ tracks: new Map(), lastUpdateAt: null }); },\r\n}));\r\n\r\nexport const selectAllTracks = (s: TrackStoreState) => Array.from(s.tracks.values());\r\nexport const selectTrack = (id: string) => (s: TrackStoreState) => s.tracks.get(id);\r\nexport const selectTrackCount = (s: TrackStoreState) => s.tracks.size;\r\nexport const selectStatus = (s: TrackStoreState) => s.status;\r\nexport const selectIsDegraded = (s: TrackStoreState) => s.isDegraded;\r\nexport const selectLastUpdateAt = (s: TrackStoreState) => s.lastUpdateAt;\r\nexport const selectError = (s: TrackStoreState) => s.error;\r\n\r\nexport const selectRenderableTracks = (s: TrackStoreState) =>\r\n Array.from(s.tracks.values()).filter(isRenderableTrack) as\r\n (LabeledTrack & { lat: number; lon: number; })[];\r\n","export { isRenderableTrack, haversineKm, isInsideBbox, toCurrentTrack, clamp } from \"@aeroswarm/core/utils\";\n"],"mappings":"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","names":["raw","message","resolve","wsBase","useEffect","useMemo","useRef","useRef","useMemo","useEffect","useEffect","useRef","useCallback","useEffect","useMemo","useRef","isRenderableTrack","isRenderableTrack","useRef","useEffect","useCallback","useMemo"]}