/
itp_practice
/
itp_frontend
Обзор
Документация
Войти
/
itp_practice
/
itp_frontend
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
src/services/WebSocketService.js
288 строк
9 KB
vivanenko
stabilize websocket
30 май 2025, 15:18
30 май 2025, 15:18
f181d6c
Код
Авторство
О чём код?
import { getWsUrl } from '../config'; class WebSocketService { constructor() { this.socket = null; this.listeners = new Map(); this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectTimeout = 1000; this.isConnecting = false; this.isManuallyDisconnected = false; this.connectionPromise = null; this.pingInterval = null; this.pendingMessages = []; } connect() { if (this.connectionPromise) { return this.connectionPromise; } if (this.socket && this.socket.readyState === WebSocket.OPEN) { return Promise.resolve(); } this.isManuallyDisconnected = false; this.isConnecting = true; this.connectionPromise = new Promise((resolve, reject) => { try { if (this.socket) { this.socket.onclose = null; this.socket.onerror = null; this.socket.onmessage = null; this.socket.close(); } const wsUrl = getWsUrl() + 'ws'; console.log('Connecting to WebSocket:', wsUrl); this.socket = new WebSocket(wsUrl); const connectionTimeout = setTimeout(() => { if (this.socket.readyState !== WebSocket.OPEN) { this.socket.close(); this.isConnecting = false; this.connectionPromise = null; reject(new Error('Connection timeout')); } }, 10000); this.socket.onopen = () => { clearTimeout(connectionTimeout); console.log('WebSocket connected'); this.reconnectAttempts = 0; this.isConnecting = false; this.connectionPromise = null; this.startPingInterval(); this.flushPendingMessages(); this.notifyListeners('connected', { connected: true }); resolve(); }; this.socket.onclose = (event) => { clearTimeout(connectionTimeout); console.log('WebSocket disconnected', event); this.isConnecting = false; this.connectionPromise = null; this.stopPingInterval(); this.notifyListeners('disconnected', { connected: false }); if (!this.isManuallyDisconnected && !event.wasClean) { this.handleReconnect(); } }; this.socket.onerror = (error) => { clearTimeout(connectionTimeout); console.error('WebSocket error:', error); this.isConnecting = false; this.connectionPromise = null; if (this.socket.readyState === WebSocket.CONNECTING) { reject(error); } }; this.socket.onmessage = (event) => { try { const data = JSON.parse(event.data); console.log('WebSocket received:', data); this.handleMessage(data); } catch (e) { console.error('Failed to parse WebSocket message:', e); } }; } catch (error) { this.isConnecting = false; this.connectionPromise = null; reject(error); } }); return this.connectionPromise; } startPingInterval() { this.stopPingInterval(); this.pingInterval = setInterval(() => { if (this.socket && this.socket.readyState === WebSocket.OPEN) { try { this.socket.send(JSON.stringify({ type: 'ping' })); } catch (e) { console.error('Failed to send ping:', e); } } }, 30000); } stopPingInterval() { if (this.pingInterval) { clearInterval(this.pingInterval); this.pingInterval = null; } } handleReconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; const delay = Math.min(this.reconnectTimeout * Math.pow(2, this.reconnectAttempts - 1), 30000); console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts}) in ${delay}ms`); setTimeout(() => { if (!this.isManuallyDisconnected) { this.connect().catch(error => { console.error('Reconnection failed:', error); }); } }, delay); } else { console.error('Max reconnection attempts reached'); this.notifyListeners('error', { type: 'max_reconnect_attempts', message: 'Failed to reconnect to server' }); } } disconnect() { this.isManuallyDisconnected = true; this.stopPingInterval(); if (this.socket) { this.socket.onclose = null; this.socket.onerror = null; this.socket.onmessage = null; this.socket.close(); this.socket = null; } this.connectionPromise = null; this.isConnecting = false; this.pendingMessages = []; } subscribe(event, callback) { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } this.listeners.get(event).add(callback); return () => { const callbacks = this.listeners.get(event); if (callbacks) { callbacks.delete(callback); if (callbacks.size === 0) { this.listeners.delete(event); } } }; } notifyListeners(event, data) { const callbacks = this.listeners.get(event); if (callbacks) { callbacks.forEach(callback => { try { callback(data); } catch (e) { console.error('Error in WebSocket listener:', e); } }); } } handleMessage(data) { this.notifyListeners('message', data); if (data.message_type) { this.notifyListeners(data.message_type, data); } } async send(message) { if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { console.log('WebSocket not connected, attempting to connect...'); this.pendingMessages.push(message); try { await this.connect(); } catch (error) { throw new Error('Failed to connect to WebSocket'); } } if (this.socket && this.socket.readyState === WebSocket.OPEN) { try { console.log('Sending WebSocket message:', message); this.socket.send(JSON.stringify(message)); } catch (error) { console.error('Failed to send message:', error); throw error; } } else { throw new Error('WebSocket is not connected'); } } flushPendingMessages() { while (this.pendingMessages.length > 0 && this.socket && this.socket.readyState === WebSocket.OPEN) { const message = this.pendingMessages.shift(); try { this.socket.send(JSON.stringify(message)); console.log('Sent pending message:', message); } catch (e) { console.error('Failed to send pending message:', e); this.pendingMessages.unshift(message); break; } } } async sendGetCode(phoneNumber, proxy = null) { console.log('Sending get code request:', { phoneNumber, proxy }); if (typeof phoneNumber !== 'string' || !phoneNumber.trim()) { console.error('Invalid phone number format:', phoneNumber); throw new Error('Phone number must be a non-empty string'); } const message = { message_type: "get_code", phone_number: phoneNumber.trim(), proxy: proxy ? `${proxy.proxy_string}` : null }; await this.send(message); } isConnected() { return this.socket && this.socket.readyState === WebSocket.OPEN; } getConnectionState() { if (!this.socket) return 'disconnected'; switch (this.socket.readyState) { case WebSocket.CONNECTING: return 'connecting'; case WebSocket.OPEN: return 'connected'; case WebSocket.CLOSING: return 'closing'; case WebSocket.CLOSED: return 'disconnected'; default: return 'unknown'; } } } export default new WebSocketService();