From 2928393b4c6ee4823de6936feae28e0b35c2824a Mon Sep 17 00:00:00 2001 From: KoalaDev <6156589+Shik3i@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:45:33 +0200 Subject: [PATCH] fix(host-control-mode): reconcile desync state on content reinjection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a split-brain where a guest who chose "watch on my own" and then reloaded the video page would appear stuck as "Solo" to the host while actually being back in sync. Root cause: background persists hcmDesynced (survives SW restart) and attaches it to content-driven heartbeats, but a freshly injected content script started with hcmDesynced=false and GET_CONTROL_MODE never returned the desync state — so content re-applied host commands (synced) while background kept relaying desynced=true to the host. Fix: GET_CONTROL_MODE now also returns desynced; content adopts it on init (re-shows the badge) and also adopts hostPeerId so later host-change resets work. Desync now survives a page reload consistently on both sides, as intended. Verified the full path: background.js heartbeat (1555 omits / 2018 attaches), reset paths, CONTENT_BOOT and keepAlive-disconnect (neither reset it), content sendHeartbeat on inject. Co-Authored-By: Claude Opus 4.8 --- extension/background.js | 7 +++++-- extension/content.js | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/extension/background.js b/extension/background.js index 5ab34f4..dfac02a 100644 --- a/extension/background.js +++ b/extension/background.js @@ -1705,8 +1705,11 @@ async function handleAsyncMessage(message, sender, sendResponse) { emit(EVENTS.SET_CONTROL_MODE, { controlMode: mode }); sendResponse({ status: 'ok' }); } else if (message.type === 'GET_CONTROL_MODE') { - // content.js asks for current mode/role (e.g. injected after ROOM_DATA). - sendResponse({ controlMode, hostPeerId, amHost: amHost() }); + // content.js asks for current mode/role on (re)injection. Include the + // persisted desync state so a page reload re-adopts it — otherwise a fresh + // content script would start synced while background keeps relaying us as + // "Solo" to the host (stale-badge split-brain). + sendResponse({ controlMode, hostPeerId, amHost: amHost(), desynced: hcmDesynced }); } else if (message.type === 'REQUEST_HOST_SYNC') { // content.js resync: hand back the host's extrapolated current position. sendResponse({ target: getHostSyncTarget() }); diff --git a/extension/content.js b/extension/content.js index 4f1b385..66f97d3 100644 --- a/extension/content.js +++ b/extension/content.js @@ -1435,6 +1435,13 @@ if (chrome.runtime.lastError || !res) return; hcmControlMode = res.controlMode || 'everyone'; hcmAmHost = !!res.amHost; + hcmHostPeerId = res.hostPeerId || null; + // Re-adopt persisted desync after a page reload so we don't start synced + // while background still relays us as "Solo" to the host (split-brain). + if (res.desynced && !hcmDesynced) { + hcmDesynced = true; + hcmShowBadge(); + } }); })();