@webex/plugin-meetings 3.12.0-next.5 → 3.12.0-next.51

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 (136) hide show
  1. package/AGENTS.md +9 -0
  2. package/dist/aiEnableRequest/index.js +15 -2
  3. package/dist/aiEnableRequest/index.js.map +1 -1
  4. package/dist/breakouts/breakout.js +6 -2
  5. package/dist/breakouts/breakout.js.map +1 -1
  6. package/dist/breakouts/index.js +1 -1
  7. package/dist/config.js +1 -0
  8. package/dist/config.js.map +1 -1
  9. package/dist/constants.js +6 -3
  10. package/dist/constants.js.map +1 -1
  11. package/dist/controls-options-manager/constants.js +11 -1
  12. package/dist/controls-options-manager/constants.js.map +1 -1
  13. package/dist/controls-options-manager/index.js +38 -24
  14. package/dist/controls-options-manager/index.js.map +1 -1
  15. package/dist/controls-options-manager/util.js +91 -0
  16. package/dist/controls-options-manager/util.js.map +1 -1
  17. package/dist/hashTree/constants.js +10 -1
  18. package/dist/hashTree/constants.js.map +1 -1
  19. package/dist/hashTree/hashTreeParser.js +646 -371
  20. package/dist/hashTree/hashTreeParser.js.map +1 -1
  21. package/dist/hashTree/utils.js +22 -0
  22. package/dist/hashTree/utils.js.map +1 -1
  23. package/dist/index.js +7 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/interceptors/locusRetry.js +23 -8
  26. package/dist/interceptors/locusRetry.js.map +1 -1
  27. package/dist/interpretation/index.js +10 -1
  28. package/dist/interpretation/index.js.map +1 -1
  29. package/dist/interpretation/siLanguage.js +1 -1
  30. package/dist/locus-info/controlsUtils.js +4 -1
  31. package/dist/locus-info/controlsUtils.js.map +1 -1
  32. package/dist/locus-info/index.js +289 -86
  33. package/dist/locus-info/index.js.map +1 -1
  34. package/dist/locus-info/types.js +19 -0
  35. package/dist/locus-info/types.js.map +1 -1
  36. package/dist/media/properties.js +1 -0
  37. package/dist/media/properties.js.map +1 -1
  38. package/dist/meeting/in-meeting-actions.js +3 -1
  39. package/dist/meeting/in-meeting-actions.js.map +1 -1
  40. package/dist/meeting/index.js +842 -521
  41. package/dist/meeting/index.js.map +1 -1
  42. package/dist/meeting/util.js +19 -2
  43. package/dist/meeting/util.js.map +1 -1
  44. package/dist/meetings/index.js +205 -77
  45. package/dist/meetings/index.js.map +1 -1
  46. package/dist/meetings/meetings.types.js +6 -1
  47. package/dist/meetings/meetings.types.js.map +1 -1
  48. package/dist/meetings/request.js +39 -0
  49. package/dist/meetings/request.js.map +1 -1
  50. package/dist/meetings/util.js +67 -5
  51. package/dist/meetings/util.js.map +1 -1
  52. package/dist/member/index.js +10 -0
  53. package/dist/member/index.js.map +1 -1
  54. package/dist/member/types.js.map +1 -1
  55. package/dist/member/util.js +3 -0
  56. package/dist/member/util.js.map +1 -1
  57. package/dist/metrics/constants.js +2 -1
  58. package/dist/metrics/constants.js.map +1 -1
  59. package/dist/recording-controller/index.js +1 -3
  60. package/dist/recording-controller/index.js.map +1 -1
  61. package/dist/types/config.d.ts +1 -0
  62. package/dist/types/constants.d.ts +2 -0
  63. package/dist/types/controls-options-manager/constants.d.ts +6 -1
  64. package/dist/types/controls-options-manager/index.d.ts +10 -0
  65. package/dist/types/hashTree/constants.d.ts +1 -0
  66. package/dist/types/hashTree/hashTreeParser.d.ts +83 -16
  67. package/dist/types/hashTree/utils.d.ts +11 -0
  68. package/dist/types/index.d.ts +2 -0
  69. package/dist/types/interceptors/locusRetry.d.ts +4 -4
  70. package/dist/types/locus-info/index.d.ts +46 -6
  71. package/dist/types/locus-info/types.d.ts +21 -1
  72. package/dist/types/media/properties.d.ts +1 -0
  73. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  74. package/dist/types/meeting/index.d.ts +70 -1
  75. package/dist/types/meeting/util.d.ts +8 -0
  76. package/dist/types/meetings/index.d.ts +20 -2
  77. package/dist/types/meetings/meetings.types.d.ts +15 -0
  78. package/dist/types/meetings/request.d.ts +14 -0
  79. package/dist/types/member/index.d.ts +1 -0
  80. package/dist/types/member/types.d.ts +1 -0
  81. package/dist/types/member/util.d.ts +1 -0
  82. package/dist/types/metrics/constants.d.ts +1 -0
  83. package/dist/webinar/index.js +361 -235
  84. package/dist/webinar/index.js.map +1 -1
  85. package/package.json +22 -22
  86. package/src/aiEnableRequest/index.ts +16 -0
  87. package/src/breakouts/breakout.ts +2 -1
  88. package/src/config.ts +1 -0
  89. package/src/constants.ts +5 -1
  90. package/src/controls-options-manager/constants.ts +14 -1
  91. package/src/controls-options-manager/index.ts +47 -24
  92. package/src/controls-options-manager/util.ts +81 -1
  93. package/src/hashTree/constants.ts +9 -0
  94. package/src/hashTree/hashTreeParser.ts +362 -174
  95. package/src/hashTree/utils.ts +17 -0
  96. package/src/index.ts +5 -0
  97. package/src/interceptors/locusRetry.ts +25 -4
  98. package/src/interpretation/index.ts +25 -8
  99. package/src/locus-info/controlsUtils.ts +3 -1
  100. package/src/locus-info/index.ts +291 -93
  101. package/src/locus-info/types.ts +25 -1
  102. package/src/media/properties.ts +1 -0
  103. package/src/meeting/in-meeting-actions.ts +4 -0
  104. package/src/meeting/index.ts +315 -26
  105. package/src/meeting/util.ts +20 -2
  106. package/src/meetings/index.ts +109 -43
  107. package/src/meetings/meetings.types.ts +19 -0
  108. package/src/meetings/request.ts +43 -0
  109. package/src/meetings/util.ts +80 -1
  110. package/src/member/index.ts +10 -0
  111. package/src/member/types.ts +1 -0
  112. package/src/member/util.ts +3 -0
  113. package/src/metrics/constants.ts +1 -0
  114. package/src/recording-controller/index.ts +1 -2
  115. package/src/webinar/index.ts +162 -21
  116. package/test/unit/spec/aiEnableRequest/index.ts +86 -0
  117. package/test/unit/spec/breakouts/breakout.ts +7 -3
  118. package/test/unit/spec/controls-options-manager/index.js +140 -29
  119. package/test/unit/spec/controls-options-manager/util.js +165 -0
  120. package/test/unit/spec/hashTree/hashTreeParser.ts +1341 -140
  121. package/test/unit/spec/hashTree/utils.ts +88 -1
  122. package/test/unit/spec/interceptors/locusRetry.ts +205 -4
  123. package/test/unit/spec/interpretation/index.ts +26 -4
  124. package/test/unit/spec/locus-info/controlsUtils.js +172 -57
  125. package/test/unit/spec/locus-info/index.js +475 -81
  126. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  127. package/test/unit/spec/meeting/index.js +836 -41
  128. package/test/unit/spec/meeting/muteState.js +3 -0
  129. package/test/unit/spec/meeting/utils.js +33 -0
  130. package/test/unit/spec/meetings/index.js +309 -10
  131. package/test/unit/spec/meetings/request.js +141 -0
  132. package/test/unit/spec/meetings/utils.js +161 -0
  133. package/test/unit/spec/member/index.js +7 -0
  134. package/test/unit/spec/member/util.js +24 -0
  135. package/test/unit/spec/recording-controller/index.js +9 -8
  136. package/test/unit/spec/webinar/index.ts +141 -16
@@ -15,17 +15,17 @@ var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequ
15
15
  _Object$defineProperty(exports, "__esModule", {
16
16
  value: true
17
17
  });
18
- exports.default = exports.MeetingEndedError = exports.LocusInfoUpdateType = void 0;
18
+ exports.default = exports.MeetingEndedError = exports.LocusNotFoundError = exports.LocusInfoUpdateType = void 0;
19
19
  var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));
20
- var _stringify = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/json/stringify"));
21
20
  var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
21
+ var _stringify = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/json/stringify"));
22
22
  var _isArray = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/array/is-array"));
23
23
  var _keys = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/keys"));
24
24
  var _values = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/values"));
25
25
  var _parseInt2 = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/parse-int"));
26
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/toConsumableArray"));
26
27
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/slicedToArray"));
27
28
  var _typeof2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/typeof"));
28
- var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/toConsumableArray"));
29
29
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/asyncToGenerator"));
30
30
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty"));
31
31
  var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
@@ -37,8 +37,12 @@ var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime-corejs2/h
37
37
  var _lodash = require("lodash");
38
38
  var _hashTree = _interopRequireDefault(require("./hashTree"));
39
39
  var _loggerProxy = _interopRequireDefault(require("../common/logs/logger-proxy"));
40
- var _constants = require("../constants");
41
- var _constants2 = require("./constants");
40
+ var _metrics = _interopRequireDefault(require("../metrics"));
41
+ var _constants = _interopRequireDefault(require("../metrics/constants"));
42
+ var _constants2 = require("../constants");
43
+ var _constants3 = require("./constants");
44
+ var _types = require("./types");
45
+ var _types2 = require("../locus-info/types");
42
46
  var _utils = require("./utils");
43
47
  function ownKeys(e, r) { var t = _Object$keys4(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
44
48
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -49,7 +53,8 @@ function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0,
49
53
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
50
54
  var LocusInfoUpdateType = exports.LocusInfoUpdateType = {
51
55
  OBJECTS_UPDATED: 'OBJECTS_UPDATED',
52
- MEETING_ENDED: 'MEETING_ENDED'
56
+ MEETING_ENDED: 'MEETING_ENDED',
57
+ LOCUS_NOT_FOUND: 'LOCUS_NOT_FOUND'
53
58
  };
54
59
  /**
55
60
  * This error is thrown if we receive information that the meeting has ended while we're processing some hash messages.
@@ -63,11 +68,24 @@ var MeetingEndedError = exports.MeetingEndedError = /*#__PURE__*/function (_Erro
63
68
  (0, _inherits2.default)(MeetingEndedError, _Error);
64
69
  return (0, _createClass2.default)(MeetingEndedError);
65
70
  }(/*#__PURE__*/(0, _wrapNativeSuper2.default)(Error));
71
+ /**
72
+ * This error is thrown when a 404 is received from Locus hash tree endpoints, indicating that the locus URL
73
+ * is no longer valid (e.g. participant moved to a breakout room, or meeting ended).
74
+ * It's handled internally by HashTreeParser and results in LOCUS_NOT_FOUND being sent up.
75
+ */
76
+ var LocusNotFoundError = exports.LocusNotFoundError = /*#__PURE__*/function (_Error2) {
77
+ function LocusNotFoundError() {
78
+ (0, _classCallCheck2.default)(this, LocusNotFoundError);
79
+ return _callSuper(this, LocusNotFoundError, arguments);
80
+ }
81
+ (0, _inherits2.default)(LocusNotFoundError, _Error2);
82
+ return (0, _createClass2.default)(LocusNotFoundError);
83
+ }(/*#__PURE__*/(0, _wrapNativeSuper2.default)(Error));
66
84
  /* Currently Locus always sends Metadata objects only in the "self" dataset.
67
85
  * If this ever changes, update all the code that relies on this constant.
68
86
  */
69
- var MetadataDataSetName = _constants2.DataSetNames.SELF;
70
- var PossibleSentinelMessageDataSetNames = [_constants2.DataSetNames.MAIN, _constants2.DataSetNames.SELF, _constants2.DataSetNames.UNJOINED];
87
+ var MetadataDataSetName = _constants3.DataSetNames.SELF;
88
+ var PossibleSentinelMessageDataSetNames = [_constants3.DataSetNames.MAIN, _constants3.DataSetNames.SELF, _constants3.DataSetNames.UNJOINED];
71
89
 
72
90
  /**
73
91
  * Parses hash tree eventing locus data
@@ -91,6 +109,10 @@ var HashTreeParser = /*#__PURE__*/function () {
91
109
  (0, _defineProperty2.default)(this, "heartbeatIntervalMs", void 0);
92
110
  (0, _defineProperty2.default)(this, "excludedDataSets", void 0);
93
111
  (0, _defineProperty2.default)(this, "state", void 0);
112
+ (0, _defineProperty2.default)(this, "syncQueue", []);
113
+ (0, _defineProperty2.default)(this, "isSyncInProgress", false);
114
+ (0, _defineProperty2.default)(this, "isSyncAllInProgress", false);
115
+ (0, _defineProperty2.default)(this, "syncQueueProcessingPromise", _promise.default.resolve());
94
116
  var _options$initialLocus = options.initialLocus,
95
117
  dataSets = _options$initialLocus.dataSets,
96
118
  locus = _options$initialLocus.locus; // extract dataSets from initialLocus
@@ -211,80 +233,65 @@ var HashTreeParser = /*#__PURE__*/function () {
211
233
  */
212
234
  }, {
213
235
  key: "initializeNewVisibleDataSet",
214
- value: function initializeNewVisibleDataSet(visibleDataSetInfo, dataSetInfo) {
215
- if (this.isVisibleDataSet(dataSetInfo.name)) {
216
- _loggerProxy.default.logger.info("HashTreeParser#initializeNewVisibleDataSet --> ".concat(this.debugId, " Data set \"").concat(dataSetInfo.name, "\" already exists, skipping init"));
217
- return _promise.default.resolve({
218
- updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
219
- updatedObjects: []
220
- });
221
- }
222
- _loggerProxy.default.logger.info("HashTreeParser#initializeNewVisibleDataSet --> ".concat(this.debugId, " Adding visible data set \"").concat(dataSetInfo.name, "\""));
223
- if (!this.addToVisibleDataSetsList(visibleDataSetInfo)) {
224
- return _promise.default.resolve({
225
- updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
226
- updatedObjects: []
227
- });
228
- }
229
- var hashTree = new _hashTree.default([], dataSetInfo.leafCount);
230
- this.dataSets[dataSetInfo.name] = _objectSpread(_objectSpread({}, dataSetInfo), {}, {
231
- hashTree: hashTree
232
- });
233
- return this.sendInitializationSyncRequestToLocus(dataSetInfo.name, 'new visible data set');
234
- }
235
-
236
- /**
237
- * Sends a special sync request to Locus with all leaves empty - this is a way to get all the data for a given dataset.
238
- *
239
- * @param {string} datasetName - name of the dataset for which to send the request
240
- * @param {string} debugText - text to include in logs
241
- * @returns {Promise}
242
- */
243
- }, {
244
- key: "sendInitializationSyncRequestToLocus",
245
- value: function sendInitializationSyncRequestToLocus(datasetName, debugText) {
246
- var _this2 = this;
247
- var dataset = this.dataSets[datasetName];
248
- if (!dataset) {
249
- _loggerProxy.default.logger.warn("HashTreeParser#sendInitializationSyncRequestToLocus --> ".concat(this.debugId, " No data set found for ").concat(datasetName, ", cannot send the request for leaf data"));
250
- return _promise.default.resolve(null);
236
+ value: (function () {
237
+ var _initializeNewVisibleDataSet = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee(visibleDataSetInfo, dataSetInfo) {
238
+ var hashTree;
239
+ return _regenerator.default.wrap(function (_context) {
240
+ while (1) switch (_context.prev = _context.next) {
241
+ case 0:
242
+ if (!this.isVisibleDataSet(dataSetInfo.name)) {
243
+ _context.next = 1;
244
+ break;
245
+ }
246
+ _loggerProxy.default.logger.info("HashTreeParser#initializeNewVisibleDataSet --> ".concat(this.debugId, " Data set \"").concat(dataSetInfo.name, "\" already exists, skipping init"));
247
+ return _context.abrupt("return");
248
+ case 1:
249
+ _loggerProxy.default.logger.info("HashTreeParser#initializeNewVisibleDataSet --> ".concat(this.debugId, " Adding visible data set \"").concat(dataSetInfo.name, "\""));
250
+ if (this.addToVisibleDataSetsList(visibleDataSetInfo)) {
251
+ _context.next = 2;
252
+ break;
253
+ }
254
+ return _context.abrupt("return");
255
+ case 2:
256
+ hashTree = new _hashTree.default([], dataSetInfo.leafCount);
257
+ this.dataSets[dataSetInfo.name] = _objectSpread(_objectSpread({}, dataSetInfo), {}, {
258
+ hashTree: hashTree
259
+ });
260
+ this.enqueueSyncForDataset(dataSetInfo.name, 'new visible data set initialization', true);
261
+ _context.next = 3;
262
+ return this.syncQueueProcessingPromise;
263
+ case 3:
264
+ case "end":
265
+ return _context.stop();
266
+ }
267
+ }, _callee, this);
268
+ }));
269
+ function initializeNewVisibleDataSet(_x, _x2) {
270
+ return _initializeNewVisibleDataSet.apply(this, arguments);
251
271
  }
252
- var emptyLeavesData = new Array(dataset.leafCount).fill([]);
253
- _loggerProxy.default.logger.info("HashTreeParser#sendInitializationSyncRequestToLocus --> ".concat(this.debugId, " Sending initial sync request to Locus for data set \"").concat(datasetName, "\" with empty leaf data"));
254
- return this.sendSyncRequestToLocus(this.dataSets[datasetName], emptyLeavesData).then(function (syncResponse) {
255
- if (syncResponse) {
256
- return {
257
- updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
258
- updatedObjects: _this2.parseMessage(syncResponse, "via empty leaves /sync API call for ".concat(debugText))
259
- };
260
- }
261
- return {
262
- updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
263
- updatedObjects: []
264
- };
265
- });
266
- }
267
-
272
+ return initializeNewVisibleDataSet;
273
+ }()
268
274
  /**
269
275
  * Queries Locus for all up-to-date information about all visible data sets
270
276
  *
271
277
  * @returns {Promise}
272
278
  */
279
+ )
273
280
  }, {
274
281
  key: "getAllVisibleDataSetsFromLocus",
275
282
  value: function getAllVisibleDataSetsFromLocus() {
276
- var _this3 = this;
283
+ var _this2 = this;
277
284
  if (!this.visibleDataSetsUrl) {
278
285
  _loggerProxy.default.logger.warn("HashTreeParser#getAllVisibleDataSetsFromLocus --> ".concat(this.debugId, " No visibleDataSetsUrl, cannot get data sets information"));
279
286
  return _promise.default.resolve([]);
280
287
  }
281
288
  return this.webexRequest({
282
- method: _constants.HTTP_VERBS.GET,
289
+ method: _constants2.HTTP_VERBS.GET,
283
290
  uri: this.visibleDataSetsUrl
284
291
  }).then(function (response) {
285
292
  return response.body.dataSets;
286
293
  }).catch(function (error) {
287
- _this3.checkForSentinelHttpResponse(error);
294
+ _this2.checkForSentinelHttpResponse(error);
288
295
  throw error;
289
296
  });
290
297
  }
@@ -298,26 +305,26 @@ var HashTreeParser = /*#__PURE__*/function () {
298
305
  }, {
299
306
  key: "initializeFromMessage",
300
307
  value: (function () {
301
- var _initializeFromMessage = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee(message) {
308
+ var _initializeFromMessage = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2(message) {
302
309
  var visibleDataSets;
303
- return _regenerator.default.wrap(function (_context) {
304
- while (1) switch (_context.prev = _context.next) {
310
+ return _regenerator.default.wrap(function (_context2) {
311
+ while (1) switch (_context2.prev = _context2.next) {
305
312
  case 0:
306
313
  this.visibleDataSetsUrl = message.visibleDataSetsUrl;
307
314
  _loggerProxy.default.logger.info("HashTreeParser#initializeFromMessage --> ".concat(this.debugId, " visibleDataSetsUrl=").concat(this.visibleDataSetsUrl));
308
- _context.next = 1;
315
+ _context2.next = 1;
309
316
  return this.getAllVisibleDataSetsFromLocus();
310
317
  case 1:
311
- visibleDataSets = _context.sent;
312
- _context.next = 2;
318
+ visibleDataSets = _context2.sent;
319
+ _context2.next = 2;
313
320
  return this.initializeDataSets(visibleDataSets, 'initialization from message');
314
321
  case 2:
315
322
  case "end":
316
- return _context.stop();
323
+ return _context2.stop();
317
324
  }
318
- }, _callee, this);
325
+ }, _callee2, this);
319
326
  }));
320
- function initializeFromMessage(_x) {
327
+ function initializeFromMessage(_x3) {
321
328
  return _initializeFromMessage.apply(this, arguments);
322
329
  }
323
330
  return initializeFromMessage;
@@ -335,34 +342,34 @@ var HashTreeParser = /*#__PURE__*/function () {
335
342
  }, {
336
343
  key: "initializeFromGetLociResponse",
337
344
  value: (function () {
338
- var _initializeFromGetLociResponse = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2(locus) {
345
+ var _initializeFromGetLociResponse = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee3(locus) {
339
346
  var _locus$links2, _locus$links2$resourc, _locus$links2$resourc2;
340
347
  var visibleDataSets;
341
- return _regenerator.default.wrap(function (_context2) {
342
- while (1) switch (_context2.prev = _context2.next) {
348
+ return _regenerator.default.wrap(function (_context3) {
349
+ while (1) switch (_context3.prev = _context3.next) {
343
350
  case 0:
344
351
  if (locus !== null && locus !== void 0 && (_locus$links2 = locus.links) !== null && _locus$links2 !== void 0 && (_locus$links2$resourc = _locus$links2.resources) !== null && _locus$links2$resourc !== void 0 && (_locus$links2$resourc2 = _locus$links2$resourc.visibleDataSets) !== null && _locus$links2$resourc2 !== void 0 && _locus$links2$resourc2.url) {
345
- _context2.next = 1;
352
+ _context3.next = 1;
346
353
  break;
347
354
  }
348
355
  _loggerProxy.default.logger.warn("HashTreeParser#initializeFromGetLociResponse --> ".concat(this.debugId, " missing visibleDataSets url in GET Loci response, cannot initialize hash trees"));
349
- return _context2.abrupt("return");
356
+ return _context3.abrupt("return");
350
357
  case 1:
351
358
  this.visibleDataSetsUrl = locus.links.resources.visibleDataSets.url;
352
359
  _loggerProxy.default.logger.info("HashTreeParser#initializeFromGetLociResponse --> ".concat(this.debugId, " visibleDataSets url: ").concat(this.visibleDataSetsUrl));
353
- _context2.next = 2;
360
+ _context3.next = 2;
354
361
  return this.getAllVisibleDataSetsFromLocus();
355
362
  case 2:
356
- visibleDataSets = _context2.sent;
357
- _context2.next = 3;
363
+ visibleDataSets = _context3.sent;
364
+ _context3.next = 3;
358
365
  return this.initializeDataSets(visibleDataSets, 'initialization from GET /loci response');
359
366
  case 3:
360
367
  case "end":
361
- return _context2.stop();
368
+ return _context3.stop();
362
369
  }
363
- }, _callee2, this);
370
+ }, _callee3, this);
364
371
  }));
365
- function initializeFromGetLociResponse(_x2) {
372
+ function initializeFromGetLociResponse(_x4) {
366
373
  return _initializeFromGetLociResponse.apply(this, arguments);
367
374
  }
368
375
  return initializeFromGetLociResponse;
@@ -378,24 +385,23 @@ var HashTreeParser = /*#__PURE__*/function () {
378
385
  }, {
379
386
  key: "initializeDataSets",
380
387
  value: (function () {
381
- var _initializeDataSets = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee3(visibleDataSets, debugText) {
382
- var updatedObjects, _iterator2, _step2, dataSet, name, leafCount, url, _data, _t;
383
- return _regenerator.default.wrap(function (_context3) {
384
- while (1) switch (_context3.prev = _context3.next) {
388
+ var _initializeDataSets = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4(visibleDataSets, debugText) {
389
+ var _iterator2, _step2, dataSet, name, leafCount, url, _t;
390
+ return _regenerator.default.wrap(function (_context4) {
391
+ while (1) switch (_context4.prev = _context4.next) {
385
392
  case 0:
386
393
  if (!(this.state === 'stopped')) {
387
- _context3.next = 1;
394
+ _context4.next = 1;
388
395
  break;
389
396
  }
390
- return _context3.abrupt("return");
397
+ return _context4.abrupt("return");
391
398
  case 1:
392
- updatedObjects = [];
393
- _iterator2 = _createForOfIteratorHelper(visibleDataSets);
394
- _context3.prev = 2;
399
+ _iterator2 = _createForOfIteratorHelper((0, _utils.sortByInitPriority)(visibleDataSets, _constants3.DATA_SET_INIT_PRIORITY));
400
+ _context4.prev = 2;
395
401
  _iterator2.s();
396
402
  case 3:
397
403
  if ((_step2 = _iterator2.n()).done) {
398
- _context3.next = 7;
404
+ _context4.next = 6;
399
405
  break;
400
406
  }
401
407
  dataSet = _step2.value;
@@ -407,59 +413,47 @@ var HashTreeParser = /*#__PURE__*/function () {
407
413
  _loggerProxy.default.logger.info("HashTreeParser#initializeDataSets --> ".concat(this.debugId, " dataset \"").concat(name, "\" already exists (").concat(debugText, ")"));
408
414
  }
409
415
  if (this.isVisibleDataSet(name)) {
410
- _context3.next = 4;
416
+ _context4.next = 4;
411
417
  break;
412
418
  }
413
419
  if (this.addToVisibleDataSetsList({
414
420
  name: name,
415
421
  url: url
416
422
  })) {
417
- _context3.next = 4;
423
+ _context4.next = 4;
418
424
  break;
419
425
  }
420
- return _context3.abrupt("continue", 6);
426
+ return _context4.abrupt("continue", 5);
421
427
  case 4:
422
- if (this.dataSets[name].hashTree) {
423
- _context3.next = 6;
424
- break;
428
+ if (!this.dataSets[name].hashTree) {
429
+ _loggerProxy.default.logger.info("HashTreeParser#initializeDataSets --> ".concat(this.debugId, " creating hash tree for visible dataset \"").concat(name, "\" (").concat(debugText, ")"));
430
+ this.dataSets[name].hashTree = new _hashTree.default([], leafCount);
431
+ this.enqueueSyncForDataset(name, "initialization sync for ".concat(debugText), true);
425
432
  }
426
- _loggerProxy.default.logger.info("HashTreeParser#initializeDataSets --> ".concat(this.debugId, " creating hash tree for visible dataset \"").concat(name, "\" (").concat(debugText, ")"));
427
- this.dataSets[name].hashTree = new _hashTree.default([], leafCount);
428
-
429
- // eslint-disable-next-line no-await-in-loop
430
- _context3.next = 5;
431
- return this.sendInitializationSyncRequestToLocus(name, debugText);
432
433
  case 5:
433
- _data = _context3.sent;
434
- if (_data.updateType === LocusInfoUpdateType.OBJECTS_UPDATED) {
435
- updatedObjects.push.apply(updatedObjects, (0, _toConsumableArray2.default)(_data.updatedObjects || []));
436
- }
434
+ _context4.next = 3;
435
+ break;
437
436
  case 6:
438
- _context3.next = 3;
437
+ _context4.next = 8;
439
438
  break;
440
439
  case 7:
441
- _context3.next = 9;
442
- break;
443
- case 8:
444
- _context3.prev = 8;
445
- _t = _context3["catch"](2);
440
+ _context4.prev = 7;
441
+ _t = _context4["catch"](2);
446
442
  _iterator2.e(_t);
447
- case 9:
448
- _context3.prev = 9;
443
+ case 8:
444
+ _context4.prev = 8;
449
445
  _iterator2.f();
450
- return _context3.finish(9);
446
+ return _context4.finish(8);
447
+ case 9:
448
+ _context4.next = 10;
449
+ return this.syncQueueProcessingPromise;
451
450
  case 10:
452
- this.callLocusInfoUpdateCallback({
453
- updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
454
- updatedObjects: updatedObjects
455
- });
456
- case 11:
457
451
  case "end":
458
- return _context3.stop();
452
+ return _context4.stop();
459
453
  }
460
- }, _callee3, this, [[2, 8, 9, 10]]);
454
+ }, _callee4, this, [[2, 7, 8, 9]]);
461
455
  }));
462
- function initializeDataSets(_x3, _x4) {
456
+ function initializeDataSets(_x5, _x6) {
463
457
  return _initializeDataSets.apply(this, arguments);
464
458
  }
465
459
  return initializeDataSets;
@@ -483,7 +477,7 @@ var HashTreeParser = /*#__PURE__*/function () {
483
477
  copyData = _ref$copyData === void 0 ? false : _ref$copyData;
484
478
  // object mapping dataset names to arrays of leaf data
485
479
  var leafInfo = {};
486
- var _findAndStoreMetaData = function findAndStoreMetaData(currentLocusPart) {
480
+ var _findAndStoreMetaData = function findAndStoreMetaData(currentLocusPart, currentLocusPartName) {
487
481
  if ((0, _typeof2.default)(currentLocusPart) !== 'object' || currentLocusPart === null) {
488
482
  return;
489
483
  }
@@ -499,10 +493,16 @@ var HashTreeParser = /*#__PURE__*/function () {
499
493
  version: version
500
494
  };
501
495
  if (copyData) {
502
- newLeafInfo.data = (0, _lodash.cloneDeep)(currentLocusPart);
496
+ if (type.toLowerCase() === _types.ObjectType.control) {
497
+ // control entries require special handling, because they are signalled by Locus
498
+ // differently when coming in messages vs API responses
499
+ newLeafInfo.data = (0, _defineProperty2.default)({}, currentLocusPartName, (0, _lodash.cloneDeep)(currentLocusPart));
500
+ } else {
501
+ newLeafInfo.data = (0, _lodash.cloneDeep)(currentLocusPart);
503
502
 
504
- // remove any nested other objects that have their own htMeta
505
- (0, _utils.deleteNestedObjectsWithHtMeta)(newLeafInfo.data);
503
+ // remove any nested other objects that have their own htMeta
504
+ (0, _utils.deleteNestedObjectsWithHtMeta)(newLeafInfo.data);
505
+ }
506
506
  }
507
507
  var _iterator3 = _createForOfIteratorHelper(dataSetNames),
508
508
  _step3;
@@ -521,12 +521,14 @@ var HashTreeParser = /*#__PURE__*/function () {
521
521
  }
522
522
  }
523
523
  if ((0, _isArray.default)(currentLocusPart)) {
524
- var _iterator4 = _createForOfIteratorHelper(currentLocusPart),
524
+ var _iterator4 = _createForOfIteratorHelper(currentLocusPart.entries()),
525
525
  _step4;
526
526
  try {
527
527
  for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
528
- var item = _step4.value;
529
- _findAndStoreMetaData(item);
528
+ var _step4$value = (0, _slicedToArray2.default)(_step4.value, 2),
529
+ index = _step4$value[0],
530
+ item = _step4$value[1];
531
+ _findAndStoreMetaData(item, index.toString());
530
532
  }
531
533
  } catch (err) {
532
534
  _iterator4.e(err);
@@ -537,12 +539,12 @@ var HashTreeParser = /*#__PURE__*/function () {
537
539
  for (var _i = 0, _Object$keys = (0, _keys.default)(currentLocusPart); _i < _Object$keys.length; _i++) {
538
540
  var key = _Object$keys[_i];
539
541
  if (Object.prototype.hasOwnProperty.call(currentLocusPart, key)) {
540
- _findAndStoreMetaData(currentLocusPart[key]);
542
+ _findAndStoreMetaData(currentLocusPart[key], key);
541
543
  }
542
544
  }
543
545
  }
544
546
  };
545
- _findAndStoreMetaData(locus);
547
+ _findAndStoreMetaData(locus, 'locus');
546
548
  return leafInfo;
547
549
  }
548
550
 
@@ -587,9 +589,9 @@ var HashTreeParser = /*#__PURE__*/function () {
587
589
  }, {
588
590
  key: "isEndMessage",
589
591
  value: function isEndMessage(message) {
590
- var _this4 = this;
592
+ var _this3 = this;
591
593
  return message.dataSets.some(function (dataSet) {
592
- if (dataSet.leafCount === 1 && dataSet.root === _constants2.EMPTY_HASH && (!_this4.dataSets[dataSet.name] || _this4.dataSets[dataSet.name].version < dataSet.version) && PossibleSentinelMessageDataSetNames.includes(dataSet.name.toLowerCase())) {
594
+ if (dataSet.leafCount === 1 && dataSet.root === _constants3.EMPTY_HASH && (!_this3.dataSets[dataSet.name] || _this3.dataSets[dataSet.name].version < dataSet.version) && PossibleSentinelMessageDataSetNames.includes(dataSet.name.toLowerCase())) {
593
595
  // this is a special way for Locus to indicate that this meeting has ended
594
596
  return true;
595
597
  }
@@ -606,7 +608,7 @@ var HashTreeParser = /*#__PURE__*/function () {
606
608
  }, {
607
609
  key: "handleRootHashHeartBeatMessage",
608
610
  value: function handleRootHashHeartBeatMessage(message) {
609
- var _this5 = this;
611
+ var _this4 = this;
610
612
  var dataSets = message.dataSets;
611
613
  _loggerProxy.default.logger.info("HashTreeParser#handleRootHashMessage --> ".concat(this.debugId, " Received heartbeat root hash message with data sets: ").concat((0, _stringify.default)(dataSets.map(function (_ref2) {
612
614
  var name = _ref2.name,
@@ -621,11 +623,36 @@ var HashTreeParser = /*#__PURE__*/function () {
621
623
  };
622
624
  }))));
623
625
  dataSets.forEach(function (dataSet) {
624
- _this5.updateDataSetInfo(dataSet);
625
- _this5.runSyncAlgorithm(dataSet);
626
+ _this4.updateDataSetInfo(dataSet);
627
+ _this4.runSyncAlgorithm(dataSet);
626
628
  });
627
629
  }
628
630
 
631
+ /**
632
+ * Handles known errors that can happen during syncs
633
+ *
634
+ * @param {any} error - The error to handle
635
+ * @returns {boolean} true if the error was recognized and handled, false otherwise
636
+ */
637
+ }, {
638
+ key: "handleSyncErrors",
639
+ value: function handleSyncErrors(error) {
640
+ if (error instanceof MeetingEndedError) {
641
+ this.callLocusInfoUpdateCallback({
642
+ updateType: LocusInfoUpdateType.MEETING_ENDED
643
+ });
644
+ return true;
645
+ }
646
+ if (error instanceof LocusNotFoundError) {
647
+ this.callLocusInfoUpdateCallback({
648
+ updateType: LocusInfoUpdateType.LOCUS_NOT_FOUND
649
+ });
650
+ this.stop();
651
+ return true;
652
+ }
653
+ return false;
654
+ }
655
+
629
656
  /**
630
657
  * Asynchronously initializes new visible data sets
631
658
  *
@@ -635,18 +662,14 @@ var HashTreeParser = /*#__PURE__*/function () {
635
662
  }, {
636
663
  key: "queueInitForNewVisibleDataSets",
637
664
  value: function queueInitForNewVisibleDataSets(dataSetsRequiringInitialization) {
638
- var _this6 = this;
665
+ var _this5 = this;
639
666
  _loggerProxy.default.logger.info("HashTreeParser#queueInitForNewVisibleDataSets --> ".concat(this.debugId, " queuing initialization of new visible datasets: ").concat(dataSetsRequiringInitialization.map(function (ds) {
640
667
  return ds.name;
641
668
  }).join(', ')));
642
669
  queueMicrotask(function () {
643
- _this6.initializeNewVisibleDataSets(dataSetsRequiringInitialization).catch(function (error) {
644
- if (error instanceof MeetingEndedError) {
645
- _this6.callLocusInfoUpdateCallback({
646
- updateType: LocusInfoUpdateType.MEETING_ENDED
647
- });
648
- } else {
649
- _loggerProxy.default.logger.warn("HashTreeParser#queueInitForNewVisibleDataSets --> ".concat(_this6.debugId, " error while initializing new visible datasets: ").concat(dataSetsRequiringInitialization.map(function (ds) {
670
+ _this5.initializeNewVisibleDataSets(dataSetsRequiringInitialization).catch(function (error) {
671
+ if (!_this5.handleSyncErrors(error)) {
672
+ _loggerProxy.default.logger.warn("HashTreeParser#queueInitForNewVisibleDataSets --> ".concat(_this5.debugId, " error while initializing new visible datasets: ").concat(dataSetsRequiringInitialization.map(function (ds) {
650
673
  return ds.name;
651
674
  }).join(', '), ": "), error);
652
675
  }
@@ -706,7 +729,7 @@ var HashTreeParser = /*#__PURE__*/function () {
706
729
  }, {
707
730
  key: "handleLocusUpdate",
708
731
  value: function handleLocusUpdate(update) {
709
- var _this7 = this;
732
+ var _this6 = this;
710
733
  if (this.state === 'stopped') {
711
734
  return;
712
735
  }
@@ -714,6 +737,7 @@ var HashTreeParser = /*#__PURE__*/function () {
714
737
  locus = update.locus,
715
738
  metadata = update.metadata;
716
739
  if (!dataSets) {
740
+ // this happens for example when we handle GET /loci response
717
741
  _loggerProxy.default.logger.info("HashTreeParser#handleLocusUpdate --> ".concat(this.debugId, " received hash tree update without dataSets"));
718
742
  } else {
719
743
  var _iterator5 = _createForOfIteratorHelper(dataSets),
@@ -743,9 +767,9 @@ var HashTreeParser = /*#__PURE__*/function () {
743
767
 
744
768
  // then process the data in hash trees, if it is a new version, then add it to updatedObjects
745
769
  (0, _keys.default)(leafInfo).forEach(function (dataSetName) {
746
- if (_this7.dataSets[dataSetName]) {
747
- if (_this7.dataSets[dataSetName].hashTree) {
748
- var appliedChangesList = _this7.dataSets[dataSetName].hashTree.putItems(leafInfo[dataSetName].map(function (leaf) {
770
+ if (_this6.dataSets[dataSetName]) {
771
+ if (_this6.dataSets[dataSetName].hashTree) {
772
+ var appliedChangesList = _this6.dataSets[dataSetName].hashTree.putItems(leafInfo[dataSetName].map(function (leaf) {
749
773
  return {
750
774
  id: leaf.id,
751
775
  type: leaf.type,
@@ -772,10 +796,10 @@ var HashTreeParser = /*#__PURE__*/function () {
772
796
  });
773
797
  } else {
774
798
  // no hash tree means that the data set is not visible
775
- _loggerProxy.default.logger.warn("HashTreeParser#handleLocusUpdate --> ".concat(_this7.debugId, " received leaf data for data set \"").concat(dataSetName, "\" that has no hash tree created, ignoring"));
799
+ _loggerProxy.default.logger.warn("HashTreeParser#handleLocusUpdate --> ".concat(_this6.debugId, " received leaf data for data set \"").concat(dataSetName, "\" that has no hash tree created, ignoring"));
776
800
  }
777
801
  } else {
778
- _loggerProxy.default.logger.info("HashTreeParser#handleLocusUpdate --> ".concat(_this7.debugId, " received leaf data for unknown data set \"").concat(dataSetName, "\", ignoring"));
802
+ _loggerProxy.default.logger.info("HashTreeParser#handleLocusUpdate --> ".concat(_this6.debugId, " received leaf data for unknown data set \"").concat(dataSetName, "\", ignoring"));
779
803
  }
780
804
  });
781
805
  if (updatedObjects.length === 0) {
@@ -815,6 +839,21 @@ var HashTreeParser = /*#__PURE__*/function () {
815
839
  }
816
840
  }
817
841
 
842
+ /**
843
+ * Updates the leaf count for a data set, resizing its hash tree accordingly.
844
+ *
845
+ * @param {InternalDataSet} dataSet - The data set to update
846
+ * @param {number} newLeafCount - The new leaf count
847
+ * @returns {void}
848
+ */
849
+ }, {
850
+ key: "updateDataSetLeafCount",
851
+ value: function updateDataSetLeafCount(dataSet, newLeafCount) {
852
+ var _dataSet$hashTree;
853
+ (_dataSet$hashTree = dataSet.hashTree) === null || _dataSet$hashTree === void 0 ? void 0 : _dataSet$hashTree.resize(newLeafCount);
854
+ dataSet.leafCount = newLeafCount;
855
+ }
856
+
818
857
  /**
819
858
  * Checks for changes in the visible data sets based on the updated objects.
820
859
  * @param {HashTreeObject[]} updatedObjects - The list of updated hash tree objects.
@@ -823,7 +862,7 @@ var HashTreeParser = /*#__PURE__*/function () {
823
862
  }, {
824
863
  key: "checkForVisibleDataSetChanges",
825
864
  value: function checkForVisibleDataSetChanges(updatedObjects) {
826
- var _this8 = this;
865
+ var _this7 = this;
827
866
  var removedDataSets = [];
828
867
  var addedDataSets = [];
829
868
 
@@ -832,20 +871,20 @@ var HashTreeParser = /*#__PURE__*/function () {
832
871
  var _object$data;
833
872
  if ((0, _utils.isMetadata)(object) && (_object$data = object.data) !== null && _object$data !== void 0 && _object$data.visibleDataSets) {
834
873
  var newVisibleDataSets = object.data.visibleDataSets.filter(function (vds) {
835
- return !_this8.isExcludedDataSet(vds.name);
874
+ return !_this7.isExcludedDataSet(vds.name);
836
875
  });
837
- removedDataSets = _this8.visibleDataSets.filter(function (ds) {
876
+ removedDataSets = _this7.visibleDataSets.filter(function (ds) {
838
877
  return !newVisibleDataSets.some(function (nvs) {
839
878
  return nvs.name === ds.name;
840
879
  });
841
880
  });
842
881
  addedDataSets = newVisibleDataSets.filter(function (nvs) {
843
- return _this8.visibleDataSets.every(function (ds) {
882
+ return _this7.visibleDataSets.every(function (ds) {
844
883
  return ds.name !== nvs.name;
845
884
  });
846
885
  });
847
886
  if (removedDataSets.length > 0 || addedDataSets.length > 0) {
848
- _loggerProxy.default.logger.info("HashTreeParser#checkForVisibleDataSetChanges --> ".concat(_this8.debugId, " visible data sets change: removed: ").concat(removedDataSets.map(function (ds) {
887
+ _loggerProxy.default.logger.info("HashTreeParser#checkForVisibleDataSetChanges --> ".concat(_this7.debugId, " visible data sets change: removed: ").concat(removedDataSets.map(function (ds) {
849
888
  return ds.name;
850
889
  }).join(', '), ", added: ").concat(addedDataSets.map(function (ds) {
851
890
  return ds.name;
@@ -899,16 +938,16 @@ var HashTreeParser = /*#__PURE__*/function () {
899
938
  }, {
900
939
  key: "processVisibleDataSetChanges",
901
940
  value: function processVisibleDataSetChanges(removedDataSets, addedDataSets, updatedObjects) {
902
- var _this9 = this;
941
+ var _this8 = this;
903
942
  var dataSetsRequiringInitialization = [];
904
943
 
905
944
  // if a visible data set was removed, we need to tell our client that all objects from it are removed
906
945
  var removedObjects = [];
907
946
  removedDataSets.forEach(function (ds) {
908
- var _this9$dataSets$ds$na;
909
- if ((_this9$dataSets$ds$na = _this9.dataSets[ds.name]) !== null && _this9$dataSets$ds$na !== void 0 && _this9$dataSets$ds$na.hashTree) {
910
- for (var i = 0; i < _this9.dataSets[ds.name].hashTree.numLeaves; i += 1) {
911
- removedObjects.push.apply(removedObjects, (0, _toConsumableArray2.default)(_this9.dataSets[ds.name].hashTree.getLeafData(i).map(function (elementId) {
947
+ var _this8$dataSets$ds$na;
948
+ if ((_this8$dataSets$ds$na = _this8.dataSets[ds.name]) !== null && _this8$dataSets$ds$na !== void 0 && _this8$dataSets$ds$na.hashTree) {
949
+ for (var i = 0; i < _this8.dataSets[ds.name].hashTree.numLeaves; i += 1) {
950
+ removedObjects.push.apply(removedObjects, (0, _toConsumableArray2.default)(_this8.dataSets[ds.name].hashTree.getLeafData(i).map(function (elementId) {
912
951
  return {
913
952
  htMeta: {
914
953
  elementId: elementId,
@@ -918,7 +957,7 @@ var HashTreeParser = /*#__PURE__*/function () {
918
957
  };
919
958
  })));
920
959
  }
921
- _this9.deleteHashTree(ds.name);
960
+ _this8.deleteHashTree(ds.name);
922
961
  }
923
962
  });
924
963
  this.visibleDataSets = this.visibleDataSets.filter(function (vds) {
@@ -979,81 +1018,78 @@ var HashTreeParser = /*#__PURE__*/function () {
979
1018
  }, {
980
1019
  key: "initializeNewVisibleDataSets",
981
1020
  value: (function () {
982
- var _initializeNewVisibleDataSets = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4(addedDataSets) {
983
- var _this0 = this;
1021
+ var _initializeNewVisibleDataSets = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee5(addedDataSets) {
1022
+ var _this9 = this;
984
1023
  var allDataSets, _iterator7, _step7, _loop, _t2;
985
- return _regenerator.default.wrap(function (_context5) {
986
- while (1) switch (_context5.prev = _context5.next) {
1024
+ return _regenerator.default.wrap(function (_context6) {
1025
+ while (1) switch (_context6.prev = _context6.next) {
987
1026
  case 0:
988
1027
  if (!(this.state === 'stopped')) {
989
- _context5.next = 1;
1028
+ _context6.next = 1;
990
1029
  break;
991
1030
  }
992
- return _context5.abrupt("return");
1031
+ return _context6.abrupt("return");
993
1032
  case 1:
994
- _context5.next = 2;
1033
+ _context6.next = 2;
995
1034
  return this.getAllVisibleDataSetsFromLocus();
996
1035
  case 2:
997
- allDataSets = _context5.sent;
998
- _iterator7 = _createForOfIteratorHelper(addedDataSets);
999
- _context5.prev = 3;
1036
+ allDataSets = _context6.sent;
1037
+ _iterator7 = _createForOfIteratorHelper((0, _utils.sortByInitPriority)(addedDataSets, _constants3.DATA_SET_INIT_PRIORITY));
1038
+ _context6.prev = 3;
1000
1039
  _loop = /*#__PURE__*/_regenerator.default.mark(function _loop() {
1001
- var ds, dataSetInfo, updates;
1002
- return _regenerator.default.wrap(function (_context4) {
1003
- while (1) switch (_context4.prev = _context4.next) {
1040
+ var ds, dataSetInfo;
1041
+ return _regenerator.default.wrap(function (_context5) {
1042
+ while (1) switch (_context5.prev = _context5.next) {
1004
1043
  case 0:
1005
1044
  ds = _step7.value;
1006
1045
  dataSetInfo = allDataSets.find(function (d) {
1007
1046
  return d.name === ds.name;
1008
1047
  });
1009
- _loggerProxy.default.logger.info("HashTreeParser#initializeNewVisibleDataSets --> ".concat(_this0.debugId, " initializing data set \"").concat(ds.name, "\""));
1048
+ _loggerProxy.default.logger.info("HashTreeParser#initializeNewVisibleDataSets --> ".concat(_this9.debugId, " initializing data set \"").concat(ds.name, "\""));
1010
1049
  if (dataSetInfo) {
1011
- _context4.next = 1;
1050
+ _context5.next = 1;
1012
1051
  break;
1013
1052
  }
1014
- _loggerProxy.default.logger.warn("HashTreeParser#initializeNewVisibleDataSets --> ".concat(_this0.debugId, " missing info about data set \"").concat(ds.name, "\" in Locus response from visibleDataSetsUrl"));
1015
- _context4.next = 3;
1053
+ _loggerProxy.default.logger.warn("HashTreeParser#initializeNewVisibleDataSets --> ".concat(_this9.debugId, " missing info about data set \"").concat(ds.name, "\" in Locus response from visibleDataSetsUrl"));
1054
+ _context5.next = 2;
1016
1055
  break;
1017
1056
  case 1:
1018
- _context4.next = 2;
1019
- return _this0.initializeNewVisibleDataSet(ds, dataSetInfo);
1057
+ _context5.next = 2;
1058
+ return _this9.initializeNewVisibleDataSet(ds, dataSetInfo);
1020
1059
  case 2:
1021
- updates = _context4.sent;
1022
- _this0.callLocusInfoUpdateCallback(updates);
1023
- case 3:
1024
1060
  case "end":
1025
- return _context4.stop();
1061
+ return _context5.stop();
1026
1062
  }
1027
1063
  }, _loop);
1028
1064
  });
1029
1065
  _iterator7.s();
1030
1066
  case 4:
1031
1067
  if ((_step7 = _iterator7.n()).done) {
1032
- _context5.next = 6;
1068
+ _context6.next = 6;
1033
1069
  break;
1034
1070
  }
1035
- return _context5.delegateYield(_loop(), "t0", 5);
1071
+ return _context6.delegateYield(_loop(), "t0", 5);
1036
1072
  case 5:
1037
- _context5.next = 4;
1073
+ _context6.next = 4;
1038
1074
  break;
1039
1075
  case 6:
1040
- _context5.next = 8;
1076
+ _context6.next = 8;
1041
1077
  break;
1042
1078
  case 7:
1043
- _context5.prev = 7;
1044
- _t2 = _context5["catch"](3);
1079
+ _context6.prev = 7;
1080
+ _t2 = _context6["catch"](3);
1045
1081
  _iterator7.e(_t2);
1046
1082
  case 8:
1047
- _context5.prev = 8;
1083
+ _context6.prev = 8;
1048
1084
  _iterator7.f();
1049
- return _context5.finish(8);
1085
+ return _context6.finish(8);
1050
1086
  case 9:
1051
1087
  case "end":
1052
- return _context5.stop();
1088
+ return _context6.stop();
1053
1089
  }
1054
- }, _callee4, this, [[3, 7, 8, 9]]);
1090
+ }, _callee5, this, [[3, 7, 8, 9]]);
1055
1091
  }));
1056
- function initializeNewVisibleDataSets(_x5) {
1092
+ function initializeNewVisibleDataSets(_x7) {
1057
1093
  return _initializeNewVisibleDataSets.apply(this, arguments);
1058
1094
  }
1059
1095
  return initializeNewVisibleDataSets;
@@ -1070,8 +1106,7 @@ var HashTreeParser = /*#__PURE__*/function () {
1070
1106
  key: "parseMessage",
1071
1107
  value: function parseMessage(message, debugText) {
1072
1108
  var _message$locusStateEl,
1073
- _this1 = this,
1074
- _message$locusStateEl2;
1109
+ _this0 = this;
1075
1110
  if (this.state === 'stopped') {
1076
1111
  return [];
1077
1112
  }
@@ -1086,7 +1121,7 @@ var HashTreeParser = /*#__PURE__*/function () {
1086
1121
  // first, update our metadata about the datasets with info from the message
1087
1122
  this.visibleDataSetsUrl = visibleDataSetsUrl;
1088
1123
  dataSets.forEach(function (dataSet) {
1089
- return _this1.updateDataSetInfo(dataSet);
1124
+ return _this0.updateDataSetInfo(dataSet);
1090
1125
  });
1091
1126
  var updatedObjects = [];
1092
1127
 
@@ -1106,9 +1141,9 @@ var HashTreeParser = /*#__PURE__*/function () {
1106
1141
  _step8;
1107
1142
  try {
1108
1143
  for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
1109
- var _this1$dataSets$dataS;
1144
+ var _this0$dataSets$dataS;
1110
1145
  var dataSetName = _step8.value;
1111
- var hashTree = (_this1$dataSets$dataS = _this1.dataSets[dataSetName]) === null || _this1$dataSets$dataS === void 0 ? void 0 : _this1$dataSets$dataS.hashTree;
1146
+ var hashTree = (_this0$dataSets$dataS = _this0.dataSets[dataSetName]) === null || _this0$dataSets$dataS === void 0 ? void 0 : _this0$dataSets$dataS.hashTree;
1112
1147
  if (hashTree && object.data) {
1113
1148
  if (hashTree.putItem(object.htMeta.elementId)) {
1114
1149
  updatedMetadataObjects.push(object);
@@ -1130,13 +1165,13 @@ var HashTreeParser = /*#__PURE__*/function () {
1130
1165
  dataSetsRequiringInitialization = this.processVisibleDataSetChanges(removedDataSets, addedDataSets, updatedObjects);
1131
1166
  }
1132
1167
  }
1133
- if (((_message$locusStateEl2 = message.locusStateElements) === null || _message$locusStateEl2 === void 0 ? void 0 : _message$locusStateEl2.length) > 0) {
1168
+ if (message.locusStateElements && message.locusStateElements.length > 0) {
1134
1169
  // by this point we now have this.dataSets setup for data sets from this message
1135
1170
  // and hash trees created for the new visible data sets,
1136
1171
  // so we can now process all the updates from the message
1137
1172
  dataSets.forEach(function (dataSet) {
1138
- if (_this1.dataSets[dataSet.name]) {
1139
- var hashTree = _this1.dataSets[dataSet.name].hashTree;
1173
+ if (_this0.dataSets[dataSet.name]) {
1174
+ var hashTree = _this0.dataSets[dataSet.name].hashTree;
1140
1175
  if (hashTree) {
1141
1176
  var locusStateElementsForThisSet = message.locusStateElements.filter(function (object) {
1142
1177
  return object.htMeta.dataSetNames.includes(dataSet.name);
@@ -1160,10 +1195,10 @@ var HashTreeParser = /*#__PURE__*/function () {
1160
1195
  }
1161
1196
  });
1162
1197
  } else {
1163
- _loggerProxy.default.logger.info("Locus-info:index#parseMessage --> ".concat(_this1.debugId, " unexpected (not visible) dataSet ").concat(dataSet.name, " received in hash tree message"));
1198
+ _loggerProxy.default.logger.info("Locus-info:index#parseMessage --> ".concat(_this0.debugId, " unexpected (not visible) dataSet ").concat(dataSet.name, " received in hash tree message"));
1164
1199
  }
1165
1200
  }
1166
- _this1.runSyncAlgorithm(dataSet);
1201
+ _this0.runSyncAlgorithm(dataSet);
1167
1202
  });
1168
1203
  }
1169
1204
  if (dataSetsRequiringInitialization.length > 0) {
@@ -1220,25 +1255,25 @@ var HashTreeParser = /*#__PURE__*/function () {
1220
1255
  }, {
1221
1256
  key: "callLocusInfoUpdateCallback",
1222
1257
  value: function callLocusInfoUpdateCallback(updates) {
1223
- var _this10 = this;
1258
+ var _updates$updatedObjec,
1259
+ _this1 = this;
1224
1260
  if (this.state === 'stopped') {
1225
1261
  return;
1226
1262
  }
1227
- var updateType = updates.updateType,
1228
- updatedObjects = updates.updatedObjects;
1229
- if (updateType === LocusInfoUpdateType.OBJECTS_UPDATED && (updatedObjects === null || updatedObjects === void 0 ? void 0 : updatedObjects.length) > 0) {
1263
+ var updateType = updates.updateType;
1264
+ if (updateType === LocusInfoUpdateType.OBJECTS_UPDATED && ((_updates$updatedObjec = updates.updatedObjects) === null || _updates$updatedObjec === void 0 ? void 0 : _updates$updatedObjec.length) > 0) {
1230
1265
  // Filter out updates for objects that already have a higher version in their datasets,
1231
1266
  // or removals for objects that still exist in any of their datasets
1232
- var filteredUpdates = updatedObjects.filter(function (object) {
1267
+ var filteredUpdates = updates.updatedObjects.filter(function (object) {
1233
1268
  var elementId = object.htMeta.elementId;
1234
1269
  var type = elementId.type,
1235
1270
  id = elementId.id,
1236
1271
  version = elementId.version;
1237
1272
 
1238
1273
  // Check all datasets
1239
- for (var _i2 = 0, _Object$keys3 = (0, _keys.default)(_this10.dataSets); _i2 < _Object$keys3.length; _i2++) {
1274
+ for (var _i2 = 0, _Object$keys3 = (0, _keys.default)(_this1.dataSets); _i2 < _Object$keys3.length; _i2++) {
1240
1275
  var dataSetName = _Object$keys3[_i2];
1241
- var dataSet = _this10.dataSets[dataSetName];
1276
+ var dataSet = _this1.dataSets[dataSetName];
1242
1277
 
1243
1278
  // only visible datasets have hash trees set
1244
1279
  if (dataSet !== null && dataSet !== void 0 && dataSet.hashTree) {
@@ -1247,12 +1282,12 @@ var HashTreeParser = /*#__PURE__*/function () {
1247
1282
  if (object.data) {
1248
1283
  // For updates: filter out if any dataset has a higher version
1249
1284
  if (existingVersion > version) {
1250
- _loggerProxy.default.logger.info("HashTreeParser#callLocusInfoUpdateCallback --> ".concat(_this10.debugId, " Filtering out update for ").concat(type, ":").concat(id, " v").concat(version, " because dataset \"").concat(dataSetName, "\" has v").concat(existingVersion));
1285
+ _loggerProxy.default.logger.info("HashTreeParser#callLocusInfoUpdateCallback --> ".concat(_this1.debugId, " Filtering out update for ").concat(type, ":").concat(id, " v").concat(version, " because dataset \"").concat(dataSetName, "\" has v").concat(existingVersion));
1251
1286
  return false;
1252
1287
  }
1253
1288
  } else if (existingVersion >= version) {
1254
1289
  // For removals: filter out if the object still exists in any dataset
1255
- _loggerProxy.default.logger.info("HashTreeParser#callLocusInfoUpdateCallback --> ".concat(_this10.debugId, " Filtering out removal for ").concat(type, ":").concat(id, " v").concat(version, " because dataset \"").concat(dataSetName, "\" still has v").concat(existingVersion));
1290
+ _loggerProxy.default.logger.info("HashTreeParser#callLocusInfoUpdateCallback --> ".concat(_this1.debugId, " Filtering out removal for ").concat(type, ":").concat(id, " v").concat(version, " because dataset \"").concat(dataSetName, "\" still has v").concat(existingVersion));
1256
1291
  return false;
1257
1292
  }
1258
1293
  }
@@ -1261,13 +1296,14 @@ var HashTreeParser = /*#__PURE__*/function () {
1261
1296
  return true;
1262
1297
  });
1263
1298
  if (filteredUpdates.length > 0) {
1264
- this.locusInfoUpdateCallback(updateType, {
1299
+ this.locusInfoUpdateCallback({
1300
+ updateType: updateType,
1265
1301
  updatedObjects: filteredUpdates
1266
1302
  });
1267
1303
  }
1268
1304
  } else if (updateType !== LocusInfoUpdateType.OBJECTS_UPDATED) {
1269
- this.locusInfoUpdateCallback(updateType, {
1270
- updatedObjects: updatedObjects
1305
+ this.locusInfoUpdateCallback({
1306
+ updateType: updateType
1271
1307
  });
1272
1308
  }
1273
1309
  }
@@ -1291,102 +1327,289 @@ var HashTreeParser = /*#__PURE__*/function () {
1291
1327
  * Performs a sync for the given data set.
1292
1328
  *
1293
1329
  * @param {InternalDataSet} dataSet - The data set to sync
1294
- * @param {string} rootHash - Our current root hash for this data set
1295
1330
  * @param {string} reason - The reason for the sync (used for logging)
1331
+ * @param {boolean} [isInitialization] - Whether this is an initialization sync (sends empty leaves data instead of comparing hashes)
1296
1332
  * @returns {Promise<void>}
1297
1333
  */
1298
1334
  }, {
1299
1335
  key: "performSync",
1300
1336
  value: (function () {
1301
- var _performSync = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee5(dataSet, rootHash, reason) {
1302
- var mismatchedLeavesData, receivedHashes, _yield$this$getHashes, hashes, latestDataSetInfo, mismatchedLeaveIndexes, syncResponse, _t3, _t4;
1303
- return _regenerator.default.wrap(function (_context6) {
1304
- while (1) switch (_context6.prev = _context6.next) {
1337
+ var _performSync = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee6(dataSet, reason, isInitialization) {
1338
+ var hashTree, rootHash, leavesData, receivedHashes, hashesResult, mismatchedLeaveIndexes, syncResponse, _t3, _t4;
1339
+ return _regenerator.default.wrap(function (_context7) {
1340
+ while (1) switch (_context7.prev = _context7.next) {
1305
1341
  case 0:
1306
1342
  if (dataSet.hashTree) {
1307
- _context6.next = 1;
1343
+ _context7.next = 1;
1308
1344
  break;
1309
1345
  }
1310
- return _context6.abrupt("return");
1346
+ return _context7.abrupt("return");
1311
1347
  case 1:
1312
- _context6.prev = 1;
1348
+ hashTree = dataSet.hashTree;
1349
+ rootHash = hashTree.getRootHash();
1350
+ _context7.prev = 2;
1313
1351
  _loggerProxy.default.logger.info("HashTreeParser#performSync --> ".concat(this.debugId, " ").concat(reason, ", syncing data set \"").concat(dataSet.name, "\""));
1314
- mismatchedLeavesData = {};
1352
+ leavesData = {};
1353
+ if (isInitialization) {
1354
+ _context7.next = 10;
1355
+ break;
1356
+ }
1315
1357
  if (!(dataSet.leafCount !== 1)) {
1316
- _context6.next = 7;
1358
+ _context7.next = 9;
1317
1359
  break;
1318
1360
  }
1319
- _context6.prev = 2;
1320
- _context6.next = 3;
1361
+ _context7.prev = 3;
1362
+ _context7.next = 4;
1321
1363
  return this.getHashesFromLocus(dataSet.name, rootHash);
1322
- case 3:
1323
- _yield$this$getHashes = _context6.sent;
1324
- hashes = _yield$this$getHashes.hashes;
1325
- latestDataSetInfo = _yield$this$getHashes.dataSet;
1326
- receivedHashes = hashes;
1327
- dataSet.hashTree.resize(latestDataSetInfo.leafCount);
1328
- _context6.next = 6;
1329
- break;
1330
1364
  case 4:
1331
- _context6.prev = 4;
1332
- _t3 = _context6["catch"](2);
1333
- if (!(_t3.statusCode === 409)) {
1334
- _context6.next = 5;
1365
+ hashesResult = _context7.sent;
1366
+ if (hashesResult) {
1367
+ _context7.next = 5;
1368
+ break;
1369
+ }
1370
+ return _context7.abrupt("return");
1371
+ case 5:
1372
+ receivedHashes = hashesResult.hashes;
1373
+ this.updateDataSetLeafCount(dataSet, hashesResult.dataSet.leafCount);
1374
+ _context7.next = 8;
1375
+ break;
1376
+ case 6:
1377
+ _context7.prev = 6;
1378
+ _t3 = _context7["catch"](3);
1379
+ if (!((_t3 === null || _t3 === void 0 ? void 0 : _t3.statusCode) === 409)) {
1380
+ _context7.next = 7;
1335
1381
  break;
1336
1382
  }
1337
1383
  // this is a leaf count mismatch, we should do nothing, just wait for another heartbeat message from Locus
1338
1384
  _loggerProxy.default.logger.info("HashTreeParser#getHashesFromLocus --> ".concat(this.debugId, " Got 409 when fetching hashes for data set \"").concat(dataSet.name, "\": ").concat(_t3.message));
1339
- return _context6.abrupt("return");
1340
- case 5:
1385
+ return _context7.abrupt("return");
1386
+ case 7:
1341
1387
  throw _t3;
1342
- case 6:
1388
+ case 8:
1343
1389
  // identify mismatched leaves
1344
- mismatchedLeaveIndexes = dataSet.hashTree.diffHashes(receivedHashes);
1390
+ mismatchedLeaveIndexes = hashTree.diffHashes(receivedHashes);
1345
1391
  mismatchedLeaveIndexes.forEach(function (index) {
1346
- mismatchedLeavesData[index] = dataSet.hashTree.getLeafData(index);
1392
+ leavesData[index] = hashTree.getLeafData(index);
1347
1393
  });
1348
- _context6.next = 8;
1394
+ _context7.next = 10;
1349
1395
  break;
1350
- case 7:
1351
- mismatchedLeavesData[0] = dataSet.hashTree.getLeafData(0);
1352
- case 8:
1353
- if (!((0, _keys.default)(mismatchedLeavesData).length > 0)) {
1354
- _context6.next = 10;
1396
+ case 9:
1397
+ leavesData = {
1398
+ 0: hashTree.getLeafData(0)
1399
+ };
1400
+ case 10:
1401
+ // request sync for mismatched leaves
1402
+ syncResponse = null;
1403
+ if (!isInitialization) {
1404
+ _context7.next = 12;
1355
1405
  break;
1356
1406
  }
1357
- _context6.next = 9;
1358
- return this.sendSyncRequestToLocus(dataSet, mismatchedLeavesData);
1359
- case 9:
1360
- syncResponse = _context6.sent;
1407
+ _context7.next = 11;
1408
+ return this.sendSyncRequestToLocus(dataSet, {
1409
+ isInitialization: true
1410
+ });
1411
+ case 11:
1412
+ syncResponse = _context7.sent;
1413
+ _context7.next = 14;
1414
+ break;
1415
+ case 12:
1416
+ if (!((0, _keys.default)(leavesData).length > 0)) {
1417
+ _context7.next = 14;
1418
+ break;
1419
+ }
1420
+ _context7.next = 13;
1421
+ return this.sendSyncRequestToLocus(dataSet, {
1422
+ mismatchedLeavesData: leavesData
1423
+ });
1424
+ case 13:
1425
+ syncResponse = _context7.sent;
1426
+ case 14:
1361
1427
  // sync API may return nothing (in that case data will arrive via messages)
1362
1428
  // or it may return a response in the same format as messages
1429
+ // We still need to restart the sync timer as a safety net in case the messages don't arrive.
1430
+ this.runSyncAlgorithm(dataSet);
1363
1431
  if (syncResponse) {
1432
+ // the format of sync response is the same as messages, so we can reuse the same handler
1364
1433
  this.handleMessage(syncResponse, 'via sync API');
1365
1434
  }
1366
- case 10:
1367
- _context6.next = 12;
1435
+ _context7.next = 16;
1368
1436
  break;
1369
- case 11:
1370
- _context6.prev = 11;
1371
- _t4 = _context6["catch"](1);
1372
- if (_t4 instanceof MeetingEndedError) {
1373
- this.callLocusInfoUpdateCallback({
1374
- updateType: LocusInfoUpdateType.MEETING_ENDED
1375
- });
1376
- } else {
1437
+ case 15:
1438
+ _context7.prev = 15;
1439
+ _t4 = _context7["catch"](2);
1440
+ if (!this.handleSyncErrors(_t4)) {
1377
1441
  _loggerProxy.default.logger.warn("HashTreeParser#performSync --> ".concat(this.debugId, " error during sync for data set \"").concat(dataSet.name, "\":"), _t4);
1378
1442
  }
1379
- case 12:
1443
+ case 16:
1380
1444
  case "end":
1381
- return _context6.stop();
1445
+ return _context7.stop();
1382
1446
  }
1383
- }, _callee5, this, [[1, 11], [2, 4]]);
1447
+ }, _callee6, this, [[2, 15], [3, 6]]);
1384
1448
  }));
1385
- function performSync(_x6, _x7, _x8) {
1449
+ function performSync(_x8, _x9, _x0) {
1386
1450
  return _performSync.apply(this, arguments);
1387
1451
  }
1388
1452
  return performSync;
1389
1453
  }()
1454
+ /**
1455
+ * Enqueues a sync for the given data set. If the data set is already in the queue, the request is ignored.
1456
+ * This ensures that all syncs are executed sequentially and no more than 1 sync runs at a time.
1457
+ *
1458
+ * @param {string} dataSetName - The name of the data set to sync
1459
+ * @param {string} reason - The reason for the sync (used for logging)
1460
+ * @param {boolean} [isInitialization=false] - Whether this is an initialization sync (uses empty leaves data instead of hash comparison)
1461
+ * @returns {void}
1462
+ */
1463
+ )
1464
+ }, {
1465
+ key: "enqueueSyncForDataset",
1466
+ value: function enqueueSyncForDataset(dataSetName, reason) {
1467
+ var isInitialization = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1468
+ if (this.state === 'stopped') return;
1469
+ var existingEntry = this.syncQueue.find(function (entry) {
1470
+ return entry.dataSetName === dataSetName;
1471
+ });
1472
+ if (existingEntry) {
1473
+ if (isInitialization) {
1474
+ existingEntry.isInitialization = true;
1475
+ }
1476
+ _loggerProxy.default.logger.info("HashTreeParser#enqueueSyncForDataset --> ".concat(this.debugId, " data set \"").concat(dataSetName, "\" already in sync queue, skipping"));
1477
+ return;
1478
+ }
1479
+ this.syncQueue.push({
1480
+ dataSetName: dataSetName,
1481
+ reason: reason,
1482
+ isInitialization: isInitialization
1483
+ });
1484
+ if (!this.isSyncInProgress) {
1485
+ this.syncQueueProcessingPromise = this.processSyncQueue();
1486
+ }
1487
+ }
1488
+
1489
+ /**
1490
+ * Processes the sync queue sequentially. Only one instance of this method runs at a time.
1491
+ *
1492
+ * @returns {Promise<void>}
1493
+ */
1494
+ }, {
1495
+ key: "processSyncQueue",
1496
+ value: (function () {
1497
+ var _processSyncQueue = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee7() {
1498
+ var _ref7, dataSetName, reason, isInitialization, dataSet;
1499
+ return _regenerator.default.wrap(function (_context8) {
1500
+ while (1) switch (_context8.prev = _context8.next) {
1501
+ case 0:
1502
+ if (!this.isSyncInProgress) {
1503
+ _context8.next = 1;
1504
+ break;
1505
+ }
1506
+ return _context8.abrupt("return");
1507
+ case 1:
1508
+ this.isSyncInProgress = true;
1509
+ _context8.prev = 2;
1510
+ case 3:
1511
+ if (!(this.syncQueue.length > 0 && this.state !== 'stopped')) {
1512
+ _context8.next = 6;
1513
+ break;
1514
+ }
1515
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1516
+ _ref7 = this.syncQueue.shift(), dataSetName = _ref7.dataSetName, reason = _ref7.reason, isInitialization = _ref7.isInitialization;
1517
+ dataSet = this.dataSets[dataSetName];
1518
+ if (dataSet !== null && dataSet !== void 0 && dataSet.hashTree) {
1519
+ _context8.next = 4;
1520
+ break;
1521
+ }
1522
+ return _context8.abrupt("continue", 3);
1523
+ case 4:
1524
+ _context8.next = 5;
1525
+ return this.performSync(dataSet, reason, isInitialization);
1526
+ case 5:
1527
+ _context8.next = 3;
1528
+ break;
1529
+ case 6:
1530
+ _context8.prev = 6;
1531
+ this.isSyncInProgress = false;
1532
+ return _context8.finish(6);
1533
+ case 7:
1534
+ case "end":
1535
+ return _context8.stop();
1536
+ }
1537
+ }, _callee7, this, [[2,, 6, 7]]);
1538
+ }));
1539
+ function processSyncQueue() {
1540
+ return _processSyncQueue.apply(this, arguments);
1541
+ }
1542
+ return processSyncQueue;
1543
+ }()
1544
+ /**
1545
+ * Syncs all data sets that have hash trees, one by one in sequence, using the priority order
1546
+ * provided by sortByInitPriority(). Does nothing if the parser is stopped or if a syncAllDatasets
1547
+ * call is already in progress.
1548
+ *
1549
+ * @returns {Promise<void>}
1550
+ */
1551
+ )
1552
+ }, {
1553
+ key: "syncAllDatasets",
1554
+ value: (function () {
1555
+ var _syncAllDatasets = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee8() {
1556
+ var dataSetsWithHashTrees, sorted, _iterator9, _step9, ds;
1557
+ return _regenerator.default.wrap(function (_context9) {
1558
+ while (1) switch (_context9.prev = _context9.next) {
1559
+ case 0:
1560
+ if (!(this.state === 'stopped')) {
1561
+ _context9.next = 1;
1562
+ break;
1563
+ }
1564
+ return _context9.abrupt("return");
1565
+ case 1:
1566
+ if (!this.isSyncAllInProgress) {
1567
+ _context9.next = 2;
1568
+ break;
1569
+ }
1570
+ return _context9.abrupt("return");
1571
+ case 2:
1572
+ this.isSyncAllInProgress = true;
1573
+ _context9.prev = 3;
1574
+ dataSetsWithHashTrees = (0, _values.default)(this.dataSets).filter(function (dataSet) {
1575
+ return dataSet === null || dataSet === void 0 ? void 0 : dataSet.hashTree;
1576
+ }).map(function (dataSet) {
1577
+ return {
1578
+ name: dataSet.name
1579
+ };
1580
+ });
1581
+ sorted = (0, _utils.sortByInitPriority)(dataSetsWithHashTrees, _constants3.DATA_SET_INIT_PRIORITY);
1582
+ _loggerProxy.default.logger.info("HashTreeParser#syncAllDatasets --> ".concat(this.debugId, " syncing datasets: ").concat(sorted.map(function (ds) {
1583
+ return ds.name;
1584
+ }).join(', ')));
1585
+ _iterator9 = _createForOfIteratorHelper(sorted);
1586
+ try {
1587
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
1588
+ ds = _step9.value;
1589
+ this.enqueueSyncForDataset(ds.name, 'syncAllDatasets');
1590
+ }
1591
+ } catch (err) {
1592
+ _iterator9.e(err);
1593
+ } finally {
1594
+ _iterator9.f();
1595
+ }
1596
+ _context9.next = 4;
1597
+ return this.syncQueueProcessingPromise;
1598
+ case 4:
1599
+ _context9.prev = 4;
1600
+ this.isSyncAllInProgress = false;
1601
+ return _context9.finish(4);
1602
+ case 5:
1603
+ case "end":
1604
+ return _context9.stop();
1605
+ }
1606
+ }, _callee8, this, [[3,, 4, 5]]);
1607
+ }));
1608
+ function syncAllDatasets() {
1609
+ return _syncAllDatasets.apply(this, arguments);
1610
+ }
1611
+ return syncAllDatasets;
1612
+ }()
1390
1613
  /**
1391
1614
  * Runs the sync algorithm for the given data set.
1392
1615
  *
@@ -1397,7 +1620,7 @@ var HashTreeParser = /*#__PURE__*/function () {
1397
1620
  }, {
1398
1621
  key: "runSyncAlgorithm",
1399
1622
  value: function runSyncAlgorithm(receivedDataSet) {
1400
- var _this11 = this;
1623
+ var _this10 = this;
1401
1624
  var dataSet = this.dataSets[receivedDataSet.name];
1402
1625
  if (!dataSet) {
1403
1626
  _loggerProxy.default.logger.warn("HashTreeParser#runSyncAlgorithm --> ".concat(this.debugId, " No data set found for ").concat(receivedDataSet.name, ", skipping sync algorithm"));
@@ -1408,47 +1631,25 @@ var HashTreeParser = /*#__PURE__*/function () {
1408
1631
  return;
1409
1632
  }
1410
1633
  dataSet.hashTree.resize(receivedDataSet.leafCount);
1411
-
1412
- // temporary log for the workshop // todo: remove
1413
- var ourCurrentRootHash = dataSet.hashTree.getRootHash();
1414
- _loggerProxy.default.logger.info("HashTreeParser#runSyncAlgorithm --> ".concat(this.debugId, " dataSet=\"").concat(dataSet.name, "\" version=").concat(dataSet.version, " hashes before starting timer: ours=").concat(ourCurrentRootHash, " Locus=").concat(dataSet.root));
1415
1634
  var delay = dataSet.idleMs + this.getWeightedBackoffTime(dataSet.backoff);
1416
1635
  if (delay > 0) {
1417
1636
  if (dataSet.timer) {
1418
1637
  clearTimeout(dataSet.timer);
1419
1638
  }
1420
1639
  _loggerProxy.default.logger.info("HashTreeParser#runSyncAlgorithm --> ".concat(this.debugId, " setting \"").concat(dataSet.name, "\" sync timer for ").concat(delay));
1421
- dataSet.timer = setTimeout(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee6() {
1422
- var rootHash;
1423
- return _regenerator.default.wrap(function (_context7) {
1424
- while (1) switch (_context7.prev = _context7.next) {
1425
- case 0:
1426
- dataSet.timer = undefined;
1427
- if (dataSet.hashTree) {
1428
- _context7.next = 1;
1429
- break;
1430
- }
1431
- _loggerProxy.default.logger.warn("HashTreeParser#runSyncAlgorithm --> ".concat(_this11.debugId, " Data set \"").concat(dataSet.name, "\" no longer has a hash tree, cannot run sync algorithm"));
1432
- return _context7.abrupt("return");
1433
- case 1:
1434
- rootHash = dataSet.hashTree.getRootHash();
1435
- if (!(dataSet.root !== rootHash)) {
1436
- _context7.next = 3;
1437
- break;
1438
- }
1439
- _context7.next = 2;
1440
- return _this11.performSync(dataSet, rootHash, "Root hash mismatch: received=".concat(dataSet.root, ", ours=").concat(rootHash));
1441
- case 2:
1442
- _context7.next = 4;
1443
- break;
1444
- case 3:
1445
- _loggerProxy.default.logger.info("HashTreeParser#runSyncAlgorithm --> ".concat(_this11.debugId, " \"").concat(dataSet.name, "\" root hash matching: ").concat(rootHash, ", version=").concat(dataSet.version));
1446
- case 4:
1447
- case "end":
1448
- return _context7.stop();
1449
- }
1450
- }, _callee6);
1451
- })), delay);
1640
+ dataSet.timer = setTimeout(function () {
1641
+ dataSet.timer = undefined;
1642
+ if (!dataSet.hashTree) {
1643
+ _loggerProxy.default.logger.warn("HashTreeParser#runSyncAlgorithm --> ".concat(_this10.debugId, " Data set \"").concat(dataSet.name, "\" no longer has a hash tree, cannot run sync algorithm"));
1644
+ return;
1645
+ }
1646
+ var rootHash = dataSet.hashTree.getRootHash();
1647
+ if (dataSet.root !== rootHash) {
1648
+ _this10.enqueueSyncForDataset(dataSet.name, "Root hash mismatch: received=".concat(dataSet.root, ", ours=").concat(rootHash));
1649
+ } else {
1650
+ _loggerProxy.default.logger.info("HashTreeParser#runSyncAlgorithm --> ".concat(_this10.debugId, " \"").concat(dataSet.name, "\" root hash matching: ").concat(rootHash, ", version=").concat(dataSet.version));
1651
+ }
1652
+ }, delay);
1452
1653
  } else {
1453
1654
  _loggerProxy.default.logger.info("HashTreeParser#runSyncAlgorithm --> ".concat(this.debugId, " No delay for \"").concat(dataSet.name, "\" data set, skipping sync timer reset/setup"));
1454
1655
  }
@@ -1466,16 +1667,16 @@ var HashTreeParser = /*#__PURE__*/function () {
1466
1667
  }, {
1467
1668
  key: "resetHeartbeatWatchdogs",
1468
1669
  value: function resetHeartbeatWatchdogs(receivedDataSets) {
1469
- var _this12 = this;
1670
+ var _this11 = this;
1470
1671
  if (!this.heartbeatIntervalMs) {
1471
1672
  return;
1472
1673
  }
1473
- var _iterator9 = _createForOfIteratorHelper(receivedDataSets),
1474
- _step9;
1674
+ var _iterator0 = _createForOfIteratorHelper(receivedDataSets),
1675
+ _step0;
1475
1676
  try {
1476
1677
  var _loop2 = function _loop2() {
1477
- var receivedDataSet = _step9.value;
1478
- var dataSet = _this12.dataSets[receivedDataSet.name];
1678
+ var receivedDataSet = _step0.value;
1679
+ var dataSet = _this11.dataSets[receivedDataSet.name];
1479
1680
  if (!(dataSet !== null && dataSet !== void 0 && dataSet.hashTree)) {
1480
1681
  // eslint-disable-next-line no-continue
1481
1682
  return 1; // continue
@@ -1484,30 +1685,22 @@ var HashTreeParser = /*#__PURE__*/function () {
1484
1685
  clearTimeout(dataSet.heartbeatWatchdogTimer);
1485
1686
  dataSet.heartbeatWatchdogTimer = undefined;
1486
1687
  }
1487
- var backoffTime = _this12.getWeightedBackoffTime(dataSet.backoff);
1488
- var delay = _this12.heartbeatIntervalMs + backoffTime;
1489
- dataSet.heartbeatWatchdogTimer = setTimeout(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee7() {
1490
- return _regenerator.default.wrap(function (_context8) {
1491
- while (1) switch (_context8.prev = _context8.next) {
1492
- case 0:
1493
- dataSet.heartbeatWatchdogTimer = undefined;
1494
- _loggerProxy.default.logger.warn("HashTreeParser#resetHeartbeatWatchdogs --> ".concat(_this12.debugId, " Heartbeat watchdog fired for data set \"").concat(dataSet.name, "\" - no heartbeat received within expected interval, initiating sync"));
1495
- _context8.next = 1;
1496
- return _this12.performSync(dataSet, dataSet.hashTree.getRootHash(), "heartbeat watchdog expired");
1497
- case 1:
1498
- case "end":
1499
- return _context8.stop();
1500
- }
1501
- }, _callee7);
1502
- })), delay);
1688
+ var backoffTime = _this11.getWeightedBackoffTime(dataSet.backoff);
1689
+ var delay = _this11.heartbeatIntervalMs + backoffTime;
1690
+ dataSet.heartbeatWatchdogTimer = setTimeout(function () {
1691
+ dataSet.heartbeatWatchdogTimer = undefined;
1692
+ _loggerProxy.default.logger.warn("HashTreeParser#resetHeartbeatWatchdogs --> ".concat(_this11.debugId, " Heartbeat watchdog fired for data set \"").concat(dataSet.name, "\" - no heartbeat received within expected interval, initiating sync"));
1693
+ _this11.enqueueSyncForDataset(dataSet.name, "heartbeat watchdog expired");
1694
+ _this11.resetHeartbeatWatchdogs([dataSet]);
1695
+ }, delay);
1503
1696
  };
1504
- for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
1697
+ for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
1505
1698
  if (_loop2()) continue;
1506
1699
  }
1507
1700
  } catch (err) {
1508
- _iterator9.e(err);
1701
+ _iterator0.e(err);
1509
1702
  } finally {
1510
- _iterator9.f();
1703
+ _iterator0.f();
1511
1704
  }
1512
1705
  }
1513
1706
 
@@ -1541,6 +1734,7 @@ var HashTreeParser = /*#__PURE__*/function () {
1541
1734
  value: function stop() {
1542
1735
  _loggerProxy.default.logger.info("HashTreeParser#stop --> ".concat(this.debugId, " Stopping HashTreeParser, clearing timers and hash trees"));
1543
1736
  this.stopAllTimers();
1737
+ this.syncQueue = [];
1544
1738
  (0, _values.default)(this.dataSets).forEach(function (dataSet) {
1545
1739
  dataSet.hashTree = undefined;
1546
1740
  });
@@ -1549,29 +1743,41 @@ var HashTreeParser = /*#__PURE__*/function () {
1549
1743
  }
1550
1744
 
1551
1745
  /**
1552
- * Resumes the HashTreeParser that was previously stopped.
1746
+ * Cleans up the HashTreeParser, stopping all timers and clearing all internal state.
1747
+ * After calling this, the parser should not be used anymore.
1748
+ * @returns {void}
1749
+ */
1750
+ }, {
1751
+ key: "cleanUp",
1752
+ value: function cleanUp() {
1753
+ this.stop();
1754
+ this.dataSets = {};
1755
+ }
1756
+
1757
+ /**
1758
+ * Resumes the HashTreeParser that was previously stopped, using a hash tree message.
1553
1759
  * @param {HashTreeMessage} message - The message to resume with, it must contain metadata with visible data sets info
1554
1760
  * @returns {void}
1555
1761
  */
1556
1762
  }, {
1557
- key: "resume",
1558
- value: function resume(message) {
1559
- var _message$locusStateEl3, _metadataObject$data;
1763
+ key: "resumeFromMessage",
1764
+ value: function resumeFromMessage(message) {
1765
+ var _message$locusStateEl2, _metadataObject$data;
1560
1766
  // check that message contains metadata with visible data sets - this is essential to be able to resume
1561
- var metadataObject = (_message$locusStateEl3 = message.locusStateElements) === null || _message$locusStateEl3 === void 0 ? void 0 : _message$locusStateEl3.find(function (el) {
1767
+ var metadataObject = (_message$locusStateEl2 = message.locusStateElements) === null || _message$locusStateEl2 === void 0 ? void 0 : _message$locusStateEl2.find(function (el) {
1562
1768
  return (0, _utils.isMetadata)(el);
1563
1769
  });
1564
1770
  if (!(metadataObject !== null && metadataObject !== void 0 && (_metadataObject$data = metadataObject.data) !== null && _metadataObject$data !== void 0 && _metadataObject$data.visibleDataSets)) {
1565
- _loggerProxy.default.logger.warn("HashTreeParser#resume --> ".concat(this.debugId, " Cannot resume HashTreeParser because the message is missing metadata with visible data sets info"));
1771
+ _loggerProxy.default.logger.warn("HashTreeParser#resumeFromMessage --> ".concat(this.debugId, " Cannot resume HashTreeParser because the message is missing metadata with visible data sets info"));
1566
1772
  return;
1567
1773
  }
1568
1774
  this.setVisibleDataSets(metadataObject.data.visibleDataSets, message.dataSets);
1569
1775
  this.dataSets = {};
1570
- var _iterator0 = _createForOfIteratorHelper(message.dataSets),
1571
- _step0;
1776
+ var _iterator1 = _createForOfIteratorHelper(message.dataSets),
1777
+ _step1;
1572
1778
  try {
1573
- for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
1574
- var dataSet = _step0.value;
1779
+ for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
1780
+ var dataSet = _step1.value;
1575
1781
  var name = dataSet.name,
1576
1782
  leafCount = dataSet.leafCount;
1577
1783
  this.dataSets[name] = _objectSpread(_objectSpread({}, dataSet), {}, {
@@ -1579,23 +1785,62 @@ var HashTreeParser = /*#__PURE__*/function () {
1579
1785
  });
1580
1786
  }
1581
1787
  } catch (err) {
1582
- _iterator0.e(err);
1788
+ _iterator1.e(err);
1583
1789
  } finally {
1584
- _iterator0.f();
1790
+ _iterator1.f();
1585
1791
  }
1586
- _loggerProxy.default.logger.info("HashTreeParser#resume --> ".concat(this.debugId, " Resuming HashTreeParser with data sets: ").concat((0, _keys.default)(this.dataSets).join(', '), ", visible data sets: ").concat(this.visibleDataSets.map(function (ds) {
1792
+ _loggerProxy.default.logger.info("HashTreeParser#resumeFromMessage --> ".concat(this.debugId, " Resuming HashTreeParser with data sets: ").concat((0, _keys.default)(this.dataSets).join(', '), ", visible data sets: ").concat(this.visibleDataSets.map(function (ds) {
1587
1793
  return ds.name;
1588
1794
  }).join(', ')));
1589
1795
  this.state = 'active';
1590
1796
  this.handleMessage(message, 'on resume');
1591
1797
  }
1798
+
1799
+ /**
1800
+ * Resumes the HashTreeParser that was previously stopped, using a Locus API response.
1801
+ * Unlike resumeFromMessage(), this does not require metadata/dataSets in the input,
1802
+ * as it fetches all necessary information from Locus via initializeFromGetLociResponse.
1803
+ * @param {LocusDTO} locus - locus object from an API response
1804
+ * @returns {Promise}
1805
+ */
1806
+ }, {
1807
+ key: "resumeFromApiResponse",
1808
+ value: (function () {
1809
+ var _resumeFromApiResponse = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee9(locus) {
1810
+ return _regenerator.default.wrap(function (_context0) {
1811
+ while (1) switch (_context0.prev = _context0.next) {
1812
+ case 0:
1813
+ this.state = 'active';
1814
+ this.dataSets = {};
1815
+ _loggerProxy.default.logger.info("HashTreeParser#resumeFromApiResponse --> ".concat(this.debugId, " Resuming HashTreeParser from API response"));
1816
+ _context0.next = 1;
1817
+ return this.initializeFromGetLociResponse(locus);
1818
+ case 1:
1819
+ case "end":
1820
+ return _context0.stop();
1821
+ }
1822
+ }, _callee9, this);
1823
+ }));
1824
+ function resumeFromApiResponse(_x1) {
1825
+ return _resumeFromApiResponse.apply(this, arguments);
1826
+ }
1827
+ return resumeFromApiResponse;
1828
+ }())
1592
1829
  }, {
1593
1830
  key: "checkForSentinelHttpResponse",
1594
1831
  value: function checkForSentinelHttpResponse(error, dataSetName) {
1595
1832
  var _error$body;
1833
+ // 404 for any dataset means the locus is no longer available at this URL - could be replaced or ended
1834
+ // if a dataset is just not visible, we would get a 400
1835
+ if (error.statusCode === 404) {
1836
+ _loggerProxy.default.logger.info("HashTreeParser#checkForSentinelHttpResponse --> ".concat(this.debugId, " Received 404 for data set \"").concat(dataSetName, "\", locus not found"));
1837
+ this.stopAllTimers();
1838
+ throw new LocusNotFoundError();
1839
+ }
1596
1840
  var isValidDataSetForSentinel = dataSetName === undefined || PossibleSentinelMessageDataSetNames.includes(dataSetName.toLowerCase());
1597
- if ((error.statusCode === 409 && ((_error$body = error.body) === null || _error$body === void 0 ? void 0 : _error$body.errorCode) === 2403004 || error.statusCode === 404) && isValidDataSetForSentinel) {
1598
- _loggerProxy.default.logger.info("HashTreeParser#checkForSentinelHttpResponse --> ".concat(this.debugId, " Received ").concat(error.statusCode, " for data set \"").concat(dataSetName, "\", indicating that the meeting has ended"));
1841
+ if (error.statusCode === 409 && ((_error$body = error.body) === null || _error$body === void 0 ? void 0 : _error$body.errorCode) === _types2.LocusErrorCodes.LOCUS_INACTIVE && isValidDataSetForSentinel) {
1842
+ var _error$body2;
1843
+ _loggerProxy.default.logger.info("HashTreeParser#checkForSentinelHttpResponse --> ".concat(this.debugId, " Received ").concat(error.statusCode, "/").concat((_error$body2 = error.body) === null || _error$body2 === void 0 ? void 0 : _error$body2.errorCode, " for data set \"").concat(dataSetName, "\", indicating that the meeting has ended"));
1599
1844
  this.stopAllTimers();
1600
1845
  throw new MeetingEndedError();
1601
1846
  }
@@ -1605,37 +1850,49 @@ var HashTreeParser = /*#__PURE__*/function () {
1605
1850
  * Gets the current hashes from the locus for a specific data set.
1606
1851
  * @param {string} dataSetName
1607
1852
  * @param {string} currentRootHash
1608
- * @returns {string[]}
1853
+ * @returns {Object|null} An object containing the hashes and leaf count, or null if the hashes match and no sync is needed
1609
1854
  */
1610
1855
  }, {
1611
1856
  key: "getHashesFromLocus",
1612
1857
  value: function getHashesFromLocus(dataSetName, currentRootHash) {
1613
- var _this13 = this;
1858
+ var _this12 = this;
1614
1859
  _loggerProxy.default.logger.info("HashTreeParser#getHashesFromLocus --> ".concat(this.debugId, " Requesting hashes for data set \"").concat(dataSetName, "\""));
1615
1860
  var dataSet = this.dataSets[dataSetName];
1616
1861
  var url = "".concat(dataSet.url, "/hashtree");
1617
1862
  return this.webexRequest({
1618
- method: _constants.HTTP_VERBS.GET,
1863
+ method: _constants2.HTTP_VERBS.GET,
1619
1864
  uri: url,
1620
1865
  qs: {
1621
1866
  rootHash: currentRootHash
1622
1867
  }
1623
1868
  }).then(function (response) {
1624
1869
  var _response$body, _response$body2;
1870
+ if (!response.body || (0, _lodash.isEmpty)(response.body)) {
1871
+ // 204 with empty body means our hashes match Locus, no sync needed
1872
+ _loggerProxy.default.logger.info("HashTreeParser#getHashesFromLocus --> ".concat(_this12.debugId, " Got ").concat(response.statusCode, " with empty body for data set \"").concat(dataSetName, "\", hashes match - no sync needed"));
1873
+ return null;
1874
+ }
1625
1875
  var hashes = (_response$body = response.body) === null || _response$body === void 0 ? void 0 : _response$body.hashes;
1626
1876
  var dataSetFromResponse = (_response$body2 = response.body) === null || _response$body2 === void 0 ? void 0 : _response$body2.dataSet;
1627
1877
  if (!hashes || !(0, _isArray.default)(hashes)) {
1628
- _loggerProxy.default.logger.warn("HashTreeParser#getHashesFromLocus --> ".concat(_this13.debugId, " Locus returned invalid hashes, response body="), response.body);
1878
+ _loggerProxy.default.logger.warn("HashTreeParser#getHashesFromLocus --> ".concat(_this12.debugId, " Locus returned invalid hashes, response body="), response.body);
1629
1879
  throw new Error("Locus returned invalid hashes: ".concat(hashes));
1630
1880
  }
1631
- _loggerProxy.default.logger.info("HashTreeParser#getHashesFromLocus --> ".concat(_this13.debugId, " Received hashes for data set \"").concat(dataSetName, "\": ").concat((0, _stringify.default)(hashes)));
1881
+ _loggerProxy.default.logger.info("HashTreeParser#getHashesFromLocus --> ".concat(_this12.debugId, " Received hashes for data set \"").concat(dataSetName, "\": ").concat((0, _stringify.default)(hashes)));
1632
1882
  return {
1633
1883
  hashes: hashes,
1634
1884
  dataSet: dataSetFromResponse
1635
1885
  };
1636
1886
  }).catch(function (error) {
1637
- _loggerProxy.default.logger.error("HashTreeParser#getHashesFromLocus --> ".concat(_this13.debugId, " Error ").concat(error.statusCode, " fetching hashes for data set \"").concat(dataSetName, "\":"), error);
1638
- _this13.checkForSentinelHttpResponse(error, dataSet.name);
1887
+ _loggerProxy.default.logger.error("HashTreeParser#getHashesFromLocus --> ".concat(_this12.debugId, " Error ").concat(error.statusCode, " fetching hashes for data set \"").concat(dataSetName, "\":"), error);
1888
+ _this12.checkForSentinelHttpResponse(error, dataSet.name);
1889
+ _metrics.default.sendBehavioralMetric(_constants.default.HASH_TREE_SYNC_FAILURE, {
1890
+ debugId: _this12.debugId,
1891
+ dataSetName: dataSetName,
1892
+ request: 'GET /hashtree',
1893
+ statusCode: error.statusCode,
1894
+ reason: error.message
1895
+ });
1639
1896
  throw error;
1640
1897
  });
1641
1898
  }
@@ -1644,43 +1901,61 @@ var HashTreeParser = /*#__PURE__*/function () {
1644
1901
  * Sends a sync request to Locus for the specified data set.
1645
1902
  *
1646
1903
  * @param {InternalDataSet} dataSet The data set to sync.
1647
- * @param {Record<number, LeafDataItem[]>} mismatchedLeavesData The mismatched leaves data to include in the sync request.
1904
+ * @param {Object} options Either `{ isInitialization: true }` for init syncs (uses leafCount=1 with empty leaf data) or `{ mismatchedLeavesData }` for normal syncs.
1648
1905
  * @returns {Promise<HashTreeMessage|null>}
1649
1906
  */
1650
1907
  }, {
1651
1908
  key: "sendSyncRequestToLocus",
1652
- value: function sendSyncRequestToLocus(dataSet, mismatchedLeavesData) {
1653
- var _this14 = this;
1909
+ value: function sendSyncRequestToLocus(dataSet, options) {
1910
+ var _this13 = this;
1654
1911
  _loggerProxy.default.logger.info("HashTreeParser#sendSyncRequestToLocus --> ".concat(this.debugId, " Sending sync request for data set \"").concat(dataSet.name, "\""));
1912
+ var isInitialization = 'isInitialization' in options;
1655
1913
  var url = "".concat(dataSet.url, "/sync");
1656
1914
  var body = {
1657
- leafCount: dataSet.leafCount,
1915
+ leafCount: isInitialization ? 1 : dataSet.leafCount,
1658
1916
  leafDataEntries: []
1659
1917
  };
1660
- (0, _keys.default)(mismatchedLeavesData).forEach(function (index) {
1918
+ if (isInitialization) {
1919
+ // initialization sync: Locus requires leafCount=1 with a single empty leaf
1661
1920
  body.leafDataEntries.push({
1662
- leafIndex: (0, _parseInt2.default)(index, 10),
1663
- elementIds: mismatchedLeavesData[index]
1921
+ leafIndex: 0,
1922
+ elementIds: []
1664
1923
  });
1665
- });
1666
- var ourCurrentRootHash = dataSet.hashTree ? dataSet.hashTree.getRootHash() : _constants2.EMPTY_HASH;
1924
+ } else {
1925
+ var mismatchedLeavesData = options.mismatchedLeavesData;
1926
+ (0, _keys.default)(mismatchedLeavesData).forEach(function (index) {
1927
+ var leafIndex = (0, _parseInt2.default)(index, 10);
1928
+ body.leafDataEntries.push({
1929
+ leafIndex: leafIndex,
1930
+ elementIds: mismatchedLeavesData[leafIndex]
1931
+ });
1932
+ });
1933
+ }
1934
+ var ourCurrentRootHash = dataSet.hashTree ? dataSet.hashTree.getRootHash() : _constants3.EMPTY_HASH;
1667
1935
  return this.webexRequest({
1668
- method: _constants.HTTP_VERBS.POST,
1936
+ method: _constants2.HTTP_VERBS.POST,
1669
1937
  uri: url,
1670
1938
  qs: {
1671
1939
  rootHash: ourCurrentRootHash
1672
1940
  },
1673
1941
  body: body
1674
1942
  }).then(function (resp) {
1675
- _loggerProxy.default.logger.info("HashTreeParser#sendSyncRequestToLocus --> ".concat(_this14.debugId, " Sync request succeeded for \"").concat(dataSet.name, "\""));
1943
+ _loggerProxy.default.logger.info("HashTreeParser#sendSyncRequestToLocus --> ".concat(_this13.debugId, " Sync request succeeded for \"").concat(dataSet.name, "\""));
1676
1944
  if (!resp.body || (0, _lodash.isEmpty)(resp.body)) {
1677
- _loggerProxy.default.logger.info("HashTreeParser#sendSyncRequestToLocus --> ".concat(_this14.debugId, " Got ").concat(resp.statusCode, " with empty body for sync request for data set \"").concat(dataSet.name, "\", data should arrive via messages"));
1945
+ _loggerProxy.default.logger.info("HashTreeParser#sendSyncRequestToLocus --> ".concat(_this13.debugId, " Got ").concat(resp.statusCode, " with empty body for sync request for data set \"").concat(dataSet.name, "\", data should arrive via messages"));
1678
1946
  return null;
1679
1947
  }
1680
1948
  return resp.body;
1681
1949
  }).catch(function (error) {
1682
- _loggerProxy.default.logger.error("HashTreeParser#sendSyncRequestToLocus --> ".concat(_this14.debugId, " Error ").concat(error.statusCode, " sending sync request for data set \"").concat(dataSet.name, "\":"), error);
1683
- _this14.checkForSentinelHttpResponse(error, dataSet.name);
1950
+ _loggerProxy.default.logger.error("HashTreeParser#sendSyncRequestToLocus --> ".concat(_this13.debugId, " Error ").concat(error.statusCode, " sending sync request for data set \"").concat(dataSet.name, "\":"), error);
1951
+ _this13.checkForSentinelHttpResponse(error, dataSet.name);
1952
+ _metrics.default.sendBehavioralMetric(_constants.default.HASH_TREE_SYNC_FAILURE, {
1953
+ debugId: _this13.debugId,
1954
+ dataSetName: dataSet.name,
1955
+ request: 'POST /sync',
1956
+ statusCode: error.statusCode,
1957
+ reason: error.message
1958
+ });
1684
1959
  throw error;
1685
1960
  });
1686
1961
  }