@webex/plugin-meetings 3.12.0-next.65 → 3.12.0-next.66
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.
- package/dist/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +17 -3
- package/dist/constants.js.map +1 -1
- package/dist/interceptors/dataChannelAuthToken.js +75 -15
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +738 -679
- package/dist/meeting/index.js.map +1 -1
- package/dist/types/constants.d.ts +1 -1
- package/dist/types/meeting/index.d.ts +2 -2
- package/dist/webinar/index.js +219 -146
- package/dist/webinar/index.js.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +2 -1
- package/src/interceptors/dataChannelAuthToken.ts +88 -12
- package/src/meeting/index.ts +111 -49
- package/src/webinar/index.ts +114 -16
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +196 -0
- package/test/unit/spec/meeting/index.js +139 -23
- package/test/unit/spec/webinar/index.ts +197 -14
|
@@ -7,6 +7,8 @@ import sinon from 'sinon';
|
|
|
7
7
|
import {DataChannelTokenType} from '@webex/internal-plugin-llm';
|
|
8
8
|
import {LLM_PRACTICE_SESSION, LOCUS_LLM_EVENT, SHARE_STATUS} from '@webex/plugin-meetings/src/constants';
|
|
9
9
|
|
|
10
|
+
const PRACTICE_SESSION_KEY = LLM_PRACTICE_SESSION || DataChannelTokenType.PracticeSession;
|
|
11
|
+
|
|
10
12
|
describe('plugin-meetings', () => {
|
|
11
13
|
describe('Webinar', () => {
|
|
12
14
|
|
|
@@ -33,6 +35,17 @@ describe('plugin-meetings', () => {
|
|
|
33
35
|
webex.internal.llm = {
|
|
34
36
|
getDatachannelToken: sinon.stub().returns(undefined),
|
|
35
37
|
setDatachannelToken: sinon.stub(),
|
|
38
|
+
setRefreshHandler: sinon.stub(),
|
|
39
|
+
getOwnerMeetingId: sinon.stub().returns(undefined),
|
|
40
|
+
resolveSessionOwnership: sinon.stub().callsFake((ownerMeetingId, sessionId) => {
|
|
41
|
+
const currentOwner = webex.internal.llm.getOwnerMeetingId(sessionId);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
currentOwner,
|
|
45
|
+
isOwner: !currentOwner || !ownerMeetingId || currentOwner === ownerMeetingId,
|
|
46
|
+
};
|
|
47
|
+
}),
|
|
48
|
+
setOwnerMeetingId: sinon.stub(),
|
|
36
49
|
isDataChannelTokenEnabled: sinon.stub().resolves(false),
|
|
37
50
|
isConnected: sinon.stub().returns(false),
|
|
38
51
|
disconnectLLM: sinon.stub().resolves(),
|
|
@@ -226,6 +239,7 @@ describe('plugin-meetings', () => {
|
|
|
226
239
|
let relayListener;
|
|
227
240
|
|
|
228
241
|
beforeEach(() => {
|
|
242
|
+
webinar.meetingId = 'meeting-id';
|
|
229
243
|
relayListener = sinon.stub();
|
|
230
244
|
webinar.llmListeners = {relay: relayListener, locusLLM: null};
|
|
231
245
|
});
|
|
@@ -236,27 +250,74 @@ describe('plugin-meetings', () => {
|
|
|
236
250
|
assert.calledOnceWithExactly(
|
|
237
251
|
webex.internal.llm.disconnectLLM,
|
|
238
252
|
{code: 3050, reason: 'done (permanent)'},
|
|
239
|
-
|
|
253
|
+
PRACTICE_SESSION_KEY,
|
|
254
|
+
webinar.meetingId
|
|
240
255
|
);
|
|
241
256
|
assert.calledWithExactly(
|
|
242
257
|
webex.internal.llm.off,
|
|
243
|
-
`event:relay.event:${
|
|
258
|
+
`event:relay.event:${PRACTICE_SESSION_KEY}`,
|
|
244
259
|
relayListener
|
|
245
260
|
);
|
|
246
261
|
assert.isNull(webinar.llmListeners.relay);
|
|
247
262
|
});
|
|
248
263
|
|
|
264
|
+
it('skips disconnect when practice-session owner is another meeting', async () => {
|
|
265
|
+
webex.internal.llm.getOwnerMeetingId.returns('other-meeting-id');
|
|
266
|
+
webex.internal.llm.disconnectLLM.resolves(false);
|
|
267
|
+
|
|
268
|
+
await webinar.cleanupPSDataChannel();
|
|
269
|
+
|
|
270
|
+
assert.calledOnceWithExactly(
|
|
271
|
+
webex.internal.llm.disconnectLLM,
|
|
272
|
+
{code: 3050, reason: 'done (permanent)'},
|
|
273
|
+
PRACTICE_SESSION_KEY,
|
|
274
|
+
webinar.meetingId
|
|
275
|
+
);
|
|
276
|
+
assert.notCalled(webex.internal.llm.setOwnerMeetingId);
|
|
277
|
+
assert.calledOnceWithExactly(
|
|
278
|
+
webex.internal.llm.off,
|
|
279
|
+
`event:relay.event:${PRACTICE_SESSION_KEY}`,
|
|
280
|
+
relayListener
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
|
|
249
284
|
it('skips relay listener removal when no listener has been tracked', async () => {
|
|
250
285
|
webinar.llmListeners.relay = null;
|
|
251
286
|
|
|
252
287
|
await webinar.cleanupPSDataChannel();
|
|
253
288
|
|
|
254
289
|
const relayOffCalls = webex.internal.llm.off.args.filter(
|
|
255
|
-
([event]) => event === `event:relay.event:${
|
|
290
|
+
([event]) => event === `event:relay.event:${PRACTICE_SESSION_KEY}`
|
|
256
291
|
);
|
|
257
292
|
assert.equal(relayOffCalls.length, 0);
|
|
258
293
|
});
|
|
259
294
|
|
|
295
|
+
it('removes tracked relay listener even when disconnect throws', async () => {
|
|
296
|
+
const disconnectError = new Error('disconnect failed');
|
|
297
|
+
webex.internal.llm.disconnectLLM.rejects(disconnectError);
|
|
298
|
+
|
|
299
|
+
let caughtError;
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
await webinar.cleanupPSDataChannel();
|
|
303
|
+
} catch (error) {
|
|
304
|
+
caughtError = error;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
assert.equal(caughtError, disconnectError);
|
|
308
|
+
assert.calledOnceWithExactly(
|
|
309
|
+
webex.internal.llm.setOwnerMeetingId,
|
|
310
|
+
undefined,
|
|
311
|
+
PRACTICE_SESSION_KEY
|
|
312
|
+
);
|
|
313
|
+
assert.calledOnceWithExactly(
|
|
314
|
+
webex.internal.llm.off,
|
|
315
|
+
`event:relay.event:${PRACTICE_SESSION_KEY}`,
|
|
316
|
+
relayListener
|
|
317
|
+
);
|
|
318
|
+
assert.notOk(webinar._practiceSessionRelayListener);
|
|
319
|
+
});
|
|
320
|
+
|
|
260
321
|
it('disconnects and removes the tracked locusLLM listener', async () => {
|
|
261
322
|
const locusLLMListener = sinon.stub();
|
|
262
323
|
webinar.llmListeners.locusLLM = locusLLMListener;
|
|
@@ -317,9 +378,11 @@ describe('plugin-meetings', () => {
|
|
|
317
378
|
let processLocusLLMEvent;
|
|
318
379
|
|
|
319
380
|
beforeEach(() => {
|
|
381
|
+
webinar.meetingId = 'meeting-id';
|
|
320
382
|
processRelayEvent = sinon.stub();
|
|
321
383
|
processLocusLLMEvent = sinon.stub();
|
|
322
384
|
meeting = {
|
|
385
|
+
id: 'meeting-id',
|
|
323
386
|
locusUrl: 'locusUrl',
|
|
324
387
|
isJoined: sinon.stub().returns(true),
|
|
325
388
|
processRelayEvent,
|
|
@@ -334,7 +397,7 @@ describe('plugin-meetings', () => {
|
|
|
334
397
|
|
|
335
398
|
// Default session is connected by default; practice session is not
|
|
336
399
|
webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
|
|
337
|
-
return sessionId !==
|
|
400
|
+
return sessionId !== PRACTICE_SESSION_KEY;
|
|
338
401
|
});
|
|
339
402
|
|
|
340
403
|
// Token is pre-saved into LLM by saveDataChannelToken
|
|
@@ -370,14 +433,15 @@ describe('plugin-meetings', () => {
|
|
|
370
433
|
assert.calledWithExactly(
|
|
371
434
|
webex.internal.llm.setDatachannelToken,
|
|
372
435
|
'ps-token-from-refresh',
|
|
373
|
-
DataChannelTokenType.PracticeSession
|
|
436
|
+
DataChannelTokenType.PracticeSession,
|
|
437
|
+
'meeting-id'
|
|
374
438
|
);
|
|
375
439
|
assert.calledWith(
|
|
376
440
|
webex.internal.llm.registerAndConnect,
|
|
377
441
|
'locus-url',
|
|
378
442
|
'dc-url',
|
|
379
443
|
'ps-token-from-refresh',
|
|
380
|
-
|
|
444
|
+
PRACTICE_SESSION_KEY
|
|
381
445
|
);
|
|
382
446
|
});
|
|
383
447
|
|
|
@@ -437,6 +501,8 @@ describe('plugin-meetings', () => {
|
|
|
437
501
|
const result = await webinar.updatePSDataChannel();
|
|
438
502
|
|
|
439
503
|
assert.isUndefined(result);
|
|
504
|
+
assert.notCalled(webex.internal.llm.setRefreshHandler);
|
|
505
|
+
assert.notCalled(webex.internal.llm.setOwnerMeetingId);
|
|
440
506
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
441
507
|
});
|
|
442
508
|
|
|
@@ -457,12 +523,17 @@ describe('plugin-meetings', () => {
|
|
|
457
523
|
const result = await webinar.updatePSDataChannel();
|
|
458
524
|
|
|
459
525
|
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
526
|
+
assert.calledWithExactly(
|
|
527
|
+
webex.internal.llm.setOwnerMeetingId,
|
|
528
|
+
'meeting-id',
|
|
529
|
+
PRACTICE_SESSION_KEY
|
|
530
|
+
);
|
|
460
531
|
assert.calledWith(
|
|
461
532
|
webex.internal.llm.registerAndConnect,
|
|
462
533
|
'locus-url',
|
|
463
534
|
'dc-url',
|
|
464
535
|
'ps-token',
|
|
465
|
-
|
|
536
|
+
PRACTICE_SESSION_KEY
|
|
466
537
|
);
|
|
467
538
|
assert.calledOnceWithExactly(webex.internal.voicea.announce);
|
|
468
539
|
assert.equal(result, 'REGISTER_AND_CONNECT_RESULT');
|
|
@@ -478,7 +549,8 @@ describe('plugin-meetings', () => {
|
|
|
478
549
|
|
|
479
550
|
assert.calledWithExactly(
|
|
480
551
|
webex.internal.llm.getDatachannelToken,
|
|
481
|
-
DataChannelTokenType.PracticeSession
|
|
552
|
+
DataChannelTokenType.PracticeSession,
|
|
553
|
+
webinar.meetingId
|
|
482
554
|
);
|
|
483
555
|
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
484
556
|
assert.calledWith(
|
|
@@ -486,7 +558,7 @@ describe('plugin-meetings', () => {
|
|
|
486
558
|
'locus-url',
|
|
487
559
|
'dc-url',
|
|
488
560
|
'cached-token',
|
|
489
|
-
|
|
561
|
+
PRACTICE_SESSION_KEY
|
|
490
562
|
);
|
|
491
563
|
});
|
|
492
564
|
|
|
@@ -507,7 +579,7 @@ describe('plugin-meetings', () => {
|
|
|
507
579
|
assert.equal(webinar.llmListeners.relay, processRelayEvent);
|
|
508
580
|
assert.calledWith(
|
|
509
581
|
webex.internal.llm.on,
|
|
510
|
-
`event:relay.event:${
|
|
582
|
+
`event:relay.event:${PRACTICE_SESSION_KEY}`,
|
|
511
583
|
processRelayEvent
|
|
512
584
|
);
|
|
513
585
|
});
|
|
@@ -520,7 +592,7 @@ describe('plugin-meetings', () => {
|
|
|
520
592
|
|
|
521
593
|
assert.calledWith(
|
|
522
594
|
webex.internal.llm.off,
|
|
523
|
-
`event:relay.event:${
|
|
595
|
+
`event:relay.event:${PRACTICE_SESSION_KEY}`,
|
|
524
596
|
previousListener
|
|
525
597
|
);
|
|
526
598
|
assert.equal(webinar.llmListeners.relay, processRelayEvent);
|
|
@@ -578,6 +650,8 @@ describe('plugin-meetings', () => {
|
|
|
578
650
|
// Should register an 'online' listener but NOT call registerAndConnect yet
|
|
579
651
|
assert.calledWith(webex.internal.llm.on, 'online', sinon.match.func);
|
|
580
652
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
653
|
+
assert.notCalled(webex.internal.llm.setRefreshHandler);
|
|
654
|
+
assert.notCalled(webex.internal.llm.setOwnerMeetingId);
|
|
581
655
|
// Should store the pending listener
|
|
582
656
|
assert.isNotNull(webinar._pendingOnlineListener);
|
|
583
657
|
});
|
|
@@ -609,7 +683,7 @@ describe('plugin-meetings', () => {
|
|
|
609
683
|
|
|
610
684
|
// Now simulate default session coming online
|
|
611
685
|
webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
|
|
612
|
-
return sessionId !==
|
|
686
|
+
return sessionId !== PRACTICE_SESSION_KEY;
|
|
613
687
|
});
|
|
614
688
|
|
|
615
689
|
// Fire the captured listener
|
|
@@ -636,7 +710,7 @@ describe('plugin-meetings', () => {
|
|
|
636
710
|
|
|
637
711
|
// Now default session comes online
|
|
638
712
|
webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
|
|
639
|
-
return sessionId !==
|
|
713
|
+
return sessionId !== PRACTICE_SESSION_KEY;
|
|
640
714
|
});
|
|
641
715
|
|
|
642
716
|
// Fire the listener — re-invokes updatePSDataChannel which will see isPracticeSession = false
|
|
@@ -649,7 +723,7 @@ describe('plugin-meetings', () => {
|
|
|
649
723
|
it('proceeds immediately when default session is already connected', async () => {
|
|
650
724
|
// Default session already connected, practice session not
|
|
651
725
|
webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
|
|
652
|
-
return sessionId !==
|
|
726
|
+
return sessionId !== PRACTICE_SESSION_KEY;
|
|
653
727
|
});
|
|
654
728
|
|
|
655
729
|
const result = await webinar.updatePSDataChannel();
|
|
@@ -661,6 +735,115 @@ describe('plugin-meetings', () => {
|
|
|
661
735
|
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
662
736
|
assert.equal(result, 'REGISTER_AND_CONNECT_RESULT');
|
|
663
737
|
});
|
|
738
|
+
|
|
739
|
+
it('does not override practice refresh handler or reconnect when owned by another meeting', async () => {
|
|
740
|
+
webex.internal.llm.getOwnerMeetingId.returns('other-meeting-id');
|
|
741
|
+
webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
|
|
742
|
+
return sessionId !== undefined;
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
const result = await webinar.updatePSDataChannel();
|
|
746
|
+
|
|
747
|
+
assert.isUndefined(result);
|
|
748
|
+
assert.notCalled(webex.internal.llm.setRefreshHandler);
|
|
749
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it('does not reconnect when practice session is disconnected but owned by another meeting', async () => {
|
|
753
|
+
webex.internal.llm.getOwnerMeetingId.returns('other-meeting-id');
|
|
754
|
+
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
755
|
+
|
|
756
|
+
const result = await webinar.updatePSDataChannel();
|
|
757
|
+
|
|
758
|
+
assert.isUndefined(result);
|
|
759
|
+
assert.notCalled(webex.internal.llm.setRefreshHandler);
|
|
760
|
+
assert.notCalled(webex.internal.llm.setOwnerMeetingId);
|
|
761
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it('does not write owner or connect if ownership changes before pre-connect owner write', async () => {
|
|
765
|
+
let ownerMeetingId = 'meeting-id';
|
|
766
|
+
|
|
767
|
+
webex.internal.llm.getOwnerMeetingId.callsFake(() => ownerMeetingId);
|
|
768
|
+
webex.internal.llm.isDataChannelTokenEnabled.resolves(true);
|
|
769
|
+
webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
|
|
770
|
+
|
|
771
|
+
let resolveRefresh;
|
|
772
|
+
meeting.refreshDataChannelToken = sinon.stub().returns(
|
|
773
|
+
new Promise((resolve) => {
|
|
774
|
+
resolveRefresh = resolve;
|
|
775
|
+
})
|
|
776
|
+
);
|
|
777
|
+
|
|
778
|
+
const updatePromise = webinar.updatePSDataChannel();
|
|
779
|
+
|
|
780
|
+
ownerMeetingId = 'other-meeting-id';
|
|
781
|
+
resolveRefresh({
|
|
782
|
+
body: {
|
|
783
|
+
datachannelToken: 'ps-token-from-refresh',
|
|
784
|
+
dataChannelTokenType: DataChannelTokenType.PracticeSession,
|
|
785
|
+
},
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
const result = await updatePromise;
|
|
789
|
+
|
|
790
|
+
assert.isUndefined(result);
|
|
791
|
+
assert.notCalled(webex.internal.llm.setRefreshHandler);
|
|
792
|
+
assert.notCalled(webex.internal.llm.setOwnerMeetingId);
|
|
793
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
it('does not overwrite owner after connect when ownership changed during registerAndConnect', async () => {
|
|
797
|
+
let ownerMeetingId = 'meeting-id';
|
|
798
|
+
|
|
799
|
+
webex.internal.llm.getOwnerMeetingId.callsFake(() => ownerMeetingId);
|
|
800
|
+
webex.internal.llm.registerAndConnect = sinon.stub().callsFake(async () => {
|
|
801
|
+
ownerMeetingId = 'other-meeting-id';
|
|
802
|
+
|
|
803
|
+
return 'REGISTER_AND_CONNECT_RESULT';
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
const result = await webinar.updatePSDataChannel();
|
|
807
|
+
|
|
808
|
+
assert.equal(result, 'REGISTER_AND_CONNECT_RESULT');
|
|
809
|
+
assert.calledOnce(webex.internal.llm.setOwnerMeetingId);
|
|
810
|
+
assert.calledWithExactly(
|
|
811
|
+
webex.internal.llm.setOwnerMeetingId,
|
|
812
|
+
'meeting-id',
|
|
813
|
+
PRACTICE_SESSION_KEY
|
|
814
|
+
);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('clears pre-claimed owner when registerAndConnect rejects', async () => {
|
|
818
|
+
const registerError = new Error('register failed');
|
|
819
|
+
let ownerMeetingId = 'meeting-id';
|
|
820
|
+
|
|
821
|
+
webex.internal.llm.getOwnerMeetingId.callsFake(() => ownerMeetingId);
|
|
822
|
+
webex.internal.llm.setOwnerMeetingId.callsFake((id) => {
|
|
823
|
+
ownerMeetingId = id;
|
|
824
|
+
});
|
|
825
|
+
webex.internal.llm.registerAndConnect = sinon.stub().rejects(registerError);
|
|
826
|
+
|
|
827
|
+
try {
|
|
828
|
+
await webinar.updatePSDataChannel();
|
|
829
|
+
assert.fail('Expected updatePSDataChannel to reject when registerAndConnect fails');
|
|
830
|
+
} catch (error) {
|
|
831
|
+
assert.equal(error, registerError);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
assert.calledTwice(webex.internal.llm.setOwnerMeetingId);
|
|
835
|
+
assert.calledWithExactly(
|
|
836
|
+
webex.internal.llm.setOwnerMeetingId.firstCall,
|
|
837
|
+
'meeting-id',
|
|
838
|
+
PRACTICE_SESSION_KEY
|
|
839
|
+
);
|
|
840
|
+
assert.calledWithExactly(
|
|
841
|
+
webex.internal.llm.setOwnerMeetingId.secondCall,
|
|
842
|
+
undefined,
|
|
843
|
+
PRACTICE_SESSION_KEY
|
|
844
|
+
);
|
|
845
|
+
assert.isUndefined(ownerMeetingId);
|
|
846
|
+
});
|
|
664
847
|
});
|
|
665
848
|
|
|
666
849
|
describe('#updateStatusByRole', () => {
|