Files
meshcore-analyzer/public
efiten b5c7612166 fix(live): use the shared WebSocket instead of opening a second one (#1991)
Closes #1980.

`app.js` opens a WebSocket on every page load and fans messages out
through `onWS()`/`offWS()`. `live.js` ignored that channel and opened
its own socket to the same endpoint. `Hub.Broadcast` does no per-client
filtering, so both carried the identical full packet stream and **every
viewer on the live map pulled it twice**. That page is the one people
leave open for hours, so it was a standing multiplier on origin
bandwidth rather than a burst.

## Measured, before and after

Against a running instance, leaving the live map and returning while
counting WebSocket constructions:

| | new sockets on re-entry | constructed by |
|---|---|---|
| before | 1 | `at connectWS (live.js:3315)` |
| after | 0 | nothing |

And with one viewer on the live map, the server now reports **one**
WebSocket client for that viewer, with the live feed counter climbing
normally (7 to 31 over one navigation cycle, 71 on a fresh load).

## The change

The live map subscribes to the shared channel like every other view, and
unsubscribes in `destroy()` rather than closing a socket the rest of the
app still needs.

`connectWS()` drops any existing registration before adding a fresh one.
An early return looks like the natural guard against double
subscription, but it keeps the previous visit's closure registered, and
re-registering without dropping the old one renders every packet twice.
Dropping first is idempotent either way and always binds the current
page.

Reconnection becomes `app.js`'s business, since it owns the socket. That
moved `WS_RECONNECT_MS` out of the only place that honoured it, so
`app.js` now uses it too. It comes from `roles.js` and operators set it
as `wsReconnectMs`; after this change it applies to the one socket
everyone shares, or to nothing at all.

## Tests

Three, in the sandbox that already loads `live.js` with a Leaflet mock:

- the page registers exactly one listener on the shared channel and
constructs no WebSocket of its own
- re-entering leaves exactly one listener, and it is the new one rather
than the previous visit's
- the handler ignores messages that carry no packet

`node test-frontend-helpers.js` passes (726 assertions), `node
test-packet-filter.js` passes.

## One note on the history

The second commit on this branch claims the early-return guard broke
rendering on re-entry, citing a real measurement. The measurement
happened, the attribution was wrong: the zero counter came from the
WebSocket constructor patch I had installed to count sockets, which
interfered with the page it was measuring. The third commit records that
rather than rewriting it away. The change is kept because dropping the
old registration first is the clearer contract, not because the guard
was broken.

## Rule 0

Strictly less work than before: one socket per viewer instead of two,
one JSON parse instead of two per packet, and no second reconnect loop.
Nothing is added to the hot path; a listener already existed for every
other view.
2026-09-09 23:16:13 +02:00
..