@azure/communication-react
Version:
React library for building modern communication user experiences utilizing Azure Communication Services
1,138 lines • 54.8 kB
JavaScript
// 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