diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-25 11:28:31 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-25 12:07:49 +0200 |
| commit | b7bf11c077bdd165400cf8d80c5cd1ad4248d854 (patch) | |
| tree | e80590cd7b798e21a4ac663fd1e653a66e5f3b34 /packages/meshbay-hub/src/meshbay_hub/static/transport-pins.js | |
| parent | 6348698322e5bfcb80d0ef0dcd833ba4e6179640 (diff) | |
| download | meshbay-b7bf11c077bdd165400cf8d80c5cd1ad4248d854.tar.gz | |
refactor(client): split transport.js into classic scripts
transport.js keeps the core (connection, reconnect, leases, dispatch).
Chat, media, admin, upload and device methods move, cut as text, into
transport-*.js scripts that hand a class of their own to extendTransport,
which copies each method onto MeshBayTransport.prototype; the codec,
roster checks, node pins and the rewrap fan-out move as they were. Both
shells load them after transport.js. Every prototype member, class
property and top-level function has the same source text as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport-pins.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport-pins.js | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport-pins.js b/packages/meshbay-hub/src/meshbay_hub/static/transport-pins.js new file mode 100644 index 0000000..c0699dc --- /dev/null +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport-pins.js @@ -0,0 +1,86 @@ +// Which node this browser has met under which key (trust on first use), and +// the version range a node declares at the handshake. + +// ── Node identity pinning (11.5.8) ─────────────────────────────────────────── + +const NODE_PIN_PREFIX = 'mb_nodepin_'; + +/** + * The node's half of the version range, from `handshake_challenge`. + * + * Mirrors meshbay_common/handshake.py::check_version(). A node that declares no + * range at all is a node that predates negotiation — that is every 0.x node, and + * none of them can serve a sealed index or a sealed ack — so it is refused here + * rather than left to fail later as a message that will not open. + */ +function _checkNodeVersion(reply) { + const parse = (v) => { + const m = /^(\d+)\.(\d+)$/.exec(String(v || '')); + return m ? [Number(m[1]), Number(m[2])] : null; + }; + const cmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]); + const fail = (reason, message) => { + const e = new Error(message); + e.reason = reason; + throw e; + }; + + const theirs = parse(reply.v); + if (!theirs) { + fail('node_version_unreadable', + 'The node did not declare a readable protocol version.'); + } + // No declared minimum means "only what I speak" — the correct reading of a + // node from before this field existed. + const theirMin = parse(reply.v_min) || theirs; + if (cmp(theirs, parse(MNP_V_MIN)) < 0) { + fail('node_too_old', + 'This node is running an older MeshBay than this page needs. ' + + 'Its operator has to update it.'); + } + if (cmp(theirMin, parse(MNP_V)) > 0) { + fail('client_too_old', + 'This page is older than the node it is talking to. ' + + 'Reload to pick up the current version.'); + } +} + +function _checkNodePin(nodeId, nodePk) { + if (!nodeId || !nodePk) return; + const key = NODE_PIN_PREFIX + nodeId; + + let pinned = null; + try { pinned = localStorage.getItem(key); } catch { return; } + + if (pinned === null) { + try { localStorage.setItem(key, nodePk); } catch {} + return; + } + if (pinned !== nodePk) { + throw new Error( + 'This node\'s identity key has changed. That is expected only if its ' + + 'operator reinstalled the node — otherwise someone may be impersonating ' + + 'it. Verify with the operator out of band, then clear the pin in ' + + 'Settings to accept the new key.'); + } +} + +/** Forget a pinned node identity — the deliberate escape hatch for a legitimate rotation. */ +function clearNodePin(nodeId) { + try { + if (nodeId) localStorage.removeItem(NODE_PIN_PREFIX + nodeId); + else { + for (const k of Object.keys(localStorage)) + if (k.startsWith(NODE_PIN_PREFIX)) localStorage.removeItem(k); + } + } catch {} +} + +function pinnedNodeCount() { + try { + return Object.keys(localStorage).filter(k => k.startsWith(NODE_PIN_PREFIX)).length; + } catch { return 0; } +} + +MeshBayTransport.clearNodePin = clearNodePin; +MeshBayTransport.pinnedNodeCount = pinnedNodeCount; |