fix: sync FORCE_SYNC_TIMEOUT constant with actual usage, tokenize showError cleanup, fix EVENT_ACK indent

- FORCE_SYNC_TIMEOUT in shared/constants.js was 5000 but all code uses
  8500ms. Update constant to 8500 and reference it in background.js
  instead of hardcoded values (4 occurrences)
- Add errorToken counter in popup showError() so stale 5s timeout
  doesn't clear styling of a newer error that arrived in between
- Fix EVENT_ACK handler indentation in server/index.js to match
  surrounding socket.on handlers
This commit is contained in:
Koala
2026-05-25 12:28:22 +02:00
parent eb5515fc1b
commit 8b3f9e1242
4 changed files with 32 additions and 29 deletions
+5 -5
View File
@@ -1,4 +1,4 @@
import { EVENTS, PROTOCOL_VERSION, OFFICIAL_SERVER_URL, OFFICIAL_SERVER_TOKEN, APP_VERSION, EPISODE_LOBBY_TIMEOUT } from './shared/constants.js'; import { EVENTS, PROTOCOL_VERSION, OFFICIAL_SERVER_URL, OFFICIAL_SERVER_TOKEN, APP_VERSION, EPISODE_LOBBY_TIMEOUT, FORCE_SYNC_TIMEOUT } from './shared/constants.js';
// --- State Management --- // --- State Management ---
let socket = null; let socket = null;
@@ -849,7 +849,7 @@ function executeEpisodeLobby() {
isForceSyncInitiator = true; isForceSyncInitiator = true;
forceSyncAcks.clear(); forceSyncAcks.clear();
const deadline = Date.now() + 8500; const deadline = Date.now() + FORCE_SYNC_TIMEOUT;
const timestamp = Date.now(); const timestamp = Date.now();
updateLastAction(EVENTS.FORCE_SYNC_PREPARE, 'You', timestamp); updateLastAction(EVENTS.FORCE_SYNC_PREPARE, 'You', timestamp);
lastActionState.targetTime = 0.0; lastActionState.targetTime = 0.0;
@@ -869,7 +869,7 @@ function executeEpisodeLobby() {
addLog('Force Sync (Episode): Timeout waiting for ACKs, executing anyway...', 'warn'); addLog('Force Sync (Episode): Timeout waiting for ACKs, executing anyway...', 'warn');
executeForceSync(); executeForceSync();
} }
}, 8500); }, FORCE_SYNC_TIMEOUT);
} }
function checkEpisodeLobbyCompletion() { function checkEpisodeLobbyCompletion() {
@@ -1149,7 +1149,7 @@ async function handleAsyncMessage(message, sender, sendResponse) {
if (message.action === EVENTS.FORCE_SYNC_PREPARE) { if (message.action === EVENTS.FORCE_SYNC_PREPARE) {
isForceSyncInitiator = true; isForceSyncInitiator = true;
forceSyncAcks.clear(); forceSyncAcks.clear();
const deadline = Date.now() + 8500; const deadline = Date.now() + FORCE_SYNC_TIMEOUT;
chrome.storage.session.set({ chrome.storage.session.set({
isForceSyncInitiator: true, isForceSyncInitiator: true,
forceSyncAcks: [], forceSyncAcks: [],
@@ -1164,7 +1164,7 @@ async function handleAsyncMessage(message, sender, sendResponse) {
addLog('Force Sync: Timeout waiting for ACKs, executing anyway...', 'warn'); addLog('Force Sync: Timeout waiting for ACKs, executing anyway...', 'warn');
executeForceSync(); executeForceSync();
} }
}, 8500); }, FORCE_SYNC_TIMEOUT);
} }
addToHistory(message.action, 'You'); addToHistory(message.action, 'You');
emit(message.action, { ...message.payload, peerId }); emit(message.action, { ...message.payload, peerId });
+3
View File
@@ -57,6 +57,7 @@ let joinBtnTimeout = null;
let forceSyncResetTimer = null; let forceSyncResetTimer = null;
let popupIntervals = []; let popupIntervals = [];
let populateTabsToken = null; let populateTabsToken = null;
let errorToken = 0;
let forceSyncDone = false; let forceSyncDone = false;
// --- Initialization --- // --- Initialization ---
@@ -838,6 +839,7 @@ function showToast(message, type = 'info', duration = 3000) {
function showError(msg) { function showError(msg) {
if (!elements.roomError) return; if (!elements.roomError) return;
const currentToken = ++errorToken;
elements.roomError.textContent = msg; elements.roomError.textContent = msg;
elements.roomError.style.display = 'block'; elements.roomError.style.display = 'block';
elements.roomId.style.borderColor = 'var(--error)'; elements.roomId.style.borderColor = 'var(--error)';
@@ -846,6 +848,7 @@ function showError(msg) {
showToast(msg, 'error', 5000); showToast(msg, 'error', 5000);
setTimeout(() => { setTimeout(() => {
if (currentToken !== errorToken) return;
if (elements.roomError) elements.roomError.style.display = 'none'; if (elements.roomError) elements.roomError.style.display = 'none';
elements.roomId.style.borderColor = ''; elements.roomId.style.borderColor = '';
elements.password.style.borderColor = ''; elements.password.style.borderColor = '';
+23 -23
View File
@@ -480,30 +480,30 @@ io.on('connection', (socket) => {
} }
}); });
socket.on(EVENTS.EVENT_ACK, (data) => { socket.on(EVENTS.EVENT_ACK, (data) => {
if (!checkEventRate(socket.id)) { if (!checkEventRate(socket.id)) {
log('SECURITY', `Event rate limit exceeded for socket (ACK): ${socket.id}`); log('SECURITY', `Event rate limit exceeded for socket (ACK): ${socket.id}`);
socket.disconnect(true); socket.disconnect(true);
return; return;
} }
if (!data || typeof data !== 'object') return; if (!data || typeof data !== 'object') return;
if (typeof data.targetId !== 'string') return; if (typeof data.targetId !== 'string') return;
if (data.actionTimestamp !== undefined && (typeof data.actionTimestamp !== 'number' || !Number.isFinite(data.actionTimestamp))) return; if (data.actionTimestamp !== undefined && (typeof data.actionTimestamp !== 'number' || !Number.isFinite(data.actionTimestamp))) return;
const senderMapping = socketToRoom.get(socket.id); const senderMapping = socketToRoom.get(socket.id);
const targetSocketId = peerToSocket.get(data.targetId); const targetSocketId = peerToSocket.get(data.targetId);
const targetMapping = targetSocketId ? socketToRoom.get(targetSocketId) : null; const targetMapping = targetSocketId ? socketToRoom.get(targetSocketId) : null;
// Security: Only relay ACK if both peers are in the same room // Security: Only relay ACK if both peers are in the same room
if (senderMapping && targetMapping && senderMapping.roomId === targetMapping.roomId) { if (senderMapping && targetMapping && senderMapping.roomId === targetMapping.roomId) {
io.to(targetSocketId).emit(EVENTS.EVENT_ACK, { io.to(targetSocketId).emit(EVENTS.EVENT_ACK, {
senderId: senderMapping.peerId, senderId: senderMapping.peerId,
actionTimestamp: data.actionTimestamp actionTimestamp: data.actionTimestamp
}); });
} else { } else {
log('SECURITY', `Blocked cross-room ACK attempt from ${socket.id} to ${data.targetId}`); log('SECURITY', `Blocked cross-room ACK attempt from ${socket.id} to ${data.targetId}`);
} }
}); });
socket.on('disconnect', () => { socket.on('disconnect', () => {
eventCounts.delete(socket.id); eventCounts.delete(socket.id);
+1 -1
View File
@@ -40,5 +40,5 @@ export const EVENTS = {
}; };
export const HEARTBEAT_INTERVAL = 15000; // 15s export const HEARTBEAT_INTERVAL = 15000; // 15s
export const FORCE_SYNC_TIMEOUT = 5000; // 5s timeout for ACKs export const FORCE_SYNC_TIMEOUT = 8500; // 8.5s timeout for force sync ACKs (must be > content.js poll timeout of 8s)
export const EPISODE_LOBBY_TIMEOUT = 60000; // 60s timeout for episode lobby export const EPISODE_LOBBY_TIMEOUT = 60000; // 60s timeout for episode lobby