/** * Queue WebSocket Client * Handles real-time queue updates via WebSocket connections * Part of Phase 11: Advanced Queue Management */ class QueueWebSocketClient { constructor(queueId, options = {}) { this.queueId = queueId; this.socket = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = options.maxReconnectAttempts || 5; this.reconnectDelay = options.reconnectDelay || 3000; this.heartbeatInterval = options.heartbeatInterval || 30000; this.heartbeatTimer = null; this.isConnected = false; // Callbacks this.onConnect = options.onConnect || (() => {}); this.onDisconnect = options.onDisconnect || (() => {}); this.onQueueUpdate = options.onQueueUpdate || (() => {}); this.onPositionChange = options.onPositionChange || (() => {}); this.onPatientCalled = options.onPatientCalled || (() => {}); this.onError = options.onError || ((error) => console.error('WebSocket error:', error)); this.connect(); } /** * Establish WebSocket connection */ connect() { const wsScheme = window.location.protocol === 'https:' ? 'wss' : 'ws'; const wsUrl = `${wsScheme}://${window.location.host}/ws/appointments/queue/${this.queueId}/`; try { this.socket = new WebSocket(wsUrl); this.setupEventHandlers(); } catch (error) { this.onError(error); this.scheduleReconnect(); } } /** * Set up WebSocket event handlers */ setupEventHandlers() { this.socket.onopen = (event) => { console.log('WebSocket connected to queue:', this.queueId); this.isConnected = true; this.reconnectAttempts = 0; this.onConnect(event); this.startHeartbeat(); }; this.socket.onmessage = (event) => { try { const data = JSON.parse(event.data); this.handleMessage(data); } catch (error) { console.error('Error parsing WebSocket message:', error); } }; this.socket.onclose = (event) => { console.log('WebSocket disconnected:', event.code, event.reason); this.isConnected = false; this.stopHeartbeat(); this.onDisconnect(event); // Attempt to reconnect if not a normal closure if (event.code !== 1000) { this.scheduleReconnect(); } }; this.socket.onerror = (error) => { console.error('WebSocket error:', error); this.onError(error); }; } /** * Handle incoming WebSocket messages */ handleMessage(data) { console.log('WebSocket message received:', data.type); switch (data.type) { case 'queue_update': this.onQueueUpdate(data.data); break; case 'position_change': this.onPositionChange(data.data); break; case 'patient_called': this.onPatientCalled(data.data); break; case 'pong': // Heartbeat response console.log('Heartbeat acknowledged'); break; default: console.warn('Unknown message type:', data.type); } } /** * Send message to server */ send(type, data = {}) { if (this.isConnected && this.socket.readyState === WebSocket.OPEN) { const message = JSON.stringify({ type, ...data }); this.socket.send(message); return true; } console.warn('Cannot send message: WebSocket not connected'); return false; } /** * Request queue status */ requestStatus() { return this.send('get_status'); } /** * Start heartbeat to keep connection alive */ startHeartbeat() { this.heartbeatTimer = setInterval(() => { if (this.isConnected) { this.send('ping'); } }, this.heartbeatInterval); } /** * Stop heartbeat */ stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } } /** * Schedule reconnection attempt */ scheduleReconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; const delay = this.reconnectDelay * this.reconnectAttempts; console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`); setTimeout(() => { this.connect(); }, delay); } else { console.error('Max reconnection attempts reached'); this.onError(new Error('Failed to reconnect after maximum attempts')); } } /** * Manually disconnect */ disconnect() { this.stopHeartbeat(); if (this.socket) { this.socket.close(1000, 'Client disconnect'); this.socket = null; } this.isConnected = false; } /** * Get connection status */ getStatus() { return { connected: this.isConnected, reconnectAttempts: this.reconnectAttempts, readyState: this.socket ? this.socket.readyState : null }; } } // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = QueueWebSocketClient; }