import Vue from "vue" import { XChatClient, wampDebug } from "xchat-client" import { NotifyMessage, Message } from "./models/chat" import { ChatLoggerService } from "./logger" import { TokenStringGetter } from "./../model" import chatType from "../xim/chat-type" wampDebug(true) const DefaultMsgPageSize = 20 function emptyFunc() { return } export type MsgListener = (msg: Message) => void export type ChatNotifyListener = (msg: NotifyMessage) => void export type StatusChangeListener = (status: any, details: any) => void export enum Events { Msg = "msg", Status = "status", } export enum Kind { Chat = "chat", ChatNotify = "chat_notify", UserNotify = "user_notify", } export class Xim { private eventBus = new Vue() private client?: XChatClient private paramsForReconnection?: { url: string token: TokenStringGetter } public close() { if (this.client) { if (this.client.connected) { this.client.close() } this.client.onconnected = emptyFunc this.client.onmsg = emptyFunc this.client = undefined } } private connectionPending = false public async open(url: string, token: TokenStringGetter) { this.connectionPending = true await new Promise((success: (p?: unknown) => void, failed) => { this.paramsForReconnection = { url, token } this.close() token().then((t) => { const client = new XChatClient(url, this.trimToken(t)) this.client = client client.onstatuschange = (status: any, details: any) => { this.onStatusChange.call(this, status, details) if (status === "DISCONNECTED" || status === "CLOSED") { failed() } } client.onconnected = () => { this.onConnected.apply(this) success() } client.onmsg = this.handleMsg.bind(this) client.open() }) }).finally(() => (this.connectionPending = false)) } private trimToken(token: string) { return token.replace(/^Bearer\s/, "") } /** * token过期或者切换用户登录时,需要设置新的token */ public async setToken(token: TokenStringGetter) { const client = this.client! client.close() client.setToken(this.trimToken(await token())) client.open() } public fetchMsgInBox(chatId: number, msgId: number) { return this.client!.fetchMsgInBox(chatType, chatId, msgId) } /** * 发送消息 */ public sendMsg( chatType: string, chatId: number, msgType: string, msg: string ) { this.checkConnected() return this.client!.sendMsg(chatType, chatId, msgType, msg, "", {}) } public inputing(chatId: number) { this.checkConnected() return this.client!.userInput(chatType, chatId) } /* * 查询会话 */ public fetchChatMembers(chat_id: number) { this.checkConnected() return this.client!.fetchChatMembers(chat_id) } /** * 查询消息 */ public async queryMsgs( chatType: string, chatId: number, lid = 0, rid = 0, limit = DefaultMsgPageSize, desc = true ): Promise<Message[]> { this.checkConnected() const res = await this.client!.fetchChatMsgs(chatType, chatId, { lid, rid, limit, desc, }) return res.args[0] } private setMessagesRead(chatId: number, msg: Message[]) { if (msg.length === 0) return return this.setRead(chatId, msg[0].id, msg[msg.length - 1].id) } /** 查询最后一页消息 */ public async queryLastPageMsg( chatType: string, chatId: number, limit: number ) { const data = await this.queryMsgs(chatType, chatId, 0, 0, limit, true) this.setMessagesRead(chatId, data) return data } /** 查询上一页消息 */ public async queryPrevPageMsg( chatType: string, chatId: number, msgId: number, limit: number ) { const data = await this.queryMsgs( chatType, chatId, 0, msgId, limit, true ) this.setMessagesRead(chatId, data) return data } /** 查询下一页消息 */ public async queryNextPageMsg( chatType: string, chatId: number, msgId: number, limit: number ) { const data = await this.queryMsgs( chatType, chatId, msgId, 0, limit, false ) this.setMessagesRead(chatId, data) return data } public on(event: "msg", chatId: number, listener: MsgListener): this public on(event: "msg", listener: MsgListener): this public on( event: "chat_notify", kind: "chat_change", listener: ChatNotifyListener ): this public on( event: "chat_notify", kind: string, listener: ChatNotifyListener ): this public on(event: "chat_notify", listener: ChatNotifyListener): this public on(event: "status", listener: StatusChangeListener): this public on(...args: any[]): this { this.eventBus.$on(...this.parseEventListener(...args)) return this } public off(event: "msg", chatId: number, listener: MsgListener): this public off(event: "msg", listener: MsgListener): this public off( event: "chat_notify", kind: "chat_change", listener: ChatNotifyListener ): this public off( event: "chat_notify", kind: string, listener: ChatNotifyListener ): this public off(event: "chat_notify", listener: ChatNotifyListener): this public off(event: "status", listener: StatusChangeListener): this public off(...args: any[]): this { this.eventBus.$off(...this.parseEventListener(...args)) return this } public once(...args: any[]): this { this.eventBus.$once(...this.parseEventListener(...args)) return this } public emit(event: string, ...args: any[]): this { this.eventBus.$emit(event, ...args) return this } /** * 移除会话(用户端/移动端使用) */ public async closeChat(chatId: number) { return this.client?.setChat(chatId, { is_remove: true }) } public isConnected() { return this.client?.connected } public setRead(chatId: number, start_msg_id: number, end_msg_id: number) { return this.client?.syncReadMsg(chatId, start_msg_id, end_msg_id) } private parseEventListener(...args: any[]): [string, Function] { if (args.length < 2) { throw new Error("参数个数不正确") } const listener: Function = args[args.length - 1] return [args.slice(0, -1).join("."), listener] } private onConnected() { // 连接成功后,需要调用pubUserInfo, 否则服务端会认为此连接无效 this.client!.pubUserInfo("") this.debug("xim connected") } /* DISCONNECTED: "DISCONNECTED", CONNECTING: "CONNECTING", CONNECTED: "CONNECTED", CLOSED: "CLOSED", */ private onStatusChange(status: any, details: any) { this.debug("onstatuschange", status, details) this.emit(Events.Status, status, details) if (status === "DISCONNECTED" || status === "CLOSED") { this.hanldeOffline() } } private hanldeOffline() { this.debug("开始重连") this.reOpen() } private reOpen() { if (this.connectionPending) return if (this.paramsForReconnection == null) return this.open( this.paramsForReconnection.url, this.paramsForReconnection.token ) } private handleMsg(kind: any, msg: any) { this.debug(`收到消息 ${new Date().getTime()}`, kind, msg) switch (kind) { case "chat": this.emit(`msg`, msg) this.emit(`msg.${msg.chat_id}`, msg) break case "chat_notify": this.emit(`chat_notify`, msg) this.emit(`chat_notify.${msg.msg_type}`, msg) break default: this.emit(kind, msg) } } private checkConnected() { if (!this.client!.connected) { try { this.client?.open() } catch (e) { console.error("checkConnected", e) this.reOpen() } } } private debug(message: any, ...params: any[]) { ChatLoggerService.logger?.debug(message, params) } } const ximInstance = new Xim() export default ximInstance