Files
zyt/wx/TUICallKit/src/TUICallService/CallService/chatCombine.ts
T
2026-03-04 15:32:30 +08:00

300 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { TUICore, TUILogin, TUIConstants, ExtensionInfo } from '@tencentcloud/tui-core-lite';
import { CallMediaType, AudioCallIcon, VideoCallIcon, LOG_LEVEL, COMPONENT, StoreName, NAME, CallStatus, CallType, ACTION_TYPE } from '../const/index';
import { isUndefined, formatTime, JSONToObject } from '../utils/common-utils';
import { getRemoteUserProfile } from './utils';
import { ITUIStore } from '../interface/ITUIStore';
import TuiStore from '../TUIStore/tuiStore';
// @ts-ignore
import TencentCloudChat from '@tencentcloud/lite-chat/basic'; // 仅支持 calls 接口
import { t } from '../locales/index';
const TUIStore: ITUIStore = TuiStore.getInstance();
const cmd2messageCardContentMap = {
audioCall: () => 'Voice call',
videoCall: () => 'Video call',
switchToAudio: () => 'Switch audio call',
switchToVideo: () => 'Switch video call',
hangup: ({ callDuration }) => `${t('Call duration')}${callDuration}`,
};
export default class ChatCombine {
static instance: ChatCombine;
private _callService: any;
constructor(options) {
this._callService = options.callService;
// 下面:TUICore注册事件,注册组件服务,注册界面拓展
TUICore.registerEvent(TUIConstants.TUILogin.EVENT.LOGIN_STATE_CHANGED, TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGIN_SUCCESS, this); // onNotifyEvent
// @ts-ignore
if (TUIConstants.TUIChat?.EVENT) {
// @ts-ignore
TUICore.registerEvent(TUIConstants.TUIChat.EVENT?.CHAT_STATE_CHANGED, TUIConstants.TUIChat.EVENT_SUB_KEY?.CHAT_OPENED, this); // onNotifyEvent
}
TUICore.registerService(TUIConstants.TUICalling.SERVICE.NAME, this); // onCall
TUICore.registerExtension(TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID, this); // onGetExtension
}
static getInstance(options) {
if (!ChatCombine.instance) {
ChatCombine.instance = new ChatCombine(options);
}
return ChatCombine.instance;
}
// ================ 【】 ================
/**
* message on screen
* @param {Any} params Parameters for message up-screening
*/
public callTUIService(params) {
const { message } = params || {};
TUICore.callService({
serviceName: TUIConstants.TUIChat.SERVICE.NAME,
method: TUIConstants.TUIChat.SERVICE.METHOD.UPDATE_MESSAGE_LIST,
params: { message },
});
}
/**
* tuicore getExtension
* @param {String} extensionID extension id
* @param {Any} params tuicore pass parameters
* @returns {Any[]} return extension
*/
public onGetExtension(extensionID: string, params: any) {
if (extensionID === TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID) {
this._callService.getTUICallEngineInstance()?.reportLog?.({ name: 'TUICallKit.onGetExtension', data: { extensionID, params } });
if (isUndefined(params)) return [];
// room and customer_service ChatType not show audio and video icon.
// @ts-ignore
if ([TUIConstants.TUIChat.TYPE.ROOM, TUIConstants.TUIChat.TYPE.CUSTOMER_SERVICE].includes(params.chatType)) return [];
let list = [];
const audioCallExtension: ExtensionInfo = {
weight: 1000,
text: '语音通话',
icon: AudioCallIcon,
data: {
name: 'voiceCall',
},
listener: {
onClicked: async options => await this._handleTUICoreOnClick(options, options.type || CallMediaType.AUDIO),
},
};
const videoCallExtension: ExtensionInfo = {
weight: 900,
text: '视频通话',
icon: VideoCallIcon,
data: {
name: 'videoCall',
},
listener: {
onClicked: async options => await this._handleTUICoreOnClick(options, options.type || CallMediaType.VIDEO),
},
};
if (params?.chatType) {
list = [audioCallExtension, videoCallExtension];
} else {
!params?.filterVoice && list.push(audioCallExtension);
!params?.filterVideo && list.push(videoCallExtension);
}
return list;
}
}
public async onCall(method: String, params: any) {
if (method === TUIConstants.TUICalling.SERVICE.METHOD.START_CALL) {
await this._handleTUICoreOnClick(params, params.type);
}
}
/**
* tuicore notify event manager
* @param {String} eventName event name
* @param {String} subKey sub key
* @param {Any} options tuicore event parameters
*/
public async onNotifyEvent(eventName: string, subKey: string, options?: any) {
try {
if (eventName === TUIConstants.TUILogin.EVENT.LOGIN_STATE_CHANGED) {
// TUICallkit executes its own business logic when it receives a successful login.
if (subKey === TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGIN_SUCCESS) {
// @ts-ignore
const { chat, userID, userSig, SDKAppID } = TUILogin.getContext();
await this._callService?.init({ tim: chat, userID, userSig, sdkAppID: SDKAppID, isFromChat: true, component: COMPONENT.TIM_CALL_KIT });
this._callService?.setIsFromChat(true);
this._callService?.setLogLevel(LOG_LEVEL.NORMAL); // setLogLevel to 0 in tuikit. easy to find out problem via logs.
this._addListenChatEvent();
} else if (subKey === TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGOUT_SUCCESS) {
this._removeListenChatEvent();
await this._callService?.destroyed();
}
}
// @ts-ignore
if (TUIConstants.TUIChat?.EVENT && eventName === TUIConstants.TUIChat.EVENT.CHAT_STATE_CHANGED) {
// @ts-ignore
if (subKey === TUIConstants.TUIChat.EVENT_SUB_KEY.CHAT_OPENED) {
this._callService?.setCurrentGroupId(options?.groupID || '');
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
const currentGroupId = this._callService?.getCurrentGroupId();
const groupAttributes = currentGroupId ? await this.getGroupAttributes(this._callService?.getTim(), currentGroupId) : {};
await this.updateStoreBasedOnGroupAttributes(groupAttributes);
}
}
} catch (error) {
console.error(`${NAME.PREFIX}TUICore onNotifyEvent failed, error: ${error}.`);
}
}
// Handling the chat+call scenario, data required for the joinInGroupCall API: update store / clear relevant store data
public async updateStoreBasedOnGroupAttributes(groupAttributes: any) {
this._callService?.getTUICallEngineInstance()?.reportLog?.({
name: 'TUICallKit.getJoinGroupCallInfo.success',
data: { groupAttributes },
});
try {
const {
call_id: callId = '', // callEngine v3.1 support
group_id: groupId = '',
room_id: roomId = 0,
room_id_type: roomIdType = 0,
call_media_type: callType = NAME.UNKNOWN,
// @ts-ignore
user_list: userList, // The default value of the user list returned by the background is null
} = groupAttributes[NAME.INNER_ATTR_KIT_INFO] ? JSON.parse(groupAttributes[NAME.INNER_ATTR_KIT_INFO]) : {};
let userListInfo = (userList || []).map(user => user.userid);
if (userListInfo.length > 0) {
userListInfo = await getRemoteUserProfile(userListInfo, this._callService?.getTim());
}
const updateStoreParams = {
[NAME.CALL_ID]: callId,
[NAME.GROUP_ID]: groupId,
[NAME.GROUP_CALL_MEMBERS]: userListInfo,
[NAME.ROOM_ID]: roomId,
[NAME.CALL_MEDIA_TYPE]: CallType[callType],
[NAME.ROOM_ID_TYPE]: roomIdType,
};
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
} catch (error) {
console.warn(`${NAME.PREFIX}updateStoreBasedOnGroupAttributes fail, error: ${error}`);
}
}
// Get group attribute
public async getGroupAttributes(tim: any, groupId: string) {
if (!groupId) return {};
try {
const { data } = await tim.getGroupAttributes({
groupID: groupId,
keyList: []
});
return data?.groupAttributes || {};
} catch (error) {
console.warn(`${NAME.PREFIX}getGroupAttributes fail: ${error}`);
return {};
}
}
isLineBusy(message) {
const callMessage: any = JSONToObject(message.payload.data);
const objectData = JSONToObject(callMessage?.data);
return objectData?.line_busy === 'line_busy' || objectData?.line_busy === '' || objectData?.data?.message === 'lineBusy';
}
async getCallKitMessage(message: any, tim: any) {
const callMessage: any = JSONToObject(message.payload.data);
if (callMessage?.businessID !== 1) {
return {};
}
let messageCardContent = '';
const objectData = JSONToObject(callMessage?.data);
const callMediaType = objectData.call_type;
const inviteeList = callMessage.inviteeList;
const inviter = objectData?.data?.inviter;
const localUserId = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO).userId;
const isInviter = inviter === localUserId;
const cmd = objectData?.data?.cmd;
switch (callMessage?.actionType) {
case ACTION_TYPE.INVITE: {
messageCardContent = cmd2messageCardContentMap[cmd]({ callDuration: formatTime(objectData?.call_end) });
break;
}
case ACTION_TYPE.CANCEL_INVITE:
messageCardContent = isInviter ? 'Call Cancel' : 'Other Side Cancel';
break;
case ACTION_TYPE.ACCEPT_INVITE:
if (['switchToAudio', 'switchToVideo'].includes(cmd)) {
messageCardContent = cmd2messageCardContentMap?.[cmd]?.();
} else {
messageCardContent = t('Answered');
}
break;
case ACTION_TYPE.REJECT_INVITE:
if (this.isLineBusy(message)) {
messageCardContent = isInviter ? 'Line Busy' : 'Other Side Line Busy';
} else {
messageCardContent = isInviter ? 'Other Side Decline' : 'Decline';
}
break;
case ACTION_TYPE.INVITE_TIMEOUT:
if (['switchToAudio', 'switchToVideo'].includes(cmd)) {
messageCardContent = cmd2messageCardContentMap?.[cmd]?.();
} else {
messageCardContent = isInviter ? 'Other Side No Answer' : 'No answer';
}
break;
}
return { messageCardContent, callMediaType, inviteeList };
}
// =========================【chat: event listening】=========================
private _addListenChatEvent() {
if (!this._callService?.getTim()) {
console.warn(`${NAME.PREFIX}add tim event listener failed, tim is empty.`);
return;
}
this._callService?.getTim().on(TencentCloudChat.EVENT.GROUP_ATTRIBUTES_UPDATED, this._handleGroupAttributesUpdated, this);
}
private _removeListenChatEvent() {
if (!this._callService?.getTim()) {
console.warn(`${NAME.PREFIX}remove tim event listener failed, tim is empty.`);
return;
}
this._callService?.getTim().off(TencentCloudChat.EVENT.GROUP_ATTRIBUTES_UPDATED, this._handleGroupAttributesUpdated, this);
}
/**
* chat start audio/video call via click
* @param {Any} options Parameters passed in when clicking on an audio/video call from chat
* @param {CallMediaType} type call media type. 0 - audio; 1 - video.
*
* priority: isForceUseV2API > version
*/
private async _handleTUICoreOnClick(options, type: CallMediaType) {
try {
const { groupID, userIDList = [], version = '', ...rest } = options;
await this._callService?.calls({ chatGroupID: groupID, userIDList, type, ...rest });
} catch (error: any) {
// call update to lite-chat only support calls interface
if (!options?.version) {
console.warn('Please upgrade to the latest Chat UIKit components.');
}
console.debug(error);
}
}
private async _handleGroupAttributesUpdated(event) {
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
const data = event?.data || {};
const { groupID: groupId = '', groupAttributes = {} } = data;
if (groupId !== this._callService?.getCurrentGroupId()) return;
await this.updateStoreBasedOnGroupAttributes(groupAttributes);
}
}