@webex/plugin-meetings 3.8.1-web-workers-keepalive.1 → 3.9.0-next.1

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 (87) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +24 -2
  4. package/dist/constants.js.map +1 -1
  5. package/dist/interpretation/index.js +1 -1
  6. package/dist/interpretation/siLanguage.js +1 -1
  7. package/dist/locus-info/index.js +39 -85
  8. package/dist/locus-info/index.js.map +1 -1
  9. package/dist/meeting/brbState.js +14 -12
  10. package/dist/meeting/brbState.js.map +1 -1
  11. package/dist/meeting/in-meeting-actions.js +6 -0
  12. package/dist/meeting/in-meeting-actions.js.map +1 -1
  13. package/dist/meeting/index.js +274 -140
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/request.js +19 -0
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/request.type.js.map +1 -1
  18. package/dist/meeting/type.js +7 -0
  19. package/dist/meeting/type.js.map +1 -0
  20. package/dist/meeting/util.js +68 -2
  21. package/dist/meeting/util.js.map +1 -1
  22. package/dist/meetings/index.js +35 -33
  23. package/dist/meetings/index.js.map +1 -1
  24. package/dist/member/index.js.map +1 -1
  25. package/dist/members/index.js +11 -9
  26. package/dist/members/index.js.map +1 -1
  27. package/dist/members/request.js +3 -3
  28. package/dist/members/request.js.map +1 -1
  29. package/dist/members/util.js +18 -6
  30. package/dist/members/util.js.map +1 -1
  31. package/dist/multistream/mediaRequestManager.js +1 -1
  32. package/dist/multistream/mediaRequestManager.js.map +1 -1
  33. package/dist/multistream/remoteMedia.js +34 -5
  34. package/dist/multistream/remoteMedia.js.map +1 -1
  35. package/dist/multistream/remoteMediaGroup.js +42 -2
  36. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  37. package/dist/multistream/sendSlotManager.js +32 -2
  38. package/dist/multistream/sendSlotManager.js.map +1 -1
  39. package/dist/types/constants.d.ts +22 -0
  40. package/dist/types/locus-info/index.d.ts +0 -9
  41. package/dist/types/meeting/brbState.d.ts +0 -1
  42. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  43. package/dist/types/meeting/index.d.ts +40 -19
  44. package/dist/types/meeting/request.d.ts +9 -1
  45. package/dist/types/meeting/request.type.d.ts +74 -0
  46. package/dist/types/meeting/type.d.ts +9 -0
  47. package/dist/types/meeting/util.d.ts +3 -0
  48. package/dist/types/members/index.d.ts +10 -7
  49. package/dist/types/members/request.d.ts +1 -1
  50. package/dist/types/members/util.d.ts +7 -3
  51. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  52. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  53. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  54. package/dist/webinar/index.js +1 -1
  55. package/package.json +22 -23
  56. package/src/constants.ts +23 -2
  57. package/src/locus-info/index.ts +48 -86
  58. package/src/meeting/brbState.ts +9 -7
  59. package/src/meeting/in-meeting-actions.ts +13 -0
  60. package/src/meeting/index.ts +165 -38
  61. package/src/meeting/request.ts +16 -0
  62. package/src/meeting/request.type.ts +64 -0
  63. package/src/meeting/type.ts +9 -0
  64. package/src/meeting/util.ts +73 -2
  65. package/src/meetings/index.ts +3 -2
  66. package/src/member/index.ts +1 -0
  67. package/src/members/index.ts +13 -10
  68. package/src/members/request.ts +2 -2
  69. package/src/members/util.ts +16 -4
  70. package/src/multistream/mediaRequestManager.ts +7 -7
  71. package/src/multistream/remoteMedia.ts +34 -4
  72. package/src/multistream/remoteMediaGroup.ts +37 -2
  73. package/src/multistream/sendSlotManager.ts +34 -2
  74. package/test/unit/spec/locus-info/index.js +199 -83
  75. package/test/unit/spec/meeting/brbState.ts +9 -9
  76. package/test/unit/spec/meeting/in-meeting-actions.ts +6 -0
  77. package/test/unit/spec/meeting/index.js +729 -80
  78. package/test/unit/spec/meeting/request.js +71 -0
  79. package/test/unit/spec/meeting/utils.js +122 -1
  80. package/test/unit/spec/meetings/index.js +2 -0
  81. package/test/unit/spec/members/index.js +68 -9
  82. package/test/unit/spec/members/request.js +2 -2
  83. package/test/unit/spec/members/utils.js +27 -7
  84. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  85. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  86. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  87. package/test/unit/spec/reachability/index.ts +3 -1
@@ -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';
@@ -248,6 +251,7 @@ export type CallStateForMetrics = {
248
251
  loginType?: string;
249
252
  userNameInput?: string;
250
253
  emailInput?: string;
254
+ pstnCorrelationId?: string;
251
255
  };
252
256
 
253
257
  export const MEDIA_UPDATE_TYPE = {
@@ -741,10 +745,11 @@ export default class Meeting extends StatelessWebexPlugin {
741
745
  /**
742
746
  * @param {Object} attrs
743
747
  * @param {Object} options
748
+ * @param {Function} callback - if provided, it will be called with the newly created meeting object as soon as the meeting.id is set
744
749
  * @constructor
745
750
  * @memberof Meeting
746
751
  */
747
- constructor(attrs: any, options: object) {
752
+ constructor(attrs: any, options: object, callback: (meeting: Meeting) => void) {
748
753
  super({}, options);
749
754
  /**
750
755
  * @instance
@@ -770,6 +775,11 @@ export default class Meeting extends StatelessWebexPlugin {
770
775
  * @memberof Meeting
771
776
  */
772
777
  this.id = uuid.v4();
778
+
779
+ if (callback) {
780
+ callback(this);
781
+ }
782
+
773
783
  /**
774
784
  * Call state used for metrics
775
785
  * @instance
@@ -1674,6 +1684,22 @@ export default class Meeting extends StatelessWebexPlugin {
1674
1684
  this.callStateForMetrics.correlationId = correlationId;
1675
1685
  }
1676
1686
 
1687
+ /**
1688
+ * Getter - Returns callStateForMetrics.pstnCorrelationId
1689
+ * @returns {string | undefined}
1690
+ */
1691
+ get pstnCorrelationId(): string | undefined {
1692
+ return this.callStateForMetrics.pstnCorrelationId;
1693
+ }
1694
+
1695
+ /**
1696
+ * Setter - sets callStateForMetrics.pstnCorrelationId
1697
+ * @param {string | undefined} correlationId
1698
+ */
1699
+ set pstnCorrelationId(correlationId: string | undefined) {
1700
+ this.callStateForMetrics.pstnCorrelationId = correlationId;
1701
+ }
1702
+
1677
1703
  /**
1678
1704
  * Getter - Returns callStateForMetrics.userNameInput
1679
1705
  * @returns {string}
@@ -3056,12 +3082,16 @@ export default class Meeting extends StatelessWebexPlugin {
3056
3082
  // There is no concept of local/remote share for whiteboard
3057
3083
  // It does not matter who requested to share the whiteboard, everyone gets the same view
3058
3084
  else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
3059
- // WHITEBOARD - sharing whiteboard
3060
- // Webinar attendee should receive whiteboard as remote share
3061
- newShareStatus =
3062
- this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee
3063
- ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
3064
- : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
3085
+ if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
3086
+ // WHITEBOARD - sharing whiteboard
3087
+ // Webinar attendee should receive whiteboard as remote share
3088
+ newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
3089
+ } else if (this.guest) {
3090
+ // If user is a guest to a meeting, they should receive whiteboard as remote share
3091
+ newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
3092
+ } else {
3093
+ newShareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
3094
+ }
3065
3095
  }
3066
3096
  // or if content share is either released or null and whiteboard share is either released or null, no one is sharing
3067
3097
  else if (
@@ -3827,49 +3857,44 @@ export default class Meeting extends StatelessWebexPlugin {
3827
3857
 
3828
3858
  /**
3829
3859
  * Invite a guest to the call that isn't normally part of this call
3830
- * @param {Object} invitee
3860
+ * @param {Invitee} invitee
3831
3861
  * @param {String} invitee.emailAddress
3832
3862
  * @param {String} invitee.email
3833
3863
  * @param {String} invitee.phoneNumber
3834
3864
  * @param {Boolean} [alertIfActive]
3865
+ * @param {Boolean} [invitee.skipEmailValidation]
3866
+ * @param {Boolean} [invitee.isInternalNumber]
3835
3867
  * @returns {Promise} see #members.addMember
3836
3868
  * @public
3837
3869
  * @memberof Meeting
3838
3870
  */
3839
- public invite(
3840
- invitee: {
3841
- emailAddress: string;
3842
- email: string;
3843
- phoneNumber: string;
3844
- roles: Array<string>;
3845
- },
3846
- alertIfActive = true
3847
- ) {
3871
+ public invite(invitee: Invitee, alertIfActive = true) {
3848
3872
  return this.members.addMember(invitee, alertIfActive);
3849
3873
  }
3850
3874
 
3851
3875
  /**
3852
3876
  * Cancel an outgoing phone call invitation made during a meeting
3853
- * @param {Object} invitee
3877
+ * @param {Invitee} invitee
3854
3878
  * @param {String} invitee.phoneNumber
3855
3879
  * @returns {Promise} see #members.cancelPhoneInvite
3856
3880
  * @public
3857
3881
  * @memberof Meeting
3858
3882
  */
3859
- public cancelPhoneInvite(invitee: {phoneNumber: string}) {
3883
+ public cancelPhoneInvite(invitee: Invitee) {
3860
3884
  return this.members.cancelPhoneInvite(invitee);
3861
3885
  }
3862
3886
 
3863
3887
  /**
3864
- * Cancel an SIP call invitation made during a meeting
3865
- * @param {Object} invitee
3888
+ * Cancel an SIP/phone call invitation made during a meeting
3889
+ * @param {Invitee} invitee
3866
3890
  * @param {String} invitee.memberId
3867
- * @returns {Promise} see #members.cancelSIPInvite
3891
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
3892
+ * @returns {Promise} see #members.cancelInviteByMemberId
3868
3893
  * @public
3869
3894
  * @memberof Meeting
3870
3895
  */
3871
- public cancelSIPInvite(invitee: {memberId: string}) {
3872
- return this.members.cancelSIPInvite(invitee);
3896
+ public cancelInviteByMemberId(invitee: Invitee) {
3897
+ return this.members.cancelInviteByMemberId(invitee);
3873
3898
  }
3874
3899
 
3875
3900
  /**
@@ -4167,6 +4192,9 @@ export default class Meeting extends StatelessWebexPlugin {
4167
4192
  isClosedCaptionActive: MeetingUtil.isClosedCaptionActive(this.userDisplayHints),
4168
4193
  canStartManualCaption: MeetingUtil.canStartManualCaption(this.userDisplayHints),
4169
4194
  canStopManualCaption: MeetingUtil.canStopManualCaption(this.userDisplayHints),
4195
+ isLocalRecordingStarted: MeetingUtil.isLocalRecordingStarted(this.userDisplayHints),
4196
+ isLocalRecordingStopped: MeetingUtil.isLocalRecordingStopped(this.userDisplayHints),
4197
+ isLocalRecordingPaused: MeetingUtil.isLocalRecordingPaused(this.userDisplayHints),
4170
4198
  isManualCaptionActive: MeetingUtil.isManualCaptionActive(this.userDisplayHints),
4171
4199
  isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(this.userDisplayHints),
4172
4200
  isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(this.userDisplayHints),
@@ -5924,15 +5952,6 @@ export default class Meeting extends StatelessWebexPlugin {
5924
5952
  this.meetingFiniteStateMachine.fail(error);
5925
5953
  LoggerProxy.logger.error('Meeting:index#join --> Failed', error);
5926
5954
 
5927
- // @ts-ignore
5928
- this.webex.internal.newMetrics.submitClientEvent({
5929
- name: 'client.locus.join.response',
5930
- payload: {
5931
- identifiers: {meetingLookupUrl: this.meetingInfo?.meetingLookupUrl},
5932
- },
5933
- options: {meetingId: this.id, rawError: error},
5934
- });
5935
-
5936
5955
  // TODO: change this to error codes and pre defined dictionary
5937
5956
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_FAILURE, {
5938
5957
  correlation_id: this.correlationId,
@@ -6082,8 +6101,9 @@ export default class Meeting extends StatelessWebexPlugin {
6082
6101
  */
6083
6102
  private dialInPstn() {
6084
6103
  if (this.isPhoneProvisioned(this.dialInDeviceStatus)) return Promise.resolve(); // prevent multiple dial in devices from being provisioned
6104
+ this.pstnCorrelationId = uuid.v4();
6085
6105
 
6086
- const {correlationId, locusUrl} = this;
6106
+ const {pstnCorrelationId, locusUrl} = this;
6087
6107
 
6088
6108
  if (!this.dialInUrl) this.dialInUrl = `dialin:///${uuid.v4()}`;
6089
6109
 
@@ -6091,7 +6111,7 @@ export default class Meeting extends StatelessWebexPlugin {
6091
6111
  this.meetingRequest
6092
6112
  // @ts-ignore
6093
6113
  .dialIn({
6094
- correlationId,
6114
+ correlationId: pstnCorrelationId,
6095
6115
  dialInUrl: this.dialInUrl,
6096
6116
  locusUrl,
6097
6117
  clientUrl: this.deviceUrl,
@@ -6100,12 +6120,17 @@ export default class Meeting extends StatelessWebexPlugin {
6100
6120
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
6101
6121
  correlation_id: this.correlationId,
6102
6122
  dial_in_url: this.dialInUrl,
6123
+ dial_in_correlation_id: pstnCorrelationId,
6103
6124
  locus_id: locusUrl.split('/').pop(),
6104
6125
  client_url: this.deviceUrl,
6105
6126
  reason: error.error?.message,
6106
6127
  stack: error.stack,
6107
6128
  });
6108
6129
 
6130
+ if (this.pstnCorrelationId === pstnCorrelationId) {
6131
+ this.pstnCorrelationId = undefined;
6132
+ }
6133
+
6109
6134
  return Promise.reject(error);
6110
6135
  })
6111
6136
  );
@@ -6120,8 +6145,9 @@ export default class Meeting extends StatelessWebexPlugin {
6120
6145
  */
6121
6146
  private dialOutPstn(phoneNumber: string) {
6122
6147
  if (this.isPhoneProvisioned(this.dialOutDeviceStatus)) return Promise.resolve(); // prevent multiple dial out devices from being provisioned
6148
+ this.pstnCorrelationId = uuid.v4();
6123
6149
 
6124
- const {correlationId, locusUrl} = this;
6150
+ const {locusUrl, pstnCorrelationId} = this;
6125
6151
 
6126
6152
  if (!this.dialOutUrl) this.dialOutUrl = `dialout:///${uuid.v4()}`;
6127
6153
 
@@ -6129,7 +6155,7 @@ export default class Meeting extends StatelessWebexPlugin {
6129
6155
  this.meetingRequest
6130
6156
  // @ts-ignore
6131
6157
  .dialOut({
6132
- correlationId,
6158
+ correlationId: pstnCorrelationId,
6133
6159
  dialOutUrl: this.dialOutUrl,
6134
6160
  phoneNumber,
6135
6161
  locusUrl,
@@ -6139,12 +6165,17 @@ export default class Meeting extends StatelessWebexPlugin {
6139
6165
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
6140
6166
  correlation_id: this.correlationId,
6141
6167
  dial_out_url: this.dialOutUrl,
6168
+ dial_out_correlation_id: pstnCorrelationId,
6142
6169
  locus_id: locusUrl.split('/').pop(),
6143
6170
  client_url: this.deviceUrl,
6144
6171
  reason: error.error?.message,
6145
6172
  stack: error.stack,
6146
6173
  });
6147
6174
 
6175
+ if (this.pstnCorrelationId === pstnCorrelationId) {
6176
+ this.pstnCorrelationId = undefined;
6177
+ }
6178
+
6148
6179
  return Promise.reject(error);
6149
6180
  })
6150
6181
  );
@@ -6158,6 +6189,8 @@ export default class Meeting extends StatelessWebexPlugin {
6158
6189
  * @returns {Promise}
6159
6190
  */
6160
6191
  public disconnectPhoneAudio() {
6192
+ const correlationToClear = this.pstnCorrelationId;
6193
+
6161
6194
  return Promise.all([
6162
6195
  this.isPhoneProvisioned(this.dialInDeviceStatus)
6163
6196
  ? MeetingUtil.disconnectPhoneAudio(this, this.dialInUrl)
@@ -6165,7 +6198,11 @@ export default class Meeting extends StatelessWebexPlugin {
6165
6198
  this.isPhoneProvisioned(this.dialOutDeviceStatus)
6166
6199
  ? MeetingUtil.disconnectPhoneAudio(this, this.dialOutUrl)
6167
6200
  : Promise.resolve(),
6168
- ]);
6201
+ ]).then(() => {
6202
+ if (this.pstnCorrelationId === correlationToClear) {
6203
+ this.pstnCorrelationId = undefined;
6204
+ }
6205
+ });
6169
6206
  }
6170
6207
 
6171
6208
  /**
@@ -8387,6 +8424,10 @@ export default class Meeting extends StatelessWebexPlugin {
8387
8424
  }
8388
8425
 
8389
8426
  if (whiteboard) {
8427
+ // @ts-ignore
8428
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8429
+ key: 'internal.client.share.initiated',
8430
+ });
8390
8431
  // @ts-ignore
8391
8432
  this.webex.internal.newMetrics.submitClientEvent({
8392
8433
  name: 'client.share.initiated',
@@ -8446,11 +8487,17 @@ export default class Meeting extends StatelessWebexPlugin {
8446
8487
  const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
8447
8488
 
8448
8489
  if (whiteboard) {
8490
+ // @ts-ignore
8491
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8492
+ key: 'internal.client.share.stopped',
8493
+ });
8449
8494
  // @ts-ignore
8450
8495
  this.webex.internal.newMetrics.submitClientEvent({
8451
8496
  name: 'client.share.stopped',
8452
8497
  payload: {
8453
8498
  mediaType: 'whiteboard',
8499
+ // @ts-ignore
8500
+ shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
8454
8501
  },
8455
8502
  options: {
8456
8503
  meetingId: this.id,
@@ -8608,12 +8655,18 @@ export default class Meeting extends StatelessWebexPlugin {
8608
8655
  }
8609
8656
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
8610
8657
  if (content) {
8658
+ // @ts-ignore
8659
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8660
+ key: 'internal.client.share.stopped',
8661
+ });
8611
8662
  // @ts-ignore
8612
8663
  this.webex.internal.newMetrics.submitClientEvent({
8613
8664
  name: 'client.share.stopped',
8614
8665
  payload: {
8615
8666
  mediaType: 'share',
8616
8667
  shareInstanceId: this.localShareInstanceId,
8668
+ // @ts-ignore
8669
+ shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
8617
8670
  },
8618
8671
  options: {meetingId: this.id},
8619
8672
  });
@@ -9590,6 +9643,11 @@ export default class Meeting extends StatelessWebexPlugin {
9590
9643
  this.shareCAEventSentStatus.transmitStart = false;
9591
9644
  this.shareCAEventSentStatus.transmitStop = false;
9592
9645
 
9646
+ // @ts-ignore
9647
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
9648
+ key: 'internal.client.share.initiated',
9649
+ });
9650
+
9593
9651
  // @ts-ignore
9594
9652
  this.webex.internal.newMetrics.submitClientEvent({
9595
9653
  name: 'client.share.initiated',
@@ -9763,4 +9821,73 @@ export default class Meeting extends StatelessWebexPlugin {
9763
9821
  selected_subnet: selectedSubnetFirstOctet ? `${selectedSubnetFirstOctet}.X.X.X` : null,
9764
9822
  };
9765
9823
  }
9824
+
9825
+ /**
9826
+ * Set the stage for the meeting
9827
+ *
9828
+ * @param {SetStageOptions} options Options to use when setting the stage
9829
+ * @returns {Promise} The locus request
9830
+ */
9831
+ setStage({
9832
+ activeSpeakerProportion = 0.5,
9833
+ customBackground,
9834
+ customLogo,
9835
+ customNameLabel,
9836
+ importantParticipants,
9837
+ lockAttendeeViewOnStage = false,
9838
+ showActiveSpeaker = false,
9839
+ }: SetStageOptions = {}) {
9840
+ const videoLayout: SetStageVideoLayout = {
9841
+ overrideDefault: true,
9842
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
9843
+ stageParameters: {
9844
+ activeSpeakerProportion,
9845
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
9846
+ stageManagerType: 0,
9847
+ },
9848
+ };
9849
+
9850
+ if (importantParticipants?.length) {
9851
+ videoLayout.stageParameters.importantParticipants = importantParticipants.map(
9852
+ (importantParticipant, index) => ({...importantParticipant, order: index + 1})
9853
+ );
9854
+ }
9855
+
9856
+ if (customLogo) {
9857
+ if (!videoLayout.customLayouts) {
9858
+ videoLayout.customLayouts = {};
9859
+ }
9860
+ videoLayout.customLayouts.logo = customLogo;
9861
+ // eslint-disable-next-line no-bitwise
9862
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.LOGO;
9863
+ }
9864
+
9865
+ if (customBackground) {
9866
+ if (!videoLayout.customLayouts) {
9867
+ videoLayout.customLayouts = {};
9868
+ }
9869
+ videoLayout.customLayouts.background = customBackground;
9870
+ // eslint-disable-next-line no-bitwise
9871
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.BACKGROUND;
9872
+ }
9873
+
9874
+ if (customNameLabel) {
9875
+ videoLayout.nameLabelStyle = customNameLabel;
9876
+ // eslint-disable-next-line no-bitwise
9877
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.NAME_LABEL;
9878
+ }
9879
+
9880
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9881
+ }
9882
+
9883
+ /**
9884
+ * Unset the stage for the meeting
9885
+ *
9886
+ * @returns {Promise} The locus request
9887
+ */
9888
+ unsetStage() {
9889
+ const videoLayout: UnsetStageVideoLayout = {overrideDefault: false};
9890
+
9891
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9892
+ }
9766
9893
  }
@@ -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
+ };
@@ -197,6 +197,17 @@ const MeetingUtil = {
197
197
  });
198
198
 
199
199
  return parsed;
200
+ })
201
+ .catch((err) => {
202
+ webex.internal.newMetrics.submitClientEvent({
203
+ name: 'client.locus.join.response',
204
+ payload: {
205
+ identifiers: {meetingLookupUrl: meeting.meetingInfo?.meetingLookupUrl},
206
+ },
207
+ options: {meetingId: meeting.id, rawError: err},
208
+ });
209
+
210
+ throw err;
200
211
  });
201
212
  },
202
213
 
@@ -208,6 +219,10 @@ const MeetingUtil = {
208
219
  meeting.simultaneousInterpretation.cleanUp();
209
220
  meeting.locusMediaRequest = undefined;
210
221
 
222
+ meeting.webex?.internal?.newMetrics?.callDiagnosticMetrics?.clearEventLimitsForCorrelationId(
223
+ meeting.correlationId
224
+ );
225
+
211
226
  // make sure we send last metrics before we close the peerconnection
212
227
  const stopStatsAnalyzer = meeting.statsAnalyzer
213
228
  ? meeting.statsAnalyzer.stopAnalyzer()
@@ -328,10 +343,57 @@ const MeetingUtil = {
328
343
  meeting.resourceId = meeting.resourceId || options.resourceId;
329
344
 
330
345
  if (meeting.requiredCaptcha) {
331
- return Promise.reject(new CaptchaError());
346
+ const errorToThrow = new CaptchaError();
347
+
348
+ // @ts-ignore
349
+ webex.internal.newMetrics.submitClientEvent({
350
+ name: 'client.meetinginfo.response',
351
+ options: {
352
+ meetingId: meeting.id,
353
+ },
354
+ payload: {
355
+ errors: [
356
+ {
357
+ fatal: false,
358
+ category: 'expected',
359
+ name: 'other',
360
+ shownToUser: false,
361
+ errorCode: errorToThrow.code,
362
+ errorDescription: errorToThrow.name,
363
+ rawErrorMessage: errorToThrow.sdkMessage,
364
+ },
365
+ ],
366
+ },
367
+ });
368
+
369
+ return Promise.reject(errorToThrow);
332
370
  }
371
+
333
372
  if (meeting.passwordStatus === PASSWORD_STATUS.REQUIRED) {
334
- return Promise.reject(new PasswordError());
373
+ const errorToThrow = new PasswordError();
374
+
375
+ // @ts-ignore
376
+ webex.internal.newMetrics.submitClientEvent({
377
+ name: 'client.meetinginfo.response',
378
+ options: {
379
+ meetingId: meeting.id,
380
+ },
381
+ payload: {
382
+ errors: [
383
+ {
384
+ fatal: false,
385
+ category: 'expected',
386
+ name: 'other',
387
+ shownToUser: false,
388
+ errorCode: errorToThrow.code,
389
+ errorDescription: errorToThrow.name,
390
+ rawErrorMessage: errorToThrow.sdkMessage,
391
+ },
392
+ ],
393
+ },
394
+ });
395
+
396
+ return Promise.reject(errorToThrow);
335
397
  }
336
398
 
337
399
  if (options.pin) {
@@ -542,6 +604,15 @@ const MeetingUtil = {
542
604
  canStartManualCaption: (displayHints) =>
543
605
  displayHints.includes(DISPLAY_HINTS.MANUAL_CAPTION_START),
544
606
 
607
+ isLocalRecordingStarted: (displayHints) =>
608
+ displayHints.includes(DISPLAY_HINTS.LOCAL_RECORDING_STATUS_STARTED),
609
+
610
+ isLocalRecordingStopped: (displayHints) =>
611
+ displayHints.includes(DISPLAY_HINTS.LOCAL_RECORDING_STATUS_STOPPED),
612
+
613
+ isLocalRecordingPaused: (displayHints) =>
614
+ displayHints.includes(DISPLAY_HINTS.LOCAL_RECORDING_STATUS_PAUSED),
615
+
545
616
  canStopManualCaption: (displayHints) => displayHints.includes(DISPLAY_HINTS.MANUAL_CAPTION_STOP),
546
617
 
547
618
  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
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import {MEETINGS, _IN_LOBBY_, _NOT_IN_MEETING_, _IN_MEETING_, _OBSERVE_} from '../constants';
5
5
  import {IExternalRoles, IMediaStatus, Participant, ParticipantUrl} from './types';
6
+
6
7
  import MemberUtil from './util';
7
8
 
8
9
  export type MemberId = string;