175 lines
6.4 KiB
TypeScript
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.');
|
|
}
|
|
}
|
|
}
|