Files
laravel_swoole/resources/admin/src/utils/websocket.js
2026-02-18 17:54:07 +08:00

260 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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