@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/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/hashTree/hashTreeParser.js +185 -75
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/types/hashTree/hashTreeParser.d.ts +22 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +13 -13
- package/src/hashTree/hashTreeParser.ts +140 -50
- package/test/unit/spec/hashTree/hashTreeParser.ts +437 -0
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.
|
|
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.
|
|
67
|
-
"@webex/internal-plugin-device": "3.11.0-next.
|
|
68
|
-
"@webex/internal-plugin-llm": "3.11.0-next.
|
|
69
|
-
"@webex/internal-plugin-mercury": "3.11.0-next.
|
|
70
|
-
"@webex/internal-plugin-metrics": "3.11.0-next.
|
|
71
|
-
"@webex/internal-plugin-support": "3.11.0-next.
|
|
72
|
-
"@webex/internal-plugin-user": "3.11.0-next.
|
|
73
|
-
"@webex/internal-plugin-voicea": "3.11.0-next.
|
|
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.
|
|
76
|
-
"@webex/plugin-rooms": "3.11.0-next.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
1151
|
-
|
|
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
|
|