File size: 5,257 Bytes
8b7b267
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;