AdditionalFieldsConfig
export type AdditionalFieldsConfig = Record<string, FieldDelivery>;
AircraftClass
export type AircraftClass = "widebody" | "narrowbody" | "regional";
AltitudeUnit
export type AltitudeUnit = "ft" | "m";
AutoStartConfirmPayload
export type AutoStartConfirmPayload = {
/** VA-issued confirmation message. */
confirmation: string;
/** Current sim variables for display. */
simVariables: Record<string, string>;
/** The flight plan built from the matched booking. */
flightPlan: FlightPlan;
/** Booking bid_id so the renderer can fetch the full booking. */
bidId: number;
/**
* Per-start override flag from the VA `/flights/start` response (`can_override`).
* Takes priority over the airline's `allowStartOverride` setting. Omitted ⇒ the
* setting applies.
*/
canOverride?: boolean;
};
BadgeProps
export type BadgeProps = React.HTMLAttributes<HTMLDivElement> &
VariantProps<typeof badgeVariants>;
BooleanSettingDef
export type BooleanSettingDef = PluginSettingBase & {
type: "boolean";
default?: boolean;
};
BounceData
export type BounceData = {
bounceNumber: number;
timestamp: number;
maxAltitudeAgl: number;
touchdownVerticalSpeed: number;
touchdownGForce: number;
durationMs: number;
};
BuiltInSound
export type BuiltInSound = "attention" | "error" | "success";
ButtonProps
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
};
CapturePoint
export type CapturePoint = {
timestamp: number;
altitude: number;
altitudeAgl: number;
verticalSpeed: number;
groundSpeed: number;
indicatedAirspeed: number;
latitude: number;
longitude: number;
pitch: number;
bank: number;
gForce: number;
heading: number;
glideslopeDeviation: number | null;
localizerDeviation: number | null;
flapsControl: number;
windDirection: number;
windSpeed: number;
totalFuelLbs: number;
/** Snapshot of data.flapsHandleIndex at the capture moment, null if the sim doesn't provide it. */
flapsHandleIndex: number | null;
};
ComboboxOption
export type ComboboxOption = {
value: string;
label: string;
/**
* Optional heading to list this option under. Groups render in the order
* they are first seen; options without a group render first, ungrouped.
* Omit on every option for a flat list.
*/
group?: string;
};
ComboboxProps
export type ComboboxProps = {
options: ComboboxOption[];
value?: string;
onValueChange?: (value: string) => void;
/** Trigger text shown when nothing is selected. */
placeholder?: string;
/** Placeholder inside the search field. */
searchPlaceholder?: string;
/** Shown when the search filter matches nothing. */
emptyText?: string;
/** Render a loading row instead of the list (e.g. while options are fetched). */
loading?: boolean;
loadingText?: string;
disabled?: boolean;
triggerClassName?: string;
contentClassName?: string;
align?: "start" | "center" | "end";
onOpenChange?: (open: boolean) => void;
/** Element id applied to the trigger button. */
id?: string;
/** Accessible name for the trigger button (and the search field inside the popover). */
"aria-label"?: string;
/** Id of an element that labels the trigger button. */
"aria-labelledby"?: string;
};
ConnectionState
export type ConnectionState =
| "connecting"
| "connected"
| "disconnected"
| "error";
CurrentFlight
export type CurrentFlight = {
flightPlan: FlightPlan;
status: FlightStatus;
vaTrackingId: string | null;
stratosFlightId: string | null;
startedAt: number | null;
pausedAt: number | null;
totalPausedMs: number;
elapsedMs: number;
timeCaptureMode: "block" | "air";
timeCaptureStartAt: number | null;
timeCaptureEndAt: number | null;
lastTickTime: number | null;
currentPhase: FlightPhase;
latitude: number;
longitude: number;
altitude: number;
groundSpeed: number;
indicatedAirspeed: number;
heading: number;
startingFuel: number;
/** Zero fuel weight (lbs), captured at the same phase as startingFuel. */
startingZfw: number;
currentFuel: number;
landingFuel: number | null;
/** Fuel burned on previously-completed legs of a multi-leg flight (refuel stops, helo repeated ops). Folded into the final fuel_used at submission. */
fuelUsedPriorLegs: number;
lastUpdateSentAt: number | null;
updatesSent: number;
updateIntervalMs: number;
comments: FlightComment[];
/**
* Pilot-reported online network for this flight (e.g. "vatsim").
* "offline" by default; resets on every flight start.
*/
network: string;
lastError: string | null;
};
DateSettingDef
export type DateSettingDef = PluginSettingBase & {
type: "date";
default?: string;
};
DistanceUnit
export type DistanceUnit = "nm" | "km" | "mi";
EventCategory
export enum EventCategory {
SYSTEM = "system",
PHASE = "phase",
CONTROLS = "controls",
ENGINE = "engine",
WARNING = "warning",
POSITION = "position",
COMMENT = "comment",
}
FieldDelivery
export type FieldDelivery = {
phases: string[];
submission: boolean;
};
FlightComment
export type FlightComment = {
id: string;
message: string;
timestamp: number;
phase: FlightPhase;
};
FlightData
export type FlightData = {
// Position & Navigation
latitude: number;
longitude: number;
altitude: number;
altitudeAgl: number;
altitudeAglGear: number;
groundSpeed: number;
indicatedAirspeed: number;
trueAirspeed: number;
verticalSpeed: number;
// Orientation
/**
* Heading against **true** north, in degrees. This is what every simulator
* reports natively, and what the client's geometry depends on: runway
* matching compares it against a runway's true bearing, and the tracking map
* is true-north-up. It is *not* what a pilot sees on the ND, so use
* `headingMagnetic` for anything displayed alongside cockpit instruments.
*/
heading: number;
/**
* Heading against **magnetic** north, in degrees. That is the reference the
* ND, the compass and the runway number all use, so this is the one to show
* a pilot. Derived from `heading` and `magVar`, and equal to `heading` when
* the sim has not reported a variation yet. Optional until every sim client
* populates it.
*/
headingMagnetic?: number;
/**
* Magnetic variation at the aircraft's position, in degrees, east-positive
* (`magnetic = true - magVar`). Zero when the sim has not reported one yet.
* MSFS: the MAGVAR SimVar. X-Plane: recovered from the gap between the true
* and magnetic heading datarefs. Optional until every sim client populates
* it.
*/
magVar?: number;
pitch: number;
bank: number;
// Flight Controls & Aircraft State
gearControl: boolean;
flapsControl: number;
flapsLeftPosition: number;
flapsRightPosition: number;
planeOnground: boolean;
// Engine Data
enginesCount: number;
engine1Firing: boolean;
engine2Firing: boolean;
engine3Firing: boolean;
engine4Firing: boolean;
engine1N1: number;
engine2N1: number;
engine3N1: number;
engine4N1: number;
engine1N2: number;
engine2N2: number;
engine3N2: number;
engine4N2: number;
engine1PctRpm: number;
engine2PctRpm: number;
engine3PctRpm: number;
engine4PctRpm: number;
// Composite engine state (shaft spinning above idle threshold)
engine1On: boolean;
engine2On: boolean;
engine3On: boolean;
engine4On: boolean;
// Fuel
fuelTotalQuantityWeight: number;
fuelUsed: number;
// Weather
windDirection: number;
windSpeed: number;
pressureQNH: number;
altimeterSettings: number;
// Time (Simulation)
clockHour: number;
clockMin: number;
clockSec: number;
zuluHour: number;
zuluMin: number;
zuluDayOfMonth: number;
zuluMonthOfYear: number;
zuluYear: number;
localDayOfMonth: number;
localMonthOfYear: number;
localYear: number;
// Aircraft Information
aircraftType: string;
aircraftIcao: string;
aircraftLivery: string;
aircraftEmptyWeight: number;
zeroWeightPlusPayload: number;
// Simulator State
simulatorVersion: string;
pauseFlag: boolean;
slewMode: boolean;
simulationRate: number;
/**
* Achieved time-compression multiplier to integrate for flight time, when it
* differs from the requested `simulationRate`. X-Plane sets this from
* `sim/time/sim_speed_actual_ogl` — the rate actually achieved after fps
* limiting and sub-19fps time dilation — so compressed flight time tracks the
* sim clock that really advanced, not the rate the pilot requested. Left
* undefined on sims with no achieved-rate source (MSFS/P3D/FSX), where the
* requested `simulationRate` is authoritative.
*/
simulationRateActual?: number;
isInMenu: boolean;
isXPlane: boolean;
isHelicopter: boolean;
// Warning States
stallWarning: boolean;
overspeedWarning: boolean;
crashed: boolean;
// Landing Analysis
gForceTouchDown: number;
landingRate: number;
touchdownNormalVelocity: number;
touchdownLatitude: number;
touchdownLongitude: number;
landingDistance: number;
// ILS Approach (optional until all sim clients populate them)
glideslopeDeviation?: number;
localizerDeviation?: number;
hasGlideslope?: boolean;
// Landing Systems (optional until all sim clients populate them)
reverser1Deployed?: boolean;
reverser2Deployed?: boolean;
autobrakePosition?: number;
speedbrakePosition?: number;
// New PIREP capture inputs (optional — populated by sim clients that support them)
/** Unlimited-fuel mode flag. MSFS: UNLIMITED FUEL SimVar. X-Plane: undefined (no clean dataref). */
unlimitedFuel?: boolean;
/** Detent index of the flap handle. MSFS: FLAPS HANDLE INDEX. X-Plane: derived from flaprqst × acf_flap_detents. */
flapsHandleIndex?: number;
// Exterior lights (optional: undefined until the sim client reports them,
// or when the sim has no source for that light).
// MSFS/P3D/FSX: the plain `LIGHT <NAME>` SimVars. X-Plane: the
// `sim/cockpit2/switches/*` light switch DataRefs. What they mean depends on
// the aircraft: some report the switch position (PMDG, X-Plane), others
// whether the light is actually lit (Fenix, so strobe AUTO and gear-mounted
// lights follow the aircraft's own logic). Aircraft without a given light
// can still report it as on, so treat per-aircraft applicability as the VA's
// call.
/** Beacon light on. */
lightBeacon?: boolean;
/** Strobe lights on. */
lightStrobe?: boolean;
/** Navigation (position) lights on. */
lightNav?: boolean;
/** Logo lights on. MSFS/P3D/FSX only (X-Plane aircraft wire logo to aircraft-specific generic lights). */
lightLogo?: boolean;
/** Wing inspection lights on. MSFS/P3D/FSX only (see `lightLogo`). */
lightWing?: boolean;
/** Any landing light on (including a nose light in its take-off position on some aircraft). */
lightLanding?: boolean;
/**
* Any taxi light on. Some aircraft also report runway turnoff lights here.
* MSFS/P3D/FSX only: X-Plane's taxi switch DataRef doesn't follow the switch
* on many airliners (default XP11 737, XP12 A330).
*/
lightTaxi?: boolean;
/**
* Landing light circuits 1-3 (`LIGHT LANDING:n`), MSFS 2020/2024 only. On
* the default airliners and the Fenix A320, 1 = nose, 2 = left, 3 = right;
* single-light GA aircraft use 1 only; some add-ons (PMDG) set all three
* together. Undefined on P3D, FSX and X-Plane.
*/
lightLanding1?: boolean;
lightLanding2?: boolean;
lightLanding3?: boolean;
// Navigation & Flight Plan
transponderFreq: string;
com1Freq: string;
com2Freq: string;
nav1Freq: string;
nav2Freq: string;
milesToGo: number;
cruiseAltitude: number;
// Flight Phase & Analysis
phase: FlightPhase;
phaseString: string;
// System timestamp (not simulation time)
timestamp: number;
// Update counter to force React re-renders
updateCounter?: number;
// Debug info from FlightStateManager (optional, for debug panel)
_debug?: FlightStateDebugInfo;
};
FlightDataSnapshot
export type FlightDataSnapshot = {
timestamp: number;
altitude: number;
altitudeAgl: number;
groundSpeed: number;
verticalSpeed: number;
heading: number;
latitude: number;
longitude: number;
planeOnground: boolean;
pitch: number;
engine1N1: number;
engine2N1: number;
engine3N1: number;
engine4N1: number;
};
FlightEventPayload
export type FlightEventPayload = {
event: FlightLogEvent;
timestamp: number;
};
FlightEventsSnapshot
export type FlightEventsSnapshot = {
events: FlightLogEvent[];
isTracking: boolean;
elapsedTime: number;
};
FlightLandingPayload
export type FlightLandingPayload = {
type: "touchdown" | "bounce" | "settled";
timestamp: number;
landingAnalysis?: LandingAnalysis;
bounceData?: BounceData;
};
FlightLogEvent
export type FlightLogEvent = {
eventId: string;
eventTimestamp: string;
eventElapsedTime: number;
eventCondition: string;
category: EventCategory;
message: string;
data?: Record<string, unknown>;
};
FlightManagerPayload
export type FlightManagerPayload = {
flight: CurrentFlight | null;
pendingRecovery: RecoverableFlight | null;
/** True when the flight was paused due to simulator disconnection. */
simDisconnected: boolean;
/**
* True while the sim is dropped mid-flight and we are silently waiting for it
* to come back (the "reconnecting" pending state). The flight stays active;
* the shell surfaces the recovery dialog off this flag so a genuine crash can
* never resume silently without the pilot seeing it.
*/
reconnecting: boolean;
/** Why tracking is suspended (continuity gate asserted a discontinuity), or null. Present whenever `simDisconnected` is true. */
suspendReason?: string | null;
/**
* Why auto-start has not started a flight yet, as a sentence fragment
* ("the aircraft is 260 nm from CYUL, the nearest booked departure"), or
* null when auto-start is off, not polling, or a flight is active. Absent
* on shells before core 2.32.17.
*/
autoStartWaitReason?: string | null;
timestamp: number;
};
FlightPhase
export enum FlightPhase {
UNKNOWN = "unknown",
BOARDING = "boarding",
PUSH_BACK = "push_back",
TAXI = "taxi",
TAKE_OFF = "take_off",
REJECTED_TAKE_OFF = "rejected_take_off",
CLIMB = "climb",
CRUISE = "cruise",
DESCENT = "descent",
APPROACH = "approach",
FINAL = "final",
LANDED = "landed",
GO_AROUND = "go_around",
TAXI_IN = "taxi_in",
ARRIVED = "arrived",
DEBOARDING = "deboarding",
}
FlightPhasePayload
export type FlightPhasePayload = {
previousPhase: FlightPhase;
currentPhase: FlightPhase;
timestamp: number;
pendingTransition?: PendingPhaseTransition;
};
FlightPhaseSnapshot
export type FlightPhaseSnapshot = {
currentPhase: FlightPhase;
previousPhase: FlightPhase | null;
pendingTransition: PendingPhaseTransition | null;
trends: FlightTrends | null;
isTracking: boolean;
};
FlightPlan
export type FlightPlan = {
source: "va" | "simbrief" | "manual";
sourceId?: string;
callsign: string;
flightNumber?: string;
airlineCode?: string;
departureIcao: string;
arrivalIcao: string;
route: string[];
aircraftIcao: string;
aircraftName?: string;
cruiseAltitude?: number;
plannedDistance?: number;
estimatedFlightTime?: number;
departureCoords?: { lat: number; lon: number };
arrivalCoords?: { lat: number; lon: number };
};
FlightStateDebugInfo
export type FlightStateDebugInfo = {
trends: FlightTrends;
pendingTransition: {
phase: string;
startTime: number;
elapsedSeconds: number;
requiredDelay: number;
altDelta?: number;
altGate?: number;
} | null;
levelFlight: {
beganAt: number | null;
durationSeconds: number | null;
startAlt: number | null;
altDelta: number | null;
};
samples: {
timestamp: number;
altitude: number;
verticalSpeed: number;
groundSpeed: number;
}[];
detectedCruiseAlt: number;
};
FlightStatus
export type FlightStatus =
| "preflight"
| "starting"
| "active"
| "paused"
| "completing"
| "completed"
| "failed"
| "cancelled";
FlightTrends
export type FlightTrends = {
/** Average vertical speed over sample window (fpm) */
avgVerticalSpeed: number;
/** Average ground speed over sample window (knots) */
avgGroundSpeed: number;
/** Altitude change rate calculated from actual altitude difference (fpm) */
altitudeChangeRate: number;
/** True if consistently climbing (positive altitude trend) */
isClimbing: boolean;
/** True if consistently descending (negative altitude trend) */
isDescending: boolean;
/** True if relatively level flight */
isLevel: boolean;
/** True if accelerating on ground (potential takeoff roll) */
isAccelerating: boolean;
/** True if decelerating on ground (potential landing rollout) */
isDecelerating: boolean;
/** Highest N1 across all engines */
highestN1: number;
/** True if any engine has significant thrust */
hasThrust: boolean;
/** Number of samples in the window */
sampleCount: number;
/** Time span of samples in ms */
timeSpanMs: number;
/** True if we have enough samples for reliable trend detection */
hasReliableData: boolean;
};
GateCapture
export type GateCapture = {
/** Actual AGL at capture, may be a few ft above/below the nominal 50 ft */
altitudeAgl: number;
indicatedAirspeed: number;
verticalSpeed: number;
bank: number;
pitch: number;
groundSpeed: number;
timestamp: number;
};
HistoryReportEntry
export type HistoryReportEntry = {
timestamp: number;
elapsedTime: number;
latitude: number;
longitude: number;
altitude: number;
altitudeAgl: number;
heading: number;
/**
* Magnetic heading (what the cockpit instruments show). `heading` is true.
* Null when the simulator did not report it, and for samples recorded
* before this field existed. Undefined from a core that predates it.
*/
headingMagnetic?: number | null;
groundSpeed: number;
indicatedAirspeed: number;
verticalSpeed: number;
pitch: number;
bank: number;
phase: FlightPhase;
planeOnground: boolean;
landingRate: number | null;
};
JsonSettingDef
export type JsonSettingDef = PluginSettingBase & {
type: "json";
default?: string;
};
LandingAnalysis
export type LandingAnalysis = {
landingRateFpm: number;
simulatorVsAtTouchdown: number;
touchdownTimestamp: number;
touchdownLatitude: number;
touchdownLongitude: number;
touchdownHeading: number;
touchdownGroundSpeed: number;
/** Indicated airspeed at touchdown (knots) — sourced from the touchdown CapturePoint. */
touchdownIas: number;
/** Flaps handle index at touchdown, null if the sim doesn't provide one. */
touchdownFlapsIndex: number | null;
touchdownPitch: number;
touchdownBank: number;
/**
* Scored touchdown vertical-load G. The peak of a framerate-independent,
* time-constant EMA of per-frame gForce over the contact second — not the
* raw spike. This is the value scoring reads.
*/
touchdownGForce: number;
/**
* Forensic raw peak gForce over the same contact window (un-smoothed). Kept
* for diagnostics; NOT used for scoring. Optional/null when no frame-buffer
* samples fell in the window (the scored value then came from the instantaneous
* touchdown-frame G and there is no raw window peak to report).
*/
touchdownGForceRaw?: number | null;
approachAltitude: number;
approachIas: number;
approachVs: number;
approachBank: number;
approachPitch: number;
approachTimestamp: number;
/**
* Snapshot at ~50 ft AGL on final, used for the stabilized-approach gate.
* Null when the capture was missed (e.g. very fast descent, sim disconnect
* right before touchdown, or capture armed below 50 ft).
*/
gateCapture: GateCapture | null;
descentTimeSeconds: number;
averageDescentRateFpm: number;
bounceCount: number;
bounces: BounceData[];
landingDistanceFt: number;
captureQuality: "excellent" | "good" | "fair" | "poor";
captureNotes: string[];
};
LandingAnalysisSnapshot
export type LandingAnalysisSnapshot = {
landingRate: number | null;
bounceCount: number;
touchdowns: BounceData[];
isOnGround: boolean;
approachData: CapturePoint | null;
landingAnalysis: LandingAnalysis | null;
};
LandingAnalyzerDebugState
export type LandingAnalyzerDebugState = {
isArmed: boolean;
hasCapturedApproach: boolean;
hasLanded: boolean;
isLandingSettled: boolean;
isBouncing: boolean;
bounceCount: number;
approachCapture: CapturePoint | null;
touchdownCapture: CapturePoint | null;
lastAirborneVs: number;
sampleCount: number;
samples: { timestamp: number; vs: number; agl: number; aglGear: number }[];
calculatedLandingRate: number | null;
landingAnalysis: LandingAnalysis | null;
};
LandingSample
export type LandingSample = {
timestamp: number;
onGround: boolean;
verticalSpeed: number;
altitudeAgl: number;
altitudeAglGear: number;
gForce: number;
pitch: number;
bank: number;
groundSpeed: number;
/**
* Indicated airspeed (knots) at the sample. Optional: absent on payloads from
* shells older than the frame-IAS fix, in which case consumers fall back to
* `groundSpeed`. When present it is the sim's true `AIRSPEED INDICATED` (MSFS)
* / `indicated_airspeed` (X-Plane), so the FDR IAS column is real IAS rather
* than a copy of groundspeed.
*/
indicatedAirspeed?: number;
latitude: number;
longitude: number;
glideslopeDeviation: number | null;
localizerDeviation: number | null;
};
ListSettingDef
export type ListSettingDef = PluginSettingBase & {
type: "list";
default?: string;
options: PluginSettingOption[];
};
LogEntryPayload
export type LogEntryPayload = {
id: number;
timestamp: string;
level: "debug" | "info" | "warn" | "error";
category: string;
message: string;
data?: string;
};
LongtextSettingDef
export type LongtextSettingDef = PluginSettingBase & {
type: "longtext";
default?: string;
placeholder?: string;
};
NotificationPayload
export type NotificationPayload = {
type: "info" | "success" | "warning" | "error";
title: string;
message: string;
/** Auto-dismiss duration in ms. */
duration?: number;
/** Stable ID for deduplication — toasts with the same ID replace each other. */
id?: string;
};
NumberSettingDef
export type NumberSettingDef = PluginSettingBase & {
type: "number";
default?: number;
min?: number;
max?: number;
step?: number;
};
PendingPhaseTransition
export type PendingPhaseTransition = {
phase: FlightPhase;
elapsed: number;
required: number;
};
PluginAirline
export type PluginAirline = {
/** Airline ID as Stratos knows it */
id: string;
/** Origin of the VA's API (no trailing slash) */
baseUrl: string;
/** Bearer for this airline's API. Null if the VA session has expired. */
token: string | null;
};
PluginAirlineAccessor
export type PluginAirlineAccessor = {
/** Returns the current airline, or null if no VA session is active. */
getCurrent(): Promise<PluginAirline | null>;
/**
* Returns an axios instance pre-configured for this airline's API
* with auto-refresh on 401. Safe to cache for the lifetime of the
* background module.
*/
createClient(): PluginVaApiClient;
};
PluginAuthAccessor
export type PluginAuthAccessor = {
/** Get the current auth token, or null if not authenticated */
getToken(): Promise<string | null>;
/** Check if the user is currently authenticated */
isAuthenticated(): Promise<boolean>;
};
PluginAuthor
export type PluginAuthor = {
/** Unique developer slug (e.g. "skyvex-software") */
id: string;
/** Display name (e.g. "Skyvex Software") */
name: string;
/** Contact email address (optional) */
contact?: string;
};
PluginAvailableSettings
export type PluginAvailableSettings = PluginSettingDefinition[];
PluginBackgroundModule
export type PluginBackgroundModule = {
/** Called during shell startup after auth is initialised */
onStart(ctx: PluginContext): Promise<void>;
/** Called during shell shutdown for graceful teardown */
onStop(ctx: PluginContext): Promise<void>;
};
PluginConfigStore
export type PluginConfigStore = {
get<T>(key: string): Promise<T | undefined>;
get<T>(key: string, defaultValue: T): Promise<T>;
set<T>(key: string, value: T): Promise<void>;
delete(key: string): Promise<void>;
getAll(): Promise<Record<string, unknown>>;
/**
* Listen for the airline changing this plugin's settings. Fires once the
* new values are in place (`get` already returns them) with the keys that
* changed. While a flight is in progress or a crash recovery is pending the
* change waits and fires when the flight ends, so it never fires mid-flight.
* Returns an unsubscribe function; listeners are also removed when the
* plugin stops. Undefined on Stratos cores older than 2.49.0, so call it as
* `ctx.config.onChange?.(...)`.
*/
onChange?(listener: (changedKeys: string[]) => void): () => void;
};
PluginContext
export type PluginContext = {
/** Scoped logger (prefixed with plugin ID) */
logger: PluginLogger;
/** Scoped config store (namespaced to plugin) */
config: PluginConfigStore;
/** IPC registration helper (auto-prefixed channels) */
ipc: PluginIPCRegistrar;
/** Read-only auth token accessor */
auth: PluginAuthAccessor;
/** Read-only accessor for the bound airline (id, base URL, VA bearer) */
airline: PluginAirlineAccessor;
/** Express server route registrar */
server: PluginServerRegistrar;
/** SQLite database accessor (files stored in plugin's data directory) */
database: PluginDatabaseAccessor;
/**
* Live flight state: reads, subscriptions, the authoritative flight-log
* writer, and start guards.
*/
flight: PluginFlightAccessor;
/** Toast + sound surface. */
notify: PluginNotifier;
/** Blocking, awaited message dialogs (alert/confirm). */
dialog: PluginDialogApi;
/** Blocking, awaited input prompts (text/number/select). */
prompt: PluginPromptApi;
/** Control this plugin's own shell presence (e.g. the sidebar indicator). */
plugin: PluginSelfApi;
};
PluginDatabaseAccessor
export type PluginDatabaseAccessor = {
/**
* Open (or create) a SQLite database file scoped to this plugin's data directory.
* Returns a better-sqlite3 Database instance.
* @param filename - Name of the database file (e.g. "landing-reports.db")
*/
open(filename: string): unknown;
};
PluginDialogAlertInput
export type PluginDialogAlertInput = {
title: string;
message: string;
/** Acknowledge-button label. Default "OK". */
okLabel?: string;
/** Optional auto-cancel; on expiry the call resolves (void). */
timeoutMs?: number;
};
PluginDialogApi
export type PluginDialogApi = {
/** Show a blocking message with a single acknowledge button. */
alert(input: PluginDialogAlertInput): Promise<void>;
/** Ask a yes/no question. Resolves false on cancel/dismiss/timeout. */
confirm(input: PluginDialogConfirmInput): Promise<boolean>;
};
PluginDialogClosePayload
export type PluginDialogClosePayload = {
requestId: string;
};
PluginDialogConfirmInput
export type PluginDialogConfirmInput = {
title: string;
message: string;
/** Confirm-button label. Default "Confirm". */
confirmLabel?: string;
/** Cancel-button label. Default "Cancel". */
cancelLabel?: string;
/** Optional auto-cancel; on expiry the call resolves false. */
timeoutMs?: number;
};
PluginDialogKind
export type PluginDialogKind =
| "alert"
| "confirm"
| "text"
| "number"
| "select";
PluginDialogResult
export type PluginDialogResult = {
submitted: boolean;
value?: string;
};
PluginDialogSelectOption
export type PluginDialogSelectOption = {
value: string;
label: string;
};
PluginDialogSpec
export type PluginDialogSpec = {
requestId: string;
pluginId: string;
pluginName: string;
kind: PluginDialogKind;
title: string;
/** Body text (dialogs) or helper text above the field (prompts). */
message?: string;
/** Field label (prompts only). */
label?: string;
placeholder?: string;
/** Pre-filled value (always a string on the wire; number prompts stringify it). */
defaultValue?: string;
required?: boolean;
maxLength?: number;
min?: number;
max?: number;
step?: number;
options?: PluginDialogSelectOption[];
okLabel?: string;
confirmLabel?: string;
cancelLabel?: string;
submitLabel?: string;
timeoutMs?: number;
};
PluginFileAttachment
export type PluginFileAttachment = {
readonly __stratosFile: true;
/** Absolute path to the file on disk. */
path: string;
/** Name sent to the airline. Defaults to the basename of `path`. */
filename?: string;
/** MIME type. Defaults to a lookup on the extension, else application/octet-stream. */
contentType?: string;
};
PluginFlightAccessor
export type PluginFlightAccessor = {
/** Current flight-manager state (flight, pending recovery, sim-disconnect). */
getState(): Promise<FlightManagerPayload>;
/** Latest simulator data snapshot, or null when no sim is connected. */
getSnapshot(): Promise<SimDataSnapshot | null>;
/** Current flight-phase snapshot. */
getPhase(): Promise<FlightPhaseSnapshot>;
/** Current landing-analysis snapshot. */
getLandingReport(): Promise<LandingAnalysisSnapshot>;
/** Subscribe to flight-manager state changes. Returns an unsubscribe. */
onUpdate(cb: (state: FlightManagerPayload) => void): () => void;
/** Subscribe to flight-phase changes. Returns an unsubscribe. */
onPhaseChange(cb: (payload: FlightPhasePayload) => void): () => void;
/** Subscribe to live simulator data frames. Returns an unsubscribe. */
onSimData(cb: (snapshot: SimDataSnapshot) => void): () => void;
/** Subscribe to landing events (touchdown/bounce/settled). Returns an unsubscribe. */
onLanding(cb: (payload: FlightLandingPayload) => void): () => void;
/** Authoritative flight-log writer. */
log: PluginFlightLogWriter;
/**
* Register a guard consulted inside `startFlight` BEFORE the flight is
* created or persisted. A guard returning `{ allow: false, reason }` vetoes
* the start (surfaced via the existing preflight-failure path). Guards are
* fail-open: a throw or timeout (~5s) allows the start with a loud warning.
* Multiple guards: the first `allow: false` wins. Returns an unsubscribe.
*/
registerStartGuard(guard: PluginStartGuard): () => void;
/**
* Register a handler consulted while the airline PIREP is assembled, after
* every milestone capture and immediately before the body is posted.
* Whatever it returns is attached to the submission under
* `plugin_data["<your-plugin-id>"]`. Return null or undefined to attach
* nothing.
*
* Handlers are fail-open: a throw, a rejection, a timeout (3s), a
* non-object return, or returned data over the size cap omits this plugin's
* key and the PIREP is submitted regardless. The cap is 256 KB per plugin
* and 512 KB across all plugins, measured on the returned JSON. Files
* attached with `attachFile()` do not count towards it; each is only a
* short reference in the JSON and is sent as its own part, with no size
* limit.
*
* Handlers run only for `completed`, `diverted` and `crashed` flights, never
* for cancelled ones, and only when an airline is bound. If the airline
* rejects the PIREP the pilot can retry, which runs every handler again, so
* a handler must be idempotent and free of side effects. Registering more
* than one handler is allowed; their results are shallow-merged under the
* single plugin key. Returns an unsubscribe.
*/
attachToPirepSubmission(handler: PluginPirepAttachHandler): () => void;
};
PluginFlightLogInput
export type PluginFlightLogInput = {
category: EventCategory;
condition: string;
message: string;
data?: Record<string, unknown>;
};
PluginFlightLogPatch
export type PluginFlightLogPatch = Partial<
Pick<PluginFlightLogInput, "category" | "message" | "data">
>;
PluginFlightLogWriter
export type PluginFlightLogWriter = {
/**
* Append an event to the flight log. Returns the created event, or null when
* there is no active tracking session (mirrors `addComment`).
*/
add(event: PluginFlightLogInput): Promise<FlightLogEvent | null>;
/** Remove a previously-added event by id. Returns true if it existed. */
remove(eventId: string): Promise<boolean>;
/** Patch an existing event in place. Returns true if it existed. */
update(eventId: string, patch: PluginFlightLogPatch): Promise<boolean>;
};
PluginIndicatorInput
export type PluginIndicatorInput = {
/** A passive dot ("notification") or an amber warning triangle ("warning"). */
type: PluginIndicatorType;
/** Optional hover text shown on the indicator. When omitted, the indicator has no tooltip. */
tooltip?: string;
/**
* When true, the indicator clears automatically the first time the user
* opens this plugin. Defaults to false — it otherwise persists until
* `clearIndicator()` or a shell restart.
*/
dismissOnOpen?: boolean;
};
PluginIndicatorType
export type PluginIndicatorType = "notification" | "warning";
PluginIPCRegistrar
export type PluginIPCRegistrar = {
/** Register a handler for an IPC channel. Channel is auto-prefixed. */
handle(channel: string, handler: (...args: unknown[]) => unknown): void;
/** Remove a handler for an IPC channel. */
removeHandler(channel: string): void;
/** Send a message to the renderer. Channel is auto-prefixed. */
send(channel: string, ...args: unknown[]): void;
};
PluginLogger
export type PluginLogger = {
info(category: string, message: string, ...args: unknown[]): void;
warn(category: string, message: string, ...args: unknown[]): void;
error(category: string, message: string, ...args: unknown[]): void;
debug(category: string, message: string, ...args: unknown[]): void;
};
PluginManifest
export type PluginManifest = {
/** Unique plugin slug (e.g. "flight-tracking") */
id: string;
/** Plugin type — "user" for plugins installed by the user, "airline" for plugins installed and managed by the VA on the user's behalf */
type: "user" | "airline";
/** Human-readable display name */
name: string;
/** Semantic version (e.g. "1.0.0") */
version: string;
/** Short description of the plugin's purpose */
description: string;
/** Plugin author information */
author: PluginAuthor;
/** Sidebar icon for light theme — relative path to a PNG in the plugin's assets/ directory (e.g. "icon-light.png") */
icon_light: string;
/** Sidebar icon for dark theme — relative path to a PNG in the plugin's assets/ directory (e.g. "icon-dark.png") */
icon_dark: string;
/** Optional homepage URL for the plugin (e.g. "https://www.example.com/"). Rendered as a clickable link in the plugin info panel. Must be a fully-qualified http(s) URL. */
homepage?: string;
/** Typed settings this plugin declares. User-scoped settings render in the Settings page; airline-scoped settings are managed by the VA platform. */
available_settings?: PluginSettingDefinition[];
/** Whether to include source maps in the published CDN bundle. Defaults to false — source maps are stripped on approval. Set to true if you want end users to have access to source maps for debugging. */
include_source_maps?: boolean;
/** Advisory: this plugin attaches files to the PIREP, which changes how flight submissions are encoded for the airline. Stratos does not read or enforce it yet. */
attaches_files_to_pirep?: boolean;
/** The oldest Stratos Core the plugin runs on (semver, e.g. "2.49.0"). An older core doesn't install the plugin (an update keeps the version already there) and doesn't start one already installed. Set it when you use an SDK feature that needs a newer core. */
min_core_version?: string;
};
PluginNavigationHelper
export type PluginNavigationHelper = {
/** Navigate to a route within this plugin */
navigateTo(path: string): void;
/** Navigate to a route in another plugin */
navigateToPlugin(pluginId: string, path: string): void;
/** Navigate to a shell route */
navigateToShell(path: string): void;
/** Get the current route path */
getCurrentPath(): string;
};
PluginNotifier
export type PluginNotifier = {
/** Show a toast in the renderer. */
toast(n: PluginToastInput): Promise<void>;
/** Play a built-in shell sound by name. */
playSound(name: BuiltInSound): Promise<void>;
/**
* Play a sound file bundled inside this plugin. `assetPath` is relative to
* the plugin root (e.g. "assets/DepGateAlert.wav"). Anything under the
* plugin's `assets/` directory is shipped with the plugin automatically.
* Obeys the user's notification-sound setting. Allowed file types: wav, mp3,
* ogg, m4a, aac.
*/
playCustomSound(assetPath: string): Promise<void>;
};
PluginPilotUser
export type PluginPilotUser = {
dbID: number;
pilotID: string;
firstName: string;
lastName: string;
email: string;
rank: string;
rankLevel: number;
rankImage: string;
avatar: string;
};
PluginPirepAttachHandler
export type PluginPirepAttachHandler = (
ctx: PluginPirepSubmissionContext,
) => PluginPirepAttachment | Promise<PluginPirepAttachment>;
PluginPirepAttachment
export type PluginPirepAttachment = Record<string, unknown> | null | undefined;
PluginPirepSubmissionContext
export type PluginPirepSubmissionContext = {
/** Local (core DB) flight id. */
flightId: string;
/** Skyvex Software flight UUID, or null if the flight was never relayed. */
stratosFlightId: string | null;
/** VA tracking id for the PIREP being filed. */
vaTrackingId: string;
/** Terminal status being filed. Cancelled flights never reach a handler. */
status: "completed" | "diverted" | "crashed";
/** The flight plan being filed. */
plan: FlightPlan;
/** Last simulator snapshot, or null when no sim is connected. */
snapshot: SimDataSnapshot | null;
};
PluginPromptApi
export type PluginPromptApi = {
/** Ask for free text. */
text(input: PluginPromptTextInput): Promise<string | null>;
/** Ask for a number. A non-numeric/blank submit resolves null. */
number(input: PluginPromptNumberInput): Promise<number | null>;
/** Ask the pilot to pick one of the supplied options. Returns the value. */
select(input: PluginPromptSelectInput): Promise<string | null>;
};
PluginPromptNumberInput
export type PluginPromptNumberInput = {
title: string;
label: string;
message?: string;
placeholder?: string;
defaultValue?: number;
required?: boolean;
min?: number;
max?: number;
step?: number;
submitLabel?: string;
cancelLabel?: string;
timeoutMs?: number;
};
PluginPromptSelectInput
export type PluginPromptSelectInput = {
title: string;
label: string;
message?: string;
options: { value: string; label: string }[];
/** Pre-selected option value. */
defaultValue?: string;
required?: boolean;
submitLabel?: string;
cancelLabel?: string;
timeoutMs?: number;
};
PluginPromptTextInput
export type PluginPromptTextInput = {
title: string;
/** Field label, e.g. "What gate are you departing from?". */
label: string;
/** Optional helper/body text shown above the field. */
message?: string;
placeholder?: string;
defaultValue?: string;
/** When true, Submit is disabled until the field is non-empty. */
required?: boolean;
maxLength?: number;
submitLabel?: string;
cancelLabel?: string;
timeoutMs?: number;
};
PluginRouteComponent
export type PluginRouteComponent =
| ComponentType
| LazyExoticComponent<ComponentType>;
PluginSelfApi
export type PluginSelfApi = {
/** Set (or replace) this plugin's sidebar indicator. */
setIndicator(indicator: PluginIndicatorInput): Promise<void>;
/** Remove this plugin's sidebar indicator, if any. */
clearIndicator(): Promise<void>;
};
PluginServerRegistrar
export type PluginServerRegistrar = {
/** Register an Express router at the given path prefix (e.g. '/api/booking') */
registerRouter(prefix: string, router: unknown): void;
};
PluginSettingDefinition
export type PluginSettingDefinition =
| BooleanSettingDef
| TextSettingDef
| LongtextSettingDef
| NumberSettingDef
| RangeSettingDef
| ListSettingDef
| RadioSettingDef
| DateSettingDef
| JsonSettingDef;
PluginSettingOption
export type PluginSettingOption = {
value: string;
label: string;
};
PluginSettingType
export type PluginSettingType =
| "boolean"
| "text"
| "longtext"
| "number"
| "range"
| "list"
| "radio"
| "date"
| "json";
PluginStartContext
export type PluginStartContext = {
plan: FlightPlan;
snapshot: SimDataSnapshot | null;
isAutoStart: boolean;
};
PluginStartDecision
export type PluginStartDecision =
| { allow: true }
| { allow: false; reason: string };
PluginStartGuard
export type PluginStartGuard = (
ctx: PluginStartContext,
) => PluginStartDecision | Promise<PluginStartDecision>;
PluginToastAPI
export type PluginToastAPI = {
success(message: string): void;
error(message: string): void;
info(message: string): void;
warning(message: string): void;
};
PluginToastInput
export type PluginToastInput = {
type: "info" | "success" | "warning" | "error";
title: string;
message: string;
durationMs?: number;
id?: string;
};
PluginUIContext
export type PluginUIContext = {
/**
* Shared axios instance for the bound airline's API, with auto-refresh on
* 401. Provided by the shell so all plugin UIs and the shell itself share
* one in-flight refresh promise — concurrent 401s coalesce to a single
* `POST /api/auth/va/refresh`. Reach this through `useVaApi()`.
*/
vaApi: AxiosInstance;
/** The plugin's unique ID */
pluginId: string;
/** Auth state from the shell */
auth: {
isAuthenticated: boolean;
token: string | null;
user: PluginPilotUser | null;
};
/** Current airline info (from Stratos API). Bundled with the VA bearer
* so plugins can call the airline backend directly. */
airline?: {
id: string;
name: string;
icao: string;
logo_light: string;
logo_dark: string;
/** Origin of the VA's API (no trailing slash). */
base_url: string;
/** Bearer for this airline's API. Null if the VA session has expired. */
token: string | null;
} | null;
/** Config access hooks */
config: {
get<T>(key: string): T | undefined;
get<T>(key: string, defaultValue: T): T;
};
/**
* Real-time bridge between this plugin's background and UI modules.
*
* - `on(event, handler)` subscribes to broadcasts the background made via
* `ctx.ipc.send(event, payload)`. The shell scopes the channel to the
* current plugin id automatically — pass the leaf event name on both
* ends. Plugin A's UI cannot receive plugin B's broadcasts.
* - `off(event, handler)` removes a previously registered handler.
* - `emit` is intentionally not implemented. Send UI→background traffic
* through HTTP routes registered via `ctx.server.registerRouter`.
* - `connected` is always `true` in the renderer; the underlying Electron
* IPC pipe has no connection lifecycle.
*/
socket: {
connected: boolean;
emit(event: string, data: unknown): void;
on(event: string, handler: (data: unknown) => void): void;
off(event: string, handler: (data: unknown) => void): void;
};
/** Navigation utilities */
navigation: PluginNavigationHelper;
/** Toast/notification API */
toast: PluginToastAPI;
/** Scoped logger for renderer-side logging */
logger: PluginLogger;
};
PluginUIModule
export type PluginUIModule = PluginRouteComponent;
PluginVaApiClient
export type PluginVaApiClient = AxiosInstance;
PreflightCheck
export type PreflightCheck = {
name: string;
passed: boolean;
message: string;
/** Not run yet because an earlier check failed (`passed` is false). */
pending?: boolean;
};
PreflightCheckResult
export type PreflightCheckResult = {
passed: boolean;
checks: PreflightCheck[];
};
ProtocolUrlPayload
export type ProtocolUrlPayload = {
/** The action segment of the URL (e.g. "connect", "open"). */
action: string;
/** Parsed query / path parameters. */
params: Record<string, string>;
/** The original full URL string. */
fullUrl: string;
};
RadioSettingDef
export type RadioSettingDef = PluginSettingBase & {
type: "radio";
default?: string;
options: PluginSettingOption[];
};
RangeSettingDef
export type RangeSettingDef = PluginSettingBase & {
type: "range";
default?: number;
min: number;
max: number;
step?: number;
};
RecoverableFlight
export type RecoverableFlight = {
id: string;
callsign: string | null;
departureIcao: string | null;
arrivalIcao: string | null;
aircraftIcao: string | null;
latitude: number | null;
longitude: number | null;
altitude: number | null;
/** True heading in degrees. The autopilot selector is magnetic, so see `headingMagnetic` for what to show the pilot. */
heading: number | null;
/**
* Magnetic heading in degrees: `heading` is true, this is what the cockpit
* shows and what the autopilot heading selector takes. Null when it was not
* recorded. Undefined when the core predates the field.
*/
headingMagnetic?: number | null;
groundSpeed: number | null;
indicatedAirspeed: number | null;
startedAt: number | null;
elapsedMs: number;
status: string;
phase: string | null;
/** Last known fuel in lbs (canonical). Null if no fuel data was captured. */
lastFuel: number | null;
};
SimDataSnapshot
export type SimDataSnapshot = {
data: FlightData | null;
isConnected: boolean;
simulatorType: string | null;
};
SimulatorDataPayload
export type SimulatorDataPayload = {
/** Identifier of the connected simulator (e.g. "xplane", "msfs"). */
simulatorType: string;
/** Full flight data snapshot from the simulator. */
data: FlightData;
/** Wall-clock timestamp (epoch ms) when the data was emitted. */
timestamp: number;
/** Computed trend analysis from the rolling sample window. */
trends: FlightTrends;
};
SimulatorStatus
export type SimulatorStatus =
| "disconnected"
| "connecting"
| "connected"
| "error";
SimulatorStatusPayload
export type SimulatorStatusPayload = {
/** Identifier of the simulator. */
simulatorType: string;
/** New connection status. */
status: SimulatorStatus;
/** Optional human-readable message (e.g. error details). */
message?: string;
};
SimulatorType
export enum SimulatorType {
XPLANE = "xplane",
MSFS = "msfs",
FSX = "fsx",
P3D = "p3d",
UNKNOWN = "unknown",
}
SocketEventName
export type SocketEventName =
(typeof SOCKET_EVENTS)[keyof typeof SOCKET_EVENTS];
SoundPayload
export type SoundPayload =
| { name: string }
| { pluginId: string; path: string };
StartFlightOptions
export type StartFlightOptions = {
/**
* Start even when preflight checks fail — set when the pilot chooses "Start
* Anyway" after a preflight error. Also overrides plugin start guards.
*/
forceStart?: boolean;
/** True when the start was triggered by auto-start (not a manual click). Surfaced to plugin start guards. */
isAutoStart?: boolean;
};
StartFlightResult
export type StartFlightResult = {
success: boolean;
flight?: CurrentFlight;
preflightResult?: PreflightCheckResult;
/** VA-issued confirmation message — pilot must acknowledge before tracking starts */
vaConfirmation?: string;
/** Sim variables sent to VA for display in confirmation dialog */
simVariables?: Record<string, string>;
/**
* Per-start override flag from the VA `/flights/start` response (`can_override`).
* Takes priority over the airline's `allowStartOverride` setting for the
* confirmation dialog: `true` always permits "Start tracking anyway", `false`
* always forbids it. Omitted ⇒ the `allowStartOverride` setting applies.
*/
canOverride?: boolean;
error?: string;
};
StratosMapProps
export type StratosMapProps = Omit<
MapProps,
"mapStyle" | "attributionControl"
> & {
/** Your own style in the light theme (URL or object). Defaults to Stratos's. */
lightStyle?: MapProps["mapStyle"];
/** Your own style in the dark theme (URL or object). Defaults to Stratos's. */
darkStyle?: MapProps["mapStyle"];
ref?: Ref<MapRef>;
};
SystemMetricsPayload
export type SystemMetricsPayload = {
cpu: {
/** System-wide CPU percentage. */
percentage: number;
/** This process's CPU percentage. */
process: number;
};
memory: {
/** System used memory (bytes). */
used: number;
/** System total memory (bytes). */
total: number;
/** Process RSS (bytes). */
process: number;
/** Process heap used (bytes). */
processHeap: number;
};
/** Wall-clock timestamp (epoch ms). */
timestamp: number;
};
TextSettingDef
export type TextSettingDef = PluginSettingBase & {
type: "text";
default?: string;
pattern?: string;
placeholder?: string;
};
ThemeMode
export type ThemeMode = "dark" | "light" | "system";
UnitPreferences
export type UnitPreferences = {
weight: WeightUnit;
altitude: AltitudeUnit;
distance: DistanceUnit;
};
WeightUnit
export type WeightUnit = "lbs" | "kg";