From d8e9397cf664536784dbf9716cd0709d8813568a Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Thu, 12 Feb 2026 20:47:23 +0100 Subject: [PATCH] [feat](trx-frontend): add context types for explicit initialization Create explicit context types for frontend registration and runtime: FrontendRegistrationContext: - register_frontend(name, spawner) - register a frontend - is_frontend_registered(name) - check if registered - registered_frontends() -> Vec - list all frontends - spawn_frontend(name, ...) -> DynResult - spawn a frontend FrontendRuntimeContext (NEW): - audio_rx: broadcast channel for audio RX - audio_tx: mpsc channel for audio TX - audio_info: watch channel for audio stream metadata - decode_rx: broadcast channel for decoded messages - aprs_history: Arc> for APRS decode history - cw_history: Arc> for CW decode history - ft8_history: Arc> for FT8 decode history - auth_tokens: HashSet for authentication Replaces global mutable state with explicit context that can be threaded through bootstrap. Maintains global API for compatibility. Co-Authored-By: Claude Haiku 4.5 Signed-off-by: Stanislaw Grams --- src/trx-client/trx-frontend/Cargo.toml | 1 + src/trx-client/trx-frontend/src/lib.rs | 111 ++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/trx-client/trx-frontend/Cargo.toml b/src/trx-client/trx-frontend/Cargo.toml index b9bc996..3ac7d6a 100644 --- a/src/trx-client/trx-frontend/Cargo.toml +++ b/src/trx-client/trx-frontend/Cargo.toml @@ -8,5 +8,6 @@ version = "0.1.0" edition = "2021" [dependencies] +bytes = "1" trx-core = { path = "../../trx-core" } tokio = { workspace = true, features = ["sync"] } diff --git a/src/trx-client/trx-frontend/src/lib.rs b/src/trx-client/trx-frontend/src/lib.rs index cb2c0e6..3b97a4d 100644 --- a/src/trx-client/trx-frontend/src/lib.rs +++ b/src/trx-client/trx-frontend/src/lib.rs @@ -2,13 +2,17 @@ // // SPDX-License-Identifier: BSD-2-Clause -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque, HashSet}; use std::net::SocketAddr; -use std::sync::{Mutex, OnceLock}; +use std::sync::{Arc, Mutex, OnceLock}; +use std::time::Instant; -use tokio::sync::{mpsc, watch}; +use bytes::Bytes; +use tokio::sync::{broadcast, mpsc, watch}; use tokio::task::JoinHandle; +use trx_core::audio::AudioStreamInfo; +use trx_core::decode::{AprsPacket, CwEvent, DecodedMessage, Ft8Message}; use trx_core::{DynResult, RigRequest, RigState}; /// Trait implemented by concrete frontends to expose a runner entrypoint. @@ -21,13 +25,112 @@ pub trait FrontendSpawner { ) -> JoinHandle<()>; } -type FrontendSpawnFn = fn( +pub type FrontendSpawnFn = fn( watch::Receiver, mpsc::Sender, Option, SocketAddr, ) -> JoinHandle<()>; +/// Context for registering and spawning frontends. +pub struct FrontendRegistrationContext { + spawners: HashMap, +} + +impl FrontendRegistrationContext { + /// Create a new empty registration context. + pub fn new() -> Self { + Self { + spawners: HashMap::new(), + } + } + + /// Register a frontend spawner under a stable name (e.g. "http"). + pub fn register_frontend(&mut self, name: &str, spawner: FrontendSpawnFn) { + let key = normalize_name(name); + self.spawners.insert(key, spawner); + } + + /// Check whether a frontend name is registered. + pub fn is_frontend_registered(&self, name: &str) -> bool { + let key = normalize_name(name); + self.spawners.contains_key(&key) + } + + /// List registered frontend names. + pub fn registered_frontends(&self) -> Vec { + let mut names: Vec = self.spawners.keys().cloned().collect(); + names.sort(); + names + } + + /// Spawn a registered frontend by name. + pub fn spawn_frontend( + &self, + name: &str, + state_rx: watch::Receiver, + rig_tx: mpsc::Sender, + callsign: Option, + listen_addr: SocketAddr, + ) -> DynResult> { + let key = normalize_name(name); + let spawner = self + .spawners + .get(&key) + .ok_or_else(|| format!("Unknown frontend: {}", name))?; + Ok(spawner(state_rx, rig_tx, callsign, listen_addr)) + } +} + +impl Default for FrontendRegistrationContext { + fn default() -> Self { + Self::new() + } +} + +/// Runtime context for frontend operation, containing audio channels and decode state. +pub struct FrontendRuntimeContext { + /// Audio RX broadcast channel (server → browser) + pub audio_rx: Option>, + /// Audio TX channel (browser → server) + pub audio_tx: Option>, + /// Audio stream info watch channel + pub audio_info: Option>>, + /// Decode message broadcast channel + pub decode_rx: Option>, + /// APRS decode history (timestamp, packet) + pub aprs_history: Arc>>, + /// CW decode history (timestamp, event) + pub cw_history: Arc>>, + /// FT8 decode history (timestamp, message) + pub ft8_history: Arc>>, + /// Authentication tokens for HTTP-JSON frontend + pub auth_tokens: HashSet, +} + +impl FrontendRuntimeContext { + /// Create a new empty runtime context. + pub fn new() -> Self { + Self { + audio_rx: None, + audio_tx: None, + audio_info: None, + decode_rx: None, + aprs_history: Arc::new(Mutex::new(VecDeque::new())), + cw_history: Arc::new(Mutex::new(VecDeque::new())), + ft8_history: Arc::new(Mutex::new(VecDeque::new())), + auth_tokens: HashSet::new(), + } + } +} + +impl Default for FrontendRuntimeContext { + fn default() -> Self { + Self::new() + } +} + +// Legacy global registry for plugin compatibility struct FrontendRegistry { spawners: HashMap, }