@webex/plugin-meetings 3.8.0-next.7 → 3.8.0-next.70
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/common/errors/webex-errors.js +12 -2
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +17 -121
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/enums.js +2 -0
- package/dist/controls-options-manager/enums.js.map +1 -1
- package/dist/controls-options-manager/types.js.map +1 -1
- package/dist/controls-options-manager/util.js +52 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +28 -10
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +20 -1
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +405 -418
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/index.js +14 -16
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +94 -6
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/brbState.js +6 -0
- package/dist/meeting/brbState.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +17 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +541 -302
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +0 -17
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +0 -2
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +30 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +13 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +359 -60
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +114 -1
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +14 -0
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +330 -353
- package/dist/member/util.js.map +1 -1
- package/dist/members/index.js +23 -0
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +21 -0
- package/dist/members/request.js.map +1 -1
- package/dist/members/util.js +15 -0
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +9 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reachability/clusterReachability.js +63 -27
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +112 -47
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/reachability.types.js +14 -0
- package/dist/reachability/reachability.types.js.map +1 -1
- package/dist/reachability/request.js +19 -3
- package/dist/reachability/request.js.map +1 -1
- package/dist/reconnection-manager/index.js +2 -2
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/recording-controller/util.js +5 -5
- package/dist/recording-controller/util.js.map +1 -1
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/turnDiscovery.js +45 -27
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/roap/types.js +17 -0
- package/dist/roap/types.js.map +1 -0
- package/dist/types/common/errors/webex-errors.d.ts +7 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/constants.d.ts +12 -85
- package/dist/types/controls-options-manager/enums.d.ts +3 -1
- package/dist/types/controls-options-manager/types.d.ts +7 -1
- package/dist/types/locus-info/index.d.ts +1 -0
- package/dist/types/locus-info/selfUtils.d.ts +247 -1
- package/dist/types/media/properties.d.ts +15 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +16 -0
- package/dist/types/meeting/index.d.ts +32 -1
- package/dist/types/meeting/muteState.d.ts +0 -1
- package/dist/types/meeting/request.d.ts +12 -1
- package/dist/types/meeting/request.type.d.ts +6 -0
- package/dist/types/meeting/util.d.ts +3 -1
- package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
- package/dist/types/meetings/index.d.ts +48 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +159 -1
- package/dist/types/members/index.d.ts +8 -0
- package/dist/types/members/request.d.ts +19 -0
- package/dist/types/members/util.d.ts +13 -0
- package/dist/types/metrics/constants.d.ts +9 -0
- package/dist/types/reachability/clusterReachability.d.ts +15 -7
- package/dist/types/reachability/index.d.ts +10 -1
- package/dist/types/reachability/reachability.types.d.ts +5 -0
- package/dist/types/roap/index.d.ts +3 -2
- package/dist/types/roap/turnDiscovery.d.ts +5 -17
- package/dist/types/roap/types.d.ts +16 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +24 -23
- package/src/common/errors/webex-errors.ts +8 -1
- package/src/config.ts +2 -0
- package/src/constants.ts +19 -90
- package/src/controls-options-manager/enums.ts +2 -0
- package/src/controls-options-manager/types.ts +11 -1
- package/src/controls-options-manager/util.ts +62 -0
- package/src/locus-info/controlsUtils.ts +44 -14
- package/src/locus-info/index.ts +23 -1
- package/src/locus-info/selfUtils.ts +451 -447
- package/src/media/index.ts +20 -21
- package/src/media/properties.ts +96 -0
- package/src/meeting/brbState.ts +7 -0
- package/src/meeting/in-meeting-actions.ts +32 -0
- package/src/meeting/index.ts +346 -93
- package/src/meeting/locusMediaRequest.ts +0 -18
- package/src/meeting/muteState.ts +0 -2
- package/src/meeting/request.ts +36 -1
- package/src/meeting/request.type.ts +7 -0
- package/src/meeting/util.ts +11 -2
- package/src/meeting-info/meeting-info-v2.ts +247 -6
- package/src/meetings/index.ts +128 -1
- package/src/meetings/util.ts +18 -0
- package/src/member/index.ts +13 -2
- package/src/member/util.ts +351 -348
- package/src/members/index.ts +25 -0
- package/src/members/request.ts +26 -0
- package/src/members/util.ts +16 -0
- package/src/metrics/constants.ts +9 -0
- package/src/reachability/clusterReachability.ts +73 -26
- package/src/reachability/index.ts +70 -1
- package/src/reachability/reachability.types.ts +6 -0
- package/src/reachability/request.ts +7 -0
- package/src/reconnection-manager/index.ts +2 -2
- package/src/recording-controller/util.ts +17 -13
- package/src/roap/index.ts +3 -7
- package/src/roap/turnDiscovery.ts +34 -39
- package/src/roap/types.ts +23 -0
- package/test/unit/spec/controls-options-manager/util.js +120 -0
- package/test/unit/spec/locus-info/controlsUtils.js +103 -9
- package/test/unit/spec/locus-info/index.js +28 -0
- package/test/unit/spec/media/index.ts +98 -16
- package/test/unit/spec/media/properties.ts +130 -0
- package/test/unit/spec/meeting/brbState.ts +19 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
- package/test/unit/spec/meeting/index.js +524 -35
- package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
- package/test/unit/spec/meeting/muteState.js +0 -2
- package/test/unit/spec/meeting/request.js +32 -1
- package/test/unit/spec/meeting/utils.js +119 -18
- package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
- package/test/unit/spec/meetings/index.js +133 -2
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/members/index.js +103 -26
- package/test/unit/spec/members/request.js +45 -22
- package/test/unit/spec/members/utils.js +33 -0
- package/test/unit/spec/reachability/clusterReachability.ts +88 -56
- package/test/unit/spec/reachability/index.ts +101 -0
- package/test/unit/spec/reachability/request.js +47 -2
- package/test/unit/spec/reconnection-manager/index.js +4 -4
- package/test/unit/spec/roap/turnDiscovery.ts +110 -28
package/src/members/index.ts
CHANGED
@@ -886,6 +886,31 @@ export default class Members extends StatelessWebexPlugin {
|
|
886
886
|
});
|
887
887
|
}
|
888
888
|
|
889
|
+
/**
|
890
|
+
* Moves a meeting member into the lobby.
|
891
|
+
* @param {String} memberId -- The ID of the member to move.
|
892
|
+
* @returns {Promise} -- Resolves with the lobby‐move response.
|
893
|
+
* @public
|
894
|
+
* @memberof Members
|
895
|
+
*/
|
896
|
+
public moveToLobby(memberId: string) {
|
897
|
+
if (!this.locusUrl) {
|
898
|
+
return Promise.reject(
|
899
|
+
new ParameterError(
|
900
|
+
'The associated locus url for this meetings members object must be defined.'
|
901
|
+
)
|
902
|
+
);
|
903
|
+
}
|
904
|
+
if (!memberId) {
|
905
|
+
return Promise.reject(
|
906
|
+
new ParameterError('The member id must be defined to move the member to lobby.')
|
907
|
+
);
|
908
|
+
}
|
909
|
+
const body = MembersUtil.getMoveMemberToLobbyRequestBody(memberId);
|
910
|
+
|
911
|
+
return this.membersRequest.moveToLobbyMember({locusUrl: this.locusUrl, memberId}, body);
|
912
|
+
}
|
913
|
+
|
889
914
|
/**
|
890
915
|
* Raise or lower the hand of a member in a meeting
|
891
916
|
* @param {String} memberId
|
package/src/members/request.ts
CHANGED
@@ -129,6 +129,32 @@ export default class MembersRequest extends StatelessWebexPlugin {
|
|
129
129
|
return this.locusDeltaRequest(requestParams);
|
130
130
|
}
|
131
131
|
|
132
|
+
/**
|
133
|
+
* Sends a request to move a meeting member into the lobby.
|
134
|
+
* *
|
135
|
+
* @param {Object} options - Request options.
|
136
|
+
* @param {string} options.locusUrl - The locus URL for the meeting.
|
137
|
+
* @param {string} options.memberId - The ID of the member to move.
|
138
|
+
* @param {Object} body - The request payload.
|
139
|
+
* @param {Object} body.moveToLobby - Container for move‐to‐lobby data.
|
140
|
+
* @param {string[]} body.moveToLobby.participantIds - Array of participant IDs to move.
|
141
|
+
* @returns {Promise} - Resolves with the locus‐delta response.
|
142
|
+
*/
|
143
|
+
moveToLobbyMember(
|
144
|
+
options: {locusUrl: string; memberId: string},
|
145
|
+
body: {moveToLobby: {participantIds: string[]}}
|
146
|
+
) {
|
147
|
+
if (!options || !options.locusUrl || !options.memberId) {
|
148
|
+
throw new ParameterError(
|
149
|
+
'memberId must be defined, and the associated locus url for this meeting object must be defined.'
|
150
|
+
);
|
151
|
+
}
|
152
|
+
|
153
|
+
const requestParams = MembersUtil.getMoveMemberToLobbyRequestParams(options, body);
|
154
|
+
|
155
|
+
return this.locusDeltaRequest(requestParams);
|
156
|
+
}
|
157
|
+
|
132
158
|
/**
|
133
159
|
* Sends a request to raise or lower a member's hand
|
134
160
|
* @param {Object} options
|
package/src/members/util.ts
CHANGED
@@ -203,6 +203,22 @@ const MembersUtil = {
|
|
203
203
|
};
|
204
204
|
},
|
205
205
|
|
206
|
+
getMoveMemberToLobbyRequestBody: (memberId: string) => ({
|
207
|
+
moveToLobby: {
|
208
|
+
participantIds: [memberId],
|
209
|
+
},
|
210
|
+
}),
|
211
|
+
|
212
|
+
getMoveMemberToLobbyRequestParams: (options: {memberId: string; locusUrl: string}, body) => {
|
213
|
+
const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;
|
214
|
+
|
215
|
+
return {
|
216
|
+
method: HTTP_VERBS.PATCH,
|
217
|
+
uri,
|
218
|
+
body,
|
219
|
+
};
|
220
|
+
},
|
221
|
+
|
206
222
|
/**
|
207
223
|
* @param {ServerRoleShape} role
|
208
224
|
* @returns {ServerRoleShape} the role shape to be added to the body
|
package/src/metrics/constants.ts
CHANGED
@@ -48,10 +48,19 @@ const BEHAVIORAL_METRICS = {
|
|
48
48
|
UPLOAD_LOGS_FAILURE: 'js_sdk_upload_logs_failure',
|
49
49
|
UPLOAD_LOGS_SUCCESS: 'js_sdk_upload_logs_success',
|
50
50
|
RECEIVE_TRANSCRIPTION_FAILURE: 'js_sdk_receive_transcription_failure',
|
51
|
+
MEETING_IS_IN_PROGRESS_ERROR: 'js_sdk_meeting_is_in_progress_error',
|
52
|
+
STATIC_MEETING_LINK_ALREADY_EXISTS_ERROR: 'js_sdk_static_meeting_link_already_exists_error',
|
51
53
|
FETCH_MEETING_INFO_V1_SUCCESS: 'js_sdk_fetch_meeting_info_v1_success',
|
52
54
|
FETCH_MEETING_INFO_V1_FAILURE: 'js_sdk_fetch_meeting_info_v1_failure',
|
55
|
+
ENABLE_STATIC_METTING_LINK_SUCCESS: 'js_sdk_enable_static_meeting_link_success',
|
56
|
+
ENABLE_STATIC_METTING_LINK_FAILURE: 'js_sdk_enable_static_meeting_link_failure',
|
57
|
+
DISABLE_STATIC_MEETING_LINK_SUCCESS: 'js_sdk_disable_static_meeting_link_success',
|
58
|
+
DISABLE_STATIC_MEETING_LINK_FAILURE: 'js_sdk_disable_static_meeting_link_failure',
|
53
59
|
ADHOC_MEETING_SUCCESS: 'js_sdk_adhoc_meeting_success',
|
54
60
|
ADHOC_MEETING_FAILURE: 'js_sdk_adhoc_meeting_failure',
|
61
|
+
FETCH_STATIC_MEETING_LINK_SUCCESS: 'js_sdk_fetch_static_meeting_link_success',
|
62
|
+
FETCH_STATIC_MEETING_LINK_FAILURE: 'js_sdk_fetch_static_meeting_link_failure',
|
63
|
+
MEETING_LINK_DOES_NOT_EXIST_ERROR: 'js_sdk_meeting_link_does_not_exist_error',
|
55
64
|
VERIFY_PASSWORD_SUCCESS: 'js_sdk_verify_password_success',
|
56
65
|
VERIFY_PASSWORD_ERROR: 'js_sdk_verify_password_error',
|
57
66
|
VERIFY_CAPTCHA_ERROR: 'js_sdk_verify_captcha_error',
|
@@ -6,7 +6,7 @@ import {convertStunUrlToTurn, convertStunUrlToTurnTls} from './util';
|
|
6
6
|
import EventsScope from '../common/events/events-scope';
|
7
7
|
|
8
8
|
import {CONNECTION_STATE, Enum, ICE_GATHERING_STATE} from '../constants';
|
9
|
-
import {ClusterReachabilityResult} from './reachability.types';
|
9
|
+
import {ClusterReachabilityResult, NatType} from './reachability.types';
|
10
10
|
|
11
11
|
// data for the Events.resultReady event
|
12
12
|
export type ResultEventData = {
|
@@ -22,9 +22,14 @@ export type ClientMediaIpsUpdatedEventData = {
|
|
22
22
|
clientMediaIPs: string[];
|
23
23
|
};
|
24
24
|
|
25
|
+
export type NatTypeUpdatedEventData = {
|
26
|
+
natType: NatType;
|
27
|
+
};
|
28
|
+
|
25
29
|
export const Events = {
|
26
30
|
resultReady: 'resultReady', // emitted when a cluster is reached successfully using specific protocol
|
27
31
|
clientMediaIpsUpdated: 'clientMediaIpsUpdated', // emitted when more public IPs are found after resultReady was already sent for a given protocol
|
32
|
+
natTypeUpdated: 'natTypeUpdated', // emitted when NAT type is determined
|
28
33
|
} as const;
|
29
34
|
|
30
35
|
export type Events = Enum<typeof Events>;
|
@@ -41,8 +46,10 @@ export class ClusterReachability extends EventsScope {
|
|
41
46
|
private pc?: RTCPeerConnection;
|
42
47
|
private defer: Defer; // this defer is resolved once reachability checks for this cluster are completed
|
43
48
|
private startTimestamp: number;
|
49
|
+
private srflxIceCandidates: RTCIceCandidate[] = [];
|
44
50
|
public readonly isVideoMesh: boolean;
|
45
51
|
public readonly name;
|
52
|
+
public readonly reachedSubnets: Set<string> = new Set();
|
46
53
|
|
47
54
|
/**
|
48
55
|
* Constructor for ClusterReachability
|
@@ -228,27 +235,13 @@ export class ClusterReachability extends EventsScope {
|
|
228
235
|
*/
|
229
236
|
private registerIceGatheringStateChangeListener() {
|
230
237
|
this.pc.onicegatheringstatechange = () => {
|
231
|
-
|
232
|
-
|
233
|
-
if (this.pc.iceConnectionState === COMPLETE) {
|
238
|
+
if (this.pc.iceGatheringState === ICE_GATHERING_STATE.COMPLETE) {
|
234
239
|
this.closePeerConnection();
|
235
240
|
this.finishReachabilityCheck();
|
236
241
|
}
|
237
242
|
};
|
238
243
|
}
|
239
244
|
|
240
|
-
/**
|
241
|
-
* Checks if we have the results for all the protocols (UDP and TCP)
|
242
|
-
*
|
243
|
-
* @returns {boolean} true if we have all results, false otherwise
|
244
|
-
*/
|
245
|
-
private haveWeGotAllResults(): boolean {
|
246
|
-
return ['udp', 'tcp', 'xtls'].every(
|
247
|
-
(protocol) =>
|
248
|
-
this.result[protocol].result === 'reachable' || this.result[protocol].result === 'untested'
|
249
|
-
);
|
250
|
-
}
|
251
|
-
|
252
245
|
/**
|
253
246
|
* Saves the latency in the result for the given protocol and marks it as reachable,
|
254
247
|
* emits the "resultReady" event if this is the first result for that protocol,
|
@@ -258,9 +251,15 @@ export class ClusterReachability extends EventsScope {
|
|
258
251
|
* @param {string} protocol
|
259
252
|
* @param {number} latency
|
260
253
|
* @param {string|null} [publicIp]
|
254
|
+
* @param {string|null} [serverIp]
|
261
255
|
* @returns {void}
|
262
256
|
*/
|
263
|
-
private saveResult(
|
257
|
+
private saveResult(
|
258
|
+
protocol: 'udp' | 'tcp' | 'xtls',
|
259
|
+
latency: number,
|
260
|
+
publicIp?: string | null,
|
261
|
+
serverIp?: string | null
|
262
|
+
) {
|
264
263
|
const result = this.result[protocol];
|
265
264
|
|
266
265
|
if (result.latencyInMilliseconds === undefined) {
|
@@ -288,6 +287,48 @@ export class ClusterReachability extends EventsScope {
|
|
288
287
|
} else {
|
289
288
|
this.addPublicIP(protocol, publicIp);
|
290
289
|
}
|
290
|
+
|
291
|
+
if (serverIp) {
|
292
|
+
this.reachedSubnets.add(serverIp);
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
/**
|
297
|
+
* Determines NAT Type.
|
298
|
+
*
|
299
|
+
* @param {RTCIceCandidate} candidate
|
300
|
+
* @returns {void}
|
301
|
+
*/
|
302
|
+
private determineNatType(candidate: RTCIceCandidate) {
|
303
|
+
this.srflxIceCandidates.push(candidate);
|
304
|
+
|
305
|
+
if (this.srflxIceCandidates.length > 1) {
|
306
|
+
const portsFound: Record<string, Set<number>> = {};
|
307
|
+
|
308
|
+
this.srflxIceCandidates.forEach((c) => {
|
309
|
+
const key = `${c.address}:${c.relatedPort}`;
|
310
|
+
if (!portsFound[key]) {
|
311
|
+
portsFound[key] = new Set();
|
312
|
+
}
|
313
|
+
portsFound[key].add(c.port);
|
314
|
+
});
|
315
|
+
|
316
|
+
Object.entries(portsFound).forEach(([, ports]) => {
|
317
|
+
if (ports.size > 1) {
|
318
|
+
// Found candidates with the same address and relatedPort, but different ports
|
319
|
+
this.emit(
|
320
|
+
{
|
321
|
+
file: 'clusterReachability',
|
322
|
+
function: 'determineNatType',
|
323
|
+
},
|
324
|
+
Events.natTypeUpdated,
|
325
|
+
{
|
326
|
+
natType: NatType.SymmetricNat,
|
327
|
+
}
|
328
|
+
);
|
329
|
+
}
|
330
|
+
});
|
331
|
+
}
|
291
332
|
}
|
292
333
|
|
293
334
|
/**
|
@@ -307,19 +348,25 @@ export class ClusterReachability extends EventsScope {
|
|
307
348
|
|
308
349
|
if (e.candidate) {
|
309
350
|
if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
|
310
|
-
|
351
|
+
let serverIp = null;
|
352
|
+
if ('url' in e.candidate) {
|
353
|
+
const stunServerUrlRegex = /stun:([\d.]+):\d+/;
|
354
|
+
|
355
|
+
const match = (e.candidate as any).url.match(stunServerUrlRegex);
|
356
|
+
if (match) {
|
357
|
+
// eslint-disable-next-line prefer-destructuring
|
358
|
+
serverIp = match[1];
|
359
|
+
}
|
360
|
+
}
|
361
|
+
|
362
|
+
this.saveResult('udp', latencyInMilliseconds, e.candidate.address, serverIp);
|
363
|
+
|
364
|
+
this.determineNatType(e.candidate);
|
311
365
|
}
|
312
366
|
|
313
367
|
if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
|
314
368
|
const protocol = e.candidate.port === TURN_TLS_PORT ? 'xtls' : 'tcp';
|
315
|
-
this.saveResult(protocol, latencyInMilliseconds);
|
316
|
-
// we don't add public IP for TCP, because in the case of relay candidates
|
317
|
-
// e.candidate.address is the TURN server address, not the client's public IP
|
318
|
-
}
|
319
|
-
|
320
|
-
if (this.haveWeGotAllResults()) {
|
321
|
-
this.closePeerConnection();
|
322
|
-
this.finishReachabilityCheck();
|
369
|
+
this.saveResult(protocol, latencyInMilliseconds, null, e.candidate.address);
|
323
370
|
}
|
324
371
|
}
|
325
372
|
};
|
@@ -23,11 +23,13 @@ import {
|
|
23
23
|
ReachabilityResultsForBackend,
|
24
24
|
TransportResultForBackend,
|
25
25
|
GetClustersTrigger,
|
26
|
+
NatType,
|
26
27
|
} from './reachability.types';
|
27
28
|
import {
|
28
29
|
ClientMediaIpsUpdatedEventData,
|
29
30
|
ClusterReachability,
|
30
31
|
Events,
|
32
|
+
NatTypeUpdatedEventData,
|
31
33
|
ResultEventData,
|
32
34
|
} from './clusterReachability';
|
33
35
|
import EventsScope from '../common/events/events-scope';
|
@@ -64,6 +66,7 @@ export default class Reachability extends EventsScope {
|
|
64
66
|
resultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
|
65
67
|
startTime = undefined;
|
66
68
|
totalDuration = undefined;
|
69
|
+
natType = NatType.Unknown;
|
67
70
|
|
68
71
|
protected lastTrigger?: string;
|
69
72
|
|
@@ -135,6 +138,60 @@ export default class Reachability extends EventsScope {
|
|
135
138
|
}
|
136
139
|
}
|
137
140
|
|
141
|
+
/**
|
142
|
+
* Checks if the given subnet is reachable
|
143
|
+
* @param {string} mediaServerIp - media server ip
|
144
|
+
* @returns {boolean | null} true if reachable, false if not reachable, null if mediaServerIp is not provided
|
145
|
+
* @public
|
146
|
+
* @memberof Reachability
|
147
|
+
*/
|
148
|
+
public isSubnetReachable(mediaServerIp?: string): boolean | null {
|
149
|
+
if (!mediaServerIp) {
|
150
|
+
LoggerProxy.logger.error(`Reachability:index#isSubnetReachable --> mediaServerIp is null`);
|
151
|
+
|
152
|
+
return null;
|
153
|
+
}
|
154
|
+
|
155
|
+
const subnetFirstOctet = mediaServerIp.split('.')[0];
|
156
|
+
|
157
|
+
LoggerProxy.logger.info(
|
158
|
+
`Reachability:index#isSubnetReachable --> Looking for subnet: ${subnetFirstOctet}.X.X.X`
|
159
|
+
);
|
160
|
+
|
161
|
+
const matchingReachedClusters = Object.values(this.clusterReachability).reduce(
|
162
|
+
(acc, cluster) => {
|
163
|
+
const reachedSubnetsArray = Array.from(cluster.reachedSubnets);
|
164
|
+
|
165
|
+
let logMessage = `Reachability:index#isSubnetReachable --> Cluster ${cluster.name} reached [`;
|
166
|
+
for (let i = 0; i < reachedSubnetsArray.length; i += 1) {
|
167
|
+
const subnet = reachedSubnetsArray[i];
|
168
|
+
const reachedSubnetFirstOctet = subnet.split('.')[0];
|
169
|
+
|
170
|
+
if (subnetFirstOctet === reachedSubnetFirstOctet) {
|
171
|
+
acc.add(cluster.name);
|
172
|
+
}
|
173
|
+
|
174
|
+
logMessage += `${subnet}`;
|
175
|
+
if (i < reachedSubnetsArray.length - 1) {
|
176
|
+
logMessage += ',';
|
177
|
+
}
|
178
|
+
}
|
179
|
+
logMessage += `]`;
|
180
|
+
|
181
|
+
LoggerProxy.logger.info(logMessage);
|
182
|
+
|
183
|
+
return acc;
|
184
|
+
},
|
185
|
+
new Set<string>()
|
186
|
+
);
|
187
|
+
|
188
|
+
LoggerProxy.logger.info(
|
189
|
+
`Reachability:index#isSubnetReachable --> Found ${matchingReachedClusters.size} clusters that use the subnet ${subnetFirstOctet}.X.X.X`
|
190
|
+
);
|
191
|
+
|
192
|
+
return matchingReachedClusters.size > 0;
|
193
|
+
}
|
194
|
+
|
138
195
|
/**
|
139
196
|
* Gets a list of media clusters from the backend and performs reachability checks on all the clusters
|
140
197
|
* @param {string} trigger - explains the reason for starting reachability
|
@@ -143,6 +200,10 @@ export default class Reachability extends EventsScope {
|
|
143
200
|
* @memberof Reachability
|
144
201
|
*/
|
145
202
|
public async gatherReachability(trigger: string): Promise<ReachabilityResults> {
|
203
|
+
// @ts-ignore
|
204
|
+
if (!this.webex.config.meetings.enableReachabilityChecks) {
|
205
|
+
throw new Error('enableReachabilityChecks is disabled in config');
|
206
|
+
}
|
146
207
|
// Fetch clusters and measure latency
|
147
208
|
try {
|
148
209
|
this.lastTrigger = trigger;
|
@@ -281,7 +342,7 @@ export default class Reachability extends EventsScope {
|
|
281
342
|
{}
|
282
343
|
);
|
283
344
|
this.sendMetric(true);
|
284
|
-
this.resolveReachabilityPromise();
|
345
|
+
this.resolveReachabilityPromise(false);
|
285
346
|
}
|
286
347
|
}
|
287
348
|
|
@@ -305,6 +366,7 @@ export default class Reachability extends EventsScope {
|
|
305
366
|
reachability_vmn_tcp_failed: 0,
|
306
367
|
reachability_vmn_xtls_success: 0,
|
307
368
|
reachability_vmn_xtls_failed: 0,
|
369
|
+
natType: this.natType,
|
308
370
|
};
|
309
371
|
|
310
372
|
const updateStats = (clusterType: 'public' | 'vmn', result: ClusterReachabilityResult) => {
|
@@ -963,6 +1025,13 @@ export default class Reachability extends EventsScope {
|
|
963
1025
|
}
|
964
1026
|
);
|
965
1027
|
|
1028
|
+
this.clusterReachability[key].on(
|
1029
|
+
Events.natTypeUpdated,
|
1030
|
+
async (data: NatTypeUpdatedEventData) => {
|
1031
|
+
this.natType = data.natType;
|
1032
|
+
}
|
1033
|
+
);
|
1034
|
+
|
966
1035
|
this.clusterReachability[key].start(); // not awaiting on purpose
|
967
1036
|
});
|
968
1037
|
}
|
@@ -7,6 +7,11 @@ export type TransportResult = {
|
|
7
7
|
clientMediaIPs?: string[];
|
8
8
|
};
|
9
9
|
|
10
|
+
export enum NatType {
|
11
|
+
Unknown = 'unknown',
|
12
|
+
SymmetricNat = 'symmetric-nat',
|
13
|
+
}
|
14
|
+
|
10
15
|
// reachability result for a specific media cluster
|
11
16
|
export type ClusterReachabilityResult = {
|
12
17
|
udp: TransportResult;
|
@@ -27,6 +32,7 @@ export type ReachabilityMetrics = {
|
|
27
32
|
reachability_vmn_tcp_failed: number;
|
28
33
|
reachability_vmn_xtls_success: number;
|
29
34
|
reachability_vmn_xtls_failed: number;
|
35
|
+
natType: NatType;
|
30
36
|
};
|
31
37
|
|
32
38
|
/**
|
@@ -45,6 +45,9 @@ class ReachabilityRequest {
|
|
45
45
|
joinCookie: any;
|
46
46
|
discoveryOptions?: Record<string, any>;
|
47
47
|
}> => {
|
48
|
+
const appType = this.webex?.config?.support?.appType;
|
49
|
+
const appVersion = this.webex?.config?.support?.appVersion;
|
50
|
+
|
48
51
|
// we only measure latency for the initial startup call, not for other triggers
|
49
52
|
const callWrapper =
|
50
53
|
trigger === 'startup'
|
@@ -67,6 +70,10 @@ class ReachabilityRequest {
|
|
67
70
|
'early-call-min-clusters': true,
|
68
71
|
},
|
69
72
|
'previous-report': previousReport,
|
73
|
+
...(appType &&
|
74
|
+
appVersion && {
|
75
|
+
'client-environment': {components: {[appType]: appVersion}},
|
76
|
+
}),
|
70
77
|
trigger,
|
71
78
|
},
|
72
79
|
timeout: this.webex.config.meetings.reachabilityGetClusterTimeout,
|
@@ -591,9 +591,9 @@ export default class ReconnectionManager {
|
|
591
591
|
|
592
592
|
const iceServers = [];
|
593
593
|
|
594
|
-
if (turnServerResult.turnServerInfo?.
|
594
|
+
if (turnServerResult.turnServerInfo?.urls.length > 0) {
|
595
595
|
iceServers.push({
|
596
|
-
urls: turnServerResult.turnServerInfo.
|
596
|
+
urls: turnServerResult.turnServerInfo.urls,
|
597
597
|
username: turnServerResult.turnServerInfo.username || '',
|
598
598
|
credential: turnServerResult.turnServerInfo.password || '',
|
599
599
|
});
|
@@ -6,33 +6,37 @@ const canUserStart = (
|
|
6
6
|
displayHints: Array<string>,
|
7
7
|
userPolicies: Record<SELF_POLICY, boolean>
|
8
8
|
): boolean =>
|
9
|
-
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START)
|
10
|
-
|
11
|
-
|
9
|
+
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START) &&
|
10
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies)) ||
|
11
|
+
(displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_START) &&
|
12
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies));
|
12
13
|
|
13
14
|
const canUserPause = (
|
14
15
|
displayHints: Array<string>,
|
15
16
|
userPolicies: Record<SELF_POLICY, boolean>
|
16
17
|
): boolean =>
|
17
|
-
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE)
|
18
|
-
|
19
|
-
|
18
|
+
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE) &&
|
19
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies)) ||
|
20
|
+
(displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE) &&
|
21
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies));
|
20
22
|
|
21
23
|
const canUserResume = (
|
22
24
|
displayHints: Array<string>,
|
23
25
|
userPolicies: Record<SELF_POLICY, boolean>
|
24
26
|
): boolean =>
|
25
|
-
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME)
|
26
|
-
|
27
|
-
|
27
|
+
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME) &&
|
28
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies)) ||
|
29
|
+
(displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME) &&
|
30
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies));
|
28
31
|
|
29
32
|
const canUserStop = (
|
30
33
|
displayHints: Array<string>,
|
31
34
|
userPolicies: Record<SELF_POLICY, boolean>
|
32
35
|
): boolean =>
|
33
|
-
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP)
|
34
|
-
|
35
|
-
|
36
|
+
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP) &&
|
37
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies)) ||
|
38
|
+
(displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP) &&
|
39
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies));
|
36
40
|
|
37
41
|
const isPremiseRecordingEnabled = (
|
38
42
|
displayHints: Array<string>,
|
@@ -42,7 +46,7 @@ const isPremiseRecordingEnabled = (
|
|
42
46
|
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE) ||
|
43
47
|
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP) ||
|
44
48
|
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME)) &&
|
45
|
-
MeetingUtil.selfSupportsFeature(SELF_POLICY.
|
49
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies);
|
46
50
|
|
47
51
|
const extractLocusId = (url: string) => {
|
48
52
|
return url?.split('/').pop();
|
package/src/roap/index.ts
CHANGED
@@ -5,17 +5,13 @@ import {ROAP} from '../constants';
|
|
5
5
|
import LoggerProxy from '../common/logs/logger-proxy';
|
6
6
|
|
7
7
|
import RoapRequest from './request';
|
8
|
-
import TurnDiscovery
|
8
|
+
import TurnDiscovery from './turnDiscovery';
|
9
|
+
import {TurnDiscoveryResult} from './types';
|
9
10
|
import Meeting from '../meeting';
|
10
|
-
import MeetingUtil from '../meeting/util';
|
11
11
|
import Metrics from '../metrics';
|
12
12
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
13
13
|
|
14
|
-
export {
|
15
|
-
type TurnDiscoveryResult,
|
16
|
-
type TurnServerInfo,
|
17
|
-
type TurnDiscoverySkipReason,
|
18
|
-
} from './turnDiscovery';
|
14
|
+
export {type TurnDiscoveryResult, type TurnServerInfo, type TurnDiscoverySkipReason} from './types';
|
19
15
|
|
20
16
|
/**
|
21
17
|
* Roap options
|
@@ -4,11 +4,11 @@ import {Defer} from '@webex/common';
|
|
4
4
|
import Metrics from '../metrics';
|
5
5
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
6
6
|
import LoggerProxy from '../common/logs/logger-proxy';
|
7
|
-
import {ROAP
|
7
|
+
import {ROAP} from '../constants';
|
8
8
|
|
9
9
|
import RoapRequest from './request';
|
10
10
|
import Meeting from '../meeting';
|
11
|
-
import
|
11
|
+
import {TurnDiscoverySkipReason, TurnServerInfo, TurnDiscoveryResult} from './types';
|
12
12
|
|
13
13
|
const TURN_DISCOVERY_TIMEOUT = 10; // in seconds
|
14
14
|
|
@@ -18,28 +18,6 @@ const TURN_DISCOVERY_TIMEOUT = 10; // in seconds
|
|
18
18
|
// and do the SDP offer with seq=1
|
19
19
|
const TURN_DISCOVERY_SEQ = 0;
|
20
20
|
|
21
|
-
const TurnDiscoverySkipReason = {
|
22
|
-
missingHttpResponse: 'missing http response', // when we asked for the TURN discovery response to be in the http response, but it wasn't there
|
23
|
-
reachability: 'reachability', // when udp reachability to public clusters is ok, so we don't need TURN (this doens't apply when joinWithMedia() is used)
|
24
|
-
alreadyInProgress: 'already in progress', // when we try to start TURN discovery while it's already in progress
|
25
|
-
} as const;
|
26
|
-
|
27
|
-
export type TurnDiscoverySkipReason =
|
28
|
-
| Enum<typeof TurnDiscoverySkipReason> // this is a kind of FYI, because in practice typescript will infer the type of TurnDiscoverySkipReason as a string
|
29
|
-
| string // used in case of errors, contains the error message
|
30
|
-
| undefined; // used when TURN discovery is not skipped
|
31
|
-
|
32
|
-
export type TurnServerInfo = {
|
33
|
-
url: string;
|
34
|
-
username: string;
|
35
|
-
password: string;
|
36
|
-
};
|
37
|
-
|
38
|
-
export type TurnDiscoveryResult = {
|
39
|
-
turnServerInfo?: TurnServerInfo;
|
40
|
-
turnDiscoverySkippedReason: TurnDiscoverySkipReason;
|
41
|
-
};
|
42
|
-
|
43
21
|
/**
|
44
22
|
* Handles the process of finding out TURN server information from Linus.
|
45
23
|
* This is achieved by sending a TURN_DISCOVERY_REQUEST.
|
@@ -53,6 +31,17 @@ export default class TurnDiscovery {
|
|
53
31
|
|
54
32
|
private responseTimer?: ReturnType<typeof setTimeout>;
|
55
33
|
|
34
|
+
/** Resets the turnInfo structure to the defaults
|
35
|
+
* @returns {void}
|
36
|
+
*/
|
37
|
+
private resetTurnInfo() {
|
38
|
+
this.turnInfo = {
|
39
|
+
urls: [],
|
40
|
+
username: '',
|
41
|
+
password: '',
|
42
|
+
};
|
43
|
+
}
|
44
|
+
|
56
45
|
/**
|
57
46
|
* Constructor
|
58
47
|
*
|
@@ -60,11 +49,7 @@ export default class TurnDiscovery {
|
|
60
49
|
*/
|
61
50
|
constructor(roapRequest: RoapRequest) {
|
62
51
|
this.roapRequest = roapRequest;
|
63
|
-
this.
|
64
|
-
url: '',
|
65
|
-
username: '',
|
66
|
-
password: '',
|
67
|
-
};
|
52
|
+
this.resetTurnInfo();
|
68
53
|
}
|
69
54
|
|
70
55
|
/**
|
@@ -134,21 +119,29 @@ export default class TurnDiscovery {
|
|
134
119
|
}
|
135
120
|
|
136
121
|
const expectedHeaders = [
|
137
|
-
{headerName: 'x-cisco-turn-url', field: '
|
138
|
-
{headerName: 'x-cisco-turn-username', field: 'username'},
|
139
|
-
{headerName: 'x-cisco-turn-password', field: 'password'},
|
122
|
+
{headerName: 'x-cisco-turn-url', field: 'urls', multipleAllowed: true},
|
123
|
+
{headerName: 'x-cisco-turn-username', field: 'username', multipleAllowed: false},
|
124
|
+
{headerName: 'x-cisco-turn-password', field: 'password', multipleAllowed: false},
|
140
125
|
];
|
141
126
|
|
142
|
-
|
127
|
+
const foundHeaders = {};
|
128
|
+
|
129
|
+
this.resetTurnInfo();
|
143
130
|
|
144
131
|
headers?.forEach((receivedHeader) => {
|
145
132
|
// check if it matches any of our expected headers
|
146
133
|
expectedHeaders.forEach((expectedHeader) => {
|
147
134
|
if (receivedHeader.startsWith(`${expectedHeader.headerName}=`)) {
|
148
|
-
|
149
|
-
|
150
|
-
);
|
151
|
-
|
135
|
+
foundHeaders[expectedHeader.headerName] = true;
|
136
|
+
|
137
|
+
const headerValue = receivedHeader.substring(expectedHeader.headerName.length + 1);
|
138
|
+
|
139
|
+
if (expectedHeader.multipleAllowed) {
|
140
|
+
this.turnInfo[expectedHeader.field].push(headerValue);
|
141
|
+
} else {
|
142
|
+
// just store the last one we find
|
143
|
+
this.turnInfo[expectedHeader.field] = headerValue;
|
144
|
+
}
|
152
145
|
}
|
153
146
|
});
|
154
147
|
});
|
@@ -156,7 +149,7 @@ export default class TurnDiscovery {
|
|
156
149
|
clearTimeout(this.responseTimer);
|
157
150
|
this.responseTimer = undefined;
|
158
151
|
|
159
|
-
if (
|
152
|
+
if (expectedHeaders.some((header) => !foundHeaders[header.headerName])) {
|
160
153
|
LoggerProxy.logger.warn(
|
161
154
|
`Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received ${from}: ${JSON.stringify(
|
162
155
|
headers
|
@@ -169,9 +162,11 @@ export default class TurnDiscovery {
|
|
169
162
|
);
|
170
163
|
} else {
|
171
164
|
LoggerProxy.logger.info(
|
172
|
-
`Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response ${from},
|
165
|
+
`Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response ${from}, urls=${this.turnInfo.urls}`
|
173
166
|
);
|
174
167
|
|
168
|
+
this.turnInfo.urls = this.turnInfo.urls.filter((url) => url !== ''); // remove empty urls, we might get them if we land on video-mesh nodes (VMN)
|
169
|
+
|
175
170
|
this.defer.resolve({isOkRequired: !headers?.includes('noOkInTransaction')});
|
176
171
|
}
|
177
172
|
}
|