@webex/plugin-meetings 3.12.0-mobius-socket.1 → 3.12.0-mobius-socket.3

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