From f9026bf623fdf646c644c8c7b5aa97eed2a89bdd Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Tue, 2 Jun 2026 12:31:11 -0400 Subject: [PATCH] =?UTF-8?q?zebra-spaces:=20idempotent=20welcome=20on=20sig?= =?UTF-8?q?nal=20reconnect=20=E2=80=94=20quiet=20'already=20publishing'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signal-WS reconnects periodically (network blips, tab background- ing). Each reconnect produces a fresh welcome → onRoleEntered() fired the full setup again, which called sfuPublish() → it bailed out via the 'already publishing' early-return + logged it as an alarm. Looked exactly like 'something kicked out my speaker' in the log even though the existing mic publish was still healthy. Two fixes: - welcome handler detects re-entry by checking myUUID === m.your_uuid. When true, just refresh role + state + flushSfuStreams + renderRoom and break — don't re-run sessionStorage saves, spotlight broadcasts, onRoleEntered, etc. - sfuPublish silently returns when sfuPubPC is non-null (still idempotent, just not log-noisy). ICE-failure recovery on sub PC stays unchanged — that's a real 'failed' state, not a duplicate welcome. --- web/zebra-spaces.html | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index 9607626..25b065d 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -1868,7 +1868,7 @@ function renderCameraTile(pubHex, stream, opts){ return renderVideoTile('camera' function removeCameraTile(pubHex){ return removeVideoTile('camera', pubHex); } async function sfuPublish(){ - if (sfuPubPC){ logLine('','sfu publish: already publishing'); return; } + if (sfuPubPC) return; /* idempotent — re-entry from signal reconnect is fine */ if (!micStream || !myKeys || !roomID){ logLine('err','sfu publish skipped: mic='+(!!micStream)+' keys='+(!!myKeys)+' room='+(!!roomID)); return; @@ -2618,9 +2618,23 @@ async function handleSignal(raw){ let m; try { m = JSON.parse(raw); } catch(_){ return; } switch (m.type){ case 'welcome': - myUUID = m.your_uuid; myRole = m.role; roomEpoch = m.epoch; - applyState(m.state); - roomMachines.call.send('WELCOME', { uuid: myUUID, role: myRole }); + { + /* a second welcome arrives whenever the signal-WS reconnects. + * If our uuid hasn't changed, the SFU + mesh are still alive — + * just refresh state + role, don't re-run the whole setup + * (that's what caused 'sfu publish: already publishing' + + * other duplicate-effect noise during reconnect cycles). */ + const reentry = !!myUUID && myUUID === m.your_uuid; + myUUID = m.your_uuid; myRole = m.role; roomEpoch = m.epoch; + applyState(m.state); + roomMachines.call.send('WELCOME', { uuid: myUUID, role: myRole }); + if (reentry){ + logLine('', 'signal re-welcomed — role still '+myRole); + flushSfuStreams(); + renderRoom(); + break; + } + } /* any tile auto-spotlit before welcome (during fast-path subscribe) * needs to be broadcast now so the room sees our viewing state */ spotlights.set(myUUID, spotlightKey()); @@ -3491,8 +3505,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');