/** * WebSocket client for backone-webhub (127.0.0.1, authtoken auth). */ window.BackOneWebHubClient = (function () { let ws = null; let connected = false; let reconnectDelay = 1000; let reconnectTimer = null; let pollTimer = null; let port = 9994; let selfPeerId = ''; const seenIncoming = new Set(); const handlers = new Map(); function emit(type, msg) { const set = handlers.get(type); if (!set) return; set.forEach(fn => { try { fn(msg); } catch (_) { /* ignore */ } }); const all = handlers.get('*'); if (all) { all.forEach(fn => { try { fn(type, msg); } catch (_) { /* ignore */ } }); } } function subscribe(type, fn) { if (!handlers.has(type)) handlers.set(type, new Set()); handlers.get(type).add(fn); return () => handlers.get(type)?.delete(fn); } function send(msg) { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(msg)); return true; } return false; } function registerPeer() { const core = BackOneNetworkSession.getCoreStatus(); selfPeerId = ((core && core.address) || '').toLowerCase(); const networkId = BackOneNetworkSession.getNetworkId(); if (!selfPeerId) return; send({ type: 'register', peerId: selfPeerId, networkId }); } function stopHttpPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } } function startHttpPoll() { stopHttpPoll(); pollTimer = setInterval(async () => { if (connected) return; const peerId = selfPeerId || ((BackOneNetworkSession.getCoreStatus().address || '').toLowerCase()); if (!peerId) return; try { const res = await BackOneAuth.fetchWithAuth( `/controller/comms/call/incoming?peerId=${encodeURIComponent(peerId)}` ); if (!res.ok) return; const data = await res.json(); const calls = Array.isArray(data.calls) ? data.calls : []; for (const call of calls) { if (seenIncoming.has(call.callId)) continue; seenIncoming.add(call.callId); emit('incoming_call', { type: 'incoming_call', callId: call.callId, callType: call.callType || 'voice', callerId: call.callerId, callerName: call.callerName || call.callerId, networkId: call.networkId, sdp: call.sdp }); } } catch (_) { /* ignore */ } }, 2000); } function scheduleReconnect() { if (reconnectTimer) return; reconnectTimer = setTimeout(() => { reconnectTimer = null; connect(); }, reconnectDelay); reconnectDelay = Math.min(reconnectDelay * 2, 30000); } async function loadPort() { try { const res = await BackOneAuth.fetchWithAuth('/app/api/webrtc-config'); if (res.ok) { const cfg = await res.json(); if (cfg.webhubPort) port = cfg.webhubPort; } } catch (_) { /* ignore */ } } async function connect() { await loadPort(); const token = await BackOneAuth.getToken(); if (!token) return false; if (ws) { try { ws.close(); } catch (_) { /* ignore */ } ws = null; } return new Promise((resolve) => { const url = `ws://127.0.0.1:${port}/?auth=${encodeURIComponent(token)}`; try { ws = new WebSocket(url); } catch (_) { connected = false; startHttpPoll(); scheduleReconnect(); resolve(false); return; } ws.onopen = () => { connected = true; reconnectDelay = 1000; stopHttpPoll(); send({ type: 'auth', token }); registerPeer(); resolve(true); }; ws.onmessage = (ev) => { try { const msg = JSON.parse(ev.data); if (msg && msg.type) emit(msg.type, msg); } catch (_) { /* ignore */ } }; ws.onclose = () => { connected = false; ws = null; startHttpPoll(); scheduleReconnect(); }; ws.onerror = () => { connected = false; }; }); } BackOneNetworkSession.subscribe(() => { if (connected) registerPeer(); }); return { connect, send, subscribe, isConnected: () => connected, getSelfPeerId: () => selfPeerId, registerPeer }; })();