/** * WebSocket管理 * 管理WebSocket连接,处理WebSocket事件 */ class WebSocketManager { constructor(url = null) { this.url = url || this.getDefaultWebSocketUrl(); this.socket = null; this.isConnected = false; this.listeners = new Map(); this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectDelay = 1000; this.connectionId = null; this.heartbeatInterval = null; } /** * 获取默认WebSocket URL * @returns {string} WebSocket URL */ getDefaultWebSocketUrl() { const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; return `${protocol}//${location.host}`; } /** * 连接WebSocket */ connect() { try { this.socket = new WebSocket(this.url); this.socket.onopen = () => { console.log('WebSocket connected'); this.isConnected = true; this.reconnectAttempts = 0; // 生成连接ID this.connectionId = this.generateConnectionId(); // 发送连接消息 this.sendConnectMessage(); // 启动心跳 this.startHeartbeat(); this.emit('connect'); }; this.socket.onclose = () => { console.log('WebSocket disconnected'); this.isConnected = false; this.stopHeartbeat(); this.emit('disconnect'); this.attemptReconnect(); }; this.socket.onmessage = (event) => { try { const message = JSON.parse(event.data); this.handleMessage(message); } catch (error) { console.error('Error parsing WebSocket message:', error); } }; this.socket.onerror = (error) => { console.error('WebSocket error:', error); this.emit('error', error); }; } catch (error) { console.error('Error connecting to WebSocket:', error); this.emit('error', error); } } /** * 断开WebSocket连接 */ disconnect() { if (this.socket) { this.socket.close(); this.socket = null; this.isConnected = false; } } /** * 发送消息 * @param {string} type - 消息类型 * @param {Object} data - 消息数据 */ send(type, data) { if (this.isConnected && this.socket) { try { let message; // 根据消息类型构建不同的消息格式 if (type === 'connect' || type === 'disconnect') { message = JSON.stringify({ type, connectionId: this.connectionId }); } else if (type === 'offer' || type === 'answer' || type === 'candidate') { message = JSON.stringify({ type, data }); } else if (type === 'broadcast') { message = JSON.stringify({ type, message: data.message, targetConnectionId: data.targetConnectionId }); } else if (type === 'ping' || type === 'pong') { message = JSON.stringify({ type }); } else { // 兼容旧格式,用于自定义事件 message = JSON.stringify({ event: type, data }); } this.socket.send(message); } catch (error) { console.error('Error sending WebSocket message:', error); } } else { console.warn('WebSocket not connected, cannot send message'); } } /** * 处理接收到的消息 * @param {Object} message - 消息对象 */ handleMessage(message) { if (message.type) { switch (message.type) { case 'user-joined': this.emit('user-joined', message.data); break; case 'user-left': this.emit('user-left', message.data); break; case 'media-state-changed': this.emit('media-state-changed', message.data); break; case 'message-received': this.emit('message-received', message.data); break; case 'network-quality': this.emit('network-quality', message.data); break; case 'call-ended': this.emit('call-ended', message.data); break; case 'call-request': this.emit('call-request', message.data); break; case 'ping': // 处理心跳请求,回复pong this.send('pong'); break; case 'pong': // 处理心跳响应 this.emit('pong'); break; case 'offer': this.emit('offer', message.data); break; case 'answer': this.emit('answer', message.data); break; case 'candidate': this.emit('candidate', message.data); break; default: // 处理旧格式消息 if (message.event) { this.emit(message.event, message.data); } else { this.emit('message', message); } break; } } else if (message.event) { // 处理旧格式消息 this.emit(message.event, message.data); } else { this.emit('message', message); } } /** * 尝试重连 */ attemptReconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1); console.log(`Attempting to reconnect in ${delay}ms...`); setTimeout(() => { console.log(`Reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`); this.connect(); }, delay); } else { console.error('Max reconnect attempts reached'); this.emit('reconnect-failed'); } } /** * 生成连接ID * @returns {string} 连接ID */ generateConnectionId() { return 'conn_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); } /** * 发送连接消息 */ sendConnectMessage() { this.send('connect'); } /** * 发送断开连接消息 */ sendDisconnectMessage() { this.send('disconnect'); } /** * 启动心跳 */ startHeartbeat() { this.heartbeatInterval = setInterval(() => { if (this.isConnected) { this.send('ping'); } }, 30000); // 每30秒发送一次心跳 } /** * 停止心跳 */ stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } /** * 获取连接ID * @returns {string} 连接ID */ getConnectionId() { return this.connectionId; } /** * 订阅事件 * @param {string} event - 事件名称 * @param {Function} callback - 回调函数 */ on(event, callback) { if (!this.listeners.has(event)) { this.listeners.set(event, []); } this.listeners.get(event).push(callback); } /** * 取消订阅事件 * @param {string} event - 事件名称 * @param {Function} callback - 回调函数 */ off(event, callback) { if (this.listeners.has(event)) { const callbacks = this.listeners.get(event); this.listeners.set(event, callbacks.filter(cb => cb !== callback)); } } /** * 触发事件 * @param {string} event - 事件名称 * @param {*} data - 事件数据 */ emit(event, data) { if (this.listeners.has(event)) { this.listeners.get(event).forEach(callback => { try { callback(data); } catch (error) { console.error(`Error in event listener for ${event}:`, error); } }); } } /** * 检查连接状态 * @returns {boolean} 是否连接 */ getIsConnected() { return this.isConnected; } } // 创建单例实例 const wsManager = new WebSocketManager(); export default wsManager;