XuqmGroup-HarmonySDK/xuqm-sdk/src/main/ets/im/ImClient.ets

103 行
3.2 KiB
Plaintext

2026-04-21 22:07:29 +08:00
import webSocket from '@ohos.net.webSocket'
import type { ImMessage, SendMessageParams } from '../core/Types'
import { SDKContext } from '../core/SDKContext'
export interface ImEventDelegate {
onConnected?(): void
onDisconnected?(code: number, reason: string): void
onMessage?(msg: ImMessage): void
onRevoke?(data: RevokeData): void
onError?(message: string): void
}
export interface RevokeData {
msgId: string
operatorId: string
}
const MAX_RECONNECT_DELAY = 30_000
export class ImClient {
private ws: webSocket.WebSocket | null = null
private reconnectDelay: number = 3_000
private reconnectTimer: number | null = null
private destroyed: boolean = false
delegate: ImEventDelegate | null = null
connect(): void {
if (this.destroyed) return
const config = SDKContext.getConfig()
const token = SDKContext.getToken() ?? ''
const url = `${config.imBaseUrl}/ws/im?token=${token}`
this.ws = webSocket.createWebSocket()
this.ws.on('open', (_err: Error, _value: Object) => {
this.reconnectDelay = 3_000
if (config.debug) console.log('[ImClient] connected')
this.delegate?.onConnected?.()
})
this.ws.on('message', (_err: Error, value: string | ArrayBuffer) => {
try {
const text = typeof value === 'string' ? value : new TextDecoder().decode(value)
const frame = JSON.parse(text) as { type: string; payload: object }
if (frame.type === 'MESSAGE') {
this.delegate?.onMessage?.(frame.payload as ImMessage)
} else if (frame.type === 'REVOKE') {
this.delegate?.onRevoke?.(frame.payload as RevokeData)
}
} catch {
// ignore malformed frames
}
})
this.ws.on('close', (_err: Error, value: webSocket.CloseResult) => {
this.delegate?.onDisconnected?.(value.code, value.reason)
if (!this.destroyed) this.scheduleReconnect()
})
this.ws.on('error', (_err: Error) => {
this.delegate?.onError?.(_err.message)
})
this.ws.connect(url, {})
}
send(params: SendMessageParams): void {
if (!this.ws) throw new Error('WebSocket not connected')
const frame = JSON.stringify({ destination: '/app/chat.send', payload: params })
this.ws.send(frame, (_err: Error) => {
if (_err) console.error('[ImClient] send error', _err.message)
})
}
revoke(msgId: string): void {
if (!this.ws) throw new Error('WebSocket not connected')
const frame = JSON.stringify({ destination: '/app/chat.revoke', payload: { msgId } })
this.ws.send(frame, (_err: Error) => {
if (_err) console.error('[ImClient] revoke error', _err.message)
})
}
disconnect(): void {
this.destroyed = true
if (this.reconnectTimer !== null) {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = null
}
this.ws?.close((_err: Error) => {})
this.ws = null
}
private scheduleReconnect(): void {
if (this.destroyed) return
const delay = this.reconnectDelay
if (SDKContext.getConfig().debug) console.log(`[ImClient] reconnect in ${delay}ms`)
this.reconnectTimer = setTimeout(() => {
this.connect()
this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY)
}, delay)
}
}