@webex/plugin-meetings 3.8.1 → 3.9.0-webinar5k.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +26 -13
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.js +16 -3
  5. package/dist/constants.js.map +1 -1
  6. package/dist/controls-options-manager/enums.js +1 -0
  7. package/dist/controls-options-manager/enums.js.map +1 -1
  8. package/dist/controls-options-manager/types.js.map +1 -1
  9. package/dist/controls-options-manager/util.js +26 -0
  10. package/dist/controls-options-manager/util.js.map +1 -1
  11. package/dist/hashTree/constants.js +23 -0
  12. package/dist/hashTree/constants.js.map +1 -0
  13. package/dist/hashTree/hashTree.js +516 -0
  14. package/dist/hashTree/hashTree.js.map +1 -0
  15. package/dist/hashTree/hashTreeParser.js +521 -0
  16. package/dist/hashTree/hashTreeParser.js.map +1 -0
  17. package/dist/interpretation/index.js +1 -1
  18. package/dist/interpretation/siLanguage.js +1 -1
  19. package/dist/locus-info/controlsUtils.js +11 -3
  20. package/dist/locus-info/controlsUtils.js.map +1 -1
  21. package/dist/locus-info/index.js +331 -59
  22. package/dist/locus-info/index.js.map +1 -1
  23. package/dist/media/index.js +2 -2
  24. package/dist/media/index.js.map +1 -1
  25. package/dist/meeting/brbState.js +17 -14
  26. package/dist/meeting/brbState.js.map +1 -1
  27. package/dist/meeting/in-meeting-actions.js +5 -1
  28. package/dist/meeting/in-meeting-actions.js.map +1 -1
  29. package/dist/meeting/index.js +264 -125
  30. package/dist/meeting/index.js.map +1 -1
  31. package/dist/meeting/muteState.js +2 -5
  32. package/dist/meeting/muteState.js.map +1 -1
  33. package/dist/meeting/request.js +19 -0
  34. package/dist/meeting/request.js.map +1 -1
  35. package/dist/meeting/request.type.js.map +1 -1
  36. package/dist/meeting/util.js +8 -11
  37. package/dist/meeting/util.js.map +1 -1
  38. package/dist/meetings/index.js +6 -2
  39. package/dist/meetings/index.js.map +1 -1
  40. package/dist/member/types.js.map +1 -1
  41. package/dist/members/collection.js +13 -0
  42. package/dist/members/collection.js.map +1 -1
  43. package/dist/members/index.js +44 -23
  44. package/dist/members/index.js.map +1 -1
  45. package/dist/members/request.js +3 -3
  46. package/dist/members/request.js.map +1 -1
  47. package/dist/members/util.js +18 -6
  48. package/dist/members/util.js.map +1 -1
  49. package/dist/metrics/constants.js +1 -0
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/multistream/sendSlotManager.js +32 -2
  52. package/dist/multistream/sendSlotManager.js.map +1 -1
  53. package/dist/reachability/index.js +5 -10
  54. package/dist/reachability/index.js.map +1 -1
  55. package/dist/types/constants.d.ts +12 -0
  56. package/dist/types/controls-options-manager/enums.d.ts +2 -1
  57. package/dist/types/controls-options-manager/types.d.ts +4 -1
  58. package/dist/types/hashTree/constants.d.ts +8 -0
  59. package/dist/types/hashTree/hashTree.d.ts +128 -0
  60. package/dist/types/hashTree/hashTreeParser.d.ts +152 -0
  61. package/dist/types/locus-info/index.d.ts +93 -3
  62. package/dist/types/meeting/brbState.d.ts +0 -1
  63. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  64. package/dist/types/meeting/index.d.ts +36 -3
  65. package/dist/types/meeting/request.d.ts +9 -1
  66. package/dist/types/meeting/request.type.d.ts +74 -0
  67. package/dist/types/meeting/util.d.ts +3 -3
  68. package/dist/types/member/types.d.ts +1 -0
  69. package/dist/types/members/collection.d.ts +6 -0
  70. package/dist/types/members/index.d.ts +15 -3
  71. package/dist/types/members/request.d.ts +1 -1
  72. package/dist/types/members/util.d.ts +5 -2
  73. package/dist/types/metrics/constants.d.ts +1 -0
  74. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  75. package/dist/types/reachability/index.d.ts +2 -2
  76. package/dist/webinar/index.js +1 -1
  77. package/package.json +26 -25
  78. package/src/constants.ts +16 -0
  79. package/src/controls-options-manager/enums.ts +1 -0
  80. package/src/controls-options-manager/types.ts +6 -1
  81. package/src/controls-options-manager/util.ts +31 -0
  82. package/src/hashTree/constants.ts +12 -0
  83. package/src/hashTree/hashTree.ts +460 -0
  84. package/src/hashTree/hashTreeParser.ts +556 -0
  85. package/src/locus-info/controlsUtils.ts +15 -0
  86. package/src/locus-info/index.ts +434 -58
  87. package/src/media/index.ts +2 -2
  88. package/src/meeting/brbState.ts +13 -9
  89. package/src/meeting/in-meeting-actions.ts +8 -0
  90. package/src/meeting/index.ts +193 -39
  91. package/src/meeting/muteState.ts +2 -6
  92. package/src/meeting/request.ts +16 -0
  93. package/src/meeting/request.type.ts +64 -0
  94. package/src/meeting/util.ts +17 -20
  95. package/src/meetings/index.ts +17 -3
  96. package/src/member/types.ts +1 -0
  97. package/src/members/collection.ts +11 -0
  98. package/src/members/index.ts +33 -7
  99. package/src/members/request.ts +2 -2
  100. package/src/members/util.ts +14 -3
  101. package/src/metrics/constants.ts +1 -0
  102. package/src/multistream/sendSlotManager.ts +34 -2
  103. package/src/reachability/index.ts +5 -13
  104. package/test/unit/spec/controls-options-manager/util.js +58 -0
  105. package/test/unit/spec/hashTree/hashTree.ts +394 -0
  106. package/test/unit/spec/hashTree/hashTreeParser.ts +156 -0
  107. package/test/unit/spec/locus-info/controlsUtils.js +52 -0
  108. package/test/unit/spec/locus-info/index.js +547 -54
  109. package/test/unit/spec/media/index.ts +107 -0
  110. package/test/unit/spec/meeting/brbState.ts +23 -4
  111. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  112. package/test/unit/spec/meeting/index.js +647 -46
  113. package/test/unit/spec/meeting/request.js +71 -0
  114. package/test/unit/spec/members/index.js +33 -10
  115. package/test/unit/spec/members/request.js +2 -2
  116. package/test/unit/spec/members/utils.js +27 -7
  117. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  118. package/test/unit/spec/reachability/index.ts +2 -6
  119. package/dist/annotation/annotation.types.d.ts +0 -42
  120. package/dist/annotation/constants.d.ts +0 -31
  121. package/dist/annotation/index.d.ts +0 -117
  122. package/dist/breakouts/breakout.d.ts +0 -8
  123. package/dist/breakouts/collection.d.ts +0 -5
  124. package/dist/breakouts/edit-lock-error.d.ts +0 -15
  125. package/dist/breakouts/events.d.ts +0 -8
  126. package/dist/breakouts/index.d.ts +0 -5
  127. package/dist/breakouts/request.d.ts +0 -22
  128. package/dist/breakouts/utils.d.ts +0 -15
  129. package/dist/common/browser-detection.d.ts +0 -9
  130. package/dist/common/collection.d.ts +0 -48
  131. package/dist/common/config.d.ts +0 -2
  132. package/dist/common/errors/captcha-error.d.ts +0 -15
  133. package/dist/common/errors/intent-to-join.d.ts +0 -16
  134. package/dist/common/errors/join-meeting.d.ts +0 -17
  135. package/dist/common/errors/media.d.ts +0 -15
  136. package/dist/common/errors/no-meeting-info.d.ts +0 -14
  137. package/dist/common/errors/parameter.d.ts +0 -15
  138. package/dist/common/errors/password-error.d.ts +0 -15
  139. package/dist/common/errors/permission.d.ts +0 -14
  140. package/dist/common/errors/reclaim-host-role-error.d.ts +0 -60
  141. package/dist/common/errors/reclaim-host-role-error.js +0 -158
  142. package/dist/common/errors/reclaim-host-role-error.js.map +0 -1
  143. package/dist/common/errors/reclaim-host-role-errors.d.ts +0 -60
  144. package/dist/common/errors/reconnection-in-progress.d.ts +0 -9
  145. package/dist/common/errors/reconnection-in-progress.js +0 -35
  146. package/dist/common/errors/reconnection-in-progress.js.map +0 -1
  147. package/dist/common/errors/reconnection.d.ts +0 -15
  148. package/dist/common/errors/stats.d.ts +0 -15
  149. package/dist/common/errors/webex-errors.d.ts +0 -81
  150. package/dist/common/errors/webex-meetings-error.d.ts +0 -20
  151. package/dist/common/events/events-scope.d.ts +0 -17
  152. package/dist/common/events/events.d.ts +0 -12
  153. package/dist/common/events/trigger-proxy.d.ts +0 -2
  154. package/dist/common/events/util.d.ts +0 -2
  155. package/dist/common/logs/logger-config.d.ts +0 -2
  156. package/dist/common/logs/logger-proxy.d.ts +0 -2
  157. package/dist/common/logs/request.d.ts +0 -34
  158. package/dist/common/queue.d.ts +0 -32
  159. package/dist/config.d.ts +0 -73
  160. package/dist/constants.d.ts +0 -952
  161. package/dist/controls-options-manager/constants.d.ts +0 -4
  162. package/dist/controls-options-manager/enums.d.ts +0 -5
  163. package/dist/controls-options-manager/index.d.ts +0 -120
  164. package/dist/controls-options-manager/types.d.ts +0 -43
  165. package/dist/controls-options-manager/util.d.ts +0 -7
  166. package/dist/index.d.ts +0 -4
  167. package/dist/interceptors/index.d.ts +0 -2
  168. package/dist/interceptors/locusRetry.d.ts +0 -27
  169. package/dist/interpretation/collection.d.ts +0 -5
  170. package/dist/interpretation/index.d.ts +0 -5
  171. package/dist/interpretation/siLanguage.d.ts +0 -5
  172. package/dist/locus-info/controlsUtils.d.ts +0 -2
  173. package/dist/locus-info/embeddedAppsUtils.d.ts +0 -2
  174. package/dist/locus-info/fullState.d.ts +0 -2
  175. package/dist/locus-info/hostUtils.d.ts +0 -2
  176. package/dist/locus-info/index.d.ts +0 -269
  177. package/dist/locus-info/infoUtils.d.ts +0 -2
  178. package/dist/locus-info/mediaSharesUtils.d.ts +0 -2
  179. package/dist/locus-info/parser.d.ts +0 -212
  180. package/dist/locus-info/selfUtils.d.ts +0 -2
  181. package/dist/media/index.d.ts +0 -32
  182. package/dist/media/properties.d.ts +0 -108
  183. package/dist/media/util.d.ts +0 -2
  184. package/dist/mediaQualityMetrics/config.d.ts +0 -233
  185. package/dist/mediaQualityMetrics/config.js +0 -513
  186. package/dist/mediaQualityMetrics/config.js.map +0 -1
  187. package/dist/meeting/effectsState.d.ts +0 -42
  188. package/dist/meeting/effectsState.js +0 -260
  189. package/dist/meeting/effectsState.js.map +0 -1
  190. package/dist/meeting/in-meeting-actions.d.ts +0 -79
  191. package/dist/meeting/index.d.ts +0 -1622
  192. package/dist/meeting/locusMediaRequest.d.ts +0 -74
  193. package/dist/meeting/muteState.d.ts +0 -116
  194. package/dist/meeting/request.d.ts +0 -257
  195. package/dist/meeting/request.type.d.ts +0 -11
  196. package/dist/meeting/state.d.ts +0 -9
  197. package/dist/meeting/util.d.ts +0 -2
  198. package/dist/meeting/voicea-meeting.d.ts +0 -16
  199. package/dist/meeting-info/collection.d.ts +0 -20
  200. package/dist/meeting-info/index.d.ts +0 -57
  201. package/dist/meeting-info/meeting-info-v2.d.ts +0 -93
  202. package/dist/meeting-info/request.d.ts +0 -22
  203. package/dist/meeting-info/util.d.ts +0 -2
  204. package/dist/meeting-info/utilv2.d.ts +0 -2
  205. package/dist/meetings/collection.d.ts +0 -23
  206. package/dist/meetings/index.d.ts +0 -296
  207. package/dist/meetings/meetings.types.d.ts +0 -4
  208. package/dist/meetings/request.d.ts +0 -27
  209. package/dist/meetings/util.d.ts +0 -18
  210. package/dist/member/index.d.ts +0 -148
  211. package/dist/member/member.types.d.ts +0 -11
  212. package/dist/member/member.types.js +0 -18
  213. package/dist/member/member.types.js.map +0 -1
  214. package/dist/member/types.d.ts +0 -32
  215. package/dist/member/util.d.ts +0 -2
  216. package/dist/members/collection.d.ts +0 -24
  217. package/dist/members/index.d.ts +0 -308
  218. package/dist/members/request.d.ts +0 -58
  219. package/dist/members/types.d.ts +0 -25
  220. package/dist/members/util.d.ts +0 -2
  221. package/dist/metrics/config.d.ts +0 -169
  222. package/dist/metrics/config.js +0 -289
  223. package/dist/metrics/config.js.map +0 -1
  224. package/dist/metrics/constants.d.ts +0 -59
  225. package/dist/metrics/index.d.ts +0 -152
  226. package/dist/multistream/mediaRequestManager.d.ts +0 -119
  227. package/dist/multistream/receiveSlot.d.ts +0 -68
  228. package/dist/multistream/receiveSlotManager.d.ts +0 -56
  229. package/dist/multistream/remoteMedia.d.ts +0 -72
  230. package/dist/multistream/remoteMediaGroup.d.ts +0 -49
  231. package/dist/multistream/remoteMediaManager.d.ts +0 -300
  232. package/dist/multistream/sendSlotManager.d.ts +0 -69
  233. package/dist/networkQualityMonitor/index.d.ts +0 -70
  234. package/dist/networkQualityMonitor/index.js +0 -226
  235. package/dist/networkQualityMonitor/index.js.map +0 -1
  236. package/dist/peer-connection-manager/index.d.ts +0 -6
  237. package/dist/peer-connection-manager/index.js +0 -671
  238. package/dist/peer-connection-manager/index.js.map +0 -1
  239. package/dist/peer-connection-manager/util.d.ts +0 -6
  240. package/dist/peer-connection-manager/util.js +0 -110
  241. package/dist/peer-connection-manager/util.js.map +0 -1
  242. package/dist/personal-meeting-room/index.d.ts +0 -47
  243. package/dist/personal-meeting-room/request.d.ts +0 -14
  244. package/dist/personal-meeting-room/util.d.ts +0 -2
  245. package/dist/reachability/clusterReachability.d.ts +0 -109
  246. package/dist/reachability/index.d.ts +0 -139
  247. package/dist/reachability/request.d.ts +0 -35
  248. package/dist/reachability/util.d.ts +0 -8
  249. package/dist/reactions/constants.d.ts +0 -3
  250. package/dist/reactions/reactions.d.ts +0 -4
  251. package/dist/reactions/reactions.type.d.ts +0 -32
  252. package/dist/reconnection-manager/index.d.ts +0 -112
  253. package/dist/recording-controller/enums.d.ts +0 -7
  254. package/dist/recording-controller/index.d.ts +0 -193
  255. package/dist/recording-controller/util.d.ts +0 -13
  256. package/dist/roap/collection.d.ts +0 -10
  257. package/dist/roap/collection.js +0 -63
  258. package/dist/roap/collection.js.map +0 -1
  259. package/dist/roap/handler.d.ts +0 -47
  260. package/dist/roap/handler.js +0 -279
  261. package/dist/roap/handler.js.map +0 -1
  262. package/dist/roap/index.d.ts +0 -116
  263. package/dist/roap/request.d.ts +0 -35
  264. package/dist/roap/state.d.ts +0 -9
  265. package/dist/roap/state.js +0 -127
  266. package/dist/roap/state.js.map +0 -1
  267. package/dist/roap/turnDiscovery.d.ts +0 -81
  268. package/dist/roap/util.d.ts +0 -2
  269. package/dist/roap/util.js +0 -76
  270. package/dist/roap/util.js.map +0 -1
  271. package/dist/rtcMetrics/constants.d.ts +0 -4
  272. package/dist/rtcMetrics/constants.js +0 -11
  273. package/dist/rtcMetrics/constants.js.map +0 -1
  274. package/dist/rtcMetrics/index.d.ts +0 -61
  275. package/dist/rtcMetrics/index.js +0 -197
  276. package/dist/rtcMetrics/index.js.map +0 -1
  277. package/dist/statsAnalyzer/global.d.ts +0 -118
  278. package/dist/statsAnalyzer/global.js +0 -127
  279. package/dist/statsAnalyzer/global.js.map +0 -1
  280. package/dist/statsAnalyzer/index.d.ts +0 -193
  281. package/dist/statsAnalyzer/index.js +0 -1019
  282. package/dist/statsAnalyzer/index.js.map +0 -1
  283. package/dist/statsAnalyzer/mqaUtil.d.ts +0 -22
  284. package/dist/statsAnalyzer/mqaUtil.js +0 -181
  285. package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
  286. package/dist/transcription/index.d.ts +0 -64
  287. package/dist/types/common/errors/reconnection-in-progress.d.ts +0 -9
  288. package/dist/types/mediaQualityMetrics/config.d.ts +0 -241
  289. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  290. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  291. package/dist/types/rtcMetrics/index.d.ts +0 -71
  292. package/dist/types/statsAnalyzer/global.d.ts +0 -36
  293. package/dist/types/statsAnalyzer/index.d.ts +0 -217
  294. package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
  295. package/dist/webinar/collection.d.ts +0 -16
  296. package/dist/webinar/index.d.ts +0 -5
@@ -306,6 +306,20 @@ describe('plugin-meetings', () => {
306
306
  );
307
307
  });
308
308
 
309
+ it('should trigger the CONTROLS_POLLING_QA_CHANGED event when necessary', () => {
310
+ locusInfo.controls = {};
311
+ locusInfo.emitScoped = sinon.stub();
312
+ newControls.pollingQAControl = { enabled: true };
313
+ locusInfo.updateControls(newControls);
314
+
315
+ assert.calledWith(
316
+ locusInfo.emitScoped,
317
+ {file: 'locus-info', function: 'updateControls'},
318
+ LOCUSINFO.EVENTS.CONTROLS_POLLING_QA_CHANGED,
319
+ {state: newControls.pollingQAControl}
320
+ );
321
+ });
322
+
309
323
  it('should keep the recording state to `IDLE`', () => {
310
324
  locusInfo.controls = {
311
325
  record: {
@@ -557,6 +571,34 @@ describe('plugin-meetings', () => {
557
571
  );
558
572
  });
559
573
 
574
+ it('should update the transcribe spoken language', () => {
575
+ locusInfo.emitScoped = sinon.stub();
576
+ locusInfo.controls = {
577
+ transcribe: {
578
+ transcribing: false,
579
+ caption: true,
580
+ spokenLanguage: 'en-US',
581
+ },
582
+ };
583
+ newControls.transcribe.transcribing = false;
584
+ newControls.transcribe.caption = true;
585
+ newControls.transcribe.spokenLanguage = 'fr';
586
+
587
+ locusInfo.updateControls(newControls);
588
+
589
+ assert.calledWith(
590
+ locusInfo.emitScoped,
591
+ {
592
+ file: 'locus-info',
593
+ function: 'updateControls',
594
+ },
595
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
596
+ {
597
+ spokenLanguage: 'fr',
598
+ }
599
+ );
600
+ });
601
+
560
602
  it('should update the manual caption state', () => {
561
603
  locusInfo.emitScoped = sinon.stub();
562
604
  locusInfo.controls = {
@@ -730,7 +772,7 @@ describe('plugin-meetings', () => {
730
772
  },
731
773
  };
732
774
  locusInfo.emitScoped = sinon.stub();
733
- locusInfo.updateParticipants({});
775
+ locusInfo.updateParticipants({}, []);
734
776
 
735
777
  // if this assertion fails, double-check the attributes used in
736
778
  // the updateParticipants function in locus-info/index.js
@@ -748,6 +790,7 @@ describe('plugin-meetings', () => {
748
790
  selfId: '2',
749
791
  hostId: '3',
750
792
  isReplace: undefined,
793
+ removedParticipantIds: [],
751
794
  }
752
795
  );
753
796
  // note: in a real use case, recordingId, selfId, and hostId would all be the same
@@ -772,7 +815,7 @@ describe('plugin-meetings', () => {
772
815
  };
773
816
 
774
817
  locusInfo.emitScoped = sinon.stub();
775
- locusInfo.updateParticipants({}, true);
818
+ locusInfo.updateParticipants({}, [], true);
776
819
 
777
820
  assert.calledWith(
778
821
  locusInfo.emitScoped,
@@ -788,6 +831,7 @@ describe('plugin-meetings', () => {
788
831
  selfId: '2',
789
832
  hostId: '3',
790
833
  isReplace: true,
834
+ removedParticipantIds: [],
791
835
  }
792
836
  );
793
837
  });
@@ -838,7 +882,7 @@ describe('plugin-meetings', () => {
838
882
  ];
839
883
 
840
884
  locusInfo.emitScoped = sinon.stub();
841
- locusInfo.updateParticipants(failureParticipant);
885
+ locusInfo.updateParticipants(failureParticipant, []);
842
886
  assert.calledWith(
843
887
  locusInfo.emitScoped,
844
888
  {
@@ -2019,7 +2063,7 @@ describe('plugin-meetings', () => {
2019
2063
 
2020
2064
  fakeLocus = {
2021
2065
  meeting: true,
2022
- participants: true,
2066
+ participants: [],
2023
2067
  url: 'newLocusUrl',
2024
2068
  syncUrl: 'newSyncUrl',
2025
2069
  };
@@ -2066,6 +2110,38 @@ describe('plugin-meetings', () => {
2066
2110
  assert.isFunction(locusParser.onDeltaAction);
2067
2111
  });
2068
2112
 
2113
+ it("#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo", () => {
2114
+ const callOrder = [];
2115
+ sinon.stub(locusInfo, "updateControls");
2116
+ sinon.stub(locusInfo, "updateConversationUrl");
2117
+ sinon.stub(locusInfo, "updateCreated");
2118
+ sinon.stub(locusInfo, "updateFullState");
2119
+ sinon.stub(locusInfo, "updateHostInfo");
2120
+ sinon.stub(locusInfo, "updateMeetingInfo").callsFake(() => {
2121
+ callOrder.push("updateMeetingInfo");
2122
+ });
2123
+ sinon.stub(locusInfo, "updateMediaShares");
2124
+ sinon.stub(locusInfo, "updateParticipantsUrl");
2125
+ sinon.stub(locusInfo, "updateReplace");
2126
+ sinon.stub(locusInfo, "updateSelf");
2127
+ sinon.stub(locusInfo, "updateLocusUrl").callsFake(() => {
2128
+ callOrder.push("updateLocusUrl");
2129
+ });
2130
+ sinon.stub(locusInfo, "updateAclUrl");
2131
+ sinon.stub(locusInfo, "updateBasequence");
2132
+ sinon.stub(locusInfo, "updateSequence");
2133
+ sinon.stub(locusInfo, "updateMemberShip");
2134
+ sinon.stub(locusInfo, "updateIdentifiers");
2135
+ sinon.stub(locusInfo, "updateEmbeddedApps");
2136
+ sinon.stub(locusInfo, "updateResources");
2137
+ sinon.stub(locusInfo, "compareAndUpdate");
2138
+
2139
+ locusInfo.updateLocusInfo(locus);
2140
+
2141
+ // Ensure updateLocusUrl is called before updateMeetingInfo if both are called
2142
+ assert.deepEqual(callOrder, ['updateLocusUrl', 'updateMeetingInfo']);
2143
+ });
2144
+
2069
2145
  it('#updateLocusInfo ignores breakout LEFT message', () => {
2070
2146
  const newLocus = {
2071
2147
  self: {
@@ -2117,6 +2193,8 @@ describe('plugin-meetings', () => {
2117
2193
  assert.notCalled(locusInfo.compareAndUpdate);
2118
2194
  });
2119
2195
 
2196
+
2197
+
2120
2198
  it('onFullLocus() updates the working-copy of locus parser', () => {
2121
2199
  const eventType = 'fakeEvent';
2122
2200
 
@@ -2215,7 +2293,7 @@ describe('plugin-meetings', () => {
2215
2293
 
2216
2294
  it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl', () => {
2217
2295
  const {DESYNC} = LocusDeltaParser.loci;
2218
- const fakeDeltaLocus = {id: 'fake delta locus'};
2296
+ const fakeDeltaLocus = {baseSequence: {}, id: 'fake delta locus'};
2219
2297
  const meeting = {
2220
2298
  meetingRequest: {
2221
2299
  getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
@@ -2350,25 +2428,22 @@ describe('plugin-meetings', () => {
2350
2428
  };
2351
2429
  });
2352
2430
 
2353
- it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', () => {
2431
+ it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', async () => {
2354
2432
  meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
2355
2433
 
2356
2434
  locusInfo.locusParser.workingCopy = {}; // no syncUrl
2357
2435
 
2358
- // Since we have a promise inside a function we want to test that's not returned,
2359
- // we will wait and stub it's last function to resolve this waiting promise.
2360
- return new Promise((resolve) => {
2361
- webex.meetings.destroy.callsFake(() => resolve());
2362
- locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2363
- }).then(() => {
2364
- assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
2436
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2365
2437
 
2366
- assert.notCalled(meeting.locusInfo.handleLocusDelta);
2367
- assert.notCalled(meeting.locusInfo.onFullLocus);
2368
- assert.notCalled(locusInfo.locusParser.resume);
2438
+ await testUtils.flushPromises();
2369
2439
 
2370
- assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2371
- });
2440
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
2441
+
2442
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2443
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2444
+ assert.notCalled(locusInfo.locusParser.resume);
2445
+
2446
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2372
2447
  });
2373
2448
 
2374
2449
  it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails, does a full locus sync', () => {
@@ -2405,44 +2480,67 @@ describe('plugin-meetings', () => {
2405
2480
  });
2406
2481
  });
2407
2482
 
2408
- it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', () => {
2483
+ it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails with 403, it does not do a full locus sync', async () => {
2484
+ const fake403Error = new Error('fake error');
2485
+ fake403Error.statusCode = 403;
2486
+
2487
+ meeting.meetingRequest.getLocusDTO.onCall(0).rejects(fake403Error);
2488
+
2489
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2490
+
2491
+ await testUtils.flushPromises();
2492
+
2493
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'deltaSyncUrl'});
2494
+
2495
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
2496
+ correlationId: meeting.correlationId,
2497
+ url: 'deltaSyncUrl',
2498
+ reason: 'fake error',
2499
+ errorName: 'Error',
2500
+ stack: sinon.match.any,
2501
+ code: sinon.match.any,
2502
+ });
2503
+
2504
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2505
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2506
+ assert.notCalled(locusInfo.locusParser.resume);
2507
+ });
2508
+
2509
+ it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', async () => {
2409
2510
  meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
2410
2511
 
2411
- // Since we have a promise inside a function we want to test that's not returned,
2412
- // we will wait and stub it's last function to resolve this waiting promise.
2413
- return new Promise((resolve) => {
2414
- webex.meetings.destroy.callsFake(() => resolve());
2415
- locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2416
- }).then(() => {
2417
- assert.calledTwice(meeting.meetingRequest.getLocusDTO);
2512
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2418
2513
 
2419
- assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [
2420
- {url: 'deltaSyncUrl'},
2421
- ]);
2422
- assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
2423
- {url: 'fullSyncUrl'},
2424
- ]);
2514
+ await testUtils.flushPromises();
2425
2515
 
2426
- assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
2427
- correlationId: meeting.correlationId,
2428
- url: 'deltaSyncUrl',
2429
- reason: 'fake error',
2430
- errorName: 'Error',
2431
- stack: sinon.match.any,
2432
- code: sinon.match.any,
2433
- });
2516
+ assert.calledTwice(meeting.meetingRequest.getLocusDTO);
2434
2517
 
2435
- assert.notCalled(meeting.locusInfo.handleLocusDelta);
2436
- assert.notCalled(meeting.locusInfo.onFullLocus);
2437
- assert.notCalled(locusInfo.locusParser.resume);
2518
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [
2519
+ {url: 'deltaSyncUrl'},
2520
+ ]);
2521
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
2522
+ {url: 'fullSyncUrl'},
2523
+ ]);
2438
2524
 
2439
- assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2525
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
2526
+ correlationId: meeting.correlationId,
2527
+ url: 'deltaSyncUrl',
2528
+ reason: 'fake error',
2529
+ errorName: 'Error',
2530
+ stack: sinon.match.any,
2531
+ code: sinon.match.any,
2440
2532
  });
2533
+
2534
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2535
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2536
+ assert.notCalled(locusInfo.locusParser.resume);
2537
+
2538
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2441
2539
  });
2442
2540
  });
2443
2541
 
2444
2542
  it('onDeltaLocus handle delta data', () => {
2445
- fakeLocus.participants = {};
2543
+ fakeLocus.participants = [];
2446
2544
  const fakeBreakout = {
2447
2545
  sessionId: 'sessionId',
2448
2546
  groupId: 'groupId',
@@ -2459,17 +2557,15 @@ describe('plugin-meetings', () => {
2459
2557
  };
2460
2558
  locusInfo.updateParticipants = sinon.stub();
2461
2559
  locusInfo.onDeltaLocus(fakeLocus);
2462
- assert.calledWith(locusInfo.updateParticipants, {}, false);
2560
+ assert.calledWith(locusInfo.updateParticipants, [], undefined, false);
2463
2561
 
2464
2562
  fakeLocus.controls.breakout.sessionId = 'sessionId2';
2465
2563
  locusInfo.onDeltaLocus(fakeLocus);
2466
- assert.calledWith(locusInfo.updateParticipants, {}, true);
2564
+ assert.calledWith(locusInfo.updateParticipants, [], undefined, true);
2467
2565
  });
2468
2566
 
2469
2567
  it('onDeltaLocus merges delta participants with existing participants', () => {
2470
- const FAKE_DELTA_PARTICIPANTS = [
2471
- {id: '1111'}, {id: '2222'}
2472
- ]
2568
+ const FAKE_DELTA_PARTICIPANTS = [{id: '1111'}, {id: '2222'}];
2473
2569
  fakeLocus.participants = FAKE_DELTA_PARTICIPANTS;
2474
2570
 
2475
2571
  sinon.spy(locusInfo, 'mergeParticipants');
@@ -2477,8 +2573,86 @@ describe('plugin-meetings', () => {
2477
2573
  const existingParticipants = locusInfo.participants;
2478
2574
 
2479
2575
  locusInfo.onDeltaLocus(fakeLocus);
2480
- assert.calledOnceWithExactly(locusInfo.mergeParticipants, existingParticipants, FAKE_DELTA_PARTICIPANTS);
2481
- assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, false);
2576
+ assert.calledOnceWithExactly(
2577
+ locusInfo.mergeParticipants,
2578
+ existingParticipants,
2579
+ FAKE_DELTA_PARTICIPANTS
2580
+ );
2581
+ assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, undefined, false);
2582
+ });
2583
+
2584
+ [true, false].forEach((isDelta) =>
2585
+ it(`applyLocusDeltaData - handles empty ${
2586
+ isDelta ? 'delta' : 'full'
2587
+ } DTO in response`, async () => {
2588
+ const {DESYNC} = LocusDeltaParser.loci;
2589
+ const fakeFullLocusDto = {};
2590
+ const meeting = {
2591
+ meetingRequest: {
2592
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
2593
+ },
2594
+ locusInfo: {
2595
+ onFullLocus: sandbox.stub(),
2596
+ handleLocusDelta: sandbox.stub(),
2597
+ },
2598
+ locusUrl: 'fake locus FULL url',
2599
+ };
2600
+
2601
+ sinon.stub(locusInfo.locusParser, 'resume').resolves();
2602
+
2603
+ if (isDelta) {
2604
+ locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
2605
+ } else {
2606
+ locusInfo.locusParser.workingCopy = {}; // no syncUrl (to trigger FULL DTO request)
2607
+ }
2608
+
2609
+ await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2610
+
2611
+ await testUtils.flushPromises();
2612
+
2613
+ if (isDelta) {
2614
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
2615
+ url: 'fake locus DELTA url',
2616
+ });
2617
+ } else {
2618
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
2619
+ url: 'fake locus FULL url',
2620
+ });
2621
+ }
2622
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2623
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2624
+ assert.calledOnce(locusInfo.locusParser.resume);
2625
+ })
2626
+ );
2627
+
2628
+ it(`applyLocusDeltaData - handles the case when we get FULL DTO when we asked for DELTA DTO`, async () => {
2629
+ const {DESYNC} = LocusDeltaParser.loci;
2630
+ const fakeFullLocusDto = {someStuff: 'data'}; // non-empty DTO, without baseSequence
2631
+ const meeting = {
2632
+ meetingRequest: {
2633
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
2634
+ },
2635
+ locusInfo: {
2636
+ onFullLocus: sandbox.stub(),
2637
+ handleLocusDelta: sandbox.stub(),
2638
+ },
2639
+ locusUrl: 'fake locus FULL url',
2640
+ };
2641
+
2642
+ sinon.stub(locusInfo.locusParser, 'resume').resolves();
2643
+
2644
+ locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
2645
+
2646
+ await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2647
+
2648
+ await testUtils.flushPromises();
2649
+
2650
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
2651
+ url: 'fake locus DELTA url',
2652
+ });
2653
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2654
+ assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
2655
+ assert.calledOnce(locusInfo.locusParser.resume);
2482
2656
  });
2483
2657
  });
2484
2658
 
@@ -2894,8 +3068,8 @@ describe('plugin-meetings', () => {
2894
3068
 
2895
3069
  sinon.stub(locusInfo, 'updateParticipantDeltas');
2896
3070
  sinon.stub(locusInfo, 'updateParticipants');
2897
- sinon.stub(locusInfo, 'isMeetingActive'),
2898
- sinon.stub(locusInfo, 'handleOneOnOneEvent'),
3071
+ sinon.stub(locusInfo, 'isMeetingActive');
3072
+ sinon.stub(locusInfo, 'handleOneOnOneEvent');
2899
3073
  (updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo'));
2900
3074
  syncRequestStub = sinon.stub().resolves({body: {}});
2901
3075
 
@@ -2916,6 +3090,7 @@ describe('plugin-meetings', () => {
2916
3090
  id: 'test person id',
2917
3091
  },
2918
3092
  },
3093
+ participants: [],
2919
3094
  });
2920
3095
 
2921
3096
  updateLocusInfoStub.resetHistory();
@@ -3064,6 +3239,7 @@ describe('plugin-meetings', () => {
3064
3239
  await testUtils.flushPromises();
3065
3240
 
3066
3241
  assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
3242
+ assert.calledOnce(updateLocusInfoStub);
3067
3243
  assert.calledOnceWithExactly(updateLocusInfoStub, fullLocus);
3068
3244
  });
3069
3245
 
@@ -3139,5 +3315,322 @@ describe('plugin-meetings', () => {
3139
3315
  assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[7]);
3140
3316
  });
3141
3317
  });
3318
+
3319
+ describe('Hash trees - webinar 5k', () => {
3320
+ let mockFullLocus;
3321
+ let clock;
3322
+
3323
+ beforeEach(() => {
3324
+
3325
+ sinon.stub(Math, 'random').returns(0.5); // to make sure the backoff timer is predictable
3326
+
3327
+ mockFullLocus = {
3328
+ dataSets: [
3329
+ {
3330
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/session/a73e9f2c/datasets/main',
3331
+ root: '9bb9d5a911a74d53a915b4dfbec7329f',
3332
+ version: 51118,
3333
+ leafCount: 16,
3334
+ name: 'main',
3335
+ idleMs : 5000,
3336
+ backoff : {
3337
+ maxMs : 500,
3338
+ exponent : 0.5
3339
+ },
3340
+ },
3341
+ {
3342
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/session/a73e9f2c/participant/713e9f99/datasets/self',
3343
+ root: '5b8cc7ffda1346d2bfb1c0b60b8ab601',
3344
+ version: 89891,
3345
+ leafCount: 1,
3346
+ name: 'self',
3347
+ idleMs : 10000,
3348
+ backoff : {
3349
+ maxMs : 500,
3350
+ exponent : 0.5
3351
+ },
3352
+ },
3353
+ {
3354
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/session/a73e9f2c/datasets/atd-unmuted',
3355
+ root: '9279d2e149da43a1b8e2cd7cbf77f9f0',
3356
+ version: 91277,
3357
+ leafCount: 16,
3358
+ name: 'atd-unmuted',
3359
+ idleMs : 15000,
3360
+ backoff : {
3361
+ maxMs : 500,
3362
+ exponent : 0.5
3363
+ },
3364
+ },
3365
+ ],
3366
+ locus: {
3367
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f',
3368
+ htMeta: {
3369
+ elementId: {
3370
+ type: 'LOCUS',
3371
+ id: 0,
3372
+ version: 5678,
3373
+ },
3374
+ dataSetNames: ['main'],
3375
+ },
3376
+ participants: [
3377
+ {
3378
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/11941033',
3379
+ person: {
3380
+ id: '111',
3381
+ name: '1st participant',
3382
+ },
3383
+ htMeta: {
3384
+ elementId: {
3385
+ type: 'PARTICIPANT',
3386
+ id: 2,
3387
+ version: 5678,
3388
+ },
3389
+ dataSetNames: ['atd-active', 'attendees', 'atd-unmuted'],
3390
+ },
3391
+ },
3392
+ {
3393
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/11941034',
3394
+ person: {
3395
+ id: '222',
3396
+ name: '2nd participant',
3397
+ },
3398
+ htMeta: {
3399
+ elementId: {
3400
+ type: 'PARTICIPANT',
3401
+ id: 3,
3402
+ version: 5678,
3403
+ },
3404
+ dataSetNames: ['attendees'],
3405
+ },
3406
+ },
3407
+ ],
3408
+ self: {
3409
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/11941033',
3410
+ visibleDataSets: ['main', 'self', 'atd-unmuted'],
3411
+ person: {
3412
+ id: '333',
3413
+ name: 'myself',
3414
+ },
3415
+ htMeta: {
3416
+ elementId: {
3417
+ type: 'SELF',
3418
+ id: 4,
3419
+ version: 5678,
3420
+ },
3421
+ dataSetNames: ['self'],
3422
+ },
3423
+ },
3424
+ },
3425
+ };
3426
+
3427
+ clock = sinon.useFakeTimers();
3428
+
3429
+ });
3430
+
3431
+ afterEach(() => {
3432
+ clock.restore();
3433
+ });
3434
+
3435
+ const getMaxBackoffTime = (dataSets) => {
3436
+ return Object.values(dataSets).reduce((max, dataSet) => {
3437
+ const maxBackOff = dataSet.idleMs + dataSet.backoff.maxMs;
3438
+ return Math.max(max, maxBackOff);
3439
+ }, 0);
3440
+ }
3441
+
3442
+ const waitForMaxPossibleBackoffTime = async (dataSets) => {
3443
+ const maxBackoffTime = getMaxBackoffTime(dataSets);
3444
+ clock.tick(maxBackoffTime);
3445
+ await testUtils.flushPromises();
3446
+ }
3447
+
3448
+ it('initializes hash trees correctly from initial full locus', () => {
3449
+ locusInfo.initialSetup(mockFullLocus.locus, mockFullLocus.dataSets);
3450
+
3451
+ // check that the hash tree parser is initialized correctly
3452
+ assert.isDefined(locusInfo.hashTreeParser);
3453
+ assert.deepEqual(Object.keys(locusInfo.hashTreeParser.dataSets), ['main', 'self', 'atd-unmuted']);
3454
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['main'].hashTree.getLeafData(0), [ { type: 'LOCUS', id: 0, version: 5678 } ]);
3455
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['self'].hashTree.getLeafData(0), [ { type: 'SELF', id: 4, version: 5678 } ]);
3456
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['atd-unmuted'].hashTree.getLeafData(2), [ { type: 'PARTICIPANT', id: 2, version: 5678 } ]);
3457
+
3458
+ // participant with id=3 is not part of any of our datasets, so should be undefined
3459
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['atd-unmuted'].hashTree.getLeafData(3), []);
3460
+ });
3461
+
3462
+ it('handles hash tree messages correctly', async () => {
3463
+ locusInfo.initialSetup(mockFullLocus.locus, mockFullLocus.dataSets);
3464
+
3465
+ // simulate a hash tree message for a participant
3466
+ locusInfo.parse(mockMeeting, {
3467
+ "locusUrl" : "https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2",
3468
+ "locusSessionId" : "fe3d019c-08ca-0f21-919f-f2cc646030ae",
3469
+ "dataSets" : [ {
3470
+ "url" : "https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/session/fe3d019c-08ca-0f21-919f-f2cc646030ae/datasets/attendees",
3471
+ "name" : "attendees",
3472
+ "root" : "7cfc14bdae7909e6fe7acd37bebf66db",
3473
+ "version" : 4739733700975,
3474
+ "leafCount" : 16,
3475
+ "idleMs" : 5000,
3476
+ "backoff" : {
3477
+ "maxMs" : 500,
3478
+ "exponent" : 0.5
3479
+ }
3480
+ }, {
3481
+ "url" : "https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/session/fe3d019c-08ca-0f21-919f-f2cc646030ae/datasets/atd-active",
3482
+ "name" : "atd-unmuted",
3483
+ "root" : "178bff6e3344f551a811712c57a9eac3",
3484
+ "version" : 5738696122316,
3485
+ "leafCount" : 4,
3486
+ "idleMs" : 5000,
3487
+ "backoff" : {
3488
+ "maxMs" : 500,
3489
+ "exponent" : 0.5
3490
+ }
3491
+ } ],
3492
+ "locusStateElements" : [ {
3493
+ "htMeta" : {
3494
+ "elementId" : {
3495
+ "type" : "PARTICIPANT",
3496
+ "id" : 2,
3497
+ "version" : 5679
3498
+ },
3499
+ "dataSetNames" : [ "attendees", "atd-unmuted" ]
3500
+ },
3501
+ "data" : "{\"isCreator\":false,\"url\":\"https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/participant/18ef210c-234a-49ac-83d6-1803bc401bc9\",\"id\":\"18ef210c-234a-49ac-83d6-1803bc401bc9\",\"guest\":false,\"resourceGuest\":false,\"moderator\":false,\"panelist\":false}"
3502
+ }, {
3503
+ "htMeta" : {
3504
+ "elementId" : {
3505
+ "type" : "PARTICIPANT",
3506
+ "id" : 3,
3507
+ "version" : 999999
3508
+ },
3509
+ "dataSetNames" : [ "attendees" ]
3510
+ },
3511
+ "data" : "{\"isCreator\":false,\"url\":\"https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/participant/29c0751a-7ada-40a1-94a4-eb5f5c80c863\",\"id\":\"29c0751a-7ada-40a1-94a4-eb5f5c80c863\",\"guest\":false,\"resourceGuest\":false,\"moderator\":false,\"panelist\":false}"
3512
+ } ]
3513
+ });
3514
+
3515
+ // main and self should be unchanged
3516
+ assert.deepEqual(Object.keys(locusInfo.hashTreeParser.dataSets), ['main', 'self', 'atd-unmuted']);
3517
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['main'].hashTree.getLeafData(0), [ { type: 'LOCUS', id: 0, version: 5678 } ]);
3518
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['self'].hashTree.getLeafData(0), [ { type: 'SELF', id: 4, version: 5678 } ]);
3519
+
3520
+ // participant should be updated
3521
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['atd-unmuted'].hashTree.getLeafData(2), [ { type: 'PARTICIPANT', id: 2, version: 5679 } ]);
3522
+
3523
+ // there should be no requests to Locus sent
3524
+ await waitForMaxPossibleBackoffTime(locusInfo.hashTreeParser.dataSets);
3525
+ assert.notCalled(webex.request);
3526
+ });
3527
+
3528
+ it('does a sync if hashes don\'t match after a timer fires', async () => {
3529
+ const atdUnmutedDataSetUrl = mockFullLocus.dataSets[2].url;
3530
+
3531
+ locusInfo.initialSetup(mockFullLocus.locus, mockFullLocus.dataSets);
3532
+
3533
+ // simulate a hash tree message for a participant
3534
+ locusInfo.parse(mockMeeting, {
3535
+ "locusUrl" : "https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2",
3536
+ "locusSessionId" : "fe3d019c-08ca-0f21-919f-f2cc646030ae",
3537
+ "dataSets" : [ {
3538
+ "url" : atdUnmutedDataSetUrl,
3539
+ "name" : "atd-unmuted",
3540
+ "root" : "deadbeef", // wrong to trigger a sync
3541
+ "version" : 5738696122316,
3542
+ "leafCount" : 4,
3543
+ "idleMs" : 25000,
3544
+ "backoff" : {
3545
+ "maxMs" : 500,
3546
+ "exponent" : 0.5
3547
+ }
3548
+ } ],
3549
+ "locusStateElements" : [ {
3550
+ "htMeta" : {
3551
+ "elementId" : {
3552
+ "type" : "PARTICIPANT",
3553
+ "id" : 2,
3554
+ "version" : 5679
3555
+ },
3556
+ "dataSetNames" : [ "attendees", "atd-unmuted" ]
3557
+ },
3558
+ "data" : "{\"isCreator\":false,\"url\":\"https://locus.wbx2.com/locus/api/v1/loci/dbe6eeb9-49c7-4727-bdb1-1c59fa6e56d2/participant/18ef210c-234a-49ac-83d6-1803bc401bc9\",\"id\":\"18ef210c-234a-49ac-83d6-1803bc401bc9\",\"guest\":false,\"resourceGuest\":false,\"moderator\":false,\"panelist\":false}"
3559
+ }]
3560
+ });
3561
+
3562
+ // main and self should be unchanged
3563
+ assert.deepEqual(Object.keys(locusInfo.hashTreeParser.dataSets), ['main', 'self', 'atd-unmuted']);
3564
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['main'].hashTree.getLeafData(0), [ { type: 'LOCUS', id: 0, version: 5678 } ]);
3565
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['self'].hashTree.getLeafData(0), [ { type: 'SELF', id: 4, version: 5678 } ]);
3566
+
3567
+ // participant should be updated
3568
+ assert.deepEqual(locusInfo.hashTreeParser.dataSets['atd-unmuted'].hashTree.getLeafData(2), [ { type: 'PARTICIPANT', id: 2, version: 5679 } ]);
3569
+
3570
+ await testUtils.flushPromises();
3571
+
3572
+ // the root hash doesn't match, but we shouldn't send a request to Locus for hashes just yet
3573
+ assert.notCalled(webex.request);
3574
+
3575
+ webex.request.callsFake(async (options) => {
3576
+ if (options?.method === 'GET' && options?.uri?.endsWith('/hashtree')) {
3577
+ return {
3578
+ body: {
3579
+ hashes: [
3580
+ '178bff6e3344f551a811712c57a9eac3',
3581
+ 'b113a76304e3a7121afecfe1606ee1c1',
3582
+ 'ae70773ebb3be3653209648071b9bdad',
3583
+ '99aa06d3014798d86001c324468d497f',
3584
+ '99aa06d3014798d86001c324468d497f',
3585
+ 'deadbeef', // wrong hash that should cause the participant with id=2 to be deemed out of sync
3586
+ '99aa06d3014798d86001c324468d497f'
3587
+ ]
3588
+ }
3589
+ };
3590
+ } else if (options?.method === 'POST' && options?.uri?.endsWith('/sync')) {
3591
+ return {
3592
+ body: {},
3593
+ statusCode: 202,
3594
+ };
3595
+ }
3596
+ return {};
3597
+ });
3598
+
3599
+ // only after timeout there should be requests to get hashes and sync sent to Locus
3600
+ await waitForMaxPossibleBackoffTime(locusInfo.hashTreeParser.dataSets);
3601
+ assert.calledTwice(webex.request);
3602
+
3603
+ const firstCallArgs = webex.request.getCall(0).args[0];
3604
+ const secondCallArgs = webex.request.getCall(1).args[0];
3605
+
3606
+ assert.deepEqual(firstCallArgs, {method: 'GET', uri: `${atdUnmutedDataSetUrl}/hashtree`});
3607
+
3608
+ assert.deepEqual(secondCallArgs, {method: 'POST', uri: `${atdUnmutedDataSetUrl}/sync`,
3609
+ body: {
3610
+ dataSet: {
3611
+ name: 'atd-unmuted',
3612
+ leafCount: 16,
3613
+ root: '178bff6e3344f551a811712c57a9eac3',
3614
+ },
3615
+ leafDataEntries: [{
3616
+ leafIndex: 2,
3617
+ elementIds: [
3618
+ {
3619
+ id: 2,
3620
+ type: "PARTICIPANT",
3621
+ version: 5679
3622
+ }
3623
+ ],
3624
+ }]
3625
+ }
3626
+ });
3627
+ });
3628
+
3629
+ it('handles end meeting message correctly', async () => {
3630
+ });
3631
+ it('handles heartbeat messages correctly', async () => {
3632
+ });
3633
+
3634
+ });
3142
3635
  });
3143
3636
  });