From 221961820a01eb8b4e1055a174f752475dcc6617 Mon Sep 17 00:00:00 2001 From: Timo <6156589+Shik3i@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:20:32 +0200 Subject: [PATCH] feat: implement visual confirmation system and redesigned Sync tab --- extension/background.js | 45 ++++++++++++++++++++++++++++-- extension/content.js | 4 +++ extension/popup.html | 32 ++++++++++----------- extension/popup.js | 62 +++++++++++++++++++++++++++++++++++++++++ server/index.js | 11 ++++++++ shared/constants.js | 1 + 6 files changed, 136 insertions(+), 19 deletions(-) diff --git a/extension/background.js b/extension/background.js index f891a70..4130bb3 100644 --- a/extension/background.js +++ b/extension/background.js @@ -18,12 +18,15 @@ let pendingLogs = []; let pendingHistory = []; let eventQueue = []; let isNamespaceJoined = false; +let lastActionState = { action: null, senderId: null, timestamp: 0, acks: [] }; +let currentCommandSenderId = null; // Track who sent the last command we are executing // Restore state from session storage -chrome.storage.session.get(['logs', 'history', 'currentRoom'], (data) => { +chrome.storage.session.get(['logs', 'history', 'currentRoom', 'lastActionState'], (data) => { if (data.logs) logs = data.logs; if (data.history) history = data.history; if (data.currentRoom) currentRoom = data.currentRoom; + if (data.lastActionState) lastActionState = data.lastActionState; storageInitialized = true; if (pendingLogs.length > 0) { @@ -383,7 +386,8 @@ function handleServerEvent(event, data) { case EVENTS.FORCE_SYNC_PREPARE: if (data.senderId) { addToHistory(event, data.senderId); - showNotification(data.senderId, event); + showNotification(event, data.senderId); + updateLastAction(event, data.senderId); } routeToContent(event, data); break; @@ -405,6 +409,15 @@ function handleServerEvent(event, data) { } routeToContent(event, data); break; + case EVENTS.EVENT_ACK: + if (lastActionState && lastActionState.action && data.senderId) { + if (!lastActionState.acks.includes(data.senderId)) { + lastActionState.acks.push(data.senderId); + if (storageInitialized) chrome.storage.session.set({ lastActionState }); + chrome.runtime.sendMessage({ type: 'ACTION_UPDATE', state: lastActionState }).catch(() => {}); + } + } + break; case EVENTS.PEER_STATUS: if (currentRoom) { if (data.status === 'joined') { @@ -449,6 +462,17 @@ function executeForceSync() { addLog('Force Sync Executed', 'success'); } +function updateLastAction(action, senderId) { + lastActionState = { + action, + senderId, + timestamp: Date.now(), + acks: [] + }; + if (storageInitialized) chrome.storage.session.set({ lastActionState }); + chrome.runtime.sendMessage({ type: 'ACTION_UPDATE', state: lastActionState }).catch(() => {}); +} + async function routeToContent(action, payload) { if (!currentTabId) { const settings = await getSettings(); @@ -459,6 +483,8 @@ async function routeToContent(action, payload) { const tabId = parseInt(currentTabId); if (isNaN(tabId)) return; + currentCommandSenderId = payload.senderId || null; + chrome.tabs.sendMessage(tabId, { type: 'SERVER_COMMAND', action, @@ -530,7 +556,12 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { const isConnected = socket && socket.readyState === WebSocket.OPEN && isNamespaceJoined; let status = isConnected ? 'connected' : (isConnecting || (socket && socket.readyState === WebSocket.CONNECTING) ? 'connecting' : 'disconnected'); if (reconnectFailed) status = 'reconnect_failed'; - sendResponse({ status, peerId, peers: currentRoom ? currentRoom.peers : [] }); + sendResponse({ + status, + peerId, + peers: currentRoom ? currentRoom.peers : [], + lastActionState + }); // Global return true at the end handles this } else if (message.type === 'LEAVE_ROOM') { emit(EVENTS.LEAVE_ROOM, { peerId }); @@ -610,6 +641,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { routeToContent(message.action, message.payload); } + // Update local state as initiator + updateLastAction(message.action, 'You'); + // Events coming from content script or popup if (message.action === EVENTS.FORCE_SYNC_PREPARE) { isForceSyncInitiator = true; @@ -640,6 +674,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { } else { emit(EVENTS.FORCE_SYNC_ACK, { peerId }); } + } else if (message.type === 'CMD_ACK') { + // Content script successfully ran a command. Send ACK back to the initiator. + if (currentCommandSenderId && currentCommandSenderId !== peerId) { + emit(EVENTS.EVENT_ACK, { senderId: peerId, targetId: currentCommandSenderId }); + } } else if (message.type === 'HEARTBEAT') { if (sender.tab) { currentTabId = sender.tab.id; diff --git a/extension/content.js b/extension/content.js index 904395e..71f2e7d 100644 --- a/extension/content.js +++ b/extension/content.js @@ -127,10 +127,13 @@ if (action === EVENTS.PLAY) { tryMediaAction(EVENTS.PLAY); + chrome.runtime.sendMessage({ type: 'CMD_ACK' }); } else if (action === EVENTS.PAUSE) { tryMediaAction(EVENTS.PAUSE); + chrome.runtime.sendMessage({ type: 'CMD_ACK' }); } else if (action === EVENTS.SEEK) { tryMediaAction(EVENTS.SEEK, payload); + chrome.runtime.sendMessage({ type: 'CMD_ACK' }); } else if (action === EVENTS.FORCE_SYNC_PREPARE) { if (!payload || payload.targetTime === undefined) return; const video = findVideo(); @@ -144,6 +147,7 @@ } } else if (action === EVENTS.FORCE_SYNC_EXECUTE) { tryMediaAction(EVENTS.PLAY); + chrome.runtime.sendMessage({ type: 'CMD_ACK' }); } } diff --git a/extension/popup.html b/extension/popup.html index d110c89..293b075 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -277,26 +277,19 @@ -