From ec8f56a85d2e52a7ac949d1043f98e6db0db307b Mon Sep 17 00:00:00 2001 From: Timo <6156589+Shik3i@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:08:29 +0200 Subject: [PATCH] =?UTF-8?q?feat(extension):=20lazy-connect=20=E2=80=94=20o?= =?UTF-8?q?nly=20maintain=20WebSocket=20when=20actively=20in=20room?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add connectIntent flag to gate all reconnect attempts - Only auto-connect on startup if roomId exists in storage - Close socket + stop reconnect after leaveRoom or idleLeave - Preserve existing behavior 1:1 when actively in a room - Guard force-sync state and peer list clearing during transient disconnects - Guard WEB_JOIN_REQUEST against empty sanitized roomId --- extension/background.js | 65 +++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/extension/background.js b/extension/background.js index f8c4aa0..7ca9ff9 100644 --- a/extension/background.js +++ b/extension/background.js @@ -176,6 +176,7 @@ let reconnectAttempts = 0; let currentServerUrl = null; let roomIdleSince = null; let lastContentHeartbeatAt = null; +let connectIntent = false; const MAX_RECONNECT_ATTEMPTS = 20; const _RECONNECT_BASE_DELAY = 500; const _RECONNECT_MAX_DELAY = 5000; @@ -404,7 +405,9 @@ function clearTargetTabForIdle() { async function leaveRoomAfterIdleGrace(reason) { if (!currentRoom) return; + connectIntent = false; emit(EVENTS.LEAVE_ROOM, { peerId }); + forceDisconnect(); currentRoom = null; currentTabId = null; currentTabTitle = null; @@ -457,7 +460,9 @@ async function connect() { addLog('Browser is offline. Waiting...', 'warn'); broadcastConnectionStatus('offline'); isConnecting = false; - scheduleReconnect(); + if (currentRoom || connectIntent) { + scheduleReconnect(); + } return; } @@ -564,25 +569,32 @@ async function connect() { isNamespaceJoined = false; stopPing(); - isForceSyncInitiator = false; - forceSyncAcks.clear(); - if (forceSyncTimeout) clearTimeout(forceSyncTimeout); - chrome.storage.session.set({ - isForceSyncInitiator: false, - forceSyncAcks: [], - forceSyncDeadline: null - }).catch(() => {}); + if (!connectIntent && !currentRoom) { + isForceSyncInitiator = false; + forceSyncAcks.clear(); + if (forceSyncTimeout) clearTimeout(forceSyncTimeout); + chrome.storage.session.set({ + isForceSyncInitiator: false, + forceSyncAcks: [], + forceSyncDeadline: null + }).catch(() => {}); + } - if (currentRoom) { + if (currentRoom && !connectIntent) { currentRoom.peers = []; if (storageInitialized) chrome.storage.session.set({ currentRoom }).catch(() => {}); chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: [] }).catch(() => {}); } broadcastConnectionStatus('disconnected'); - addLog('Disconnected. Scheduling reconnect...', 'warn'); - socket = null; - scheduleReconnect(); + if (currentRoom || connectIntent) { + addLog('Disconnected. Scheduling reconnect...', 'warn'); + socket = null; + scheduleReconnect(); + } else { + addLog('Disconnected. No active session — staying disconnected.', 'info'); + socket = null; + } }; socket.onerror = () => { @@ -597,7 +609,9 @@ async function connect() { const errMsg = (e && e.message) ? e.message : String(e || 'Unknown connection error'); addLog(errMsg, logType); broadcastConnectionStatus('disconnected'); - scheduleReconnect(); + if (currentRoom || connectIntent) { + scheduleReconnect(); + } } } @@ -738,6 +752,9 @@ function sendPing() { addLog('Ping timeout reached, force disconnecting to trigger reconnect', 'warn'); pendingPingT = null; forceDisconnect(); + if (currentRoom || connectIntent) { + scheduleReconnect(); + } } pingTimeout = null; }, 5000); @@ -1334,7 +1351,7 @@ chrome.alarms.onAlarm.addListener(async (alarm) => { if (alarm.name === 'keepAlive') { chrome.storage.session.get('keepAlive', () => {}); if (!socket || socket.readyState !== WebSocket.OPEN) { - if (!reconnectFailed) { + if (!reconnectFailed && (currentRoom || connectIntent)) { connect(); } } else if (currentRoom) { @@ -1417,6 +1434,7 @@ async function handleAsyncMessage(message, sender, sendResponse) { if (message.type === 'CONNECT') { const settings = await getSettings(); + connectIntent = !!settings.roomId; const desiredUrl = resolveServerUrl(settings); if (settings.roomId && currentRoom && currentRoom.roomId === settings.roomId && socket && socket.readyState === WebSocket.OPEN && isNamespaceJoined && desiredUrl === currentServerUrl) { @@ -1439,7 +1457,7 @@ async function handleAsyncMessage(message, sender, sendResponse) { } if (desiredUrl !== currentServerUrl || !socket || socket.readyState !== WebSocket.OPEN || !isNamespaceJoined) { if (desiredUrl !== currentServerUrl) forceDisconnect(); - connect(); + if (settings.roomId) connect(); } else if (settings.roomId) { emit(EVENTS.JOIN_ROOM, { roomId: settings.roomId, @@ -1452,6 +1470,7 @@ async function handleAsyncMessage(message, sender, sendResponse) { } sendResponse({ status: 'ok' }); } else if (message.type === 'RETRY_CONNECT') { + connectIntent = true; reconnectFailed = false; reconnectStartTime = null; reconnectAttempts = 0; @@ -1480,6 +1499,7 @@ async function handleAsyncMessage(message, sender, sendResponse) { ping: currentPingMs }); } else if (message.type === 'LEAVE_ROOM') { + connectIntent = false; resetAudioProcessingInTab(currentTabId); emit(EVENTS.LEAVE_ROOM, { peerId }); currentRoom = null; @@ -1510,8 +1530,10 @@ async function handleAsyncMessage(message, sender, sendResponse) { episodeLobby: null, expectedAcksCount: 0 }); + chrome.storage.local.set({ roomId: '', password: '' }).catch(() => {}); addLog('Left Room', 'info'); chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: [] }).catch(() => {}); + forceDisconnect(); sendResponse({ status: 'ok' }); } else if (message.type === 'CLEAR_LOGS') { logs = []; @@ -1526,6 +1548,8 @@ async function handleAsyncMessage(message, sender, sendResponse) { } else if (message.type === 'WEB_JOIN_REQUEST') { const { roomId: rawRoomId, password, useCustomServer, serverUrl } = message; const roomId = typeof rawRoomId === 'string' ? rawRoomId.replace(/[^a-zA-Z0-9\-]/g, '') : ''; + connectIntent = !!roomId; + if (!roomId) { sendResponse({ error: 'invalid_room_id' }); return; } chrome.storage.local.set({ roomId, password, @@ -1554,7 +1578,7 @@ async function handleAsyncMessage(message, sender, sendResponse) { if (desiredUrl !== currentServerUrl || !socket || socket.readyState !== WebSocket.OPEN || !isNamespaceJoined) { if (desiredUrl !== currentServerUrl) forceDisconnect(); connect(); - } else { + } else if (roomId) { emit(EVENTS.JOIN_ROOM, { roomId, password, @@ -1939,5 +1963,8 @@ chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, _tab) => { } }); -// Initial Connect -connect(); +// Initial Connect — only if user has an active room configuration +getSettings().then(settings => { + connectIntent = !!settings.roomId; + if (connectIntent) connect(); +}).catch(() => connectIntent = false);