Initial commit: SyncWatch Chrome extension + WebSocket server

This commit is contained in:
2026-05-23 22:22:19 -04:00
commit 24d88f4754
8 changed files with 649 additions and 0 deletions

109
extension/popup.js Normal file
View File

@@ -0,0 +1,109 @@
const $ = (id) => document.getElementById(id);
const connectSection = $('connect-section');
const roomSection = $('room-section');
const serverUrlInput = $('server-url');
const connectBtn = $('connect-btn');
const roomControls = $('room-controls');
const inRoom = $('in-room');
const createBtn = $('create-btn');
const codeInput = $('code-input');
const joinBtn = $('join-btn');
const disconnectBtn = $('disconnect-btn');
const roomCodeEl = $('room-code');
const peerStatusEl = $('peer-status');
const errorMsg = $('error-msg');
function showError(text) {
errorMsg.textContent = text;
errorMsg.classList.remove('hidden');
setTimeout(() => errorMsg.classList.add('hidden'), 3000);
}
function showRoomView(code, hasPeer) {
roomControls.classList.add('hidden');
inRoom.classList.remove('hidden');
roomCodeEl.textContent = code;
peerStatusEl.textContent = hasPeer ? 'Peer connected' : 'Waiting for peer...';
peerStatusEl.className = 'peer-status' + (hasPeer ? ' connected' : '');
}
connectBtn.addEventListener('click', () => {
const url = serverUrlInput.value.trim();
if (!url) return;
connectBtn.disabled = true;
connectBtn.textContent = 'Connecting...';
chrome.runtime.sendMessage({ type: 'connect', url });
});
createBtn.addEventListener('click', () => {
chrome.runtime.sendMessage({ type: 'create_room' });
});
joinBtn.addEventListener('click', () => {
const code = codeInput.value.trim();
if (!/^\d{6}$/.test(code)) { showError('Enter a 6-digit code'); return; }
chrome.runtime.sendMessage({ type: 'join_room', code });
});
codeInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') joinBtn.click();
});
disconnectBtn.addEventListener('click', () => {
chrome.runtime.sendMessage({ type: 'disconnect' });
roomSection.classList.add('hidden');
connectSection.classList.remove('hidden');
roomControls.classList.remove('hidden');
inRoom.classList.add('hidden');
connectBtn.disabled = false;
connectBtn.textContent = 'Connect';
});
chrome.runtime.onMessage.addListener((msg) => {
switch (msg.type) {
case 'ws_status':
if (msg.status === 'connected') {
connectSection.classList.add('hidden');
roomSection.classList.remove('hidden');
} else {
connectSection.classList.remove('hidden');
roomSection.classList.add('hidden');
connectBtn.disabled = false;
connectBtn.textContent = 'Connect';
if (msg.status === 'error') showError('Could not connect to server');
}
break;
case 'created':
showRoomView(msg.code, false);
break;
case 'joined':
showRoomView(msg.code, msg.memberCount > 1);
break;
case 'peer_joined':
peerStatusEl.textContent = 'Peer connected';
peerStatusEl.className = 'peer-status connected';
break;
case 'peer_left':
peerStatusEl.textContent = 'Peer disconnected';
peerStatusEl.className = 'peer-status';
break;
case 'error':
showError(msg.message);
break;
}
});
// Restore state when popup opens
chrome.storage.local.get(['serverUrl'], ({ serverUrl }) => {
if (serverUrl) serverUrlInput.value = serverUrl;
});
chrome.runtime.sendMessage({ type: 'get_state' }, (state) => {
if (!state) return;
if (state.connectState === 'connected') {
connectSection.classList.add('hidden');
roomSection.classList.remove('hidden');
if (state.roomCode) showRoomView(state.roomCode, false);
}
});