diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-24 23:47:01 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-24 23:47:01 +0200 |
| commit | a41be5c5d6212f4eadc54bc17354311b0655a5a4 (patch) | |
| tree | 85a46658b9cacf5752c3bfdc0ee513aa303a13d5 | |
| parent | 926ebce735afd01800a669a266b90fc98f673a6b (diff) | |
| download | meshbay-a41be5c5d6212f4eadc54bc17354311b0655a5a4.tar.gz | |
fix(hub): keep an unmatched admin_challenge visible, add trace logging
A real report showed audio_root timing out with *nothing* logged in
between the connection handshake and the timeout — no admin_challenge, no
error, nothing. The previous fix made an unmatched admin_challenge return
silently (correctly, to stop it stealing an unrelated pending request —
see the earlier commit), but that silence is indistinguishable from "the
request never reached the node at all", which is exactly the ambiguity
blocking this investigation. An unmatched admin_challenge is now logged
(op, op_id, and the full set of currently-pending keys) instead of
dropped quietly, and setAudioRoot/_authorizeAdminOp trace both hops of
the round trip explicitly. Node-side, _do_audio_root gets a debug log at
entry — cheap, and the only way to know from server logs alone whether
the request was ever received if the client-side trail comes up empty.
Diagnostic only: no routing behavior changed from the previous fix,
verified against the same reproduction script.
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport.js | 18 | ||||
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py | 2 |
2 files changed, 19 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index 8d8027e..7528a92 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -634,7 +634,9 @@ class MeshBayTransport { */ async setAudioRoot(path, signFn) { const clean = (path || '').replace(/^\/+|\/+$/g, ''); + console.log('[MeshBay] setAudioRoot: sending request, path=', JSON.stringify(clean)); const msg = await this._sendAndWait({ type: 'audio_root', v: '0.10', path: clean }); + console.log('[MeshBay] setAudioRoot: first reply =', msg); if (msg.type === 'error') throw new Error(msg.detail); if (msg.type === 'admin_challenge') { return this._authorizeAdminOp(msg, 'audio_root', clean, signFn); @@ -800,6 +802,8 @@ class MeshBayTransport { challenge.subject, challenge.nonce, challenge.ts); const signature = await signFn(transcript); + console.log('[MeshBay] _authorizeAdminOp: signed', challenge.op, 'op_id=', challenge.op_id, + '— sending admin_response'); const ack = await this._sendAndWait({ type: 'admin_response', v: '0.1', @@ -813,6 +817,7 @@ class MeshBayTransport { // pending) are matched by nothing more than arrival order. op: challenge.op, }); + console.log('[MeshBay] _authorizeAdminOp:', challenge.op, 'admin_response reply =', ack); if (ack.type === 'error') throw new Error(ack.detail); return ack; } @@ -1522,8 +1527,19 @@ class MeshBayTransport { // "oldest pending" here can only ever be wrong, never a fallback // that happens to be right. const key = `admin:${msg.op}`; + let matched = false; for (const [, handler] of this._pending) { - if (handler._key === key) { handler.resolve(msg); break; } + if (handler._key === key) { handler.resolve(msg); matched = true; break; } + } + if (!matched) { + // Should not happen — every caller that can receive this type keys + // its own request the same way. Logged rather than silently + // dropped (the old fallback below at least warned, however wrongly + // it guessed) so a real mismatch is still visible instead of + // looking exactly like the request never left the browser at all. + console.warn('[MeshBay] admin_challenge for op=', msg.op, 'op_id=', msg.op_id, + 'matched no pending request (pending keys:', + [...this._pending.values()].map(h => h._key), ')'); } return; } else if (typeof msg.type === 'string' && msg.type.endsWith('_ack')) { diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py index a5cccaa..6d21175 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py @@ -1862,6 +1862,8 @@ class WebRTCPeerSession: def _do_audio_root(self, msg: dict) -> None: """Same shape as _do_video_root above — the Music app's own entry point.""" path = msg.get("path") + log.debug("audio_root request user=%s path=%r", + (self._user_id or "?")[:8], path) if not isinstance(path, str): self._send({"type": "error", "detail": "Missing or invalid 'path'"}) return |