fix: audit corrections for sameEpisode, CMD_ACK, lastSeqBySender, forceSyncTimeout

- sameEpisode: return false when one title is null (prevent false lobby completions)
- Episode guard: skip CMD_ACK for FORCE_SYNC_EXECUTE on mismatch (H1)
- Persist lastSeqBySender in chrome.storage.session (M3, survives SW restart)
- Clear forceSyncTimeout before overwriting (H3, pre-existing timer leak)
This commit is contained in:
Koala
2026-05-28 04:45:52 +02:00
parent 1fba2fb69c
commit 762d6425be
2 changed files with 18 additions and 7 deletions
+15 -5
View File
@@ -19,6 +19,10 @@ let lastActionState = { action: null, senderId: null, timestamp: 0, acks: [] };
let localSeq = 0; // Monotonically increasing command sequence for this peer
const lastSeqBySender = {}; // senderId → last received seq (stale command guard)
function _persistLastSeq() {
if (storageInitialized) chrome.storage.session.set({ lastSeqBySender });
}
// --- Boot Sequence Lock ---
let restorationTask = null;
@@ -38,7 +42,7 @@ function ensureState() {
'logs', 'history', 'currentRoom', 'lastActionState',
'eventQueue', 'isForceSyncInitiator', 'forceSyncAcks',
'forceSyncDeadline', 'reconnectFailed', 'reconnectStartTime', 'currentTabId', 'currentTabTitle',
'episodeLobby', 'localSeq'
'episodeLobby', 'localSeq', 'lastSeqBySender'
], (data) => {
clearTimeout(storageTimeout);
if (data.currentTabId !== undefined) currentTabId = data.currentTabId;
@@ -88,6 +92,7 @@ function ensureState() {
}
if (data.localSeq !== undefined && !isNaN(data.localSeq)) localSeq = data.localSeq;
if (data.lastSeqBySender && typeof data.lastSeqBySender === 'object') Object.assign(lastSeqBySender, data.lastSeqBySender);
storageInitialized = true;
@@ -141,12 +146,13 @@ function extractEpisodeId(title) {
}
function sameEpisode(titleA, titleB) {
if (!titleA || !titleB) return true;
if (!titleA && !titleB) return true; // Both unknown → assume same (backward compat)
if (!titleA || !titleB) return false; // One unknown, one known → different
const idA = extractEpisodeId(titleA);
const idB = extractEpisodeId(titleB);
if (idA && idB) return idA === idB;
if (idA || idB) return false;
return titleA === titleB;
if (idA && idB) return idA === idB; // Both have parseable IDs → compare IDs
if (idA || idB) return false; // One has ID, other doesn't → different
return titleA === titleB; // Neither has ID → exact string match
}
// --- Storage Utils ---
@@ -605,6 +611,7 @@ function handleServerEvent(event, data) {
break;
}
lastSeqBySender[data.senderId] = data.seq;
_persistLastSeq();
}
if (data.senderId) {
addToHistory(event, data.senderId);
@@ -626,6 +633,7 @@ function handleServerEvent(event, data) {
const lastSeq = lastSeqBySender[data.senderId];
if (lastSeq !== undefined && data.seq <= lastSeq) break;
lastSeqBySender[data.senderId] = data.seq;
_persistLastSeq();
}
if (isForceSyncInitiator) {
forceSyncAcks.add(data.senderId);
@@ -660,6 +668,7 @@ function handleServerEvent(event, data) {
const lastSeq = lastSeqBySender[data.senderId];
if (lastSeq !== undefined && data.seq <= lastSeq) break;
lastSeqBySender[data.senderId] = data.seq;
_persistLastSeq();
}
if (data?.senderId) {
addToHistory(event, data.senderId);
@@ -1209,6 +1218,7 @@ async function handleAsyncMessage(message, sender, sendResponse) {
routeToContent(EVENTS.FORCE_SYNC_PREPARE, message.payload);
if (forceSyncTimeout) clearTimeout(forceSyncTimeout);
forceSyncTimeout = setTimeout(() => {
if (isForceSyncInitiator) {
addLog('Force Sync: Timeout waiting for ACKs, executing anyway...', 'warn');
+3 -2
View File
@@ -114,7 +114,8 @@
// Returns true if two titles likely refer to the same episode.
// Strict: both must have IDs and match, OR neither has IDs and exact match.
function sameEpisode(titleA, titleB) {
if (!titleA || !titleB) return true; // Can't compare, assume same (backward compat)
if (!titleA && !titleB) return true; // Both unknown → assume same (backward compat)
if (!titleA || !titleB) return false; // One unknown, one known → different
const idA = extractEpisodeId(titleA);
const idB = extractEpisodeId(titleB);
if (idA && idB) return idA === idB; // Both have parseable IDs → compare IDs
@@ -340,7 +341,7 @@
const myTitle = getMediaTitle();
if (isDifferentEpisode(senderTitle, myTitle)) {
reportLog(`Episode mismatch: sender="${senderTitle || '?'}" vs mine="${myTitle || '?'}" — skipping ${action}. Disable "Auto-Sync next Episode" in settings if this causes issues.`, 'warn');
if (action !== EVENTS.FORCE_SYNC_PREPARE) {
if (action !== EVENTS.FORCE_SYNC_PREPARE && action !== EVENTS.FORCE_SYNC_EXECUTE) {
chrome.runtime.sendMessage({ type: 'CMD_ACK', actionTimestamp: message.actionTimestamp, commandSenderId: message.commandSenderId });
}
return;