Skip to content

Staging#32

Merged
MichaelWheeley merged 17 commits intoMichaelWheeley:feature/thai-language-devfrom
accius:Staging
Apr 2, 2026
Merged

Staging#32
MichaelWheeley merged 17 commits intoMichaelWheeley:feature/thai-language-devfrom
accius:Staging

Conversation

@MichaelWheeley
Copy link
Copy Markdown
Owner

What does this PR do?

Type of change

  • Bug fix
  • New feature
  • Performance improvement
  • Refactor / code cleanup
  • Documentation
  • Translation
  • Map layer plugin

How to test

Checklist

  • App loads without console errors
  • Tested in Dark, Light, and Retro themes
  • Responsive at different screen sizes (desktop + mobile)
  • If touching server.js: caches have TTLs and size caps (we serve 2,000+ concurrent users)
  • If adding an API route: includes caching and error handling
  • If adding a panel: wired into Modern, Classic, and Dockable layouts
  • No hardcoded colors — uses CSS variables (var(--accent-cyan), etc.)
  • No .bak, .old, console.log debug lines, or test scripts included

Screenshots (if visual change)

ceotjoe and others added 17 commits March 28, 2026 16:45
When a cloudRelaySession is active the Cloud Relay card now shows the
session status and a Disconnect button. Clicking it clears the session
and immediately saves config (no manual Save required), switching
RigContext back to the direct SSE connection path.

Previously the only way to leave cloud-relay mode was to manually clear
the hidden session field; without that the direct connection path in
RigContext was never reached even after disabling the relay in rig-bridge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of rig-bridge firing fire-and-forget HTTP POSTs to a hardcoded
localhost:8080 (wrong port, always failed silently), all plugin data now
flows over the same SSE /stream connection already used for freq/mode/ptt.

rig-bridge changes:
- state.js: add 100-entry decode ring-buffer (addToDecodeRingBuffer /
  getDecodeRingBuffer) so connecting browsers see recent decodes immediately
- rig-bridge.js: subscribe to pluginBus after connectIntegrations() and
  broadcast typed plugin messages for decode/status/qso/aprs events
- server.js: send plugin-init after the rig init message on /stream connect,
  carrying the ring-buffer replay and list of running integration plugins
- aprs-tnc.js: remove forwardToLocal() call from handleKissData — SSE
  broadcast replaces it; fix default ohcUrl port from 8080 → 3000
- config.js: fix default aprs.ohcUrl port 8080 → 3000

Frontend changes:
- RigContext.jsx: dispatch window CustomEvent 'rig-plugin-data' for
  type:'plugin' and type:'plugin-init' messages from local SSE
- useAPRS.js: listen for rig-plugin-data aprs events, POST raw packet to
  /api/aprs/local (same-origin, always reachable), then refresh stations;
  seed tncConnected from plugin-init
- useWSJTX.js: listen for decode/status events; seed decode list from
  plugin-init ring-buffer replay; populate clients map from status events
- useDigitalModes.js: listen for status events to update plugin statuses
  (OHC server has no /api/mshv|jtdx|js8call/status routes, so HTTP
  polling was always failing silently in local mode)

Cloud relay path is entirely unchanged — window events only fire when
the browser is connected directly to rig-bridge's /stream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useWSJTX: add isLocalMode ref that stops the adaptive polling loop the
  moment the first rig-bridge SSE event arrives; add qso event handling;
  avoid setting hasDataFlowing from SSE path (prevents spurious 2 s burst)
- rig-bridge/lib/aprs-parser.js: new standalone APRS position parser
  (!, =, /, @, ; data types) extracted so rig-bridge can produce fully-
  parsed station objects without a server round-trip
- aprs-tnc: parse packet before bus emit; spread lat/lon/symbol/comment
  into the SSE payload alongside raw AX.25 fields; tag with stationSource
- useAPRS: maintain separate rfStations Map fed purely by SSE events;
  merge RF + internet stations (RF wins on duplicate callsign); add
  60-minute aging cleanup matching server APRS_MAX_AGE_MINUTES; remove
  server POST call entirely for local-TNC path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- APRSPanel: add distance-to-DE column using calculateDistance/formatDistance,
  respecting user metric/imperial setting; pass deLocation + units from
  DockableApp
- APRSPanel: hover tooltip showing full comment, coordinates, distance, age,
  speed/course, altitude and symbol — fixed-position, pointer-events:none
- APRSPanel: fix age display for RF stations (NaNh) by computing age from
  timestamp when server-provided age field is absent
- APRSPanel: prevent server poll from resetting aprsEnabled to false when
  aprs-tnc was detected via SSE (tncDetectedViaSse ref)
- CallsignLink: strip APRS SSID suffix (-0…-15) before QRZ lookup so
  W1ABC-9 opens QRZ as W1ABC

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add standard APRS symbol sprite sheets (hessu/aprs-symbols, 24 px) to
  public/ — primary table (0), alternate table (1), overlay table (2)
- New src/utils/aprs-symbols.js: getAprsSymbolIcon() maps the two-char
  APRS symbol field to a Leaflet divIcon using CSS background-position
  into the sprite sheet; supports primary (/), alternate (\), and
  alphanumeric overlay table chars; falls back to null for unknown symbols
- WorldMap.jsx: use symbol sprite icon when available, keeping the CSS
  triangle as fallback; colour ring (amber/green/cyan) preserved via
  box-shadow on the icon wrapper; watched stations rendered at 20 px,
  others at 16 px
- WorldMap.jsx: fix age display for RF stations in popup (NaN) by falling
  back to timestamp when station.age is absent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking an APRS station on the map or in the panel should not set
the DX target — APRS is a monitoring tool, not a contact/spotting source.

- WorldMap: remove onSpotClick call from APRS marker click; popup still
  opens via Leaflet bindPopup as before
- DockableApp: stop passing onSpotClick to APRSPanel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In local/LAN mode the browser already receives all decodes via the SSE
/stream — the HTTP batch POST to /api/wsjtx/relay on the OHC server is
redundant and generates unnecessary traffic.

- config.js: add wsjtxRelay.relayToServer (default false) — false means
  SSE-only, true means also POST batches to OHC server
- wsjtx-relay.js: compute willRelay flag at connect() time; gate message
  queue push, scheduleBatch, heartbeat and health-check intervals behind
  willRelay; SSE bus.emit paths are always active regardless of mode;
  getStatus() now surfaces relayToServer and serverUrl only when active
- state.js: export getSseClientCount() so other modules can read the
  number of live SSE connections
- server.js: import getSseClientCount; add GET /api/status returning
  { sseClients, uptime } — lightweight endpoint for UI health display
- SettingsPanel: add wsjtxRelayToServer toggle that immediately PATCHes
  rig-bridge config; handleConfigureWsjtxRelay now also sets
  relayToServer:true when pushing cloud-relay credentials; add SSE
  client count badge with Refresh button so users can verify local
  connections before disabling server relay

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The delivery mode toggle belongs on the source side (rig-bridge) not in
the OHC settings panel.

rig-bridge UI (server.js):
- Replace the flat wsjtx opts section with a two-option delivery mode
  selector: "SSE only" (local/LAN) and "Relay to OHC server" (cloud)
- SSE mode shows only UDP port and multicast options — server fields
  (URL, relay key, session ID, batch interval) are hidden in a separate
  wsjtxServerOpts div that only appears in relay mode
- populateIntegrations() reads relayToServer flag to set the radio
- toggleWsjtxMode() shows/hides the server-specific block
- saveIntegrations() writes relayToServer from the selected radio

OHC SettingsPanel:
- Remove wsjtxRelayToServer state, handleToggleRelayToServer handler,
  fetchSseClientCount handler, toggle UI block and SSE count badge —
  all moved to rig-bridge UI
- relayToServer:true is still sent when the user clicks Configure
  Cloud Relay, since that action explicitly enables server delivery

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt and lib files

- Rewrite WSJT-X Relay section: document SSE-only (default) vs relay-to-server
  delivery modes, three setup options, updated config reference with relayToServer
- Add GET /api/status endpoint to API reference table ({sseClients, uptime})
- Add lib/aprs-parser.js and lib/wsjtx-protocol.js to project structure tree
- Update pluginBus event docs to include status, qso and aprs events
- Add SSE ring-buffer replay note to Digital Mode Plugins section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enables rig-bridge to serve over HTTPS so browsers running OpenHamClock
on an HTTPS origin can connect without mixed-content errors.

- New core/tls.js module: generates RSA-2048 self-signed cert (10-year
  validity, SAN for localhost/127.0.0.1) using node-forge; stores key+cert
  in ~/.config/openhamclock/certs/; exposes ensureCerts(), loadCreds(),
  getCertInfo() (fingerprint, expiry, days remaining)
- core/config.js: bump CONFIG_VERSION 7→8, add tls.{enabled,certGenerated}
  defaults, export CONFIG_DIR
- core/server.js: startServer() made async, switches to https.createServer()
  when tls.enabled, falls back to HTTP on failure; CORS list auto-includes
  https:// variants when TLS is active; POST /api/config made async and
  handles tls section with cert generation and forceRegen support; three
  new routes: GET /api/tls/status, GET /api/tls/cert (download),
  POST /api/tls/install (OS cert install with manual fallback)
- Setup UI: new 🔒 Security tab with HTTPS toggle, certificate fingerprint/
  expiry info, download button, OS-detected install instructions, and
  one-click Install Certificate button with graceful fallback to manual
  command on permission error
- rig-bridge.js: wrapped startup in async IIFE so startServer() is awaited
  before connectActive()/connectIntegrations()
- Add node-forge ^1.4.0 dependency (pure JS, pkg-compatible)
- Update rig-bridge-config.example.json and README with HTTPS setup docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ip clamp, config defaults

- useWSJTX: reset isLocalMode after 30 s SSE silence so HTTP polling resumes
  if rig-bridge disconnects mid-session (was permanently latched)
- APRSPanel: clamp hover tooltip to viewport bounds; remove dead onSpotClick prop
- rig-bridge config: revert ohcUrl default to localhost:8080 (was 3000, dev-only port)
- rig-bridge config: bump CONFIG_VERSION 7 → 8 for relayToServer key addition

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rig-bridge: Direct SSE mode, plugin data pipeline & APRS improvements
The prototype pollution guard used isValidSessionId() to validate
WSJT-X client IDs, but its regex only allowed [A-Za-z0-9_\-:.].
Users with custom instance names containing spaces (e.g.
"WSJT-X - FT991A") had all packets silently rejected.

Add a separate isValidClientId() that only blocks __proto__,
constructor, and prototype while allowing any normal characters.

Closes #846

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Brings in upstream changes from Staging:
- fix(wsjtx): allow spaces in WSJT-X client IDs
- feat: satellite layer improvements (useSatelliteLayer)
- feat: Thai language support
- feat: QRZ lookup for active users
- Various language key updates and corrections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full rewrite focused on clarity and accessibility for non-IT users:

- Added table of contents for easy navigation
- Rewrote all sections in plain language — explains COM ports, localhost,
  API tokens, baud rate, and other concepts inline without jargon
- Reorganised structure: Getting Started → per-radio setup → OHC connection
  → digital modes → APRS → rotator → HTTPS → troubleshooting → advanced
- Added per-radio model setup tables (Yaesu, Icom, Kenwood, Elecraft)
- Rewrote HTTPS section as a complete end-to-end walkthrough including:
  browser security warning steps for Chrome/Edge, Firefox and Safari,
  full manual cert install steps for macOS, Windows and Linux,
  verification checklist, and revert-to-HTTP instructions
- Expanded troubleshooting table with plain-language descriptions
- Moved developer content (API reference, plugin writing, build scripts,
  project structure) to an Advanced Topics section at the bottom

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tls.js: use random 16-byte serial number instead of hardcoded '01' so
  regenerated certificates are not silently returned from OS trust-store
  caches that key on issuer+serial (macOS Keychain, some browsers)
- server.js: fix terminal banner alignment — 🔒 emoji renders as 2 display
  columns, so shortened the text to keep the box correctly aligned
- server.js: move tls module to top-level require (tlsModule) instead of
  inline require() inside each route handler and startServer()
- server.js: replace exec() + shell string interpolation in /api/tls/install
  with execFile() + explicit args array so unusual home directory characters
  (quotes, dollar signs) cannot affect command parsing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(rig-bridge): HTTPS/TLS support + README rewrite
@MichaelWheeley MichaelWheeley merged commit b195e5b into MichaelWheeley:feature/thai-language-dev Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants