From ddb33b6ff36d27e55415e3a8f8da05a9109df43b Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 1 Aug 2026 22:27:35 +0200 Subject: [PATCH] feat: generate frontend status metadata contract --- .../assets/web/generated/app.js | 4 +- .../examples/generate_typescript.rs | 2 + .../frontend/src/api/generated.ts | 2 + .../trx-frontend-http/frontend/src/app.ts | 39 +++++++++---------- .../frontend/tests/browser-smoke.mjs | 12 ++++++ .../trx-frontend-http/src/api/mod.rs | 4 +- 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js index e388c310..0a9be794 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js @@ -1640,7 +1640,7 @@ async function responseJsonUnknown(response) { return await response.json(); } function isAppUpdate(value) { - return isRecord2(value) && (value.status === void 0 || isRecord2(value.status)); + return isRecord2(value) && typeof value.clients === "number" && typeof value.audio_clients === "number" && typeof value.rigctl_clients === "number" && Array.isArray(value.remotes) && value.remotes.every((remote) => typeof remote === "string") && typeof value.show_sdr_gain_control === "boolean" && typeof value.initial_map_zoom === "number" && typeof value.spectrum_coverage_margin_hz === "number" && typeof value.spectrum_usable_span_ratio === "number" && typeof value.bandplan_enabled === "boolean" && typeof value.bandplan_region === "string" && typeof value.decode_history_retention_min === "number" && typeof value.server_connected === "boolean" && isRigSnapshot(value); } function isAudioStreamInfo(value) { return isRecord2(value) && typeof value.sample_rate === "number" && typeof value.channels === "number"; @@ -4767,7 +4767,7 @@ async function pollFreshSnapshot() { const resp = await fetch(statusUrl, { cache: "no-store" }); if (!resp.ok) return; const data = await responseJsonUnknown(resp); - if (!isRigSnapshot(data)) return; + if (!isAppUpdate(data)) return; render(data); void refreshRigList(); lastEventAt = Date.now(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/examples/generate_typescript.rs b/src/trx-client/trx-frontend/trx-frontend-http/examples/generate_typescript.rs index 483efcb6..c984b34c 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/examples/generate_typescript.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/examples/generate_typescript.rs @@ -14,6 +14,7 @@ use trx_core::rig::{ }; use trx_core::{DecoderConfig, RdsData, RigFilterState, RigMode, RigSnapshot, WfmDenoiseLevel}; use trx_frontend_http::server::api::rig::{RigListItem, RigListResponse}; +use trx_frontend_http::server::api::FrontendMeta; use trx_protocol::{DecoderActivation, DecoderDescriptor}; use ts_rs::{Config, TS}; @@ -53,6 +54,7 @@ fn main() -> Result<(), Box> { export!(RigSnapshot); export!(RigListItem); export!(RigListResponse); + export!(FrontendMeta); export!(DecoderActivation); export!(DecoderDescriptor); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/api/generated.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/api/generated.ts index 51ee7a65..0a772a85 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/api/generated.ts +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/api/generated.ts @@ -117,6 +117,8 @@ export type RigListItem = { remote: string, display_name: string | null, manufac export type RigListResponse = { active_remote: string | null, rigs: Array, }; +export type FrontendMeta = { clients: number, rigctl_clients: number, audio_clients: number, rigctl_addr: string | null, active_remote: string | null, remotes: Array, owner_callsign: string | null, owner_website_url: string | null, owner_website_name: string | null, ais_vessel_url_base: string | null, show_sdr_gain_control: boolean, initial_map_zoom: number, spectrum_coverage_margin_hz: number, spectrum_usable_span_ratio: number, bandplan_enabled: boolean, bandplan_region: string, decode_history_retention_min: bigint, server_connected: boolean, }; + export type DecoderActivation = "mode_bound" | "toggle"; export type DecoderDescriptor = { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts index 490b2218..f91e733b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts @@ -44,7 +44,7 @@ import { } from "./features/spectrum/math.js"; import type { NumericBins } from "./features/spectrum/math.js"; import type { AuthRole } from "./api/auth.js"; -import type { RdsData, RigCapabilities, RigListItem, RigSnapshot, VchanRdsEntry } from "./api/generated.js"; +import type { FrontendMeta, RdsData, RigCapabilities, RigListItem, RigSnapshot, VchanRdsEntry } from "./api/generated.js"; import type { TrxPluginRuntime } from "./plugins/runtime-contract.js"; import { loadEagerPlugins, loadPluginsForTab } from "./plugin-loader.js"; import { isRigListResponse, isRigSnapshot } from "./api/client.js"; @@ -126,25 +126,8 @@ interface Bookmark { locator?: string | null; comment?: string | null; } -type AppUpdate = Partial & { +type AppUpdate = RigSnapshot & FrontendMeta & { [key: string]: unknown; - owner_callsign?: string; - owner_website_url?: string; - owner_website_name?: string; - ais_vessel_url_base?: string; - initial_map_zoom?: number; - spectrum_coverage_margin_hz?: number; - spectrum_usable_span_ratio?: number; - decode_history_retention_min?: number; - show_sdr_gain_control?: boolean; - bandplan_enabled?: boolean; - bandplan_region?: string; - clients?: number; - audio_clients?: number; - rigctl_clients?: number; - rigctl_addr?: string; - remotes?: string[]; - active_remote?: string | null; }; interface BandplanSegment { low_hz: number; high_hz: number; mode: string; label: string } interface BandplanBand { name: string; low_hz: number; high_hz: number; segments: BandplanSegment[] } @@ -317,7 +300,21 @@ async function responseJsonUnknown(response: Response): Promise { } function isAppUpdate(value: unknown): value is AppUpdate { - return isRecord(value) && (value.status === undefined || isRecord(value.status)); + return isRecord(value) + && typeof value.clients === "number" + && typeof value.audio_clients === "number" + && typeof value.rigctl_clients === "number" + && Array.isArray(value.remotes) + && value.remotes.every((remote) => typeof remote === "string") + && typeof value.show_sdr_gain_control === "boolean" + && typeof value.initial_map_zoom === "number" + && typeof value.spectrum_coverage_margin_hz === "number" + && typeof value.spectrum_usable_span_ratio === "number" + && typeof value.bandplan_enabled === "boolean" + && typeof value.bandplan_region === "string" + && typeof value.decode_history_retention_min === "number" + && typeof value.server_connected === "boolean" + && isRigSnapshot(value); } function isAudioStreamInfo(value: unknown): value is AudioStreamInfo { @@ -3800,7 +3797,7 @@ async function pollFreshSnapshot() { const resp = await fetch(statusUrl, { cache: "no-store" }); if (!resp.ok) return; const data = await responseJsonUnknown(resp); - if (!isRigSnapshot(data)) return; + if (!isAppUpdate(data)) return; render(data); void refreshRigList(); lastEventAt = Date.now(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs index 7bc84060..c5f7d423 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs @@ -59,6 +59,18 @@ const jsonRoutes = new Map([ lrpt_decode_enabled: false, wefax_decode_enabled: false, recorder_enabled: false, + clients: 1, + rigctl_clients: 0, + audio_clients: 0, + remotes: [], + show_sdr_gain_control: false, + initial_map_zoom: 10, + spectrum_coverage_margin_hz: 50_000, + spectrum_usable_span_ratio: 0.92, + bandplan_enabled: false, + bandplan_region: "iaru1", + decode_history_retention_min: 1440, + server_connected: true, }], ["/bandplan.json", {}], ["/api/recorder/status", []], diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/api/mod.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/api/mod.rs index 512719eb..528f313b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/api/mod.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/api/mod.rs @@ -60,8 +60,8 @@ pub struct StatusQuery { pub remote: Option, } -#[derive(serde::Serialize)] -struct FrontendMeta { +#[derive(serde::Serialize, ts_rs::TS)] +pub struct FrontendMeta { #[serde(rename = "clients")] http_clients: usize, rigctl_clients: usize,