@webex/plugin-meetings 3.8.1-next.4 → 3.8.1-next.41

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.
Files changed (94) hide show
  1. package/README.md +26 -13
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.js +24 -2
  5. package/dist/constants.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/locus-info/index.js +38 -84
  9. package/dist/locus-info/index.js.map +1 -1
  10. package/dist/media/index.js +2 -2
  11. package/dist/media/index.js.map +1 -1
  12. package/dist/meeting/brbState.js +14 -12
  13. package/dist/meeting/brbState.js.map +1 -1
  14. package/dist/meeting/in-meeting-actions.js +6 -0
  15. package/dist/meeting/in-meeting-actions.js.map +1 -1
  16. package/dist/meeting/index.js +213 -77
  17. package/dist/meeting/index.js.map +1 -1
  18. package/dist/meeting/request.js +19 -0
  19. package/dist/meeting/request.js.map +1 -1
  20. package/dist/meeting/request.type.js.map +1 -1
  21. package/dist/meeting/type.js +7 -0
  22. package/dist/meeting/type.js.map +1 -0
  23. package/dist/meeting/util.js +11 -0
  24. package/dist/meeting/util.js.map +1 -1
  25. package/dist/meetings/index.js +35 -33
  26. package/dist/meetings/index.js.map +1 -1
  27. package/dist/members/index.js +11 -9
  28. package/dist/members/index.js.map +1 -1
  29. package/dist/members/request.js +3 -3
  30. package/dist/members/request.js.map +1 -1
  31. package/dist/members/util.js +18 -6
  32. package/dist/members/util.js.map +1 -1
  33. package/dist/multistream/mediaRequestManager.js +1 -1
  34. package/dist/multistream/mediaRequestManager.js.map +1 -1
  35. package/dist/multistream/remoteMedia.js +34 -5
  36. package/dist/multistream/remoteMedia.js.map +1 -1
  37. package/dist/multistream/remoteMediaGroup.js +42 -2
  38. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  39. package/dist/multistream/sendSlotManager.js +32 -2
  40. package/dist/multistream/sendSlotManager.js.map +1 -1
  41. package/dist/reachability/index.js +5 -10
  42. package/dist/reachability/index.js.map +1 -1
  43. package/dist/types/constants.d.ts +22 -0
  44. package/dist/types/locus-info/index.d.ts +0 -9
  45. package/dist/types/meeting/brbState.d.ts +0 -1
  46. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  47. package/dist/types/meeting/index.d.ts +35 -18
  48. package/dist/types/meeting/request.d.ts +9 -1
  49. package/dist/types/meeting/request.type.d.ts +74 -0
  50. package/dist/types/meeting/type.d.ts +9 -0
  51. package/dist/types/meeting/util.d.ts +3 -0
  52. package/dist/types/members/index.d.ts +10 -7
  53. package/dist/types/members/request.d.ts +1 -1
  54. package/dist/types/members/util.d.ts +7 -3
  55. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  56. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  57. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  58. package/dist/types/reachability/index.d.ts +2 -2
  59. package/dist/webinar/index.js +1 -1
  60. package/package.json +24 -25
  61. package/src/constants.ts +23 -2
  62. package/src/locus-info/index.ts +47 -82
  63. package/src/media/index.ts +2 -2
  64. package/src/meeting/brbState.ts +9 -7
  65. package/src/meeting/in-meeting-actions.ts +13 -0
  66. package/src/meeting/index.ts +168 -42
  67. package/src/meeting/request.ts +16 -0
  68. package/src/meeting/request.type.ts +64 -0
  69. package/src/meeting/type.ts +9 -0
  70. package/src/meeting/util.ts +13 -0
  71. package/src/meetings/index.ts +3 -2
  72. package/src/members/index.ts +13 -10
  73. package/src/members/request.ts +2 -2
  74. package/src/members/util.ts +16 -4
  75. package/src/multistream/mediaRequestManager.ts +7 -7
  76. package/src/multistream/remoteMedia.ts +34 -4
  77. package/src/multistream/remoteMediaGroup.ts +37 -2
  78. package/src/multistream/sendSlotManager.ts +34 -2
  79. package/src/reachability/index.ts +5 -13
  80. package/test/unit/spec/locus-info/index.js +177 -83
  81. package/test/unit/spec/media/index.ts +107 -0
  82. package/test/unit/spec/meeting/brbState.ts +9 -9
  83. package/test/unit/spec/meeting/in-meeting-actions.ts +6 -0
  84. package/test/unit/spec/meeting/index.js +694 -60
  85. package/test/unit/spec/meeting/request.js +71 -0
  86. package/test/unit/spec/meeting/utils.js +21 -0
  87. package/test/unit/spec/meetings/index.js +2 -0
  88. package/test/unit/spec/members/index.js +68 -9
  89. package/test/unit/spec/members/request.js +2 -2
  90. package/test/unit/spec/members/utils.js +27 -7
  91. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  92. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  93. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  94. package/test/unit/spec/reachability/index.ts +2 -6
@@ -121,6 +121,7 @@ import {
121
121
  WEBINAR_ERROR_REGISTRATION_ID,
122
122
  JOIN_BEFORE_HOST,
123
123
  REGISTRATION_ID_STATUS,
124
+ STAGE_MANAGER_TYPE,
124
125
  } from '../constants';
125
126
  import BEHAVIORAL_METRICS from '../metrics/constants';
126
127
  import ParameterError from '../common/errors/parameter';
@@ -164,6 +165,8 @@ import {BrbState, createBrbState} from './brbState';
164
165
  import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
165
166
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
166
167
  import {ReachabilityMetrics} from '../reachability/reachability.types';
168
+ import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
169
+ import {Invitee} from './type';
167
170
 
168
171
  // default callback so we don't call an undefined function, but in practice it should never be used
169
172
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -231,6 +234,14 @@ export type AddMediaOptions = {
231
234
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
232
235
  bundlePolicy?: BundlePolicy; // applies only to multistream meetings
233
236
  allowMediaInLobby?: boolean; // allows adding media when in the lobby
237
+ additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
238
+ };
239
+
240
+ export type AdditionalMediaOptions = {
241
+ sendVideo?: boolean; // if not specified, default value of videoEnabled is used
242
+ receiveVideo?: boolean; // if not specified, default value of videoEnabled is used
243
+ sendAudio?: boolean; // if not specified, default value of audioEnabled true is used
244
+ receiveAudio?: boolean; // if not specified, default value of audioEnabled true is used
234
245
  };
235
246
 
236
247
  export type CallStateForMetrics = {
@@ -263,8 +274,9 @@ type FetchMeetingInfoParams = {
263
274
  };
264
275
 
265
276
  type MediaReachabilityMetrics = ReachabilityMetrics & {
266
- isSubnetReachable: boolean;
267
- selectedCluster: string | null;
277
+ subnet_reachable: boolean;
278
+ selected_cluster: string | null;
279
+ selected_subnet: string | null;
268
280
  };
269
281
 
270
282
  /**
@@ -732,10 +744,11 @@ export default class Meeting extends StatelessWebexPlugin {
732
744
  /**
733
745
  * @param {Object} attrs
734
746
  * @param {Object} options
747
+ * @param {Function} callback - if provided, it will be called with the newly created meeting object as soon as the meeting.id is set
735
748
  * @constructor
736
749
  * @memberof Meeting
737
750
  */
738
- constructor(attrs: any, options: object) {
751
+ constructor(attrs: any, options: object, callback: (meeting: Meeting) => void) {
739
752
  super({}, options);
740
753
  /**
741
754
  * @instance
@@ -761,6 +774,11 @@ export default class Meeting extends StatelessWebexPlugin {
761
774
  * @memberof Meeting
762
775
  */
763
776
  this.id = uuid.v4();
777
+
778
+ if (callback) {
779
+ callback(this);
780
+ }
781
+
764
782
  /**
765
783
  * Call state used for metrics
766
784
  * @instance
@@ -2757,9 +2775,11 @@ export default class Meeting extends StatelessWebexPlugin {
2757
2775
  LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
2758
2776
  ({spokenLanguage}) => {
2759
2777
  if (spokenLanguage) {
2760
- this.transcription.languageOptions.currentSpokenLanguage = spokenLanguage;
2778
+ if (this.transcription?.languageOptions) {
2779
+ this.transcription.languageOptions.currentSpokenLanguage = spokenLanguage;
2780
+ }
2761
2781
  // @ts-ignore
2762
- this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage);
2782
+ this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage, this.id);
2763
2783
 
2764
2784
  Trigger.trigger(
2765
2785
  this,
@@ -2768,7 +2788,7 @@ export default class Meeting extends StatelessWebexPlugin {
2768
2788
  function: 'setupLocusControlsListener',
2769
2789
  },
2770
2790
  EVENT_TRIGGERS.MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
2771
- {spokenLanguage}
2791
+ {spokenLanguage, meetingId: this.id}
2772
2792
  );
2773
2793
  }
2774
2794
  }
@@ -3045,12 +3065,16 @@ export default class Meeting extends StatelessWebexPlugin {
3045
3065
  // There is no concept of local/remote share for whiteboard
3046
3066
  // It does not matter who requested to share the whiteboard, everyone gets the same view
3047
3067
  else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
3048
- // WHITEBOARD - sharing whiteboard
3049
- // Webinar attendee should receive whiteboard as remote share
3050
- newShareStatus =
3051
- this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee
3052
- ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
3053
- : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
3068
+ if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
3069
+ // WHITEBOARD - sharing whiteboard
3070
+ // Webinar attendee should receive whiteboard as remote share
3071
+ newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
3072
+ } else if (this.guest) {
3073
+ // If user is a guest to a meeting, they should receive whiteboard as remote share
3074
+ newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
3075
+ } else {
3076
+ newShareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
3077
+ }
3054
3078
  }
3055
3079
  // or if content share is either released or null and whiteboard share is either released or null, no one is sharing
3056
3080
  else if (
@@ -3816,49 +3840,44 @@ export default class Meeting extends StatelessWebexPlugin {
3816
3840
 
3817
3841
  /**
3818
3842
  * Invite a guest to the call that isn't normally part of this call
3819
- * @param {Object} invitee
3843
+ * @param {Invitee} invitee
3820
3844
  * @param {String} invitee.emailAddress
3821
3845
  * @param {String} invitee.email
3822
3846
  * @param {String} invitee.phoneNumber
3823
3847
  * @param {Boolean} [alertIfActive]
3848
+ * @param {Boolean} [invitee.skipEmailValidation]
3849
+ * @param {Boolean} [invitee.isInternalNumber]
3824
3850
  * @returns {Promise} see #members.addMember
3825
3851
  * @public
3826
3852
  * @memberof Meeting
3827
3853
  */
3828
- public invite(
3829
- invitee: {
3830
- emailAddress: string;
3831
- email: string;
3832
- phoneNumber: string;
3833
- roles: Array<string>;
3834
- },
3835
- alertIfActive = true
3836
- ) {
3854
+ public invite(invitee: Invitee, alertIfActive = true) {
3837
3855
  return this.members.addMember(invitee, alertIfActive);
3838
3856
  }
3839
3857
 
3840
3858
  /**
3841
3859
  * Cancel an outgoing phone call invitation made during a meeting
3842
- * @param {Object} invitee
3860
+ * @param {Invitee} invitee
3843
3861
  * @param {String} invitee.phoneNumber
3844
3862
  * @returns {Promise} see #members.cancelPhoneInvite
3845
3863
  * @public
3846
3864
  * @memberof Meeting
3847
3865
  */
3848
- public cancelPhoneInvite(invitee: {phoneNumber: string}) {
3866
+ public cancelPhoneInvite(invitee: Invitee) {
3849
3867
  return this.members.cancelPhoneInvite(invitee);
3850
3868
  }
3851
3869
 
3852
3870
  /**
3853
- * Cancel an SIP call invitation made during a meeting
3854
- * @param {Object} invitee
3871
+ * Cancel an SIP/phone call invitation made during a meeting
3872
+ * @param {Invitee} invitee
3855
3873
  * @param {String} invitee.memberId
3856
- * @returns {Promise} see #members.cancelSIPInvite
3874
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
3875
+ * @returns {Promise} see #members.cancelInviteByMemberId
3857
3876
  * @public
3858
3877
  * @memberof Meeting
3859
3878
  */
3860
- public cancelSIPInvite(invitee: {memberId: string}) {
3861
- return this.members.cancelSIPInvite(invitee);
3879
+ public cancelInviteByMemberId(invitee: Invitee) {
3880
+ return this.members.cancelInviteByMemberId(invitee);
3862
3881
  }
3863
3882
 
3864
3883
  /**
@@ -4156,6 +4175,9 @@ export default class Meeting extends StatelessWebexPlugin {
4156
4175
  isClosedCaptionActive: MeetingUtil.isClosedCaptionActive(this.userDisplayHints),
4157
4176
  canStartManualCaption: MeetingUtil.canStartManualCaption(this.userDisplayHints),
4158
4177
  canStopManualCaption: MeetingUtil.canStopManualCaption(this.userDisplayHints),
4178
+ isLocalRecordingStarted: MeetingUtil.isLocalRecordingStarted(this.userDisplayHints),
4179
+ isLocalRecordingStopped: MeetingUtil.isLocalRecordingStopped(this.userDisplayHints),
4180
+ isLocalRecordingPaused: MeetingUtil.isLocalRecordingPaused(this.userDisplayHints),
4159
4181
  isManualCaptionActive: MeetingUtil.isManualCaptionActive(this.userDisplayHints),
4160
4182
  isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(this.userDisplayHints),
4161
4183
  isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(this.userDisplayHints),
@@ -7754,8 +7776,21 @@ export default class Meeting extends StatelessWebexPlugin {
7754
7776
  shareVideoEnabled = true,
7755
7777
  remoteMediaManagerConfig,
7756
7778
  bundlePolicy = 'max-bundle',
7779
+ additionalMediaOptions = {},
7757
7780
  } = options;
7758
7781
 
7782
+ const {
7783
+ sendVideo: rawSendVideo,
7784
+ receiveVideo: rawReceiveVideo,
7785
+ sendAudio: rawSendAudio,
7786
+ receiveAudio: rawReceiveAudio,
7787
+ } = additionalMediaOptions;
7788
+
7789
+ const sendVideo = videoEnabled && (rawSendVideo ?? true);
7790
+ const receiveVideo = videoEnabled && (rawReceiveVideo ?? true);
7791
+ const sendAudio = audioEnabled && (rawSendAudio ?? true);
7792
+ const receiveAudio = audioEnabled && (rawReceiveAudio ?? true);
7793
+
7759
7794
  this.allowMediaInLobby = options?.allowMediaInLobby;
7760
7795
 
7761
7796
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
@@ -7791,11 +7826,11 @@ export default class Meeting extends StatelessWebexPlugin {
7791
7826
  // when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any streams are published
7792
7827
  // to avoid doing an extra SDP exchange when they are published for the first time
7793
7828
  this.mediaProperties.setMediaDirection({
7794
- sendAudio: audioEnabled,
7795
- sendVideo: videoEnabled,
7829
+ sendAudio,
7830
+ sendVideo,
7796
7831
  sendShare: false,
7797
- receiveAudio: audioEnabled,
7798
- receiveVideo: videoEnabled,
7832
+ receiveAudio,
7833
+ receiveVideo,
7799
7834
  receiveShare: shareAudioEnabled || shareVideoEnabled,
7800
7835
  });
7801
7836
 
@@ -8363,6 +8398,10 @@ export default class Meeting extends StatelessWebexPlugin {
8363
8398
  }
8364
8399
 
8365
8400
  if (whiteboard) {
8401
+ // @ts-ignore
8402
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8403
+ key: 'internal.client.share.initiated',
8404
+ });
8366
8405
  // @ts-ignore
8367
8406
  this.webex.internal.newMetrics.submitClientEvent({
8368
8407
  name: 'client.share.initiated',
@@ -8422,11 +8461,17 @@ export default class Meeting extends StatelessWebexPlugin {
8422
8461
  const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
8423
8462
 
8424
8463
  if (whiteboard) {
8464
+ // @ts-ignore
8465
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8466
+ key: 'internal.client.share.stopped',
8467
+ });
8425
8468
  // @ts-ignore
8426
8469
  this.webex.internal.newMetrics.submitClientEvent({
8427
8470
  name: 'client.share.stopped',
8428
8471
  payload: {
8429
8472
  mediaType: 'whiteboard',
8473
+ // @ts-ignore
8474
+ shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
8430
8475
  },
8431
8476
  options: {
8432
8477
  meetingId: this.id,
@@ -8584,12 +8629,18 @@ export default class Meeting extends StatelessWebexPlugin {
8584
8629
  }
8585
8630
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
8586
8631
  if (content) {
8632
+ // @ts-ignore
8633
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8634
+ key: 'internal.client.share.stopped',
8635
+ });
8587
8636
  // @ts-ignore
8588
8637
  this.webex.internal.newMetrics.submitClientEvent({
8589
8638
  name: 'client.share.stopped',
8590
8639
  payload: {
8591
8640
  mediaType: 'share',
8592
8641
  shareInstanceId: this.localShareInstanceId,
8642
+ // @ts-ignore
8643
+ shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
8593
8644
  },
8594
8645
  options: {meetingId: this.id},
8595
8646
  });
@@ -9566,6 +9617,11 @@ export default class Meeting extends StatelessWebexPlugin {
9566
9617
  this.shareCAEventSentStatus.transmitStart = false;
9567
9618
  this.shareCAEventSentStatus.transmitStop = false;
9568
9619
 
9620
+ // @ts-ignore
9621
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
9622
+ key: 'internal.client.share.initiated',
9623
+ });
9624
+
9569
9625
  // @ts-ignore
9570
9626
  this.webex.internal.newMetrics.submitClientEvent({
9571
9627
  name: 'client.share.initiated',
@@ -9721,21 +9777,91 @@ export default class Meeting extends StatelessWebexPlugin {
9721
9777
  return total;
9722
9778
  }, 0);
9723
9779
 
9780
+ const selectedSubnetFirstOctet = this.mediaServerIp?.split('.')[0];
9781
+
9724
9782
  let isSubnetReachable = null;
9725
- if (totalSuccessCases > 0) {
9726
- // @ts-ignore
9727
- isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(this.mediaServerIp);
9783
+ if (totalSuccessCases > 0 && selectedSubnetFirstOctet) {
9784
+ isSubnetReachable =
9785
+ // @ts-ignore
9786
+ this.webex.meetings.reachability.isSubnetReachable(selectedSubnetFirstOctet);
9728
9787
  }
9729
9788
 
9730
- let selectedCluster = null;
9731
- if (this.mediaConnections && this.mediaConnections.length > 0) {
9732
- selectedCluster = this.mediaConnections[0].mediaAgentCluster;
9733
- }
9789
+ const selectedCluster = this.mediaConnections?.[0]?.mediaAgentCluster ?? null;
9734
9790
 
9735
9791
  return {
9736
9792
  ...reachabilityMetrics,
9737
- isSubnetReachable,
9738
- selectedCluster,
9793
+ subnet_reachable: isSubnetReachable,
9794
+ selected_cluster: selectedCluster,
9795
+ selected_subnet: selectedSubnetFirstOctet ? `${selectedSubnetFirstOctet}.X.X.X` : null,
9739
9796
  };
9740
9797
  }
9798
+
9799
+ /**
9800
+ * Set the stage for the meeting
9801
+ *
9802
+ * @param {SetStageOptions} options Options to use when setting the stage
9803
+ * @returns {Promise} The locus request
9804
+ */
9805
+ setStage({
9806
+ activeSpeakerProportion = 0.5,
9807
+ customBackground,
9808
+ customLogo,
9809
+ customNameLabel,
9810
+ importantParticipants,
9811
+ lockAttendeeViewOnStage = false,
9812
+ showActiveSpeaker = false,
9813
+ }: SetStageOptions = {}) {
9814
+ const videoLayout: SetStageVideoLayout = {
9815
+ overrideDefault: true,
9816
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
9817
+ stageParameters: {
9818
+ activeSpeakerProportion,
9819
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
9820
+ stageManagerType: 0,
9821
+ },
9822
+ };
9823
+
9824
+ if (importantParticipants?.length) {
9825
+ videoLayout.stageParameters.importantParticipants = importantParticipants.map(
9826
+ (importantParticipant, index) => ({...importantParticipant, order: index + 1})
9827
+ );
9828
+ }
9829
+
9830
+ if (customLogo) {
9831
+ if (!videoLayout.customLayouts) {
9832
+ videoLayout.customLayouts = {};
9833
+ }
9834
+ videoLayout.customLayouts.logo = customLogo;
9835
+ // eslint-disable-next-line no-bitwise
9836
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.LOGO;
9837
+ }
9838
+
9839
+ if (customBackground) {
9840
+ if (!videoLayout.customLayouts) {
9841
+ videoLayout.customLayouts = {};
9842
+ }
9843
+ videoLayout.customLayouts.background = customBackground;
9844
+ // eslint-disable-next-line no-bitwise
9845
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.BACKGROUND;
9846
+ }
9847
+
9848
+ if (customNameLabel) {
9849
+ videoLayout.nameLabelStyle = customNameLabel;
9850
+ // eslint-disable-next-line no-bitwise
9851
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.NAME_LABEL;
9852
+ }
9853
+
9854
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9855
+ }
9856
+
9857
+ /**
9858
+ * Unset the stage for the meeting
9859
+ *
9860
+ * @returns {Promise} The locus request
9861
+ */
9862
+ unsetStage() {
9863
+ const videoLayout: UnsetStageVideoLayout = {overrideDefault: false};
9864
+
9865
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9866
+ }
9741
9867
  }
@@ -32,6 +32,7 @@ import {
32
32
  BrbOptions,
33
33
  ToggleReactionsOptions,
34
34
  PostMeetingDataConsentOptions,
35
+ SynchronizeVideoLayout,
35
36
  } from './request.type';
36
37
  import MeetingUtil from './util';
37
38
  import {AnnotationInfo} from '../annotation/annotation.types';
@@ -969,4 +970,19 @@ export default class MeetingRequest extends StatelessWebexPlugin {
969
970
  },
970
971
  });
971
972
  }
973
+
974
+ /**
975
+ * Synchronize the stage for a meeting
976
+ *
977
+ * @param {LocusUrl} locusUrl The locus URL
978
+ * @param {SetStageVideoLayout} videoLayout The video layout to synchronize
979
+ * @returns {Promise} The locus request
980
+ */
981
+ synchronizeStage(locusUrl: string, videoLayout: SynchronizeVideoLayout) {
982
+ return this.locusDeltaRequest({
983
+ method: HTTP_VERBS.PATCH,
984
+ uri: `${locusUrl}/${CONTROLS}`,
985
+ body: {videoLayout},
986
+ });
987
+ }
972
988
  }
@@ -25,3 +25,67 @@ export type PostMeetingDataConsentOptions = {
25
25
  deviceUrl: string;
26
26
  selfId: string;
27
27
  };
28
+
29
+ export type StageCustomLogoPositions =
30
+ | 'LowerLeft'
31
+ | 'LowerMiddle'
32
+ | 'LowerRight'
33
+ | 'UpperLeft'
34
+ | 'UpperMiddle'
35
+ | 'UpperRight';
36
+
37
+ export type StageNameLabelType = 'Primary' | 'PrimaryInverted' | 'Secondary' | 'SecondaryInverted';
38
+
39
+ export type StageCustomBackground = {
40
+ url: string;
41
+ [others: string]: unknown;
42
+ };
43
+
44
+ export type StageCustomLogo = {
45
+ url: string;
46
+ position: StageCustomLogoPositions;
47
+ [others: string]: unknown;
48
+ };
49
+
50
+ export type StageCustomNameLabel = {
51
+ accentColor: string;
52
+ background: {color: string};
53
+ border: {color: string};
54
+ content: {displayName: {color: string}; subtitle: {color: string}};
55
+ decoration: {color: string};
56
+ fadeOut?: {delay: number};
57
+ type: StageNameLabelType;
58
+ [others: string]: unknown;
59
+ };
60
+
61
+ export type SetStageOptions = {
62
+ activeSpeakerProportion?: number;
63
+ customBackground?: StageCustomBackground;
64
+ customLogo?: StageCustomLogo;
65
+ customNameLabel?: StageCustomNameLabel;
66
+ importantParticipants?: {mainCsi: number; participantId: string}[];
67
+ lockAttendeeViewOnStage?: boolean;
68
+ showActiveSpeaker?: boolean;
69
+ };
70
+
71
+ export type SetStageVideoLayout = {
72
+ overrideDefault: true;
73
+ lockAttendeeViewOnStageOnly: boolean;
74
+ stageParameters: {
75
+ importantParticipants?: {participantId: string; mainCsi: number; order: number}[];
76
+ showActiveSpeaker: {show: boolean; order: number};
77
+ activeSpeakerProportion: number;
78
+ stageManagerType: number;
79
+ };
80
+ customLayouts?: {
81
+ background?: StageCustomBackground;
82
+ logo?: StageCustomLogo;
83
+ };
84
+ nameLabelStyle?: StageCustomNameLabel;
85
+ };
86
+
87
+ export type UnsetStageVideoLayout = {
88
+ overrideDefault: false;
89
+ };
90
+
91
+ export type SynchronizeVideoLayout = SetStageVideoLayout | UnsetStageVideoLayout;
@@ -0,0 +1,9 @@
1
+ export type Invitee = {
2
+ memberId: string;
3
+ emailAddress: string;
4
+ email: string;
5
+ phoneNumber: string;
6
+ roles: Array<string>;
7
+ skipEmailValidation?: boolean;
8
+ isInternalNumber?: boolean;
9
+ };
@@ -208,6 +208,10 @@ const MeetingUtil = {
208
208
  meeting.simultaneousInterpretation.cleanUp();
209
209
  meeting.locusMediaRequest = undefined;
210
210
 
211
+ meeting.webex?.internal?.newMetrics?.callDiagnosticMetrics?.clearEventLimitsForCorrelationId(
212
+ meeting.correlationId
213
+ );
214
+
211
215
  // make sure we send last metrics before we close the peerconnection
212
216
  const stopStatsAnalyzer = meeting.statsAnalyzer
213
217
  ? meeting.statsAnalyzer.stopAnalyzer()
@@ -542,6 +546,15 @@ const MeetingUtil = {
542
546
  canStartManualCaption: (displayHints) =>
543
547
  displayHints.includes(DISPLAY_HINTS.MANUAL_CAPTION_START),
544
548
 
549
+ isLocalRecordingStarted: (displayHints) =>
550
+ displayHints.includes(DISPLAY_HINTS.LOCAL_RECORDING_STATUS_STARTED),
551
+
552
+ isLocalRecordingStopped: (displayHints) =>
553
+ displayHints.includes(DISPLAY_HINTS.LOCAL_RECORDING_STATUS_STOPPED),
554
+
555
+ isLocalRecordingPaused: (displayHints) =>
556
+ displayHints.includes(DISPLAY_HINTS.LOCAL_RECORDING_STATUS_PAUSED),
557
+
545
558
  canStopManualCaption: (displayHints) => displayHints.includes(DISPLAY_HINTS.MANUAL_CAPTION_STOP),
546
559
 
547
560
  isManualCaptionActive: (displayHints) =>
@@ -1560,11 +1560,12 @@ export default class Meetings extends WebexPlugin {
1560
1560
  {
1561
1561
  // @ts-ignore
1562
1562
  parent: this.webex,
1563
+ },
1564
+ (newMeeting) => {
1565
+ this.meetingCollection.set(newMeeting);
1563
1566
  }
1564
1567
  );
1565
1568
 
1566
- this.meetingCollection.set(meeting);
1567
-
1568
1569
  try {
1569
1570
  // if no participant has joined the scheduled meeting (meaning meeting is not active) and we get a locusEvent,
1570
1571
  // it means the meeting will start in 5-6 min. In that case, we want to fetchMeetingInfo
@@ -30,6 +30,7 @@ import MembersUtil from './util';
30
30
  import {ReceiveSlotManager} from '../multistream/receiveSlotManager';
31
31
  import {MediaRequestManager} from '../multistream/mediaRequestManager';
32
32
  import {ServerRoleShape} from './types';
33
+ import {Invitee} from '../meeting/type';
33
34
 
34
35
  /**
35
36
  * Members Update Event
@@ -800,18 +801,18 @@ export default class Members extends StatelessWebexPlugin {
800
801
 
801
802
  /**
802
803
  * Adds a guest Member to the associated meeting
803
- * @param {String} invitee
804
+ * @param {Invitee} invitee
804
805
  * @param {Boolean} [alertIfActive]
805
806
  * @returns {Promise}
806
807
  * @memberof Members
807
808
  */
808
- addMember(invitee: any, alertIfActive?: boolean) {
809
+ addMember(invitee: Invitee, alertIfActive?: boolean) {
809
810
  if (!this.locusUrl) {
810
811
  return Promise.reject(
811
812
  new ParameterError('The associated locus url for this meeting object must be defined.')
812
813
  );
813
814
  }
814
- if (MembersUtil.isInvalidInvitee(invitee)) {
815
+ if (invitee?.skipEmailValidation !== true && MembersUtil.isInvalidInvitee(invitee)) {
815
816
  return Promise.reject(
816
817
  new ParameterError(
817
818
  'The invitee must be defined with either a valid email, emailAddress or phoneNumber property.'
@@ -825,11 +826,11 @@ export default class Members extends StatelessWebexPlugin {
825
826
 
826
827
  /**
827
828
  * Cancels an outgoing PSTN call to the associated meeting
828
- * @param {String} invitee
829
+ * @param {Invitee} invitee
829
830
  * @returns {Promise}
830
831
  * @memberof Members
831
832
  */
832
- cancelPhoneInvite(invitee: any) {
833
+ cancelPhoneInvite(invitee: Invitee) {
833
834
  if (!this.locusUrl) {
834
835
  return Promise.reject(
835
836
  new ParameterError('The associated locus url for this meeting object must be defined.')
@@ -846,12 +847,14 @@ export default class Members extends StatelessWebexPlugin {
846
847
  }
847
848
 
848
849
  /**
849
- * Cancels an SIP call to the associated meeting
850
- * @param {String} invitee
850
+ * Cancels an SIP/phone call to the associated meeting
851
+ * @param {Invitee} invitee
852
+ * @param {String} invitee.memberId - The memberId of the invitee
853
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
851
854
  * @returns {Promise}
852
855
  * @memberof Members
853
856
  */
854
- cancelSIPInvite(invitee: any) {
857
+ cancelInviteByMemberId(invitee: Invitee) {
855
858
  if (!this.locusUrl) {
856
859
  return Promise.reject(
857
860
  new ParameterError('The associated locus url for this meeting object must be defined.')
@@ -862,9 +865,9 @@ export default class Members extends StatelessWebexPlugin {
862
865
  new ParameterError('The invitee must be defined with a memberId property.')
863
866
  );
864
867
  }
865
- const options = MembersUtil.cancelSIPInviteOptions(invitee, this.locusUrl);
868
+ const options = MembersUtil.cancelInviteByMemberIdOptions(invitee, this.locusUrl);
866
869
 
867
- return this.membersRequest.cancelSIPInvite(options);
870
+ return this.membersRequest.cancelInviteByMemberId(options);
868
871
  }
869
872
 
870
873
  /**
@@ -285,14 +285,14 @@ export default class MembersRequest extends StatelessWebexPlugin {
285
285
  * @throws {Error} if the options are not valid and complete, must have invitee with memberId AND locusUrl
286
286
  * @memberof MembersRequest
287
287
  */
288
- cancelSIPInvite(options: any) {
288
+ cancelInviteByMemberId(options: any) {
289
289
  if (!options?.invitee?.memberId || !options?.locusUrl) {
290
290
  throw new ParameterError(
291
291
  'invitee must be passed and the associated locus url for this meeting object must be defined.'
292
292
  );
293
293
  }
294
294
 
295
- const requestParams = MembersUtil.generateCancelSIPInviteRequestParams(options);
295
+ const requestParams = MembersUtil.generateCancelInviteByMemberIdRequestParams(options);
296
296
 
297
297
  return this.locusDeltaRequest(requestParams);
298
298
  }
@@ -1,4 +1,5 @@
1
1
  import uuid from 'uuid';
2
+ import {has} from 'lodash';
2
3
  import {
3
4
  HTTP_VERBS,
4
5
  CONTROLS,
@@ -13,6 +14,7 @@ import {
13
14
  } from '../constants';
14
15
 
15
16
  import {RoleAssignmentOptions, RoleAssignmentRequest, ServerRoleShape} from './types';
17
+ import {Invitee} from '../meeting/type';
16
18
 
17
19
  const MembersUtil = {
18
20
  /**
@@ -47,6 +49,9 @@ const MembersUtil = {
47
49
  address:
48
50
  options.invitee.emailAddress || options.invitee.email || options.invitee.phoneNumber,
49
51
  ...(options.invitee.roles ? {roles: options.invitee.roles} : {}),
52
+ ...(has(options.invitee, 'isInternalNumber')
53
+ ? {isInternalNumber: options.invitee.isInternalNumber}
54
+ : {}),
50
55
  },
51
56
  ],
52
57
  alertIfActive: options.alertIfActive,
@@ -101,12 +106,16 @@ const MembersUtil = {
101
106
  return requestParams;
102
107
  },
103
108
 
104
- isInvalidInvitee: (invitee) => {
109
+ isInvalidInvitee: (invitee: Invitee) => {
105
110
  if (!(invitee && (invitee.email || invitee.emailAddress || invitee.phoneNumber))) {
106
111
  return true;
107
112
  }
108
113
 
109
114
  if (invitee.phoneNumber) {
115
+ if (invitee.isInternalNumber) {
116
+ return !DIALER_REGEX.INTERNAL_NUMBER.test(invitee.phoneNumber);
117
+ }
118
+
110
119
  return !DIALER_REGEX.E164_FORMAT.test(invitee.phoneNumber);
111
120
  }
112
121
 
@@ -371,17 +380,20 @@ const MembersUtil = {
371
380
  return requestParams;
372
381
  },
373
382
 
374
- cancelSIPInviteOptions: (invitee, locusUrl) => ({
383
+ cancelInviteByMemberIdOptions: (invitee, locusUrl) => ({
375
384
  invitee,
376
385
  locusUrl,
377
386
  }),
378
387
 
379
- generateCancelSIPInviteRequestParams: (options) => {
388
+ generateCancelInviteByMemberIdRequestParams: (options) => {
389
+ const {memberId, isInternalNumber} = options.invitee;
390
+ const hasIsInternalNumberProp = has(options.invitee, 'isInternalNumber');
380
391
  const body = {
381
392
  actionType: _REMOVE_,
382
393
  invitees: [
383
394
  {
384
- address: options.invitee.memberId,
395
+ address: memberId,
396
+ ...(hasIsInternalNumberProp ? {isInternalNumber} : {}),
385
397
  },
386
398
  ],
387
399
  };