solver-sdk
Version:
SDK для интеграции с Code Solver Backend API
166 lines • 8.16 kB
JavaScript
import { SessionManager } from '../utils/session-manager.js';
import { PingPongManager } from '../utils/ping-pong-manager.js';
import { ConnectionStateManager } from '../utils/connection-state-manager.js';
import { createWebSocketLogger } from '../utils/logger.js';
/**
* Сервис для диагностики WebSocket соединений
*/
export class DiagnosticsService {
/**
* Создает новый сервис диагностики
* @param {DiagnosticsServiceOptions} options Опции сервиса
*/
constructor(options = {}) {
/** Таймер для проверки здоровья соединений */
this.healthCheckTimer = null;
/** Клиенты WebSocket по namespace */
this.clients = new Map();
this.healthCheckInterval = options.healthCheckInterval || 30000;
this.logger = options.logger || createWebSocketLogger('DiagnosticsService');
this.sessionManager = options.sessionManager || new SessionManager();
this.pingPongManager = options.pingPongManager || new PingPongManager();
this.connectionStateManager = options.connectionStateManager || new ConnectionStateManager();
}
/**
* Регистрирует WebSocket клиент для мониторинга
* @param {WebSocketNamespace} namespace Пространство имен
* @param {WebSocketClient} client WebSocket клиент
*/
registerClient(namespace, client) {
this.clients.set(namespace, client);
this.logger.debug(`Зарегистрирован клиент для ${namespace}`, {
socketId: client.getSocketId()
});
}
/**
* Удаляет регистрацию WebSocket клиента
* @param {WebSocketNamespace} namespace Пространство имен
*/
unregisterClient(namespace) {
this.clients.delete(namespace);
this.logger.debug(`Удалена регистрация клиента для ${namespace}`);
}
/**
* Запускает периодическую проверку здоровья соединений
* @returns {boolean} true, если проверка успешно запущена
*/
startHealthCheck() {
// Останавливаем существующую проверку, если она есть
this.stopHealthCheck();
this.healthCheckTimer = setInterval(() => {
this.performHealthCheck();
}, this.healthCheckInterval);
this.logger.info(`Запущена периодическая проверка здоровья соединений с интервалом ${this.healthCheckInterval}ms`);
return true;
}
/**
* Останавливает периодическую проверку здоровья соединений
*/
stopHealthCheck() {
if (this.healthCheckTimer) {
clearInterval(this.healthCheckTimer);
this.healthCheckTimer = null;
this.logger.info('Остановлена периодическая проверка здоровья соединений');
}
}
/**
* Выполняет проверку здоровья соединений
*/
performHealthCheck() {
for (const [namespace, client] of this.clients.entries()) {
try {
// Проверяем соединение через WebSocket клиент
if (!client.isConnected()) {
this.logger.warn(`Соединение с ${namespace} не активно`);
continue;
}
// Проверяем статистику ping/pong
const stats = this.pingPongManager.getPingStats(namespace);
if (stats) {
const now = Date.now();
// Если последний pong был получен слишком давно
if (stats.lastPongTimestamp && now - stats.lastPongTimestamp > this.healthCheckInterval * 2) {
this.logger.warn(`Долгое отсутствие активности для ${namespace}, последний pong: ${new Date(stats.lastPongTimestamp).toISOString()}`);
// Отправляем проверочный ping для обновления статистики
client.send({
event: 'connection_health_check',
data: { timestamp: now, echo: now }
});
}
}
}
catch (error) {
this.logger.error(`Ошибка при проверке здоровья соединения ${namespace}`, error);
}
}
}
/**
* Выполняет диагностику соединения
* @param {WebSocketNamespace} namespace Пространство имен
* @returns {ConnectionDiagnostics} Диагностическая информация
*/
diagnoseConnection(namespace) {
const client = this.clients.get(namespace);
const stats = this.pingPongManager.getPingStats(namespace);
const state = this.connectionStateManager.getState(namespace);
const sessionToken = this.sessionManager.getSessionToken(namespace);
return {
namespace,
isConnected: client?.isConnected() || false,
socketId: client?.getSocketId() || null,
lastActivity: stats?.lastPongTimestamp || 0,
rtt: {
current: stats?.lastRtt || -1,
min: stats?.minRtt === Number.MAX_SAFE_INTEGER ? -1 : (stats?.minRtt || -1),
max: stats?.maxRtt || -1,
avg: stats?.averageRtt || -1
},
pingSent: stats?.pingSent || 0,
pongReceived: stats?.pongReceived || 0,
missedPongs: (stats?.pingSent || 0) - (stats?.pongReceived || 0),
timeoutCount: 0, // Это значение не хранится в PingPongManager, нужно дорабатывать логику
reconnectAttempts: state.reconnectAttempts,
lastConnectTime: state.lastConnectTime,
sessionRecovery: {
hasSessionToken: !!sessionToken,
tokenLength: sessionToken?.length || 0,
wasRecovered: !!sessionToken && (stats?.pongReceived || 0) > 0
}
};
}
/**
* Выполняет диагностику всех соединений
* @returns {Record<string, ConnectionDiagnostics>} Диагностическая информация по всем соединениям
*/
diagnoseAllConnections() {
const result = {};
// Проверяем каждое зарегистрированное пространство имен
for (const namespace of this.clients.keys()) {
result[String(namespace)] = this.diagnoseConnection(namespace);
}
return result;
}
/**
* Получает статистику ping/pong для указанного пространства имен
* @param {WebSocketNamespace} namespace Пространство имен
* @returns {PingPongStats | null} Статистика ping/pong
*/
getPingStats(namespace) {
return this.pingPongManager.getPingStats(namespace);
}
/**
* Возвращает функцию-обработчик для pong-ответов, которая рассчитывает RTT
* @returns {(data: any) => void} Функция-обработчик
*/
getPongHandler() {
return (data) => {
if (data && data.echo) {
const rtt = Date.now() - data.echo;
this.logger.debug(`[PONG] RTT: ${rtt}ms, namespace: ${data.namespace || 'unknown'}`);
return rtt;
}
return -1;
};
}
}
//# sourceMappingURL=diagnostics-service.js.map