@webex/plugin-meetings 3.10.0-next.9 → 3.10.0-webex-services-ready.2

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 (73) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +11 -3
  4. package/dist/constants.js.map +1 -1
  5. package/dist/hashTree/constants.js +20 -0
  6. package/dist/hashTree/constants.js.map +1 -0
  7. package/dist/hashTree/hashTree.js +515 -0
  8. package/dist/hashTree/hashTree.js.map +1 -0
  9. package/dist/hashTree/hashTreeParser.js +1266 -0
  10. package/dist/hashTree/hashTreeParser.js.map +1 -0
  11. package/dist/hashTree/types.js +22 -0
  12. package/dist/hashTree/types.js.map +1 -0
  13. package/dist/hashTree/utils.js +48 -0
  14. package/dist/hashTree/utils.js.map +1 -0
  15. package/dist/interpretation/index.js +1 -1
  16. package/dist/interpretation/siLanguage.js +1 -1
  17. package/dist/locus-info/index.js +529 -75
  18. package/dist/locus-info/index.js.map +1 -1
  19. package/dist/locus-info/types.js +7 -0
  20. package/dist/locus-info/types.js.map +1 -0
  21. package/dist/meeting/index.js +63 -22
  22. package/dist/meeting/index.js.map +1 -1
  23. package/dist/meeting/util.js +1 -0
  24. package/dist/meeting/util.js.map +1 -1
  25. package/dist/meetings/index.js +112 -70
  26. package/dist/meetings/index.js.map +1 -1
  27. package/dist/metrics/constants.js +3 -1
  28. package/dist/metrics/constants.js.map +1 -1
  29. package/dist/reachability/clusterReachability.js +44 -358
  30. package/dist/reachability/clusterReachability.js.map +1 -1
  31. package/dist/reachability/reachability.types.js +14 -1
  32. package/dist/reachability/reachability.types.js.map +1 -1
  33. package/dist/reachability/reachabilityPeerConnection.js +445 -0
  34. package/dist/reachability/reachabilityPeerConnection.js.map +1 -0
  35. package/dist/types/constants.d.ts +26 -21
  36. package/dist/types/hashTree/constants.d.ts +8 -0
  37. package/dist/types/hashTree/hashTree.d.ts +129 -0
  38. package/dist/types/hashTree/hashTreeParser.d.ts +260 -0
  39. package/dist/types/hashTree/types.d.ts +27 -0
  40. package/dist/types/hashTree/utils.d.ts +9 -0
  41. package/dist/types/locus-info/index.d.ts +95 -56
  42. package/dist/types/locus-info/types.d.ts +54 -0
  43. package/dist/types/meeting/index.d.ts +22 -9
  44. package/dist/types/meetings/index.d.ts +9 -2
  45. package/dist/types/metrics/constants.d.ts +2 -0
  46. package/dist/types/reachability/clusterReachability.d.ts +10 -88
  47. package/dist/types/reachability/reachability.types.d.ts +12 -1
  48. package/dist/types/reachability/reachabilityPeerConnection.d.ts +111 -0
  49. package/dist/webinar/index.js +1 -1
  50. package/package.json +22 -21
  51. package/src/constants.ts +13 -1
  52. package/src/hashTree/constants.ts +9 -0
  53. package/src/hashTree/hashTree.ts +463 -0
  54. package/src/hashTree/hashTreeParser.ts +1161 -0
  55. package/src/hashTree/types.ts +32 -0
  56. package/src/hashTree/utils.ts +42 -0
  57. package/src/locus-info/index.ts +571 -106
  58. package/src/locus-info/types.ts +53 -0
  59. package/src/meeting/index.ts +78 -27
  60. package/src/meeting/util.ts +1 -0
  61. package/src/meetings/index.ts +104 -51
  62. package/src/metrics/constants.ts +2 -0
  63. package/src/reachability/clusterReachability.ts +50 -347
  64. package/src/reachability/reachability.types.ts +15 -1
  65. package/src/reachability/reachabilityPeerConnection.ts +416 -0
  66. package/test/unit/spec/hashTree/hashTree.ts +655 -0
  67. package/test/unit/spec/hashTree/hashTreeParser.ts +1532 -0
  68. package/test/unit/spec/hashTree/utils.ts +103 -0
  69. package/test/unit/spec/locus-info/index.js +722 -4
  70. package/test/unit/spec/meeting/index.js +120 -20
  71. package/test/unit/spec/meeting/utils.js +77 -0
  72. package/test/unit/spec/meetings/index.js +71 -26
  73. package/test/unit/spec/reachability/clusterReachability.ts +281 -138
@@ -12,6 +12,7 @@ import EmbeddedAppsUtils from '@webex/plugin-meetings/src/locus-info/embeddedApp
12
12
  import MediaSharesUtils from '@webex/plugin-meetings/src/locus-info//mediaSharesUtils';
13
13
  import LocusDeltaParser from '@webex/plugin-meetings/src/locus-info/parser';
14
14
  import Metrics from '@webex/plugin-meetings/src/metrics';
15
+ import * as HashTreeParserModule from '@webex/plugin-meetings/src/hashTree/hashTreeParser';
15
16
 
16
17
  import {
17
18
  LOCUSINFO,
@@ -28,6 +29,7 @@ import {
28
29
  } from '../../../../src/constants';
29
30
 
30
31
  import {self, selfWithInactivity} from './selfConstant';
32
+ import { MEETING_REMOVED_REASON } from '@webex/plugin-meetings/src/constants';
31
33
 
32
34
  describe('plugin-meetings', () => {
33
35
  describe('LocusInfo index', () => {
@@ -77,6 +79,645 @@ describe('plugin-meetings', () => {
77
79
  sinon.restore();
78
80
  });
79
81
 
82
+ describe('#initialSetup', () => {
83
+ let HashTreeParserStub;
84
+ let mockHashTreeParser;
85
+ let updateLocusCacheStub;
86
+ let updateLocusInfoStub;
87
+ let isNewFullLocusStub;
88
+
89
+ beforeEach(() => {
90
+ mockHashTreeParser = {
91
+ initializeFromMessage: sinon.stub().resolves(),
92
+ initializeFromGetLociResponse: sinon.stub().resolves(),
93
+ };
94
+ HashTreeParserStub = sinon
95
+ .stub(HashTreeParserModule, 'default')
96
+ .returns(mockHashTreeParser);
97
+ updateLocusCacheStub = sinon.stub(locusInfo, 'updateLocusCache');
98
+ updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo');
99
+ isNewFullLocusStub = sinon.stub(locusInfo.locusParser, 'isNewFullLocus').returns(true);
100
+ });
101
+
102
+ afterEach(() => {
103
+ sinon.restore();
104
+ });
105
+
106
+ const createHashTreeMessage = (visibleDataSets) => ({
107
+ locusStateElements: [
108
+ {
109
+ htMeta: {elementId: {type: 'self'}},
110
+ data: {visibleDataSets},
111
+ },
112
+ ],
113
+ dataSets: [{name: 'dataset1', url: 'test-url'}],
114
+ });
115
+
116
+ const createLocusWithVisibleDataSets = (visibleDataSets) => ({
117
+ self: {visibleDataSets},
118
+ participants: [],
119
+ links: {
120
+ resources: {
121
+ visibleDataSets: {url: 'http://visible-datasets-url.com'},
122
+ },
123
+ },
124
+ });
125
+
126
+ it('should initialize the hash tree parser when triggered from a hash tree locus message', async () => {
127
+ const visibleDataSets = ['dataset1', 'dataset2'];
128
+ const hashTreeMessage = createHashTreeMessage(visibleDataSets);
129
+
130
+ await locusInfo.initialSetup({
131
+ trigger: 'locus-message',
132
+ hashTreeMessage,
133
+ });
134
+
135
+ assert.calledOnceWithExactly(
136
+ HashTreeParserStub,
137
+ sinon.match({
138
+ initialLocus: {
139
+ locus: {self: {visibleDataSets}},
140
+ dataSets: [],
141
+ },
142
+ webexRequest: sinon.match.func,
143
+ locusInfoUpdateCallback: sinon.match.func,
144
+ debugId: sinon.match.string,
145
+ })
146
+ );
147
+ assert.calledOnceWithExactly(mockHashTreeParser.initializeFromMessage, hashTreeMessage);
148
+ assert.notCalled(updateLocusCacheStub);
149
+ assert.notCalled(updateLocusInfoStub);
150
+ assert.isTrue(locusInfo.emitChange);
151
+ });
152
+
153
+ it('should not initialize the hash tree when triggered from a non-hash tree locus message', async () => {
154
+ const locus = {url: 'http://locus-url.com', participants: []};
155
+
156
+ await locusInfo.initialSetup({
157
+ trigger: 'locus-message',
158
+ locus,
159
+ });
160
+
161
+ assert.notCalled(HashTreeParserStub);
162
+ assert.notCalled(mockHashTreeParser.initializeFromMessage);
163
+ assert.calledOnceWithExactly(updateLocusCacheStub, locus);
164
+ assert.calledOnce(updateLocusInfoStub);
165
+ assert.isTrue(locusInfo.emitChange);
166
+ });
167
+
168
+ it('should initialize the hash tree parser correctly when triggered from a join response containing datasets', async () => {
169
+ const visibleDataSets = ['dataset1', 'dataset2'];
170
+ const locus = createLocusWithVisibleDataSets(visibleDataSets);
171
+ const dataSets = [{name: 'dataset1', url: 'http://dataset-url.com'}];
172
+
173
+ await locusInfo.initialSetup({
174
+ trigger: 'join-response',
175
+ locus,
176
+ dataSets,
177
+ });
178
+
179
+ assert.calledOnceWithExactly(
180
+ HashTreeParserStub,
181
+ sinon.match({
182
+ initialLocus: {
183
+ locus,
184
+ dataSets,
185
+ },
186
+ webexRequest: sinon.match.func,
187
+ locusInfoUpdateCallback: sinon.match.func,
188
+ debugId: sinon.match.string,
189
+ })
190
+ );
191
+ assert.calledOnceWithExactly(updateLocusCacheStub, locus);
192
+ assert.calledOnce(updateLocusInfoStub);
193
+ assert.isTrue(locusInfo.emitChange);
194
+ });
195
+
196
+ it('should do normal (classic) initialization when triggered from a join response without datasets', async () => {
197
+ const locus = {url: 'http://locus-url.com', participants: []};
198
+
199
+ await locusInfo.initialSetup({
200
+ trigger: 'join-response',
201
+ locus,
202
+ });
203
+
204
+ assert.notCalled(HashTreeParserStub);
205
+ assert.calledOnceWithExactly(updateLocusCacheStub, locus);
206
+ assert.calledOnce(updateLocusInfoStub);
207
+ assert.isTrue(locusInfo.emitChange);
208
+ });
209
+
210
+ it('should initialize the hash tree parser correctly when triggered from a get loci response containing visible datasets', async () => {
211
+ const visibleDataSets = ['dataset1', 'dataset2'];
212
+ const locus = createLocusWithVisibleDataSets(visibleDataSets);
213
+
214
+ await locusInfo.initialSetup({
215
+ trigger: 'get-loci-response',
216
+ locus,
217
+ });
218
+
219
+ assert.calledOnceWithExactly(
220
+ HashTreeParserStub,
221
+ sinon.match({
222
+ initialLocus: {
223
+ locus: {self: {visibleDataSets}},
224
+ dataSets: [],
225
+ },
226
+ webexRequest: sinon.match.func,
227
+ locusInfoUpdateCallback: sinon.match.func,
228
+ debugId: sinon.match.string,
229
+ })
230
+ );
231
+ assert.calledOnceWithExactly(mockHashTreeParser.initializeFromGetLociResponse, locus);
232
+ assert.notCalled(updateLocusCacheStub);
233
+ assert.notCalled(updateLocusInfoStub);
234
+ assert.isTrue(locusInfo.emitChange);
235
+ });
236
+
237
+ it('should do normal (classic) initialization when triggered from a get loci response without visible datasets', async () => {
238
+ const locus = {url: 'http://locus-url.com', participants: []};
239
+
240
+ await locusInfo.initialSetup({
241
+ trigger: 'get-loci-response',
242
+ locus,
243
+ });
244
+
245
+ assert.notCalled(HashTreeParserStub);
246
+ assert.notCalled(mockHashTreeParser.initializeFromGetLociResponse);
247
+ assert.calledOnceWithExactly(updateLocusCacheStub, locus);
248
+ assert.calledOnce(updateLocusInfoStub);
249
+ assert.isTrue(locusInfo.emitChange);
250
+ });
251
+
252
+ describe('should setup correct locusInfoUpdateCallback when creating HashTreeParser', () => {
253
+ const OBJECTS_UPDATED = HashTreeParserModule.LocusInfoUpdateType.OBJECTS_UPDATED;
254
+ const MEETING_ENDED = HashTreeParserModule.LocusInfoUpdateType.MEETING_ENDED;
255
+
256
+ let locusInfoUpdateCallback;
257
+ let onDeltaLocusStub;
258
+ let expectedLocusInfo;
259
+
260
+ beforeEach(async () => {
261
+ onDeltaLocusStub = sinon.stub(locusInfo, 'onDeltaLocus');
262
+
263
+ await locusInfo.initialSetup({
264
+ trigger: 'locus-message',
265
+ hashTreeMessage: {
266
+ locusStateElements: [
267
+ {
268
+ htMeta: {elementId: {type: 'self'}},
269
+ data: {visibleDataSets: ['dataset1']},
270
+ },
271
+ ],
272
+ dataSets: [{name: 'dataset1', url: 'test-url'}],
273
+ },
274
+ });
275
+
276
+ locusInfoUpdateCallback = HashTreeParserStub.firstCall.args[0].locusInfoUpdateCallback;
277
+
278
+ assert.isDefined(locusInfoUpdateCallback);
279
+
280
+ // setup fake initial locusInfo state
281
+ locusInfo.controls = {id: 'fake-controls'};
282
+ locusInfo.fullState = {id: 'fake-full-state'};
283
+ locusInfo.host = {id: 'fake-host'};
284
+ locusInfo.info = {id: 'fake-info'};
285
+ locusInfo.links = {id: 'fake-links'};
286
+ locusInfo.mediaShares = [
287
+ {
288
+ id: 'fake-media-share-1',
289
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1', version: 1}},
290
+ },
291
+ {
292
+ id: 'fake-media-share-2',
293
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
294
+ },
295
+ ];
296
+ locusInfo.meetings = {id: 'fake-meetings'};
297
+ locusInfo.participants = [
298
+ {id: 'fake-participant-1', name: 'Participant One'},
299
+ {id: 'fake-participant-2', name: 'Participant Two'},
300
+ ];
301
+ locusInfo.hashTreeObjectId2ParticipantId.set(
302
+ 'fake-ht-participant-1',
303
+ 'fake-participant-1'
304
+ );
305
+ locusInfo.hashTreeObjectId2ParticipantId.set(
306
+ 'fake-ht-participant-2',
307
+ 'fake-participant-2'
308
+ );
309
+ locusInfo.replaces = {id: 'fake-replaces'};
310
+ locusInfo.self = {id: 'fake-self'};
311
+ locusInfo.url = 'fake-locus-url';
312
+ locusInfo.htMeta = {elementId: {type: 'locus', id: 'fake-ht-locus-id', version: 1}};
313
+
314
+ // setup the default expected locus info state that each test builds upon
315
+ expectedLocusInfo = {
316
+ controls: {id: 'fake-controls'},
317
+ fullState: {id: 'fake-full-state'},
318
+ host: {id: 'fake-host'},
319
+ info: {id: 'fake-info'},
320
+ links: {id: 'fake-links'},
321
+ mediaShares: [
322
+ {
323
+ id: 'fake-media-share-1',
324
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1', version: 1}},
325
+ },
326
+ {
327
+ id: 'fake-media-share-2',
328
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
329
+ },
330
+ ],
331
+ meetings: {id: 'fake-meetings'},
332
+ jsSdkMeta: {removedParticipantIds: []},
333
+ participants: [], // empty means there were no participant updates
334
+ replaces: {id: 'fake-replaces'},
335
+ self: {id: 'fake-self'},
336
+ url: 'fake-locus-url',
337
+ htMeta: {elementId: {type: 'locus', id: 'fake-ht-locus-id', version: 1}},
338
+ sequence: null, // not relevant for hash trees, so should remain null
339
+ syncUrl: undefined, // not relevant for hash trees, so should remain undefined
340
+ };
341
+ });
342
+
343
+ it('should process locus update correctly when called with updated SELF', () => {
344
+ const newSelf = {
345
+ id: 'new-self',
346
+ visibleDataSets: ['dataset1', 'dataset2'],
347
+ };
348
+
349
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
350
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
351
+ updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
352
+ });
353
+
354
+ // check onDeltaLocus() was called with correctly updated locus info
355
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
356
+ ...expectedLocusInfo,
357
+ self: newSelf,
358
+ });
359
+ });
360
+
361
+ it('should process locus update correctly when called with updated fullState', () => {
362
+ const newFullState = {
363
+ id: 'new-fullState',
364
+ visibleDataSets: ['dataset1', 'dataset2'],
365
+ };
366
+
367
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
368
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
369
+ updatedObjects: [{htMeta: {elementId: {type: 'fullState'}}, data: newFullState}],
370
+ });
371
+
372
+ // check onDeltaLocus() was called with correctly updated locus info
373
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
374
+ ...expectedLocusInfo,
375
+ fullState: newFullState,
376
+ });
377
+ });
378
+
379
+ it('should process locus update correctly when called with updated info', () => {
380
+ const newInfo = {
381
+ id: 'new-info',
382
+ visibleDataSets: ['dataset1', 'dataset2'],
383
+ };
384
+
385
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
386
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
387
+ updatedObjects: [{htMeta: {elementId: {type: 'info'}}, data: newInfo}],
388
+ });
389
+
390
+ // check onDeltaLocus() was called with correctly updated locus info
391
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
392
+ ...expectedLocusInfo,
393
+ info: newInfo,
394
+ });
395
+ });
396
+
397
+ it('should process locus update correctly when called with updated links', () => {
398
+ const newLinks = {
399
+ id: 'new-links',
400
+ visibleDataSets: ['dataset1', 'dataset2'],
401
+ };
402
+
403
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
404
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
405
+ updatedObjects: [{htMeta: {elementId: {type: 'links'}}, data: newLinks}],
406
+ });
407
+
408
+ // check onDeltaLocus() was called with correctly updated locus info
409
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
410
+ ...expectedLocusInfo,
411
+ links: newLinks,
412
+ });
413
+ });
414
+
415
+ it('should process locus update correctly when called with updated LOCUS object', () => {
416
+ // setup new updated locus that has many things missing
417
+ const newLocusHtMeta = {elementId: {type: 'locus', version: 42}};
418
+ const newLocus = {
419
+ controls: 'new-controls',
420
+ host: 'new-host',
421
+ htMeta: newLocusHtMeta,
422
+ };
423
+
424
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
425
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
426
+ updatedObjects: [{htMeta: newLocusHtMeta, data: newLocus}],
427
+ });
428
+
429
+ // check onDeltaLocus() was called with correctly updated locus info
430
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
431
+ // these fields are not part of Locus object, so should keep their old values:
432
+ info: {id: 'fake-info'},
433
+ fullState: {id: 'fake-full-state'},
434
+ self: {id: 'fake-self'},
435
+ links: { id: 'fake-links' },
436
+ mediaShares: expectedLocusInfo.mediaShares,
437
+ // and now the new fields
438
+ ...newLocus,
439
+ htMeta: newLocusHtMeta,
440
+ participants: [], // empty means there were no participant updates
441
+ jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
442
+ });
443
+ });
444
+
445
+ // this test is checking that we cope with an edge case if Locus
446
+ // sends us something that they shouldn't
447
+ it('should process locus update correctly when called with updated LOCUS object that contains info/fullState/self/participants etc', () => {
448
+ // setup new updated locus that has many things missing
449
+ const newLocusHtMeta = {elementId: {type: 'locus', version: 42}};
450
+ const newLocus = {
451
+ controls: 'new-controls',
452
+ host: 'new-host',
453
+ htMeta: newLocusHtMeta,
454
+ };
455
+
456
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
457
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
458
+ updatedObjects: [
459
+ {
460
+ htMeta: newLocusHtMeta,
461
+ data: {
462
+ ...newLocus,
463
+ // all these fields below should be ignored and not override the existing ones in our "old" Locus
464
+ info: 'new-info',
465
+ fullState: 'new-fullState',
466
+ self: 'new-self',
467
+ participants: 'new-participants',
468
+ mediaShares: 'new-mediaShares',
469
+ },
470
+ },
471
+ ],
472
+ });
473
+
474
+ // check onDeltaLocus() was called with correctly updated locus info
475
+ // with old values for the fields that should be ignored (like "info" or "fullState")
476
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
477
+ // these fields have the "old" values:
478
+ info: {id: 'fake-info'},
479
+ fullState: {id: 'fake-full-state'},
480
+ self: {id: 'fake-self'},
481
+ links: { id: 'fake-links' },
482
+ mediaShares: expectedLocusInfo.mediaShares,
483
+ participants: [], // empty means there were no participant updates
484
+ jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
485
+ ...newLocus,
486
+ htMeta: newLocusHtMeta,
487
+ });
488
+ });
489
+
490
+ it('should process locus update correctly when called with removed LOCUS object followed by updated LOCUS object', () => {
491
+ // setup new updated locus that has many things missing
492
+ const newLocusHtMeta = {elementId: {type: 'locus', version: 99}};
493
+ const newLocus = {
494
+ info: 'new-info',
495
+ links: 'new-links',
496
+ htMeta: newLocusHtMeta,
497
+ };
498
+
499
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
500
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
501
+ updatedObjects: [
502
+ // first, a removal of LOCUS object
503
+ {htMeta: {elementId: {type: 'locus'}}, data: null},
504
+ // followed by an update of LOCUS object
505
+ {htMeta: newLocusHtMeta, data: newLocus},
506
+ ],
507
+ });
508
+
509
+ // check onDeltaLocus() was called with correctly updated locus info
510
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
511
+ // these fields are not part of Locus object, so should keep their old values:
512
+ info: {id: 'fake-info'},
513
+ fullState: {id: 'fake-full-state'},
514
+ self: {id: 'fake-self'},
515
+ links: {id: 'fake-links'},
516
+ mediaShares: expectedLocusInfo.mediaShares,
517
+ // and now the new fields
518
+ ...newLocus,
519
+ htMeta: newLocusHtMeta,
520
+ participants: [], // empty means there were no participant updates
521
+ jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
522
+ });
523
+ });
524
+
525
+ it('should send a metric if unsupported sequence of LOCUS object updates occurs (update followed by removal)', () => {
526
+ const newLocus = {
527
+ info: 'new-info',
528
+ };
529
+ const newLocusHtMeta = {elementId: {type: 'locus', version: 99}};
530
+
531
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
532
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
533
+ updatedObjects: [
534
+ // first, an update
535
+ {htMeta: newLocusHtMeta, data: newLocus},
536
+ // followed by removal
537
+ {htMeta: {elementId: {type: 'locus'}}, data: null},
538
+ ],
539
+ });
540
+
541
+ assert.calledWith(
542
+ sendBehavioralMetricStub,
543
+ 'js_sdk_locus_hash_tree_unsupported_operation',
544
+ {
545
+ locusUrl: 'fake-locus-url',
546
+ message: 'LOCUS object update followed by removal',
547
+ }
548
+ );
549
+ });
550
+
551
+ it('should send a metric if unsupported sequence of LOCUS object updates occurs (multiple updates)', () => {
552
+ const newLocus1 = {
553
+ info: 'new-info-1',
554
+ };
555
+ const newLocus2 = {
556
+ info: 'new-info-2',
557
+ url: 'new-url-2',
558
+ };
559
+
560
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
561
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
562
+ updatedObjects: [
563
+ // first, an update
564
+ {htMeta: {elementId: {type: 'locus'}}, data: newLocus1},
565
+ // followed by another update
566
+ {htMeta: {elementId: {type: 'locus'}}, data: newLocus2},
567
+ ],
568
+ });
569
+
570
+ assert.calledWith(
571
+ sendBehavioralMetricStub,
572
+ 'js_sdk_locus_hash_tree_unsupported_operation',
573
+ {
574
+ locusUrl: 'new-url-2',
575
+ message: 'multiple LOCUS object updates',
576
+ }
577
+ );
578
+ });
579
+
580
+ it('should process locus update correctly when called with added/updated/removed PARTICIPANT objects', () => {
581
+ const newParticipant = {
582
+ id: 'fake-participant-3',
583
+ name: 'New Participant',
584
+ };
585
+ const updatedParticipant2 = {
586
+ id: 'fake-participant-2',
587
+ name: 'Updated Participant Two',
588
+ };
589
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
590
+ // with 1 participant added, 1 updated, and 1 removed
591
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
592
+ updatedObjects: [
593
+ {htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-1'}}, data: null},
594
+ {
595
+ htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-3'}},
596
+ data: newParticipant,
597
+ },
598
+ {
599
+ htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-2'}},
600
+ data: updatedParticipant2,
601
+ },
602
+ ],
603
+ });
604
+
605
+ // check onDeltaLocus() was called with correctly updated locus info
606
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
607
+ ...expectedLocusInfo,
608
+ participants: [newParticipant, updatedParticipant2],
609
+ jsSdkMeta: {removedParticipantIds: ['fake-participant-1']},
610
+ });
611
+ // and that the hashTreeObjectId2ParticipantId map was updated correctly
612
+ assert.isUndefined(locusInfo.hashTreeObjectId2ParticipantId.get('fake-ht-participant-1'));
613
+ assert.equal(
614
+ locusInfo.hashTreeObjectId2ParticipantId.get('fake-ht-participant-2'),
615
+ 'fake-participant-2'
616
+ );
617
+ assert.equal(
618
+ locusInfo.hashTreeObjectId2ParticipantId.get('fake-ht-participant-3'),
619
+ 'fake-participant-3'
620
+ );
621
+ });
622
+
623
+ it('should process locus update correctly when called with updated MEDIASHARE objects', () => {
624
+ const newMediaShare = {
625
+ id: 'new-mediaShare-3',
626
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-3', version: 100}},
627
+ };
628
+ const updatedMediaShare2 = {
629
+ id: 'fake-media-share-2',
630
+ someNewProp: 'newValue',
631
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 100}},
632
+ };
633
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
634
+ // with 1 participant added, 1 updated, and 1 removed
635
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
636
+ updatedObjects: [
637
+ {htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1'}}, data: null},
638
+ {
639
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2'}},
640
+ data: updatedMediaShare2,
641
+ },
642
+ {
643
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-3'}},
644
+ data: newMediaShare,
645
+ },
646
+ ],
647
+ });
648
+
649
+ // check onDeltaLocus() was called with correctly updated locus info
650
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
651
+ ...expectedLocusInfo,
652
+ mediaShares: [updatedMediaShare2, newMediaShare],
653
+ });
654
+ });
655
+
656
+ it('should process locus update correctly when called with a combination of various updated objects', () => {
657
+ const newSelf = {
658
+ id: 'new-self',
659
+ visibleDataSets: ['dataset1', 'dataset2'],
660
+ };
661
+ const updatedMediaShare2 = {
662
+ id: 'fake-media-share-2',
663
+ someNewProp: 'newValue',
664
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 100}},
665
+ };
666
+ const updatedParticipant2 = {
667
+ id: 'fake-participant-2',
668
+ name: 'Updated Participant Two',
669
+ };
670
+
671
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
672
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
673
+ updatedObjects: [
674
+ {
675
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2'}},
676
+ data: updatedMediaShare2,
677
+ },
678
+ {htMeta: {elementId: {type: 'self'}}, data: newSelf},
679
+ {
680
+ htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-2'}},
681
+ data: updatedParticipant2,
682
+ },
683
+ ],
684
+ });
685
+
686
+ // check onDeltaLocus() was called with correctly updated locus info
687
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
688
+ ...expectedLocusInfo,
689
+ mediaShares: [
690
+ {
691
+ id: 'fake-media-share-1',
692
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1', version: 1}},
693
+ },
694
+ updatedMediaShare2,
695
+ ],
696
+ participants: [updatedParticipant2],
697
+ self: newSelf,
698
+ });
699
+ });
700
+
701
+ it('should handle MEETING_ENDED correctly', () => {
702
+ const fakeMeeting = {id: 'fake-meeting-from-collection'};
703
+ const collectionGetStub = sinon
704
+ .stub(locusInfo.webex.meetings.meetingCollection, 'get')
705
+ .returns(fakeMeeting);
706
+ const destroyStub = sinon.stub(locusInfo.webex.meetings, 'destroy');
707
+
708
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
709
+ locusInfoUpdateCallback(MEETING_ENDED);
710
+
711
+ assert.calledOnceWithExactly(collectionGetStub, locusInfo.meetingId);
712
+ assert.calledOnceWithExactly(
713
+ destroyStub,
714
+ fakeMeeting,
715
+ MEETING_REMOVED_REASON.SELF_REMOVED
716
+ );
717
+ });
718
+ });
719
+ });
720
+
80
721
  describe('#updateControls', () => {
81
722
  let newControls;
82
723
 
@@ -2041,7 +2682,7 @@ describe('plugin-meetings', () => {
2041
2682
  });
2042
2683
 
2043
2684
  describe('#handleLocusAPIResponse', () => {
2044
- it('calls handleLocusDelta', () => {
2685
+ it('calls handleLocusDelta when we are not using hash trees', () => {
2045
2686
  const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE};
2046
2687
 
2047
2688
  sinon.stub(locusInfo, 'handleLocusDelta');
@@ -2050,6 +2691,23 @@ describe('plugin-meetings', () => {
2050
2691
 
2051
2692
  assert.calledWith(locusInfo.handleLocusDelta, fakeLocus, mockMeeting);
2052
2693
  });
2694
+ it('calls hash tree parser when we are using hash trees', () => {
2695
+ const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE};
2696
+ const fakeDataSets = [{name: 'dataset1', url: 'http://test.com'}];
2697
+ const responseBody = {locus: fakeLocus, dataSets: fakeDataSets};
2698
+
2699
+ // Create a mock hash tree parser
2700
+ const mockHashTreeParser = {
2701
+ handleLocusUpdate: sinon.stub(),
2702
+ };
2703
+ locusInfo.hashTreeParser = mockHashTreeParser;
2704
+
2705
+ sinon.stub(locusInfo, 'onDeltaLocus');
2706
+
2707
+ locusInfo.handleLocusAPIResponse(mockMeeting, responseBody);
2708
+
2709
+ assert.calledOnceWithExactly(mockHashTreeParser.handleLocusUpdate, responseBody);
2710
+ });
2053
2711
  });
2054
2712
 
2055
2713
  describe('#LocusDeltaEvents', () => {
@@ -2134,7 +2792,7 @@ describe('plugin-meetings', () => {
2134
2792
  sinon.stub(locusInfo, "updateMemberShip");
2135
2793
  sinon.stub(locusInfo, "updateIdentifiers");
2136
2794
  sinon.stub(locusInfo, "updateEmbeddedApps");
2137
- sinon.stub(locusInfo, "updateResources");
2795
+ sinon.stub(locusInfo, "updateLinks");
2138
2796
  sinon.stub(locusInfo, "compareAndUpdate");
2139
2797
 
2140
2798
  locusInfo.updateLocusInfo(locus);
@@ -2168,7 +2826,7 @@ describe('plugin-meetings', () => {
2168
2826
  locusInfo.updateMemberShip = sinon.stub();
2169
2827
  locusInfo.updateIdentifiers = sinon.stub();
2170
2828
  locusInfo.updateEmbeddedApps = sinon.stub();
2171
- locusInfo.updateResources = sinon.stub();
2829
+ locusInfo.updateLinks = sinon.stub();
2172
2830
  locusInfo.compareAndUpdate = sinon.stub();
2173
2831
 
2174
2832
  locusInfo.updateLocusInfo(newLocus);
@@ -2190,11 +2848,42 @@ describe('plugin-meetings', () => {
2190
2848
  assert.notCalled(locusInfo.updateMemberShip);
2191
2849
  assert.notCalled(locusInfo.updateIdentifiers);
2192
2850
  assert.notCalled(locusInfo.updateEmbeddedApps);
2193
- assert.notCalled(locusInfo.updateResources);
2851
+ assert.notCalled(locusInfo.updateLinks);
2194
2852
  assert.notCalled(locusInfo.compareAndUpdate);
2195
2853
  });
2196
2854
 
2855
+ it('#updateLocusInfo puts the Locus DTO top level properties at the right place in LocusInfo class', () => {
2856
+ // this test verifies that the top-level properties of Locus DTO are copied
2857
+ // into LocusInfo class and set as top level properties too
2858
+ // this is important, because the code handling Locus hass trees relies on it, see updateFromHashTree()
2859
+ const info = {id: 'info id'};
2860
+ const fullState = {id: 'fullState id'};
2861
+ const links = {services: {id: 'service links'}, resources: {id: 'resource links'}};
2862
+ const self = {id: 'self id'};
2863
+ const mediaShares = [{id: 'fake media share'}];
2864
+
2865
+ sinon.stub(SelfUtils, 'getSelves').returns({
2866
+ current: {},
2867
+ previous: {},
2868
+ updates: {},
2869
+ });
2870
+
2871
+ const newLocus = {
2872
+ info,
2873
+ fullState,
2874
+ links,
2875
+ self,
2876
+ mediaShares,
2877
+ };
2197
2878
 
2879
+ locusInfo.updateLocusInfo(newLocus);
2880
+
2881
+ assert.deepEqual(locusInfo.info, newLocus.info);
2882
+ assert.deepEqual(locusInfo.fullState, newLocus.fullState);
2883
+ assert.deepEqual(locusInfo.links, newLocus.links);
2884
+ assert.deepEqual(locusInfo.self, newLocus.self);
2885
+ assert.deepEqual(locusInfo.mediaShares, newLocus.mediaShares);
2886
+ });
2198
2887
 
2199
2888
  it('onFullLocus() updates the working-copy of locus parser', () => {
2200
2889
  const eventType = 'fakeEvent';
@@ -3351,5 +4040,34 @@ describe('plugin-meetings', () => {
3351
4040
  assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[7]);
3352
4041
  });
3353
4042
  });
4043
+
4044
+ describe('#parse', () => {
4045
+ it('handles hash tree messages correctly', () => {
4046
+ const fakeHashTreeMessage = {
4047
+ locusStateElements: [
4048
+ {
4049
+ htMeta: {elementId: {type: 'self'}},
4050
+ data: {visibleDataSets: ['dataset1']},
4051
+ },
4052
+ ],
4053
+ dataSets: [{name: 'dataset1', url: 'http://test.com'}],
4054
+ };
4055
+
4056
+ const data = {
4057
+ eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
4058
+ stateElementsMessage: fakeHashTreeMessage,
4059
+ };
4060
+
4061
+ // Create a mock hash tree parser
4062
+ const mockHashTreeParser = {
4063
+ handleMessage: sinon.stub(),
4064
+ };
4065
+ locusInfo.hashTreeParser = mockHashTreeParser;
4066
+
4067
+ locusInfo.parse(mockMeeting, data);
4068
+
4069
+ assert.calledOnceWithExactly(mockHashTreeParser.handleMessage, fakeHashTreeMessage);
4070
+ });
4071
+ });
3354
4072
  });
3355
4073
  });