@webex/plugin-meetings 3.11.0-next.2 → 3.11.0-next.21
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/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +307 -139
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +2 -1
- package/dist/hashTree/types.js.map +1 -1
- package/dist/hashTree/utils.js +10 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +55 -42
- package/dist/locus-info/index.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +57 -1
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +4 -2
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.js +33 -22
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +108 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +76 -26
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +1 -1
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +47 -12
- package/dist/types/hashTree/types.d.ts +1 -0
- package/dist/types/hashTree/utils.d.ts +6 -0
- package/dist/types/locus-info/index.d.ts +9 -2
- package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
- package/dist/types/media/properties.d.ts +2 -1
- package/dist/types/meeting/index.d.ts +8 -5
- package/dist/types/meeting/util.d.ts +28 -0
- package/dist/types/meetings/index.d.ts +3 -1
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -22
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +294 -96
- package/src/hashTree/types.ts +1 -0
- package/src/hashTree/utils.ts +9 -0
- package/src/locus-info/index.ts +83 -35
- package/src/media/MediaConnectionAwaiter.ts +41 -1
- package/src/media/properties.ts +3 -1
- package/src/meeting/index.ts +24 -11
- package/src/meeting/util.ts +132 -1
- package/src/meetings/index.ts +93 -8
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +1 -1
- package/src/reactions/reactions.type.ts +1 -0
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +942 -110
- package/test/unit/spec/locus-info/index.js +88 -17
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/index.js +160 -2
- package/test/unit/spec/meeting/utils.js +294 -22
- package/test/unit/spec/meetings/index.js +594 -17
|
@@ -5,7 +5,7 @@ import {Enum, HTTP_VERBS} from '../constants';
|
|
|
5
5
|
import {DataSetNames, EMPTY_HASH} from './constants';
|
|
6
6
|
import {ObjectType, HtMeta, HashTreeObject} from './types';
|
|
7
7
|
import {LocusDTO} from '../locus-info/types';
|
|
8
|
-
import {deleteNestedObjectsWithHtMeta, isSelf} from './utils';
|
|
8
|
+
import {deleteNestedObjectsWithHtMeta, isMetadata, isSelf} from './utils';
|
|
9
9
|
|
|
10
10
|
export interface DataSet {
|
|
11
11
|
url: string;
|
|
@@ -31,6 +31,17 @@ export interface HashTreeMessage {
|
|
|
31
31
|
locusUrl: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export interface VisibleDataSetInfo {
|
|
35
|
+
name: string;
|
|
36
|
+
url: string;
|
|
37
|
+
dataChannelUrl?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Metadata {
|
|
41
|
+
htMeta: HtMeta;
|
|
42
|
+
visibleDataSets: VisibleDataSetInfo[];
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
interface InternalDataSet extends DataSet {
|
|
35
46
|
hashTree?: HashTree; // set only for visible data sets
|
|
36
47
|
timer?: ReturnType<typeof setTimeout>;
|
|
@@ -49,12 +60,24 @@ export type LocusInfoUpdateCallback = (
|
|
|
49
60
|
data?: {updatedObjects: HashTreeObject[]}
|
|
50
61
|
) => void;
|
|
51
62
|
|
|
63
|
+
interface LeafInfo {
|
|
64
|
+
type: ObjectType;
|
|
65
|
+
id: number;
|
|
66
|
+
version: number;
|
|
67
|
+
data?: any;
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
/**
|
|
53
71
|
* This error is thrown if we receive information that the meeting has ended while we're processing some hash messages.
|
|
54
72
|
* It's handled internally by HashTreeParser and results in MEETING_ENDED being sent up.
|
|
55
73
|
*/
|
|
56
74
|
class MeetingEndedError extends Error {}
|
|
57
75
|
|
|
76
|
+
/* Currently Locus always sends Metadata objects only in the "self" dataset.
|
|
77
|
+
* If this ever changes, update all the code that relies on this constant.
|
|
78
|
+
*/
|
|
79
|
+
const MetadataDataSetName = DataSetNames.SELF;
|
|
80
|
+
|
|
58
81
|
/**
|
|
59
82
|
* Parses hash tree eventing locus data
|
|
60
83
|
*/
|
|
@@ -63,7 +86,7 @@ class HashTreeParser {
|
|
|
63
86
|
visibleDataSetsUrl: string; // url from which we can get info about all data sets
|
|
64
87
|
webexRequest: WebexRequestMethod;
|
|
65
88
|
locusInfoUpdateCallback: LocusInfoUpdateCallback;
|
|
66
|
-
visibleDataSets:
|
|
89
|
+
visibleDataSets: VisibleDataSetInfo[];
|
|
67
90
|
debugId: string;
|
|
68
91
|
|
|
69
92
|
/**
|
|
@@ -76,6 +99,7 @@ class HashTreeParser {
|
|
|
76
99
|
dataSets: Array<DataSet>;
|
|
77
100
|
locus: any;
|
|
78
101
|
};
|
|
102
|
+
metadata: Metadata | null;
|
|
79
103
|
webexRequest: WebexRequestMethod;
|
|
80
104
|
locusInfoUpdateCallback: LocusInfoUpdateCallback;
|
|
81
105
|
debugId: string;
|
|
@@ -85,16 +109,22 @@ class HashTreeParser {
|
|
|
85
109
|
this.debugId = options.debugId;
|
|
86
110
|
this.webexRequest = options.webexRequest;
|
|
87
111
|
this.locusInfoUpdateCallback = options.locusInfoUpdateCallback;
|
|
88
|
-
this.
|
|
112
|
+
this.visibleDataSetsUrl = locus?.links?.resources?.visibleDataSets?.url;
|
|
113
|
+
this.visibleDataSets = cloneDeep(options.metadata?.visibleDataSets || []);
|
|
89
114
|
|
|
90
|
-
if (
|
|
115
|
+
if (options.metadata?.visibleDataSets?.length === 0) {
|
|
91
116
|
LoggerProxy.logger.warn(
|
|
92
|
-
`HashTreeParser#constructor --> ${this.debugId} No visibleDataSets found in
|
|
117
|
+
`HashTreeParser#constructor --> ${this.debugId} No visibleDataSets found in Metadata`
|
|
93
118
|
);
|
|
94
119
|
}
|
|
95
120
|
// object mapping dataset names to arrays of leaf data
|
|
96
121
|
const leafData = this.analyzeLocusHtMeta(locus);
|
|
97
122
|
|
|
123
|
+
if (options.metadata) {
|
|
124
|
+
// add also the metadata that's outside of locus object itself
|
|
125
|
+
this.analyzeMetadata(leafData, options.metadata);
|
|
126
|
+
}
|
|
127
|
+
|
|
98
128
|
LoggerProxy.logger.info(
|
|
99
129
|
`HashTreeParser#constructor --> creating HashTreeParser for datasets: ${JSON.stringify(
|
|
100
130
|
dataSets.map((ds) => ds.name)
|
|
@@ -106,46 +136,57 @@ class HashTreeParser {
|
|
|
106
136
|
|
|
107
137
|
this.dataSets[name] = {
|
|
108
138
|
...dataSet,
|
|
109
|
-
hashTree: this.
|
|
139
|
+
hashTree: this.isVisibleDataSet(name)
|
|
110
140
|
? new HashTree(leafData[name] || [], leafCount)
|
|
111
141
|
: undefined,
|
|
112
142
|
};
|
|
113
143
|
}
|
|
114
144
|
}
|
|
115
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Checks if the given data set name is in the list of visible data sets
|
|
148
|
+
* @param {string} dataSetName data set name to check
|
|
149
|
+
* @returns {Boolean} True if the data set is visible, false otherwise
|
|
150
|
+
*/
|
|
151
|
+
private isVisibleDataSet(dataSetName: string): boolean {
|
|
152
|
+
return this.visibleDataSets.some((vds) => vds.name === dataSetName);
|
|
153
|
+
}
|
|
154
|
+
|
|
116
155
|
/**
|
|
117
156
|
* Initializes a new visible data set by creating a hash tree for it, adding it to all the internal structures,
|
|
118
157
|
* and sending an initial sync request to Locus with empty leaf data - that will trigger Locus to gives us all the data
|
|
119
158
|
* from that dataset (in the response or via messages).
|
|
120
159
|
*
|
|
121
|
-
* @param {
|
|
160
|
+
* @param {VisibleDataSetInfo} visibleDataSetInfo Information about the new visible data set
|
|
161
|
+
* @param {DataSet} dataSetInfo The new data set to be added
|
|
122
162
|
* @returns {Promise}
|
|
123
163
|
*/
|
|
124
164
|
private initializeNewVisibleDataSet(
|
|
125
|
-
|
|
165
|
+
visibleDataSetInfo: VisibleDataSetInfo,
|
|
166
|
+
dataSetInfo: DataSet
|
|
126
167
|
): Promise<{updateType: LocusInfoUpdateType; updatedObjects?: HashTreeObject[]}> {
|
|
127
|
-
if (this.
|
|
168
|
+
if (this.isVisibleDataSet(dataSetInfo.name)) {
|
|
128
169
|
LoggerProxy.logger.info(
|
|
129
|
-
`HashTreeParser#initializeNewVisibleDataSet --> ${this.debugId} Data set "${
|
|
170
|
+
`HashTreeParser#initializeNewVisibleDataSet --> ${this.debugId} Data set "${dataSetInfo.name}" already exists, skipping init`
|
|
130
171
|
);
|
|
131
172
|
|
|
132
173
|
return Promise.resolve({updateType: LocusInfoUpdateType.OBJECTS_UPDATED, updatedObjects: []});
|
|
133
174
|
}
|
|
134
175
|
|
|
135
176
|
LoggerProxy.logger.info(
|
|
136
|
-
`HashTreeParser#initializeNewVisibleDataSet --> ${this.debugId} Adding visible data set "${
|
|
177
|
+
`HashTreeParser#initializeNewVisibleDataSet --> ${this.debugId} Adding visible data set "${dataSetInfo.name}"`
|
|
137
178
|
);
|
|
138
179
|
|
|
139
|
-
this.visibleDataSets.push(
|
|
180
|
+
this.visibleDataSets.push(visibleDataSetInfo);
|
|
140
181
|
|
|
141
|
-
const hashTree = new HashTree([],
|
|
182
|
+
const hashTree = new HashTree([], dataSetInfo.leafCount);
|
|
142
183
|
|
|
143
|
-
this.dataSets[
|
|
144
|
-
...
|
|
184
|
+
this.dataSets[dataSetInfo.name] = {
|
|
185
|
+
...dataSetInfo,
|
|
145
186
|
hashTree,
|
|
146
187
|
};
|
|
147
188
|
|
|
148
|
-
return this.sendInitializationSyncRequestToLocus(
|
|
189
|
+
return this.sendInitializationSyncRequestToLocus(dataSetInfo.name, 'new visible data set');
|
|
149
190
|
}
|
|
150
191
|
|
|
151
192
|
/**
|
|
@@ -190,15 +231,22 @@ class HashTreeParser {
|
|
|
190
231
|
}
|
|
191
232
|
|
|
192
233
|
/**
|
|
193
|
-
* Queries Locus for information about all
|
|
234
|
+
* Queries Locus for all up-to-date information about all visible data sets
|
|
194
235
|
*
|
|
195
|
-
* @param {string} url - url from which we can get info about all data sets
|
|
196
236
|
* @returns {Promise}
|
|
197
237
|
*/
|
|
198
|
-
private
|
|
238
|
+
private getAllVisibleDataSetsFromLocus() {
|
|
239
|
+
if (!this.visibleDataSetsUrl) {
|
|
240
|
+
LoggerProxy.logger.warn(
|
|
241
|
+
`HashTreeParser#getAllVisibleDataSetsFromLocus --> ${this.debugId} No visibleDataSetsUrl, cannot get data sets information`
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return Promise.resolve([]);
|
|
245
|
+
}
|
|
246
|
+
|
|
199
247
|
return this.webexRequest({
|
|
200
248
|
method: HTTP_VERBS.GET,
|
|
201
|
-
uri:
|
|
249
|
+
uri: this.visibleDataSetsUrl,
|
|
202
250
|
}).then((response) => {
|
|
203
251
|
return response.body.dataSets as Array<DataSet>;
|
|
204
252
|
});
|
|
@@ -211,12 +259,14 @@ class HashTreeParser {
|
|
|
211
259
|
* @returns {Promise}
|
|
212
260
|
*/
|
|
213
261
|
async initializeFromMessage(message: HashTreeMessage) {
|
|
262
|
+
this.visibleDataSetsUrl = message.visibleDataSetsUrl;
|
|
263
|
+
|
|
214
264
|
LoggerProxy.logger.info(
|
|
215
|
-
`HashTreeParser#initializeFromMessage --> ${this.debugId} visibleDataSetsUrl=${
|
|
265
|
+
`HashTreeParser#initializeFromMessage --> ${this.debugId} visibleDataSetsUrl=${this.visibleDataSetsUrl}`
|
|
216
266
|
);
|
|
217
|
-
const
|
|
267
|
+
const visibleDataSets = await this.getAllVisibleDataSetsFromLocus();
|
|
218
268
|
|
|
219
|
-
await this.initializeDataSets(
|
|
269
|
+
await this.initializeDataSets(visibleDataSets, 'initialization from message');
|
|
220
270
|
}
|
|
221
271
|
|
|
222
272
|
/**
|
|
@@ -236,28 +286,29 @@ class HashTreeParser {
|
|
|
236
286
|
|
|
237
287
|
return;
|
|
238
288
|
}
|
|
289
|
+
this.visibleDataSetsUrl = locus.links.resources.visibleDataSets.url;
|
|
239
290
|
|
|
240
291
|
LoggerProxy.logger.info(
|
|
241
|
-
`HashTreeParser#initializeFromGetLociResponse --> ${this.debugId} visibleDataSets url: ${
|
|
292
|
+
`HashTreeParser#initializeFromGetLociResponse --> ${this.debugId} visibleDataSets url: ${this.visibleDataSetsUrl}`
|
|
242
293
|
);
|
|
243
294
|
|
|
244
|
-
const
|
|
295
|
+
const visibleDataSets = await this.getAllVisibleDataSetsFromLocus();
|
|
245
296
|
|
|
246
|
-
await this.initializeDataSets(
|
|
297
|
+
await this.initializeDataSets(visibleDataSets, 'initialization from GET /loci response');
|
|
247
298
|
}
|
|
248
299
|
|
|
249
300
|
/**
|
|
250
301
|
* Initializes data sets by doing an initialization sync on each visible data set that doesn't have a hash tree yet.
|
|
251
302
|
*
|
|
252
|
-
* @param {DataSet[]}
|
|
303
|
+
* @param {DataSet[]} visibleDataSets Array of visible DataSet objects to initialize
|
|
253
304
|
* @param {string} debugText Text to include in logs for debugging purposes
|
|
254
305
|
* @returns {Promise}
|
|
255
306
|
*/
|
|
256
|
-
private async initializeDataSets(
|
|
307
|
+
private async initializeDataSets(visibleDataSets: Array<DataSet>, debugText: string) {
|
|
257
308
|
const updatedObjects: HashTreeObject[] = [];
|
|
258
309
|
|
|
259
|
-
for (const dataSet of
|
|
260
|
-
const {name, leafCount} = dataSet;
|
|
310
|
+
for (const dataSet of visibleDataSets) {
|
|
311
|
+
const {name, leafCount, url} = dataSet;
|
|
261
312
|
|
|
262
313
|
if (!this.dataSets[name]) {
|
|
263
314
|
LoggerProxy.logger.info(
|
|
@@ -273,7 +324,14 @@ class HashTreeParser {
|
|
|
273
324
|
);
|
|
274
325
|
}
|
|
275
326
|
|
|
276
|
-
if (this.
|
|
327
|
+
if (!this.isVisibleDataSet(name)) {
|
|
328
|
+
this.visibleDataSets.push({
|
|
329
|
+
name,
|
|
330
|
+
url,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!this.dataSets[name].hashTree) {
|
|
277
335
|
LoggerProxy.logger.info(
|
|
278
336
|
`HashTreeParser#initializeDataSets --> ${this.debugId} creating hash tree for visible dataset "${name}" (${debugText})`
|
|
279
337
|
);
|
|
@@ -316,10 +374,7 @@ class HashTreeParser {
|
|
|
316
374
|
private analyzeLocusHtMeta(locus: any, options?: {copyData?: boolean}) {
|
|
317
375
|
const {copyData = false} = options || {};
|
|
318
376
|
// object mapping dataset names to arrays of leaf data
|
|
319
|
-
const leafInfo: Record<
|
|
320
|
-
string,
|
|
321
|
-
Array<{type: ObjectType; id: number; version: number; data?: any}>
|
|
322
|
-
> = {};
|
|
377
|
+
const leafInfo: Record<string, Array<LeafInfo>> = {};
|
|
323
378
|
|
|
324
379
|
const findAndStoreMetaData = (currentLocusPart: any) => {
|
|
325
380
|
if (typeof currentLocusPart !== 'object' || currentLocusPart === null) {
|
|
@@ -329,7 +384,7 @@ class HashTreeParser {
|
|
|
329
384
|
if (currentLocusPart.htMeta && currentLocusPart.htMeta.dataSetNames) {
|
|
330
385
|
const {type, id, version} = currentLocusPart.htMeta.elementId;
|
|
331
386
|
const {dataSetNames} = currentLocusPart.htMeta;
|
|
332
|
-
const newLeafInfo:
|
|
387
|
+
const newLeafInfo: LeafInfo = {
|
|
333
388
|
type,
|
|
334
389
|
id,
|
|
335
390
|
version,
|
|
@@ -368,6 +423,43 @@ class HashTreeParser {
|
|
|
368
423
|
return leafInfo;
|
|
369
424
|
}
|
|
370
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Analyzes the Metadata object that is sent outside of Locus object, and appends its data to passed in leafInfo
|
|
428
|
+
* structure.
|
|
429
|
+
*
|
|
430
|
+
* @param {Record<string, LeafInfo[]>} leafInfo the structure to which the Metadata info will be appended
|
|
431
|
+
* @param {Metadata} metadata Metadata object
|
|
432
|
+
* @returns {void}
|
|
433
|
+
*/
|
|
434
|
+
private analyzeMetadata(leafInfo: Record<string, LeafInfo[]>, metadata: Metadata) {
|
|
435
|
+
const {htMeta} = metadata;
|
|
436
|
+
|
|
437
|
+
if (
|
|
438
|
+
htMeta?.dataSetNames?.length === 1 &&
|
|
439
|
+
htMeta.dataSetNames[0].toLowerCase() === MetadataDataSetName
|
|
440
|
+
) {
|
|
441
|
+
const {type, id, version} = metadata.htMeta.elementId;
|
|
442
|
+
|
|
443
|
+
const dataSetName = htMeta.dataSetNames[0];
|
|
444
|
+
|
|
445
|
+
if (!leafInfo[dataSetName]) {
|
|
446
|
+
leafInfo[dataSetName] = [];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
leafInfo[dataSetName].push({
|
|
450
|
+
type,
|
|
451
|
+
id,
|
|
452
|
+
version,
|
|
453
|
+
});
|
|
454
|
+
} else {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`${this.debugId} Metadata htMeta has unexpected dataSetNames: ${
|
|
457
|
+
htMeta && htMeta.dataSetNames.join(',')
|
|
458
|
+
}`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
371
463
|
/**
|
|
372
464
|
* Checks if the provided hash tree message indicates the end of the meeting and that there won't be any more updates.
|
|
373
465
|
*
|
|
@@ -420,6 +512,58 @@ class HashTreeParser {
|
|
|
420
512
|
});
|
|
421
513
|
}
|
|
422
514
|
|
|
515
|
+
/**
|
|
516
|
+
* Handles updates to Metadata object that we receive from Locus via other means than messages. Right now
|
|
517
|
+
* that means only in the API response alongside locus object.
|
|
518
|
+
*
|
|
519
|
+
* @param {Metadata} metadata received in Locus update other than a message (for example in an API response)
|
|
520
|
+
* @param {HashTreeObject[]} updatedObjects a list of updated hash tree objects to which any updates resulting from new Metadata will be added
|
|
521
|
+
* @returns {void}
|
|
522
|
+
*/
|
|
523
|
+
handleMetadataUpdate(metadata: Metadata, updatedObjects: HashTreeObject[]): void {
|
|
524
|
+
let dataSetsRequiringInitialization: VisibleDataSetInfo[] = [];
|
|
525
|
+
|
|
526
|
+
// current assumption based on Locus docs is that Metadata object lives always in "self" data set
|
|
527
|
+
const hashTree = this.dataSets[MetadataDataSetName]?.hashTree;
|
|
528
|
+
|
|
529
|
+
if (!hashTree) {
|
|
530
|
+
LoggerProxy.logger.warn(
|
|
531
|
+
`HashTreeParser#handleLocusUpdate --> ${this.debugId} received Metadata object but no hash tree for "${MetadataDataSetName}" data set exists`
|
|
532
|
+
);
|
|
533
|
+
} else {
|
|
534
|
+
const metadataUpdated = hashTree.putItem(metadata.htMeta.elementId);
|
|
535
|
+
|
|
536
|
+
if (metadataUpdated) {
|
|
537
|
+
// metadata in Locus API response is in a slightly different format than the objects in messages, so need to adapt it
|
|
538
|
+
const metadataObject: HashTreeObject = {
|
|
539
|
+
htMeta: metadata.htMeta,
|
|
540
|
+
data: metadata,
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
updatedObjects.push(metadataObject);
|
|
544
|
+
|
|
545
|
+
const {changeDetected, removedDataSets, addedDataSets} = this.checkForVisibleDataSetChanges(
|
|
546
|
+
[metadataObject]
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
if (changeDetected) {
|
|
550
|
+
dataSetsRequiringInitialization = this.processVisibleDataSetChanges(
|
|
551
|
+
removedDataSets,
|
|
552
|
+
addedDataSets,
|
|
553
|
+
updatedObjects
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (dataSetsRequiringInitialization.length > 0) {
|
|
558
|
+
// there are some data sets that we need to initialize asynchronously
|
|
559
|
+
queueMicrotask(() => {
|
|
560
|
+
this.initializeNewVisibleDataSets(dataSetsRequiringInitialization);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
423
567
|
/**
|
|
424
568
|
* This method should be called when we receive a partial locus DTO that contains dataSets and htMeta information
|
|
425
569
|
* It updates the hash trees with the new leaf data based on the received Locus
|
|
@@ -427,8 +571,8 @@ class HashTreeParser {
|
|
|
427
571
|
* @param {Object} update - The locus update containing data sets and locus information
|
|
428
572
|
* @returns {void}
|
|
429
573
|
*/
|
|
430
|
-
handleLocusUpdate(update: {dataSets?: Array<DataSet>; locus: any}): void {
|
|
431
|
-
const {dataSets, locus} = update;
|
|
574
|
+
handleLocusUpdate(update: {dataSets?: Array<DataSet>; locus: any; metadata?: Metadata}): void {
|
|
575
|
+
const {dataSets, locus, metadata} = update;
|
|
432
576
|
|
|
433
577
|
if (!dataSets) {
|
|
434
578
|
LoggerProxy.logger.warn(
|
|
@@ -443,6 +587,11 @@ class HashTreeParser {
|
|
|
443
587
|
// first, analyze the locus object to extract the hash tree objects' htMeta and data from it
|
|
444
588
|
const leafInfo = this.analyzeLocusHtMeta(locus, {copyData: true});
|
|
445
589
|
|
|
590
|
+
// if we got metadata, process it (currently that means only potential visible data set list changes)
|
|
591
|
+
if (metadata) {
|
|
592
|
+
this.handleMetadataUpdate(metadata, updatedObjects);
|
|
593
|
+
}
|
|
594
|
+
|
|
446
595
|
// then process the data in hash trees, if it is a new version, then add it to updatedObjects
|
|
447
596
|
Object.keys(leafInfo).forEach((dataSetName) => {
|
|
448
597
|
if (this.dataSets[dataSetName]) {
|
|
@@ -493,9 +642,6 @@ class HashTreeParser {
|
|
|
493
642
|
updatedObjects,
|
|
494
643
|
});
|
|
495
644
|
}
|
|
496
|
-
|
|
497
|
-
// todo: once Locus design on how visible data sets will be communicated in subsequent API responses is confirmed,
|
|
498
|
-
// we'll need to check here if visible data sets have changed and update this.visibleDataSets, remove/create hash trees etc
|
|
499
645
|
}
|
|
500
646
|
|
|
501
647
|
/**
|
|
@@ -511,7 +657,7 @@ class HashTreeParser {
|
|
|
511
657
|
};
|
|
512
658
|
|
|
513
659
|
LoggerProxy.logger.info(
|
|
514
|
-
`HashTreeParser#
|
|
660
|
+
`HashTreeParser#updateDataSetInfo --> ${this.debugId} created entry for "${receivedDataSet.name}" dataset: version=${receivedDataSet.version}, root=${receivedDataSet.root}`
|
|
515
661
|
);
|
|
516
662
|
|
|
517
663
|
return;
|
|
@@ -526,7 +672,7 @@ class HashTreeParser {
|
|
|
526
672
|
exponent: receivedDataSet.backoff.exponent,
|
|
527
673
|
};
|
|
528
674
|
LoggerProxy.logger.info(
|
|
529
|
-
`HashTreeParser#
|
|
675
|
+
`HashTreeParser#updateDataSetInfo --> ${this.debugId} updated "${receivedDataSet.name}" dataset to version=${receivedDataSet.version}, root=${receivedDataSet.root}`
|
|
530
676
|
);
|
|
531
677
|
}
|
|
532
678
|
}
|
|
@@ -537,25 +683,28 @@ class HashTreeParser {
|
|
|
537
683
|
* @returns {Object} An object containing the removed and added visible data sets.
|
|
538
684
|
*/
|
|
539
685
|
private checkForVisibleDataSetChanges(updatedObjects: HashTreeObject[]) {
|
|
540
|
-
let removedDataSets:
|
|
541
|
-
let addedDataSets:
|
|
686
|
+
let removedDataSets: VisibleDataSetInfo[] = [];
|
|
687
|
+
let addedDataSets: VisibleDataSetInfo[] = [];
|
|
542
688
|
|
|
543
|
-
// visibleDataSets can only be changed by
|
|
689
|
+
// visibleDataSets can only be changed by Metadata object updates
|
|
544
690
|
updatedObjects.forEach((object) => {
|
|
545
|
-
|
|
546
|
-
if (isSelf(object) && object.data?.visibleDataSets) {
|
|
691
|
+
if (isMetadata(object) && object.data?.visibleDataSets) {
|
|
547
692
|
const newVisibleDataSets = object.data.visibleDataSets;
|
|
548
693
|
|
|
549
|
-
removedDataSets = this.visibleDataSets.filter(
|
|
550
|
-
|
|
694
|
+
removedDataSets = this.visibleDataSets.filter(
|
|
695
|
+
(ds) => !newVisibleDataSets.some((nvs) => nvs.name === ds.name)
|
|
696
|
+
);
|
|
697
|
+
addedDataSets = newVisibleDataSets.filter((nvs) =>
|
|
698
|
+
this.visibleDataSets.every((ds) => ds.name !== nvs.name)
|
|
699
|
+
);
|
|
551
700
|
|
|
552
701
|
if (removedDataSets.length > 0 || addedDataSets.length > 0) {
|
|
553
702
|
LoggerProxy.logger.info(
|
|
554
703
|
`HashTreeParser#checkForVisibleDataSetChanges --> ${
|
|
555
704
|
this.debugId
|
|
556
|
-
} visible data sets change: removed: ${removedDataSets
|
|
557
|
-
|
|
558
|
-
|
|
705
|
+
} visible data sets change: removed: ${removedDataSets
|
|
706
|
+
.map((ds) => ds.name)
|
|
707
|
+
.join(', ')}, added: ${addedDataSets.map((ds) => ds.name).join(', ')}`
|
|
559
708
|
);
|
|
560
709
|
}
|
|
561
710
|
}
|
|
@@ -593,49 +742,51 @@ class HashTreeParser {
|
|
|
593
742
|
* visible data sets and they require async initialization, the names of these data sets
|
|
594
743
|
* are returned in an array.
|
|
595
744
|
*
|
|
596
|
-
* @param {
|
|
597
|
-
* @param {
|
|
745
|
+
* @param {VisibleDataSetInfo[]} removedDataSets - The list of removed data sets.
|
|
746
|
+
* @param {VisibleDataSetInfo[]} addedDataSets - The list of added data sets.
|
|
598
747
|
* @param {HashTreeObject[]} updatedObjects - The list of updated hash tree objects to which changes will be added.
|
|
599
|
-
* @returns {
|
|
748
|
+
* @returns {VisibleDataSetInfo[]} list of data sets that couldn't be initialized synchronously
|
|
600
749
|
*/
|
|
601
750
|
private processVisibleDataSetChanges(
|
|
602
|
-
removedDataSets:
|
|
603
|
-
addedDataSets:
|
|
751
|
+
removedDataSets: VisibleDataSetInfo[],
|
|
752
|
+
addedDataSets: VisibleDataSetInfo[],
|
|
604
753
|
updatedObjects: HashTreeObject[]
|
|
605
|
-
):
|
|
606
|
-
const dataSetsRequiringInitialization = [];
|
|
754
|
+
): VisibleDataSetInfo[] {
|
|
755
|
+
const dataSetsRequiringInitialization: VisibleDataSetInfo[] = [];
|
|
607
756
|
|
|
608
757
|
// if a visible data set was removed, we need to tell our client that all objects from it are removed
|
|
609
758
|
const removedObjects: HashTreeObject[] = [];
|
|
610
759
|
|
|
611
760
|
removedDataSets.forEach((ds) => {
|
|
612
|
-
if (this.dataSets[ds]?.hashTree) {
|
|
613
|
-
for (let i = 0; i < this.dataSets[ds].hashTree.numLeaves; i += 1) {
|
|
761
|
+
if (this.dataSets[ds.name]?.hashTree) {
|
|
762
|
+
for (let i = 0; i < this.dataSets[ds.name].hashTree.numLeaves; i += 1) {
|
|
614
763
|
removedObjects.push(
|
|
615
|
-
...this.dataSets[ds].hashTree.getLeafData(i).map((elementId) => ({
|
|
764
|
+
...this.dataSets[ds.name].hashTree.getLeafData(i).map((elementId) => ({
|
|
616
765
|
htMeta: {
|
|
617
766
|
elementId,
|
|
618
|
-
dataSetNames: [ds],
|
|
767
|
+
dataSetNames: [ds.name],
|
|
619
768
|
},
|
|
620
769
|
data: null,
|
|
621
770
|
}))
|
|
622
771
|
);
|
|
623
772
|
}
|
|
624
773
|
|
|
625
|
-
this.deleteHashTree(ds);
|
|
774
|
+
this.deleteHashTree(ds.name);
|
|
626
775
|
}
|
|
627
776
|
});
|
|
628
|
-
this.visibleDataSets = this.visibleDataSets.filter(
|
|
777
|
+
this.visibleDataSets = this.visibleDataSets.filter(
|
|
778
|
+
(vds) => !removedDataSets.some((rds) => rds.name === vds.name)
|
|
779
|
+
);
|
|
629
780
|
updatedObjects.push(...removedObjects);
|
|
630
781
|
|
|
631
782
|
// now setup the new visible data sets
|
|
632
783
|
for (const ds of addedDataSets) {
|
|
633
|
-
const dataSetInfo = this.dataSets[ds];
|
|
784
|
+
const dataSetInfo = this.dataSets[ds.name];
|
|
634
785
|
|
|
635
786
|
if (dataSetInfo) {
|
|
636
|
-
if (this.
|
|
787
|
+
if (this.isVisibleDataSet(dataSetInfo.name)) {
|
|
637
788
|
LoggerProxy.logger.info(
|
|
638
|
-
`HashTreeParser#processVisibleDataSetChanges --> ${this.debugId} Data set "${ds}" is already visible, skipping`
|
|
789
|
+
`HashTreeParser#processVisibleDataSetChanges --> ${this.debugId} Data set "${ds.name}" is already visible, skipping`
|
|
639
790
|
);
|
|
640
791
|
|
|
641
792
|
// eslint-disable-next-line no-continue
|
|
@@ -643,7 +794,7 @@ class HashTreeParser {
|
|
|
643
794
|
}
|
|
644
795
|
|
|
645
796
|
LoggerProxy.logger.info(
|
|
646
|
-
`HashTreeParser#processVisibleDataSetChanges --> ${this.debugId} Adding visible data set "${ds}"`
|
|
797
|
+
`HashTreeParser#processVisibleDataSetChanges --> ${this.debugId} Adding visible data set "${ds.name}"`
|
|
647
798
|
);
|
|
648
799
|
|
|
649
800
|
this.visibleDataSets.push(ds);
|
|
@@ -656,7 +807,7 @@ class HashTreeParser {
|
|
|
656
807
|
};
|
|
657
808
|
} else {
|
|
658
809
|
LoggerProxy.logger.info(
|
|
659
|
-
`HashTreeParser#processVisibleDataSetChanges --> ${this.debugId} visible data set "${ds}" added but no info about it in our dataSets structures`
|
|
810
|
+
`HashTreeParser#processVisibleDataSetChanges --> ${this.debugId} visible data set "${ds.name}" added but no info about it in our dataSets structures`
|
|
660
811
|
);
|
|
661
812
|
// todo: add a metric here
|
|
662
813
|
dataSetsRequiringInitialization.push(ds);
|
|
@@ -670,32 +821,28 @@ class HashTreeParser {
|
|
|
670
821
|
* Adds entries to the passed in updateObjects array
|
|
671
822
|
* for the changes that result from adding and removing visible data sets.
|
|
672
823
|
*
|
|
673
|
-
* @param {
|
|
674
|
-
* @param {string[]} addedDataSets - The list of added data sets.
|
|
824
|
+
* @param {VisibleDataSetInfo[]} addedDataSets - The list of added data sets.
|
|
675
825
|
* @returns {Promise<void>}
|
|
676
826
|
*/
|
|
677
|
-
private async initializeNewVisibleDataSets(
|
|
678
|
-
|
|
679
|
-
addedDataSets: string[]
|
|
680
|
-
): Promise<void> {
|
|
681
|
-
const allDataSets = await this.getAllDataSetsMetadata(message.visibleDataSetsUrl);
|
|
827
|
+
private async initializeNewVisibleDataSets(addedDataSets: VisibleDataSetInfo[]): Promise<void> {
|
|
828
|
+
const allDataSets = await this.getAllVisibleDataSetsFromLocus();
|
|
682
829
|
|
|
683
830
|
for (const ds of addedDataSets) {
|
|
684
|
-
const dataSetInfo = allDataSets.find((d) => d.name === ds);
|
|
831
|
+
const dataSetInfo = allDataSets.find((d) => d.name === ds.name);
|
|
685
832
|
|
|
686
833
|
LoggerProxy.logger.info(
|
|
687
|
-
`HashTreeParser#initializeNewVisibleDataSets --> ${this.debugId} initializing data set "${ds}"`
|
|
834
|
+
`HashTreeParser#initializeNewVisibleDataSets --> ${this.debugId} initializing data set "${ds.name}"`
|
|
688
835
|
);
|
|
689
836
|
|
|
690
837
|
if (!dataSetInfo) {
|
|
691
838
|
LoggerProxy.logger.warn(
|
|
692
|
-
`HashTreeParser#handleHashTreeMessage --> ${this.debugId} missing info about data set "${ds}" in Locus response from visibleDataSetsUrl`
|
|
839
|
+
`HashTreeParser#handleHashTreeMessage --> ${this.debugId} missing info about data set "${ds.name}" in Locus response from visibleDataSetsUrl`
|
|
693
840
|
);
|
|
694
841
|
} else {
|
|
695
842
|
// we're awaiting in a loop, because in practice there will be only one new data set at a time,
|
|
696
843
|
// so no point in trying to parallelize this
|
|
697
844
|
// eslint-disable-next-line no-await-in-loop
|
|
698
|
-
const updates = await this.initializeNewVisibleDataSet(dataSetInfo);
|
|
845
|
+
const updates = await this.initializeNewVisibleDataSet(ds, dataSetInfo);
|
|
699
846
|
|
|
700
847
|
this.callLocusInfoUpdateCallback(updates);
|
|
701
848
|
}
|
|
@@ -746,32 +893,31 @@ class HashTreeParser {
|
|
|
746
893
|
// available in the message, they will require separate async initialization
|
|
747
894
|
let dataSetsRequiringInitialization = [];
|
|
748
895
|
|
|
749
|
-
// first find out if there are any visible data set changes - they're signalled in
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
isSelf(object)
|
|
896
|
+
// first find out if there are any visible data set changes - they're signalled in Metadata object updates
|
|
897
|
+
const metadataUpdates = (message.locusStateElements || []).filter((object) =>
|
|
898
|
+
isMetadata(object)
|
|
753
899
|
);
|
|
754
900
|
|
|
755
|
-
if (
|
|
756
|
-
const
|
|
901
|
+
if (metadataUpdates.length > 0) {
|
|
902
|
+
const updatedMetadataObjects = [];
|
|
757
903
|
|
|
758
|
-
|
|
904
|
+
metadataUpdates.forEach((object) => {
|
|
759
905
|
// todo: once Locus supports it, we will use the "view" field here instead of dataSetNames
|
|
760
906
|
for (const dataSetName of object.htMeta.dataSetNames) {
|
|
761
907
|
const hashTree = this.dataSets[dataSetName]?.hashTree;
|
|
762
908
|
|
|
763
909
|
if (hashTree && object.data) {
|
|
764
910
|
if (hashTree.putItem(object.htMeta.elementId)) {
|
|
765
|
-
|
|
911
|
+
updatedMetadataObjects.push(object);
|
|
766
912
|
}
|
|
767
913
|
}
|
|
768
914
|
}
|
|
769
915
|
});
|
|
770
916
|
|
|
771
|
-
updatedObjects.push(...
|
|
917
|
+
updatedObjects.push(...updatedMetadataObjects);
|
|
772
918
|
|
|
773
919
|
const {changeDetected, removedDataSets, addedDataSets} =
|
|
774
|
-
this.checkForVisibleDataSetChanges(
|
|
920
|
+
this.checkForVisibleDataSetChanges(updatedMetadataObjects);
|
|
775
921
|
|
|
776
922
|
if (changeDetected) {
|
|
777
923
|
dataSetsRequiringInitialization = this.processVisibleDataSetChanges(
|
|
@@ -838,7 +984,7 @@ class HashTreeParser {
|
|
|
838
984
|
if (dataSetsRequiringInitialization.length > 0) {
|
|
839
985
|
// there are some data sets that we need to initialize asynchronously
|
|
840
986
|
queueMicrotask(() => {
|
|
841
|
-
this.initializeNewVisibleDataSets(
|
|
987
|
+
this.initializeNewVisibleDataSets(dataSetsRequiringInitialization);
|
|
842
988
|
});
|
|
843
989
|
}
|
|
844
990
|
|
|
@@ -880,7 +1026,49 @@ class HashTreeParser {
|
|
|
880
1026
|
}) {
|
|
881
1027
|
const {updateType, updatedObjects} = updates;
|
|
882
1028
|
|
|
883
|
-
if (updateType
|
|
1029
|
+
if (updateType === LocusInfoUpdateType.OBJECTS_UPDATED && updatedObjects?.length > 0) {
|
|
1030
|
+
// Filter out updates for objects that already have a higher version in their datasets,
|
|
1031
|
+
// or removals for objects that still exist in any of their datasets
|
|
1032
|
+
const filteredUpdates = updatedObjects.filter((object) => {
|
|
1033
|
+
const {elementId} = object.htMeta;
|
|
1034
|
+
const {type, id, version} = elementId;
|
|
1035
|
+
|
|
1036
|
+
// Check all datasets
|
|
1037
|
+
for (const dataSetName of Object.keys(this.dataSets)) {
|
|
1038
|
+
const dataSet = this.dataSets[dataSetName];
|
|
1039
|
+
|
|
1040
|
+
// only visible datasets have hash trees set
|
|
1041
|
+
if (dataSet?.hashTree) {
|
|
1042
|
+
const existingVersion = dataSet.hashTree.getItemVersion(id, type);
|
|
1043
|
+
if (existingVersion !== undefined) {
|
|
1044
|
+
if (object.data) {
|
|
1045
|
+
// For updates: filter out if any dataset has a higher version
|
|
1046
|
+
if (existingVersion > version) {
|
|
1047
|
+
LoggerProxy.logger.info(
|
|
1048
|
+
`HashTreeParser#callLocusInfoUpdateCallback --> ${this.debugId} Filtering out update for ${type}:${id} v${version} because dataset "${dataSetName}" has v${existingVersion}`
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
} else if (existingVersion >= version) {
|
|
1054
|
+
// For removals: filter out if the object still exists in any dataset
|
|
1055
|
+
LoggerProxy.logger.info(
|
|
1056
|
+
`HashTreeParser#callLocusInfoUpdateCallback --> ${this.debugId} Filtering out removal for ${type}:${id} v${version} because dataset "${dataSetName}" still has v${existingVersion}`
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
return true;
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
if (filteredUpdates.length > 0) {
|
|
1069
|
+
this.locusInfoUpdateCallback(updateType, {updatedObjects: filteredUpdates});
|
|
1070
|
+
}
|
|
1071
|
+
} else if (updateType !== LocusInfoUpdateType.OBJECTS_UPDATED) {
|
|
884
1072
|
this.locusInfoUpdateCallback(updateType, {updatedObjects});
|
|
885
1073
|
}
|
|
886
1074
|
}
|
|
@@ -969,7 +1157,8 @@ class HashTreeParser {
|
|
|
969
1157
|
try {
|
|
970
1158
|
// request hashes from sender
|
|
971
1159
|
const {hashes, dataSet: latestDataSetInfo} = await this.getHashesFromLocus(
|
|
972
|
-
dataSet.name
|
|
1160
|
+
dataSet.name,
|
|
1161
|
+
rootHash
|
|
973
1162
|
);
|
|
974
1163
|
|
|
975
1164
|
receivedHashes = hashes;
|
|
@@ -1035,9 +1224,10 @@ class HashTreeParser {
|
|
|
1035
1224
|
/**
|
|
1036
1225
|
* Gets the current hashes from the locus for a specific data set.
|
|
1037
1226
|
* @param {string} dataSetName
|
|
1227
|
+
* @param {string} currentRootHash
|
|
1038
1228
|
* @returns {string[]}
|
|
1039
1229
|
*/
|
|
1040
|
-
private getHashesFromLocus(dataSetName: string) {
|
|
1230
|
+
private getHashesFromLocus(dataSetName: string, currentRootHash: string) {
|
|
1041
1231
|
LoggerProxy.logger.info(
|
|
1042
1232
|
`HashTreeParser#getHashesFromLocus --> ${this.debugId} Requesting hashes for data set "${dataSetName}"`
|
|
1043
1233
|
);
|
|
@@ -1049,6 +1239,9 @@ class HashTreeParser {
|
|
|
1049
1239
|
return this.webexRequest({
|
|
1050
1240
|
method: HTTP_VERBS.GET,
|
|
1051
1241
|
uri: url,
|
|
1242
|
+
qs: {
|
|
1243
|
+
rootHash: currentRootHash,
|
|
1244
|
+
},
|
|
1052
1245
|
})
|
|
1053
1246
|
.then((response) => {
|
|
1054
1247
|
const hashes = response.body?.hashes as string[] | undefined;
|
|
@@ -1110,9 +1303,14 @@ class HashTreeParser {
|
|
|
1110
1303
|
});
|
|
1111
1304
|
});
|
|
1112
1305
|
|
|
1306
|
+
const ourCurrentRootHash = dataSet.hashTree ? dataSet.hashTree.getRootHash() : EMPTY_HASH;
|
|
1307
|
+
|
|
1113
1308
|
return this.webexRequest({
|
|
1114
1309
|
method: HTTP_VERBS.POST,
|
|
1115
1310
|
uri: url,
|
|
1311
|
+
qs: {
|
|
1312
|
+
rootHash: ourCurrentRootHash,
|
|
1313
|
+
},
|
|
1116
1314
|
body,
|
|
1117
1315
|
})
|
|
1118
1316
|
.then((resp) => {
|