@webex/plugin-meetings 3.12.0-next.9 → 3.12.0-task-refactor.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/dist/annotation/index.js +5 -14
  2. package/dist/annotation/index.js.map +1 -1
  3. package/dist/breakouts/breakout.js +1 -1
  4. package/dist/breakouts/index.js +1 -1
  5. package/dist/config.js +2 -8
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +6 -29
  8. package/dist/constants.js.map +1 -1
  9. package/dist/hashTree/hashTreeParser.js +29 -1563
  10. package/dist/hashTree/hashTreeParser.js.map +1 -1
  11. package/dist/hashTree/types.js +3 -13
  12. package/dist/hashTree/types.js.map +1 -1
  13. package/dist/index.js +2 -11
  14. package/dist/index.js.map +1 -1
  15. package/dist/interceptors/index.js +0 -7
  16. package/dist/interceptors/index.js.map +1 -1
  17. package/dist/interceptors/locusRouteToken.js +5 -27
  18. package/dist/interceptors/locusRouteToken.js.map +1 -1
  19. package/dist/interpretation/index.js +2 -2
  20. package/dist/interpretation/index.js.map +1 -1
  21. package/dist/interpretation/siLanguage.js +1 -1
  22. package/dist/locus-info/controlsUtils.js +3 -7
  23. package/dist/locus-info/controlsUtils.js.map +1 -1
  24. package/dist/locus-info/index.js +247 -642
  25. package/dist/locus-info/index.js.map +1 -1
  26. package/dist/locus-info/selfUtils.js +0 -1
  27. package/dist/locus-info/selfUtils.js.map +1 -1
  28. package/dist/locus-info/types.js.map +1 -1
  29. package/dist/media/MediaConnectionAwaiter.js +1 -57
  30. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  31. package/dist/media/properties.js +2 -4
  32. package/dist/media/properties.js.map +1 -1
  33. package/dist/meeting/in-meeting-actions.js +1 -7
  34. package/dist/meeting/in-meeting-actions.js.map +1 -1
  35. package/dist/meeting/index.js +1036 -1481
  36. package/dist/meeting/index.js.map +1 -1
  37. package/dist/meeting/request.js +0 -50
  38. package/dist/meeting/request.js.map +1 -1
  39. package/dist/meeting/request.type.js.map +1 -1
  40. package/dist/meeting/util.js +3 -133
  41. package/dist/meeting/util.js.map +1 -1
  42. package/dist/meetings/index.js +59 -142
  43. package/dist/meetings/index.js.map +1 -1
  44. package/dist/meetings/util.js +7 -11
  45. package/dist/meetings/util.js.map +1 -1
  46. package/dist/member/index.js +0 -10
  47. package/dist/member/index.js.map +1 -1
  48. package/dist/member/util.js +0 -10
  49. package/dist/member/util.js.map +1 -1
  50. package/dist/metrics/constants.js +1 -7
  51. package/dist/metrics/constants.js.map +1 -1
  52. package/dist/multistream/mediaRequestManager.js +60 -9
  53. package/dist/multistream/mediaRequestManager.js.map +1 -1
  54. package/dist/multistream/remoteMediaManager.js +0 -11
  55. package/dist/multistream/remoteMediaManager.js.map +1 -1
  56. package/dist/multistream/sendSlotManager.js +2 -116
  57. package/dist/multistream/sendSlotManager.js.map +1 -1
  58. package/dist/reachability/clusterReachability.js +18 -171
  59. package/dist/reachability/clusterReachability.js.map +1 -1
  60. package/dist/reachability/index.js +11 -21
  61. package/dist/reachability/index.js.map +1 -1
  62. package/dist/reachability/reachabilityPeerConnection.js +1 -1
  63. package/dist/reachability/reachabilityPeerConnection.js.map +1 -1
  64. package/dist/reactions/reactions.type.js.map +1 -1
  65. package/dist/reconnection-manager/index.js +1 -0
  66. package/dist/reconnection-manager/index.js.map +1 -1
  67. package/dist/types/common/browser-detection.d.ts +0 -1
  68. package/dist/types/common/events/events-scope.d.ts +0 -1
  69. package/dist/types/common/events/events.d.ts +0 -1
  70. package/dist/types/config.d.ts +0 -5
  71. package/dist/types/constants.d.ts +1 -24
  72. package/dist/types/hashTree/hashTreeParser.d.ts +11 -260
  73. package/dist/types/hashTree/types.d.ts +0 -20
  74. package/dist/types/index.d.ts +0 -1
  75. package/dist/types/interceptors/index.d.ts +1 -2
  76. package/dist/types/interceptors/locusRouteToken.d.ts +0 -2
  77. package/dist/types/locus-info/index.d.ts +47 -68
  78. package/dist/types/locus-info/types.d.ts +12 -28
  79. package/dist/types/media/MediaConnectionAwaiter.d.ts +1 -10
  80. package/dist/types/media/properties.d.ts +1 -2
  81. package/dist/types/meeting/in-meeting-actions.d.ts +0 -6
  82. package/dist/types/meeting/index.d.ts +7 -86
  83. package/dist/types/meeting/request.d.ts +1 -16
  84. package/dist/types/meeting/request.type.d.ts +0 -5
  85. package/dist/types/meeting/util.d.ts +0 -31
  86. package/dist/types/meeting-info/util.d.ts +0 -1
  87. package/dist/types/meeting-info/utilv2.d.ts +0 -1
  88. package/dist/types/meetings/index.d.ts +2 -4
  89. package/dist/types/member/index.d.ts +0 -1
  90. package/dist/types/member/types.d.ts +4 -4
  91. package/dist/types/member/util.d.ts +0 -5
  92. package/dist/types/metrics/constants.d.ts +0 -6
  93. package/dist/types/multistream/mediaRequestManager.d.ts +23 -0
  94. package/dist/types/multistream/sendSlotManager.d.ts +1 -23
  95. package/dist/types/reachability/clusterReachability.d.ts +3 -30
  96. package/dist/types/reactions/reactions.type.d.ts +0 -1
  97. package/dist/types/recording-controller/util.d.ts +5 -5
  98. package/dist/types/roap/index.d.ts +1 -1
  99. package/dist/webinar/index.js +163 -438
  100. package/dist/webinar/index.js.map +1 -1
  101. package/package.json +24 -26
  102. package/src/annotation/index.ts +7 -27
  103. package/src/config.ts +0 -5
  104. package/src/constants.ts +1 -30
  105. package/src/hashTree/hashTreeParser.ts +25 -1523
  106. package/src/hashTree/types.ts +1 -24
  107. package/src/index.ts +1 -8
  108. package/src/interceptors/index.ts +1 -2
  109. package/src/interceptors/locusRouteToken.ts +5 -22
  110. package/src/interpretation/index.ts +2 -2
  111. package/src/locus-info/controlsUtils.ts +0 -17
  112. package/src/locus-info/index.ts +213 -707
  113. package/src/locus-info/selfUtils.ts +0 -1
  114. package/src/locus-info/types.ts +12 -27
  115. package/src/media/MediaConnectionAwaiter.ts +1 -41
  116. package/src/media/properties.ts +1 -3
  117. package/src/meeting/in-meeting-actions.ts +0 -12
  118. package/src/meeting/index.ts +84 -461
  119. package/src/meeting/request.ts +0 -42
  120. package/src/meeting/request.type.ts +0 -6
  121. package/src/meeting/util.ts +2 -160
  122. package/src/meetings/index.ts +60 -180
  123. package/src/meetings/util.ts +9 -10
  124. package/src/member/index.ts +0 -10
  125. package/src/member/util.ts +0 -12
  126. package/src/metrics/constants.ts +0 -7
  127. package/src/multistream/mediaRequestManager.ts +54 -4
  128. package/src/multistream/remoteMediaManager.ts +0 -13
  129. package/src/multistream/sendSlotManager.ts +3 -97
  130. package/src/reachability/clusterReachability.ts +27 -153
  131. package/src/reachability/index.ts +1 -15
  132. package/src/reachability/reachabilityPeerConnection.ts +1 -3
  133. package/src/reactions/reactions.type.ts +0 -1
  134. package/src/reconnection-manager/index.ts +1 -0
  135. package/src/webinar/index.ts +6 -265
  136. package/test/unit/spec/annotation/index.ts +7 -69
  137. package/test/unit/spec/interceptors/locusRouteToken.ts +0 -44
  138. package/test/unit/spec/locus-info/controlsUtils.js +1 -56
  139. package/test/unit/spec/locus-info/index.js +90 -1457
  140. package/test/unit/spec/media/MediaConnectionAwaiter.ts +1 -41
  141. package/test/unit/spec/media/properties.ts +3 -12
  142. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -8
  143. package/test/unit/spec/meeting/index.js +128 -981
  144. package/test/unit/spec/meeting/request.js +0 -70
  145. package/test/unit/spec/meeting/utils.js +26 -438
  146. package/test/unit/spec/meetings/index.js +33 -845
  147. package/test/unit/spec/meetings/utils.js +1 -51
  148. package/test/unit/spec/member/index.js +4 -28
  149. package/test/unit/spec/member/util.js +27 -65
  150. package/test/unit/spec/multistream/mediaRequestManager.ts +85 -2
  151. package/test/unit/spec/multistream/remoteMediaManager.ts +0 -30
  152. package/test/unit/spec/multistream/sendSlotManager.ts +36 -135
  153. package/test/unit/spec/reachability/clusterReachability.ts +1 -125
  154. package/test/unit/spec/reachability/index.ts +3 -26
  155. package/test/unit/spec/reconnection-manager/index.js +8 -4
  156. package/test/unit/spec/webinar/index.ts +37 -534
  157. package/dist/aiEnableRequest/index.js +0 -184
  158. package/dist/aiEnableRequest/index.js.map +0 -1
  159. package/dist/aiEnableRequest/utils.js +0 -36
  160. package/dist/aiEnableRequest/utils.js.map +0 -1
  161. package/dist/hashTree/constants.js +0 -22
  162. package/dist/hashTree/constants.js.map +0 -1
  163. package/dist/hashTree/hashTree.js +0 -533
  164. package/dist/hashTree/hashTree.js.map +0 -1
  165. package/dist/hashTree/utils.js +0 -69
  166. package/dist/hashTree/utils.js.map +0 -1
  167. package/dist/interceptors/constant.js +0 -12
  168. package/dist/interceptors/constant.js.map +0 -1
  169. package/dist/interceptors/dataChannelAuthToken.js +0 -290
  170. package/dist/interceptors/dataChannelAuthToken.js.map +0 -1
  171. package/dist/interceptors/utils.js +0 -27
  172. package/dist/interceptors/utils.js.map +0 -1
  173. package/dist/types/aiEnableRequest/index.d.ts +0 -5
  174. package/dist/types/aiEnableRequest/utils.d.ts +0 -2
  175. package/dist/types/hashTree/constants.d.ts +0 -9
  176. package/dist/types/hashTree/hashTree.d.ts +0 -136
  177. package/dist/types/hashTree/utils.d.ts +0 -22
  178. package/dist/types/interceptors/constant.d.ts +0 -5
  179. package/dist/types/interceptors/dataChannelAuthToken.d.ts +0 -43
  180. package/dist/types/interceptors/utils.d.ts +0 -1
  181. package/dist/types/webinar/utils.d.ts +0 -6
  182. package/dist/webinar/utils.js +0 -25
  183. package/dist/webinar/utils.js.map +0 -1
  184. package/src/aiEnableRequest/README.md +0 -84
  185. package/src/aiEnableRequest/index.ts +0 -170
  186. package/src/aiEnableRequest/utils.ts +0 -25
  187. package/src/hashTree/constants.ts +0 -10
  188. package/src/hashTree/hashTree.ts +0 -480
  189. package/src/hashTree/utils.ts +0 -62
  190. package/src/interceptors/constant.ts +0 -6
  191. package/src/interceptors/dataChannelAuthToken.ts +0 -170
  192. package/src/interceptors/utils.ts +0 -16
  193. package/src/webinar/utils.ts +0 -16
  194. package/test/unit/spec/aiEnableRequest/index.ts +0 -981
  195. package/test/unit/spec/aiEnableRequest/utils.ts +0 -130
  196. package/test/unit/spec/hashTree/hashTree.ts +0 -721
  197. package/test/unit/spec/hashTree/hashTreeParser.ts +0 -3670
  198. package/test/unit/spec/hashTree/utils.ts +0 -140
  199. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +0 -210
  200. package/test/unit/spec/interceptors/utils.ts +0 -75
  201. package/test/unit/spec/webinar/utils.ts +0 -39
@@ -1,11 +1,9 @@
1
- import {assert} from '@webex/test-helper-chai';
1
+ import {assert, expect} from '@webex/test-helper-chai';
2
2
  import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
3
3
  import Webinar from '@webex/plugin-meetings/src/webinar';
4
4
  import MockWebex from '@webex/test-helper-mock-webex';
5
5
  import uuid from 'uuid';
6
6
  import sinon from 'sinon';
7
- import {DataChannelTokenType} from '@webex/internal-plugin-llm';
8
- import {LLM_PRACTICE_SESSION, SHARE_STATUS} from '@webex/plugin-meetings/src/constants';
9
7
 
10
8
  describe('plugin-meetings', () => {
11
9
  describe('Webinar', () => {
@@ -28,20 +26,7 @@ describe('plugin-meetings', () => {
28
26
  webex.meetings = {};
29
27
  webex.credentials.getUserToken = getUserTokenStub;
30
28
  webex.meetings.getMeetingByType = sinon.stub();
31
- webex.internal.voicea.announce = sinon.stub();
32
-
33
- webex.internal.llm = {
34
- getDatachannelToken: sinon.stub().returns(undefined),
35
- setDatachannelToken: sinon.stub(),
36
- isDataChannelTokenEnabled: sinon.stub().resolves(false),
37
- isConnected: sinon.stub().returns(false),
38
- disconnectLLM: sinon.stub().resolves(),
39
- off: sinon.stub(),
40
- on: sinon.stub(),
41
- getLocusUrl: sinon.stub().returns('old-locus-url'),
42
- getDatachannelUrl: sinon.stub().returns('old-dc-url'),
43
- registerAndConnect: sinon.stub().resolves('REGISTER_AND_CONNECT_RESULT'),
44
- };
29
+
45
30
  });
46
31
 
47
32
  afterEach(() => {
@@ -162,399 +147,20 @@ describe('plugin-meetings', () => {
162
147
  assert.equal(result.isPromoted, false, 'should not indicate promotion');
163
148
  assert.equal(result.isDemoted, false, 'should not indicate demotion');
164
149
  });
165
-
166
- it('handles missing role payload safely', () => {
167
- const updateStatusByRoleStub = sinon.stub(webinar, 'updateStatusByRole');
168
-
169
- const result = webinar.updateRoleChanged(undefined);
170
-
171
- assert.equal(webinar.selfIsPanelist, false);
172
- assert.equal(webinar.selfIsAttendee, false);
173
- assert.equal(webinar.canManageWebcast, false);
174
- assert.deepEqual(result, {isPromoted: false, isDemoted: false});
175
- assert.calledOnceWithExactly(updateStatusByRoleStub, {isPromoted: false, isDemoted: false});
176
- });
177
- });
178
-
179
- describe('#cleanUp', () => {
180
- it('delegates to cleanupPSDataChannel', () => {
181
- const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
182
-
183
- webinar.cleanUp();
184
-
185
- assert.calledOnceWithExactly(cleanupPSDataChannelStub);
186
- });
187
- });
188
-
189
- describe('#cleanupPSDataChannel', () => {
190
- let meeting;
191
-
192
- beforeEach(() => {
193
- meeting = {
194
- processRelayEvent: sinon.stub(),
195
- };
196
-
197
- webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
198
- });
199
-
200
- it('disconnects the practice session channel and removes the relay listener', async () => {
201
- await webinar.cleanupPSDataChannel();
202
-
203
- assert.calledOnceWithExactly(
204
- webex.internal.llm.disconnectLLM,
205
- {code: 3050, reason: 'done (permanent)'},
206
- LLM_PRACTICE_SESSION
207
- );
208
- assert.calledOnceWithExactly(
209
- webex.internal.llm.off,
210
- `event:relay.event:${LLM_PRACTICE_SESSION}`,
211
- meeting.processRelayEvent
212
- );
213
- });
214
-
215
- it('removes a pending online listener if one exists', async () => {
216
- const listener = sinon.stub();
217
- webinar._pendingOnlineListener = listener;
218
-
219
- await webinar.cleanupPSDataChannel();
220
-
221
- assert.calledWith(webex.internal.llm.off, 'online', listener);
222
- assert.isNull(webinar._pendingOnlineListener);
223
- });
224
-
225
- it('skips online listener removal when none is pending', async () => {
226
- webinar._pendingOnlineListener = null;
227
-
228
- await webinar.cleanupPSDataChannel();
229
-
230
- // 'off' should only be called for the relay event, not for 'online'
231
- const onlineOffCalls = webex.internal.llm.off.args.filter(([event]) => event === 'online');
232
- assert.equal(onlineOffCalls.length, 0);
233
- });
234
- });
235
-
236
- describe('#updatePSDataChannel', () => {
237
- let meeting;
238
- let processRelayEvent;
239
-
240
- beforeEach(() => {
241
- processRelayEvent = sinon.stub();
242
- meeting = {
243
- isJoined: sinon.stub().returns(true),
244
- processRelayEvent,
245
- locusInfo: {
246
- url: 'locus-url',
247
- info: {practiceSessionDatachannelUrl: 'dc-url'},
248
- },
249
- };
250
-
251
- webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
252
-
253
- // Default session is connected by default; practice session is not
254
- webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
255
- return sessionId !== LLM_PRACTICE_SESSION;
256
- });
257
-
258
- // Token is pre-saved into LLM by saveDataChannelToken
259
- webex.internal.llm.getDatachannelToken = sinon.stub().callsFake((tokenType) => {
260
- if (tokenType === DataChannelTokenType.PracticeSession) return 'ps-token';
261
- return undefined;
262
- });
263
-
264
- // Ensure connect path is eligible
265
- webinar.selfIsPanelist = true;
266
- webinar.practiceSessionEnabled = true;
267
- webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
268
- webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
269
- });
270
-
271
- it('refreshes practice-session token before register when cached token is missing', async () => {
272
- webex.internal.llm.isDataChannelTokenEnabled.resolves(true);
273
- webex.internal.llm.getDatachannelToken = sinon.stub().callsFake((tokenType) => {
274
- if (tokenType === DataChannelTokenType.PracticeSession) return undefined;
275
-
276
- return undefined;
277
- });
278
- meeting.refreshDataChannelToken = sinon.stub().resolves({
279
- body: {
280
- datachannelToken: 'ps-token-from-refresh',
281
- dataChannelTokenType: DataChannelTokenType.PracticeSession,
282
- },
283
- });
284
-
285
- await webinar.updatePSDataChannel();
286
-
287
- assert.calledOnceWithExactly(meeting.refreshDataChannelToken);
288
- assert.calledWithExactly(
289
- webex.internal.llm.setDatachannelToken,
290
- 'ps-token-from-refresh',
291
- DataChannelTokenType.PracticeSession
292
- );
293
- assert.calledWith(
294
- webex.internal.llm.registerAndConnect,
295
- 'locus-url',
296
- 'dc-url',
297
- 'ps-token-from-refresh',
298
- LLM_PRACTICE_SESSION
299
- );
300
- });
301
-
302
- it('does not reconnect if practice-session eligibility changes during async token refresh', async () => {
303
- webex.internal.llm.isDataChannelTokenEnabled.resolves(true);
304
- webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
305
-
306
- let resolveRefresh;
307
- meeting.refreshDataChannelToken = sinon.stub().returns(
308
- new Promise((resolve) => {
309
- resolveRefresh = resolve;
310
- })
311
- );
312
-
313
- const updatePromise = webinar.updatePSDataChannel();
314
-
315
- webinar.practiceSessionEnabled = false;
316
-
317
- resolveRefresh({
318
- body: {
319
- datachannelToken: 'stale-ps-token',
320
- dataChannelTokenType: DataChannelTokenType.PracticeSession,
321
- },
322
- });
323
-
324
- const result = await updatePromise;
325
-
326
- assert.isUndefined(result);
327
- assert.notCalled(webex.internal.llm.registerAndConnect);
328
- });
329
-
330
- it('no-ops when practice session join eligibility is false', async () => {
331
- webinar.practiceSessionEnabled = false;
332
- const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
333
-
334
- const result = await webinar.updatePSDataChannel();
335
-
336
- assert.isUndefined(result);
337
- assert.calledOnceWithExactly(cleanupPSDataChannelStub);
338
- assert.notCalled(webex.internal.llm.registerAndConnect);
339
- });
340
-
341
- it('no-ops when meeting is not joined', async () => {
342
- meeting.isJoined.returns(false);
343
- const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
344
-
345
- const result = await webinar.updatePSDataChannel();
346
-
347
- assert.isUndefined(result);
348
- assert.calledOnceWithExactly(cleanupPSDataChannelStub);
349
- assert.notCalled(webex.internal.llm.registerAndConnect);
350
- });
351
-
352
- it('no-ops when practiceSessionDatachannelUrl is missing', async () => {
353
- meeting.locusInfo.info.practiceSessionDatachannelUrl = undefined;
354
-
355
- const result = await webinar.updatePSDataChannel();
356
-
357
- assert.isUndefined(result);
358
- assert.notCalled(webex.internal.llm.registerAndConnect);
359
- });
360
-
361
- it('no-ops when already connected to the same endpoints', async () => {
362
- webex.internal.llm.isConnected.returns(true);
363
- webex.internal.llm.getLocusUrl.returns('locus-url');
364
- webex.internal.llm.getDatachannelUrl.returns('dc-url');
365
- const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
366
-
367
- const result = await webinar.updatePSDataChannel();
368
-
369
- assert.isUndefined(result);
370
- assert.notCalled(cleanupPSDataChannelStub);
371
- assert.notCalled(webex.internal.llm.registerAndConnect);
372
- });
373
-
374
- it('connects when eligible', async () => {
375
- const result = await webinar.updatePSDataChannel();
376
-
377
- assert.calledOnce(webex.internal.llm.registerAndConnect);
378
- assert.calledWith(
379
- webex.internal.llm.registerAndConnect,
380
- 'locus-url',
381
- 'dc-url',
382
- 'ps-token',
383
- LLM_PRACTICE_SESSION
384
- );
385
- assert.calledOnceWithExactly(webex.internal.voicea.announce);
386
- assert.equal(result, 'REGISTER_AND_CONNECT_RESULT');
387
- });
388
-
389
- it('uses token from LLM', async () => {
390
- webex.internal.llm.getDatachannelToken = sinon.stub().callsFake((tokenType) => {
391
- if (tokenType === DataChannelTokenType.PracticeSession) return 'cached-token';
392
- return undefined;
393
- });
394
-
395
- await webinar.updatePSDataChannel();
396
-
397
- assert.calledWithExactly(
398
- webex.internal.llm.getDatachannelToken,
399
- DataChannelTokenType.PracticeSession
400
- );
401
- assert.notCalled(webex.internal.llm.setDatachannelToken);
402
- assert.calledWith(
403
- webex.internal.llm.registerAndConnect,
404
- 'locus-url',
405
- 'dc-url',
406
- 'cached-token',
407
- LLM_PRACTICE_SESSION
408
- );
409
- });
410
-
411
- it('cleans up the existing practice session channel before reconnecting to new endpoints', async () => {
412
- webex.internal.llm.isConnected.returns(true);
413
- const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
414
-
415
- await webinar.updatePSDataChannel();
416
-
417
- assert.calledOnceWithExactly(cleanupPSDataChannelStub);
418
- assert.calledOnce(webex.internal.llm.registerAndConnect);
419
- });
420
-
421
- it('rebinds relay listener after successful connect', async () => {
422
- await webinar.updatePSDataChannel();
423
-
424
- assert.calledWith(
425
- webex.internal.llm.off,
426
- `event:relay.event:${LLM_PRACTICE_SESSION}`,
427
- processRelayEvent
428
- );
429
- assert.calledWith(
430
- webex.internal.llm.on,
431
- `event:relay.event:${LLM_PRACTICE_SESSION}`,
432
- processRelayEvent
433
- );
434
- });
435
-
436
- it('subscribes to transcription when caption intent is enabled', async () => {
437
- webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(true);
438
-
439
- await webinar.updatePSDataChannel();
440
-
441
- assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, { subscribe: ['transcription'] });
442
- });
443
-
444
- it('does not subscribe to transcription when caption intent is disabled', async () => {
445
- webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
446
-
447
- await webinar.updatePSDataChannel();
448
-
449
- assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
450
- });
451
-
452
- it('defers connect when default session is not yet connected', async () => {
453
- // Default session is not connected initially
454
- webex.internal.llm.isConnected = sinon.stub().returns(false);
455
-
456
- const result = await webinar.updatePSDataChannel();
457
-
458
- // Should return undefined immediately (deferred)
459
- assert.isUndefined(result);
460
- // Should register an 'online' listener but NOT call registerAndConnect yet
461
- assert.calledWith(webex.internal.llm.on, 'online', sinon.match.func);
462
- assert.notCalled(webex.internal.llm.registerAndConnect);
463
- // Should store the pending listener
464
- assert.isNotNull(webinar._pendingOnlineListener);
465
- });
466
-
467
- it('does not register duplicate online listeners on repeated calls', async () => {
468
- webex.internal.llm.isConnected = sinon.stub().returns(false);
469
-
470
- await webinar.updatePSDataChannel();
471
- await webinar.updatePSDataChannel();
472
- await webinar.updatePSDataChannel();
473
-
474
- // Only one 'online' listener should have been registered
475
- const onlineCalls = webex.internal.llm.on.args.filter(([event]) => event === 'online');
476
- assert.equal(onlineCalls.length, 1, 'should register exactly one online listener');
477
- });
478
-
479
- it('re-invokes updatePSDataChannel when default session comes online', async () => {
480
- // Default session is not connected initially
481
- webex.internal.llm.isConnected = sinon.stub().returns(false);
482
-
483
- const updatePSDataChannelSpy = sinon.spy(webinar, 'updatePSDataChannel');
484
-
485
- // First call defers
486
- await webinar.updatePSDataChannel();
487
-
488
- // Capture the 'online' listener
489
- const onlineCall = webex.internal.llm.on.args.find(([event]) => event === 'online');
490
- assert.isDefined(onlineCall, 'should have registered an online listener');
491
-
492
- // Now simulate default session coming online
493
- webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
494
- return sessionId !== LLM_PRACTICE_SESSION;
495
- });
496
-
497
- // Fire the captured listener
498
- onlineCall[1]();
499
-
500
- // The listener should have cleared itself, removed itself, and re-called updatePSDataChannel
501
- assert.isNull(webinar._pendingOnlineListener);
502
- assert.calledWith(webex.internal.llm.off, 'online', sinon.match.func);
503
- assert.equal(updatePSDataChannelSpy.callCount, 2);
504
- });
505
-
506
- it('does not reconnect with stale data if demoted before default session comes online', async () => {
507
- // Default session is not connected initially
508
- webex.internal.llm.isConnected = sinon.stub().returns(false);
509
-
510
- await webinar.updatePSDataChannel();
511
-
512
- // Capture the 'online' listener
513
- const onlineCall = webex.internal.llm.on.args.find(([event]) => event === 'online');
514
- assert.isDefined(onlineCall);
515
-
516
- // Simulate demotion while waiting
517
- webinar.selfIsPanelist = false;
518
-
519
- // Now default session comes online
520
- webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
521
- return sessionId !== LLM_PRACTICE_SESSION;
522
- });
523
-
524
- // Fire the listener — re-invokes updatePSDataChannel which will see isPracticeSession = false
525
- onlineCall[1]();
526
-
527
- // Should NOT have called registerAndConnect since the user is no longer eligible
528
- assert.notCalled(webex.internal.llm.registerAndConnect);
529
- });
530
-
531
- it('proceeds immediately when default session is already connected', async () => {
532
- // Default session already connected, practice session not
533
- webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
534
- return sessionId !== LLM_PRACTICE_SESSION;
535
- });
536
-
537
- const result = await webinar.updatePSDataChannel();
538
-
539
- // The 'online' listener is registered then immediately removed since default session is already connected
540
- assert.calledWith(webex.internal.llm.on, 'online', sinon.match.func);
541
- assert.calledWith(webex.internal.llm.off, 'online', sinon.match.func);
542
- assert.isNull(webinar._pendingOnlineListener);
543
- assert.calledOnce(webex.internal.llm.registerAndConnect);
544
- assert.equal(result, 'REGISTER_AND_CONNECT_RESULT');
545
- });
546
150
  });
547
151
 
548
152
  describe('#updateStatusByRole', () => {
153
+ let updateLLMConnection;
549
154
  let updateMediaShares;
550
155
  beforeEach(() => {
156
+ // @ts-ignore
157
+ updateLLMConnection = sinon.stub();
551
158
  updateMediaShares = sinon.stub()
552
159
  webinar.webex.meetings = {
553
160
  getMeetingByType: sinon.stub().returns({
554
161
  id: 'meeting-id',
555
- isJoined: sinon.stub().returns(false),
556
- updateLLMConnection: sinon.stub(),
557
- shareStatus: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE,
162
+ updateLLMConnection: updateLLMConnection,
163
+ shareStatus: 'whiteboard_share_active',
558
164
  locusInfo: {
559
165
  mediaShares: 'mediaShares',
560
166
  updateMediaShares: updateMediaShares
@@ -567,20 +173,40 @@ describe('plugin-meetings', () => {
567
173
  sinon.restore();
568
174
  });
569
175
 
176
+ it('trigger updateLLMConnection if PS started', () => {
177
+
178
+ webinar.practiceSessionEnabled = true;
179
+ const roleChange = {isPromoted: true, isDemoted: false};
180
+
181
+ const result = webinar.updateStatusByRole(roleChange);
182
+
183
+ assert.calledOnce(updateLLMConnection);
184
+ });
185
+
186
+ it('Not trigger updateLLMConnection if PS not started', () => {
187
+
188
+ webinar.practiceSessionEnabled = false;
189
+ const roleChange = {isPromoted: true, isDemoted: false};
190
+
191
+ const result = webinar.updateStatusByRole(roleChange);
192
+
193
+ assert.notCalled(updateLLMConnection);
194
+ });
195
+
570
196
  it('trigger updateMediaShares if promoted', () => {
571
197
 
572
198
  const roleChange = {isPromoted: true, isDemoted: false};
573
199
 
574
- webinar.updateStatusByRole(roleChange);
200
+ const result = webinar.updateStatusByRole(roleChange);
575
201
 
576
- assert.calledOnceWithExactly(updateMediaShares, 'mediaShares', true);
202
+ assert.calledOnce(updateMediaShares);
577
203
  });
578
204
 
579
205
  it('Not trigger updateMediaShares if no role change', () => {
580
206
 
581
207
  const roleChange = {isPromoted: false, isDemoted: false};
582
208
 
583
- webinar.updateStatusByRole(roleChange);
209
+ const result = webinar.updateStatusByRole(roleChange);
584
210
 
585
211
  assert.notCalled(updateMediaShares);
586
212
  });
@@ -588,18 +214,18 @@ describe('plugin-meetings', () => {
588
214
 
589
215
  const roleChange = {isPromoted: true, isDemoted: false};
590
216
 
591
- webinar.updateStatusByRole(roleChange);
217
+ const result = webinar.updateStatusByRole(roleChange);
592
218
 
593
- assert.calledOnceWithExactly(updateMediaShares, 'mediaShares', true);
219
+ assert.calledOnce(updateMediaShares);
594
220
  });
595
221
 
596
222
  it('trigger updateMediaShares if is attendee with whiteboard share', () => {
597
223
 
598
224
  const roleChange = {isPromoted: false, isDemoted: true};
599
225
 
600
- webinar.updateStatusByRole(roleChange);
226
+ const result = webinar.updateStatusByRole(roleChange);
601
227
 
602
- assert.calledOnceWithExactly(updateMediaShares, 'mediaShares', true);
228
+ assert.calledOnce(updateMediaShares);
603
229
  });
604
230
 
605
231
  it('Not trigger updateMediaShares if is attendee with screen share', () => {
@@ -607,9 +233,8 @@ describe('plugin-meetings', () => {
607
233
  webinar.webex.meetings = {
608
234
  getMeetingByType: sinon.stub().returns({
609
235
  id: 'meeting-id',
610
- isJoined: sinon.stub().returns(false),
611
- updateLLMConnection: sinon.stub(),
612
- shareStatus: SHARE_STATUS.REMOTE_SHARE_ACTIVE,
236
+ updateLLMConnection: updateLLMConnection,
237
+ shareStatus: 'remote_share_active',
613
238
  locusInfo: {
614
239
  mediaShares: 'mediaShares',
615
240
  updateMediaShares: updateMediaShares
@@ -619,18 +244,10 @@ describe('plugin-meetings', () => {
619
244
 
620
245
  const roleChange = {isPromoted: false, isDemoted: true};
621
246
 
622
- webinar.updateStatusByRole(roleChange);
247
+ const result = webinar.updateStatusByRole(roleChange);
623
248
 
624
249
  assert.notCalled(updateMediaShares);
625
250
  });
626
-
627
- it('updates PS data channel based on join eligibility', () => {
628
- const updatePSDataChannelStub = sinon.stub(webinar, 'updatePSDataChannel').resolves();
629
-
630
- webinar.updateStatusByRole({isPromoted: false, isDemoted: false});
631
-
632
- assert.calledOnceWithExactly(updatePSDataChannelStub);
633
- });
634
251
  });
635
252
 
636
253
  describe("#setPracticeSessionState", () => {
@@ -706,14 +323,6 @@ describe('plugin-meetings', () => {
706
323
 
707
324
  assert.equal(webinar.practiceSessionEnabled, false);
708
325
  });
709
- it('triggers PS data channel update using computed eligibility', () => {
710
- webinar.selfIsPanelist = true;
711
- const updatePSDataChannelStub = sinon.stub(webinar, 'updatePSDataChannel').resolves();
712
-
713
- webinar.updatePracticeSessionStatus({enabled: true});
714
-
715
- assert.calledOnceWithExactly(updatePSDataChannelStub);
716
- });
717
326
  });
718
327
 
719
328
  describe("#startWebcast", () => {
@@ -1022,111 +631,5 @@ describe('plugin-meetings', () => {
1022
631
  }
1023
632
  });
1024
633
  });
1025
-
1026
- describe("#searchLargeScaleWebinarAttendees", () => {
1027
- const attendeeSearchUrl = 'https://locusUrl/attendees/search';
1028
- const params = {
1029
- queryString: 'queryString',
1030
- limit: 50,
1031
- next: null,
1032
- };
1033
- beforeEach(() => {
1034
- // @ts-ignore
1035
- webinar.webex.meetings = {
1036
- getMeetingByType: sinon.stub().returns({
1037
- id: 'meeting-id',
1038
- locusInfo: {
1039
- links:{
1040
- resources: {
1041
- attendeeSearch: {
1042
- url: attendeeSearchUrl
1043
- }
1044
- }
1045
- }
1046
- }
1047
- })
1048
- };
1049
- });
1050
-
1051
- it('throws an error if attendeeSearchUrl is not available', async () => {
1052
- webinar.webex.meetings = {
1053
- getMeetingByType: sinon.stub().returns({
1054
- id: 'meeting-id',
1055
- locusInfo: {
1056
- links:{
1057
- resources: {
1058
- attendeeSearch: {
1059
- url: null
1060
- }
1061
- }
1062
- }
1063
- }
1064
- })
1065
- };
1066
- try {
1067
- await webinar.searchLargeScaleWebinarAttendees(params);
1068
- assert.fail('searchLargeScaleWebinarAttendees should throw an error');
1069
- } catch (error) {
1070
- assert.equal(error.message,'Meeting:webinar5k#Attendee search url is not available', 'should throw the correct error');
1071
- }
1072
- });
1073
-
1074
- it('sends a GET request to search the large scale webinar attendees', async () => {
1075
- const result = await webinar.searchLargeScaleWebinarAttendees(params);
1076
- assert.calledOnce(webex.request);
1077
- assert.calledWith(webex.request, {
1078
- method: 'GET',
1079
- uri: `${attendeeSearchUrl}?search_text=${encodeURIComponent(params.queryString)}&limit=50`,
1080
- headers: {
1081
- authorization: 'test-token',
1082
- trackingId: 'webex-js-sdk_test-uuid',
1083
- },
1084
- });
1085
- assert.equal(
1086
- result,
1087
- 'REQUEST_RETURN_VALUE',
1088
- 'should return the resolved value from the request'
1089
- );
1090
- });
1091
-
1092
- it('queryString is empty string', async () => {
1093
- params.queryString = '';
1094
- const result = await webinar.searchLargeScaleWebinarAttendees(params);
1095
- assert.calledOnce(webex.request);
1096
- assert.calledWith(webex.request, {
1097
- method: 'GET',
1098
- uri: `${attendeeSearchUrl}?limit=50`,
1099
- headers: {
1100
- authorization: 'test-token',
1101
- trackingId: 'webex-js-sdk_test-uuid',
1102
- },
1103
- });
1104
- assert.equal(
1105
- result,
1106
- 'REQUEST_RETURN_VALUE',
1107
- 'should return the resolved value from the request'
1108
- );
1109
- });
1110
-
1111
- it('handles API call failures gracefully', async () => {
1112
- webex.request.rejects(new Error('API_ERROR'));
1113
- const errorLogger = sinon.stub(LoggerProxy.logger, 'error');
1114
-
1115
- try {
1116
- await webinar.searchLargeScaleWebinarAttendees(params);
1117
- assert.fail('searchLargeScaleWebinarAttendees should throw an error');
1118
- } catch (error) {
1119
- assert.equal(error.message, 'API_ERROR', 'should throw the correct error');
1120
- assert.calledOnce(errorLogger);
1121
- assert.calledWith(
1122
- errorLogger,
1123
- 'Meeting:webinar5k#searchLargeScaleWebinarAttendees failed',
1124
- sinon.match.instanceOf(Error)
1125
- );
1126
- } finally {
1127
- errorLogger.restore();
1128
- }
1129
- });
1130
- });
1131
634
  })
1132
635
  })