let ws = null; let roomCode = null; let serverUrl = null; let connectState = 'disconnected'; // 'disconnected' | 'connecting' | 'connected' function send(msg) { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(msg)); } } function notifyPopup(msg) { chrome.runtime.sendMessage(msg).catch(() => {}); } function notifyAllTabs(msg) { chrome.tabs.query({}, (tabs) => { for (const tab of tabs) { chrome.tabs.sendMessage(tab.id, msg).catch(() => {}); } }); } function connect(url) { if (ws) { ws.close(); ws = null; } serverUrl = url; connectState = 'connecting'; try { ws = new WebSocket(url); } catch { connectState = 'disconnected'; notifyPopup({ type: 'ws_status', status: 'error' }); return; } ws.onopen = () => { connectState = 'connected'; notifyPopup({ type: 'ws_status', status: 'connected' }); }; ws.onclose = () => { connectState = 'disconnected'; roomCode = null; notifyPopup({ type: 'ws_status', status: 'disconnected' }); }; ws.onerror = () => { connectState = 'disconnected'; notifyPopup({ type: 'ws_status', status: 'error' }); }; ws.onmessage = (event) => { let msg; try { msg = JSON.parse(event.data); } catch { return; } switch (msg.type) { case 'created': case 'joined': roomCode = msg.code; notifyPopup(msg); break; case 'error': case 'peer_joined': case 'peer_left': notifyPopup(msg); break; case 'sync': notifyAllTabs({ type: 'apply_sync', action: msg.action, time: msg.time }); break; } }; } // Keep service worker alive and reconnect if needed chrome.alarms.create('keepalive', { periodInMinutes: 0.4 }); chrome.alarms.onAlarm.addListener((alarm) => { if (alarm.name !== 'keepalive') return; if (ws && ws.readyState === WebSocket.OPEN) { send({ type: 'ping' }); } else if (serverUrl && connectState !== 'connecting') { connect(serverUrl); } }); chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => { switch (msg.type) { case 'connect': chrome.storage.local.set({ serverUrl: msg.url }); connect(msg.url); sendResponse({ ok: true }); break; case 'disconnect': serverUrl = null; roomCode = null; if (ws) ws.close(); sendResponse({ ok: true }); break; case 'create_room': send({ type: 'create' }); sendResponse({ ok: true }); break; case 'join_room': send({ type: 'join', code: msg.code }); sendResponse({ ok: true }); break; case 'media_event': if (roomCode) { send({ type: 'sync', action: msg.action, time: msg.time }); } sendResponse({ ok: true }); break; case 'get_state': sendResponse({ connectState, roomCode }); break; } return true; }); // Restore WS connection on service worker restart chrome.storage.local.get(['serverUrl'], ({ serverUrl: saved }) => { if (saved) connect(saved); });