/** * WebSocket-based Live Updates for ANNOTATE * Provides real-time chart updates and model predictions */ class LiveUpdatesWebSocket { constructor() { this.socket = null; this.connected = false; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectDelay = 1000; // Start with 1 second this.subscriptions = new Set(); // Callbacks this.onChartUpdate = null; this.onPredictionUpdate = null; this.onConnectionChange = null; console.log('LiveUpdatesWebSocket initialized'); } connect() { if (this.connected) { console.log('Already connected to WebSocket'); return; } try { // Initialize SocketIO connection this.socket = io({ transports: ['websocket', 'polling'], upgrade: true, rememberUpgrade: true }); this._setupEventHandlers(); console.log('Connecting to WebSocket...'); } catch (error) { console.error('Failed to initialize WebSocket:', error); this._scheduleReconnect(); } } _setupEventHandlers() { // Connection events this.socket.on('connect', () => { console.log('✅ WebSocket connected'); this.connected = true; this.reconnectAttempts = 0; this.reconnectDelay = 1000; if (this.onConnectionChange) { this.onConnectionChange(true); } // Resubscribe to previous subscriptions this.subscriptions.forEach(sub => { this._subscribe(sub.symbol, sub.timeframe); }); }); this.socket.on('disconnect', () => { console.log('❌ WebSocket disconnected'); this.connected = false; if (this.onConnectionChange) { this.onConnectionChange(false); } this._scheduleReconnect(); }); this.socket.on('connection_response', (data) => { console.log('Connection response:', data); }); this.socket.on('subscription_confirmed', (data) => { console.log('Subscription confirmed:', data); }); // Data events this.socket.on('chart_update', (data) => { console.debug('Chart update received:', data); if (this.onChartUpdate) { this.onChartUpdate(data); } }); this.socket.on('prediction_update', (data) => { console.debug('Prediction update received:', data); if (this.onPredictionUpdate) { this.onPredictionUpdate(data); } }); this.socket.on('prediction_error', (data) => { console.error('Prediction error:', data); }); // Error events this.socket.on('connect_error', (error) => { console.error('WebSocket connection error:', error); this._scheduleReconnect(); }); this.socket.on('error', (error) => { console.error('WebSocket error:', error); }); } _scheduleReconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('Max reconnection attempts reached. Please refresh the page.'); return; } this.reconnectAttempts++; const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1); // Exponential backoff console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})...`); setTimeout(() => { if (!this.connected) { this.connect(); } }, delay); } subscribe(symbol, timeframe) { this.subscriptions.add({ symbol, timeframe }); if (this.connected) { this._subscribe(symbol, timeframe); } } _subscribe(symbol, timeframe) { if (!this.socket || !this.connected) { console.warn('Cannot subscribe - not connected'); return; } console.log(`Subscribing to live updates: ${symbol} ${timeframe}`); this.socket.emit('subscribe_live_updates', { symbol: symbol, timeframe: timeframe }); } requestPrediction(symbol, timeframe, predictionSteps = 1) { if (!this.socket || !this.connected) { console.warn('Cannot request prediction - not connected'); return; } console.log(`Requesting prediction: ${symbol} ${timeframe} (${predictionSteps} steps)`); this.socket.emit('request_prediction', { symbol: symbol, timeframe: timeframe, prediction_steps: predictionSteps }); } disconnect() { if (this.socket) { console.log('Disconnecting WebSocket...'); this.socket.disconnect(); this.socket = null; this.connected = false; this.subscriptions.clear(); } } isConnected() { return this.connected; } } // Global instance window.liveUpdatesWS = null; // Initialize on page load document.addEventListener('DOMContentLoaded', function() { // Check if SocketIO is available if (typeof io === 'undefined') { console.warn('⚠️ Socket.IO not loaded - live updates will not work'); console.warn('Add to your HTML'); return; } // Initialize WebSocket window.liveUpdatesWS = new LiveUpdatesWebSocket(); // Setup callbacks window.liveUpdatesWS.onConnectionChange = function(connected) { const statusElement = document.getElementById('ws-connection-status'); if (statusElement) { if (connected) { statusElement.innerHTML = '🟢 Live'; } else { statusElement.innerHTML = '🔴 Disconnected'; } } }; window.liveUpdatesWS.onChartUpdate = function(data) { // Update chart with new candle if (window.appState && window.appState.chartManager) { window.appState.chartManager.updateLatestCandle(data.symbol, data.timeframe, data.candle); } }; window.liveUpdatesWS.onPredictionUpdate = function(data) { // Update prediction display if (typeof updatePredictionDisplay === 'function') { updatePredictionDisplay(data); } // Add to prediction history if (typeof predictionHistory !== 'undefined') { predictionHistory.unshift(data); if (predictionHistory.length > 5) { predictionHistory = predictionHistory.slice(0, 5); } if (typeof updatePredictionHistory === 'function') { updatePredictionHistory(); } } }; // Auto-connect console.log('Auto-connecting to WebSocket...'); window.liveUpdatesWS.connect(); }); // Cleanup on page unload window.addEventListener('beforeunload', function() { if (window.liveUpdatesWS) { window.liveUpdatesWS.disconnect(); } });