@webex/plugin-meetings 3.12.0-next.10 → 3.12.0-next.11

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.
@@ -57,10 +57,15 @@ export const LocusInfoUpdateType = {
57
57
  } as const;
58
58
 
59
59
  export type LocusInfoUpdateType = Enum<typeof LocusInfoUpdateType>;
60
- export type LocusInfoUpdateCallback = (
61
- updateType: LocusInfoUpdateType,
62
- data?: {updatedObjects: HashTreeObject[]}
63
- ) => void;
60
+ export type LocusInfoUpdate =
61
+ | {
62
+ updateType: typeof LocusInfoUpdateType.OBJECTS_UPDATED;
63
+ updatedObjects: HashTreeObject[];
64
+ }
65
+ | {
66
+ updateType: typeof LocusInfoUpdateType.MEETING_ENDED;
67
+ };
68
+ export type LocusInfoUpdateCallback = (update: LocusInfoUpdate) => void;
64
69
 
65
70
  interface LeafInfo {
66
71
  type: ObjectType;
@@ -227,7 +232,7 @@ class HashTreeParser {
227
232
  private initializeNewVisibleDataSet(
228
233
  visibleDataSetInfo: VisibleDataSetInfo,
229
234
  dataSetInfo: DataSet
230
- ): Promise<{updateType: LocusInfoUpdateType; updatedObjects?: HashTreeObject[]}> {
235
+ ): Promise<LocusInfoUpdate> {
231
236
  if (this.isVisibleDataSet(dataSetInfo.name)) {
232
237
  LoggerProxy.logger.info(
233
238
  `HashTreeParser#initializeNewVisibleDataSet --> ${this.debugId} Data set "${dataSetInfo.name}" already exists, skipping init`
@@ -264,7 +269,7 @@ class HashTreeParser {
264
269
  private sendInitializationSyncRequestToLocus(
265
270
  datasetName: string,
266
271
  debugText: string
267
- ): Promise<{updateType: LocusInfoUpdateType; updatedObjects?: HashTreeObject[]}> {
272
+ ): Promise<LocusInfoUpdate> {
268
273
  const dataset = this.dataSets[datasetName];
269
274
 
270
275
  if (!dataset) {
@@ -272,7 +277,7 @@ class HashTreeParser {
272
277
  `HashTreeParser#sendInitializationSyncRequestToLocus --> ${this.debugId} No data set found for ${datasetName}, cannot send the request for leaf data`
273
278
  );
274
279
 
275
- return Promise.resolve(null);
280
+ return Promise.resolve({updateType: LocusInfoUpdateType.OBJECTS_UPDATED, updatedObjects: []});
276
281
  }
277
282
 
278
283
  const emptyLeavesData = new Array(dataset.leafCount).fill([]);
@@ -1014,7 +1019,7 @@ class HashTreeParser {
1014
1019
 
1015
1020
  // when we detect new visible datasets, it may be that the metadata about them is not
1016
1021
  // available in the message, they will require separate async initialization
1017
- let dataSetsRequiringInitialization = [];
1022
+ let dataSetsRequiringInitialization: VisibleDataSetInfo[] = [];
1018
1023
 
1019
1024
  // first find out if there are any visible data set changes - they're signalled in Metadata object updates
1020
1025
  const metadataUpdates = (message.locusStateElements || []).filter((object) =>
@@ -1022,7 +1027,7 @@ class HashTreeParser {
1022
1027
  );
1023
1028
 
1024
1029
  if (metadataUpdates.length > 0) {
1025
- const updatedMetadataObjects = [];
1030
+ const updatedMetadataObjects: HashTreeObject[] = [];
1026
1031
 
1027
1032
  metadataUpdates.forEach((object) => {
1028
1033
  // todo: once Locus supports it, we will use the "view" field here instead of dataSetNames
@@ -1051,7 +1056,7 @@ class HashTreeParser {
1051
1056
  }
1052
1057
  }
1053
1058
 
1054
- if (message.locusStateElements?.length > 0) {
1059
+ if (message.locusStateElements && message.locusStateElements.length > 0) {
1055
1060
  // by this point we now have this.dataSets setup for data sets from this message
1056
1061
  // and hash trees created for the new visible data sets,
1057
1062
  // so we can now process all the updates from the message
@@ -1147,20 +1152,17 @@ class HashTreeParser {
1147
1152
  * @param {Object} updates parsed from a Locus message
1148
1153
  * @returns {void}
1149
1154
  */
1150
- private callLocusInfoUpdateCallback(updates: {
1151
- updateType: LocusInfoUpdateType;
1152
- updatedObjects?: HashTreeObject[];
1153
- }) {
1155
+ private callLocusInfoUpdateCallback(updates: LocusInfoUpdate) {
1154
1156
  if (this.state === 'stopped') {
1155
1157
  return;
1156
1158
  }
1157
1159
 
1158
- const {updateType, updatedObjects} = updates;
1160
+ const {updateType} = updates;
1159
1161
 
1160
- if (updateType === LocusInfoUpdateType.OBJECTS_UPDATED && updatedObjects?.length > 0) {
1162
+ if (updateType === LocusInfoUpdateType.OBJECTS_UPDATED && updates.updatedObjects?.length > 0) {
1161
1163
  // Filter out updates for objects that already have a higher version in their datasets,
1162
1164
  // or removals for objects that still exist in any of their datasets
1163
- const filteredUpdates = updatedObjects.filter((object) => {
1165
+ const filteredUpdates = updates.updatedObjects.filter((object) => {
1164
1166
  const {elementId} = object.htMeta;
1165
1167
  const {type, id, version} = elementId;
1166
1168
 
@@ -1197,10 +1199,10 @@ class HashTreeParser {
1197
1199
  });
1198
1200
 
1199
1201
  if (filteredUpdates.length > 0) {
1200
- this.locusInfoUpdateCallback(updateType, {updatedObjects: filteredUpdates});
1202
+ this.locusInfoUpdateCallback({updateType, updatedObjects: filteredUpdates});
1201
1203
  }
1202
1204
  } else if (updateType !== LocusInfoUpdateType.OBJECTS_UPDATED) {
1203
- this.locusInfoUpdateCallback(updateType, {updatedObjects});
1205
+ this.locusInfoUpdateCallback({updateType});
1204
1206
  }
1205
1207
  }
1206
1208
 
@@ -1457,6 +1459,16 @@ class HashTreeParser {
1457
1459
  this.state = 'stopped';
1458
1460
  }
1459
1461
 
1462
+ /**
1463
+ * Cleans up the HashTreeParser, stopping all timers and clearing all internal state.
1464
+ * After calling this, the parser should not be used anymore.
1465
+ * @returns {void}
1466
+ */
1467
+ public cleanUp() {
1468
+ this.stop();
1469
+ this.dataSets = {};
1470
+ }
1471
+
1460
1472
  /**
1461
1473
  * Resumes the HashTreeParser that was previously stopped.
1462
1474
  * @param {HashTreeMessage} message - The message to resume with, it must contain metadata with visible data sets info
@@ -1591,15 +1603,20 @@ class HashTreeParser {
1591
1603
  );
1592
1604
 
1593
1605
  const url = `${dataSet.url}/sync`;
1594
- const body = {
1606
+ const body: {
1607
+ leafCount: number;
1608
+ leafDataEntries: {leafIndex: number; elementIds: LeafDataItem[]}[];
1609
+ } = {
1595
1610
  leafCount: dataSet.leafCount,
1596
1611
  leafDataEntries: [],
1597
1612
  };
1598
1613
 
1599
1614
  Object.keys(mismatchedLeavesData).forEach((index) => {
1615
+ const leafIndex = parseInt(index, 10);
1616
+
1600
1617
  body.leafDataEntries.push({
1601
- leafIndex: parseInt(index, 10),
1602
- elementIds: mismatchedLeavesData[index],
1618
+ leafIndex,
1619
+ elementIds: mismatchedLeavesData[leafIndex],
1603
1620
  });
1604
1621
  });
1605
1622
 
@@ -34,6 +34,7 @@ import BEHAVIORAL_METRICS from '../metrics/constants';
34
34
  import HashTreeParser, {
35
35
  DataSet,
36
36
  HashTreeMessage,
37
+ LocusInfoUpdate,
37
38
  LocusInfoUpdateType,
38
39
  Metadata,
39
40
  } from '../hashTree/hashTreeParser';
@@ -545,7 +546,7 @@ export default class LocusInfo extends EventsScope {
545
546
  dataSets: Array<DataSet>;
546
547
  locus: any;
547
548
  };
548
- metadata: Metadata;
549
+ metadata: Metadata | null;
549
550
  replacedAt?: string;
550
551
  }): HashTreeParser {
551
552
  const parser = new HashTreeParser({
@@ -553,7 +554,7 @@ export default class LocusInfo extends EventsScope {
553
554
  metadata,
554
555
  webexRequest: this.webex.request.bind(this.webex),
555
556
  locusInfoUpdateCallback: this.updateFromHashTree.bind(this, locusUrl),
556
- debugId: `HT-${locusUrl.split('/').pop().substring(0, 4)}`,
557
+ debugId: `HT-${locusUrl.split('/')?.pop()?.substring(0, 4)}`,
557
558
  excludedDataSets: this.webex.config.meetings.locus?.excludedDataSets,
558
559
  });
559
560
 
@@ -656,7 +657,7 @@ export default class LocusInfo extends EventsScope {
656
657
  );
657
658
  // first create the HashTreeParser, but don't initialize it with any data yet
658
659
  const hashTreeParser = this.createHashTreeParser({
659
- locusUrl: data.locus.url,
660
+ locusUrl: data.locus.url as string,
660
661
  initialLocus: {
661
662
  locus: null,
662
663
  dataSets: [], // empty, because we don't have them yet
@@ -965,7 +966,7 @@ export default class LocusInfo extends EventsScope {
965
966
  // but it's buried inside the message, we need to find it and pass it to HashTreeParser constructor
966
967
  const metadata = message.locusStateElements?.find((el) => isMetadata(el));
967
968
 
968
- if (metadata?.data?.visibleDataSets?.length > 0) {
969
+ if (metadata && metadata.data?.visibleDataSets?.length > 0) {
969
970
  LoggerProxy.logger.info(
970
971
  `Locus-info:index#handleHashTreeParserSwitch --> no hash tree parser found for locusUrl ${message.locusUrl}, creating a new one`
971
972
  );
@@ -1056,7 +1057,10 @@ export default class LocusInfo extends EventsScope {
1056
1057
 
1057
1058
  const entry = this.hashTreeParsers.get(message.locusUrl);
1058
1059
 
1059
- entry.parser.handleMessage(message);
1060
+ // the check is just for typescript, the case of no entry in hashTreeParsers is handled in handleHashTreeParserSwitch() above
1061
+ if (entry) {
1062
+ entry.parser.handleMessage(message);
1063
+ }
1060
1064
  }
1061
1065
 
1062
1066
  /**
@@ -1064,16 +1068,11 @@ export default class LocusInfo extends EventsScope {
1064
1068
  * Updates our locus info based on the data parsed by the hash tree parser.
1065
1069
  *
1066
1070
  * @param {string} locusUrl - the locus URL for which the update is received
1067
- * @param {LocusInfoUpdateType} updateType - The type of update received.
1068
- * @param {Object} [data] - Additional data for the update, if applicable.
1071
+ * @param {LocusInfoUpdate} update - Details about the update.
1069
1072
  * @returns {void}
1070
1073
  */
1071
- private updateFromHashTree(
1072
- locusUrl: string,
1073
- updateType: LocusInfoUpdateType,
1074
- data?: {updatedObjects: HashTreeObject[]}
1075
- ) {
1076
- switch (updateType) {
1074
+ private updateFromHashTree(locusUrl: string, update: LocusInfoUpdate) {
1075
+ switch (update.updateType) {
1077
1076
  case LocusInfoUpdateType.OBJECTS_UPDATED: {
1078
1077
  // initialize our new locus
1079
1078
  let locus: LocusDTO = {
@@ -1087,7 +1086,7 @@ export default class LocusInfo extends EventsScope {
1087
1086
  // first go over all the updates and check what happens with the main locus object
1088
1087
  let locusObjectStateAfterUpdates: LocusObjectStateAfterUpdates =
1089
1088
  LocusObjectStateAfterUpdates.unchanged;
1090
- data.updatedObjects.forEach((object) => {
1089
+ update.updatedObjects.forEach((object) => {
1091
1090
  if (object.htMeta.elementId.type.toLowerCase() === ObjectType.locus) {
1092
1091
  if (locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.updated) {
1093
1092
  // this code doesn't supported it right now,
@@ -1116,6 +1115,14 @@ export default class LocusInfo extends EventsScope {
1116
1115
 
1117
1116
  const hashTreeParserEntry = this.hashTreeParsers.get(locusUrl);
1118
1117
 
1118
+ if (!hashTreeParserEntry) {
1119
+ LoggerProxy.logger.warn(
1120
+ `Locus-info:index#updateFromHashTree --> no HashTreeParser found for locusUrl ${locusUrl} when trying to apply updates from hash tree`
1121
+ );
1122
+
1123
+ return;
1124
+ }
1125
+
1119
1126
  if (!hashTreeParserEntry.initializedFromHashTree) {
1120
1127
  // this is the first time we're getting an update for this locusUrl,
1121
1128
  // so it's probably a move to/from breakout. We need to start from a clean state,
@@ -1124,7 +1131,8 @@ export default class LocusInfo extends EventsScope {
1124
1131
  `Locus-info:index#updateFromHashTree --> first INITIAL update for locusUrl ${locusUrl}, starting from empty state`
1125
1132
  );
1126
1133
  hashTreeParserEntry.initializedFromHashTree = true;
1127
- locus.jsSdkMeta.forceReplaceMembers = true;
1134
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1135
+ locus.jsSdkMeta!.forceReplaceMembers = true;
1128
1136
  } else if (
1129
1137
  // if Locus object is unchanged or removed, we need to keep using the existing locus
1130
1138
  // because the rest of the locusInfo code expects locus to always be present (with at least some of the fields)
@@ -1137,7 +1145,7 @@ export default class LocusInfo extends EventsScope {
1137
1145
  // copy over all of existing locus except participants
1138
1146
  LocusDtoTopLevelKeys.forEach((key) => {
1139
1147
  if (key !== 'participants') {
1140
- locus[key] = cloneDeep(this[key]);
1148
+ (locus as Record<string, any>)[key] = cloneDeep((this as Record<string, any>)[key]);
1141
1149
  }
1142
1150
  });
1143
1151
  } else {
@@ -1145,14 +1153,16 @@ export default class LocusInfo extends EventsScope {
1145
1153
  // (except participants, which need to stay empty - that means "no participant changes")
1146
1154
  Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
1147
1155
  if (locusDtoKey !== 'participants') {
1148
- locus[locusDtoKey] = cloneDeep(this[locusDtoKey]);
1156
+ (locus as Record<string, any>)[locusDtoKey] = cloneDeep(
1157
+ (this as Record<string, any>)[locusDtoKey]
1158
+ );
1149
1159
  }
1150
1160
  });
1151
1161
  }
1152
1162
 
1153
1163
  LoggerProxy.logger.info(
1154
1164
  `Locus-info:index#updateFromHashTree --> LOCUS object is ${locusObjectStateAfterUpdates}, all updates: ${JSON.stringify(
1155
- data.updatedObjects.map((o) => ({
1165
+ update.updatedObjects.map((o) => ({
1156
1166
  type: o.htMeta.elementId.type,
1157
1167
  id: o.htMeta.elementId.id,
1158
1168
  hasData: !!o.data,
@@ -1160,7 +1170,7 @@ export default class LocusInfo extends EventsScope {
1160
1170
  )}`
1161
1171
  );
1162
1172
  // now apply all the updates from the hash tree onto the locus
1163
- data.updatedObjects.forEach((object) => {
1173
+ update.updatedObjects.forEach((object) => {
1164
1174
  locus = this.updateLocusFromHashTreeObject(object, locus);
1165
1175
  });
1166
1176
 
@@ -1260,16 +1270,16 @@ export default class LocusInfo extends EventsScope {
1260
1270
  * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
1261
1271
  * @param {object} locus locus object
1262
1272
  * @param {object} metadata locus hash trees metadata
1263
- * @param {string} eventType locus event
1264
1273
  * @param {DataSet[]} dataSets
1274
+ * @param {string} eventType locus event
1265
1275
  * @returns {void}
1266
1276
  */
1267
1277
  private onFullLocusWithHashTrees(
1268
1278
  debugText: string,
1269
1279
  locus: any,
1270
1280
  metadata: Metadata,
1271
- eventType?: string,
1272
- dataSets?: Array<DataSet>
1281
+ dataSets: Array<DataSet>,
1282
+ eventType?: string
1273
1283
  ) {
1274
1284
  if (!this.hashTreeParsers.has(locus.url)) {
1275
1285
  LoggerProxy.logger.info(
@@ -1289,7 +1299,8 @@ export default class LocusInfo extends EventsScope {
1289
1299
  metadata,
1290
1300
  });
1291
1301
  // we have a full locus to start with, so we consider Locus info to be "initialized"
1292
- this.hashTreeParsers.get(locus.url).initializedFromHashTree = true;
1302
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1303
+ this.hashTreeParsers.get(locus.url)!.initializedFromHashTree = true;
1293
1304
  this.onFullLocusCommon(locus, eventType);
1294
1305
  } else {
1295
1306
  // in this case the Locus we're getting is not necessarily the full one
@@ -1351,7 +1362,7 @@ export default class LocusInfo extends EventsScope {
1351
1362
  );
1352
1363
  }
1353
1364
  // this is the new hashmap Locus DTO format (only applicable to webinars for now)
1354
- this.onFullLocusWithHashTrees(debugText, locus, metadata, eventType, dataSets);
1365
+ this.onFullLocusWithHashTrees(debugText, locus, metadata, dataSets, eventType);
1355
1366
  } else {
1356
1367
  this.onFullLocusClassic(debugText, locus, eventType);
1357
1368
  }
@@ -2859,4 +2870,17 @@ export default class LocusInfo extends EventsScope {
2859
2870
  clearMainSessionLocusCache() {
2860
2871
  this.mainSessionLocusCache = null;
2861
2872
  }
2873
+
2874
+ /**
2875
+ * Cleans up all hash tree parsers and clears internal maps.
2876
+ * @returns {void}
2877
+ * @memberof LocusInfo
2878
+ */
2879
+ cleanUp() {
2880
+ this.hashTreeParsers.forEach((entry) => {
2881
+ entry.parser.cleanUp();
2882
+ });
2883
+ this.hashTreeParsers.clear();
2884
+ this.hashTreeObjectId2ParticipantId.clear();
2885
+ }
2862
2886
  }
@@ -371,6 +371,7 @@ const MeetingUtil = {
371
371
  meeting.breakouts.cleanUp();
372
372
  meeting.webinar.cleanUp();
373
373
  meeting.simultaneousInterpretation.cleanUp();
374
+ meeting.locusInfo.cleanUp();
374
375
  meeting.locusMediaRequest = undefined;
375
376
 
376
377
  meeting.webex?.internal?.newMetrics?.callDiagnosticMetrics?.clearEventLimitsForCorrelationId(
@@ -553,8 +553,7 @@ describe('HashTreeParser', () => {
553
553
  );
554
554
 
555
555
  // Verify callback was called with OBJECTS_UPDATED and correct updatedObjects list
556
- // Note: main is initialized before self due to sortByInitPriority
557
- assert.calledWith(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
556
+ assert.calledWith(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
558
557
  updatedObjects: [
559
558
  {
560
559
  htMeta: {
@@ -824,7 +823,7 @@ describe('HashTreeParser', () => {
824
823
  expect(parser.dataSets.self.version).to.equal(2100);
825
824
  expect(parser.dataSets['atd-unmuted'].version).to.equal(3100);
826
825
 
827
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
826
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
828
827
  updatedObjects: [
829
828
  {
830
829
  htMeta: {
@@ -955,7 +954,7 @@ describe('HashTreeParser', () => {
955
954
  {type: 'ControlEntry', id: 10101, version: 100}
956
955
  ]);
957
956
 
958
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
957
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
959
958
  updatedObjects: [
960
959
  {
961
960
  htMeta: {
@@ -1045,7 +1044,7 @@ describe('HashTreeParser', () => {
1045
1044
  assert.calledOnceWithExactly(mainPutItemsSpy, [{type: 'locus', id: 0, version: 201}]);
1046
1045
 
1047
1046
  // Verify callback was called only for known dataset
1048
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
1047
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
1049
1048
  updatedObjects: [
1050
1049
  {
1051
1050
  htMeta: {
@@ -1145,7 +1144,7 @@ describe('HashTreeParser', () => {
1145
1144
  assert.calledOnceWithExactly(selfPutItemSpy, {type: 'metadata', id: 5, version: 51});
1146
1145
 
1147
1146
  // Verify callback was called with metadata object and removed dataset objects
1148
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
1147
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
1149
1148
  updatedObjects: [
1150
1149
  // updated metadata object:
1151
1150
  {
@@ -1306,7 +1305,7 @@ describe('HashTreeParser', () => {
1306
1305
  assert.notCalled(atdUnmutedPutItemsSpy);
1307
1306
 
1308
1307
  // Verify callback was called with the updated object
1309
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
1308
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
1310
1309
  updatedObjects: [
1311
1310
  {
1312
1311
  htMeta: {
@@ -1534,7 +1533,7 @@ describe('HashTreeParser', () => {
1534
1533
  ]);
1535
1534
 
1536
1535
  // Verify callback was called with OBJECTS_UPDATED and all updated objects
1537
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
1536
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
1538
1537
  updatedObjects: [
1539
1538
  {
1540
1539
  htMeta: {
@@ -1599,9 +1598,7 @@ describe('HashTreeParser', () => {
1599
1598
  parser.handleMessage(sentinelMessage, 'sentinel message');
1600
1599
 
1601
1600
  // Verify callback was called with MEETING_ENDED
1602
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED, {
1603
- updatedObjects: undefined,
1604
- });
1601
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
1605
1602
 
1606
1603
  // Verify that all timers were stopped
1607
1604
  Object.values(parser.dataSets).forEach((ds: any) => {
@@ -1623,9 +1620,7 @@ describe('HashTreeParser', () => {
1623
1620
  parser.handleMessage(sentinelMessage, 'sentinel message');
1624
1621
 
1625
1622
  // Verify callback was called with MEETING_ENDED
1626
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED, {
1627
- updatedObjects: undefined,
1628
- });
1623
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
1629
1624
 
1630
1625
  // Verify that all timers were stopped
1631
1626
  Object.values(parser.dataSets).forEach((ds: any) => {
@@ -1721,7 +1716,7 @@ describe('HashTreeParser', () => {
1721
1716
  );
1722
1717
 
1723
1718
  // Verify that callback was called with synced objects
1724
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
1719
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
1725
1720
  updatedObjects: [
1726
1721
  {
1727
1722
  htMeta: {
@@ -1783,9 +1778,7 @@ describe('HashTreeParser', () => {
1783
1778
  await clock.tickAsync(1000);
1784
1779
 
1785
1780
  // Verify callback was called with MEETING_ENDED
1786
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED, {
1787
- updatedObjects: undefined,
1788
- });
1781
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
1789
1782
 
1790
1783
  // Verify all timers are stopped
1791
1784
  Object.values(parser.dataSets).forEach((ds: any) => {
@@ -1848,9 +1841,7 @@ describe('HashTreeParser', () => {
1848
1841
  await clock.tickAsync(1000);
1849
1842
 
1850
1843
  // Verify callback was called with MEETING_ENDED
1851
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED, {
1852
- updatedObjects: undefined,
1853
- });
1844
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
1854
1845
 
1855
1846
  // Verify all timers are stopped
1856
1847
  Object.values(parser.dataSets).forEach((ds: any) => {
@@ -2088,7 +2079,7 @@ describe('HashTreeParser', () => {
2088
2079
  assert.equal(parser.dataSets.attendees.hashTree.numLeaves, 8);
2089
2080
 
2090
2081
  // Verify callback was called with the metadata update (appears twice - processed once for visible dataset changes, once in main loop)
2091
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
2082
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
2092
2083
  updatedObjects: [
2093
2084
  {
2094
2085
  htMeta: {
@@ -2366,9 +2357,7 @@ describe('HashTreeParser', () => {
2366
2357
  await clock.tickAsync(0);
2367
2358
 
2368
2359
  // Verify callback was called with MEETING_ENDED
2369
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED, {
2370
- updatedObjects: undefined,
2371
- });
2360
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
2372
2361
  });
2373
2362
 
2374
2363
  it('handles removal of visible data set', async () => {
@@ -2431,7 +2420,7 @@ describe('HashTreeParser', () => {
2431
2420
  assert.isUndefined(parser.dataSets['atd-unmuted'].timer);
2432
2421
 
2433
2422
  // Verify callback was called with the metadata update and the removed objects (metadata appears twice - processed once for dataset changes, once in main loop)
2434
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
2423
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
2435
2424
  updatedObjects: [
2436
2425
  {
2437
2426
  htMeta: {
@@ -3050,7 +3039,7 @@ describe('HashTreeParser', () => {
3050
3039
  parser.handleMessage(updateMessage, 'update with newer version');
3051
3040
 
3052
3041
  // Callback should be called with the update
3053
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
3042
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
3054
3043
  updatedObjects: [
3055
3044
  {
3056
3045
  htMeta: {
@@ -3121,7 +3110,7 @@ describe('HashTreeParser', () => {
3121
3110
  parser.handleMessage(removalMessage, 'removal of non-existent object');
3122
3111
 
3123
3112
  // Callback should be called with the removal
3124
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
3113
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
3125
3114
  updatedObjects: [
3126
3115
  {
3127
3116
  htMeta: {
@@ -3256,7 +3245,7 @@ describe('HashTreeParser', () => {
3256
3245
  parser.handleMessage(mixedMessage, 'mixed updates');
3257
3246
 
3258
3247
  // Callback should be called with only the valid updates (participant 1 v110 and participant 3 v10)
3259
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
3248
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
3260
3249
  updatedObjects: [
3261
3250
  {
3262
3251
  htMeta: {
@@ -3434,9 +3423,7 @@ describe('HashTreeParser', () => {
3434
3423
  parser.handleMessage(sentinelMessage as any, 'sentinel message');
3435
3424
 
3436
3425
  // Callback should be called with MEETING_ENDED
3437
- assert.calledOnceWithExactly(callback, LocusInfoUpdateType.MEETING_ENDED, {
3438
- updatedObjects: undefined,
3439
- });
3426
+ assert.calledOnceWithExactly(callback, {updateType: LocusInfoUpdateType.MEETING_ENDED});
3440
3427
  });
3441
3428
  });
3442
3429
 
@@ -3795,4 +3782,44 @@ describe('HashTreeParser', () => {
3795
3782
  assert.notCalled(callback);
3796
3783
  });
3797
3784
  });
3785
+
3786
+ describe('#cleanUp', () => {
3787
+ it('should stop the parser, clear all timers and clear all dataSets', () => {
3788
+ const parser = createHashTreeParser();
3789
+
3790
+ // Send a message to set up sync timers via runSyncAlgorithm
3791
+ const message = {
3792
+ dataSets: [
3793
+ {
3794
+ ...createDataSet('main', 16, 1100),
3795
+ root: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1',
3796
+ },
3797
+ ],
3798
+ visibleDataSetsUrl,
3799
+ locusUrl,
3800
+ heartbeatIntervalMs: 5000,
3801
+ locusStateElements: [
3802
+ {
3803
+ htMeta: {
3804
+ elementId: {type: 'locus' as const, id: 0, version: 201},
3805
+ dataSetNames: ['main'],
3806
+ },
3807
+ data: {someData: 'value'},
3808
+ },
3809
+ ],
3810
+ };
3811
+
3812
+ parser.handleMessage(message, 'setup timers');
3813
+
3814
+ // Verify timers were set by handleMessage
3815
+ expect(parser.dataSets.main.timer).to.not.be.undefined;
3816
+ expect(parser.dataSets.main.heartbeatWatchdogTimer).to.not.be.undefined;
3817
+
3818
+ parser.cleanUp();
3819
+
3820
+ expect(parser.state).to.equal('stopped');
3821
+ expect(parser.visibleDataSets).to.deep.equal([]);
3822
+ expect(parser.dataSets).to.deep.equal({});
3823
+ });
3824
+ });
3798
3825
  });