PageSourceSearch

https://rapidcyber.io/js/websocket-client.js

js rapidcyber.io collected 2026-10-09 23:10:05 UTC 7,560 bytes, 287 lines download raw bytes

1/**
2 * Unified WebSocket Client cho Real-time Updates
3 * Tương thích với server-socket service từ colatv40 react
4 * 
5 * Usage:
6 *   const wsClient = new RealtimeWebSocketClient('wss://your-socket-server.com');
7 *   wsClient.subscribe('matches:updated', (data) => { ... });
8 *   wsClient.connect();
9 */
10class RealtimeWebSocketClient {
11  constructor(wsUrl) {
12    this.wsUrl = wsUrl;
13    this.ws = null;
14    this.reconnectAttempts = 0;
15    this.maxReconnectAttempts = 10;
16    this.reconnectDelay = 2000;
17    this.maxReconnectDelay = 30000;
18    this.listeners = new Map(); // eventType -> Set<callbacks>
19    this.isConnected = false;
20    this.heartbeatInterval = null;
21    this.heartbeatIntervalMs = 30000; // 30 seconds
22    this.shouldReconnect = true;
23    this.reconnectTimeout = null;
24  }
25
26  /**
27   * Connect to WebSocket server
28   */
29  connect() {
30    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
31      console.log('[WebSocket] Already connected');
32      return;
33    }
34
35    if (this.ws && this.ws.readyState === WebSocket.CONNECTING) {
36      console.log('[WebSocket] Connection in progress...');
37      return;
38    }
39
40    try {
41      console.log('[WebSocket] Connecting to:', this.wsUrl);
42      this.ws = new WebSocket(this.wsUrl);
43
44      this.ws.onopen = () => {
45        console.log('[WebSocket] ✅ Connected');
46        this.isConnected = true;
47        this.reconnectAttempts = 0;
48        this.startHeartbeat();
49        this.emit('connected');
50      };
51
52      this.ws.onmessage = (event) => {
53        try {
54          const message = JSON.parse(event.data);
55          this.handleMessage(message);
56        } catch (error) {
57          console.error('[WebSocket] Error parsing message:', error, event.data);
58        }
59      };
60
61      this.ws.onerror = (error) => {
62        console.error('[WebSocket] ❌ Error:', error);
63        this.isConnected = false;
64        this.emit('error', error);
65      };
66
67      this.ws.onclose = (event) => {
68        console.log('[WebSocket] Connection closed:', event.code, event.reason || 'N/A');
69        this.isConnected = false;
70        this.stopHeartbeat();
71
72        // Auto reconnect nếu không phải đóng bình thường
73        if (this.shouldReconnect && event.code !== 1000) {
74          this.scheduleReconnect();
75        }
76
77        this.emit('disconnected', event);
78      };
79
80    } catch (error) {
81      console.error('[WebSocket] Failed to create connection:', error);
82      this.scheduleReconnect();
83    }
84  }
85
86  /**
87   * Handle incoming WebSocket messages
88   */
89  handleMessage(message) {
90    if (message.type === 'pong') {
91      // Heartbeat response - không cần xử lý
92      return;
93    }
94
95    if (message.type === 'connected') {
96      console.log('[WebSocket] ✅ Connection confirmed by server');
97      return;
98    }
99
100    // Emit to all listeners for this event type
101    const callbacks = this.listeners.get(message.type);
102    if (callbacks) {
103      callbacks.forEach(callback => {
104        try {
105          callback(message.data || message);
106        } catch (error) {
107          console.error('[WebSocket] Error in callback for', message.type, ':', error);
108        }
109      });
110    }
111
112    // Also emit generic 'message' event
113    this.emit('message', message);
114  }
115
116  /**
117   * Subscribe to specific event type
118   * @param {string} eventType - Event type to listen for (e.g., 'matches:updated', 'detail_live:updated')
119   * @param {Function} callback - Callback function to call when event is received
120   * @returns {Function} Unsubscribe function
121   */
122  subscribe(eventType, callback) {
123    if (typeof callback !== 'function') {
124      console.error('[WebSocket] Callback must be a function');
125      return () => {};
126    }
127
128    if (!this.listeners.has(eventType)) {
129      this.listeners.set(eventType, new Set());
130    }
131    this.listeners.get(eventType).add(callback);
132
133    // Auto-connect if not connected
134    if (!this.isConnected && !this.ws) {
135      this.connect();
136    }
137
138    // Return unsubscribe function
139    return () => this.unsubscribe(eventType, callback);
140  }
141
142  /**
143   * Unsubscribe from event type
144   */
145  unsubscribe(eventType, callback) {
146    const callbacks = this.listeners.get(eventType);
147    if (callbacks) {
148      callbacks.delete(callback);
149      if (callbacks.size === 0) {
150        this.listeners.delete(eventType);
151      }
152    }
153  }
154
155  /**
156   * Emit event to internal listeners
157   */
158  emit(eventType, data) {
159    const callbacks = this.listeners.get(eventType);
160    if (callbacks) {
161      callbacks.forEach(callback => {
162        try {
163          callback(data);
164        } catch (error) {
165          console.error('[WebSocket] Error in emit callback:', error);
166        }
167      });
168    }
169  }
170
171  /**
172   * Send message to server
173   */
174  send(data) {
175    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
176      this.ws.send(JSON.stringify(data));
177    } else {
178      console.warn('[WebSocket] Cannot send, not connected. ReadyState:', this.ws ? this.ws.readyState : 'null');
179    }
180  }
181
182  /**
183   * Start heartbeat to keep connection alive
184   */
185  startHeartbeat() {
186    this.stopHeartbeat();
187    this.heartbeatInterval = setInterval(() => {
188      if (this.isConnected && this.ws && this.ws.readyState === WebSocket.OPEN) {
189        this.send({ type: 'ping' });
190      } else {
191        // Connection lost, stop heartbeat
192        this.stopHeartbeat();
193      }
194    }, this.heartbeatIntervalMs);
195  }
196
197  /**
198   * Stop heartbeat
199   */
200  stopHeartbeat() {
201    if (this.heartbeatInterval) {
202      clearInterval(this.heartbeatInterval);
203      this.heartbeatInterval = null;
204    }
205  }
206
207  /**
208   * Schedule reconnection with exponential backoff
209   */
210  scheduleReconnect() {
211    if (!this.shouldReconnect) return;
212    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
213      console.error('[WebSocket] Max reconnect attempts reached, stopping reconnection');
214      this.emit('maxReconnectAttemptsReached');
215      return;
216    }
217
218    // Clear existing reconnect timeout
219    if (this.reconnectTimeout) {
220      clearTimeout(this.reconnectTimeout);
221    }
222
223    const delay = Math.min(
224      this.reconnectDelay * Math.pow(2, this.reconnectAttempts),
225      this.maxReconnectDelay
226    );
227
228    this.reconnectAttempts++;
229    console.log(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
230
231    this.reconnectTimeout = setTimeout(() => {
232      this.reconnectTimeout = null;
233      this.connect();
234    }, delay);
235  }
236
237  /**
238   * Disconnect and prevent reconnection
239   */
240  disconnect() {
241    console.log('[WebSocket] Disconnecting...');
242    this.shouldReconnect = false;
243    this.stopHeartbeat();
244    
245    if (this.reconnectTimeout) {
246      clearTimeout(this.reconnectTimeout);
247      this.reconnectTimeout = null;
248    }
249
250    if (this.ws) {
251      this.ws.close(1000, 'Client disconnect');
252      this.ws = null;
253    }
254    
255    this.isConnected = false;
256  }
257
258  /**
259   * Get connection status
260   */
261  getStatus() {
262    return {
263      isConnected: this.isConnected,
264      readyState: this.ws ? this.ws.readyState : WebSocket.CLOSED,
265      reconnectAttempts: this.reconnectAttempts,
266      url: this.wsUrl
267    };
268  }
269
270  /**
271   * Reset reconnection attempts (useful after manual reconnect)
272   */
273  resetReconnectAttempts() {
274    this.reconnectAttempts = 0;
275  }
276}
277
278// Export for use in other files
279if (typeof window !== 'undefined') {
280  window.RealtimeWebSocketClient = RealtimeWebSocketClient;
281}
282
283// Also support CommonJS/Node.js if needed
284if (typeof module !== 'undefined' && module.exports) {
285  module.exports = RealtimeWebSocketClient;
286}
287

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.