From 4ebf353e2f30bd4139f183cc956132583be5ff4d Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Thu, 4 Jun 2026 11:27:33 -0400 Subject: [PATCH] zebra-spaces: tap-to-resume for Firefox Android autoplay block + kick clears auto-rejoin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes pulled from today's QA: 1. activateListenerAudio() existed in the page but was orphaned — never called from anywhere after a prior refactor. Wire it back via flagAudioBlocked(): when attachSfuTrack's play() promise rejects (Firefox Android autoplay block past the entry gesture window), set audioPlaybackBlocked=true, show a warn notice "Audio paused by browser. Tap anywhere to resume." and arm a single document-level click/touchstart listener. First tap fires activateListenerAudio() inside the gesture, retries play() on every paused element (rtc + stream), clears the notice. Telemetry that exposed this: phone showed `rtc[3110] rs=4 ns=1 pa=1 ct=0.00` — element had data, network idle, paused, never started playing. No recovery path until the user reloaded. Scoped — the document-click handler is armed only while the flag is true, removed on first tap. Doesn't add noise to working sessions (which was why the prior tap-anywhere was reverted). 2. Kick clears sessionStorage[ACTIVE_CALL_KEY]. The confirm() text says "they can rejoin" — but that means MANUALLY (type the code, click enter), not automatically on a hard-refresh / bfcache restore via auto-rejoin. Fox 2026-06-04: "we have auto-join on the phone that is kicked just rejoins". Closes that loophole; victim can still re-enter manually. --- web/zebra-spaces.html | 61 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index d911724..892c79f 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -1667,9 +1667,13 @@ function attachSfuTrack(uuid, stream){ (t0 ? ' tr0={en='+t0.enabled+' mu='+t0.muted+' rs='+t0.readyState+'}' : '')); try { const p = a.play(); - if (p && p.catch) p.catch(e => logLine('err','rtc autoplay '+uuid.slice(0,4)+': '+e.message)); + if (p && p.catch) p.catch(e => { + logLine('err','rtc autoplay '+uuid.slice(0,4)+': '+e.message); + flagAudioBlocked('rtc autoplay '+uuid.slice(0,4)); + }); } catch(e){ logLine('err','rtc play threw '+uuid.slice(0,4)+': '+e.message); + flagAudioBlocked('rtc play threw '+uuid.slice(0,4)); } stopMeter(uuid); startMeter(uuid, stream); logLine('', 'sfu: receiving '+((members.get(uuid)||{}).handle || uuid)); @@ -3800,6 +3804,13 @@ async function handleSignal(raw){ * down our SFU + mesh PCs too so we actually stop hearing / * broadcasting — closing the WS alone leaves the WebRTC paths up. */ wantConnected = false; + /* Clear the active-call key so a hard-refresh / bfcache restore + * doesn't sneak the kicked user right back in via auto-rejoin. + * The confirm() text says "they can rejoin" — but that means + * MANUALLY (type the code, click enter), not automatically on + * any tab event. Fox 2026-06-04: "we have auto-join on the + * phone that is kicked just rejoins". */ + try { sessionStorage.removeItem(ACTIVE_CALL_KEY); } catch(_){} const noticeVerb = m.action === 'kick' ? 'kicked from' : m.action === 'ban' ? 'banned from' : 'removed from'; @@ -4269,19 +4280,49 @@ let selfListenerMode = false; function activateListenerAudio(){ /* Fire .play() on every existing