103 行
3.2 KiB
Plaintext
103 行
3.2 KiB
Plaintext
|
|
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)
|
||
|
|
}
|
||
|
|
}
|