import { io, Socket } from 'socket.io-client'; import { EventBus } from './EventBus'; export class WebSocketService { private socket: Socket | null = null; private eventBus: EventBus; private apiBaseUrl: string; //private receivedEvents: Set = new Set(); // Для отслеживания полученных событий //private reconnectAttempts = 0; private maxReconnectAttempts = 10; private pingIntervalId: ReturnType | null = null; constructor(eventBus: EventBus, apiBaseUrl: string) { this.eventBus = eventBus; this.apiBaseUrl = apiBaseUrl; } /** * Подключается к WebSocket серверу */ connect(): void { if (this.socket?.connected) { console.log('WebSocket уже подключен'); return; } // Извлекаем базовый URL без /api const wsUrl = this.apiBaseUrl.replace('/api', ''); console.log('🔌 Подключение к WebSocket:', wsUrl); this.socket = io(wsUrl, { transports: ['websocket', 'polling'], reconnection: true, reconnectionDelay: 1000, reconnectionDelayMax: 5000, reconnectionAttempts: this.maxReconnectAttempts, timeout: 20000, forceNew: false, upgrade: true, // Добавляем дополнительные опции для стабильности autoConnect: true, rememberUpgrade: true, rejectUnauthorized: false }); this.setupEventHandlers(); this.startProactivePing(); // Запускаем проактивный пинг } /** * Настраивает обработчики WebSocket событий */ private setupEventHandlers(): void { if (!this.socket) return; this.socket.on('connect', () => { console.log('✅ WebSocket подключен'); }); this.socket.on('disconnect', (reason) => { console.log('❌ WebSocket отключен:', reason); }); this.socket.on('connect_error', (error) => { console.error('Ошибка подключения WebSocket:', error); }); // ----------------------------------------------- Nodes Update Events --------------------------------------------------------------- this.socket.on( 'node_type_changed', (data: { graph_id: string; node_id: string; node_type: string }, callback?: (response: { status: string }) => void) => { console.log('📡 WebSocket получено: node_type_changed', data); this.eventBus.emit('ws-node-type-changed', { graphId: data.graph_id, nodeId: data.node_id, nodeType: data.node_type }); if (callback) { callback({ status: 'ok' }); // Отправляем подтверждение } } ); this.socket.on( 'graph_title_updated', (data: { graph_id: string; title: string }, callback?: (response: { status: string }) => void) => { console.log('📡 WebSocket получено: graph_title_updated', data); this.eventBus.emit('ws-graph-title-updated', data); if (callback) { callback({ status: 'ok' }); // Отправляем подтверждение } } ); this.socket.on( 'node_title_updated', (data: { graph_id: string; node_id: string; title: string }, callback?: (response: { status: string }) => void) => { console.log('📡 WebSocket получено: node_title_updated', data); this.eventBus.emit('ws-node-title-updated', data); if (callback) { callback({ status: 'ok' }); // Отправляем подтверждение } } ); } /** * Отключается от WebSocket сервера */ disconnect(): void { if (this.socket) { this.socket.disconnect(); this.socket = null; console.log('WebSocket отключен'); } this.stopProactivePing(); // Останавливаем проактивный пинг } /** * Проверяет статус подключения */ isConnected(): boolean { return this.socket?.connected ?? false; } /** * Запускает проактивный пинг для проверки соединения. */ private startProactivePing(): void { this.stopProactivePing(); // Убедимся, что предыдущий интервал остановлен this.pingIntervalId = setInterval(() => { if (this.socket && this.socket.connected) { console.log('Client sending proactive ping...'); let ackTimeout = setTimeout(() => { console.warn('Client: No pong response received for proactive ping within timeout. Forcing reconnect.'); this.disconnect(); this.connect(); }, 3000); // Таймаут ожидания ответа на пинг от сервера (3 секунды) // Используем callback для получения подтверждения от сервера this.socket.emit('client_proactive_ping', (response: { status: string }) => { clearTimeout(ackTimeout); if (response && response.status === 'pong') { console.log('Client received proactive pong.'); } else { console.warn('Client: Invalid pong response or no response for proactive ping. Forcing reconnect.'); this.disconnect(); this.connect(); } }); } else if (this.socket && !this.socket.connected) { // Здесь убираем '&& !this.socket.connecting' // Если сокет не подключен, Socket.IO должен сам управлять переподключением, // так как reconnection: true. Ручной вызов connect() может быть избыточным. console.log('Client proactive ping: Socket not connected. Relying on Socket.IO\'s auto-reconnect logic.'); // Если вы все же хотите агрессивно вызвать this.connect() при каждом обнаружении отключения, // оставьте следующую строку: this.connect(); } }, 5000); // Проверять каждые 5 секунд } /** * Останавливает проактивный пинг. */ private stopProactivePing(): void { if (this.pingIntervalId) { clearInterval(this.pingIntervalId); this.pingIntervalId = null; console.log('Proactive ping stopped.'); } } }