Files
nl-um-vue-ts/src/api/websocket/index.ts
2025-12-08 09:07:56 +08:00

212 lines
5.1 KiB
TypeScript
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.
import type { ChatMessage } from '@/types/api'
import type { MomentNotifPayload } from '@/types/moment'
export interface WebSocketMessage {
request_type?: string
clientId?: string
data?: ChatMessage | MomentNotifPayload
}
export type MessageHandler = (message: ChatMessage) => void
export type SignalHandler = (message: ChatMessage) => void
export type MomentNotifHandler = (payload: MomentNotifPayload) => void
class WebSocketManager {
private ws: WebSocket | null = null
private clientId: string | null = null
private userId: string | null = null
private messageHandlers: MessageHandler[] = []
private signalHandlers: SignalHandler[] = []
private momentNotifHandlers: MomentNotifHandler[] = []
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private reconnectDelay = 3000
/**
* 连接WebSocket
*/
connect(userId: string): Promise<void> {
return new Promise((resolve, reject) => {
if (this.ws?.readyState === WebSocket.OPEN) {
resolve()
return
}
this.userId = userId
const wsUrl = `ws://localhost:12080/ws?user_id=${userId}`
try {
this.ws = new WebSocket(wsUrl)
this.ws.onopen = () => {
console.log('✅ WebSocket connected')
this.reconnectAttempts = 0
resolve()
}
this.ws.onmessage = (event) => {
try {
// 处理可能的多行JSON或消息分割
const data = event.data.toString().trim()
const lines = data.split('\n').filter(line => line.trim())
for (const line of lines) {
try {
const payload: WebSocketMessage = JSON.parse(line)
// 接收clientId
if (payload.clientId) {
this.clientId = payload.clientId
}
// 处理接收消息
if (payload.request_type === 'receive_message' && payload.data) {
this.handleMessage(payload.data as ChatMessage)
}
// 处理朋友圈通知
if (payload.request_type === 'moment_notification' && payload.data) {
this.handleMomentNotification(payload.data as MomentNotifPayload)
}
} catch (e) {
// Ignore single line parse error
}
}
} catch (error) {
console.error('WebSocket message parse error:', error)
}
}
this.ws.onerror = (error) => {
console.error('WebSocket error:', error)
reject(error)
}
this.ws.onclose = () => {
console.log('WebSocket closed')
this.ws = null
this.attemptReconnect()
}
} catch (error) {
reject(error)
}
})
}
/**
* 断开连接
*/
disconnect() {
if (this.ws) {
this.ws.close()
this.ws = null
}
this.clientId = null
this.userId = null
this.messageHandlers = []
this.signalHandlers = []
this.momentNotifHandlers = []
}
/**
* 获取ClientID
*/
getClientId(): string | null {
return this.clientId
}
/**
* 添加普通消息处理器
*/
onMessage(handler: MessageHandler) {
this.messageHandlers.push(handler)
}
/**
* 移除普通消息处理器
*/
offMessage(handler: MessageHandler) {
const index = this.messageHandlers.indexOf(handler)
if (index > -1) {
this.messageHandlers.splice(index, 1)
}
}
/**
* 添加信令处理器 (WebRTC用)
*/
onSignal(handler: SignalHandler) {
this.signalHandlers.push(handler)
}
/**
* 移除信令处理器
*/
offSignal(handler: SignalHandler) {
const index = this.signalHandlers.indexOf(handler)
if (index > -1) {
this.signalHandlers.splice(index, 1)
}
}
/**
* 添加朋友圈通知处理器
*/
onMomentNotification(handler: MomentNotifHandler) {
this.momentNotifHandlers.push(handler)
}
/**
* 移除朋友圈通知处理器
*/
offMomentNotification(handler: MomentNotifHandler) {
const index = this.momentNotifHandlers.indexOf(handler)
if (index > -1) {
this.momentNotifHandlers.splice(index, 1)
}
}
/**
* 内部处理朋友圈通知
*/
private handleMomentNotification(payload: MomentNotifPayload) {
this.momentNotifHandlers.forEach(handler => handler(payload))
}
/**
* 内部处理接收到的消息
*/
private handleMessage(message: ChatMessage) {
// 信令消息message_type = 6- 路由到signalHandlers
if (message.message_type === 6) {
this.signalHandlers.forEach(handler => handler(message))
return
}
// 普通消息 - 路由到messageHandlers
this.messageHandlers.forEach(handler => handler(message))
}
/**
* 尝试重连
*/
private attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
return
}
if (!this.userId) {
return
}
this.reconnectAttempts++
setTimeout(() => {
if (this.userId) {
this.connect(this.userId).catch(console.error)
}
}, this.reconnectDelay)
}
}
export const wsManager = new WebSocketManager()