@webex/plugin-meetings 3.0.0-beta.42 → 3.0.0-beta.44

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 (62) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +10 -2
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/constants.js +9 -2
  5. package/dist/constants.js.map +1 -1
  6. package/dist/controls-options-manager/enums.js +1 -0
  7. package/dist/controls-options-manager/enums.js.map +1 -1
  8. package/dist/controls-options-manager/index.js +51 -14
  9. package/dist/controls-options-manager/index.js.map +1 -1
  10. package/dist/controls-options-manager/util.js +12 -1
  11. package/dist/controls-options-manager/util.js.map +1 -1
  12. package/dist/locus-info/controlsUtils.js +6 -2
  13. package/dist/locus-info/controlsUtils.js.map +1 -1
  14. package/dist/locus-info/index.js +35 -1
  15. package/dist/locus-info/index.js.map +1 -1
  16. package/dist/locus-info/selfUtils.js +28 -0
  17. package/dist/locus-info/selfUtils.js.map +1 -1
  18. package/dist/meeting/in-meeting-actions.js +4 -0
  19. package/dist/meeting/in-meeting-actions.js.map +1 -1
  20. package/dist/meeting/index.js +55 -3
  21. package/dist/meeting/index.js.map +1 -1
  22. package/dist/meeting/muteState.js +45 -20
  23. package/dist/meeting/muteState.js.map +1 -1
  24. package/dist/members/index.js +15 -3
  25. package/dist/members/index.js.map +1 -1
  26. package/dist/members/util.js +33 -12
  27. package/dist/members/util.js.map +1 -1
  28. package/dist/types/constants.d.ts +7 -0
  29. package/dist/types/controls-options-manager/enums.d.ts +2 -1
  30. package/dist/types/controls-options-manager/index.d.ts +9 -1
  31. package/dist/types/controls-options-manager/util.d.ts +2 -0
  32. package/dist/types/locus-info/index.d.ts +7 -0
  33. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  34. package/dist/types/meeting/index.d.ts +22 -2
  35. package/dist/types/meeting/muteState.d.ts +16 -0
  36. package/dist/types/members/index.d.ts +7 -2
  37. package/package.json +18 -18
  38. package/src/breakouts/index.ts +6 -0
  39. package/src/constants.ts +7 -0
  40. package/src/controls-options-manager/enums.ts +1 -0
  41. package/src/controls-options-manager/index.ts +55 -20
  42. package/src/controls-options-manager/util.ts +10 -0
  43. package/src/locus-info/controlsUtils.ts +8 -0
  44. package/src/locus-info/index.ts +42 -1
  45. package/src/locus-info/selfUtils.ts +34 -0
  46. package/src/meeting/in-meeting-actions.ts +8 -0
  47. package/src/meeting/index.ts +68 -4
  48. package/src/meeting/muteState.ts +49 -30
  49. package/src/members/index.ts +12 -4
  50. package/src/members/util.ts +21 -8
  51. package/test/unit/spec/breakouts/index.ts +2 -2
  52. package/test/unit/spec/controls-options-manager/index.js +56 -0
  53. package/test/unit/spec/controls-options-manager/util.js +20 -0
  54. package/test/unit/spec/locus-info/controlsUtils.js +104 -46
  55. package/test/unit/spec/locus-info/index.js +131 -16
  56. package/test/unit/spec/locus-info/selfConstant.js +9 -5
  57. package/test/unit/spec/locus-info/selfUtils.js +39 -16
  58. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  59. package/test/unit/spec/meeting/index.js +208 -79
  60. package/test/unit/spec/meeting/muteState.js +72 -6
  61. package/test/unit/spec/members/index.js +75 -0
  62. package/test/unit/spec/members/utils.js +112 -0
@@ -32,6 +32,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
32
32
  const pstnDevices = self.devices.filter((device) => PSTN_DEVICE_TYPE === device.deviceType);
33
33
 
34
34
  return {
35
+ remoteVideoMuted: SelfUtils.getRemoteVideoMuted(self),
35
36
  remoteMuted: SelfUtils.getRemoteMuted(self),
36
37
  unmuteAllowed: SelfUtils.getUnmuteAllowed(self),
37
38
  localAudioUnmuteRequested: SelfUtils.getLocalAudioUnmuteRequested(self),
@@ -93,6 +94,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
93
94
 
94
95
  updates.isUserUnadmitted = SelfUtils.isUserUnadmitted(current);
95
96
  updates.isUserAdmitted = SelfUtils.isUserAdmitted(previous, current);
97
+ updates.isVideoMutedByOthersChanged = SelfUtils.videoMutedByOthersChanged(previous, current);
96
98
  updates.isMutedByOthersChanged = SelfUtils.mutedByOthersChanged(previous, current);
97
99
  updates.localAudioUnmuteRequestedByServer = SelfUtils.localAudioUnmuteRequestedByServer(
98
100
  previous,
@@ -236,6 +238,19 @@ SelfUtils.getSelfIdentity = (self: any) => {
236
238
  return self.person.id;
237
239
  };
238
240
 
241
+ /**
242
+ * get the "remote video mute" property from the self object
243
+ * @param {Object} self
244
+ * @returns {Boolean}
245
+ */
246
+ SelfUtils.getRemoteVideoMuted = (self: any) => {
247
+ if (!self || !self.controls || !self.controls.video) {
248
+ return null;
249
+ }
250
+
251
+ return self.controls.video.muted;
252
+ };
253
+
239
254
  /**
240
255
  * get the "remote mute" property from the self object
241
256
  * @param {Object} self
@@ -351,6 +366,25 @@ SelfUtils.isUserAdmitted = (oldSelf: object, changedSelf: object) => {
351
366
  return SelfUtils.isLocusUserUnadmitted(oldSelf) && SelfUtils.isLocusUserAdmitted(changedSelf);
352
367
  };
353
368
 
369
+ SelfUtils.videoMutedByOthersChanged = (oldSelf, changedSelf) => {
370
+ if (!changedSelf) {
371
+ throw new ParameterError(
372
+ 'New self must be defined to determine if self was video muted by others.'
373
+ );
374
+ }
375
+
376
+ if (!oldSelf || oldSelf.remoteVideoMuted === null) {
377
+ if (changedSelf.remoteVideoMuted) {
378
+ return true; // this happens when host disables "Allow start video"
379
+ }
380
+
381
+ // we don't want to be sending the 'meeting:self:videoUnmutedByOthers' notification on meeting join
382
+ return false;
383
+ }
384
+
385
+ return oldSelf.remoteVideoMuted !== changedSelf.remoteVideoMuted;
386
+ };
387
+
354
388
  SelfUtils.mutedByOthersChanged = (oldSelf, changedSelf) => {
355
389
  if (!changedSelf) {
356
390
  throw new ParameterError('New self must be defined to determine if self was muted by others.');
@@ -17,6 +17,8 @@ interface IInMeetingActions {
17
17
  canUnsetMuteOnEntry?: boolean;
18
18
  canSetDisallowUnmute?: boolean;
19
19
  canUnsetDisallowUnmute?: boolean;
20
+ canSetMuted?: boolean;
21
+ canUnsetMuted?: boolean;
20
22
  canAssignHost?: boolean;
21
23
  canStartRecording?: boolean;
22
24
  canPauseRecording?: boolean;
@@ -71,6 +73,10 @@ export default class InMeetingActions implements IInMeetingActions {
71
73
 
72
74
  canUnsetDisallowUnmute = null;
73
75
 
76
+ canSetMuted = null;
77
+
78
+ canUnsetMuted = null;
79
+
74
80
  canRaiseHand = null;
75
81
 
76
82
  canLowerAllHands = null;
@@ -114,6 +120,8 @@ export default class InMeetingActions implements IInMeetingActions {
114
120
  canSetMuteOnEntry: this.canSetMuteOnEntry,
115
121
  canUnsetMuteOnEntry: this.canUnsetMuteOnEntry,
116
122
  canSetDisallowUnmute: this.canSetDisallowUnmute,
123
+ canSetMuted: this.canSetMuted,
124
+ canUnsetMuted: this.canUnsetMuted,
117
125
  canUnsetDisallowUnmute: this.canUnsetDisallowUnmute,
118
126
  canStartRecording: this.canStartRecording,
119
127
  canPauseRecording: this.canPauseRecording,
@@ -17,7 +17,6 @@ import {
17
17
 
18
18
  import {
19
19
  MeetingNotActiveError,
20
- createMeetingsError,
21
20
  UserInLobbyError,
22
21
  NoMediaEstablishedYetError,
23
22
  UserNotJoinedError,
@@ -2290,6 +2289,8 @@ export default class Meeting extends StatelessWebexPlugin {
2290
2289
  canUnsetMuteOnEntry: ControlsOptionsUtil.canUnsetMuteOnEntry(
2291
2290
  payload.info.userDisplayHints
2292
2291
  ),
2292
+ canSetMuted: ControlsOptionsUtil.canSetMuted(payload.info.userDisplayHints),
2293
+ canUnsetMuted: ControlsOptionsUtil.canUnsetMuted(payload.info.userDisplayHints),
2293
2294
  canStartRecording: RecordingUtil.canUserStart(payload.info.userDisplayHints),
2294
2295
  canStopRecording: RecordingUtil.canUserStop(payload.info.userDisplayHints),
2295
2296
  canPauseRecording: RecordingUtil.canUserPause(payload.info.userDisplayHints),
@@ -2406,6 +2407,30 @@ export default class Meeting extends StatelessWebexPlugin {
2406
2407
  );
2407
2408
  }
2408
2409
  });
2410
+
2411
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED, (payload) => {
2412
+ if (payload) {
2413
+ if (this.video) {
2414
+ payload.muted = payload.muted ?? this.video.isRemotelyMuted();
2415
+ payload.unmuteAllowed = payload.unmuteAllowed ?? this.video.isUnmuteAllowed();
2416
+ this.video.handleServerRemoteMuteUpdate(payload.muted, payload.unmuteAllowed);
2417
+ }
2418
+ Trigger.trigger(
2419
+ this,
2420
+ {
2421
+ file: 'meeting/index',
2422
+ function: 'setUpLocusInfoSelfListener',
2423
+ },
2424
+ payload.muted
2425
+ ? EVENT_TRIGGERS.MEETING_SELF_VIDEO_MUTED_BY_OTHERS
2426
+ : EVENT_TRIGGERS.MEETING_SELF_VIDEO_UNMUTED_BY_OTHERS,
2427
+ {
2428
+ payload,
2429
+ }
2430
+ );
2431
+ }
2432
+ });
2433
+
2409
2434
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED, (payload) => {
2410
2435
  if (payload) {
2411
2436
  if (this.audio) {
@@ -2693,14 +2718,32 @@ export default class Meeting extends StatelessWebexPlugin {
2693
2718
  }
2694
2719
 
2695
2720
  /**
2696
- * Admit the guest(s) to the call once they are waiting
2721
+ * Admit the guest(s) to the call once they are waiting.
2722
+ * If the host/cohost is in a breakout session, the locus url
2723
+ * of the session must be provided as the authorizingLocusUrl.
2724
+ * Regardless of host/cohost location, the locus Id (lid) in
2725
+ * the path should be the locus Id of the main, which means the
2726
+ * locus url of the api call must be from the main session.
2727
+ * If these loucs urls are not provided, the function will do the check.
2697
2728
  * @param {Array} memberIds
2729
+ * @param {Object} sessionLocusUrls: {authorizingLocusUrl, mainLocusUrl}
2698
2730
  * @returns {Promise} see #members.admitMembers
2699
2731
  * @public
2700
2732
  * @memberof Meeting
2701
2733
  */
2702
- public admit(memberIds: Array<any>) {
2703
- return this.members.admitMembers(memberIds);
2734
+ public admit(
2735
+ memberIds: Array<any>,
2736
+ sessionLocusUrls?: {authorizingLocusUrl: string; mainLocusUrl: string}
2737
+ ) {
2738
+ let locusUrls = sessionLocusUrls;
2739
+ if (!locusUrls) {
2740
+ const {locusUrl, mainLocusUrl} = this.breakouts;
2741
+ if (locusUrl && mainLocusUrl) {
2742
+ locusUrls = {authorizingLocusUrl: locusUrl, mainLocusUrl};
2743
+ }
2744
+ }
2745
+
2746
+ return this.members.admitMembers(memberIds, locusUrls);
2704
2747
  }
2705
2748
 
2706
2749
  /**
@@ -6216,6 +6259,27 @@ export default class Meeting extends StatelessWebexPlugin {
6216
6259
  return this.controlsOptionsManager.setDisallowUnmute(enabled);
6217
6260
  }
6218
6261
 
6262
+ /**
6263
+ * set the mute all flag for participants if you're the host
6264
+ * @returns {Promise}
6265
+ * @param {boolean} mutedEnabled
6266
+ * @param {boolean} disallowUnmuteEnabled
6267
+ * @param {boolean} muteOnEntryEnabled
6268
+ * @public
6269
+ * @memberof Meeting
6270
+ */
6271
+ public setMuteAll(
6272
+ mutedEnabled: boolean,
6273
+ disallowUnmuteEnabled: boolean,
6274
+ muteOnEntryEnabled: boolean
6275
+ ) {
6276
+ return this.controlsOptionsManager.setMuteAll(
6277
+ mutedEnabled,
6278
+ disallowUnmuteEnabled,
6279
+ muteOnEntryEnabled
6280
+ );
6281
+ }
6282
+
6219
6283
  /**
6220
6284
  * End the recording of this meeting
6221
6285
  * @returns {Promise}
@@ -52,9 +52,9 @@ class MuteState {
52
52
  },
53
53
  server: {
54
54
  localMute: false,
55
- // initial values available only for audio (REMOTE_MUTE_VIDEO_MISSING_IMPLEMENTATION)
56
- remoteMute: type === AUDIO ? meeting.remoteMuted : false,
57
- unmuteAllowed: type === AUDIO ? meeting.unmuteAllowed : true,
55
+ // because remoteVideoMuted and unmuteVideoAllowed are updated seperately, they might be undefined
56
+ remoteMute: type === AUDIO ? meeting.remoteMuted : meeting.remoteVideoMuted ?? false,
57
+ unmuteAllowed: type === AUDIO ? meeting.unmuteAllowed : meeting.unmuteVideoAllowed ?? true,
58
58
  },
59
59
  syncToServerInProgress: false,
60
60
  };
@@ -241,35 +241,28 @@ class MuteState {
241
241
  * @returns {Promise}
242
242
  */
243
243
  private sendRemoteMuteRequestToServer(meeting?: any) {
244
- if (this.type === AUDIO) {
245
- const remoteMute = this.state.client.localMute;
244
+ const remoteMute = this.state.client.localMute;
246
245
 
247
- LoggerProxy.logger.info(
248
- `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: sending remote mute:${remoteMute} to server`
249
- );
246
+ LoggerProxy.logger.info(
247
+ `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: sending remote mute:${remoteMute} to server`
248
+ );
250
249
 
251
- return meeting.members
252
- .muteMember(meeting.members.selfId, remoteMute)
253
- .then(() => {
254
- LoggerProxy.logger.info(
255
- `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: remote mute:${remoteMute} applied to server`
256
- );
257
-
258
- this.state.server.remoteMute = remoteMute;
259
- })
260
- .catch((remoteUpdateError) => {
261
- LoggerProxy.logger.warn(
262
- `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: failed to apply remote mute ${remoteMute} to server: ${remoteUpdateError}`
263
- );
264
-
265
- return Promise.reject(remoteUpdateError);
266
- });
267
- }
250
+ return meeting.members
251
+ .muteMember(meeting.members.selfId, remoteMute, this.type === AUDIO)
252
+ .then(() => {
253
+ LoggerProxy.logger.info(
254
+ `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: remote mute:${remoteMute} applied to server`
255
+ );
268
256
 
269
- // for now we don't need to support remote muting of video (REMOTE_MUTE_VIDEO_MISSING_IMPLEMENTATION)
270
- this.state.server.remoteMute = this.state.client.localMute;
257
+ this.state.server.remoteMute = remoteMute;
258
+ })
259
+ .catch((remoteUpdateError) => {
260
+ LoggerProxy.logger.warn(
261
+ `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: failed to apply remote mute ${remoteMute} to server: ${remoteUpdateError}`
262
+ );
271
263
 
272
- return Promise.resolve();
264
+ return Promise.reject(remoteUpdateError);
265
+ });
273
266
  }
274
267
 
275
268
  /**
@@ -285,8 +278,12 @@ class MuteState {
285
278
  LoggerProxy.logger.info(
286
279
  `Meeting:muteState#handleServerRemoteMuteUpdate --> ${this.type}: updating server remoteMute to (${muted})`
287
280
  );
288
- this.state.server.remoteMute = muted;
289
- this.state.server.unmuteAllowed = unmuteAllowed;
281
+ if (muted !== undefined) {
282
+ this.state.server.remoteMute = muted;
283
+ }
284
+ if (unmuteAllowed !== undefined) {
285
+ this.state.server.unmuteAllowed = unmuteAllowed;
286
+ }
290
287
  }
291
288
 
292
289
  /**
@@ -330,6 +327,28 @@ class MuteState {
330
327
  );
331
328
  }
332
329
 
330
+ /**
331
+ * Returns true if the user is remotely muted
332
+ *
333
+ * @public
334
+ * @memberof MuteState
335
+ * @returns {Boolean}
336
+ */
337
+ public isRemotelyMuted() {
338
+ return this.state.server.remoteMute;
339
+ }
340
+
341
+ /**
342
+ * Returns true if unmute is allowed
343
+ *
344
+ * @public
345
+ * @memberof MuteState
346
+ * @returns {Boolean}
347
+ */
348
+ public isUnmuteAllowed() {
349
+ return this.state.server.unmuteAllowed;
350
+ }
351
+
333
352
  /**
334
353
  * Returns true if the user is locally muted
335
354
  *
@@ -733,15 +733,22 @@ export default class Members extends StatelessWebexPlugin {
733
733
  /**
734
734
  * Admits waiting members (invited guests to meeting)
735
735
  * @param {Array} memberIds
736
+ * @param {Object} sessionLocusUrls: {authorizingLocusUrl, mainLocusUrl}
736
737
  * @returns {Promise}
737
738
  * @public
738
739
  * @memberof Members
739
740
  */
740
- public admitMembers(memberIds: Array<any>) {
741
+ public admitMembers(
742
+ memberIds: Array<any>,
743
+ sessionLocusUrls?: {authorizingLocusUrl: string; mainLocusUrl: string}
744
+ ) {
741
745
  if (isEmpty(memberIds)) {
742
746
  return Promise.reject(new ParameterError('No member ids provided to admit.'));
743
747
  }
744
- const options = MembersUtil.generateAdmitMemberOptions(memberIds, this.locusUrl);
748
+ const options = {
749
+ sessionLocusUrls,
750
+ ...MembersUtil.generateAdmitMemberOptions(memberIds, this.locusUrl),
751
+ };
745
752
 
746
753
  return this.membersRequest.admitMember(options);
747
754
  }
@@ -773,11 +780,12 @@ export default class Members extends StatelessWebexPlugin {
773
780
  * Audio mutes another member in a meeting
774
781
  * @param {String} memberId
775
782
  * @param {boolean} [mute] default true
783
+ * @param {boolean} [isAudio] default true
776
784
  * @returns {Promise}
777
785
  * @public
778
786
  * @memberof Members
779
787
  */
780
- public muteMember(memberId: string, mute = true) {
788
+ public muteMember(memberId: string, mute = true, isAudio = true) {
781
789
  if (!this.locusUrl) {
782
790
  return Promise.reject(
783
791
  new ParameterError(
@@ -790,7 +798,7 @@ export default class Members extends StatelessWebexPlugin {
790
798
  new ParameterError('The member id must be defined to mute the member.')
791
799
  );
792
800
  }
793
- const options = MembersUtil.generateMuteMemberOptions(memberId, mute, this.locusUrl);
801
+ const options = MembersUtil.generateMuteMemberOptions(memberId, mute, this.locusUrl, isAudio);
794
802
 
795
803
  return this.membersRequest.muteMember(options);
796
804
  }
@@ -54,20 +54,31 @@ MembersUtil.getAddMemberBody = (options: any) => ({
54
54
  });
55
55
 
56
56
  /**
57
- * @param {Object} options with {memberIds}
57
+ * @param {Object} options with {memberIds, authorizingLocusUrl}
58
58
  * @returns {Object} admit with {memberIds}
59
59
  */
60
- MembersUtil.getAdmitMemberRequestBody = (options: any) => ({
61
- admit: {participantIds: options.memberIds},
62
- });
60
+ MembersUtil.getAdmitMemberRequestBody = (options: any) => {
61
+ const {memberIds, sessionLocusUrls} = options;
62
+ const body: any = {admit: {participantIds: memberIds}};
63
+ if (sessionLocusUrls) {
64
+ const {authorizingLocusUrl} = sessionLocusUrls;
65
+
66
+ return {authorizingLocusUrl, ...body};
67
+ }
68
+
69
+ return body;
70
+ };
63
71
 
64
72
  /**
65
- * @param {Object} format with {memberIds, locusUrl}
73
+ * @param {Object} format with {memberIds, locusUrl, sessionLocusUrls}
66
74
  * @returns {Object} the request parameters (method, uri, body) needed to make a admitMember request
75
+ * if a host/cohost is in a breakout session, the locus url should be the main session locus url
67
76
  */
68
77
  MembersUtil.getAdmitMemberRequestParams = (format: any) => {
69
78
  const body = MembersUtil.getAdmitMemberRequestBody(format);
70
- const uri = `${format.locusUrl}/${CONTROLS}`;
79
+ const {locusUrl, sessionLocusUrls} = format;
80
+ const baseUrl = sessionLocusUrls?.mainLocusUrl || locusUrl;
81
+ const uri = `${baseUrl}/${CONTROLS}`;
71
82
 
72
83
  return {
73
84
  method: HTTP_VERBS.PUT,
@@ -128,10 +139,11 @@ MembersUtil.generateRemoveMemberOptions = (removal, locusUrl) => ({
128
139
  locusUrl,
129
140
  });
130
141
 
131
- MembersUtil.generateMuteMemberOptions = (memberId, status, locusUrl) => ({
142
+ MembersUtil.generateMuteMemberOptions = (memberId, status, locusUrl, isAudio) => ({
132
143
  memberId,
133
144
  muted: status,
134
145
  locusUrl,
146
+ isAudio,
135
147
  });
136
148
 
137
149
  MembersUtil.generateRaiseHandMemberOptions = (memberId, status, locusUrl) => ({
@@ -146,8 +158,9 @@ MembersUtil.generateLowerAllHandsMemberOptions = (requestingParticipantId, locus
146
158
  });
147
159
 
148
160
  MembersUtil.getMuteMemberRequestParams = (options) => {
161
+ const property = options.isAudio === false ? 'video' : 'audio';
149
162
  const body = {
150
- audio: {
163
+ [property]: {
151
164
  muted: options.muted,
152
165
  },
153
166
  };
@@ -511,8 +511,8 @@ describe('plugin-meetings', () => {
511
511
 
512
512
  assert.equal(arg.uri, 'url');
513
513
  assert.equal(arg.method, 'PUT');
514
- assert.deepEqual(argObj1, {id:'groupId', action: 'START', allowBackToMain: false, allowToJoinLater: false});
515
- assert.deepEqual(argObj2, {id:'id', action: 'START', allowBackToMain: false, allowToJoinLater: false, someOtherParam: 'someOtherParam'});
514
+ assert.deepEqual(argObj1, {id:'groupId', action: 'START', allowBackToMain: false, allowToJoinLater: false, duration: BREAKOUTS.DEFAULT_DURATION});
515
+ assert.deepEqual(argObj2, {id:'id', action: 'START', allowBackToMain: false, allowToJoinLater: false, someOtherParam: 'someOtherParam', duration: BREAKOUTS.DEFAULT_DURATION});
516
516
  assert.deepEqual(result, {body: getBOResponse('OPEN')});
517
517
  });
518
518
 
@@ -119,6 +119,62 @@ describe('plugin-meetings', () => {
119
119
  });
120
120
  });
121
121
  });
122
+
123
+ describe('Mute/Unmute All', () => {
124
+ let manager;
125
+ beforeEach(() => {
126
+ request = {
127
+ request: sinon.stub().returns(Promise.resolve()),
128
+ };
129
+
130
+ manager = new ControlsOptionsManager(request);
131
+
132
+ manager.set({
133
+ locusUrl: 'test/id',
134
+ displayHints: [],
135
+ })
136
+ });
137
+
138
+ it('rejects when correct display hint is not present mutedEnabled=false', () => {
139
+ const result = manager.setMuteAll(false, false, false);
140
+
141
+ assert.notCalled(request.request);
142
+
143
+ assert.isRejected(result);
144
+ });
145
+
146
+ it('rejects when correct display hint is not present mutedEnabled=true', () => {
147
+ const result = manager.setMuteAll(true, false, false);
148
+
149
+ assert.notCalled(request.request);
150
+
151
+ assert.isRejected(result);
152
+ });
153
+
154
+ it('can set mute all when the display hint is available mutedEnabled=true', () => {
155
+ manager.setDisplayHints(['MUTE_ALL', 'ENABLE_HARD_MUTE', 'ENABLE_MUTE_ON_ENTRY']);
156
+
157
+ const result = manager.setMuteAll(true, true, true);
158
+
159
+ assert.calledWith(request.request, { uri: 'test/id/controls',
160
+ body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
161
+ method: HTTP_VERBS.PATCH});
162
+
163
+ assert.deepEqual(result, request.request.firstCall.returnValue);
164
+ });
165
+
166
+ it('can set mute all when the display hint is available mutedEnabled=false', () => {
167
+ manager.setDisplayHints(['UNMUTE_ALL', 'DISABLE_HARD_MUTE', 'DISABLE_MUTE_ON_ENTRY']);
168
+
169
+ const result = manager.setMuteAll(false, false, false);
170
+
171
+ assert.calledWith(request.request, { uri: 'test/id/controls',
172
+ body: { audio: { muted: false, disallowUnmute: false, muteOnEntry: false } },
173
+ method: HTTP_VERBS.PATCH});
174
+
175
+ assert.deepEqual(result, request.request.firstCall.returnValue);
176
+ });
177
+ });
122
178
  });
123
179
  });
124
180
  });
@@ -61,6 +61,26 @@ describe('plugin-meetings', () => {
61
61
  });
62
62
  });
63
63
 
64
+ describe('canSetMuteAll', () => {
65
+ it('can mute all', () => {
66
+ locusInfo.parsedLocus.info.userDisplayHints.push('MUTE_ALL');
67
+
68
+ assert.equal(ControlsOptionsUtil.canSetMuted(locusInfo.parsedLocus.info.userDisplayHints), true);
69
+ });
70
+
71
+ it('can unmute all', () => {
72
+ locusInfo.parsedLocus.info.userDisplayHints.push('UNMUTE_ALL');
73
+
74
+ assert.equal(ControlsOptionsUtil.canUnsetMuted(locusInfo.parsedLocus.info.userDisplayHints), true);
75
+ });
76
+ it('rejects when correct display hint is not present', () => {
77
+ assert.equal(ControlsOptionsUtil.canSetMuted(locusInfo.parsedLocus.info.userDisplayHints), false);
78
+ });
79
+
80
+ it('rejects when correct display hint is not present', () => {
81
+ assert.equal(ControlsOptionsUtil.canUnsetMuted(locusInfo.parsedLocus.info.userDisplayHints), false);
82
+ });
83
+ });
64
84
  });
65
85
  });
66
86
  });