@webex/plugin-meetings 3.12.0-next.10 → 3.12.0-next.12
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/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +23 -21
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +36 -20
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +38 -14
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/hashTree/hashTreeParser.d.ts +12 -2
- package/dist/types/locus-info/index.d.ts +8 -3
- package/dist/webinar/index.js +1 -1
- package/package.json +13 -13
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +26 -19
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/hashTreeParser.ts +39 -22
- package/src/locus-info/index.ts +48 -24
- package/src/meeting/util.ts +1 -0
- package/test/unit/spec/controls-options-manager/index.js +114 -6
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +59 -32
- package/test/unit/spec/locus-info/index.js +47 -22
- package/test/unit/spec/meeting/utils.js +4 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {camelCase} from 'lodash';
|
|
2
|
+
import ParameterError from '../common/errors/parameter';
|
|
2
3
|
import PermissionError from '../common/errors/permission';
|
|
3
|
-
import {CONTROLS, HTTP_VERBS} from '../constants';
|
|
4
4
|
import MeetingRequest from '../meeting/request';
|
|
5
5
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
6
6
|
import {Control, Setting} from './enums';
|
|
@@ -153,6 +153,12 @@ export default class ControlsOptionsManager {
|
|
|
153
153
|
* @returns {Promise<Array<any>>}- Promise resolving if the request was successful.
|
|
154
154
|
*/
|
|
155
155
|
public update(...controls: Array<ControlConfig>) {
|
|
156
|
+
if (!this.locusUrl) {
|
|
157
|
+
return Promise.reject(
|
|
158
|
+
new ParameterError('The associated locusUrl for update() controls must be defined.')
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
156
162
|
const payloads = controls.map((control) => {
|
|
157
163
|
if (!Object.keys(Control).includes(control.scope)) {
|
|
158
164
|
throw new Error(
|
|
@@ -172,18 +178,15 @@ export default class ControlsOptionsManager {
|
|
|
172
178
|
});
|
|
173
179
|
|
|
174
180
|
return payloads.reduce((previous, payload) => {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
const requestParams = Util.getControlsRequestParams({
|
|
182
|
+
body: payload,
|
|
183
|
+
locusUrl: this.locusUrl,
|
|
184
|
+
mainLocusUrl: this.mainLocusUrl,
|
|
185
|
+
});
|
|
179
186
|
|
|
180
187
|
return previous.then(() =>
|
|
181
188
|
// @ts-ignore
|
|
182
|
-
this.request.request(
|
|
183
|
-
uri: `${this.mainLocusUrl || this.locusUrl}/${CONTROLS}`,
|
|
184
|
-
body: {...payload, ...extraBody},
|
|
185
|
-
method: HTTP_VERBS.PATCH,
|
|
186
|
-
})
|
|
189
|
+
this.request.request(requestParams)
|
|
187
190
|
);
|
|
188
191
|
}, Promise.resolve());
|
|
189
192
|
}
|
|
@@ -200,6 +203,12 @@ export default class ControlsOptionsManager {
|
|
|
200
203
|
[Setting.muteOnEntry]?: boolean;
|
|
201
204
|
[Setting.roles]?: Array<string>;
|
|
202
205
|
}): Promise<any> {
|
|
206
|
+
if (!this.locusUrl) {
|
|
207
|
+
return Promise.reject(
|
|
208
|
+
new ParameterError('The associated locusUrl for setControls() must be defined.')
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
203
212
|
LoggerProxy.logger.log(
|
|
204
213
|
`ControlsOptionsManager:index#setControls --> ${JSON.stringify(setting)}`
|
|
205
214
|
);
|
|
@@ -258,17 +267,15 @@ export default class ControlsOptionsManager {
|
|
|
258
267
|
if (error) {
|
|
259
268
|
return Promise.reject(error);
|
|
260
269
|
}
|
|
261
|
-
const extraBody =
|
|
262
|
-
this.mainLocusUrl && this.mainLocusUrl !== this.locusUrl
|
|
263
|
-
? {authorizingLocusUrl: this.locusUrl}
|
|
264
|
-
: {};
|
|
265
270
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
method: HTTP_VERBS.PATCH,
|
|
271
|
+
const requestParams = Util.getControlsRequestParams({
|
|
272
|
+
body,
|
|
273
|
+
locusUrl: this.locusUrl,
|
|
274
|
+
mainLocusUrl: this.mainLocusUrl,
|
|
271
275
|
});
|
|
276
|
+
|
|
277
|
+
// @ts-ignore
|
|
278
|
+
return this.request.request(requestParams);
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {DISPLAY_HINTS} from '../constants';
|
|
1
|
+
import {CONTROLS, DISPLAY_HINTS, HTTP_VERBS} from '../constants';
|
|
2
2
|
import {Control} from './enums';
|
|
3
|
+
import {AUDIO_CONTROL_BODY_KEYS} from './constants';
|
|
3
4
|
import {
|
|
4
5
|
ControlConfig,
|
|
5
6
|
AudioProperties,
|
|
@@ -400,6 +401,85 @@ class Utils {
|
|
|
400
401
|
|
|
401
402
|
return determinant;
|
|
402
403
|
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Check if all body keys represent audio controls.
|
|
407
|
+
*
|
|
408
|
+
* @param {Record<string, any>} body - The request body to inspect.
|
|
409
|
+
* @returns {boolean} - True if every key in the body is an audio control key.
|
|
410
|
+
*/
|
|
411
|
+
public static isAudioControl(body: Record<string, any>): boolean {
|
|
412
|
+
return Object.keys(body).every((key) => AUDIO_CONTROL_BODY_KEYS.has(key));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Check if the current locus URL differs from the main locus URL,
|
|
417
|
+
* indicating a breakout session.
|
|
418
|
+
*
|
|
419
|
+
* @param {string} locusUrl - The current locus URL.
|
|
420
|
+
* @param {string} [mainLocusUrl] - The main locus URL.
|
|
421
|
+
* @returns {boolean} - True if in a breakout session.
|
|
422
|
+
*/
|
|
423
|
+
public static isBreakoutLocusUrl(locusUrl: string, mainLocusUrl?: string): boolean {
|
|
424
|
+
return Boolean(mainLocusUrl) && mainLocusUrl !== locusUrl;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Resolve the target URL and extra body fields for a controls request,
|
|
429
|
+
* handling breakout session routing. Note: This is a pure computation function.
|
|
430
|
+
* It does not validate that locusUrl is
|
|
431
|
+
* defined. Callers must guard against falsy locusUrl before
|
|
432
|
+
* invoking this function.
|
|
433
|
+
* Mixed audio and non-audio keys in a single body (e.g., {audio: {...},
|
|
434
|
+
* raiseHand: {...}}) are treated as non-audio and routed to mainLocusUrl with
|
|
435
|
+
* authorizingLocusUrl. This means the audio portion would go through unsupported
|
|
436
|
+
* cross-locus authorization. Callers must not produce mixed payloads — update()
|
|
437
|
+
* sends each control scope as a separate request, and setControls() only handles
|
|
438
|
+
* audio-related settings.
|
|
439
|
+
*
|
|
440
|
+
* The authorizingLocusUrl mechanism on PATCH /loci/{lid}/controls is not supported
|
|
441
|
+
* for audio control updates (mute/unmute, muteOnEntry, disallowUnmute).
|
|
442
|
+
* Audio controls are not wired into the cross-locus GraphQL authorization path that
|
|
443
|
+
* other control types (raiseHand, viewParticipantList, admit, reactions, etc.) use.
|
|
444
|
+
* Specifically, the GraphQL authorization layer does not recognize audio as a control
|
|
445
|
+
* type eligible for remote locus authorization.
|
|
446
|
+
* This means authorizingLocusUrl is effectively ignored for audio controls and the
|
|
447
|
+
* server evaluates the request against the target locus only, where the host may not
|
|
448
|
+
* be currently joined.
|
|
449
|
+
* Audio control updates must be sent directly to the locus the user is currently in.
|
|
450
|
+
* If the host is in a breakout and wants to mute participants in that breakout, the
|
|
451
|
+
* request should target the breakout locus URL directly, not the main session locus
|
|
452
|
+
* with authorizingLocusUrl.
|
|
453
|
+
* Meeting-wide audio control actions (e.g., muting panelists across all breakouts
|
|
454
|
+
* from a single request) are not currently supported through this mechanism.
|
|
455
|
+
*
|
|
456
|
+
* @param {object} options
|
|
457
|
+
* @param {Record<string, any>} options.body - The request body.
|
|
458
|
+
* @param {string} options.locusUrl - The current locus URL. Must be defined (callers must validate).
|
|
459
|
+
* @param {string} [options.mainLocusUrl] - The main locus URL.
|
|
460
|
+
* @returns {{ uri: string, body: Record<string, any>, method: string }}
|
|
461
|
+
*/
|
|
462
|
+
public static getControlsRequestParams(options: {
|
|
463
|
+
body: Record<string, any>;
|
|
464
|
+
locusUrl: string;
|
|
465
|
+
mainLocusUrl?: string;
|
|
466
|
+
}): {
|
|
467
|
+
uri: string;
|
|
468
|
+
body: Record<string, any>;
|
|
469
|
+
method: string;
|
|
470
|
+
} {
|
|
471
|
+
const {body, locusUrl, mainLocusUrl} = options;
|
|
472
|
+
|
|
473
|
+
const isAudio = Utils.isAudioControl(body);
|
|
474
|
+
const inBreakout = Utils.isBreakoutLocusUrl(locusUrl, mainLocusUrl);
|
|
475
|
+
const targetUrl = inBreakout && !isAudio ? mainLocusUrl : locusUrl;
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
uri: `${targetUrl}/${CONTROLS}`,
|
|
479
|
+
body: inBreakout && !isAudio ? {...body, authorizingLocusUrl: locusUrl} : body,
|
|
480
|
+
method: HTTP_VERBS.PATCH,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
403
483
|
}
|
|
404
484
|
|
|
405
485
|
export default Utils;
|
|
@@ -57,10 +57,15 @@ export const LocusInfoUpdateType = {
|
|
|
57
57
|
} as const;
|
|
58
58
|
|
|
59
59
|
export type LocusInfoUpdateType = Enum<typeof LocusInfoUpdateType>;
|
|
60
|
-
export type
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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<
|
|
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<
|
|
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(
|
|
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
|
|
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
|
|
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,
|
|
1202
|
+
this.locusInfoUpdateCallback({updateType, updatedObjects: filteredUpdates});
|
|
1201
1203
|
}
|
|
1202
1204
|
} else if (updateType !== LocusInfoUpdateType.OBJECTS_UPDATED) {
|
|
1203
|
-
this.locusInfoUpdateCallback(updateType
|
|
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
|
|
1602
|
-
elementIds: mismatchedLeavesData[
|
|
1618
|
+
leafIndex,
|
|
1619
|
+
elementIds: mismatchedLeavesData[leafIndex],
|
|
1603
1620
|
});
|
|
1604
1621
|
});
|
|
1605
1622
|
|
package/src/locus-info/index.ts
CHANGED
|
@@ -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('/')
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1272
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
}
|
package/src/meeting/util.ts
CHANGED
|
@@ -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(
|