/** * WebSocket Client Helper * * Provides a simple interface for WebSocket connections */ class WebSocketClient { constructor(url, options = {}) { this.url = url this.ws = null this.reconnectAttempts = 0 this.maxReconnectAttempts = options.maxReconnectAttempts || 5 this.reconnectInterval = options.reconnectInterval || 3000 this.reconnectDelay = options.reconnectDelay || 1000 this.heartbeatInterval = options.heartbeatInterval || 30000 this.heartbeatTimer = null this.isManualClose = false this.isConnecting = false // Event handlers this.onOpen = options.onOpen || null this.onMessage = options.onMessage || null this.onError = options.onError || null this.onClose = options.onClose || null // Message handlers this.messageHandlers = new Map() } /** * Connect to WebSocket server */ connect() { if (this.isConnecting || (this.ws && this.ws.readyState === WebSocket.OPEN)) { return } this.isConnecting = true this.isManualClose = false try { this.ws = new WebSocket(this.url) this.ws.onopen = (event) => { console.log('WebSocket connected', event) this.isConnecting = false this.reconnectAttempts = 0 // Start heartbeat this.startHeartbeat() // Call onOpen handler if (this.onOpen) { this.onOpen(event) } } this.ws.onmessage = (event) => { try { const message = JSON.parse(event.data) console.log('WebSocket message received', message) // Handle different message types this.handleMessage(message) // Call onMessage handler if (this.onMessage) { this.onMessage(message, event) } } catch (error) { console.error('Failed to parse WebSocket message', error) } } this.ws.onerror = (error) => { console.error('WebSocket error', error) this.isConnecting = false // Stop heartbeat this.stopHeartbeat() // Call onError handler if (this.onError) { this.onError(error) } } this.ws.onclose = (event) => { console.log('WebSocket closed', event) this.isConnecting = false // Stop heartbeat this.stopHeartbeat() // Call onClose handler if (this.onClose) { this.onClose(event) } // Attempt to reconnect if not manually closed if (!this.isManualClose && this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnect() } } } catch (error) { console.error('Failed to create WebSocket connection', error) this.isConnecting = false // Call onError handler if (this.onError) { this.onError(error) } } } /** * Reconnect to WebSocket server */ reconnect() { if (this.isConnecting || this.reconnectAttempts >= this.maxReconnectAttempts) { console.log('Max reconnection attempts reached') return } this.reconnectAttempts++ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1) console.log(`Reconnecting attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`) setTimeout(() => { this.connect() }, delay) } /** * Disconnect from WebSocket server */ disconnect() { this.isManualClose = true this.stopHeartbeat() if (this.ws) { this.ws.close() this.ws = null } } /** * Send message to server */ send(type, data = {}) { if (this.ws && this.ws.readyState === WebSocket.OPEN) { const message = JSON.stringify({ type, data }) this.ws.send(message) console.log('WebSocket message sent', { type, data }) } else { console.warn('WebSocket is not connected') } } /** * Handle incoming messages */ handleMessage(message) { const { type, data } = message // Get handler for this message type const handler = this.messageHandlers.get(type) if (handler) { handler(data) } } /** * Register message handler */ on(messageType, handler) { this.messageHandlers.set(messageType, handler) } /** * Unregister message handler */ off(messageType) { this.messageHandlers.delete(messageType) } /** * Start heartbeat */ startHeartbeat() { this.stopHeartbeat() this.heartbeatTimer = setInterval(() => { this.send('heartbeat', { timestamp: Date.now() }) }, this.heartbeatInterval) } /** * Stop heartbeat */ stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer) this.heartbeatTimer = null } } /** * Get connection state */ get readyState() { if (!this.ws) return WebSocket.CLOSED return this.ws.readyState } /** * Check if connected */ get isConnected() { return this.ws && this.ws.readyState === WebSocket.OPEN } } /** * Create WebSocket connection */ export function createWebSocket(userId, token, options = {}) { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' // 优先使用配置的 WS_URL,否则使用当前域名 const host = options.wsUrl || window.location.host const url = `${protocol}//${host}/ws?user_id=${userId}&token=${token}` return new WebSocketClient(url, options) } /** * WebSocket singleton instance */ let wsClient = null export function getWebSocket(userId, token, options = {}) { if (!wsClient || !wsClient.isConnected) { wsClient = createWebSocket(userId, token, options) } return wsClient } export function closeWebSocket() { if (wsClient) { wsClient.disconnect() wsClient = null } } export default WebSocketClient