@webex/plugin-meetings 3.12.0-next.2 → 3.12.0-next.21
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/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +23 -21
- 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 +56 -31
- 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/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +51 -23
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/index.js +372 -292
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +8 -9
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +21 -2
- package/dist/meetings/util.js.map +1 -1
- package/dist/metrics/constants.js +5 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +116 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +12 -2
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/locus-info/index.d.ts +9 -5
- package/dist/types/meeting/index.d.ts +11 -0
- package/dist/types/metrics/constants.d.ts +4 -0
- package/dist/types/multistream/sendSlotManager.d.ts +23 -1
- package/dist/webinar/index.js +301 -226
- package/dist/webinar/index.js.map +1 -1
- package/package.json +16 -16
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +26 -19
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +60 -36
- package/src/hashTree/utils.ts +17 -0
- package/src/locus-info/index.ts +56 -30
- package/src/meeting/index.ts +98 -11
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +15 -16
- package/src/meetings/util.ts +26 -1
- package/src/metrics/constants.ts +5 -0
- package/src/multistream/sendSlotManager.ts +97 -3
- package/src/webinar/index.ts +75 -1
- package/test/unit/spec/controls-options-manager/index.js +114 -6
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +441 -30
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/locus-info/index.js +75 -27
- package/test/unit/spec/meeting/index.js +54 -36
- package/test/unit/spec/meeting/utils.js +4 -0
- package/test/unit/spec/meetings/index.js +36 -3
- package/test/unit/spec/meetings/utils.js +108 -0
- package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
- package/test/unit/spec/webinar/index.ts +60 -0
|
@@ -7,6 +7,7 @@ import {expect} from '@webex/test-helper-chai';
|
|
|
7
7
|
import sinon from 'sinon';
|
|
8
8
|
import {assert} from '@webex/test-helper-chai';
|
|
9
9
|
import {EMPTY_HASH} from '@webex/plugin-meetings/src/hashTree/constants';
|
|
10
|
+
import { some } from 'lodash';
|
|
10
11
|
|
|
11
12
|
const visibleDataSetsUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/visibleDataSets';
|
|
12
13
|
|
|
@@ -553,7 +554,7 @@ describe('HashTreeParser', () => {
|
|
|
553
554
|
);
|
|
554
555
|
|
|
555
556
|
// Verify callback was called with OBJECTS_UPDATED and correct updatedObjects list
|
|
556
|
-
assert.calledWith(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
557
|
+
assert.calledWith(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
557
558
|
updatedObjects: [
|
|
558
559
|
{
|
|
559
560
|
htMeta: {
|
|
@@ -596,6 +597,41 @@ describe('HashTreeParser', () => {
|
|
|
596
597
|
});
|
|
597
598
|
});
|
|
598
599
|
|
|
600
|
+
it('initializes "main" before "self" regardless of order from Locus', async () => {
|
|
601
|
+
const parser = createHashTreeParser({dataSets: [], locus: null}, null);
|
|
602
|
+
|
|
603
|
+
// Locus returns datasets in non-priority order: atd-active, main, self
|
|
604
|
+
const atdActiveDataSet = createDataSet('atd-active', 4, 500);
|
|
605
|
+
const mainDataSet = createDataSet('main', 16, 1100);
|
|
606
|
+
const selfDataSet = createDataSet('self', 1, 2100);
|
|
607
|
+
|
|
608
|
+
mockGetAllDataSetsMetadata(webexRequest, visibleDataSetsUrl, [
|
|
609
|
+
atdActiveDataSet,
|
|
610
|
+
mainDataSet,
|
|
611
|
+
selfDataSet,
|
|
612
|
+
]);
|
|
613
|
+
|
|
614
|
+
mockSyncRequest(webexRequest, selfDataSet.url);
|
|
615
|
+
mockSyncRequest(webexRequest, mainDataSet.url);
|
|
616
|
+
mockSyncRequest(webexRequest, atdActiveDataSet.url);
|
|
617
|
+
|
|
618
|
+
await parser.initializeFromMessage({
|
|
619
|
+
dataSets: [],
|
|
620
|
+
visibleDataSetsUrl,
|
|
621
|
+
locusUrl,
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// Verify sync requests were sent in priority order: main, self, then atd-active
|
|
625
|
+
const syncCalls = webexRequest
|
|
626
|
+
.getCalls()
|
|
627
|
+
.filter((call) => call.args[0]?.method === 'POST' && call.args[0]?.uri?.endsWith('/sync'));
|
|
628
|
+
|
|
629
|
+
expect(syncCalls).to.have.lengthOf(3);
|
|
630
|
+
expect(syncCalls[0].args[0].uri).to.equal(`${mainDataSet.url}/sync`);
|
|
631
|
+
expect(syncCalls[1].args[0].uri).to.equal(`${selfDataSet.url}/sync`);
|
|
632
|
+
expect(syncCalls[2].args[0].uri).to.equal(`${atdActiveDataSet.url}/sync`);
|
|
633
|
+
});
|
|
634
|
+
|
|
599
635
|
it('handles sync response that has locusStateElements undefined', async () => {
|
|
600
636
|
const minimalInitialLocus = {
|
|
601
637
|
dataSets: [],
|
|
@@ -788,7 +824,7 @@ describe('HashTreeParser', () => {
|
|
|
788
824
|
expect(parser.dataSets.self.version).to.equal(2100);
|
|
789
825
|
expect(parser.dataSets['atd-unmuted'].version).to.equal(3100);
|
|
790
826
|
|
|
791
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
827
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
792
828
|
updatedObjects: [
|
|
793
829
|
{
|
|
794
830
|
htMeta: {
|
|
@@ -861,6 +897,116 @@ describe('HashTreeParser', () => {
|
|
|
861
897
|
});
|
|
862
898
|
});
|
|
863
899
|
|
|
900
|
+
it('handles updates to control entries correctly', () => {
|
|
901
|
+
const parser = createHashTreeParser();
|
|
902
|
+
|
|
903
|
+
const mainPutItemsSpy = sinon.spy(parser.dataSets.main.hashTree, 'putItems');
|
|
904
|
+
|
|
905
|
+
// Create a locus update with new htMeta information for some things
|
|
906
|
+
const locusUpdate = {
|
|
907
|
+
dataSets: [
|
|
908
|
+
createDataSet('main', 16, 1100),
|
|
909
|
+
],
|
|
910
|
+
locus: {
|
|
911
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f',
|
|
912
|
+
htMeta: {
|
|
913
|
+
elementId: {
|
|
914
|
+
type: 'locus',
|
|
915
|
+
id: 0,
|
|
916
|
+
version: 200, // same version
|
|
917
|
+
},
|
|
918
|
+
dataSetNames: ['main'],
|
|
919
|
+
},
|
|
920
|
+
participants: [],
|
|
921
|
+
controls: {
|
|
922
|
+
lock: {
|
|
923
|
+
locked: true,
|
|
924
|
+
htMeta: {
|
|
925
|
+
elementId: {
|
|
926
|
+
type: 'ControlEntry',
|
|
927
|
+
id: 10100,
|
|
928
|
+
version: 100,
|
|
929
|
+
},
|
|
930
|
+
dataSetNames: ['main'],
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
stream: {
|
|
934
|
+
streaming: true,
|
|
935
|
+
htMeta: {
|
|
936
|
+
elementId: {
|
|
937
|
+
type: 'ControlEntry',
|
|
938
|
+
id: 10101,
|
|
939
|
+
version: 100,
|
|
940
|
+
},
|
|
941
|
+
dataSetNames: ['main'],
|
|
942
|
+
},
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
},
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
// Call handleLocusUpdate
|
|
949
|
+
parser.handleLocusUpdate(locusUpdate);
|
|
950
|
+
|
|
951
|
+
// Verify putItems was called on main hash tree with correct data
|
|
952
|
+
assert.calledOnceWithExactly(mainPutItemsSpy, [
|
|
953
|
+
{type: 'locus', id: 0, version: 200},
|
|
954
|
+
{type: 'ControlEntry', id: 10100, version: 100},
|
|
955
|
+
{type: 'ControlEntry', id: 10101, version: 100}
|
|
956
|
+
]);
|
|
957
|
+
|
|
958
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
959
|
+
updatedObjects: [
|
|
960
|
+
{
|
|
961
|
+
htMeta: {
|
|
962
|
+
elementId: {
|
|
963
|
+
type: 'ControlEntry',
|
|
964
|
+
id: 10100,
|
|
965
|
+
version: 100,
|
|
966
|
+
},
|
|
967
|
+
dataSetNames: ['main'],
|
|
968
|
+
},
|
|
969
|
+
data: {
|
|
970
|
+
lock: {
|
|
971
|
+
locked: true,
|
|
972
|
+
htMeta: {
|
|
973
|
+
elementId: {
|
|
974
|
+
type: 'ControlEntry',
|
|
975
|
+
id: 10100,
|
|
976
|
+
version: 100,
|
|
977
|
+
},
|
|
978
|
+
dataSetNames: ['main'],
|
|
979
|
+
},
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
htMeta: {
|
|
985
|
+
elementId: {
|
|
986
|
+
type: 'ControlEntry',
|
|
987
|
+
id: 10101,
|
|
988
|
+
version: 100,
|
|
989
|
+
},
|
|
990
|
+
dataSetNames: ['main'],
|
|
991
|
+
},
|
|
992
|
+
data: {
|
|
993
|
+
stream: {
|
|
994
|
+
streaming: true,
|
|
995
|
+
htMeta: {
|
|
996
|
+
elementId: {
|
|
997
|
+
type: 'ControlEntry',
|
|
998
|
+
id: 10101,
|
|
999
|
+
version: 100,
|
|
1000
|
+
},
|
|
1001
|
+
dataSetNames: ['main'],
|
|
1002
|
+
},
|
|
1003
|
+
},
|
|
1004
|
+
},
|
|
1005
|
+
}
|
|
1006
|
+
],
|
|
1007
|
+
});
|
|
1008
|
+
});
|
|
1009
|
+
|
|
864
1010
|
it('handles unknown datasets gracefully', () => {
|
|
865
1011
|
const parser = createHashTreeParser();
|
|
866
1012
|
|
|
@@ -899,7 +1045,7 @@ describe('HashTreeParser', () => {
|
|
|
899
1045
|
assert.calledOnceWithExactly(mainPutItemsSpy, [{type: 'locus', id: 0, version: 201}]);
|
|
900
1046
|
|
|
901
1047
|
// Verify callback was called only for known dataset
|
|
902
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1048
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
903
1049
|
updatedObjects: [
|
|
904
1050
|
{
|
|
905
1051
|
htMeta: {
|
|
@@ -999,7 +1145,7 @@ describe('HashTreeParser', () => {
|
|
|
999
1145
|
assert.calledOnceWithExactly(selfPutItemSpy, {type: 'metadata', id: 5, version: 51});
|
|
1000
1146
|
|
|
1001
1147
|
// Verify callback was called with metadata object and removed dataset objects
|
|
1002
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1148
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1003
1149
|
updatedObjects: [
|
|
1004
1150
|
// updated metadata object:
|
|
1005
1151
|
{
|
|
@@ -1160,7 +1306,7 @@ describe('HashTreeParser', () => {
|
|
|
1160
1306
|
assert.notCalled(atdUnmutedPutItemsSpy);
|
|
1161
1307
|
|
|
1162
1308
|
// Verify callback was called with the updated object
|
|
1163
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1309
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1164
1310
|
updatedObjects: [
|
|
1165
1311
|
{
|
|
1166
1312
|
htMeta: {
|
|
@@ -1388,7 +1534,7 @@ describe('HashTreeParser', () => {
|
|
|
1388
1534
|
]);
|
|
1389
1535
|
|
|
1390
1536
|
// Verify callback was called with OBJECTS_UPDATED and all updated objects
|
|
1391
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1537
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1392
1538
|
updatedObjects: [
|
|
1393
1539
|
{
|
|
1394
1540
|
htMeta: {
|
|
@@ -1453,9 +1599,7 @@ describe('HashTreeParser', () => {
|
|
|
1453
1599
|
parser.handleMessage(sentinelMessage, 'sentinel message');
|
|
1454
1600
|
|
|
1455
1601
|
// Verify callback was called with MEETING_ENDED
|
|
1456
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED
|
|
1457
|
-
updatedObjects: undefined,
|
|
1458
|
-
});
|
|
1602
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
|
|
1459
1603
|
|
|
1460
1604
|
// Verify that all timers were stopped
|
|
1461
1605
|
Object.values(parser.dataSets).forEach((ds: any) => {
|
|
@@ -1477,9 +1621,7 @@ describe('HashTreeParser', () => {
|
|
|
1477
1621
|
parser.handleMessage(sentinelMessage, 'sentinel message');
|
|
1478
1622
|
|
|
1479
1623
|
// Verify callback was called with MEETING_ENDED
|
|
1480
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED
|
|
1481
|
-
updatedObjects: undefined,
|
|
1482
|
-
});
|
|
1624
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
|
|
1483
1625
|
|
|
1484
1626
|
// Verify that all timers were stopped
|
|
1485
1627
|
Object.values(parser.dataSets).forEach((ds: any) => {
|
|
@@ -1575,7 +1717,7 @@ describe('HashTreeParser', () => {
|
|
|
1575
1717
|
);
|
|
1576
1718
|
|
|
1577
1719
|
// Verify that callback was called with synced objects
|
|
1578
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1720
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1579
1721
|
updatedObjects: [
|
|
1580
1722
|
{
|
|
1581
1723
|
htMeta: {
|
|
@@ -1637,9 +1779,7 @@ describe('HashTreeParser', () => {
|
|
|
1637
1779
|
await clock.tickAsync(1000);
|
|
1638
1780
|
|
|
1639
1781
|
// Verify callback was called with MEETING_ENDED
|
|
1640
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED
|
|
1641
|
-
updatedObjects: undefined,
|
|
1642
|
-
});
|
|
1782
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
|
|
1643
1783
|
|
|
1644
1784
|
// Verify all timers are stopped
|
|
1645
1785
|
Object.values(parser.dataSets).forEach((ds: any) => {
|
|
@@ -1702,9 +1842,7 @@ describe('HashTreeParser', () => {
|
|
|
1702
1842
|
await clock.tickAsync(1000);
|
|
1703
1843
|
|
|
1704
1844
|
// Verify callback was called with MEETING_ENDED
|
|
1705
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED
|
|
1706
|
-
updatedObjects: undefined,
|
|
1707
|
-
});
|
|
1845
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
|
|
1708
1846
|
|
|
1709
1847
|
// Verify all timers are stopped
|
|
1710
1848
|
Object.values(parser.dataSets).forEach((ds: any) => {
|
|
@@ -1942,7 +2080,7 @@ describe('HashTreeParser', () => {
|
|
|
1942
2080
|
assert.equal(parser.dataSets.attendees.hashTree.numLeaves, 8);
|
|
1943
2081
|
|
|
1944
2082
|
// Verify callback was called with the metadata update (appears twice - processed once for visible dataset changes, once in main loop)
|
|
1945
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
2083
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
1946
2084
|
updatedObjects: [
|
|
1947
2085
|
{
|
|
1948
2086
|
htMeta: {
|
|
@@ -2062,6 +2200,98 @@ describe('HashTreeParser', () => {
|
|
|
2062
2200
|
await checkAsyncDatasetInitialization(parser, newDataSet);
|
|
2063
2201
|
});
|
|
2064
2202
|
|
|
2203
|
+
it('initializes new visible data sets in priority order', async () => {
|
|
2204
|
+
// Create a parser that only has "self" as visible (no "main")
|
|
2205
|
+
const initialLocusWithoutMain = {
|
|
2206
|
+
dataSets: [createDataSet('self', 1, 2000)],
|
|
2207
|
+
locus: {
|
|
2208
|
+
...exampleInitialLocus.locus,
|
|
2209
|
+
},
|
|
2210
|
+
};
|
|
2211
|
+
const metadataWithoutMain = {
|
|
2212
|
+
...exampleMetadata,
|
|
2213
|
+
visibleDataSets: [
|
|
2214
|
+
{
|
|
2215
|
+
name: 'self',
|
|
2216
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/713e9f99/datasets/self',
|
|
2217
|
+
},
|
|
2218
|
+
],
|
|
2219
|
+
};
|
|
2220
|
+
const parser = createHashTreeParser(initialLocusWithoutMain, metadataWithoutMain);
|
|
2221
|
+
|
|
2222
|
+
// Verify "main" is not visible initially
|
|
2223
|
+
expect(parser.visibleDataSets.some((vds) => vds.name === 'main')).to.be.false;
|
|
2224
|
+
|
|
2225
|
+
// Stub updateItems on self hash tree to return true
|
|
2226
|
+
sinon.stub(parser.dataSets.self.hashTree, 'updateItems').returns([true]);
|
|
2227
|
+
|
|
2228
|
+
// Send a message that adds "main" and "atd-active" as new visible datasets.
|
|
2229
|
+
// Neither has info in dataSets, so both require async initialization.
|
|
2230
|
+
const newMainDataSet = createDataSet('main', 16, 6000);
|
|
2231
|
+
const newAtdActiveDataSet = createDataSet('atd-active', 4, 7000);
|
|
2232
|
+
|
|
2233
|
+
const message = {
|
|
2234
|
+
dataSets: [createDataSet('self', 1, 2100)],
|
|
2235
|
+
visibleDataSetsUrl,
|
|
2236
|
+
locusUrl,
|
|
2237
|
+
locusStateElements: [
|
|
2238
|
+
{
|
|
2239
|
+
htMeta: {
|
|
2240
|
+
elementId: {
|
|
2241
|
+
type: 'metadata' as const,
|
|
2242
|
+
id: 5,
|
|
2243
|
+
version: 51,
|
|
2244
|
+
},
|
|
2245
|
+
dataSetNames: ['self'],
|
|
2246
|
+
},
|
|
2247
|
+
data: {
|
|
2248
|
+
visibleDataSets: [
|
|
2249
|
+
{
|
|
2250
|
+
name: 'self',
|
|
2251
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/713e9f99/datasets/self',
|
|
2252
|
+
},
|
|
2253
|
+
// listed in non-priority order: atd-active before main
|
|
2254
|
+
{name: 'atd-active', url: newAtdActiveDataSet.url},
|
|
2255
|
+
{name: 'main', url: newMainDataSet.url},
|
|
2256
|
+
],
|
|
2257
|
+
},
|
|
2258
|
+
},
|
|
2259
|
+
],
|
|
2260
|
+
};
|
|
2261
|
+
|
|
2262
|
+
// Mock getAllVisibleDataSetsFromLocus to return both new datasets (in non-priority order)
|
|
2263
|
+
mockGetAllDataSetsMetadata(webexRequest, visibleDataSetsUrl, [
|
|
2264
|
+
newAtdActiveDataSet,
|
|
2265
|
+
newMainDataSet,
|
|
2266
|
+
]);
|
|
2267
|
+
mockSyncRequest(webexRequest, newMainDataSet.url);
|
|
2268
|
+
mockSyncRequest(webexRequest, newAtdActiveDataSet.url);
|
|
2269
|
+
|
|
2270
|
+
parser.handleMessage(message, 'add main and atd-active datasets');
|
|
2271
|
+
|
|
2272
|
+
// Wait for the async initialization (queueMicrotask) to complete
|
|
2273
|
+
await clock.tickAsync(0);
|
|
2274
|
+
|
|
2275
|
+
// Verify both datasets are initialized
|
|
2276
|
+
expect(parser.dataSets.main?.hashTree).to.exist;
|
|
2277
|
+
expect(parser.dataSets['atd-active']?.hashTree).to.exist;
|
|
2278
|
+
|
|
2279
|
+
// Verify sync requests were sent in priority order: "main" before "atd-active",
|
|
2280
|
+
// even though atd-active was listed first in both the message and the Locus response
|
|
2281
|
+
const syncCalls = webexRequest
|
|
2282
|
+
.getCalls()
|
|
2283
|
+
.filter(
|
|
2284
|
+
(call) =>
|
|
2285
|
+
call.args[0]?.method === 'POST' &&
|
|
2286
|
+
call.args[0]?.uri?.endsWith('/sync') &&
|
|
2287
|
+
(call.args[0]?.uri?.includes('/main/') || call.args[0]?.uri?.includes('/atd-active/'))
|
|
2288
|
+
);
|
|
2289
|
+
|
|
2290
|
+
expect(syncCalls).to.have.lengthOf(2);
|
|
2291
|
+
expect(syncCalls[0].args[0].uri).to.equal(`${newMainDataSet.url}/sync`);
|
|
2292
|
+
expect(syncCalls[1].args[0].uri).to.equal(`${newAtdActiveDataSet.url}/sync`);
|
|
2293
|
+
});
|
|
2294
|
+
|
|
2065
2295
|
it('emits MEETING_ENDED if async init of a new visible dataset fails with 404', async () => {
|
|
2066
2296
|
const parser = createHashTreeParser();
|
|
2067
2297
|
|
|
@@ -2128,9 +2358,7 @@ describe('HashTreeParser', () => {
|
|
|
2128
2358
|
await clock.tickAsync(0);
|
|
2129
2359
|
|
|
2130
2360
|
// Verify callback was called with MEETING_ENDED
|
|
2131
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED
|
|
2132
|
-
updatedObjects: undefined,
|
|
2133
|
-
});
|
|
2361
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
|
|
2134
2362
|
});
|
|
2135
2363
|
|
|
2136
2364
|
it('handles removal of visible data set', async () => {
|
|
@@ -2193,7 +2421,7 @@ describe('HashTreeParser', () => {
|
|
|
2193
2421
|
assert.isUndefined(parser.dataSets['atd-unmuted'].timer);
|
|
2194
2422
|
|
|
2195
2423
|
// Verify callback was called with the metadata update and the removed objects (metadata appears twice - processed once for dataset changes, once in main loop)
|
|
2196
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
2424
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
2197
2425
|
updatedObjects: [
|
|
2198
2426
|
{
|
|
2199
2427
|
htMeta: {
|
|
@@ -2290,6 +2518,151 @@ describe('HashTreeParser', () => {
|
|
|
2290
2518
|
// Verify callback was NOT called (no updates for non-visible datasets)
|
|
2291
2519
|
assert.notCalled(callback);
|
|
2292
2520
|
});
|
|
2521
|
+
|
|
2522
|
+
it('reports update for object that moves from removed visible dataset to new visible dataset even if version is unchanged', async () => {
|
|
2523
|
+
// The purpose of this test is to verify that when an object
|
|
2524
|
+
// moves from one visible dataset to another without version change,
|
|
2525
|
+
// the parser still reports it as an update.
|
|
2526
|
+
// Locus has some additional signalling for this - the "view" property in htMeta.elementId.
|
|
2527
|
+
// When a view changes, the contents of the object may change even if version doesn't.
|
|
2528
|
+
// HashTreeParser doesn't use the "view" property, because it doesn't need to -
|
|
2529
|
+
// the same functionality is achieved thanks to the fact that a new visible data set means
|
|
2530
|
+
// a new hash tree is created, so HashTreeParser still detects the change as new
|
|
2531
|
+
// object is added to the new hash tree.
|
|
2532
|
+
|
|
2533
|
+
// Setup: parser with visible datasets "self" and "unjoined"
|
|
2534
|
+
const unjoinedDataSet = createDataSet('unjoined', 4, 3000);
|
|
2535
|
+
const selfDataSet = createDataSet('self', 1, 2000);
|
|
2536
|
+
|
|
2537
|
+
// start with Locus that has "info" in both "unjoined" and "main" datasets,
|
|
2538
|
+
// but only "unjoined" is visible.
|
|
2539
|
+
const initialLocus = {
|
|
2540
|
+
dataSets: [selfDataSet, unjoinedDataSet],
|
|
2541
|
+
locus: {
|
|
2542
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f',
|
|
2543
|
+
links: {resources: {visibleDataSets: {url: visibleDataSetsUrl}}},
|
|
2544
|
+
// info object in "unjoined" dataset with version 500
|
|
2545
|
+
info: {
|
|
2546
|
+
htMeta: {
|
|
2547
|
+
elementId: {
|
|
2548
|
+
type: 'info',
|
|
2549
|
+
id: 42,
|
|
2550
|
+
version: 500,
|
|
2551
|
+
view: ['unjoined'], // not used by our code, but here for completeness - that's what real Locus would send
|
|
2552
|
+
},
|
|
2553
|
+
dataSetNames: ['main', 'unjoined'],
|
|
2554
|
+
},
|
|
2555
|
+
someField: 'some-initial-value',
|
|
2556
|
+
},
|
|
2557
|
+
self: {
|
|
2558
|
+
htMeta: {
|
|
2559
|
+
elementId: {
|
|
2560
|
+
type: 'self',
|
|
2561
|
+
id: 4,
|
|
2562
|
+
version: 100,
|
|
2563
|
+
},
|
|
2564
|
+
dataSetNames: ['self'],
|
|
2565
|
+
},
|
|
2566
|
+
},
|
|
2567
|
+
},
|
|
2568
|
+
};
|
|
2569
|
+
|
|
2570
|
+
const metadata = {
|
|
2571
|
+
htMeta: {
|
|
2572
|
+
elementId: {
|
|
2573
|
+
type: 'metadata',
|
|
2574
|
+
id: 5,
|
|
2575
|
+
version: 50,
|
|
2576
|
+
},
|
|
2577
|
+
dataSetNames: ['self'],
|
|
2578
|
+
},
|
|
2579
|
+
visibleDataSets: [
|
|
2580
|
+
{name: 'self', url: selfDataSet.url},
|
|
2581
|
+
{name: 'unjoined', url: unjoinedDataSet.url},
|
|
2582
|
+
],
|
|
2583
|
+
};
|
|
2584
|
+
|
|
2585
|
+
const parser = createHashTreeParser(initialLocus, metadata);
|
|
2586
|
+
|
|
2587
|
+
// Verify initial state: unjoined is visible and has the info object
|
|
2588
|
+
expect(parser.visibleDataSets.some((vds) => vds.name === 'unjoined')).to.be.true;
|
|
2589
|
+
assert.exists(parser.dataSets.unjoined.hashTree);
|
|
2590
|
+
assert.equal(parser.dataSets.unjoined.hashTree?.getItemVersion(42, 'info'), 500);
|
|
2591
|
+
|
|
2592
|
+
// Stub updateItems on self hash tree to return true for metadata update
|
|
2593
|
+
sinon.stub(parser.dataSets.self.hashTree, 'updateItems').returns([true]);
|
|
2594
|
+
|
|
2595
|
+
// Now send a message that:
|
|
2596
|
+
// 1. Changes visible datasets: removes "unjoined", adds "main"
|
|
2597
|
+
// 2. Contains the same info object (same id=42, same version=500) but we see the view from "main" dataset
|
|
2598
|
+
const mainDataSet = createDataSet('main', 16, 1000);
|
|
2599
|
+
|
|
2600
|
+
const message = {
|
|
2601
|
+
dataSets: [selfDataSet, mainDataSet],
|
|
2602
|
+
visibleDataSetsUrl,
|
|
2603
|
+
locusUrl,
|
|
2604
|
+
locusStateElements: [
|
|
2605
|
+
{
|
|
2606
|
+
htMeta: {
|
|
2607
|
+
elementId: {
|
|
2608
|
+
type: 'metadata' as const,
|
|
2609
|
+
id: 5,
|
|
2610
|
+
version: 51,
|
|
2611
|
+
},
|
|
2612
|
+
dataSetNames: ['self'],
|
|
2613
|
+
},
|
|
2614
|
+
data: {
|
|
2615
|
+
visibleDataSets: [
|
|
2616
|
+
{name: 'self', url: selfDataSet.url},
|
|
2617
|
+
{name: 'main', url: mainDataSet.url},
|
|
2618
|
+
// "unjoined" is no longer here
|
|
2619
|
+
],
|
|
2620
|
+
},
|
|
2621
|
+
},
|
|
2622
|
+
{
|
|
2623
|
+
htMeta: {
|
|
2624
|
+
elementId: {
|
|
2625
|
+
type: 'info' as const,
|
|
2626
|
+
id: 42,
|
|
2627
|
+
version: 500, // same version as before
|
|
2628
|
+
view: ['main'], // now points to "main" instead of "unjoined"
|
|
2629
|
+
},
|
|
2630
|
+
dataSetNames: ['main', 'unjoined'], // still in both datasets, but only "main" is visible now
|
|
2631
|
+
},
|
|
2632
|
+
data: {someNewField: 'some-value'},
|
|
2633
|
+
},
|
|
2634
|
+
],
|
|
2635
|
+
};
|
|
2636
|
+
|
|
2637
|
+
parser.handleMessage(message, 'visible dataset swap with same-version object');
|
|
2638
|
+
|
|
2639
|
+
// Verify "unjoined" is no longer visible and "main" is now visible
|
|
2640
|
+
expect(parser.visibleDataSets.some((vds) => vds.name === 'unjoined')).to.be.false;
|
|
2641
|
+
expect(parser.visibleDataSets.some((vds) => vds.name === 'main')).to.be.true;
|
|
2642
|
+
|
|
2643
|
+
// Verify the info object is now in the "main" hash tree
|
|
2644
|
+
assert.exists(parser.dataSets.main.hashTree);
|
|
2645
|
+
assert.equal(parser.dataSets.main.hashTree?.getItemVersion(42, 'info'), 500);
|
|
2646
|
+
|
|
2647
|
+
// The key assertion: callback should be called with the info object update even though
|
|
2648
|
+
// its version hasn't changed - because visible datasets changed (moved from unjoined to main)
|
|
2649
|
+
assert.calledOnce(callback);
|
|
2650
|
+
const callbackArgs = callback.firstCall.args[0];
|
|
2651
|
+
assert.equal(callbackArgs.updateType, LocusInfoUpdateType.OBJECTS_UPDATED);
|
|
2652
|
+
|
|
2653
|
+
// Should contain the info object update (with data)
|
|
2654
|
+
const infoUpdate = callbackArgs.updatedObjects.find(
|
|
2655
|
+
(obj) => obj.htMeta.elementId.type === 'info' && obj.htMeta.elementId.id === 42
|
|
2656
|
+
);
|
|
2657
|
+
assert.exists(infoUpdate);
|
|
2658
|
+
assert.deepEqual(infoUpdate.htMeta.elementId, {
|
|
2659
|
+
type: 'info',
|
|
2660
|
+
id: 42,
|
|
2661
|
+
version: 500,
|
|
2662
|
+
view: ['main'],
|
|
2663
|
+
});
|
|
2664
|
+
assert.deepEqual(infoUpdate.data, {someNewField: 'some-value'});
|
|
2665
|
+
});
|
|
2293
2666
|
});
|
|
2294
2667
|
|
|
2295
2668
|
describe('heartbeat watchdog', () => {
|
|
@@ -2812,7 +3185,7 @@ describe('HashTreeParser', () => {
|
|
|
2812
3185
|
parser.handleMessage(updateMessage, 'update with newer version');
|
|
2813
3186
|
|
|
2814
3187
|
// Callback should be called with the update
|
|
2815
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
3188
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
2816
3189
|
updatedObjects: [
|
|
2817
3190
|
{
|
|
2818
3191
|
htMeta: {
|
|
@@ -2883,7 +3256,7 @@ describe('HashTreeParser', () => {
|
|
|
2883
3256
|
parser.handleMessage(removalMessage, 'removal of non-existent object');
|
|
2884
3257
|
|
|
2885
3258
|
// Callback should be called with the removal
|
|
2886
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
3259
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
2887
3260
|
updatedObjects: [
|
|
2888
3261
|
{
|
|
2889
3262
|
htMeta: {
|
|
@@ -3018,7 +3391,7 @@ describe('HashTreeParser', () => {
|
|
|
3018
3391
|
parser.handleMessage(mixedMessage, 'mixed updates');
|
|
3019
3392
|
|
|
3020
3393
|
// Callback should be called with only the valid updates (participant 1 v110 and participant 3 v10)
|
|
3021
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
3394
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
|
|
3022
3395
|
updatedObjects: [
|
|
3023
3396
|
{
|
|
3024
3397
|
htMeta: {
|
|
@@ -3196,9 +3569,7 @@ describe('HashTreeParser', () => {
|
|
|
3196
3569
|
parser.handleMessage(sentinelMessage as any, 'sentinel message');
|
|
3197
3570
|
|
|
3198
3571
|
// Callback should be called with MEETING_ENDED
|
|
3199
|
-
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED
|
|
3200
|
-
updatedObjects: undefined,
|
|
3201
|
-
});
|
|
3572
|
+
assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
|
|
3202
3573
|
});
|
|
3203
3574
|
});
|
|
3204
3575
|
|
|
@@ -3557,4 +3928,44 @@ describe('HashTreeParser', () => {
|
|
|
3557
3928
|
assert.notCalled(callback);
|
|
3558
3929
|
});
|
|
3559
3930
|
});
|
|
3931
|
+
|
|
3932
|
+
describe('#cleanUp', () => {
|
|
3933
|
+
it('should stop the parser, clear all timers and clear all dataSets', () => {
|
|
3934
|
+
const parser = createHashTreeParser();
|
|
3935
|
+
|
|
3936
|
+
// Send a message to set up sync timers via runSyncAlgorithm
|
|
3937
|
+
const message = {
|
|
3938
|
+
dataSets: [
|
|
3939
|
+
{
|
|
3940
|
+
...createDataSet('main', 16, 1100),
|
|
3941
|
+
root: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1',
|
|
3942
|
+
},
|
|
3943
|
+
],
|
|
3944
|
+
visibleDataSetsUrl,
|
|
3945
|
+
locusUrl,
|
|
3946
|
+
heartbeatIntervalMs: 5000,
|
|
3947
|
+
locusStateElements: [
|
|
3948
|
+
{
|
|
3949
|
+
htMeta: {
|
|
3950
|
+
elementId: {type: 'locus' as const, id: 0, version: 201},
|
|
3951
|
+
dataSetNames: ['main'],
|
|
3952
|
+
},
|
|
3953
|
+
data: {someData: 'value'},
|
|
3954
|
+
},
|
|
3955
|
+
],
|
|
3956
|
+
};
|
|
3957
|
+
|
|
3958
|
+
parser.handleMessage(message, 'setup timers');
|
|
3959
|
+
|
|
3960
|
+
// Verify timers were set by handleMessage
|
|
3961
|
+
expect(parser.dataSets.main.timer).to.not.be.undefined;
|
|
3962
|
+
expect(parser.dataSets.main.heartbeatWatchdogTimer).to.not.be.undefined;
|
|
3963
|
+
|
|
3964
|
+
parser.cleanUp();
|
|
3965
|
+
|
|
3966
|
+
expect(parser.state).to.equal('stopped');
|
|
3967
|
+
expect(parser.visibleDataSets).to.deep.equal([]);
|
|
3968
|
+
expect(parser.dataSets).to.deep.equal({});
|
|
3969
|
+
});
|
|
3970
|
+
});
|
|
3560
3971
|
});
|