@webex/plugin-meetings 3.12.0-next.6 → 3.12.0-next.60

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 (158) hide show
  1. package/AGENTS.md +9 -0
  2. package/dist/aiEnableRequest/index.js +15 -2
  3. package/dist/aiEnableRequest/index.js.map +1 -1
  4. package/dist/breakouts/breakout.js +8 -3
  5. package/dist/breakouts/breakout.js.map +1 -1
  6. package/dist/breakouts/index.js +26 -2
  7. package/dist/breakouts/index.js.map +1 -1
  8. package/dist/config.js +2 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/constants.js +6 -3
  11. package/dist/constants.js.map +1 -1
  12. package/dist/controls-options-manager/constants.js +11 -1
  13. package/dist/controls-options-manager/constants.js.map +1 -1
  14. package/dist/controls-options-manager/index.js +38 -24
  15. package/dist/controls-options-manager/index.js.map +1 -1
  16. package/dist/controls-options-manager/util.js +91 -0
  17. package/dist/controls-options-manager/util.js.map +1 -1
  18. package/dist/hashTree/constants.js +10 -1
  19. package/dist/hashTree/constants.js.map +1 -1
  20. package/dist/hashTree/hashTreeParser.js +716 -370
  21. package/dist/hashTree/hashTreeParser.js.map +1 -1
  22. package/dist/hashTree/utils.js +22 -0
  23. package/dist/hashTree/utils.js.map +1 -1
  24. package/dist/index.js +7 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/interceptors/locusRetry.js +23 -8
  27. package/dist/interceptors/locusRetry.js.map +1 -1
  28. package/dist/interpretation/index.js +10 -1
  29. package/dist/interpretation/index.js.map +1 -1
  30. package/dist/interpretation/siLanguage.js +1 -1
  31. package/dist/locus-info/controlsUtils.js +4 -1
  32. package/dist/locus-info/controlsUtils.js.map +1 -1
  33. package/dist/locus-info/index.js +289 -87
  34. package/dist/locus-info/index.js.map +1 -1
  35. package/dist/locus-info/types.js +19 -0
  36. package/dist/locus-info/types.js.map +1 -1
  37. package/dist/media/index.js +3 -1
  38. package/dist/media/index.js.map +1 -1
  39. package/dist/media/properties.js +1 -0
  40. package/dist/media/properties.js.map +1 -1
  41. package/dist/meeting/in-meeting-actions.js +3 -1
  42. package/dist/meeting/in-meeting-actions.js.map +1 -1
  43. package/dist/meeting/index.js +907 -535
  44. package/dist/meeting/index.js.map +1 -1
  45. package/dist/meeting/util.js +19 -2
  46. package/dist/meeting/util.js.map +1 -1
  47. package/dist/meetings/index.js +231 -78
  48. package/dist/meetings/index.js.map +1 -1
  49. package/dist/meetings/meetings.types.js +6 -1
  50. package/dist/meetings/meetings.types.js.map +1 -1
  51. package/dist/meetings/request.js +39 -0
  52. package/dist/meetings/request.js.map +1 -1
  53. package/dist/meetings/util.js +79 -5
  54. package/dist/meetings/util.js.map +1 -1
  55. package/dist/member/index.js +10 -0
  56. package/dist/member/index.js.map +1 -1
  57. package/dist/member/types.js.map +1 -1
  58. package/dist/member/util.js +3 -0
  59. package/dist/member/util.js.map +1 -1
  60. package/dist/metrics/constants.js +4 -1
  61. package/dist/metrics/constants.js.map +1 -1
  62. package/dist/multistream/codec/constants.js +63 -0
  63. package/dist/multistream/codec/constants.js.map +1 -0
  64. package/dist/multistream/mediaRequestManager.js +62 -15
  65. package/dist/multistream/mediaRequestManager.js.map +1 -1
  66. package/dist/multistream/receiveSlot.js +9 -0
  67. package/dist/multistream/receiveSlot.js.map +1 -1
  68. package/dist/reactions/reactions.type.js.map +1 -1
  69. package/dist/recording-controller/index.js +1 -3
  70. package/dist/recording-controller/index.js.map +1 -1
  71. package/dist/types/config.d.ts +2 -0
  72. package/dist/types/constants.d.ts +2 -0
  73. package/dist/types/controls-options-manager/constants.d.ts +6 -1
  74. package/dist/types/controls-options-manager/index.d.ts +10 -0
  75. package/dist/types/hashTree/constants.d.ts +1 -0
  76. package/dist/types/hashTree/hashTreeParser.d.ts +92 -16
  77. package/dist/types/hashTree/utils.d.ts +11 -0
  78. package/dist/types/index.d.ts +2 -0
  79. package/dist/types/interceptors/locusRetry.d.ts +4 -4
  80. package/dist/types/locus-info/index.d.ts +46 -6
  81. package/dist/types/locus-info/types.d.ts +21 -1
  82. package/dist/types/media/properties.d.ts +1 -0
  83. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  84. package/dist/types/meeting/index.d.ts +87 -3
  85. package/dist/types/meeting/util.d.ts +8 -0
  86. package/dist/types/meetings/index.d.ts +30 -2
  87. package/dist/types/meetings/meetings.types.d.ts +15 -0
  88. package/dist/types/meetings/request.d.ts +14 -0
  89. package/dist/types/member/index.d.ts +1 -0
  90. package/dist/types/member/types.d.ts +1 -0
  91. package/dist/types/member/util.d.ts +1 -0
  92. package/dist/types/metrics/constants.d.ts +3 -0
  93. package/dist/types/multistream/codec/constants.d.ts +7 -0
  94. package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
  95. package/dist/types/reactions/reactions.type.d.ts +3 -0
  96. package/dist/webinar/index.js +361 -235
  97. package/dist/webinar/index.js.map +1 -1
  98. package/package.json +22 -22
  99. package/src/aiEnableRequest/index.ts +16 -0
  100. package/src/breakouts/breakout.ts +3 -1
  101. package/src/breakouts/index.ts +31 -0
  102. package/src/config.ts +2 -0
  103. package/src/constants.ts +5 -1
  104. package/src/controls-options-manager/constants.ts +14 -1
  105. package/src/controls-options-manager/index.ts +47 -24
  106. package/src/controls-options-manager/util.ts +81 -1
  107. package/src/hashTree/constants.ts +9 -0
  108. package/src/hashTree/hashTreeParser.ts +429 -183
  109. package/src/hashTree/utils.ts +17 -0
  110. package/src/index.ts +5 -0
  111. package/src/interceptors/locusRetry.ts +25 -4
  112. package/src/interpretation/index.ts +25 -8
  113. package/src/locus-info/controlsUtils.ts +3 -1
  114. package/src/locus-info/index.ts +291 -97
  115. package/src/locus-info/types.ts +25 -1
  116. package/src/media/index.ts +3 -0
  117. package/src/media/properties.ts +1 -0
  118. package/src/meeting/in-meeting-actions.ts +4 -0
  119. package/src/meeting/index.ts +388 -33
  120. package/src/meeting/util.ts +20 -2
  121. package/src/meetings/index.ts +134 -44
  122. package/src/meetings/meetings.types.ts +19 -0
  123. package/src/meetings/request.ts +43 -0
  124. package/src/meetings/util.ts +97 -1
  125. package/src/member/index.ts +10 -0
  126. package/src/member/types.ts +1 -0
  127. package/src/member/util.ts +3 -0
  128. package/src/metrics/constants.ts +3 -0
  129. package/src/multistream/codec/constants.ts +58 -0
  130. package/src/multistream/mediaRequestManager.ts +119 -28
  131. package/src/multistream/receiveSlot.ts +18 -0
  132. package/src/reactions/reactions.type.ts +3 -0
  133. package/src/recording-controller/index.ts +1 -2
  134. package/src/webinar/index.ts +162 -21
  135. package/test/unit/spec/aiEnableRequest/index.ts +86 -0
  136. package/test/unit/spec/breakouts/breakout.ts +9 -3
  137. package/test/unit/spec/breakouts/index.ts +49 -0
  138. package/test/unit/spec/controls-options-manager/index.js +140 -29
  139. package/test/unit/spec/controls-options-manager/util.js +165 -0
  140. package/test/unit/spec/hashTree/hashTreeParser.ts +1508 -149
  141. package/test/unit/spec/hashTree/utils.ts +88 -1
  142. package/test/unit/spec/interceptors/locusRetry.ts +205 -4
  143. package/test/unit/spec/interpretation/index.ts +26 -4
  144. package/test/unit/spec/locus-info/controlsUtils.js +172 -57
  145. package/test/unit/spec/locus-info/index.js +475 -81
  146. package/test/unit/spec/media/index.ts +31 -0
  147. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  148. package/test/unit/spec/meeting/index.js +1131 -49
  149. package/test/unit/spec/meeting/muteState.js +3 -0
  150. package/test/unit/spec/meeting/utils.js +33 -0
  151. package/test/unit/spec/meetings/index.js +360 -10
  152. package/test/unit/spec/meetings/request.js +141 -0
  153. package/test/unit/spec/meetings/utils.js +189 -0
  154. package/test/unit/spec/member/index.js +7 -0
  155. package/test/unit/spec/member/util.js +24 -0
  156. package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
  157. package/test/unit/spec/recording-controller/index.js +9 -8
  158. package/test/unit/spec/webinar/index.ts +141 -16
@@ -55,6 +55,27 @@ describe('plugin-meetings', () => {
55
55
  });
56
56
  });
57
57
 
58
+ describe('#locusUrlUpdate', () => {
59
+ it('should update the locusUrl property', () => {
60
+ const testLocusUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id';
61
+
62
+ aiEnableRequest.locusUrlUpdate(testLocusUrl);
63
+
64
+ assert.equal(aiEnableRequest.locusUrl, testLocusUrl);
65
+ });
66
+
67
+ it('should handle updating locusUrl multiple times', () => {
68
+ const firstUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id-1';
69
+ const secondUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id-2';
70
+
71
+ aiEnableRequest.locusUrlUpdate(firstUrl);
72
+ assert.equal(aiEnableRequest.locusUrl, firstUrl);
73
+
74
+ aiEnableRequest.locusUrlUpdate(secondUrl);
75
+ assert.equal(aiEnableRequest.locusUrl, secondUrl);
76
+ });
77
+ });
78
+
58
79
  describe('#selfParticipantIdUpdate', () => {
59
80
  it('should update the selfParticipantId property', () => {
60
81
  const testSelfParticipantId = 'participant-123';
@@ -254,6 +275,71 @@ describe('plugin-meetings', () => {
254
275
  sinon.assert.notCalled(triggerSpy);
255
276
  });
256
277
 
278
+ it('should not trigger event when locusUrl does not match', () => {
279
+ const testLocusUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id';
280
+ const differentLocusUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/different-id';
281
+
282
+ aiEnableRequest.locusUrl = testLocusUrl;
283
+
284
+ // Reset the spy after setting locusUrl to avoid counting property change events
285
+ triggerSpy.resetHistory();
286
+
287
+ aiEnableRequest.listenToApprovalRequests();
288
+
289
+ const event = {
290
+ data: {
291
+ locusUrl: differentLocusUrl,
292
+ approval: {
293
+ resourceType: AI_ENABLE_REQUEST.RESOURCE_TYPE,
294
+ receivers: [{participantId: testSelfParticipantId}],
295
+ initiator: {participantId: testInitiatorId},
296
+ actionType: AI_ENABLE_REQUEST.ACTION_TYPE.REQUESTED,
297
+ url: testUrl,
298
+ },
299
+ },
300
+ };
301
+
302
+ webex.internal.mercury.emit(`event:${LOCUSEVENT.APPROVAL_REQUEST}`, event);
303
+
304
+ sinon.assert.notCalled(triggerSpy);
305
+ });
306
+
307
+ it('should trigger event when locusUrl matches', () => {
308
+ const testLocusUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id';
309
+
310
+ aiEnableRequest.locusUrl = testLocusUrl;
311
+
312
+ // Reset the spy after setting locusUrl to avoid counting property change events
313
+ triggerSpy.resetHistory();
314
+
315
+ aiEnableRequest.listenToApprovalRequests();
316
+
317
+ const event = {
318
+ data: {
319
+ locusUrl: testLocusUrl,
320
+ approval: {
321
+ resourceType: AI_ENABLE_REQUEST.RESOURCE_TYPE,
322
+ receivers: [{participantId: testSelfParticipantId}],
323
+ initiator: {participantId: testInitiatorId},
324
+ actionType: AI_ENABLE_REQUEST.ACTION_TYPE.REQUESTED,
325
+ url: testUrl,
326
+ },
327
+ },
328
+ };
329
+
330
+ webex.internal.mercury.emit(`event:${LOCUSEVENT.APPROVAL_REQUEST}`, event);
331
+
332
+ sinon.assert.calledOnce(triggerSpy);
333
+ sinon.assert.calledWith(triggerSpy, AI_ENABLE_REQUEST.EVENTS.APPROVAL_REQUEST_ARRIVED, {
334
+ actionType: AI_ENABLE_REQUEST.ACTION_TYPE.REQUESTED,
335
+ isApprover: true,
336
+ isInitiator: false,
337
+ initiatorId: testInitiatorId,
338
+ approverId: testSelfParticipantId,
339
+ url: testUrl,
340
+ });
341
+ });
342
+
257
343
  it('should handle events with different action types', () => {
258
344
  aiEnableRequest.listenToApprovalRequests();
259
345
 
@@ -26,6 +26,7 @@ describe('plugin-meetings', () => {
26
26
  breakout.sessionId = 'sessionId';
27
27
  breakout.sessionType = 'BREAKOUT';
28
28
  breakout.url = 'url';
29
+ breakout.resourceLink = 'resource-link';
29
30
  breakout.collection = {
30
31
  parent: {
31
32
  meetingId: 'activeMeetingId',
@@ -45,6 +46,7 @@ describe('plugin-meetings', () => {
45
46
  describe('initialize', () => {
46
47
  it('creates the object correctly', () => {
47
48
  assert.instanceOf(breakout.breakoutRequest, BreakoutRequest);
49
+ assert.equal(breakout.resourceLink, 'resource-link');
48
50
  });
49
51
  });
50
52
 
@@ -217,10 +219,14 @@ describe('plugin-meetings', () => {
217
219
  locusParticipantsUpdate: sinon.stub(),
218
220
  };
219
221
 
220
- const locusData = {some: 'data'};
222
+ const locusData = {participants: [{id: 'participant-1'}], sequence: {entries: [123]}};
221
223
  const result = breakout.parseRoster(locusData);
222
224
 
223
- assert.calledOnceWithExactly(breakout.members.locusParticipantsUpdate, locusData);
225
+ assert.calledOnceWithExactly(breakout.members.locusParticipantsUpdate, {
226
+ participants: [{id: 'participant-1'}],
227
+ isReplace: true,
228
+ });
229
+ assert.equal(breakout.breakoutRosterLocus, locusData);
224
230
  assert.equal(result, undefined);
225
231
  });
226
232
  it('not call locusParticipantsUpdate if sequence is expired', () => {
@@ -228,7 +234,7 @@ describe('plugin-meetings', () => {
228
234
  locusParticipantsUpdate: sinon.stub(),
229
235
  };
230
236
  breakout.isNeedHandleRoster = sinon.stub().returns(false);
231
- const locusData = {some: 'data'};
237
+ const locusData = {participants: [{id: 'participant-1'}], sequence: {entries: [123]}};
232
238
  breakout.parseRoster(locusData);
233
239
 
234
240
  assert.notCalled(breakout.members.locusParticipantsUpdate);
@@ -313,6 +313,7 @@ describe('plugin-meetings', () => {
313
313
  groupId: 'groupId',
314
314
  sessionType: 'sessionType',
315
315
  url: 'url',
316
+ resourceLink: 'resource-link',
316
317
  name: 'name',
317
318
  allowBackToMain: true,
318
319
  delayCloseTime: 10,
@@ -339,6 +340,7 @@ describe('plugin-meetings', () => {
339
340
  assert.equal(breakouts.currentBreakoutSession.current, true);
340
341
  assert.equal(breakouts.currentBreakoutSession.sessionType, 'sessionType');
341
342
  assert.equal(breakouts.currentBreakoutSession.url, 'url');
343
+ assert.equal(breakouts.currentBreakoutSession.resourceLink, 'resource-link');
342
344
  assert.equal(breakouts.currentBreakoutSession.active, false);
343
345
  assert.equal(breakouts.currentBreakoutSession.allowed, false);
344
346
  assert.equal(breakouts.currentBreakoutSession.assigned, false);
@@ -1847,6 +1849,53 @@ describe('plugin-meetings', () => {
1847
1849
  });
1848
1850
  });
1849
1851
 
1852
+ describe('#removeFromBreakout', () => {
1853
+ it('should make a POST request with correct body and return the result', async () => {
1854
+ breakouts.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
1855
+ breakouts.set('url', 'url');
1856
+ breakouts.set('mainGroupId', 'mainGroupId');
1857
+ breakouts.set('mainSessionId', 'mainSessionId');
1858
+
1859
+ const participants = ['participant1', 'participant2'];
1860
+ const result = await breakouts.removeFromBreakout(participants);
1861
+
1862
+ assert.calledOnceWithExactly(breakouts.request, {
1863
+ method: 'POST',
1864
+ uri: 'url/move',
1865
+ body: {
1866
+ groups: [
1867
+ {
1868
+ id: 'mainGroupId',
1869
+ sessions: [
1870
+ {
1871
+ id: 'mainSessionId',
1872
+ participants,
1873
+ },
1874
+ ],
1875
+ },
1876
+ ],
1877
+ },
1878
+ });
1879
+ assert.equal(result, 'REQUEST_RETURN_VALUE');
1880
+ });
1881
+
1882
+ it('should throw an error if mainGroupId is missing', () => {
1883
+ breakouts.set('mainSessionId', 'mainSessionId');
1884
+ assert.throws(
1885
+ () => breakouts.removeFromBreakout(['participant1']),
1886
+ 'Main group ID and session ID must be available to remove participants from breakout'
1887
+ );
1888
+ });
1889
+
1890
+ it('should throw an error if mainSessionId is missing', () => {
1891
+ breakouts.set('mainGroupId', 'mainGroupId');
1892
+ assert.throws(
1893
+ () => breakouts.removeFromBreakout(['participant1']),
1894
+ 'Main group ID and session ID must be available to remove participants from breakout'
1895
+ );
1896
+ });
1897
+ });
1898
+
1850
1899
  describe('#triggerReturnToMainEvent', () => {
1851
1900
  const checkTrigger = ({breakout, shouldTrigger}) => {
1852
1901
  breakouts.trigger = sinon.stub();
@@ -1,5 +1,6 @@
1
1
  import ControlsOptionsManager from '@webex/plugin-meetings/src/controls-options-manager';
2
2
  import Util from '@webex/plugin-meetings/src/controls-options-manager/util';
3
+ import ParameterError from '@webex/plugin-meetings/src/common/errors/parameter';
3
4
  import sinon from 'sinon';
4
5
  import {assert} from '@webex/test-helper-chai';
5
6
  import { HTTP_VERBS } from '@webex/plugin-meetings/src/constants';
@@ -26,6 +27,7 @@ describe('plugin-meetings', () => {
26
27
  beforeEach(() => {
27
28
  request = {
28
29
  request: sinon.stub().returns(Promise.resolve()),
30
+ locusDeltaRequest: sinon.stub().returns(Promise.resolve()),
29
31
  };
30
32
 
31
33
  manager = new ControlsOptionsManager(request);
@@ -58,11 +60,11 @@ describe('plugin-meetings', () => {
58
60
 
59
61
  const result = manager.setMuteOnEntry(true);
60
62
 
61
- assert.calledWith(request.request, { uri: 'test/id/controls',
63
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
62
64
  body: { muteOnEntry: { enabled: true } },
63
65
  method: HTTP_VERBS.PATCH});
64
66
 
65
- assert.deepEqual(result, request.request.firstCall.returnValue);
67
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
66
68
  });
67
69
 
68
70
  it('can set mute on entry when the display hint is available enabled=false', () => {
@@ -70,11 +72,24 @@ describe('plugin-meetings', () => {
70
72
 
71
73
  const result = manager.setMuteOnEntry(false);
72
74
 
73
- assert.calledWith(request.request, { uri: 'test/id/controls',
75
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
74
76
  body: { muteOnEntry: { enabled: false } },
75
77
  method: HTTP_VERBS.PATCH});
76
78
 
77
- assert.deepEqual(result, request.request.firstCall.returnValue);
79
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
80
+ });
81
+
82
+ it('should send setMuteOnEntry to locusUrl without authorizingLocusUrl when in breakout', () => {
83
+ manager.setDisplayHints(['ENABLE_MUTE_ON_ENTRY']);
84
+ manager.mainLocusUrl = 'test/main';
85
+
86
+ const result = manager.setMuteOnEntry(true);
87
+
88
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
89
+ body: { muteOnEntry: { enabled: true } },
90
+ method: HTTP_VERBS.PATCH});
91
+
92
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
78
93
  });
79
94
  });
80
95
 
@@ -100,11 +115,11 @@ describe('plugin-meetings', () => {
100
115
 
101
116
  const result = manager.setDisallowUnmute(true);
102
117
 
103
- assert.calledWith(request.request, { uri: 'test/id/controls',
118
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
104
119
  body: { disallowUnmute: { enabled: true } },
105
120
  method: HTTP_VERBS.PATCH});
106
121
 
107
- assert.deepEqual(result, request.request.firstCall.returnValue);
122
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
108
123
  });
109
124
 
110
125
  it('can set allow unmute when DISABLE_HARD_MUTE display hint is available', () => {
@@ -112,11 +127,24 @@ describe('plugin-meetings', () => {
112
127
 
113
128
  const result = manager.setDisallowUnmute(false);
114
129
 
115
- assert.calledWith(request.request, { uri: 'test/id/controls',
130
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
116
131
  body: { disallowUnmute: { enabled: false } },
117
132
  method: HTTP_VERBS.PATCH});
118
133
 
119
- assert.deepEqual(result, request.request.firstCall.returnValue);
134
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
135
+ });
136
+
137
+ it('should send setDisallowUnmute to locusUrl without authorizingLocusUrl when in breakout', () => {
138
+ manager.setDisplayHints(['ENABLE_HARD_MUTE']);
139
+ manager.mainLocusUrl = 'test/main';
140
+
141
+ const result = manager.setDisallowUnmute(true);
142
+
143
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
144
+ body: { disallowUnmute: { enabled: true } },
145
+ method: HTTP_VERBS.PATCH});
146
+
147
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
120
148
  });
121
149
  });
122
150
  });
@@ -127,6 +155,7 @@ describe('plugin-meetings', () => {
127
155
  beforeEach(() => {
128
156
  request = {
129
157
  request: sinon.stub().resolves(),
158
+ locusDeltaRequest: sinon.stub().resolves(),
130
159
  };
131
160
 
132
161
  manager = new ControlsOptionsManager(request);
@@ -138,6 +167,18 @@ describe('plugin-meetings', () => {
138
167
  });
139
168
  });
140
169
 
170
+ it('should reject with ParameterError when locusUrl is not set', () => {
171
+ const noLocusManager = new ControlsOptionsManager(request);
172
+
173
+ const result = noLocusManager.update({scope: 'audio', properties: {muted: true}});
174
+
175
+ assert.notCalled(request.request);
176
+ return assert.isRejected(result).then((err) => {
177
+ assert.instanceOf(err, ParameterError);
178
+ assert.match(err.message, /locusUrl.*must be defined/);
179
+ });
180
+ });
181
+
141
182
  it('should throw an error if the scope is not supported', () => {
142
183
  const scope = 'invalid';
143
184
 
@@ -163,7 +204,7 @@ describe('plugin-meetings', () => {
163
204
 
164
205
  return manager.update(audio, reactions)
165
206
  .then(() => {
166
- assert.calledWith(request.request, {
207
+ assert.calledWith(request.locusDeltaRequest, {
167
208
  uri: 'test/id/controls',
168
209
  body: {
169
210
  audio: audio.properties,
@@ -171,7 +212,7 @@ describe('plugin-meetings', () => {
171
212
  method: HTTP_VERBS.PATCH,
172
213
  });
173
214
 
174
- assert.calledWith(request.request, {
215
+ assert.calledWith(request.locusDeltaRequest, {
175
216
  uri: 'test/id/controls',
176
217
  body: {
177
218
  reactions: reactions.properties,
@@ -203,7 +244,7 @@ describe('plugin-meetings', () => {
203
244
  });
204
245
  });
205
246
 
206
- it('should call request with mainLocusUrl and locusUrl as authorizingLocusUrl if mainLocusUrl is exist and not same with locusUrl', () => {
247
+ it('should send audio controls to locusUrl without authorizingLocusUrl and non-audio to mainLocusUrl with authorizingLocusUrl when in breakout', () => {
207
248
  const restorable = Util.canUpdate;
208
249
  Util.canUpdate = sinon.stub().returns(true);
209
250
  manager.mainLocusUrl = 'test/main';
@@ -213,20 +254,64 @@ describe('plugin-meetings', () => {
213
254
 
214
255
  return manager.update(audio, reactions)
215
256
  .then(() => {
257
+ // Audio controls go directly to current locusUrl (no cross-locus authorization)
258
+ assert.calledWith(request.locusDeltaRequest, {
259
+ uri: 'test/id/controls',
260
+ body: {
261
+ audio: audio.properties,
262
+ },
263
+ method: HTTP_VERBS.PATCH,
264
+ });
265
+
266
+ // Non-audio controls go to mainLocusUrl with authorizingLocusUrl
216
267
  assert.calledWith(request.request, {
217
268
  uri: 'test/main/controls',
218
269
  body: {
219
- audio: audio.properties,
270
+ reactions: reactions.properties,
220
271
  authorizingLocusUrl: 'test/id'
221
272
  },
222
273
  method: HTTP_VERBS.PATCH,
223
274
  });
224
275
 
276
+ Util.canUpdate = restorable;
277
+ });
278
+ });
279
+
280
+ it('should send audio controls to locusUrl without authorizingLocusUrl when in breakout', () => {
281
+ const restorable = Util.canUpdate;
282
+ Util.canUpdate = sinon.stub().returns(true);
283
+ manager.mainLocusUrl = 'test/main';
284
+
285
+ const audio = {scope: 'audio', properties: {muted: true, disallowUnmute: false}};
286
+
287
+ return manager.update(audio)
288
+ .then(() => {
289
+ assert.calledWith(request.locusDeltaRequest, {
290
+ uri: 'test/id/controls',
291
+ body: {
292
+ audio: audio.properties,
293
+ },
294
+ method: HTTP_VERBS.PATCH,
295
+ });
296
+
297
+ Util.canUpdate = restorable;
298
+ });
299
+ });
300
+
301
+ it('should send non-audio controls to mainLocusUrl with authorizingLocusUrl when in breakout', () => {
302
+ const restorable = Util.canUpdate;
303
+ Util.canUpdate = sinon.stub().returns(true);
304
+ manager.mainLocusUrl = 'test/main';
305
+
306
+ const reactions = {scope: 'reactions', properties: {enabled: true}};
307
+
308
+ return manager.update(reactions)
309
+ .then(() => {
225
310
  assert.calledWith(request.request, {
226
311
  uri: 'test/main/controls',
227
312
  body: {
228
313
  reactions: reactions.properties,
229
- authorizingLocusUrl: 'test/id'
314
+ authorizingLocusUrl: 'test/id',
230
315
  },
231
316
  method: HTTP_VERBS.PATCH,
232
317
  });
@@ -241,6 +326,7 @@ describe('plugin-meetings', () => {
241
326
  beforeEach(() => {
242
327
  request = {
243
328
  request: sinon.stub().returns(Promise.resolve()),
329
+ locusDeltaRequest: sinon.stub().returns(Promise.resolve()),
244
330
  };
245
331
 
246
332
  manager = new ControlsOptionsManager(request);
@@ -252,6 +338,18 @@ describe('plugin-meetings', () => {
252
338
  })
253
339
  });
254
340
 
341
+ it('should reject with ParameterError when locusUrl is not set', () => {
342
+ const noLocusManager = new ControlsOptionsManager(request);
343
+
344
+ const result = noLocusManager.setMuteAll(true, true, true);
345
+
346
+ assert.notCalled(request.request);
347
+ return assert.isRejected(result).then((err) => {
348
+ assert.instanceOf(err, ParameterError);
349
+ assert.match(err.message, /locusUrl.*must be defined/);
350
+ });
351
+ });
352
+
255
353
  it('rejects when correct display hint is not present mutedEnabled=false', () => {
256
354
  const result = manager.setMuteAll(false, false, false);
257
355
 
@@ -273,11 +371,11 @@ describe('plugin-meetings', () => {
273
371
 
274
372
  const result = manager.setMuteAll(true, true, true);
275
373
 
276
- assert.calledWith(request.request, { uri: 'test/id/controls',
374
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
277
375
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
278
376
  method: HTTP_VERBS.PATCH});
279
377
 
280
- assert.deepEqual(result, request.request.firstCall.returnValue);
378
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
281
379
  });
282
380
 
283
381
  it('can set mute all when the display hint is available mutedEnabled=true', () => {
@@ -285,11 +383,11 @@ describe('plugin-meetings', () => {
285
383
 
286
384
  const result = manager.setMuteAll(true, true, true);
287
385
 
288
- assert.calledWith(request.request, { uri: 'test/id/controls',
386
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
289
387
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
290
388
  method: HTTP_VERBS.PATCH});
291
389
 
292
- assert.deepEqual(result, request.request.firstCall.returnValue);
390
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
293
391
  });
294
392
 
295
393
  it('can set mute all when the display hint is available mutedEnabled=true', () => {
@@ -297,11 +395,11 @@ describe('plugin-meetings', () => {
297
395
 
298
396
  const result = manager.setMuteAll(true, true, true);
299
397
 
300
- assert.calledWith(request.request, { uri: 'test/id/controls',
398
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
301
399
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
302
400
  method: HTTP_VERBS.PATCH});
303
401
 
304
- assert.deepEqual(result, request.request.firstCall.returnValue);
402
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
305
403
  });
306
404
 
307
405
  it('can set mute all when the display hint is available mutedEnabled=false', () => {
@@ -309,11 +407,11 @@ describe('plugin-meetings', () => {
309
407
 
310
408
  const result = manager.setMuteAll(false, false, false);
311
409
 
312
- assert.calledWith(request.request, { uri: 'test/id/controls',
410
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
313
411
  body: { audio: { muted: false, disallowUnmute: false, muteOnEntry: false } },
314
412
  method: HTTP_VERBS.PATCH});
315
413
 
316
- assert.deepEqual(result, request.request.firstCall.returnValue);
414
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
317
415
  });
318
416
 
319
417
  it('can set mute all panelists when the display hint is available mutedEnabled=true', () => {
@@ -321,11 +419,11 @@ describe('plugin-meetings', () => {
321
419
 
322
420
  const result = manager.setMuteAll(true, true, true, ['panelist']);
323
421
 
324
- assert.calledWith(request.request, { uri: 'test/id/controls',
422
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
325
423
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['panelist'] } },
326
424
  method: HTTP_VERBS.PATCH});
327
425
 
328
- assert.deepEqual(result, request.request.firstCall.returnValue);
426
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
329
427
  });
330
428
 
331
429
  it('can set mute all attendees when the display hint is available mutedEnabled=true', () => {
@@ -333,24 +431,37 @@ describe('plugin-meetings', () => {
333
431
 
334
432
  const result = manager.setMuteAll(true, true, true, ['attendee']);
335
433
 
336
- assert.calledWith(request.request, { uri: 'test/id/controls',
434
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
337
435
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] } },
338
436
  method: HTTP_VERBS.PATCH});
339
437
 
340
- assert.deepEqual(result, request.request.firstCall.returnValue);
438
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
341
439
  });
342
440
 
343
- it('request with mainLocusUrl and make locusUrl as authorizingLocusUrl if mainLocusUrl is exist and not same with locusUrl', () => {
441
+ it('should send setMuteAll to locusUrl without authorizingLocusUrl when in breakout', () => {
344
442
  manager.setDisplayHints(['MUTE_ALL', 'DISABLE_HARD_MUTE', 'DISABLE_MUTE_ON_ENTRY']);
345
443
  manager.mainLocusUrl = `test/main`;
346
444
 
347
445
  const result = manager.setMuteAll(true, true, true, ['attendee']);
348
446
 
349
- assert.calledWith(request.request, { uri: 'test/main/controls',
350
- body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] }, authorizingLocusUrl: 'test/id' },
447
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
448
+ body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] } },
449
+ method: HTTP_VERBS.PATCH});
450
+
451
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
452
+ });
453
+
454
+ it('should send setMuteAll with PANELIST role to locusUrl without authorizingLocusUrl when in breakout', () => {
455
+ manager.setDisplayHints(['MUTE_ALL', 'ENABLE_HARD_MUTE', 'ENABLE_MUTE_ON_ENTRY']);
456
+ manager.mainLocusUrl = `test/main`;
457
+
458
+ const result = manager.setMuteAll(true, true, true, ['PANELIST']);
459
+
460
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
461
+ body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['PANELIST'] } },
351
462
  method: HTTP_VERBS.PATCH});
352
463
 
353
- assert.deepEqual(result, request.request.firstCall.returnValue);
464
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
354
465
  });
355
466
  });
356
467
  });