UNPKG

@azure/communication-react

Version:

React library for building modern communication user experiences utilizing Azure Communication Services

1,138 lines • 54.8 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { toFlatCommunicationIdentifier } from "../../../../../acs-ui-common/src"; import { callWithChatAdapterStateFromBackingStates, mergeCallAdapterStateIntoCallWithChatAdapterState, mergeChatAdapterStateIntoCallWithChatAdapterState } from '../state/CallWithChatAdapterState'; import { _createAzureCommunicationChatAdapterInner, _createLazyAzureCommunicationChatAdapterInner, createAzureCommunicationChatAdapterFromClient } from '../../ChatComposite/adapter/AzureCommunicationChatAdapter'; import { EventEmitter } from 'events'; import { isCommunicationUserIdentifier } from '@azure/communication-common'; import { _createAzureCommunicationCallAdapterInner } from '../../CallComposite/adapter/AzureCommunicationCallAdapter'; import { createAzureCommunicationCallAdapterFromClient } from '../../CallComposite/adapter/AzureCommunicationCallAdapter'; import { useEffect, useRef, useState } from 'react'; import { _toCommunicationIdentifier } from "../../../../../acs-ui-common/src"; import { busyWait } from '../../common/utils'; /** * For each time that we use the hook {@link useSelector} in the {@link CallWithChatComposite} we add another listener * to the `stateChanged` event on the this adapter. This number is set in relation to the number of * times that we are using the hook useSelector in the CallComposite. * * We will need to update this as the threshold is reached with more usages of useSelector. */ const MAX_EVENT_LISTENERS = 125; /** Context of Call with Chat, which is a centralized context for all state updates */ class CallWithChatContext { constructor(clientState, maxListeners = MAX_EVENT_LISTENERS) { this.emitter = new EventEmitter(); this.state = clientState; this.emitter.setMaxListeners(maxListeners); } onStateChange(handler) { this.emitter.on('stateChanged', handler); } offStateChange(handler) { this.emitter.off('stateChanged', handler); } setState(state) { this.state = state; this.emitter.emit('stateChanged', this.state); } getState() { return this.state; } updateClientState(clientState) { this.setState(clientState); } updateClientStateWithChatState(chatAdapterState) { this.updateClientState(mergeChatAdapterStateIntoCallWithChatAdapterState(this.state, chatAdapterState)); } unsetChatState() { this.updateClientState(Object.assign(Object.assign({}, this.state), { chat: undefined })); } updateClientStateWithCallState(callAdapterState) { this.updateClientState(mergeCallAdapterStateIntoCallWithChatAdapterState(this.state, callAdapterState)); } } /** * CallWithChat adapter backed by Azure Communication Services. * Created for easy use with the {@link CallWithChatComposite}. */ export class AzureCommunicationCallWithChatAdapter { // This function is used to store a listener to the internal map of chat event listeners and should be // used when a listener subscribes to a chat event storeChatEventListener(event, listener) { var _a; if (!this.chatEventListeners.has(event)) { this.chatEventListeners.set(event, new Set()); } (_a = this.chatEventListeners.get(event)) === null || _a === void 0 ? void 0 : _a.add(listener); } // This function is used to remove a listener from the internal map of chat event listeners and should // be used when a listener unsubscribes from a chat event removeChatEventListener(event, listener) { var _a; if (this.chatEventListeners.has(event)) { (_a = this.chatEventListeners.get(event)) === null || _a === void 0 ? void 0 : _a.delete(listener); } } constructor(callAdapter, chatAdapter) { this.emitter = new EventEmitter(); this.isAdapterDisposed = false; // This map is used to store the listeners subscribed to chat events so that we can re-subscribe them // to the new chat adapter when the thread id changes this.chatEventListeners = new Map(); this.bindPublicMethods(); this.callAdapter = callAdapter; this.context = new CallWithChatContext(callWithChatAdapterStateFromBackingStates(callAdapter)); const onChatStateChange = (newChatAdapterState) => { this.context.updateClientStateWithChatState(newChatAdapterState); }; this.onChatStateChange = onChatStateChange; if (chatAdapter) { this.updateChatAdapter(chatAdapter); this.originCallChatAdapter = chatAdapter; } const onCallStateChange = (newCallAdapterState) => { this.context.updateClientStateWithCallState(newCallAdapterState); }; this.callAdapter.onStateChange(onCallStateChange); this.callAdapter.on('breakoutRoomsUpdated', (eventData) => __awaiter(this, void 0, void 0, function* () { var _a, _b; if (eventData.type === 'join') { yield this.breakoutRoomJoined(eventData.data); } else if (eventData.type === 'assignedBreakoutRooms') { if (!eventData.data || eventData.data.state === 'closed') { if (this.originCallChatAdapter && ((_a = this.originCallChatAdapter) === null || _a === void 0 ? void 0 : _a.getState().thread.threadId) !== ((_b = this.context.getState().chat) === null || _b === void 0 ? void 0 : _b.threadId)) { this.updateChatAdapter(this.originCallChatAdapter); } } } })); this.callAdapter.on('callEnded', () => { var _a, _b, _c; // If the call ended is a breakout room call with breakout room settings then update the chat adapter to the // origin call if ((_b = (_a = this.context.getState().call) === null || _a === void 0 ? void 0 : _a.breakoutRooms) === null || _b === void 0 ? void 0 : _b.breakoutRoomSettings) { // Unsubscribe from chat adapter state changes (_c = this.chatAdapter) === null || _c === void 0 ? void 0 : _c.offStateChange(this.onChatStateChange); // Unassign chat adapter this.chatAdapter = undefined; // Set chat state to undefined to ensure that the chat thread of the breakout room is not shown this.context.unsetChatState(); // Update chat state to the origin call chat adapter if (this.originCallChatAdapter) { this.updateChatAdapter(this.originCallChatAdapter); } } }); this.onCallStateChange = onCallStateChange; } breakoutRoomJoined(call) { return __awaiter(this, void 0, void 0, function* () { var _a; const targetThreadId = call.info.threadId; // If the chat adapter is not on the target thread then we need to switch to the breakout room chat adapter if (targetThreadId && this.chatAdapter && this.chatAdapter.getState().thread.threadId !== targetThreadId) { // Unsubscribe from chat adapter state changes this.chatAdapter.offStateChange(this.onChatStateChange); // Set chat state to undefined to prevent showing chat thread of origin call this.context.unsetChatState(); // Check if the breakout room chat adapter has been initialized this.breakoutRoomChatAdapter = yield this.setBreakoutRoomChatAdapterToThread(targetThreadId); // Wait for the user to be added to the thread of chat adapter before updating the current chat adapter // to avoid chat errors of not having access to the chat thread. This delayed access to the chat thread // is also seen in Teams // Check up to 20 times every 500ms and then continue. yield busyWait(() => { var _a, _b; // If the call adapter's call id has been changed, then stop waiting and don't update the chat adapter if (((_a = this.context.getState().call) === null || _a === void 0 ? void 0 : _a.id) !== call.id) { return true; } const userAddedToThread = !!((_b = this.breakoutRoomChatAdapter) === null || _b === void 0 ? void 0 : _b.getState().thread.participants[toFlatCommunicationIdentifier(this.context.getState().userId)]); return userAddedToThread; }, 20); // If the call adapter's call has been changed while for waiting for breakout room chat adapter to be ready // then don't update the chat adapter if (((_a = this.context.getState().call) === null || _a === void 0 ? void 0 : _a.id) !== call.id) { return; } this.updateChatAdapter(this.breakoutRoomChatAdapter); } }); } setBreakoutRoomChatAdapterToThread(targetThreadId) { return __awaiter(this, void 0, void 0, function* () { if (this.breakoutRoomChatAdapter) { // If the breakout room chat adapter is not set on the target thread then unsubscribe, dispose, and // reinitialize the chat adapter for the target thread if (this.breakoutRoomChatAdapter.getState().thread.threadId !== targetThreadId) { this.breakoutRoomChatAdapter.offStateChange(this.onChatStateChange); this.breakoutRoomChatAdapter.dispose(); const newBreakoutRoomChatAdapter = yield this.createNewChatAdapterForThread(targetThreadId); return newBreakoutRoomChatAdapter; } else { return this.breakoutRoomChatAdapter; } } else { // Initiliaze the breakout room chat adapter for the target thread const newBreakoutRoomChatAdapter = yield this.createNewChatAdapterForThread(targetThreadId); return newBreakoutRoomChatAdapter; } }); } setChatAdapterPromise(chatAdapter) { chatAdapter.then(adapter => { if (!this.isAdapterDisposed) { this.updateChatAdapter(adapter); this.originCallChatAdapter = adapter; } }); } setCreateChatAdapterCallback(chatThreadCallBack) { this.createChatAdapterCallback = chatThreadCallBack; } createNewChatAdapterForThread(threadId) { if (this.createChatAdapterCallback) { return this.createChatAdapterCallback(threadId); } throw new Error('Unable to create chat adapter for thread because createChatAdapterCallback is not set'); } updateChatAdapter(chatAdapter) { var _a; (_a = this.chatAdapter) === null || _a === void 0 ? void 0 : _a.offStateChange(this.onChatStateChange); this.updateChatEventListeners(chatAdapter); this.chatAdapter = chatAdapter; this.chatAdapter.onStateChange(this.onChatStateChange); this.context.updateClientStateWithChatState(chatAdapter.getState()); this.emitter.emit('chatInitialized', this.chatAdapter); } updateChatEventListeners(chatAdapter) { var _a; for (const [eventName, listeners] of this.chatEventListeners) { for (const listener of listeners) { // eslint-disable-next-line @typescript-eslint/no-explicit-any (_a = this.chatAdapter) === null || _a === void 0 ? void 0 : _a.off(eventName, listener); // eslint-disable-next-line @typescript-eslint/no-explicit-any chatAdapter.on(eventName, listener); } } } bindPublicMethods() { this.joinCall.bind(this); this.leaveCall.bind(this); this.startCall.bind(this); this.onStateChange.bind(this); this.offStateChange.bind(this); this.getState.bind(this); this.dispose.bind(this); this.setCamera.bind(this); this.setMicrophone.bind(this); this.setSpeaker.bind(this); this.askDevicePermission.bind(this); this.queryCameras.bind(this); this.queryMicrophones.bind(this); this.querySpeakers.bind(this); this.startCamera.bind(this); this.stopCamera.bind(this); this.mute.bind(this); this.unmute.bind(this); this.startScreenShare.bind(this); this.stopScreenShare.bind(this); this.raiseHand.bind(this); this.lowerHand.bind(this); this.onReactionClick.bind(this); this.removeParticipant.bind(this); this.createStreamView.bind(this); this.disposeStreamView.bind(this); this.disposeScreenShareStreamView.bind(this); this.fetchInitialData.bind(this); this.sendMessage.bind(this); this.sendReadReceipt.bind(this); this.sendTypingIndicator.bind(this); this.loadPreviousChatMessages.bind(this); this.updateMessage.bind(this); this.deleteMessage.bind(this); this.on.bind(this); this.off.bind(this); this.downloadResourceToCache = this.downloadResourceToCache.bind(this); this.removeResourceFromCache = this.removeResourceFromCache.bind(this); this.holdCall.bind(this); this.resumeCall.bind(this); this.addParticipant.bind(this); this.sendDtmfTone.bind(this); this.startCaptions.bind(this); this.stopCaptions.bind(this); this.setSpokenLanguage.bind(this); this.setCaptionLanguage.bind(this); this.sendRealTimeText.bind(this); this.startVideoBackgroundEffect.bind(this); this.stopVideoBackgroundEffects.bind(this); this.updateBackgroundPickerImages.bind(this); this.startNoiseSuppressionEffect.bind(this); this.stopNoiseSuppressionEffect.bind(this); } /** Join existing Call. */ joinCall(options) { if (typeof options === 'boolean') { return this.callAdapter.joinCall(options); } else { return this.callAdapter.joinCall(options); } } /** Leave current Call. */ leaveCall(forEveryone) { return __awaiter(this, void 0, void 0, function* () { // Only remove self from the GroupCall. Contoso must manage access to Chat. yield this.callAdapter.leaveCall(forEveryone); }); } /** Start a new Call. */ startCall(participants, options) { if (participants.length === 0) { throw new Error('At least one participant is required to start a call'); } if (typeof participants[0] === 'string') { return this.callAdapter.startCall(participants, options); } else { return this.callAdapter.startCall(participants, options); } } /** * Subscribe to state change events. * @param handler - handler to be called when the state changes. This is passed the new state. */ onStateChange(handler) { this.context.onStateChange(handler); } /** * Unsubscribe to state change events. * @param handler - handler to be no longer called when state changes. */ offStateChange(handler) { this.context.offStateChange(handler); } /** Get current Call and Chat state. */ getState() { return this.context.getState(); } /** Dispose of the current CallWithChatAdapter. */ dispose() { this.isAdapterDisposed = true; if (this.chatAdapter) { this.chatAdapter.offStateChange(this.onChatStateChange); this.chatAdapter.dispose(); } this.callAdapter.offStateChange(this.onCallStateChange); this.callAdapter.dispose(); // Clear chat event listeners this.chatEventListeners.clear(); } /** Remove a participant from the Call only. */ removeParticipant(userId) { return __awaiter(this, void 0, void 0, function* () { let participant = userId; participant = _toCommunicationIdentifier(userId); yield this.callAdapter.removeParticipant(participant); }); } setCamera(device, options) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.setCamera(device, options); }); } /** Set the microphone to be used in the Call. */ setMicrophone(device) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.setMicrophone(device); }); } /** Set the speaker to be used in the Call. */ setSpeaker(device) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.setSpeaker(device); }); } askDevicePermission(constraints) { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.askDevicePermission(constraints); }); } /** Query for available cameras. */ queryCameras() { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.queryCameras(); }); } /** Query for available microphones. */ queryMicrophones() { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.queryMicrophones(); }); } /** Query for available speakers. */ querySpeakers() { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.querySpeakers(); }); } /** Start the camera for the user in the Call. */ startCamera(options) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.startCamera(options); }); } /** Stop the camera for the user in the Call. */ stopCamera() { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.stopCamera(); }); } /** Mute the user in the Call. */ mute() { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.mute(); }); } /** Unmute the user in the Call. */ unmute() { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.unmute(); }); } /** Trigger the user to start screen share. */ startScreenShare() { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.startScreenShare(); }); } /** Stop the current active screen share. */ stopScreenShare() { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.stopScreenShare(); }); } /** Raise hand for local user. */ raiseHand() { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.raiseHand(); }); } /** Lower hand for local user. */ lowerHand() { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.lowerHand(); }); } /** Reaction clicked by the local user. */ onReactionClick(reaction) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.onReactionClick(reaction); }); } /** Create a stream view for a remote participants video feed. */ createStreamView(remoteUserId, options) { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.createStreamView(remoteUserId, options); }); } /** Dispose of a created stream view of a remote participants video feed. */ disposeStreamView(remoteUserId, options) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.disposeStreamView(remoteUserId, options); }); } /** Dispose of a remote screen share */ disposeScreenShareStreamView(remoteUserId) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.disposeScreenShareStreamView(remoteUserId); }); } /** Dispose of a remote video stream */ disposeRemoteVideoStreamView(remoteUserId) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.disposeRemoteVideoStreamView(remoteUserId); }); } /** Dispose of the local video stream */ disposeLocalVideoStreamView() { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.disposeLocalVideoStreamView(); }); } /** Create a together mode stream view */ createTogetherModeStreamView(options) { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.createTogetherModeStreamView(options); }); } /** Start together mode for all participants */ startTogetherMode() { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.startTogetherMode(); }); } /** Set together mode scene size */ setTogetherModeSceneSize(width, height) { return this.callAdapter.setTogetherModeSceneSize(width, height); } /** Dispose together mode video stream */ disposeTogetherModeStreamView() { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.disposeTogetherModeStreamView(); }); } /** Fetch initial Call and Chat data such as chat messages. */ fetchInitialData() { return __awaiter(this, void 0, void 0, function* () { return yield this.executeWithResolvedChatAdapter(adapter => { return adapter.fetchInitialData(); }); }); } /** Send a chat message. */ sendMessage(content) { return __awaiter(this, void 0, void 0, function* () { return yield this.executeWithResolvedChatAdapter(adapter => { return adapter.sendMessage(content); }); }); } /** Send a chat read receipt. */ sendReadReceipt(chatMessageId) { return __awaiter(this, void 0, void 0, function* () { return yield this.executeWithResolvedChatAdapter(adapter => { return adapter.sendReadReceipt(chatMessageId); }); }); } /** Send an isTyping indicator. */ sendTypingIndicator() { return __awaiter(this, void 0, void 0, function* () { return yield this.executeWithResolvedChatAdapter(adapter => { return adapter.sendTypingIndicator(); }); }); } /** Load previous Chat messages. */ loadPreviousChatMessages(messagesToLoad) { return __awaiter(this, void 0, void 0, function* () { return yield this.executeWithResolvedChatAdapter(adapter => { return adapter.loadPreviousChatMessages(messagesToLoad); }); }); } /** Update an existing message. */ updateMessage(messageId, content, options) { return __awaiter(this, void 0, void 0, function* () { return this.executeWithResolvedChatAdapter(adapter => { return adapter.updateMessage(messageId, content, options); }); }); } /** Delete an existing message. */ deleteMessage(messageId) { return __awaiter(this, void 0, void 0, function* () { return yield this.executeWithResolvedChatAdapter(adapter => { return adapter.deleteMessage(messageId); }); }); } downloadResourceToCache(resourceDetails) { return __awaiter(this, void 0, void 0, function* () { this.executeWithResolvedChatAdapter(adapter => { adapter.downloadResourceToCache(resourceDetails); }); }); } removeResourceFromCache(resourceDetails) { this.executeWithResolvedChatAdapter(adapter => { adapter.removeResourceFromCache(resourceDetails); }); } holdCall() { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.holdCall(); }); } resumeCall() { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.resumeCall(); }); } addParticipant(participant, options) { return __awaiter(this, void 0, void 0, function* () { if (isCommunicationUserIdentifier(participant)) { return yield this.callAdapter.addParticipant(participant); } else { return yield this.callAdapter.addParticipant(participant, options); } }); } sendDtmfTone(dtmfTone) { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.sendDtmfTone(dtmfTone); }); } startCaptions(options) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.startCaptions(options); }); } stopCaptions(options) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.stopCaptions(options); }); } setCaptionLanguage(language) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.setCaptionLanguage(language); }); } setSpokenLanguage(language) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.setSpokenLanguage(language); }); } sendRealTimeText(text, isFinalized) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.sendRealTimeText(text, isFinalized); }); } startVideoBackgroundEffect(videoBackgroundEffect) { return __awaiter(this, void 0, void 0, function* () { yield this.callAdapter.startVideoBackgroundEffect(videoBackgroundEffect); }); } stopVideoBackgroundEffects() { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.stopVideoBackgroundEffects(); }); } updateBackgroundPickerImages(backgroundImages) { return this.callAdapter.updateBackgroundPickerImages(backgroundImages); } updateSelectedVideoBackgroundEffect(selectedVideoBackground) { return this.callAdapter.updateSelectedVideoBackgroundEffect(selectedVideoBackground); } startNoiseSuppressionEffect() { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.startNoiseSuppressionEffect(); }); } stopNoiseSuppressionEffect() { return __awaiter(this, void 0, void 0, function* () { return yield this.callAdapter.stopNoiseSuppressionEffect(); }); } submitSurvey(survey) { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.submitSurvey(survey); }); } startSpotlight(userIds) { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.startSpotlight(userIds); }); } stopSpotlight(userIds) { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.stopSpotlight(userIds); }); } stopAllSpotlight() { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.stopAllSpotlight(); }); } muteParticipant(userId) { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.muteParticipant(userId); }); } muteAllRemoteParticipants() { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.muteAllRemoteParticipants(); }); } returnFromBreakoutRoom() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d; if (this.originCallChatAdapter && ((_a = this.context.getState().chat) === null || _a === void 0 ? void 0 : _a.threadId) !== this.originCallChatAdapter.getState().thread.threadId) { (_b = this.breakoutRoomChatAdapter) === null || _b === void 0 ? void 0 : _b.dispose(); this.updateChatAdapter(this.originCallChatAdapter); } // Check if breakout room settings are defined to verify the user is in a breakout room before // returning to the origin call. const breakoutRoomSettings = (_d = (_c = this.context.getState().call) === null || _c === void 0 ? void 0 : _c.breakoutRooms) === null || _d === void 0 ? void 0 : _d.breakoutRoomSettings; if (breakoutRoomSettings) { yield this.callAdapter.returnFromBreakoutRoom(); } }); } forbidAudio(userIds) { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.forbidAudio(userIds); }); } permitAudio(userIds) { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.permitAudio(userIds); }); } forbidOthersAudio() { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.forbidOthersAudio(); }); } permitOthersAudio() { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.permitOthersAudio(); }); } forbidVideo(userIds) { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.forbidVideo(userIds); }); } permitVideo(userIds) { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.permitVideo(userIds); }); } forbidOthersVideo() { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.forbidOthersVideo(); }); } permitOthersVideo() { return __awaiter(this, void 0, void 0, function* () { return this.callAdapter.permitOthersVideo(); }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any on(event, listener) { switch (event) { case 'callParticipantsJoined': this.callAdapter.on('participantsJoined', listener); break; case 'callParticipantsLeft': this.callAdapter.on('participantsLeft', listener); break; case 'callEnded': this.callAdapter.on('callEnded', listener); break; case 'isMutedChanged': this.callAdapter.on('isMutedChanged', listener); break; case 'callIdChanged': this.callAdapter.on('callIdChanged', listener); break; case 'isLocalScreenSharingActiveChanged': this.callAdapter.on('isLocalScreenSharingActiveChanged', listener); break; case 'displayNameChanged': this.callAdapter.on('displayNameChanged', listener); break; case 'isSpeakingChanged': this.callAdapter.on('isSpeakingChanged', listener); break; case 'selectedMicrophoneChanged': this.callAdapter.on('selectedMicrophoneChanged', listener); break; case 'selectedSpeakerChanged': this.callAdapter.on('selectedSpeakerChanged', listener); break; case 'captionsReceived': this.callAdapter.on('captionsReceived', listener); break; case 'realTimeTextReceived': this.callAdapter.on('realTimeTextReceived', listener); break; case 'isCaptionsActiveChanged': this.callAdapter.on('isCaptionsActiveChanged', listener); break; case 'isCaptionLanguageChanged': this.callAdapter.on('isCaptionLanguageChanged', listener); break; case 'isSpokenLanguageChanged': this.callAdapter.on('isSpokenLanguageChanged', listener); break; case 'messageReceived': this.storeChatEventListener('messageReceived', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.on('messageReceived', listener); }); break; case 'messageEdited': this.storeChatEventListener('messageEdited', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.on('messageEdited', listener); }); break; case 'messageDeleted': this.storeChatEventListener('messageDeleted', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.on('messageDeleted', listener); }); break; case 'messageSent': this.storeChatEventListener('messageSent', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.on('messageSent', listener); }); break; case 'messageRead': this.storeChatEventListener('messageRead', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.on('messageRead', listener); }); break; case 'chatParticipantsAdded': this.storeChatEventListener('chatParticipantsAdded', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.on('participantsAdded', listener); }); break; case 'chatParticipantsRemoved': this.storeChatEventListener('chatParticipantsRemoved', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.on('participantsRemoved', listener); }); break; case 'callError': this.callAdapter.on('error', listener); break; case 'chatError': this.storeChatEventListener('chatError', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.on('error', listener); }); break; case 'chatInitialized': this.emitter.on(event, listener); break; case 'capabilitiesChanged': this.callAdapter.on('capabilitiesChanged', listener); break; case 'spotlightChanged': this.callAdapter.on('spotlightChanged', listener); break; case 'breakoutRoomsUpdated': this.callAdapter.on('breakoutRoomsUpdated', listener); break; default: throw `Unknown AzureCommunicationCallWithChatAdapter Event: ${event}`; } } // eslint-disable-next-line @typescript-eslint/no-explicit-any off(event, listener) { switch (event) { case 'callParticipantsJoined': this.callAdapter.off('participantsJoined', listener); break; case 'callParticipantsLeft': this.callAdapter.off('participantsLeft', listener); break; case 'callEnded': this.callAdapter.off('callEnded', listener); break; case 'isMutedChanged': this.callAdapter.off('isMutedChanged', listener); break; case 'callIdChanged': this.callAdapter.off('callIdChanged', listener); break; case 'isLocalScreenSharingActiveChanged': this.callAdapter.off('isLocalScreenSharingActiveChanged', listener); break; case 'displayNameChanged': this.callAdapter.off('displayNameChanged', listener); break; case 'isSpeakingChanged': this.callAdapter.off('isSpeakingChanged', listener); break; case 'selectedMicrophoneChanged': this.callAdapter.off('selectedMicrophoneChanged', listener); break; case 'selectedSpeakerChanged': this.callAdapter.off('selectedSpeakerChanged', listener); break; case 'captionsReceived': this.callAdapter.off('captionsReceived', listener); break; case 'realTimeTextReceived': this.callAdapter.off('realTimeTextReceived', listener); break; case 'isCaptionsActiveChanged': this.callAdapter.off('isCaptionsActiveChanged', listener); break; case 'isCaptionLanguageChanged': this.callAdapter.off('isCaptionLanguageChanged', listener); break; case 'isSpokenLanguageChanged': this.callAdapter.off('isSpokenLanguageChanged', listener); break; case 'messageReceived': this.removeChatEventListener('messageReceived', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.off('messageReceived', listener); }); break; case 'messageEdited': this.removeChatEventListener('messageEdited', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.off('messageEdited', listener); }); break; case 'messageDeleted': this.removeChatEventListener('messageDeleted', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.off('messageDeleted', listener); }); break; case 'messageSent': this.removeChatEventListener('messageSent', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.off('messageSent', listener); }); break; case 'messageRead': this.removeChatEventListener('messageRead', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.off('messageRead', listener); }); break; case 'chatParticipantsAdded': this.removeChatEventListener('chatParticipantsAdded', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.off('participantsAdded', listener); }); break; case 'chatParticipantsRemoved': this.removeChatEventListener('chatParticipantsRemoved', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.off('participantsRemoved', listener); }); break; case 'callError': this.callAdapter.off('error', listener); break; case 'chatError': this.removeChatEventListener('chatError', listener); this.executeWithResolvedChatAdapter(adapter => { adapter.off('error', listener); }); break; case 'chatInitialized': this.emitter.off(event, listener); break; case 'capabilitiesChanged': this.callAdapter.off('capabilitiesChanged', listener); break; case 'spotlightChanged': this.callAdapter.off('spotlightChanged', listener); break; case 'breakoutRoomsUpdated': this.callAdapter.off('breakoutRoomsUpdated', listener); break; default: throw `Unknown AzureCommunicationCallWithChatAdapter Event: ${event}`; } } // eslint-disable-next-line @typescript-eslint/no-explicit-any executeWithResolvedChatAdapter(callback) { if (!this.chatAdapter) { console.error('Chat is not initialized'); } else { return callback(this.chatAdapter); } } } /** * Arguments for use in {@link createAzureCommunicationCallWithChatAdapter} to join a Group Call with an associated Chat thread. * @private */ export class CallAndChatProvider { constructor(locator) { this.locator = locator; } isCallInfoRequired() { return false; } getChatThreadPromise() { return __awaiter(this, void 0, void 0, function* () { return this.getChatThread(); }); } getChatThread() { return this.locator.chatThreadId; } } /** * Arguments for use in {@link createAzureCommunicationCallWithChatAdapter} to join a Teams meeting with an associated Chat thread. * * @private */ export class TeamsMeetingLinkProvider { constructor(locator, callAdapterPromise) { this.locator = locator; this.callAdapterPromise = callAdapterPromise; } isCallInfoRequired() { return true; } getChatThread() { throw new Error('Chat thread ID should be retrieved from call.callInfo using method getChatThreadPromise'); } getChatThreadPromise() { return __awaiter(this, void 0, void 0, function* () { { // Wait for the call to be connected and get the chat thread ID from `call.callInfo`. const chatThreadPromise = new Promise(resolve => { this.callAdapterPromise.then(callAdapter => { // Ensure function is idempotent by removing any existing subscription. if (this.callAdapterSubscription) { callAdapter.offStateChange(this.callAdapterSubscription); } this.callAdapterSubscription = (state) => { var _a, _b, _c; if (((_a = state.call) === null || _a === void 0 ? void 0 : _a.state) === 'Connected' && ((_b = state.call.info) === null || _b === void 0 ? void 0 : _b.threadId)) { if (this.callAdapterSubscription) { callAdapter.offStateChange(this.callAdapterSubscription); } this.callAdapterSubscription = undefined; resolve((_c = state.call.info) === null || _c === void 0 ? void 0 : _c.threadId); } }; callAdapter.onStateChange(this.callAdapterSubscription); }); }); return chatThreadPromise; } }); } } /** * Arguments for use in {@link createAzureCommunicationCallWithChatAdapter} to join a Teams meeting using meeting id. * * @private */ export class TeamsMeetingIdProvider { constructor(locator, callAdapter) { this.locator = locator; this.callAdapter = callAdapter; } isCallInfoRequired() { return true; } getChatThread() { throw new Error('Chat thread ID is not available for Teams meeting ID'); } /** * Wait call to be connected to get thread ID. * @returns the chat thread ID for the given meeting ID. */ getChatThreadPromise() { return __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => { const stateChangeListener = (state) => { var _a, _b, _c; if (((_a = state.call) === null || _a === void 0 ? void 0 : _a.state) === 'Connected' && ((_b = state.call.info) === null || _b === void 0 ? void 0 : _b.threadId)) { this.callAdapter.then(adapter => { adapter.offStateChange(stateChangeListener); }); resolve((_c = state.call.info) === null || _c === void 0 ? void 0 : _c.threadId); } }; this.callAdapter.then(adapter => { var _a, _b, _c; const callState = (_a = adapter.getState().call) === null || _a === void 0 ? void 0 : _a.state; const threadId = (_c = (_b = adapter.getState().call) === null || _b === void 0 ? void 0 : _b.info) === null || _c === void 0 ? void 0 : _c.threadId; if (callState === 'Connected' && threadId) { resolve(threadId); } else { adapter.onStateChange(stateChangeListener); } }); }); }); } } /** * Create a CallWithChatAdapter backed by Azure Communication services * to plug into the {@link CallWithChatComposite}. * * @public */ export const createAzureCommunicationCallWithChatAdapter = (_a) => __awaiter(void 0, [_a], void 0, function* ({ userId, displayName, credential, endpoint, locator, alternateCallerId, callAdapterOptions }) { const callAdapterLocator = isTeamsMeetingLocator(locator) ? locator : locator.callLocator; const callAdapter = _createAzureCommunicationCallAdapterInner({ userId, displayName, credential, locator: callAdapterLocator, alternateCallerId, options: callAdapterOptions, telemetryImplementationHint: 'CallWithChat' }); const chatThreadAdapter = _createChatThreadAdapterInner(locator, callAdapter); const chatAdapterOptions = { onFetchProfile: callAdapterOptions === null || callAdapterOptions === void 0 ? void 0 : callAdapterOptions.onFetchProfile }; if (chatThreadAdapter.isCallInfoRequired()) { const callWithChatAdapter = new AzureCommunicationCallWithChatAdapter(yield callAdapter); const chatAdapterPromise = _createLazyAzureCommunicationChatAdapterInner(endpoint, userId, displayName, credential, chatThreadAdapter.getChatThreadPromise(), 'CallWithChat', chatAdapterOptions); callWithChatAdapter.setChatAdapterPromise(chatAdapterPromise); callWithChatAdapter.setCreateChatAdapterCallback((threadId) => _createAzureCommunicationChatAdapterInner(endpoint, userId, displayName, credential, threadId, 'CallWithChat', chatAdapterOptions)); return callWithChatAdapter; } else { const chatAdapter = _createAzureCommunicationChatAdapterInner(endpoint, userId, displayName, credential, chatThreadAdapter.getChatThread(), 'CallWithChat', chatAdapterOptions); const callWithChatAdapter = new AzureCommunicationCallWithChatAdapter(yield callAdapter, yield chatAdapter); callWithChatAdapter.setCreateChatAdapterCallback((threadId) => _createAzureCommunicationChatAdapterInner(endpoint, userId, displayName, credential, threadId, 'CallWithChat', chatAdapterOptions)); return callWithChatAdapter; } }); /** * A custom React hook to simplify the creation of {@link CallWithChatAdapter}. * * Similar to {@link createAzureCommunicationCallWithChatAdapter}, but takes care of asynchronous * creation of the adapter internally. * * Allows arguments to be undefined so that you can respect the rule-of-hooks and pass in arguments * as they are created. The adapter is only created when all arguments are defined. * * Note that you must memoize the arguments to avoid recreating adapter on each render. * See storybook for typical usage examples. * * @public */ export const useAzureCommunicationCallWithChatAdapter = (args, afterCreate, beforeDispose) => { const { credential, displayName, endpoint, locator, userId, alternateCallerId, callAdapterOptions } = args; // State update needed to rerender the parent component when a new adapter is created. const [adapter, setAdapter] = useState(undefined); // Ref needed for cleanup to access the old adapter created asynchronously. const adapterRef = useRef(undefined); const creatingAdapterRef = useRef(false); const afterCreateRef = useRef(undefined); const beforeDisposeRef = useRef(undefined); // These refs are updated on *each* render, so that the latest values // are used in the `useEffect` closures below. // Using a Ref ensures that new values for the callbacks do not trigger the // useEffect blocks, and