@webex/plugin-meetings 3.9.0-next.23 → 3.9.0-next.25

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 (52) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +4 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/controls-options-manager/index.js +22 -5
  6. package/dist/controls-options-manager/index.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/locus-info/controlsUtils.js +11 -2
  10. package/dist/locus-info/controlsUtils.js.map +1 -1
  11. package/dist/locus-info/index.js +17 -3
  12. package/dist/locus-info/index.js.map +1 -1
  13. package/dist/meeting/in-meeting-actions.js +2 -0
  14. package/dist/meeting/in-meeting-actions.js.map +1 -1
  15. package/dist/meeting/index.js +74 -32
  16. package/dist/meeting/index.js.map +1 -1
  17. package/dist/meeting/request.js +48 -14
  18. package/dist/meeting/request.js.map +1 -1
  19. package/dist/meeting/util.js +3 -0
  20. package/dist/meeting/util.js.map +1 -1
  21. package/dist/member/index.js +9 -0
  22. package/dist/member/index.js.map +1 -1
  23. package/dist/member/util.js +10 -0
  24. package/dist/member/util.js.map +1 -1
  25. package/dist/types/constants.d.ts +3 -0
  26. package/dist/types/controls-options-manager/index.d.ts +9 -1
  27. package/dist/types/locus-info/index.d.ts +2 -1
  28. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  29. package/dist/types/meeting/index.d.ts +14 -0
  30. package/dist/types/meeting/request.d.ts +16 -0
  31. package/dist/types/meeting/util.d.ts +1 -0
  32. package/dist/types/member/index.d.ts +1 -0
  33. package/dist/types/member/util.d.ts +5 -0
  34. package/dist/webinar/index.js +1 -1
  35. package/package.json +1 -1
  36. package/src/constants.ts +6 -0
  37. package/src/controls-options-manager/index.ts +26 -5
  38. package/src/locus-info/controlsUtils.ts +18 -0
  39. package/src/locus-info/index.ts +14 -4
  40. package/src/meeting/in-meeting-actions.ts +4 -0
  41. package/src/meeting/index.ts +67 -20
  42. package/src/meeting/request.ts +38 -0
  43. package/src/meeting/util.ts +3 -0
  44. package/src/member/index.ts +10 -0
  45. package/src/member/util.ts +14 -0
  46. package/test/unit/spec/controls-options-manager/index.js +47 -0
  47. package/test/unit/spec/fixture/locus.js +1 -0
  48. package/test/unit/spec/locus-info/index.js +39 -0
  49. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  50. package/test/unit/spec/meeting/index.js +41 -2
  51. package/test/unit/spec/meeting/utils.js +1 -0
  52. package/test/unit/spec/member/util.js +24 -0
@@ -886,6 +886,44 @@ export default class MeetingRequest extends StatelessWebexPlugin {
886
886
  });
887
887
  }
888
888
 
889
+ /**
890
+ * Extend the current meeting duration.
891
+ *
892
+ * @param {Object} params - Parameters for extending the meeting.
893
+ * @param {string} params.meetingInstanceId - The unique ID of the meeting instance.
894
+ * @param {string} params.participantId - The ID of the participant requesting the extension.
895
+ * @param {number} params.extensionMinutes - The number of minutes to extend the meeting by.
896
+ * @param {string} params.meetingPolicyUrl - The base URL for meeting policy service (dynamic, from locus links)
897
+ * @returns {Promise<any>} A promise that resolves with the server response.
898
+ */
899
+ extendMeeting({
900
+ meetingInstanceId,
901
+ participantId,
902
+ extensionMinutes,
903
+ meetingPolicyUrl,
904
+ }: {
905
+ meetingInstanceId: string;
906
+ participantId: string;
907
+ extensionMinutes: number;
908
+ meetingPolicyUrl: string;
909
+ }) {
910
+ if (!meetingPolicyUrl) {
911
+ return Promise.reject(new Error('meetingPolicyUrl is required'));
912
+ }
913
+ const uri = `${meetingPolicyUrl}/continueMeeting`;
914
+
915
+ // @ts-ignore
916
+ return this.request({
917
+ method: HTTP_VERBS.POST,
918
+ uri,
919
+ body: {
920
+ meetingInstanceId,
921
+ requestParticipantId: participantId,
922
+ extensionMinutes,
923
+ },
924
+ });
925
+ }
926
+
889
927
  /**
890
928
  * Make a network request to enable or disable reactions.
891
929
  * @param {boolean} options.enable - determines if we need to enable or disable.
@@ -654,6 +654,9 @@ const MeetingUtil = {
654
654
 
655
655
  waitingForOthersToJoin: (displayHints) => displayHints.includes(DISPLAY_HINTS.WAITING_FOR_OTHERS),
656
656
 
657
+ showAutoEndMeetingWarning: (displayHints) =>
658
+ displayHints.includes(DISPLAY_HINTS.SHOW_AUTO_END_MEETING_WARNING),
659
+
657
660
  canSendReactions: (originalValue, displayHints) => {
658
661
  if (displayHints.includes(DISPLAY_HINTS.REACTIONS_ACTIVE)) {
659
662
  return true;
@@ -42,6 +42,7 @@ export default class Member {
42
42
  status: any;
43
43
  supportsBreakouts: boolean;
44
44
  supportsInterpretation: boolean;
45
+ supportsSingleUserAutoEndMeeting: boolean;
45
46
  supportLiveAnnotation: boolean;
46
47
  type: any;
47
48
  namespace = MEETINGS;
@@ -130,6 +131,13 @@ export default class Member {
130
131
  * @memberof Member
131
132
  */
132
133
  this.supportsBreakouts = null;
134
+ /**
135
+ * @instance
136
+ * @type {Boolean}
137
+ * @public
138
+ * @memberof Member
139
+ */
140
+ this.supportsSingleUserAutoEndMeeting = null;
133
141
  /**
134
142
  * @instance
135
143
  * @type {Boolean}
@@ -339,6 +347,8 @@ export default class Member {
339
347
  this.isVideoMuted = MemberUtil.isVideoMuted(participant);
340
348
  this.isHandRaised = MemberUtil.isHandRaised(participant);
341
349
  this.supportsBreakouts = MemberUtil.isBreakoutsSupported(participant);
350
+ this.supportsSingleUserAutoEndMeeting =
351
+ MemberUtil.isSupportsSingleUserAutoEndMeeting(participant);
342
352
  this.supportsInterpretation = MemberUtil.isInterpretationSupported(participant);
343
353
  this.supportLiveAnnotation = MemberUtil.isLiveAnnotationSupported(participant);
344
354
  this.isGuest = MemberUtil.isGuest(participant);
@@ -207,6 +207,20 @@ const MemberUtil = {
207
207
  return !participant.doesNotSupportBreakouts;
208
208
  },
209
209
 
210
+ /**
211
+ * @param {Object} participant - The locus participant object.
212
+ * @returns {Boolean}
213
+ */
214
+ isSupportsSingleUserAutoEndMeeting: (participant) => {
215
+ if (!participant) {
216
+ throw new ParameterError(
217
+ 'Single user auto end meeting support could not be processed, participant is undefined.'
218
+ );
219
+ }
220
+
221
+ return !participant.doesNotSupportSingleUserAutoEndMeeting;
222
+ },
223
+
210
224
  /**
211
225
  * @param {Object} participant - The locus participant object.
212
226
  * @returns {Boolean}
@@ -133,6 +133,7 @@ describe('plugin-meetings', () => {
133
133
 
134
134
  manager.set({
135
135
  locusUrl: 'test/id',
136
+ mainLocusUrl: '',
136
137
  displayHints: [],
137
138
  });
138
139
  });
@@ -201,6 +202,38 @@ describe('plugin-meetings', () => {
201
202
  Util.canUpdate = restorable;
202
203
  });
203
204
  });
205
+
206
+ it('should call request with mainLocusUrl and locusUrl as authorizingLocusUrl if mainLocusUrl is exist and not same with locusUrl', () => {
207
+ const restorable = Util.canUpdate;
208
+ Util.canUpdate = sinon.stub().returns(true);
209
+ manager.mainLocusUrl = 'test/main';
210
+
211
+ const audio = {scope: 'audio', properties: {a: 1, b: 2}};
212
+ const reactions = {scope: 'reactions', properties: {c: 3, d: 4}};
213
+
214
+ return manager.update(audio, reactions)
215
+ .then(() => {
216
+ assert.calledWith(request.request, {
217
+ uri: 'test/main/controls',
218
+ body: {
219
+ audio: audio.properties,
220
+ authorizingLocusUrl: 'test/id'
221
+ },
222
+ method: HTTP_VERBS.PATCH,
223
+ });
224
+
225
+ assert.calledWith(request.request, {
226
+ uri: 'test/main/controls',
227
+ body: {
228
+ reactions: reactions.properties,
229
+ authorizingLocusUrl: 'test/id'
230
+ },
231
+ method: HTTP_VERBS.PATCH,
232
+ });
233
+
234
+ Util.canUpdate = restorable;
235
+ });
236
+ });
204
237
  });
205
238
 
206
239
  describe('Mute/Unmute All', () => {
@@ -214,6 +247,7 @@ describe('plugin-meetings', () => {
214
247
 
215
248
  manager.set({
216
249
  locusUrl: 'test/id',
250
+ mainLocusUrl: '',
217
251
  displayHints: [],
218
252
  })
219
253
  });
@@ -305,6 +339,19 @@ describe('plugin-meetings', () => {
305
339
 
306
340
  assert.deepEqual(result, request.request.firstCall.returnValue);
307
341
  });
342
+
343
+ it('request with mainLocusUrl and make locusUrl as authorizingLocusUrl if mainLocusUrl is exist and not same with locusUrl', () => {
344
+ manager.setDisplayHints(['MUTE_ALL', 'DISABLE_HARD_MUTE', 'DISABLE_MUTE_ON_ENTRY']);
345
+ manager.mainLocusUrl = `test/main`;
346
+
347
+ const result = manager.setMuteAll(true, true, true, ['attendee']);
348
+
349
+ assert.calledWith(request.request, { uri: 'test/main/controls',
350
+ body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] }, authorizingLocusUrl: 'test/id' },
351
+ method: HTTP_VERBS.PATCH});
352
+
353
+ assert.deepEqual(result, request.request.firstCall.returnValue);
354
+ });
308
355
  });
309
356
  });
310
357
  });
@@ -36,6 +36,7 @@ export default {
36
36
  'LOCK_STATUS_UNLOCKED',
37
37
  'ROSTER_IN_MEETING',
38
38
  'SHARE_WHITEBOARD',
39
+ 'SHOW_AUTO_END_MEETING_WARNING',
39
40
  'SHARE_WHITEBOARD_POLICY',
40
41
  'WEBEX_ASSISTANT_STATUS_INACTIVE',
41
42
  'CAPTION_START',
@@ -3020,6 +3020,45 @@ describe('plugin-meetings', () => {
3020
3020
  });
3021
3021
  });
3022
3022
 
3023
+ describe('#updateLocusUrl', () => {
3024
+ it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is true as default', () => {
3025
+ const fakeUrl = "https://fake.com/locus";
3026
+ locusInfo.emitScoped = sinon.stub();
3027
+ locusInfo.updateLocusUrl(fakeUrl);
3028
+
3029
+ assert.calledWith(
3030
+ locusInfo.emitScoped,
3031
+ {
3032
+ file: 'locus-info',
3033
+ function: 'updateLocusUrl',
3034
+ },
3035
+ EVENTS.LOCUS_INFO_UPDATE_URL,
3036
+ {
3037
+ url: fakeUrl,
3038
+ isMainLocus: true
3039
+ },
3040
+ );
3041
+ });
3042
+ it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is false', () => {
3043
+ const fakeUrl = "https://fake.com/locus";
3044
+ locusInfo.emitScoped = sinon.stub();
3045
+ locusInfo.updateLocusUrl(fakeUrl, false);
3046
+
3047
+ assert.calledWith(
3048
+ locusInfo.emitScoped,
3049
+ {
3050
+ file: 'locus-info',
3051
+ function: 'updateLocusUrl',
3052
+ },
3053
+ EVENTS.LOCUS_INFO_UPDATE_URL,
3054
+ {
3055
+ url: fakeUrl,
3056
+ isMainLocus: false
3057
+ },
3058
+ );
3059
+ });
3060
+ });
3061
+
3023
3062
  // semi-integration tests that use real LocusInfo with real Parser
3024
3063
  // and test various scenarios related to handling out-of-order Locus delta events
3025
3064
  describe('handling of out-of-order Locus delta events', () => {
@@ -86,6 +86,7 @@ describe('plugin-meetings', () => {
86
86
  canDoVideo: null,
87
87
  canAnnotate: null,
88
88
  canUseVoip: null,
89
+ showAutoEndMeetingWarning: null,
89
90
  supportHQV: null,
90
91
  supportHDV: null,
91
92
  canShareWhiteBoard: null,
@@ -197,6 +198,7 @@ describe('plugin-meetings', () => {
197
198
  'canRealtimeCloseCaption',
198
199
  'canRealtimeCloseCaptionManual',
199
200
  'canChat',
201
+ 'showAutoEndMeetingWarning',
200
202
  'canDoVideo',
201
203
  'canAnnotate',
202
204
  'canUseVoip',
@@ -10471,6 +10471,24 @@ describe('plugin-meetings', () => {
10471
10471
  );
10472
10472
  });
10473
10473
 
10474
+ it('listens to CONTROLS_AUTO_END_MEETING_WARNING_CHANGED', async () => {
10475
+ const state = {example: 'value'};
10476
+
10477
+ await meeting.locusInfo.emitScoped(
10478
+ {function: 'test', file: 'test'},
10479
+ LOCUSINFO.EVENTS.CONTROLS_AUTO_END_MEETING_WARNING_CHANGED,
10480
+ {state}
10481
+ );
10482
+
10483
+ assert.calledWith(
10484
+ TriggerProxy.trigger,
10485
+ meeting,
10486
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
10487
+ EVENT_TRIGGERS.MEETING_CONTROLS_AUTO_END_MEETING_WARNING_UPDATED,
10488
+ {state}
10489
+ );
10490
+ });
10491
+
10474
10492
  it('listens to CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED', async () => {
10475
10493
  const state = {example: 'value'};
10476
10494
 
@@ -10550,6 +10568,7 @@ describe('plugin-meetings', () => {
10550
10568
  describe('#setUpLocusUrlListener', () => {
10551
10569
  it('listens to the locus url update event', (done) => {
10552
10570
  const newLocusUrl = 'newLocusUrl/12345';
10571
+ const payload = {url: newLocusUrl}
10553
10572
 
10554
10573
  meeting.members = {locusUrlUpdate: sinon.stub().returns(Promise.resolve(test1))};
10555
10574
  meeting.recordingController = {setLocusUrl: sinon.stub().returns(undefined)};
@@ -10563,14 +10582,14 @@ describe('plugin-meetings', () => {
10563
10582
  meeting.locusInfo.emit(
10564
10583
  {function: 'test', file: 'test'},
10565
10584
  'LOCUS_INFO_UPDATE_URL',
10566
- newLocusUrl
10585
+ payload
10567
10586
  );
10568
10587
  assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
10569
10588
  assert.calledOnceWithExactly(meeting.breakouts.locusUrlUpdate, newLocusUrl);
10570
10589
  assert.calledOnceWithExactly(meeting.annotation.locusUrlUpdate, newLocusUrl);
10571
10590
  assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
10572
10591
  assert.calledWith(meeting.recordingController.setLocusUrl, newLocusUrl);
10573
- assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl);
10592
+ assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl, false);
10574
10593
  assert.calledWith(meeting.simultaneousInterpretation.locusUrlUpdate, newLocusUrl);
10575
10594
  assert.calledWith(meeting.webinar.locusUrlUpdate, newLocusUrl);
10576
10595
  assert.equal(meeting.locusUrl, newLocusUrl);
@@ -10588,6 +10607,22 @@ describe('plugin-meetings', () => {
10588
10607
  {locusUrl: 'newLocusUrl/12345'}
10589
10608
  );
10590
10609
 
10610
+ done();
10611
+ });
10612
+ it('update mainLocusUrl for controlsOptionManager if payload.isMainLocus as true', (done) => {
10613
+ const newLocusUrl = 'newLocusUrl/12345';
10614
+ const payload = {url: newLocusUrl, isMainLocus: true}
10615
+
10616
+ meeting.controlsOptionsManager = {setLocusUrl: sinon.stub().returns(undefined)};
10617
+
10618
+ meeting.locusInfo.emit(
10619
+ {function: 'test', file: 'test'},
10620
+ 'LOCUS_INFO_UPDATE_URL',
10621
+ payload
10622
+ );
10623
+
10624
+ assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl, true);
10625
+
10591
10626
  done();
10592
10627
  });
10593
10628
  });
@@ -11426,6 +11461,7 @@ describe('plugin-meetings', () => {
11426
11461
  let canShareWhiteBoardSpy;
11427
11462
  let canMoveToLobbySpy;
11428
11463
  let isSpokenLanguageAutoDetectionEnabledSpy;
11464
+ let showAutoEndMeetingWarningSpy;
11429
11465
  // Due to import tree issues, hasHints must be stubed within the scope of the `it`.
11430
11466
 
11431
11467
  beforeEach(() => {
@@ -11457,6 +11493,7 @@ describe('plugin-meetings', () => {
11457
11493
  canUserRenameOthersSpy = sinon.spy(MeetingUtil, 'canUserRenameOthers');
11458
11494
  canShareWhiteBoardSpy = sinon.spy(MeetingUtil, 'canShareWhiteBoard');
11459
11495
  canMoveToLobbySpy = sinon.spy(MeetingUtil, 'canMoveToLobby');
11496
+ showAutoEndMeetingWarningSpy = sinon.spy(MeetingUtil, 'showAutoEndMeetingWarning');
11460
11497
  isSpokenLanguageAutoDetectionEnabledSpy = sinon.spy(MeetingUtil, 'isSpokenLanguageAutoDetectionEnabled');
11461
11498
 
11462
11499
  });
@@ -11464,6 +11501,7 @@ describe('plugin-meetings', () => {
11464
11501
  afterEach(() => {
11465
11502
  inMeetingActionsSetSpy.restore();
11466
11503
  waitingForOthersToJoinSpy.restore();
11504
+ showAutoEndMeetingWarningSpy.restore();
11467
11505
  });
11468
11506
 
11469
11507
  forEach(
@@ -12011,6 +12049,7 @@ describe('plugin-meetings', () => {
12011
12049
  assert.calledWith(canUserRenameOthersSpy, userDisplayHints);
12012
12050
  assert.calledWith(canShareWhiteBoardSpy, userDisplayHints, selfUserPolicies);
12013
12051
  assert.calledWith(canMoveToLobbySpy, userDisplayHints);
12052
+ assert.calledWith(showAutoEndMeetingWarningSpy, userDisplayHints);
12014
12053
  assert.calledWith(isSpokenLanguageAutoDetectionEnabledSpy, userDisplayHints);
12015
12054
 
12016
12055
  assert.calledWith(ControlsOptionsUtil.hasHints, {
@@ -985,6 +985,7 @@ describe('plugin-meetings', () => {
985
985
  {functionName: 'isRealTimeTranslationEnabled', displayHint: 'DISPLAY_REAL_TIME_TRANSLATION'},
986
986
  {functionName: 'canSelectSpokenLanguages', displayHint: 'DISPLAY_NON_ENGLISH_ASR'},
987
987
  {functionName: 'waitingForOthersToJoin', displayHint: 'WAITING_FOR_OTHERS'},
988
+ {functionName: 'showAutoEndMeetingWarning', displayHint: 'SHOW_AUTO_END_MEETING_WARNING'},
988
989
  ].forEach(({functionName, displayHint}) => {
989
990
  describe(functionName, () => {
990
991
  it('works as expected', () => {
@@ -417,6 +417,30 @@ describe('plugin-meetings', () => {
417
417
  });
418
418
  });
419
419
 
420
+ describe('MemberUtil.isSupportsSingleUserAutoEndMeeting', () => {
421
+ it('throws an error when there is no participant', () => {
422
+ assert.throws(() => {
423
+ MemberUtil.isSupportsSingleUserAutoEndMeeting();
424
+ }, 'Single user auto end meeting support could not be processed, participant is undefined.');
425
+ });
426
+
427
+ it('returns true when single user auto end meeting is supported', () => {
428
+ const participant = {
429
+ supportsSingleUserAutoEndMeeting: {},
430
+ };
431
+ assert.isTrue(MemberUtil.isSupportsSingleUserAutoEndMeeting(participant));
432
+ });
433
+
434
+ it('returns false when single user auto end meeting is not supported', () => {
435
+ const participant = {
436
+ doesNotSupportSingleUserAutoEndMeeting: {},
437
+ };
438
+
439
+ assert.isFalse(MemberUtil.isSupportsSingleUserAutoEndMeeting(participant));
440
+ });
441
+ });
442
+
443
+
420
444
  describe('MemberUtil.isLiveAnnotationSupported', () => {
421
445
  it('throws an error when there is no participant', () => {
422
446
  assert.throws(() => {