// WebSocket communication module export class WebSocketManager { constructor(onMessage, onStatusChange) { this.websocket = null; this.onMessage = onMessage; this.onStatusChange = onStatusChange; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectInterval = null; this.lastMessageTime = 0; this.messageCount = 0; this.isManualDisconnect = false; } async connect() { // Clear any existing reconnect interval if (this.reconnectInterval) { clearTimeout(this.reconnectInterval); this.reconnectInterval = null; } const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws`; try { console.log(`WebSocket connecting to ${wsUrl} (attempt ${this.reconnectAttempts + 1})`); this.websocket = new WebSocket(wsUrl); this.websocket.onopen = () => { console.log('WebSocket connected successfully'); this.reconnectAttempts = 0; // Reset on successful connection this.onStatusChange('connected'); }; this.websocket.onclose = (event) => { console.log(`WebSocket closed: code=${event.code}, reason=${event.reason}, wasClean=${event.wasClean}`); this.websocket = null; if (!this.isManualDisconnect) { this.onStatusChange('disconnected'); this.scheduleReconnect(); } }; this.websocket.onerror = (error) => { console.error('WebSocket error:', error); this.onStatusChange('disconnected'); }; this.websocket.onmessage = (event) => { try { const message = JSON.parse(event.data); this.lastMessageTime = Date.now(); this.messageCount++; // Log message reception for debugging if (this.messageCount % 10 === 0) { console.debug(`Received ${this.messageCount} WebSocket messages`); } this.onMessage(message); } catch (error) { console.error('Failed to parse WebSocket message:', error, event.data); } }; } catch (error) { console.error('WebSocket connection failed:', error); this.onStatusChange('disconnected'); this.scheduleReconnect(); } } scheduleReconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error(`Max reconnection attempts (${this.maxReconnectAttempts}) reached. Giving up.`); this.onStatusChange('failed'); return; } this.reconnectAttempts++; const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); // Exponential backoff, max 30s console.log(`Scheduling WebSocket reconnection in ${delay}ms (attempt ${this.reconnectAttempts})`); this.onStatusChange('reconnecting'); this.reconnectInterval = setTimeout(() => { this.connect(); }, delay); } disconnect() { this.isManualDisconnect = true; if (this.reconnectInterval) { clearTimeout(this.reconnectInterval); this.reconnectInterval = null; } if (this.websocket) { this.websocket.close(); this.websocket = null; } } getStats() { return { messageCount: this.messageCount, lastMessageTime: this.lastMessageTime, reconnectAttempts: this.reconnectAttempts, isConnected: this.websocket && this.websocket.readyState === WebSocket.OPEN }; } }