@webex/plugin-meetings 3.9.0 → 3.10.0
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/constants.js +8 -0
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +22 -5
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/index.js +7 -0
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/locusRouteToken.js +116 -0
- package/dist/interceptors/locusRouteToken.js.map +1 -0
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +11 -2
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +56 -14
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/parser.js +4 -1
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/media/properties.js +53 -5
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +8 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +339 -185
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +2 -5
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +177 -14
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +39 -11
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +29 -21
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +31 -25
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js +9 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +10 -0
- package/dist/member/util.js.map +1 -1
- package/dist/members/collection.js +13 -0
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +42 -20
- package/dist/members/index.js.map +1 -1
- package/dist/members/util.js +7 -2
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reachability/index.js +3 -3
- package/dist/reachability/index.js.map +1 -1
- package/dist/types/constants.d.ts +7 -0
- package/dist/types/controls-options-manager/index.d.ts +9 -1
- package/dist/types/interceptors/index.d.ts +2 -1
- package/dist/types/interceptors/locusRouteToken.d.ts +38 -0
- package/dist/types/locus-info/index.d.ts +56 -2
- package/dist/types/media/properties.d.ts +21 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
- package/dist/types/meeting/index.d.ts +41 -1
- package/dist/types/meeting/request.d.ts +42 -0
- package/dist/types/meeting/util.d.ts +13 -3
- package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
- package/dist/types/meetings/index.d.ts +3 -1
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +5 -0
- package/dist/types/members/collection.d.ts +6 -0
- package/dist/types/members/index.d.ts +12 -2
- package/dist/types/members/util.d.ts +6 -3
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +23 -23
- package/src/constants.ts +10 -0
- package/src/controls-options-manager/index.ts +26 -5
- package/src/index.ts +2 -1
- package/src/interceptors/index.ts +2 -1
- package/src/interceptors/locusRouteToken.ts +80 -0
- package/src/locus-info/controlsUtils.ts +18 -0
- package/src/locus-info/index.ts +99 -17
- package/src/locus-info/parser.ts +5 -1
- package/src/media/properties.ts +43 -0
- package/src/meeting/in-meeting-actions.ts +16 -0
- package/src/meeting/index.ts +204 -24
- package/src/meeting/muteState.ts +2 -6
- package/src/meeting/request.ts +141 -0
- package/src/meeting/util.ts +50 -20
- package/src/meeting-info/meeting-info-v2.ts +24 -5
- package/src/meetings/index.ts +9 -3
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +14 -0
- package/src/members/collection.ts +11 -0
- package/src/members/index.ts +38 -5
- package/src/members/util.ts +18 -2
- package/src/metrics/constants.ts +1 -0
- package/src/reachability/index.ts +3 -3
- package/test/unit/spec/common/browser-detection.js +0 -24
- package/test/unit/spec/controls-options-manager/index.js +47 -0
- package/test/unit/spec/fixture/locus.js +1 -0
- package/test/unit/spec/interceptors/locusRouteToken.ts +87 -0
- package/test/unit/spec/locus-info/index.js +91 -15
- package/test/unit/spec/locus-info/parser.js +3 -2
- package/test/unit/spec/media/properties.ts +137 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
- package/test/unit/spec/meeting/index.js +398 -30
- package/test/unit/spec/meeting/muteState.js +32 -6
- package/test/unit/spec/meeting/request.js +21 -0
- package/test/unit/spec/meeting/utils.js +49 -17
- package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
- package/test/unit/spec/meetings/index.js +10 -5
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/members/collection.js +120 -0
- package/test/unit/spec/members/index.js +72 -3
- package/test/unit/spec/members/request.js +55 -0
- package/test/unit/spec/members/utils.js +116 -14
- package/test/unit/spec/reachability/index.ts +158 -3
- package/test/unit/spec/roap/turnDiscovery.ts +3 -3
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {registerPlugin} from '@webex/webex-core';
|
|
|
3
3
|
|
|
4
4
|
import Meetings from './meetings';
|
|
5
5
|
import config from './config';
|
|
6
|
-
import {LocusRetryStatusInterceptor} from './interceptors';
|
|
6
|
+
import {LocusRetryStatusInterceptor, LocusRouteTokenInterceptor} from './interceptors';
|
|
7
7
|
import CaptchaError from './common/errors/captcha-error';
|
|
8
8
|
import IntentToJoinError from './common/errors/intent-to-join';
|
|
9
9
|
import PasswordError from './common/errors/password-error';
|
|
@@ -23,6 +23,7 @@ registerPlugin('meetings', Meetings, {
|
|
|
23
23
|
config,
|
|
24
24
|
interceptors: {
|
|
25
25
|
LocusRetryStatusInterceptor: LocusRetryStatusInterceptor.create,
|
|
26
|
+
LocusRouteTokenInterceptor: LocusRouteTokenInterceptor.create,
|
|
26
27
|
},
|
|
27
28
|
});
|
|
28
29
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {Interceptor} from '@webex/http-core';
|
|
6
|
+
import {has} from 'lodash';
|
|
7
|
+
|
|
8
|
+
const LOCUS_ID_REGEX = /\/locus\/api\/v1\/loci\/([a-f0-9-]{36})/i;
|
|
9
|
+
const X_CISCO_PART_ROUTE_TOKEN = 'X-Cisco-Part-Route-Token';
|
|
10
|
+
const ROUTE_TOKEN = {};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @class LocusRouteTokenInterceptor
|
|
14
|
+
*/
|
|
15
|
+
export default class LocusRouteTokenInterceptor extends Interceptor {
|
|
16
|
+
/**
|
|
17
|
+
* @returns {LocusRouteTokenInterceptor}
|
|
18
|
+
*/
|
|
19
|
+
static create() {
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
return new LocusRouteTokenInterceptor({webex: this});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getLocusIdByRequestUrl(url: string) {
|
|
25
|
+
return url?.match(LOCUS_ID_REGEX)?.[1];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {Object} options
|
|
30
|
+
* @param {HttpResponse} response
|
|
31
|
+
* @returns {Promise<HttpResponse>}
|
|
32
|
+
*/
|
|
33
|
+
onResponse(options, response) {
|
|
34
|
+
const locusId = this.getLocusIdByRequestUrl(options.uri);
|
|
35
|
+
if (locusId) {
|
|
36
|
+
const hasRouteToken = has(response.headers, X_CISCO_PART_ROUTE_TOKEN);
|
|
37
|
+
const token = response.headers[X_CISCO_PART_ROUTE_TOKEN];
|
|
38
|
+
if (hasRouteToken) {
|
|
39
|
+
this.updateToken(locusId, token);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return Promise.resolve(response);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {Object} options
|
|
48
|
+
* @returns {Promise<Object>} options
|
|
49
|
+
*/
|
|
50
|
+
onRequest(options) {
|
|
51
|
+
const locusId = this.getLocusIdByRequestUrl(options.uri);
|
|
52
|
+
if (locusId) {
|
|
53
|
+
const token = this.getToken(locusId);
|
|
54
|
+
if (token) {
|
|
55
|
+
options.headers[X_CISCO_PART_ROUTE_TOKEN] = token;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Promise.resolve(options);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Update the meeting route token
|
|
64
|
+
* @param {string} locusId
|
|
65
|
+
* @param {string} token
|
|
66
|
+
* @returns {void}
|
|
67
|
+
*/
|
|
68
|
+
updateToken(locusId, token) {
|
|
69
|
+
ROUTE_TOKEN[locusId] = token;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the meeting route token
|
|
74
|
+
* @param {string} locusId
|
|
75
|
+
* @returns {string|undefined}
|
|
76
|
+
*/
|
|
77
|
+
getToken(locusId) {
|
|
78
|
+
return ROUTE_TOKEN[locusId];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -130,6 +130,15 @@ ControlsUtils.parse = (controls: any) => {
|
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
if (controls?.autoEndMeetingWarning) {
|
|
134
|
+
parsedControls.autoEndMeetingWarning = {
|
|
135
|
+
enabled: controls.autoEndMeetingWarning.enabled,
|
|
136
|
+
extensionDurationMinutes: controls.autoEndMeetingWarning.extensionDurationMinutes,
|
|
137
|
+
countdownDurationMinutes: controls.autoEndMeetingWarning.countdownDurationMinutes,
|
|
138
|
+
countdownStartedAt: controls.autoEndMeetingWarning.countdownStartedAt,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
133
142
|
return parsedControls;
|
|
134
143
|
};
|
|
135
144
|
|
|
@@ -244,6 +253,15 @@ ControlsUtils.getControls = (oldControls: any, newControls: any) => {
|
|
|
244
253
|
|
|
245
254
|
hasPollingQAControlChanged:
|
|
246
255
|
current?.pollingQAControl?.enabled !== previous?.pollingQAControl?.enabled,
|
|
256
|
+
|
|
257
|
+
hasAutoEndMeetingChanged:
|
|
258
|
+
current?.autoEndMeetingWarning?.enabled !== previous?.autoEndMeetingWarning?.enabled ||
|
|
259
|
+
current?.autoEndMeetingWarning?.extensionDurationMinutes !==
|
|
260
|
+
previous?.autoEndMeetingWarning?.extensionDurationMinutes ||
|
|
261
|
+
current?.autoEndMeetingWarning?.countdownDurationMinutes !==
|
|
262
|
+
previous?.autoEndMeetingWarning?.countdownDurationMinutes ||
|
|
263
|
+
current?.autoEndMeetingWarning?.countdownStartedAt !==
|
|
264
|
+
previous?.autoEndMeetingWarning?.countdownStartedAt,
|
|
247
265
|
},
|
|
248
266
|
};
|
|
249
267
|
};
|
package/src/locus-info/index.ts
CHANGED
|
@@ -31,6 +31,51 @@ import LocusDeltaParser from './parser';
|
|
|
31
31
|
import Metrics from '../metrics';
|
|
32
32
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
33
33
|
|
|
34
|
+
export type LocusDTO = {
|
|
35
|
+
controls?: any;
|
|
36
|
+
fullState?: {
|
|
37
|
+
active: boolean;
|
|
38
|
+
count: number;
|
|
39
|
+
lastActive: string;
|
|
40
|
+
locked: boolean;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
seessionIds: string[];
|
|
43
|
+
startTime: number;
|
|
44
|
+
state: string;
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
host?: {
|
|
48
|
+
id: string;
|
|
49
|
+
incomingCallProtocols: any[];
|
|
50
|
+
isExternal: boolean;
|
|
51
|
+
name: string;
|
|
52
|
+
orgId: string;
|
|
53
|
+
};
|
|
54
|
+
info?: any;
|
|
55
|
+
links?: any;
|
|
56
|
+
mediaShares?: any[];
|
|
57
|
+
meetings?: any[];
|
|
58
|
+
participants: any[];
|
|
59
|
+
replaces?: any[];
|
|
60
|
+
self?: any;
|
|
61
|
+
sequence?: {
|
|
62
|
+
dirtyParticipants: number;
|
|
63
|
+
entries: number[];
|
|
64
|
+
rangeEnd: number;
|
|
65
|
+
rangeStart: number;
|
|
66
|
+
sequenceHash: number;
|
|
67
|
+
sessionToken: string;
|
|
68
|
+
since: string;
|
|
69
|
+
totalParticipants: number;
|
|
70
|
+
};
|
|
71
|
+
syncUrl?: string;
|
|
72
|
+
url?: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type LocusApiResponseBody = {
|
|
76
|
+
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)
|
|
77
|
+
};
|
|
78
|
+
|
|
34
79
|
/**
|
|
35
80
|
* @description LocusInfo extends ChildEmitter to convert locusInfo info a private emitter to parent object
|
|
36
81
|
* @export
|
|
@@ -93,19 +138,26 @@ export default class LocusInfo extends EventsScope {
|
|
|
93
138
|
* Does a Locus sync. It tries to get the latest delta DTO or if it can't, it falls back to getting the full Locus DTO.
|
|
94
139
|
*
|
|
95
140
|
* @param {Meeting} meeting
|
|
141
|
+
* @param {boolean} isLocusUrlChanged
|
|
142
|
+
* @param {Locus} locus
|
|
96
143
|
* @returns {undefined}
|
|
97
144
|
*/
|
|
98
|
-
private doLocusSync(meeting: any) {
|
|
99
|
-
let isDelta;
|
|
145
|
+
private doLocusSync(meeting: any, isLocusUrlChanged: boolean, locus: any) {
|
|
100
146
|
let url;
|
|
147
|
+
let isDelta = false;
|
|
101
148
|
let meetingDestroyed = false;
|
|
102
149
|
|
|
103
|
-
if (
|
|
150
|
+
if (isLocusUrlChanged) {
|
|
151
|
+
// for the locus url changed case from breakout to main session, we should always do a full sync, in this case, the url from locus is always on main session,
|
|
152
|
+
// so use the main session locus url to get the full locus(full participants list in the response).
|
|
153
|
+
// for the locus url changed case from main session to breakout, we don't need to care about it here,
|
|
154
|
+
// because it is a USE_INCOMING case, it will not be executed here.
|
|
155
|
+
url = locus.url;
|
|
156
|
+
} else if (this.locusParser.workingCopy?.syncUrl) {
|
|
104
157
|
url = this.locusParser.workingCopy.syncUrl;
|
|
105
158
|
isDelta = true;
|
|
106
159
|
} else {
|
|
107
160
|
url = meeting.locusUrl;
|
|
108
|
-
isDelta = false;
|
|
109
161
|
}
|
|
110
162
|
|
|
111
163
|
LoggerProxy.logger.info(
|
|
@@ -217,6 +269,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
217
269
|
*/
|
|
218
270
|
applyLocusDeltaData(action: string, locus: any, meeting: any) {
|
|
219
271
|
const {DESYNC, USE_CURRENT, USE_INCOMING, WAIT, LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
|
|
272
|
+
const isLocusUrlChanged = action === LOCUS_URL_CHANGED;
|
|
220
273
|
|
|
221
274
|
switch (action) {
|
|
222
275
|
case USE_INCOMING:
|
|
@@ -228,7 +281,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
228
281
|
break;
|
|
229
282
|
case DESYNC:
|
|
230
283
|
case LOCUS_URL_CHANGED:
|
|
231
|
-
this.doLocusSync(meeting);
|
|
284
|
+
this.doLocusSync(meeting, isLocusUrlChanged, locus);
|
|
232
285
|
break;
|
|
233
286
|
default:
|
|
234
287
|
LoggerProxy.logger.info(
|
|
@@ -286,11 +339,11 @@ export default class LocusInfo extends EventsScope {
|
|
|
286
339
|
this.updateLocusCache(locus);
|
|
287
340
|
// above section only updates the locusInfo object
|
|
288
341
|
// The below section makes sure it updates the locusInfo as well as updates the meeting object
|
|
289
|
-
this.updateParticipants(locus.participants);
|
|
342
|
+
this.updateParticipants(locus.participants, []);
|
|
290
343
|
// For 1:1 space meeting the conversation Url does not exist in locus.conversation
|
|
291
344
|
this.updateConversationUrl(locus.conversationUrl, locus.info);
|
|
292
345
|
this.updateControls(locus.controls, locus.self);
|
|
293
|
-
this.updateLocusUrl(locus.url);
|
|
346
|
+
this.updateLocusUrl(locus.url, ControlsUtils.isMainSessionDTO(locus));
|
|
294
347
|
this.updateFullState(locus.fullState);
|
|
295
348
|
this.updateMeetingInfo(locus.info);
|
|
296
349
|
this.updateEmbeddedApps(locus.embeddedApps);
|
|
@@ -315,6 +368,16 @@ export default class LocusInfo extends EventsScope {
|
|
|
315
368
|
this.emitChange = true;
|
|
316
369
|
}
|
|
317
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Handles HTTP response from Locus API call.
|
|
373
|
+
* @param {Meeting} meeting meeting object
|
|
374
|
+
* @param {LocusApiResponseBody} responseBody body of the http response from Locus API call
|
|
375
|
+
* @returns {void}
|
|
376
|
+
*/
|
|
377
|
+
handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
|
|
378
|
+
this.handleLocusDelta(responseBody.locus, meeting);
|
|
379
|
+
}
|
|
380
|
+
|
|
318
381
|
/**
|
|
319
382
|
* @param {Meeting} meeting
|
|
320
383
|
* @param {Object} data
|
|
@@ -327,6 +390,8 @@ export default class LocusInfo extends EventsScope {
|
|
|
327
390
|
const locus = this.getTheLocusToUpdate(data.locus);
|
|
328
391
|
LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
|
|
329
392
|
|
|
393
|
+
locus.jsSdkMeta = {removedParticipantIds: []};
|
|
394
|
+
|
|
330
395
|
switch (eventType) {
|
|
331
396
|
case LOCUSEVENT.PARTICIPANT_JOIN:
|
|
332
397
|
case LOCUSEVENT.PARTICIPANT_LEFT:
|
|
@@ -392,7 +457,11 @@ export default class LocusInfo extends EventsScope {
|
|
|
392
457
|
this.participants = locus.participants;
|
|
393
458
|
const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
|
|
394
459
|
this.updateLocusInfo(locus);
|
|
395
|
-
this.updateParticipants(
|
|
460
|
+
this.updateParticipants(
|
|
461
|
+
locus.participants,
|
|
462
|
+
locus.jsSdkMeta?.removedParticipantIds,
|
|
463
|
+
isReplaceMembers
|
|
464
|
+
);
|
|
396
465
|
this.isMeetingActive();
|
|
397
466
|
this.handleOneOnOneEvent(eventType);
|
|
398
467
|
this.updateEmbeddedApps(locus.embeddedApps);
|
|
@@ -454,7 +523,11 @@ export default class LocusInfo extends EventsScope {
|
|
|
454
523
|
const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
|
|
455
524
|
this.mergeParticipants(this.participants, locus.participants);
|
|
456
525
|
this.updateLocusInfo(locus);
|
|
457
|
-
this.updateParticipants(
|
|
526
|
+
this.updateParticipants(
|
|
527
|
+
locus.participants,
|
|
528
|
+
locus.jsSdkMeta?.removedParticipantIds,
|
|
529
|
+
isReplaceMembers
|
|
530
|
+
);
|
|
458
531
|
this.isMeetingActive();
|
|
459
532
|
}
|
|
460
533
|
|
|
@@ -476,7 +549,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
476
549
|
this.updateCreated(locus.created);
|
|
477
550
|
this.updateFullState(locus.fullState);
|
|
478
551
|
this.updateHostInfo(locus.host);
|
|
479
|
-
this.updateLocusUrl(locus.url);
|
|
552
|
+
this.updateLocusUrl(locus.url, ControlsUtils.isMainSessionDTO(locus));
|
|
480
553
|
this.updateMeetingInfo(locus.info, locus.self);
|
|
481
554
|
this.updateMediaShares(locus.mediaShares);
|
|
482
555
|
this.updateParticipantsUrl(locus.participantsUrl);
|
|
@@ -745,11 +818,12 @@ export default class LocusInfo extends EventsScope {
|
|
|
745
818
|
/**
|
|
746
819
|
* update meeting's members
|
|
747
820
|
* @param {Object} participants new participants object
|
|
821
|
+
* @param {Array} removedParticipantIds list of removed participants
|
|
748
822
|
* @param {Boolean} isReplace is replace the whole members
|
|
749
823
|
* @returns {Array} updatedParticipants
|
|
750
824
|
* @memberof LocusInfo
|
|
751
825
|
*/
|
|
752
|
-
updateParticipants(participants: object, isReplace?: boolean) {
|
|
826
|
+
updateParticipants(participants: object, removedParticipantIds?: string[], isReplace?: boolean) {
|
|
753
827
|
this.emitScoped(
|
|
754
828
|
{
|
|
755
829
|
file: 'locus-info',
|
|
@@ -758,6 +832,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
758
832
|
EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS,
|
|
759
833
|
{
|
|
760
834
|
participants,
|
|
835
|
+
removedParticipantIds,
|
|
761
836
|
recordingId: this.parsedLocus.controls && this.parsedLocus.controls.record?.modifiedBy,
|
|
762
837
|
selfIdentity: this.parsedLocus.self && this.parsedLocus.self.selfIdentity,
|
|
763
838
|
selfId: this.parsedLocus.self && this.parsedLocus.self.selfId,
|
|
@@ -820,6 +895,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
820
895
|
hasAnnotationControlChanged,
|
|
821
896
|
hasRemoteDesktopControlChanged,
|
|
822
897
|
hasPollingQAControlChanged,
|
|
898
|
+
hasAutoEndMeetingChanged,
|
|
823
899
|
},
|
|
824
900
|
current,
|
|
825
901
|
} = ControlsUtils.getControls(this.controls, controls);
|
|
@@ -1094,6 +1170,14 @@ export default class LocusInfo extends EventsScope {
|
|
|
1094
1170
|
);
|
|
1095
1171
|
}
|
|
1096
1172
|
|
|
1173
|
+
if (hasAutoEndMeetingChanged) {
|
|
1174
|
+
this.emitScoped(
|
|
1175
|
+
{file: 'locus-info', function: 'updateControls'},
|
|
1176
|
+
LOCUSINFO.EVENTS.CONTROLS_AUTO_END_MEETING_WARNING_CHANGED,
|
|
1177
|
+
{state: current.autoEndMeetingWarning}
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1097
1181
|
this.controls = controls;
|
|
1098
1182
|
}
|
|
1099
1183
|
}
|
|
@@ -1254,10 +1338,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1254
1338
|
*/
|
|
1255
1339
|
updateMeetingInfo(info: object, self?: object) {
|
|
1256
1340
|
const roles = self ? SelfUtils.getRoles(self) : this.parsedLocus.self?.roles || [];
|
|
1257
|
-
if (
|
|
1258
|
-
(info && !isEqual(this.info, info)) ||
|
|
1259
|
-
(roles.length && !isEqual(this.roles, roles) && info)
|
|
1260
|
-
) {
|
|
1341
|
+
if ((info && !isEqual(this.info, info)) || (!isEqual(this.roles, roles) && info)) {
|
|
1261
1342
|
const isJoined = SelfUtils.isJoined(self || this.parsedLocus.self);
|
|
1262
1343
|
const parsedInfo = InfoUtils.getInfos(this.parsedLocus.info, info, roles, isJoined);
|
|
1263
1344
|
|
|
@@ -1660,10 +1741,11 @@ export default class LocusInfo extends EventsScope {
|
|
|
1660
1741
|
/**
|
|
1661
1742
|
* handles when the locus.url is updated
|
|
1662
1743
|
* @param {String} url
|
|
1744
|
+
* @param {Boolean} isMainLocus
|
|
1663
1745
|
* @returns {undefined}
|
|
1664
1746
|
* emits internal event locus_info_update_url
|
|
1665
1747
|
*/
|
|
1666
|
-
updateLocusUrl(url: string) {
|
|
1748
|
+
updateLocusUrl(url: string, isMainLocus = true) {
|
|
1667
1749
|
if (url && this.url !== url) {
|
|
1668
1750
|
this.url = url;
|
|
1669
1751
|
this.updateMeeting({locusUrl: url});
|
|
@@ -1673,7 +1755,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1673
1755
|
function: 'updateLocusUrl',
|
|
1674
1756
|
},
|
|
1675
1757
|
EVENTS.LOCUS_INFO_UPDATE_URL,
|
|
1676
|
-
url
|
|
1758
|
+
{url, isMainLocus}
|
|
1677
1759
|
);
|
|
1678
1760
|
}
|
|
1679
1761
|
}
|
package/src/locus-info/parser.ts
CHANGED
|
@@ -728,13 +728,17 @@ export default class Parser {
|
|
|
728
728
|
break;
|
|
729
729
|
|
|
730
730
|
case USE_INCOMING:
|
|
731
|
-
case LOCUS_URL_CHANGED:
|
|
732
731
|
// update working copy for future comparisons.
|
|
733
732
|
// Note: The working copy of parser gets updated in .onFullLocus()
|
|
734
733
|
// and here when USE_INCOMING or LOCUS_URL_CHANGED locus.
|
|
735
734
|
this.workingCopy = newLoci;
|
|
736
735
|
break;
|
|
737
736
|
|
|
737
|
+
case LOCUS_URL_CHANGED:
|
|
738
|
+
// clear the working copy completely, do a full locus sync
|
|
739
|
+
this.workingCopy = null;
|
|
740
|
+
break;
|
|
741
|
+
|
|
738
742
|
case WAIT:
|
|
739
743
|
// we've taken newLoci from the front of the queue, so put it back there as we have to wait
|
|
740
744
|
// for the one that should be in front of it, before we can process it
|
package/src/media/properties.ts
CHANGED
|
@@ -9,9 +9,12 @@ import {
|
|
|
9
9
|
|
|
10
10
|
import {parse} from '@webex/ts-sdp';
|
|
11
11
|
import {ClientEvent} from '@webex/internal-plugin-metrics';
|
|
12
|
+
import {throttle} from 'lodash';
|
|
13
|
+
import Metrics from '../metrics';
|
|
12
14
|
import {MEETINGS, QUALITY_LEVELS} from '../constants';
|
|
13
15
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
14
16
|
import MediaConnectionAwaiter from './MediaConnectionAwaiter';
|
|
17
|
+
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
15
18
|
|
|
16
19
|
export type MediaDirection = {
|
|
17
20
|
sendAudio: boolean;
|
|
@@ -41,6 +44,8 @@ export default class MediaProperties {
|
|
|
41
44
|
videoDeviceId: any;
|
|
42
45
|
videoStream?: LocalCameraStream;
|
|
43
46
|
namespace = MEETINGS;
|
|
47
|
+
mediaIssueCounters: {[key: string]: number} = {};
|
|
48
|
+
throttledSendMediaIssueMetric: ReturnType<typeof throttle>;
|
|
44
49
|
|
|
45
50
|
/**
|
|
46
51
|
* @param {Object} [options] -- to auto construct
|
|
@@ -66,6 +71,15 @@ export default class MediaProperties {
|
|
|
66
71
|
this.remoteQualityLevel = QUALITY_LEVELS.HIGH;
|
|
67
72
|
this.mediaSettings = {};
|
|
68
73
|
this.videoDeviceId = null;
|
|
74
|
+
|
|
75
|
+
this.throttledSendMediaIssueMetric = throttle((eventPayload) => {
|
|
76
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED, {
|
|
77
|
+
...eventPayload,
|
|
78
|
+
});
|
|
79
|
+
Object.keys(this.mediaIssueCounters).forEach((key) => {
|
|
80
|
+
this.mediaIssueCounters[key] = 0;
|
|
81
|
+
});
|
|
82
|
+
}, 1000 * 60 * 5); // at most once every 5 minutes
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
/**
|
|
@@ -139,8 +153,14 @@ export default class MediaProperties {
|
|
|
139
153
|
this.videoDeviceId = deviceId;
|
|
140
154
|
}
|
|
141
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Clears the webrtcMediaConnection. This method should be called after
|
|
158
|
+
* peer connection is closed and no longer needed.
|
|
159
|
+
* @returns {void}
|
|
160
|
+
*/
|
|
142
161
|
unsetPeerConnection() {
|
|
143
162
|
this.webrtcMediaConnection = null;
|
|
163
|
+
this.throttledSendMediaIssueMetric.flush();
|
|
144
164
|
}
|
|
145
165
|
|
|
146
166
|
/**
|
|
@@ -424,4 +444,27 @@ export default class MediaProperties {
|
|
|
424
444
|
};
|
|
425
445
|
}
|
|
426
446
|
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Sends a metric about a media issue. Metrics are throttled so that we don't
|
|
450
|
+
* send too many of them, but include a count so that we know how many issues
|
|
451
|
+
* were detected.
|
|
452
|
+
*
|
|
453
|
+
* @param {string} issueType
|
|
454
|
+
* @param {string} issueSubType
|
|
455
|
+
* @param {string} correlationId
|
|
456
|
+
* @returns {void}
|
|
457
|
+
*/
|
|
458
|
+
public sendMediaIssueMetric(issueType: string, issueSubType: string, correlationId) {
|
|
459
|
+
const key = `${issueType}_${issueSubType}`;
|
|
460
|
+
|
|
461
|
+
const count = (this.mediaIssueCounters[key] || 0) + 1;
|
|
462
|
+
|
|
463
|
+
this.mediaIssueCounters[key] = count;
|
|
464
|
+
|
|
465
|
+
this.throttledSendMediaIssueMetric({
|
|
466
|
+
correlationId,
|
|
467
|
+
...this.mediaIssueCounters,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
427
470
|
}
|
|
@@ -41,9 +41,12 @@ interface IInMeetingActions {
|
|
|
41
41
|
isLocalRecordingStarted?: boolean;
|
|
42
42
|
isLocalRecordingStopped?: boolean;
|
|
43
43
|
isLocalRecordingPaused?: boolean;
|
|
44
|
+
isLocalStreamingStarted?: boolean;
|
|
45
|
+
isLocalStreamingStopped?: boolean;
|
|
44
46
|
|
|
45
47
|
isManualCaptionActive?: boolean;
|
|
46
48
|
isSaveTranscriptsEnabled?: boolean;
|
|
49
|
+
isSpokenLanguageAutoDetectionEnabled?: boolean;
|
|
47
50
|
isWebexAssistantActive?: boolean;
|
|
48
51
|
canViewCaptionPanel?: boolean;
|
|
49
52
|
isRealTimeTranslationEnabled?: boolean;
|
|
@@ -91,6 +94,7 @@ interface IInMeetingActions {
|
|
|
91
94
|
canDoVideo?: boolean;
|
|
92
95
|
canAnnotate?: boolean;
|
|
93
96
|
canUseVoip?: boolean;
|
|
97
|
+
showAutoEndMeetingWarning?: boolean;
|
|
94
98
|
supportHQV?: boolean;
|
|
95
99
|
supportHDV?: boolean;
|
|
96
100
|
canShareWhiteBoard?: boolean;
|
|
@@ -185,8 +189,14 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
|
185
189
|
|
|
186
190
|
isManualCaptionActive = null;
|
|
187
191
|
|
|
192
|
+
isLocalStreamingStarted = null;
|
|
193
|
+
|
|
194
|
+
isLocalStreamingStopped = null;
|
|
195
|
+
|
|
188
196
|
isSaveTranscriptsEnabled = null;
|
|
189
197
|
|
|
198
|
+
isSpokenLanguageAutoDetectionEnabled = null;
|
|
199
|
+
|
|
190
200
|
isWebexAssistantActive = null;
|
|
191
201
|
|
|
192
202
|
canViewCaptionPanel = null;
|
|
@@ -281,6 +291,8 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
|
281
291
|
|
|
282
292
|
canUseVoip = null;
|
|
283
293
|
|
|
294
|
+
showAutoEndMeetingWarning = null;
|
|
295
|
+
|
|
284
296
|
supportHQV = null;
|
|
285
297
|
|
|
286
298
|
enforceVirtualBackground = null;
|
|
@@ -360,9 +372,12 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
|
360
372
|
isLocalRecordingStarted: this.isLocalRecordingStarted,
|
|
361
373
|
isLocalRecordingStopped: this.isLocalRecordingStopped,
|
|
362
374
|
isLocalRecordingPaused: this.isLocalRecordingPaused,
|
|
375
|
+
isLocalStreamingStarted: this.isLocalStreamingStarted,
|
|
376
|
+
isLocalStreamingStopped: this.isLocalStreamingStopped,
|
|
363
377
|
canStopManualCaption: this.canStopManualCaption,
|
|
364
378
|
isManualCaptionActive: this.isManualCaptionActive,
|
|
365
379
|
isSaveTranscriptsEnabled: this.isSaveTranscriptsEnabled,
|
|
380
|
+
isSpokenLanguageAutoDetectionEnabled: this.isSpokenLanguageAutoDetectionEnabled,
|
|
366
381
|
isWebexAssistantActive: this.isWebexAssistantActive,
|
|
367
382
|
canViewCaptionPanel: this.canViewCaptionPanel,
|
|
368
383
|
isRealTimeTranslationEnabled: this.isRealTimeTranslationEnabled,
|
|
@@ -401,6 +416,7 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
|
401
416
|
canShareFile: this.canShareFile,
|
|
402
417
|
canShareApplication: this.canShareApplication,
|
|
403
418
|
canShareCamera: this.canShareCamera,
|
|
419
|
+
showAutoEndMeetingWarning: this.showAutoEndMeetingWarning,
|
|
404
420
|
canShareDesktop: this.canShareDesktop,
|
|
405
421
|
canShareContent: this.canShareContent,
|
|
406
422
|
canTransferFile: this.canTransferFile,
|