UNPKG

@tencentcloud/chat-uikit-vue

Version:

Build In-App Chat & Audio/Video Call & Live Streaming in minutes with UIKit components for Vue.

388 lines (362 loc) 12.6 kB
import TUIChatEngine, { TUIStore, StoreName, IMessageModel, IConversationModel, TUITranslateService, TUIConversationService, } from '@tencentcloud/chat-uikit-engine'; import { ITUINotification, NotificationType, INotificationConstructorParams, } from './interface'; import { safeParse, isTypingMessage } from './utils'; const TYPES = TUIChatEngine.TYPES; /** * The TUINotification class. * Just can use in web environment. * @extends {ITUINotification} * @param {INotificationConstructorParams | undefined} params - An optional parameter specifying the options for creating the TUINotification instance. * @return {TUINotification} - The instance of TUINotification. * @example * TUINotification.setNotificationConfiguration({ * showPreviews: true, * allowNotifications: true * }); * TUINotification.getInstance(config).notify(message); */ class TUINotification implements ITUINotification { public showPreviews = true; public allowNotifications = true; public notificationTitle = TUITranslateService.t('TUIChat.腾讯云 IM'); public notificationIcon = 'https://web.sdk.qcloud.com/im/demo/latest/faviconnew.png'; private static instance: TUINotification | null = null; // private constructor implementing the Singleton Pattern private constructor(params?: INotificationConstructorParams) { this.setConfiguration(params); } /** * Returns an instance of TUINotification. * * @param {INotificationConstructorParams | undefined} options - An optional parameter specifying the options for creating the TUINotification instance. * @return {TUINotification} - The instance of TUINotification. */ static getInstance(options?: INotificationConstructorParams): TUINotification { if (!this.instance) { this.instance = new TUINotification(options); } return this.instance; } /** * Notifies the user with a given message. * * @param {IMessageModel} message - The message to be notified. * @return {Promise<void>} - A promise that resolves when the notification is handled. */ public async notify(message: IMessageModel): Promise<void> { const browserPermission = await this.requestNotificationPermission(); if ( !this.allowNotifications || !browserPermission || !this.isMessageNeedNotification(message) ) { return; } await this.handleNotification(message); } /** * Handles the notification message. * * @param {IMessageModel} message - The notification message to handle. * @return {Promise<void>} - A Promise that resolves once the notification is handled. */ public async handleNotification(message: IMessageModel): Promise<void> { let options = { badge: this.notificationIcon, icon: this.notificationIcon, body: '', requireInteraction: false, }; const notificationType = this.getNotificationType(message); let content; let callEnd; switch (notificationType) { case 'call': ({ content, callEnd } = this.getCallNotificationInfo(message)); options = { ...options, body: content, requireInteraction: !callEnd, }; break; case 'chat': options = { ...options, body: await this.getChatNotificationContent(message), requireInteraction: false, }; break; } const notification = new Notification(this.notificationTitle, options); notification.onclick = async () => { window.focus(); if (!message || !message.conversationID) { return; } if ( message?.conversationType === TYPES.CONV_C2C || message?.conversationType === TYPES.CONV_GROUP ) { const conversationProfile = await TUIConversationService.getConversationProfile(message.conversationID); const { conversationID: clickedMessageConvID } = conversationProfile?.data?.conversation || {}; if (clickedMessageConvID) { TUIConversationService.switchConversation(clickedMessageConvID); } } notification.close(); }; } /** * Sets the notification configuration. * * @param {INotificationConstructorParams} params - The parameters for the notification configuration. * @return {void} This function does not return a value. */ public setNotificationConfiguration(params?: INotificationConstructorParams): void { this.setConfiguration(params); } /** * Checks if the browser supports notifications. * * @return {boolean} true if the browser supports notifications, false otherwise. */ public checkNotificationAbility(): boolean { if (window && 'Notification' in window) { return true; } return false; } /** * Checks if the browser has notification capability * If the browser already has notification permission, returns true * Otherwise, requests permission and returns the result * @return Promise<boolean> */ public async requestNotificationPermission(): Promise<boolean> { if (!this.checkNotificationAbility()) { console.error('The browser does not support notifications.'); return false; } if (window.Notification.permission === 'granted') { return true; } try { const permission = await Notification.requestPermission(); if (permission === 'granted') { return true; } } catch { return false; } return false; } /** * Checks if the current page has focus. * @return {boolean} */ public checkPageFocus(): boolean { return document.hasFocus(); } /** * use new configuration to overwrite old configuration * @param params * @return {void} */ private setConfiguration(params?: INotificationConstructorParams): void { if (!params) return; if (params.allowNotifications !== undefined) { this.allowNotifications = params.allowNotifications; } if (params.showPreviews !== undefined) { this.showPreviews = params.showPreviews; } if (params.notificationTitle !== undefined) { this.notificationTitle = params.notificationTitle; } if (params.notificationIcon !== undefined) { this.notificationIcon = params.notificationIcon; } } /** * Checks if a message needs to be notified. * * A message does not need to be notified if it: * - is null or undefined * - does not have an ID or a type * - is revoked or deleted * - is a typing message * - is of type MSG_GRP_TIP or MSG_GRP_SYS_NOTICE * - the page is focused and the message belongs to the current conversation * * @param {IMessageModel} message - The message to check * @return {boolean} - True if the message needs to be notified, false otherwise */ private isMessageNeedNotification(message: IMessageModel): boolean { if (!message || !message.ID || !message.type || message.isRevoked || message.isDeleted || isTypingMessage?.(message)) { return false; } if (message.type === TYPES.MSG_GRP_TIP || message.type === TYPES.MSG_GRP_SYS_NOTICE) { return false; } const currentConversationID = TUIStore.getData(StoreName.CONV, 'currentConversationID'); if (this.checkPageFocus() && message?.conversationID === currentConversationID) { return false; } return true; } /** * Returns the notification type 'call' or 'chat'. * @param {IMessageModel} message * @return {'chat' | 'call'} call' or 'chat' */ private getNotificationType(message: IMessageModel): NotificationType { if (message.type === TYPES.MSG_CUSTOM) { const { businessID } = safeParse(message.payload?.data); if (businessID === 1 || businessID === 'av_call') { return 'call'; } } return 'chat'; } /** * Retrieves the content for chat type message notifications. * @param {IMessageModel} message * @return {Promise<string>} */ private async getChatNotificationContent(message: IMessageModel): Promise<string> { let content = ''; if (!message || !message.ID || !message.type) { return content; } if (this.showPreviews) { content = await this.getChatNotificationContentTitle(message); content += this.getChatNotificationContentText(message); } else { const totalUnreadMessageCount = TUIStore.getData(StoreName.CONV, 'totalUnreadCount'); content = `${TUITranslateService.t('TUIChat.您有')} ${totalUnreadMessageCount} ${TUITranslateService.t('TUIChat.条新消息')}`; } return content; } /** * Retrieves the content title for chat notifications. * @param {IMessageModel} message - The message object. * @returns {Promise<string>} - The content title for chat notifications. */ private async getChatNotificationContentTitle(message: IMessageModel): Promise<string> { let title = ''; let conversationProfile; let conversation: IConversationModel; switch (message.conversationType) { case TYPES.CONV_C2C: title = (message.nick || message.from) + ': '; break; case TYPES.CONV_GROUP: conversationProfile = await TUIConversationService.getConversationProfile(message.conversationID); conversation = conversationProfile?.data?.conversation; title = conversation?.groupProfile?.name || message.conversationID; title += ': '; break; case TYPES.CONV_SYSTEM: title = `${TUITranslateService.t('TUIChat.系统消息')}: `; break; default: break; } return title; } /** * Get the content of the chat notification * @param {IMessageModel} message * @return {string} */ private getChatNotificationContentText(message: IMessageModel): string { let content = ''; switch (message.type) { case TYPES.MSG_TEXT: content += message?.payload?.text; break; case TYPES.MSG_CUSTOM: content += TUITranslateService.t('TUIChat.自定义'); break; case TYPES.MSG_IMAGE: content += TUITranslateService.t('TUIChat.图片'); break; case TYPES.MSG_AUDIO: content += TUITranslateService.t('TUIChat.语音'); break; case TYPES.MSG_VIDEO: content += TUITranslateService.t('TUIChat.视频'); break; case TYPES.MSG_FILE: content += TUITranslateService.t('TUIChat.文件'); break; case TYPES.MSG_FACE: content += TUITranslateService.t('TUIChat.表情'); break; case TYPES.MSG_MERGER: content += TUITranslateService.t('TUIChat.聊天记录'); break; case TYPES.MSG_LOCATION: content += TUITranslateService.t('TUIChat.位置'); break; default: break; } return content; } /** * Get the content of the call notification. * @param message - The message object. * @returns An object containing the notification content and call end status. */ private getCallNotificationInfo(message: IMessageModel): { content: string; callEnd: boolean } { let content = ''; let callEnd = false; try { if (message.type !== TYPES.MSG_CUSTOM || !message.payload?.data) { throw new Error(); } const callInfo = safeParse(message.payload.data); const callDataInfo = safeParse(callInfo.data); if (callInfo.businessID !== 1) { throw new Error(); } switch (callInfo.actionType) { case 1: if ( (callInfo.groupID && callInfo.timeout > 0) || (!callInfo.call_end && !callInfo.groupID && !(callDataInfo?.data && (callDataInfo?.data.cmd === 'switchToAudio' || callDataInfo?.data.cmd === 'switchToVideo'))) ) { callEnd = false; content = this.showPreviews ? `${callInfo.inviter} ${TUITranslateService.t('TUIChat.发起通话')}` : `${TUITranslateService.t('TUIChat.发起通话')}`; } break; case 2: callEnd = true; content = this.showPreviews ? `${callInfo.inviter} ${TUITranslateService.t('TUIChat.取消通话')}` : `${TUITranslateService.t('TUIChat.取消通话')}`; break; default: throw new Error(); } } catch (error: unknown) { console.warn(error); } return { content, callEnd, }; } } export default TUINotification.getInstance();