@webex/plugin-meetings 3.8.1-web-workers-keepalive.1 → 3.9.0-webinar5k.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.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +8 -2
- package/dist/constants.js.map +1 -1
- package/dist/hashTree/constants.js +23 -0
- package/dist/hashTree/constants.js.map +1 -0
- package/dist/hashTree/hashTree.js +516 -0
- package/dist/hashTree/hashTree.js.map +1 -0
- package/dist/hashTree/hashTreeParser.js +521 -0
- package/dist/hashTree/hashTreeParser.js.map +1 -0
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +301 -59
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/brbState.js +14 -12
- package/dist/meeting/brbState.js.map +1 -1
- package/dist/meeting/index.js +110 -12
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +2 -5
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +19 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +8 -11
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +6 -2
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/members/collection.js +13 -0
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +44 -23
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +3 -3
- package/dist/members/request.js.map +1 -1
- package/dist/members/util.js +18 -6
- package/dist/members/util.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +32 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/types/constants.d.ts +6 -0
- package/dist/types/hashTree/constants.d.ts +8 -0
- package/dist/types/hashTree/hashTree.d.ts +128 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +152 -0
- package/dist/types/locus-info/index.d.ts +93 -3
- package/dist/types/meeting/brbState.d.ts +0 -1
- package/dist/types/meeting/index.d.ts +29 -3
- package/dist/types/meeting/request.d.ts +9 -1
- package/dist/types/meeting/request.type.d.ts +74 -0
- package/dist/types/meeting/util.d.ts +3 -3
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/members/collection.d.ts +6 -0
- package/dist/types/members/index.d.ts +15 -3
- package/dist/types/members/request.d.ts +1 -1
- package/dist/types/members/util.d.ts +5 -2
- package/dist/types/multistream/sendSlotManager.d.ts +16 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +24 -23
- package/src/constants.ts +7 -0
- package/src/hashTree/constants.ts +12 -0
- package/src/hashTree/hashTree.ts +460 -0
- package/src/hashTree/hashTreeParser.ts +556 -0
- package/src/locus-info/index.ts +393 -58
- package/src/meeting/brbState.ts +9 -7
- package/src/meeting/index.ts +104 -6
- package/src/meeting/muteState.ts +2 -6
- package/src/meeting/request.ts +16 -0
- package/src/meeting/request.type.ts +64 -0
- package/src/meeting/util.ts +17 -20
- package/src/meetings/index.ts +17 -3
- package/src/member/index.ts +1 -0
- package/src/member/types.ts +1 -0
- package/src/members/collection.ts +11 -0
- package/src/members/index.ts +33 -7
- package/src/members/request.ts +2 -2
- package/src/members/util.ts +14 -3
- package/src/multistream/sendSlotManager.ts +34 -2
- package/test/unit/spec/hashTree/hashTree.ts +394 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +156 -0
- package/test/unit/spec/locus-info/index.js +506 -55
- package/test/unit/spec/meeting/brbState.ts +9 -9
- package/test/unit/spec/meeting/index.js +475 -42
- package/test/unit/spec/meeting/request.js +71 -0
- package/test/unit/spec/members/index.js +33 -10
- package/test/unit/spec/members/request.js +2 -2
- package/test/unit/spec/members/utils.js +27 -7
- package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
- package/test/unit/spec/reachability/index.ts +3 -1
@@ -305,7 +305,7 @@ describe('plugin-meetings', () => {
|
|
305
305
|
{state: newControls.rdcControl}
|
306
306
|
);
|
307
307
|
});
|
308
|
-
|
308
|
+
|
309
309
|
it('should trigger the CONTROLS_POLLING_QA_CHANGED event when necessary', () => {
|
310
310
|
locusInfo.controls = {};
|
311
311
|
locusInfo.emitScoped = sinon.stub();
|
@@ -772,7 +772,7 @@ describe('plugin-meetings', () => {
|
|
772
772
|
},
|
773
773
|
};
|
774
774
|
locusInfo.emitScoped = sinon.stub();
|
775
|
-
locusInfo.updateParticipants({});
|
775
|
+
locusInfo.updateParticipants({}, []);
|
776
776
|
|
777
777
|
// if this assertion fails, double-check the attributes used in
|
778
778
|
// the updateParticipants function in locus-info/index.js
|
@@ -790,6 +790,7 @@ describe('plugin-meetings', () => {
|
|
790
790
|
selfId: '2',
|
791
791
|
hostId: '3',
|
792
792
|
isReplace: undefined,
|
793
|
+
removedParticipantIds: [],
|
793
794
|
}
|
794
795
|
);
|
795
796
|
// note: in a real use case, recordingId, selfId, and hostId would all be the same
|
@@ -814,7 +815,7 @@ describe('plugin-meetings', () => {
|
|
814
815
|
};
|
815
816
|
|
816
817
|
locusInfo.emitScoped = sinon.stub();
|
817
|
-
locusInfo.updateParticipants({}, true);
|
818
|
+
locusInfo.updateParticipants({}, [], true);
|
818
819
|
|
819
820
|
assert.calledWith(
|
820
821
|
locusInfo.emitScoped,
|
@@ -830,6 +831,7 @@ describe('plugin-meetings', () => {
|
|
830
831
|
selfId: '2',
|
831
832
|
hostId: '3',
|
832
833
|
isReplace: true,
|
834
|
+
removedParticipantIds: [],
|
833
835
|
}
|
834
836
|
);
|
835
837
|
});
|
@@ -880,7 +882,7 @@ describe('plugin-meetings', () => {
|
|
880
882
|
];
|
881
883
|
|
882
884
|
locusInfo.emitScoped = sinon.stub();
|
883
|
-
locusInfo.updateParticipants(failureParticipant);
|
885
|
+
locusInfo.updateParticipants(failureParticipant, []);
|
884
886
|
assert.calledWith(
|
885
887
|
locusInfo.emitScoped,
|
886
888
|
{
|
@@ -2061,7 +2063,7 @@ describe('plugin-meetings', () => {
|
|
2061
2063
|
|
2062
2064
|
fakeLocus = {
|
2063
2065
|
meeting: true,
|
2064
|
-
participants:
|
2066
|
+
participants: [],
|
2065
2067
|
url: 'newLocusUrl',
|
2066
2068
|
syncUrl: 'newSyncUrl',
|
2067
2069
|
};
|
@@ -2108,6 +2110,38 @@ describe('plugin-meetings', () => {
|
|
2108
2110
|
assert.isFunction(locusParser.onDeltaAction);
|
2109
2111
|
});
|
2110
2112
|
|
2113
|
+
it("#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo", () => {
|
2114
|
+
const callOrder = [];
|
2115
|
+
sinon.stub(locusInfo, "updateControls");
|
2116
|
+
sinon.stub(locusInfo, "updateConversationUrl");
|
2117
|
+
sinon.stub(locusInfo, "updateCreated");
|
2118
|
+
sinon.stub(locusInfo, "updateFullState");
|
2119
|
+
sinon.stub(locusInfo, "updateHostInfo");
|
2120
|
+
sinon.stub(locusInfo, "updateMeetingInfo").callsFake(() => {
|
2121
|
+
callOrder.push("updateMeetingInfo");
|
2122
|
+
});
|
2123
|
+
sinon.stub(locusInfo, "updateMediaShares");
|
2124
|
+
sinon.stub(locusInfo, "updateParticipantsUrl");
|
2125
|
+
sinon.stub(locusInfo, "updateReplace");
|
2126
|
+
sinon.stub(locusInfo, "updateSelf");
|
2127
|
+
sinon.stub(locusInfo, "updateLocusUrl").callsFake(() => {
|
2128
|
+
callOrder.push("updateLocusUrl");
|
2129
|
+
});
|
2130
|
+
sinon.stub(locusInfo, "updateAclUrl");
|
2131
|
+
sinon.stub(locusInfo, "updateBasequence");
|
2132
|
+
sinon.stub(locusInfo, "updateSequence");
|
2133
|
+
sinon.stub(locusInfo, "updateMemberShip");
|
2134
|
+
sinon.stub(locusInfo, "updateIdentifiers");
|
2135
|
+
sinon.stub(locusInfo, "updateEmbeddedApps");
|
2136
|
+
sinon.stub(locusInfo, "updateResources");
|
2137
|
+
sinon.stub(locusInfo, "compareAndUpdate");
|
2138
|
+
|
2139
|
+
locusInfo.updateLocusInfo(locus);
|
2140
|
+
|
2141
|
+
// Ensure updateLocusUrl is called before updateMeetingInfo if both are called
|
2142
|
+
assert.deepEqual(callOrder, ['updateLocusUrl', 'updateMeetingInfo']);
|
2143
|
+
});
|
2144
|
+
|
2111
2145
|
it('#updateLocusInfo ignores breakout LEFT message', () => {
|
2112
2146
|
const newLocus = {
|
2113
2147
|
self: {
|
@@ -2159,6 +2193,8 @@ describe('plugin-meetings', () => {
|
|
2159
2193
|
assert.notCalled(locusInfo.compareAndUpdate);
|
2160
2194
|
});
|
2161
2195
|
|
2196
|
+
|
2197
|
+
|
2162
2198
|
it('onFullLocus() updates the working-copy of locus parser', () => {
|
2163
2199
|
const eventType = 'fakeEvent';
|
2164
2200
|
|
@@ -2257,7 +2293,7 @@ describe('plugin-meetings', () => {
|
|
2257
2293
|
|
2258
2294
|
it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl', () => {
|
2259
2295
|
const {DESYNC} = LocusDeltaParser.loci;
|
2260
|
-
const fakeDeltaLocus = {id: 'fake delta locus'};
|
2296
|
+
const fakeDeltaLocus = {baseSequence: {}, id: 'fake delta locus'};
|
2261
2297
|
const meeting = {
|
2262
2298
|
meetingRequest: {
|
2263
2299
|
getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
|
@@ -2392,25 +2428,22 @@ describe('plugin-meetings', () => {
|
|
2392
2428
|
};
|
2393
2429
|
});
|
2394
2430
|
|
2395
|
-
it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', () => {
|
2431
|
+
it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', async () => {
|
2396
2432
|
meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
|
2397
2433
|
|
2398
2434
|
locusInfo.locusParser.workingCopy = {}; // no syncUrl
|
2399
2435
|
|
2400
|
-
|
2401
|
-
// we will wait and stub it's last function to resolve this waiting promise.
|
2402
|
-
return new Promise((resolve) => {
|
2403
|
-
webex.meetings.destroy.callsFake(() => resolve());
|
2404
|
-
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2405
|
-
}).then(() => {
|
2406
|
-
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
|
2436
|
+
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2407
2437
|
|
2408
|
-
|
2409
|
-
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2410
|
-
assert.notCalled(locusInfo.locusParser.resume);
|
2438
|
+
await testUtils.flushPromises();
|
2411
2439
|
|
2412
|
-
|
2413
|
-
|
2440
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
|
2441
|
+
|
2442
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2443
|
+
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2444
|
+
assert.notCalled(locusInfo.locusParser.resume);
|
2445
|
+
|
2446
|
+
assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
|
2414
2447
|
});
|
2415
2448
|
|
2416
2449
|
it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails, does a full locus sync', () => {
|
@@ -2447,44 +2480,67 @@ describe('plugin-meetings', () => {
|
|
2447
2480
|
});
|
2448
2481
|
});
|
2449
2482
|
|
2450
|
-
it('applyLocusDeltaData
|
2483
|
+
it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails with 403, it does not do a full locus sync', async () => {
|
2484
|
+
const fake403Error = new Error('fake error');
|
2485
|
+
fake403Error.statusCode = 403;
|
2486
|
+
|
2487
|
+
meeting.meetingRequest.getLocusDTO.onCall(0).rejects(fake403Error);
|
2488
|
+
|
2489
|
+
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2490
|
+
|
2491
|
+
await testUtils.flushPromises();
|
2492
|
+
|
2493
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'deltaSyncUrl'});
|
2494
|
+
|
2495
|
+
assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
|
2496
|
+
correlationId: meeting.correlationId,
|
2497
|
+
url: 'deltaSyncUrl',
|
2498
|
+
reason: 'fake error',
|
2499
|
+
errorName: 'Error',
|
2500
|
+
stack: sinon.match.any,
|
2501
|
+
code: sinon.match.any,
|
2502
|
+
});
|
2503
|
+
|
2504
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2505
|
+
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2506
|
+
assert.notCalled(locusInfo.locusParser.resume);
|
2507
|
+
});
|
2508
|
+
|
2509
|
+
it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', async () => {
|
2451
2510
|
meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
|
2452
2511
|
|
2453
|
-
|
2454
|
-
// we will wait and stub it's last function to resolve this waiting promise.
|
2455
|
-
return new Promise((resolve) => {
|
2456
|
-
webex.meetings.destroy.callsFake(() => resolve());
|
2457
|
-
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2458
|
-
}).then(() => {
|
2459
|
-
assert.calledTwice(meeting.meetingRequest.getLocusDTO);
|
2512
|
+
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2460
2513
|
|
2461
|
-
|
2462
|
-
{url: 'deltaSyncUrl'},
|
2463
|
-
]);
|
2464
|
-
assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
|
2465
|
-
{url: 'fullSyncUrl'},
|
2466
|
-
]);
|
2514
|
+
await testUtils.flushPromises();
|
2467
2515
|
|
2468
|
-
|
2469
|
-
correlationId: meeting.correlationId,
|
2470
|
-
url: 'deltaSyncUrl',
|
2471
|
-
reason: 'fake error',
|
2472
|
-
errorName: 'Error',
|
2473
|
-
stack: sinon.match.any,
|
2474
|
-
code: sinon.match.any,
|
2475
|
-
});
|
2516
|
+
assert.calledTwice(meeting.meetingRequest.getLocusDTO);
|
2476
2517
|
|
2477
|
-
|
2478
|
-
|
2479
|
-
|
2518
|
+
assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [
|
2519
|
+
{url: 'deltaSyncUrl'},
|
2520
|
+
]);
|
2521
|
+
assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
|
2522
|
+
{url: 'fullSyncUrl'},
|
2523
|
+
]);
|
2480
2524
|
|
2481
|
-
|
2525
|
+
assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
|
2526
|
+
correlationId: meeting.correlationId,
|
2527
|
+
url: 'deltaSyncUrl',
|
2528
|
+
reason: 'fake error',
|
2529
|
+
errorName: 'Error',
|
2530
|
+
stack: sinon.match.any,
|
2531
|
+
code: sinon.match.any,
|
2482
2532
|
});
|
2533
|
+
|
2534
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2535
|
+
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2536
|
+
assert.notCalled(locusInfo.locusParser.resume);
|
2537
|
+
|
2538
|
+
assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
|
2483
2539
|
});
|
2484
2540
|
});
|
2485
2541
|
|
2486
2542
|
it('onDeltaLocus handle delta data', () => {
|
2487
|
-
fakeLocus.participants =
|
2543
|
+
fakeLocus.participants = [];
|
2488
2544
|
const fakeBreakout = {
|
2489
2545
|
sessionId: 'sessionId',
|
2490
2546
|
groupId: 'groupId',
|
@@ -2501,17 +2557,15 @@ describe('plugin-meetings', () => {
|
|
2501
2557
|
};
|
2502
2558
|
locusInfo.updateParticipants = sinon.stub();
|
2503
2559
|
locusInfo.onDeltaLocus(fakeLocus);
|
2504
|
-
assert.calledWith(locusInfo.updateParticipants,
|
2560
|
+
assert.calledWith(locusInfo.updateParticipants, [], undefined, false);
|
2505
2561
|
|
2506
2562
|
fakeLocus.controls.breakout.sessionId = 'sessionId2';
|
2507
2563
|
locusInfo.onDeltaLocus(fakeLocus);
|
2508
|
-
assert.calledWith(locusInfo.updateParticipants,
|
2564
|
+
assert.calledWith(locusInfo.updateParticipants, [], undefined, true);
|
2509
2565
|
});
|
2510
2566
|
|
2511
2567
|
it('onDeltaLocus merges delta participants with existing participants', () => {
|
2512
|
-
const FAKE_DELTA_PARTICIPANTS = [
|
2513
|
-
{id: '1111'}, {id: '2222'}
|
2514
|
-
]
|
2568
|
+
const FAKE_DELTA_PARTICIPANTS = [{id: '1111'}, {id: '2222'}];
|
2515
2569
|
fakeLocus.participants = FAKE_DELTA_PARTICIPANTS;
|
2516
2570
|
|
2517
2571
|
sinon.spy(locusInfo, 'mergeParticipants');
|
@@ -2519,8 +2573,86 @@ describe('plugin-meetings', () => {
|
|
2519
2573
|
const existingParticipants = locusInfo.participants;
|
2520
2574
|
|
2521
2575
|
locusInfo.onDeltaLocus(fakeLocus);
|
2522
|
-
assert.calledOnceWithExactly(
|
2523
|
-
|
2576
|
+
assert.calledOnceWithExactly(
|
2577
|
+
locusInfo.mergeParticipants,
|
2578
|
+
existingParticipants,
|
2579
|
+
FAKE_DELTA_PARTICIPANTS
|
2580
|
+
);
|
2581
|
+
assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, undefined, false);
|
2582
|
+
});
|
2583
|
+
|
2584
|
+
[true, false].forEach((isDelta) =>
|
2585
|
+
it(`applyLocusDeltaData - handles empty ${
|
2586
|
+
isDelta ? 'delta' : 'full'
|
2587
|
+
} DTO in response`, async () => {
|
2588
|
+
const {DESYNC} = LocusDeltaParser.loci;
|
2589
|
+
const fakeFullLocusDto = {};
|
2590
|
+
const meeting = {
|
2591
|
+
meetingRequest: {
|
2592
|
+
getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
|
2593
|
+
},
|
2594
|
+
locusInfo: {
|
2595
|
+
onFullLocus: sandbox.stub(),
|
2596
|
+
handleLocusDelta: sandbox.stub(),
|
2597
|
+
},
|
2598
|
+
locusUrl: 'fake locus FULL url',
|
2599
|
+
};
|
2600
|
+
|
2601
|
+
sinon.stub(locusInfo.locusParser, 'resume').resolves();
|
2602
|
+
|
2603
|
+
if (isDelta) {
|
2604
|
+
locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
|
2605
|
+
} else {
|
2606
|
+
locusInfo.locusParser.workingCopy = {}; // no syncUrl (to trigger FULL DTO request)
|
2607
|
+
}
|
2608
|
+
|
2609
|
+
await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2610
|
+
|
2611
|
+
await testUtils.flushPromises();
|
2612
|
+
|
2613
|
+
if (isDelta) {
|
2614
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
|
2615
|
+
url: 'fake locus DELTA url',
|
2616
|
+
});
|
2617
|
+
} else {
|
2618
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
|
2619
|
+
url: 'fake locus FULL url',
|
2620
|
+
});
|
2621
|
+
}
|
2622
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2623
|
+
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2624
|
+
assert.calledOnce(locusInfo.locusParser.resume);
|
2625
|
+
})
|
2626
|
+
);
|
2627
|
+
|
2628
|
+
it(`applyLocusDeltaData - handles the case when we get FULL DTO when we asked for DELTA DTO`, async () => {
|
2629
|
+
const {DESYNC} = LocusDeltaParser.loci;
|
2630
|
+
const fakeFullLocusDto = {someStuff: 'data'}; // non-empty DTO, without baseSequence
|
2631
|
+
const meeting = {
|
2632
|
+
meetingRequest: {
|
2633
|
+
getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
|
2634
|
+
},
|
2635
|
+
locusInfo: {
|
2636
|
+
onFullLocus: sandbox.stub(),
|
2637
|
+
handleLocusDelta: sandbox.stub(),
|
2638
|
+
},
|
2639
|
+
locusUrl: 'fake locus FULL url',
|
2640
|
+
};
|
2641
|
+
|
2642
|
+
sinon.stub(locusInfo.locusParser, 'resume').resolves();
|
2643
|
+
|
2644
|
+
locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
|
2645
|
+
|
2646
|
+
await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2647
|
+
|
2648
|
+
await testUtils.flushPromises();
|
2649
|
+
|
2650
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
|
2651
|
+
url: 'fake locus DELTA url',
|
2652
|
+
});
|
2653
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2654
|
+
assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
|
2655
|
+
assert.calledOnce(locusInfo.locusParser.resume);
|
2524
2656
|
});
|
2525
2657
|
});
|
2526
2658
|
|
@@ -2936,8 +3068,8 @@ describe('plugin-meetings', () => {
|
|
2936
3068
|
|
2937
3069
|
sinon.stub(locusInfo, 'updateParticipantDeltas');
|
2938
3070
|
sinon.stub(locusInfo, 'updateParticipants');
|
2939
|
-
sinon.stub(locusInfo, 'isMeetingActive')
|
2940
|
-
sinon.stub(locusInfo, 'handleOneOnOneEvent')
|
3071
|
+
sinon.stub(locusInfo, 'isMeetingActive');
|
3072
|
+
sinon.stub(locusInfo, 'handleOneOnOneEvent');
|
2941
3073
|
(updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo'));
|
2942
3074
|
syncRequestStub = sinon.stub().resolves({body: {}});
|
2943
3075
|
|
@@ -2958,6 +3090,7 @@ describe('plugin-meetings', () => {
|
|
2958
3090
|
id: 'test person id',
|
2959
3091
|
},
|
2960
3092
|
},
|
3093
|
+
participants: [],
|
2961
3094
|
});
|
2962
3095
|
|
2963
3096
|
updateLocusInfoStub.resetHistory();
|
@@ -3106,6 +3239,7 @@ describe('plugin-meetings', () => {
|
|
3106
3239
|
await testUtils.flushPromises();
|
3107
3240
|
|
3108
3241
|
assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
|
3242
|
+
assert.calledOnce(updateLocusInfoStub);
|
3109
3243
|
assert.calledOnceWithExactly(updateLocusInfoStub, fullLocus);
|
3110
3244
|
});
|
3111
3245
|
|
@@ -3181,5 +3315,322 @@ describe('plugin-meetings', () => {
|
|
3181
3315
|
assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[7]);
|
3182
3316
|
});
|
3183
3317
|
});
|
3318
|
+
|
3319
|
+
describe('Hash trees - webinar 5k', () => {
|
3320
|
+
let mockFullLocus;
|
3321
|
+
let clock;
|
3322
|
+
|
3323
|
+
beforeEach(() => {
|
3324
|
+
|
3325
|
+
sinon.stub(Math, 'random').returns(0.5); // to make sure the backoff timer is predictable
|
3326
|
+
|
3327
|
+
mockFullLocus = {
|
3328
|
+
dataSets: [
|
3329
|
+
{
|
3330
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/session/a73e9f2c/datasets/main',
|
3331
|
+
root: '9bb9d5a911a74d53a915b4dfbec7329f',
|
3332
|
+
version: 51118,
|
3333
|
+
leafCount: 16,
|
3334
|
+
name: 'main',
|
3335
|
+
idleMs : 5000,
|
3336
|
+
backoff : {
|
3337
|
+
maxMs : 500,
|
3338
|
+
exponent : 0.5
|
3339
|
+
},
|
3340
|
+
},
|
3341
|
+
{
|
3342
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/session/a73e9f2c/participant/713e9f99/datasets/self',
|
3343
|
+
root: '5b8cc7ffda1346d2bfb1c0b60b8ab601',
|
3344
|
+
version: 89891,
|
3345
|
+
leafCount: 1,
|
3346
|
+
name: 'self',
|
3347
|
+
idleMs : 10000,
|
3348
|
+
backoff : {
|
3349
|
+
maxMs : 500,
|
3350
|
+
exponent : 0.5
|
3351
|
+
},
|
3352
|
+
},
|
3353
|
+
{
|
3354
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/session/a73e9f2c/datasets/atd-unmuted',
|
3355
|
+
root: '9279d2e149da43a1b8e2cd7cbf77f9f0',
|
3356
|
+
version: 91277,
|
3357
|
+
leafCount: 16,
|
3358
|
+
name: 'atd-unmuted',
|
3359
|
+
idleMs : 15000,
|
3360
|
+
backoff : {
|
3361
|
+
maxMs : 500,
|
3362
|
+
exponent : 0.5
|
3363
|
+
},
|
3364
|
+
},
|
3365
|
+
],
|
3366
|
+
locus: {
|
3367
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f',
|
3368
|
+
htMeta: {
|
3369
|
+
elementId: {
|
3370
|
+
type: 'LOCUS',
|
3371
|
+
id: 0,
|
3372
|
+
version: 5678,
|
3373
|
+
},
|
3374
|
+
dataSetNames: ['main'],
|
3375
|
+
},
|
3376
|
+
participants: [
|
3377
|
+
{
|
3378
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/11941033',
|
3379
|
+
person: {
|
3380
|
+
id: '111',
|
3381
|
+
name: '1st participant',
|
3382
|
+
},
|
3383
|
+
htMeta: {
|
3384
|
+
elementId: {
|
3385
|
+
type: 'PARTICIPANT',
|
3386
|
+
id: 2,
|
3387
|
+
version: 5678,
|
3388
|
+
},
|
3389
|
+
dataSetNames: ['atd-active', 'attendees', 'atd-unmuted'],
|
3390
|
+
},
|
3391
|
+
},
|
3392
|
+
{
|
3393
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/11941034',
|
3394
|
+
person: {
|
3395
|
+
id: '222',
|
3396
|
+
name: '2nd participant',
|
3397
|
+
},
|
3398
|
+
htMeta: {
|
3399
|
+
elementId: {
|
3400
|
+
type: 'PARTICIPANT',
|
3401
|
+
id: 3,
|
3402
|
+
version: 5678,
|
3403
|
+
},
|
3404
|
+
dataSetNames: ['attendees'],
|
3405
|
+
},
|
3406
|
+
},
|
3407
|
+
],
|
3408
|
+
self: {
|
3409
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/11941033',
|
3410
|
+
visibleDataSets: ['main', 'self', 'atd-unmuted'],
|
3411
|
+
person: {
|
3412
|
+
id: '333',
|
3413
|
+
name: 'myself',
|
3414
|
+
},
|
3415
|
+
htMeta: {
|
3416
|
+
elementId: {
|
3417
|
+
type: 'SELF',
|
3418
|
+
id: 4,
|
3419
|
+
version: 5678,
|
3420
|
+
},
|
3421
|
+
dataSetNames: ['self'],
|
3422
|
+
},
|
3423
|
+
},
|
3424
|
+
},
|
3425
|
+
};
|
3426
|
+
|
3427
|
+
clock = sinon.useFakeTimers();
|
3428
|
+
|
3429
|
+
});
|
3430
|
+
|
3431
|
+
afterEach(() => {
|
3432
|
+
clock.restore();
|
3433
|
+
});
|
3434
|
+
|
3435
|
+
const getMaxBackoffTime = (dataSets) => {
|
3436
|
+
return Object.values(dataSets).reduce((max, dataSet) => {
|
3437
|
+
const maxBackOff = dataSet.idleMs + dataSet.backoff.maxMs;
|
3438
|
+
return Math.max(max, maxBackOff);
|
3439
|
+
}, 0);
|
3440
|
+
}
|
3441
|
+
|
3442
|
+
const waitForMaxPossibleBackoffTime = async (dataSets) => {
|
3443
|
+
const maxBackoffTime = getMaxBackoffTime(dataSets);
|
3444
|
+
clock.tick(maxBackoffTime);
|
3445
|
+
await testUtils.flushPromises();
|
3446
|
+
}
|
3447
|
+
|
3448
|
+
it('initializes hash trees correctly from initial full locus', () => {
|
3449
|
+
locusInfo.initialSetup(mockFullLocus.locus, mockFullLocus.dataSets);
|
3450
|
+
|
3451
|
+
// check that the hash tree parser is initialized correctly
|
3452
|
+
assert.isDefined(locusInfo.hashTreeParser);
|
3453
|
+
assert.deepEqual(Object.keys(locusInfo.hashTreeParser.dataSets), ['main', 'self', 'atd-unmuted']);
|
3454
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['main'].hashTree.getLeafData(0), [ { type: 'LOCUS', id: 0, version: 5678 } ]);
|
3455
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['self'].hashTree.getLeafData(0), [ { type: 'SELF', id: 4, version: 5678 } ]);
|
3456
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['atd-unmuted'].hashTree.getLeafData(2), [ { type: 'PARTICIPANT', id: 2, version: 5678 } ]);
|
3457
|
+
|
3458
|
+
// participant with id=3 is not part of any of our datasets, so should be undefined
|
3459
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['atd-unmuted'].hashTree.getLeafData(3), []);
|
3460
|
+
});
|
3461
|
+
|
3462
|
+
it('handles hash tree messages correctly', async () => {
|
3463
|
+
locusInfo.initialSetup(mockFullLocus.locus, mockFullLocus.dataSets);
|
3464
|
+
|
3465
|
+
// simulate a hash tree message for a participant
|
3466
|
+
locusInfo.parse(mockMeeting, {
|
3467
|
+
"locusUrl" : "https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2",
|
3468
|
+
"locusSessionId" : "fe3d019c-08ca-0f21-919f-f2cc646030ae",
|
3469
|
+
"dataSets" : [ {
|
3470
|
+
"url" : "https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/session/fe3d019c-08ca-0f21-919f-f2cc646030ae/datasets/attendees",
|
3471
|
+
"name" : "attendees",
|
3472
|
+
"root" : "7cfc14bdae7909e6fe7acd37bebf66db",
|
3473
|
+
"version" : 4739733700975,
|
3474
|
+
"leafCount" : 16,
|
3475
|
+
"idleMs" : 5000,
|
3476
|
+
"backoff" : {
|
3477
|
+
"maxMs" : 500,
|
3478
|
+
"exponent" : 0.5
|
3479
|
+
}
|
3480
|
+
}, {
|
3481
|
+
"url" : "https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/session/fe3d019c-08ca-0f21-919f-f2cc646030ae/datasets/atd-active",
|
3482
|
+
"name" : "atd-unmuted",
|
3483
|
+
"root" : "178bff6e3344f551a811712c57a9eac3",
|
3484
|
+
"version" : 5738696122316,
|
3485
|
+
"leafCount" : 4,
|
3486
|
+
"idleMs" : 5000,
|
3487
|
+
"backoff" : {
|
3488
|
+
"maxMs" : 500,
|
3489
|
+
"exponent" : 0.5
|
3490
|
+
}
|
3491
|
+
} ],
|
3492
|
+
"locusStateElements" : [ {
|
3493
|
+
"htMeta" : {
|
3494
|
+
"elementId" : {
|
3495
|
+
"type" : "PARTICIPANT",
|
3496
|
+
"id" : 2,
|
3497
|
+
"version" : 5679
|
3498
|
+
},
|
3499
|
+
"dataSetNames" : [ "attendees", "atd-unmuted" ]
|
3500
|
+
},
|
3501
|
+
"data" : "{\"isCreator\":false,\"url\":\"https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/participant/18ef210c-234a-49ac-83d6-1803bc401bc9\",\"id\":\"18ef210c-234a-49ac-83d6-1803bc401bc9\",\"guest\":false,\"resourceGuest\":false,\"moderator\":false,\"panelist\":false}"
|
3502
|
+
}, {
|
3503
|
+
"htMeta" : {
|
3504
|
+
"elementId" : {
|
3505
|
+
"type" : "PARTICIPANT",
|
3506
|
+
"id" : 3,
|
3507
|
+
"version" : 999999
|
3508
|
+
},
|
3509
|
+
"dataSetNames" : [ "attendees" ]
|
3510
|
+
},
|
3511
|
+
"data" : "{\"isCreator\":false,\"url\":\"https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/participant/29c0751a-7ada-40a1-94a4-eb5f5c80c863\",\"id\":\"29c0751a-7ada-40a1-94a4-eb5f5c80c863\",\"guest\":false,\"resourceGuest\":false,\"moderator\":false,\"panelist\":false}"
|
3512
|
+
} ]
|
3513
|
+
});
|
3514
|
+
|
3515
|
+
// main and self should be unchanged
|
3516
|
+
assert.deepEqual(Object.keys(locusInfo.hashTreeParser.dataSets), ['main', 'self', 'atd-unmuted']);
|
3517
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['main'].hashTree.getLeafData(0), [ { type: 'LOCUS', id: 0, version: 5678 } ]);
|
3518
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['self'].hashTree.getLeafData(0), [ { type: 'SELF', id: 4, version: 5678 } ]);
|
3519
|
+
|
3520
|
+
// participant should be updated
|
3521
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['atd-unmuted'].hashTree.getLeafData(2), [ { type: 'PARTICIPANT', id: 2, version: 5679 } ]);
|
3522
|
+
|
3523
|
+
// there should be no requests to Locus sent
|
3524
|
+
await waitForMaxPossibleBackoffTime(locusInfo.hashTreeParser.dataSets);
|
3525
|
+
assert.notCalled(webex.request);
|
3526
|
+
});
|
3527
|
+
|
3528
|
+
it('does a sync if hashes don\'t match after a timer fires', async () => {
|
3529
|
+
const atdUnmutedDataSetUrl = mockFullLocus.dataSets[2].url;
|
3530
|
+
|
3531
|
+
locusInfo.initialSetup(mockFullLocus.locus, mockFullLocus.dataSets);
|
3532
|
+
|
3533
|
+
// simulate a hash tree message for a participant
|
3534
|
+
locusInfo.parse(mockMeeting, {
|
3535
|
+
"locusUrl" : "https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2",
|
3536
|
+
"locusSessionId" : "fe3d019c-08ca-0f21-919f-f2cc646030ae",
|
3537
|
+
"dataSets" : [ {
|
3538
|
+
"url" : atdUnmutedDataSetUrl,
|
3539
|
+
"name" : "atd-unmuted",
|
3540
|
+
"root" : "deadbeef", // wrong to trigger a sync
|
3541
|
+
"version" : 5738696122316,
|
3542
|
+
"leafCount" : 4,
|
3543
|
+
"idleMs" : 25000,
|
3544
|
+
"backoff" : {
|
3545
|
+
"maxMs" : 500,
|
3546
|
+
"exponent" : 0.5
|
3547
|
+
}
|
3548
|
+
} ],
|
3549
|
+
"locusStateElements" : [ {
|
3550
|
+
"htMeta" : {
|
3551
|
+
"elementId" : {
|
3552
|
+
"type" : "PARTICIPANT",
|
3553
|
+
"id" : 2,
|
3554
|
+
"version" : 5679
|
3555
|
+
},
|
3556
|
+
"dataSetNames" : [ "attendees", "atd-unmuted" ]
|
3557
|
+
},
|
3558
|
+
"data" : "{\"isCreator\":false,\"url\":\"https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/participant/18ef210c-234a-49ac-83d6-1803bc401bc9\",\"id\":\"18ef210c-234a-49ac-83d6-1803bc401bc9\",\"guest\":false,\"resourceGuest\":false,\"moderator\":false,\"panelist\":false}"
|
3559
|
+
}]
|
3560
|
+
});
|
3561
|
+
|
3562
|
+
// main and self should be unchanged
|
3563
|
+
assert.deepEqual(Object.keys(locusInfo.hashTreeParser.dataSets), ['main', 'self', 'atd-unmuted']);
|
3564
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['main'].hashTree.getLeafData(0), [ { type: 'LOCUS', id: 0, version: 5678 } ]);
|
3565
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['self'].hashTree.getLeafData(0), [ { type: 'SELF', id: 4, version: 5678 } ]);
|
3566
|
+
|
3567
|
+
// participant should be updated
|
3568
|
+
assert.deepEqual(locusInfo.hashTreeParser.dataSets['atd-unmuted'].hashTree.getLeafData(2), [ { type: 'PARTICIPANT', id: 2, version: 5679 } ]);
|
3569
|
+
|
3570
|
+
await testUtils.flushPromises();
|
3571
|
+
|
3572
|
+
// the root hash doesn't match, but we shouldn't send a request to Locus for hashes just yet
|
3573
|
+
assert.notCalled(webex.request);
|
3574
|
+
|
3575
|
+
webex.request.callsFake(async (options) => {
|
3576
|
+
if (options?.method === 'GET' && options?.uri?.endsWith('/hashtree')) {
|
3577
|
+
return {
|
3578
|
+
body: {
|
3579
|
+
hashes: [
|
3580
|
+
'178bff6e3344f551a811712c57a9eac3',
|
3581
|
+
'b113a76304e3a7121afecfe1606ee1c1',
|
3582
|
+
'ae70773ebb3be3653209648071b9bdad',
|
3583
|
+
'99aa06d3014798d86001c324468d497f',
|
3584
|
+
'99aa06d3014798d86001c324468d497f',
|
3585
|
+
'deadbeef', // wrong hash that should cause the participant with id=2 to be deemed out of sync
|
3586
|
+
'99aa06d3014798d86001c324468d497f'
|
3587
|
+
]
|
3588
|
+
}
|
3589
|
+
};
|
3590
|
+
} else if (options?.method === 'POST' && options?.uri?.endsWith('/sync')) {
|
3591
|
+
return {
|
3592
|
+
body: {},
|
3593
|
+
statusCode: 202,
|
3594
|
+
};
|
3595
|
+
}
|
3596
|
+
return {};
|
3597
|
+
});
|
3598
|
+
|
3599
|
+
// only after timeout there should be requests to get hashes and sync sent to Locus
|
3600
|
+
await waitForMaxPossibleBackoffTime(locusInfo.hashTreeParser.dataSets);
|
3601
|
+
assert.calledTwice(webex.request);
|
3602
|
+
|
3603
|
+
const firstCallArgs = webex.request.getCall(0).args[0];
|
3604
|
+
const secondCallArgs = webex.request.getCall(1).args[0];
|
3605
|
+
|
3606
|
+
assert.deepEqual(firstCallArgs, {method: 'GET', uri: `${atdUnmutedDataSetUrl}/hashtree`});
|
3607
|
+
|
3608
|
+
assert.deepEqual(secondCallArgs, {method: 'POST', uri: `${atdUnmutedDataSetUrl}/sync`,
|
3609
|
+
body: {
|
3610
|
+
dataSet: {
|
3611
|
+
name: 'atd-unmuted',
|
3612
|
+
leafCount: 16,
|
3613
|
+
root: '178bff6e3344f551a811712c57a9eac3',
|
3614
|
+
},
|
3615
|
+
leafDataEntries: [{
|
3616
|
+
leafIndex: 2,
|
3617
|
+
elementIds: [
|
3618
|
+
{
|
3619
|
+
id: 2,
|
3620
|
+
type: "PARTICIPANT",
|
3621
|
+
version: 5679
|
3622
|
+
}
|
3623
|
+
],
|
3624
|
+
}]
|
3625
|
+
}
|
3626
|
+
});
|
3627
|
+
});
|
3628
|
+
|
3629
|
+
it('handles end meeting message correctly', async () => {
|
3630
|
+
});
|
3631
|
+
it('handles heartbeat messages correctly', async () => {
|
3632
|
+
});
|
3633
|
+
|
3634
|
+
});
|
3184
3635
|
});
|
3185
3636
|
});
|