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) } }