-
Notifications
You must be signed in to change notification settings - Fork 117
Description
Background
The cloud relay feature allows users to control their home rig-bridge from a remote browser (e.g. openhamclock.com) by proxying state and commands through the OHC server. It works for single-user self-hosted setups but has architectural issues that prevent it from scaling to multi-user deployments.
PR #852 (direct SSE mode) is the right long-term direction for self-hosted/LAN users — it eliminates server round-trips entirely. But cloud relay remains necessary for users who access openhamclock.com from outside their home network, especially on browsers with mixed-content restrictions (Safari, Chrome on HTTPS).
This issue tracks the session management and scalability rework needed to make cloud relay production-ready for multi-user environments.
Problems to solve
1. Session ID is not consistently paired between browser and rig-bridge
The session ID lives in two places that aren't automatically linked:
- Rig-bridge config file (
cloud-relay.jsreadscfg.session) — set manually by the user - Browser Settings panel (
cloudRelaySessioninSettingsPanel.jsx) — typed in manually by the user
There's no automatic pairing. The user must copy the same session ID into both places. If they mismatch, data goes to one session but the browser listens on another. The /api/rig-bridge/relay/credentials endpoint can generate a session ID, but nothing ensures the browser and rig-bridge actually use the same one.
2. Per-plugin data is not scoped to sessions consistently
All plugin data (decodes, APRS packets, rig state) flows through a single session. But there's no clear contract about which data is session-private vs. shared:
- Rig freq/mode/PTT — clearly session-private (each user has their own rig)
- WSJT-X decodes — session-private (from that user's WSJT-X instance)
- APRS RF packets — could be shared (local RF data from a club station TNC) or private (user's personal iGate)
- QSOs — session-private
Currently everything is bundled into one session blob. There's no mechanism for a user to share their APRS TNC data publicly while keeping rig control private.
3. Server memory scales linearly with connected users
Each cloud relay session stores in a server-side Map:
- Rig state object
- Up to 500 WSJT-X decodes
- APRS packet buffer
- Command queue
- SSE client connections
- Long-poll command waiters
With the current cap of MAX_RELAY_SESSIONS = 50 and RELAY_SESSION_TTL = 1 hour, this is bounded but still means potentially 50 × (500 decodes + APRS buffers + SSE connections) in memory. For openhamclock.com with many simultaneous users, this could become a performance concern.
4. No session lifecycle management
- Sessions are created implicitly on first contact (no explicit "connect" step)
- The only cleanup is a 1-hour TTL with 5-minute sweep interval
- No way for a user to see active sessions, force-disconnect, or manage their session from the browser
- If a user's rig-bridge disconnects, stale sessions linger for up to an hour serving stale data
5. Credential endpoint exposes relay key to browser
GET /api/rig-bridge/relay/credentials returns relayKey (the RIG_BRIDGE_RELAY_KEY env var) to the browser. This is the same key rig-bridge uses to authenticate state pushes. Any browser user could extract it and push fake rig state to any session.
Proposed phased approach
Phase 1 — Automatic session pairing
Goal: Eliminate manual session ID copy-paste. Browser and rig-bridge should auto-pair.
- Generate session ID server-side when a user enables cloud relay in Settings
- Store session ID in browser
localStorage(persist across refreshes) - Provide a one-time pairing code or QR code the user enters in rig-bridge setup UI (port 5555)
- Rig-bridge exchanges the pairing code for the actual session ID + relay credentials
- Display connection status in both browser Settings and rig-bridge UI
Phase 2 — Separate auth for push vs. consume
Goal: Rig-bridge can push data, browser can consume data, but browser can't impersonate rig-bridge.
- Split
RIG_BRIDGE_RELAY_KEYinto two tokens:- Push token — only rig-bridge has this, used for
POST /relay/state - Session token — browser gets this, used for SSE stream + command POST
- Push token — only rig-bridge has this, used for
- Remove relay key from
/relay/credentialsresponse - Commands from browser are authenticated by session token, not relay key
Phase 3 — Data scoping (private vs. shared)
Goal: Let users choose which plugin data is session-private vs. publicly visible.
- Add per-plugin visibility flags to session config:
{ aprs: 'public', wsjtx: 'private', rig: 'private' } - Public data (e.g. APRS RF from a club TNC) gets merged into the global APRS station pool
- Private data (rig state, decodes) only accessible to the owning session
- Default: everything private (current behavior, safe default)
Phase 4 — Session lifecycle and memory optimization
Goal: Reduce server memory footprint, add explicit session management.
- Add explicit connect/disconnect lifecycle (not just implicit creation)
- Reduce decode buffer per session (500 → 100, or use a ring buffer with age-based eviction)
- Add session dashboard for admins (active sessions, memory usage, last activity)
- Consider moving session storage to Redis for horizontal scaling (future, if needed)
- Add configurable
MAX_RELAY_SESSIONSvia env var for different deployment sizes
References
- PR rig-bridge: Direct SSE mode, plugin data pipeline & APRS improvements #852 — Direct SSE mode (eliminates server traffic for local/LAN users)
- Discord discussion (2026-03-28) — @ceotjoe, @VK2MG, @accius on session architecture concerns
server/routes/rig-bridge.js— current cloud relay implementationrig-bridge/plugins/cloud-relay.js— rig-bridge sidesrc/contexts/RigContext.jsx— browser side
cc @ceotjoe
Metadata
Metadata
Assignees
Labels
Projects
Status