@azure/communication-react 1.4.3-alpha-202212140016.0 → 1.4.3-alpha-202212150014.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -202,7 +202,7 @@ const _toCommunicationIdentifier = (id) => {
202
202
  // Copyright (c) Microsoft Corporation.
203
203
  // Licensed under the MIT license.
204
204
  // GENERATED FILE. DO NOT EDIT MANUALLY.
205
- var telemetryVersion = '1.4.3-alpha-202212140016.0';
205
+ var telemetryVersion = '1.4.3-alpha-202212150014.0';
206
206
 
207
207
  // Copyright (c) Microsoft Corporation.
208
208
  /**
@@ -14747,6 +14747,280 @@ const convertObservableFileUploadToFileUploadsUiState = (fileUploads) => {
14747
14747
  }, {});
14748
14748
  };
14749
14749
 
14750
+ // Copyright (c) Microsoft Corporation.
14751
+ // Licensed under the MIT license.
14752
+ /**
14753
+ * Subset of CallCompositePages that represent an end call state.
14754
+ * @private
14755
+ */
14756
+ const END_CALL_PAGES = [
14757
+ 'accessDeniedTeamsMeeting',
14758
+ 'joinCallFailedDueToNoNetwork',
14759
+ 'leftCall',
14760
+ /* @conditional-compile-remove(rooms) */ 'deniedPermissionToRoom',
14761
+ 'removedFromCall',
14762
+ /* @conditional-compile-remove(rooms) */ 'roomNotFound',
14763
+ /* @conditional-compile-remove(unsupported-browser) */ 'unsupportedEnvironment'
14764
+ ];
14765
+
14766
+ // Copyright (c) Microsoft Corporation.
14767
+ const ACCESS_DENIED_TEAMS_MEETING_SUB_CODE = 5854;
14768
+ const REMOTE_PSTN_USER_HUNG_UP = 560000;
14769
+ const REMOVED_FROM_CALL_SUB_CODES = [5000, 5300, REMOTE_PSTN_USER_HUNG_UP];
14770
+ /* @conditional-compile-remove(rooms) */
14771
+ const ROOM_NOT_FOUND_SUB_CODE = 5751;
14772
+ /* @conditional-compile-remove(rooms) */
14773
+ const DENIED_PERMISSION_TO_ROOM_SUB_CODE = 5828;
14774
+ /**
14775
+ * @private
14776
+ */
14777
+ const isCameraOn = (state) => {
14778
+ if (state.call) {
14779
+ const stream = state.call.localVideoStreams.find((stream) => stream.mediaStreamType === 'Video');
14780
+ return !!stream;
14781
+ }
14782
+ else {
14783
+ if (state.devices.selectedCamera) {
14784
+ const previewOn = _isPreviewOn(state.devices);
14785
+ return previewOn;
14786
+ }
14787
+ }
14788
+ return false;
14789
+ };
14790
+ /**
14791
+ * Reduce the set of call controls visible on mobile.
14792
+ * For example do not show screenshare button.
14793
+ *
14794
+ * @private
14795
+ */
14796
+ const reduceCallControlsForMobile = (callControlOptions) => {
14797
+ if (callControlOptions === false) {
14798
+ return false;
14799
+ }
14800
+ // Ensure call controls a valid object.
14801
+ const reduceCallControlOptions = callControlOptions === true ? {} : callControlOptions || {};
14802
+ // Set to compressed mode when composite is optimized for mobile
14803
+ reduceCallControlOptions.displayType = 'compact';
14804
+ // Do not show screen share button when composite is optimized for mobile unless the developer
14805
+ // has explicitly opted in.
14806
+ if (reduceCallControlOptions.screenShareButton !== true) {
14807
+ reduceCallControlOptions.screenShareButton = false;
14808
+ }
14809
+ return reduceCallControlOptions;
14810
+ };
14811
+ var CallEndReasons;
14812
+ (function (CallEndReasons) {
14813
+ CallEndReasons[CallEndReasons["LEFT_CALL"] = 0] = "LEFT_CALL";
14814
+ CallEndReasons[CallEndReasons["ACCESS_DENIED"] = 1] = "ACCESS_DENIED";
14815
+ CallEndReasons[CallEndReasons["REMOVED_FROM_CALL"] = 2] = "REMOVED_FROM_CALL";
14816
+ CallEndReasons[CallEndReasons["ROOM_NOT_FOUND"] = 3] = "ROOM_NOT_FOUND";
14817
+ CallEndReasons[CallEndReasons["DENIED_PERMISSION_TO_ROOM"] = 4] = "DENIED_PERMISSION_TO_ROOM";
14818
+ })(CallEndReasons || (CallEndReasons = {}));
14819
+ const getCallEndReason = (call) => {
14820
+ var _a, _b, _c, _d, _e;
14821
+ const remoteParticipantsEndedArray = Array.from(Object.values(call.remoteParticipantsEnded));
14822
+ /**
14823
+ * Handle the special case in a PSTN call where removing the last user kicks the caller out of the call.
14824
+ * The code and subcode is the same as when a user is removed from a teams interop call.
14825
+ * Hence, we look at the last remote participant removed to determine if the last participant removed was a phone number.
14826
+ * If yes, the caller was kicked out of the call, but we need to show them that they left the call.
14827
+ * Note: This check will only work for 1:1 PSTN Calls. The subcode is different for 1:N PSTN calls, and we do not need to handle that case.
14828
+ */
14829
+ if (remoteParticipantsEndedArray.length === 1 &&
14830
+ communicationCommon.isPhoneNumberIdentifier(remoteParticipantsEndedArray[0].identifier) &&
14831
+ ((_a = call.callEndReason) === null || _a === void 0 ? void 0 : _a.subCode) !== REMOTE_PSTN_USER_HUNG_UP) {
14832
+ return CallEndReasons.LEFT_CALL;
14833
+ }
14834
+ if (((_b = call.callEndReason) === null || _b === void 0 ? void 0 : _b.subCode) && call.callEndReason.subCode === ACCESS_DENIED_TEAMS_MEETING_SUB_CODE) {
14835
+ return CallEndReasons.ACCESS_DENIED;
14836
+ }
14837
+ if (((_c = call.callEndReason) === null || _c === void 0 ? void 0 : _c.subCode) && REMOVED_FROM_CALL_SUB_CODES.includes(call.callEndReason.subCode)) {
14838
+ return CallEndReasons.REMOVED_FROM_CALL;
14839
+ }
14840
+ /* @conditional-compile-remove(rooms) */
14841
+ if (((_d = call.callEndReason) === null || _d === void 0 ? void 0 : _d.subCode) && call.callEndReason.subCode === ROOM_NOT_FOUND_SUB_CODE) {
14842
+ return CallEndReasons.ROOM_NOT_FOUND;
14843
+ }
14844
+ /* @conditional-compile-remove(rooms) */
14845
+ if (((_e = call.callEndReason) === null || _e === void 0 ? void 0 : _e.subCode) && call.callEndReason.subCode === DENIED_PERMISSION_TO_ROOM_SUB_CODE) {
14846
+ return CallEndReasons.DENIED_PERMISSION_TO_ROOM;
14847
+ }
14848
+ if (call.callEndReason) {
14849
+ // No error codes match, assume the user simply left the call regularly
14850
+ return CallEndReasons.LEFT_CALL;
14851
+ }
14852
+ throw new Error('No matching call end reason');
14853
+ };
14854
+ /**
14855
+ * Get the current call composite page based on the current call composite state
14856
+ *
14857
+ * @param Call - The current call state
14858
+ * @param previousCall - The state of the most recent previous call that has ended.
14859
+ *
14860
+ * @remarks - The previousCall state is needed to determine if the call has ended.
14861
+ * When the call ends a new call object is created, and so we must lookback at the
14862
+ * previous call state to understand how the call has ended. If there is no previous
14863
+ * call we know that this is a fresh call and can display the configuration page.
14864
+ *
14865
+ * @private
14866
+ */
14867
+ const getCallCompositePage = (call, previousCall, unsupportedBrowserInfo) => {
14868
+ /* @conditional-compile-remove(unsupported-browser) */
14869
+ if (isUnsupportedEnvironment(unsupportedBrowserInfo.features, unsupportedBrowserInfo.environmentInfo, unsupportedBrowserInfo.unsupportedBrowserVersionOptedIn)) {
14870
+ return 'unsupportedEnvironment';
14871
+ }
14872
+ if (call) {
14873
+ // Must check for ongoing call *before* looking at any previous calls.
14874
+ // If the composite completes one call and joins another, the previous calls
14875
+ // will be populated, but not relevant for determining the page.
14876
+ // `_isInLobbyOrConnecting` needs to be checked first because `_isInCall` also returns true when call is in lobby.
14877
+ if (_isInLobbyOrConnecting(call === null || call === void 0 ? void 0 : call.state)) {
14878
+ return 'lobby';
14879
+ // `LocalHold` needs to be checked before `isInCall` since it is also a state that's considered in call.
14880
+ }
14881
+ else if ((call === null || call === void 0 ? void 0 : call.state) === 'LocalHold') {
14882
+ /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
14883
+ return 'hold';
14884
+ }
14885
+ else if (_isInCall(call === null || call === void 0 ? void 0 : call.state)) {
14886
+ return 'call';
14887
+ }
14888
+ else {
14889
+ // When the call object has been constructed after clicking , but before 'connecting' has been
14890
+ // set on the call object, we continue to show the configuration screen.
14891
+ // The call object does not correctly reflect local device state until `call.state` moves to `connecting`.
14892
+ // Moving to the 'lobby' page too soon leads to components that depend on the `call` object to show incorrect
14893
+ // transitional state.
14894
+ return 'configuration';
14895
+ }
14896
+ }
14897
+ if (previousCall) {
14898
+ const reason = getCallEndReason(previousCall);
14899
+ /* @conditional-compile-remove(rooms) */
14900
+ switch (reason) {
14901
+ case CallEndReasons.ROOM_NOT_FOUND:
14902
+ return 'roomNotFound';
14903
+ case CallEndReasons.DENIED_PERMISSION_TO_ROOM:
14904
+ return 'deniedPermissionToRoom';
14905
+ }
14906
+ switch (reason) {
14907
+ case CallEndReasons.ACCESS_DENIED:
14908
+ return 'accessDeniedTeamsMeeting';
14909
+ case CallEndReasons.REMOVED_FROM_CALL:
14910
+ return 'removedFromCall';
14911
+ case CallEndReasons.LEFT_CALL:
14912
+ if (previousCall.diagnostics.network.latest.noNetwork) {
14913
+ return 'joinCallFailedDueToNoNetwork';
14914
+ }
14915
+ return 'leftCall';
14916
+ }
14917
+ }
14918
+ // No call state - show starting page (configuration)
14919
+ return 'configuration';
14920
+ };
14921
+ /** @private */
14922
+ const IsCallEndedPage = (
14923
+ /**
14924
+ * Explicitly listing the pages of this function intentionally.
14925
+ * This protects against adding a new composite page that should be marked as an callEndedPage.
14926
+ * EndCallPages are used to trigger onCallEnded events so this could easily be missed.
14927
+ * When you add a new composite page this will throw a compiler error. If this new page is an
14928
+ * EndCallPage ensure you update the END_CALL_PAGES. Afterwards update the `page` parameter
14929
+ * type below to allow your new page, i.e. add `| <your new page>
14930
+ */
14931
+ page) => END_CALL_PAGES.includes(page);
14932
+ /**
14933
+ * Creates a new call control options object and sets the correct values for disabling
14934
+ * the buttons provided in the `disabledControls` array.
14935
+ * Returns a new object without changing the original object.
14936
+ * @param callControlOptions options for the call control component that need to be modified.
14937
+ * @param disabledControls An array of controls to disable.
14938
+ * @returns a copy of callControlOptions with disabledControls disabled
14939
+ * @private
14940
+ */
14941
+ const disableCallControls = (callControlOptions, disabledControls) => {
14942
+ var _a;
14943
+ if (callControlOptions === false) {
14944
+ return false;
14945
+ }
14946
+ // Ensure we clone the prop if it is an object to ensure we do not mutate the original prop.
14947
+ let newOptions = (_a = (callControlOptions instanceof Object ? Object.assign({}, callControlOptions) : callControlOptions)) !== null && _a !== void 0 ? _a : {};
14948
+ if (newOptions === true || newOptions === undefined) {
14949
+ newOptions = disabledControls.reduce((acc, key) => {
14950
+ acc[key] = { disabled: true };
14951
+ return acc;
14952
+ }, {});
14953
+ }
14954
+ else {
14955
+ disabledControls.forEach((key) => {
14956
+ if (newOptions[key] !== false) {
14957
+ newOptions[key] = { disabled: true };
14958
+ }
14959
+ });
14960
+ }
14961
+ return newOptions;
14962
+ };
14963
+ /**
14964
+ * Check if a disabled object is provided for a button and returns if the button is disabled.
14965
+ * A button is only disabled if is explicitly set to disabled.
14966
+ *
14967
+ * @param option
14968
+ * @returns whether a button is disabled
14969
+ * @private
14970
+ */
14971
+ const isDisabled$2 = (option) => {
14972
+ if (option === undefined || typeof option === 'boolean') {
14973
+ return false;
14974
+ }
14975
+ return option.disabled;
14976
+ };
14977
+ /* @conditional-compile-remove(call-readiness) */
14978
+ /**
14979
+ *
14980
+ * This function uses permission API to determine if device permission state is granted, prompt or denied
14981
+ * @returns whether device permission state is granted, prompt or denied
14982
+ * If permission API is not supported on this browser, do nothing and log out error
14983
+ * @private
14984
+ */
14985
+ const getDevicePermissionState = (setVideoState, setAudioState) => {
14986
+ navigator.permissions
14987
+ .query({ name: 'camera' })
14988
+ .then((result) => {
14989
+ setVideoState(result.state);
14990
+ })
14991
+ .catch(() => {
14992
+ setVideoState('unsupported');
14993
+ });
14994
+ navigator.permissions
14995
+ .query({ name: 'microphone' })
14996
+ .then((result) => {
14997
+ setAudioState(result.state);
14998
+ })
14999
+ .catch(() => {
15000
+ setAudioState('unsupported');
15001
+ });
15002
+ };
15003
+ /* @conditional-compile-remove(unsupported-browser) */
15004
+ const isUnsupportedEnvironment = (features, environmentInfo, unsupportedBrowserVersionOptedIn) => {
15005
+ return !!((features === null || features === void 0 ? void 0 : features.unsupportedEnvironment) &&
15006
+ ((environmentInfo === null || environmentInfo === void 0 ? void 0 : environmentInfo.isSupportedBrowser) === false ||
15007
+ ((environmentInfo === null || environmentInfo === void 0 ? void 0 : environmentInfo.isSupportedBrowserVersion) === false && !unsupportedBrowserVersionOptedIn) ||
15008
+ (environmentInfo === null || environmentInfo === void 0 ? void 0 : environmentInfo.isSupportedPlatform) === false));
15009
+ };
15010
+ /**
15011
+ * Check if an object is identifier.
15012
+ *
15013
+ * @param identifier
15014
+ * @returns whether an identifier is one of identifier types (for runtime validation)
15015
+ * @private
15016
+ */
15017
+ const isValidIdentifier = (identifier) => {
15018
+ return (communicationCommon.isCommunicationUserIdentifier(identifier) ||
15019
+ communicationCommon.isPhoneNumberIdentifier(identifier) ||
15020
+ communicationCommon.isMicrosoftTeamsUserIdentifier(identifier) ||
15021
+ communicationCommon.isUnknownIdentifier(identifier));
15022
+ };
15023
+
14750
15024
  // Copyright (c) Microsoft Corporation.
14751
15025
  // Licensed under the MIT license.
14752
15026
  var __awaiter$c = (window && window.__awaiter) || function (thisArg, _arguments, P, generator) {
@@ -15097,6 +15371,9 @@ const convertEventType = (type) => {
15097
15371
  * @public
15098
15372
  */
15099
15373
  const createAzureCommunicationChatAdapter = ({ endpoint: endpointUrl, userId, displayName, credential, threadId }) => __awaiter$c(void 0, void 0, void 0, function* () {
15374
+ if (!isValidIdentifier(userId)) {
15375
+ throw new Error('Provided userId is invalid. Please provide valid identifier object.');
15376
+ }
15100
15377
  const chatClient = createStatefulChatClient({
15101
15378
  userId,
15102
15379
  displayName,
@@ -15864,409 +16141,156 @@ const ChatScreen = (props) => {
15864
16141
  React__default['default'].createElement(MessageThread, Object.assign({}, messageThreadProps, { onRenderAvatar: onRenderAvatarCallback, onRenderMessage: onRenderMessage,
15865
16142
  /* @conditional-compile-remove(file-sharing) */
15866
16143
  onRenderFileDownloads: onRenderFileDownloads, numberOfChatMessagesToReload: defaultNumberOfChatMessagesToReload, styles: messageThreadStyles })),
15867
- React__default['default'].createElement(react.Stack, { className: react.mergeStyles(sendboxContainerStyles) },
15868
- React__default['default'].createElement("div", { className: react.mergeStyles(typingIndicatorContainerStyles) }, onRenderTypingIndicator ? (onRenderTypingIndicator(typingIndicatorProps.typingUsers)) : (React__default['default'].createElement(TypingIndicator, Object.assign({}, typingIndicatorProps, { styles: typingIndicatorStyles })))),
15869
- React__default['default'].createElement(react.Stack, { horizontal: formFactor === 'mobile' },
15870
- formFactor === 'mobile' && (React__default['default'].createElement(react.Stack, { verticalAlign: "center" },
15871
- React__default['default'].createElement(AttachFileButton, null))),
15872
- React__default['default'].createElement(react.Stack, { grow: true },
15873
- React__default['default'].createElement(SendBox, Object.assign({}, sendBoxProps, { autoFocus: options === null || options === void 0 ? void 0 : options.autoFocus, styles: sendBoxStyles,
15874
- /* @conditional-compile-remove(file-sharing) */
15875
- activeFileUploads: useSelector$2(fileUploadsSelector).files,
15876
- /* @conditional-compile-remove(file-sharing) */
15877
- onCancelFileUpload: adapter.cancelFileUpload }))),
15878
- formFactor !== 'mobile' && React__default['default'].createElement(AttachFileButton, null)))),
15879
- /* @conditional-compile-remove(chat-composite-participant-pane) */
15880
- (options === null || options === void 0 ? void 0 : options.participantPane) === true && (React__default['default'].createElement(ChatScreenPeoplePane, { onFetchAvatarPersonaData: onFetchAvatarPersonaData, onFetchParticipantMenuItems: props.onFetchParticipantMenuItems, isMobile: formFactor === 'mobile' })))));
15881
- };
15882
-
15883
- // Copyright (c) Microsoft Corporation.
15884
- /**
15885
- * A customizable UI composite for the chat experience.
15886
- *
15887
- * @remarks Chat composite min width and height are respectively 17.5rem and 20rem (280px and 320px, with default rem at 16px)
15888
- *
15889
- * @public
15890
- */
15891
- const ChatComposite = (props) => {
15892
- const { adapter, options, onFetchAvatarPersonaData, onRenderTypingIndicator, onRenderMessage, onFetchParticipantMenuItems } = props;
15893
- const formFactor = props['formFactor'] || 'desktop';
15894
- /**
15895
- * @TODO Remove this function and pass the props directly when file-sharing is promoted to stable.
15896
- * @private
15897
- */
15898
- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
15899
- const fileSharingOptions = () => {
15900
- /* @conditional-compile-remove(file-sharing) */
15901
- return {
15902
- fileSharing: options === null || options === void 0 ? void 0 : options.fileSharing
15903
- };
15904
- };
15905
- return (React__default['default'].createElement("div", { className: chatScreenContainerStyle },
15906
- React__default['default'].createElement(BaseProvider, Object.assign({}, props),
15907
- React__default['default'].createElement(ChatAdapterProvider, { adapter: adapter },
15908
- React__default['default'].createElement(ChatScreen, Object.assign({ formFactor: formFactor, options: options, onFetchAvatarPersonaData: onFetchAvatarPersonaData, onRenderTypingIndicator: onRenderTypingIndicator, onRenderMessage: onRenderMessage, onFetchParticipantMenuItems: onFetchParticipantMenuItems }, fileSharingOptions()))))));
15909
- };
15910
-
15911
- // Copyright (c) Microsoft Corporation.
15912
- const CallAdapterContext = React.createContext(undefined);
15913
- /**
15914
- * @private
15915
- */
15916
- const CallAdapterProvider = (props) => {
15917
- const { adapter } = props;
15918
- return React__default['default'].createElement(CallAdapterContext.Provider, { value: adapter }, props.children);
15919
- };
15920
- /**
15921
- * @private
15922
- */
15923
- const useAdapter = () => {
15924
- const adapter = React.useContext(CallAdapterContext);
15925
- if (!adapter) {
15926
- throw 'Cannot find adapter please initialize before usage.';
15927
- }
15928
- return adapter;
15929
- };
15930
-
15931
- // Copyright (c) Microsoft Corporation.
15932
- // Licensed under the MIT license.
15933
- /** @private */
15934
- const containerDivStyles = { position: 'relative', width: '100%', height: '100%' };
15935
-
15936
- // Copyright (c) Microsoft Corporation.
15937
- /**
15938
- * @private
15939
- */
15940
- const useAdaptedSelector = (selector, selectorProps) => {
15941
- return useSelectorWithAdaptation(selector, adaptCompositeState, selectorProps);
15942
- };
15943
- /**
15944
- * @private
15945
- */
15946
- const useSelectorWithAdaptation = (selector, adaptState, selectorProps) => {
15947
- var _a;
15948
- const adapter = useAdapter();
15949
- // Keeps track of whether the current component is mounted or not. If it has unmounted, make sure we do not modify the
15950
- // state or it will cause React warnings in the console. https://skype.visualstudio.com/SPOOL/_workitems/edit/2453212
15951
- const mounted = React.useRef(false);
15952
- React.useEffect(() => {
15953
- mounted.current = true;
15954
- return () => {
15955
- mounted.current = false;
15956
- };
15957
- });
15958
- const callId = (_a = adapter.getState().call) === null || _a === void 0 ? void 0 : _a.id;
15959
- const callConfigProps = React.useMemo(() => ({
15960
- callId
15961
- }), [callId]);
15962
- const [props, setProps] = React.useState(selector(adaptState(adapter.getState()), selectorProps !== null && selectorProps !== void 0 ? selectorProps : callConfigProps));
15963
- const propRef = React.useRef(props);
15964
- propRef.current = props;
15965
- React.useEffect(() => {
15966
- const onStateChange = (state) => {
15967
- if (!mounted.current) {
15968
- return;
15969
- }
15970
- const newProps = selector(adaptState(state), selectorProps !== null && selectorProps !== void 0 ? selectorProps : callConfigProps);
15971
- if (propRef.current !== newProps) {
15972
- setProps(newProps);
15973
- }
15974
- };
15975
- adapter.onStateChange(onStateChange);
15976
- return () => {
15977
- adapter.offStateChange(onStateChange);
15978
- };
15979
- }, [adaptState, adapter, selector, selectorProps, callConfigProps]);
15980
- return props;
15981
- };
15982
- const memoizeState = memoizeOne__default['default']((userId, deviceManager, calls, latestErrors, displayName) => ({
15983
- userId,
15984
- incomingCalls: {},
15985
- incomingCallsEnded: {},
15986
- callsEnded: {},
15987
- deviceManager,
15988
- callAgent: { displayName },
15989
- calls,
15990
- latestErrors
15991
- }));
15992
- const memoizeCalls = memoizeOne__default['default']((call) => (call ? { [call.id]: call } : {}));
15993
- const adaptCompositeState = (compositeState) => {
15994
- return memoizeState(compositeState.userId, compositeState.devices, memoizeCalls(compositeState.call),
15995
- // This is an unsafe type expansion.
15996
- // compositeState.latestErrors can contain properties that are not valid in CallErrors.
15997
- //
15998
- // But there is no way to check for valid property names at runtime:
15999
- // - The set of valid property names is built from types in the @azure/communication-calling.
16000
- // Thus we don't have a literal array of allowed strings at runtime.
16001
- // - Due to minification / uglification, the property names from the objects at runtime can't be used
16002
- // to compare against permissible values inferred from the types.
16003
- //
16004
- // This is not a huge problem -- it simply means that our adapted selector will include some extra operations
16005
- // that are unknown to the UI component and data binding libraries. Generic handling of the errors (e.g.,
16006
- // just displaying them in some UI surface) will continue to work for these operations. Handling of
16007
- // specific operations (e.g., acting on errors related to permission issues) will ignore these operations.
16008
- compositeState.latestErrors, compositeState.displayName);
16009
- };
16010
-
16011
- // Copyright (c) Microsoft Corporation.
16012
- // Licensed under the MIT license.
16013
- /**
16014
- * Subset of CallCompositePages that represent an end call state.
16015
- * @private
16016
- */
16017
- const END_CALL_PAGES = [
16018
- 'accessDeniedTeamsMeeting',
16019
- 'joinCallFailedDueToNoNetwork',
16020
- 'leftCall',
16021
- /* @conditional-compile-remove(rooms) */ 'deniedPermissionToRoom',
16022
- 'removedFromCall',
16023
- /* @conditional-compile-remove(rooms) */ 'roomNotFound',
16024
- /* @conditional-compile-remove(unsupported-browser) */ 'unsupportedEnvironment'
16025
- ];
16026
-
16027
- // Copyright (c) Microsoft Corporation.
16028
- const ACCESS_DENIED_TEAMS_MEETING_SUB_CODE = 5854;
16029
- const REMOTE_PSTN_USER_HUNG_UP = 560000;
16030
- const REMOVED_FROM_CALL_SUB_CODES = [5000, 5300, REMOTE_PSTN_USER_HUNG_UP];
16031
- /* @conditional-compile-remove(rooms) */
16032
- const ROOM_NOT_FOUND_SUB_CODE = 5751;
16033
- /* @conditional-compile-remove(rooms) */
16034
- const DENIED_PERMISSION_TO_ROOM_SUB_CODE = 5828;
16035
- /**
16036
- * @private
16037
- */
16038
- const isCameraOn = (state) => {
16039
- if (state.call) {
16040
- const stream = state.call.localVideoStreams.find((stream) => stream.mediaStreamType === 'Video');
16041
- return !!stream;
16042
- }
16043
- else {
16044
- if (state.devices.selectedCamera) {
16045
- const previewOn = _isPreviewOn(state.devices);
16046
- return previewOn;
16047
- }
16048
- }
16049
- return false;
16050
- };
16051
- /**
16052
- * Reduce the set of call controls visible on mobile.
16053
- * For example do not show screenshare button.
16054
- *
16055
- * @private
16056
- */
16057
- const reduceCallControlsForMobile = (callControlOptions) => {
16058
- if (callControlOptions === false) {
16059
- return false;
16060
- }
16061
- // Ensure call controls a valid object.
16062
- const reduceCallControlOptions = callControlOptions === true ? {} : callControlOptions || {};
16063
- // Set to compressed mode when composite is optimized for mobile
16064
- reduceCallControlOptions.displayType = 'compact';
16065
- // Do not show screen share button when composite is optimized for mobile unless the developer
16066
- // has explicitly opted in.
16067
- if (reduceCallControlOptions.screenShareButton !== true) {
16068
- reduceCallControlOptions.screenShareButton = false;
16069
- }
16070
- return reduceCallControlOptions;
16071
- };
16072
- var CallEndReasons;
16073
- (function (CallEndReasons) {
16074
- CallEndReasons[CallEndReasons["LEFT_CALL"] = 0] = "LEFT_CALL";
16075
- CallEndReasons[CallEndReasons["ACCESS_DENIED"] = 1] = "ACCESS_DENIED";
16076
- CallEndReasons[CallEndReasons["REMOVED_FROM_CALL"] = 2] = "REMOVED_FROM_CALL";
16077
- CallEndReasons[CallEndReasons["ROOM_NOT_FOUND"] = 3] = "ROOM_NOT_FOUND";
16078
- CallEndReasons[CallEndReasons["DENIED_PERMISSION_TO_ROOM"] = 4] = "DENIED_PERMISSION_TO_ROOM";
16079
- })(CallEndReasons || (CallEndReasons = {}));
16080
- const getCallEndReason = (call) => {
16081
- var _a, _b, _c, _d, _e;
16082
- const remoteParticipantsEndedArray = Array.from(Object.values(call.remoteParticipantsEnded));
16083
- /**
16084
- * Handle the special case in a PSTN call where removing the last user kicks the caller out of the call.
16085
- * The code and subcode is the same as when a user is removed from a teams interop call.
16086
- * Hence, we look at the last remote participant removed to determine if the last participant removed was a phone number.
16087
- * If yes, the caller was kicked out of the call, but we need to show them that they left the call.
16088
- * Note: This check will only work for 1:1 PSTN Calls. The subcode is different for 1:N PSTN calls, and we do not need to handle that case.
16089
- */
16090
- if (remoteParticipantsEndedArray.length === 1 &&
16091
- communicationCommon.isPhoneNumberIdentifier(remoteParticipantsEndedArray[0].identifier) &&
16092
- ((_a = call.callEndReason) === null || _a === void 0 ? void 0 : _a.subCode) !== REMOTE_PSTN_USER_HUNG_UP) {
16093
- return CallEndReasons.LEFT_CALL;
16094
- }
16095
- if (((_b = call.callEndReason) === null || _b === void 0 ? void 0 : _b.subCode) && call.callEndReason.subCode === ACCESS_DENIED_TEAMS_MEETING_SUB_CODE) {
16096
- return CallEndReasons.ACCESS_DENIED;
16097
- }
16098
- if (((_c = call.callEndReason) === null || _c === void 0 ? void 0 : _c.subCode) && REMOVED_FROM_CALL_SUB_CODES.includes(call.callEndReason.subCode)) {
16099
- return CallEndReasons.REMOVED_FROM_CALL;
16100
- }
16101
- /* @conditional-compile-remove(rooms) */
16102
- if (((_d = call.callEndReason) === null || _d === void 0 ? void 0 : _d.subCode) && call.callEndReason.subCode === ROOM_NOT_FOUND_SUB_CODE) {
16103
- return CallEndReasons.ROOM_NOT_FOUND;
16104
- }
16105
- /* @conditional-compile-remove(rooms) */
16106
- if (((_e = call.callEndReason) === null || _e === void 0 ? void 0 : _e.subCode) && call.callEndReason.subCode === DENIED_PERMISSION_TO_ROOM_SUB_CODE) {
16107
- return CallEndReasons.DENIED_PERMISSION_TO_ROOM;
16108
- }
16109
- if (call.callEndReason) {
16110
- // No error codes match, assume the user simply left the call regularly
16111
- return CallEndReasons.LEFT_CALL;
16112
- }
16113
- throw new Error('No matching call end reason');
16144
+ React__default['default'].createElement(react.Stack, { className: react.mergeStyles(sendboxContainerStyles) },
16145
+ React__default['default'].createElement("div", { className: react.mergeStyles(typingIndicatorContainerStyles) }, onRenderTypingIndicator ? (onRenderTypingIndicator(typingIndicatorProps.typingUsers)) : (React__default['default'].createElement(TypingIndicator, Object.assign({}, typingIndicatorProps, { styles: typingIndicatorStyles })))),
16146
+ React__default['default'].createElement(react.Stack, { horizontal: formFactor === 'mobile' },
16147
+ formFactor === 'mobile' && (React__default['default'].createElement(react.Stack, { verticalAlign: "center" },
16148
+ React__default['default'].createElement(AttachFileButton, null))),
16149
+ React__default['default'].createElement(react.Stack, { grow: true },
16150
+ React__default['default'].createElement(SendBox, Object.assign({}, sendBoxProps, { autoFocus: options === null || options === void 0 ? void 0 : options.autoFocus, styles: sendBoxStyles,
16151
+ /* @conditional-compile-remove(file-sharing) */
16152
+ activeFileUploads: useSelector$2(fileUploadsSelector).files,
16153
+ /* @conditional-compile-remove(file-sharing) */
16154
+ onCancelFileUpload: adapter.cancelFileUpload }))),
16155
+ formFactor !== 'mobile' && React__default['default'].createElement(AttachFileButton, null)))),
16156
+ /* @conditional-compile-remove(chat-composite-participant-pane) */
16157
+ (options === null || options === void 0 ? void 0 : options.participantPane) === true && (React__default['default'].createElement(ChatScreenPeoplePane, { onFetchAvatarPersonaData: onFetchAvatarPersonaData, onFetchParticipantMenuItems: props.onFetchParticipantMenuItems, isMobile: formFactor === 'mobile' })))));
16114
16158
  };
16159
+
16160
+ // Copyright (c) Microsoft Corporation.
16115
16161
  /**
16116
- * Get the current call composite page based on the current call composite state
16117
- *
16118
- * @param Call - The current call state
16119
- * @param previousCall - The state of the most recent previous call that has ended.
16162
+ * A customizable UI composite for the chat experience.
16120
16163
  *
16121
- * @remarks - The previousCall state is needed to determine if the call has ended.
16122
- * When the call ends a new call object is created, and so we must lookback at the
16123
- * previous call state to understand how the call has ended. If there is no previous
16124
- * call we know that this is a fresh call and can display the configuration page.
16164
+ * @remarks Chat composite min width and height are respectively 17.5rem and 20rem (280px and 320px, with default rem at 16px)
16125
16165
  *
16126
- * @private
16166
+ * @public
16127
16167
  */
16128
- const getCallCompositePage = (call, previousCall, unsupportedBrowserInfo) => {
16129
- /* @conditional-compile-remove(unsupported-browser) */
16130
- if (isUnsupportedEnvironment(unsupportedBrowserInfo.features, unsupportedBrowserInfo.environmentInfo, unsupportedBrowserInfo.unsupportedBrowserVersionOptedIn)) {
16131
- return 'unsupportedEnvironment';
16132
- }
16133
- if (call) {
16134
- // Must check for ongoing call *before* looking at any previous calls.
16135
- // If the composite completes one call and joins another, the previous calls
16136
- // will be populated, but not relevant for determining the page.
16137
- // `_isInLobbyOrConnecting` needs to be checked first because `_isInCall` also returns true when call is in lobby.
16138
- if (_isInLobbyOrConnecting(call === null || call === void 0 ? void 0 : call.state)) {
16139
- return 'lobby';
16140
- // `LocalHold` needs to be checked before `isInCall` since it is also a state that's considered in call.
16141
- }
16142
- else if ((call === null || call === void 0 ? void 0 : call.state) === 'LocalHold') {
16143
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
16144
- return 'hold';
16145
- }
16146
- else if (_isInCall(call === null || call === void 0 ? void 0 : call.state)) {
16147
- return 'call';
16148
- }
16149
- else {
16150
- // When the call object has been constructed after clicking , but before 'connecting' has been
16151
- // set on the call object, we continue to show the configuration screen.
16152
- // The call object does not correctly reflect local device state until `call.state` moves to `connecting`.
16153
- // Moving to the 'lobby' page too soon leads to components that depend on the `call` object to show incorrect
16154
- // transitional state.
16155
- return 'configuration';
16156
- }
16157
- }
16158
- if (previousCall) {
16159
- const reason = getCallEndReason(previousCall);
16160
- /* @conditional-compile-remove(rooms) */
16161
- switch (reason) {
16162
- case CallEndReasons.ROOM_NOT_FOUND:
16163
- return 'roomNotFound';
16164
- case CallEndReasons.DENIED_PERMISSION_TO_ROOM:
16165
- return 'deniedPermissionToRoom';
16166
- }
16167
- switch (reason) {
16168
- case CallEndReasons.ACCESS_DENIED:
16169
- return 'accessDeniedTeamsMeeting';
16170
- case CallEndReasons.REMOVED_FROM_CALL:
16171
- return 'removedFromCall';
16172
- case CallEndReasons.LEFT_CALL:
16173
- if (previousCall.diagnostics.network.latest.noNetwork) {
16174
- return 'joinCallFailedDueToNoNetwork';
16175
- }
16176
- return 'leftCall';
16177
- }
16178
- }
16179
- // No call state - show starting page (configuration)
16180
- return 'configuration';
16168
+ const ChatComposite = (props) => {
16169
+ const { adapter, options, onFetchAvatarPersonaData, onRenderTypingIndicator, onRenderMessage, onFetchParticipantMenuItems } = props;
16170
+ const formFactor = props['formFactor'] || 'desktop';
16171
+ /**
16172
+ * @TODO Remove this function and pass the props directly when file-sharing is promoted to stable.
16173
+ * @private
16174
+ */
16175
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
16176
+ const fileSharingOptions = () => {
16177
+ /* @conditional-compile-remove(file-sharing) */
16178
+ return {
16179
+ fileSharing: options === null || options === void 0 ? void 0 : options.fileSharing
16180
+ };
16181
+ };
16182
+ return (React__default['default'].createElement("div", { className: chatScreenContainerStyle },
16183
+ React__default['default'].createElement(BaseProvider, Object.assign({}, props),
16184
+ React__default['default'].createElement(ChatAdapterProvider, { adapter: adapter },
16185
+ React__default['default'].createElement(ChatScreen, Object.assign({ formFactor: formFactor, options: options, onFetchAvatarPersonaData: onFetchAvatarPersonaData, onRenderTypingIndicator: onRenderTypingIndicator, onRenderMessage: onRenderMessage, onFetchParticipantMenuItems: onFetchParticipantMenuItems }, fileSharingOptions()))))));
16181
16186
  };
16182
- /** @private */
16183
- const IsCallEndedPage = (
16187
+
16188
+ // Copyright (c) Microsoft Corporation.
16189
+ const CallAdapterContext = React.createContext(undefined);
16184
16190
  /**
16185
- * Explicitly listing the pages of this function intentionally.
16186
- * This protects against adding a new composite page that should be marked as an callEndedPage.
16187
- * EndCallPages are used to trigger onCallEnded events so this could easily be missed.
16188
- * When you add a new composite page this will throw a compiler error. If this new page is an
16189
- * EndCallPage ensure you update the END_CALL_PAGES. Afterwards update the `page` parameter
16190
- * type below to allow your new page, i.e. add `| <your new page>
16191
+ * @private
16191
16192
  */
16192
- page) => END_CALL_PAGES.includes(page);
16193
+ const CallAdapterProvider = (props) => {
16194
+ const { adapter } = props;
16195
+ return React__default['default'].createElement(CallAdapterContext.Provider, { value: adapter }, props.children);
16196
+ };
16193
16197
  /**
16194
- * Creates a new call control options object and sets the correct values for disabling
16195
- * the buttons provided in the `disabledControls` array.
16196
- * Returns a new object without changing the original object.
16197
- * @param callControlOptions options for the call control component that need to be modified.
16198
- * @param disabledControls An array of controls to disable.
16199
- * @returns a copy of callControlOptions with disabledControls disabled
16200
16198
  * @private
16201
16199
  */
16202
- const disableCallControls = (callControlOptions, disabledControls) => {
16203
- var _a;
16204
- if (callControlOptions === false) {
16205
- return false;
16206
- }
16207
- // Ensure we clone the prop if it is an object to ensure we do not mutate the original prop.
16208
- let newOptions = (_a = (callControlOptions instanceof Object ? Object.assign({}, callControlOptions) : callControlOptions)) !== null && _a !== void 0 ? _a : {};
16209
- if (newOptions === true || newOptions === undefined) {
16210
- newOptions = disabledControls.reduce((acc, key) => {
16211
- acc[key] = { disabled: true };
16212
- return acc;
16213
- }, {});
16214
- }
16215
- else {
16216
- disabledControls.forEach((key) => {
16217
- if (newOptions[key] !== false) {
16218
- newOptions[key] = { disabled: true };
16219
- }
16220
- });
16200
+ const useAdapter = () => {
16201
+ const adapter = React.useContext(CallAdapterContext);
16202
+ if (!adapter) {
16203
+ throw 'Cannot find adapter please initialize before usage.';
16221
16204
  }
16222
- return newOptions;
16205
+ return adapter;
16223
16206
  };
16207
+
16208
+ // Copyright (c) Microsoft Corporation.
16209
+ // Licensed under the MIT license.
16210
+ /** @private */
16211
+ const containerDivStyles = { position: 'relative', width: '100%', height: '100%' };
16212
+
16213
+ // Copyright (c) Microsoft Corporation.
16224
16214
  /**
16225
- * Check if a disabled object is provided for a button and returns if the button is disabled.
16226
- * A button is only disabled if is explicitly set to disabled.
16227
- *
16228
- * @param option
16229
- * @returns whether a button is disabled
16230
16215
  * @private
16231
16216
  */
16232
- const isDisabled$2 = (option) => {
16233
- if (option === undefined || typeof option === 'boolean') {
16234
- return false;
16235
- }
16236
- return option.disabled;
16217
+ const useAdaptedSelector = (selector, selectorProps) => {
16218
+ return useSelectorWithAdaptation(selector, adaptCompositeState, selectorProps);
16237
16219
  };
16238
- /* @conditional-compile-remove(call-readiness) */
16239
16220
  /**
16240
- *
16241
- * This function uses permission API to determine if device permission state is granted, prompt or denied
16242
- * @returns whether device permission state is granted, prompt or denied
16243
- * If permission API is not supported on this browser, do nothing and log out error
16244
16221
  * @private
16245
16222
  */
16246
- const getDevicePermissionState = (setVideoState, setAudioState) => {
16247
- navigator.permissions
16248
- .query({ name: 'camera' })
16249
- .then((result) => {
16250
- setVideoState(result.state);
16251
- })
16252
- .catch(() => {
16253
- setVideoState('unsupported');
16254
- });
16255
- navigator.permissions
16256
- .query({ name: 'microphone' })
16257
- .then((result) => {
16258
- setAudioState(result.state);
16259
- })
16260
- .catch(() => {
16261
- setAudioState('unsupported');
16223
+ const useSelectorWithAdaptation = (selector, adaptState, selectorProps) => {
16224
+ var _a;
16225
+ const adapter = useAdapter();
16226
+ // Keeps track of whether the current component is mounted or not. If it has unmounted, make sure we do not modify the
16227
+ // state or it will cause React warnings in the console. https://skype.visualstudio.com/SPOOL/_workitems/edit/2453212
16228
+ const mounted = React.useRef(false);
16229
+ React.useEffect(() => {
16230
+ mounted.current = true;
16231
+ return () => {
16232
+ mounted.current = false;
16233
+ };
16262
16234
  });
16235
+ const callId = (_a = adapter.getState().call) === null || _a === void 0 ? void 0 : _a.id;
16236
+ const callConfigProps = React.useMemo(() => ({
16237
+ callId
16238
+ }), [callId]);
16239
+ const [props, setProps] = React.useState(selector(adaptState(adapter.getState()), selectorProps !== null && selectorProps !== void 0 ? selectorProps : callConfigProps));
16240
+ const propRef = React.useRef(props);
16241
+ propRef.current = props;
16242
+ React.useEffect(() => {
16243
+ const onStateChange = (state) => {
16244
+ if (!mounted.current) {
16245
+ return;
16246
+ }
16247
+ const newProps = selector(adaptState(state), selectorProps !== null && selectorProps !== void 0 ? selectorProps : callConfigProps);
16248
+ if (propRef.current !== newProps) {
16249
+ setProps(newProps);
16250
+ }
16251
+ };
16252
+ adapter.onStateChange(onStateChange);
16253
+ return () => {
16254
+ adapter.offStateChange(onStateChange);
16255
+ };
16256
+ }, [adaptState, adapter, selector, selectorProps, callConfigProps]);
16257
+ return props;
16263
16258
  };
16264
- /* @conditional-compile-remove(unsupported-browser) */
16265
- const isUnsupportedEnvironment = (features, environmentInfo, unsupportedBrowserVersionOptedIn) => {
16266
- return !!((features === null || features === void 0 ? void 0 : features.unsupportedEnvironment) &&
16267
- ((environmentInfo === null || environmentInfo === void 0 ? void 0 : environmentInfo.isSupportedBrowser) === false ||
16268
- ((environmentInfo === null || environmentInfo === void 0 ? void 0 : environmentInfo.isSupportedBrowserVersion) === false && !unsupportedBrowserVersionOptedIn) ||
16269
- (environmentInfo === null || environmentInfo === void 0 ? void 0 : environmentInfo.isSupportedPlatform) === false));
16259
+ const memoizeState = memoizeOne__default['default']((userId, deviceManager, calls, latestErrors, displayName, alternateCallerId, environmentInfo) => ({
16260
+ userId,
16261
+ incomingCalls: {},
16262
+ incomingCallsEnded: {},
16263
+ callsEnded: {},
16264
+ deviceManager,
16265
+ callAgent: { displayName },
16266
+ calls,
16267
+ latestErrors,
16268
+ /* @conditional-compile-remove(PSTN-calls) */
16269
+ alternateCallerId,
16270
+ /* @conditional-compile-remove(unsupported-browser) */
16271
+ environmentInfo
16272
+ }));
16273
+ const memoizeCalls = memoizeOne__default['default']((call) => (call ? { [call.id]: call } : {}));
16274
+ const adaptCompositeState = (compositeState) => {
16275
+ return memoizeState(compositeState.userId, compositeState.devices, memoizeCalls(compositeState.call),
16276
+ // This is an unsafe type expansion.
16277
+ // compositeState.latestErrors can contain properties that are not valid in CallErrors.
16278
+ //
16279
+ // But there is no way to check for valid property names at runtime:
16280
+ // - The set of valid property names is built from types in the @azure/communication-calling.
16281
+ // Thus we don't have a literal array of allowed strings at runtime.
16282
+ // - Due to minification / uglification, the property names from the objects at runtime can't be used
16283
+ // to compare against permissible values inferred from the types.
16284
+ //
16285
+ // This is not a huge problem -- it simply means that our adapted selector will include some extra operations
16286
+ // that are unknown to the UI component and data binding libraries. Generic handling of the errors (e.g.,
16287
+ // just displaying them in some UI surface) will continue to work for these operations. Handling of
16288
+ // specific operations (e.g., acting on errors related to permission issues) will ignore these operations.
16289
+ compositeState.latestErrors, compositeState.displayName,
16290
+ /* @conditional-compile-remove(PSTN-calls) */
16291
+ compositeState.alternateCallerId,
16292
+ /* @conditional-compile-remove(unsupported-browser) */
16293
+ compositeState.environmentInfo);
16270
16294
  };
16271
16295
 
16272
16296
  // Copyright (c) Microsoft Corporation.
@@ -20603,6 +20627,9 @@ class AzureCommunicationCallAdapter {
20603
20627
  const createAzureCommunicationCallAdapter = ({ userId, displayName, credential, locator,
20604
20628
  /* @conditional-compile-remove(PSTN-calls) */ alternateCallerId,
20605
20629
  /* @conditional-compile-remove(rooms) */ options }) => __awaiter$4(void 0, void 0, void 0, function* () {
20630
+ if (!isValidIdentifier(userId)) {
20631
+ throw new Error('Invalid identifier. Please provide valid identifier object.');
20632
+ }
20606
20633
  const callClient = createStatefulCallClient({
20607
20634
  userId,
20608
20635
  /* @conditional-compile-remove(PSTN-calls) */ alternateCallerId