@aeroswarm/client 0.1.13 → 0.1.14

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,3 +1,3 @@
1
1
  import '@aeroswarm/core/types';
2
- export { A as AeroReplayClient, a as AeroSwarm, b as AeroSwarmOptions, c as AeroWsClient, d as AftnMessageRow, D as DayAvailability, F as FlightPlanRow, g as LiveTrackCallbacks, R as ReplayCallbacks, h as ReplayHandle, i as ReplayProgress, j as ReplayStreamOptions, S as SourceRow, k as StreamMeta, l as SubscribeOptions, T as TrackCache, m as TrackCallbacks, n as TrackHistoryPoint, U as UseAeroTracksResult } from '../index-BXiTmKdS.js';
2
+ export { A as AeroApiError, a as AeroReplayClient, b as AeroSwarm, c as AeroSwarmOptions, d as AeroWsClient, e as AftnMessageRow, f as ApiErrorCode, g as ApiErrorInit, D as DayAvailability, F as FlightPlanRow, j as LiveTrackCallbacks, R as ReplayCallbacks, k as ReplayHandle, l as ReplayProgress, m as ReplayStreamOptions, S as SourceRow, n as StreamMeta, o as SubscribeOptions, T as TrackCache, p as TrackCallbacks, q as TrackHistoryPoint, U as UseAeroTracksResult, r as codeForStatus, t as parseApiResponse, u as readApiBody } from '../index-CyxVoJXL.js';
3
3
  import '@trpc/client';
package/dist/sdk/index.js CHANGED
@@ -314,6 +314,85 @@ function createAeroTrpcClient(endpoint, token) {
314
314
  });
315
315
  }
316
316
 
317
+ // src/sdk/api/response.ts
318
+ var ENVELOPE_HEADER = "x-aero-envelope";
319
+ var STATUS_CODES = [
320
+ [401, "UNAUTHORIZED"],
321
+ [403, "FORBIDDEN"],
322
+ [404, "NOT_FOUND"],
323
+ [400, "BAD_REQUEST"],
324
+ [409, "CONFLICT"],
325
+ [429, "RATE_LIMITED"],
326
+ [503, "UNAVAILABLE"],
327
+ [500, "INTERNAL"]
328
+ ];
329
+ function codeForStatus(status) {
330
+ const hit = STATUS_CODES.find(([s]) => s === status);
331
+ if (hit) return hit[1];
332
+ return status >= 500 ? "INTERNAL" : "BAD_REQUEST";
333
+ }
334
+ var AeroApiError = class extends Error {
335
+ status;
336
+ code;
337
+ /** Retrying later may succeed (rate limit, server busy or failing). */
338
+ recoverable;
339
+ details;
340
+ constructor(init) {
341
+ super(init.message);
342
+ this.name = "AeroApiError";
343
+ this.status = init.status;
344
+ this.code = init.code;
345
+ this.recoverable = init.recoverable;
346
+ this.details = init.details;
347
+ }
348
+ };
349
+ var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
350
+ function isEnvelope(body) {
351
+ return isObject(body) && body.ok === true && "data" in body;
352
+ }
353
+ function isFailure(body) {
354
+ return isObject(body) && body.ok === false;
355
+ }
356
+ function errorFrom(res, body, text, label) {
357
+ const prefix = label ? `${label} \u2192 ${res.status}` : `HTTP ${res.status}`;
358
+ const failure = isFailure(body) ? body : void 0;
359
+ const e = failure?.error;
360
+ if (isObject(e)) {
361
+ const code = typeof e.code === "string" ? e.code : codeForStatus(res.status);
362
+ const message2 = typeof e.message === "string" ? e.message : res.statusText || prefix;
363
+ return new AeroApiError({
364
+ status: res.status,
365
+ code,
366
+ message: `${prefix}: ${message2}`,
367
+ recoverable: typeof e.recoverable === "boolean" ? e.recoverable : res.status >= 500,
368
+ details: e.details
369
+ });
370
+ }
371
+ const message = typeof e === "string" ? e : text.slice(0, 200) || res.statusText;
372
+ return new AeroApiError({
373
+ status: res.status,
374
+ code: codeForStatus(res.status),
375
+ message: message ? `${prefix}: ${message}` : prefix,
376
+ recoverable: res.status >= 500 || res.status === 429,
377
+ details: failure?.detail
378
+ });
379
+ }
380
+ async function readApiBody(res, label) {
381
+ const text = await res.text();
382
+ let body;
383
+ try {
384
+ body = text ? JSON.parse(text) : void 0;
385
+ } catch {
386
+ body = void 0;
387
+ }
388
+ if (!res.ok || isFailure(body)) throw errorFrom(res, body, text, label);
389
+ return body;
390
+ }
391
+ async function parseApiResponse(res, label) {
392
+ const body = await readApiBody(res, label);
393
+ return isEnvelope(body) ? body.data : body;
394
+ }
395
+
317
396
  // src/sdk/api/rest.ts
318
397
  var AeroRestClient = class {
319
398
  constructor(endpoint, token) {
@@ -331,8 +410,7 @@ var AeroRestClient = class {
331
410
  },
332
411
  body: body ? JSON.stringify(body) : void 0
333
412
  });
334
- if (!res.ok) throw new Error(`${method} ${path} \u2192 ${res.status}`);
335
- return res.json();
413
+ return await readApiBody(res, `${method} ${path}`);
336
414
  }
337
415
  createSource(body) {
338
416
  return this.req("POST", "/v1/sources", body);
@@ -554,9 +632,10 @@ var AeroSwarm = class _AeroSwarm {
554
632
  async federatedFetch(base, path, query) {
555
633
  const url = new URL(`${base}${path}`);
556
634
  for (const [k, v] of Object.entries(query ?? {})) if (v !== void 0) url.searchParams.set(k, v);
557
- const res = await fetch(url, { headers: { Authorization: `Bearer ${this.opts.token}` } });
558
- if (!res.ok) throw new Error(`Federated request failed: ${res.status} ${res.statusText} (${url.pathname})`);
559
- return res.json();
635
+ const res = await fetch(url, {
636
+ headers: { Authorization: `Bearer ${this.opts.token}`, [ENVELOPE_HEADER]: "1" }
637
+ });
638
+ return parseApiResponse(res, `Federated request ${url.pathname}`);
560
639
  }
561
640
  tracks = {
562
641
  subscribe: (opts, callbacks) => {
@@ -785,9 +864,13 @@ var AeroSwarm = class _AeroSwarm {
785
864
  }
786
865
  };
787
866
  export {
867
+ AeroApiError,
788
868
  AeroReplayClient,
789
869
  AeroSwarm,
790
870
  AeroWsClient,
791
- TrackCache
871
+ TrackCache,
872
+ codeForStatus,
873
+ parseApiResponse,
874
+ readApiBody
792
875
  };
793
876
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../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"],"sourcesContent":["\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"],"mappings":"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","names":["raw","wsBase"]}
1
+ {"version":3,"sources":["../../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"],"sourcesContent":["\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/**\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 const body = await readApiBody(res, label);\n return (isEnvelope(body) ? body.data : body) as T;\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 { ENVELOPE_HEADER, parseApiResponse } from \"./api/response.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, readApiBody, codeForStatus } from \"./api/response.js\";\r\nexport type { ApiErrorCode, ApiErrorInit } 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 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 // Ask for the standard envelope. Servers that predate it ignore the header and answer\r\n // with bare data, which parseApiResponse 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 parseApiResponse<T>(res, `Federated request ${url.pathname}`);\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"],"mappings":"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","names":["raw","message","wsBase"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeroswarm/client",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -39,7 +39,7 @@
39
39
  "release:major": "release-it major --no-git.requireCleanWorkingDir"
40
40
  },
41
41
  "dependencies": {
42
- "@aeroswarm/core": "^0.2.10",
42
+ "@aeroswarm/core": "^0.2.12",
43
43
  "@tanstack/react-query": "^5.100.11",
44
44
  "@trpc/client": "^11.17.0",
45
45
  "zod": "^4.4.3",