File size: 2,511 Bytes
0342026
 
e8845c2
 
0342026
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e8845c2
 
 
 
0342026
e8845c2
 
0342026
 
 
e8845c2
0342026
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e8845c2
0342026
 
 
e8845c2
0342026
 
e8845c2
0342026
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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();
  }
}