skyview/assets/static/js/modules/websocket.js
Ole-Morten Duesund 1fe15c06a3 Fix aircraft track propagation issues in web frontend
This commit addresses issue #23 where aircraft track changes were not
propagating properly to the web frontend. The fixes include:

**Server-side improvements:**
- Enhanced WebSocket broadcast reliability with timeout-based queueing
- Increased broadcast channel buffer size (1000 -> 2000)
- Improved error handling and connection management
- Added write timeouts to prevent slow clients from blocking updates
- Enhanced connection cleanup and ping/pong handling
- Added debug endpoint /api/debug/websocket for troubleshooting
- Relaxed position validation thresholds for better track acceptance

**Frontend improvements:**
- Enhanced WebSocket manager with exponential backoff reconnection
- Improved aircraft position update detection and logging
- Fixed position update logic to always propagate changes to map
- Better coordinate validation and error reporting
- Enhanced debugging with detailed console logging
- Fixed track rotation update thresholds and logic
- Improved marker lifecycle management and cleanup
- Better handling of edge cases in aircraft state transitions

**Key bug fixes:**
- Removed overly aggressive position change detection that blocked updates
- Fixed track rotation sensitivity (5° -> 10° threshold)
- Enhanced coordinate validation to handle null/undefined values
- Improved WebSocket message ordering and processing
- Fixed marker position updates to always propagate to Leaflet

These changes ensure reliable real-time aircraft tracking with proper
position, heading, and altitude updates across multiple data sources.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-25 10:14:03 +02:00

114 lines
No EOL
4 KiB
JavaScript

// WebSocket communication module
export class WebSocketManager {
constructor(onMessage, onStatusChange) {
this.websocket = null;
this.onMessage = onMessage;
this.onStatusChange = onStatusChange;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = null;
this.lastMessageTime = 0;
this.messageCount = 0;
this.isManualDisconnect = false;
}
async connect() {
// Clear any existing reconnect interval
if (this.reconnectInterval) {
clearTimeout(this.reconnectInterval);
this.reconnectInterval = null;
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws`;
try {
console.log(`WebSocket connecting to ${wsUrl} (attempt ${this.reconnectAttempts + 1})`);
this.websocket = new WebSocket(wsUrl);
this.websocket.onopen = () => {
console.log('WebSocket connected successfully');
this.reconnectAttempts = 0; // Reset on successful connection
this.onStatusChange('connected');
};
this.websocket.onclose = (event) => {
console.log(`WebSocket closed: code=${event.code}, reason=${event.reason}, wasClean=${event.wasClean}`);
this.websocket = null;
if (!this.isManualDisconnect) {
this.onStatusChange('disconnected');
this.scheduleReconnect();
}
};
this.websocket.onerror = (error) => {
console.error('WebSocket error:', error);
this.onStatusChange('disconnected');
};
this.websocket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.lastMessageTime = Date.now();
this.messageCount++;
// Log message reception for debugging
if (this.messageCount % 10 === 0) {
console.debug(`Received ${this.messageCount} WebSocket messages`);
}
this.onMessage(message);
} catch (error) {
console.error('Failed to parse WebSocket message:', error, event.data);
}
};
} catch (error) {
console.error('WebSocket connection failed:', error);
this.onStatusChange('disconnected');
this.scheduleReconnect();
}
}
scheduleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error(`Max reconnection attempts (${this.maxReconnectAttempts}) reached. Giving up.`);
this.onStatusChange('failed');
return;
}
this.reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); // Exponential backoff, max 30s
console.log(`Scheduling WebSocket reconnection in ${delay}ms (attempt ${this.reconnectAttempts})`);
this.onStatusChange('reconnecting');
this.reconnectInterval = setTimeout(() => {
this.connect();
}, delay);
}
disconnect() {
this.isManualDisconnect = true;
if (this.reconnectInterval) {
clearTimeout(this.reconnectInterval);
this.reconnectInterval = null;
}
if (this.websocket) {
this.websocket.close();
this.websocket = null;
}
}
getStats() {
return {
messageCount: this.messageCount,
lastMessageTime: this.lastMessageTime,
reconnectAttempts: this.reconnectAttempts,
isConnected: this.websocket && this.websocket.readyState === WebSocket.OPEN
};
}
}