File size: 5,257 Bytes
b190b45
 
 
 
 
 
 
 
 
452f691
 
 
 
 
 
 
 
dd7ffbd
 
452f691
b190b45
452f691
 
 
dd7ffbd
 
b190b45
dd7ffbd
452f691
 
 
dd7ffbd
452f691
dd7ffbd
452f691
 
 
 
 
 
 
 
 
 
 
 
 
 
96af7c9
 
 
452f691
 
 
 
 
 
dd7ffbd
 
452f691
 
b190b45
 
 
 
 
452f691
dd7ffbd
452f691
 
 
b190b45
452f691
dd7ffbd
b190b45
dd7ffbd
 
 
 
 
 
 
 
 
 
 
 
 
 
96af7c9
dd7ffbd
 
 
 
452f691
dd7ffbd
 
b190b45
 
 
 
452f691
 
b190b45
452f691
b190b45
 
452f691
dd7ffbd
 
 
b190b45
 
 
 
 
 
 
 
 
 
 
 
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
 * WebSocket Client (OPTIONAL)
 * 
 * IMPORTANT: WebSocket is completely optional. All data can be retrieved via HTTP REST API.
 * This WebSocket client is provided as an alternative method for users who prefer real-time streaming.
 * If WebSocket is unavailable or you prefer HTTP, use the HTTP endpoints instead.
 * 
 * The application automatically falls back to HTTP polling if WebSocket fails.
 */
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;
        this.isOptional = true; // Mark as optional feature
    }

    get url() {
        const { protocol, host } = window.location;
        const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
        // For HuggingFace Space: wss://Really-amin-Datasourceforcryptocurrency-2.hf.space/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 to WebSocket (OPTIONAL - HTTP endpoints work fine)
     * This is just an alternative method for real-time updates.
     * If connection fails, use HTTP polling instead.
     */
    connect() {
        if (this.socket && (this.status === 'connecting' || this.status === 'connected')) {
            return;
        }

        console.log('[WebSocket] Attempting optional WebSocket connection (HTTP endpoints are recommended)');
        this.updateStatus('connecting');
        this.socket = new WebSocket(this.url);
        this.logEvent({ type: 'status', status: 'connecting', note: 'optional' });

        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', note: 'optional - use HTTP if needed' });
            // Don't auto-reconnect aggressively - WebSocket is optional
            // Users can use HTTP endpoints instead
            if (this.shouldReconnect && this.backoff < this.maxBackoff) {
                const delay = this.backoff;
                this.backoff = Math.min(this.backoff * 2, this.maxBackoff);
                console.log(`[WebSocket] Optional reconnection in ${delay}ms (or use HTTP endpoints)`);
                setTimeout(() => this.connect(), delay);
            } else if (this.shouldReconnect) {
                console.log('[WebSocket] Max reconnection attempts reached. Use HTTP endpoints instead.');
            }
        });

        this.socket.addEventListener('error', (error) => {
            console.warn('[WebSocket] Optional WebSocket error (non-critical):', error);
            console.info('[WebSocket] Tip: Use HTTP REST API endpoints instead - they work perfectly');
            this.logEvent({ 
                type: 'error', 
                details: error.message || 'unknown',
                timestamp: new Date().toISOString(),
                note: 'optional - HTTP endpoints available'
            });
            this.updateStatus('error');
            
            // Don't close immediately - let close event handle cleanup
            // This allows for proper reconnection logic
        });
    }

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

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

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