@webex/plugin-meetings 3.8.1-next.3 → 3.8.1-next.31

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 (80) 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 +21 -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/index.js +169 -66
  15. package/dist/meeting/index.js.map +1 -1
  16. package/dist/meeting/request.js +19 -0
  17. package/dist/meeting/request.js.map +1 -1
  18. package/dist/meeting/request.type.js.map +1 -1
  19. package/dist/meetings/index.js +35 -33
  20. package/dist/meetings/index.js.map +1 -1
  21. package/dist/members/index.js +8 -6
  22. package/dist/members/index.js.map +1 -1
  23. package/dist/members/request.js +3 -3
  24. package/dist/members/request.js.map +1 -1
  25. package/dist/members/util.js +18 -6
  26. package/dist/members/util.js.map +1 -1
  27. package/dist/multistream/mediaRequestManager.js +1 -1
  28. package/dist/multistream/mediaRequestManager.js.map +1 -1
  29. package/dist/multistream/remoteMedia.js +34 -5
  30. package/dist/multistream/remoteMedia.js.map +1 -1
  31. package/dist/multistream/remoteMediaGroup.js +42 -2
  32. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  33. package/dist/multistream/sendSlotManager.js +32 -2
  34. package/dist/multistream/sendSlotManager.js.map +1 -1
  35. package/dist/reachability/index.js +5 -10
  36. package/dist/reachability/index.js.map +1 -1
  37. package/dist/types/constants.d.ts +19 -0
  38. package/dist/types/locus-info/index.d.ts +0 -9
  39. package/dist/types/meeting/brbState.d.ts +0 -1
  40. package/dist/types/meeting/index.d.ts +28 -4
  41. package/dist/types/meeting/request.d.ts +9 -1
  42. package/dist/types/meeting/request.type.d.ts +74 -0
  43. package/dist/types/members/index.d.ts +8 -3
  44. package/dist/types/members/request.d.ts +1 -1
  45. package/dist/types/members/util.d.ts +5 -2
  46. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  47. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  48. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  49. package/dist/types/reachability/index.d.ts +2 -2
  50. package/dist/webinar/index.js +1 -1
  51. package/package.json +24 -24
  52. package/src/constants.ts +20 -0
  53. package/src/locus-info/index.ts +47 -82
  54. package/src/media/index.ts +2 -2
  55. package/src/meeting/brbState.ts +9 -7
  56. package/src/meeting/index.ts +126 -23
  57. package/src/meeting/request.ts +16 -0
  58. package/src/meeting/request.type.ts +64 -0
  59. package/src/meetings/index.ts +3 -2
  60. package/src/members/index.ts +7 -5
  61. package/src/members/request.ts +2 -2
  62. package/src/members/util.ts +14 -3
  63. package/src/multistream/mediaRequestManager.ts +7 -7
  64. package/src/multistream/remoteMedia.ts +34 -4
  65. package/src/multistream/remoteMediaGroup.ts +37 -2
  66. package/src/multistream/sendSlotManager.ts +34 -2
  67. package/src/reachability/index.ts +5 -13
  68. package/test/unit/spec/locus-info/index.js +177 -83
  69. package/test/unit/spec/media/index.ts +107 -0
  70. package/test/unit/spec/meeting/brbState.ts +9 -9
  71. package/test/unit/spec/meeting/index.js +606 -55
  72. package/test/unit/spec/meeting/request.js +71 -0
  73. package/test/unit/spec/meetings/index.js +1 -0
  74. package/test/unit/spec/members/index.js +32 -9
  75. package/test/unit/spec/members/request.js +2 -2
  76. package/test/unit/spec/members/utils.js +27 -7
  77. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  78. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  79. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  80. package/test/unit/spec/reachability/index.ts +2 -6
@@ -48,7 +48,6 @@ export default class LocusInfo extends EventsScope {
48
48
  aclUrl: any;
49
49
  baseSequence: any;
50
50
  created: any;
51
- deltaParticipants: any;
52
51
  identities: any;
53
52
  membership: any;
54
53
  participants: any;
@@ -99,6 +98,7 @@ export default class LocusInfo extends EventsScope {
99
98
  private doLocusSync(meeting: any) {
100
99
  let isDelta;
101
100
  let url;
101
+ let meetingDestroyed = false;
102
102
 
103
103
  if (this.locusParser.workingCopy.syncUrl) {
104
104
  url = this.locusParser.workingCopy.syncUrl;
@@ -134,32 +134,56 @@ export default class LocusInfo extends EventsScope {
134
134
 
135
135
  isDelta = false;
136
136
 
137
- return meeting.meetingRequest.getLocusDTO({url: meeting.locusUrl}).catch((err) => {
138
- LoggerProxy.logger.info(
139
- 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
140
- );
141
- this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
142
- throw err;
143
- });
137
+ // Locus sometimes returns 403, for example if meeting has ended, no point trying the fallback to full sync in that case
138
+ if (e.statusCode !== 403) {
139
+ return meeting.meetingRequest.getLocusDTO({url: meeting.locusUrl}).catch((err) => {
140
+ LoggerProxy.logger.info(
141
+ 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
142
+ );
143
+ this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
144
+ meetingDestroyed = true;
145
+ throw err;
146
+ });
147
+ }
148
+ LoggerProxy.logger.info(
149
+ 'Locus-info:index#doLocusSync --> got 403 from Locus, skipping fallback to full sync, destroying the meeting'
150
+ );
151
+ } else {
152
+ LoggerProxy.logger.info(
153
+ 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
154
+ );
144
155
  }
145
- LoggerProxy.logger.info(
146
- 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
147
- );
148
156
  this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
157
+ meetingDestroyed = true;
149
158
  throw e;
150
159
  })
151
160
  .then((res) => {
152
- if (isDelta) {
153
- if (!isEmpty(res.body)) {
154
- meeting.locusInfo.handleLocusDelta(res.body, meeting);
155
- } else {
161
+ if (isEmpty(res.body)) {
162
+ if (isDelta) {
156
163
  LoggerProxy.logger.info(
157
164
  'Locus-info:index#doLocusSync --> received empty body from syncUrl, so we already have latest Locus DTO'
158
165
  );
166
+ } else {
167
+ LoggerProxy.logger.info(
168
+ 'Locus-info:index#doLocusSync --> received empty body from full DTO sync request'
169
+ );
159
170
  }
160
- } else {
161
- meeting.locusInfo.onFullLocus(res.body);
171
+
172
+ return;
173
+ }
174
+
175
+ if (isDelta) {
176
+ if (res.body.baseSequence) {
177
+ meeting.locusInfo.handleLocusDelta(res.body, meeting);
178
+
179
+ return;
180
+ }
181
+ // in some cases Locus might return us full DTO even when we asked for a delta
182
+ LoggerProxy.logger.info(
183
+ 'Locus-info:index#doLocusSync --> got full DTO when we asked for delta'
184
+ );
162
185
  }
186
+ meeting.locusInfo.onFullLocus(res.body);
163
187
  })
164
188
  .catch((e) => {
165
189
  LoggerProxy.logger.info(
@@ -176,9 +200,11 @@ export default class LocusInfo extends EventsScope {
176
200
  });
177
201
  })
178
202
  .finally(() => {
179
- // Notify parser to resume processing delta events.
180
- // Any deltas in the queue that have now been superseded by this sync will simply be ignored
181
- this.locusParser.resume();
203
+ if (!meetingDestroyed) {
204
+ // Notify parser to resume processing delta events.
205
+ // Any deltas in the queue that have now been superseded by this sync will simply be ignored
206
+ this.locusParser.resume();
207
+ }
182
208
  });
183
209
  }
184
210
 
@@ -257,17 +283,6 @@ export default class LocusInfo extends EventsScope {
257
283
  * @property {Object} person - Contains person data.
258
284
  */
259
285
 
260
- /**
261
- * Stored participant changes between the last event and the current event.
262
- * All previously stored events are overwritten between events.
263
- *
264
- * @instance
265
- * @type {Array<DeltaParticipant>}
266
- * @private
267
- * @member LocusInfo
268
- */
269
- this.deltaParticipants = [];
270
-
271
286
  this.updateLocusCache(locus);
272
287
  // above section only updates the locusInfo object
273
288
  // The below section makes sure it updates the locusInfo as well as updates the meeting object
@@ -373,7 +388,6 @@ export default class LocusInfo extends EventsScope {
373
388
  return;
374
389
  }
375
390
 
376
- this.updateParticipantDeltas(locus.participants);
377
391
  this.scheduledMeeting = locus.meeting || null;
378
392
  this.participants = locus.participants;
379
393
  const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
@@ -462,12 +476,12 @@ export default class LocusInfo extends EventsScope {
462
476
  this.updateCreated(locus.created);
463
477
  this.updateFullState(locus.fullState);
464
478
  this.updateHostInfo(locus.host);
479
+ this.updateLocusUrl(locus.url);
465
480
  this.updateMeetingInfo(locus.info, locus.self);
466
481
  this.updateMediaShares(locus.mediaShares);
467
482
  this.updateParticipantsUrl(locus.participantsUrl);
468
483
  this.updateReplace(locus.replace);
469
484
  this.updateSelf(locus.self);
470
- this.updateLocusUrl(locus.url);
471
485
  this.updateAclUrl(locus.aclUrl);
472
486
  this.updateBasequence(locus.baseSequence);
473
487
  this.updateSequence(locus.sequence);
@@ -728,55 +742,6 @@ export default class LocusInfo extends EventsScope {
728
742
  }
729
743
  }
730
744
 
731
- /**
732
- * Update the deltaParticipants property of this object based on a list of
733
- * provided participants.
734
- *
735
- * @param {Array} [participants] - The participants to update against.
736
- * @returns {void}
737
- */
738
- updateParticipantDeltas(participants: Array<any> = []) {
739
- // Used to find a participant within a participants collection.
740
- const findParticipant = (participant, collection) =>
741
- collection.find((item) => item.person.id === participant.person.id);
742
-
743
- // Generates an object that indicates which state properties have changed.
744
- const generateDelta = (prevState: any = {}, newState: any = {}) => {
745
- // Setup deltas.
746
- const deltas = {
747
- audioStatus: prevState.audioStatus !== newState.audioStatus,
748
- videoSlidesStatus: prevState.videoSlidesStatus !== newState.videoSlidesStatus,
749
- videoStatus: prevState.videoStatus !== newState.videoStatus,
750
- };
751
-
752
- // Clean the object
753
- Object.keys(deltas).forEach((key) => {
754
- if (deltas[key] !== true) {
755
- delete deltas[key];
756
- }
757
- });
758
-
759
- return deltas;
760
- };
761
-
762
- this.deltaParticipants = participants.reduce((collection, participant) => {
763
- const existingParticipant = findParticipant(participant, this.participants || []) || {};
764
-
765
- const delta = generateDelta(existingParticipant.status, participant.status);
766
-
767
- const changed = Object.keys(delta).length > 0;
768
-
769
- if (changed) {
770
- collection.push({
771
- person: participant.person,
772
- delta,
773
- });
774
- }
775
-
776
- return collection;
777
- }, []);
778
- }
779
-
780
745
  /**
781
746
  * update meeting's members
782
747
  * @param {Object} participants new participants object
@@ -239,8 +239,8 @@ Media.createMediaConnection = (
239
239
  screenShareAudio: shareAudioStream?.outputStream?.getTracks()[0], // TODO: add type for screenShareAudio in internal-media-core SPARK-446923
240
240
  } as unknown,
241
241
  direction: {
242
- audio: Media.getDirection(true, mediaDirection.receiveAudio, mediaDirection.sendAudio),
243
- video: Media.getDirection(true, mediaDirection.receiveVideo, mediaDirection.sendVideo),
242
+ audio: Media.getDirection(false, mediaDirection.receiveAudio, mediaDirection.sendAudio),
243
+ video: Media.getDirection(false, mediaDirection.receiveVideo, mediaDirection.sendVideo),
244
244
  screenShareVideo: Media.getDirection(
245
245
  false,
246
246
  mediaDirection.receiveShare,
@@ -58,7 +58,13 @@ export class BrbState {
58
58
  public enable(enabled: boolean, sendSlotManager: SendSlotManager) {
59
59
  this.state.client.enabled = enabled;
60
60
 
61
- return this.applyClientStateToServer(sendSlotManager);
61
+ // Don't set the source state override if enabling brb fails
62
+ return this.applyClientStateToServer(sendSlotManager).then(() => {
63
+ sendSlotManager.setSourceStateOverride(
64
+ MediaType.VideoMain,
65
+ this.state.client.enabled ? 'away' : null
66
+ );
67
+ });
62
68
  }
63
69
 
64
70
  /**
@@ -92,7 +98,7 @@ export class BrbState {
92
98
 
93
99
  this.state.syncToServerInProgress = true;
94
100
 
95
- return this.sendLocalBrbStateToServer(sendSlotManager)
101
+ return this.sendLocalBrbStateToServer()
96
102
  .then(() => {
97
103
  this.state.syncToServerInProgress = false;
98
104
 
@@ -120,10 +126,9 @@ export class BrbState {
120
126
  /**
121
127
  * Send the local brb state to the server
122
128
  *
123
- * @param {SendSlotManager} sendSlotManager
124
129
  * @returns {Promise}
125
130
  */
126
- private async sendLocalBrbStateToServer(sendSlotManager: SendSlotManager) {
131
+ private async sendLocalBrbStateToServer() {
127
132
  const {enabled} = this.state.client;
128
133
 
129
134
  if (!this.meeting.isMultistream) {
@@ -153,9 +158,6 @@ export class BrbState {
153
158
  deviceUrl: this.meeting.deviceUrl,
154
159
  selfId: this.meeting.selfId,
155
160
  })
156
- .then(() => {
157
- sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
158
- })
159
161
  .catch((error) => {
160
162
  LoggerProxy.logger.error('Meeting:brbState#sendLocalBrbStateToServer: Error ', error);
161
163
 
@@ -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,7 @@ 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';
167
169
 
168
170
  // default callback so we don't call an undefined function, but in practice it should never be used
169
171
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -231,6 +233,14 @@ export type AddMediaOptions = {
231
233
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
232
234
  bundlePolicy?: BundlePolicy; // applies only to multistream meetings
233
235
  allowMediaInLobby?: boolean; // allows adding media when in the lobby
236
+ additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
237
+ };
238
+
239
+ export type AdditionalMediaOptions = {
240
+ sendVideo?: boolean; // if not specified, default value of videoEnabled is used
241
+ receiveVideo?: boolean; // if not specified, default value of videoEnabled is used
242
+ sendAudio?: boolean; // if not specified, default value of audioEnabled true is used
243
+ receiveAudio?: boolean; // if not specified, default value of audioEnabled true is used
234
244
  };
235
245
 
236
246
  export type CallStateForMetrics = {
@@ -263,8 +273,9 @@ type FetchMeetingInfoParams = {
263
273
  };
264
274
 
265
275
  type MediaReachabilityMetrics = ReachabilityMetrics & {
266
- isSubnetReachable: boolean;
267
- selectedCluster: string | null;
276
+ subnet_reachable: boolean;
277
+ selected_cluster: string | null;
278
+ selected_subnet: string | null;
268
279
  };
269
280
 
270
281
  /**
@@ -732,10 +743,11 @@ export default class Meeting extends StatelessWebexPlugin {
732
743
  /**
733
744
  * @param {Object} attrs
734
745
  * @param {Object} options
746
+ * @param {Function} callback - if provided, it will be called with the newly created meeting object as soon as the meeting.id is set
735
747
  * @constructor
736
748
  * @memberof Meeting
737
749
  */
738
- constructor(attrs: any, options: object) {
750
+ constructor(attrs: any, options: object, callback: (meeting: Meeting) => void) {
739
751
  super({}, options);
740
752
  /**
741
753
  * @instance
@@ -761,6 +773,11 @@ export default class Meeting extends StatelessWebexPlugin {
761
773
  * @memberof Meeting
762
774
  */
763
775
  this.id = uuid.v4();
776
+
777
+ if (callback) {
778
+ callback(this);
779
+ }
780
+
764
781
  /**
765
782
  * Call state used for metrics
766
783
  * @instance
@@ -2757,9 +2774,11 @@ export default class Meeting extends StatelessWebexPlugin {
2757
2774
  LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
2758
2775
  ({spokenLanguage}) => {
2759
2776
  if (spokenLanguage) {
2760
- this.transcription.languageOptions.currentSpokenLanguage = spokenLanguage;
2777
+ if (this.transcription?.languageOptions) {
2778
+ this.transcription.languageOptions.currentSpokenLanguage = spokenLanguage;
2779
+ }
2761
2780
  // @ts-ignore
2762
- this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage);
2781
+ this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage, this.id);
2763
2782
 
2764
2783
  Trigger.trigger(
2765
2784
  this,
@@ -2768,7 +2787,7 @@ export default class Meeting extends StatelessWebexPlugin {
2768
2787
  function: 'setupLocusControlsListener',
2769
2788
  },
2770
2789
  EVENT_TRIGGERS.MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
2771
- {spokenLanguage}
2790
+ {spokenLanguage, meetingId: this.id}
2772
2791
  );
2773
2792
  }
2774
2793
  }
@@ -3850,15 +3869,16 @@ export default class Meeting extends StatelessWebexPlugin {
3850
3869
  }
3851
3870
 
3852
3871
  /**
3853
- * Cancel an SIP call invitation made during a meeting
3872
+ * Cancel an SIP/phone call invitation made during a meeting
3854
3873
  * @param {Object} invitee
3855
3874
  * @param {String} invitee.memberId
3856
- * @returns {Promise} see #members.cancelSIPInvite
3875
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
3876
+ * @returns {Promise} see #members.cancelInviteByMemberId
3857
3877
  * @public
3858
3878
  * @memberof Meeting
3859
3879
  */
3860
- public cancelSIPInvite(invitee: {memberId: string}) {
3861
- return this.members.cancelSIPInvite(invitee);
3880
+ public cancelInviteByMemberId(invitee: {memberId: string; isInternalNumber?: boolean}) {
3881
+ return this.members.cancelInviteByMemberId(invitee);
3862
3882
  }
3863
3883
 
3864
3884
  /**
@@ -7754,8 +7774,21 @@ export default class Meeting extends StatelessWebexPlugin {
7754
7774
  shareVideoEnabled = true,
7755
7775
  remoteMediaManagerConfig,
7756
7776
  bundlePolicy = 'max-bundle',
7777
+ additionalMediaOptions = {},
7757
7778
  } = options;
7758
7779
 
7780
+ const {
7781
+ sendVideo: rawSendVideo,
7782
+ receiveVideo: rawReceiveVideo,
7783
+ sendAudio: rawSendAudio,
7784
+ receiveAudio: rawReceiveAudio,
7785
+ } = additionalMediaOptions;
7786
+
7787
+ const sendVideo = videoEnabled && (rawSendVideo ?? true);
7788
+ const receiveVideo = videoEnabled && (rawReceiveVideo ?? true);
7789
+ const sendAudio = audioEnabled && (rawSendAudio ?? true);
7790
+ const receiveAudio = audioEnabled && (rawReceiveAudio ?? true);
7791
+
7759
7792
  this.allowMediaInLobby = options?.allowMediaInLobby;
7760
7793
 
7761
7794
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
@@ -7791,11 +7824,11 @@ export default class Meeting extends StatelessWebexPlugin {
7791
7824
  // when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any streams are published
7792
7825
  // to avoid doing an extra SDP exchange when they are published for the first time
7793
7826
  this.mediaProperties.setMediaDirection({
7794
- sendAudio: audioEnabled,
7795
- sendVideo: videoEnabled,
7827
+ sendAudio,
7828
+ sendVideo,
7796
7829
  sendShare: false,
7797
- receiveAudio: audioEnabled,
7798
- receiveVideo: videoEnabled,
7830
+ receiveAudio,
7831
+ receiveVideo,
7799
7832
  receiveShare: shareAudioEnabled || shareVideoEnabled,
7800
7833
  });
7801
7834
 
@@ -9721,21 +9754,91 @@ export default class Meeting extends StatelessWebexPlugin {
9721
9754
  return total;
9722
9755
  }, 0);
9723
9756
 
9757
+ const selectedSubnetFirstOctet = this.mediaServerIp?.split('.')[0];
9758
+
9724
9759
  let isSubnetReachable = null;
9725
- if (totalSuccessCases > 0) {
9726
- // @ts-ignore
9727
- isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(this.mediaServerIp);
9760
+ if (totalSuccessCases > 0 && selectedSubnetFirstOctet) {
9761
+ isSubnetReachable =
9762
+ // @ts-ignore
9763
+ this.webex.meetings.reachability.isSubnetReachable(selectedSubnetFirstOctet);
9728
9764
  }
9729
9765
 
9730
- let selectedCluster = null;
9731
- if (this.mediaConnections && this.mediaConnections.length > 0) {
9732
- selectedCluster = this.mediaConnections[0].mediaAgentCluster;
9733
- }
9766
+ const selectedCluster = this.mediaConnections?.[0]?.mediaAgentCluster ?? null;
9734
9767
 
9735
9768
  return {
9736
9769
  ...reachabilityMetrics,
9737
- isSubnetReachable,
9738
- selectedCluster,
9770
+ subnet_reachable: isSubnetReachable,
9771
+ selected_cluster: selectedCluster,
9772
+ selected_subnet: selectedSubnetFirstOctet ? `${selectedSubnetFirstOctet}.X.X.X` : null,
9773
+ };
9774
+ }
9775
+
9776
+ /**
9777
+ * Set the stage for the meeting
9778
+ *
9779
+ * @param {SetStageOptions} options Options to use when setting the stage
9780
+ * @returns {Promise} The locus request
9781
+ */
9782
+ setStage({
9783
+ activeSpeakerProportion = 0.5,
9784
+ customBackground,
9785
+ customLogo,
9786
+ customNameLabel,
9787
+ importantParticipants,
9788
+ lockAttendeeViewOnStage = false,
9789
+ showActiveSpeaker = false,
9790
+ }: SetStageOptions = {}) {
9791
+ const videoLayout: SetStageVideoLayout = {
9792
+ overrideDefault: true,
9793
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
9794
+ stageParameters: {
9795
+ activeSpeakerProportion,
9796
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
9797
+ stageManagerType: 0,
9798
+ },
9739
9799
  };
9800
+
9801
+ if (importantParticipants?.length) {
9802
+ videoLayout.stageParameters.importantParticipants = importantParticipants.map(
9803
+ (importantParticipant, index) => ({...importantParticipant, order: index + 1})
9804
+ );
9805
+ }
9806
+
9807
+ if (customLogo) {
9808
+ if (!videoLayout.customLayouts) {
9809
+ videoLayout.customLayouts = {};
9810
+ }
9811
+ videoLayout.customLayouts.logo = customLogo;
9812
+ // eslint-disable-next-line no-bitwise
9813
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.LOGO;
9814
+ }
9815
+
9816
+ if (customBackground) {
9817
+ if (!videoLayout.customLayouts) {
9818
+ videoLayout.customLayouts = {};
9819
+ }
9820
+ videoLayout.customLayouts.background = customBackground;
9821
+ // eslint-disable-next-line no-bitwise
9822
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.BACKGROUND;
9823
+ }
9824
+
9825
+ if (customNameLabel) {
9826
+ videoLayout.nameLabelStyle = customNameLabel;
9827
+ // eslint-disable-next-line no-bitwise
9828
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.NAME_LABEL;
9829
+ }
9830
+
9831
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9832
+ }
9833
+
9834
+ /**
9835
+ * Unset the stage for the meeting
9836
+ *
9837
+ * @returns {Promise} The locus request
9838
+ */
9839
+ unsetStage() {
9840
+ const videoLayout: UnsetStageVideoLayout = {overrideDefault: false};
9841
+
9842
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9740
9843
  }
9741
9844
  }
@@ -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;
@@ -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
@@ -846,12 +846,14 @@ export default class Members extends StatelessWebexPlugin {
846
846
  }
847
847
 
848
848
  /**
849
- * Cancels an SIP call to the associated meeting
850
- * @param {String} invitee
849
+ * Cancels an SIP/phone call to the associated meeting
850
+ * @param {Object} invitee
851
+ * @param {String} invitee.memberId - The memberId of the invitee
852
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
851
853
  * @returns {Promise}
852
854
  * @memberof Members
853
855
  */
854
- cancelSIPInvite(invitee: any) {
856
+ cancelInviteByMemberId(invitee: {memberId: string; isInternalNumber?: boolean}) {
855
857
  if (!this.locusUrl) {
856
858
  return Promise.reject(
857
859
  new ParameterError('The associated locus url for this meeting object must be defined.')
@@ -862,9 +864,9 @@ export default class Members extends StatelessWebexPlugin {
862
864
  new ParameterError('The invitee must be defined with a memberId property.')
863
865
  );
864
866
  }
865
- const options = MembersUtil.cancelSIPInviteOptions(invitee, this.locusUrl);
867
+ const options = MembersUtil.cancelInviteByMemberIdOptions(invitee, this.locusUrl);
866
868
 
867
- return this.membersRequest.cancelSIPInvite(options);
869
+ return this.membersRequest.cancelInviteByMemberId(options);
868
870
  }
869
871
 
870
872
  /**
@@ -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
  }