更新
This commit is contained in:
+241
@@ -0,0 +1,241 @@
|
||||
import type { Message } from '@tencentcloud/lite-chat/basic';
|
||||
import type { IMessageHandler, ITUIChatService } from '../interface/service';
|
||||
import { JSONToObject, formatTime } from '../utils/common-utils';
|
||||
|
||||
export default class MessageHandler implements IMessageHandler {
|
||||
private TUIChatService: ITUIChatService;
|
||||
private userShowNameMap: Map<string, string>; // Map<userID, showName>
|
||||
private requestedUserMap: Map<string, number>; // Map<userID, 1>
|
||||
constructor(TUIChatService: ITUIChatService) {
|
||||
this.TUIChatService = TUIChatService;
|
||||
this.userShowNameMap = new Map();
|
||||
this.requestedUserMap = new Map();
|
||||
}
|
||||
|
||||
private getEngine() {
|
||||
return this.TUIChatService.getEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化翻译
|
||||
* @param {string} text 待翻译的文本
|
||||
* @returns {string}
|
||||
*/
|
||||
private t(text: string) {
|
||||
return text;
|
||||
}
|
||||
|
||||
handleTextMessage(message: Message) {
|
||||
const ret: any = {
|
||||
text: message.payload.text,
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
handleImageMessage(message: Message) {
|
||||
return {
|
||||
url: message.payload.imageInfoArray[0].url,
|
||||
width: message.payload.imageInfoArray[0].width,
|
||||
height: message.payload.imageInfoArray[0].height,
|
||||
};
|
||||
}
|
||||
|
||||
handleVideoMessage(message: Message) {
|
||||
return {
|
||||
url: message.payload.videoUrl,
|
||||
snapshotUrl: message.payload.snapshotUrl,
|
||||
snapshotWidth: message.payload.snapshotWidth,
|
||||
snapshotHeight: message.payload.snapshotHeight,
|
||||
};
|
||||
}
|
||||
|
||||
handleCustomMessage(message: Message) {
|
||||
const createGroupText = this.handleCreateGroupCustomMessage(message);
|
||||
return {
|
||||
custom: this.handleCallKitSignaling(message) || createGroupText || message?.payload?.extension || `[自定义消息]`,
|
||||
businessID: createGroupText ? 'group_create' : '',
|
||||
};
|
||||
}
|
||||
|
||||
handleGroupTipsMessage(message: Message) {
|
||||
const chatEngine = this.getEngine();
|
||||
const ret: any = {
|
||||
text: '',
|
||||
};
|
||||
|
||||
// 优先取 nick,nick 不存在时取 userID,以下逻辑是为兼容不同群提示消息的处理
|
||||
let showName: string = message?.nick || message?.payload?.userIDList?.join(',');
|
||||
if (message?.payload?.memberList?.length > 0) {
|
||||
showName = '';
|
||||
message?.payload?.memberList?.map((user: any) => {
|
||||
const _showName = user?.nick || user?.userID;
|
||||
showName += `${this.substringByLength(_showName)},`;
|
||||
return user;
|
||||
});
|
||||
showName = showName?.slice(0, -1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
handleGroupSystemMessage(message: Message) {
|
||||
const groupName = message.payload.groupProfile.name || message.payload.groupProfile.groupID;
|
||||
const ret = {
|
||||
text: '',
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
handleCallKitSignaling(message: any) {
|
||||
const callMessage: any = JSONToObject(message.payload.data);
|
||||
if (callMessage?.businessID !== 1) {
|
||||
return '';
|
||||
}
|
||||
const objectData = JSONToObject(callMessage?.data);
|
||||
const userID = message.fromAccount || message.from; // 取 message.fromAccount 兼容会话 lastMsg
|
||||
const myUserID = this.getEngine().getMyUserID();
|
||||
let messageSender = message.nameCard || message.nick || userID;
|
||||
messageSender = this.substringByLength(messageSender);
|
||||
switch (callMessage?.actionType) {
|
||||
case 1: {
|
||||
if (objectData?.data?.cmd === 'audioCall' || objectData?.data?.cmd === 'videoCall') {
|
||||
if (callMessage?.groupID) {
|
||||
return `${messageSender} ${this.t('message.custom.发起通话')}`;
|
||||
}
|
||||
}
|
||||
if (objectData?.data?.cmd === 'hangup') {
|
||||
if (callMessage?.groupID) {
|
||||
return `${this.t('message.custom.通话结束')}`;
|
||||
}
|
||||
return `${this.t('message.custom.通话时长')}:${formatTime(objectData?.call_end)}`;
|
||||
}
|
||||
if (objectData?.data?.cmd === 'switchToAudio') {
|
||||
return `${this.t('message.custom.切换语音通话')}`;
|
||||
}
|
||||
if (objectData?.data?.cmd === 'switchToVideo') {
|
||||
return `${this.t('message.custom.切换视频通话')}`;
|
||||
}
|
||||
// CDM 异常时【发起通话】作为默认返回值
|
||||
return `${this.t('message.custom.发起通话')}`;
|
||||
}
|
||||
case 2:
|
||||
if (callMessage?.groupID) {
|
||||
return `${messageSender} ${this.t('message.custom.取消通话')}`;
|
||||
}
|
||||
if (this.isOldUIKit('message.custom.已取消')) {
|
||||
return this.t('message.custom.取消通话');
|
||||
}
|
||||
if (callMessage?.inviter === myUserID) {
|
||||
return this.t('message.custom.已取消');
|
||||
}
|
||||
return this.t('message.custom.对方已取消');
|
||||
case 3:
|
||||
if (objectData?.data?.cmd === 'switchToAudio') {
|
||||
return `${this.t('message.custom.切换语音通话')}`;
|
||||
}
|
||||
if (objectData?.data?.cmd === 'switchToVideo') {
|
||||
return `${this.t('message.custom.切换视频通话')}`;
|
||||
}
|
||||
if (callMessage?.groupID) {
|
||||
return `${messageSender} ${this.t('message.custom.已接听')}`;
|
||||
}
|
||||
return this.t('message.custom.已接听');
|
||||
case 4:
|
||||
if (callMessage?.groupID) {
|
||||
return `${messageSender} ${this.t('message.custom.拒绝通话')}`;
|
||||
}
|
||||
if (this.isOldUIKit('message.custom.已拒绝')) {
|
||||
return this.t('message.custom.拒绝通话');
|
||||
}
|
||||
if (objectData?.line_busy === 'line_busy' || objectData?.data.message === 'lineBusy') {
|
||||
if (callMessage?.inviter === myUserID) {
|
||||
return this.t('message.custom.对方忙线中');
|
||||
}
|
||||
return this.t('message.custom.忙线未接听');
|
||||
}
|
||||
if (callMessage?.inviter === myUserID) {
|
||||
return this.t('message.custom.对方已拒绝');
|
||||
}
|
||||
return this.t('message.custom.已拒绝');
|
||||
case 5:
|
||||
if (objectData?.data?.cmd === 'switchToAudio') {
|
||||
return `${this.t('message.custom.切换语音通话')}`;
|
||||
}
|
||||
if (objectData?.data?.cmd === 'switchToVideo') {
|
||||
return `${this.t('message.custom.切换视频通话')}`;
|
||||
}
|
||||
if (callMessage?.groupID) {
|
||||
// 群通话主叫超时
|
||||
if (userID === callMessage?.inviter) {
|
||||
this.handleCallkitTimeoutSignaling(callMessage.inviteeList);
|
||||
let inviteeList = '';
|
||||
callMessage.inviteeList?.forEach((inviteeUserID: string) => {
|
||||
const showName = this.userShowNameMap.get(inviteeUserID) || inviteeUserID;
|
||||
inviteeList += `${this.substringByLength(showName)}、`;
|
||||
});
|
||||
inviteeList = inviteeList.substring(0, inviteeList.lastIndexOf('、'));
|
||||
return `${inviteeList} ${this.t('message.custom.无应答')}`;
|
||||
}
|
||||
// 群通话被叫超时
|
||||
return `${messageSender} ${this.t('message.custom.无应答')}`;
|
||||
}
|
||||
if (this.isOldUIKit('message.custom.对方无应答')) {
|
||||
return this.t('message.custom.无应答');
|
||||
}
|
||||
if (callMessage?.inviter === myUserID) {
|
||||
return this.t('message.custom.对方无应答');
|
||||
}
|
||||
return this.t('message.custom.超时无应答');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private handleCreateGroupCustomMessage(message: Message) {
|
||||
let text: undefined | string;
|
||||
const data = JSONToObject(message.payload.data);
|
||||
if (data?.businessID === 'group_create') {
|
||||
text = `${data.opUser} ${data.content}`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private handleCallkitTimeoutSignaling(inviteeList: string[] = []) {
|
||||
if (inviteeList.length === 0) {
|
||||
return;
|
||||
}
|
||||
const userIDList: string[] = [];
|
||||
inviteeList.forEach((userID: string) => {
|
||||
const showName = false;
|
||||
if (showName) {
|
||||
this.userShowNameMap.set(userID, showName);
|
||||
} else if (!this.requestedUserMap.has(userID)) {
|
||||
userIDList.push(userID);
|
||||
this.requestedUserMap.set(userID, 1);
|
||||
}
|
||||
});
|
||||
// 注意:按照一条主叫超时信令请求一次处理,先不用截流处理
|
||||
if (userIDList.length > 0) {
|
||||
this.getEngine().TUIUser.getUserProfile({ userIDList }).then((imResponse: any) => {
|
||||
const profileList = imResponse.data || [];
|
||||
profileList.forEach((profile: any) => {
|
||||
const { userID, nick } = profile;
|
||||
const showName = nick || userID;
|
||||
this.userShowNameMap.set(userID, showName);
|
||||
});
|
||||
}).catch(() => {
|
||||
// 请求出错时捕获异常,避免阻塞代码执行流程
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private substringByLength(str: string, len = 12) {
|
||||
return str.length > len ? `${str.slice(0, len)}...` : str;
|
||||
}
|
||||
|
||||
private isOldUIKit(target: string) {
|
||||
const index = target.lastIndexOf('.');
|
||||
const flag = target.slice(0, index + 1);
|
||||
return this.t(target)?.startsWith(flag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,768 @@
|
||||
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<string, any>;
|
||||
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<any>} 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<any>} im 接口的 response 原样返回
|
||||
*/
|
||||
private getResponse(promise: Promise<any>, 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<GetMessageListParams> = {
|
||||
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<string, any>) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
import type { Conversation, Message } from '@tencentcloud/lite-chat/basic';
|
||||
import TUIBase from '../tui-base';
|
||||
import type { ITUIConversationService } from '../interface/service';
|
||||
import { StoreName, ERROR_CODE_ENGINE, ERROR_MSG_ENGINE } from '../const';
|
||||
import { isUndefined } from '../utils/common-utils';
|
||||
import ConversationModel from '../model/conversation';
|
||||
|
||||
export default class TUIConversationService extends TUIBase implements ITUIConversationService {
|
||||
static instance: TUIConversationService;
|
||||
private serv: string;
|
||||
constructor() {
|
||||
super();
|
||||
this.serv = 'TUIConversationService';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 TUIConversationService 实例
|
||||
*/
|
||||
static getInstance() {
|
||||
if (!TUIConversationService.instance) {
|
||||
TUIConversationService.instance = new TUIConversationService();
|
||||
}
|
||||
return TUIConversationService.instance;
|
||||
}
|
||||
|
||||
public init() {
|
||||
const chatEngine = this.getEngine();
|
||||
chatEngine.eventCenter.addEvent(chatEngine.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated.bind(this));
|
||||
chatEngine.eventCenter.addEvent(chatEngine.EVENT.TOTAL_UNREAD_MESSAGE_COUNT_UPDATED, this.onTotalUnreadCountUpdated.bind(this));
|
||||
chatEngine.eventCenter.addEvent(chatEngine.EVENT.MESSAGE_RECEIVED, this.onMessageReceived.bind(this));
|
||||
this.getConversationInitData();
|
||||
}
|
||||
|
||||
private onConversationListUpdated(conversationList: Conversation[]) {
|
||||
const tmpConversationList = this.filterSystemConversation(conversationList);
|
||||
this.getEngine().TUIStore.update(StoreName.CONV, 'conversationList', tmpConversationList);
|
||||
this.updateCurrentConversation();
|
||||
}
|
||||
|
||||
private onTotalUnreadCountUpdated(totalUnreadCount: number) {
|
||||
this.getEngine().TUIStore.update(StoreName.CONV, 'totalUnreadCount', totalUnreadCount);
|
||||
}
|
||||
|
||||
private onMessageReceived(messageList: Message[]) {
|
||||
const chatEngine = this.getEngine();
|
||||
const conversationList = this.getEngine().TUIStore.getData(StoreName.CONV, 'conversationList');
|
||||
let toBeUpdated = false;
|
||||
for (let i = 0; i < messageList.length; i++) {
|
||||
if (messageList[i].type !== chatEngine.TYPES.MSG_GRP_SYS_NOTICE) {
|
||||
continue;
|
||||
}
|
||||
const { operationType } = messageList[i].payload;
|
||||
const conversationID = `GROUP${messageList[i].to}`;
|
||||
const condition1 = (operationType === 4 || operationType === 5 || operationType === 8); // 群成员被踢(operationType=4)、群组被解散(operationType=5)、主动退群(operationType=8)
|
||||
const condition2 = (operationType === 2 || operationType === 6 || operationType === 7); // 主动加群(operationType=2)、创建群组(operationType=6)、被邀请进群(operationType=7)
|
||||
if (condition1 || condition2) {
|
||||
for (let j = 0; j < conversationList.length; j++) {
|
||||
// C2C 会话直接跳过,节省循环执行时间
|
||||
if (conversationList[j].type === chatEngine.TYPES.CONV_C2C) {
|
||||
continue;
|
||||
}
|
||||
if (conversationList[j].conversationID === conversationID) {
|
||||
if (condition1) {
|
||||
this.getEngine().TUIStore.update(StoreName.CONV, 'operationTypeMap', { conversationID, operationType });
|
||||
toBeUpdated = true;
|
||||
break;
|
||||
}
|
||||
// 当前登录生命周期内,主动退群、解散群组、被踢后,再次主动加群、创建群组、被邀请进群时需要重置 operationType 为 0
|
||||
if (condition2 && conversationList[j].operationType > 0) {
|
||||
this.getEngine().TUIStore.update(StoreName.CONV, 'operationTypeMap', { conversationID, operationType: 0 });
|
||||
toBeUpdated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (toBeUpdated) {
|
||||
this.getEngine().TUIStore.update(StoreName.CONV, 'conversationList', conversationList);
|
||||
const currentConversationID = this.getEngine().TUIStore.getData(StoreName.CONV, 'currentConversationID') || '';
|
||||
const currentConversation = this.findConversation(currentConversationID);
|
||||
if (currentConversation) {
|
||||
this.getEngine().TUIStore.update(StoreName.CONV, 'currentConversation', currentConversation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解决 TUIChatEngine 含 UI集成通过 TUILogin 登录时注册 Chat SDK 事件时机后置问题
|
||||
private getConversationInitData() {
|
||||
const chatEngine = this.getEngine();
|
||||
// TUIChatEngine 无 UI 集成时不需要执行此逻辑
|
||||
if (!chatEngine.chat.isReady()) {
|
||||
return;
|
||||
}
|
||||
chatEngine.chat.getConversationList().then((imResponse: any) => {
|
||||
const { conversationList, isSyncCompleted } = imResponse.data;
|
||||
console.log(`${this.serv}.init, getConversationList count:${conversationList.length} isSyncCompleted:${isSyncCompleted}`);
|
||||
if (conversationList.length > 0) {
|
||||
this.onConversationListUpdated(conversationList);
|
||||
const totalUnreadCount: number = chatEngine.chat.getTotalUnreadMessageCount();
|
||||
this.onTotalUnreadCountUpdated(totalUnreadCount);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async switchConversation(conversationID: string) {
|
||||
const logTxt = `${this.serv}.switchConversation`;
|
||||
const chatEngine = this.getEngine();
|
||||
if (!conversationID) {
|
||||
chatEngine.TUIStore.reset(StoreName.CHAT, ['messageList', 'isCompleted', 'nextReqMessageID']);
|
||||
chatEngine.TUIStore.update(StoreName.CONV, 'currentConversationID', '');
|
||||
chatEngine.TUIStore.update(StoreName.CONV, 'currentConversation', null);
|
||||
console.log(`${logTxt} conversationID is empty, conversationID:${conversationID}`);
|
||||
return Promise.resolve({});
|
||||
}
|
||||
if (!conversationID.startsWith(chatEngine.TYPES.CONV_C2C) && !conversationID.startsWith(chatEngine.TYPES.CONV_GROUP)) {
|
||||
console.warn(`${logTxt} conversationID is invalid, conversationID:${conversationID}`);
|
||||
return Promise.reject({
|
||||
code: ERROR_CODE_ENGINE.INVALID_CONV_ID,
|
||||
message: ERROR_MSG_ENGINE.INVALID_CONV_ID,
|
||||
});
|
||||
}
|
||||
const enableAutoMessageRead = chatEngine.TUIStore.getData(StoreName.APP, 'enableAutoMessageRead');
|
||||
const currentConversationID = chatEngine.TUIStore.getData(StoreName.CONV, 'currentConversationID');
|
||||
if (currentConversationID && currentConversationID === conversationID) {
|
||||
if (enableAutoMessageRead) {
|
||||
this.setMessageRead(currentConversationID);
|
||||
}
|
||||
console.warn(`${logTxt} please check conversationID, conversationID:${conversationID}`);
|
||||
return Promise.resolve({
|
||||
code: ERROR_CODE_ENGINE.CONV_ID_SAME,
|
||||
message: ERROR_MSG_ENGINE.CONV_ID_SAME,
|
||||
});
|
||||
}
|
||||
|
||||
// 需要切换的目标会话
|
||||
const targetConversation: any = await this.getConversationModel(conversationID);
|
||||
if (isUndefined(targetConversation)) {
|
||||
console.warn(`${logTxt} target conversation is not exist, conversationID:${conversationID}`);
|
||||
return Promise.reject({
|
||||
code: ERROR_CODE_ENGINE.CONV_NOT_EXIST,
|
||||
message: ERROR_MSG_ENGINE.CONV_NOT_EXIST,
|
||||
});
|
||||
}
|
||||
if (enableAutoMessageRead) {
|
||||
if (currentConversationID) {
|
||||
this.setMessageRead(currentConversationID);
|
||||
}
|
||||
if (conversationID) {
|
||||
this.setMessageRead(conversationID);
|
||||
}
|
||||
}
|
||||
chatEngine.TUIStore.reset(StoreName.CHAT, ['messageList', 'isCompleted', 'nextReqMessageID']);
|
||||
chatEngine.TUIStore.update(StoreName.CONV, 'currentConversationID', conversationID);
|
||||
chatEngine.TUIStore.update(StoreName.CONV, 'currentConversation', targetConversation);
|
||||
return Promise.resolve(targetConversation);
|
||||
}
|
||||
|
||||
private async getConversationModel(conversationID: string) {
|
||||
let conversationModel: any = this.findConversation(conversationID);
|
||||
if (isUndefined(conversationModel)) {
|
||||
try {
|
||||
const res: any = await this.getConversationProfile(conversationID);
|
||||
if (res.data && res.data.conversation) {
|
||||
conversationModel = new ConversationModel(res.data.conversation);
|
||||
}
|
||||
} catch (error) {
|
||||
conversationModel = undefined;
|
||||
}
|
||||
}
|
||||
return conversationModel;
|
||||
}
|
||||
|
||||
private findConversation(conversationID: string) {
|
||||
let conversation: any;
|
||||
const conversationList = this.getEngine().TUIStore.getData(StoreName.CONV, 'conversationList');
|
||||
for (let i = 0; i < conversationList.length; i++) {
|
||||
if (conversationList[i].conversationID === conversationID) {
|
||||
conversation = conversationList[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return conversation;
|
||||
}
|
||||
|
||||
private updateCurrentConversation() {
|
||||
const chatEngine = this.getEngine();
|
||||
const currentConversationID: string = chatEngine.TUIStore.getData(StoreName.CONV, 'currentConversationID');
|
||||
const currentConversation = this.findConversation(currentConversationID);
|
||||
// 更新当前会话
|
||||
if (currentConversation) {
|
||||
chatEngine.TUIStore.update(StoreName.CONV, 'currentConversation', currentConversation);
|
||||
}
|
||||
}
|
||||
|
||||
public getConversationList() {
|
||||
return this.getEngine().chat.getConversationList();
|
||||
}
|
||||
|
||||
public getConversationProfile(conversationID: string) {
|
||||
return this.getEngine().chat.getConversationProfile(conversationID);
|
||||
}
|
||||
|
||||
public clearHistoryMessage(conversationID: string) {
|
||||
return this.getEngine().chat.clearHistoryMessage(conversationID).then((imResponse: any) => {
|
||||
this.getEngine().TUIStore.update(StoreName.CHAT, 'messageList', []);
|
||||
this.getEngine().TUIStore.update(StoreName.CHAT, 'nextReqMessageID', '');
|
||||
this.getEngine().TUIStore.update(StoreName.CHAT, 'isCompleted', true);
|
||||
return imResponse;
|
||||
});
|
||||
}
|
||||
|
||||
public setMessageRead(conversationID: string) {
|
||||
return this.getEngine().chat.setMessageRead({ conversationID });
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤系统会话
|
||||
* @param {Array<any>} list SDK 更新下来的会话列表数据
|
||||
*/
|
||||
private filterSystemConversation(list: any[]) {
|
||||
const toBeUpdatedList = list.filter(conversation => conversation.type !== this.getEngine().TYPES.CONV_SYSTEM);
|
||||
return toBeUpdatedList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
import type { ChatSDK } from '@tencentcloud/lite-chat/basic';
|
||||
import TencentCloudChat from '@tencentcloud/lite-chat/basic';
|
||||
import EventCenter from './event-center';
|
||||
import type { ITUIChatEngine, IEventCenter } from '../interface/engine';
|
||||
import type {
|
||||
ITUIConversationService,
|
||||
ITUIChatService,
|
||||
ITUIGroupService,
|
||||
ITUIUserService,
|
||||
ITUIReportService,
|
||||
} from '../interface/service';
|
||||
import type { ITUIStore } from '../interface/store';
|
||||
import type { LoginParams } from '../type';
|
||||
import type { MountedList } from '../const';
|
||||
import { StoreName } from '../const';
|
||||
import { isUndefined } from '../utils/common-utils';
|
||||
import { commercialAbility } from '../config';
|
||||
import TUIGlobal from '../TUIGlobal/tui-global';
|
||||
|
||||
export default class ChatEngine implements ITUIChatEngine {
|
||||
static instance: ChatEngine;
|
||||
public isInited: boolean; // 是否执行了初始化
|
||||
public chat!: ChatSDK;
|
||||
public EVENT: any;
|
||||
public TYPES: any;
|
||||
public eventCenter!: IEventCenter;
|
||||
public TUIStore!: ITUIStore;
|
||||
public TUIConversation!: ITUIConversationService;
|
||||
public TUIChat!: ITUIChatService;
|
||||
public TUIGroup!: ITUIGroupService;
|
||||
public TUIUser!: ITUIUserService;
|
||||
public TUIReport!: ITUIReportService;
|
||||
private loginStatusPromise: Map<string, unknown>; // 保存 login Promise
|
||||
private userID: string; // 当前用户 ID
|
||||
|
||||
constructor() {
|
||||
this.EVENT = TencentCloudChat.EVENT;
|
||||
this.TYPES = TencentCloudChat.TYPES;
|
||||
this.loginStatusPromise = new Map();
|
||||
this.userID = '';
|
||||
this.isInited = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ChatEngine 实例
|
||||
*/
|
||||
static getInstance() {
|
||||
if (!ChatEngine.instance) {
|
||||
ChatEngine.instance = new ChatEngine();
|
||||
}
|
||||
return ChatEngine.instance;
|
||||
}
|
||||
|
||||
mount(name: MountedList, instance: any) {
|
||||
this[name] = instance;
|
||||
}
|
||||
|
||||
login(options: LoginParams) {
|
||||
const { chat, SDKAppID, userID } = options;
|
||||
const isOfficial = SDKAppID === 1400187352 || SDKAppID === 1400188366;
|
||||
this.createChat(options);
|
||||
this.userID = userID;
|
||||
TUIGlobal.getInstance().initOfficial(isOfficial); // 兼容低版本 uikit
|
||||
this.TUIStore.update(StoreName.APP, 'isOfficial', isOfficial);
|
||||
this.TUIStore.update(StoreName.APP, 'SDKVersion', (TencentCloudChat as any).VERSION);
|
||||
this.eventCenter = new EventCenter(this);
|
||||
this.eventCenter.removeEvents(); // 移除注册的事件监听,避免重复注册
|
||||
this.resetStore();
|
||||
this.initService();
|
||||
if (chat && chat.isReady()) {
|
||||
console.log('TUIChatEngine.login ok, from TUICore.');
|
||||
this.TUIUser.getUserProfile();
|
||||
this.checkCommercialAbility();
|
||||
return Promise.resolve({});
|
||||
}
|
||||
this.eventCenter.addEvent(this.EVENT.SDK_READY, () => {
|
||||
this.onSDKReady();
|
||||
});
|
||||
this.eventCenter.addEvent(this.EVENT.SDK_NOT_READY, () => {
|
||||
this.onSDKNotReady();
|
||||
});
|
||||
return this.loginChat(options);
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.userID = '';
|
||||
this.isInited = false;
|
||||
this.resetStore();
|
||||
return this.chat.logout();
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this.chat?.isReady() || false;
|
||||
}
|
||||
|
||||
setLogLevel(level: number) {
|
||||
if (!this.chat) {
|
||||
console.warn('TUIChatEngine 初始化未完成,请确认 TUIChatEngine.login 接口调用是否正常。');
|
||||
return;
|
||||
}
|
||||
this.chat.setLogLevel(level);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// 解绑 IM 事件
|
||||
this.eventCenter.unbindIMEvents();
|
||||
this.isInited = false;
|
||||
this.resetStore();
|
||||
return this.chat.destroy();
|
||||
}
|
||||
|
||||
getMyUserID() {
|
||||
return this.userID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置 Store
|
||||
*/
|
||||
private resetStore() {
|
||||
this.TUIStore.reset(StoreName.CHAT);
|
||||
this.TUIStore.reset(StoreName.CONV);
|
||||
this.TUIStore.reset(StoreName.GRP);
|
||||
this.TUIStore.reset(StoreName.USER);
|
||||
this.TUIStore.reset(StoreName.SEARCH);
|
||||
this.TUIStore.reset(StoreName.FRIEND);
|
||||
this.TUIStore.reset(StoreName.CUSTOM);
|
||||
console.log('TUIChatEngine.resetStore ok.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 Service
|
||||
*/
|
||||
private initService() {
|
||||
this.TUIChat.init();
|
||||
this.TUIConversation.init();
|
||||
this.TUIUser.init();
|
||||
this.isInited = true;
|
||||
console.log('TUIChatEngine.initService ok.');
|
||||
}
|
||||
|
||||
private createChat(options: LoginParams) {
|
||||
const { chat, ...rest } = options;
|
||||
if (!isUndefined(chat)) {
|
||||
this.chat = chat;
|
||||
return;
|
||||
}
|
||||
this.chat = TencentCloudChat.create({
|
||||
...rest,
|
||||
scene: 'engine-lite',
|
||||
});
|
||||
}
|
||||
|
||||
private loginChat(options: LoginParams) {
|
||||
const { userID, userSig } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.chat.login({ userID, userSig }).then((imResponse: any) => {
|
||||
console.log('TUIChatEngine.loginChat ok.');
|
||||
this.checkCommercialAbility();
|
||||
if (imResponse.data.repeatLogin && this.chat.isReady()) {
|
||||
resolve(imResponse);
|
||||
}
|
||||
this.loginStatusPromise.set('login', { resolve, reject, imResponse });
|
||||
}).catch((error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private onSDKReady() {
|
||||
if (this.loginStatusPromise.has('login')) {
|
||||
const handler: any = this.loginStatusPromise.get('login');
|
||||
handler.resolve(handler.imResponse);
|
||||
// 独立使用 engine 登录成功 SDK Ready 后获取一次自己的资料
|
||||
this.TUIUser.getUserProfile();
|
||||
}
|
||||
this.loginStatusPromise.delete('login');
|
||||
}
|
||||
|
||||
private onSDKNotReady() {
|
||||
if (this.loginStatusPromise.has('login')) {
|
||||
const handler: any = this.loginStatusPromise.get('login');
|
||||
handler.reject(new Error('sdk not ready'));
|
||||
}
|
||||
this.loginStatusPromise.delete('login');
|
||||
this.resetStore();
|
||||
}
|
||||
|
||||
// 登录成功后校验商业化能力位
|
||||
private checkCommercialAbility() {
|
||||
Object.keys(commercialAbility).forEach((key: string) => {
|
||||
const ability: number = commercialAbility[key];
|
||||
this.chat.callExperimentalAPI('isCommercialAbilityEnabled', ability).then((imResponse: any) => {
|
||||
const { enabled = false } = imResponse.data;
|
||||
this.TUIStore.update(StoreName.APP, key, enabled);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { IEventCenter, ITUIChatEngine } from '../interface/engine';
|
||||
import type { func } from '../type';
|
||||
|
||||
export default class EventCenter implements IEventCenter {
|
||||
static instance: EventCenter | null;
|
||||
private engine!: ITUIChatEngine;
|
||||
private events: any;
|
||||
|
||||
constructor(ChatEngine: any) {
|
||||
if (!EventCenter.instance) {
|
||||
EventCenter.instance = this;
|
||||
this.engine = ChatEngine;
|
||||
this.events = {};
|
||||
this.bindIMEvents();
|
||||
}
|
||||
return EventCenter.instance;
|
||||
}
|
||||
|
||||
public addEvent(event: string, callback: func) {
|
||||
if (this.events[event]) {
|
||||
this.events[event].set(callback, 1);
|
||||
} else {
|
||||
this.events[event] = new Map();
|
||||
this.events[event].set(callback, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public removeEvents() {
|
||||
Object.keys(this.events).forEach((event) => {
|
||||
this.events[event].clear();
|
||||
});
|
||||
this.events = {};
|
||||
}
|
||||
|
||||
private dispatch(event: string, data: any) {
|
||||
if (!this.events[event]) {
|
||||
return;
|
||||
}
|
||||
for (const callback of this.events[event].keys()) {
|
||||
callback.call(this, data);
|
||||
}
|
||||
}
|
||||
|
||||
private bindIMEvents() {
|
||||
this.engine.chat.on(this.engine.EVENT.SDK_READY, this.onSDKReady, this);
|
||||
this.engine.chat.on(this.engine.EVENT.SDK_NOT_READY, this.onSDKNotReady, this);
|
||||
this.engine.chat.on(this.engine.EVENT.KICKED_OUT, this.onKickedOut, this);
|
||||
this.engine.chat.on(this.engine.EVENT.MESSAGE_RECEIVED, this.onReceiveMessage, this);
|
||||
this.engine.chat.on(this.engine.EVENT.MESSAGE_REACTIONS_UPDATED, this.onMessageReactionsUpdated, this);
|
||||
this.engine.chat.on(this.engine.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated, this);
|
||||
this.engine.chat.on(this.engine.EVENT.TOTAL_UNREAD_MESSAGE_COUNT_UPDATED, this.onTotalMessageCountUpdated, this);
|
||||
this.engine.chat.on(this.engine.EVENT.PROFILE_UPDATED, this.onProfileUpdated, this);
|
||||
this.engine.chat.on(this.engine.EVENT.GROUP_LIST_UPDATED, this.onGroupListUpdated, this);
|
||||
this.engine.chat.on(this.engine.EVENT.GROUP_ATTRIBUTES_UPDATED, this.onGroupAttributesUpdated, this);
|
||||
this.engine.chat.on(this.engine.EVENT.GROUP_COUNTER_UPDATED, this.onGroupCounterUpdated, this);
|
||||
}
|
||||
|
||||
public unbindIMEvents() {
|
||||
this.engine.chat.off(this.engine.EVENT.SDK_READY, this.onSDKReady, this);
|
||||
this.engine.chat.off(this.engine.EVENT.SDK_NOT_READY, this.onSDKNotReady, this);
|
||||
this.engine.chat.off(this.engine.EVENT.KICKED_OUT, this.onKickedOut, this);
|
||||
this.engine.chat.off(this.engine.EVENT.MESSAGE_RECEIVED, this.onReceiveMessage, this);
|
||||
this.engine.chat.off(this.engine.EVENT.MESSAGE_REACTIONS_UPDATED, this.onMessageReactionsUpdated, this);
|
||||
this.engine.chat.off(this.engine.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated, this);
|
||||
this.engine.chat.off(this.engine.EVENT.TOTAL_UNREAD_MESSAGE_COUNT_UPDATED, this.onTotalMessageCountUpdated, this);
|
||||
this.engine.chat.off(this.engine.EVENT.PROFILE_UPDATED, this.onProfileUpdated, this);
|
||||
this.engine.chat.off(this.engine.EVENT.GROUP_LIST_UPDATED, this.onGroupListUpdated, this);
|
||||
this.engine.chat.off(this.engine.EVENT.GROUP_ATTRIBUTES_UPDATED, this.onGroupAttributesUpdated, this);
|
||||
this.engine.chat.off(this.engine.EVENT.GROUP_COUNTER_UPDATED, this.onGroupCounterUpdated, this);
|
||||
EventCenter.instance = null;
|
||||
}
|
||||
|
||||
private onSDKReady(event: any) {
|
||||
this.dispatch(this.engine.EVENT.SDK_READY, event.data);
|
||||
}
|
||||
|
||||
private onSDKNotReady(event: any) {
|
||||
this.dispatch(this.engine.EVENT.SDK_NOT_READY, event.data);
|
||||
}
|
||||
|
||||
private onKickedOut(event: any) {
|
||||
this.dispatch(this.engine.EVENT.KICKED_OUT, event.data);
|
||||
}
|
||||
|
||||
private onReceiveMessage(event: any) {
|
||||
this.dispatch(this.engine.EVENT.MESSAGE_RECEIVED, event.data);
|
||||
}
|
||||
|
||||
private onMessageReactionsUpdated(event: any) {
|
||||
this.dispatch(this.engine.EVENT.MESSAGE_REACTIONS_UPDATED, event.data);
|
||||
}
|
||||
|
||||
private onConversationListUpdated(event: any) {
|
||||
this.dispatch(this.engine.EVENT.CONVERSATION_LIST_UPDATED, event.data);
|
||||
}
|
||||
|
||||
private onTotalMessageCountUpdated(event: any) {
|
||||
this.dispatch(this.engine.EVENT.TOTAL_UNREAD_MESSAGE_COUNT_UPDATED, event.data);
|
||||
}
|
||||
|
||||
private onProfileUpdated(event: any) {
|
||||
this.dispatch(this.engine.EVENT.PROFILE_UPDATED, event.data);
|
||||
}
|
||||
|
||||
private onGroupListUpdated(event: any) {
|
||||
this.dispatch(this.engine.EVENT.GROUP_LIST_UPDATED, event.data);
|
||||
}
|
||||
|
||||
private onGroupAttributesUpdated(event: any) {
|
||||
this.dispatch(this.engine.EVENT.GROUP_ATTRIBUTES_UPDATED, event.data);
|
||||
}
|
||||
|
||||
private onGroupCounterUpdated(event: any) {
|
||||
this.dispatch(this.engine.EVENT.GROUP_COUNTER_UPDATED, event.data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { APP_NAMESPACE, IS_PC, IS_H5, IN_WX_MINI_APP, IN_UNI_NATIVE_APP } from '../utils/env';
|
||||
import type { ITUIGlobal } from '../interface/global';
|
||||
|
||||
export default class TUIGlobal implements ITUIGlobal {
|
||||
static instance: TUIGlobal;
|
||||
public global: any;
|
||||
public isOfficial: boolean;
|
||||
|
||||
constructor() {
|
||||
this.global = APP_NAMESPACE;
|
||||
this.isOfficial = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 TUIGlobal 实例
|
||||
*/
|
||||
static getInstance() {
|
||||
if (!TUIGlobal.instance) {
|
||||
TUIGlobal.instance = new TUIGlobal();
|
||||
}
|
||||
return TUIGlobal.instance;
|
||||
}
|
||||
|
||||
initOfficial(isOfficial: boolean) {
|
||||
this.isOfficial = isOfficial;
|
||||
}
|
||||
|
||||
getPlatform() {
|
||||
let platform = '';
|
||||
if (IS_PC) {
|
||||
platform = 'pc';
|
||||
} else if (IS_H5) {
|
||||
platform = 'h5';
|
||||
} else if (IN_WX_MINI_APP) {
|
||||
platform = 'wechat';
|
||||
} else if (IN_UNI_NATIVE_APP && !IN_WX_MINI_APP) {
|
||||
platform = 'app';
|
||||
}
|
||||
return platform;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
import type { Group, GroupMember, Message } from '@tencentcloud/lite-chat/basic';
|
||||
import type { ITUIGroupService } from '../interface/service';
|
||||
import type {
|
||||
AddMemberParams,
|
||||
ChangGroupOwnerParams,
|
||||
CountersParams,
|
||||
CreateGroupParams,
|
||||
DeleteMemberParams,
|
||||
GetGroupProfileParams,
|
||||
GetMemberListParams,
|
||||
GetMemberProfileParams,
|
||||
GroupAttrParams,
|
||||
JoinGroupParams,
|
||||
KeyListParams,
|
||||
MarkMemberParams,
|
||||
SetCountersParams,
|
||||
SetMemberCustomFiledParams,
|
||||
SetMemberMuteParams,
|
||||
SetMemberNameCardParams,
|
||||
SetMemberRoleParams,
|
||||
UpdateGroupParams,
|
||||
handleGroupApplicationParams,
|
||||
} from '../type';
|
||||
import type GroupModel from '../model/group';
|
||||
import type { IGroupModel } from '../interface/model';
|
||||
import TUIBase from '../tui-base';
|
||||
import { StoreName } from '../const';
|
||||
import { isUndefined } from '../utils/common-utils';
|
||||
|
||||
export default class TUIGroupService extends TUIBase implements ITUIGroupService {
|
||||
static instance: TUIGroupService;
|
||||
private groupMap: Map<string, any>;
|
||||
constructor() {
|
||||
super();
|
||||
this.groupMap = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 TUIGroupService 实例
|
||||
*/
|
||||
static getInstance() {
|
||||
if (!TUIGroupService.instance) {
|
||||
TUIGroupService.instance = new TUIGroupService();
|
||||
}
|
||||
return TUIGroupService.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 Service
|
||||
*/
|
||||
public init() {
|
||||
const chatEngine = this.getEngine();
|
||||
chatEngine.eventCenter.addEvent(chatEngine.EVENT.GROUP_LIST_UPDATED, this.onGroupListUpdated.bind(this));
|
||||
chatEngine.eventCenter.addEvent(chatEngine.EVENT.GROUP_ATTRIBUTES_UPDATED, this.onGroupAttributesUpdated.bind(this));
|
||||
chatEngine.eventCenter.addEvent(chatEngine.EVENT.GROUP_COUNTER_UPDATED, this.onGroupCounterUpdated.bind(this));
|
||||
chatEngine.eventCenter.addEvent(chatEngine.EVENT.MESSAGE_RECEIVED, this.onMessageReceived.bind(this));
|
||||
this.getGroupInitData();
|
||||
}
|
||||
|
||||
private onGroupListUpdated(groupList: Group[]) {
|
||||
const chatEngine = this.getEngine();
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'groupList', groupList);
|
||||
const currentGroupID = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
groupList.forEach((item: Group) => {
|
||||
if (item.groupID === currentGroupID) {
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroup', item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onGroupAttributesUpdated(data: any) {
|
||||
const chatEngine = this.getEngine();
|
||||
const currentGroupID = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
let groupList = chatEngine.TUIStore.getData(StoreName.GRP, 'groupList');
|
||||
const { groupID, groupAttributes } = data;
|
||||
if (currentGroupID === groupID) {
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroupAttributes', groupAttributes);
|
||||
}
|
||||
groupList = groupList.map((item: GroupModel) => {
|
||||
if (item.groupID === groupID) {
|
||||
item.groupAttributes = groupAttributes;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'groupList', groupList);
|
||||
}
|
||||
|
||||
private onGroupCounterUpdated(data: any) {
|
||||
const chatEngine = this.getEngine();
|
||||
const currentGroupID = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
const currentGroupCounters = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupCounters') || {};
|
||||
let groupList = chatEngine.TUIStore.getData(StoreName.GRP, 'groupList');
|
||||
const { groupID, key, value } = data;
|
||||
if (currentGroupID === groupID) {
|
||||
currentGroupCounters[key] = value;
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroupCounters', currentGroupCounters);
|
||||
}
|
||||
groupList = groupList.map((item: GroupModel) => {
|
||||
if (item.groupID === groupID) {
|
||||
item.groupCounters = {
|
||||
...item.groupCounters,
|
||||
[key]: value,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'groupList', groupList);
|
||||
}
|
||||
|
||||
private onMessageReceived(messageList: Message[]) {
|
||||
const chatEngine = this.getEngine();
|
||||
const groupSystemNoticeList: Message[] = [];
|
||||
messageList.forEach((message: Message) => {
|
||||
if (message.type === chatEngine.TYPES.MSG_GRP_TIP) {
|
||||
const { payload } = message;
|
||||
const { operationType, userIDList } = payload;
|
||||
switch (operationType) {
|
||||
case chatEngine.TYPES.GRP_TIP_MBR_JOIN:
|
||||
this.addMemberList(userIDList);
|
||||
break;
|
||||
case chatEngine.TYPES.GRP_TIP_MBR_QUIT:
|
||||
this.removeMemberList(userIDList);
|
||||
break;
|
||||
case chatEngine.TYPES.GRP_TIP_MBR_KICKED_OUT:
|
||||
this.removeMemberList(userIDList);
|
||||
break;
|
||||
case chatEngine.TYPES.GRP_TIP_MBR_SET_ADMIN:
|
||||
this.updateGroupMember(userIDList);
|
||||
break;
|
||||
case chatEngine.TYPES.GRP_TIP_MBR_CANCELED_ADMIN:
|
||||
this.updateGroupMember(userIDList);
|
||||
break;
|
||||
case chatEngine.TYPES.GRP_TIP_GRP_PROFILE_UPDATED:
|
||||
// v2.3.3, update currentGroup by GROUP_LIST_UPDATED when group profile is updated.
|
||||
break;
|
||||
case chatEngine.TYPES.GRP_TIP_MBR_PROFILE_UPDATED:
|
||||
this.updateGroupMember(userIDList);
|
||||
break;
|
||||
case chatEngine.TYPES.GRP_TIP_BAN_AVCHATROOM_MEMBER:
|
||||
this.updateGroupMember(userIDList);
|
||||
break;
|
||||
case chatEngine.TYPES.GRP_TIP_UNBAN_AVCHATROOM_MEMBER:
|
||||
this.updateGroupMember(userIDList);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (message.type === chatEngine.TYPES.MSG_GRP_SYS_NOTICE) {
|
||||
groupSystemNoticeList.push(message);
|
||||
}
|
||||
});
|
||||
if (groupSystemNoticeList.length > 0) {
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'groupSystemNoticeList', groupSystemNoticeList);
|
||||
}
|
||||
}
|
||||
|
||||
private getGroupInitData() {
|
||||
const chatEngine = this.getEngine();
|
||||
// TUIChatEngine 无 UI 集成时不需要执行此逻辑
|
||||
if (!chatEngine.chat.isReady()) {
|
||||
return;
|
||||
}
|
||||
chatEngine.chat.getGroupList().then((imResponse: any) => {
|
||||
const { groupList = [] } = imResponse.data;
|
||||
console.log(`TUIGroupService.init, getGroupList count:${groupList.length}`);
|
||||
if (groupList.length > 0) {
|
||||
this.onGroupListUpdated(groupList);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async updateGroupMember(userIDList: string[]) {
|
||||
const groupID = this.getEngine().TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
if (groupID) {
|
||||
const imResponse = await this.getGroupMemberProfile({
|
||||
groupID,
|
||||
userIDList,
|
||||
});
|
||||
const { memberList } = imResponse.data;
|
||||
this.updateMemberList(memberList);
|
||||
}
|
||||
}
|
||||
|
||||
private resetCurrentStore() {
|
||||
const keyList = [
|
||||
'currentGroupID',
|
||||
'currentGroup',
|
||||
'currentGroupAttributes',
|
||||
'currentGroupCounters',
|
||||
'currentGroupMemberList',
|
||||
];
|
||||
this.getEngine().TUIStore.reset(StoreName.GRP, keyList, true);
|
||||
}
|
||||
|
||||
public async switchGroup(groupID: string) {
|
||||
const chatEngine = this.getEngine();
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'offset', 0);
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'isCompleted', false);
|
||||
|
||||
const conversation = chatEngine.TUIStore.getConversationModel(`GROUP${groupID}`);
|
||||
// operationType > 0 说明用户已经不在群组内了,直接返回
|
||||
if (conversation?.operationType > 0) {
|
||||
const currentGroup = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroup');
|
||||
return Promise.resolve(currentGroup);
|
||||
}
|
||||
const currentGroupID = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
if (!groupID) {
|
||||
this.resetCurrentStore();
|
||||
return Promise.resolve({});
|
||||
}
|
||||
if (currentGroupID === groupID) {
|
||||
const currentGroup = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroup');
|
||||
return Promise.resolve(currentGroup);
|
||||
}
|
||||
this.resetCurrentStore();
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroupID', groupID);
|
||||
try {
|
||||
await this.getGroupInfo(groupID);
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
// 5s 后删除是为了解决快速反复切换到群管理操作面板是重复调用问题
|
||||
const timer = setTimeout(() => {
|
||||
this.groupMap.delete(groupID);
|
||||
clearTimeout(timer);
|
||||
}, 5000);
|
||||
const currentGroup = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroup');
|
||||
return Promise.resolve(currentGroup);
|
||||
}
|
||||
|
||||
private async getGroupInfo(groupID: string) {
|
||||
const chatEngine = this.getEngine();
|
||||
const currentGroupMap = this.groupMap.get(groupID);
|
||||
if (currentGroupMap) {
|
||||
this.updateMemberList(currentGroupMap?.memberList || []);
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroup', currentGroupMap.group);
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroupAttributes', currentGroupMap.groupAttributes || {});
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroupCounters', currentGroupMap.counters || {});
|
||||
return;
|
||||
}
|
||||
const groupInfo = {
|
||||
group: {},
|
||||
memberList: [],
|
||||
groupAttributes: undefined,
|
||||
counters: undefined,
|
||||
};
|
||||
const { data: { group } } = await this.getGroupProfile({ groupID });
|
||||
groupInfo.group = group;
|
||||
const { data: { memberList } } = await this.getGroupMemberList({ groupID });
|
||||
groupInfo.memberList = memberList;
|
||||
const attributesRes = await this.getGroupAttributes({ groupID, keyList: [] });
|
||||
const { groupAttributes } = attributesRes.data;
|
||||
groupInfo.groupAttributes = groupAttributes;
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroupAttributes', groupAttributes);
|
||||
try {
|
||||
const countersRes = await this.getGroupCounters({ groupID, keyList: [] });
|
||||
const { counters } = countersRes.data;
|
||||
groupInfo.counters = counters;
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroupCounters', counters);
|
||||
} catch (error: any) {
|
||||
console.warn(error?.message); // 旗舰功能,预防初次跑通(直接打印 error uni 打包小程序会报错)
|
||||
}
|
||||
this.groupMap.set(groupID, groupInfo);
|
||||
}
|
||||
|
||||
public getGroupProfile(options: GetGroupProfileParams) {
|
||||
const chatEngine = this.getEngine();
|
||||
return chatEngine.chat.getGroupProfile(options).then(async (imResponse: any) => {
|
||||
const currentGroupID = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
if (currentGroupID === options.groupID) {
|
||||
const { group } = imResponse.data;
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroup', group);
|
||||
}
|
||||
return imResponse;
|
||||
});
|
||||
}
|
||||
|
||||
public updateGroupProfile(options: UpdateGroupParams) {
|
||||
return this.getEngine().chat.updateGroupProfile(options);
|
||||
}
|
||||
|
||||
public createGroup(options: CreateGroupParams) {
|
||||
return this.getEngine().chat.createGroup(options);
|
||||
}
|
||||
|
||||
public dismissGroup(groupID: string) {
|
||||
return this.getEngine().chat.dismissGroup(groupID);
|
||||
}
|
||||
|
||||
public searchGroupByID(groupID: string) {
|
||||
const chatEngine = this.getEngine();
|
||||
return chatEngine.chat.searchGroupByID(groupID).then((imResponse: any) => {
|
||||
const { group } = imResponse.data;
|
||||
const groupList = chatEngine.TUIStore.getData(StoreName.GRP, 'groupList');
|
||||
imResponse.data.group.isJoinedGroup = groupList.some((item: IGroupModel) => item.groupID === group.groupID);
|
||||
return imResponse;
|
||||
});
|
||||
}
|
||||
|
||||
public joinGroup(options: JoinGroupParams) {
|
||||
return this.getEngine().chat.joinGroup(options);
|
||||
}
|
||||
|
||||
public quitGroup(groupID: string) {
|
||||
return this.getEngine().chat.quitGroup(groupID);
|
||||
}
|
||||
|
||||
public getGroupApplicationList() {
|
||||
return this.getEngine().chat.getGroupApplicationList();
|
||||
}
|
||||
|
||||
public handleGroupApplication(options: handleGroupApplicationParams) {
|
||||
return this.getEngine().chat.handleGroupApplication(options);
|
||||
}
|
||||
|
||||
public getGroupOnlineMemberCount(groupID: string) {
|
||||
return this.getEngine().chat.getGroupOnlineMemberCount(groupID);
|
||||
}
|
||||
|
||||
public changeGroupOwner(options: ChangGroupOwnerParams) {
|
||||
return this.getEngine().chat.changeGroupOwner(options);
|
||||
}
|
||||
|
||||
public initGroupAttributes(options: GroupAttrParams) {
|
||||
return this.getEngine().chat.initGroupAttributes(options);
|
||||
}
|
||||
|
||||
public setGroupAttributes(options: GroupAttrParams) {
|
||||
return this.getEngine().chat.setGroupAttributes(options);
|
||||
}
|
||||
|
||||
public deleteGroupAttributes(options: KeyListParams) {
|
||||
return this.getEngine().chat.deleteGroupAttributes(options);
|
||||
}
|
||||
|
||||
public getGroupAttributes(options: KeyListParams) {
|
||||
return this.getEngine().chat.getGroupAttributes(options);
|
||||
}
|
||||
|
||||
public setGroupCounters(options: SetCountersParams) {
|
||||
return this.getEngine().chat.setGroupCounters(options);
|
||||
}
|
||||
|
||||
public increaseGroupCounter(options: CountersParams) {
|
||||
return this.getEngine().chat.increaseGroupCounter(options);
|
||||
}
|
||||
|
||||
public decreaseGroupCounter(options: CountersParams) {
|
||||
return this.getEngine().chat.decreaseGroupCounter(options);
|
||||
}
|
||||
|
||||
public getGroupCounters(options: KeyListParams) {
|
||||
return this.getEngine().chat.getGroupCounters(options);
|
||||
}
|
||||
|
||||
// group member
|
||||
private updateMemberList(memberList: GroupMember[]) {
|
||||
const chatEngine = this.getEngine();
|
||||
const currentGroupMemberList = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupMemberList') || [];
|
||||
const filterMemberList = currentGroupMemberList.filter((currentMember: GroupMember) => {
|
||||
const isExist = memberList.find((member: GroupMember) => member.userID === currentMember.userID);
|
||||
return !isExist;
|
||||
});
|
||||
const newMemberList = [...filterMemberList, ...memberList];
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroupMemberList', newMemberList);
|
||||
}
|
||||
|
||||
private async addMemberList(userIDList: string[]) {
|
||||
const groupID = this.getEngine().TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
if (groupID) {
|
||||
try {
|
||||
const imResponse = await this.getGroupMemberProfile({
|
||||
groupID,
|
||||
userIDList,
|
||||
});
|
||||
const { memberList } = imResponse.data;
|
||||
this.updateMemberList(memberList);
|
||||
} catch (error) {
|
||||
const memberList: GroupMember[] = userIDList.map((userID: string) => {
|
||||
const GroupMember = {
|
||||
userID,
|
||||
avatar: '',
|
||||
nick: '',
|
||||
role: '',
|
||||
joinTime: 0,
|
||||
nameCard: '',
|
||||
muteUntil: 0,
|
||||
memberCustomField: [],
|
||||
};
|
||||
return GroupMember;
|
||||
});
|
||||
this.updateMemberList(memberList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeMemberList(userIDList: string[]) {
|
||||
const chatEngine = this.getEngine();
|
||||
const currentGroupMemberList = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupMemberList');
|
||||
const newMemberList = currentGroupMemberList.filter((member: GroupMember) => userIDList.indexOf(member.userID) === -1);
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroupMemberList', newMemberList);
|
||||
}
|
||||
|
||||
public getGroupMemberList(options: GetMemberListParams) {
|
||||
const chatEngine = this.getEngine();
|
||||
if (isUndefined(options.offset)) {
|
||||
const _offset = chatEngine.TUIStore.getData(StoreName.GRP, 'offset');
|
||||
options.offset = _offset;
|
||||
}
|
||||
return chatEngine.chat.getGroupMemberList(options).then((imResponse: any) => {
|
||||
const currentGroupID = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
if (currentGroupID === options.groupID) {
|
||||
const { memberList, offset = 0 } = imResponse.data;
|
||||
this.updateMemberList(memberList);
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'offset', offset);
|
||||
if (offset === 0) {
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'isCompleted', true);
|
||||
}
|
||||
}
|
||||
return imResponse;
|
||||
});
|
||||
}
|
||||
|
||||
public getGroupMemberProfile(options: GetMemberProfileParams) {
|
||||
return this.getEngine().chat.getGroupMemberProfile(options);
|
||||
}
|
||||
|
||||
public addGroupMember(options: AddMemberParams) {
|
||||
const chatEngine = this.getEngine();
|
||||
return chatEngine.chat.addGroupMember(options).then(async (imResponse: any) => {
|
||||
const currentGroupID = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
if (currentGroupID === options.groupID) {
|
||||
const { successUserIDList, group } = imResponse.data;
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroup', group);
|
||||
this.addMemberList(successUserIDList);
|
||||
}
|
||||
return imResponse;
|
||||
});
|
||||
}
|
||||
|
||||
public deleteGroupMember(options: DeleteMemberParams) {
|
||||
const chatEngine = this.getEngine();
|
||||
return chatEngine.chat.deleteGroupMember(options).then((imResponse: any) => {
|
||||
const currentGroupID = chatEngine.TUIStore.getData(StoreName.GRP, 'currentGroupID');
|
||||
if (currentGroupID === options.groupID) {
|
||||
const { userIDList, group } = imResponse.data;
|
||||
this.removeMemberList(userIDList);
|
||||
chatEngine.TUIStore.update(StoreName.GRP, 'currentGroup', group);
|
||||
}
|
||||
return imResponse;
|
||||
});
|
||||
}
|
||||
|
||||
public setGroupMemberMuteTime(options: SetMemberMuteParams) {
|
||||
return this.getEngine().chat.setGroupMemberMuteTime(options);
|
||||
}
|
||||
|
||||
public setGroupMemberRole(options: SetMemberRoleParams) {
|
||||
return this.getEngine().chat.setGroupMemberRole(options);
|
||||
}
|
||||
|
||||
public setGroupMemberNameCard(options: SetMemberNameCardParams) {
|
||||
return this.getEngine().chat.setGroupMemberNameCard(options);
|
||||
}
|
||||
|
||||
public setGroupMemberCustomField(options: SetMemberCustomFiledParams) {
|
||||
return this.getEngine().chat.setGroupMemberCustomField(options);
|
||||
}
|
||||
|
||||
public markGroupMemberList(options: MarkMemberParams) {
|
||||
return this.getEngine().chat.markGroupMemberList(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import TUIBase from '../tui-base';
|
||||
|
||||
export default class TUIReportService extends TUIBase {
|
||||
static instance: TUIReportService;
|
||||
/**
|
||||
* Get TUIReportService instance
|
||||
*/
|
||||
static getInstance() {
|
||||
if (!TUIReportService.instance) {
|
||||
TUIReportService.instance = new TUIReportService();
|
||||
}
|
||||
return TUIReportService.instance;
|
||||
}
|
||||
/**
|
||||
* Report key feature usage for analytics
|
||||
* @param code - Event code for analytics
|
||||
* @param feature - Feature name or identifier
|
||||
*/
|
||||
public reportFeature(code: number, feature?: string) {
|
||||
return this.getEngine().chat?.callExperimentalAPI('statTUIKeyFeatures', {
|
||||
code,
|
||||
msg: feature ? `${code}-${feature}` : ''
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import type { IAppStore, ITasks } from '../../interface/store';
|
||||
|
||||
export default class AppStore implements IAppStore {
|
||||
public store: {
|
||||
enabledMessageReadReceipt: boolean;
|
||||
enabledEmojiPlugin: boolean;
|
||||
enabledOnlineStatus: boolean;
|
||||
enabledCustomerServicePlugin: boolean;
|
||||
enabledTranslationPlugin: boolean;
|
||||
enabledVoiceToText: boolean;
|
||||
enableTyping: boolean;
|
||||
enableConversationDraft: boolean;
|
||||
enableAutoMessageRead: boolean;
|
||||
isOfficial: boolean;
|
||||
SDKVersion: string;
|
||||
tasks: ITasks;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
public defaultStore = {
|
||||
enabledMessageReadReceipt: false,
|
||||
enabledEmojiPlugin: false,
|
||||
enabledOnlineStatus: false,
|
||||
enabledCustomerServicePlugin: false,
|
||||
enabledTranslationPlugin: false,
|
||||
enabledVoiceToText: false,
|
||||
enableTyping: true,
|
||||
enableConversationDraft: true,
|
||||
enableAutoMessageRead: true,
|
||||
isOfficial: false,
|
||||
SDKVersion: '3.0.0',
|
||||
tasks: {
|
||||
sendMessage: false,
|
||||
revokeMessage: false,
|
||||
modifyNickName: false,
|
||||
groupChat: false,
|
||||
muteGroup: false,
|
||||
dismissGroup: false,
|
||||
call: false,
|
||||
searchCloudMessage: false,
|
||||
customerService: false,
|
||||
translateTextMessage: false,
|
||||
},
|
||||
} as any;
|
||||
|
||||
constructor() {
|
||||
this.store = {
|
||||
enabledEmojiPlugin: false,
|
||||
enabledMessageReadReceipt: false,
|
||||
enabledOnlineStatus: false,
|
||||
enabledCustomerServicePlugin: false,
|
||||
enabledTranslationPlugin: false,
|
||||
enabledVoiceToText: false,
|
||||
enableTyping: true,
|
||||
enableConversationDraft: true,
|
||||
enableAutoMessageRead: true,
|
||||
isOfficial: false,
|
||||
SDKVersion: '3.0.0',
|
||||
tasks: {
|
||||
sendMessage: false,
|
||||
revokeMessage: false,
|
||||
modifyNickName: false,
|
||||
groupChat: false,
|
||||
muteGroup: false,
|
||||
dismissGroup: false,
|
||||
call: false,
|
||||
searchCloudMessage: false,
|
||||
customerService: false,
|
||||
translateTextMessage: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
update(key: string, data: any) {
|
||||
this.store[key] = data;
|
||||
}
|
||||
|
||||
getData(key: string) {
|
||||
return this.store[key];
|
||||
}
|
||||
|
||||
reset(keyList: string[] = []) {
|
||||
this.store = {
|
||||
...this.defaultStore,
|
||||
...this.store,
|
||||
...keyList.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import type { Message } from '@tencentcloud/lite-chat/basic';
|
||||
import type { IChatStore } from '../../interface/store';
|
||||
import type { IMessageModel } from '../../interface/model';
|
||||
import MessageModel from '../../model/message';
|
||||
import { CHAT_IGNORE_KEYS } from '../../config';
|
||||
|
||||
interface ITextInfo {
|
||||
messageID: string;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export default class ChatStore implements IChatStore {
|
||||
public store: {
|
||||
messageList: IMessageModel[];
|
||||
isCompleted: boolean;
|
||||
nextReqMessageID: string;
|
||||
quoteMessage: Message | any;
|
||||
newMessageList: Message[];
|
||||
typingStatus: boolean;
|
||||
messageSource: IMessageModel | undefined;
|
||||
translateTextInfo: Map<string, ITextInfo[]> | undefined;
|
||||
voiceToTextInfo: Map<string, ITextInfo[]> | undefined;
|
||||
userInfo: Record<string, any>;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
public defaultStore = {
|
||||
messageList: [],
|
||||
isCompleted: false,
|
||||
nextReqMessageID: '',
|
||||
quoteMessage: {},
|
||||
newMessageList: [],
|
||||
typingStatus: false,
|
||||
messageSource: undefined,
|
||||
translateTextInfo: undefined,
|
||||
voiceToTextInfo: undefined,
|
||||
userInfo: {},
|
||||
} as any;
|
||||
|
||||
constructor() {
|
||||
this.store = {
|
||||
messageList: [],
|
||||
isCompleted: false,
|
||||
nextReqMessageID: '',
|
||||
quoteMessage: {},
|
||||
newMessageList: [],
|
||||
typingStatus: false,
|
||||
messageSource: undefined,
|
||||
translateTextInfo: undefined,
|
||||
voiceToTextInfo: undefined,
|
||||
userInfo: {},
|
||||
};
|
||||
}
|
||||
|
||||
update(key: string, data: any): void {
|
||||
switch (key) {
|
||||
case 'messageList':
|
||||
this.updateMessageList(data);
|
||||
break;
|
||||
case 'translateTextInfo':
|
||||
this.updateTranslateTextInfo(data);
|
||||
break;
|
||||
case 'voiceToTextInfo':
|
||||
this.updateVoiceToTextInfo(data);
|
||||
break;
|
||||
default:
|
||||
this.store[key] = data;
|
||||
}
|
||||
}
|
||||
|
||||
getData(key: string) {
|
||||
return this.store[key];
|
||||
}
|
||||
|
||||
getModel(messageID: string) {
|
||||
return this.store.messageList.find((message: IMessageModel) => message.ID === messageID);
|
||||
}
|
||||
|
||||
reset(keyList: string[] = []) {
|
||||
const resetkeyList = keyList.filter((key: string) => !CHAT_IGNORE_KEYS.includes(key));
|
||||
this.store = {
|
||||
...this.defaultStore,
|
||||
...this.store,
|
||||
...resetkeyList?.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
|
||||
};
|
||||
}
|
||||
|
||||
private updateMessageList(list: any[]) {
|
||||
const toBeUpdatedList: IMessageModel[] = [];
|
||||
list.forEach((item) => {
|
||||
let messageModel: Message | IMessageModel | undefined = item;
|
||||
if (!(item instanceof MessageModel)) {
|
||||
messageModel = this.getModel(item.ID);
|
||||
if (messageModel) {
|
||||
(messageModel as MessageModel).updateProperties(item);
|
||||
} else {
|
||||
messageModel = new MessageModel(item);
|
||||
}
|
||||
}
|
||||
toBeUpdatedList.push(messageModel as IMessageModel);
|
||||
});
|
||||
this.store.messageList = toBeUpdatedList;
|
||||
}
|
||||
|
||||
private updateTranslateTextInfo(data: Record<string, any>) {
|
||||
this.updateBykey('translateTextInfo', data);
|
||||
}
|
||||
|
||||
private updateVoiceToTextInfo(data: Record<string, any>) {
|
||||
this.updateBykey('voiceToTextInfo', data);
|
||||
}
|
||||
|
||||
private updateBykey(key: string, data: Record<string, any>) {
|
||||
const { conversationID, messageID, visible = false } = data;
|
||||
if (!this.store[key]) {
|
||||
this.store[key] = new Map();
|
||||
}
|
||||
if (!this.store[key].has(conversationID)) {
|
||||
this.store[key].set(conversationID, []);
|
||||
}
|
||||
const list = this.store[key].get(conversationID) || [];
|
||||
let isAdded = true;
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].messageID === messageID) {
|
||||
list[i].visible = visible;
|
||||
isAdded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isAdded) {
|
||||
list.push({ messageID, visible });
|
||||
}
|
||||
this.store[key].set(conversationID, list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { Conversation } from '@tencentcloud/lite-chat/basic';
|
||||
import type { IConversationStore } from '../../interface/store';
|
||||
import type { IConversationModel } from '../../interface/model';
|
||||
import ConversationModel from '../../model/conversation';
|
||||
|
||||
export default class ConversationStore implements IConversationStore {
|
||||
public store: {
|
||||
currentConversationID: string;
|
||||
currentConversation: IConversationModel | null;
|
||||
totalUnreadCount: number;
|
||||
conversationList: IConversationModel[];
|
||||
operationTypeMap: Map<string, number>; // 记录退群、群解散、被踢的群
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
public defaultStore = {
|
||||
currentConversationID: '',
|
||||
totalUnreadCount: 0,
|
||||
conversationList: [],
|
||||
currentConversation: null,
|
||||
operationTypeMap: new Map(),
|
||||
} as any;
|
||||
|
||||
constructor() {
|
||||
this.store = {
|
||||
currentConversationID: '',
|
||||
totalUnreadCount: 0,
|
||||
conversationList: [],
|
||||
currentConversation: null,
|
||||
operationTypeMap: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
update(key: string, data: any) {
|
||||
switch (key) {
|
||||
case 'conversationList':
|
||||
this.updateConversationList(data);
|
||||
break;
|
||||
case 'operationTypeMap':
|
||||
this.updateOperationTypeMap(data);
|
||||
break;
|
||||
default:
|
||||
this.store[key] = data;
|
||||
}
|
||||
}
|
||||
|
||||
getData(key: string) {
|
||||
return this.store[key];
|
||||
}
|
||||
|
||||
getModel(conversationID: string) {
|
||||
return this.store.conversationList.find((conversation: IConversationModel) => conversation.conversationID === conversationID);
|
||||
}
|
||||
|
||||
reset(keyList: string[] = []) {
|
||||
this.store = {
|
||||
...this.defaultStore,
|
||||
...this.store,
|
||||
...keyList.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
|
||||
};
|
||||
}
|
||||
|
||||
private updateConversationList(list: Conversation[] | ConversationModel[]) {
|
||||
const toBeUpdatedList: any[] = [];
|
||||
list.forEach((item: Conversation | ConversationModel) => {
|
||||
let conversationModel: Conversation | IConversationModel | undefined = item;
|
||||
if (!(item instanceof ConversationModel)) {
|
||||
conversationModel = new ConversationModel(item);
|
||||
} else {
|
||||
(conversationModel as ConversationModel).updateProperties(item);
|
||||
}
|
||||
const optType = this.getOperationType(item);
|
||||
(conversationModel as ConversationModel).updateOperationType(optType);
|
||||
toBeUpdatedList.push(conversationModel);
|
||||
});
|
||||
this.store.conversationList = toBeUpdatedList;
|
||||
}
|
||||
|
||||
private updateOperationTypeMap(data: Record<string, any>) {
|
||||
const { conversationID, operationType = 0 } = data;
|
||||
this.store.operationTypeMap.set(conversationID, operationType);
|
||||
}
|
||||
|
||||
private getOperationType(conversation: any) {
|
||||
const { conversationID } = conversation;
|
||||
return this.store.operationTypeMap.get(conversationID) || 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { ICustomStore } from '../../interface/store';
|
||||
|
||||
export default class CustomStore implements ICustomStore {
|
||||
public store: any;
|
||||
|
||||
constructor() {
|
||||
this.store = {};
|
||||
}
|
||||
|
||||
update(key: string, data: any) {
|
||||
this.store[key] = data;
|
||||
}
|
||||
|
||||
getData(key: string) {
|
||||
return this.store[key];
|
||||
}
|
||||
|
||||
reset(keyList: string[] = []) {
|
||||
if (keyList.length === 0) {
|
||||
this.store = {};
|
||||
}
|
||||
this.store = {
|
||||
...this.store,
|
||||
...keyList.reduce((acc, key) => ({ ...acc, [key]: undefined }), {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import type { Group, Message } from '@tencentcloud/lite-chat/basic';
|
||||
import type { GroupMember, IGroupStore } from '../../interface/store';
|
||||
import type { IGroupModel } from '../../interface/model';
|
||||
import GroupModel from '../../model/group';
|
||||
|
||||
export default class GroupStore implements IGroupStore {
|
||||
public store: {
|
||||
currentGroupID: string;
|
||||
currentGroup: IGroupModel | undefined;
|
||||
currentGroupAttributes: Record<string, any>;
|
||||
currentGroupCounters: Record<string, number>;
|
||||
currentGroupMemberList: GroupMember[];
|
||||
groupList: IGroupModel[];
|
||||
groupSystemNoticeList: Message[]; // 下发给个人的群系统通知
|
||||
isCompleted: boolean;
|
||||
offset: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
public defaultStore = {
|
||||
currentGroupID: '',
|
||||
currentGroup: {},
|
||||
currentGroupAttributes: {},
|
||||
currentGroupCounters: {},
|
||||
currentGroupMemberList: [],
|
||||
groupList: [],
|
||||
groupSystemNoticeList: [],
|
||||
isCompleted: false,
|
||||
offset: 0,
|
||||
} as any;
|
||||
|
||||
constructor() {
|
||||
this.store = {
|
||||
currentGroupID: '',
|
||||
currentGroup: undefined,
|
||||
currentGroupAttributes: {},
|
||||
currentGroupCounters: {},
|
||||
currentGroupMemberList: [],
|
||||
groupList: [],
|
||||
groupSystemNoticeList: [],
|
||||
isCompleted: false,
|
||||
offset: 0,
|
||||
};
|
||||
}
|
||||
|
||||
update(key: string, data: any) {
|
||||
switch (key) {
|
||||
case 'groupList':
|
||||
this.updateGroupList(data);
|
||||
break;
|
||||
case 'currentGroup':
|
||||
if (data instanceof GroupModel) {
|
||||
this.store.currentGroup = data;
|
||||
} else {
|
||||
this.store.currentGroup = new GroupModel(data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.store[key] = data;
|
||||
}
|
||||
}
|
||||
|
||||
getData(key: string) {
|
||||
if (key === 'groupSystemNoticeList') {
|
||||
const groupSystemNoticeList = [...this.store.groupSystemNoticeList];
|
||||
// 群系统通知给 UI 后立即清空
|
||||
this.store.groupSystemNoticeList.length = 0;
|
||||
return groupSystemNoticeList;
|
||||
}
|
||||
return this.store[key];
|
||||
}
|
||||
|
||||
reset(keyList: string[] = []) {
|
||||
this.store = {
|
||||
...this.defaultStore,
|
||||
...this.store,
|
||||
...keyList.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
|
||||
};
|
||||
}
|
||||
|
||||
private updateGroupList(list: any[]) {
|
||||
this.store.groupList = list.map((item: Group | GroupModel) => {
|
||||
if (item instanceof GroupModel) {
|
||||
return item;
|
||||
}
|
||||
return new GroupModel(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { IUserStore } from '../../interface/store';
|
||||
import { UserStatus } from '../../const';
|
||||
|
||||
interface IBlackUser {
|
||||
userID: string;
|
||||
nick: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
export default class UserStore implements IUserStore {
|
||||
public store: {
|
||||
userProfile: object;
|
||||
displayOnlineStatus: boolean;
|
||||
displayMessageReadReceipt: boolean;
|
||||
userStatusList: Map<string, {
|
||||
statusType: number;
|
||||
customStatus: string;
|
||||
}>;
|
||||
kickedOut: string;
|
||||
netStateChange: string;
|
||||
userBlacklist: IBlackUser[];
|
||||
targetLanguage: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
public defaultStore = {
|
||||
userProfile: {},
|
||||
displayOnlineStatus: false,
|
||||
displayMessageReadReceipt: true,
|
||||
userStatusList: new Map(),
|
||||
kickedOut: '',
|
||||
netStateChange: '',
|
||||
userBlacklist: [],
|
||||
targetLanguage: 'zh',
|
||||
} as any;
|
||||
|
||||
constructor() {
|
||||
this.store = {
|
||||
userProfile: {},
|
||||
displayOnlineStatus: false, // 默认关闭在线状态功能,防止专业版集成引起报错
|
||||
displayMessageReadReceipt: true, // 默认在用户纬度开启消息阅读状态功能
|
||||
userStatusList: new Map(), // 获取/订阅/取消订阅用户状态的 userID 列表
|
||||
kickedOut: '',
|
||||
netStateChange: '',
|
||||
userBlacklist: [],
|
||||
targetLanguage: 'zh',
|
||||
};
|
||||
}
|
||||
|
||||
update(key: string, data: any) {
|
||||
switch (key) {
|
||||
case 'userStatusList':
|
||||
this.updateUserStatusList(data);
|
||||
break;
|
||||
default:
|
||||
this.store[key] = data;
|
||||
}
|
||||
}
|
||||
|
||||
getData(key: string) {
|
||||
return this.store[key];
|
||||
}
|
||||
|
||||
reset(keyList: string[] = []) {
|
||||
this.store = {
|
||||
...this.defaultStore,
|
||||
...this.store,
|
||||
...keyList.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
|
||||
};
|
||||
}
|
||||
|
||||
private updateUserStatusList(list: any[]) {
|
||||
if (list.length === 0) {
|
||||
this.store.userStatusList.clear();
|
||||
return;
|
||||
}
|
||||
list.forEach((item: { userID: string; statusType: number; customStatus: string }) => {
|
||||
const { userID, statusType = 0, customStatus = '' } = item;
|
||||
// statusType = -1 表示该用户已被取消订阅,内部约定规则
|
||||
if (statusType === UserStatus.UNSUB_USER) {
|
||||
this.store.userStatusList.delete(userID);
|
||||
} else {
|
||||
this.store.userStatusList.set(userID, { statusType, customStatus });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import type { ITUIStore, IOptions, Task } from '../interface/store';
|
||||
import { StoreName } from '../const';
|
||||
import { notNotifyList } from '../config';
|
||||
import AppStore from './store/app';
|
||||
import UserStore from './store/user';
|
||||
import ConversationStore from './store/conversation';
|
||||
import ChatStore from './store/chat';
|
||||
import GroupStore from './store/group';
|
||||
import CustomStore from './store/custom';
|
||||
import type { func } from '../type';
|
||||
|
||||
export default class TUIStore implements ITUIStore {
|
||||
static instance: TUIStore;
|
||||
public task: Task;
|
||||
private storeMap: Partial<Record<StoreName, any>>;
|
||||
|
||||
constructor() {
|
||||
this.storeMap = {
|
||||
[StoreName.APP]: new AppStore(),
|
||||
[StoreName.USER]: new UserStore(),
|
||||
[StoreName.CONV]: new ConversationStore(),
|
||||
[StoreName.CHAT]: new ChatStore(),
|
||||
[StoreName.GRP]: new GroupStore(),
|
||||
};
|
||||
|
||||
// todo 此处后续优化结构后调整
|
||||
this.task = {} as Task; // 保存监听回调列表
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 TUIStore 实例
|
||||
*/
|
||||
static getInstance() {
|
||||
if (!TUIStore.instance) {
|
||||
TUIStore.instance = new TUIStore();
|
||||
}
|
||||
return TUIStore.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI 组件注册监听回调
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {IOptions} options 监听信息
|
||||
*/
|
||||
watch(storeName: StoreName, options: IOptions) {
|
||||
if (!this.task[storeName]) {
|
||||
this.task[storeName] = {};
|
||||
}
|
||||
const watcher = this.task[storeName];
|
||||
Object.keys(options).forEach((key) => {
|
||||
const callback = options[key];
|
||||
if (!watcher[key]) {
|
||||
watcher[key] = new Map();
|
||||
}
|
||||
watcher[key].set(callback, 1);
|
||||
// 注册监听后立即通知一次默认数据,避免UI层初始化调用 getData
|
||||
this.notifyOnWatch(storeName, key, callback);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UI 取消组件监听回调
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {IOptions} options 监听信息,包含需要取消的回掉等
|
||||
*/
|
||||
unwatch(storeName: StoreName, options: IOptions) {
|
||||
// todo 该接口暂未支持,unwatch掉同一类,如仅传入store注销掉该store下的所有callback,同样options仅传入key注销掉该key下的所有callback
|
||||
// options的callback function为必填参数,后续修改
|
||||
if (!this.task[storeName]) {
|
||||
return;
|
||||
}
|
||||
const watcher = this.task[storeName];
|
||||
Object.keys(options).forEach((key) => {
|
||||
watcher[key]?.delete(options[key]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用 store 数据更新,messageList 的变更需要单独处理
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {string} key 变更的 key
|
||||
* @param {any} data 变更的数据
|
||||
*/
|
||||
update(storeName: StoreName, key: string, data: any) {
|
||||
if (storeName === StoreName.CUSTOM && !this.storeMap[storeName]) {
|
||||
this.storeMap[storeName] = new CustomStore();
|
||||
}
|
||||
this.storeMap[storeName]?.update(key, data);
|
||||
this.notify(storeName, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Store 数据
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {string} key 待获取的 key
|
||||
*/
|
||||
getData(storeName: StoreName, key: string) {
|
||||
if (storeName === StoreName.CUSTOM && !this.storeMap[storeName]) {
|
||||
this.storeMap[storeName] = new CustomStore();
|
||||
}
|
||||
return this.storeMap[storeName]?.getData(key);
|
||||
}
|
||||
|
||||
getConversationModel(conversationID: string) {
|
||||
return this.storeMap[StoreName.CONV]?.getModel(conversationID);
|
||||
}
|
||||
|
||||
getMessageModel(messageID: string) {
|
||||
return this.storeMap[StoreName.CHAT]?.getModel(messageID);
|
||||
}
|
||||
|
||||
reset(storeName: StoreName, keyList: string[] = [], isNotificationNeeded = false) {
|
||||
if (storeName in this.storeMap) {
|
||||
const store = this.storeMap[storeName];
|
||||
if (keyList.length === 0) {
|
||||
// reset all
|
||||
keyList = Object.keys(store?.store);
|
||||
}
|
||||
store.reset(keyList);
|
||||
if (isNotificationNeeded) {
|
||||
keyList.forEach((key) => {
|
||||
this.notify(storeName, key);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注册监听时默认给当前 callback 通知一次,notNotifyList 中配置的 key 如果数据为空就不触发 callback
|
||||
private notifyOnWatch(storeName: StoreName, key: string, callback: func) {
|
||||
const data = this.getData(storeName, key);
|
||||
if (notNotifyList.indexOf(key) > -1 && data.length === 0) {
|
||||
return;
|
||||
}
|
||||
callback && callback.call(this, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* UI 组件注册监听回调
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {string} key 变更的 key
|
||||
*/
|
||||
private notify(storeName: StoreName, key: string) {
|
||||
if (!this.task[storeName]) {
|
||||
return;
|
||||
}
|
||||
const watcher = this.task[storeName];
|
||||
if (watcher[key]) {
|
||||
const callbackMap = watcher[key];
|
||||
const data = this.getData(storeName, key);
|
||||
for (const [callback] of callbackMap.entries()) {
|
||||
callback.call(this, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import TUIBase from '../tui-base';
|
||||
import type { ITUIUserService } from '../interface/service';
|
||||
import type { UpdateMyProfileParams, UserIDListParams } from '../type';
|
||||
import UserProfileHandler from './user-profile-handler';
|
||||
import { StoreName } from '../const';
|
||||
|
||||
export default class TUIUserService extends TUIBase implements ITUIUserService {
|
||||
static instance: TUIUserService;
|
||||
private userProfileHandler: UserProfileHandler;
|
||||
constructor() {
|
||||
super();
|
||||
this.userProfileHandler = new UserProfileHandler(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 TUIUserService 实例
|
||||
*/
|
||||
static getInstance() {
|
||||
if (!TUIUserService.instance) {
|
||||
TUIUserService.instance = new TUIUserService();
|
||||
}
|
||||
return TUIUserService.instance;
|
||||
}
|
||||
|
||||
init() {
|
||||
const chatEngine = this.getEngine();
|
||||
// 监听用户被踢通知
|
||||
chatEngine.eventCenter.addEvent(chatEngine.EVENT.KICKED_OUT, this.onKickedOut.bind(this));
|
||||
|
||||
this.userProfileHandler.init();
|
||||
}
|
||||
|
||||
private onKickedOut(data: any) {
|
||||
this.getEngine().TUIStore.update(StoreName.USER, 'kickedOut', data.type);
|
||||
}
|
||||
|
||||
// 用户资料相关的方法
|
||||
public getUserProfile(userIDList?: UserIDListParams) {
|
||||
return this.userProfileHandler.getUserProfile(userIDList);
|
||||
}
|
||||
|
||||
public updateMyProfile(options: UpdateMyProfileParams) {
|
||||
return this.userProfileHandler.updateMyProfile(options);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import type { Profile } from '@tencentcloud/lite-chat/basic';
|
||||
import type { UpdateMyProfileParams, UserIDListParams } from '../type';
|
||||
import type { ITUIUserService } from '../interface/service';
|
||||
import { StoreName } from '../const';
|
||||
import { isUndefined } from '../utils/common-utils';
|
||||
|
||||
export default class UserProfileHandler {
|
||||
private TUIUserService: ITUIUserService;
|
||||
constructor(TUIUserService: ITUIUserService) {
|
||||
this.TUIUserService = TUIUserService;
|
||||
}
|
||||
|
||||
private getEngine() {
|
||||
return this.TUIUserService.getEngine();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.getEngine().eventCenter.addEvent(this.getEngine().EVENT.PROFILE_UPDATED, this.onProfileUpdated.bind(this));
|
||||
this.getUserProfileInitData();
|
||||
}
|
||||
|
||||
private onProfileUpdated(userProfileList: Profile[]) {
|
||||
const chatEngine = this.getEngine();
|
||||
const myProfile = chatEngine.TUIStore.getData(StoreName.USER, 'userProfile');
|
||||
userProfileList.forEach((profile: Profile) => {
|
||||
// 自己资料的更新通知需要更新 store 并通知给 UI,好友资料更新通知先不处理
|
||||
if (profile.userID === myProfile.userID) {
|
||||
chatEngine.TUIStore.update(StoreName.USER, 'userProfile', profile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getUserProfileInitData() {
|
||||
const chatEngine = this.getEngine();
|
||||
if (!chatEngine.chat.isReady()) {
|
||||
return;
|
||||
}
|
||||
// chatEngine.chat.getBlacklist().then((imResponse: any) => {
|
||||
// const blockList = imResponse.data || [];
|
||||
// console.log(`TUIUserProfileHandler.init, getBlacklist count:${blockList.length}`);
|
||||
// if (blockList.length > 0) {
|
||||
// this.onBlacklistUpdated(blockList);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
public getUserProfile(options?: UserIDListParams) {
|
||||
const chatEngine = this.getEngine();
|
||||
if (isUndefined(options)) {
|
||||
return chatEngine.chat.getMyProfile().then((imResponse: any) => {
|
||||
chatEngine.TUIStore.update(StoreName.USER, 'userProfile', imResponse.data);
|
||||
return imResponse;
|
||||
}).catch((error: any) => Promise.reject(error));
|
||||
}
|
||||
return chatEngine.chat.getUserProfile(options as UserIDListParams);
|
||||
}
|
||||
|
||||
public updateMyProfile(options: UpdateMyProfileParams) {
|
||||
return this.getEngine().chat.updateMyProfile(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const AVATAR = {
|
||||
C2C: 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_16.png',
|
||||
GROUP: 'https://web.sdk.qcloud.com/im/demo/TUIkit/web/img/constomer.png',
|
||||
SYSTEM: 'https://web.sdk.qcloud.com/component/TUIKit/assets/group_avatar.png',
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
// 商业化能力位文档:https://iwiki.woa.com/pages/viewpage.action?pageId=717923757
|
||||
interface ICommercialAbility {
|
||||
enabledMessageReadReceipt: number;
|
||||
enabledEmojiPlugin: number;
|
||||
enabledOnlineStatus: number;
|
||||
enabledCustomerServicePlugin: number;
|
||||
enabledTranslationPlugin: number;
|
||||
enabledVoiceToText: number;
|
||||
[key: string]: number;
|
||||
}
|
||||
export const commercialAbility: ICommercialAbility = {
|
||||
enabledMessageReadReceipt: Math.pow(2, 5),
|
||||
enabledEmojiPlugin: Math.pow(2, 48),
|
||||
enabledOnlineStatus: Math.pow(2, 7),
|
||||
enabledCustomerServicePlugin: Math.pow(2, 40),
|
||||
enabledTranslationPlugin: Math.pow(2, 38),
|
||||
enabledVoiceToText: Math.pow(2, 39),
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export const CHAT_IGNORE_KEYS = [
|
||||
'translateTextInfo',
|
||||
'voiceToTextInfo',
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './avatar';
|
||||
export * from './commercial-ability';
|
||||
export * from './not-notify-list';
|
||||
export * from './ignore-store-key';
|
||||
@@ -0,0 +1,6 @@
|
||||
// 注册监听时如果列表数据为空不触发 callback
|
||||
export const notNotifyList = [
|
||||
'messageList',
|
||||
'conversationList',
|
||||
'newMessageList',
|
||||
];
|
||||
@@ -0,0 +1,14 @@
|
||||
export enum ERROR_CODE {
|
||||
MSG_MODIFY_CONFLICT = 2480,
|
||||
MSG_MODIFY_DISABLED_IN_AVCHATROOM = 2481,
|
||||
MODIFY_MESSAGE_NOT_EXIST = 20026,
|
||||
}
|
||||
|
||||
export enum ERROR_CODE_ENGINE {
|
||||
NOT_INIT = -100000,
|
||||
INVALID_CONV_ID = -100001,
|
||||
CONV_ID_SAME = -100002,
|
||||
CONV_NOT_EXIST = -100003,
|
||||
GET_MSG_LIST_ERROR = -100004,
|
||||
MISMATCH_TYPE_AND_PAYLOAD = -100005,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export enum ERROR_MSG {
|
||||
MSG_MODIFY_CONFLICT = 'MODIFY_MESSAGE_ERROR,修改消息发生冲突, data.message 是最新的消息',
|
||||
MSG_MODIFY_DISABLED_IN_AVCHATROOM = 'MODIFY_MESSAGE_ERROR,不支持修改直播群消息.',
|
||||
MODIFY_MESSAGE_NOT_EXIST = 'MODIFY_MESSAGE_ERROR,消息不存在.',
|
||||
}
|
||||
|
||||
export enum ERROR_MSG_ENGINE {
|
||||
NOT_INIT = 'TUIChatEngine 初始化未完成,请确认 TUIChatEngine.login 接口调用是否正常。',
|
||||
INVALID_CONV_ID = '会话 ID 无效',
|
||||
CONV_ID_SAME = '您切换的是同一个会话 ID',
|
||||
CONV_NOT_EXIST = '会话不存在',
|
||||
GET_MSG_LIST_ERROR = 'Chat SDK is not ready.',
|
||||
MISMATCH_TYPE_AND_PAYLOAD = 'type 与 payload 不匹配.',
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './mounted-list';
|
||||
export * from './store-name';
|
||||
export * from './user-status';
|
||||
export * from './operate-type';
|
||||
export * from './error-code';
|
||||
export * from './error-msg';
|
||||
export * from './validate-api-list';
|
||||
@@ -0,0 +1,9 @@
|
||||
// ChatEngine 上挂载的业务模块列表
|
||||
export enum MountedList {
|
||||
TUIStore = 'TUIStore',
|
||||
TUIConversation = 'TUIConversation',
|
||||
TUIChat = 'TUIChat',
|
||||
TUIGroup = 'TUIGroup',
|
||||
TUIUser = 'TUIUser',
|
||||
TUIReport = 'TUIReport',
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum operateType {
|
||||
ADD = 'add',
|
||||
REMOVE = 'remove',
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
const OPTIONAL_MODULE = {
|
||||
GRP: 'group-module',
|
||||
SNS: 'relationship-module',
|
||||
};
|
||||
|
||||
export default OPTIONAL_MODULE;
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @property {String} APP 应用级别的数据管理,主要用于某些功能的全局开关控制。
|
||||
* @property {String} CONV 会话数据管理
|
||||
* @property {String} CHAT 聊天数据管理
|
||||
* @property {String} GRP 群组数据管理
|
||||
* @property {String} USER 用户数据管理
|
||||
* @property {String} FRIEND 好友数据管理
|
||||
* @property {String} SEARCH 搜索数据管理
|
||||
* @property {String} CUSTOM 自定义数据管理,业务侧可根据需要添加自定义 key-value。
|
||||
*/
|
||||
export enum StoreName {
|
||||
APP = 'application',
|
||||
CONV = 'conversation',
|
||||
CHAT = 'chat',
|
||||
GRP = 'group',
|
||||
USER = 'user',
|
||||
FRIEND = 'friend',
|
||||
SEARCH = 'search',
|
||||
CUSTOM = 'custom',
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum UserStatus {
|
||||
UNSUB_USER = -1, // 用于取消订阅时删除 userStore 中 map 缓存
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// 需要校验的 API
|
||||
export const ValidateAPIList = {
|
||||
// TUIChatEngine
|
||||
ENGINE: {
|
||||
logout: 1,
|
||||
destroy: 1,
|
||||
},
|
||||
CONV: {
|
||||
// ConversationModel
|
||||
// deleteConversation: 1,
|
||||
// pinConversation: 1,
|
||||
// muteConversation: 1,
|
||||
|
||||
// TUIConversationService
|
||||
switchConversation: 1,
|
||||
getConversationProfile: 1,
|
||||
clearHistoryMessage: 1,
|
||||
setMessageRead: 1,
|
||||
},
|
||||
CHAT: {
|
||||
// MessageModel
|
||||
modifyMessage: 1,
|
||||
revokeMessage: 1,
|
||||
resendMessage: 1,
|
||||
deleteMessage: 1,
|
||||
quoteMessage: 1,
|
||||
replyMessage: 1,
|
||||
setMessageExtensions: 1,
|
||||
deleteMessageExtensions: 1,
|
||||
getMessageExtensions: 1,
|
||||
|
||||
// TUIChatService
|
||||
sendTextMessage: 1,
|
||||
sendTextAtMessage: 1,
|
||||
sendImageMessage: 1,
|
||||
sendAudioMessage: 1,
|
||||
sendVideoMessage: 1,
|
||||
sendFileMessage: 1,
|
||||
sendCustomMessage: 1,
|
||||
sendFaceMessage: 1,
|
||||
sendLocationMessage: 1,
|
||||
sendForwardMessage: 1,
|
||||
enterTypingState: 1,
|
||||
leaveTypingState: 1,
|
||||
sendMessageReadReceipt: 1,
|
||||
getGroupMessageReadMemberList: 1,
|
||||
getMessageList: 1,
|
||||
downloadMergedMessages: 1,
|
||||
setTranslationLanguage: 1,
|
||||
translateText: 1,
|
||||
searchCloudMessages: 1,
|
||||
addMessageReaction: 1,
|
||||
removeMessageReaction: 1,
|
||||
getMessageReactions: 1,
|
||||
getAllUserListOfMessageReaction: 1,
|
||||
},
|
||||
GRP: {
|
||||
// TUIGroupService
|
||||
switchGroup: 1,
|
||||
getGroupProfile: 1,
|
||||
updateGroupProfile: 1,
|
||||
createGroup: 1,
|
||||
dismissGroup: 1,
|
||||
searchGroupByID: 1,
|
||||
joinGroup: 1,
|
||||
quitGroup: 1,
|
||||
getGroupApplicationList: 1,
|
||||
handleGroupApplication: 1,
|
||||
getGroupOnlineMemberCount: 1,
|
||||
changeGroupOwner: 1,
|
||||
initGroupAttributes: 1,
|
||||
setGroupAttributes: 1,
|
||||
deleteGroupAttributes: 1,
|
||||
getGroupAttributes: 1,
|
||||
setGroupCounters: 1,
|
||||
increaseGroupCounter: 1,
|
||||
decreaseGroupCounter: 1,
|
||||
getGroupCounters: 1,
|
||||
getGroupMemberList: 1,
|
||||
getGroupMemberProfile: 1,
|
||||
addGroupMember: 1,
|
||||
deleteGroupMember: 1,
|
||||
setGroupMemberMuteTime: 1,
|
||||
setGroupMemberRole: 1,
|
||||
setGroupMemberNameCard: 1,
|
||||
setGroupMemberCustomField: 1,
|
||||
markGroupMemberList: 1,
|
||||
},
|
||||
USER: {
|
||||
// TUIUserService
|
||||
switchUserStatus: 1,
|
||||
getUserProfile: 1,
|
||||
updateMyProfile: 1,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { ITUIChatEngine } from './interface/engine';
|
||||
import type { ITUIGlobal } from './interface/global';
|
||||
import type { ITUIStore } from './interface/store';
|
||||
import type {
|
||||
ITUIConversationService,
|
||||
ITUIUserService,
|
||||
ITUIChatService,
|
||||
ITUIGroupService,
|
||||
ITUIReportService,
|
||||
} from './interface/service';
|
||||
import ChatEngine from './TUIEngine/engine';
|
||||
import TUIGlobal from './TUIGlobal/tui-global';
|
||||
import TUIStore from './TUIStore/tui-store';
|
||||
import TUIConversationService from './TUIConversationService/tui-conversation';
|
||||
import TUIGroupService from './TUIGroupService/tui-group';
|
||||
import TUIUserService from './TUIUserService/tui-user';
|
||||
import TUIChatService from './TUIChatService/tui-chat';
|
||||
import TUIReportService from './TUIReportService/tui-report';
|
||||
import { MountedList, StoreName, ValidateAPIList } from './const';
|
||||
import validateInitialization from './utils/validate-initialization';
|
||||
|
||||
const version = 'BUNDLE_VERSION';
|
||||
console.log(`TUIChatEngine-Lite.VERSION:${version}`);
|
||||
|
||||
// 实例化
|
||||
const TUIChatEngine: ITUIChatEngine = ChatEngine.getInstance();
|
||||
const tuiGlobal: ITUIGlobal = TUIGlobal.getInstance();
|
||||
const tuiStore: ITUIStore = TUIStore.getInstance();
|
||||
const tuiConversation: ITUIConversationService = TUIConversationService.getInstance();
|
||||
const tuiGroup: ITUIGroupService = TUIGroupService.getInstance();
|
||||
const tuiUser: ITUIUserService = TUIUserService.getInstance();
|
||||
const tuiChat: ITUIChatService = TUIChatService.getInstance();
|
||||
const tuiReport: ITUIReportService = TUIReportService.getInstance();
|
||||
|
||||
// 模块挂载
|
||||
TUIChatEngine.mount(MountedList.TUIStore, tuiStore);
|
||||
TUIChatEngine.mount(MountedList.TUIConversation, tuiConversation);
|
||||
TUIChatEngine.mount(MountedList.TUIUser, tuiUser);
|
||||
TUIChatEngine.mount(MountedList.TUIChat, tuiChat);
|
||||
TUIChatEngine.mount(MountedList.TUIReport, tuiReport);
|
||||
|
||||
// API 调用校验 Engine 初始化状态
|
||||
validateInitialization(TUIChatEngine, TUIChatEngine, ValidateAPIList.ENGINE);
|
||||
validateInitialization(TUIChatEngine, tuiConversation, ValidateAPIList.CONV);
|
||||
validateInitialization(TUIChatEngine, tuiChat, ValidateAPIList.CHAT);
|
||||
validateInitialization(TUIChatEngine, tuiUser, ValidateAPIList.USER);
|
||||
validateInitialization(TUIChatEngine, tuiReport, ValidateAPIList.ENGINE);
|
||||
|
||||
// 输出 service 方法参数和 model 声明文件
|
||||
export * from './type';
|
||||
export * from './interface/model';
|
||||
|
||||
// 输出产物
|
||||
export {
|
||||
TUIChatEngine,
|
||||
tuiGlobal as TUIGlobal,
|
||||
tuiStore as TUIStore,
|
||||
tuiConversation as TUIConversationService,
|
||||
tuiGroup as TUIGroupService,
|
||||
tuiUser as TUIUserService,
|
||||
tuiChat as TUIChatService,
|
||||
tuiReport as TUIReportService,
|
||||
StoreName,
|
||||
};
|
||||
|
||||
export default TUIChatEngine;
|
||||
@@ -0,0 +1,102 @@
|
||||
import type { ChatSDK } from '@tencentcloud/lite-chat/basic';
|
||||
import type { LoginParams } from '../../type';
|
||||
import type { IEventCenter } from './event-center';
|
||||
import type {
|
||||
ITUIConversationService,
|
||||
ITUIChatService,
|
||||
ITUIGroupService,
|
||||
ITUIUserService,
|
||||
} from '../service';
|
||||
import type { ITUIStore } from '../store';
|
||||
|
||||
/**
|
||||
* @interface ITUIChatEngine
|
||||
* @property {Object} EVENT {@link https://web.sdk.qcloud.com/im/doc/v3/zh-cn/module-EVENT.html Chat SDK 定义的事件列表 }
|
||||
* @property {Object} TYPES {@link https://web.sdk.qcloud.com/im/doc/v3/zh-cn/module-TYPES.html Chat SDK 定义的类型常量}
|
||||
*/
|
||||
export interface ITUIChatEngine {
|
||||
isInited: boolean;
|
||||
chat: ChatSDK;
|
||||
EVENT: any;
|
||||
TYPES: any;
|
||||
eventCenter: IEventCenter;
|
||||
TUIStore: ITUIStore;
|
||||
TUIConversation: ITUIConversationService;
|
||||
TUIChat: ITUIChatService;
|
||||
TUIGroup: ITUIGroupService;
|
||||
TUIUser: ITUIUserService;
|
||||
|
||||
/**
|
||||
* 创建 Chat SDK 实例 & 登录 Chat SDK
|
||||
* @function
|
||||
* @param {LoginParams} options 登录参数
|
||||
* @example
|
||||
* let promise = TUIChatEngine.login({
|
||||
* SDKAppID: xxx,
|
||||
* userID: 'xxx',
|
||||
* userSig: 'xxx',
|
||||
* useUploadPlugin: true, // 使用文件上传插件
|
||||
* });
|
||||
* promise.then(() => {
|
||||
* // 登录成功后进行相关业务逻辑处理
|
||||
* })
|
||||
*/
|
||||
login(options: LoginParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 登出 Chat SDK
|
||||
* @function
|
||||
* @example
|
||||
* let promise = TUIChatEngine.logout();
|
||||
* promise.then(() => {
|
||||
* // 登出成功后进行相关业务逻辑处理
|
||||
* })
|
||||
*/
|
||||
logout(): Promise<any>;
|
||||
|
||||
/**
|
||||
* Chat SDK 是否 ready。SDK ready 后,开发者可调用 SDK 发送消息等 API,使用 SDK 的各项功能。
|
||||
* @function
|
||||
* @example
|
||||
* let isReady = TUIChatEngine.isReady();
|
||||
*/
|
||||
isReady(): boolean;
|
||||
|
||||
/**
|
||||
* 销毁 Chat SDK 实例,SDK 会先 logout,然后断开 WebSocket 长连接,并释放资源。
|
||||
* @function
|
||||
* @example
|
||||
* let promise = TUIChatEngine.destroy();
|
||||
*/
|
||||
destroy(): Promise<any>;
|
||||
|
||||
/**
|
||||
* 设置 SDK 日志级别
|
||||
* @function
|
||||
* @param {number} level 日志级别
|
||||
* - 0 普通级别,日志量较多,接入时建议使用
|
||||
* - 1 release级别,SDK 输出关键信息,生产环境时建议使用
|
||||
* - 2 告警级别,SDK 只输出告警和错误级别的日志
|
||||
* - 3 错误级别,SDK 只输出错误级别的日志
|
||||
* - 4 无日志级别,SDK 将不打印任何日志
|
||||
* @example
|
||||
* TUIChatEngine.setLogLevel(0)
|
||||
*/
|
||||
setLogLevel(level: number): void;
|
||||
|
||||
/**
|
||||
* 模块挂载
|
||||
* @function
|
||||
* @param {string} name 模块名
|
||||
* @param {any} instance 类的实例
|
||||
* @private
|
||||
*/
|
||||
mount(name: string, instance: any): void;
|
||||
|
||||
/**
|
||||
* 获取当前用户 ID
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
getMyUserID(): string;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { func } from '../../type';
|
||||
|
||||
export interface IEventCenter {
|
||||
/**
|
||||
* Service 添加监听事件
|
||||
* @param {string} event 事件名
|
||||
* @param {Function} callback 回调函数
|
||||
*/
|
||||
addEvent(event: string, callback: func): void;
|
||||
|
||||
/**
|
||||
* 移除 chatEngine 内部注册的事件回调
|
||||
*/
|
||||
removeEvents(): void;
|
||||
|
||||
/**
|
||||
* 解绑 IM SDK 事件
|
||||
*/
|
||||
unbindIMEvents(): void;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './event-center';
|
||||
export * from './engine';
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @interface TUIGlobal
|
||||
*/
|
||||
export interface ITUIGlobal {
|
||||
global: any; // 挂在 wx、uni、window 对象
|
||||
isOfficial: boolean;
|
||||
[key: string]: any;
|
||||
|
||||
initOfficial(isOfficial: boolean): void;
|
||||
|
||||
getPlatform(): string;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import type { Conversation } from '@tencentcloud/lite-chat/basic';
|
||||
|
||||
/**
|
||||
* @description ConversationModel 主要负责会话操作和会话数据处理,ConversationModel 不需要开发者进行开发,由 TUIChatEngine 通过 {@link https://web.sdk.qcloud.com/im/doc/chat-engine/global.html#ConversationStore ConversationStore} 的 conversationList 提供给开发者直接使用。
|
||||
* @interface ConversationModel
|
||||
* @property {String} conversationID - 会话 ID。会话ID组成方式:<br/>
|
||||
* - `C2C${userID}`(单聊)
|
||||
* - `GROUP${groupID}`(群聊)
|
||||
* @property {String} type - 会话类型,具体如下:<br/>
|
||||
* | 类型 | 含义 |
|
||||
* | :--- | :---- |
|
||||
* | TUIChatEngine.TYPES.CONV_C2C | C2C(Client to Client, 端到端)会话 |
|
||||
* | TUIChatEngine.TYPES.CONV_GROUP | GROUP(群组)会话 |
|
||||
* | TUIChatEngine.TYPES.CONV_SYSTEM | SYSTEM(系统)会话。该会话只能接收来自系统的通知消息,不能发送消息。 |
|
||||
* @property {String} subType - 群组会话的群组类型,具体如下:<br/>
|
||||
* | 类型 | 含义 |
|
||||
* | :--- | :---- |
|
||||
* | TUIChatEngine.TYPES.GRP_WORK | 好友工作群 |
|
||||
* | TUIChatEngine.TYPES.GRP_PUBLIC | 陌生人社交群 |
|
||||
* | TUIChatEngine.TYPES.GRP_MEETING | 临时会议群 |
|
||||
* | TUIChatEngine.TYPES.GRP_AVCHATROOM | 直播群 |
|
||||
* @property {Number} unreadCount - 未读计数。TUIChatEngine.TYPES.GRP_MEETING / TUIChatEngine.TYPES.GRP_AVCHATROOM 类型的群组会话不记录未读计数,该字段值为0
|
||||
* @property {Object} lastMessage - 会话最新的消息
|
||||
* @property {String} lastMessage.nick - 群会话最新消息的发送者的昵称,C2C 会话为 ''
|
||||
* @property {String} lastMessage.nameCard - 群会话最新消息的发送者的群名片,C2C 会话为 ''
|
||||
* @property {Number} lastMessage.lastTime - 当前会话最新消息的时间戳,单位:秒
|
||||
* @property {Number} lastMessage.lastSequence - 当前会话的最新消息的 Sequence
|
||||
* @property {String} lastMessage.fromAccount - 最新消息来源用户的 userID
|
||||
* @property {Boolean} lastMessage.isRevoked - 会话最新的消息是否已被撤回,true 表示已撤回,默认值为 false
|
||||
* @property {String|null} lastMessage.revoker - 消息撤回者的 userID
|
||||
* @property {Boolean} lastMessage.isPeerRead - 对端是否已读 C2C 会话的最新消息,默认值为 false
|
||||
* @property {String} lastMessage.messageForShow - 最新消息的内容,用于展示。可能值:文本消息内容、"[图片]"、"[语音]"、"[位置]"、"[表情]"、"[文件]"、"[自定义消息]"。<br/>
|
||||
* 若该字段不满足您的需求,您可以使用 payload 来自定义渲染。
|
||||
* @property {String} lastMessage.type - 消息类型,具体如下:<br/>
|
||||
* | 类型 | 含义 |
|
||||
* | :--- | :---- |
|
||||
* | TUIChatEngine.TYPES.MSG_TEXT | 文本消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_IMAGE | 图片消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_SOUND | 音频消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_AUDIO | 音频消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_VIDEO | 视频消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_FILE | 文件消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_LOCATION | 地理位置消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_CUSTOM | 自定义消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_GRP_TIP | 群提示消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_GRP_SYS_NOTICE | 群系统通知消息 |
|
||||
* @property {Object} lastMessage.payload - 消息的内容,收到的音频 / 文件消息的 payload 中没有 url 字段。
|
||||
* @property {Group} groupProfile - 群会话的群组资料
|
||||
* @property {Profile} userProfile - C2C会话的用户资料
|
||||
* @property {GroupAtInfo[]} groupAtInfoList - 群会话的 at 信息列表,接入侧可根据此信息在会话列表展示【有人@我】【@所有人】等效果。
|
||||
* @property {String} remark - 好友备注,只有C2C会话且对端是我的好友,且我设置过此好友的备注才有值
|
||||
* @property {Boolean} isPinned - 会话是否置顶
|
||||
* @property {String} messageRemindType - 消息提醒类型,具体如下:<br/>
|
||||
* - TUIChatEngine.TYPES.MSG_REMIND_ACPT_AND_NOTE 在线正常接收消息,离线时会有厂商的离线推送通知(Web 和小程序端无离线推送)
|
||||
* - TUIChatEngine.TYPES.MSG_REMIND_DISCARD 在线和离线都拒收消息
|
||||
* - TUIChatEngine.TYPES.MSG_REMIND_ACPT_NOT_NOTE 在线正常接收消息,离线不会有推送通知(消息免打扰)
|
||||
* @property {Array} markList - 会话标记列表,具体如下:<br/>
|
||||
* - TUIChatEngine.TYPES.CONV_MARK_TYPE_STAR 会话标星
|
||||
* - TUIChatEngine.TYPES.CONV_MARK_TYPE_UNREAD 会话标记未读(重要会话)
|
||||
* - TUIChatEngine.TYPES.CONV_MARK_TYPE_FOLD 会话折叠
|
||||
* - TUIChatEngine.TYPES.CONV_MARK_TYPE_HIDE 会话隐藏
|
||||
* @property {String} customData - 会话自定义数据
|
||||
* @property {Array} conversationGroupList - 会话所属分组列表
|
||||
* @property {String} draftText - 会话草稿
|
||||
* @property {Boolean} isMuted 会话是否已设置免打扰,默认为 false
|
||||
* @property {Number} operationType 群组操作类型, 默认 0。4-被踢出群组 5-群组被解散 8-主动退群
|
||||
*/
|
||||
export interface IConversationModel {
|
||||
conversationID: string;
|
||||
type: string;
|
||||
subType: string;
|
||||
unreadCount: number;
|
||||
lastMessage?: {
|
||||
nick: string;
|
||||
nameCard: string;
|
||||
lastTime: number | string;
|
||||
lastSequence: string;
|
||||
fromAccount: string;
|
||||
isRevoked: boolean;
|
||||
revoker?: string;
|
||||
isPeerRead: boolean;
|
||||
messageForShow: string;
|
||||
type: string;
|
||||
payload: any;
|
||||
};
|
||||
groupProfile?: any;
|
||||
userProfile?: any;
|
||||
groupAtInfoList?: any[];
|
||||
remark: string;
|
||||
isPinned: boolean;
|
||||
messageRemindType: string;
|
||||
markList: string[];
|
||||
customData: string;
|
||||
conversationGroupList: any[];
|
||||
draftText: string;
|
||||
isMuted: boolean;
|
||||
operationType: number; // 群组会话的操作类型
|
||||
_conversation: any; // 保存原始 conversation 实例数据
|
||||
|
||||
/**
|
||||
* 更新 conversationModel 属性
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
updateProperties(options: Conversation): void;
|
||||
|
||||
/**
|
||||
* 更新 operationType 属性
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
updateOperationType(operationType: number): void;
|
||||
|
||||
/**
|
||||
* 获取 IM SDK 提供的 conversation 对象
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
getConversation(): any;
|
||||
|
||||
// 以下方法用于处理 conversation 需要展示的数据
|
||||
/**
|
||||
* 获取会话头像
|
||||
* @function
|
||||
* @example
|
||||
* let avatar = conversationModel.getAvatar();
|
||||
*/
|
||||
getAvatar(): string;
|
||||
|
||||
/**
|
||||
* 获取会话名称
|
||||
* @function
|
||||
* @example
|
||||
* let name = conversationModel.getShowName();
|
||||
*/
|
||||
getShowName(): string;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type TencentCloudChat from '@tencentcloud/lite-chat/basic';
|
||||
/**
|
||||
* @description GroupModel 的属性与 IM SDK Group 属性字段保持一致,详情可参考:{@link https://web.sdk.qcloud.com/im/doc/zh-cn/Group.html Group}。
|
||||
* @interface GroupModel
|
||||
*/
|
||||
export interface IGroupModel {
|
||||
groupID: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
type: TencentCloudChat.TYPES.GRP_WORK
|
||||
| TencentCloudChat.TYPES.GRP_PUBLIC
|
||||
| TencentCloudChat.TYPES.GRP_MEETING
|
||||
| TencentCloudChat.TYPES.GRP_AVCHATROOM
|
||||
| TencentCloudChat.TYPES.GRP_COMMUNITY;
|
||||
introduction: string;
|
||||
notification: string;
|
||||
ownerID: string;
|
||||
createTime: number;
|
||||
infoSequence?: number;
|
||||
lastInfoTime?: number;
|
||||
selfInfo?: {
|
||||
role?: string;
|
||||
messageRemindType?: string;
|
||||
joinTime?: number;
|
||||
nameCard?: string;
|
||||
userID?: string;
|
||||
memberCustomField?: any[];
|
||||
};
|
||||
lastMessage?: any;
|
||||
nextMessageSeq: number;
|
||||
memberCount: number;
|
||||
maxMemberCount: number;
|
||||
muteAllMembers: boolean;
|
||||
joinOption: string;
|
||||
groupCustomField?: any[];
|
||||
isSupportTopic: boolean;
|
||||
groupAttributes: any;
|
||||
groupCounters: any;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './conversation';
|
||||
export * from './group';
|
||||
export * from './message';
|
||||
@@ -0,0 +1,326 @@
|
||||
import type { Message } from '@tencentcloud/lite-chat/basic';
|
||||
import type TencentCloudChat from '@tencentcloud/lite-chat/basic';
|
||||
import type { ModifyMessageParams, ReactionInfo } from '../../type';
|
||||
|
||||
/**
|
||||
* @description MessageModel 主要负责消息操作和消息上屏数据处理,MessageModel 不需要开发者进行开发,由 TUIChatEngine 通过 {@link https://web.sdk.qcloud.com/im/doc/chat-engine/global.html#ChatStore ChatStore} 的 messageList 提供给开发者直接使用。<br/>
|
||||
* @interface IMessageModel
|
||||
* @property {String} ID - 消息 ID
|
||||
* @property {String} type - 消息类型,具体如下:<br/>
|
||||
* | 类型 | 含义 |
|
||||
* | :--- | :---- |
|
||||
* | TUIChatEngine.TYPES.MSG_TEXT | 文本消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_IMAGE | 图片消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_SOUND | 音频消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_AUDIO | 音频消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_VIDEO | 视频消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_FILE | 文件消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_CUSTOM | 自定义消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_MERGER | 合并消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_LOCATION | 位置消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_FACE | 表情消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_GRP_TIP | 群提示消息 |
|
||||
* | TUIChatEngine.TYPES.MSG_GRP_SYS_NOTICE | 群系统通知消息 |
|
||||
* @property {Object} payload - 消息的内容,详细内容请查看: [Message](https://web.sdk.qcloud.com/im/doc/v3/zh-cn/Message.html)
|
||||
* @property {String} conversationID - 消息所属的会话 ID
|
||||
* @property {String} conversationType - 消息所属会话的类型,具体如下:<br/>
|
||||
* | 类型 | 含义 |
|
||||
* | :--- | :---- |
|
||||
* | TUIChatEngine.TYPES.CONV_C2C | C2C(Client to Client, 端到端) 会话 |
|
||||
* | TUIChatEngine.TYPES.CONV_GROUP | GROUP(群组) 会话 |
|
||||
* | TUIChatEngine.TYPES.CONV_SYSTEM | SYSTEM(系统) 会话 |
|
||||
* @property {String} to - 接收方的 userID
|
||||
* @property {String} from - 发送方的 userID,在消息发送时,会默认设置为当前登录的用户
|
||||
* @property {String} flow - 消息的流向<br/>
|
||||
* - in 为收到的消息
|
||||
* - out 为发出的消息
|
||||
* @property {Number} time - 消息时间戳。单位:秒
|
||||
* @property {String} status - 消息状态
|
||||
* - unSend(未发送)
|
||||
* - success(发送成功)
|
||||
* - fail(发送失败)
|
||||
* @property {Boolean} isRevoked =false - 是否被撤回的消息,true 标识被撤回的消息
|
||||
* @property {String} priority=TUIChatEngineTYPES.MSG_PRIORITY_NORMAL - 消息优先级,用于群聊
|
||||
* @property {String} nick='' 消息发送者的昵称(在 AVChatRoom 内支持,需提前调用 [TUIUserService.updateMyProfile](https://web.sdk.qcloud.com/im/doc/chat-engine/ITUIUserService.html#updateMyProfile) 设置)
|
||||
* @property {String} avatar='' 消息发送者的头像地址(在 AVChatRoom 内支持,需提前调用 [TUIUserService.updateMyProfile](https://web.sdk.qcloud.com/im/doc/chat-engine/ITUIUserService.html#updateMyProfile) 设置)
|
||||
* @property {Boolean} isPeerRead=false - C2C 消息对端是否已读,true 标识对端已读
|
||||
* @property {String} nameCard='' 非直播群消息发送者的群名片(也可称之为消息发送者的群昵称),需提前调用 [TUIGroupService.setGroupMemberNameCard](https://web.sdk.qcloud.com/im/doc/chat-engine/ITUIGroupService.html#setGroupMemberNameCard) 设置
|
||||
* @property {Array} atUserList 群聊时此字段存储被 at 的群成员的 userID
|
||||
* @property {String} cloudCustomData='' - 消息自定义数据(云端保存,会发送到对端,程序卸载重装后还能拉取到)
|
||||
* @property {Boolean} isDeleted=false - 是否被删除的消息,true 标识被删除的消息
|
||||
* @property {Boolean} isModified=false - 是否被修改过,true 标识被修改过的消息
|
||||
* @property {Boolean} needReadReceipt=false - 是否需要已读回执,true 标识需要(需要您购买旗舰版套餐)
|
||||
* @property {Object} readReceiptInfo={readCount,unreadCount,isPeerRead} - 消息已读回执信息
|
||||
* @property {Number|undefined} readReceiptInfo.readCount - 群消息已读数<br/>
|
||||
* 如果想要查询哪些群成员已读了消息,可调用 [TUIChatService.getGroupMessageReadMemberList](https://web.sdk.qcloud.com/im/doc/chat-engine/ITUIChatService.html#getGroupMessageReadMemberList)
|
||||
* @property {Number|undefined} readReceiptInfo.unreadCount - 群消息未读数
|
||||
* @property {Boolean|undefined} readReceiptInfo.isPeerRead - C2C消息对端是否已发送已读回执,消息发送方收到已读回执通知或拉漫游时会更新此属性
|
||||
* @property {Boolean} isBroadcastMessage=false - 对所有直播群广播消息,true 标识直播群广播消息(需要您购买旗舰版套餐)
|
||||
* @property {Boolean} isSupportExtension=false - 是否支持消息扩展,true 支持 false 不支持(需要您购买旗舰版套餐)
|
||||
* @property {String|null} revoker - 消息撤回者的 userID
|
||||
* @property {Number} progress - 图片、视频、语音、文件消息上传进度, 默认 0
|
||||
* @property {Object} revokerInfo - 消息撤回者的信息。
|
||||
* @property {String} revokerInfo.avatar - 消息撤回者的头像
|
||||
* @property {String} revokerInfo.nick - 消息撤回者的昵称
|
||||
* @property {String} revokerInfo.userID - 消息撤回者的 userID
|
||||
* @property {String} revokeReason - 消息撤回的原因。
|
||||
* @property {Boolean} hasRiskContent - 语音、视频消息是否被标记为有安全风险的消息,默认为 false。
|
||||
* - 只有在开通【云端审核】功能后才生效,【云端审核】开通流程请参考 [云端审核功能](https://cloud.tencent.com/document/product/269/83795#.E4.BA.91.E7.AB.AF.E5.AE.A1.E6.A0.B8.E5.8A.9F.E8.83.BD)。
|
||||
* @property {Array<ReactionInfo>} reactionList - 需要您购买旗舰版才支持此功能,购买旗舰版能力并重新登录后,拉漫游时会自动获取表情回复摘要信息。
|
||||
*/
|
||||
export interface IMessageModel {
|
||||
ID: string;
|
||||
type: TencentCloudChat.TYPES;
|
||||
payload: any;
|
||||
conversationID: string;
|
||||
conversationType: TencentCloudChat.TYPES;
|
||||
to: string;
|
||||
from: string;
|
||||
flow: string;
|
||||
time: number;
|
||||
status: string;
|
||||
isRevoked: boolean;
|
||||
priority: TencentCloudChat.TYPES;
|
||||
nick: string;
|
||||
avatar: string;
|
||||
isPeerRead: boolean;
|
||||
nameCard: string;
|
||||
atUserList: string[];
|
||||
cloudCustomData: string;
|
||||
isDeleted: boolean;
|
||||
isModified: boolean;
|
||||
needReadReceipt: boolean;
|
||||
readReceiptInfo: any;
|
||||
isBroadcastMessage: boolean;
|
||||
isSupportExtension: boolean;
|
||||
receiverList?: string[];
|
||||
revoker: string;
|
||||
sequence: number;
|
||||
progress: number;
|
||||
revokerInfo: { userID: string; nick: string; avatar: string };
|
||||
revokeReason: string;
|
||||
hasRiskContent: boolean;
|
||||
reactionList: ReactionInfo[];
|
||||
|
||||
/**
|
||||
* 更新 messageModel 属性
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
updateProperties(options: Message): void;
|
||||
|
||||
/**
|
||||
* 获取 Chat SDK 提供的 message 实例
|
||||
* @function
|
||||
* @private
|
||||
* @example
|
||||
* let message = messageModel.getMessage();
|
||||
*/
|
||||
getMessage(): Message;
|
||||
|
||||
/**
|
||||
* 修改消息
|
||||
*
|
||||
* @param {ModifyMessageParams} options 修改的内容
|
||||
* @function
|
||||
* @example
|
||||
* let promise = messageModel.modifyMessage(options);
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
modifyMessage(options: ModifyMessageParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 撤回单聊消息或者群聊消息。撤回成功后,消息对象的 isRevoked 属性值为 true。
|
||||
* @function
|
||||
* @example
|
||||
* let promise = messageModel.revokeMessage();
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
revokeMessage(): Promise<any>;
|
||||
|
||||
/**
|
||||
* 重发消息的接口,当消息发送失败时,可调用该接口进行重发
|
||||
* @function
|
||||
* @example
|
||||
* let promise = messageModel.resendMessage();
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
resendMessage(): Promise<any>;
|
||||
|
||||
/**
|
||||
* 删除消息的接口。删除成功后,被删除消息的 isDeleted 属性值为 true。
|
||||
* @function
|
||||
* @example
|
||||
* let promise = messageModel.deleteMessage();
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
deleteMessage(): Promise<any>;
|
||||
|
||||
/**
|
||||
* 引用当前消息
|
||||
* @function
|
||||
* @example
|
||||
* let message = messageModel.quoteMessage();
|
||||
*/
|
||||
quoteMessage(): Message;
|
||||
|
||||
/**
|
||||
* 回复当前消息
|
||||
* @function
|
||||
* @example
|
||||
* let message = messageModel.replyMessage();
|
||||
*/
|
||||
replyMessage(): Message;
|
||||
|
||||
/**
|
||||
* 设置消息扩展
|
||||
* @function
|
||||
* @param {Array<object>} extensions 扩展信息
|
||||
* @private
|
||||
* @example
|
||||
* let promise = messageModel.setMessageExtensions([
|
||||
* { key: 'a', value: '1' },
|
||||
* { key: 'b', value: '2' }
|
||||
* ]);
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
setMessageExtensions(extensions: object[]): Promise<any>;
|
||||
|
||||
/**
|
||||
* 删除消息扩展
|
||||
* @function
|
||||
* @param {Array<string>|undefined} keyList 消息扩展 key 列表,不传 keyList 表示删除所有扩展 key
|
||||
* @private
|
||||
* @example
|
||||
* // 删除部分 key
|
||||
* let promise = messageModel.deleteMessageExtensions(['a', 'b']);
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
* // 删除全部 key
|
||||
* let promise = messageModel.deleteMessageExtensions();
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
deleteMessageExtensions(keyList?: string[]): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取消息扩展
|
||||
* @function
|
||||
* @private
|
||||
* @example
|
||||
* let promise = messageModel.getMessageExtensions();
|
||||
* promise.then((chatResponse) => {
|
||||
* // 消息扩展信息
|
||||
* const { extensions } = chatResponse.data;
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getMessageExtensions(): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取信令信息
|
||||
* @function
|
||||
* @example
|
||||
* let signaling = messageModel.getSignalingInfo();
|
||||
* // signaling != null 说明是信令消息,可以进行信令相关的业务逻辑处理
|
||||
* // signaling = null 说明是非信令消息
|
||||
* if (signaling) {
|
||||
* console.log(signaling)
|
||||
* }
|
||||
*/
|
||||
getSignalingInfo(): Record<string, any> | null;
|
||||
|
||||
/**
|
||||
* 获取消息展示内容 <br/>
|
||||
* 注意1:以下解析为默认解析行为,如果需要自定义解析,可以通过 messageModel 结构体内容自行解析。<br/>
|
||||
* 注意2:该接口不支持群系统消息解析。<br/>
|
||||
* 注意3:群提示消息不支持返回 showName。<br/>
|
||||
* 注意4:showName 显示优先级如下:
|
||||
* - 单聊:remark > nick > userID
|
||||
* - 群聊:remark > nameCard > nick > userID
|
||||
* @function
|
||||
* @example
|
||||
* // 文本消息返回结果
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.showName - 消息发送方名称
|
||||
* // result.text - 文本消息展示内容
|
||||
* // result.name - 文本消息名称,值为 text 或 img, 返回 img 时标识是 emoji 表情消息
|
||||
* // result.type - 文本消息类型,自定义 emoji 表情消息时返回 custom,UIKit 默认的 emoji 表情和普通文本消息不返回 type 字段(v2.2.0 起支持)
|
||||
* // result.emojiKey - emoji 表情的 key(v2.2.0 起支持)
|
||||
* // result.src - emoji 表情消息展示链接,type = custom 时返回空字符串,需要 UIKit 层根据 baseUrl 和 emojiKey 拼接完整链接
|
||||
* @example
|
||||
* // 表情消息返回结果
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.showName - 消息发送方名称
|
||||
* // result.name - 表情消息名称
|
||||
* // retsult.type - 表情消息类型,自定义 face 表情消息时返回 custom,UIKit 默认 face 表情包 type 返回 ‘’(v2.2.0 起支持)
|
||||
* // result.url - 表情消息展示链接,type = custom 时返回空字符串,需要 UIKit 层根据 baseUrl 和 name 拼接完整链接
|
||||
* @example
|
||||
* // 地理位置消息返回结果
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.showName - 消息发送方名称
|
||||
* // result.lon - 经度
|
||||
* // result.lat - 纬度
|
||||
* // result.href - 地图跳转链接
|
||||
* // result.url - 地图展示链接
|
||||
* // result.description - 描述信息
|
||||
* @example
|
||||
* // 图片消息返回结果,默认返回的是原图信息,如果需要缩略图或大图信息请从 messageModel.payload 中获取
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.showName - 消息发送方名称
|
||||
* // result.url - 图片访问链接
|
||||
* // result.width - 图片宽度
|
||||
* // result.width - 图片高度
|
||||
* @example
|
||||
* // 语音消息返回结果
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.showName - 消息发送方名称
|
||||
* // result.url - 语音播放链接
|
||||
* // result.second - 语音时长
|
||||
* @example
|
||||
* // 视频消息返回结果
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.showName - 消息发送方名称
|
||||
* // result.url - 视频播放链接
|
||||
* // result.snapshotUrl - 视频封面图链接
|
||||
* // result.snapshotWidth - 视频封面图宽度
|
||||
* // result.snapshotHeight - 视频封面图高度
|
||||
* @example
|
||||
* // 文件消息返回结果
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.showName - 消息发送方名称
|
||||
* // result.url - 文件下载链接
|
||||
* // result.name - 文件名称
|
||||
* // result.size - 文件大小
|
||||
* @example
|
||||
* // 自定义消息返回结果
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.showName - 消息发送方名称
|
||||
* // result.custom - 自定义消息展示内容
|
||||
* // result.businessID - 业务标识信息,创建群组自定义消息的 businessID 是 group_create,其他的自定义消息返回为空字符串
|
||||
* @example
|
||||
* // 合并消息返回结果
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.showName - 消息发送方名称
|
||||
* // 合并消息返回内容是 message.payload 内容
|
||||
* @example
|
||||
* // 群提示消息返回结果
|
||||
* const result = messageModel.getMessageContent();
|
||||
* // result.text - 群提示消息展示内容
|
||||
*/
|
||||
getMessageContent(): Record<string, any>;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './tui-conversation';
|
||||
export * from './tui-chat';
|
||||
export * from './message-handler';
|
||||
export * from './tui-group';
|
||||
export * from './tui-user';
|
||||
export * from './tui-report';
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import type { Message } from '@tencentcloud/lite-chat/basic';
|
||||
|
||||
// message handler 因为需要在 MessageModel 中调用,需要保留声明文件
|
||||
export interface IMessageHandler {
|
||||
/**
|
||||
* 处理文本消息
|
||||
* @param {Message} message 文本消息
|
||||
*/
|
||||
handleTextMessage(message: Message): object;
|
||||
|
||||
/**
|
||||
* 处理图片消息
|
||||
* @param {Message} message 图片消息
|
||||
*/
|
||||
handleImageMessage(message: Message): object;
|
||||
|
||||
/**
|
||||
* 处理视频消息
|
||||
* @param {Message} message 视频消息
|
||||
*/
|
||||
handleVideoMessage(message: Message): object;
|
||||
|
||||
/**
|
||||
* 处理自定义消息
|
||||
* @param {Message} message 自定义消息
|
||||
*/
|
||||
handleCustomMessage(message: Message): object;
|
||||
|
||||
/**
|
||||
* 处理群提示消息
|
||||
* @param {Message} message 群 tips 消息
|
||||
*/
|
||||
handleGroupTipsMessage(message: Message): object;
|
||||
|
||||
/**
|
||||
* 处理群系统消息
|
||||
* @param {Message} message 群系统消息
|
||||
*/
|
||||
handleGroupSystemMessage(message: Message): object;
|
||||
|
||||
/**
|
||||
* 处理音视频通话信令
|
||||
* @param {any} message 会话最后一条消息或漫游消息
|
||||
*/
|
||||
handleCallKitSignaling(message: any): string | undefined;
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
import type { Message } from '@tencentcloud/lite-chat/basic';
|
||||
import type TUIBase from '../../tui-base';
|
||||
import type {
|
||||
SendMessageParams,
|
||||
GetMessageListParams,
|
||||
SendMessageOptions,
|
||||
GetMessageListHoppingParams,
|
||||
} from '../../type';
|
||||
import type { IMessageHandler } from './message-handler';
|
||||
|
||||
/**
|
||||
* @interface TUIChatService
|
||||
*/
|
||||
export interface ITUIChatService extends TUIBase {
|
||||
messageHandler: IMessageHandler;
|
||||
|
||||
/**
|
||||
* 初始化 Service
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
init: () => void;
|
||||
|
||||
/**
|
||||
* 更新 store 里的 messageList
|
||||
* @param messageList 要进行更新的 messageList
|
||||
* @param type 更新类型
|
||||
* @private
|
||||
*/
|
||||
updateMessageList(messageList: Message[], type: string): void;
|
||||
|
||||
/**
|
||||
* 发送文本消息
|
||||
* @function
|
||||
* @param {SendMessageParams} options 文本消息相关参数
|
||||
* @param {SendMessageOptions}[sendMessageOptions] 消息发送选项
|
||||
* @example
|
||||
* // 发送普通文本消息
|
||||
* let promise = TUIChatService.sendTextMessage({
|
||||
* payload: {
|
||||
* text: 'Hello world!'
|
||||
* },
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
* @example
|
||||
* // 发送普通文本消息并携带消息自定义数据
|
||||
* let promise = TUIChatService.sendTextMessage({
|
||||
* payload: {
|
||||
* text: 'Hello world!'
|
||||
* },
|
||||
* cloudCustomData: 'your cloud custom data'
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
sendTextMessage(
|
||||
options: SendMessageParams,
|
||||
sendMessageOptions?: SendMessageOptions,
|
||||
): Promise<any>;
|
||||
|
||||
/**
|
||||
* 发送附带 @ 提醒功能的文本消息
|
||||
* @function
|
||||
* @param {SendMessageParams} options 附带 @ 提醒功能的文本消息相关参数
|
||||
* @param {SendMessageOptions}[sendMessageOptions] 消息发送选项
|
||||
* @example
|
||||
* // 发送 @ 消息
|
||||
* let promise = TUIChatService.sendTextAtMessage({
|
||||
* payload: {
|
||||
* text: '@denny @lucy @所有人 今晚聚餐,收到的请回复1',
|
||||
* atUserList: ['denny', 'lucy', TUIChatEngine.TYPES.MSG_AT_ALL] // 'denny' 'lucy' 都是 userID,而非昵称
|
||||
* },
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
sendTextAtMessage(
|
||||
options: SendMessageParams,
|
||||
sendMessageOptions?: SendMessageOptions,
|
||||
): Promise<any>;
|
||||
|
||||
/**
|
||||
* 发送图片消息
|
||||
* @function
|
||||
* @param {SendMessageParams} options 图片消息相关参数
|
||||
* @param {SendMessageOptions}[sendMessageOptions] 消息发送选项
|
||||
* @example
|
||||
* // 发送图片消息
|
||||
* let promise = TUIChatService.sendImageMessage({
|
||||
* payload: {
|
||||
* file: file
|
||||
* },
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
sendImageMessage(
|
||||
options: SendMessageParams,
|
||||
sendMessageOptions?: SendMessageOptions,
|
||||
): Promise<any>;
|
||||
|
||||
/**
|
||||
* 发送音频消息
|
||||
* @function
|
||||
* @param {SendMessageParams} options 音频消息相关参数
|
||||
* @param {SendMessageOptions}[sendMessageOptions] 消息发送选项
|
||||
* @example
|
||||
* // 发送音频消息
|
||||
* let promise = TUIChatService.sendAudioMessage({
|
||||
* payload: {
|
||||
* file: file
|
||||
* },
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
sendAudioMessage(
|
||||
options: SendMessageParams,
|
||||
sendMessageOptions?: SendMessageOptions,
|
||||
): Promise<any>;
|
||||
|
||||
/**
|
||||
* 发送视频消息
|
||||
* @function
|
||||
* @param {SendMessageParams} options 视频消息相关参数
|
||||
* @param {SendMessageOptions}[sendMessageOptions] 消息发送选项
|
||||
* @example
|
||||
* // 发送视频消息
|
||||
* let promise = TUIChatService.sendVideoMessage({
|
||||
* payload: {
|
||||
* file: file
|
||||
* },
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
sendVideoMessage(
|
||||
options: SendMessageParams,
|
||||
sendMessageOptions?: SendMessageOptions,
|
||||
): Promise<any>;
|
||||
|
||||
/**
|
||||
* 发送自定义消息
|
||||
* @function
|
||||
* @param {SendMessageParams} options 自定义消息相关参数
|
||||
* @param {SendMessageOptions}[sendMessageOptions] 消息发送选项
|
||||
* @example
|
||||
* // 发送自定义消息
|
||||
* let promise = TUIChatService.sendCustomMessage({
|
||||
* payload: {
|
||||
* data: 'dice', // 用于标识该消息是骰子类型消息
|
||||
* description: String(random(1,6)), // 获取骰子点数
|
||||
* extension: ''
|
||||
* },
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
sendCustomMessage(
|
||||
options: SendMessageParams,
|
||||
sendMessageOptions?: SendMessageOptions,
|
||||
): Promise<any>;
|
||||
|
||||
/**
|
||||
* 重发消息的接口,当消息发送失败时,可调用该接口进行重发
|
||||
* @function
|
||||
* @param {Message} message 需要重发的消息对象
|
||||
* @private
|
||||
*/
|
||||
resendMessage(message: Message): Promise<any>;
|
||||
|
||||
/**
|
||||
* 分页拉取指定会话的消息列表的接口,当用户进入会话首次渲染消息列表或者用户“下拉查看更多消息”时,需调用该接口。
|
||||
* @function
|
||||
* @param {GetMessageListParams} [options] 获取漫游消息参数
|
||||
* @example
|
||||
* let promise = TUIChatService.getMessageList();
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getMessageList(options?: GetMessageListParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 根据指定的消息 sequence 或 消息时间拉取会话的消息列表
|
||||
* @function
|
||||
* @param {GetMessageListHoppingParams} options 消息实例
|
||||
* @private
|
||||
*/
|
||||
getMessageListHopping(options?: GetMessageListHoppingParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 清空单聊或群聊本地及云端的消息(不删除会话)
|
||||
* @function
|
||||
* @param {conversationID} string 会话 ID
|
||||
* @example
|
||||
* // 清空群聊 groupID 为 test 的历史消息
|
||||
* let promise = TUIChatService.clearHistoryMessage('GROUPtest');
|
||||
* promise.then((chatResponse) => {
|
||||
* const { result } = chatResponse.data;
|
||||
* });
|
||||
*/
|
||||
clearHistoryMessage(conversationID: string): Promise<any>;
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import type { PinConversationParams, MuteConversationParams, SetConversationDraftParams, MarkConversationParams, DeleteConversationParams } from '../../type';
|
||||
|
||||
/**
|
||||
* @interface TUIConversationService
|
||||
*/
|
||||
export interface ITUIConversationService {
|
||||
|
||||
/**
|
||||
* 初始化 Service
|
||||
* @private
|
||||
*/
|
||||
init(): void;
|
||||
|
||||
/**
|
||||
* 切换会话
|
||||
* @function
|
||||
* @param {String} conversationID 会话 ID,conversationID 生成规则:单聊(`C2C${userID}`),群聊(`GROUP${groupID}`)。
|
||||
* @example
|
||||
* // 切换到 public001 群会话
|
||||
* let promise = TUIConversationService.switchConversation('GROUPpublic001');
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
switchConversation(conversationID: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
getConversationList(): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取会话资料
|
||||
* 注意:Service API 主要用于跨组件时调用
|
||||
* @function
|
||||
* @param {String} conversationID 会话 ID
|
||||
* @example
|
||||
* // 获取 public001 群会话资料
|
||||
* let promise = TUIConversationService.getConversationProfile('GROUPpublic001');
|
||||
* promise.then((chatResponse) => {
|
||||
* console.log(chatResponse.data.conversation); // 会话资料
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getConversationProfile(conversationID: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* 设置会话已读上报
|
||||
* @function
|
||||
* @param {String} conversationID 会话 ID
|
||||
* @example
|
||||
* let promise = TUIConversationService.setMessageRead('GROUPpublic001');
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
setMessageRead(conversationID: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* 一键清空历史消息
|
||||
* @function
|
||||
* @param {String} conversationID 会话 ID
|
||||
* @example
|
||||
* @private
|
||||
*/
|
||||
clearHistoryMessage(conversationID: string): Promise<any>;
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
import type {
|
||||
AddMemberParams,
|
||||
ChangGroupOwnerParams,
|
||||
CountersParams,
|
||||
CreateGroupParams,
|
||||
DeleteMemberParams,
|
||||
GetGroupProfileParams,
|
||||
GetMemberListParams,
|
||||
GetMemberProfileParams,
|
||||
GroupAttrParams,
|
||||
JoinGroupParams,
|
||||
KeyListParams,
|
||||
MarkMemberParams,
|
||||
SetCountersParams,
|
||||
SetMemberCustomFiledParams,
|
||||
SetMemberMuteParams,
|
||||
SetMemberNameCardParams,
|
||||
SetMemberRoleParams,
|
||||
UpdateGroupParams,
|
||||
handleGroupApplicationParams,
|
||||
} from '../../type';
|
||||
|
||||
/**
|
||||
* @interface TUIGroupService
|
||||
*/
|
||||
export interface ITUIGroupService {
|
||||
/**
|
||||
* 初始化 Service
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
init(): void;
|
||||
|
||||
/**
|
||||
* 切换群组
|
||||
* @function
|
||||
* @param {String} groupID 群组ID
|
||||
* @example
|
||||
* // 切换到 group1 群
|
||||
* let promise = TUIGroupService.switchGroup('group1');
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
switchGroup(groupID: string): void;
|
||||
|
||||
/**
|
||||
* 获取群详细资料
|
||||
* @function
|
||||
* @param {GetGroupProfileParams} options 获取群详细资料参数
|
||||
* @example
|
||||
* let promise = TUIGroupService.getGroupProfile({
|
||||
* groupID: 'group1',
|
||||
* groupCustomFieldFilter: ['key1','key2']
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getGroupProfile(options: GetGroupProfileParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 更新群详细资料
|
||||
* @function
|
||||
* @param {UpdateGroupParams} options 更新群详细资料参数
|
||||
* @example
|
||||
* let promise = TUIGroupService.updateGroupProfile({
|
||||
* groupID: 'group1',
|
||||
* name: 'new name', // 修改群名称
|
||||
* introduction: 'this is introduction.', // 修改群简介
|
||||
* groupCustomField: [{ key: 'group_level', value: 'high'}] // 修改群组维度自定义字段
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
* @example
|
||||
* let promise = TUIGroupService.updateGroupProfile({
|
||||
* groupID: 'group1',
|
||||
* muteAllMembers: true, // true 表示全体禁言,false表示取消全体禁言
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
updateGroupProfile(options: UpdateGroupParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 创建群组
|
||||
* @function
|
||||
* @param {CreateGroupParams} options 创建群组参数
|
||||
* @example
|
||||
* let promise = TUIGroupService.createGroup({
|
||||
* type: TUIChatEngine.TYPES.GRP_WORK,
|
||||
* name: 'newGroup',
|
||||
* memberList: [{
|
||||
* userID: 'user1',
|
||||
* // 群成员维度的自定义字段,默认情况是没有的,需要开通,详情请参阅自定义字段
|
||||
* memberCustomField: [{key: 'group_member_test', value: 'test'}]
|
||||
* }, {
|
||||
* userID: 'user2'
|
||||
* }] // 如果填写了 memberList,则必须填写 userID
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
createGroup(options: CreateGroupParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 解散群组
|
||||
* @function
|
||||
* @param {string} groupID 群组ID
|
||||
* @example
|
||||
* let promise = TUIGroupService.dismissGroup('group1');
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
dismissGroup(groupID: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* 通过 groupID 搜索群组
|
||||
* @function
|
||||
* @param {string} groupID 群组ID
|
||||
* @example
|
||||
* let promise = TUIGroupService.searchGroupByID('group1');
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
searchGroupByID(groupID: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* 申请加群
|
||||
* @function
|
||||
* @param {JoinGroupParams} options 加群参数
|
||||
* @example
|
||||
* let promise = TUIGroupService.joinGroup({
|
||||
* groupID: 'group1',
|
||||
* applyMessage: 'xxxx'
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
joinGroup(options: JoinGroupParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 退出群组
|
||||
* @function
|
||||
* @param {string} groupID 群组ID
|
||||
* @example
|
||||
* let promise = TUIGroupService.quitGroup('group1');
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
quitGroup(groupID: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取加群申请和邀请进群申请列表
|
||||
* @function
|
||||
* @example
|
||||
* let promise = TUIGroupService.getGroupApplicationList();
|
||||
* promise.then(function(chatResponse) {
|
||||
* const { applicationList } = chatResponse.data;
|
||||
* applicationList.forEach((item) => {
|
||||
* // item.applicant - 申请者 userID
|
||||
* // item.applicantNick - 申请者昵称
|
||||
* // item.groupID - 群 ID
|
||||
* // item.groupName - 群名称
|
||||
* // item.applicationType - 申请类型:0 加群申请,2 邀请进群申请
|
||||
* // item.userID - applicationType = 2 时,是被邀请人的 userID
|
||||
* // 接入侧可调用 handleGroupApplication 接口同意或拒绝加群申请
|
||||
* TUIGroupService.handleGroupApplication({
|
||||
* handleAction: 'Agree',
|
||||
* application: {...item},
|
||||
* });
|
||||
* });
|
||||
* }).catch(function(imError) {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getGroupApplicationList(): Promise<any>;
|
||||
|
||||
/**
|
||||
* 处理加群申请和邀请进群申请
|
||||
* @function
|
||||
* @param {handleGroupApplicationParams} options 处理申请加群参数
|
||||
* @example
|
||||
* // 通过获取未决列表处理加群申请
|
||||
* let promise = TUIGroupService.getGroupApplicationList();
|
||||
* promise.then(function(chatResponse) {
|
||||
* const { applicationList } = chatResponse.data;
|
||||
* applicationList.forEach((item) => {
|
||||
* if (item.applicationType === 0) {
|
||||
* TUIGroupService.handleGroupApplication({
|
||||
* handleAction: 'Agree',
|
||||
* application: {...item},
|
||||
* });
|
||||
* }
|
||||
* });
|
||||
* }).catch(function(imError) {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
* @example
|
||||
* // 通过获取未决列表处理邀请进群申请
|
||||
* let promise = TUIGroupService.getGroupApplicationList();
|
||||
* promise.then(function(chatResponse) {
|
||||
* const { applicationList } = chatResponse.data;
|
||||
* applicationList.forEach((item) => {
|
||||
* if (item.applicationType === 2) {
|
||||
* TUIGroupService.handleGroupApplication({
|
||||
* handleAction: 'Agree',
|
||||
* application: {...item},
|
||||
* });
|
||||
* }
|
||||
* });
|
||||
* }).catch(function(imError) {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
handleGroupApplication(options: handleGroupApplicationParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取群在线人数
|
||||
* - 注意:仅支持获取直播群在线人数
|
||||
* @function
|
||||
* @param {string} groupID 群组ID
|
||||
* @example
|
||||
* let promise = TUIGroupService.getGroupOnlineMemberCount('group1');
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getGroupOnlineMemberCount(groupID: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* 转让群组
|
||||
* @function
|
||||
* @param {ChangGroupOwnerParams} options 群资料信息
|
||||
* @example
|
||||
* let promise = TUIGroupService.changeGroupOwner({
|
||||
* groupID: 'group1',
|
||||
* newOwnerID: 'user2'
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
changeGroupOwner(options: ChangGroupOwnerParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 初始化群属性
|
||||
* @function
|
||||
* @param {GroupAttrParams} groupAttributes 群属性信息
|
||||
* @example
|
||||
* let promise = TUIGroupService.initGroupAttributes({
|
||||
* groupID: 'group1',
|
||||
* groupAttributes: { key1: 'value1', key2: 'value2' }
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
initGroupAttributes(groupAttributes: GroupAttrParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 设置群属性
|
||||
* @function
|
||||
* @param {GroupAttrParams} groupAttributes 群属性信息
|
||||
* @example
|
||||
* let promise = TUIGroupService.setGroupAttributes({
|
||||
* groupID: 'group1',
|
||||
* groupAttributes: { key1: 'value1', key2: 'value2' }
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
setGroupAttributes(groupAttributes: GroupAttrParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 删除群属性
|
||||
* @function
|
||||
* @param {KeyListParams} options 群属性参数
|
||||
* @example
|
||||
* let promise = TUIGroupService.deleteGroupAttributes({
|
||||
* groupID: 'group1',
|
||||
* keyList: ['key1', 'key2']
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
deleteGroupAttributes(options: KeyListParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取群属性
|
||||
* @function
|
||||
* @param {KeyListParams} options 群属性参数
|
||||
* @example
|
||||
* let promise = TUIGroupService.getGroupAttributes({
|
||||
* groupID: 'group1',
|
||||
* keyList: ['key1', 'key2']
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getGroupAttributes(options: KeyListParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 设置计数器
|
||||
* @function
|
||||
* @param {SetCountersParams} counters 计数器信息
|
||||
* @example
|
||||
* let promise = TUIGroupService.setGroupCounters({
|
||||
* groupID: 'group1',
|
||||
* counters: { key1: 1, key2: 2 }
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
setGroupCounters(counters: SetCountersParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 递增计数器
|
||||
* @function
|
||||
* @param {CountersParams} options 计数器信息
|
||||
* @example
|
||||
* let promise = TUIGroupService.increaseGroupCounter({
|
||||
* groupID: 'group1',
|
||||
* key: 'key1',
|
||||
* value: 1,
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
increaseGroupCounter(options: CountersParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 递减计数器
|
||||
* @function
|
||||
* @param {CountersParams} options 计数器信息
|
||||
* @example
|
||||
* let promise = TUIGroupService.decreaseGroupCounter({
|
||||
* groupID: 'group1',
|
||||
* key: 'key1',
|
||||
* value: 2,
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
decreaseGroupCounter(options: CountersParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取计数器
|
||||
* @function
|
||||
* @param {KeyListParams} options 计数器参数
|
||||
* @example
|
||||
* let promise = TUIGroupService.getGroupCounters({
|
||||
* groupID: 'group1',
|
||||
* keyList: ['key1', 'key2']
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getGroupCounters(options: KeyListParams): Promise<any>;
|
||||
|
||||
// group member
|
||||
/**
|
||||
* 获取群成员列表
|
||||
* @function
|
||||
* @param {GetMemberListParams} options 参数选项
|
||||
* @example
|
||||
* let promise = TUIGroupService.getGroupMemberList({
|
||||
* groupID: 'group1',
|
||||
* count: 15,
|
||||
* offset: 0
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getGroupMemberList(options: GetMemberListParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取群成员资料
|
||||
* @function
|
||||
* @param {GetMemberProfileParams} options 参数选项
|
||||
* @example
|
||||
* let promise = TUIGroupService.getGroupMemberProfile({
|
||||
* groupID: 'group1',
|
||||
* userIDList: ['user1', 'user2'], // 请注意:即使只拉取一个群成员的资料,也需要用数组类型,例如:userIDList: ['user1']
|
||||
* memberCustomFieldFilter: ['group_member_custom'], // 筛选群成员自定义字段:group_member_custom
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getGroupMemberProfile(options: GetMemberProfileParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 邀请群成员
|
||||
* @function
|
||||
* @param {AddMemberParams} options 参数选项
|
||||
* @example
|
||||
* let promise = TUIGroupService.addGroupMember({
|
||||
* groupID: 'group1',
|
||||
* userIDList: ['user1','user2','user3']
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
addGroupMember(options: AddMemberParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 删除群成员,群主可移除群成员。
|
||||
* - 注意1:旗舰版套餐包支持删除直播群群成员。您可以通过调用此接口实现直播群【封禁群成员】的效果。
|
||||
* - 注意2:踢出时长字段仅直播群(AVChatRoom)支持。
|
||||
* - 注意3:群成员被移除出直播群后,在【踢出时长内】如果用户想再次进群,需要 APP 管理员调用 restapi 解封。过了【踢出时长】,用户可再次主动加入直播群。
|
||||
* @function
|
||||
* @param {DeleteMemberParams} options 参数选项
|
||||
* @example
|
||||
* let promise = TUIGroupService.deleteGroupMember({
|
||||
* groupID: 'group1',
|
||||
* userIDList: ['user1'],
|
||||
* reason: '你违规了,我要踢你!',
|
||||
* duration: 60
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
deleteGroupMember(options: DeleteMemberParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 设置群成员禁言
|
||||
* @function
|
||||
* @param {SetMemberMuteParams} options 参数选项
|
||||
* @example
|
||||
* let promise = TUIGroupService.setGroupMemberMuteTime({
|
||||
* groupID: 'group1',
|
||||
* userID: 'user1',
|
||||
* muteTime: 600 // 禁言10分钟;设为0,则表示取消禁言
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
setGroupMemberMuteTime(options: SetMemberMuteParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 设置群成员角色
|
||||
* @function
|
||||
* @param {SetMemberRoleParams} options 参数选项
|
||||
* @example
|
||||
* let promise = TUIGroupService.setGroupMemberRole({
|
||||
* groupID: 'group1',
|
||||
* userID: 'user1',
|
||||
* role: TUIChatEngine.TYPES.GRP_MBR_ROLE_ADMIN
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
setGroupMemberRole(options: SetMemberRoleParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 设置群成员名片
|
||||
* - 注意1:群主可设置所有群成员的名片。
|
||||
* - 注意2: 群管理员可设置自身和其他普通群成员的群名片。
|
||||
* - 注意3:普通群成员只能设置自身群名片。
|
||||
* @function
|
||||
* @param {SetMemberNameCardParams} options 参数选项
|
||||
* @example
|
||||
* let promise = TUIGroupService.setGroupMemberNameCard({
|
||||
* groupID: 'group1',
|
||||
* userID: 'user1',
|
||||
* nameCard: '用户名片'
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
setGroupMemberNameCard(options: SetMemberNameCardParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 设置群成员自定义字段
|
||||
* @function
|
||||
* @param {SetMemberCustomFiledParams} options 参数选项
|
||||
* @example
|
||||
* let promise = TUIGroupService.setGroupMemberCustomField({
|
||||
* groupID: 'group1',
|
||||
* memberCustomField: [{key: 'group_member_test', value: 'test'}]
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
setGroupMemberCustomField(options: SetMemberCustomFiledParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 标记群成员
|
||||
* - 注意1:仅支持直播群。
|
||||
* - 注意2:只有群主才有权限标记群组中其他成员。
|
||||
* - 注意3:使用该接口需要您购买旗舰版套餐。
|
||||
* @function
|
||||
* @param {MarkMemberParams} options 参数选项
|
||||
* @example
|
||||
* let promise = TUIGroupService.markGroupMemberList({
|
||||
* groupID: 'group1',
|
||||
* userIDList: ['user1', 'user2'],
|
||||
* markType: 1000,
|
||||
* enableMark: true,
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
markGroupMemberList(options: MarkMemberParams): Promise<any>;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @interface TUITranslateService
|
||||
*/
|
||||
export interface ITUIReportService {
|
||||
/**
|
||||
* Report key feature usage for analytics
|
||||
* @param code - Event code for analytics
|
||||
* @param feature - Feature name or identifier
|
||||
*/
|
||||
reportFeature(code: number, feature?: string): void;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import type TUIBase from '../../tui-base';
|
||||
import type { SwitchUserStatusParams, UserIDListParams, UpdateMyProfileParams } from '../../type';
|
||||
|
||||
/**
|
||||
* @interface TUIUserService
|
||||
*/
|
||||
export interface ITUIUserService extends TUIBase {
|
||||
|
||||
/**
|
||||
* 初始化 Service
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
init(): void;
|
||||
|
||||
/**
|
||||
* 获取用户资料
|
||||
* @function
|
||||
* @param {UserIDListParams} options [options = undefined] 用户 ID 列表,不传 options 默认查询自己的资料
|
||||
* @example
|
||||
* // 查询自己的资料
|
||||
* let promise = TUIUserService.getUserProfile();
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
* @example
|
||||
* // 查询其他用户的资料
|
||||
* let promise = TUIUserService.getUserProfile({ userIDList: ['user1', 'user2'] });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
getUserProfile(options?: UserIDListParams): Promise<any>;
|
||||
|
||||
/**
|
||||
* 更新自己的资料信息
|
||||
* @function
|
||||
* @param {UpdateMyProfileParams} options 参数选项
|
||||
* @example
|
||||
* // 更新自己的昵称
|
||||
* let promise = TUIUserService.updateMyProfile({
|
||||
* nick: 'newNick'
|
||||
* });
|
||||
* promise.catch((error) => {
|
||||
* // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
|
||||
* });
|
||||
*/
|
||||
updateMyProfile(options: UpdateMyProfileParams): Promise<any>;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
export interface ITasks {
|
||||
sendMessage: boolean; // 发送一条消息
|
||||
revokeMessage: boolean; // 撤回一条消息
|
||||
modifyNickName: boolean; // 修改一次我的昵称
|
||||
groupChat: boolean; // 发起一个群聊
|
||||
muteGroup: boolean; // 开启一次群禁言
|
||||
dismissGroup: boolean; // 解散一个群聊
|
||||
call: boolean; // 发起一次通话
|
||||
searchCloudMessage: boolean; // 进行一次消息云端搜索
|
||||
customerService: boolean; // 进行一次客服会话
|
||||
translateTextMessage: boolean; // 进行一次文本消息翻译
|
||||
}
|
||||
export interface IAppStore {
|
||||
store: {
|
||||
enabledMessageReadReceipt: boolean; // 消息已读回执功能是否已开启
|
||||
enabledEmojiPlugin: boolean; // 表情回复插件能力是否已开启
|
||||
enabledOnlineStatus: boolean; // 用户在线状态能力是否已开启
|
||||
enabledCustomerServicePlugin: boolean; // 客服插件能力是否已开启
|
||||
enabledTranslationPlugin: boolean; // 文本消息翻译能力是否已开启
|
||||
enabledVoiceToText: boolean; // 语音转文字能力是否已开启
|
||||
enableTyping: boolean; // 正在输入能力开关
|
||||
enableConversationDraft: boolean; // 会话草稿能力开关
|
||||
enableAutoMessageRead: boolean; // 接收消息自动已读上报能力开关
|
||||
isOfficial: boolean; // 是否是官网 SDKAppID
|
||||
SDKVersion: string; // Chat SDK 版本号
|
||||
tasks: ITasks; // sample demo 任务列表
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新 store
|
||||
* @param {Sting} key 待更新的 key
|
||||
* @param {any} data 更新的数据
|
||||
*/
|
||||
update(key: string, data: any): void;
|
||||
|
||||
/**
|
||||
* 从 store 中获取数据
|
||||
* @param {Sting} key 需要获取的 key
|
||||
*/
|
||||
getData(key: string): any;
|
||||
|
||||
/**
|
||||
* reset Store 内数据
|
||||
* @param { Array<string>} keyList 需要 reset 的 keyList
|
||||
*/
|
||||
reset(keyList?: string[]): void;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { Message } from '@tencentcloud/lite-chat/basic';
|
||||
import type { IMessageModel } from '../model';
|
||||
|
||||
interface ITextInfo {
|
||||
messageID: string;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export interface IChatStore {
|
||||
store: {
|
||||
messageList: IMessageModel[];
|
||||
isCompleted: boolean;
|
||||
nextReqMessageID: string;
|
||||
quoteMessage: Message | any;
|
||||
typingStatus: boolean;
|
||||
messageSource: IMessageModel | undefined;
|
||||
newMessageList: Message[];
|
||||
translateTextInfo: Map<string, ITextInfo[]> | undefined;
|
||||
voiceToTextInfo: Map<string, ITextInfo[]> | undefined;
|
||||
userInfo: Record<string, any>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新 store
|
||||
* @param {Sting} key 待更新的 key
|
||||
* @param {any} data 更新的数据
|
||||
*/
|
||||
update(key: string, data: any): void;
|
||||
|
||||
/**
|
||||
* 从 store 中获取数据
|
||||
* @param {Sting} key 需要获取的 key
|
||||
*/
|
||||
getData(key: string): any;
|
||||
|
||||
/**
|
||||
* 从 messageList 中获取 messageModel
|
||||
* @param {Sting} id 需要获取的 messageID
|
||||
*/
|
||||
getModel(id: string): any;
|
||||
|
||||
/**
|
||||
* reset Store 内数据
|
||||
* @param { Array<string>} keyList 需要 reset 的 keyList
|
||||
*/
|
||||
reset(keyList?: string[]): void;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { IConversationModel } from '../model';
|
||||
|
||||
export interface IConversationStore {
|
||||
store: {
|
||||
currentConversationID: string;
|
||||
currentConversation: IConversationModel | null;
|
||||
totalUnreadCount: number;
|
||||
conversationList: IConversationModel[];
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新 store
|
||||
* @param {Sting} key 待更新的 key
|
||||
* @param {any} data 更新的数据
|
||||
*/
|
||||
update(key: string, data: any): void;
|
||||
|
||||
/**
|
||||
* 从 store 中获取数据
|
||||
* @param {Sting} key 需要获取的 key
|
||||
*/
|
||||
getData(key: string): any;
|
||||
|
||||
/**
|
||||
* 从 conversationList 中获取 conversationModel
|
||||
* @param {Sting} conversationID 需要获取的会话 ID
|
||||
*/
|
||||
getModel(id: string): any;
|
||||
|
||||
/**
|
||||
* reset Store 内数据
|
||||
* @param { Array<string>} keyList 需要 reset 的 keyList
|
||||
*/
|
||||
reset(keyList?: string[]): void;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface ICustomStore {
|
||||
store: any; // 自定义 store,属性可根据需要进行自定义
|
||||
|
||||
/**
|
||||
* 更新自定义 store
|
||||
* @param {Sting} key 待更新的 key,没有则写入
|
||||
* @param {any} value 待更新的数据
|
||||
*/
|
||||
update(key: string, value: any): void;
|
||||
|
||||
/**
|
||||
* 从自定义 store 中获取数据
|
||||
* @param {Sting} store store 的名称
|
||||
* @param {Sting} key 待获取的 key
|
||||
*/
|
||||
getData(key: string): any;
|
||||
|
||||
/**
|
||||
* reset Store 内数据
|
||||
* @param { Array<string>} keyList 需要 reset 的 keyList
|
||||
*/
|
||||
reset(keyList?: string[]): void;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { Friend, FriendApplication, FriendGroup } from '../../type';
|
||||
|
||||
export interface IFriendStore {
|
||||
store: {
|
||||
friendList: Friend[];
|
||||
friendApplicationList: FriendApplication[];
|
||||
friendApplicationUnreadCount: number; // 好友申请的未读数
|
||||
friendGroupList: FriendGroup[];
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新 store
|
||||
* @param {Sting} key 待更新的 key
|
||||
* @param {any} data 更新的数据
|
||||
*/
|
||||
update(key: string, data: any): void;
|
||||
|
||||
/**
|
||||
* 从 store 中获取数据
|
||||
* @param {Sting} key 需要获取的 key
|
||||
*/
|
||||
getData(key: string): any;
|
||||
|
||||
/**
|
||||
* reset Store 内数据
|
||||
* @param { Array<string>} keyList 需要 reset 的 keyList
|
||||
*/
|
||||
reset(keyList?: string[]): void;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { Message } from '@tencentcloud/lite-chat/basic';
|
||||
import type { IGroupModel } from '../model';
|
||||
|
||||
export interface GroupMember {
|
||||
userID: string;
|
||||
avatar: string;
|
||||
nick: string;
|
||||
role: string;
|
||||
joinTime: number;
|
||||
nameCard: string;
|
||||
muteUntil: number;
|
||||
memberCustomField: Record<string, any>[];
|
||||
}
|
||||
|
||||
export interface IGroupStore {
|
||||
store: {
|
||||
currentGroupID: string;
|
||||
currentGroup: IGroupModel | undefined;
|
||||
currentGroupMemberList: GroupMember[];
|
||||
currentGroupAttributes: Record<string, any>;
|
||||
currentGroupCounters: Record<string, number>;
|
||||
groupList: IGroupModel[];
|
||||
groupSystemNoticeList: Message[];
|
||||
isCompleted: boolean;
|
||||
offset: number | string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新 store
|
||||
* @param {Sting} key 待更新的 key
|
||||
* @param {any} data 更新的数据
|
||||
*/
|
||||
update(key: string, data: any): void;
|
||||
|
||||
/**
|
||||
* 从 store 中获取数据
|
||||
* @param {Sting} key 需要获取的 key
|
||||
*/
|
||||
getData(key: string): any;
|
||||
|
||||
/**
|
||||
* reset Store 内数据
|
||||
* @param { Array<string>} keyList 需要 reset 的 keyList
|
||||
*/
|
||||
reset(keyList?: string[]): void;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from './tui-store';
|
||||
export * from './user';
|
||||
export * from './conversation';
|
||||
export * from './chat';
|
||||
export * from './group';
|
||||
export * from './custom';
|
||||
export * from './friend';
|
||||
export * from './app';
|
||||
export * from './search';
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { ISearchParamsMap, ISearchResult, ISearchType } from '../../type';
|
||||
|
||||
export interface ISearchStoreStore {
|
||||
searchMessagesResult: ISearchResult<ISearchType.MESSAGE>;
|
||||
searchChatMessagesResult: ISearchResult<ISearchType.MESSAGE>;
|
||||
searchUserResult: ISearchResult<ISearchType.USER>;
|
||||
searchGroupResult: ISearchResult<ISearchType.GROUP>;
|
||||
searchMessageParams: ISearchParamsMap[ISearchType.MESSAGE];
|
||||
searchChatMessageParams: ISearchParamsMap[ISearchType.MESSAGE];
|
||||
searchUserParams: ISearchParamsMap[ISearchType.USER];
|
||||
searchGroupParams: ISearchParamsMap[ISearchType.GROUP];
|
||||
error: Error | null;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ISearchStore {
|
||||
store: ISearchStoreStore;
|
||||
|
||||
/**
|
||||
* 更新 store
|
||||
* @param {Sting} key 待更新的 key
|
||||
* @param {any} data 更新的数据
|
||||
*/
|
||||
update(key: string, data: any): void;
|
||||
|
||||
/**
|
||||
* 从 store 中获取数据
|
||||
* @param {Sting} key 需要获取的 key
|
||||
*/
|
||||
getData(key: string): any;
|
||||
|
||||
/**
|
||||
* reset Store 内数据
|
||||
* @param { Array<string>} keyList 需要 reset 的 keyList
|
||||
*/
|
||||
reset(keyList?: string[]): void;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* @property {Boolean} enableTyping 正在输入功能是否开启,默认开启 true
|
||||
* @property {Boolean} enabledMessageReadReceipt 消息已读回执功能是否已开启,默认 false,购买旗舰版套餐后开启
|
||||
* @property {Boolean} enabledEmojiPlugin 表情回复插件能力是否已开启,默认 false,购买旗舰版套餐后开启
|
||||
* @property {Boolean} enabledOnlineStatus 用户在线状态能力是否已开启,默认 false,购买旗舰版套餐后开启
|
||||
* @property {Boolean} enabledCustomerServicePlugin 客服插件能力是否已开启,默认 false,购买客服插件后开启
|
||||
* @property {Boolean} enabledTranslationPlugin 文本消息翻译能力是否已开启,默认 false,购买翻译插件后开启
|
||||
* @property {Boolean} enableConversationDraft 会话草稿功能是否开启,默认开启 true
|
||||
* @example
|
||||
* // UI 层调用以下逻辑关闭正在输入功能
|
||||
* TUIStore.update(StoreName.APP, 'enableTyping', false);
|
||||
* @example
|
||||
* // UI 层调用以下逻辑关闭会话草稿功能
|
||||
* TUIStore.update(StoreName.APP, 'enableConversationDraft', false);
|
||||
*/
|
||||
export enum AppStore {}
|
||||
|
||||
/**
|
||||
* @property {String} currentConversationID 当前会话ID
|
||||
* @property {Array<IConversationModel>} conversationList 会话列表
|
||||
* @property {Number} totalUnreadCount 会话未读总数
|
||||
* @example
|
||||
* // UI 层监听会话列表更新通知
|
||||
* let onConversationListUpdated = function(conversationList) {
|
||||
* console.warn(conversationList);
|
||||
* }
|
||||
* TUIStore.watch(StoreName.CONV, {
|
||||
* conversationList: onConversationListUpdated,
|
||||
* })
|
||||
*/
|
||||
export enum ConversationStore {}
|
||||
|
||||
/**
|
||||
* @property {Array<IMessageModel>} messageList 消息列表
|
||||
* @property {Boolean} isCompleted 漫游是否拉完(用于控制‘查看更多’按钮显示)
|
||||
* @property {Message | any} quoteMessage 被引用的消息信息,引用消息时会触发更新
|
||||
* @property {Boolean} typingStatus 正在输入的状态标识, 默认 false,开启正在输入状态后,输入消息时会触发更新
|
||||
* @property {IMessageModel} messageSource 用于消息云端搜索结果跳转至指定消息标识
|
||||
* @property {Array<Message>} newMessageList 新消息通知列表,提供给 TUINotification 组件使用
|
||||
* @property {Record<string, string | undefined | boolean>} translateTextInfo 文本消息翻译信息
|
||||
* @example
|
||||
* // UI 层监听当前会话消息列表更新通知
|
||||
* let onMessageListUpdated = function(messageList) {
|
||||
* console.warn(messageList);
|
||||
* }
|
||||
* TUIStore.watch(StoreName.CHAT, {
|
||||
* messageList: onMessageListUpdated,
|
||||
* })
|
||||
* @example
|
||||
* // UI 层更新消息云端搜索结果跳转至指定消息标识
|
||||
* TUIStore.update(StoreName.CHAT, 'messageSource', message);
|
||||
* // UI 层监听消息云端搜索结果跳转至指定消息标识
|
||||
* let onMessageSourceUpdated = function(message) {
|
||||
* console.warn(message);
|
||||
* }
|
||||
* TUIStore.watch(StoreName.CHAT, {
|
||||
* messageSource: onMessageSourceUpdated,
|
||||
* })
|
||||
* @example
|
||||
* // UI 层更新文本消息翻译信息
|
||||
* TUIStore.update(StoreName.CHAT, 'translateTextInfo', {
|
||||
* conversationID: 'xxx',
|
||||
* messageID: 'xxx',
|
||||
* visible: false,
|
||||
* });
|
||||
* // UI 层监听文本消息翻译更新通知
|
||||
* let onTranslateTextInfoUpdated = function(info) {
|
||||
* // info 返回的是 map 或 undefined
|
||||
* if (info) {
|
||||
* const list = info.get('conversationID') || [];
|
||||
* list.forEach(item => {
|
||||
* const { messageID, visible } = item;
|
||||
* // messageID - 当前操作的消息的 ID
|
||||
* // visible - 是否显示翻译文本
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* TUIStore.watch(StoreName.CHAT, {
|
||||
* translateTextInfo: onTranslateTextInfoUpdated,
|
||||
* })
|
||||
*/
|
||||
export enum ChatStore {}
|
||||
|
||||
/**
|
||||
* @property {String} currentGroupID 当前群组ID
|
||||
* @property {Group} currentGroup 当前群组信息
|
||||
* @property {Array} currentGroupMemberList 当前群组群成员列表
|
||||
* @property {Object} currentGroupAttributes 当前群组群属性信息
|
||||
* @property {Object} currentGroupCounters 当前群组计数器信息
|
||||
* @property {Array<Group>} groupList 群组列表
|
||||
* @property {Array<Message>} groupSystemNoticeList 群组系统通知列表(注意:Store 中不会存储群系统通知,即时通知)
|
||||
* @example
|
||||
* // UI 层监听群组列表更新通知
|
||||
* let onGroupListUpdated = function(groupList) {
|
||||
* console.warn(groupList);
|
||||
* }
|
||||
* TUIStore.watch(StoreName.GRP, {
|
||||
* groupList: onGroupListUpdated,
|
||||
* })
|
||||
*/
|
||||
export enum GroupStore {}
|
||||
|
||||
/**
|
||||
* @property {Object} userProfile 当前登录用户的资料信息
|
||||
* @property {Boolean} displayOnlineStatus 是否开启用户状态显示,默认 false:关闭
|
||||
* @property {Boolean} displayMessageReadReceipt 是否开启消息阅读状态显示,默认 true:开启
|
||||
* @property {String} kickedOut 用户被踢的类型信息
|
||||
* @property {String} netStateChange 网络状态变更信息
|
||||
* @property {Map<key, statusInfo>} userStatusList 订阅用户的状态信息的列表
|
||||
* - key 用户 userID
|
||||
* - statusInfo.statusType 用户当前状态
|
||||
* - statusInfo.customStatus 用户自定义状态
|
||||
* @property {Array<string>} userBlacklist 用户黑名单列表,UI 组件层可以通过监听该属性来获取用户黑名单列表
|
||||
* @example
|
||||
* // UI 层监听网络变更通知
|
||||
* let onNetStateChange = function(state) {
|
||||
* console.warn(state);
|
||||
* }
|
||||
* TUIStore.watch(StoreName.USER, {
|
||||
* netStateChange: onNetStateChange,
|
||||
* })
|
||||
*/
|
||||
export enum UserStore {}
|
||||
|
||||
/**
|
||||
* @property {Array<Friend>} friendList 好友列表,UI 组件层可以通过监听该属性来获取好友列表
|
||||
* @property {Array<FriendApplication>} friendApplicationList 好友申请列表,UI 组件层可以通过监听该属性来获取好友申请列表
|
||||
* @property {number} friendApplicationUnreadCount 好友申请未读数,UI 组件层可以通过监听该属性来获取好友申请未读数
|
||||
* @example
|
||||
* // UI 层监听好友列表更新变更通知
|
||||
* let onFriendListUpdated = function(friendList) {
|
||||
* console.warn(friendList);
|
||||
* }
|
||||
* TUIStore.watch(StoreName.FRIEND, {
|
||||
* friendList: onFriendListUpdated ,
|
||||
* })
|
||||
*/
|
||||
export enum FriendStore {}
|
||||
@@ -0,0 +1,102 @@
|
||||
import type { StoreName } from '../../const';
|
||||
import type { IConversationModel, IMessageModel } from '../model';
|
||||
|
||||
// 此处 Map 的 value = 1 为占位作用
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export type Task = Record<StoreName, Record<string, Map<(data?: unknown) => void, 1>>>;
|
||||
|
||||
export interface IOptions {
|
||||
[key: string]: (newData?: any) => void;
|
||||
}
|
||||
// 自定义 store 暂不支持
|
||||
// @property {ICustomStore} customStore 自定义 store,可根据业务需要通过以下 API 进行数据操作。
|
||||
/**
|
||||
* @class TUIStore
|
||||
*/
|
||||
export interface ITUIStore {
|
||||
task: Task;
|
||||
/**
|
||||
* UI 组件注册监听
|
||||
* @function
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {IOptions} options UI 组件注册的监听信息
|
||||
* @example
|
||||
* // UI 层监听会话列表更新通知
|
||||
* let onConversationListUpdated = function(conversationList) {
|
||||
* console.warn(conversationList);
|
||||
* }
|
||||
* TUIStore.watch(StoreName.CONV, {
|
||||
* conversationList: onConversationListUpdated,
|
||||
* })
|
||||
*/
|
||||
watch(storeName: StoreName, options: IOptions): void;
|
||||
|
||||
/**
|
||||
* UI 组件取消监听回调
|
||||
* 注意:取消监听时传入的回调函数和注册监听的回调函数是同一个
|
||||
* @function
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {IOptions} options 监听信息,包含需要取消的回调等
|
||||
* @example
|
||||
* // UI 层取消监听会话列表更新通知
|
||||
* TUIStore.unwatch(StoreName.CONV, {
|
||||
* conversationList: onConversationListUpdated,
|
||||
* })
|
||||
*/
|
||||
unwatch(storeName: StoreName, options: IOptions): void;
|
||||
|
||||
/**
|
||||
* 更新 store
|
||||
* - 使用自定义 store 时,可以用此 API 更新自定义数据
|
||||
* @function
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {String} key 需要更新的 key
|
||||
* @param {any} data 待更新的数据
|
||||
* @example
|
||||
* // UI 层更新自定义 Store 数据
|
||||
* const data: string = '自定义数据'
|
||||
* TUIStore.update(StoreName.CUSTOM, 'customKey', data)
|
||||
*/
|
||||
update(storeName: StoreName, key: string, data: any): void;
|
||||
|
||||
/**
|
||||
* 获取 store 中的数据
|
||||
* @function
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {String} key 需要获取的 key
|
||||
* @private
|
||||
*/
|
||||
getData(storeName: StoreName, key: string): any;
|
||||
|
||||
/**
|
||||
* 获取 conversationModel
|
||||
* @function
|
||||
* @param {string} conversationID 会话 ID
|
||||
* @example
|
||||
* // 获取 conversationModel
|
||||
* const conversationID = 'C2C9241'
|
||||
* const conversationModel = TUIStore.getConversationModel(conversationID);
|
||||
*/
|
||||
getConversationModel(conversationID: string): IConversationModel;
|
||||
|
||||
/**
|
||||
* 获取 messageModel
|
||||
* @function
|
||||
* @param {string} messageID 消息 ID
|
||||
* @example
|
||||
* // 获取 messageModel
|
||||
* const messageID = '144115225790497300-1686557023-31267600'
|
||||
* const messageModel = TUIStore.getMessageModel(messageID);
|
||||
*/
|
||||
getMessageModel(messageID: string): IMessageModel;
|
||||
|
||||
/**
|
||||
* 重置 store 内数据
|
||||
* @function
|
||||
* @param {StoreName} storeName store 名称
|
||||
* @param {Array<string>} keyList 需要 reset 的 keyList
|
||||
* @param {boolean} isNotificationNeeded 是否需要触发更新
|
||||
* @private
|
||||
*/
|
||||
reset(storeName: StoreName, keyList?: string[], isNotificationNeeded?: boolean): void;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
export interface IUserStore {
|
||||
store: {
|
||||
userProfile: object;
|
||||
displayOnlineStatus: boolean; // 是否开启用户状态显示
|
||||
displayMessageReadReceipt: boolean; // 是否开启消息阅读状态显示
|
||||
userStatusList: Map<string, {
|
||||
statusType: number;
|
||||
customStatus: string;
|
||||
}>; // 订阅用户的状态信息的列表
|
||||
kickedOut: string; // 用户被踢的类型信息
|
||||
netStateChange: string; // 网络状态变更信息
|
||||
userBlacklist: any[]; // 用户黑名单列表
|
||||
targetLanguage: string; // 文本消息翻译目标语言,内部使用
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新 store
|
||||
* @param {Sting} key 待更新的 key
|
||||
* @param {any} data 更新的数据
|
||||
*/
|
||||
update(key: string, data: any): void;
|
||||
|
||||
/**
|
||||
* 从 store 中获取数据
|
||||
* @param {Sting} key 需要获取的 key
|
||||
*/
|
||||
getData(key: string): any;
|
||||
|
||||
/**
|
||||
* reset Store 内数据
|
||||
* @param { Array<string>} keyList 需要 reset 的 keyList
|
||||
*/
|
||||
reset(keyList?: any[]): void;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import TUIBase from '../tui-base';
|
||||
import type { IConversationModel } from '../interface/model';
|
||||
import { isPrivateKey, isUrl, calculateTimeAgo, JSONToObject } from '../utils/common-utils';
|
||||
import type { MuteConversationParams } from '../type';
|
||||
import { AVATAR } from '../config';
|
||||
import isEmpty from '../utils/is-empty';
|
||||
import { IN_WX_MINI_APP } from '../utils/env';
|
||||
|
||||
const LAST_MSG_FOR_SHOW = [
|
||||
'[图片]',
|
||||
'[语音]',
|
||||
'[视频]',
|
||||
'[文件]',
|
||||
'[位置]',
|
||||
'[地理位置]',
|
||||
'[动画表情]',
|
||||
'[自定义消息]',
|
||||
'[群提示消息]',
|
||||
'[聊天记录]',
|
||||
];
|
||||
|
||||
export default class ConversationModel extends TUIBase implements IConversationModel {
|
||||
public conversationID!: string;
|
||||
public type!: string;
|
||||
public subType!: string;
|
||||
public unreadCount!: number;
|
||||
public lastMessage!: {
|
||||
nick: string;
|
||||
nameCard: string;
|
||||
lastTime: number;
|
||||
lastSequence: string;
|
||||
fromAccount: string;
|
||||
isRevoked: boolean;
|
||||
revoker?: string | undefined;
|
||||
isPeerRead: boolean;
|
||||
messageForShow: string;
|
||||
type: string;
|
||||
payload: any;
|
||||
};
|
||||
|
||||
public groupProfile?: any;
|
||||
public userProfile?: any;
|
||||
public groupAtInfoList!: any[];
|
||||
public remark!: string;
|
||||
public isPinned!: boolean;
|
||||
public messageRemindType!: string;
|
||||
public markList!: string[];
|
||||
public customData!: string;
|
||||
public conversationGroupList!: any[];
|
||||
public draftText!: string;
|
||||
public isMuted!: boolean;
|
||||
public operationType!: number;
|
||||
public _conversation: any;
|
||||
|
||||
constructor(options: any) {
|
||||
super();
|
||||
this.initProxy(options);
|
||||
this.isMuted = (this.messageRemindType === this.getEngine().TYPES.MSG_REMIND_ACPT_NOT_NOTE)
|
||||
|| (this.messageRemindType === this.getEngine().TYPES.MSG_REMIND_DISCARD);
|
||||
this.operationType = 0;
|
||||
this._conversation = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化挂载 conversation 字段
|
||||
*/
|
||||
private initProxy(options: any) {
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (!isPrivateKey(key)) {
|
||||
(this as any)[key] = options[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateProperties(options: any) {
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (!isPrivateKey(key)) {
|
||||
(this as any)[key] = options[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateOperationType(operationType: number) {
|
||||
this.operationType = operationType;
|
||||
}
|
||||
|
||||
getConversation() {
|
||||
return this._conversation;
|
||||
}
|
||||
|
||||
setMessageRead() {
|
||||
return this.getEngine().TUIConversation.setMessageRead(this.conversationID);
|
||||
}
|
||||
|
||||
// 以下方法用于处理 conversation 需要展示的数据
|
||||
getAvatar() {
|
||||
const chatEngine = this.getEngine();
|
||||
let avatar = '';
|
||||
switch (this.type) {
|
||||
case chatEngine.TYPES.CONV_C2C:
|
||||
avatar = isUrl(this.userProfile?.avatar) ? this.userProfile?.avatar : AVATAR.C2C;
|
||||
break;
|
||||
case chatEngine.TYPES.CONV_GROUP:
|
||||
avatar = isUrl(this.groupProfile?.avatar) ? this.groupProfile?.avatar : AVATAR.GROUP;
|
||||
break;
|
||||
case chatEngine.TYPES.CONV_SYSTEM:
|
||||
avatar = isUrl(this.groupProfile?.avatar) ? this.groupProfile?.avatar : AVATAR.SYSTEM;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return avatar;
|
||||
}
|
||||
|
||||
getShowName() {
|
||||
const chatEngine = this.getEngine();
|
||||
let name = '';
|
||||
switch (this.type) {
|
||||
case chatEngine.TYPES.CONV_C2C:
|
||||
name = this.remark || this.userProfile?.nick || this.userProfile?.userID || '';
|
||||
break;
|
||||
case chatEngine.TYPES.CONV_GROUP:
|
||||
name = this.groupProfile?.name || this.groupProfile?.groupID || '';
|
||||
break;
|
||||
case chatEngine.TYPES.CONV_SYSTEM:
|
||||
name = '系统通知';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// @ts-nocheck
|
||||
import type { Group } from '@tencentcloud/chat';
|
||||
import type TencentCloudChat from '@tencentcloud/chat';
|
||||
import type { IGroupModel } from '../interface/model';
|
||||
import { isPrivateKey } from '../utils/common-utils';
|
||||
|
||||
export default class GroupModel implements IGroupModel {
|
||||
public groupID!: string;
|
||||
public name!: string;
|
||||
public avatar!: string;
|
||||
public type!: TencentCloudChat.TYPES.GRP_WORK
|
||||
| TencentCloudChat.TYPES.GRP_PUBLIC
|
||||
| TencentCloudChat.TYPES.GRP_MEETING
|
||||
| TencentCloudChat.TYPES.GRP_AVCHATROOM
|
||||
| TencentCloudChat.TYPES.GRP_COMMUNITY;
|
||||
|
||||
public introduction!: string;
|
||||
public notification!: string;
|
||||
public ownerID!: string;
|
||||
public createTime!: number;
|
||||
public infoSequence?: number | undefined;
|
||||
public lastInfoTime?: number | undefined;
|
||||
public selfInfo?: {
|
||||
role?: string | undefined;
|
||||
messageRemindType?: string | undefined;
|
||||
joinTime?: number | undefined;
|
||||
nameCard?: string | undefined;
|
||||
userID?: string | undefined;
|
||||
memberCustomField?: any[] | undefined;
|
||||
} | undefined;
|
||||
|
||||
public lastMessage?: any;
|
||||
public nextMessageSeq!: number;
|
||||
public memberCount!: number;
|
||||
public maxMemberCount!: number;
|
||||
public muteAllMembers!: boolean;
|
||||
public joinOption!: string;
|
||||
public inviteOption!: string;
|
||||
public groupCustomField?: any[] | undefined;
|
||||
public isSupportTopic!: boolean;
|
||||
public groupAttributes: any;
|
||||
public groupCounters: any;
|
||||
constructor(options: Group) {
|
||||
this.groupAttributes = {};
|
||||
this.groupCounters = {};
|
||||
this.initProxy(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化挂载 group 字段
|
||||
*/
|
||||
private initProxy(options: Group) {
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (!isPrivateKey(key)) {
|
||||
(this as any)[key] = options[key as keyof Group];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* eslint-disable prefer-promise-reject-errors */
|
||||
// @ts-nocheck
|
||||
import type { Conversation, Message } from '@tencentcloud/chat';
|
||||
import type TencentCloudChat from '@tencentcloud/chat';
|
||||
import { ERROR_CODE_ENGINE, ERROR_MSG_ENGINE } from '../const';
|
||||
import type { IConversationModel, IMessageModel } from '../interface/model';
|
||||
import { isPrivateKey, isUndefined } from '../utils/common-utils';
|
||||
import TUIBase from '../tui-base';
|
||||
import type { ModifyMessageParams, ReactionInfo } from '../type';
|
||||
|
||||
export default class MessageModel extends TUIBase implements IMessageModel {
|
||||
public ID!: string;
|
||||
public type!: TencentCloudChat.TYPES;
|
||||
payload: any;
|
||||
conversationID!: string;
|
||||
conversationType!: TencentCloudChat.TYPES;
|
||||
to!: string;
|
||||
from!: string;
|
||||
flow!: string;
|
||||
time!: number;
|
||||
status!: string;
|
||||
isRevoked!: boolean;
|
||||
priority!: TencentCloudChat.TYPES;
|
||||
nick!: string;
|
||||
avatar!: string;
|
||||
isPeerRead!: boolean;
|
||||
nameCard!: string;
|
||||
atUserList!: string[];
|
||||
cloudCustomData!: string;
|
||||
isDeleted!: boolean;
|
||||
isModified!: boolean;
|
||||
needReadReceipt!: boolean;
|
||||
readReceiptInfo!: {
|
||||
readCount?: number;
|
||||
unreadCount?: number;
|
||||
isPeerRead?: boolean;
|
||||
};
|
||||
|
||||
isBroadcastMessage!: boolean;
|
||||
isSupportExtension!: boolean;
|
||||
receiverList!: string[];
|
||||
revoker!: string;
|
||||
sequence!: number;
|
||||
progress!: number;
|
||||
revokerInfo!: { userID: string; nick: string; avatar: string };
|
||||
revokeReason!: string;
|
||||
hasRiskContent!: boolean;
|
||||
reactionList!: ReactionInfo[];
|
||||
|
||||
private _message: Message;
|
||||
private _signalingInfo!: any; // 默认值 undefined,_signalingInfo 的类型是 undefined|null|object,因为 getSignalingInfo 返回值是 null|object,枚举会引起编译报错,所以此处用 any 代替。
|
||||
// 维护映射关系,方便使用
|
||||
private messageHandlers = {
|
||||
[this.getEngine().TYPES.MSG_TEXT]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleTextMessage(message),
|
||||
[this.getEngine().TYPES.MSG_FACE]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleFaceMessage(message),
|
||||
[this.getEngine().TYPES.MSG_LOCATION]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleLocationMessage(message),
|
||||
[this.getEngine().TYPES.MSG_IMAGE]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleImageMessage(message),
|
||||
[this.getEngine().TYPES.MSG_AUDIO]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleAudioMessage(message),
|
||||
[this.getEngine().TYPES.MSG_VIDEO]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleVideoMessage(message),
|
||||
[this.getEngine().TYPES.MSG_FILE]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleFileMessage(message),
|
||||
[this.getEngine().TYPES.MSG_CUSTOM]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleCustomMessage(message),
|
||||
[this.getEngine().TYPES.MSG_MERGER]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleMergeMessage(message),
|
||||
[this.getEngine().TYPES.MSG_GRP_TIP]: (message: Message) => this.getEngine().TUIChat.messageHandler.handleGroupTipsMessage(message),
|
||||
};
|
||||
|
||||
constructor(options: Message) {
|
||||
super();
|
||||
this._message = options;
|
||||
this._signalingInfo = undefined;
|
||||
this.progress = 0;
|
||||
this.reactionList = [];
|
||||
this.initProperties(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 messageModel 属性
|
||||
*/
|
||||
private initProperties(options: Message): void {
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (!isPrivateKey(key)) {
|
||||
(this as any)[key] = options[key as keyof Message];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateProperties(options: Message): void {
|
||||
this._message = options;
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (!isPrivateKey(key)) {
|
||||
(this as any)[key] = options[key as keyof Message];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getMessage() {
|
||||
return this._message;
|
||||
}
|
||||
|
||||
modifyMessage(options: ModifyMessageParams) {
|
||||
if (options.type && this._message.type !== options.type && !options.payload) {
|
||||
return Promise.reject({
|
||||
code: ERROR_CODE_ENGINE.MISMATCH_TYPE_AND_PAYLOAD,
|
||||
message: ERROR_MSG_ENGINE.MISMATCH_TYPE_AND_PAYLOAD,
|
||||
});
|
||||
}
|
||||
this._message.type = options.type || this._message.type;
|
||||
this._message.payload = options.payload || this._message.payload;
|
||||
this._message.cloudCustomData = options.cloudCustomData || this._message.cloudCustomData;
|
||||
return this.getEngine().TUIChat.modifyMessage(this._message);
|
||||
}
|
||||
|
||||
revokeMessage() {
|
||||
return this.getEngine().TUIChat.revokeMessage(this._message);
|
||||
}
|
||||
|
||||
resendMessage() {
|
||||
return this.getEngine().TUIChat.resendMessage(this._message);
|
||||
}
|
||||
|
||||
deleteMessage() {
|
||||
return this.getEngine().TUIChat.deleteMessage([this._message]);
|
||||
}
|
||||
|
||||
quoteMessage() {
|
||||
return this.getEngine().TUIChat.quoteMessage(this._message);
|
||||
}
|
||||
|
||||
replyMessage() {
|
||||
return this.getEngine().TUIChat.replyMessage(this._message);
|
||||
}
|
||||
|
||||
setMessageExtensions(extensions: Record<string, any>[]) {
|
||||
return this.getEngine().TUIChat.setMessageExtensions(this._message, extensions);
|
||||
}
|
||||
|
||||
getMessageExtensions() {
|
||||
return this.getEngine().TUIChat.getMessageExtensions(this._message);
|
||||
}
|
||||
|
||||
deleteMessageExtensions(keyList?: string[]) {
|
||||
return this.getEngine().TUIChat.deleteMessageExtensions(this._message, keyList);
|
||||
}
|
||||
|
||||
getSignalingInfo() {
|
||||
if (this.type !== this.getEngine().TYPES.MSG_CUSTOM) {
|
||||
return null;
|
||||
}
|
||||
if (isUndefined(this._signalingInfo)) {
|
||||
this._signalingInfo = this.getEngine().chat.getSignalingInfo(this._message);
|
||||
return this._signalingInfo;
|
||||
}
|
||||
return this._signalingInfo;
|
||||
}
|
||||
|
||||
getMessageContent() {
|
||||
const handler = this.messageHandlers[this.type as any];
|
||||
if (isUndefined(handler)) {
|
||||
return {};
|
||||
}
|
||||
// 群提示消息不支持 showName
|
||||
if (this.type === this.getEngine().TYPES.MSG_GRP_TIP) {
|
||||
return handler(this._message);
|
||||
}
|
||||
// 普通消息发送方名称显示优先级(对齐微信体验)
|
||||
// C2C 会话:remark > nick > userID
|
||||
// 群会话:remark > nameCard > nick > userID
|
||||
// const friendRemark: any = this.getEngine().TUIFriend.getFriendRemark([this.from]);
|
||||
return {
|
||||
...handler(this._message),
|
||||
showName: this.nick || this.from,
|
||||
};
|
||||
}
|
||||
|
||||
sendForwardMessage(conversationList: Conversation[] | IConversationModel[]) {
|
||||
return this.getEngine().TUIChat.sendForwardMessage(conversationList, [this._message]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
declare module 'tim-profanity-filter-plugin' {
|
||||
const e: any;
|
||||
export = e;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import ChatEngine from './TUIEngine/engine';
|
||||
|
||||
export default class TUIBase {
|
||||
public getEngine() {
|
||||
return ChatEngine.getInstance();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,162 @@
|
||||
import type { func } from '../type';
|
||||
|
||||
export const isUndefined = function (input: any) {
|
||||
return typeof input === 'undefined';
|
||||
};
|
||||
|
||||
export const isPlainObject = function (input: any) {
|
||||
// 注意不能使用以下方式判断,因为IE9/IE10下,对象的__proto__是 undefined
|
||||
// return isObject(input) && input.__proto__ === Object.prototype;
|
||||
if (typeof input !== 'object' || input === null) {
|
||||
return false;
|
||||
}
|
||||
const proto = Object.getPrototypeOf(input);
|
||||
if (proto === null) { // edge case Object.create(null)
|
||||
return true;
|
||||
}
|
||||
let baseProto = proto;
|
||||
while (Object.getPrototypeOf(baseProto) !== null) {
|
||||
baseProto = Object.getPrototypeOf(baseProto);
|
||||
}
|
||||
// 原型链第一个和最后一个比较
|
||||
return proto === baseProto;
|
||||
};
|
||||
|
||||
export const isArray = function (input: any) {
|
||||
if (typeof Array.isArray === 'function') {
|
||||
return Array.isArray(input);
|
||||
}
|
||||
return (Object as any).prototype.toString.call(input).match(/^\[object (.*)\]$/)[1].toLowerCase() === 'array';
|
||||
};
|
||||
|
||||
export const isPrivateKey = function (key: string) {
|
||||
return key.startsWith('_');
|
||||
};
|
||||
|
||||
export const isUrl = function (url: string) {
|
||||
return /^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/.test(url);
|
||||
};
|
||||
|
||||
export const calculateTimeAgo = function (dateTimeStamp: number, t: func): string {
|
||||
const minute = 1000 * 60;
|
||||
const hour = minute * 60;
|
||||
const day = hour * 24;
|
||||
const week = day * 7;
|
||||
const now = new Date().getTime();
|
||||
const diffValue = now - dateTimeStamp;
|
||||
let result = '';
|
||||
|
||||
if (diffValue < 0) {
|
||||
return result;
|
||||
}
|
||||
const minC = diffValue / minute;
|
||||
const hourC = diffValue / hour;
|
||||
const dayC = diffValue / day;
|
||||
const weekC = diffValue / week;
|
||||
if (weekC >= 1 && weekC <= 4) {
|
||||
result = ` ${parseInt(`${weekC}`, 10)} ${t('time.周')}${t('time.前')}`;
|
||||
} else if (dayC >= 1 && dayC <= 6) {
|
||||
result = ` ${parseInt(`${dayC}`, 10)} ${t('time.天')}${t('time.前')}`;
|
||||
} else if (hourC >= 1 && hourC <= 23) {
|
||||
result = ` ${parseInt(`${hourC}`, 10)} ${t('time.小时')}${t('time.前')}`;
|
||||
} else if (minC >= 1 && minC <= 59) {
|
||||
result = ` ${parseInt(`${minC}`, 10)} ${t('time.分钟')}${t('time.前')}`;
|
||||
} else if (diffValue >= 0 && diffValue <= minute) {
|
||||
result = `${t('time.刚刚')}`;
|
||||
} else {
|
||||
const dateTime = new Date();
|
||||
dateTime.setTime(dateTimeStamp);
|
||||
const _year = dateTime.getFullYear();
|
||||
const _month = dateTime.getMonth() + 1 < 10 ? `0${dateTime.getMonth() + 1}` : dateTime.getMonth() + 1;
|
||||
const _date = dateTime.getDate() < 10 ? `0${dateTime.getDate()}` : dateTime.getDate();
|
||||
result = `${_year}-${_month}-${_date}`;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export function formatTime(secondTime: number) {
|
||||
const time: number = secondTime;
|
||||
let newTime = '';
|
||||
let hour: number | string;
|
||||
let minute: number | string;
|
||||
let seconds: any;
|
||||
if (time >= 3600) {
|
||||
hour = parseInt(`${time / 3600}`, 10) < 10 ? `0${parseInt(`${time / 3600}`, 10)}` : parseInt(`${time / 3600}`, 10);
|
||||
minute = parseInt(`${(time % 60) / 60}`, 10) < 10 ? `0${parseInt(`${(time % 60) / 60}`, 10)}` : parseInt(`${(time % 60) / 60}`, 10);
|
||||
seconds = time % 3600 < 10 ? `0${time % 3600}` : time % 3600;
|
||||
if (seconds > 60) {
|
||||
minute = parseInt(`${seconds / 60}`, 10) < 10 ? `0${parseInt(`${seconds / 60}`, 10)}` : parseInt(`${seconds / 60}`, 10);
|
||||
seconds = seconds % 60 < 10 ? `0${seconds % 60}` : seconds % 60;
|
||||
}
|
||||
newTime = `${hour}:${minute}:${seconds}`;
|
||||
} else if (time >= 60 && time < 3600) {
|
||||
minute = parseInt(`${time / 60}`, 10) < 10 ? `0${parseInt(`${time / 60}`, 10)}` : parseInt(`${time / 60}`, 10);
|
||||
seconds = time % 60 < 10 ? `0${time % 60}` : time % 60;
|
||||
newTime = `00:${minute}:${seconds}`;
|
||||
} else if (time < 60) {
|
||||
seconds = time < 10 ? `0${time}` : time;
|
||||
newTime = `00:00:${seconds}`;
|
||||
}
|
||||
return newTime;
|
||||
}
|
||||
|
||||
// Determine if it is a JSON string
|
||||
export function isJSON(str: string) {
|
||||
if (typeof str === 'string') {
|
||||
try {
|
||||
const data = JSON.parse(str);
|
||||
if (data) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine if it is a JSON string
|
||||
export const JSONToObject = function (str: string) {
|
||||
if (!str || !isJSON(str)) {
|
||||
return str;
|
||||
}
|
||||
return JSON.parse(str);
|
||||
};
|
||||
|
||||
// formate file size
|
||||
export const formateFileSize = function (fileSize: number) {
|
||||
let ret = '';
|
||||
if (fileSize >= 1024 * 1024) {
|
||||
ret = `${(fileSize / (1024 * 1024)).toFixed(2)} Mb`;
|
||||
} else if (fileSize >= 1024) {
|
||||
ret = `${(fileSize / 1024).toFixed(2)} Kb`;
|
||||
} else {
|
||||
ret = `${fileSize.toFixed(2)}B`;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* 重试函数, catch 时,重试
|
||||
*
|
||||
* @param {Promise} promise 需重试的函数
|
||||
* @param {number} num 需要重试的次数
|
||||
* @param {number} time 间隔时间(s)
|
||||
* @returns {Promise<any>} im 接口的 response 原样返回
|
||||
*/
|
||||
export const retryPromise = (promise: Promise<any>, num = 6, time = 0.5) => {
|
||||
let n = num;
|
||||
const func = () => promise;
|
||||
return func()
|
||||
.catch((error: any) => {
|
||||
if (n === 0) {
|
||||
throw error;
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
func();
|
||||
clearTimeout(timer);
|
||||
n--;
|
||||
}, time * 1000);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
declare const wx: any;
|
||||
declare const qq: any;
|
||||
declare const tt: any;
|
||||
declare const swan: any;
|
||||
declare const my: any;
|
||||
declare const jd: any;
|
||||
declare const uni: any;
|
||||
declare const window: any;
|
||||
|
||||
export const IN_WX_MINI_APP_DESK = (typeof wx !== 'undefined' && typeof wx.getSystemInfoSync === 'function' && (wx.getSystemInfoSync().platform === 'mac' || wx.getSystemInfoSync().platform === 'windows'));
|
||||
export const IN_WX_MINI_APP = (typeof wx !== 'undefined' && typeof wx.getSystemInfoSync === 'function' && Boolean(wx.getSystemInfoSync().fontSizeSetting)) || IN_WX_MINI_APP_DESK;
|
||||
export const IN_QQ_MINI_APP = (typeof qq !== 'undefined' && typeof qq.getSystemInfoSync === 'function' && Boolean(qq.getSystemInfoSync().fontSizeSetting));
|
||||
export const IN_TT_MINI_APP = (typeof tt !== 'undefined' && typeof tt.getSystemInfoSync === 'function' && Boolean(tt.getSystemInfoSync().fontSizeSetting));
|
||||
export const IN_BAIDU_MINI_APP = (typeof swan !== 'undefined' && typeof swan.getSystemInfoSync === 'function' && Boolean(swan.getSystemInfoSync().fontSizeSetting));
|
||||
export const IN_ALIPAY_MINI_APP = (typeof my !== 'undefined' && typeof my.getSystemInfoSync === 'function' && Boolean(my.getSystemInfoSync().fontSizeSetting));
|
||||
export const IN_JD_MINI_APP = (typeof jd !== 'undefined' && typeof jd.getSystemInfoSync === 'function');
|
||||
export const IN_UNI_NATIVE_APP = (typeof uni !== 'undefined' && typeof window === 'undefined');
|
||||
// eslint-disable-next-line @stylistic/max-len
|
||||
export const IN_MINI_APP = IN_WX_MINI_APP || IN_QQ_MINI_APP || IN_TT_MINI_APP || IN_BAIDU_MINI_APP || IN_ALIPAY_MINI_APP || IN_JD_MINI_APP || IN_UNI_NATIVE_APP;
|
||||
export const IN_UNI_APP = (typeof uni !== 'undefined');
|
||||
export const IN_BROWSER = (function () {
|
||||
if (typeof uni !== 'undefined') {
|
||||
return !IN_MINI_APP;
|
||||
}
|
||||
return (typeof window !== 'undefined') && !IN_MINI_APP;
|
||||
})();
|
||||
|
||||
// runtime env
|
||||
export const APP_NAMESPACE = (() => {
|
||||
if (IN_QQ_MINI_APP) {
|
||||
return qq;
|
||||
}
|
||||
if (IN_TT_MINI_APP) {
|
||||
return tt;
|
||||
}
|
||||
if (IN_BAIDU_MINI_APP) {
|
||||
return swan;
|
||||
}
|
||||
if (IN_ALIPAY_MINI_APP) {
|
||||
return my;
|
||||
}
|
||||
if (IN_WX_MINI_APP) {
|
||||
return wx;
|
||||
}
|
||||
if (IN_UNI_NATIVE_APP) {
|
||||
return uni;
|
||||
}
|
||||
if (IN_JD_MINI_APP) {
|
||||
return jd;
|
||||
}
|
||||
if (IN_BROWSER) {
|
||||
return window;
|
||||
}
|
||||
return {};
|
||||
})();
|
||||
|
||||
const USER_AGENT = (IN_BROWSER && window && window.navigator && window.navigator.userAgent) || '';
|
||||
const IS_ANDROID = /Android/i.test(USER_AGENT);
|
||||
const IS_WIN_PHONE = /(?:Windows Phone)/.test(USER_AGENT);
|
||||
const IS_SYMBIAN = /(?:SymbianOS)/.test(USER_AGENT);
|
||||
const IS_IOS = /iPad/i.test(USER_AGENT) || /iPhone/i.test(USER_AGENT) || /iPod/i.test(USER_AGENT);
|
||||
|
||||
export const IS_H5 = IS_ANDROID || IS_WIN_PHONE || IS_SYMBIAN || IS_IOS;
|
||||
|
||||
export const IS_PC = IN_BROWSER && !IS_H5;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { isPlainObject } from './common-utils';
|
||||
|
||||
const isEmpty = function (input: any) {
|
||||
// Null and Undefined...
|
||||
if (input === null || typeof (input) === 'undefined') return true;
|
||||
// Booleans...
|
||||
if (typeof input === 'boolean') return false;
|
||||
// Numbers...
|
||||
if (typeof input === 'number') return input === 0;
|
||||
// Strings...
|
||||
if (typeof input === 'string') return input.length === 0;
|
||||
// Functions...
|
||||
if (typeof input === 'function') return input.length === 0;
|
||||
// Arrays...
|
||||
if (Array.isArray(input)) return input.length === 0;
|
||||
// Errors...
|
||||
if (input instanceof Error) return input.message === '';
|
||||
// plain object
|
||||
if (isPlainObject(input)) {
|
||||
for (const key in input) {
|
||||
if (Object.prototype.hasOwnProperty.call(input, key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export default isEmpty;
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { func } from '../type';
|
||||
|
||||
class Middleware {
|
||||
private cache: func[];
|
||||
private middlewares: func[];
|
||||
private options: any;
|
||||
constructor() {
|
||||
this.cache = [];
|
||||
this.middlewares = [];
|
||||
this.options = null; // 缓存options
|
||||
}
|
||||
|
||||
use(fn: func) {
|
||||
if (typeof fn !== 'function') {
|
||||
console.error('middleware must be a function');
|
||||
}
|
||||
this.cache.push(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
next(): any {
|
||||
if (this.middlewares && this.middlewares.length > 0) {
|
||||
const ware: any = this.middlewares.shift();
|
||||
return ware.call(this, this.options, this.next.bind(this));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} options - 数据的入口
|
||||
* @returns {Function}
|
||||
*/
|
||||
run(options: any): any {
|
||||
this.middlewares = this.cache.map(fn => fn);
|
||||
this.options = options; // 缓存数据
|
||||
return this.next();
|
||||
}
|
||||
}
|
||||
|
||||
export default Middleware;
|
||||
@@ -0,0 +1,85 @@
|
||||
import { IN_MINI_APP, APP_NAMESPACE } from './env';
|
||||
|
||||
/**
|
||||
* storage 中写入一条数据。若key已存在,则会覆盖已有数据。
|
||||
* @param {String} key - 缓存的key
|
||||
* @param {*} value - 任何可JSON序列化的数据
|
||||
* @param {Boolean} withPrefix 是否需要拼接 prefix 和 key,默认为 true,即需要拼接。
|
||||
*/
|
||||
export function setStorageItem(key: string, value: any, withPrefix = true) {
|
||||
const realKey = withPrefix ? _getKey(key) : key;
|
||||
_setStorageSync(realKey, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取storage中的一条数据
|
||||
* @param {String} key - 缓存的key
|
||||
* @param {Boolean} withPrefix 是否需要拼接 prefix 和 key,默认为 true,即需要拼接。
|
||||
* @returns {any} 返回指定key的数据
|
||||
*/
|
||||
export function getStorageItem(key: string, withPrefix = true): any {
|
||||
try {
|
||||
const realKey: string = withPrefix ? _getKey(key) : key;
|
||||
return _getStorageSync(realKey);
|
||||
} catch (error) {
|
||||
console.warn('Storage.getStorageItem error:', error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定key的数据
|
||||
* @param {String} key - 缓存的key
|
||||
* @param {Boolean} withPrefix 是否需要拼接 prefix 和 key,默认为 true,即需要拼接。
|
||||
*/
|
||||
export function removeStorageItem(key: string, withPrefix = true) {
|
||||
try {
|
||||
const realKey = withPrefix ? _getKey(key) : key;
|
||||
_removeStorageSync(realKey);
|
||||
} catch (error) {
|
||||
console.warn('Storage.removeStorageItem error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function _setStorageSync(key: string, data: string) {
|
||||
if (IN_MINI_APP) {
|
||||
APP_NAMESPACE.setStorageSync(key, data);
|
||||
} else if (_canIUseCookies()) {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
function _getStorageSync(key: string): any {
|
||||
if (IN_MINI_APP) {
|
||||
return APP_NAMESPACE.getStorageSync(key);
|
||||
}
|
||||
if (_canIUseCookies()) {
|
||||
const data: any = localStorage.getItem(key);
|
||||
if (data !== 'undefined') {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function _removeStorageSync(key: string) {
|
||||
if (IN_MINI_APP) {
|
||||
APP_NAMESPACE.removeStorageSync(key);
|
||||
} else if (_canIUseCookies()) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
function _getKey(key: string) {
|
||||
return `chat_engine_${key}`;
|
||||
}
|
||||
|
||||
// 如果用户选择 block cookies,此时访问 localStorage 浏览器会抛错
|
||||
// Uncaught SecurityError: Failed to read the 'localStorage' property from 'Window': Access is denied for this document
|
||||
// 通过 navigator.cookieEnabled 短路逻辑规避
|
||||
function _canIUseCookies() {
|
||||
// When the browser is configured to block third-party cookies, and navigator.cookieEnabled is invoked inside a third-party iframe,
|
||||
// it returns true in Safari, Edge Spartan and IE (while trying to set a cookie in such scenario would fail).
|
||||
// It returns false in Firefox and Chromium-based browsers.
|
||||
return navigator && navigator.cookieEnabled && localStorage;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable prefer-rest-params */
|
||||
import type { ITUIChatEngine } from '../interface/engine';
|
||||
import Middleware from './middleware';
|
||||
import { ERROR_CODE_ENGINE, ERROR_MSG_ENGINE } from '../const';
|
||||
|
||||
export default function validateInitialization(
|
||||
TUIChatEngine: ITUIChatEngine,
|
||||
target: any,
|
||||
APIList: object,
|
||||
) {
|
||||
const obj = Object.create(null);
|
||||
Object.keys(APIList).forEach((api) => {
|
||||
if (!target[api]) {
|
||||
return;
|
||||
}
|
||||
obj[api] = target[api];
|
||||
|
||||
const validateMiddleware = new Middleware();
|
||||
target[api] = function () {
|
||||
const args = Array.from(arguments);
|
||||
validateMiddleware
|
||||
.use((options: any, next: any) => {
|
||||
if (TUIChatEngine.isInited) {
|
||||
return next();
|
||||
}
|
||||
return Promise.reject({
|
||||
code: ERROR_CODE_ENGINE.NOT_INIT,
|
||||
message: `${api} | ${ERROR_MSG_ENGINE.NOT_INIT}`,
|
||||
});
|
||||
})
|
||||
.use((options: any) => obj[api].apply(target, options));
|
||||
return validateMiddleware.run(args);
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user