@webex/plugin-meetings 3.12.0-next.1 → 3.12.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/aiEnableRequest/index.js +1 -1
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/hashTree/constants.js +10 -1
  5. package/dist/hashTree/constants.js.map +1 -1
  6. package/dist/hashTree/hashTreeParser.js +20 -11
  7. package/dist/hashTree/hashTreeParser.js.map +1 -1
  8. package/dist/hashTree/utils.js +22 -0
  9. package/dist/hashTree/utils.js.map +1 -1
  10. package/dist/interpretation/index.js +1 -1
  11. package/dist/interpretation/siLanguage.js +1 -1
  12. package/dist/meeting/index.js +427 -323
  13. package/dist/meeting/index.js.map +1 -1
  14. package/dist/metrics/constants.js +5 -1
  15. package/dist/metrics/constants.js.map +1 -1
  16. package/dist/multistream/sendSlotManager.js +116 -2
  17. package/dist/multistream/sendSlotManager.js.map +1 -1
  18. package/dist/types/hashTree/constants.d.ts +1 -0
  19. package/dist/types/hashTree/utils.d.ts +11 -0
  20. package/dist/types/meeting/index.d.ts +24 -1
  21. package/dist/types/metrics/constants.d.ts +4 -0
  22. package/dist/types/multistream/sendSlotManager.d.ts +23 -1
  23. package/dist/webinar/index.js +325 -220
  24. package/dist/webinar/index.js.map +1 -1
  25. package/package.json +15 -15
  26. package/src/hashTree/constants.ts +9 -0
  27. package/src/hashTree/hashTreeParser.ts +21 -14
  28. package/src/hashTree/utils.ts +17 -0
  29. package/src/meeting/index.ts +165 -57
  30. package/src/metrics/constants.ts +5 -0
  31. package/src/multistream/sendSlotManager.ts +97 -3
  32. package/src/webinar/index.ts +120 -18
  33. package/test/unit/spec/hashTree/hashTreeParser.ts +238 -0
  34. package/test/unit/spec/hashTree/utils.ts +88 -1
  35. package/test/unit/spec/meeting/index.js +179 -48
  36. package/test/unit/spec/meetings/index.js +3 -3
  37. package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
  38. package/test/unit/spec/webinar/index.ts +193 -8
@@ -553,6 +553,7 @@ describe('HashTreeParser', () => {
553
553
  );
554
554
 
555
555
  // Verify callback was called with OBJECTS_UPDATED and correct updatedObjects list
556
+ // Note: main is initialized before self due to sortByInitPriority
556
557
  assert.calledWith(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
557
558
  updatedObjects: [
558
559
  {
@@ -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: [],
@@ -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, 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
 
@@ -2062,6 +2208,98 @@ describe('HashTreeParser', () => {
2062
2208
  await checkAsyncDatasetInitialization(parser, newDataSet);
2063
2209
  });
2064
2210
 
2211
+ it('initializes new visible data sets in priority order', async () => {
2212
+ // Create a parser that only has "self" as visible (no "main")
2213
+ const initialLocusWithoutMain = {
2214
+ dataSets: [createDataSet('self', 1, 2000)],
2215
+ locus: {
2216
+ ...exampleInitialLocus.locus,
2217
+ },
2218
+ };
2219
+ const metadataWithoutMain = {
2220
+ ...exampleMetadata,
2221
+ visibleDataSets: [
2222
+ {
2223
+ name: 'self',
2224
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/713e9f99/datasets/self',
2225
+ },
2226
+ ],
2227
+ };
2228
+ const parser = createHashTreeParser(initialLocusWithoutMain, metadataWithoutMain);
2229
+
2230
+ // Verify "main" is not visible initially
2231
+ expect(parser.visibleDataSets.some((vds) => vds.name === 'main')).to.be.false;
2232
+
2233
+ // Stub updateItems on self hash tree to return true
2234
+ sinon.stub(parser.dataSets.self.hashTree, 'updateItems').returns([true]);
2235
+
2236
+ // Send a message that adds "main" and "atd-active" as new visible datasets.
2237
+ // Neither has info in dataSets, so both require async initialization.
2238
+ const newMainDataSet = createDataSet('main', 16, 6000);
2239
+ const newAtdActiveDataSet = createDataSet('atd-active', 4, 7000);
2240
+
2241
+ const message = {
2242
+ dataSets: [createDataSet('self', 1, 2100)],
2243
+ visibleDataSetsUrl,
2244
+ locusUrl,
2245
+ locusStateElements: [
2246
+ {
2247
+ htMeta: {
2248
+ elementId: {
2249
+ type: 'metadata' as const,
2250
+ id: 5,
2251
+ version: 51,
2252
+ },
2253
+ dataSetNames: ['self'],
2254
+ },
2255
+ data: {
2256
+ visibleDataSets: [
2257
+ {
2258
+ name: 'self',
2259
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/713e9f99/datasets/self',
2260
+ },
2261
+ // listed in non-priority order: atd-active before main
2262
+ {name: 'atd-active', url: newAtdActiveDataSet.url},
2263
+ {name: 'main', url: newMainDataSet.url},
2264
+ ],
2265
+ },
2266
+ },
2267
+ ],
2268
+ };
2269
+
2270
+ // Mock getAllVisibleDataSetsFromLocus to return both new datasets (in non-priority order)
2271
+ mockGetAllDataSetsMetadata(webexRequest, visibleDataSetsUrl, [
2272
+ newAtdActiveDataSet,
2273
+ newMainDataSet,
2274
+ ]);
2275
+ mockSyncRequest(webexRequest, newMainDataSet.url);
2276
+ mockSyncRequest(webexRequest, newAtdActiveDataSet.url);
2277
+
2278
+ parser.handleMessage(message, 'add main and atd-active datasets');
2279
+
2280
+ // Wait for the async initialization (queueMicrotask) to complete
2281
+ await clock.tickAsync(0);
2282
+
2283
+ // Verify both datasets are initialized
2284
+ expect(parser.dataSets.main?.hashTree).to.exist;
2285
+ expect(parser.dataSets['atd-active']?.hashTree).to.exist;
2286
+
2287
+ // Verify sync requests were sent in priority order: "main" before "atd-active",
2288
+ // even though atd-active was listed first in both the message and the Locus response
2289
+ const syncCalls = webexRequest
2290
+ .getCalls()
2291
+ .filter(
2292
+ (call) =>
2293
+ call.args[0]?.method === 'POST' &&
2294
+ call.args[0]?.uri?.endsWith('/sync') &&
2295
+ (call.args[0]?.uri?.includes('/main/') || call.args[0]?.uri?.includes('/atd-active/'))
2296
+ );
2297
+
2298
+ expect(syncCalls).to.have.lengthOf(2);
2299
+ expect(syncCalls[0].args[0].uri).to.equal(`${newMainDataSet.url}/sync`);
2300
+ expect(syncCalls[1].args[0].uri).to.equal(`${newAtdActiveDataSet.url}/sync`);
2301
+ });
2302
+
2065
2303
  it('emits MEETING_ENDED if async init of a new visible dataset fails with 404', async () => {
2066
2304
  const parser = createHashTreeParser();
2067
2305
 
@@ -1,5 +1,10 @@
1
1
  import {HashTreeObject, ObjectType} from '../../../../src/hashTree/types';
2
- import {deleteNestedObjectsWithHtMeta, isSelf} from '../../../../src/hashTree/utils';
2
+ import {
3
+ deleteNestedObjectsWithHtMeta,
4
+ isSelf,
5
+ sortByInitPriority,
6
+ } from '../../../../src/hashTree/utils';
7
+ import {DataSetNames, DATA_SET_INIT_PRIORITY} from '../../../../src/hashTree/constants';
3
8
 
4
9
  import {assert} from '@webex/test-helper-chai';
5
10
 
@@ -137,4 +142,86 @@ describe('Hash Tree Utils', () => {
137
142
  assert.isFalse(isSelf(participantObject));
138
143
  });
139
144
  });
145
+
146
+ describe('#sortByInitPriority', () => {
147
+ [
148
+ {
149
+ description: 'places "main" and "self" first when both appear',
150
+ input: ['atd-active', 'main', 'atd-unmuted', 'self'],
151
+ expected: ['main', 'self', 'atd-active', 'atd-unmuted'],
152
+ },
153
+ {
154
+ description: 'preserves original order of non-priority items',
155
+ input: ['atd-unmuted', 'atd-active', 'self'],
156
+ expected: ['self', 'atd-unmuted', 'atd-active'],
157
+ },
158
+ {
159
+ description: 'returns items unchanged when no priority items present',
160
+ input: ['atd-active', 'atd-unmuted'],
161
+ expected: ['atd-active', 'atd-unmuted'],
162
+ },
163
+ {
164
+ description: 'reorders when only priority items present',
165
+ input: ['self', 'main'],
166
+ expected: ['main', 'self'],
167
+ },
168
+ {
169
+ description: 'handles empty list',
170
+ input: [],
171
+ expected: [],
172
+ },
173
+ {
174
+ description: 'handles only some priority items present',
175
+ input: ['atd-active', 'main'],
176
+ expected: ['main', 'atd-active'],
177
+ },
178
+ {
179
+ description: 'handles single non-priority item',
180
+ input: ['atd-active'],
181
+ expected: ['atd-active'],
182
+ },
183
+ {
184
+ description: 'handles single priority item',
185
+ input: ['self'],
186
+ expected: ['self'],
187
+ },
188
+ ].forEach(({description, input, expected}) => {
189
+ it(description, () => {
190
+ const items = input.map((name) => ({name}));
191
+
192
+ const result = sortByInitPriority(items, DATA_SET_INIT_PRIORITY);
193
+
194
+ assert.deepEqual(
195
+ result.map((i) => i.name),
196
+ expected
197
+ );
198
+ });
199
+ });
200
+
201
+ it('should not mutate the original array', () => {
202
+ const items = [{name: DataSetNames.ATD_ACTIVE}, {name: DataSetNames.SELF}];
203
+ const originalOrder = items.map((i) => i.name);
204
+
205
+ sortByInitPriority(items, DATA_SET_INIT_PRIORITY);
206
+
207
+ assert.deepEqual(
208
+ items.map((i) => i.name),
209
+ originalOrder
210
+ );
211
+ });
212
+
213
+ it('should preserve extra properties on items', () => {
214
+ const items = [
215
+ {name: DataSetNames.ATD_ACTIVE, url: 'url1'},
216
+ {name: DataSetNames.SELF, url: 'url2'},
217
+ ];
218
+
219
+ const result = sortByInitPriority(items, DATA_SET_INIT_PRIORITY);
220
+
221
+ assert.deepEqual(result, [
222
+ {name: DataSetNames.SELF, url: 'url2'},
223
+ {name: DataSetNames.ATD_ACTIVE, url: 'url1'},
224
+ ]);
225
+ });
226
+ });
140
227
  });