import type { Message } from '@tencentcloud/lite-chat/basic'; import TUIBase from '../tui-base'; import type { ITUIChatService } from '../interface/service'; import type { SendMessageParams, GetMessageListParams, SendMessageOptions, GetMessageListHoppingParams, func, } from '../type'; import type { IMessageModel } from '../interface/model'; import MessageHandler from './message-handler'; import { JSONToObject, isArray, isUndefined } from '../utils/common-utils'; import { StoreName, ERROR_CODE, ERROR_MSG, ERROR_CODE_ENGINE, ERROR_MSG_ENGINE } from '../const'; import isEmpty from '../utils/is-empty'; export default class TUIChatService extends TUIBase implements ITUIChatService { static instance: TUIChatService; private serv: string; public messageHandler: MessageHandler; private isSwitching: boolean; private delayGetHoppingFunction: func | undefined; private hoppingConfigMap: Map; constructor() { super(); this.serv = 'TUIChatService'; this.messageHandler = new MessageHandler(this); this.isSwitching = true; this.delayGetHoppingFunction = undefined; this.hoppingConfigMap = new Map(); } /** * 获取 TUIChatService 实例 */ static getInstance() { if (!TUIChatService.instance) { TUIChatService.instance = new TUIChatService(); } return TUIChatService.instance; } init() { const chatEngine = this.getEngine(); chatEngine.eventCenter.addEvent(chatEngine.EVENT.MESSAGE_RECEIVED, this.onMessageReceived.bind(this)); this.onCurrentConversationIDUpdated(); this.onMessageSource(); } private onMessageReceived(messageList: Message[]) { this.updateMessageList(messageList, 'received'); // 提供给 TUINotification 组件使用(即时消费,Store 不存储) this.getEngine().TUIStore.update(StoreName.CHAT, 'newMessageList', messageList); } private onCurrentConversationIDUpdated() { const chatEngine = this.getEngine(); chatEngine.TUIStore.watch(StoreName.CONV, { currentConversationID: (conversationID) => { // 记录切换会话开始状态 this.isSwitching = true; this.delayGetHoppingFunction = undefined; this.hoppingConfigMap.clear(); // conversation 切换, 需重置 chat 数据 chatEngine.TUIStore.reset(StoreName.CHAT); // todo 此处特化处理,防止在 switch conversation 的时候调用用户页面还没打开,导致事件没监听 // 注: 此时 Chat Store 内的数值已经在 conversation 模块被 reset if (!isEmpty(conversationID)) { this.getMessageList().finally(() => { // 切换会话完成 this.isSwitching = false; this.delayGetHoppingFunction && this.delayGetHoppingFunction(); }); } }, }); } private onMessageSource() { const chatEngine = this.getEngine(); chatEngine.TUIStore.watch(StoreName.CHAT, { messageSource: (targetMessage: IMessageModel) => { const currentConversationID = this.getStoreData(StoreName.CONV, 'currentConversationID'); // 当前没有打开会话或搜索的消息不在当前会话中,不进行 messageList 切换 if (!currentConversationID || (targetMessage && targetMessage.conversationID !== currentConversationID)) { return; } // targetMessage === undefined 有两种情况,1.切换会话; 2.回到最新消息 if (isUndefined(targetMessage)) { this.hoppingConfigMap.clear(); chatEngine.TUIStore.update(StoreName.CHAT, 'messageList', []); chatEngine.TUIStore.update(StoreName.CHAT, 'nextReqMessageID', ''); chatEngine.TUIStore.update(StoreName.CHAT, 'isCompleted', false); this.getMessageList(); return; } const currentMessageList = this.getStoreData(StoreName.CHAT, 'messageList'); const message = currentMessageList && currentMessageList.find((item: IMessageModel) => targetMessage && item.ID === targetMessage.ID); if (message) { return; } // 防止切换会话,getMessageList 没有执行完成,再执行 getMessageListHopping 会导致 messageList 错乱 if (this.isSwitching) { this.delayGetHoppingFunction = this.getMessageListHoppingForDown; return; } this.getMessageListHoppingForDown(); }, }); } private getMessageListHoppingForDown() { const currentMessageList = this.getStoreData(StoreName.CHAT, 'messageList'); const { conversationID, sequence, time, ID } = this.getStoreData(StoreName.CHAT, 'messageSource'); const message = currentMessageList && currentMessageList.find((item: IMessageModel) => ID && item.ID === ID); if (message) { return; } const chatEngine = this.getEngine(); chatEngine.TUIStore.update(StoreName.CHAT, 'messageList', []); chatEngine.TUIStore.update(StoreName.CHAT, 'nextReqMessageID', ''); chatEngine.TUIStore.update(StoreName.CHAT, 'isCompleted', false); this.getMessageListHopping({ conversationID, sequence, time, direction: 1 }); } /** * 私有方法,获取 store data */ private getStoreData(storeName: StoreName, key: string) { return this.getEngine().TUIStore.getData(storeName, key); } /** * 私有方法,调用 TIM 接口发送消息 * @param {Message} message 要发送的 message * @param {SendMessageOptions} [options] 消息发送选项 * @returns {Promise} im 接口的 response 原样返回 * */ private sendMessage(message: Message, options?: SendMessageOptions) { this.updateMessageList([message], 'send'); // 不论成功失败,先消息上屏,更新 UI const promise = this.getEngine().chat.sendMessage(message, options); return this.getResponse(promise); } /** * 私有方法,用于 messageList 状态变更并 return * @param {Promise} promise 调用 im 接口返回的 promise * @param {boolean} success 成功回调是否更新数据 * @param {boolean} fail 失败回调是否更新数据 * @returns {Promise} im 接口的 response 原样返回 */ private getResponse(promise: Promise, success = true, fail = true) { return promise .then((imResponse: any) => { const messageList = imResponse.data.messageList ? imResponse.data.messageList : [imResponse.data.message]; success && this.updateMessageList(messageList, 'edit'); return imResponse; }) .catch((error: any) => { fail && error?.data?.message && this.updateMessageList([error.data.message], 'edit'); return Promise.reject(error); }); } updateMessageList(messageList: Message[], type = '') { if (this.getStoreData(StoreName.CHAT, 'messageSource') && type !== 'unshift' && type !== 'edit') { return; } const currentMessageList = this.getStoreData(StoreName.CHAT, 'messageList'); const tempMessageList = this.updateTargetMessageList(messageList, currentMessageList, type); this.getEngine().TUIStore.update(StoreName.CHAT, 'messageList', tempMessageList); } updateTargetMessageList(newMessageList: Message[], target: IMessageModel[], type = '') { const conversationID = this.getStoreData(StoreName.CONV, 'currentConversationID'); // filter current conversation message let toBeUpdatedMessageList: Message[] = newMessageList.filter(item => item.conversationID === conversationID); // handle call signaling message toBeUpdatedMessageList = this.handleC2CCallSignaling(toBeUpdatedMessageList); if (!type || toBeUpdatedMessageList.length === 0) return target; const currentMessageList: any = target || []; let tempMessageList: Message[] = []; // When Chat is integrated into Room, it needs to be updated. if (type === 'send' || type === 'push' || type === 'received') { const userInfo: any = this.getStoreData(StoreName.CHAT, 'userInfo'); if (Object.keys(userInfo).length > 0) { this.updateLocalMessage(toBeUpdatedMessageList, userInfo); } } const enableAutoMessageRead = this.getStoreData(StoreName.APP, 'enableAutoMessageRead'); switch (type) { case 'edit': for (const currentMessage of target) { const message = toBeUpdatedMessageList.find(m => m.ID === currentMessage.ID); tempMessageList.push(message || currentMessage); } break; case 'resend': // Resending message is not typing and only one message tempMessageList = currentMessageList.filter((message: IMessageModel) => message.ID !== toBeUpdatedMessageList[0].ID).concat(toBeUpdatedMessageList); break; case 'send': tempMessageList = currentMessageList.concat(toBeUpdatedMessageList); break; case 'push': // message from call or room tempMessageList = currentMessageList.concat(toBeUpdatedMessageList); this.getEngine().chat.setMessageRead({ conversationID }); break; case 'received': // message from MESSAGE_RECEIVED tempMessageList = currentMessageList.concat(toBeUpdatedMessageList); tempMessageList = this.sortMessageList(tempMessageList); if (enableAutoMessageRead) { this.getEngine().chat.setMessageRead({ conversationID }); } break; case 'unshift': // Deduplicate the data in the Store when pulling roaming tempMessageList = toBeUpdatedMessageList.filter((message: Message) => ( (currentMessageList.length === 0) || !currentMessageList.find((m: Message | IMessageModel) => m.ID === message.ID) )); tempMessageList.push(...currentMessageList); tempMessageList = this.sortMessageList(tempMessageList); break; default: break; } return tempMessageList; } enterTypingState() { const enableTyping = this.getStoreData(StoreName.APP, 'enableTyping'); if (enableTyping) { this.sendTyping(true); } } leaveTypingState() { const enableTyping = this.getStoreData(StoreName.APP, 'enableTyping'); if (enableTyping) { this.sendTyping(false); } } private sendTyping(status: boolean) { const chatEngine = this.getEngine(); const currentConversationID = this.getStoreData(StoreName.CONV, 'currentConversationID'); if (!currentConversationID.startsWith(chatEngine.TYPES.CONV_C2C)) return; const to = currentConversationID.replace(chatEngine.TYPES.CONV_C2C, ''); if (status) { const messageList: IMessageModel[] = this.getStoreData(StoreName.CHAT, 'messageList'); const receivedMessageList = messageList.filter((item: IMessageModel) => item.flow === 'in'); if (receivedMessageList.length === 0) return; const latestMessageTime = receivedMessageList[receivedMessageList.length - 1].time * 1000; const nowTime = new Date().getTime(); const diff = nowTime - latestMessageTime; if (diff > 1000 * 30) return; } } quoteMessage(message: Message) { this.getEngine().TUIStore.update(StoreName.CHAT, 'quoteMessage', { message, type: 'quote', }); this.getEngine().TUIReport?.reportFeature(205); return message; } replyMessage(message: Message) { this.getEngine().TUIStore.update(StoreName.CHAT, 'quoteMessage', { message, type: 'reply', }); return message; } private getCurrentConvInfo() { const { conversationID = '', type } = this.getStoreData(StoreName.CONV, 'currentConversation') || {}; const to = conversationID.replace(type, ''); return { to, conversationType: type, }; } private getMessageAbstractAndType(message: Message) { const chatEngine = this.getEngine(); const data = { abstract: '', type: 0, }; // 对齐 native, abstract 不传 // 兼容低版本,已传入的 abstract 不进行调整,新增的不传 abstract switch (message.type) { case chatEngine.TYPES.MSG_TEXT: data.abstract = message?.payload?.text; data.type = 1; break; case chatEngine.TYPES.MSG_CUSTOM: data.abstract = '[自定义消息]'; data.type = 2; break; case chatEngine.TYPES.MSG_IMAGE: data.abstract = '[图片]'; data.type = 3; break; case chatEngine.TYPES.MSG_AUDIO: data.abstract = '[语音]'; data.type = 4; break; case chatEngine.TYPES.MSG_VIDEO: data.abstract = '[视频]'; data.type = 5; break; case chatEngine.TYPES.MSG_FILE: data.abstract = '[文件]'; data.type = 6; break; case chatEngine.TYPES.MSG_LOCATION: data.type = 7; break; case chatEngine.TYPES.MSG_FACE: data.abstract = '[表情]'; data.type = 8; break; case chatEngine.TYPES.MSG_GRP_TIP: data.type = 9; break; case chatEngine.TYPES.MSG_MERGER: data.abstract = message?.payload?.title; data.type = 10; break; default: break; } return data; } /** * - 注意: 暂只支持文本发送时携带引用消息,结构体参考 https://iwiki.woa.com/pages/viewpage.action?pageId=1238962637 */ private genMessageReply(message: Message, type: string) { if (type !== 'reply' && type !== 'quote') { return {}; } const { abstract, type: messageType } = this.getMessageAbstractAndType(message); const messageReplyRoot: any = { messageAbstract: abstract, messageSender: message.nick || message.from, messageID: message.ID, }; const messageReply: any = { ...messageReplyRoot, messageType, messageTime: message?.time, messageSequence: message?.sequence, version: 1, }; if (type === 'reply') { messageReply.messageRootID = message.ID; if (message.cloudCustomData) { const rootCloudCustomData = JSONToObject(message.cloudCustomData); if (rootCloudCustomData.messageReply && rootCloudCustomData.messageReply.messageRootID) { messageReply.messageRootID = rootCloudCustomData.messageReply.messageRootID; } } } return { messageReply, messageReplyRoot, }; } private getMessageInfo(options: SendMessageParams, message: Message, type: string) { const { messageReply, messageReplyRoot } = this.genMessageReply(message, type); const cloudCustomData = options.cloudCustomData ? JSONToObject(options.cloudCustomData) : {}; let rootMessage: any; if (cloudCustomData.messageReply) { cloudCustomData.messageReply = { ...messageReply, ...cloudCustomData.messageReply }; } else { cloudCustomData.messageReply = messageReply; } if (type === 'reply') { const { messageRootID } = messageReply; rootMessage = this.getEngine().chat.findMessage(messageRootID); const rootCloudCustomData = rootMessage?.cloudCustomData ? JSONToObject(rootMessage.cloudCustomData) : {}; if (!rootCloudCustomData.messageReplies) { rootCloudCustomData.messageReplies = {}; } if (!isArray(rootCloudCustomData.messageReplies.replies)) { rootCloudCustomData.messageReplies.replies = []; } rootCloudCustomData.messageReplies.replies.push(messageReplyRoot); rootMessage.cloudCustomData = JSON.stringify(rootCloudCustomData); } return { cloudCustomData: JSON.stringify(cloudCustomData), rootMessage, }; } sendTextMessage(options: SendMessageParams, sendMessageOptions?: SendMessageOptions) { const chatEngine = this.getEngine(); const { message: quoteMessage, type } = this.getStoreData(StoreName.CHAT, 'quoteMessage'); let quoteInfo = { cloudCustomData: options.cloudCustomData || '', rootMessage: undefined, }; if (quoteMessage) { quoteInfo = this.getMessageInfo(options, quoteMessage, type); } const message = chatEngine.chat.createTextMessage({ ...this.getCurrentConvInfo(), ...options, cloudCustomData: quoteInfo.cloudCustomData, }); return this.sendMessage(message, sendMessageOptions).then((imResponse: any) => { if (quoteInfo.rootMessage) { this.modifyMessage(quoteInfo.rootMessage as any); } chatEngine.TUIStore.reset(StoreName.CHAT, ['quoteMessage'], true); return imResponse; }); } sendTextAtMessage(options: SendMessageParams, sendMessageOptions?: SendMessageOptions) { const chatEngine = this.getEngine(); const { message: quoteMessage, type } = this.getStoreData(StoreName.CHAT, 'quoteMessage'); let quoteInfo = { cloudCustomData: options.cloudCustomData || '', rootMessage: undefined, }; if (quoteMessage) { quoteInfo = this.getMessageInfo(options, quoteMessage, type); } const message = chatEngine.chat.createTextAtMessage({ ...this.getCurrentConvInfo(), ...options, cloudCustomData: quoteInfo.cloudCustomData, }); return this.sendMessage(message, sendMessageOptions).then((imResponse: any) => { if (quoteInfo.rootMessage) { this.modifyMessage(quoteInfo.rootMessage as any); } chatEngine.TUIStore.reset(StoreName.CHAT, ['quoteMessage'], true); return imResponse; }); } sendImageMessage(options: SendMessageParams, sendMessageOptions?: SendMessageOptions) { const message = this.getEngine().chat.createImageMessage({ ...this.getCurrentConvInfo(), ...options, onProgress: (progress: number) => { this.onProgress(message.ID, progress); }, }); return this.sendMessage(message, sendMessageOptions); } sendAudioMessage(options: SendMessageParams, sendMessageOptions?: SendMessageOptions) { const message = this.getEngine().chat.createAudioMessage({ ...this.getCurrentConvInfo(), ...options, onProgress: (progress: number) => { this.onProgress(message.ID, progress); }, }); return this.sendMessage(message, sendMessageOptions); } sendVideoMessage(options: SendMessageParams, sendMessageOptions?: SendMessageOptions) { const message = this.getEngine().chat.createVideoMessage({ ...this.getCurrentConvInfo(), ...options, onProgress: (progress: number) => { this.onProgress(message.ID, progress); }, }); return this.sendMessage(message, sendMessageOptions); } sendCustomMessage(options: SendMessageParams, sendMessageOptions?: SendMessageOptions) { const message = this.getEngine().chat.createCustomMessage({ ...this.getCurrentConvInfo(), ...options, }); return this.sendMessage(message, sendMessageOptions); } private onProgress(messageID: string, progress: number) { const message: IMessageModel = this.getEngine().TUIStore.getMessageModel(messageID); if (message) { // 避免频繁触发 messageList 更新 const diff = progress - message.progress; if (diff >= 0.1 || progress === 1) { message.progress = progress; this.updateMessageList([message], 'edit'); } } } resendMessage(message: Message) { message.status = 'unSend'; this.updateMessageList([message], 'resend'); const promise = this.getEngine().chat.resendMessage(message); return this.getResponse(promise, true, true); } setMessageExtensions(message: Message, extensions: object[]) { return this.getEngine().chat.setMessageExtensions(message, extensions); } getMessageExtensions(message: Message) { return this.getEngine().chat.getMessageExtensions(message); } deleteMessageExtensions(message: Message, keyList?: string[]) { return this.getEngine().chat.deleteMessageExtensions(message, keyList); } modifyMessage(message: Message) { const promise = this.getEngine().chat.modifyMessage(message); return this.getResponse(promise, true, false).catch((error: any) => { const { code = 0, data = {} } = error.code; if (code === ERROR_CODE.MSG_MODIFY_CONFLICT) { console.warn(`${ERROR_MSG.MSG_MODIFY_CONFLICT} data.message: ${data?.message}`); } else if (code === ERROR_CODE.MSG_MODIFY_DISABLED_IN_AVCHATROOM) { console.warn(ERROR_MSG.MSG_MODIFY_DISABLED_IN_AVCHATROOM); } else if (code === ERROR_CODE.MODIFY_MESSAGE_NOT_EXIST) { console.warn(ERROR_MSG.MODIFY_MESSAGE_NOT_EXIST); } throw error; }); } getMessageList(options: Partial = { conversationID: this.getStoreData(StoreName.CONV, 'currentConversationID'), nextReqMessageID: this.getStoreData(StoreName.CHAT, 'nextReqMessageID'), }) { const chatEngine = this.getEngine(); if (!chatEngine.chat.isReady()) { return Promise.reject({ code: ERROR_CODE_ENGINE.GET_MSG_LIST_ERROR, message: ERROR_MSG_ENGINE.GET_MSG_LIST_ERROR, }); } if (this.getStoreData(StoreName.CHAT, 'isCompleted')) { return Promise.resolve({ data: { messageList: [], nextReqMessageID: '', isCompleted: true, }, }); } const messageSource = this.getStoreData(StoreName.CHAT, 'messageSource'); const nextMessageSeq = this.hoppingConfigMap.get('nextMessageSeq'); const nextMessageTime = this.hoppingConfigMap.get('nextMessageTime'); const isHopping = nextMessageSeq || nextMessageTime; if (messageSource && messageSource.conversationID === options?.conversationID && isHopping) { return this.getMessageListHopping(); } return chatEngine.chat.getMessageList(options as GetMessageListParams).then((imResponse: any) => { const { messageList, nextReqMessageID, isCompleted } = imResponse.data; const userInfo = this.getStoreData(StoreName.CHAT, 'userInfo'); if (Object.keys(userInfo).length > 0) { // When Chat is integrated into Room, it needs to be updated. this.updateLocalMessage(messageList, userInfo); } this.updateMessageList(messageList, 'unshift'); chatEngine.TUIStore.update(StoreName.CHAT, 'nextReqMessageID', nextReqMessageID); chatEngine.TUIStore.update(StoreName.CHAT, 'isCompleted', isCompleted); // 异步获取消息回应、群已读回执信息(operationType > 0 说明用户已经不在群组内) // const conversationID = messageList[0]?.conversationID; // const { operationType = 0 } = this.getEngine().TUIStore.getConversationModel(conversationID) || {}; // if (operationType === 0) { // this.getMessageReactions({ messageList }); // this.readReceiptHandler.getMessageReadReceiptList(messageList); // } return imResponse; }).catch((error: any) => Promise.reject(error)); } getMessageListHopping(options: GetMessageListHoppingParams = { conversationID: this.getStoreData(StoreName.CHAT, 'messageSource')?.conversationID, sequence: this.hoppingConfigMap.get('nextMessageSeq'), time: this.hoppingConfigMap.get('nextMessageTime'), }) { const chatEngine = this.getEngine(); const promise = chatEngine.chat.getMessageListHopping(options); return promise.then((imResponse: any) => { const { messageList, nextMessageSeq, nextMessageTime, isCompleted } = imResponse.data; const _nextMessageSeq = options.direction === 1 ? options.sequence : nextMessageSeq; const _nextMessageTime = options.direction === 1 ? options.time : nextMessageTime; this.updateMessageList(messageList, 'unshift'); this.delayGetHoppingFunction = undefined; this.hoppingConfigMap.set('nextMessageSeq', _nextMessageSeq); this.hoppingConfigMap.set('nextMessageTime', _nextMessageTime); chatEngine.TUIStore.update(StoreName.CHAT, 'isCompleted', isCompleted); return imResponse; }).catch((error: any) => Promise.reject(error)); } clearHistoryMessage(conversationID: string) { const chatEngine = this.getEngine(); return chatEngine.chat.clearHistoryMessage(conversationID).then((imResponse: any) => { chatEngine.TUIStore.update(StoreName.CHAT, 'messageList', []); chatEngine.TUIStore.update(StoreName.CHAT, 'nextReqMessageID', ''); chatEngine.TUIStore.update(StoreName.CHAT, 'isCompleted', false); return imResponse; }); } private updateLocalMessage(messageList: any[], userInfo: Record) { let flag = false; messageList.forEach((message: any) => { if (userInfo[message.from]) { const { nick, nameCard, avatar } = userInfo[message.from]; if (nick) { message.nick = nick; flag = true; } if (nameCard) { message.nameCard = nameCard; flag = true; } if (avatar) { message.avatar = avatar; flag = true; } } }); return flag; } private handleC2CCallSignaling(messageList: Message[]) { const chatEngine = this.getEngine(); const myUserID = chatEngine.getMyUserID(); const list = messageList.filter((message: Message) => { const { conversationType, type, payload } = message; let isDisplayInChat = true; // handle C2C and custom message if (conversationType === chatEngine.TYPES.CONV_C2C && type === chatEngine.TYPES.MSG_CUSTOM) { const signalingInfo = this.getSignalingInfo(message); if (signalingInfo) { const callSignaling: any = JSONToObject(payload.data); // callSignaling(businessID = 1) if (callSignaling?.businessID === 1) { const customData = JSONToObject(callSignaling.data); isDisplayInChat = !((message as any)._isExcludedFromUnreadCount && (message as any)._isExcludedFromLastMessage); if (isDisplayInChat) { // handle call signaling when it is not consumed if (customData?.data?.consumed !== true) { let inviter = customData?.data?.inviter; if (customData?.line_busy === 'line_busy' || customData?.data?.message === 'lineBusy') { inviter = callSignaling.inviter; } const { from, to } = message; // reverse message: currentUser is not inviter if (inviter !== myUserID && message.from === myUserID) { const currentConversation = this.getStoreData(StoreName.CONV, 'currentConversation'); message.from = to; message.to = from; message.flow = 'in'; message.avatar = currentConversation?.userProfile?.avatar || ''; } // reverse message: currentUser is inviter if (inviter === myUserID && message.from !== myUserID) { const myProfile = this.getStoreData(StoreName.USER, 'userProfile'); message.from = to; message.to = from; message.flow = 'out'; message.avatar = myProfile?.avatar; } console.log(`${this.serv}.handleC2CCallSignaling myUserID:${myUserID} callSignaling.inviter:${callSignaling.inviter} customData.data.inviter:${customData?.data?.inviter}`); } } } } } return isDisplayInChat; }); return list; } private getSignalingInfo(message: Message) { const signalingList = this.filterSignalFromMessageList([message]); if (signalingList.length === 0) { return; } const signalingData = this.getPayloadData(message); if (!signalingData) { return; } const signaling = { businessID: signalingData.businessID || 1, inviteID: signalingData.inviteID, groupID: signalingData.groupID || '', inviter: signalingData.inviter || '', inviteeList: signalingData.inviteeList || [], data: signalingData.data || '', actionType: signalingData.actionType || 1, timeout: signalingData.timeout || 0, }; return signaling; } private filterSignalFromMessageList(messageList: Message[]) { return messageList.filter((message) => { const chatEngine = this.getEngine(); if (message.type === chatEngine.TYPES.MSG_CUSTOM) { const { cloudCustomData = '', payload = {} } = message; const data = payload?.data || ''; if (!cloudCustomData && !data) { return false; } const isSignalingTypeExisted = cloudCustomData && cloudCustomData.match(/"type":"tsignaling"/); const isInviteIDExisted = data && data.match(/inviteID/); const isActionTypeExisted = data && data.match(/actionType/); return isSignalingTypeExisted || (isInviteIDExisted && isActionTypeExisted); } return false; }); } private getPayloadData(message: Message) { const { data } = message.payload; try { return JSON.parse(data); } catch (error) { console.error(error); return null; } } private sortMessageList(messageList: Message[] | IMessageModel[]) { const { conversationType } = messageList[0]; // C2C sort by time if (conversationType === this.getEngine().TYPES.CONV_C2C) { return messageList.sort((a, b) => a.time - b.time); } // GROUP sort by sequence const sortedMessageList = messageList.filter(item => item.status === 'success').sort((a, b) => a.sequence - b.sequence); // Insert not success message into sortedList for (let i = 0; i < messageList.length; i++) { if (messageList[i].status !== 'success') { sortedMessageList.splice(i, 0, messageList[i]); } } return sortedMessageList; } }