File size: 1,797 Bytes
0ce9643
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import type { CollabC2S, CollabS2C } from '../types/collab';

const WS_BASE = import.meta.env.VITE_WS_BASE_URL || `ws://${window.location.host}`;

type MessageHandler = (msg: CollabS2C) => void;

export class CollabSocket {
  private ws: WebSocket | null = null;
  private onMessage: MessageHandler;
  private onDisconnect: (() => void) | null;
  private pingInterval: ReturnType<typeof setInterval> | null = null;

  constructor(onMessage: MessageHandler, onDisconnect?: () => void) {
    this.onMessage = onMessage;
    this.onDisconnect = onDisconnect ?? null;
  }

  connect(sessionId: string): Promise<void> {
    return new Promise((resolve, reject) => {
      this.ws = new WebSocket(`${WS_BASE}/ws/collab/${sessionId}`);

      this.ws.onopen = () => {
        this.pingInterval = setInterval(() => {
          this.send({ type: 'c2s_ping' });
        }, 30_000);
        resolve();
      };

      this.ws.onmessage = (event) => {
        try {
          const msg: CollabS2C = JSON.parse(event.data);
          this.onMessage(msg);
        } catch {
          // ignore malformed
        }
      };

      this.ws.onerror = () => reject(new Error('Collab WebSocket connection failed'));

      this.ws.onclose = () => {
        if (this.pingInterval) {
          clearInterval(this.pingInterval);
          this.pingInterval = null;
        }
        this.onDisconnect?.();
      };
    });
  }

  send(msg: CollabC2S): void {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(msg));
    }
  }

  close(): void {
    if (this.pingInterval) {
      clearInterval(this.pingInterval);
      this.pingInterval = null;
    }
    this.ws?.close();
    this.ws = null;
  }

  get connected(): boolean {
    return this.ws?.readyState === WebSocket.OPEN;
  }
}