@webex/plugin-meetings 3.12.0-next.6 → 3.12.0-next.60

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 (158) 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 +26 -2
  7. package/dist/breakouts/index.js.map +1 -1
  8. package/dist/config.js +2 -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 +716 -370
  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/index.js +3 -1
  38. package/dist/media/index.js.map +1 -1
  39. package/dist/media/properties.js +1 -0
  40. package/dist/media/properties.js.map +1 -1
  41. package/dist/meeting/in-meeting-actions.js +3 -1
  42. package/dist/meeting/in-meeting-actions.js.map +1 -1
  43. package/dist/meeting/index.js +907 -535
  44. package/dist/meeting/index.js.map +1 -1
  45. package/dist/meeting/util.js +19 -2
  46. package/dist/meeting/util.js.map +1 -1
  47. package/dist/meetings/index.js +231 -78
  48. package/dist/meetings/index.js.map +1 -1
  49. package/dist/meetings/meetings.types.js +6 -1
  50. package/dist/meetings/meetings.types.js.map +1 -1
  51. package/dist/meetings/request.js +39 -0
  52. package/dist/meetings/request.js.map +1 -1
  53. package/dist/meetings/util.js +79 -5
  54. package/dist/meetings/util.js.map +1 -1
  55. package/dist/member/index.js +10 -0
  56. package/dist/member/index.js.map +1 -1
  57. package/dist/member/types.js.map +1 -1
  58. package/dist/member/util.js +3 -0
  59. package/dist/member/util.js.map +1 -1
  60. package/dist/metrics/constants.js +4 -1
  61. package/dist/metrics/constants.js.map +1 -1
  62. package/dist/multistream/codec/constants.js +63 -0
  63. package/dist/multistream/codec/constants.js.map +1 -0
  64. package/dist/multistream/mediaRequestManager.js +62 -15
  65. package/dist/multistream/mediaRequestManager.js.map +1 -1
  66. package/dist/multistream/receiveSlot.js +9 -0
  67. package/dist/multistream/receiveSlot.js.map +1 -1
  68. package/dist/reactions/reactions.type.js.map +1 -1
  69. package/dist/recording-controller/index.js +1 -3
  70. package/dist/recording-controller/index.js.map +1 -1
  71. package/dist/types/config.d.ts +2 -0
  72. package/dist/types/constants.d.ts +2 -0
  73. package/dist/types/controls-options-manager/constants.d.ts +6 -1
  74. package/dist/types/controls-options-manager/index.d.ts +10 -0
  75. package/dist/types/hashTree/constants.d.ts +1 -0
  76. package/dist/types/hashTree/hashTreeParser.d.ts +92 -16
  77. package/dist/types/hashTree/utils.d.ts +11 -0
  78. package/dist/types/index.d.ts +2 -0
  79. package/dist/types/interceptors/locusRetry.d.ts +4 -4
  80. package/dist/types/locus-info/index.d.ts +46 -6
  81. package/dist/types/locus-info/types.d.ts +21 -1
  82. package/dist/types/media/properties.d.ts +1 -0
  83. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  84. package/dist/types/meeting/index.d.ts +87 -3
  85. package/dist/types/meeting/util.d.ts +8 -0
  86. package/dist/types/meetings/index.d.ts +30 -2
  87. package/dist/types/meetings/meetings.types.d.ts +15 -0
  88. package/dist/types/meetings/request.d.ts +14 -0
  89. package/dist/types/member/index.d.ts +1 -0
  90. package/dist/types/member/types.d.ts +1 -0
  91. package/dist/types/member/util.d.ts +1 -0
  92. package/dist/types/metrics/constants.d.ts +3 -0
  93. package/dist/types/multistream/codec/constants.d.ts +7 -0
  94. package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
  95. package/dist/types/reactions/reactions.type.d.ts +3 -0
  96. package/dist/webinar/index.js +361 -235
  97. package/dist/webinar/index.js.map +1 -1
  98. package/package.json +22 -22
  99. package/src/aiEnableRequest/index.ts +16 -0
  100. package/src/breakouts/breakout.ts +3 -1
  101. package/src/breakouts/index.ts +31 -0
  102. package/src/config.ts +2 -0
  103. package/src/constants.ts +5 -1
  104. package/src/controls-options-manager/constants.ts +14 -1
  105. package/src/controls-options-manager/index.ts +47 -24
  106. package/src/controls-options-manager/util.ts +81 -1
  107. package/src/hashTree/constants.ts +9 -0
  108. package/src/hashTree/hashTreeParser.ts +429 -183
  109. package/src/hashTree/utils.ts +17 -0
  110. package/src/index.ts +5 -0
  111. package/src/interceptors/locusRetry.ts +25 -4
  112. package/src/interpretation/index.ts +25 -8
  113. package/src/locus-info/controlsUtils.ts +3 -1
  114. package/src/locus-info/index.ts +291 -97
  115. package/src/locus-info/types.ts +25 -1
  116. package/src/media/index.ts +3 -0
  117. package/src/media/properties.ts +1 -0
  118. package/src/meeting/in-meeting-actions.ts +4 -0
  119. package/src/meeting/index.ts +388 -33
  120. package/src/meeting/util.ts +20 -2
  121. package/src/meetings/index.ts +134 -44
  122. package/src/meetings/meetings.types.ts +19 -0
  123. package/src/meetings/request.ts +43 -0
  124. package/src/meetings/util.ts +97 -1
  125. package/src/member/index.ts +10 -0
  126. package/src/member/types.ts +1 -0
  127. package/src/member/util.ts +3 -0
  128. package/src/metrics/constants.ts +3 -0
  129. package/src/multistream/codec/constants.ts +58 -0
  130. package/src/multistream/mediaRequestManager.ts +119 -28
  131. package/src/multistream/receiveSlot.ts +18 -0
  132. package/src/reactions/reactions.type.ts +3 -0
  133. package/src/recording-controller/index.ts +1 -2
  134. package/src/webinar/index.ts +162 -21
  135. package/test/unit/spec/aiEnableRequest/index.ts +86 -0
  136. package/test/unit/spec/breakouts/breakout.ts +9 -3
  137. package/test/unit/spec/breakouts/index.ts +49 -0
  138. package/test/unit/spec/controls-options-manager/index.js +140 -29
  139. package/test/unit/spec/controls-options-manager/util.js +165 -0
  140. package/test/unit/spec/hashTree/hashTreeParser.ts +1508 -149
  141. package/test/unit/spec/hashTree/utils.ts +88 -1
  142. package/test/unit/spec/interceptors/locusRetry.ts +205 -4
  143. package/test/unit/spec/interpretation/index.ts +26 -4
  144. package/test/unit/spec/locus-info/controlsUtils.js +172 -57
  145. package/test/unit/spec/locus-info/index.js +475 -81
  146. package/test/unit/spec/media/index.ts +31 -0
  147. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  148. package/test/unit/spec/meeting/index.js +1131 -49
  149. package/test/unit/spec/meeting/muteState.js +3 -0
  150. package/test/unit/spec/meeting/utils.js +33 -0
  151. package/test/unit/spec/meetings/index.js +360 -10
  152. package/test/unit/spec/meetings/request.js +141 -0
  153. package/test/unit/spec/meetings/utils.js +189 -0
  154. package/test/unit/spec/member/index.js +7 -0
  155. package/test/unit/spec/member/util.js +24 -0
  156. package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
  157. package/test/unit/spec/recording-controller/index.js +9 -8
  158. 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,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,7 +608,7 @@ 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
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) {
621
614
  var name = _ref2.name,
@@ -629,12 +622,40 @@ var HashTreeParser = /*#__PURE__*/function () {
629
622
  version: version
630
623
  };
631
624
  }))));
625
+ this.cancelPendingSyncsForDataSets(dataSets.map(function (ds) {
626
+ return ds.name;
627
+ }));
632
628
  dataSets.forEach(function (dataSet) {
633
- _this5.updateDataSetInfo(dataSet);
634
- _this5.runSyncAlgorithm(dataSet);
629
+ _this4.updateDataSetInfo(dataSet);
630
+ _this4.runSyncAlgorithm(dataSet);
635
631
  });
636
632
  }
637
633
 
634
+ /**
635
+ * Handles known errors that can happen during syncs
636
+ *
637
+ * @param {any} error - The error to handle
638
+ * @returns {boolean} true if the error was recognized and handled, false otherwise
639
+ */
640
+ }, {
641
+ key: "handleSyncErrors",
642
+ value: function handleSyncErrors(error) {
643
+ if (error instanceof MeetingEndedError) {
644
+ this.callLocusInfoUpdateCallback({
645
+ updateType: LocusInfoUpdateType.MEETING_ENDED
646
+ });
647
+ return true;
648
+ }
649
+ if (error instanceof LocusNotFoundError) {
650
+ this.callLocusInfoUpdateCallback({
651
+ updateType: LocusInfoUpdateType.LOCUS_NOT_FOUND
652
+ });
653
+ this.stop();
654
+ return true;
655
+ }
656
+ return false;
657
+ }
658
+
638
659
  /**
639
660
  * Asynchronously initializes new visible data sets
640
661
  *
@@ -644,18 +665,14 @@ var HashTreeParser = /*#__PURE__*/function () {
644
665
  }, {
645
666
  key: "queueInitForNewVisibleDataSets",
646
667
  value: function queueInitForNewVisibleDataSets(dataSetsRequiringInitialization) {
647
- var _this6 = this;
668
+ var _this5 = this;
648
669
  _loggerProxy.default.logger.info("HashTreeParser#queueInitForNewVisibleDataSets --> ".concat(this.debugId, " queuing initialization of new visible datasets: ").concat(dataSetsRequiringInitialization.map(function (ds) {
649
670
  return ds.name;
650
671
  }).join(', ')));
651
672
  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) {
673
+ _this5.initializeNewVisibleDataSets(dataSetsRequiringInitialization).catch(function (error) {
674
+ if (!_this5.handleSyncErrors(error)) {
675
+ _loggerProxy.default.logger.warn("HashTreeParser#queueInitForNewVisibleDataSets --> ".concat(_this5.debugId, " error while initializing new visible datasets: ").concat(dataSetsRequiringInitialization.map(function (ds) {
659
676
  return ds.name;
660
677
  }).join(', '), ": "), error);
661
678
  }
@@ -715,14 +732,18 @@ var HashTreeParser = /*#__PURE__*/function () {
715
732
  }, {
716
733
  key: "handleLocusUpdate",
717
734
  value: function handleLocusUpdate(update) {
718
- var _this7 = this;
735
+ var _this6 = this;
719
736
  if (this.state === 'stopped') {
720
737
  return;
721
738
  }
722
739
  var dataSets = update.dataSets,
723
740
  locus = update.locus,
724
741
  metadata = update.metadata;
742
+ _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) {
743
+ return ds.name;
744
+ }).join(','), " metadata=").concat(metadata ? 'yes' : 'no'));
725
745
  if (!dataSets) {
746
+ // this happens for example when we handle GET /loci response
726
747
  _loggerProxy.default.logger.info("HashTreeParser#handleLocusUpdate --> ".concat(this.debugId, " received hash tree update without dataSets"));
727
748
  } else {
728
749
  var _iterator5 = _createForOfIteratorHelper(dataSets),
@@ -752,9 +773,9 @@ var HashTreeParser = /*#__PURE__*/function () {
752
773
 
753
774
  // then process the data in hash trees, if it is a new version, then add it to updatedObjects
754
775
  (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) {
776
+ if (_this6.dataSets[dataSetName]) {
777
+ if (_this6.dataSets[dataSetName].hashTree) {
778
+ var appliedChangesList = _this6.dataSets[dataSetName].hashTree.putItems(leafInfo[dataSetName].map(function (leaf) {
758
779
  return {
759
780
  id: leaf.id,
760
781
  type: leaf.type,
@@ -781,10 +802,10 @@ var HashTreeParser = /*#__PURE__*/function () {
781
802
  });
782
803
  } else {
783
804
  // 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"));
805
+ _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
806
  }
786
807
  } else {
787
- _loggerProxy.default.logger.info("HashTreeParser#handleLocusUpdate --> ".concat(_this7.debugId, " received leaf data for unknown data set \"").concat(dataSetName, "\", ignoring"));
808
+ _loggerProxy.default.logger.info("HashTreeParser#handleLocusUpdate --> ".concat(_this6.debugId, " received leaf data for unknown data set \"").concat(dataSetName, "\", ignoring"));
788
809
  }
789
810
  });
790
811
  if (updatedObjects.length === 0) {
@@ -824,6 +845,21 @@ var HashTreeParser = /*#__PURE__*/function () {
824
845
  }
825
846
  }
826
847
 
848
+ /**
849
+ * Updates the leaf count for a data set, resizing its hash tree accordingly.
850
+ *
851
+ * @param {InternalDataSet} dataSet - The data set to update
852
+ * @param {number} newLeafCount - The new leaf count
853
+ * @returns {void}
854
+ */
855
+ }, {
856
+ key: "updateDataSetLeafCount",
857
+ value: function updateDataSetLeafCount(dataSet, newLeafCount) {
858
+ var _dataSet$hashTree;
859
+ (_dataSet$hashTree = dataSet.hashTree) === null || _dataSet$hashTree === void 0 ? void 0 : _dataSet$hashTree.resize(newLeafCount);
860
+ dataSet.leafCount = newLeafCount;
861
+ }
862
+
827
863
  /**
828
864
  * Checks for changes in the visible data sets based on the updated objects.
829
865
  * @param {HashTreeObject[]} updatedObjects - The list of updated hash tree objects.
@@ -832,7 +868,7 @@ var HashTreeParser = /*#__PURE__*/function () {
832
868
  }, {
833
869
  key: "checkForVisibleDataSetChanges",
834
870
  value: function checkForVisibleDataSetChanges(updatedObjects) {
835
- var _this8 = this;
871
+ var _this7 = this;
836
872
  var removedDataSets = [];
837
873
  var addedDataSets = [];
838
874
 
@@ -841,20 +877,20 @@ var HashTreeParser = /*#__PURE__*/function () {
841
877
  var _object$data;
842
878
  if ((0, _utils.isMetadata)(object) && (_object$data = object.data) !== null && _object$data !== void 0 && _object$data.visibleDataSets) {
843
879
  var newVisibleDataSets = object.data.visibleDataSets.filter(function (vds) {
844
- return !_this8.isExcludedDataSet(vds.name);
880
+ return !_this7.isExcludedDataSet(vds.name);
845
881
  });
846
- removedDataSets = _this8.visibleDataSets.filter(function (ds) {
882
+ removedDataSets = _this7.visibleDataSets.filter(function (ds) {
847
883
  return !newVisibleDataSets.some(function (nvs) {
848
884
  return nvs.name === ds.name;
849
885
  });
850
886
  });
851
887
  addedDataSets = newVisibleDataSets.filter(function (nvs) {
852
- return _this8.visibleDataSets.every(function (ds) {
888
+ return _this7.visibleDataSets.every(function (ds) {
853
889
  return ds.name !== nvs.name;
854
890
  });
855
891
  });
856
892
  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) {
893
+ _loggerProxy.default.logger.info("HashTreeParser#checkForVisibleDataSetChanges --> ".concat(_this7.debugId, " visible data sets change: removed: ").concat(removedDataSets.map(function (ds) {
858
894
  return ds.name;
859
895
  }).join(', '), ", added: ").concat(addedDataSets.map(function (ds) {
860
896
  return ds.name;
@@ -878,7 +914,10 @@ var HashTreeParser = /*#__PURE__*/function () {
878
914
  }, {
879
915
  key: "deleteHashTree",
880
916
  value: function deleteHashTree(dataSetName) {
917
+ var _this$dataSets$dataSe;
881
918
  this.dataSets[dataSetName].hashTree = undefined;
919
+ (_this$dataSets$dataSe = this.dataSets[dataSetName].syncAbortController) === null || _this$dataSets$dataSe === void 0 ? void 0 : _this$dataSets$dataSe.abort();
920
+ this.dataSets[dataSetName].syncAbortController = undefined;
882
921
 
883
922
  // we also need to stop the timers as there is no hash tree anymore to sync
884
923
  if (this.dataSets[dataSetName].timer) {
@@ -908,16 +947,16 @@ var HashTreeParser = /*#__PURE__*/function () {
908
947
  }, {
909
948
  key: "processVisibleDataSetChanges",
910
949
  value: function processVisibleDataSetChanges(removedDataSets, addedDataSets, updatedObjects) {
911
- var _this9 = this;
950
+ var _this8 = this;
912
951
  var dataSetsRequiringInitialization = [];
913
952
 
914
953
  // if a visible data set was removed, we need to tell our client that all objects from it are removed
915
954
  var removedObjects = [];
916
955
  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) {
956
+ var _this8$dataSets$ds$na;
957
+ if ((_this8$dataSets$ds$na = _this8.dataSets[ds.name]) !== null && _this8$dataSets$ds$na !== void 0 && _this8$dataSets$ds$na.hashTree) {
958
+ for (var i = 0; i < _this8.dataSets[ds.name].hashTree.numLeaves; i += 1) {
959
+ removedObjects.push.apply(removedObjects, (0, _toConsumableArray2.default)(_this8.dataSets[ds.name].hashTree.getLeafData(i).map(function (elementId) {
921
960
  return {
922
961
  htMeta: {
923
962
  elementId: elementId,
@@ -927,7 +966,7 @@ var HashTreeParser = /*#__PURE__*/function () {
927
966
  };
928
967
  })));
929
968
  }
930
- _this9.deleteHashTree(ds.name);
969
+ _this8.deleteHashTree(ds.name);
931
970
  }
932
971
  });
933
972
  this.visibleDataSets = this.visibleDataSets.filter(function (vds) {
@@ -988,81 +1027,78 @@ var HashTreeParser = /*#__PURE__*/function () {
988
1027
  }, {
989
1028
  key: "initializeNewVisibleDataSets",
990
1029
  value: (function () {
991
- var _initializeNewVisibleDataSets = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4(addedDataSets) {
992
- var _this0 = this;
1030
+ var _initializeNewVisibleDataSets = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee5(addedDataSets) {
1031
+ var _this9 = this;
993
1032
  var allDataSets, _iterator7, _step7, _loop, _t2;
994
- return _regenerator.default.wrap(function (_context5) {
995
- while (1) switch (_context5.prev = _context5.next) {
1033
+ return _regenerator.default.wrap(function (_context6) {
1034
+ while (1) switch (_context6.prev = _context6.next) {
996
1035
  case 0:
997
1036
  if (!(this.state === 'stopped')) {
998
- _context5.next = 1;
1037
+ _context6.next = 1;
999
1038
  break;
1000
1039
  }
1001
- return _context5.abrupt("return");
1040
+ return _context6.abrupt("return");
1002
1041
  case 1:
1003
- _context5.next = 2;
1042
+ _context6.next = 2;
1004
1043
  return this.getAllVisibleDataSetsFromLocus();
1005
1044
  case 2:
1006
- allDataSets = _context5.sent;
1007
- _iterator7 = _createForOfIteratorHelper(addedDataSets);
1008
- _context5.prev = 3;
1045
+ allDataSets = _context6.sent;
1046
+ _iterator7 = _createForOfIteratorHelper((0, _utils.sortByInitPriority)(addedDataSets, _constants3.DATA_SET_INIT_PRIORITY));
1047
+ _context6.prev = 3;
1009
1048
  _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) {
1049
+ var ds, dataSetInfo;
1050
+ return _regenerator.default.wrap(function (_context5) {
1051
+ while (1) switch (_context5.prev = _context5.next) {
1013
1052
  case 0:
1014
1053
  ds = _step7.value;
1015
1054
  dataSetInfo = allDataSets.find(function (d) {
1016
1055
  return d.name === ds.name;
1017
1056
  });
1018
- _loggerProxy.default.logger.info("HashTreeParser#initializeNewVisibleDataSets --> ".concat(_this0.debugId, " initializing data set \"").concat(ds.name, "\""));
1057
+ _loggerProxy.default.logger.info("HashTreeParser#initializeNewVisibleDataSets --> ".concat(_this9.debugId, " initializing data set \"").concat(ds.name, "\""));
1019
1058
  if (dataSetInfo) {
1020
- _context4.next = 1;
1059
+ _context5.next = 1;
1021
1060
  break;
1022
1061
  }
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;
1062
+ _loggerProxy.default.logger.warn("HashTreeParser#initializeNewVisibleDataSets --> ".concat(_this9.debugId, " missing info about data set \"").concat(ds.name, "\" in Locus response from visibleDataSetsUrl"));
1063
+ _context5.next = 2;
1025
1064
  break;
1026
1065
  case 1:
1027
- _context4.next = 2;
1028
- return _this0.initializeNewVisibleDataSet(ds, dataSetInfo);
1066
+ _context5.next = 2;
1067
+ return _this9.initializeNewVisibleDataSet(ds, dataSetInfo);
1029
1068
  case 2:
1030
- updates = _context4.sent;
1031
- _this0.callLocusInfoUpdateCallback(updates);
1032
- case 3:
1033
1069
  case "end":
1034
- return _context4.stop();
1070
+ return _context5.stop();
1035
1071
  }
1036
1072
  }, _loop);
1037
1073
  });
1038
1074
  _iterator7.s();
1039
1075
  case 4:
1040
1076
  if ((_step7 = _iterator7.n()).done) {
1041
- _context5.next = 6;
1077
+ _context6.next = 6;
1042
1078
  break;
1043
1079
  }
1044
- return _context5.delegateYield(_loop(), "t0", 5);
1080
+ return _context6.delegateYield(_loop(), "t0", 5);
1045
1081
  case 5:
1046
- _context5.next = 4;
1082
+ _context6.next = 4;
1047
1083
  break;
1048
1084
  case 6:
1049
- _context5.next = 8;
1085
+ _context6.next = 8;
1050
1086
  break;
1051
1087
  case 7:
1052
- _context5.prev = 7;
1053
- _t2 = _context5["catch"](3);
1088
+ _context6.prev = 7;
1089
+ _t2 = _context6["catch"](3);
1054
1090
  _iterator7.e(_t2);
1055
1091
  case 8:
1056
- _context5.prev = 8;
1092
+ _context6.prev = 8;
1057
1093
  _iterator7.f();
1058
- return _context5.finish(8);
1094
+ return _context6.finish(8);
1059
1095
  case 9:
1060
1096
  case "end":
1061
- return _context5.stop();
1097
+ return _context6.stop();
1062
1098
  }
1063
- }, _callee4, this, [[3, 7, 8, 9]]);
1099
+ }, _callee5, this, [[3, 7, 8, 9]]);
1064
1100
  }));
1065
- function initializeNewVisibleDataSets(_x5) {
1101
+ function initializeNewVisibleDataSets(_x7) {
1066
1102
  return _initializeNewVisibleDataSets.apply(this, arguments);
1067
1103
  }
1068
1104
  return initializeNewVisibleDataSets;
@@ -1078,25 +1114,37 @@ var HashTreeParser = /*#__PURE__*/function () {
1078
1114
  }, {
1079
1115
  key: "parseMessage",
1080
1116
  value: function parseMessage(message, debugText) {
1081
- var _message$locusStateEl,
1082
- _this1 = this,
1083
- _message$locusStateEl2;
1117
+ var _message$dataSets,
1118
+ _message$locusStateEl,
1119
+ _message$locusStateEl2,
1120
+ _this0 = this;
1084
1121
  if (this.state === 'stopped') {
1085
1122
  return [];
1086
1123
  }
1087
1124
  var dataSets = message.dataSets,
1088
1125
  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) {
1126
+ _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 (_ref5) {
1127
+ var name = _ref5.name,
1128
+ version = _ref5.version;
1129
+ return "".concat(name, ":").concat(version);
1130
+ }).join(','), ", elements: ").concat((_message$locusStateEl = message.locusStateElements) === null || _message$locusStateEl === void 0 ? void 0 : _message$locusStateEl.map(function (el) {
1131
+ return "".concat(el.htMeta.elementId.type, ":").concat(el.htMeta.elementId.id, ":").concat(el.htMeta.elementId.version).concat(el.data ? '+' : '-');
1132
+ }).join(',')));
1133
+ if (((_message$locusStateEl2 = message.locusStateElements) === null || _message$locusStateEl2 === void 0 ? void 0 : _message$locusStateEl2.length) === 0) {
1091
1134
  _loggerProxy.default.logger.warn("HashTreeParser#parseMessage --> ".concat(this.debugId, " got empty locusStateElements!!!"));
1092
- // todo: send a metric
1135
+ _metrics.default.sendBehavioralMetric(_constants.default.HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS, {
1136
+ debugId: this.debugId
1137
+ });
1093
1138
  }
1094
1139
 
1095
1140
  // first, update our metadata about the datasets with info from the message
1096
1141
  this.visibleDataSetsUrl = visibleDataSetsUrl;
1097
1142
  dataSets.forEach(function (dataSet) {
1098
- return _this1.updateDataSetInfo(dataSet);
1143
+ return _this0.updateDataSetInfo(dataSet);
1099
1144
  });
1145
+ this.cancelPendingSyncsForDataSets(dataSets.map(function (ds) {
1146
+ return ds.name;
1147
+ }));
1100
1148
  var updatedObjects = [];
1101
1149
 
1102
1150
  // when we detect new visible datasets, it may be that the metadata about them is not
@@ -1115,9 +1163,9 @@ var HashTreeParser = /*#__PURE__*/function () {
1115
1163
  _step8;
1116
1164
  try {
1117
1165
  for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
1118
- var _this1$dataSets$dataS;
1166
+ var _this0$dataSets$dataS;
1119
1167
  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;
1168
+ var hashTree = (_this0$dataSets$dataS = _this0.dataSets[dataSetName]) === null || _this0$dataSets$dataS === void 0 ? void 0 : _this0$dataSets$dataS.hashTree;
1121
1169
  if (hashTree && object.data) {
1122
1170
  if (hashTree.putItem(object.htMeta.elementId)) {
1123
1171
  updatedMetadataObjects.push(object);
@@ -1139,13 +1187,13 @@ var HashTreeParser = /*#__PURE__*/function () {
1139
1187
  dataSetsRequiringInitialization = this.processVisibleDataSetChanges(removedDataSets, addedDataSets, updatedObjects);
1140
1188
  }
1141
1189
  }
1142
- if (((_message$locusStateEl2 = message.locusStateElements) === null || _message$locusStateEl2 === void 0 ? void 0 : _message$locusStateEl2.length) > 0) {
1190
+ if (message.locusStateElements && message.locusStateElements.length > 0) {
1143
1191
  // by this point we now have this.dataSets setup for data sets from this message
1144
1192
  // and hash trees created for the new visible data sets,
1145
1193
  // so we can now process all the updates from the message
1146
1194
  dataSets.forEach(function (dataSet) {
1147
- if (_this1.dataSets[dataSet.name]) {
1148
- var hashTree = _this1.dataSets[dataSet.name].hashTree;
1195
+ if (_this0.dataSets[dataSet.name]) {
1196
+ var hashTree = _this0.dataSets[dataSet.name].hashTree;
1149
1197
  if (hashTree) {
1150
1198
  var locusStateElementsForThisSet = message.locusStateElements.filter(function (object) {
1151
1199
  return object.htMeta.dataSetNames.includes(dataSet.name);
@@ -1159,20 +1207,20 @@ var HashTreeParser = /*#__PURE__*/function () {
1159
1207
  item: object.htMeta.elementId
1160
1208
  };
1161
1209
  }));
1162
- (0, _lodash.zip)(appliedChangesList, locusStateElementsForThisSet).forEach(function (_ref5) {
1163
- var _ref6 = (0, _slicedToArray2.default)(_ref5, 2),
1164
- changeApplied = _ref6[0],
1165
- object = _ref6[1];
1210
+ (0, _lodash.zip)(appliedChangesList, locusStateElementsForThisSet).forEach(function (_ref6) {
1211
+ var _ref7 = (0, _slicedToArray2.default)(_ref6, 2),
1212
+ changeApplied = _ref7[0],
1213
+ object = _ref7[1];
1166
1214
  if (changeApplied) {
1167
1215
  // add to updatedObjects so that our locus DTO will get updated with the new object
1168
1216
  updatedObjects.push(object);
1169
1217
  }
1170
1218
  });
1171
1219
  } 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"));
1220
+ _loggerProxy.default.logger.info("Locus-info:index#parseMessage --> ".concat(_this0.debugId, " unexpected (not visible) dataSet ").concat(dataSet.name, " received in hash tree message"));
1173
1221
  }
1174
1222
  }
1175
- _this1.runSyncAlgorithm(dataSet);
1223
+ _this0.runSyncAlgorithm(dataSet);
1176
1224
  });
1177
1225
  }
1178
1226
  if (dataSetsRequiringInitialization.length > 0) {
@@ -1229,25 +1277,25 @@ var HashTreeParser = /*#__PURE__*/function () {
1229
1277
  }, {
1230
1278
  key: "callLocusInfoUpdateCallback",
1231
1279
  value: function callLocusInfoUpdateCallback(updates) {
1232
- var _this10 = this;
1280
+ var _updates$updatedObjec,
1281
+ _this1 = this;
1233
1282
  if (this.state === 'stopped') {
1234
1283
  return;
1235
1284
  }
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) {
1285
+ var updateType = updates.updateType;
1286
+ if (updateType === LocusInfoUpdateType.OBJECTS_UPDATED && ((_updates$updatedObjec = updates.updatedObjects) === null || _updates$updatedObjec === void 0 ? void 0 : _updates$updatedObjec.length) > 0) {
1239
1287
  // Filter out updates for objects that already have a higher version in their datasets,
1240
1288
  // or removals for objects that still exist in any of their datasets
1241
- var filteredUpdates = updatedObjects.filter(function (object) {
1289
+ var filteredUpdates = updates.updatedObjects.filter(function (object) {
1242
1290
  var elementId = object.htMeta.elementId;
1243
1291
  var type = elementId.type,
1244
1292
  id = elementId.id,
1245
1293
  version = elementId.version;
1246
1294
 
1247
1295
  // Check all datasets
1248
- for (var _i2 = 0, _Object$keys3 = (0, _keys.default)(_this10.dataSets); _i2 < _Object$keys3.length; _i2++) {
1296
+ for (var _i2 = 0, _Object$keys3 = (0, _keys.default)(_this1.dataSets); _i2 < _Object$keys3.length; _i2++) {
1249
1297
  var dataSetName = _Object$keys3[_i2];
1250
- var dataSet = _this10.dataSets[dataSetName];
1298
+ var dataSet = _this1.dataSets[dataSetName];
1251
1299
 
1252
1300
  // only visible datasets have hash trees set
1253
1301
  if (dataSet !== null && dataSet !== void 0 && dataSet.hashTree) {
@@ -1256,12 +1304,12 @@ var HashTreeParser = /*#__PURE__*/function () {
1256
1304
  if (object.data) {
1257
1305
  // For updates: filter out if any dataset has a higher version
1258
1306
  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));
1307
+ _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
1308
  return false;
1261
1309
  }
1262
1310
  } else if (existingVersion >= version) {
1263
1311
  // 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));
1312
+ _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
1313
  return false;
1266
1314
  }
1267
1315
  }
@@ -1270,13 +1318,14 @@ var HashTreeParser = /*#__PURE__*/function () {
1270
1318
  return true;
1271
1319
  });
1272
1320
  if (filteredUpdates.length > 0) {
1273
- this.locusInfoUpdateCallback(updateType, {
1321
+ this.locusInfoUpdateCallback({
1322
+ updateType: updateType,
1274
1323
  updatedObjects: filteredUpdates
1275
1324
  });
1276
1325
  }
1277
1326
  } else if (updateType !== LocusInfoUpdateType.OBJECTS_UPDATED) {
1278
- this.locusInfoUpdateCallback(updateType, {
1279
- updatedObjects: updatedObjects
1327
+ this.locusInfoUpdateCallback({
1328
+ updateType: updateType
1280
1329
  });
1281
1330
  }
1282
1331
  }
@@ -1300,102 +1349,342 @@ var HashTreeParser = /*#__PURE__*/function () {
1300
1349
  * Performs a sync for the given data set.
1301
1350
  *
1302
1351
  * @param {InternalDataSet} dataSet - The data set to sync
1303
- * @param {string} rootHash - Our current root hash for this data set
1304
1352
  * @param {string} reason - The reason for the sync (used for logging)
1353
+ * @param {boolean} [isInitialization] - Whether this is an initialization sync (sends empty leaves data instead of comparing hashes)
1305
1354
  * @returns {Promise<void>}
1306
1355
  */
1307
1356
  }, {
1308
1357
  key: "performSync",
1309
1358
  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) {
1359
+ var _performSync = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee6(dataSet, reason, isInitialization) {
1360
+ var _dataSet$syncAbortCon;
1361
+ var abortController, hashTree, rootHash, leavesData, receivedHashes, hashesResult, mismatchedLeaveIndexes, syncResponse, _t3, _t4;
1362
+ return _regenerator.default.wrap(function (_context7) {
1363
+ while (1) switch (_context7.prev = _context7.next) {
1314
1364
  case 0:
1315
1365
  if (dataSet.hashTree) {
1316
- _context6.next = 1;
1366
+ _context7.next = 1;
1317
1367
  break;
1318
1368
  }
1319
- return _context6.abrupt("return");
1369
+ return _context7.abrupt("return");
1320
1370
  case 1:
1321
- _context6.prev = 1;
1371
+ abortController = (_dataSet$syncAbortCon = dataSet.syncAbortController) !== null && _dataSet$syncAbortCon !== void 0 ? _dataSet$syncAbortCon : new AbortController();
1372
+ dataSet.syncAbortController = abortController;
1373
+ hashTree = dataSet.hashTree;
1374
+ rootHash = hashTree.getRootHash();
1375
+ _context7.prev = 2;
1322
1376
  _loggerProxy.default.logger.info("HashTreeParser#performSync --> ".concat(this.debugId, " ").concat(reason, ", syncing data set \"").concat(dataSet.name, "\""));
1323
- mismatchedLeavesData = {};
1377
+ leavesData = {};
1378
+ if (isInitialization) {
1379
+ _context7.next = 10;
1380
+ break;
1381
+ }
1324
1382
  if (!(dataSet.leafCount !== 1)) {
1325
- _context6.next = 7;
1383
+ _context7.next = 9;
1326
1384
  break;
1327
1385
  }
1328
- _context6.prev = 2;
1329
- _context6.next = 3;
1386
+ _context7.prev = 3;
1387
+ _context7.next = 4;
1330
1388
  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
1389
  case 4:
1340
- _context6.prev = 4;
1341
- _t3 = _context6["catch"](2);
1342
- if (!(_t3.statusCode === 409)) {
1343
- _context6.next = 5;
1390
+ hashesResult = _context7.sent;
1391
+ if (hashesResult) {
1392
+ _context7.next = 5;
1393
+ break;
1394
+ }
1395
+ return _context7.abrupt("return");
1396
+ case 5:
1397
+ receivedHashes = hashesResult.hashes;
1398
+ this.updateDataSetLeafCount(dataSet, hashesResult.dataSet.leafCount);
1399
+ _context7.next = 8;
1400
+ break;
1401
+ case 6:
1402
+ _context7.prev = 6;
1403
+ _t3 = _context7["catch"](3);
1404
+ if (!((_t3 === null || _t3 === void 0 ? void 0 : _t3.statusCode) === 409)) {
1405
+ _context7.next = 7;
1344
1406
  break;
1345
1407
  }
1346
1408
  // this is a leaf count mismatch, we should do nothing, just wait for another heartbeat message from Locus
1347
1409
  _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:
1410
+ return _context7.abrupt("return");
1411
+ case 7:
1350
1412
  throw _t3;
1351
- case 6:
1413
+ case 8:
1352
1414
  // identify mismatched leaves
1353
- mismatchedLeaveIndexes = dataSet.hashTree.diffHashes(receivedHashes);
1415
+ mismatchedLeaveIndexes = hashTree.diffHashes(receivedHashes);
1354
1416
  mismatchedLeaveIndexes.forEach(function (index) {
1355
- mismatchedLeavesData[index] = dataSet.hashTree.getLeafData(index);
1417
+ leavesData[index] = hashTree.getLeafData(index);
1356
1418
  });
1357
- _context6.next = 8;
1419
+ _context7.next = 10;
1358
1420
  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;
1421
+ case 9:
1422
+ leavesData = {
1423
+ 0: hashTree.getLeafData(0)
1424
+ };
1425
+ case 10:
1426
+ if (!abortController.signal.aborted) {
1427
+ _context7.next = 11;
1364
1428
  break;
1365
1429
  }
1366
- _context6.next = 9;
1367
- return this.sendSyncRequestToLocus(dataSet, mismatchedLeavesData);
1368
- case 9:
1369
- syncResponse = _context6.sent;
1430
+ _loggerProxy.default.logger.info("HashTreeParser#performSync --> ".concat(this.debugId, " abandoning sync for \"").concat(dataSet.name, "\" before /sync - message received during sync"));
1431
+ return _context7.abrupt("return");
1432
+ case 11:
1433
+ // request sync for mismatched leaves
1434
+ syncResponse = null;
1435
+ if (!isInitialization) {
1436
+ _context7.next = 13;
1437
+ break;
1438
+ }
1439
+ _context7.next = 12;
1440
+ return this.sendSyncRequestToLocus(dataSet, {
1441
+ isInitialization: true
1442
+ });
1443
+ case 12:
1444
+ syncResponse = _context7.sent;
1445
+ _context7.next = 15;
1446
+ break;
1447
+ case 13:
1448
+ if (!((0, _keys.default)(leavesData).length > 0)) {
1449
+ _context7.next = 15;
1450
+ break;
1451
+ }
1452
+ _context7.next = 14;
1453
+ return this.sendSyncRequestToLocus(dataSet, {
1454
+ mismatchedLeavesData: leavesData
1455
+ });
1456
+ case 14:
1457
+ syncResponse = _context7.sent;
1458
+ case 15:
1370
1459
  // sync API may return nothing (in that case data will arrive via messages)
1371
1460
  // or it may return a response in the same format as messages
1461
+ // We still need to restart the sync timer as a safety net in case the messages don't arrive.
1462
+ this.runSyncAlgorithm(dataSet);
1372
1463
  if (syncResponse) {
1464
+ // clear the abort controller before processing the response so that
1465
+ // parseMessage() -> cancelPendingSyncsForDataSets() doesn't log a
1466
+ // misleading "aborting sync" message for this already-completed sync
1467
+ dataSet.syncAbortController = undefined;
1468
+ // the format of sync response is the same as messages, so we can reuse the same handler
1373
1469
  this.handleMessage(syncResponse, 'via sync API');
1374
1470
  }
1375
- case 10:
1376
- _context6.next = 12;
1471
+ _context7.next = 17;
1377
1472
  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 {
1473
+ case 16:
1474
+ _context7.prev = 16;
1475
+ _t4 = _context7["catch"](2);
1476
+ if (!this.handleSyncErrors(_t4)) {
1386
1477
  _loggerProxy.default.logger.warn("HashTreeParser#performSync --> ".concat(this.debugId, " error during sync for data set \"").concat(dataSet.name, "\":"), _t4);
1387
1478
  }
1388
- case 12:
1479
+ case 17:
1480
+ _context7.prev = 17;
1481
+ dataSet.syncAbortController = undefined;
1482
+ return _context7.finish(17);
1483
+ case 18:
1389
1484
  case "end":
1390
- return _context6.stop();
1485
+ return _context7.stop();
1391
1486
  }
1392
- }, _callee5, this, [[1, 11], [2, 4]]);
1487
+ }, _callee6, this, [[2, 16, 17, 18], [3, 6]]);
1393
1488
  }));
1394
- function performSync(_x6, _x7, _x8) {
1489
+ function performSync(_x8, _x9, _x0) {
1395
1490
  return _performSync.apply(this, arguments);
1396
1491
  }
1397
1492
  return performSync;
1398
1493
  }()
1494
+ /**
1495
+ * Cancels any pending or in-flight syncs for the specified data sets.
1496
+ * This removes matching entries from the sync queue and aborts any in-flight sync HTTP requests.
1497
+ *
1498
+ * @param {string[]} dataSetNames - The names of the data sets to cancel syncs for
1499
+ * @returns {void}
1500
+ */
1501
+ )
1502
+ }, {
1503
+ key: "cancelPendingSyncsForDataSets",
1504
+ value: function cancelPendingSyncsForDataSets(dataSetNames) {
1505
+ var previousLength = this.syncQueue.length;
1506
+ this.syncQueue = this.syncQueue.filter(function (entry) {
1507
+ return !dataSetNames.includes(entry.dataSetName);
1508
+ });
1509
+ if (previousLength !== this.syncQueue.length) {
1510
+ _loggerProxy.default.logger.info("HashTreeParser#cancelPendingSyncsForDataSets --> ".concat(this.debugId, " removed ").concat(previousLength - this.syncQueue.length, " entries from sync queue for data sets: ").concat(dataSetNames.join(', ')));
1511
+ }
1512
+ var _iterator9 = _createForOfIteratorHelper(dataSetNames),
1513
+ _step9;
1514
+ try {
1515
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
1516
+ var _this$dataSets$name;
1517
+ var name = _step9.value;
1518
+ if ((_this$dataSets$name = this.dataSets[name]) !== null && _this$dataSets$name !== void 0 && _this$dataSets$name.syncAbortController) {
1519
+ _loggerProxy.default.logger.info("HashTreeParser#cancelPendingSyncsForDataSets --> ".concat(this.debugId, " aborting in-flight sync for data set \"").concat(name, "\""));
1520
+ this.dataSets[name].syncAbortController.abort();
1521
+ }
1522
+ }
1523
+ } catch (err) {
1524
+ _iterator9.e(err);
1525
+ } finally {
1526
+ _iterator9.f();
1527
+ }
1528
+ }
1529
+
1530
+ /**
1531
+ * Enqueues a sync for the given data set. If the data set is already in the queue, the request is ignored.
1532
+ * This ensures that all syncs are executed sequentially and no more than 1 sync runs at a time.
1533
+ *
1534
+ * @param {string} dataSetName - The name of the data set to sync
1535
+ * @param {string} reason - The reason for the sync (used for logging)
1536
+ * @param {boolean} [isInitialization=false] - Whether this is an initialization sync (uses empty leaves data instead of hash comparison)
1537
+ * @returns {void}
1538
+ */
1539
+ }, {
1540
+ key: "enqueueSyncForDataset",
1541
+ value: function enqueueSyncForDataset(dataSetName, reason) {
1542
+ var isInitialization = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1543
+ if (this.state === 'stopped') return;
1544
+ var existingEntry = this.syncQueue.find(function (entry) {
1545
+ return entry.dataSetName === dataSetName;
1546
+ });
1547
+ if (existingEntry) {
1548
+ if (isInitialization) {
1549
+ existingEntry.isInitialization = true;
1550
+ }
1551
+ _loggerProxy.default.logger.info("HashTreeParser#enqueueSyncForDataset --> ".concat(this.debugId, " data set \"").concat(dataSetName, "\" already in sync queue, skipping"));
1552
+ return;
1553
+ }
1554
+ this.syncQueue.push({
1555
+ dataSetName: dataSetName,
1556
+ reason: reason,
1557
+ isInitialization: isInitialization
1558
+ });
1559
+ if (!this.isSyncInProgress) {
1560
+ this.syncQueueProcessingPromise = this.processSyncQueue();
1561
+ }
1562
+ }
1563
+
1564
+ /**
1565
+ * Processes the sync queue sequentially. Only one instance of this method runs at a time.
1566
+ *
1567
+ * @returns {Promise<void>}
1568
+ */
1569
+ }, {
1570
+ key: "processSyncQueue",
1571
+ value: (function () {
1572
+ var _processSyncQueue = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee7() {
1573
+ var _ref8, dataSetName, reason, isInitialization, dataSet;
1574
+ return _regenerator.default.wrap(function (_context8) {
1575
+ while (1) switch (_context8.prev = _context8.next) {
1576
+ case 0:
1577
+ if (!this.isSyncInProgress) {
1578
+ _context8.next = 1;
1579
+ break;
1580
+ }
1581
+ return _context8.abrupt("return");
1582
+ case 1:
1583
+ this.isSyncInProgress = true;
1584
+ _context8.prev = 2;
1585
+ case 3:
1586
+ if (!(this.syncQueue.length > 0 && this.state !== 'stopped')) {
1587
+ _context8.next = 6;
1588
+ break;
1589
+ }
1590
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1591
+ _ref8 = this.syncQueue.shift(), dataSetName = _ref8.dataSetName, reason = _ref8.reason, isInitialization = _ref8.isInitialization;
1592
+ dataSet = this.dataSets[dataSetName];
1593
+ if (dataSet !== null && dataSet !== void 0 && dataSet.hashTree) {
1594
+ _context8.next = 4;
1595
+ break;
1596
+ }
1597
+ return _context8.abrupt("continue", 3);
1598
+ case 4:
1599
+ _context8.next = 5;
1600
+ return this.performSync(dataSet, reason, isInitialization);
1601
+ case 5:
1602
+ _context8.next = 3;
1603
+ break;
1604
+ case 6:
1605
+ _context8.prev = 6;
1606
+ this.isSyncInProgress = false;
1607
+ return _context8.finish(6);
1608
+ case 7:
1609
+ case "end":
1610
+ return _context8.stop();
1611
+ }
1612
+ }, _callee7, this, [[2,, 6, 7]]);
1613
+ }));
1614
+ function processSyncQueue() {
1615
+ return _processSyncQueue.apply(this, arguments);
1616
+ }
1617
+ return processSyncQueue;
1618
+ }()
1619
+ /**
1620
+ * Syncs all data sets that have hash trees, one by one in sequence, using the priority order
1621
+ * provided by sortByInitPriority(). Does nothing if the parser is stopped or if a syncAllDatasets
1622
+ * call is already in progress.
1623
+ *
1624
+ * @returns {Promise<void>}
1625
+ */
1626
+ )
1627
+ }, {
1628
+ key: "syncAllDatasets",
1629
+ value: (function () {
1630
+ var _syncAllDatasets = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee8() {
1631
+ var dataSetsWithHashTrees, sorted, _iterator0, _step0, ds;
1632
+ return _regenerator.default.wrap(function (_context9) {
1633
+ while (1) switch (_context9.prev = _context9.next) {
1634
+ case 0:
1635
+ if (!(this.state === 'stopped')) {
1636
+ _context9.next = 1;
1637
+ break;
1638
+ }
1639
+ return _context9.abrupt("return");
1640
+ case 1:
1641
+ if (!this.isSyncAllInProgress) {
1642
+ _context9.next = 2;
1643
+ break;
1644
+ }
1645
+ return _context9.abrupt("return");
1646
+ case 2:
1647
+ this.isSyncAllInProgress = true;
1648
+ _context9.prev = 3;
1649
+ dataSetsWithHashTrees = (0, _values.default)(this.dataSets).filter(function (dataSet) {
1650
+ return dataSet === null || dataSet === void 0 ? void 0 : dataSet.hashTree;
1651
+ }).map(function (dataSet) {
1652
+ return {
1653
+ name: dataSet.name
1654
+ };
1655
+ });
1656
+ sorted = (0, _utils.sortByInitPriority)(dataSetsWithHashTrees, _constants3.DATA_SET_INIT_PRIORITY);
1657
+ _loggerProxy.default.logger.info("HashTreeParser#syncAllDatasets --> ".concat(this.debugId, " syncing datasets: ").concat(sorted.map(function (ds) {
1658
+ return ds.name;
1659
+ }).join(', ')));
1660
+ _iterator0 = _createForOfIteratorHelper(sorted);
1661
+ try {
1662
+ for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
1663
+ ds = _step0.value;
1664
+ this.enqueueSyncForDataset(ds.name, 'syncAllDatasets');
1665
+ }
1666
+ } catch (err) {
1667
+ _iterator0.e(err);
1668
+ } finally {
1669
+ _iterator0.f();
1670
+ }
1671
+ _context9.next = 4;
1672
+ return this.syncQueueProcessingPromise;
1673
+ case 4:
1674
+ _context9.prev = 4;
1675
+ this.isSyncAllInProgress = false;
1676
+ return _context9.finish(4);
1677
+ case 5:
1678
+ case "end":
1679
+ return _context9.stop();
1680
+ }
1681
+ }, _callee8, this, [[3,, 4, 5]]);
1682
+ }));
1683
+ function syncAllDatasets() {
1684
+ return _syncAllDatasets.apply(this, arguments);
1685
+ }
1686
+ return syncAllDatasets;
1687
+ }()
1399
1688
  /**
1400
1689
  * Runs the sync algorithm for the given data set.
1401
1690
  *
@@ -1406,58 +1695,35 @@ var HashTreeParser = /*#__PURE__*/function () {
1406
1695
  }, {
1407
1696
  key: "runSyncAlgorithm",
1408
1697
  value: function runSyncAlgorithm(receivedDataSet) {
1409
- var _this11 = this;
1698
+ var _this10 = this;
1410
1699
  var dataSet = this.dataSets[receivedDataSet.name];
1411
1700
  if (!dataSet) {
1412
1701
  _loggerProxy.default.logger.warn("HashTreeParser#runSyncAlgorithm --> ".concat(this.debugId, " No data set found for ").concat(receivedDataSet.name, ", skipping sync algorithm"));
1413
1702
  return;
1414
1703
  }
1415
1704
  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"));
1705
+ // no hash tree, so no need to do any syncing
1706
+ // we fall into this branch often, because Locus sends dataSets in messages that are not visible to us
1707
+
1417
1708
  return;
1418
1709
  }
1419
1710
  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
1711
  var delay = dataSet.idleMs + this.getWeightedBackoffTime(dataSet.backoff);
1425
1712
  if (delay > 0) {
1426
1713
  if (dataSet.timer) {
1427
1714
  clearTimeout(dataSet.timer);
1428
1715
  }
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);
1716
+ dataSet.timer = setTimeout(function () {
1717
+ dataSet.timer = undefined;
1718
+ if (!dataSet.hashTree) {
1719
+ _loggerProxy.default.logger.warn("HashTreeParser#runSyncAlgorithm --> ".concat(_this10.debugId, " Data set \"").concat(dataSet.name, "\" no longer has a hash tree, cannot run sync algorithm"));
1720
+ return;
1721
+ }
1722
+ var rootHash = dataSet.hashTree.getRootHash();
1723
+ if (dataSet.root !== rootHash) {
1724
+ _this10.enqueueSyncForDataset(dataSet.name, "Root hash mismatch: received=".concat(dataSet.root, ", ours=").concat(rootHash));
1725
+ }
1726
+ }, delay);
1461
1727
  } else {
1462
1728
  _loggerProxy.default.logger.info("HashTreeParser#runSyncAlgorithm --> ".concat(this.debugId, " No delay for \"").concat(dataSet.name, "\" data set, skipping sync timer reset/setup"));
1463
1729
  }
@@ -1475,16 +1741,16 @@ var HashTreeParser = /*#__PURE__*/function () {
1475
1741
  }, {
1476
1742
  key: "resetHeartbeatWatchdogs",
1477
1743
  value: function resetHeartbeatWatchdogs(receivedDataSets) {
1478
- var _this12 = this;
1744
+ var _this11 = this;
1479
1745
  if (!this.heartbeatIntervalMs) {
1480
1746
  return;
1481
1747
  }
1482
- var _iterator9 = _createForOfIteratorHelper(receivedDataSets),
1483
- _step9;
1748
+ var _iterator1 = _createForOfIteratorHelper(receivedDataSets),
1749
+ _step1;
1484
1750
  try {
1485
1751
  var _loop2 = function _loop2() {
1486
- var receivedDataSet = _step9.value;
1487
- var dataSet = _this12.dataSets[receivedDataSet.name];
1752
+ var receivedDataSet = _step1.value;
1753
+ var dataSet = _this11.dataSets[receivedDataSet.name];
1488
1754
  if (!(dataSet !== null && dataSet !== void 0 && dataSet.hashTree)) {
1489
1755
  // eslint-disable-next-line no-continue
1490
1756
  return 1; // continue
@@ -1493,30 +1759,26 @@ var HashTreeParser = /*#__PURE__*/function () {
1493
1759
  clearTimeout(dataSet.heartbeatWatchdogTimer);
1494
1760
  dataSet.heartbeatWatchdogTimer = undefined;
1495
1761
  }
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);
1762
+ var backoffTime = _this11.getWeightedBackoffTime(dataSet.backoff);
1763
+ var delay = _this11.heartbeatIntervalMs + backoffTime;
1764
+ dataSet.heartbeatWatchdogTimer = setTimeout(function () {
1765
+ dataSet.heartbeatWatchdogTimer = undefined;
1766
+ _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"));
1767
+ _metrics.default.sendBehavioralMetric(_constants.default.HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED, {
1768
+ debugId: _this11.debugId,
1769
+ dataSetName: dataSet.name
1770
+ });
1771
+ _this11.enqueueSyncForDataset(dataSet.name, "heartbeat watchdog expired");
1772
+ _this11.resetHeartbeatWatchdogs([dataSet]);
1773
+ }, delay);
1512
1774
  };
1513
- for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
1775
+ for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
1514
1776
  if (_loop2()) continue;
1515
1777
  }
1516
1778
  } catch (err) {
1517
- _iterator9.e(err);
1779
+ _iterator1.e(err);
1518
1780
  } finally {
1519
- _iterator9.f();
1781
+ _iterator1.f();
1520
1782
  }
1521
1783
  }
1522
1784
 
@@ -1550,7 +1812,11 @@ var HashTreeParser = /*#__PURE__*/function () {
1550
1812
  value: function stop() {
1551
1813
  _loggerProxy.default.logger.info("HashTreeParser#stop --> ".concat(this.debugId, " Stopping HashTreeParser, clearing timers and hash trees"));
1552
1814
  this.stopAllTimers();
1815
+ this.syncQueue = [];
1553
1816
  (0, _values.default)(this.dataSets).forEach(function (dataSet) {
1817
+ var _dataSet$syncAbortCon2;
1818
+ (_dataSet$syncAbortCon2 = dataSet.syncAbortController) === null || _dataSet$syncAbortCon2 === void 0 ? void 0 : _dataSet$syncAbortCon2.abort();
1819
+ dataSet.syncAbortController = undefined;
1554
1820
  dataSet.hashTree = undefined;
1555
1821
  });
1556
1822
  this.visibleDataSets = [];
@@ -1558,29 +1824,41 @@ var HashTreeParser = /*#__PURE__*/function () {
1558
1824
  }
1559
1825
 
1560
1826
  /**
1561
- * Resumes the HashTreeParser that was previously stopped.
1827
+ * Cleans up the HashTreeParser, stopping all timers and clearing all internal state.
1828
+ * After calling this, the parser should not be used anymore.
1829
+ * @returns {void}
1830
+ */
1831
+ }, {
1832
+ key: "cleanUp",
1833
+ value: function cleanUp() {
1834
+ this.stop();
1835
+ this.dataSets = {};
1836
+ }
1837
+
1838
+ /**
1839
+ * Resumes the HashTreeParser that was previously stopped, using a hash tree message.
1562
1840
  * @param {HashTreeMessage} message - The message to resume with, it must contain metadata with visible data sets info
1563
1841
  * @returns {void}
1564
1842
  */
1565
1843
  }, {
1566
- key: "resume",
1567
- value: function resume(message) {
1844
+ key: "resumeFromMessage",
1845
+ value: function resumeFromMessage(message) {
1568
1846
  var _message$locusStateEl3, _metadataObject$data;
1569
1847
  // check that message contains metadata with visible data sets - this is essential to be able to resume
1570
1848
  var metadataObject = (_message$locusStateEl3 = message.locusStateElements) === null || _message$locusStateEl3 === void 0 ? void 0 : _message$locusStateEl3.find(function (el) {
1571
1849
  return (0, _utils.isMetadata)(el);
1572
1850
  });
1573
1851
  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"));
1852
+ _loggerProxy.default.logger.warn("HashTreeParser#resumeFromMessage --> ".concat(this.debugId, " Cannot resume HashTreeParser because the message is missing metadata with visible data sets info"));
1575
1853
  return;
1576
1854
  }
1577
1855
  this.setVisibleDataSets(metadataObject.data.visibleDataSets, message.dataSets);
1578
1856
  this.dataSets = {};
1579
- var _iterator0 = _createForOfIteratorHelper(message.dataSets),
1580
- _step0;
1857
+ var _iterator10 = _createForOfIteratorHelper(message.dataSets),
1858
+ _step10;
1581
1859
  try {
1582
- for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
1583
- var dataSet = _step0.value;
1860
+ for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
1861
+ var dataSet = _step10.value;
1584
1862
  var name = dataSet.name,
1585
1863
  leafCount = dataSet.leafCount;
1586
1864
  this.dataSets[name] = _objectSpread(_objectSpread({}, dataSet), {}, {
@@ -1588,23 +1866,62 @@ var HashTreeParser = /*#__PURE__*/function () {
1588
1866
  });
1589
1867
  }
1590
1868
  } catch (err) {
1591
- _iterator0.e(err);
1869
+ _iterator10.e(err);
1592
1870
  } finally {
1593
- _iterator0.f();
1871
+ _iterator10.f();
1594
1872
  }
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) {
1873
+ _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
1874
  return ds.name;
1597
1875
  }).join(', ')));
1598
1876
  this.state = 'active';
1599
1877
  this.handleMessage(message, 'on resume');
1600
1878
  }
1879
+
1880
+ /**
1881
+ * Resumes the HashTreeParser that was previously stopped, using a Locus API response.
1882
+ * Unlike resumeFromMessage(), this does not require metadata/dataSets in the input,
1883
+ * as it fetches all necessary information from Locus via initializeFromGetLociResponse.
1884
+ * @param {LocusDTO} locus - locus object from an API response
1885
+ * @returns {Promise}
1886
+ */
1887
+ }, {
1888
+ key: "resumeFromApiResponse",
1889
+ value: (function () {
1890
+ var _resumeFromApiResponse = (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee9(locus) {
1891
+ return _regenerator.default.wrap(function (_context0) {
1892
+ while (1) switch (_context0.prev = _context0.next) {
1893
+ case 0:
1894
+ this.state = 'active';
1895
+ this.dataSets = {};
1896
+ _loggerProxy.default.logger.info("HashTreeParser#resumeFromApiResponse --> ".concat(this.debugId, " Resuming HashTreeParser from API response"));
1897
+ _context0.next = 1;
1898
+ return this.initializeFromGetLociResponse(locus);
1899
+ case 1:
1900
+ case "end":
1901
+ return _context0.stop();
1902
+ }
1903
+ }, _callee9, this);
1904
+ }));
1905
+ function resumeFromApiResponse(_x1) {
1906
+ return _resumeFromApiResponse.apply(this, arguments);
1907
+ }
1908
+ return resumeFromApiResponse;
1909
+ }())
1601
1910
  }, {
1602
1911
  key: "checkForSentinelHttpResponse",
1603
1912
  value: function checkForSentinelHttpResponse(error, dataSetName) {
1604
1913
  var _error$body;
1914
+ // 404 for any dataset means the locus is no longer available at this URL - could be replaced or ended
1915
+ // if a dataset is just not visible, we would get a 400
1916
+ if (error.statusCode === 404) {
1917
+ _loggerProxy.default.logger.info("HashTreeParser#checkForSentinelHttpResponse --> ".concat(this.debugId, " Received 404 for data set \"").concat(dataSetName, "\", locus not found"));
1918
+ this.stopAllTimers();
1919
+ throw new LocusNotFoundError();
1920
+ }
1605
1921
  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"));
1922
+ if (error.statusCode === 409 && ((_error$body = error.body) === null || _error$body === void 0 ? void 0 : _error$body.errorCode) === _types2.LocusErrorCodes.LOCUS_INACTIVE && isValidDataSetForSentinel) {
1923
+ var _error$body2;
1924
+ _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
1925
  this.stopAllTimers();
1609
1926
  throw new MeetingEndedError();
1610
1927
  }
@@ -1614,37 +1931,49 @@ var HashTreeParser = /*#__PURE__*/function () {
1614
1931
  * Gets the current hashes from the locus for a specific data set.
1615
1932
  * @param {string} dataSetName
1616
1933
  * @param {string} currentRootHash
1617
- * @returns {string[]}
1934
+ * @returns {Object|null} An object containing the hashes and leaf count, or null if the hashes match and no sync is needed
1618
1935
  */
1619
1936
  }, {
1620
1937
  key: "getHashesFromLocus",
1621
1938
  value: function getHashesFromLocus(dataSetName, currentRootHash) {
1622
- var _this13 = this;
1939
+ var _this12 = this;
1623
1940
  _loggerProxy.default.logger.info("HashTreeParser#getHashesFromLocus --> ".concat(this.debugId, " Requesting hashes for data set \"").concat(dataSetName, "\""));
1624
1941
  var dataSet = this.dataSets[dataSetName];
1625
1942
  var url = "".concat(dataSet.url, "/hashtree");
1626
1943
  return this.webexRequest({
1627
- method: _constants.HTTP_VERBS.GET,
1944
+ method: _constants2.HTTP_VERBS.GET,
1628
1945
  uri: url,
1629
1946
  qs: {
1630
1947
  rootHash: currentRootHash
1631
1948
  }
1632
1949
  }).then(function (response) {
1633
1950
  var _response$body, _response$body2;
1951
+ if (!response.body || (0, _lodash.isEmpty)(response.body)) {
1952
+ // 204 with empty body means our hashes match Locus, no sync needed
1953
+ _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"));
1954
+ return null;
1955
+ }
1634
1956
  var hashes = (_response$body = response.body) === null || _response$body === void 0 ? void 0 : _response$body.hashes;
1635
1957
  var dataSetFromResponse = (_response$body2 = response.body) === null || _response$body2 === void 0 ? void 0 : _response$body2.dataSet;
1636
1958
  if (!hashes || !(0, _isArray.default)(hashes)) {
1637
- _loggerProxy.default.logger.warn("HashTreeParser#getHashesFromLocus --> ".concat(_this13.debugId, " Locus returned invalid hashes, response body="), response.body);
1959
+ _loggerProxy.default.logger.warn("HashTreeParser#getHashesFromLocus --> ".concat(_this12.debugId, " Locus returned invalid hashes, response body="), response.body);
1638
1960
  throw new Error("Locus returned invalid hashes: ".concat(hashes));
1639
1961
  }
1640
- _loggerProxy.default.logger.info("HashTreeParser#getHashesFromLocus --> ".concat(_this13.debugId, " Received hashes for data set \"").concat(dataSetName, "\": ").concat((0, _stringify.default)(hashes)));
1962
+ _loggerProxy.default.logger.info("HashTreeParser#getHashesFromLocus --> ".concat(_this12.debugId, " Received hashes for data set \"").concat(dataSetName, "\": ").concat((0, _stringify.default)(hashes)));
1641
1963
  return {
1642
1964
  hashes: hashes,
1643
1965
  dataSet: dataSetFromResponse
1644
1966
  };
1645
1967
  }).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);
1968
+ _loggerProxy.default.logger.error("HashTreeParser#getHashesFromLocus --> ".concat(_this12.debugId, " Error ").concat(error.statusCode, " fetching hashes for data set \"").concat(dataSetName, "\":"), error);
1969
+ _this12.checkForSentinelHttpResponse(error, dataSet.name);
1970
+ _metrics.default.sendBehavioralMetric(_constants.default.HASH_TREE_SYNC_FAILURE, {
1971
+ debugId: _this12.debugId,
1972
+ dataSetName: dataSetName,
1973
+ request: 'GET /hashtree',
1974
+ statusCode: error.statusCode,
1975
+ reason: error.message
1976
+ });
1648
1977
  throw error;
1649
1978
  });
1650
1979
  }
@@ -1653,43 +1982,60 @@ var HashTreeParser = /*#__PURE__*/function () {
1653
1982
  * Sends a sync request to Locus for the specified data set.
1654
1983
  *
1655
1984
  * @param {InternalDataSet} dataSet The data set to sync.
1656
- * @param {Record<number, LeafDataItem[]>} mismatchedLeavesData The mismatched leaves data to include in the sync request.
1985
+ * @param {Object} options Either `{ isInitialization: true }` for init syncs (uses leafCount=1 with empty leaf data) or `{ mismatchedLeavesData }` for normal syncs.
1657
1986
  * @returns {Promise<HashTreeMessage|null>}
1658
1987
  */
1659
1988
  }, {
1660
1989
  key: "sendSyncRequestToLocus",
1661
- value: function sendSyncRequestToLocus(dataSet, mismatchedLeavesData) {
1662
- var _this14 = this;
1990
+ value: function sendSyncRequestToLocus(dataSet, options) {
1991
+ var _this13 = this;
1663
1992
  _loggerProxy.default.logger.info("HashTreeParser#sendSyncRequestToLocus --> ".concat(this.debugId, " Sending sync request for data set \"").concat(dataSet.name, "\""));
1993
+ var isInitialization = 'isInitialization' in options;
1664
1994
  var url = "".concat(dataSet.url, "/sync");
1665
1995
  var body = {
1666
- leafCount: dataSet.leafCount,
1996
+ leafCount: isInitialization ? 1 : dataSet.leafCount,
1667
1997
  leafDataEntries: []
1668
1998
  };
1669
- (0, _keys.default)(mismatchedLeavesData).forEach(function (index) {
1999
+ if (isInitialization) {
2000
+ // initialization sync: Locus requires leafCount=1 with a single empty leaf
1670
2001
  body.leafDataEntries.push({
1671
- leafIndex: (0, _parseInt2.default)(index, 10),
1672
- elementIds: mismatchedLeavesData[index]
2002
+ leafIndex: 0,
2003
+ elementIds: []
1673
2004
  });
1674
- });
1675
- var ourCurrentRootHash = dataSet.hashTree ? dataSet.hashTree.getRootHash() : _constants2.EMPTY_HASH;
2005
+ } else {
2006
+ var mismatchedLeavesData = options.mismatchedLeavesData;
2007
+ (0, _keys.default)(mismatchedLeavesData).forEach(function (index) {
2008
+ var leafIndex = (0, _parseInt2.default)(index, 10);
2009
+ body.leafDataEntries.push({
2010
+ leafIndex: leafIndex,
2011
+ elementIds: mismatchedLeavesData[leafIndex]
2012
+ });
2013
+ });
2014
+ }
2015
+ var ourCurrentRootHash = dataSet.hashTree ? dataSet.hashTree.getRootHash() : _constants3.EMPTY_HASH;
1676
2016
  return this.webexRequest({
1677
- method: _constants.HTTP_VERBS.POST,
2017
+ method: _constants2.HTTP_VERBS.POST,
1678
2018
  uri: url,
1679
2019
  qs: {
1680
2020
  rootHash: ourCurrentRootHash
1681
2021
  },
1682
2022
  body: body
1683
2023
  }).then(function (resp) {
1684
- _loggerProxy.default.logger.info("HashTreeParser#sendSyncRequestToLocus --> ".concat(_this14.debugId, " Sync request succeeded for \"").concat(dataSet.name, "\""));
1685
2024
  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"));
2025
+ _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
2026
  return null;
1688
2027
  }
1689
2028
  return resp.body;
1690
2029
  }).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);
2030
+ _loggerProxy.default.logger.error("HashTreeParser#sendSyncRequestToLocus --> ".concat(_this13.debugId, " Error ").concat(error.statusCode, " sending sync request for data set \"").concat(dataSet.name, "\":"), error);
2031
+ _this13.checkForSentinelHttpResponse(error, dataSet.name);
2032
+ _metrics.default.sendBehavioralMetric(_constants.default.HASH_TREE_SYNC_FAILURE, {
2033
+ debugId: _this13.debugId,
2034
+ dataSetName: dataSet.name,
2035
+ request: 'POST /sync',
2036
+ statusCode: error.statusCode,
2037
+ reason: error.message
2038
+ });
1693
2039
  throw error;
1694
2040
  });
1695
2041
  }