mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
fix(host-control-mode): hold desync invariant + stop Solo-badge flicker
Two issues surfaced by a holistic audit of the combined desync-persist + reconcile-on-init changes: 1. Stale desync outside gated-guest state. hcmDesynced is persisted, but neither ROOM_DATA nor CONTROL_MODE cleared it when the local peer became host or the room switched to 'everyone'. Combined with reconcile-on-init, a desynced guest promoted to host (host-leave fallback) while content was reloading could re-adopt desynced=true — self-labelling "Solo" to all peers and ignoring host commands in 'everyone' mode. Enforce the invariant centrally (hcmEnforceDesyncInvariant: desynced ⟹ host-only && !host) on every role/mode change, and guard the content reconcile to only adopt when actually a gated guest. 2. Solo-badge flicker. The host-side PEER_STATUS handler set peer.desynced unconditionally, but the background-driven keepAlive heartbeat omits the field — so every ~30s it clobbered desynced to false, flickering the badge. Only update when present (matching the sibling fields), and include desynced in the background heartbeat so both heartbeat sources are consistent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+20
-2
@@ -680,6 +680,18 @@ async function connect() {
|
||||
}
|
||||
|
||||
|
||||
// Invariant: only a gated guest (host-only room AND not the host) can be
|
||||
// "desynced". Any role/mode change that makes us the host, or switches the room
|
||||
// to 'everyone', must clear the persisted flag — otherwise a stale value would
|
||||
// mislabel us as "Solo" to peers and (in content) keep us ignoring host commands
|
||||
// after the reason to is gone. Call after any controlMode/hostPeerId change.
|
||||
function hcmEnforceDesyncInvariant() {
|
||||
if (hcmDesynced && !(controlMode === CONTROL_MODES.HOST_ONLY && !amHost())) {
|
||||
hcmDesynced = false;
|
||||
if (storageInitialized) chrome.storage.session.set({ hcmDesynced: false });
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastControlMode() {
|
||||
// Notify popup (role badge / host toggle) and the active content tab
|
||||
// (so it can enable/disable the host-only guest gate).
|
||||
@@ -943,6 +955,7 @@ function handleServerEvent(event, data) {
|
||||
// Host Control Mode: adopt room role/mode on (re)join.
|
||||
controlMode = data.controlMode || CONTROL_MODES.EVERYONE;
|
||||
hostPeerId = data.hostPeerId || null;
|
||||
hcmEnforceDesyncInvariant();
|
||||
broadcastControlMode();
|
||||
markRoomPotentiallyIdle();
|
||||
if (currentRoom && Array.isArray(currentRoom.peers)) {
|
||||
@@ -1005,6 +1018,7 @@ function handleServerEvent(event, data) {
|
||||
// Host Control Mode changed (toggle or host-leave fallback).
|
||||
controlMode = data.controlMode || CONTROL_MODES.EVERYONE;
|
||||
hostPeerId = data.hostPeerId || null;
|
||||
hcmEnforceDesyncInvariant();
|
||||
if (currentRoom) {
|
||||
currentRoom.controlMode = controlMode;
|
||||
currentRoom.hostPeerId = hostPeerId;
|
||||
@@ -1215,7 +1229,10 @@ function handleServerEvent(event, data) {
|
||||
peer.mediaTitle = data.mediaTitle !== undefined ? data.mediaTitle : peer.mediaTitle;
|
||||
peer.volume = data.volume !== undefined ? data.volume : peer.volume;
|
||||
peer.muted = data.muted !== undefined ? data.muted : peer.muted;
|
||||
peer.desynced = data.desynced === true;
|
||||
// Only update when present — the background-driven keepAlive
|
||||
// heartbeat omits 'desynced', and clobbering it to false there
|
||||
// would make the host's "Solo" badge flicker between heartbeats.
|
||||
if (data.desynced !== undefined) peer.desynced = data.desynced === true;
|
||||
|
||||
const timeSinceReactive = peer.lastReactiveUpdate ? (Date.now() - peer.lastReactiveUpdate) : Infinity;
|
||||
const ignoreStatus = timeSinceReactive < 300;
|
||||
@@ -1556,7 +1573,8 @@ chrome.alarms.onAlarm.addListener(async (alarm) => {
|
||||
peerId,
|
||||
status: 'heartbeat',
|
||||
username: settings.username,
|
||||
tabTitle: currentTabTitle
|
||||
tabTitle: currentTabTitle,
|
||||
desynced: hcmDesynced
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1440,7 +1440,9 @@
|
||||
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) {
|
||||
// Only when we're actually a gated guest — never adopt a stale flag as the
|
||||
// host or in 'everyone' mode (would self-label "Solo" / ignore commands).
|
||||
if (res.desynced && res.controlMode === 'host-only' && !res.amHost && !hcmDesynced) {
|
||||
hcmDesynced = true;
|
||||
hcmShowBadge();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user