import { getToken } from './api.js'; const HDR = 9; // type(1) + groupId(4) + memberId(4) const AUDIO = 0x01; export class DispatchSocket { constructor(handlers) { this.handlers = handlers; this.closed = false; this.retries = 0; this.connect(); } connect() { const proto = location.protocol === 'https:' ? 'wss' : 'ws'; this.ws = new WebSocket(`${proto}://${location.host}/ws`); this.ws.binaryType = 'arraybuffer'; this.ws.onopen = () => { this.retries = 0; this.ws.send(JSON.stringify({ type: 'auth', token: getToken() })); this.handlers.onStatus?.(true); }; this.ws.onmessage = (ev) => { if (typeof ev.data === 'string') { let msg; try { msg = JSON.parse(ev.data); } catch { return; } switch (msg.type) { case 'welcome': this.handlers.onWelcome?.(msg); break; case 'members': this.handlers.onMembers?.(msg.members); break; case 'groups': this.handlers.onGroups?.(msg.groups); break; case 'sos_new': this.handlers.onSosNew?.(msg.alert); break; case 'sos_update': this.handlers.onSosUpdate?.(msg.alert); break; case 'floor': this.handlers.onFloor?.(msg); break; case 'error': console.warn('[ws]', msg.message); break; } } else if (ev.data instanceof ArrayBuffer && ev.data.byteLength > HDR) { const dv = new DataView(ev.data); if (dv.getUint8(0) !== AUDIO) return; this.handlers.onAudio?.({ groupId: dv.getUint32(1), memberId: dv.getUint32(5), pcm: ev.data.slice(HDR), }); } }; this.ws.onclose = () => { this.handlers.onStatus?.(false); if (this.closed) return; const delay = Math.min(10000, 500 * 2 ** this.retries++); setTimeout(() => this.connect(), delay); }; this.ws.onerror = () => this.ws.close(); } send(obj) { if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(obj)); } sendAudio(groupId, memberId, int16buffer) { if (this.ws?.readyState !== WebSocket.OPEN) return; const head = new Uint8Array(HDR); const dv = new DataView(head.buffer); dv.setUint8(0, AUDIO); dv.setUint32(1, groupId); dv.setUint32(5, memberId); const body = new Uint8Array(int16buffer); const out = new Uint8Array(HDR + body.byteLength); out.set(head); out.set(body, HDR); this.ws.send(out); } close() { this.closed = true; this.ws?.close(); } }