agdar/static/js/queue_websocket.js
2025-11-02 14:35:35 +03:00

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;
}