@webex/plugin-meetings 3.11.0-next.24 → 3.11.0-next.26

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.
package/package.json CHANGED
@@ -44,7 +44,7 @@
44
44
  "@webex/event-dictionary-ts": "^1.0.2073",
45
45
  "@webex/jest-config-legacy": "0.0.0",
46
46
  "@webex/legacy-tools": "0.0.0",
47
- "@webex/plugin-rooms": "3.11.0-next.6",
47
+ "@webex/plugin-rooms": "3.11.0-next.7",
48
48
  "@webex/test-helper-chai": "3.11.0-next.1",
49
49
  "@webex/test-helper-mocha": "3.11.0-next.1",
50
50
  "@webex/test-helper-mock-webex": "3.11.0-next.1",
@@ -63,20 +63,20 @@
63
63
  "dependencies": {
64
64
  "@webex/common": "3.11.0-next.1",
65
65
  "@webex/internal-media-core": "2.22.1",
66
- "@webex/internal-plugin-conversation": "3.11.0-next.6",
67
- "@webex/internal-plugin-device": "3.11.0-next.5",
68
- "@webex/internal-plugin-llm": "3.11.0-next.6",
69
- "@webex/internal-plugin-mercury": "3.11.0-next.6",
70
- "@webex/internal-plugin-metrics": "3.11.0-next.5",
71
- "@webex/internal-plugin-support": "3.11.0-next.6",
72
- "@webex/internal-plugin-user": "3.11.0-next.5",
73
- "@webex/internal-plugin-voicea": "3.11.0-next.6",
66
+ "@webex/internal-plugin-conversation": "3.11.0-next.7",
67
+ "@webex/internal-plugin-device": "3.11.0-next.6",
68
+ "@webex/internal-plugin-llm": "3.11.0-next.7",
69
+ "@webex/internal-plugin-mercury": "3.11.0-next.7",
70
+ "@webex/internal-plugin-metrics": "3.11.0-next.6",
71
+ "@webex/internal-plugin-support": "3.11.0-next.7",
72
+ "@webex/internal-plugin-user": "3.11.0-next.6",
73
+ "@webex/internal-plugin-voicea": "3.11.0-next.7",
74
74
  "@webex/media-helpers": "3.11.0-next.3",
75
- "@webex/plugin-people": "3.11.0-next.6",
76
- "@webex/plugin-rooms": "3.11.0-next.6",
75
+ "@webex/plugin-people": "3.11.0-next.7",
76
+ "@webex/plugin-rooms": "3.11.0-next.7",
77
77
  "@webex/ts-sdp": "^1.8.1",
78
78
  "@webex/web-capabilities": "^1.9.0",
79
- "@webex/webex-core": "3.11.0-next.5",
79
+ "@webex/webex-core": "3.11.0-next.6",
80
80
  "ampersand-collection": "^2.0.2",
81
81
  "bowser": "^2.11.0",
82
82
  "btoa": "^1.2.1",
@@ -93,5 +93,5 @@
93
93
  "//": [
94
94
  "TODO: upgrade jwt-decode when moving to node 18"
95
95
  ],
96
- "version": "3.11.0-next.24"
96
+ "version": "3.11.0-next.26"
97
97
  }
@@ -29,6 +29,7 @@ export interface HashTreeMessage {
29
29
  locusStateElements?: Array<HashTreeObject>;
30
30
  locusSessionId?: string;
31
31
  locusUrl: string;
32
+ heartbeatIntervalMs?: number;
32
33
  }
33
34
 
34
35
  export interface VisibleDataSetInfo {
@@ -45,6 +46,7 @@ export interface Metadata {
45
46
  interface InternalDataSet extends DataSet {
46
47
  hashTree?: HashTree; // set only for visible data sets
47
48
  timer?: ReturnType<typeof setTimeout>;
49
+ heartbeatWatchdogTimer?: ReturnType<typeof setTimeout>;
48
50
  }
49
51
 
50
52
  type WebexRequestMethod = (options: Record<string, any>) => Promise<any>;
@@ -88,6 +90,7 @@ class HashTreeParser {
88
90
  locusInfoUpdateCallback: LocusInfoUpdateCallback;
89
91
  visibleDataSets: VisibleDataSetInfo[];
90
92
  debugId: string;
93
+ heartbeatIntervalMs?: number;
91
94
 
92
95
  /**
93
96
  * Constructor for HashTreeParser
@@ -726,11 +729,15 @@ class HashTreeParser {
726
729
  private deleteHashTree(dataSetName: string) {
727
730
  this.dataSets[dataSetName].hashTree = undefined;
728
731
 
729
- // we also need to stop the timer as there is no hash tree anymore to sync
732
+ // we also need to stop the timers as there is no hash tree anymore to sync
730
733
  if (this.dataSets[dataSetName].timer) {
731
734
  clearTimeout(this.dataSets[dataSetName].timer);
732
735
  this.dataSets[dataSetName].timer = undefined;
733
736
  }
737
+ if (this.dataSets[dataSetName].heartbeatWatchdogTimer) {
738
+ clearTimeout(this.dataSets[dataSetName].heartbeatWatchdogTimer);
739
+ this.dataSets[dataSetName].heartbeatWatchdogTimer = undefined;
740
+ }
734
741
  }
735
742
 
736
743
  /**
@@ -1007,11 +1014,20 @@ class HashTreeParser {
1007
1014
  * @returns {void}
1008
1015
  */
1009
1016
  async handleMessage(message: HashTreeMessage, debugText?: string): Promise<void> {
1017
+ if (message.heartbeatIntervalMs) {
1018
+ this.heartbeatIntervalMs = message.heartbeatIntervalMs;
1019
+ }
1010
1020
  if (message.locusStateElements === undefined) {
1011
1021
  this.handleRootHashHeartBeatMessage(message);
1022
+ this.resetHeartbeatWatchdogs(message.dataSets);
1012
1023
  } else {
1013
1024
  const updates = await this.parseMessage(message, debugText);
1014
1025
 
1026
+ // Only reset watchdogs if the meeting hasn't ended
1027
+ if (updates.updateType !== LocusInfoUpdateType.MEETING_ENDED) {
1028
+ this.resetHeartbeatWatchdogs(message.dataSets);
1029
+ }
1030
+
1015
1031
  this.callLocusInfoUpdateCallback(updates);
1016
1032
  }
1017
1033
  }
@@ -1089,6 +1105,75 @@ class HashTreeParser {
1089
1105
  return Math.round(randomValue ** exponent * maxMs);
1090
1106
  }
1091
1107
 
1108
+ /**
1109
+ * Performs a sync for the given data set.
1110
+ *
1111
+ * @param {InternalDataSet} dataSet - The data set to sync
1112
+ * @param {string} rootHash - Our current root hash for this data set
1113
+ * @param {string} reason - The reason for the sync (used for logging)
1114
+ * @returns {Promise<void>}
1115
+ */
1116
+ private async performSync(
1117
+ dataSet: InternalDataSet,
1118
+ rootHash: string,
1119
+ reason: string
1120
+ ): Promise<void> {
1121
+ if (!dataSet.hashTree) {
1122
+ return;
1123
+ }
1124
+
1125
+ LoggerProxy.logger.info(
1126
+ `HashTreeParser#performSync --> ${this.debugId} ${reason}, syncing data set "${dataSet.name}"`
1127
+ );
1128
+
1129
+ const mismatchedLeavesData: Record<number, LeafDataItem[]> = {};
1130
+
1131
+ if (dataSet.leafCount !== 1) {
1132
+ let receivedHashes;
1133
+
1134
+ try {
1135
+ // request hashes from sender
1136
+ const {hashes, dataSet: latestDataSetInfo} = await this.getHashesFromLocus(
1137
+ dataSet.name,
1138
+ rootHash
1139
+ );
1140
+
1141
+ receivedHashes = hashes;
1142
+
1143
+ dataSet.hashTree.resize(latestDataSetInfo.leafCount);
1144
+ } catch (error) {
1145
+ if (error.statusCode === 409) {
1146
+ // this is a leaf count mismatch, we should do nothing, just wait for another heartbeat message from Locus
1147
+ LoggerProxy.logger.info(
1148
+ `HashTreeParser#getHashesFromLocus --> ${this.debugId} Got 409 when fetching hashes for data set "${dataSet.name}": ${error.message}`
1149
+ );
1150
+
1151
+ return;
1152
+ }
1153
+ throw error;
1154
+ }
1155
+
1156
+ // identify mismatched leaves
1157
+ const mismatchedLeaveIndexes = dataSet.hashTree.diffHashes(receivedHashes);
1158
+
1159
+ mismatchedLeaveIndexes.forEach((index) => {
1160
+ mismatchedLeavesData[index] = dataSet.hashTree.getLeafData(index);
1161
+ });
1162
+ } else {
1163
+ mismatchedLeavesData[0] = dataSet.hashTree.getLeafData(0);
1164
+ }
1165
+ // request sync for mismatched leaves
1166
+ if (Object.keys(mismatchedLeavesData).length > 0) {
1167
+ const syncResponse = await this.sendSyncRequestToLocus(dataSet, mismatchedLeavesData);
1168
+
1169
+ // sync API may return nothing (in that case data will arrive via messages)
1170
+ // or it may return a response in the same format as messages
1171
+ if (syncResponse) {
1172
+ this.handleMessage(syncResponse, 'via sync API');
1173
+ }
1174
+ }
1175
+ }
1176
+
1092
1177
  /**
1093
1178
  * Runs the sync algorithm for the given data set.
1094
1179
  *
@@ -1147,56 +1232,11 @@ class HashTreeParser {
1147
1232
  const rootHash = dataSet.hashTree.getRootHash();
1148
1233
 
1149
1234
  if (dataSet.root !== rootHash) {
1150
- LoggerProxy.logger.info(
1151
- `HashTreeParser#runSyncAlgorithm --> ${this.debugId} Root hash mismatch: received=${dataSet.root}, ours=${rootHash}, syncing data set "${dataSet.name}"`
1235
+ await this.performSync(
1236
+ dataSet,
1237
+ rootHash,
1238
+ `Root hash mismatch: received=${dataSet.root}, ours=${rootHash}`
1152
1239
  );
1153
-
1154
- const mismatchedLeavesData: Record<number, LeafDataItem[]> = {};
1155
-
1156
- if (dataSet.leafCount !== 1) {
1157
- let receivedHashes;
1158
-
1159
- try {
1160
- // request hashes from sender
1161
- const {hashes, dataSet: latestDataSetInfo} = await this.getHashesFromLocus(
1162
- dataSet.name,
1163
- rootHash
1164
- );
1165
-
1166
- receivedHashes = hashes;
1167
-
1168
- dataSet.hashTree.resize(latestDataSetInfo.leafCount);
1169
- } catch (error) {
1170
- if (error.statusCode === 409) {
1171
- // this is a leaf count mismatch, we should do nothing, just wait for another heartbeat message from Locus
1172
- LoggerProxy.logger.info(
1173
- `HashTreeParser#getHashesFromLocus --> ${this.debugId} Got 409 when fetching hashes for data set "${dataSet.name}": ${error.message}`
1174
- );
1175
-
1176
- return;
1177
- }
1178
- throw error;
1179
- }
1180
-
1181
- // identify mismatched leaves
1182
- const mismatchedLeaveIndexes = dataSet.hashTree.diffHashes(receivedHashes);
1183
-
1184
- mismatchedLeaveIndexes.forEach((index) => {
1185
- mismatchedLeavesData[index] = dataSet.hashTree.getLeafData(index);
1186
- });
1187
- } else {
1188
- mismatchedLeavesData[0] = dataSet.hashTree.getLeafData(0);
1189
- }
1190
- // request sync for mismatched leaves
1191
- if (Object.keys(mismatchedLeavesData).length > 0) {
1192
- const syncResponse = await this.sendSyncRequestToLocus(dataSet, mismatchedLeavesData);
1193
-
1194
- // sync API may return nothing (in that case data will arrive via messages)
1195
- // or it may return a response in the same format as messages
1196
- if (syncResponse) {
1197
- this.handleMessage(syncResponse, 'via sync API');
1198
- }
1199
- }
1200
1240
  } else {
1201
1241
  LoggerProxy.logger.info(
1202
1242
  `HashTreeParser#runSyncAlgorithm --> ${this.debugId} "${dataSet.name}" root hash matching: ${rootHash}, version=${dataSet.version}`
@@ -1210,6 +1250,52 @@ class HashTreeParser {
1210
1250
  }
1211
1251
  }
1212
1252
 
1253
+ /**
1254
+ * Resets the heartbeat watchdog timers for the specified data sets. Each data set has its own
1255
+ * watchdog timer that monitors whether heartbeats are being received within the expected interval.
1256
+ * If a heartbeat is not received for a specific data set within heartbeatIntervalMs plus
1257
+ * a backoff-calculated time, the sync algorithm is initiated for that data set
1258
+ *
1259
+ * @param {Array<DataSet>} receivedDataSets - The data sets from the received message for which watchdog timers should be reset
1260
+ * @returns {void}
1261
+ */
1262
+ private resetHeartbeatWatchdogs(receivedDataSets: Array<DataSet>): void {
1263
+ if (!this.heartbeatIntervalMs) {
1264
+ return;
1265
+ }
1266
+
1267
+ for (const receivedDataSet of receivedDataSets) {
1268
+ const dataSet = this.dataSets[receivedDataSet.name];
1269
+
1270
+ if (!dataSet?.hashTree) {
1271
+ // eslint-disable-next-line no-continue
1272
+ continue;
1273
+ }
1274
+
1275
+ if (dataSet.heartbeatWatchdogTimer) {
1276
+ clearTimeout(dataSet.heartbeatWatchdogTimer);
1277
+ dataSet.heartbeatWatchdogTimer = undefined;
1278
+ }
1279
+
1280
+ const backoffTime = this.getWeightedBackoffTime(dataSet.backoff);
1281
+ const delay = this.heartbeatIntervalMs + backoffTime;
1282
+
1283
+ dataSet.heartbeatWatchdogTimer = setTimeout(async () => {
1284
+ dataSet.heartbeatWatchdogTimer = undefined;
1285
+
1286
+ LoggerProxy.logger.warn(
1287
+ `HashTreeParser#resetHeartbeatWatchdogs --> ${this.debugId} Heartbeat watchdog fired for data set "${dataSet.name}" - no heartbeat received within expected interval, initiating sync`
1288
+ );
1289
+
1290
+ await this.performSync(
1291
+ dataSet,
1292
+ dataSet.hashTree.getRootHash(),
1293
+ `heartbeat watchdog expired`
1294
+ );
1295
+ }, delay);
1296
+ }
1297
+ }
1298
+
1213
1299
  /**
1214
1300
  * Stops all timers for the data sets to prevent any further sync attempts.
1215
1301
  * @returns {void}
@@ -1220,6 +1306,10 @@ class HashTreeParser {
1220
1306
  clearTimeout(dataSet.timer);
1221
1307
  dataSet.timer = undefined;
1222
1308
  }
1309
+ if (dataSet.heartbeatWatchdogTimer) {
1310
+ clearTimeout(dataSet.heartbeatWatchdogTimer);
1311
+ dataSet.heartbeatWatchdogTimer = undefined;
1312
+ }
1223
1313
  });
1224
1314
  }
1225
1315