Fix Disney+ force sync, seek, and HCM regressions for v2.5.3

The v2.5.2 Disney+ page-API integration leaked blob-relative <video>
time into force sync, seeks, and heartbeats when the page-API bridge
had no fresh data, so force sync on Disney+ appeared broken.

- getSyncCurrentTime/getSyncDuration now refuse native values on Disney+
  (return null/0) so stale bridge data degrades to a clean no-op instead
  of broadcasting garbage to peers. The get_current_time handler and
  episode/lobby/hcmIsLive paths are routed through the same accessor.
- Validate FORCE_SYNC_PREPARE/SEEK payloads as finite before relaying;
  the internal coercion no longer treats '' as 0.
- Stop double-routing FORCE_SYNC_PREPARE from the popup path (the
  generic popup route now covers only play/pause/seek).
- popup force-sync: exclude null/empty peer times from the jump-to-others
  median (Number(null)===0 was dragging the target to 0), guard against
  NaN end-to-end, clear the dangling reset timer on failure, and retry
  without re-injecting when the content script responds but the Disney+
  bridge has not yet delivered a finite time.
- hcmIsLive skips the native-duration live signal on Disney+ only,
  preserving YouTube/Twitch Infinity-duration live detection.

Disney-specific logic remains strictly gated to disneyplus.com; no
Netflix/YouTube/Twitch/generic path is affected.
This commit is contained in:
Timo
2026-07-02 16:57:48 +02:00
parent 6ccaf45f8e
commit 869c64171b
5 changed files with 126 additions and 51 deletions
+33 -2
View File
@@ -2227,12 +2227,40 @@ async function handleAsyncMessage(message, sender, sendResponse) {
return;
}
const payload = message.payload && typeof message.payload === 'object' ? message.payload : {};
const payloadNumber = (value) => value !== undefined && value !== null && value !== '' ? Number(value) : NaN;
if (message.action === EVENTS.FORCE_SYNC_PREPARE) {
const targetTime = payloadNumber(payload.targetTime);
if (!Number.isFinite(targetTime)) {
sendResponse({ status: 'invalid_params' });
return;
}
payload.targetTime = targetTime;
} else if (message.action === EVENTS.SEEK) {
const targetTime = payloadNumber(payload.targetTime !== undefined ? payload.targetTime : payload.currentTime);
if (!Number.isFinite(targetTime)) {
sendResponse({ status: 'invalid_params' });
return;
}
payload.currentTime = targetTime;
payload.targetTime = targetTime;
}
const timestamp = Date.now();
localSeq++;
chrome.storage.session.set({ localSeq });
updateLastAction(message.action, 'You', timestamp);
const payload = message.payload || {};
const hasPlaybackTime = Number.isFinite(payload.currentTime) || Number.isFinite(payload.targetTime);
if (!sender?.tab && (message.action === EVENTS.PLAY || message.action === EVENTS.PAUSE) && !hasPlaybackTime) {
const tabId = currentTabId ? parseInt(currentTabId) : NaN;
if (!isNaN(tabId)) {
const state = await getReadyTabVideoState(tabId);
if (state && !state.error && state.found && Number.isFinite(state.currentTime)) {
payload.currentTime = state.currentTime;
}
}
}
lastActionState.targetTime = payload.targetTime !== undefined ? payload.targetTime : payload.currentTime;
if (storageInitialized) chrome.storage.session.set({ lastActionState });
@@ -2246,6 +2274,10 @@ async function handleAsyncMessage(message, sender, sendResponse) {
currentTime: payload.currentTime !== undefined ? payload.currentTime : (payload.targetTime !== undefined ? payload.targetTime : undefined)
});
if (!sender?.tab && (message.action === EVENTS.PLAY || message.action === EVENTS.PAUSE || message.action === EVENTS.SEEK)) {
routeToContent(message.action, message.payload);
}
if (message.action === EVENTS.FORCE_SYNC_PREPARE) {
isForceSyncInitiator = true;
forceSyncAcks.clear();
@@ -2299,7 +2331,6 @@ async function handleAsyncMessage(message, sender, sendResponse) {
sendResponse({ status: 'error' });
});
} else {
routeToContent(message.action, message.payload);
processEvent().catch(err => {
addLog('Content event privacy error: ' + err.message, 'error');
sendResponse({ status: 'error' });