llm-agent-plugin/src/utils/WebSocketService.ts

175 lines
6.4 KiB
TypeScript

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<string> = new Set(); // Для отслеживания полученных событий
//private reconnectAttempts = 0;
private maxReconnectAttempts = 10;
private pingIntervalId: ReturnType<typeof setInterval> | 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.');
}
}
}