201 lines
5.8 KiB
JavaScript
201 lines
5.8 KiB
JavaScript
/**
|
|
* 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;
|
|
}
|