File size: 3,511 Bytes
452f691
 
 
 
 
 
 
 
dd7ffbd
 
452f691
 
 
 
dd7ffbd
 
 
452f691
 
 
dd7ffbd
452f691
dd7ffbd
452f691
 
 
 
 
 
 
 
 
 
 
 
 
 
96af7c9
 
 
452f691
 
 
 
 
 
dd7ffbd
 
452f691
 
 
dd7ffbd
452f691
 
 
 
dd7ffbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96af7c9
dd7ffbd
 
 
 
452f691
dd7ffbd
 
 
452f691
 
 
 
 
dd7ffbd
 
 
 
 
 
 
 
 
452f691
 
 
 
96af7c9
dd7ffbd
96af7c9
 
 
452f691
 
 
 
 
 
dd7ffbd
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
class WSClient {
    constructor() {
        this.socket = null;
        this.status = 'disconnected';
        this.statusSubscribers = new Set();
        this.globalSubscribers = new Set();
        this.typeSubscribers = new Map();
        this.eventLog = [];
        this.backoff = 1000;
        this.maxBackoff = 16000;
        this.shouldReconnect = true;
    }

    get url() {
        const { protocol, host } = window.location;
        const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
        return `${wsProtocol}//${host}/ws`;
    }

    logEvent(event) {
        const entry = { ...event, time: new Date().toISOString() };
        this.eventLog.push(entry);
        this.eventLog = this.eventLog.slice(-100);
    }

    onStatusChange(callback) {
        this.statusSubscribers.add(callback);
        callback(this.status);
        return () => this.statusSubscribers.delete(callback);
    }

    onMessage(callback) {
        this.globalSubscribers.add(callback);
        return () => this.globalSubscribers.delete(callback);
    }

    subscribe(type, callback) {
        if (!this.typeSubscribers.has(type)) {
            this.typeSubscribers.set(type, new Set());
        }
        const set = this.typeSubscribers.get(type);
        set.add(callback);
        return () => set.delete(callback);
    }

    updateStatus(newStatus) {
        this.status = newStatus;
        this.statusSubscribers.forEach((cb) => cb(newStatus));
    }

    connect() {
        if (this.socket && (this.status === 'connecting' || this.status === 'connected')) {
            return;
        }

        this.updateStatus('connecting');
        this.socket = new WebSocket(this.url);
        this.logEvent({ type: 'status', status: 'connecting' });

        this.socket.addEventListener('open', () => {
            this.backoff = 1000;
            this.updateStatus('connected');
            this.logEvent({ type: 'status', status: 'connected' });
        });

        this.socket.addEventListener('message', (event) => {
            try {
                const data = JSON.parse(event.data);
                this.logEvent({ type: 'message', messageType: data.type || 'unknown' });
                this.globalSubscribers.forEach((cb) => cb(data));
                if (data.type && this.typeSubscribers.has(data.type)) {
                    this.typeSubscribers.get(data.type).forEach((cb) => cb(data));
                }
            } catch (error) {
                console.error('WS message parse error', error);
            }
        });

        this.socket.addEventListener('close', () => {
            this.updateStatus('disconnected');
            this.logEvent({ type: 'status', status: 'disconnected' });
            if (this.shouldReconnect) {
                const delay = this.backoff;
                this.backoff = Math.min(this.backoff * 2, this.maxBackoff);
                setTimeout(() => this.connect(), delay);
            }
        });

        this.socket.addEventListener('error', (error) => {
            console.error('WebSocket error', error);
            this.logEvent({ type: 'error', details: error.message || 'unknown' });
            if (this.socket) {
                this.socket.close();
            }
        });
    }

    disconnect() {
        this.shouldReconnect = false;
        if (this.socket) {
            this.socket.close();
        }
    }

    getEvents() {
        return [...this.eventLog];
    }
}

const wsClient = new WSClient();
export default wsClient;