@skyvexsoftware/stratos-sdk 0.8.0 → 0.9.0

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.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Aircraft size classification used by the landing analyser (and any future
3
+ * scorer) to apply class-aware thresholds — a 1000 fpm descent at 50 ft is
4
+ * marginal in a narrowbody but a clear bust in a widebody, etc.
5
+ */
6
+ export type AircraftClass = "widebody" | "narrowbody" | "regional";
7
+ /**
8
+ * Classify an ICAO aircraft type into widebody / narrowbody / regional.
9
+ * Returns "narrowbody" when the type is unknown or unparseable — it's the
10
+ * most common class and yields sensible (non-overly-strict) thresholds.
11
+ */
12
+ export declare function classifyAircraft(aircraftType: string | null | undefined): AircraftClass;
13
+ //# sourceMappingURL=aircraft.d.ts.map
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Aircraft size classification used by the landing analyser (and any future
3
+ * scorer) to apply class-aware thresholds — a 1000 fpm descent at 50 ft is
4
+ * marginal in a narrowbody but a clear bust in a widebody, etc.
5
+ */
6
+ /**
7
+ * Known ICAO type designators by class. Lists are deliberately
8
+ * conservative — when a type doesn't match, we fall back to narrowbody as the
9
+ * least-restrictive sensible default.
10
+ */
11
+ const WIDEBODY_TYPES = [
12
+ "A306",
13
+ "A30B",
14
+ "A310",
15
+ "A330",
16
+ "A332",
17
+ "A333",
18
+ "A338",
19
+ "A339",
20
+ "A340",
21
+ "A342",
22
+ "A343",
23
+ "A345",
24
+ "A346",
25
+ "A350",
26
+ "A359",
27
+ "A35K",
28
+ "A380",
29
+ "A388",
30
+ "B742",
31
+ "B743",
32
+ "B744",
33
+ "B748",
34
+ "B74F",
35
+ "B752",
36
+ "B753",
37
+ "B762",
38
+ "B763",
39
+ "B764",
40
+ "B772",
41
+ "B773",
42
+ "B77F",
43
+ "B77L",
44
+ "B77W",
45
+ "B788",
46
+ "B789",
47
+ "B78X",
48
+ "DC10",
49
+ "IL96",
50
+ "MD11",
51
+ ];
52
+ const REGIONAL_TYPES = [
53
+ "AT42",
54
+ "AT43",
55
+ "AT45",
56
+ "AT46",
57
+ "AT72",
58
+ "AT75",
59
+ "AT76",
60
+ "ATR",
61
+ "ATR42",
62
+ "ATR72",
63
+ "BCS1",
64
+ "BCS3",
65
+ "C25A",
66
+ "C56X",
67
+ "CRJ",
68
+ "CRJ1",
69
+ "CRJ2",
70
+ "CRJ7",
71
+ "CRJ9",
72
+ "CRJX",
73
+ "CRJ200",
74
+ "CRJ700",
75
+ "CRJ900",
76
+ "CRJ1000",
77
+ "DH8A",
78
+ "DH8B",
79
+ "DH8C",
80
+ "DH8D",
81
+ "DHC8",
82
+ "E135",
83
+ "E140",
84
+ "E145",
85
+ "E170",
86
+ "E175",
87
+ "E190",
88
+ "E195",
89
+ "E75L",
90
+ "E75S",
91
+ "ERJ",
92
+ "ERJ135",
93
+ "ERJ140",
94
+ "ERJ145",
95
+ "SB20",
96
+ "SF34",
97
+ "SF50",
98
+ ];
99
+ /** Strip whitespace and non-alphanumeric chars and uppercase. */
100
+ function normalizeType(aircraftType) {
101
+ if (!aircraftType)
102
+ return null;
103
+ const value = aircraftType.toUpperCase().trim();
104
+ if (!value || value === "—")
105
+ return null;
106
+ return value.replace(/[^A-Z0-9]/g, "");
107
+ }
108
+ /**
109
+ * Classify an ICAO aircraft type into widebody / narrowbody / regional.
110
+ * Returns "narrowbody" when the type is unknown or unparseable — it's the
111
+ * most common class and yields sensible (non-overly-strict) thresholds.
112
+ */
113
+ export function classifyAircraft(aircraftType) {
114
+ const normalized = normalizeType(aircraftType);
115
+ if (!normalized)
116
+ return "narrowbody";
117
+ if (WIDEBODY_TYPES.includes(normalized))
118
+ return "widebody";
119
+ if (REGIONAL_TYPES.includes(normalized))
120
+ return "regional";
121
+ for (const prefix of WIDEBODY_TYPES) {
122
+ if (normalized.startsWith(prefix))
123
+ return "widebody";
124
+ }
125
+ for (const prefix of REGIONAL_TYPES) {
126
+ if (normalized.startsWith(prefix))
127
+ return "regional";
128
+ }
129
+ return "narrowbody";
130
+ }
@@ -2,4 +2,6 @@ export { createPlugin } from "./createPlugin";
2
2
  export { STRATOS_APP_PORT, STRATOS_APP_BASE } from "./server";
3
3
  export { weightToLbs, weightFromLbs, altitudeToFt, altitudeFromFt, verticalSpeedFromFpm, verticalSpeedToFpm, distanceToNm, distanceFromNm, formatWeight, formatAltitude, formatDistance, formatVerticalSpeed, } from "./units";
4
4
  export type { WeightUnit, AltitudeUnit, DistanceUnit, UnitPreferences, } from "./units";
5
+ export { classifyAircraft } from "./aircraft";
6
+ export type { AircraftClass } from "./aircraft";
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,4 @@
1
1
  export { createPlugin } from "./createPlugin";
2
2
  export { STRATOS_APP_PORT, STRATOS_APP_BASE } from "./server";
3
3
  export { weightToLbs, weightFromLbs, altitudeToFt, altitudeFromFt, verticalSpeedFromFpm, verticalSpeedToFpm, distanceToNm, distanceFromNm, formatWeight, formatAltitude, formatDistance, formatVerticalSpeed, } from "./units";
4
+ export { classifyAircraft } from "./aircraft";
@@ -34,12 +34,16 @@ async function fetchFlightEvents() {
34
34
  export function useFlightEvents(options) {
35
35
  const queryClient = useQueryClient();
36
36
  const { socket } = useSocket();
37
- const queryKey = flightEventsKeys.log(options?.flightId);
37
+ // Memoized so the effect below can list queryKey in its deps without
38
+ // reattaching socket listeners on every render.
39
+ const queryKey = useMemo(() => flightEventsKeys.log(options?.flightId), [options?.flightId]);
38
40
  const { data, isLoading } = useQuery({
39
41
  queryKey,
40
42
  queryFn: fetchFlightEvents,
41
43
  staleTime: Infinity,
42
- refetchOnWindowFocus: false,
44
+ // "always" (not true) because staleTime: Infinity would otherwise
45
+ // suppress the default focus refetch.
46
+ refetchOnWindowFocus: "always",
43
47
  refetchOnReconnect: false,
44
48
  });
45
49
  // Subscribe to Socket.io events and append new events to the cache
@@ -56,11 +60,18 @@ export function useFlightEvents(options) {
56
60
  };
57
61
  });
58
62
  };
63
+ // Refetch on (re)connect — broadcasts during a disconnect window
64
+ // are lost from the append-only cache, so we resync from the DB.
65
+ const handleConnect = () => {
66
+ void queryClient.invalidateQueries({ queryKey });
67
+ };
59
68
  socket.on(SOCKET_EVENTS.FLIGHT_EVENT, handleEvent);
69
+ socket.on("connect", handleConnect);
60
70
  return () => {
61
71
  socket.off(SOCKET_EVENTS.FLIGHT_EVENT, handleEvent);
72
+ socket.off("connect", handleConnect);
62
73
  };
63
- }, [socket, queryClient, options?.flightId]);
74
+ }, [socket, queryClient, queryKey]);
64
75
  // ── Comment Mutations ──────────────────────────────────────────────
65
76
  const addCommentMutation = useMutation({
66
77
  mutationFn: async (message) => {
@@ -68,7 +68,9 @@ class SocketManager {
68
68
  this.socket = io(STRATOS_APP_BASE, {
69
69
  transports: ["websocket", "polling"],
70
70
  reconnection: true,
71
- reconnectionAttempts: 5,
71
+ // Default Infinity — the shell is long-running and a user who
72
+ // alt-tabs for a minute should still find a live socket on return.
73
+ reconnectionAttempts: Infinity,
72
74
  reconnectionDelay: 1000,
73
75
  autoConnect: true,
74
76
  });
@@ -41,7 +41,10 @@ export function useTrackingSession() {
41
41
  queryKey: trackingSessionKeys.state,
42
42
  queryFn: fetchFlightManagerState,
43
43
  staleTime: Infinity,
44
- refetchOnWindowFocus: false,
44
+ // "always" (not true) because staleTime: Infinity would otherwise
45
+ // suppress the default focus refetch. Defensive against a socket
46
+ // that appears connected but stopped delivering broadcasts.
47
+ refetchOnWindowFocus: "always",
45
48
  refetchOnReconnect: false,
46
49
  });
47
50
  useEffect(() => {
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export type { PluginAuthor, PluginManifest } from "./types/manifest";
2
2
  export type { PluginAirline, PluginAirlineAccessor, PluginAuthAccessor, PluginVaApiClient, PluginConfigStore, PluginContext, PluginDatabaseAccessor, PluginIPCRegistrar, PluginLogger, PluginNavigationHelper, PluginPilotUser, PluginServerRegistrar, PluginToastAPI, PluginUIContext, PluginSettingType, PluginSettingOption, PluginSettingDefinition, PluginAvailableSettings, BooleanSettingDef, TextSettingDef, LongtextSettingDef, NumberSettingDef, RangeSettingDef, ListSettingDef, RadioSettingDef, DateSettingDef, JsonSettingDef, } from "./types/context";
3
3
  export type { PluginBackgroundModule, PluginRouteComponent, PluginUIModule, } from "./types/module";
4
4
  export { FlightPhase, SimulatorType, EventCategory, } from "./shared-types/simulator";
5
- export type { BounceData, CapturePoint, FlightData, FlightDataSnapshot, FlightEventPayload, FlightEventsSnapshot, FlightLandingPayload, FlightLogEvent, FlightPhasePayload, FlightPhaseSnapshot, FlightStateDebugInfo, FlightTrends, HistoryReportEntry, LandingAnalysis, LandingAnalysisSnapshot, LandingAnalyzerDebugState, LandingSample, PendingPhaseTransition, SimDataSnapshot, SimulatorStatus, } from "./shared-types/simulator";
5
+ export type { BounceData, CapturePoint, FlightData, FlightDataSnapshot, FlightEventPayload, FlightEventsSnapshot, FlightLandingPayload, FlightLogEvent, FlightPhasePayload, FlightPhaseSnapshot, FlightStateDebugInfo, FlightTrends, GateCapture, HistoryReportEntry, LandingAnalysis, LandingAnalysisSnapshot, LandingAnalyzerDebugState, LandingSample, PendingPhaseTransition, SimDataSnapshot, SimulatorStatus, } from "./shared-types/simulator";
6
6
  export type { ThemeMode } from "./shared-types/theme";
7
7
  export type { FlightPlan, FlightStatus, CurrentFlight, FlightComment, PreflightCheck, PreflightCheckResult, StartFlightOptions, FlightManagerPayload, StartFlightResult, RecoverableFlight, } from "./shared-types/flight-manager";
8
8
  export { createPlugin } from "./helpers/createPlugin";
@@ -10,6 +10,8 @@ export { STRATOS_APP_PORT, STRATOS_APP_BASE } from "./helpers/server";
10
10
  export { vaApi } from "./api/vaApi";
11
11
  export { weightToLbs, weightFromLbs, altitudeToFt, altitudeFromFt, verticalSpeedFromFpm, verticalSpeedToFpm, distanceToNm, distanceFromNm, formatWeight, formatAltitude, formatDistance, formatVerticalSpeed, } from "./helpers/units";
12
12
  export type { WeightUnit, AltitudeUnit, DistanceUnit, UnitPreferences, } from "./helpers/units";
13
+ export { classifyAircraft } from "./helpers/aircraft";
14
+ export type { AircraftClass } from "./helpers/aircraft";
13
15
  export { PluginUICtx, usePluginContext } from "./hooks/context";
14
16
  export { useFlightEvents, flightEventsKeys } from "./hooks/useFlightEvents";
15
17
  export { useFlightManager } from "./hooks/useFlightManager";
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export { createPlugin } from "./helpers/createPlugin";
5
5
  export { STRATOS_APP_PORT, STRATOS_APP_BASE } from "./helpers/server";
6
6
  export { vaApi } from "./api/vaApi";
7
7
  export { weightToLbs, weightFromLbs, altitudeToFt, altitudeFromFt, verticalSpeedFromFpm, verticalSpeedToFpm, distanceToNm, distanceFromNm, formatWeight, formatAltitude, formatDistance, formatVerticalSpeed, } from "./helpers/units";
8
+ export { classifyAircraft } from "./helpers/aircraft";
8
9
  // ── React Hooks ────────────────────────────────────────────────────────
9
10
  export { PluginUICtx, usePluginContext } from "./hooks/context";
10
11
  export { useFlightEvents, flightEventsKeys } from "./hooks/useFlightEvents";
@@ -1,5 +1,5 @@
1
1
  export { FlightPhase, SimulatorType, EventCategory } from "./simulator";
2
- export type { BounceData, CapturePoint, FlightData, FlightDataSnapshot, FlightEventPayload, FlightEventsSnapshot, FlightLandingPayload, FlightLogEvent, FlightPhasePayload, FlightPhaseSnapshot, FlightStateDebugInfo, FlightTrends, HistoryReportEntry, LandingAnalysis, LandingAnalysisSnapshot, LandingAnalyzerDebugState, PendingPhaseTransition, SimDataSnapshot, SimulatorStatus, } from "./simulator";
2
+ export type { BounceData, CapturePoint, FlightData, FlightDataSnapshot, FlightEventPayload, FlightEventsSnapshot, FlightLandingPayload, FlightLogEvent, FlightPhasePayload, FlightPhaseSnapshot, FlightStateDebugInfo, FlightTrends, GateCapture, HistoryReportEntry, LandingAnalysis, LandingAnalysisSnapshot, LandingAnalyzerDebugState, PendingPhaseTransition, SimDataSnapshot, SimulatorStatus, } from "./simulator";
3
3
  export type { ThemeMode } from "./theme";
4
4
  export type { FlightPlan, FlightStatus, CurrentFlight, FlightComment, PreflightCheck, PreflightCheckResult, StartFlightOptions, FlightManagerPayload, StartFlightResult, RecoverableFlight, } from "./flight-manager";
5
5
  export { SOCKET_EVENTS } from "./socket-events";
@@ -266,6 +266,17 @@ export type BounceData = {
266
266
  touchdownGForce: number;
267
267
  durationMs: number;
268
268
  };
269
+ /** Snapshot of aircraft state at the 50 ft stabilized-approach gate */
270
+ export type GateCapture = {
271
+ /** Actual AGL at capture, may be a few ft above/below the nominal 50 ft */
272
+ altitudeAgl: number;
273
+ indicatedAirspeed: number;
274
+ verticalSpeed: number;
275
+ bank: number;
276
+ pitch: number;
277
+ groundSpeed: number;
278
+ timestamp: number;
279
+ };
269
280
  /** Complete landing analysis result */
270
281
  export type LandingAnalysis = {
271
282
  landingRateFpm: number;
@@ -281,7 +292,15 @@ export type LandingAnalysis = {
281
292
  approachAltitude: number;
282
293
  approachIas: number;
283
294
  approachVs: number;
295
+ approachBank: number;
296
+ approachPitch: number;
284
297
  approachTimestamp: number;
298
+ /**
299
+ * Snapshot at ~50 ft AGL on final, used for the stabilized-approach gate.
300
+ * Null when the capture was missed (e.g. very fast descent, sim disconnect
301
+ * right before touchdown, or capture armed below 50 ft).
302
+ */
303
+ gateCapture: GateCapture | null;
285
304
  descentTimeSeconds: number;
286
305
  averageDescentRateFpm: number;
287
306
  bounceCount: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyvexsoftware/stratos-sdk",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Plugin SDK for Stratos — types, hooks, and UI components",
5
5
  "author": {
6
6
  "name": "Skyvex Software Pty Ltd",