mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-05 17:07:43 +00:00
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:
+15
-5
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user