@azure/communication-react
Version:
React library for building modern communication user experiences utilizing Azure Communication Services
1,149 lines (1,130 loc) • 3.54 MB
JavaScript
'use strict';
var communicationCommon = require('@azure/communication-common');
var reselect = require('reselect');
var communicationCalling = require('@azure/communication-calling');
var memoizeOne = require('memoize-one');
var logger = require('@azure/logger');
var events = require('events');
var immer = require('immer');
var React = require('react');
var react = require('@fluentui/react');
var reactComponents = require('@fluentui/react-components');
var reactIcons = require('@fluentui/react-icons');
var uuid = require('uuid');
var reactChat = require('@fluentui-contrib/react-chat');
var react$1 = require('@griffel/react');
var parse = require('html-react-parser');
var Linkify = require('react-linkify');
var DOMPurify = require('dompurify');
var reactFileTypeIcons = require('@fluentui/react-file-type-icons');
var reactHooks = require('@fluentui/react-hooks');
var reactUseDraggableScroll = require('react-use-draggable-scroll');
var copy = require('copy-to-clipboard');
var communicationChat = require('@azure/communication-chat');
var nanoid = require('nanoid');
var communicationCallingEffects = require('@azure/communication-calling-effects');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var reselect__namespace = /*#__PURE__*/_interopNamespaceDefault(reselect);
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const argsCmp = (args1, args2, objCmp) => {
return args1.length === args2.length && args1.every((arg1, index) => objCmp(args2[index], arg1));
};
/**
* The function memoize a series of function calls in a single pass,
* it memoizes all the args and return in a single run of the callback function, and read it in the next round of execution
* note: this is a memory opimized function which will only memoize one round of bulk calls
* @param fnToMemoize - the function needs to be bulk memorized and a key key paramter needs to be provided as cache id
* @param shouldCacheUpdate - the validate function for comparing 2 argument, return true when 2 args are equal
* @returns callback function includes a series calls of memoizedFn, and each call will get cache result if args are the same(according to shouldCacheUpdate fn)
* @example
* ```ts
* const items = [{id:1, value:3}];
* const heavyFn = (_key, value) => { // key is not used in the function, but it is a cache id
* // assume this is a heavy caculation
* return value+1;
* }
*
* const memoizeHeavyFnAll = memoizeFnAll(heavyFn);
* const generateValueArray = (memoizedHeavyFn) => (
* items.map(item => {
* memoizedHeavyFn(item.id, item.value);
* })
* );
*
* const result = memoizeHeavyFnAll(generateValueArray); // Cache: {}, nextCache: {1: 4 *new}, heavyFn call times: 1
*
* // Argument changed
* items[0].value = 2
* const result0 = memoizeHeavyFnAll(generateValueArray); // Cache: {1: 4}, nextCache: {1: 3 *new}, heavyFn call times: 1
*
* // Cache added
* items.push({id:3, value:4});
* const result1 = memoizeHeavyFnAll(generateValueArray); // Cache: {1: 3 *hit}, nextCache: {1: 3, 3: 5 *new}, heavyFn call times: 1
*
* // Cache removed
* delete items[0];
* const result2 = memoizeHeavyFnAll(generateValueArray); // Cache: {1: 3, 3: 5 *hit}, nextCache: {3: 5}, heavyFn call times: 0
* ```
*
* @public
*/
const memoizeFnAll = (fnToMemoize, shouldCacheUpdate = Object.is) => {
let cache = new Map();
let nextCache = new Map();
return (callback) => {
const memoizedFn = (key, ...args) => {
const value = cache.get(key);
if (value) {
const [preArgs, ret] = value;
if (argsCmp(preArgs, args, shouldCacheUpdate)) {
nextCache.set(key, [args, ret]);
return ret;
}
}
const ret = fnToMemoize(key, ...args);
nextCache.set(key, [args, ret]);
return ret;
};
const retValue = callback(memoizedFn);
cache = nextCache;
nextCache = new Map();
return retValue;
};
};
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* A string representation of a {@link @azure/communication-common#CommunicationIdentifier}.
*
* This string representation of CommunicationIdentifier is guaranteed to be stable for
* a unique Communication user. Thus,
* - it can be used to persist a user's identity in external databases.
* - it can be used as keys into a Map to store data for the user.
*
* @public
*/
const toFlatCommunicationIdentifier = (identifier) => {
return communicationCommon.getIdentifierRawId(identifier);
};
/**
* Reverse operation of {@link toFlatCommunicationIdentifier}.
*
* @public
*/
const fromFlatCommunicationIdentifier = (id) => {
// if the id passed is a phone number we need to build the rawId to pass in
const rawId = id.indexOf('+') === 0 ? '4:' + id : id;
return communicationCommon.createIdentifierFromRawId(rawId);
};
/**
* Returns a CommunicationIdentifier.
* @internal
*/
const _toCommunicationIdentifier = (id) => {
if (typeof id === 'string') {
return fromFlatCommunicationIdentifier(id);
}
return id;
};
/**
* Check if an object is identifier.
*
* @internal
*/
const _isValidIdentifier = (identifier) => {
return communicationCommon.isCommunicationUserIdentifier(identifier) || communicationCommon.isPhoneNumberIdentifier(identifier) || communicationCommon.isMicrosoftTeamsUserIdentifier(identifier) || communicationCommon.isUnknownIdentifier(identifier);
};
/**
* Check if given identifier is a Microsoft Teams user.
*
* @internal
* @param rawId - The rawId of the identifier.
* @returns True if the identifier is a Microsoft Teams user. False otherwise.
*/
const _isIdentityMicrosoftTeamsUser = (rawId) => {
if (!rawId) {
return false;
}
const identifier = _toCommunicationIdentifier(rawId);
return communicationCommon.isMicrosoftTeamsUserIdentifier(identifier);
};
function getDefaultExportFromCjs (x) {
return x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// GENERATED FILE. DO NOT EDIT MANUALLY.
var telemetryVersion = '1.30.0';
var telemetryVersion$1 = /*@__PURE__*/getDefaultExportFromCjs(telemetryVersion);
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* @private
*/
// Removes long suffixes that don't fit the constraints for telemetry application ID.
// e.g., the build suffix is dropped for alpha package versions.
const sanitize = (version) => {
const alphaIndex = version.search(/alpha/);
if (alphaIndex >= 0) {
return version.substring(0, alphaIndex + 5);
}
return version;
};
/**
* Takes a telemetry implementation hint and returns the numerical value.
*
* @private
*/
const getTelemetryImplementationHint = (telemetryImplementationHint) => {
switch (telemetryImplementationHint) {
case 'Call':
return 1;
case 'Chat':
return 2;
case 'CallWithChat':
return 3;
case 'StatefulComponents':
return 4;
default:
return 0;
}
};
/**
* Application ID to be included in telemetry data from the UI library.
* Template: acXYYY/<version>
* Where:
* - X describes a platform, [r: web, i: iOS, a: Android]
* - YYY describes what's running on this platform (optional, currently unused by this library):
* Y[0] is high-level artifact,
* [0: undefined, 1: AzureCommunicationLibrary, 2: ACS SampleApp]
* Y[1] is specific implementation,
* [0: undefined, 1: Call Composite, 2: Chat Composite, 3: CallWithChatComposite, 4: UI Components]
* Y[2] is reserved for implementation details,
* [0: undefined]
*
* @internal
*/
const _getApplicationId = (telemetryImplementationHint) => {
// We assume AzureCommunicationLibrary, as we don't currently have any public API to inject otherwise.
// This is consistent with the native iOS and Android implementations of telemetry.
const highLevelArtifact = 1;
const specificImplementation = getTelemetryImplementationHint(telemetryImplementationHint);
const implementationDetails = 0;
const version = telemetryVersion$1;
return sanitize(`acr${highLevelArtifact}${specificImplementation}${implementationDetails}/${version}`);
};
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* @internal
*
* Replace the pattern "\{\}" in str with the values passed in as vars
*
* @example
* ```ts
* _formatString("hello {name}. '{name}' is a rare name.", {name: "Foo"});
* // returns "hello Foo. 'Foo' is a rare name."
* ```
* @param str - The string to be formatted
* @param variables - Variables to use to format the string
* @returns a formatted string
*/
const _formatString = (str, vars) => {
if (!str) {
return '';
}
if (!vars) {
return str;
}
// regex to search for the pattern "\{\}"
const placeholdersRegex = /{(\w+)}/g;
return str.replace(placeholdersRegex, (_, k) => {
const replaceValue = vars[k];
return replaceValue === undefined ? `{${k}}` : replaceValue;
});
};
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* Wrap JSON.stringify in a try-catch as JSON.stringify throws an exception if it fails.
*
* Use this only in areas where the JSON.stringify is non-critical and OK for the JSON.stringify to fail, such as logging.
*
* @internal
*/
const _safeJSONStringify = (value, replacer = createSafeReplacer(), space) => {
try {
return JSON.stringify(value, replacer, space);
}
catch (e) {
console.error(e);
return undefined;
}
};
// Log all visited refs to avoid circular ref
const createSafeReplacer = () => {
const visited = new Set();
return function replacer(key, value) {
if (typeof value !== 'object') {
return value;
}
if (visited.has(value)) {
return 'Visited-Ref';
}
else {
visited.add(value);
return value;
}
};
};
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* @internal
* Converts units of rem to units of pixels
* @param rem - units of rem
* @returns units of pixels
*/
const _convertRemToPx = (rem) => {
return rem * getBrowserFontSizeInPx();
};
/**
* @internal
* Converts units of pixels to units of rem
* @param px - units of px
* @returns units of rem
*/
const _convertPxToRem = (px) => {
return px / getBrowserFontSizeInPx();
};
const getBrowserFontSizeInPx = () => {
let fontSizeInPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
// If browser font size is not a number, default to 16 px
if (Number.isNaN(fontSizeInPx)) {
fontSizeInPx = 16;
}
return fontSizeInPx;
};
/**
* @internal
* Disable dismiss on resize to work around a couple Fluent UI bugs
* - The Callout is dismissed whenever *any child of window (inclusive)* is resized. In practice, this
* happens when we change the VideoGallery layout, or even when the video stream element is internally resized
* by the headless SDK.
* - We also want to prevent dismiss when chat pane is scrolling especially a new message is added.
* A side effect of this workaround is that the context menu stays open when window is resized, and may
* get detached from original target visually. That bug is preferable to the bug when this value is not set -
* The Callout (frequently) gets dismissed automatically.
*/
const _preventDismissOnEvent = (ev) => {
return ev.type === 'resize' || ev.type === 'scroll';
};
/**
* @internal
* Helper function to get the keys of an object
*/
function _getKeys(obj) {
return Object.keys(obj);
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* @internal
* Converts px value to rem value.
* For example, an input of `16` will return `1rem`.
*/
const _pxToRem = (px) => `${px / 16}rem`;
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* @internal
* This is a log function to log structural data for easier parse in telemetry
*/
const _logEvent = (logger, event) => {
logger[event.level](_safeJSONStringify(event));
};
/**
* @private
*/
const getDeviceManager$1 = (state) => state.deviceManager;
/**
* @private
*/
const getRole$1 = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.role;
};
/**
* @private
*/
const isHideAttendeeNamesEnabled = (state, props) => {
var _a, _b;
return (_b = (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.hideAttendeeNames) !== null && _b !== void 0 ? _b : false;
};
/**
* @private
*/
const getCapabilities = (state, props) => { var _a, _b; return (_b = (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.capabilitiesFeature) === null || _b === void 0 ? void 0 : _b.capabilities; };
/**
* @private
*/
const getCallExists = (state, props) => !!state.calls[props.callId];
/**
* @private
*/
const getDominantSpeakers = (state, props) => { var _a; return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.dominantSpeakers; };
/**
* @private
*/
const getRemoteParticipants$1 = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.remoteParticipants;
};
/**
* @private
*/
const getRemoteParticipantsEnded = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.remoteParticipantsEnded;
};
/**
* @private
*/
const getLocalParticipantRaisedHand$1 = (state, props) => {
var _a, _b;
return (_b = (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.raiseHand) === null || _b === void 0 ? void 0 : _b.localParticipantRaisedHand;
};
/**
* @private
*/
const getSpotlightCallFeature = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.spotlight;
};
/**
* @private
*/
const getLocalParticipantReactionState = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.localParticipantReaction;
};
/**
* @private
*/
const getIsScreenSharingOn = (state, props) => { var _a; return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.isScreenSharingOn; };
/**
* @private
*/
const getIsMuted = (state, props) => { var _a; return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.isMuted; };
/**
* @private
*/
const getOptimalVideoCount = (state, props) => { var _a; return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.optimalVideoCount.maxRemoteVideoStreams; };
/**
* @private
*/
const getLocalVideoStreams$1 = (state, props) => { var _a; return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.localVideoStreams; };
/**
* @private
*/
const getScreenShareRemoteParticipant = (state, props) => { var _a; return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.screenShareRemoteParticipant; };
/**
* @private
*/
const getDisplayName$2 = (state) => { var _a; return (_a = state.callAgent) === null || _a === void 0 ? void 0 : _a.displayName; };
/**
* @private
*/
const getIdentifier = (state) => toFlatCommunicationIdentifier(state.userId);
/**
* @private
*/
const getLatestErrors$1 = (state) => state.latestErrors;
/**
* @private
*/
const getLatestNotifications$1 = (state) => state.latestNotifications;
/**
* @private
*/
const getDiagnostics = (state, props) => { var _a; return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.diagnostics; };
/**
* @private
*/
const getCallState = (state, props) => { var _a; return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.state; };
/**
* @private
*/
const getEnvironmentInfo$1 = (state) => {
return state.environmentInfo;
};
/** @private */
const getParticipantCount = (state, props) => {
return undefined;
};
/** @private */
const getCaptions = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.captionsFeature.captions;
};
/** @private */
const getCaptionsStatus$1 = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.captionsFeature.isCaptionsFeatureActive;
};
/** @private */
const getStartCaptionsInProgress = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.captionsFeature.startCaptionsInProgress;
};
/** @private */
const getCurrentCaptionLanguage = (state, props) => {
var _a, _b;
// we default to 'en' if the currentCaptionLanguage is not set
if (((_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.captionsFeature.currentCaptionLanguage) === '') {
return 'en';
}
return (_b = state.calls[props.callId]) === null || _b === void 0 ? void 0 : _b.captionsFeature.currentCaptionLanguage;
};
/** @private */
const getCurrentSpokenLanguage = (state, props) => {
var _a;
// when currentSpokenLanguage is '', it means the spoken language is not set.
// In this case we suggest showing the captions modal to set the spoken language when starting captions
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.captionsFeature.currentSpokenLanguage;
};
/** @private */
const getSupportedCaptionLanguages = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.captionsFeature.supportedCaptionLanguages;
};
/** @private */
const getSupportedSpokenLanguages = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.captionsFeature.supportedSpokenLanguages;
};
/** @private */
const getMeetingConferencePhones = (state, props) => {
var _a, _b;
return (_b = (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.meetingConference) === null || _b === void 0 ? void 0 : _b.conferencePhones;
};
/**
* selector for retrieving the incoming calls from state
* @returns the incoming calls in the call client state
* @private
*/
const getIncomingCalls = (state) => {
return Object.values(state.incomingCalls);
};
/**
* selector for retrieving the incoming calls that have been removed from state
* @returns the incoming calls that have been removed
* @private
*/
const getRemovedIncomingCalls = (state) => {
return Object.values(state.incomingCallsEnded);
};
/** @private */
const getAssignedBreakoutRoom$1 = (state, props) => {
var _a, _b;
return (_b = (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.breakoutRooms) === null || _b === void 0 ? void 0 : _b.assignedBreakoutRoom;
};
/** @private */
const getRealTimeTextStatus = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.realTimeTextFeature.isRealTimeTextFeatureActive;
};
/** @private */
const getRealTimeText = (state, props) => {
var _a;
return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.realTimeTextFeature.realTimeTexts;
};
/**
* @private
*/
const getTogetherModeCallFeature = (state, props) => { var _a; return (_a = state.calls[props.callId]) === null || _a === void 0 ? void 0 : _a.togetherMode; };
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __awaiter$$ = (window && window.__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());
});
};
/**
* Check if the call state represents being in the call
*
* @internal
*/
const _isInCall = (callStatus) => !!callStatus && !['None', 'Disconnected', 'Connecting', 'Ringing', 'EarlyMedia', 'Disconnecting'].includes(callStatus);
/**
* Check if the call state represents being in the lobby or waiting to be admitted.
*
* @internal
*/
const _isInLobbyOrConnecting = (callStatus) => !!callStatus && ['Connecting', 'Ringing', 'InLobby', 'EarlyMedia'].includes(callStatus);
/**
* Check if the device manager local video is on when not part of a call
* i.e. do unparented views exist.
*
* @internal
*/
const _isPreviewOn = (deviceManager) => {
var _a;
// TODO: we should take in a LocalVideoStream that developer wants to use as their 'Preview' view. We should also
// handle cases where 'Preview' view is in progress and not necessary completed.
return ((_a = deviceManager.unparentedViews[0]) === null || _a === void 0 ? void 0 : _a.view) !== undefined;
};
/**
* Dispose of all preview views
* We assume all unparented views are local preview views.
*
* @private
*/
const disposeAllLocalPreviewViews = (callClient) => __awaiter$$(void 0, void 0, void 0, function* () {
const unparentedViews = callClient.getState().deviceManager.unparentedViews;
for (const view of unparentedViews) {
yield callClient.disposeView(undefined, undefined, view);
}
});
/**
* Update the users displayNames based on the type of user they are
*
* @internal
*/
const _updateUserDisplayNames = (participants) => {
if (participants) {
return memoizedUpdateDisplayName(memoizedFn => {
return Object.values(participants).map(p => {
const pid = toFlatCommunicationIdentifier(p.identifier);
return memoizedFn(pid, p);
});
});
}
else {
return [];
}
};
/**
* Get the display name of a participant for given userId
*
* @internal
*
*/
const getRemoteParticipantDisplayName = (participantUserId, remoteParticipants) => {
let displayName;
if (remoteParticipants) {
const participant = remoteParticipants[participantUserId];
if (participant) {
displayName = participant.displayName;
}
}
return displayName;
};
const memoizedUpdateDisplayName = memoizeFnAll((participantId, participant) => {
if (communicationCommon.isPhoneNumberIdentifier(participant.identifier)) {
return Object.assign(Object.assign({}, participant), { displayName: participant.identifier.phoneNumber });
}
else {
return participant;
}
});
/**
* @private
* A type guard to ensure all participants are acceptable type for Teams call
*/
const isTeamsCallParticipants = (participants) => {
return participants.every(p => !communicationCommon.isCommunicationUserIdentifier(p));
};
/**
* @private
* Checks whether the user is a 'Ringing' PSTN user or in a 'Connecting' state.
*/
const _convertParticipantState = (participant) => {
return communicationCommon.isPhoneNumberIdentifier(participant.identifier) && participant.state === 'Connecting' ? 'Ringing' : participant.state;
};
/**
* @private
* Changes the display name of the participant based on the local and remote user's role.
*/
const maskDisplayNameWithRole = (displayName, localUserRole, participantRole, isHideAttendeeNamesEnabled) => {
let maskedDisplayName = displayName;
if (isHideAttendeeNamesEnabled && participantRole && participantRole === 'Attendee') {
if (localUserRole && localUserRole === 'Attendee') {
maskedDisplayName = '{AttendeeRole}';
}
if (localUserRole && (localUserRole === 'Presenter' || localUserRole === 'Co-organizer' || localUserRole === 'Organizer')) {
maskedDisplayName = `{AttendeeRole}(${displayName})`;
}
}
return maskedDisplayName;
};
/**
* Helper to create a local video stream from the selected camera.
* @private
*/
const createLocalVideoStream = (callClient) => __awaiter$$(void 0, void 0, void 0, function* () {
const camera = yield (callClient === null || callClient === void 0 ? void 0 : callClient.getState().deviceManager.selectedCamera);
if (camera) {
return new communicationCalling.LocalVideoStream(camera);
}
return undefined;
});
/**
* Get call state if existing, if not and the call not exists in ended record return undefined, if it never exists, throw an error.
* @private
*/
const getCallStateIfExist = (state, callId) => {
if (!state.calls[callId]) {
// If call has ended, we don't need to throw an error.
if (state.callsEnded[callId]) {
return undefined;
}
throw new Error(`Call Not Found: ${callId}`);
}
return state.calls[callId];
};
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* Selector for {@link MicrophoneButton} component.
*
* @public
*/
const microphoneButtonSelector = reselect__namespace.createSelector([getCallExists, getIsMuted, getDeviceManager$1, getCapabilities, getRole$1, getCallState], (callExists, isMuted, deviceManager, capabilities, role, callState) => {
const permission = deviceManager.deviceAccess ? deviceManager.deviceAccess.audio : true;
const incapable = (capabilities === null || capabilities === void 0 ? void 0 : capabilities.unmuteMic.isPresent) === false && (capabilities === null || capabilities === void 0 ? void 0 : capabilities.unmuteMic.reason) !== 'NotInitialized' || role === 'Consumer';
return {
disabled: !callExists || !permission || incapable || callState === 'LocalHold',
checked: callExists ? !isMuted : false,
microphones: deviceManager.microphones,
speakers: deviceManager.speakers,
selectedMicrophone: deviceManager.selectedMicrophone,
selectedSpeaker: deviceManager.selectedSpeaker
};
});
/**
* Selector for {@link CameraButton} component.
*
* @public
*/
const cameraButtonSelector = reselect__namespace.createSelector([getLocalVideoStreams$1, getDeviceManager$1, getCapabilities, getRole$1, getCallState], (localVideoStreams, deviceManager, capabilities, role, callState) => {
const previewOn = _isPreviewOn(deviceManager);
const localVideoFromCall = localVideoStreams === null || localVideoStreams === void 0 ? void 0 : localVideoStreams.find(stream => stream.mediaStreamType === 'Video');
const permission = deviceManager.deviceAccess ? deviceManager.deviceAccess.video : true;
const incapable = (capabilities === null || capabilities === void 0 ? void 0 : capabilities.turnVideoOn.isPresent) === false && (capabilities === null || capabilities === void 0 ? void 0 : capabilities.turnVideoOn.reason) !== 'NotInitialized' || role === 'Consumer';
return {
disabled: !deviceManager.selectedCamera || !permission || !deviceManager.cameras.length || incapable || callState === 'LocalHold',
checked: localVideoStreams !== undefined && localVideoStreams.length > 0 ? !!localVideoFromCall : previewOn,
cameras: deviceManager.cameras,
selectedCamera: deviceManager.selectedCamera
};
});
/**
* Selector for {@link RaiseHandButton} component.
*
* @public
*/
const raiseHandButtonSelector = reselect__namespace.createSelector([getLocalParticipantRaisedHand$1, getCallState], (raisedHand, callState) => {
return {
checked: raisedHand ? true : false,
disabled: callState === 'InLobby' ? true : callState === 'Connecting'
};
});
/**
* Selector for {@link ReactionButton} component.
*
* @public
*/
const reactionButtonSelector = reselect__namespace.createSelector([getLocalParticipantReactionState, getCallState], (reaction, callState) => {
return {
checked: reaction ? true : false,
disabled: callState !== 'Connected'
};
});
/**
* Selector for {@link ScreenShareButton} component.
*
* @public
*/
const screenShareButtonSelector = reselect__namespace.createSelector([getIsScreenSharingOn, getCallState, getCapabilities, getRole$1], (isScreenSharingOn, callState, capabilities, role) => {
let disabled = undefined;
disabled = disabled || (capabilities === null || capabilities === void 0 ? void 0 : capabilities.shareScreen.isPresent) === false && (capabilities === null || capabilities === void 0 ? void 0 : capabilities.shareScreen.reason) !== 'NotInitialized' || role === 'Consumer' || role === 'Attendee';
disabled = disabled || ['InLobby', 'Connecting', 'LocalHold'].includes(callState !== null && callState !== void 0 ? callState : 'None');
return {
checked: isScreenSharingOn,
disabled
};
});
/**
* Selector for {@link DevicesButton} component.
*
* @public
*/
const devicesButtonSelector = reselect__namespace.createSelector([getDeviceManager$1], deviceManager => {
return {
microphones: deviceManager.microphones,
speakers: deviceManager.speakers,
cameras: deviceManager.cameras,
selectedMicrophone: deviceManager.selectedMicrophone,
selectedSpeaker: deviceManager.selectedSpeaker,
selectedCamera: deviceManager.selectedCamera
};
});
/**
* Selector for the {@link HoldButton} component.
* @public
*/
const holdButtonSelector = reselect__namespace.createSelector([getCallState], callState => {
return {
checked: callState === 'LocalHold'
};
});
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __awaiter$_ = (window && window.__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());
});
};
/**
* @private
*/
const areStreamsEqual = (prevStream, newStream) => {
return !!prevStream && !!newStream && prevStream.source.id === newStream.source.id;
};
/**
* Create the common implementation of {@link CallingHandlers} for all types of Call
*
* @private
*/
const createDefaultCommonCallingHandlers = memoizeOne((callClient, deviceManager, call, options) => {
const onStartLocalVideo = () => __awaiter$_(void 0, void 0, void 0, function* () {
// Before the call object creates a stream, dispose of any local preview streams.
// @TODO: is there any way to parent the unparented view to the call object instead
// of disposing and creating a new stream?
yield disposeAllLocalPreviewViews(callClient);
const callId = call === null || call === void 0 ? void 0 : call.id;
let videoDeviceInfo = callClient.getState().deviceManager.selectedCamera;
if (!videoDeviceInfo) {
const cameras = yield (deviceManager === null || deviceManager === void 0 ? void 0 : deviceManager.getCameras());
videoDeviceInfo = cameras && cameras.length > 0 ? cameras[0] : undefined;
if (videoDeviceInfo) {
deviceManager === null || deviceManager === void 0 ? void 0 : deviceManager.selectCamera(videoDeviceInfo);
}
}
if (!callId || !videoDeviceInfo) {
return;
}
const stream = new communicationCalling.LocalVideoStream(videoDeviceInfo);
if (call && !call.localVideoStreams.find(s => areStreamsEqual(s, stream))) {
yield call.startVideo(stream);
}
});
const onStopLocalVideo = (stream) => __awaiter$_(void 0, void 0, void 0, function* () {
const callId = call === null || call === void 0 ? void 0 : call.id;
if (!callId) {
return;
}
if (call && call.localVideoStreams.find(s => areStreamsEqual(s, stream))) {
yield call.stopVideo(stream);
}
});
const onToggleCamera = (options) => __awaiter$_(void 0, void 0, void 0, function* () {
const previewOn = _isPreviewOn(callClient.getState().deviceManager);
// the disposal of the unparented views is to workaround: https://skype.visualstudio.com/SPOOL/_workitems/edit/3030558.
// The root cause of the issue is caused by never transitioning the unparented view to the
// call object when going from configuration page (disconnected call state) to connecting.
//
// Currently the only time the local video stream is moved from unparented view to the call
// object is when we transition from connecting -> call state. If the camera was on,
// inside the MediaGallery we trigger toggleCamera. This triggers onStartLocalVideo which
// destroys the unparentedView and creates a new stream in the call - so all looks well.
//
// However, if someone turns off their camera during the lobbyOrConnecting screen, the
// call.localVideoStreams will be empty (as the stream is currently stored in the unparented
// views and was never transitioned to the call object) and thus we incorrectly try to create
// a new video stream for the call object, instead of only stopping the unparented view.
//
// The correct fix for this is to ensure that callAgent.onStartCall is called with the
// localvideostream as a videoOption. That will mean call.onLocalVideoStreamsUpdated will
// be triggered when the call is in connecting state, which we can then transition the
// local video stream to the stateful call client and get into a clean state.
if (call && (_isInCall(call.state) || _isInLobbyOrConnecting(call.state))) {
const stream = call.localVideoStreams.find(stream => stream.mediaStreamType === 'Video');
const unparentedViews = callClient.getState().deviceManager.unparentedViews;
if (stream || unparentedViews.length > 0) {
unparentedViews.forEach(view => view.mediaStreamType === 'Video' && callClient.disposeView(undefined, undefined, view));
if (stream) {
yield onStopLocalVideo(stream);
}
}
else {
yield onStartLocalVideo();
}
}
else {
/**
* This will create a unparented view to be used on the configuration page and the connecting screen
*
* If the device that the stream will come from is not on from permissions checks, then it will take time
* to create the stream since device is off. If we are turn the camera on immedietly on the configuration page we see it is
* fast but that is because the device is already primed to return a stream.
*
* On the connecting page the device has already turned off and the connecting window is so small we do not see the resulting
* unparented view from the code below.
*/
const selectedCamera = callClient.getState().deviceManager.selectedCamera;
if (selectedCamera) {
if (previewOn) {
yield onDisposeLocalStreamView();
}
else {
yield callClient.createView(undefined, undefined, {
source: selectedCamera,
mediaStreamType: 'Video'
}, options);
}
}
}
});
const onSelectMicrophone = (device) => __awaiter$_(void 0, void 0, void 0, function* () {
if (!deviceManager) {
return;
}
return deviceManager.selectMicrophone(device);
});
const onSelectSpeaker = (device) => __awaiter$_(void 0, void 0, void 0, function* () {
if (!deviceManager) {
return;
}
return deviceManager.selectSpeaker(device);
});
const onSelectCamera = (device, options) => __awaiter$_(void 0, void 0, void 0, function* () {
if (!deviceManager) {
return;
}
if (call && _isInCall(call.state)) {
deviceManager.selectCamera(device);
const stream = call.localVideoStreams.find(stream => stream.mediaStreamType === 'Video');
yield (stream === null || stream === void 0 ? void 0 : stream.switchSource(device));
yield Promise.all([
/// TODO: TEMPORARY SOLUTION
/// The Calling SDK needs to wait until the stream is ready before resolving the switchSource promise.
/// This is a temporary solution to wait for the stream to be ready before resolving the promise.
/// This allows the onSelectCamera to be throttled to prevent the streams from getting in to a frozen state
/// if the user switches cameras too rapidly.
/// This is to be removed once the Calling SDK has issued a fix.
stream === null || stream === void 0 ? void 0 : stream.getMediaStream(),
// An extra wait here is also necessary to prevent the remote stream freezing issue.
// If this is removed, please test switching cameras rapidly won't cause stream to freeze for remote users.
// When this mitigation was introduced, the repro interval time that caused the issue was:
// - iPhone 11, safari, v8.3.1: 750ms
// - Pixel 6, chrome, Android 15: 400ms
// - Windows 11, edge: 100ms
new Promise(resolve => setTimeout(resolve, 1000))
]);
}
else {
const previewOn = _isPreviewOn(callClient.getState().deviceManager);
if (!previewOn) {
deviceManager.selectCamera(device);
return;
}
yield onDisposeLocalStreamView();
deviceManager.selectCamera(device);
yield callClient.createView(undefined, undefined, {
source: device,
mediaStreamType: 'Video'
}, options);
}
});
const onRaiseHand = () => __awaiter$_(void 0, void 0, void 0, function* () { var _a; return yield ((_a = call === null || call === void 0 ? void 0 : call.feature(communicationCalling.Features.RaiseHand)) === null || _a === void 0 ? void 0 : _a.raiseHand()); });
const onLowerHand = () => __awaiter$_(void 0, void 0, void 0, function* () { var _b; return yield ((_b = call === null || call === void 0 ? void 0 : call.feature(communicationCalling.Features.RaiseHand)) === null || _b === void 0 ? void 0 : _b.lowerHand()); });
const onToggleRaiseHand = () => __awaiter$_(void 0, void 0, void 0, function* () {
const raiseHandFeature = call === null || call === void 0 ? void 0 : call.feature(communicationCalling.Features.RaiseHand);
const localUserId = callClient.getState().userId;
const isLocalRaisedHand = raiseHandFeature === null || raiseHandFeature === void 0 ? void 0 : raiseHandFeature.getRaisedHands().find(publishedState => toFlatCommunicationIdentifier(publishedState.identifier) === toFlatCommunicationIdentifier(localUserId));
if (isLocalRaisedHand) {
yield (raiseHandFeature === null || raiseHandFeature === void 0 ? void 0 : raiseHandFeature.lowerHand());
}
else {
yield (raiseHandFeature === null || raiseHandFeature === void 0 ? void 0 : raiseHandFeature.raiseHand());
}
});
const onReactionClick = (reaction) => __awaiter$_(void 0, void 0, void 0, function* () {
var _c;
if (reaction === 'like' || reaction === 'applause' || reaction === 'heart' || reaction === 'laugh' || reaction === 'surprised') {
yield ((_c = call === null || call === void 0 ? void 0 : call.feature(communicationCalling.Features.Reaction)) === null || _c === void 0 ? void 0 : _c.sendReaction({
reactionType: reaction
}));
}
else {
console.warn(`Can not recognize ${reaction} as meeting reaction`);
}
return;
});
const onToggleMicrophone = () => __awaiter$_(void 0, void 0, void 0, function* () {
if (!call || !(_isInCall(call.state) || _isInLobbyOrConnecting(call.state))) {
throw new Error(`Please invoke onToggleMicrophone after call is started`);
}
return call.isMuted ? yield call.unmute() : yield call.mute();
});
const onStartScreenShare = () => __awaiter$_(void 0, void 0, void 0, function* () { return yield (call === null || call === void 0 ? void 0 : call.startScreenSharing()); });
const onStopScreenShare = () => __awaiter$_(void 0, void 0, void 0, function* () { return yield (call === null || call === void 0 ? void 0 : call.stopScreenSharing()); });
const onToggleScreenShare = () => __awaiter$_(void 0, void 0, void 0, function* () { return (call === null || call === void 0 ? void 0 : call.isScreenSharingOn) ? yield onStopScreenShare() : yield onStartScreenShare(); });
const onHangUp = (forEveryone) => __awaiter$_(void 0, void 0, void 0, function* () {
return yield (call === null || call === void 0 ? void 0 : call.hangUp({
forEveryone: forEveryone === true ? true : false
}));
});
const onToggleHold = () => __awaiter$_(void 0, void 0, void 0, function* () { return (call === null || call === void 0 ? void 0 : call.state) === 'LocalHold' ? yield (call === null || call === void 0 ? void 0 : call.resume()) : yield (call === null || call === void 0 ? void 0 : call.hold()); });
const onCreateLocalStreamView = (...args_1) => __awaiter$_(void 0, [...args_1], void 0, function* (options = {
scalingMode: 'Crop',
isMirrored: true
}) {
if (!call || call.localVideoStreams.length === 0) {
return;
}
const callState = callClient.getState().calls[call.id];
if (!callState) {
return;
}
const localStream = callState.localVideoStreams.find(item => item.mediaStreamType === 'Video');
const localScreenSharingStream = callState.localVideoStreams.find(item => item.mediaStreamType === 'ScreenSharing');
let createViewResult = undefined;
if (localStream && !localStream.view) {
createViewResult = yield callClient.createView(call.id, undefined, localStream, options);
}
if (localScreenSharingStream && !localScreenSharingStream.view && call.isScreenSharingOn) {
// Hardcoded `scalingMode` since it is highly unlikely that CONTOSO would ever want to use a different scaling mode for screenshare.
// Using `Crop` would crop the contents of screenshare and `Stretch` would warp it.
// `Fit` is the only mode that maintains the integrity of the screen being shared.
createViewResult = yield callClient.createView(call.id, undefined, localScreenSharingStream, {
scalingMode: 'Fit'
});
}
return (createViewResult === null || createViewResult === void 0 ? void 0 : createViewResult.view) ? {
view: createViewResult === null || createViewResult === void 0 ? void 0 : createViewResult.view
} : undefined;
});
const onCreateRemoteStreamView = (userId_1, ...args_2) => __awaiter$_(void 0, [userId_1, ...args_2], void 0, function* (userId, options = {
scalingMode: 'Crop'
}) {
if (!call) {
return;
}
const callState = getCallStateIfExist(callClient.getState(), call.id);
if (!callState) {
return;
}
const participant = Object.values(callState.remoteParticipants).find(participant => toFlatCommunicationIdentifier(participant.identifier) === userId);
if (!participant || !participant.videoStreams) {
return;
}
/**
* There is a bug from the calling sdk where if a user leaves and rejoins immediately
* it adds 2 more potential streams this remote participant can use. The old 2 streams
* still show as available and that is how we got a frozen stream in this case. The stopgap
* until streams accurately reflect their availability is to always prioritize the latest streams of a certain type
* e.g findLast instead of find
*/
// Find the first available stream, if there is none, then get the first stream
const remoteVideoStream = Object.values(participant.videoStreams).findLast(i => i.mediaStreamType === 'Video' && i.isAvailable) || Object.values(participant.videoStreams).findLast(i => i.mediaStreamType === 'Video');
const screenShareStream = Object.values(participant.videoStreams).findLast(i => i.mediaStreamType === 'ScreenSharing' && i.isAvailable) || Object.values(participant.videoStreams).findLast(i => i.mediaStreamType === 'ScreenSharing');
let createViewResult = undefined;
if (remoteVideoStream && remoteVideoStream.isAvailable && !remoteVideoStream.view) {
createViewResult = yield callClient.createView(call.id, participant.identifier, remoteVideoStream, options);
}
if (screenShareStream && screenShareStream.isAvailable && !screenShareStream.view) {
// Hardcoded `scalingMode` since it is highly unlikely that CONTOSO would ever want to use a different scaling mode for screenshare.
// Using `Crop` would crop the contents of screenshare and `Stretch` would warp it.
// `Fit` is the only mode that maintains the integrity of the screen being shared.
createViewResult = yield callClient.createView(call.id, participant.identifier, screenShareStream, {
scalingMode: 'Fit'
});
}
return (createViewResult === null || createViewResult === void 0 ? void 0 : createViewResult.view) ? {
view: createViewResult === null || createViewResult === void 0 ? void 0 : createViewResult.view
} : undefined;
});
const onDisposeRemoteStreamView = (userId) => __awaiter$_(void 0, void 0, void 0, function* () {
if (!call) {
return;
}
const callState = getCallStateIfExist(callClient.getState(), call.id);
if (!callState) {
return;
}
const participant = Object.values(callState.remoteParticipants).find(participant => toFlatCommunicationIdentifier(participant.identifier) === userId);
if (!participant || !participant.videoStreams) {
return;
}
const remoteVideoStream = Object.values(participant.videoStreams).find(i => i.mediaStreamType === 'Video');
const screenShareStream = Object.values(participant.videoStreams).find(i => i.mediaStreamType === 'ScreenSharing');
if (remoteVideoStream && remoteVideoStream.view) {
callClient.disposeView(call.id, participant.identifier, remoteVideoStream);
}
if (screenShareStream && screenShareStream.view) {
callClient.disposeView(call.id, participant.identifier, screenShareStream);
}
});
const onDisposeRemoteVideoStreamView = (userId) => __awaiter$_(void 0, void 0, void 0, function* () {
if (!call) {
return;
}
const callState = getCallStateIfExist(callClient.getState(), call.id);
if (!callState) {
return;
}
const participant = Object.values(callState.remoteParticipants).find(participant => toFlatCommunicationIdentifier(participant.identifier) === userId);
if (!participant || !participant.videoStreams) {
return;
}
const remoteVideoStream = Object.values(participant.videoStreams).filter(i => i.mediaStreamType === 'Video');
for (const stream of remoteVideoStream) {
if (stream.view) {
callClient.disposeView(call.id, participant.identifier, stream);
}
}
});
const onDisposeRemoteScreenShareStreamView = (userId) => __awaiter$_(void 0, void 0, void 0, function* () {
if (!call) {
return;
}
const callState = getCallStateIfExist(callClient.getState(), call.id);
if (!callState) {
return;
}
const participant = Object.values(callState.remoteParticipants).find(participant => toFlatCommunicationIdentifier(participant.identifier) === userId);
if (!participant || !participant.videoStreams) {
return;
}
const screenShareStreams = Object.values(participant.videoStreams).filter(i => i.mediaStreamType === 'ScreenSharing');
for (const stream of screenShareStreams) {
if (stream.view) {
callClient.dispose