// Reconnecting WebSocket shared by both frontends. A party laptop that sleeps, // or a server restart, must not require reloading the tabs. export function connect({ onMessage, onStatus } = {}) { let ws = null; let retry = 500; const outbox = []; function open() { const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; ws = new WebSocket(`${proto}//${location.host}`); ws.addEventListener('open', () => { retry = 500; onStatus?.('online'); while (outbox.length) ws.send(outbox.shift()); }); ws.addEventListener('message', (event) => { let msg; try { msg = JSON.parse(event.data); } catch { return; } onMessage?.(msg); }); ws.addEventListener('close', () => { onStatus?.('offline'); setTimeout(open, retry); retry = Math.min(retry * 2, 5000); }); ws.addEventListener('error', () => ws.close()); } open(); return { send(message) { const payload = JSON.stringify(message); if (ws && ws.readyState === WebSocket.OPEN) ws.send(payload); else outbox.push(payload); }, }; } /** Seconds → H:MM:SS (or M:SS below an hour). */ export function formatDuration(seconds) { if (!Number.isFinite(seconds) || seconds < 0) return '?'; const total = Math.round(seconds); const h = Math.floor(total / 3600); const m = Math.floor((total % 3600) / 60); const s = total % 60; return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}` : `${m}:${String(s).padStart(2, '0')}`; }