UNPKG

solver-sdk

Version:

SDK для интеграции с Code Solver Backend API

166 lines 8.16 kB
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