@webex/plugin-meetings 3.12.0-next.6 → 3.12.0-next.61
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/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +15 -2
- package/dist/aiEnableRequest/index.js.map +1 -1
- package/dist/breakouts/breakout.js +8 -3
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +26 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +6 -3
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +38 -24
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +716 -370
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +22 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +10 -1
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +4 -1
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +289 -87
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/types.js +19 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/index.js +3 -1
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +1 -0
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +3 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +907 -535
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +19 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +231 -78
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +6 -1
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/request.js +39 -0
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +79 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +3 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +4 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/codec/constants.js +63 -0
- package/dist/multistream/codec/constants.js.map +1 -0
- package/dist/multistream/mediaRequestManager.js +62 -15
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +9 -0
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/recording-controller/index.js +1 -3
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/controls-options-manager/index.d.ts +10 -0
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +92 -16
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +46 -6
- package/dist/types/locus-info/types.d.ts +21 -1
- package/dist/types/media/properties.d.ts +1 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
- package/dist/types/meeting/index.d.ts +87 -3
- package/dist/types/meeting/util.d.ts +8 -0
- package/dist/types/meetings/index.d.ts +30 -2
- package/dist/types/meetings/meetings.types.d.ts +15 -0
- package/dist/types/meetings/request.d.ts +14 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +1 -0
- package/dist/types/metrics/constants.d.ts +3 -0
- package/dist/types/multistream/codec/constants.d.ts +7 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
- package/dist/types/reactions/reactions.type.d.ts +3 -0
- package/dist/webinar/index.js +361 -235
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/aiEnableRequest/index.ts +16 -0
- package/src/breakouts/breakout.ts +3 -1
- package/src/breakouts/index.ts +31 -0
- package/src/config.ts +2 -0
- package/src/constants.ts +5 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +47 -24
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +429 -183
- package/src/hashTree/utils.ts +17 -0
- package/src/index.ts +5 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/interpretation/index.ts +25 -8
- package/src/locus-info/controlsUtils.ts +3 -1
- package/src/locus-info/index.ts +291 -97
- package/src/locus-info/types.ts +25 -1
- package/src/media/index.ts +3 -0
- package/src/media/properties.ts +1 -0
- package/src/meeting/in-meeting-actions.ts +4 -0
- package/src/meeting/index.ts +388 -33
- package/src/meeting/util.ts +20 -2
- package/src/meetings/index.ts +134 -44
- package/src/meetings/meetings.types.ts +19 -0
- package/src/meetings/request.ts +43 -0
- package/src/meetings/util.ts +97 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/metrics/constants.ts +3 -0
- package/src/multistream/codec/constants.ts +58 -0
- package/src/multistream/mediaRequestManager.ts +119 -28
- package/src/multistream/receiveSlot.ts +18 -0
- package/src/reactions/reactions.type.ts +3 -0
- package/src/recording-controller/index.ts +1 -2
- package/src/webinar/index.ts +162 -21
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +9 -3
- package/test/unit/spec/breakouts/index.ts +49 -0
- package/test/unit/spec/controls-options-manager/index.js +140 -29
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1508 -149
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/interpretation/index.ts +26 -4
- package/test/unit/spec/locus-info/controlsUtils.js +172 -57
- package/test/unit/spec/locus-info/index.js +475 -81
- package/test/unit/spec/media/index.ts +31 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
- package/test/unit/spec/meeting/index.js +1131 -49
- package/test/unit/spec/meeting/muteState.js +3 -0
- package/test/unit/spec/meeting/utils.js +33 -0
- package/test/unit/spec/meetings/index.js +360 -10
- package/test/unit/spec/meetings/request.js +141 -0
- package/test/unit/spec/meetings/utils.js +189 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
- package/test/unit/spec/recording-controller/index.js +9 -8
- package/test/unit/spec/webinar/index.ts +141 -16
|
@@ -13,6 +13,7 @@ import MediaSharesUtils from '@webex/plugin-meetings/src/locus-info//mediaShares
|
|
|
13
13
|
import LocusDeltaParser from '@webex/plugin-meetings/src/locus-info/parser';
|
|
14
14
|
import Metrics from '@webex/plugin-meetings/src/metrics';
|
|
15
15
|
import * as HashTreeParserModule from '@webex/plugin-meetings/src/hashTree/hashTreeParser';
|
|
16
|
+
import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util';
|
|
16
17
|
|
|
17
18
|
import {
|
|
18
19
|
LOCUSINFO,
|
|
@@ -220,6 +221,47 @@ describe('plugin-meetings', () => {
|
|
|
220
221
|
assert.isTrue(locusInfo.emitChange);
|
|
221
222
|
});
|
|
222
223
|
|
|
224
|
+
it('calls onLocusSynced callback passed as second argument with full locus from join response', async () => {
|
|
225
|
+
const syncedLocus = {url: 'http://locus-url.com', participants: []};
|
|
226
|
+
const onLocusSynced = sinon.stub();
|
|
227
|
+
|
|
228
|
+
await locusInfo.initialSetup(
|
|
229
|
+
{
|
|
230
|
+
trigger: 'join-response',
|
|
231
|
+
locus: syncedLocus,
|
|
232
|
+
},
|
|
233
|
+
onLocusSynced
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
assert.calledOnceWithExactly(onLocusSynced, syncedLocus);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('swallows onLocusSynced callback errors and logs warn', async () => {
|
|
240
|
+
const syncedLocus = {url: 'http://locus-url.com', participants: []};
|
|
241
|
+
const callbackError = new Error('onLocusSynced failed');
|
|
242
|
+
const onLocusSynced = sinon.stub().throws(callbackError);
|
|
243
|
+
const loggerWarnStub = LoggerProxy.logger.warn?.isSinonProxy
|
|
244
|
+
? LoggerProxy.logger.warn
|
|
245
|
+
: sinon.stub(LoggerProxy.logger, 'warn');
|
|
246
|
+
|
|
247
|
+
loggerWarnStub.resetHistory();
|
|
248
|
+
|
|
249
|
+
await locusInfo.initialSetup(
|
|
250
|
+
{
|
|
251
|
+
trigger: 'join-response',
|
|
252
|
+
locus: syncedLocus,
|
|
253
|
+
},
|
|
254
|
+
onLocusSynced
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
assert.calledOnceWithExactly(onLocusSynced, syncedLocus);
|
|
258
|
+
assert.calledOnce(loggerWarnStub);
|
|
259
|
+
assert.match(
|
|
260
|
+
loggerWarnStub.firstCall.args[0],
|
|
261
|
+
/Locus-info:index#initialSetup --> onLocusSynced callback failed/
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
223
265
|
it('should initialize the hash tree parser correctly when triggered from a get loci response containing visible datasets', async () => {
|
|
224
266
|
const visibleDataSets = ['dataset1', 'dataset2'];
|
|
225
267
|
const locus = createLocusWithVisibleDataSets(visibleDataSets);
|
|
@@ -290,6 +332,7 @@ describe('plugin-meetings', () => {
|
|
|
290
332
|
describe('should setup correct locusInfoUpdateCallback when creating HashTreeParser', () => {
|
|
291
333
|
const OBJECTS_UPDATED = HashTreeParserModule.LocusInfoUpdateType.OBJECTS_UPDATED;
|
|
292
334
|
const MEETING_ENDED = HashTreeParserModule.LocusInfoUpdateType.MEETING_ENDED;
|
|
335
|
+
const LOCUS_NOT_FOUND = HashTreeParserModule.LocusInfoUpdateType.LOCUS_NOT_FOUND;
|
|
293
336
|
|
|
294
337
|
let locusInfoUpdateCallback;
|
|
295
338
|
let onDeltaLocusStub;
|
|
@@ -413,7 +456,7 @@ describe('plugin-meetings', () => {
|
|
|
413
456
|
};
|
|
414
457
|
|
|
415
458
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
416
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
459
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
417
460
|
updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
|
|
418
461
|
});
|
|
419
462
|
|
|
@@ -440,7 +483,7 @@ describe('plugin-meetings', () => {
|
|
|
440
483
|
locusInfo.info.isWebinar = true;
|
|
441
484
|
|
|
442
485
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
443
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
486
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
444
487
|
updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
|
|
445
488
|
});
|
|
446
489
|
|
|
@@ -473,7 +516,7 @@ describe('plugin-meetings', () => {
|
|
|
473
516
|
locusInfo.info.isWebinar = true;
|
|
474
517
|
|
|
475
518
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
476
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
519
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
477
520
|
updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
|
|
478
521
|
});
|
|
479
522
|
|
|
@@ -501,7 +544,7 @@ describe('plugin-meetings', () => {
|
|
|
501
544
|
};
|
|
502
545
|
|
|
503
546
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
504
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
547
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
505
548
|
updatedObjects: [{htMeta: {elementId: {type: 'fullState'}}, data: newFullState}],
|
|
506
549
|
});
|
|
507
550
|
|
|
@@ -519,7 +562,7 @@ describe('plugin-meetings', () => {
|
|
|
519
562
|
};
|
|
520
563
|
|
|
521
564
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
522
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
565
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
523
566
|
updatedObjects: [{htMeta: {elementId: {type: 'info'}}, data: newInfo}],
|
|
524
567
|
});
|
|
525
568
|
|
|
@@ -537,7 +580,7 @@ describe('plugin-meetings', () => {
|
|
|
537
580
|
};
|
|
538
581
|
|
|
539
582
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
540
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
583
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
541
584
|
updatedObjects: [{htMeta: {elementId: {type: 'links'}}, data: newLinks}],
|
|
542
585
|
});
|
|
543
586
|
|
|
@@ -557,7 +600,7 @@ describe('plugin-meetings', () => {
|
|
|
557
600
|
};
|
|
558
601
|
|
|
559
602
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
560
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
603
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
561
604
|
updatedObjects: [{htMeta: newLocusHtMeta, data: newLocus}],
|
|
562
605
|
});
|
|
563
606
|
|
|
@@ -590,7 +633,7 @@ describe('plugin-meetings', () => {
|
|
|
590
633
|
};
|
|
591
634
|
|
|
592
635
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
593
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
636
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
594
637
|
updatedObjects: [
|
|
595
638
|
{
|
|
596
639
|
htMeta: newLocusHtMeta,
|
|
@@ -637,7 +680,7 @@ describe('plugin-meetings', () => {
|
|
|
637
680
|
};
|
|
638
681
|
|
|
639
682
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
640
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
683
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
641
684
|
updatedObjects: [
|
|
642
685
|
// first, a removal of LOCUS object
|
|
643
686
|
{htMeta: {elementId: {type: 'locus'}}, data: null},
|
|
@@ -671,7 +714,7 @@ describe('plugin-meetings', () => {
|
|
|
671
714
|
const newLocusHtMeta = {elementId: {type: 'locus', version: 99}};
|
|
672
715
|
|
|
673
716
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
674
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
717
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
675
718
|
updatedObjects: [
|
|
676
719
|
// first, an update
|
|
677
720
|
{htMeta: newLocusHtMeta, data: newLocus},
|
|
@@ -700,7 +743,7 @@ describe('plugin-meetings', () => {
|
|
|
700
743
|
};
|
|
701
744
|
|
|
702
745
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
703
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
746
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
704
747
|
updatedObjects: [
|
|
705
748
|
// first, an update
|
|
706
749
|
{htMeta: {elementId: {type: 'locus'}}, data: newLocus1},
|
|
@@ -730,7 +773,7 @@ describe('plugin-meetings', () => {
|
|
|
730
773
|
};
|
|
731
774
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
732
775
|
// with 1 participant added, 1 updated, and 1 removed
|
|
733
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
776
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
734
777
|
updatedObjects: [
|
|
735
778
|
{htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-1'}}, data: null},
|
|
736
779
|
{
|
|
@@ -774,7 +817,7 @@ describe('plugin-meetings', () => {
|
|
|
774
817
|
};
|
|
775
818
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
776
819
|
// with 1 participant added, 1 updated, and 1 removed
|
|
777
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
820
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
778
821
|
updatedObjects: [
|
|
779
822
|
{htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1'}}, data: null},
|
|
780
823
|
{
|
|
@@ -807,7 +850,7 @@ describe('plugin-meetings', () => {
|
|
|
807
850
|
};
|
|
808
851
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
809
852
|
// with 1 embedded app added, 1 updated, and 1 removed
|
|
810
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
853
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
811
854
|
updatedObjects: [
|
|
812
855
|
{htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1'}}, data: null},
|
|
813
856
|
{
|
|
@@ -844,7 +887,7 @@ describe('plugin-meetings', () => {
|
|
|
844
887
|
};
|
|
845
888
|
|
|
846
889
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
847
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
890
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
848
891
|
updatedObjects: [
|
|
849
892
|
{
|
|
850
893
|
htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2'}},
|
|
@@ -883,7 +926,7 @@ describe('plugin-meetings', () => {
|
|
|
883
926
|
};
|
|
884
927
|
|
|
885
928
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
886
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
929
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
887
930
|
updatedObjects: [
|
|
888
931
|
{
|
|
889
932
|
htMeta: {elementId: {type: 'controlentry', id: 'control-1'}},
|
|
@@ -911,7 +954,7 @@ describe('plugin-meetings', () => {
|
|
|
911
954
|
|
|
912
955
|
it('should process locus update correctly when CONTROL object is received with no data', () => {
|
|
913
956
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
914
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
957
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
915
958
|
updatedObjects: [
|
|
916
959
|
{
|
|
917
960
|
htMeta: {elementId: {type: 'controlentry', id: 'some-control-id'}},
|
|
@@ -935,7 +978,7 @@ describe('plugin-meetings', () => {
|
|
|
935
978
|
const destroyStub = sinon.stub(locusInfo.webex.meetings, 'destroy');
|
|
936
979
|
|
|
937
980
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
938
|
-
locusInfoUpdateCallback(MEETING_ENDED);
|
|
981
|
+
locusInfoUpdateCallback({updateType: MEETING_ENDED});
|
|
939
982
|
|
|
940
983
|
assert.calledOnceWithExactly(collectionGetStub, locusInfo.meetingId);
|
|
941
984
|
assert.calledOnceWithExactly(
|
|
@@ -953,17 +996,48 @@ describe('plugin-meetings', () => {
|
|
|
953
996
|
const destroyStub = sinon.stub(locusInfo.webex.meetings, 'destroy');
|
|
954
997
|
|
|
955
998
|
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
956
|
-
locusInfoUpdateCallback(MEETING_ENDED);
|
|
999
|
+
locusInfoUpdateCallback({updateType: MEETING_ENDED});
|
|
957
1000
|
|
|
958
1001
|
assert.calledOnceWithExactly(collectionGetStub, locusInfo.meetingId);
|
|
959
1002
|
assert.notCalled(destroyStub);
|
|
960
1003
|
});
|
|
961
1004
|
|
|
1005
|
+
it('should handle LOCUS_NOT_FOUND by calling syncMeetings with skipHashTreeSync', () => {
|
|
1006
|
+
const syncMeetingsStub = sinon.stub(locusInfo.webex.meetings, 'syncMeetings').resolves();
|
|
1007
|
+
|
|
1008
|
+
locusInfoUpdateCallback({updateType: LOCUS_NOT_FOUND});
|
|
1009
|
+
|
|
1010
|
+
assert.calledOnceWithExactly(syncMeetingsStub, {keepOnlyLocusMeetings: false, skipHashTreeSync: true});
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it('should handle LOCUS_NOT_FOUND and log error if syncMeetings fails', async () => {
|
|
1014
|
+
const syncError = new Error('sync failed');
|
|
1015
|
+
const syncMeetingsStub = sinon.stub(locusInfo.webex.meetings, 'syncMeetings').rejects(syncError);
|
|
1016
|
+
const logErrorStub = LoggerProxy.logger.error?.isSinonProxy
|
|
1017
|
+
? LoggerProxy.logger.error
|
|
1018
|
+
: sinon.stub(LoggerProxy.logger, 'error');
|
|
1019
|
+
|
|
1020
|
+
logErrorStub.resetHistory();
|
|
1021
|
+
|
|
1022
|
+
locusInfoUpdateCallback({updateType: LOCUS_NOT_FOUND});
|
|
1023
|
+
|
|
1024
|
+
assert.calledOnceWithExactly(syncMeetingsStub, {keepOnlyLocusMeetings: false, skipHashTreeSync: true});
|
|
1025
|
+
|
|
1026
|
+
// wait for the promise rejection to be handled
|
|
1027
|
+
await testUtils.flushPromises();
|
|
1028
|
+
|
|
1029
|
+
assert.calledOnce(logErrorStub);
|
|
1030
|
+
assert.match(
|
|
1031
|
+
logErrorStub.firstCall.args[0],
|
|
1032
|
+
/syncMeetings failed after LOCUS_NOT_FOUND/
|
|
1033
|
+
);
|
|
1034
|
+
});
|
|
1035
|
+
|
|
962
1036
|
it('should set forceReplaceMembers to true on the first update for a locusUrl (initializedFromHashTree is false)', () => {
|
|
963
1037
|
const createdHashTreeParser = locusInfo.hashTreeParsers.get('fake-locus-url');
|
|
964
1038
|
createdHashTreeParser.initializedFromHashTree = false;
|
|
965
1039
|
|
|
966
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
1040
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
967
1041
|
updatedObjects: [
|
|
968
1042
|
{
|
|
969
1043
|
htMeta: {elementId: {type: 'self'}},
|
|
@@ -978,7 +1052,7 @@ describe('plugin-meetings', () => {
|
|
|
978
1052
|
});
|
|
979
1053
|
|
|
980
1054
|
it('should set forceReplaceMembers to false on subsequent updates (initializedFromHashTree is true)', () => {
|
|
981
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
1055
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
982
1056
|
updatedObjects: [
|
|
983
1057
|
{
|
|
984
1058
|
htMeta: {elementId: {type: 'self'}},
|
|
@@ -994,7 +1068,7 @@ describe('plugin-meetings', () => {
|
|
|
994
1068
|
it('should copy participant data to self when participant matches self identity and state is LEFT with reason MOVED', () => {
|
|
995
1069
|
locusInfo.self = {id: 'fake-self', identity: 'user-123'};
|
|
996
1070
|
|
|
997
|
-
locusInfoUpdateCallback(OBJECTS_UPDATED,
|
|
1071
|
+
locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
|
|
998
1072
|
updatedObjects: [
|
|
999
1073
|
{
|
|
1000
1074
|
htMeta: {elementId: {type: 'participant', id: 99}},
|
|
@@ -1289,6 +1363,8 @@ describe('plugin-meetings', () => {
|
|
|
1289
1363
|
state: RECORDING_STATE.IDLE,
|
|
1290
1364
|
modifiedBy: 'George Kittle',
|
|
1291
1365
|
lastModified: 'TODAY',
|
|
1366
|
+
modifiedByServiceAppName: undefined,
|
|
1367
|
+
modifiedByServiceAppId: undefined,
|
|
1292
1368
|
}
|
|
1293
1369
|
);
|
|
1294
1370
|
});
|
|
@@ -1323,6 +1399,8 @@ describe('plugin-meetings', () => {
|
|
|
1323
1399
|
state: RECORDING_STATE.RECORDING,
|
|
1324
1400
|
modifiedBy: 'George Kittle',
|
|
1325
1401
|
lastModified: 'TODAY',
|
|
1402
|
+
modifiedByServiceAppName: undefined,
|
|
1403
|
+
modifiedByServiceAppId: undefined,
|
|
1326
1404
|
}
|
|
1327
1405
|
);
|
|
1328
1406
|
});
|
|
@@ -1358,6 +1436,8 @@ describe('plugin-meetings', () => {
|
|
|
1358
1436
|
state: RECORDING_STATE.PAUSED,
|
|
1359
1437
|
modifiedBy: 'George Kittle',
|
|
1360
1438
|
lastModified: 'TODAY',
|
|
1439
|
+
modifiedByServiceAppName: undefined,
|
|
1440
|
+
modifiedByServiceAppId: undefined,
|
|
1361
1441
|
}
|
|
1362
1442
|
);
|
|
1363
1443
|
});
|
|
@@ -1394,6 +1474,8 @@ describe('plugin-meetings', () => {
|
|
|
1394
1474
|
state: RECORDING_STATE.RESUMED,
|
|
1395
1475
|
modifiedBy: 'George Kittle',
|
|
1396
1476
|
lastModified: 'TODAY',
|
|
1477
|
+
modifiedByServiceAppName: undefined,
|
|
1478
|
+
modifiedByServiceAppId: undefined,
|
|
1397
1479
|
}
|
|
1398
1480
|
);
|
|
1399
1481
|
});
|
|
@@ -1429,6 +1511,44 @@ describe('plugin-meetings', () => {
|
|
|
1429
1511
|
state: RECORDING_STATE.IDLE,
|
|
1430
1512
|
modifiedBy: 'George Kittle',
|
|
1431
1513
|
lastModified: 'TODAY',
|
|
1514
|
+
modifiedByServiceAppName: undefined,
|
|
1515
|
+
modifiedByServiceAppId: undefined,
|
|
1516
|
+
}
|
|
1517
|
+
);
|
|
1518
|
+
});
|
|
1519
|
+
|
|
1520
|
+
it('should include service app fields in the recording event when present', () => {
|
|
1521
|
+
locusInfo.controls = {
|
|
1522
|
+
record: {
|
|
1523
|
+
recording: false,
|
|
1524
|
+
paused: false,
|
|
1525
|
+
meta: {
|
|
1526
|
+
lastModified: 'TODAY',
|
|
1527
|
+
modifiedBy: 'George Kittle',
|
|
1528
|
+
},
|
|
1529
|
+
},
|
|
1530
|
+
shareControl: {},
|
|
1531
|
+
transcribe: {},
|
|
1532
|
+
};
|
|
1533
|
+
newControls.record.recording = true;
|
|
1534
|
+
newControls.record.meta.modifiedByServiceAppName = 'My Bot';
|
|
1535
|
+
newControls.record.meta.modifiedByServiceAppId = 'app-id-123';
|
|
1536
|
+
locusInfo.emitScoped = sinon.stub();
|
|
1537
|
+
locusInfo.updateControls(newControls);
|
|
1538
|
+
|
|
1539
|
+
assert.calledWith(
|
|
1540
|
+
locusInfo.emitScoped,
|
|
1541
|
+
{
|
|
1542
|
+
file: 'locus-info',
|
|
1543
|
+
function: 'updateControls',
|
|
1544
|
+
},
|
|
1545
|
+
LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
|
|
1546
|
+
{
|
|
1547
|
+
state: RECORDING_STATE.RECORDING,
|
|
1548
|
+
modifiedBy: 'George Kittle',
|
|
1549
|
+
lastModified: 'TODAY',
|
|
1550
|
+
modifiedByServiceAppName: 'My Bot',
|
|
1551
|
+
modifiedByServiceAppId: 'app-id-123',
|
|
1432
1552
|
}
|
|
1433
1553
|
);
|
|
1434
1554
|
});
|
|
@@ -2024,7 +2144,7 @@ describe('plugin-meetings', () => {
|
|
|
2024
2144
|
function: 'updateSelf',
|
|
2025
2145
|
},
|
|
2026
2146
|
LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
|
|
2027
|
-
{muted: true, unmuteAllowed: true}
|
|
2147
|
+
{muted: true, unmuteAllowed: true, modifiedBy: null}
|
|
2028
2148
|
);
|
|
2029
2149
|
|
|
2030
2150
|
// but sometimes "previous self" is defined, but without controls.audio.muted, so we test this here:
|
|
@@ -2039,7 +2159,7 @@ describe('plugin-meetings', () => {
|
|
|
2039
2159
|
function: 'updateSelf',
|
|
2040
2160
|
},
|
|
2041
2161
|
LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
|
|
2042
|
-
{muted: true, unmuteAllowed: true}
|
|
2162
|
+
{muted: true, unmuteAllowed: true, modifiedBy: null}
|
|
2043
2163
|
);
|
|
2044
2164
|
});
|
|
2045
2165
|
|
|
@@ -2098,7 +2218,7 @@ describe('plugin-meetings', () => {
|
|
|
2098
2218
|
function: 'updateSelf',
|
|
2099
2219
|
},
|
|
2100
2220
|
LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
|
|
2101
|
-
{muted: true, unmuteAllowed: true}
|
|
2221
|
+
{muted: true, unmuteAllowed: true, modifiedBy: null}
|
|
2102
2222
|
);
|
|
2103
2223
|
});
|
|
2104
2224
|
|
|
@@ -2237,7 +2357,7 @@ describe('plugin-meetings', () => {
|
|
|
2237
2357
|
function: 'updateSelf',
|
|
2238
2358
|
},
|
|
2239
2359
|
LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
|
|
2240
|
-
{muted: true, unmuteAllowed: false}
|
|
2360
|
+
{muted: true, unmuteAllowed: false, modifiedBy: null}
|
|
2241
2361
|
);
|
|
2242
2362
|
|
|
2243
2363
|
// now change only disallowUnmute
|
|
@@ -2255,7 +2375,28 @@ describe('plugin-meetings', () => {
|
|
|
2255
2375
|
function: 'updateSelf',
|
|
2256
2376
|
},
|
|
2257
2377
|
LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
|
|
2258
|
-
{muted: true, unmuteAllowed: true}
|
|
2378
|
+
{muted: true, unmuteAllowed: true, modifiedBy: null}
|
|
2379
|
+
);
|
|
2380
|
+
});
|
|
2381
|
+
|
|
2382
|
+
it('should include modifiedBy in payload when muted by host', () => {
|
|
2383
|
+
locusInfo.webex.internal.device.url = self.deviceUrl;
|
|
2384
|
+
locusInfo.updateSelf(self);
|
|
2385
|
+
const newSelf = cloneDeep(self);
|
|
2386
|
+
newSelf.controls.audio.muted = true;
|
|
2387
|
+
newSelf.controls.audio.meta = {modifiedBy: 'host-uuid-123'};
|
|
2388
|
+
|
|
2389
|
+
locusInfo.emitScoped = sinon.stub();
|
|
2390
|
+
locusInfo.updateSelf(newSelf);
|
|
2391
|
+
|
|
2392
|
+
assert.calledWith(
|
|
2393
|
+
locusInfo.emitScoped,
|
|
2394
|
+
{
|
|
2395
|
+
file: 'locus-info',
|
|
2396
|
+
function: 'updateSelf',
|
|
2397
|
+
},
|
|
2398
|
+
LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
|
|
2399
|
+
{muted: true, unmuteAllowed: true, modifiedBy: 'host-uuid-123'}
|
|
2259
2400
|
);
|
|
2260
2401
|
});
|
|
2261
2402
|
|
|
@@ -3154,13 +3295,14 @@ describe('plugin-meetings', () => {
|
|
|
3154
3295
|
const createMockParser = (state = 'active') => ({
|
|
3155
3296
|
state,
|
|
3156
3297
|
stop: sinon.stub(),
|
|
3157
|
-
|
|
3298
|
+
resumeFromMessage: sinon.stub(),
|
|
3158
3299
|
handleMessage: sinon.stub(),
|
|
3159
3300
|
});
|
|
3160
3301
|
|
|
3161
3302
|
const createSelfElementWithReplaces = (replacedLocusUrl, replacedAt) => ({
|
|
3162
3303
|
htMeta: {elementId: {type: 'Self'}},
|
|
3163
3304
|
data: {
|
|
3305
|
+
deviceUrl,
|
|
3164
3306
|
devices: [{url: deviceUrl, replaces: [{locusUrl: replacedLocusUrl, replacedAt}]}],
|
|
3165
3307
|
},
|
|
3166
3308
|
});
|
|
@@ -3236,7 +3378,7 @@ describe('plugin-meetings', () => {
|
|
|
3236
3378
|
stateElementsMessage: message,
|
|
3237
3379
|
});
|
|
3238
3380
|
|
|
3239
|
-
assert.calledOnce(parserA.
|
|
3381
|
+
assert.calledOnce(parserA.resumeFromMessage);
|
|
3240
3382
|
assert.calledOnce(parserB.stop);
|
|
3241
3383
|
});
|
|
3242
3384
|
|
|
@@ -3259,7 +3401,7 @@ describe('plugin-meetings', () => {
|
|
|
3259
3401
|
stateElementsMessage: message,
|
|
3260
3402
|
});
|
|
3261
3403
|
|
|
3262
|
-
assert.notCalled(parserA.
|
|
3404
|
+
assert.notCalled(parserA.resumeFromMessage);
|
|
3263
3405
|
assert.notCalled(parserB.stop);
|
|
3264
3406
|
});
|
|
3265
3407
|
|
|
@@ -3278,7 +3420,7 @@ describe('plugin-meetings', () => {
|
|
|
3278
3420
|
stateElementsMessage: message,
|
|
3279
3421
|
});
|
|
3280
3422
|
|
|
3281
|
-
assert.notCalled(parserA.
|
|
3423
|
+
assert.notCalled(parserA.resumeFromMessage);
|
|
3282
3424
|
assert.notCalled(parserA.handleMessage);
|
|
3283
3425
|
});
|
|
3284
3426
|
|
|
@@ -3362,6 +3504,51 @@ describe('plugin-meetings', () => {
|
|
|
3362
3504
|
|
|
3363
3505
|
assert.calledOnceWithExactly(parserA.handleMessage, message);
|
|
3364
3506
|
});
|
|
3507
|
+
|
|
3508
|
+
it('should send mismatch metric when eventType is not HASH_TREE_DATA_UPDATED', () => {
|
|
3509
|
+
const locusUrlA = 'http://locus-url-A.com';
|
|
3510
|
+
const parserA = {state: 'active', handleMessage: sinon.stub()};
|
|
3511
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, initializedFromHashTree: true});
|
|
3512
|
+
|
|
3513
|
+
locusInfo.parse(mockMeeting, {
|
|
3514
|
+
eventType: LOCUSEVENT.SELF_CHANGED,
|
|
3515
|
+
stateElementsMessage: {locusUrl: locusUrlA, locusStateElements: [], dataSets: []},
|
|
3516
|
+
});
|
|
3517
|
+
|
|
3518
|
+
assert.calledOnceWithExactly(
|
|
3519
|
+
sendBehavioralMetricStub,
|
|
3520
|
+
'js_sdk_locus_classic_vs_hash_tree_mismatch',
|
|
3521
|
+
{
|
|
3522
|
+
correlationId: mockMeeting.correlationId,
|
|
3523
|
+
message: `got ${LOCUSEVENT.SELF_CHANGED}, expected ${LOCUSEVENT.HASH_TREE_DATA_UPDATED}`,
|
|
3524
|
+
}
|
|
3525
|
+
);
|
|
3526
|
+
assert.notCalled(parserA.handleMessage);
|
|
3527
|
+
});
|
|
3528
|
+
});
|
|
3529
|
+
|
|
3530
|
+
describe('#sendClassicVsHashTreeMismatchMetric', () => {
|
|
3531
|
+
it('should send the metric when called for the first time', () => {
|
|
3532
|
+
locusInfo.sendClassicVsHashTreeMismatchMetric(mockMeeting, 'some mismatch');
|
|
3533
|
+
|
|
3534
|
+
assert.calledOnceWithExactly(
|
|
3535
|
+
sendBehavioralMetricStub,
|
|
3536
|
+
'js_sdk_locus_classic_vs_hash_tree_mismatch',
|
|
3537
|
+
{
|
|
3538
|
+
correlationId: mockMeeting.correlationId,
|
|
3539
|
+
message: 'some mismatch',
|
|
3540
|
+
}
|
|
3541
|
+
);
|
|
3542
|
+
});
|
|
3543
|
+
|
|
3544
|
+
it('should send the metric up to 5 times and stop after that', () => {
|
|
3545
|
+
for (let i = 0; i < 7; i += 1) {
|
|
3546
|
+
locusInfo.sendClassicVsHashTreeMismatchMetric(mockMeeting, `mismatch ${i}`);
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
assert.callCount(sendBehavioralMetricStub, 5);
|
|
3550
|
+
assert.equal(locusInfo.classicVsHashTreeMismatchMetricCounter, 5);
|
|
3551
|
+
});
|
|
3365
3552
|
});
|
|
3366
3553
|
|
|
3367
3554
|
describe('#handleLocusAPIResponse', () => {
|
|
@@ -3417,19 +3604,174 @@ describe('plugin-meetings', () => {
|
|
|
3417
3604
|
assert.calledOnceWithExactly(locusInfo.handleLocusDelta, fakeLocus, mockMeeting);
|
|
3418
3605
|
});
|
|
3419
3606
|
|
|
3420
|
-
it('should send mismatch metric
|
|
3607
|
+
it('should send mismatch metric in classic mode when wrapped response has dataSets', () => {
|
|
3421
3608
|
const fakeLocus = {url: 'http://locus-url.com'};
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3609
|
+
sinon.stub(locusInfo, 'handleLocusDelta');
|
|
3610
|
+
|
|
3611
|
+
locusInfo.handleLocusAPIResponse(mockMeeting, {
|
|
3612
|
+
locus: fakeLocus,
|
|
3613
|
+
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
3426
3614
|
});
|
|
3427
|
-
sinon.stub(locusInfo, 'sendClassicVsHashTreeMismatchMetric');
|
|
3428
3615
|
|
|
3429
|
-
|
|
3616
|
+
assert.calledOnceWithExactly(
|
|
3617
|
+
sendBehavioralMetricStub,
|
|
3618
|
+
'js_sdk_locus_classic_vs_hash_tree_mismatch',
|
|
3619
|
+
{
|
|
3620
|
+
correlationId: mockMeeting.correlationId,
|
|
3621
|
+
message: 'unexpected hash tree dataSets in API response',
|
|
3622
|
+
}
|
|
3623
|
+
);
|
|
3624
|
+
assert.calledOnce(locusInfo.handleLocusDelta);
|
|
3625
|
+
});
|
|
3430
3626
|
|
|
3431
|
-
|
|
3432
|
-
|
|
3627
|
+
describe('parser switch via API response', () => {
|
|
3628
|
+
const deviceUrl = 'http://device-url.com';
|
|
3629
|
+
const locusUrlA = 'http://locus-url-A.com';
|
|
3630
|
+
const locusUrlB = 'http://locus-url-B.com';
|
|
3631
|
+
|
|
3632
|
+
let HashTreeParserStub;
|
|
3633
|
+
|
|
3634
|
+
const createMockApiParser = (state = 'active') => ({
|
|
3635
|
+
state,
|
|
3636
|
+
stop: sinon.stub(),
|
|
3637
|
+
resumeFromApiResponse: sinon.stub(),
|
|
3638
|
+
handleLocusUpdate: sinon.stub(),
|
|
3639
|
+
initializeFromGetLociResponse: sinon.stub(),
|
|
3640
|
+
});
|
|
3641
|
+
|
|
3642
|
+
const createLocusWithReplaces = (url, replacedLocusUrl, replacedAt) => ({
|
|
3643
|
+
url,
|
|
3644
|
+
self: {
|
|
3645
|
+
devices: [{url: deviceUrl, replaces: [{locusUrl: replacedLocusUrl, replacedAt}]}],
|
|
3646
|
+
},
|
|
3647
|
+
});
|
|
3648
|
+
|
|
3649
|
+
const createLocusWithoutReplaces = (url) => ({
|
|
3650
|
+
url,
|
|
3651
|
+
self: {devices: [{url: deviceUrl}]},
|
|
3652
|
+
});
|
|
3653
|
+
|
|
3654
|
+
beforeEach(() => {
|
|
3655
|
+
locusInfo.webex.internal.device.url = deviceUrl;
|
|
3656
|
+
HashTreeParserStub = sinon
|
|
3657
|
+
.stub(HashTreeParserModule, 'default')
|
|
3658
|
+
.returns(createMockApiParser());
|
|
3659
|
+
});
|
|
3660
|
+
|
|
3661
|
+
it('should create a new parser and initialize it when no entry exists for the locusUrl', () => {
|
|
3662
|
+
// existing parser for a different url so hashTreeParsers.size > 0
|
|
3663
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: createMockApiParser(), initializedFromHashTree: true});
|
|
3664
|
+
|
|
3665
|
+
const locus = createLocusWithReplaces(locusUrlB, locusUrlA, '2026-01-01T00:00:00Z');
|
|
3666
|
+
sinon.stub(locusInfo, 'handleLocusDelta');
|
|
3667
|
+
|
|
3668
|
+
locusInfo.handleLocusAPIResponse(mockMeeting, {locus});
|
|
3669
|
+
|
|
3670
|
+
assert.isTrue(locusInfo.hashTreeParsers.has(locusUrlB));
|
|
3671
|
+
const newEntry = locusInfo.hashTreeParsers.get(locusUrlB);
|
|
3672
|
+
assert.isFalse(newEntry.initializedFromHashTree);
|
|
3673
|
+
|
|
3674
|
+
// the stub returns the mock, so initializeFromGetLociResponse should be called on it
|
|
3675
|
+
const createdParser = HashTreeParserStub.returnValues[0];
|
|
3676
|
+
assert.calledOnceWithExactly(createdParser.initializeFromGetLociResponse, locus);
|
|
3677
|
+
assert.notCalled(locusInfo.handleLocusDelta);
|
|
3678
|
+
});
|
|
3679
|
+
|
|
3680
|
+
it('should reactivate a stopped parser when replaces info is newer', () => {
|
|
3681
|
+
const parserA = createMockApiParser('stopped');
|
|
3682
|
+
const parserB = createMockApiParser('active');
|
|
3683
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, replacedAt: '2026-01-01T00:00:00Z', initializedFromHashTree: true});
|
|
3684
|
+
locusInfo.hashTreeParsers.set(locusUrlB, {parser: parserB, initializedFromHashTree: true});
|
|
3685
|
+
|
|
3686
|
+
const locus = createLocusWithReplaces(locusUrlA, locusUrlB, '2026-02-01T00:00:00Z');
|
|
3687
|
+
|
|
3688
|
+
locusInfo.handleLocusAPIResponse(mockMeeting, {locus});
|
|
3689
|
+
|
|
3690
|
+
assert.calledOnce(parserA.resumeFromApiResponse);
|
|
3691
|
+
assert.calledWithExactly(parserA.resumeFromApiResponse, locus);
|
|
3692
|
+
assert.calledOnce(parserB.stop);
|
|
3693
|
+
assert.equal(locusInfo.hashTreeParsers.get(locusUrlB).replacedAt, '2026-02-01T00:00:00Z');
|
|
3694
|
+
assert.isFalse(locusInfo.hashTreeParsers.get(locusUrlA).initializedFromHashTree);
|
|
3695
|
+
});
|
|
3696
|
+
|
|
3697
|
+
it('should not reactivate a stopped parser when replaces info is not newer', () => {
|
|
3698
|
+
const parserA = createMockApiParser('stopped');
|
|
3699
|
+
const parserB = createMockApiParser('active');
|
|
3700
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, replacedAt: '2026-03-01T00:00:00Z', initializedFromHashTree: true});
|
|
3701
|
+
locusInfo.hashTreeParsers.set(locusUrlB, {parser: parserB, initializedFromHashTree: true});
|
|
3702
|
+
|
|
3703
|
+
const locus = createLocusWithReplaces(locusUrlA, locusUrlB, '2026-01-01T00:00:00Z');
|
|
3704
|
+
|
|
3705
|
+
locusInfo.handleLocusAPIResponse(mockMeeting, {locus});
|
|
3706
|
+
|
|
3707
|
+
assert.notCalled(parserA.resumeFromApiResponse);
|
|
3708
|
+
assert.notCalled(parserB.stop);
|
|
3709
|
+
});
|
|
3710
|
+
|
|
3711
|
+
it('should not reactivate a stopped parser when no replaces info is available', () => {
|
|
3712
|
+
const parserA = createMockApiParser('stopped');
|
|
3713
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, initializedFromHashTree: true});
|
|
3714
|
+
|
|
3715
|
+
const locus = createLocusWithoutReplaces(locusUrlA);
|
|
3716
|
+
|
|
3717
|
+
locusInfo.handleLocusAPIResponse(mockMeeting, {locus});
|
|
3718
|
+
|
|
3719
|
+
assert.notCalled(parserA.resumeFromApiResponse);
|
|
3720
|
+
});
|
|
3721
|
+
});
|
|
3722
|
+
});
|
|
3723
|
+
|
|
3724
|
+
describe('#syncAllHashTreeDatasets', () => {
|
|
3725
|
+
it('should call syncAllDatasets on each parser that has an entry', async () => {
|
|
3726
|
+
const parser1 = {syncAllDatasets: sinon.stub().resolves()};
|
|
3727
|
+
const parser2 = {syncAllDatasets: sinon.stub().resolves()};
|
|
3728
|
+
locusInfo.hashTreeParsers.set('url1', {parser: parser1});
|
|
3729
|
+
locusInfo.hashTreeParsers.set('url2', {parser: parser2});
|
|
3730
|
+
|
|
3731
|
+
await locusInfo.syncAllHashTreeDatasets();
|
|
3732
|
+
|
|
3733
|
+
assert.calledOnce(parser1.syncAllDatasets);
|
|
3734
|
+
assert.calledOnce(parser2.syncAllDatasets);
|
|
3735
|
+
});
|
|
3736
|
+
|
|
3737
|
+
it('should skip parser entries without a parser object', async () => {
|
|
3738
|
+
const parser1 = {syncAllDatasets: sinon.stub().resolves()};
|
|
3739
|
+
locusInfo.hashTreeParsers.set('url1', {parser: parser1});
|
|
3740
|
+
locusInfo.hashTreeParsers.set('url2', {parser: undefined});
|
|
3741
|
+
|
|
3742
|
+
await locusInfo.syncAllHashTreeDatasets();
|
|
3743
|
+
|
|
3744
|
+
assert.calledOnce(parser1.syncAllDatasets);
|
|
3745
|
+
});
|
|
3746
|
+
|
|
3747
|
+
it('should await each parsers syncAllDatasets sequentially', async () => {
|
|
3748
|
+
const callOrder = [];
|
|
3749
|
+
const parser1 = {syncAllDatasets: sinon.stub().callsFake(() => {
|
|
3750
|
+
callOrder.push('start1');
|
|
3751
|
+
return new Promise((resolve) => {
|
|
3752
|
+
setTimeout(() => {
|
|
3753
|
+
callOrder.push('end1');
|
|
3754
|
+
resolve();
|
|
3755
|
+
}, 100);
|
|
3756
|
+
});
|
|
3757
|
+
})};
|
|
3758
|
+
const parser2 = {syncAllDatasets: sinon.stub().callsFake(() => {
|
|
3759
|
+
callOrder.push('start2');
|
|
3760
|
+
return Promise.resolve();
|
|
3761
|
+
})};
|
|
3762
|
+
locusInfo.hashTreeParsers.set('url1', {parser: parser1});
|
|
3763
|
+
locusInfo.hashTreeParsers.set('url2', {parser: parser2});
|
|
3764
|
+
|
|
3765
|
+
const clock = sinon.useFakeTimers();
|
|
3766
|
+
const promise = locusInfo.syncAllHashTreeDatasets();
|
|
3767
|
+
// parser1 started but parser2 not yet
|
|
3768
|
+
assert.deepEqual(callOrder, ['start1']);
|
|
3769
|
+
|
|
3770
|
+
await clock.tickAsync(100);
|
|
3771
|
+
await promise;
|
|
3772
|
+
// parser1 finished, then parser2 started and finished
|
|
3773
|
+
assert.deepEqual(callOrder, ['start1', 'end1', 'start2']);
|
|
3774
|
+
clock.restore();
|
|
3433
3775
|
});
|
|
3434
3776
|
});
|
|
3435
3777
|
|
|
@@ -3521,49 +3863,23 @@ describe('plugin-meetings', () => {
|
|
|
3521
3863
|
assert.deepEqual(callOrder, ['updateLocusUrl', 'updateMeetingInfo']);
|
|
3522
3864
|
});
|
|
3523
3865
|
|
|
3524
|
-
it('#updateLocusInfo ignores
|
|
3525
|
-
const newLocus = {
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
state: 'LEFT',
|
|
3529
|
-
},
|
|
3530
|
-
};
|
|
3866
|
+
it('#updateLocusInfo ignores locus when isSelfMovedOrBreakoutEnded returns true', () => {
|
|
3867
|
+
const newLocus = {self: {state: 'JOINED'}};
|
|
3868
|
+
|
|
3869
|
+
sinon.stub(MeetingsUtil, 'isSelfMovedOrBreakoutEnded').returns(true);
|
|
3531
3870
|
|
|
3532
3871
|
locusInfo.updateControls = sinon.stub();
|
|
3533
|
-
locusInfo.updateConversationUrl = sinon.stub();
|
|
3534
|
-
locusInfo.updateCreated = sinon.stub();
|
|
3535
3872
|
locusInfo.updateFullState = sinon.stub();
|
|
3536
|
-
locusInfo.updateHostInfo = sinon.stub();
|
|
3537
|
-
locusInfo.updateMeetingInfo = sinon.stub();
|
|
3538
|
-
locusInfo.updateMediaShares = sinon.stub();
|
|
3539
|
-
locusInfo.updateReplaces = sinon.stub();
|
|
3540
3873
|
locusInfo.updateSelf = sinon.stub();
|
|
3541
|
-
locusInfo.updateLocusUrl = sinon.stub();
|
|
3542
|
-
locusInfo.updateAclUrl = sinon.stub();
|
|
3543
|
-
locusInfo.updateBasequence = sinon.stub();
|
|
3544
|
-
locusInfo.updateSequence = sinon.stub();
|
|
3545
|
-
locusInfo.updateEmbeddedApps = sinon.stub();
|
|
3546
|
-
locusInfo.updateLinks = sinon.stub();
|
|
3547
|
-
locusInfo.compareAndUpdate = sinon.stub();
|
|
3548
3874
|
|
|
3549
3875
|
locusInfo.updateLocusInfo(newLocus);
|
|
3550
3876
|
|
|
3877
|
+
assert.calledOnceWithExactly(MeetingsUtil.isSelfMovedOrBreakoutEnded, newLocus);
|
|
3551
3878
|
assert.notCalled(locusInfo.updateControls);
|
|
3552
|
-
assert.notCalled(locusInfo.updateConversationUrl);
|
|
3553
|
-
assert.notCalled(locusInfo.updateCreated);
|
|
3554
3879
|
assert.notCalled(locusInfo.updateFullState);
|
|
3555
|
-
assert.notCalled(locusInfo.updateHostInfo);
|
|
3556
|
-
assert.notCalled(locusInfo.updateMeetingInfo);
|
|
3557
|
-
assert.notCalled(locusInfo.updateMediaShares);
|
|
3558
|
-
assert.notCalled(locusInfo.updateReplaces);
|
|
3559
3880
|
assert.notCalled(locusInfo.updateSelf);
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
assert.notCalled(locusInfo.updateBasequence);
|
|
3563
|
-
assert.notCalled(locusInfo.updateSequence);
|
|
3564
|
-
assert.notCalled(locusInfo.updateEmbeddedApps);
|
|
3565
|
-
assert.notCalled(locusInfo.updateLinks);
|
|
3566
|
-
assert.notCalled(locusInfo.compareAndUpdate);
|
|
3881
|
+
|
|
3882
|
+
MeetingsUtil.isSelfMovedOrBreakoutEnded.restore();
|
|
3567
3883
|
});
|
|
3568
3884
|
|
|
3569
3885
|
it('#updateLocusInfo puts the Locus DTO top level properties at the right place in LocusInfo class', () => {
|
|
@@ -4413,6 +4729,31 @@ describe('plugin-meetings', () => {
|
|
|
4413
4729
|
});
|
|
4414
4730
|
});
|
|
4415
4731
|
|
|
4732
|
+
describe('#cleanUp', () => {
|
|
4733
|
+
it('calls cleanUp on all hash tree parsers and clears maps', () => {
|
|
4734
|
+
const parser1 = {cleanUp: sinon.stub()};
|
|
4735
|
+
const parser2 = {cleanUp: sinon.stub()};
|
|
4736
|
+
|
|
4737
|
+
locusInfo.hashTreeParsers.set('url1', {parser: parser1, initializedFromHashTree: true});
|
|
4738
|
+
locusInfo.hashTreeParsers.set('url2', {parser: parser2, initializedFromHashTree: true});
|
|
4739
|
+
locusInfo.hashTreeObjectId2ParticipantId.set(1, 'participant1');
|
|
4740
|
+
|
|
4741
|
+
locusInfo.cleanUp();
|
|
4742
|
+
|
|
4743
|
+
assert.calledOnce(parser1.cleanUp);
|
|
4744
|
+
assert.calledOnce(parser2.cleanUp);
|
|
4745
|
+
assert.equal(locusInfo.hashTreeParsers.size, 0);
|
|
4746
|
+
assert.equal(locusInfo.hashTreeObjectId2ParticipantId.size, 0);
|
|
4747
|
+
});
|
|
4748
|
+
|
|
4749
|
+
it('works when there are no hash tree parsers', () => {
|
|
4750
|
+
locusInfo.cleanUp();
|
|
4751
|
+
|
|
4752
|
+
assert.equal(locusInfo.hashTreeParsers.size, 0);
|
|
4753
|
+
assert.equal(locusInfo.hashTreeObjectId2ParticipantId.size, 0);
|
|
4754
|
+
});
|
|
4755
|
+
});
|
|
4756
|
+
|
|
4416
4757
|
describe('#handleOneonOneEvent', () => {
|
|
4417
4758
|
beforeEach(() => {
|
|
4418
4759
|
locusInfo.emitScoped = sinon.stub();
|
|
@@ -4455,6 +4796,9 @@ describe('plugin-meetings', () => {
|
|
|
4455
4796
|
});
|
|
4456
4797
|
|
|
4457
4798
|
describe('#isMeetingActive', () => {
|
|
4799
|
+
beforeEach(() => {
|
|
4800
|
+
webex.internal.newMetrics.submitClientEvent.resetHistory();
|
|
4801
|
+
});
|
|
4458
4802
|
forEach([_CALL_, _SIP_BRIDGE_, _SPACE_SHARE_], (type) => {
|
|
4459
4803
|
describe(`type = ${type}`, () => {
|
|
4460
4804
|
it('sends client event correctly for state = inactive', () => {
|
|
@@ -4521,7 +4865,7 @@ describe('plugin-meetings', () => {
|
|
|
4521
4865
|
});
|
|
4522
4866
|
});
|
|
4523
4867
|
|
|
4524
|
-
it('sends client event correctly for state =
|
|
4868
|
+
it('sends client event correctly for state = MEETING_INACTIVE', () => {
|
|
4525
4869
|
locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
|
|
4526
4870
|
locusInfo.parsedLocus = {
|
|
4527
4871
|
fullState: {
|
|
@@ -4543,7 +4887,7 @@ describe('plugin-meetings', () => {
|
|
|
4543
4887
|
});
|
|
4544
4888
|
});
|
|
4545
4889
|
|
|
4546
|
-
it('
|
|
4890
|
+
it('does not send client event when state = INACTIVE and endMeetingReason = BREAKOUT_ENDED', () => {
|
|
4547
4891
|
locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
|
|
4548
4892
|
locusInfo.parsedLocus = {
|
|
4549
4893
|
fullState: {
|
|
@@ -4552,17 +4896,41 @@ describe('plugin-meetings', () => {
|
|
|
4552
4896
|
};
|
|
4553
4897
|
|
|
4554
4898
|
locusInfo.fullState = {
|
|
4555
|
-
|
|
4899
|
+
state: LOCUS.STATE.INACTIVE,
|
|
4900
|
+
endMeetingReason: 'BREAKOUT_ENDED',
|
|
4556
4901
|
};
|
|
4557
4902
|
|
|
4558
4903
|
locusInfo.isMeetingActive();
|
|
4559
4904
|
|
|
4560
|
-
assert.
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4905
|
+
assert.notCalled(webex.internal.newMetrics.submitClientEvent);
|
|
4906
|
+
});
|
|
4907
|
+
|
|
4908
|
+
it('sends client event correctly for state self removed', () => {
|
|
4909
|
+
locusInfo.emitScoped = sinon.stub();
|
|
4910
|
+
locusInfo.parsedLocus = {
|
|
4911
|
+
fullState: {
|
|
4912
|
+
type: _MEETING_,
|
|
4564
4913
|
},
|
|
4565
|
-
|
|
4914
|
+
self: {
|
|
4915
|
+
removed: true,
|
|
4916
|
+
}
|
|
4917
|
+
};
|
|
4918
|
+
|
|
4919
|
+
locusInfo.isMeetingActive();
|
|
4920
|
+
|
|
4921
|
+
assert.notCalled(webex.internal.newMetrics.submitClientEvent);
|
|
4922
|
+
assert.calledOnceWithExactly(
|
|
4923
|
+
locusInfo.emitScoped,
|
|
4924
|
+
{
|
|
4925
|
+
file: 'locus-info',
|
|
4926
|
+
function: 'isMeetingActive',
|
|
4927
|
+
},
|
|
4928
|
+
EVENTS.DESTROY_MEETING,
|
|
4929
|
+
{
|
|
4930
|
+
reason: MEETING_REMOVED_REASON.SELF_REMOVED,
|
|
4931
|
+
shouldLeave: false,
|
|
4932
|
+
}
|
|
4933
|
+
);
|
|
4566
4934
|
});
|
|
4567
4935
|
});
|
|
4568
4936
|
|
|
@@ -4954,6 +5322,31 @@ describe('plugin-meetings', () => {
|
|
|
4954
5322
|
);
|
|
4955
5323
|
assert.notCalled(getTheLocusToUpdateStub);
|
|
4956
5324
|
});
|
|
5325
|
+
|
|
5326
|
+
it('should call handleLocusAPIResponse for SDK_LOCUS_FROM_SYNC_MEETINGS when hash tree parsers exist', () => {
|
|
5327
|
+
const fakeLocusUrl = 'http://locus-url.com';
|
|
5328
|
+
const fakeLocus = {url: fakeLocusUrl, fullState: {state: 'ACTIVE'}};
|
|
5329
|
+
const mockHashTreeParser = {
|
|
5330
|
+
handleMessage: sinon.stub(),
|
|
5331
|
+
handleLocusUpdate: sinon.stub(),
|
|
5332
|
+
};
|
|
5333
|
+
locusInfo.hashTreeParsers.set(fakeLocusUrl, {
|
|
5334
|
+
parser: mockHashTreeParser,
|
|
5335
|
+
initializedFromHashTree: true,
|
|
5336
|
+
});
|
|
5337
|
+
|
|
5338
|
+
sinon.stub(locusInfo, 'handleLocusDelta');
|
|
5339
|
+
|
|
5340
|
+
locusInfo.parse(mockMeeting, {
|
|
5341
|
+
eventType: LOCUSEVENT.SDK_LOCUS_FROM_SYNC_MEETINGS,
|
|
5342
|
+
locus: fakeLocus,
|
|
5343
|
+
});
|
|
5344
|
+
|
|
5345
|
+
// should route through handleLocusAPIResponse which passes unwrapped LocusDTO to parser
|
|
5346
|
+
assert.calledOnce(mockHashTreeParser.handleLocusUpdate);
|
|
5347
|
+
assert.notCalled(mockHashTreeParser.handleMessage);
|
|
5348
|
+
assert.notCalled(locusInfo.handleLocusDelta);
|
|
5349
|
+
});
|
|
4957
5350
|
});
|
|
4958
5351
|
});
|
|
4959
5352
|
|
|
@@ -5146,6 +5539,7 @@ describe('plugin-meetings', () => {
|
|
|
5146
5539
|
return {
|
|
5147
5540
|
htMeta: {elementId: {type: 'Self'}},
|
|
5148
5541
|
data: {
|
|
5542
|
+
deviceUrl,
|
|
5149
5543
|
devices,
|
|
5150
5544
|
},
|
|
5151
5545
|
};
|