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