260 lines
5.7 KiB
JavaScript
260 lines
5.7 KiB
JavaScript
/**
|
||
* 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
|