@webex/plugin-meetings 3.11.0 → 3.12.0-next.2
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 +184 -0
- package/dist/aiEnableRequest/index.js.map +1 -0
- package/dist/aiEnableRequest/utils.js +36 -0
- package/dist/aiEnableRequest/utils.js.map +1 -0
- package/dist/annotation/index.js +14 -5
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.js +7 -2
- package/dist/config.js.map +1 -1
- package/dist/constants.js +28 -6
- package/dist/constants.js.map +1 -1
- package/dist/hashTree/constants.js +3 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +850 -410
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +4 -2
- 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/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/interceptors/constant.js +12 -0
- package/dist/interceptors/constant.js.map +1 -0
- package/dist/interceptors/dataChannelAuthToken.js +290 -0
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
- package/dist/interceptors/index.js +7 -0
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/utils.js +27 -0
- package/dist/interceptors/utils.js.map +1 -0
- package/dist/interpretation/index.js +2 -2
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +5 -3
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +522 -131
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +1 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/locus-info/types.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/in-meeting-actions.js +7 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1173 -877
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +50 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +133 -3
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +117 -48
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +10 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +9 -60
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +11 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/reachability/index.js +18 -10
- package/dist/reachability/index.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +0 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/types/aiEnableRequest/index.d.ts +5 -0
- package/dist/types/aiEnableRequest/utils.d.ts +2 -0
- package/dist/types/config.d.ts +4 -0
- package/dist/types/constants.d.ts +23 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +122 -14
- package/dist/types/hashTree/types.d.ts +3 -0
- package/dist/types/hashTree/utils.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/interceptors/constant.d.ts +5 -0
- package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
- package/dist/types/interceptors/index.d.ts +2 -1
- package/dist/types/interceptors/utils.d.ts +1 -0
- package/dist/types/locus-info/index.d.ts +60 -8
- package/dist/types/locus-info/types.d.ts +7 -0
- package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
- package/dist/types/media/properties.d.ts +2 -1
- package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
- package/dist/types/meeting/index.d.ts +61 -7
- package/dist/types/meeting/request.d.ts +16 -1
- package/dist/types/meeting/request.type.d.ts +5 -0
- package/dist/types/meeting/util.d.ts +31 -0
- package/dist/types/meetings/index.d.ts +4 -2
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +5 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/types/webinar/utils.d.ts +6 -0
- package/dist/webinar/index.js +291 -91
- package/dist/webinar/index.js.map +1 -1
- package/dist/webinar/utils.js +25 -0
- package/dist/webinar/utils.js.map +1 -0
- package/package.json +24 -23
- package/src/aiEnableRequest/README.md +84 -0
- package/src/aiEnableRequest/index.ts +170 -0
- package/src/aiEnableRequest/utils.ts +25 -0
- package/src/annotation/index.ts +27 -7
- package/src/config.ts +4 -0
- package/src/constants.ts +29 -1
- package/src/hashTree/constants.ts +1 -0
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +745 -252
- package/src/hashTree/types.ts +4 -0
- package/src/hashTree/utils.ts +9 -0
- package/src/index.ts +8 -1
- package/src/interceptors/constant.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +170 -0
- package/src/interceptors/index.ts +2 -1
- package/src/interceptors/utils.ts +16 -0
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/controlsUtils.ts +11 -0
- package/src/locus-info/index.ts +579 -113
- package/src/locus-info/selfUtils.ts +1 -0
- package/src/locus-info/types.ts +8 -0
- package/src/media/MediaConnectionAwaiter.ts +41 -1
- package/src/media/properties.ts +3 -1
- package/src/meeting/in-meeting-actions.ts +12 -0
- package/src/meeting/index.ts +291 -76
- package/src/meeting/request.ts +42 -0
- package/src/meeting/request.type.ts +6 -0
- package/src/meeting/util.ts +160 -2
- package/src/meetings/index.ts +157 -44
- package/src/member/index.ts +10 -0
- package/src/member/util.ts +12 -0
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +4 -54
- package/src/multistream/remoteMediaManager.ts +13 -0
- package/src/reachability/index.ts +9 -0
- package/src/reactions/reactions.type.ts +1 -0
- package/src/reconnection-manager/index.ts +0 -1
- package/src/webinar/index.ts +191 -6
- package/src/webinar/utils.ts +16 -0
- package/test/unit/spec/aiEnableRequest/index.ts +981 -0
- package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
- package/test/unit/spec/annotation/index.ts +69 -7
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +2225 -189
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
- package/test/unit/spec/interceptors/utils.ts +75 -0
- package/test/unit/spec/locus-info/controlsUtils.js +29 -0
- package/test/unit/spec/locus-info/index.js +1134 -55
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
- package/test/unit/spec/meeting/index.js +829 -115
- package/test/unit/spec/meeting/request.js +70 -0
- package/test/unit/spec/meeting/utils.js +438 -26
- package/test/unit/spec/meetings/index.js +653 -32
- package/test/unit/spec/member/index.js +28 -4
- package/test/unit/spec/member/util.js +65 -27
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
- package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
- package/test/unit/spec/reachability/index.ts +23 -0
- package/test/unit/spec/reconnection-manager/index.js +4 -8
- package/test/unit/spec/webinar/index.ts +474 -37
- package/test/unit/spec/webinar/utils.ts +39 -0
package/src/locus-info/index.ts
CHANGED
|
@@ -35,10 +35,14 @@ import HashTreeParser, {
|
|
|
35
35
|
DataSet,
|
|
36
36
|
HashTreeMessage,
|
|
37
37
|
LocusInfoUpdateType,
|
|
38
|
+
Metadata,
|
|
38
39
|
} from '../hashTree/hashTreeParser';
|
|
39
40
|
import {HashTreeObject, ObjectType, ObjectTypeToLocusKeyMap} from '../hashTree/types';
|
|
40
|
-
import {isSelf} from '../hashTree/utils';
|
|
41
|
-
import {Links, LocusDTO,
|
|
41
|
+
import {isMetadata, isSelf} from '../hashTree/utils';
|
|
42
|
+
import {Links, LocusDTO, ReplacesInfo} from './types';
|
|
43
|
+
import MeetingsUtil from '../meetings/util';
|
|
44
|
+
import {MEETING_KEY} from '../meetings/meetings.types';
|
|
45
|
+
import MeetingCollection from '../meetings/collection';
|
|
42
46
|
|
|
43
47
|
export type LocusLLMEvent = {
|
|
44
48
|
data: {
|
|
@@ -52,6 +56,7 @@ export type LocusLLMEvent = {
|
|
|
52
56
|
const LocusDtoTopLevelKeys = [
|
|
53
57
|
'controls',
|
|
54
58
|
'fullState',
|
|
59
|
+
'embeddedApps',
|
|
55
60
|
'host',
|
|
56
61
|
'info',
|
|
57
62
|
'links',
|
|
@@ -66,10 +71,13 @@ const LocusDtoTopLevelKeys = [
|
|
|
66
71
|
'htMeta', // only exists when hash trees are used
|
|
67
72
|
];
|
|
68
73
|
|
|
69
|
-
export type LocusApiResponseBody =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
export type LocusApiResponseBody =
|
|
75
|
+
| {
|
|
76
|
+
dataSets?: DataSet[];
|
|
77
|
+
locus: LocusDTO; // this LocusDTO here might not be the full one (for example it won't have all the participants, but it should have self)
|
|
78
|
+
metadata?: Metadata;
|
|
79
|
+
}
|
|
80
|
+
| LocusDTO; // when we invoke APIs on the whole Locus like "mute all" backend returns the whole Locus in the response like this
|
|
73
81
|
|
|
74
82
|
const LocusObjectStateAfterUpdates = {
|
|
75
83
|
unchanged: 'unchanged',
|
|
@@ -79,6 +87,167 @@ const LocusObjectStateAfterUpdates = {
|
|
|
79
87
|
|
|
80
88
|
type LocusObjectStateAfterUpdates = Enum<typeof LocusObjectStateAfterUpdates>;
|
|
81
89
|
|
|
90
|
+
export type HashTreeParserEntry = {
|
|
91
|
+
parser: HashTreeParser;
|
|
92
|
+
replacedAt?: string;
|
|
93
|
+
initializedFromHashTree: boolean;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gets the replacement information
|
|
98
|
+
*
|
|
99
|
+
* @param {any} self - "self" object from Locus DTO
|
|
100
|
+
* @param {string} deviceUrl - The URL of the user's device
|
|
101
|
+
* @returns {any} The replace information if available, otherwise undefined
|
|
102
|
+
*/
|
|
103
|
+
function getReplaceInfoFromSelf(self: any, deviceUrl: string): ReplacesInfo | undefined {
|
|
104
|
+
if (self) {
|
|
105
|
+
const device = MeetingsUtil.getThisDevice({self}, deviceUrl);
|
|
106
|
+
|
|
107
|
+
if (device?.replaces?.length > 0) {
|
|
108
|
+
return device.replaces[0];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Finds a meeting by its locus URL in meeting collection. It checks all HashTreeParsers of all meetings in the collection.
|
|
117
|
+
*
|
|
118
|
+
* @param {MeetingCollection} meetingCollection - The collection of meetings to search
|
|
119
|
+
* @param {string} locusUrl - The locus URL to search for
|
|
120
|
+
* @returns {any} The meeting if found, otherwise undefined
|
|
121
|
+
*/
|
|
122
|
+
function findLocusUrlInAnyHashTreeParser(
|
|
123
|
+
meetingCollection: MeetingCollection,
|
|
124
|
+
locusUrl: string
|
|
125
|
+
): any {
|
|
126
|
+
for (const meeting of Object.values(meetingCollection.getAll()) as any[]) {
|
|
127
|
+
if (meeting?.locusInfo?.hashTreeParsers?.has(locusUrl)) {
|
|
128
|
+
return meeting;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Finds a meeting for a given hash tree message.
|
|
137
|
+
*
|
|
138
|
+
* @param {HashTreeMessage} message - The hash tree message to find the meeting for
|
|
139
|
+
* @param {MeetingCollection} meetingCollection - The collection of meetings to search
|
|
140
|
+
* @param {string} deviceUrl - The URL of the user's device
|
|
141
|
+
* @returns {any} The meeting if found, otherwise undefined
|
|
142
|
+
*/
|
|
143
|
+
export function findMeetingForHashTreeMessage(
|
|
144
|
+
message: HashTreeMessage,
|
|
145
|
+
meetingCollection: MeetingCollection,
|
|
146
|
+
deviceUrl: string
|
|
147
|
+
): any {
|
|
148
|
+
let foundMeeting = findLocusUrlInAnyHashTreeParser(meetingCollection, message.locusUrl);
|
|
149
|
+
|
|
150
|
+
if (foundMeeting) {
|
|
151
|
+
return foundMeeting;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// if we haven't found anything, it may mean that message has a new locusUrl
|
|
155
|
+
// check if it indicates that it replaces some existing current locusUrl (this is indicated in "self")
|
|
156
|
+
const self = message.locusStateElements?.find((el) => isSelf(el))?.data;
|
|
157
|
+
const replaces = getReplaceInfoFromSelf(self, deviceUrl);
|
|
158
|
+
|
|
159
|
+
if (replaces?.locusUrl) {
|
|
160
|
+
foundMeeting = findLocusUrlInAnyHashTreeParser(meetingCollection, replaces.locusUrl);
|
|
161
|
+
|
|
162
|
+
return foundMeeting;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Creates a locus object from the objects received in a hash tree message. It usually will be
|
|
170
|
+
* incomplete, because hash tree messages only contain the parts of locus that have changed,
|
|
171
|
+
* and some updates come separately over Mercury or LLM in separate messages.
|
|
172
|
+
*
|
|
173
|
+
* @param {HashTreeMessage} message hash tree message to created the locus from
|
|
174
|
+
* @returns {Object} the created locus object and metadata if present
|
|
175
|
+
*/
|
|
176
|
+
export function createLocusFromHashTreeMessage(message: HashTreeMessage): {
|
|
177
|
+
locus: LocusDTO;
|
|
178
|
+
metadata?: Metadata;
|
|
179
|
+
} {
|
|
180
|
+
const locus: LocusDTO = {
|
|
181
|
+
participants: [],
|
|
182
|
+
url: message.locusUrl,
|
|
183
|
+
};
|
|
184
|
+
let metadata: Metadata | undefined;
|
|
185
|
+
|
|
186
|
+
if (!message.locusStateElements) {
|
|
187
|
+
return {locus, metadata};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const element of message.locusStateElements) {
|
|
191
|
+
if (!element.data) {
|
|
192
|
+
// eslint-disable-next-line no-continue
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const type = element.htMeta.elementId.type.toLowerCase();
|
|
197
|
+
|
|
198
|
+
switch (type) {
|
|
199
|
+
case ObjectType.locus: {
|
|
200
|
+
// spread locus object data onto the top level, but remove keys managed by other ObjectTypes
|
|
201
|
+
const locusObjectData = {...element.data};
|
|
202
|
+
|
|
203
|
+
Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
|
|
204
|
+
delete locusObjectData[locusDtoKey];
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
Object.assign(locus, locusObjectData);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case ObjectType.participant:
|
|
211
|
+
locus.participants.push(element.data);
|
|
212
|
+
break;
|
|
213
|
+
case ObjectType.mediaShare:
|
|
214
|
+
if (!locus.mediaShares) {
|
|
215
|
+
locus.mediaShares = [];
|
|
216
|
+
}
|
|
217
|
+
locus.mediaShares.push(element.data);
|
|
218
|
+
break;
|
|
219
|
+
case ObjectType.embeddedApp:
|
|
220
|
+
if (!locus.embeddedApps) {
|
|
221
|
+
locus.embeddedApps = [];
|
|
222
|
+
}
|
|
223
|
+
locus.embeddedApps.push(element.data);
|
|
224
|
+
break;
|
|
225
|
+
case ObjectType.control:
|
|
226
|
+
if (!locus.controls) {
|
|
227
|
+
locus.controls = {};
|
|
228
|
+
}
|
|
229
|
+
Object.assign(locus.controls, element.data);
|
|
230
|
+
break;
|
|
231
|
+
case ObjectType.links:
|
|
232
|
+
case ObjectType.info:
|
|
233
|
+
case ObjectType.fullState:
|
|
234
|
+
case ObjectType.self: {
|
|
235
|
+
const locusDtoKey = ObjectTypeToLocusKeyMap[type];
|
|
236
|
+
locus[locusDtoKey] = element.data;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case ObjectType.metadata:
|
|
240
|
+
// metadata is not part of Locus DTO
|
|
241
|
+
metadata = {...element.data, htMeta: element.htMeta} as Metadata;
|
|
242
|
+
break;
|
|
243
|
+
default:
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {locus, metadata};
|
|
249
|
+
}
|
|
250
|
+
|
|
82
251
|
/**
|
|
83
252
|
* @description LocusInfo extends ChildEmitter to convert locusInfo info a private emitter to parent object
|
|
84
253
|
* @export
|
|
@@ -112,7 +281,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
112
281
|
links?: Links;
|
|
113
282
|
mainSessionLocusCache: any;
|
|
114
283
|
self: any;
|
|
115
|
-
|
|
284
|
+
hashTreeParsers: Map<string, HashTreeParserEntry>;
|
|
116
285
|
hashTreeObjectId2ParticipantId: Map<number, string>; // mapping of hash tree object ids to participant ids
|
|
117
286
|
classicVsHashTreeMismatchMetricCounter = 0;
|
|
118
287
|
|
|
@@ -134,6 +303,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
134
303
|
this.meetingId = meetingId;
|
|
135
304
|
this.updateMeeting = updateMeeting;
|
|
136
305
|
this.locusParser = new LocusDeltaParser();
|
|
306
|
+
this.hashTreeParsers = new Map();
|
|
137
307
|
this.hashTreeObjectId2ParticipantId = new Map();
|
|
138
308
|
}
|
|
139
309
|
|
|
@@ -239,7 +409,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
239
409
|
'Locus-info:index#doLocusSync --> got full DTO when we asked for delta'
|
|
240
410
|
);
|
|
241
411
|
}
|
|
242
|
-
meeting.locusInfo.onFullLocus(res.body);
|
|
412
|
+
meeting.locusInfo.onFullLocus('classic Locus sync', res.body);
|
|
243
413
|
})
|
|
244
414
|
.catch((e) => {
|
|
245
415
|
LoggerProxy.logger.info(
|
|
@@ -356,24 +526,56 @@ export default class LocusInfo extends EventsScope {
|
|
|
356
526
|
}
|
|
357
527
|
|
|
358
528
|
/**
|
|
359
|
-
* Creates
|
|
360
|
-
* @param {Object}
|
|
361
|
-
* @
|
|
529
|
+
* Creates a HashTreeParser instance for a given locusUrl and stores it in the map.
|
|
530
|
+
* @param {Object} params
|
|
531
|
+
* @param {string} params.locusUrl - the locus URL used as the map key
|
|
532
|
+
* @param {Object} params.initialLocus - initial locus data
|
|
533
|
+
* @param {Object} params.metadata - hash tree metadata
|
|
534
|
+
* @param {string} params.replacedAt - timestamp from Locus indicating when the replacement happened
|
|
535
|
+
* @returns {HashTreeParser} the newly created parser
|
|
362
536
|
*/
|
|
363
537
|
private createHashTreeParser({
|
|
538
|
+
locusUrl,
|
|
364
539
|
initialLocus,
|
|
540
|
+
metadata,
|
|
541
|
+
replacedAt,
|
|
365
542
|
}: {
|
|
543
|
+
locusUrl: string;
|
|
366
544
|
initialLocus: {
|
|
367
545
|
dataSets: Array<DataSet>;
|
|
368
546
|
locus: any;
|
|
369
547
|
};
|
|
370
|
-
|
|
371
|
-
|
|
548
|
+
metadata: Metadata;
|
|
549
|
+
replacedAt?: string;
|
|
550
|
+
}): HashTreeParser {
|
|
551
|
+
const parser = new HashTreeParser({
|
|
372
552
|
initialLocus,
|
|
553
|
+
metadata,
|
|
373
554
|
webexRequest: this.webex.request.bind(this.webex),
|
|
374
|
-
locusInfoUpdateCallback: this.updateFromHashTree.bind(this),
|
|
375
|
-
debugId: `HT-${
|
|
555
|
+
locusInfoUpdateCallback: this.updateFromHashTree.bind(this, locusUrl),
|
|
556
|
+
debugId: `HT-${locusUrl.split('/').pop().substring(0, 4)}`,
|
|
557
|
+
excludedDataSets: this.webex.config.meetings.locus?.excludedDataSets,
|
|
376
558
|
});
|
|
559
|
+
|
|
560
|
+
// When a new HashTreeParser is created, previous one should be stopped.
|
|
561
|
+
// Locus will only be sending us updates for the current one.
|
|
562
|
+
for (const [existingLocusUrl, existingEntry] of this.hashTreeParsers) {
|
|
563
|
+
if (existingEntry.parser.state !== 'stopped') {
|
|
564
|
+
existingEntry.parser.stop();
|
|
565
|
+
if (replacedAt) {
|
|
566
|
+
existingEntry.replacedAt = replacedAt;
|
|
567
|
+
} else {
|
|
568
|
+
LoggerProxy.logger.warn(
|
|
569
|
+
`Locus-info:index#createHashTreeParser --> no replacedAt timestamp provided for new HashTreeParser with locusUrl ${locusUrl}, replacing ${existingLocusUrl}`
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
this.hashTreeParsers.set(locusUrl, {parser, initializedFromHashTree: false});
|
|
576
|
+
this.hashTreeObjectId2ParticipantId.clear();
|
|
577
|
+
|
|
578
|
+
return parser;
|
|
377
579
|
}
|
|
378
580
|
|
|
379
581
|
/**
|
|
@@ -387,6 +589,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
387
589
|
trigger: 'join-response';
|
|
388
590
|
locus: LocusDTO;
|
|
389
591
|
dataSets?: DataSet[];
|
|
592
|
+
metadata?: Metadata;
|
|
390
593
|
}
|
|
391
594
|
| {
|
|
392
595
|
trigger: 'locus-message';
|
|
@@ -401,41 +604,50 @@ export default class LocusInfo extends EventsScope {
|
|
|
401
604
|
switch (data.trigger) {
|
|
402
605
|
case 'locus-message':
|
|
403
606
|
if (data.hashTreeMessage) {
|
|
404
|
-
// we need the
|
|
607
|
+
// we need the Metadata object to be in the received message, because it contains visibleDataSets
|
|
405
608
|
// and these are needed to initialize all the hash trees
|
|
406
|
-
const
|
|
609
|
+
const metadataObject = data.hashTreeMessage.locusStateElements?.find((el) =>
|
|
610
|
+
isMetadata(el)
|
|
611
|
+
);
|
|
407
612
|
|
|
408
|
-
if (!
|
|
409
|
-
|
|
410
|
-
|
|
613
|
+
if (!metadataObject?.data?.visibleDataSets) {
|
|
614
|
+
// this is a common case (not an error)
|
|
615
|
+
// it happens for example after we leave the meeting and still get some heartbeats or delayed messages
|
|
616
|
+
LoggerProxy.logger.info(
|
|
617
|
+
`Locus-info:index#initialSetup --> cannot initialize HashTreeParser, Metadata object with visibleDataSets is missing in the message`
|
|
411
618
|
);
|
|
412
619
|
|
|
413
|
-
throw
|
|
620
|
+
// throw so that handleLocusEvent() catches it and destroys the partially created meeting object
|
|
621
|
+
throw new Error('Metadata object with visibleDataSets is missing in the message');
|
|
414
622
|
}
|
|
415
623
|
|
|
416
624
|
LoggerProxy.logger.info(
|
|
417
625
|
'Locus-info:index#initialSetup --> creating HashTreeParser from message'
|
|
418
626
|
);
|
|
419
627
|
// first create the HashTreeParser, but don't initialize it with any data yet
|
|
420
|
-
|
|
421
|
-
|
|
628
|
+
const hashTreeParser = this.createHashTreeParser({
|
|
629
|
+
locusUrl: data.hashTreeMessage.locusUrl,
|
|
422
630
|
initialLocus: {
|
|
423
|
-
locus:
|
|
424
|
-
dataSets:
|
|
631
|
+
locus: null,
|
|
632
|
+
dataSets: data.hashTreeMessage.dataSets,
|
|
633
|
+
},
|
|
634
|
+
metadata: {
|
|
635
|
+
htMeta: metadataObject.htMeta,
|
|
636
|
+
visibleDataSets: metadataObject.data.visibleDataSets,
|
|
425
637
|
},
|
|
426
638
|
});
|
|
427
639
|
|
|
428
640
|
// now handle the message - that should populate all the visible datasets
|
|
429
|
-
await
|
|
641
|
+
await hashTreeParser.initializeFromMessage(data.hashTreeMessage);
|
|
430
642
|
} else {
|
|
431
643
|
// "classic" Locus case, no hash trees involved
|
|
432
644
|
this.updateLocusCache(data.locus);
|
|
433
|
-
this.onFullLocus(data.locus, undefined);
|
|
645
|
+
this.onFullLocus('classic locus message', data.locus, undefined);
|
|
434
646
|
}
|
|
435
647
|
break;
|
|
436
648
|
case 'join-response':
|
|
437
649
|
this.updateLocusCache(data.locus);
|
|
438
|
-
this.onFullLocus(data.locus, undefined, data.dataSets);
|
|
650
|
+
this.onFullLocus('join response', data.locus, undefined, data.dataSets, data.metadata);
|
|
439
651
|
break;
|
|
440
652
|
case 'get-loci-response':
|
|
441
653
|
if (data.locus?.links?.resources?.visibleDataSets?.url) {
|
|
@@ -443,20 +655,21 @@ export default class LocusInfo extends EventsScope {
|
|
|
443
655
|
'Locus-info:index#initialSetup --> creating HashTreeParser from get-loci-response'
|
|
444
656
|
);
|
|
445
657
|
// first create the HashTreeParser, but don't initialize it with any data yet
|
|
446
|
-
|
|
447
|
-
|
|
658
|
+
const hashTreeParser = this.createHashTreeParser({
|
|
659
|
+
locusUrl: data.locus.url,
|
|
448
660
|
initialLocus: {
|
|
449
|
-
locus:
|
|
661
|
+
locus: null,
|
|
450
662
|
dataSets: [], // empty, because we don't have them yet
|
|
451
663
|
},
|
|
664
|
+
metadata: null, // get-loci-response doesn't contain Metadata object
|
|
452
665
|
});
|
|
453
666
|
|
|
454
667
|
// now initialize all the data
|
|
455
|
-
await
|
|
668
|
+
await hashTreeParser.initializeFromGetLociResponse(data.locus);
|
|
456
669
|
} else {
|
|
457
670
|
// "classic" Locus case, no hash trees involved
|
|
458
671
|
this.updateLocusCache(data.locus);
|
|
459
|
-
this.onFullLocus(data.locus, undefined);
|
|
672
|
+
this.onFullLocus('classic get-loci-response', data.locus, undefined);
|
|
460
673
|
}
|
|
461
674
|
}
|
|
462
675
|
// Change it to true after it receives it first locus object
|
|
@@ -470,34 +683,42 @@ export default class LocusInfo extends EventsScope {
|
|
|
470
683
|
* @returns {void}
|
|
471
684
|
*/
|
|
472
685
|
handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
686
|
+
const isWrapped = 'locus' in responseBody;
|
|
687
|
+
const locusUrl = isWrapped ? responseBody.locus?.url : responseBody.url;
|
|
688
|
+
const hashTreeParserEntry = locusUrl && this.hashTreeParsers.get(locusUrl);
|
|
689
|
+
if (hashTreeParserEntry) {
|
|
690
|
+
if (isWrapped) {
|
|
691
|
+
if (!responseBody.dataSets) {
|
|
692
|
+
this.sendClassicVsHashTreeMismatchMetric(
|
|
693
|
+
meeting,
|
|
694
|
+
`expected hash tree dataSets in API response but they are missing`
|
|
695
|
+
);
|
|
696
|
+
// continuing as we can still manage without responseBody.dataSets, but this is very suspicious
|
|
697
|
+
}
|
|
698
|
+
LoggerProxy.logger.info(
|
|
699
|
+
'Locus-info:index#handleLocusAPIResponse --> passing Locus API response to HashTreeParser: ',
|
|
700
|
+
responseBody
|
|
478
701
|
);
|
|
479
|
-
//
|
|
702
|
+
// update the data in our hash trees
|
|
703
|
+
hashTreeParserEntry.parser.handleLocusUpdate(responseBody);
|
|
704
|
+
} else {
|
|
705
|
+
// LocusDTO without wrapper - pass it through as if it had no dataSets
|
|
706
|
+
hashTreeParserEntry.parser.handleLocusUpdate({locus: responseBody});
|
|
480
707
|
}
|
|
481
|
-
LoggerProxy.logger.info(
|
|
482
|
-
'Locus-info:index#handleLocusAPIResponse --> passing Locus API response to HashTreeParser: ',
|
|
483
|
-
responseBody
|
|
484
|
-
);
|
|
485
|
-
// update the data in our hash trees
|
|
486
|
-
this.hashTreeParser.handleLocusUpdate(responseBody);
|
|
487
708
|
} else {
|
|
488
|
-
if (responseBody.dataSets) {
|
|
709
|
+
if (isWrapped && responseBody.dataSets) {
|
|
489
710
|
this.sendClassicVsHashTreeMismatchMetric(
|
|
490
711
|
meeting,
|
|
491
712
|
`unexpected hash tree dataSets in API response`
|
|
492
713
|
);
|
|
493
714
|
}
|
|
494
715
|
// classic Locus delta
|
|
495
|
-
|
|
716
|
+
const locus = isWrapped ? responseBody.locus : responseBody;
|
|
717
|
+
this.handleLocusDelta(locus, meeting);
|
|
496
718
|
}
|
|
497
719
|
}
|
|
498
720
|
|
|
499
721
|
/**
|
|
500
|
-
*
|
|
501
722
|
* @param {HashTreeObject} object data set object
|
|
502
723
|
* @param {any} locus
|
|
503
724
|
* @returns {void}
|
|
@@ -571,6 +792,31 @@ export default class LocusInfo extends EventsScope {
|
|
|
571
792
|
);
|
|
572
793
|
}
|
|
573
794
|
break;
|
|
795
|
+
case ObjectType.embeddedApp:
|
|
796
|
+
if (object.data) {
|
|
797
|
+
LoggerProxy.logger.info(
|
|
798
|
+
`Locus-info:index#updateLocusFromHashTreeObject --> embeddedApp id=${object.htMeta.elementId.id} url='${object.data.url}' updated version=${object.htMeta.elementId.version}:`,
|
|
799
|
+
object.data
|
|
800
|
+
);
|
|
801
|
+
const existingEmbeddedApp = locus.embeddedApps?.find(
|
|
802
|
+
(ms) => ms.htMeta.elementId.id === object.htMeta.elementId.id
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
if (existingEmbeddedApp) {
|
|
806
|
+
Object.assign(existingEmbeddedApp, object.data);
|
|
807
|
+
} else {
|
|
808
|
+
locus.embeddedApps = locus.embeddedApps || [];
|
|
809
|
+
locus.embeddedApps.push(object.data);
|
|
810
|
+
}
|
|
811
|
+
} else {
|
|
812
|
+
LoggerProxy.logger.info(
|
|
813
|
+
`Locus-info:index#updateLocusFromHashTreeObject --> embeddedApp id=${object.htMeta.elementId.id} removed, version=${object.htMeta.elementId.version}`
|
|
814
|
+
);
|
|
815
|
+
locus.embeddedApps = locus.embeddedApps?.filter(
|
|
816
|
+
(ms) => ms.htMeta.elementId.id !== object.htMeta.elementId.id
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
break;
|
|
574
820
|
case ObjectType.participant:
|
|
575
821
|
LoggerProxy.logger.info(
|
|
576
822
|
`Locus-info:index#updateLocusFromHashTreeObject --> participant id=${
|
|
@@ -588,6 +834,23 @@ export default class LocusInfo extends EventsScope {
|
|
|
588
834
|
locus.jsSdkMeta.removedParticipantIds.push(participantId);
|
|
589
835
|
this.hashTreeObjectId2ParticipantId.delete(object.htMeta.elementId.id);
|
|
590
836
|
}
|
|
837
|
+
// Create self from the participant if it matches self identity and is being moved.
|
|
838
|
+
// We need this, because participant update comes in LLM message often before the self update from Mercury.
|
|
839
|
+
// Other parts of the code detect move only by looking at self, while some other parts of the SDK/webapp code
|
|
840
|
+
// look at participant for roles etc, so if participant is updated but not self, then it looks like we our lost roles temporarily
|
|
841
|
+
// (until self is updated)
|
|
842
|
+
// This will be fixed properly in SPARK-790239
|
|
843
|
+
if (
|
|
844
|
+
object.data &&
|
|
845
|
+
object.data.identity === locus.self?.identity &&
|
|
846
|
+
object.data.state === 'LEFT' &&
|
|
847
|
+
object.data.reason === 'MOVED'
|
|
848
|
+
) {
|
|
849
|
+
LoggerProxy.logger.info(
|
|
850
|
+
`Locus-info:index#updateLocusFromHashTreeObject --> FOUND a match for MOVED self in participant object ${object.htMeta.elementId.id}`
|
|
851
|
+
);
|
|
852
|
+
Object.assign(locus[ObjectTypeToLocusKeyMap[ObjectType.self]], object.data);
|
|
853
|
+
}
|
|
591
854
|
break;
|
|
592
855
|
case ObjectType.control:
|
|
593
856
|
if (object.data) {
|
|
@@ -643,6 +906,12 @@ export default class LocusInfo extends EventsScope {
|
|
|
643
906
|
}
|
|
644
907
|
}
|
|
645
908
|
break;
|
|
909
|
+
case ObjectType.metadata:
|
|
910
|
+
LoggerProxy.logger.info(
|
|
911
|
+
`Locus-info:index#updateLocusFromHashTreeObject --> metadata object updated to version ${object.htMeta.elementId.version}`
|
|
912
|
+
);
|
|
913
|
+
// we don't use hash tree metadata right now for anything, it's mainly used internally by HashTreeParser
|
|
914
|
+
break;
|
|
646
915
|
default:
|
|
647
916
|
LoggerProxy.logger.warn(
|
|
648
917
|
`Locus-info:index#updateLocusFromHashTreeObject --> received unsupported object type ${type}`
|
|
@@ -675,6 +944,92 @@ export default class LocusInfo extends EventsScope {
|
|
|
675
944
|
}
|
|
676
945
|
}
|
|
677
946
|
|
|
947
|
+
/**
|
|
948
|
+
* Checks if the hash tree message should trigger a switch to a different HashTreeParser
|
|
949
|
+
*
|
|
950
|
+
* @param {HashTreeMessage} message incoming hash tree message
|
|
951
|
+
* @returns {boolean} true if the message was handled as a parser switch, false otherwise
|
|
952
|
+
*/
|
|
953
|
+
private handleHashTreeParserSwitch(message: HashTreeMessage): boolean {
|
|
954
|
+
const entry = this.hashTreeParsers.get(message.locusUrl);
|
|
955
|
+
|
|
956
|
+
const self = message.locusStateElements?.find((el) => isSelf(el))?.data;
|
|
957
|
+
const replaces = getReplaceInfoFromSelf(
|
|
958
|
+
self,
|
|
959
|
+
// @ts-ignore
|
|
960
|
+
this.webex.internal.device.url
|
|
961
|
+
);
|
|
962
|
+
|
|
963
|
+
if (!entry) {
|
|
964
|
+
// Metadata object that contains information about visible datasets is needed to initialize the HashTreeParser,
|
|
965
|
+
// but it's buried inside the message, we need to find it and pass it to HashTreeParser constructor
|
|
966
|
+
const metadata = message.locusStateElements?.find((el) => isMetadata(el));
|
|
967
|
+
|
|
968
|
+
if (metadata?.data?.visibleDataSets?.length > 0) {
|
|
969
|
+
LoggerProxy.logger.info(
|
|
970
|
+
`Locus-info:index#handleHashTreeParserSwitch --> no hash tree parser found for locusUrl ${message.locusUrl}, creating a new one`
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
const parser = this.createHashTreeParser({
|
|
974
|
+
locusUrl: message.locusUrl,
|
|
975
|
+
initialLocus: {
|
|
976
|
+
locus: null,
|
|
977
|
+
dataSets: message.dataSets,
|
|
978
|
+
},
|
|
979
|
+
metadata: {
|
|
980
|
+
htMeta: metadata.htMeta,
|
|
981
|
+
visibleDataSets: metadata.data.visibleDataSets,
|
|
982
|
+
},
|
|
983
|
+
replacedAt: replaces?.replacedAt,
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
// handle the message with the new parser
|
|
987
|
+
parser.handleMessage(message);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
if (entry.parser.state === 'stopped') {
|
|
993
|
+
// the message matches a stopped parser, we need to check if maybe this is a new "replacement" and we need to re-activate the parser
|
|
994
|
+
// this happens when you move from breakout A -> breakout B -> back to breakout A
|
|
995
|
+
if (replaces) {
|
|
996
|
+
if (replaces.replacedAt > (entry.replacedAt || '')) {
|
|
997
|
+
LoggerProxy.logger.info(
|
|
998
|
+
`Locus-info:index#handleHashTreeParserSwitch --> resuming a HashTreeParser for locusUrl=${message.locusUrl}, which replaces ${replaces.locusUrl}`
|
|
999
|
+
);
|
|
1000
|
+
const replacedEntry = this.hashTreeParsers.get(replaces.locusUrl);
|
|
1001
|
+
|
|
1002
|
+
if (replacedEntry) {
|
|
1003
|
+
replacedEntry.replacedAt = replaces.replacedAt;
|
|
1004
|
+
entry.initializedFromHashTree = false;
|
|
1005
|
+
this.hashTreeObjectId2ParticipantId.clear();
|
|
1006
|
+
|
|
1007
|
+
replacedEntry.parser.stop();
|
|
1008
|
+
entry.parser.resume(message);
|
|
1009
|
+
} else {
|
|
1010
|
+
LoggerProxy.logger.warn(
|
|
1011
|
+
`Locus-info:index#handleHashTreeParserSwitch --> the parser that is supposed to be replaced with the currently resumed parser is not found, locusUrl=${replaces.locusUrl}`
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
} else {
|
|
1015
|
+
LoggerProxy.logger.info(
|
|
1016
|
+
`Locus-info:index#handleHashTreeParserSwitch --> received message for stopped HashTreeParser with locusUrl ${message.locusUrl}, but replaces info provided is not newer, so not re-activating the parser`
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
LoggerProxy.logger.info(
|
|
1024
|
+
`Locus-info:index#handleHashTreeParserSwitch --> received message for stopped HashTreeParser with locusUrl ${message.locusUrl}, but no replaces info provided, so not re-activating the parser`
|
|
1025
|
+
);
|
|
1026
|
+
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
678
1033
|
/**
|
|
679
1034
|
* Handles a hash tree message received from Locus.
|
|
680
1035
|
*
|
|
@@ -693,18 +1048,28 @@ export default class LocusInfo extends EventsScope {
|
|
|
693
1048
|
return;
|
|
694
1049
|
}
|
|
695
1050
|
|
|
696
|
-
this.
|
|
1051
|
+
const parserSwitched = this.handleHashTreeParserSwitch(message);
|
|
1052
|
+
|
|
1053
|
+
if (parserSwitched) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const entry = this.hashTreeParsers.get(message.locusUrl);
|
|
1058
|
+
|
|
1059
|
+
entry.parser.handleMessage(message);
|
|
697
1060
|
}
|
|
698
1061
|
|
|
699
1062
|
/**
|
|
700
1063
|
* Callback registered with HashTreeParser to receive locus info updates.
|
|
701
1064
|
* Updates our locus info based on the data parsed by the hash tree parser.
|
|
702
1065
|
*
|
|
1066
|
+
* @param {string} locusUrl - the locus URL for which the update is received
|
|
703
1067
|
* @param {LocusInfoUpdateType} updateType - The type of update received.
|
|
704
1068
|
* @param {Object} [data] - Additional data for the update, if applicable.
|
|
705
1069
|
* @returns {void}
|
|
706
1070
|
*/
|
|
707
1071
|
private updateFromHashTree(
|
|
1072
|
+
locusUrl: string,
|
|
708
1073
|
updateType: LocusInfoUpdateType,
|
|
709
1074
|
data?: {updatedObjects: HashTreeObject[]}
|
|
710
1075
|
) {
|
|
@@ -713,7 +1078,10 @@ export default class LocusInfo extends EventsScope {
|
|
|
713
1078
|
// initialize our new locus
|
|
714
1079
|
let locus: LocusDTO = {
|
|
715
1080
|
participants: [],
|
|
716
|
-
jsSdkMeta: {
|
|
1081
|
+
jsSdkMeta: {
|
|
1082
|
+
removedParticipantIds: [],
|
|
1083
|
+
forceReplaceMembers: false,
|
|
1084
|
+
},
|
|
717
1085
|
};
|
|
718
1086
|
|
|
719
1087
|
// first go over all the updates and check what happens with the main locus object
|
|
@@ -746,12 +1114,23 @@ export default class LocusInfo extends EventsScope {
|
|
|
746
1114
|
}
|
|
747
1115
|
});
|
|
748
1116
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1117
|
+
const hashTreeParserEntry = this.hashTreeParsers.get(locusUrl);
|
|
1118
|
+
|
|
1119
|
+
if (!hashTreeParserEntry.initializedFromHashTree) {
|
|
1120
|
+
// this is the first time we're getting an update for this locusUrl,
|
|
1121
|
+
// so it's probably a move to/from breakout. We need to start from a clean state,
|
|
1122
|
+
// so empty locus and we rely on Locus giving us sufficient data in the updates to populate it.
|
|
1123
|
+
LoggerProxy.logger.info(
|
|
1124
|
+
`Locus-info:index#updateFromHashTree --> first INITIAL update for locusUrl ${locusUrl}, starting from empty state`
|
|
1125
|
+
);
|
|
1126
|
+
hashTreeParserEntry.initializedFromHashTree = true;
|
|
1127
|
+
locus.jsSdkMeta.forceReplaceMembers = true;
|
|
1128
|
+
} else if (
|
|
1129
|
+
// if Locus object is unchanged or removed, we need to keep using the existing locus
|
|
1130
|
+
// because the rest of the locusInfo code expects locus to always be present (with at least some of the fields)
|
|
1131
|
+
// if it gets updated, we only need to have the fields that are not part of "locus" object (like "info" or "mediaShares")
|
|
1132
|
+
// so that when Locus object gets updated, if the new one is missing some field, that field will
|
|
1133
|
+
// be removed from our locusInfo
|
|
755
1134
|
locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.unchanged ||
|
|
756
1135
|
locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.removed
|
|
757
1136
|
) {
|
|
@@ -792,11 +1171,14 @@ export default class LocusInfo extends EventsScope {
|
|
|
792
1171
|
}
|
|
793
1172
|
|
|
794
1173
|
case LocusInfoUpdateType.MEETING_ENDED: {
|
|
795
|
-
LoggerProxy.logger.info(
|
|
796
|
-
`Locus-info:index#updateFromHashTree --> received signal that meeting ended, destroying meeting ${this.meetingId}`
|
|
797
|
-
);
|
|
798
1174
|
const meeting = this.webex.meetings.meetingCollection.get(this.meetingId);
|
|
799
|
-
|
|
1175
|
+
|
|
1176
|
+
if (meeting) {
|
|
1177
|
+
LoggerProxy.logger.info(
|
|
1178
|
+
`Locus-info:index#updateFromHashTree --> received signal that meeting ended, destroying meeting ${this.meetingId}`
|
|
1179
|
+
);
|
|
1180
|
+
this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.SELF_REMOVED);
|
|
1181
|
+
}
|
|
800
1182
|
}
|
|
801
1183
|
}
|
|
802
1184
|
}
|
|
@@ -808,15 +1190,25 @@ export default class LocusInfo extends EventsScope {
|
|
|
808
1190
|
* @memberof LocusInfo
|
|
809
1191
|
*/
|
|
810
1192
|
parse(meeting: any, data: any) {
|
|
811
|
-
if (this.
|
|
1193
|
+
if (this.hashTreeParsers.size > 0) {
|
|
812
1194
|
this.handleHashTreeMessage(
|
|
813
1195
|
meeting,
|
|
814
1196
|
data.eventType,
|
|
815
1197
|
data.stateElementsMessage as HashTreeMessage
|
|
816
1198
|
);
|
|
817
1199
|
} else {
|
|
818
|
-
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
819
1200
|
const {eventType} = data;
|
|
1201
|
+
|
|
1202
|
+
if (eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
|
|
1203
|
+
// this can happen when we get an event before join http response
|
|
1204
|
+
// it's OK to just ignore it
|
|
1205
|
+
LoggerProxy.logger.info(
|
|
1206
|
+
`Locus-info:index#parse --> received locus hash tree event before hashTreeParser is created`
|
|
1207
|
+
);
|
|
1208
|
+
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
820
1212
|
const locus = this.getTheLocusToUpdate(data.locus);
|
|
821
1213
|
LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
|
|
822
1214
|
|
|
@@ -837,17 +1229,11 @@ export default class LocusInfo extends EventsScope {
|
|
|
837
1229
|
case LOCUSEVENT.PARTICIPANT_DECLINED:
|
|
838
1230
|
case LOCUSEVENT.FLOOR_GRANTED:
|
|
839
1231
|
case LOCUSEVENT.FLOOR_RELEASED:
|
|
840
|
-
this.onFullLocus(locus, eventType);
|
|
1232
|
+
this.onFullLocus(`classic locus event ${eventType}`, locus, eventType);
|
|
841
1233
|
break;
|
|
842
1234
|
case LOCUSEVENT.DIFFERENCE:
|
|
843
1235
|
this.handleLocusDelta(locus, meeting);
|
|
844
1236
|
break;
|
|
845
|
-
case LOCUSEVENT.HASH_TREE_DATA_UPDATED:
|
|
846
|
-
this.sendClassicVsHashTreeMismatchMetric(
|
|
847
|
-
meeting,
|
|
848
|
-
`got ${eventType}, expected classic events`
|
|
849
|
-
);
|
|
850
|
-
break;
|
|
851
1237
|
|
|
852
1238
|
default:
|
|
853
1239
|
// Why will there be a event with no eventType ????
|
|
@@ -871,46 +1257,63 @@ export default class LocusInfo extends EventsScope {
|
|
|
871
1257
|
/**
|
|
872
1258
|
* Function for handling full locus when it's using hash trees (so not the "classic" one).
|
|
873
1259
|
*
|
|
1260
|
+
* @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
|
|
874
1261
|
* @param {object} locus locus object
|
|
1262
|
+
* @param {object} metadata locus hash trees metadata
|
|
875
1263
|
* @param {string} eventType locus event
|
|
876
1264
|
* @param {DataSet[]} dataSets
|
|
877
1265
|
* @returns {void}
|
|
878
1266
|
*/
|
|
879
|
-
private onFullLocusWithHashTrees(
|
|
880
|
-
|
|
881
|
-
|
|
1267
|
+
private onFullLocusWithHashTrees(
|
|
1268
|
+
debugText: string,
|
|
1269
|
+
locus: any,
|
|
1270
|
+
metadata: Metadata,
|
|
1271
|
+
eventType?: string,
|
|
1272
|
+
dataSets?: Array<DataSet>
|
|
1273
|
+
) {
|
|
1274
|
+
if (!this.hashTreeParsers.has(locus.url)) {
|
|
882
1275
|
LoggerProxy.logger.info(
|
|
883
|
-
|
|
1276
|
+
`Locus-info:index#onFullLocus (${debugText}) --> creating hash tree parser for locusUrl=${locus.url}`
|
|
1277
|
+
);
|
|
1278
|
+
LoggerProxy.logger.info(
|
|
1279
|
+
`Locus-info:index#onFullLocus (${debugText}) --> dataSets:`,
|
|
884
1280
|
dataSets,
|
|
885
1281
|
' and locus:',
|
|
886
|
-
locus
|
|
1282
|
+
locus,
|
|
1283
|
+
' and metadata:',
|
|
1284
|
+
metadata
|
|
887
1285
|
);
|
|
888
|
-
this.
|
|
1286
|
+
this.createHashTreeParser({
|
|
1287
|
+
locusUrl: locus.url,
|
|
889
1288
|
initialLocus: {locus, dataSets},
|
|
1289
|
+
metadata,
|
|
890
1290
|
});
|
|
1291
|
+
// we have a full locus to start with, so we consider Locus info to be "initialized"
|
|
1292
|
+
this.hashTreeParsers.get(locus.url).initializedFromHashTree = true;
|
|
891
1293
|
this.onFullLocusCommon(locus, eventType);
|
|
892
1294
|
} else {
|
|
893
1295
|
// in this case the Locus we're getting is not necessarily the full one
|
|
894
1296
|
// so treat it like if we just got it in any api response
|
|
895
1297
|
|
|
896
1298
|
LoggerProxy.logger.info(
|
|
897
|
-
|
|
1299
|
+
`Locus-info:index#onFullLocus (${debugText}) --> hash tree parser already exists, handling it like a normal API response`
|
|
898
1300
|
);
|
|
899
|
-
this.handleLocusAPIResponse(undefined, {dataSets, locus});
|
|
1301
|
+
this.handleLocusAPIResponse(undefined, {dataSets, locus, metadata});
|
|
900
1302
|
}
|
|
901
1303
|
}
|
|
902
1304
|
|
|
903
1305
|
/**
|
|
904
1306
|
* Function for handling full locus when it's the "classic" one (not hash trees)
|
|
905
1307
|
*
|
|
1308
|
+
* @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
|
|
906
1309
|
* @param {object} locus locus object
|
|
907
1310
|
* @param {string} eventType locus event
|
|
908
1311
|
* @returns {void}
|
|
909
1312
|
*/
|
|
910
|
-
private onFullLocusClassic(locus: any, eventType?: string) {
|
|
1313
|
+
private onFullLocusClassic(debugText: string, locus: any, eventType?: string) {
|
|
911
1314
|
if (!this.locusParser.isNewFullLocus(locus)) {
|
|
912
1315
|
LoggerProxy.logger.info(
|
|
913
|
-
`Locus-info:index#onFullLocus --> ignoring old full locus DTO, eventType=${eventType}`
|
|
1316
|
+
`Locus-info:index#onFullLocus (${debugText}) --> ignoring old full locus DTO, eventType=${eventType}`
|
|
914
1317
|
);
|
|
915
1318
|
|
|
916
1319
|
return;
|
|
@@ -920,24 +1323,37 @@ export default class LocusInfo extends EventsScope {
|
|
|
920
1323
|
|
|
921
1324
|
/**
|
|
922
1325
|
* updates the locus with full locus object
|
|
1326
|
+
* @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
|
|
923
1327
|
* @param {object} locus locus object
|
|
924
1328
|
* @param {string} eventType locus event
|
|
925
1329
|
* @param {DataSet[]} dataSets
|
|
1330
|
+
* @param {object} metadata locus hash trees metadata
|
|
926
1331
|
* @returns {object} null
|
|
927
1332
|
* @memberof LocusInfo
|
|
928
1333
|
*/
|
|
929
|
-
onFullLocus(
|
|
1334
|
+
onFullLocus(
|
|
1335
|
+
debugText: string,
|
|
1336
|
+
locus: any,
|
|
1337
|
+
eventType?: string,
|
|
1338
|
+
dataSets?: Array<DataSet>,
|
|
1339
|
+
metadata?: Metadata
|
|
1340
|
+
) {
|
|
930
1341
|
if (!locus) {
|
|
931
1342
|
LoggerProxy.logger.error(
|
|
932
|
-
|
|
1343
|
+
`Locus-info:index#onFullLocus (${debugText}) --> object passed as argument was invalid, continuing.`
|
|
933
1344
|
);
|
|
934
1345
|
}
|
|
935
1346
|
|
|
936
1347
|
if (dataSets) {
|
|
1348
|
+
if (!metadata) {
|
|
1349
|
+
throw new Error(
|
|
1350
|
+
`Locus-info:index#onFullLocus (${debugText}) --> hash tree metadata is missing with full Locus`
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
937
1353
|
// this is the new hashmap Locus DTO format (only applicable to webinars for now)
|
|
938
|
-
this.onFullLocusWithHashTrees(locus, eventType, dataSets);
|
|
1354
|
+
this.onFullLocusWithHashTrees(debugText, locus, metadata, eventType, dataSets);
|
|
939
1355
|
} else {
|
|
940
|
-
this.onFullLocusClassic(locus, eventType);
|
|
1356
|
+
this.onFullLocusClassic(debugText, locus, eventType);
|
|
941
1357
|
}
|
|
942
1358
|
}
|
|
943
1359
|
|
|
@@ -952,7 +1368,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
952
1368
|
this.participants = locus.participants;
|
|
953
1369
|
this.participants?.forEach((participant) => {
|
|
954
1370
|
// participant.htMeta is set only for hash tree based locus
|
|
955
|
-
if (participant.htMeta?.elementId.id) {
|
|
1371
|
+
if (typeof participant.htMeta?.elementId.id === 'number') {
|
|
956
1372
|
this.hashTreeObjectId2ParticipantId.set(participant.htMeta.elementId.id, participant.id);
|
|
957
1373
|
}
|
|
958
1374
|
});
|
|
@@ -1015,20 +1431,61 @@ export default class LocusInfo extends EventsScope {
|
|
|
1015
1431
|
}
|
|
1016
1432
|
}
|
|
1017
1433
|
|
|
1434
|
+
/**
|
|
1435
|
+
* Makes sure that passed in locus object has a participant object for self.
|
|
1436
|
+
*
|
|
1437
|
+
* @param {LocusDTO} locus The locus object to check and modify if needed
|
|
1438
|
+
* @returns {void}
|
|
1439
|
+
*/
|
|
1440
|
+
ensureSelfParticipantExists(locus: any) {
|
|
1441
|
+
const {self} = locus;
|
|
1442
|
+
|
|
1443
|
+
// sanity check, this should never fail
|
|
1444
|
+
if (!self?.identity || !Array.isArray(locus.participants)) {
|
|
1445
|
+
LoggerProxy.logger.warn(
|
|
1446
|
+
`Locus-info:index#ensureSelfParticipantExists --> locus object is missing required fields, cannot ensure self participant exists. self?.identity="${self?.identity}"`
|
|
1447
|
+
);
|
|
1448
|
+
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
const selfExists = locus.participants.some(
|
|
1453
|
+
(participant) => participant.identity === self.identity
|
|
1454
|
+
);
|
|
1455
|
+
|
|
1456
|
+
if (!selfExists) {
|
|
1457
|
+
locus.participants.push({...self});
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1018
1461
|
/**
|
|
1019
1462
|
* @param {Object} locus
|
|
1020
1463
|
* @returns {undefined}
|
|
1021
1464
|
* @memberof LocusInfo
|
|
1022
1465
|
*/
|
|
1023
1466
|
onDeltaLocus(locus: any) {
|
|
1024
|
-
const isReplaceMembers =
|
|
1467
|
+
const isReplaceMembers =
|
|
1468
|
+
locus.jsSdkMeta?.forceReplaceMembers !== undefined
|
|
1469
|
+
? locus.jsSdkMeta.forceReplaceMembers
|
|
1470
|
+
: ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
|
|
1471
|
+
|
|
1472
|
+
if (isReplaceMembers) {
|
|
1473
|
+
// when we're moving between breakouts, Locus sometimes doesn't send us
|
|
1474
|
+
// any participants at all for a few seconds
|
|
1475
|
+
// Web app relies on having at least the self participant always there
|
|
1476
|
+
// so we copy self into participants if it's not there.
|
|
1477
|
+
this.ensureSelfParticipantExists(locus);
|
|
1478
|
+
}
|
|
1025
1479
|
this.mergeParticipants(this.participants, locus.participants);
|
|
1026
|
-
this.updateLocusInfo(locus);
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1480
|
+
const updatesApplied = this.updateLocusInfo(locus);
|
|
1481
|
+
|
|
1482
|
+
if (updatesApplied) {
|
|
1483
|
+
this.updateParticipants(
|
|
1484
|
+
locus.participants,
|
|
1485
|
+
locus.jsSdkMeta?.removedParticipantIds,
|
|
1486
|
+
isReplaceMembers
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1032
1489
|
this.isMeetingActive();
|
|
1033
1490
|
}
|
|
1034
1491
|
|
|
@@ -1042,7 +1499,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1042
1499
|
// When moved to a breakout session locus sends a message for the previous locus
|
|
1043
1500
|
// indicating that we have been moved. It isn't helpful to continue parsing this
|
|
1044
1501
|
// as it gets interpreted as if we have left the call
|
|
1045
|
-
return;
|
|
1502
|
+
return false;
|
|
1046
1503
|
}
|
|
1047
1504
|
|
|
1048
1505
|
this.updateControls(locus.controls, locus.self);
|
|
@@ -1062,6 +1519,8 @@ export default class LocusInfo extends EventsScope {
|
|
|
1062
1519
|
this.updateLinks(locus.links);
|
|
1063
1520
|
this.compareAndUpdate();
|
|
1064
1521
|
// update which required to compare different objects from locus
|
|
1522
|
+
|
|
1523
|
+
return true;
|
|
1065
1524
|
}
|
|
1066
1525
|
|
|
1067
1526
|
/**
|
|
@@ -1217,27 +1676,6 @@ export default class LocusInfo extends EventsScope {
|
|
|
1217
1676
|
shouldLeave: false,
|
|
1218
1677
|
}
|
|
1219
1678
|
);
|
|
1220
|
-
} else if (this.fullState && this.fullState.removed) {
|
|
1221
|
-
// user has been dropped from a meeting
|
|
1222
|
-
|
|
1223
|
-
// @ts-ignore
|
|
1224
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
1225
|
-
name: 'client.call.remote-ended',
|
|
1226
|
-
options: {
|
|
1227
|
-
meetingId: this.meetingId,
|
|
1228
|
-
},
|
|
1229
|
-
});
|
|
1230
|
-
this.emitScoped(
|
|
1231
|
-
{
|
|
1232
|
-
file: 'locus-info',
|
|
1233
|
-
function: 'isMeetingActive',
|
|
1234
|
-
},
|
|
1235
|
-
EVENTS.DESTROY_MEETING,
|
|
1236
|
-
{
|
|
1237
|
-
reason: MEETING_REMOVED_REASON.FULLSTATE_REMOVED,
|
|
1238
|
-
shouldLeave: false,
|
|
1239
|
-
}
|
|
1240
|
-
);
|
|
1241
1679
|
}
|
|
1242
1680
|
// If you are guest and you are removed from the meeting
|
|
1243
1681
|
// You wont get any further events
|
|
@@ -1373,6 +1811,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1373
1811
|
hasMeetingContainerChanged,
|
|
1374
1812
|
hasTranscribeChanged,
|
|
1375
1813
|
hasHesiodLLMIdChanged,
|
|
1814
|
+
hasAiSummaryNotificationChanged,
|
|
1376
1815
|
hasTranscribeSpokenLanguageChanged,
|
|
1377
1816
|
hasManualCaptionChanged,
|
|
1378
1817
|
hasEntryExitToneChanged,
|
|
@@ -1529,6 +1968,19 @@ export default class LocusInfo extends EventsScope {
|
|
|
1529
1968
|
);
|
|
1530
1969
|
}
|
|
1531
1970
|
|
|
1971
|
+
if (hasAiSummaryNotificationChanged) {
|
|
1972
|
+
this.emitScoped(
|
|
1973
|
+
{
|
|
1974
|
+
file: 'locus-info',
|
|
1975
|
+
function: 'updateControls',
|
|
1976
|
+
},
|
|
1977
|
+
LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
1978
|
+
{
|
|
1979
|
+
aiSummaryNotification: current.transcribe.aiSummaryNotification,
|
|
1980
|
+
}
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1532
1984
|
if (hasTranscribeSpokenLanguageChanged) {
|
|
1533
1985
|
const {spokenLanguage} = current.transcribe;
|
|
1534
1986
|
|
|
@@ -1561,6 +2013,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1561
2013
|
|
|
1562
2014
|
if (hasBreakoutChanged) {
|
|
1563
2015
|
const {breakout} = current;
|
|
2016
|
+
|
|
1564
2017
|
breakout.breakoutMoveId = SelfUtils.getReplacedBreakoutMoveId(
|
|
1565
2018
|
self,
|
|
1566
2019
|
this.webex.internal.device.url
|
|
@@ -2038,6 +2491,19 @@ export default class LocusInfo extends EventsScope {
|
|
|
2038
2491
|
);
|
|
2039
2492
|
}
|
|
2040
2493
|
|
|
2494
|
+
if (parsedSelves.updates.selfIdChanged) {
|
|
2495
|
+
this.emitScoped(
|
|
2496
|
+
{
|
|
2497
|
+
file: 'locus-info',
|
|
2498
|
+
function: 'updateSelf',
|
|
2499
|
+
},
|
|
2500
|
+
LOCUSINFO.EVENTS.SELF_ID_CHANGED,
|
|
2501
|
+
{
|
|
2502
|
+
selfId: parsedSelves.current.selfId,
|
|
2503
|
+
}
|
|
2504
|
+
);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2041
2507
|
if (parsedSelves.updates.interpretationChanged) {
|
|
2042
2508
|
this.emitScoped(
|
|
2043
2509
|
{
|