@aeroswarm/client 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/{index-CyxVoJXL.d.ts → index-CeayQnTp.d.ts} +121 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +124 -4
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +23 -2
- package/dist/react/index.js +194 -25
- package/dist/react/index.js.map +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +49 -4
- package/dist/sdk/index.js.map +1 -1
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -392,9 +392,11 @@ async function readApiBody(res, label) {
|
|
|
392
392
|
if (!res.ok || isFailure(body)) throw errorFrom(res, body, text, label);
|
|
393
393
|
return body;
|
|
394
394
|
}
|
|
395
|
-
async function
|
|
395
|
+
async function parseApiEnvelope(res, label) {
|
|
396
396
|
const body = await readApiBody(res, label);
|
|
397
|
-
|
|
397
|
+
if (!isEnvelope(body)) return { data: body };
|
|
398
|
+
const meta = body.meta;
|
|
399
|
+
return { data: body.data, ...isObject(meta) && { meta } };
|
|
398
400
|
}
|
|
399
401
|
|
|
400
402
|
// src/sdk/api/rest.ts
|
|
@@ -633,13 +635,16 @@ var AeroSwarm = class _AeroSwarm {
|
|
|
633
635
|
return null;
|
|
634
636
|
}
|
|
635
637
|
}
|
|
636
|
-
async
|
|
638
|
+
async federatedRequest(base, path, query) {
|
|
637
639
|
const url = new URL(`${base}${path}`);
|
|
638
640
|
for (const [k, v] of Object.entries(query ?? {})) if (v !== void 0) url.searchParams.set(k, v);
|
|
639
641
|
const res = await fetch(url, {
|
|
640
642
|
headers: { Authorization: `Bearer ${this.opts.token}`, [ENVELOPE_HEADER]: "1" }
|
|
641
643
|
});
|
|
642
|
-
return
|
|
644
|
+
return parseApiEnvelope(res, `Federated request ${url.pathname}`);
|
|
645
|
+
}
|
|
646
|
+
async federatedFetch(base, path, query) {
|
|
647
|
+
return (await this.federatedRequest(base, path, query)).data;
|
|
643
648
|
}
|
|
644
649
|
tracks = {
|
|
645
650
|
subscribe: (opts, callbacks) => {
|
|
@@ -730,6 +735,42 @@ var AeroSwarm = class _AeroSwarm {
|
|
|
730
735
|
});
|
|
731
736
|
}
|
|
732
737
|
return this.trpc.tracks.history.query(input);
|
|
738
|
+
},
|
|
739
|
+
/**
|
|
740
|
+
* The path an aircraft has flown, ready to draw: a GeoJSON line plus a point for every time
|
|
741
|
+
* mark, with the label already written in `tz`. By default the whole flight so far; `fromMs`
|
|
742
|
+
* and `toMs` choose a period, `sinceMs` fetches only what is new (see `createTrailPoller`),
|
|
743
|
+
* `stepM` asks for a coarser line. The airport's backend builds it (memory first, history
|
|
744
|
+
* as a fallback), so nothing is computed here. There is no AeroSwarm-side equivalent:
|
|
745
|
+
* pass `endpoint` or an `airportId` with a registered `historyEndpoint`.
|
|
746
|
+
*/
|
|
747
|
+
trail: async (input) => {
|
|
748
|
+
const base = await this.resolveHistoryEndpoint(input.airportId, input.endpoint);
|
|
749
|
+
if (!base) {
|
|
750
|
+
throw new AeroApiError({
|
|
751
|
+
status: 400,
|
|
752
|
+
code: "BAD_REQUEST",
|
|
753
|
+
recoverable: false,
|
|
754
|
+
message: "tracks.trail needs the airport's backend: pass `endpoint`, or an `airportId` that has a historyEndpoint"
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
const num = (v) => v === void 0 ? void 0 : String(v);
|
|
758
|
+
const { data, meta } = await this.federatedRequest(
|
|
759
|
+
base,
|
|
760
|
+
`/tracks/${encodeURIComponent(input.trackId)}/trail`,
|
|
761
|
+
{
|
|
762
|
+
fromMs: num(input.fromMs),
|
|
763
|
+
toMs: num(input.toMs),
|
|
764
|
+
sinceMs: num(input.sinceMs),
|
|
765
|
+
stepM: num(input.stepM),
|
|
766
|
+
limit: num(input.limit),
|
|
767
|
+
labelEveryMs: num(input.labelEveryMs),
|
|
768
|
+
tz: input.tz,
|
|
769
|
+
labelFormat: input.labelFormat,
|
|
770
|
+
format: "geojson"
|
|
771
|
+
}
|
|
772
|
+
);
|
|
773
|
+
return { trail: data, ...meta && { meta } };
|
|
733
774
|
}
|
|
734
775
|
};
|
|
735
776
|
/** Flight plans received from AFTN feeds. Live tracks that correlate with a
|
|
@@ -900,8 +941,135 @@ function AeroSwarmProvider({
|
|
|
900
941
|
return /* @__PURE__ */ jsx(QueryClientProvider, { client: qc, children: /* @__PURE__ */ jsx(AeroSwarmContext.Provider, { value: { client }, children }) });
|
|
901
942
|
}
|
|
902
943
|
|
|
944
|
+
// src/react/hooks/use-track-trail.ts
|
|
945
|
+
import { useEffect, useRef, useState } from "react";
|
|
946
|
+
|
|
947
|
+
// src/shared/trail/index.ts
|
|
948
|
+
var lineOf = (fc) => fc.features.find((f) => f.properties.kind === "trail");
|
|
949
|
+
var marksOf = (fc) => fc.features.filter((f) => f.properties.kind === "mark");
|
|
950
|
+
function trailEnd(trail) {
|
|
951
|
+
const line = lineOf(trail);
|
|
952
|
+
return line && line.geometry.coordinates.length > 0 ? line.properties.to : void 0;
|
|
953
|
+
}
|
|
954
|
+
function mergeTrail(prev, delta) {
|
|
955
|
+
if (!prev) return delta;
|
|
956
|
+
const before = lineOf(prev);
|
|
957
|
+
const added = lineOf(delta);
|
|
958
|
+
if (!before || !added) return prev;
|
|
959
|
+
if (added.geometry.coordinates.length === 0) return prev;
|
|
960
|
+
const seen = new Set(marksOf(prev).map((m) => m.properties.t));
|
|
961
|
+
const newMarks = marksOf(delta).filter((m) => !seen.has(m.properties.t));
|
|
962
|
+
const line = {
|
|
963
|
+
...before,
|
|
964
|
+
properties: { ...before.properties, to: added.properties.to, stepM: Math.max(before.properties.stepM, added.properties.stepM) },
|
|
965
|
+
geometry: { type: "LineString", coordinates: [...before.geometry.coordinates, ...added.geometry.coordinates] }
|
|
966
|
+
};
|
|
967
|
+
return { type: "FeatureCollection", features: [line, ...marksOf(prev), ...newMarks] };
|
|
968
|
+
}
|
|
969
|
+
var isGone = (err) => typeof err === "object" && err !== null && err.code === "NOT_FOUND";
|
|
970
|
+
function createTrailPoller(fetchTrail, options) {
|
|
971
|
+
const pollMs = options.pollMs ?? 5e3;
|
|
972
|
+
const stopWhenGone = options.stopWhenGone ?? true;
|
|
973
|
+
let current;
|
|
974
|
+
let stopped = false;
|
|
975
|
+
let timer;
|
|
976
|
+
let running;
|
|
977
|
+
const schedule = () => {
|
|
978
|
+
if (stopped || pollMs <= 0) return;
|
|
979
|
+
timer = setTimeout(() => void tick(), pollMs);
|
|
980
|
+
};
|
|
981
|
+
async function once() {
|
|
982
|
+
try {
|
|
983
|
+
const since = current ? trailEnd(current) : void 0;
|
|
984
|
+
const { trail } = await fetchTrail(since);
|
|
985
|
+
if (stopped) return;
|
|
986
|
+
const next = mergeTrail(current, trail);
|
|
987
|
+
if (next !== current) {
|
|
988
|
+
current = next;
|
|
989
|
+
options.onUpdate(next);
|
|
990
|
+
}
|
|
991
|
+
} catch (err) {
|
|
992
|
+
if (stopped) return;
|
|
993
|
+
options.onError?.(err);
|
|
994
|
+
if (stopWhenGone && isGone(err)) stopped = true;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function tick() {
|
|
998
|
+
if (stopped) return Promise.resolve();
|
|
999
|
+
running ??= once().finally(() => {
|
|
1000
|
+
running = void 0;
|
|
1001
|
+
schedule();
|
|
1002
|
+
});
|
|
1003
|
+
return running;
|
|
1004
|
+
}
|
|
1005
|
+
void tick();
|
|
1006
|
+
return {
|
|
1007
|
+
stop() {
|
|
1008
|
+
stopped = true;
|
|
1009
|
+
if (timer) clearTimeout(timer);
|
|
1010
|
+
},
|
|
1011
|
+
async refresh() {
|
|
1012
|
+
if (timer) clearTimeout(timer);
|
|
1013
|
+
await (running ?? tick());
|
|
1014
|
+
},
|
|
1015
|
+
current: () => current
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/react/hooks/use-track-trail.ts
|
|
1020
|
+
function useTrackTrail(trackId, options = {}) {
|
|
1021
|
+
const { client } = useAeroSwarmContext();
|
|
1022
|
+
const [trail, setTrail] = useState(void 0);
|
|
1023
|
+
const [loading, setLoading] = useState(false);
|
|
1024
|
+
const [error, setError] = useState(void 0);
|
|
1025
|
+
const optionsRef = useRef(options);
|
|
1026
|
+
optionsRef.current = options;
|
|
1027
|
+
const enabled = options.enabled !== false && !!trackId;
|
|
1028
|
+
const key = JSON.stringify([
|
|
1029
|
+
trackId,
|
|
1030
|
+
options.endpoint,
|
|
1031
|
+
options.airportId,
|
|
1032
|
+
options.fromMs,
|
|
1033
|
+
options.toMs,
|
|
1034
|
+
options.stepM,
|
|
1035
|
+
options.limit,
|
|
1036
|
+
options.labelEveryMs,
|
|
1037
|
+
options.tz,
|
|
1038
|
+
options.labelFormat,
|
|
1039
|
+
options.pollMs
|
|
1040
|
+
]);
|
|
1041
|
+
useEffect(() => {
|
|
1042
|
+
setTrail(void 0);
|
|
1043
|
+
setError(void 0);
|
|
1044
|
+
if (!enabled || !trackId) {
|
|
1045
|
+
setLoading(false);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
setLoading(true);
|
|
1049
|
+
const { enabled: _enabled, pollMs, ...request } = optionsRef.current;
|
|
1050
|
+
const poller = createTrailPoller(
|
|
1051
|
+
(sinceMs) => client.tracks.trail({ ...request, trackId, sinceMs }),
|
|
1052
|
+
{
|
|
1053
|
+
// A fixed period in the past does not grow: ask once.
|
|
1054
|
+
pollMs: request.toMs !== void 0 ? 0 : pollMs,
|
|
1055
|
+
onUpdate: (t) => {
|
|
1056
|
+
setTrail(t);
|
|
1057
|
+
setLoading(false);
|
|
1058
|
+
setError(void 0);
|
|
1059
|
+
},
|
|
1060
|
+
onError: (e) => {
|
|
1061
|
+
setError(e);
|
|
1062
|
+
setLoading(false);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
);
|
|
1066
|
+
return () => poller.stop();
|
|
1067
|
+
}, [client, enabled, key]);
|
|
1068
|
+
return { trail, loading, error };
|
|
1069
|
+
}
|
|
1070
|
+
|
|
903
1071
|
// src/react/hooks/use-track-motion.ts
|
|
904
|
-
import { useEffect, useMemo as useMemo2, useRef } from "react";
|
|
1072
|
+
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
905
1073
|
|
|
906
1074
|
// src/shared/motion/index.ts
|
|
907
1075
|
var KT_TO_MS = 0.514444;
|
|
@@ -1210,10 +1378,10 @@ function createTrackMotion(options = {}) {
|
|
|
1210
1378
|
// src/react/hooks/use-track-motion.ts
|
|
1211
1379
|
function useTrackMotion(options) {
|
|
1212
1380
|
const { onFrame, ...motionOptions } = options;
|
|
1213
|
-
const onFrameRef =
|
|
1381
|
+
const onFrameRef = useRef2(onFrame);
|
|
1214
1382
|
onFrameRef.current = onFrame;
|
|
1215
1383
|
const motion = useMemo2(() => createTrackMotion(motionOptions), []);
|
|
1216
|
-
|
|
1384
|
+
useEffect2(() => {
|
|
1217
1385
|
const stop = motion.start((frame) => onFrameRef.current(frame));
|
|
1218
1386
|
return () => {
|
|
1219
1387
|
stop();
|
|
@@ -1224,7 +1392,7 @@ function useTrackMotion(options) {
|
|
|
1224
1392
|
}
|
|
1225
1393
|
|
|
1226
1394
|
// src/react/hooks/use-track-history.ts
|
|
1227
|
-
import { useCallback, useEffect as
|
|
1395
|
+
import { useCallback, useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
1228
1396
|
var DR_MIN_SPEED_KT = 10;
|
|
1229
1397
|
var MAX_SPEED_KT = 1200;
|
|
1230
1398
|
var GAP_RESET_MS = 3e4;
|
|
@@ -1244,22 +1412,22 @@ function useTrackHistory(options) {
|
|
|
1244
1412
|
maxJumpDeg = 5,
|
|
1245
1413
|
onFrame
|
|
1246
1414
|
} = options;
|
|
1247
|
-
const currentPositions =
|
|
1248
|
-
const lastActualPos =
|
|
1249
|
-
const history =
|
|
1250
|
-
const lastSeen =
|
|
1251
|
-
const lastUpdPerf =
|
|
1252
|
-
const interp =
|
|
1253
|
-
const velocity =
|
|
1254
|
-
const drState =
|
|
1255
|
-
const rafRef =
|
|
1256
|
-
const onFrameRef =
|
|
1415
|
+
const currentPositions = useRef3(/* @__PURE__ */ new Map());
|
|
1416
|
+
const lastActualPos = useRef3(/* @__PURE__ */ new Map());
|
|
1417
|
+
const history = useRef3(/* @__PURE__ */ new Map());
|
|
1418
|
+
const lastSeen = useRef3(/* @__PURE__ */ new Map());
|
|
1419
|
+
const lastUpdPerf = useRef3(/* @__PURE__ */ new Map());
|
|
1420
|
+
const interp = useRef3(/* @__PURE__ */ new Map());
|
|
1421
|
+
const velocity = useRef3(/* @__PURE__ */ new Map());
|
|
1422
|
+
const drState = useRef3(/* @__PURE__ */ new Map());
|
|
1423
|
+
const rafRef = useRef3(null);
|
|
1424
|
+
const onFrameRef = useRef3(onFrame);
|
|
1257
1425
|
onFrameRef.current = onFrame;
|
|
1258
|
-
const maxHistoryPointsRef =
|
|
1426
|
+
const maxHistoryPointsRef = useRef3(maxHistoryPoints);
|
|
1259
1427
|
maxHistoryPointsRef.current = maxHistoryPoints;
|
|
1260
|
-
const minDeltaDegRef =
|
|
1428
|
+
const minDeltaDegRef = useRef3(minDeltaDeg);
|
|
1261
1429
|
minDeltaDegRef.current = minDeltaDeg;
|
|
1262
|
-
const maxJumpDegRef =
|
|
1430
|
+
const maxJumpDegRef = useRef3(maxJumpDeg);
|
|
1263
1431
|
maxJumpDegRef.current = maxJumpDeg;
|
|
1264
1432
|
function emitFrame() {
|
|
1265
1433
|
onFrameRef.current({
|
|
@@ -1422,7 +1590,7 @@ function useTrackHistory(options) {
|
|
|
1422
1590
|
drState.current.delete(trackId);
|
|
1423
1591
|
emitFrame();
|
|
1424
1592
|
}, []);
|
|
1425
|
-
|
|
1593
|
+
useEffect3(() => {
|
|
1426
1594
|
return () => {
|
|
1427
1595
|
if (rafRef.current !== null) {
|
|
1428
1596
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -1437,7 +1605,7 @@ function useTrackHistory(options) {
|
|
|
1437
1605
|
}
|
|
1438
1606
|
|
|
1439
1607
|
// src/react/hooks/index.ts
|
|
1440
|
-
import { useCallback as useCallback2, useEffect as
|
|
1608
|
+
import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4 } from "react";
|
|
1441
1609
|
import { useQuery } from "@tanstack/react-query";
|
|
1442
1610
|
import { useShallow } from "zustand/react/shallow";
|
|
1443
1611
|
|
|
@@ -1537,7 +1705,7 @@ function useAeroTracks(opts = {}) {
|
|
|
1537
1705
|
const isDegraded = useTrackStore(selectIsDegraded);
|
|
1538
1706
|
const lastUpdateAt = useTrackStore(selectLastUpdateAt);
|
|
1539
1707
|
const error = useTrackStore(selectError);
|
|
1540
|
-
const unsubRef =
|
|
1708
|
+
const unsubRef = useRef4(null);
|
|
1541
1709
|
const { isLoading } = useQuery({
|
|
1542
1710
|
queryKey: ["aero-snapshot", opts.bbox?.join(","), opts.source, opts.minConfidence],
|
|
1543
1711
|
enabled: opts.enabled !== false,
|
|
@@ -1551,7 +1719,7 @@ function useAeroTracks(opts = {}) {
|
|
|
1551
1719
|
return result;
|
|
1552
1720
|
}
|
|
1553
1721
|
});
|
|
1554
|
-
|
|
1722
|
+
useEffect4(() => {
|
|
1555
1723
|
if (opts.enabled === false) return;
|
|
1556
1724
|
const actions = useTrackStore.getState();
|
|
1557
1725
|
unsubRef.current = client.tracks.subscribe(
|
|
@@ -1688,6 +1856,7 @@ export {
|
|
|
1688
1856
|
useTrackHistory,
|
|
1689
1857
|
useTrackMotion,
|
|
1690
1858
|
useTrackStore,
|
|
1859
|
+
useTrackTrail,
|
|
1691
1860
|
useTracks,
|
|
1692
1861
|
useWebhooks
|
|
1693
1862
|
};
|