@webex/plugin-meetings 3.11.0 → 3.12.0

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 (170) hide show
  1. package/dist/aiEnableRequest/index.js +184 -0
  2. package/dist/aiEnableRequest/index.js.map +1 -0
  3. package/dist/aiEnableRequest/utils.js +36 -0
  4. package/dist/aiEnableRequest/utils.js.map +1 -0
  5. package/dist/annotation/index.js +14 -5
  6. package/dist/annotation/index.js.map +1 -1
  7. package/dist/breakouts/breakout.js +1 -1
  8. package/dist/breakouts/index.js +1 -1
  9. package/dist/config.js +5 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +28 -6
  12. package/dist/constants.js.map +1 -1
  13. package/dist/hashTree/constants.js +3 -1
  14. package/dist/hashTree/constants.js.map +1 -1
  15. package/dist/hashTree/hashTree.js +18 -0
  16. package/dist/hashTree/hashTree.js.map +1 -1
  17. package/dist/hashTree/hashTreeParser.js +709 -380
  18. package/dist/hashTree/hashTreeParser.js.map +1 -1
  19. package/dist/hashTree/types.js +4 -2
  20. package/dist/hashTree/types.js.map +1 -1
  21. package/dist/hashTree/utils.js +10 -0
  22. package/dist/hashTree/utils.js.map +1 -1
  23. package/dist/index.js +11 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/interceptors/constant.js +12 -0
  26. package/dist/interceptors/constant.js.map +1 -0
  27. package/dist/interceptors/dataChannelAuthToken.js +290 -0
  28. package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
  29. package/dist/interceptors/index.js +7 -0
  30. package/dist/interceptors/index.js.map +1 -1
  31. package/dist/interceptors/utils.js +27 -0
  32. package/dist/interceptors/utils.js.map +1 -0
  33. package/dist/interpretation/index.js +2 -2
  34. package/dist/interpretation/index.js.map +1 -1
  35. package/dist/interpretation/siLanguage.js +1 -1
  36. package/dist/locus-info/controlsUtils.js +5 -3
  37. package/dist/locus-info/controlsUtils.js.map +1 -1
  38. package/dist/locus-info/index.js +217 -79
  39. package/dist/locus-info/index.js.map +1 -1
  40. package/dist/locus-info/selfUtils.js +1 -0
  41. package/dist/locus-info/selfUtils.js.map +1 -1
  42. package/dist/locus-info/types.js.map +1 -1
  43. package/dist/media/MediaConnectionAwaiter.js +57 -1
  44. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  45. package/dist/media/properties.js +4 -2
  46. package/dist/media/properties.js.map +1 -1
  47. package/dist/meeting/in-meeting-actions.js +7 -1
  48. package/dist/meeting/in-meeting-actions.js.map +1 -1
  49. package/dist/meeting/index.js +1082 -861
  50. package/dist/meeting/index.js.map +1 -1
  51. package/dist/meeting/request.js +50 -0
  52. package/dist/meeting/request.js.map +1 -1
  53. package/dist/meeting/request.type.js.map +1 -1
  54. package/dist/meeting/util.js +133 -3
  55. package/dist/meeting/util.js.map +1 -1
  56. package/dist/meetings/index.js +100 -45
  57. package/dist/meetings/index.js.map +1 -1
  58. package/dist/member/index.js +10 -0
  59. package/dist/member/index.js.map +1 -1
  60. package/dist/member/util.js +10 -0
  61. package/dist/member/util.js.map +1 -1
  62. package/dist/metrics/constants.js +2 -1
  63. package/dist/metrics/constants.js.map +1 -1
  64. package/dist/multistream/mediaRequestManager.js +9 -60
  65. package/dist/multistream/mediaRequestManager.js.map +1 -1
  66. package/dist/multistream/remoteMediaManager.js +11 -0
  67. package/dist/multistream/remoteMediaManager.js.map +1 -1
  68. package/dist/reachability/index.js +18 -10
  69. package/dist/reachability/index.js.map +1 -1
  70. package/dist/reactions/reactions.type.js.map +1 -1
  71. package/dist/reconnection-manager/index.js +0 -1
  72. package/dist/reconnection-manager/index.js.map +1 -1
  73. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  74. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  75. package/dist/types/config.d.ts +3 -0
  76. package/dist/types/constants.d.ts +23 -1
  77. package/dist/types/hashTree/constants.d.ts +1 -0
  78. package/dist/types/hashTree/hashTree.d.ts +7 -0
  79. package/dist/types/hashTree/hashTreeParser.d.ts +99 -14
  80. package/dist/types/hashTree/types.d.ts +3 -0
  81. package/dist/types/hashTree/utils.d.ts +6 -0
  82. package/dist/types/index.d.ts +1 -0
  83. package/dist/types/interceptors/constant.d.ts +5 -0
  84. package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
  85. package/dist/types/interceptors/index.d.ts +2 -1
  86. package/dist/types/interceptors/utils.d.ts +1 -0
  87. package/dist/types/locus-info/index.d.ts +21 -2
  88. package/dist/types/locus-info/types.d.ts +1 -0
  89. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  90. package/dist/types/media/properties.d.ts +2 -1
  91. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  92. package/dist/types/meeting/index.d.ts +38 -6
  93. package/dist/types/meeting/request.d.ts +16 -1
  94. package/dist/types/meeting/request.type.d.ts +5 -0
  95. package/dist/types/meeting/util.d.ts +31 -0
  96. package/dist/types/meetings/index.d.ts +4 -2
  97. package/dist/types/member/index.d.ts +1 -0
  98. package/dist/types/member/util.d.ts +5 -0
  99. package/dist/types/metrics/constants.d.ts +1 -0
  100. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  101. package/dist/types/reactions/reactions.type.d.ts +1 -0
  102. package/dist/types/webinar/utils.d.ts +6 -0
  103. package/dist/webinar/index.js +260 -90
  104. package/dist/webinar/index.js.map +1 -1
  105. package/dist/webinar/utils.js +25 -0
  106. package/dist/webinar/utils.js.map +1 -0
  107. package/package.json +24 -23
  108. package/src/aiEnableRequest/README.md +84 -0
  109. package/src/aiEnableRequest/index.ts +170 -0
  110. package/src/aiEnableRequest/utils.ts +25 -0
  111. package/src/annotation/index.ts +27 -7
  112. package/src/config.ts +3 -0
  113. package/src/constants.ts +29 -1
  114. package/src/hashTree/constants.ts +1 -0
  115. package/src/hashTree/hashTree.ts +17 -0
  116. package/src/hashTree/hashTreeParser.ts +627 -249
  117. package/src/hashTree/types.ts +4 -0
  118. package/src/hashTree/utils.ts +9 -0
  119. package/src/index.ts +8 -1
  120. package/src/interceptors/constant.ts +6 -0
  121. package/src/interceptors/dataChannelAuthToken.ts +170 -0
  122. package/src/interceptors/index.ts +2 -1
  123. package/src/interceptors/utils.ts +16 -0
  124. package/src/interpretation/index.ts +2 -2
  125. package/src/locus-info/controlsUtils.ts +11 -0
  126. package/src/locus-info/index.ts +231 -61
  127. package/src/locus-info/selfUtils.ts +1 -0
  128. package/src/locus-info/types.ts +1 -0
  129. package/src/media/MediaConnectionAwaiter.ts +41 -1
  130. package/src/media/properties.ts +3 -1
  131. package/src/meeting/in-meeting-actions.ts +12 -0
  132. package/src/meeting/index.ts +205 -44
  133. package/src/meeting/request.ts +42 -0
  134. package/src/meeting/request.type.ts +6 -0
  135. package/src/meeting/util.ts +160 -2
  136. package/src/meetings/index.ts +135 -41
  137. package/src/member/index.ts +10 -0
  138. package/src/member/util.ts +12 -0
  139. package/src/metrics/constants.ts +1 -0
  140. package/src/multistream/mediaRequestManager.ts +4 -54
  141. package/src/multistream/remoteMediaManager.ts +13 -0
  142. package/src/reachability/index.ts +9 -0
  143. package/src/reactions/reactions.type.ts +1 -0
  144. package/src/reconnection-manager/index.ts +0 -1
  145. package/src/webinar/index.ts +162 -5
  146. package/src/webinar/utils.ts +16 -0
  147. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  148. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  149. package/test/unit/spec/annotation/index.ts +69 -7
  150. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  151. package/test/unit/spec/hashTree/hashTreeParser.ts +1869 -189
  152. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
  153. package/test/unit/spec/interceptors/utils.ts +75 -0
  154. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  155. package/test/unit/spec/locus-info/index.js +383 -46
  156. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  157. package/test/unit/spec/media/properties.ts +12 -3
  158. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  159. package/test/unit/spec/meeting/index.js +716 -115
  160. package/test/unit/spec/meeting/request.js +70 -0
  161. package/test/unit/spec/meeting/utils.js +438 -26
  162. package/test/unit/spec/meetings/index.js +652 -31
  163. package/test/unit/spec/member/index.js +28 -4
  164. package/test/unit/spec/member/util.js +65 -27
  165. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  166. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
  167. package/test/unit/spec/reachability/index.ts +23 -0
  168. package/test/unit/spec/reconnection-manager/index.js +4 -8
  169. package/test/unit/spec/webinar/index.ts +348 -36
  170. package/test/unit/spec/webinar/utils.ts +39 -0
@@ -5,7 +5,7 @@ import {assert} from '@webex/test-helper-chai';
5
5
  import MockWebex from '@webex/test-helper-mock-webex';
6
6
  import testUtils from '../../../utils/testUtils';
7
7
  import Meetings from '@webex/plugin-meetings';
8
- import LocusInfo from '@webex/plugin-meetings/src/locus-info';
8
+ import LocusInfo, {createLocusFromHashTreeMessage} from '@webex/plugin-meetings/src/locus-info';
9
9
  import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
10
10
  import InfoUtils from '@webex/plugin-meetings/src/locus-info/infoUtils';
11
11
  import EmbeddedAppsUtils from '@webex/plugin-meetings/src/locus-info/embeddedAppsUtils';
@@ -29,7 +29,8 @@ import {
29
29
  } from '../../../../src/constants';
30
30
 
31
31
  import {self, selfWithInactivity} from './selfConstant';
32
- import { MEETING_REMOVED_REASON } from '@webex/plugin-meetings/src/constants';
32
+ import {MEETING_REMOVED_REASON} from '@webex/plugin-meetings/src/constants';
33
+ import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
33
34
 
34
35
  describe('plugin-meetings', () => {
35
36
  describe('LocusInfo index', () => {
@@ -106,7 +107,7 @@ describe('plugin-meetings', () => {
106
107
  const createHashTreeMessage = (visibleDataSets) => ({
107
108
  locusStateElements: [
108
109
  {
109
- htMeta: {elementId: {type: 'self'}},
110
+ htMeta: {elementId: {type: 'metadata'}},
110
111
  data: {visibleDataSets},
111
112
  },
112
113
  ],
@@ -136,9 +137,13 @@ describe('plugin-meetings', () => {
136
137
  HashTreeParserStub,
137
138
  sinon.match({
138
139
  initialLocus: {
139
- locus: {self: {visibleDataSets}},
140
+ locus: null,
140
141
  dataSets: [],
141
142
  },
143
+ metadata: {
144
+ htMeta: hashTreeMessage.locusStateElements[0].htMeta,
145
+ visibleDataSets,
146
+ },
142
147
  webexRequest: sinon.match.func,
143
148
  locusInfoUpdateCallback: sinon.match.func,
144
149
  debugId: sinon.match.string,
@@ -169,11 +174,16 @@ describe('plugin-meetings', () => {
169
174
  const visibleDataSets = ['dataset1', 'dataset2'];
170
175
  const locus = createLocusWithVisibleDataSets(visibleDataSets);
171
176
  const dataSets = [{name: 'dataset1', url: 'http://dataset-url.com'}];
177
+ const metadata = {
178
+ htMeta: {elementId: {type: 'metadata'}},
179
+ visibleDataSets,
180
+ };
172
181
 
173
182
  await locusInfo.initialSetup({
174
183
  trigger: 'join-response',
175
184
  locus,
176
185
  dataSets,
186
+ metadata,
177
187
  });
178
188
 
179
189
  assert.calledOnceWithExactly(
@@ -183,6 +193,7 @@ describe('plugin-meetings', () => {
183
193
  locus,
184
194
  dataSets,
185
195
  },
196
+ metadata,
186
197
  webexRequest: sinon.match.func,
187
198
  locusInfoUpdateCallback: sinon.match.func,
188
199
  debugId: sinon.match.string,
@@ -220,12 +231,13 @@ describe('plugin-meetings', () => {
220
231
  HashTreeParserStub,
221
232
  sinon.match({
222
233
  initialLocus: {
223
- locus: {self: {visibleDataSets}},
234
+ locus: null,
224
235
  dataSets: [],
225
236
  },
226
237
  webexRequest: sinon.match.func,
227
238
  locusInfoUpdateCallback: sinon.match.func,
228
239
  debugId: sinon.match.string,
240
+ metadata: null,
229
241
  })
230
242
  );
231
243
  assert.calledOnceWithExactly(mockHashTreeParser.initializeFromGetLociResponse, locus);
@@ -249,6 +261,30 @@ describe('plugin-meetings', () => {
249
261
  assert.isTrue(locusInfo.emitChange);
250
262
  });
251
263
 
264
+ it('throws if called with "locus-message" and Metadata object without visibleDataSets', async () => {
265
+ const hashTreeMessage = {
266
+ locusStateElements: [
267
+ {
268
+ htMeta: {elementId: {type: 'Metadata'}},
269
+ data: {},
270
+ },
271
+ ],
272
+ dataSets: [{name: 'dataset1', url: 'test-url'}],
273
+ };
274
+ try {
275
+ await locusInfo.initialSetup({
276
+ trigger: 'locus-message',
277
+ hashTreeMessage,
278
+ });
279
+ assert.fail('should have thrown an error');
280
+ } catch (error) {
281
+ assert.equal(
282
+ error.message,
283
+ 'Metadata object with visibleDataSets is missing in the message'
284
+ );
285
+ }
286
+ });
287
+
252
288
  describe('should setup correct locusInfoUpdateCallback when creating HashTreeParser', () => {
253
289
  const OBJECTS_UPDATED = HashTreeParserModule.LocusInfoUpdateType.OBJECTS_UPDATED;
254
290
  const MEETING_ENDED = HashTreeParserModule.LocusInfoUpdateType.MEETING_ENDED;
@@ -265,8 +301,8 @@ describe('plugin-meetings', () => {
265
301
  hashTreeMessage: {
266
302
  locusStateElements: [
267
303
  {
268
- htMeta: {elementId: {type: 'self'}},
269
- data: {visibleDataSets: ['dataset1']},
304
+ htMeta: {elementId: {type: 'Metadata'}},
305
+ data: {visibleDataSets: [{name: 'dataset1', url: 'test-url'}]},
270
306
  },
271
307
  ],
272
308
  dataSets: [{name: 'dataset1', url: 'test-url'}],
@@ -293,6 +329,16 @@ describe('plugin-meetings', () => {
293
329
  htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
294
330
  },
295
331
  ];
332
+ locusInfo.embeddedApps = [
333
+ {
334
+ id: 'fake-embedded-app-1',
335
+ htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1', version: 1}},
336
+ },
337
+ {
338
+ id: 'fake-embedded-app-2',
339
+ htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2', version: 1}},
340
+ },
341
+ ];
296
342
  locusInfo.meetings = {id: 'fake-meetings'};
297
343
  locusInfo.participants = [
298
344
  {id: 'fake-participant-1', name: 'Participant One'},
@@ -328,6 +374,16 @@ describe('plugin-meetings', () => {
328
374
  htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
329
375
  },
330
376
  ],
377
+ embeddedApps: [
378
+ {
379
+ id: 'fake-embedded-app-1',
380
+ htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1', version: 1}},
381
+ },
382
+ {
383
+ id: 'fake-embedded-app-2',
384
+ htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2', version: 1}},
385
+ },
386
+ ],
331
387
  meetings: {id: 'fake-meetings'},
332
388
  jsSdkMeta: {removedParticipantIds: []},
333
389
  participants: [], // empty means there were no participant updates
@@ -504,6 +560,7 @@ describe('plugin-meetings', () => {
504
560
  self: {id: 'fake-self'},
505
561
  links: {id: 'fake-links'},
506
562
  mediaShares: expectedLocusInfo.mediaShares,
563
+ embeddedApps: expectedLocusInfo.embeddedApps,
507
564
  // and now the new fields
508
565
  ...newLocus,
509
566
  htMeta: newLocusHtMeta,
@@ -536,6 +593,7 @@ describe('plugin-meetings', () => {
536
593
  self: 'new-self',
537
594
  participants: 'new-participants',
538
595
  mediaShares: 'new-mediaShares',
596
+ embeddedApps: 'new-embeddedApps',
539
597
  },
540
598
  },
541
599
  ],
@@ -551,6 +609,7 @@ describe('plugin-meetings', () => {
551
609
  self: {id: 'fake-self'},
552
610
  links: {id: 'fake-links'},
553
611
  mediaShares: expectedLocusInfo.mediaShares,
612
+ embeddedApps: expectedLocusInfo.embeddedApps,
554
613
  participants: [], // empty means there were no participant updates
555
614
  jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
556
615
  ...newLocus,
@@ -586,6 +645,7 @@ describe('plugin-meetings', () => {
586
645
  self: {id: 'fake-self'},
587
646
  links: {id: 'fake-links'},
588
647
  mediaShares: expectedLocusInfo.mediaShares,
648
+ embeddedApps: expectedLocusInfo.embeddedApps,
589
649
  // and now the new fields
590
650
  ...newLocus,
591
651
  htMeta: newLocusHtMeta,
@@ -725,6 +785,39 @@ describe('plugin-meetings', () => {
725
785
  });
726
786
  });
727
787
 
788
+ it('should process locus update correctly when called with updated EMBEDDEDAPP objects', () => {
789
+ const newEmbeddedApp = {
790
+ id: 'new-embedded-app-3',
791
+ htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-3', version: 100}},
792
+ };
793
+ const updatedEmbeddedApp2 = {
794
+ id: 'fake-embedded-app-2',
795
+ someNewProp: 'newValue',
796
+ htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2', version: 100}},
797
+ };
798
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
799
+ // with 1 embedded app added, 1 updated, and 1 removed
800
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
801
+ updatedObjects: [
802
+ {htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1'}}, data: null},
803
+ {
804
+ htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2'}},
805
+ data: updatedEmbeddedApp2,
806
+ },
807
+ {
808
+ htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-3'}},
809
+ data: newEmbeddedApp,
810
+ },
811
+ ],
812
+ });
813
+
814
+ // check onDeltaLocus() was called with correctly updated locus info
815
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
816
+ ...expectedLocusInfo,
817
+ embeddedApps: [updatedEmbeddedApp2, newEmbeddedApp],
818
+ });
819
+ });
820
+
728
821
  it('should process locus update correctly when called with a combination of various updated objects', () => {
729
822
  const newSelf = {
730
823
  id: 'new-self',
@@ -841,6 +934,20 @@ describe('plugin-meetings', () => {
841
934
  MEETING_REMOVED_REASON.SELF_REMOVED
842
935
  );
843
936
  });
937
+
938
+ // this could happen if meeting gets destroyed while we're doing some async hash tree operation like a sync
939
+ it('should handle MEETING_ENDED correctly when meeting is not found in the collection', () => {
940
+ const collectionGetStub = sinon
941
+ .stub(locusInfo.webex.meetings.meetingCollection, 'get')
942
+ .returns(null);
943
+ const destroyStub = sinon.stub(locusInfo.webex.meetings, 'destroy');
944
+
945
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
946
+ locusInfoUpdateCallback(MEETING_ENDED);
947
+
948
+ assert.calledOnceWithExactly(collectionGetStub, locusInfo.meetingId);
949
+ assert.notCalled(destroyStub);
950
+ });
844
951
  });
845
952
  });
846
953
 
@@ -1076,7 +1183,7 @@ describe('plugin-meetings', () => {
1076
1183
  it('should trigger the CONTROLS_POLLING_QA_CHANGED event when necessary', () => {
1077
1184
  locusInfo.controls = {};
1078
1185
  locusInfo.emitScoped = sinon.stub();
1079
- newControls.pollingQAControl = { enabled: true };
1186
+ newControls.pollingQAControl = {enabled: true};
1080
1187
  locusInfo.updateControls(newControls);
1081
1188
 
1082
1189
  assert.calledWith(
@@ -1366,6 +1473,34 @@ describe('plugin-meetings', () => {
1366
1473
  );
1367
1474
  });
1368
1475
 
1476
+ it('should emit CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED when aiSummaryNotification changes', () => {
1477
+ locusInfo.emitScoped = sinon.stub();
1478
+ locusInfo.controls = {
1479
+ transcribe: {
1480
+ transcribing: false,
1481
+ caption: false,
1482
+ aiSummaryNotification: false,
1483
+ },
1484
+ };
1485
+ newControls.transcribe.transcribing = false;
1486
+ newControls.transcribe.caption = false;
1487
+ newControls.transcribe.aiSummaryNotification = true;
1488
+
1489
+ locusInfo.updateControls(newControls);
1490
+
1491
+ assert.calledWith(
1492
+ locusInfo.emitScoped,
1493
+ {
1494
+ file: 'locus-info',
1495
+ function: 'updateControls',
1496
+ },
1497
+ LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
1498
+ {
1499
+ aiSummaryNotification: true,
1500
+ }
1501
+ );
1502
+ });
1503
+
1369
1504
  it('should update the transcribe spoken language', () => {
1370
1505
  locusInfo.emitScoped = sinon.stub();
1371
1506
  locusInfo.controls = {
@@ -1631,7 +1766,6 @@ describe('plugin-meetings', () => {
1631
1766
  );
1632
1767
  });
1633
1768
 
1634
-
1635
1769
  it('should call with participant display name', () => {
1636
1770
  const failureParticipant = [
1637
1771
  {
@@ -1656,7 +1790,7 @@ describe('plugin-meetings', () => {
1656
1790
  displayName: 'Test User',
1657
1791
  }
1658
1792
  );
1659
- })
1793
+ });
1660
1794
  });
1661
1795
 
1662
1796
  describe('#updateSelf', () => {
@@ -2457,8 +2591,8 @@ describe('plugin-meetings', () => {
2457
2591
  {
2458
2592
  isInitializing: !self,
2459
2593
  }
2460
- );
2461
- });
2594
+ );
2595
+ });
2462
2596
 
2463
2597
  const checkMeetingInfoUpdatedCalled = (expected, payload) => {
2464
2598
  const expectedArgs = [
@@ -2923,28 +3057,28 @@ describe('plugin-meetings', () => {
2923
3057
  assert.isFunction(locusParser.onDeltaAction);
2924
3058
  });
2925
3059
 
2926
- it("#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo", () => {
3060
+ it('#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo', () => {
2927
3061
  const callOrder = [];
2928
- sinon.stub(locusInfo, "updateControls");
2929
- sinon.stub(locusInfo, "updateConversationUrl");
2930
- sinon.stub(locusInfo, "updateCreated");
2931
- sinon.stub(locusInfo, "updateFullState");
2932
- sinon.stub(locusInfo, "updateHostInfo");
2933
- sinon.stub(locusInfo, "updateMeetingInfo").callsFake(() => {
2934
- callOrder.push("updateMeetingInfo");
3062
+ sinon.stub(locusInfo, 'updateControls');
3063
+ sinon.stub(locusInfo, 'updateConversationUrl');
3064
+ sinon.stub(locusInfo, 'updateCreated');
3065
+ sinon.stub(locusInfo, 'updateFullState');
3066
+ sinon.stub(locusInfo, 'updateHostInfo');
3067
+ sinon.stub(locusInfo, 'updateMeetingInfo').callsFake(() => {
3068
+ callOrder.push('updateMeetingInfo');
2935
3069
  });
2936
- sinon.stub(locusInfo, "updateMediaShares");
2937
- sinon.stub(locusInfo, "updateReplaces");
2938
- sinon.stub(locusInfo, "updateSelf");
2939
- sinon.stub(locusInfo, "updateLocusUrl").callsFake(() => {
2940
- callOrder.push("updateLocusUrl");
3070
+ sinon.stub(locusInfo, 'updateMediaShares');
3071
+ sinon.stub(locusInfo, 'updateReplaces');
3072
+ sinon.stub(locusInfo, 'updateSelf');
3073
+ sinon.stub(locusInfo, 'updateLocusUrl').callsFake(() => {
3074
+ callOrder.push('updateLocusUrl');
2941
3075
  });
2942
- sinon.stub(locusInfo, "updateAclUrl");
2943
- sinon.stub(locusInfo, "updateBasequence");
2944
- sinon.stub(locusInfo, "updateSequence");
2945
- sinon.stub(locusInfo, "updateEmbeddedApps");
2946
- sinon.stub(locusInfo, "updateLinks");
2947
- sinon.stub(locusInfo, "compareAndUpdate");
3076
+ sinon.stub(locusInfo, 'updateAclUrl');
3077
+ sinon.stub(locusInfo, 'updateBasequence');
3078
+ sinon.stub(locusInfo, 'updateSequence');
3079
+ sinon.stub(locusInfo, 'updateEmbeddedApps');
3080
+ sinon.stub(locusInfo, 'updateLinks');
3081
+ sinon.stub(locusInfo, 'compareAndUpdate');
2948
3082
 
2949
3083
  locusInfo.updateLocusInfo(locus);
2950
3084
 
@@ -3000,7 +3134,7 @@ describe('plugin-meetings', () => {
3000
3134
  it('#updateLocusInfo puts the Locus DTO top level properties at the right place in LocusInfo class', () => {
3001
3135
  // this test verifies that the top-level properties of Locus DTO are copied
3002
3136
  // into LocusInfo class and set as top level properties too
3003
- // this is important, because the code handling Locus hass trees relies on it, see updateFromHashTree()
3137
+ // this is important, because the code handling Locus hash trees relies on it, see updateFromHashTree()
3004
3138
  const info = {id: 'info id'};
3005
3139
  const fullState = {id: 'fullState id'};
3006
3140
  const links = {services: {id: 'service links'}, resources: {id: 'resource links'}};
@@ -3039,7 +3173,7 @@ describe('plugin-meetings', () => {
3039
3173
  sandbox.stub(locusInfo, 'handleOneOnOneEvent');
3040
3174
  sandbox.stub(locusParser, 'isNewFullLocus').returns(true);
3041
3175
 
3042
- locusInfo.onFullLocus(fakeLocus, eventType);
3176
+ locusInfo.onFullLocus('test', fakeLocus, eventType);
3043
3177
 
3044
3178
  assert.equal(fakeLocus, locusParser.workingCopy);
3045
3179
  });
@@ -3060,7 +3194,7 @@ describe('plugin-meetings', () => {
3060
3194
 
3061
3195
  sandbox.stub(locusParser, 'isNewFullLocus').returns(false);
3062
3196
 
3063
- locusInfo.onFullLocus(fakeLocus, eventType);
3197
+ locusInfo.onFullLocus('test', fakeLocus, eventType);
3064
3198
 
3065
3199
  spies.forEach((spy) => {
3066
3200
  assert.notCalled(spy);
@@ -3210,7 +3344,11 @@ describe('plugin-meetings', () => {
3210
3344
  }).then(() => {
3211
3345
  assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldLocusUrl'});
3212
3346
 
3213
- assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
3347
+ assert.calledOnceWithExactly(
3348
+ meeting.locusInfo.onFullLocus,
3349
+ 'classic Locus sync',
3350
+ fakeFullLocusDto
3351
+ );
3214
3352
  assert.calledOnce(locusInfo.locusParser.resume);
3215
3353
  });
3216
3354
  });
@@ -3308,7 +3446,11 @@ describe('plugin-meetings', () => {
3308
3446
  });
3309
3447
 
3310
3448
  assert.notCalled(meeting.locusInfo.handleLocusDelta);
3311
- assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
3449
+ assert.calledOnceWithExactly(
3450
+ meeting.locusInfo.onFullLocus,
3451
+ 'classic Locus sync',
3452
+ fakeFullLocusDto
3453
+ );
3312
3454
  assert.calledOnce(locusInfo.locusParser.resume);
3313
3455
  });
3314
3456
  });
@@ -3484,7 +3626,11 @@ describe('plugin-meetings', () => {
3484
3626
  url: 'fake locus DELTA url',
3485
3627
  });
3486
3628
  assert.notCalled(meeting.locusInfo.handleLocusDelta);
3487
- assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
3629
+ assert.calledOnceWithExactly(
3630
+ meeting.locusInfo.onFullLocus,
3631
+ 'classic Locus sync',
3632
+ fakeFullLocusDto
3633
+ );
3488
3634
  assert.calledOnce(locusInfo.locusParser.resume);
3489
3635
  });
3490
3636
  });
@@ -3856,7 +4002,7 @@ describe('plugin-meetings', () => {
3856
4002
 
3857
4003
  describe('#updateLocusUrl', () => {
3858
4004
  it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is true as default', () => {
3859
- const fakeUrl = "https://fake.com/locus";
4005
+ const fakeUrl = 'https://fake.com/locus';
3860
4006
  locusInfo.emitScoped = sinon.stub();
3861
4007
  locusInfo.updateLocusUrl(fakeUrl);
3862
4008
 
@@ -3869,12 +4015,12 @@ describe('plugin-meetings', () => {
3869
4015
  EVENTS.LOCUS_INFO_UPDATE_URL,
3870
4016
  {
3871
4017
  url: fakeUrl,
3872
- isMainLocus: true
3873
- },
4018
+ isMainLocus: true,
4019
+ }
3874
4020
  );
3875
4021
  });
3876
4022
  it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is false', () => {
3877
- const fakeUrl = "https://fake.com/locus";
4023
+ const fakeUrl = 'https://fake.com/locus';
3878
4024
  locusInfo.emitScoped = sinon.stub();
3879
4025
  locusInfo.updateLocusUrl(fakeUrl, false);
3880
4026
 
@@ -3887,8 +4033,8 @@ describe('plugin-meetings', () => {
3887
4033
  EVENTS.LOCUS_INFO_UPDATE_URL,
3888
4034
  {
3889
4035
  url: fakeUrl,
3890
- isMainLocus: false
3891
- },
4036
+ isMainLocus: false,
4037
+ }
3892
4038
  );
3893
4039
  });
3894
4040
  });
@@ -3940,8 +4086,8 @@ describe('plugin-meetings', () => {
3940
4086
 
3941
4087
  sinon.stub(locusInfo, 'updateParticipants');
3942
4088
  sinon.stub(locusInfo, 'isMeetingActive');
3943
- sinon.stub(locusInfo, 'handleOneOnOneEvent');
3944
- (updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo'));
4089
+ sinon.stub(locusInfo, 'handleOneOnOneEvent');
4090
+ updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo');
3945
4091
  syncRequestStub = sinon.stub().resolves({body: {}});
3946
4092
 
3947
4093
  mockMeeting.locusInfo = locusInfo;
@@ -3950,7 +4096,7 @@ describe('plugin-meetings', () => {
3950
4096
  getLocusDTO: syncRequestStub,
3951
4097
  };
3952
4098
 
3953
- locusInfo.onFullLocus({
4099
+ locusInfo.onFullLocus('test', {
3954
4100
  sequence: {
3955
4101
  rangeStart: 0,
3956
4102
  rangeEnd: 0,
@@ -4213,6 +4359,197 @@ describe('plugin-meetings', () => {
4213
4359
 
4214
4360
  assert.calledOnceWithExactly(mockHashTreeParser.handleMessage, fakeHashTreeMessage);
4215
4361
  });
4362
+
4363
+ it('ignores hash tree event when hashTreeParser is not created yet', () => {
4364
+ const data = {
4365
+ eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
4366
+ stateElementsMessage: {
4367
+ locusStateElements: [],
4368
+ dataSets: [],
4369
+ },
4370
+ };
4371
+
4372
+ const loggerSpy = sinon.spy(LoggerProxy.logger, 'info');
4373
+ const getTheLocusToUpdateStub = sinon.stub(locusInfo, 'getTheLocusToUpdate');
4374
+
4375
+ // Ensure we're not using hash trees
4376
+ assert.isUndefined(locusInfo.hashTreeParser);
4377
+
4378
+ locusInfo.parse(mockMeeting, data);
4379
+
4380
+ assert.calledWith(
4381
+ loggerSpy,
4382
+ 'Locus-info:index#parse --> received locus hash tree event before hashTreeParser is created'
4383
+ );
4384
+ assert.notCalled(getTheLocusToUpdateStub);
4385
+ });
4386
+ });
4387
+ });
4388
+
4389
+ describe('#createLocusFromHashTreeMessage', () => {
4390
+ const LOCUS_URL = 'https://locus.example.com/loci/abc-123';
4391
+
4392
+ const createElement = (type, data) => ({
4393
+ htMeta: {elementId: {type, id: 1, version: 1}},
4394
+ data,
4395
+ });
4396
+
4397
+ it('returns locus with url and empty participants when no locusStateElements', () => {
4398
+ const result = createLocusFromHashTreeMessage({locusUrl: LOCUS_URL});
4399
+
4400
+ assert.deepEqual(result.locus, {participants: [], url: LOCUS_URL});
4401
+ assert.isUndefined(result.metadata);
4402
+ });
4403
+
4404
+ it('skips elements without data', () => {
4405
+ const result = createLocusFromHashTreeMessage({
4406
+ locusUrl: LOCUS_URL,
4407
+ locusStateElements: [{htMeta: {elementId: {type: 'Self', id: 1, version: 1}}, data: null}],
4408
+ });
4409
+
4410
+ assert.deepEqual(result.locus, {participants: [], url: LOCUS_URL});
4411
+ });
4412
+
4413
+ [
4414
+ {type: 'Self', locusKey: 'self', data: {id: 'self-1', state: 'JOINED'}},
4415
+ {type: 'Info', locusKey: 'info', data: {webExMeetingId: '123'}},
4416
+ {type: 'FullState', locusKey: 'fullState', data: {state: 'ACTIVE'}},
4417
+ {type: 'Links', locusKey: 'links', data: {resources: {}}},
4418
+ ].forEach(({type, locusKey, data}) => {
4419
+ it(`maps ${type} element to locus.${locusKey}`, () => {
4420
+ const result = createLocusFromHashTreeMessage({
4421
+ locusUrl: LOCUS_URL,
4422
+ locusStateElements: [createElement(type, data)],
4423
+ });
4424
+
4425
+ assert.deepEqual(result.locus[locusKey], data);
4426
+ });
4427
+ });
4428
+
4429
+ it('pushes Participant elements to locus.participants', () => {
4430
+ const p1 = {id: 'p1', state: 'JOINED'};
4431
+ const p2 = {id: 'p2', state: 'LEFT'};
4432
+
4433
+ const result = createLocusFromHashTreeMessage({
4434
+ locusUrl: LOCUS_URL,
4435
+ locusStateElements: [createElement('Participant', p1), createElement('Participant', p2)],
4436
+ });
4437
+
4438
+ assert.deepEqual(result.locus.participants, [p1, p2]);
4439
+ });
4440
+
4441
+ it('pushes MediaShare elements to locus.mediaShares array', () => {
4442
+ const share1 = {name: 'whiteboard'};
4443
+ const share2 = {name: 'content'};
4444
+
4445
+ const result = createLocusFromHashTreeMessage({
4446
+ locusUrl: LOCUS_URL,
4447
+ locusStateElements: [
4448
+ createElement('MediaShare', share1),
4449
+ createElement('MediaShare', share2),
4450
+ ],
4451
+ });
4452
+
4453
+ assert.deepEqual(result.locus.mediaShares, [share1, share2]);
4454
+ });
4455
+
4456
+ it('pushes EmbeddedApp elements to locus.embeddedApps array', () => {
4457
+ const app = {appId: 'app-1', state: 'STARTED'};
4458
+
4459
+ const result = createLocusFromHashTreeMessage({
4460
+ locusUrl: LOCUS_URL,
4461
+ locusStateElements: [createElement('EmbeddedApp', app)],
4462
+ });
4463
+
4464
+ assert.deepEqual(result.locus.embeddedApps, [app]);
4465
+ });
4466
+
4467
+ it('merges ControlEntry elements into locus.controls', () => {
4468
+ const control1 = {record: {recording: true}};
4469
+ const control2 = {lock: {locked: false}};
4470
+
4471
+ const result = createLocusFromHashTreeMessage({
4472
+ locusUrl: LOCUS_URL,
4473
+ locusStateElements: [
4474
+ createElement('ControlEntry', control1),
4475
+ createElement('ControlEntry', control2),
4476
+ ],
4477
+ });
4478
+
4479
+ assert.deepEqual(result.locus.controls, {record: {recording: true}, lock: {locked: false}});
4480
+ });
4481
+
4482
+ it('spreads Locus element data onto top level but removes managed keys', () => {
4483
+ const locusData = {
4484
+ url: 'should-be-overridden',
4485
+ someTopLevelField: 'value',
4486
+ // these are managed by other ObjectTypes and should be removed
4487
+ links: {should: 'be removed'},
4488
+ info: {should: 'be removed'},
4489
+ fullState: {should: 'be removed'},
4490
+ self: {should: 'be removed'},
4491
+ participants: [{should: 'be removed'}],
4492
+ mediaShares: [{should: 'be removed'}],
4493
+ controls: {should: 'be removed'},
4494
+ embeddedApps: [{should: 'be removed'}],
4495
+ };
4496
+
4497
+ const result = createLocusFromHashTreeMessage({
4498
+ locusUrl: LOCUS_URL,
4499
+ locusStateElements: [createElement('Locus', locusData)],
4500
+ });
4501
+
4502
+ assert.equal(result.locus.someTopLevelField, 'value');
4503
+ assert.deepEqual(result.locus.participants, []);
4504
+ assert.isUndefined(result.locus.links);
4505
+ assert.isUndefined(result.locus.info);
4506
+ assert.isUndefined(result.locus.fullState);
4507
+ assert.isUndefined(result.locus.self);
4508
+ assert.isUndefined(result.locus.mediaShares);
4509
+ assert.isUndefined(result.locus.controls);
4510
+ assert.isUndefined(result.locus.embeddedApps);
4511
+ });
4512
+
4513
+ it('extracts Metadata element as metadata in the result', () => {
4514
+ const metadataData = {visibleDataSets: [{name: 'ds1', url: 'http://ds1.url'}]};
4515
+ const htMeta = {elementId: {type: 'Metadata', id: 99, version: 3}};
4516
+
4517
+ const result = createLocusFromHashTreeMessage({
4518
+ locusUrl: LOCUS_URL,
4519
+ locusStateElements: [{htMeta, data: metadataData}],
4520
+ });
4521
+
4522
+ assert.deepEqual(result.metadata, {...metadataData, htMeta});
4523
+ assert.isUndefined(result.locus.metadata);
4524
+ });
4525
+
4526
+ it('handles a message with multiple element types', () => {
4527
+ const selfData = {id: 'self-1'};
4528
+ const participantData = {id: 'p1'};
4529
+ const infoData = {webExMeetingId: '456'};
4530
+
4531
+ const result = createLocusFromHashTreeMessage({
4532
+ locusUrl: LOCUS_URL,
4533
+ locusStateElements: [
4534
+ createElement('Self', selfData),
4535
+ createElement('Participant', participantData),
4536
+ createElement('Info', infoData),
4537
+ ],
4538
+ });
4539
+
4540
+ assert.deepEqual(result.locus.self, selfData);
4541
+ assert.deepEqual(result.locus.participants, [participantData]);
4542
+ assert.deepEqual(result.locus.info, infoData);
4543
+ assert.equal(result.locus.url, LOCUS_URL);
4544
+ });
4545
+
4546
+ it('ignores unknown element types', () => {
4547
+ const result = createLocusFromHashTreeMessage({
4548
+ locusUrl: LOCUS_URL,
4549
+ locusStateElements: [createElement('UnknownType', {foo: 'bar'})],
4550
+ });
4551
+
4552
+ assert.deepEqual(result.locus, {participants: [], url: LOCUS_URL});
4216
4553
  });
4217
4554
  });
4218
4555
  });