feat: add WebSocket client with fallback
- Add Socket.IO client library (v4.7.5) - Create RFMapperWS class with: - Automatic reconnection (5 attempts) - HTTP polling fallback - Event listener system for scanUpdate, connected, disconnected - Floor subscription support Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
7
src/rf_mapper/web/static/js/vendor/socket.io.min.js
generated
vendored
Normal file
7
src/rf_mapper/web/static/js/vendor/socket.io.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
101
src/rf_mapper/web/static/js/websocket.js
Normal file
101
src/rf_mapper/web/static/js/websocket.js
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* RF Mapper WebSocket client with automatic reconnection and HTTP fallback
|
||||||
|
*/
|
||||||
|
class RFMapperWS {
|
||||||
|
constructor() {
|
||||||
|
this.socket = null;
|
||||||
|
this.connected = false;
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
this.maxReconnectAttempts = 5;
|
||||||
|
this.listeners = {
|
||||||
|
scanUpdate: [],
|
||||||
|
connected: [],
|
||||||
|
disconnected: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
// Check if socket.io is loaded
|
||||||
|
if (typeof io === 'undefined') {
|
||||||
|
console.warn('[WS] socket.io not loaded, using HTTP polling');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.socket = io('/ws/scan', {
|
||||||
|
transports: ['websocket', 'polling'],
|
||||||
|
reconnection: true,
|
||||||
|
reconnectionDelay: 1000,
|
||||||
|
reconnectionDelayMax: 5000,
|
||||||
|
reconnectionAttempts: this.maxReconnectAttempts
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('connect', () => {
|
||||||
|
console.log('[WS] Connected');
|
||||||
|
this.connected = true;
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
this._emit('connected');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('disconnect', (reason) => {
|
||||||
|
console.log('[WS] Disconnected:', reason);
|
||||||
|
this.connected = false;
|
||||||
|
this._emit('disconnected', { reason });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('scan_update', (data) => {
|
||||||
|
this._emit('scanUpdate', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('connect_error', (error) => {
|
||||||
|
console.warn('[WS] Connection error:', error.message);
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||||
|
console.log('[WS] Max reconnect attempts, falling back to HTTP');
|
||||||
|
this.connected = false;
|
||||||
|
this._emit('disconnected', { reason: 'max_reconnect' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[WS] Failed to initialize:', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.disconnect();
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
this.connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeFloor(floor) {
|
||||||
|
if (this.socket?.connected) {
|
||||||
|
this.socket.emit('subscribe_floor', { floor });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event, callback) {
|
||||||
|
if (this.listeners[event]) {
|
||||||
|
this.listeners[event].push(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event, callback) {
|
||||||
|
if (this.listeners[event]) {
|
||||||
|
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_emit(event, data) {
|
||||||
|
if (this.listeners[event]) {
|
||||||
|
this.listeners[event].forEach(cb => cb(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global instance
|
||||||
|
const rfMapperWS = new RFMapperWS();
|
||||||
Reference in New Issue
Block a user