@webex/plugin-meetings 3.8.0-next.4 → 3.8.0-next.41
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/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +14 -1
- 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/media/index.js +3 -15
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +11 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +443 -256
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +21 -22
- 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 +10 -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 +60 -1
- 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 +3 -0
- package/dist/member/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 +52 -8
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +70 -45
- 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/config.d.ts +1 -0
- package/dist/types/constants.d.ts +10 -0
- 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/meeting/in-meeting-actions.d.ts +10 -0
- package/dist/types/meeting/index.d.ts +47 -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 +2 -1
- package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
- package/dist/types/meetings/index.d.ts +29 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/metrics/constants.d.ts +9 -0
- package/dist/types/reachability/clusterReachability.d.ts +13 -1
- package/dist/types/reachability/index.d.ts +2 -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 +22 -22
- package/src/config.ts +1 -0
- package/src/constants.ts +17 -0
- 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/media/index.ts +5 -21
- package/src/meeting/in-meeting-actions.ts +20 -0
- package/src/meeting/index.ts +263 -69
- package/src/meeting/locusMediaRequest.ts +27 -22
- 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 +9 -2
- package/src/meeting-info/meeting-info-v2.ts +247 -6
- package/src/meetings/index.ts +72 -1
- package/src/member/index.ts +11 -0
- package/src/member/util.ts +3 -0
- package/src/metrics/constants.ts +9 -0
- package/src/reachability/clusterReachability.ts +47 -1
- package/src/reachability/index.ts +15 -0
- 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 +6 -16
- package/test/unit/spec/meeting/in-meeting-actions.ts +13 -4
- package/test/unit/spec/meeting/index.js +490 -130
- package/test/unit/spec/meeting/locusMediaRequest.ts +95 -87
- 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 +115 -18
- package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
- package/test/unit/spec/meetings/index.js +78 -1
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/reachability/clusterReachability.ts +47 -1
- package/test/unit/spec/reachability/index.ts +12 -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
@@ -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,6 +46,7 @@ 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;
|
46
52
|
|
@@ -290,6 +296,44 @@ export class ClusterReachability extends EventsScope {
|
|
290
296
|
}
|
291
297
|
}
|
292
298
|
|
299
|
+
/**
|
300
|
+
* Determines NAT Type.
|
301
|
+
*
|
302
|
+
* @param {RTCIceCandidate} candidate
|
303
|
+
* @returns {void}
|
304
|
+
*/
|
305
|
+
private determineNatType(candidate: RTCIceCandidate) {
|
306
|
+
this.srflxIceCandidates.push(candidate);
|
307
|
+
|
308
|
+
if (this.srflxIceCandidates.length > 1) {
|
309
|
+
const portsFound: Record<string, Set<number>> = {};
|
310
|
+
|
311
|
+
this.srflxIceCandidates.forEach((c) => {
|
312
|
+
const key = `${c.address}:${c.relatedPort}`;
|
313
|
+
if (!portsFound[key]) {
|
314
|
+
portsFound[key] = new Set();
|
315
|
+
}
|
316
|
+
portsFound[key].add(c.port);
|
317
|
+
});
|
318
|
+
|
319
|
+
Object.entries(portsFound).forEach(([, ports]) => {
|
320
|
+
if (ports.size > 1) {
|
321
|
+
// Found candidates with the same address and relatedPort, but different ports
|
322
|
+
this.emit(
|
323
|
+
{
|
324
|
+
file: 'clusterReachability',
|
325
|
+
function: 'determineNatType',
|
326
|
+
},
|
327
|
+
Events.natTypeUpdated,
|
328
|
+
{
|
329
|
+
natType: NatType.SymmetricNat,
|
330
|
+
}
|
331
|
+
);
|
332
|
+
}
|
333
|
+
});
|
334
|
+
}
|
335
|
+
}
|
336
|
+
|
293
337
|
/**
|
294
338
|
* Registers a listener for the icecandidate event
|
295
339
|
*
|
@@ -308,6 +352,8 @@ export class ClusterReachability extends EventsScope {
|
|
308
352
|
if (e.candidate) {
|
309
353
|
if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
|
310
354
|
this.saveResult('udp', latencyInMilliseconds, e.candidate.address);
|
355
|
+
|
356
|
+
this.determineNatType(e.candidate);
|
311
357
|
}
|
312
358
|
|
313
359
|
if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
|
@@ -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
|
|
@@ -143,6 +146,10 @@ export default class Reachability extends EventsScope {
|
|
143
146
|
* @memberof Reachability
|
144
147
|
*/
|
145
148
|
public async gatherReachability(trigger: string): Promise<ReachabilityResults> {
|
149
|
+
// @ts-ignore
|
150
|
+
if (!this.webex.config.meetings.enableReachabilityChecks) {
|
151
|
+
throw new Error('enableReachabilityChecks is disabled in config');
|
152
|
+
}
|
146
153
|
// Fetch clusters and measure latency
|
147
154
|
try {
|
148
155
|
this.lastTrigger = trigger;
|
@@ -305,6 +312,7 @@ export default class Reachability extends EventsScope {
|
|
305
312
|
reachability_vmn_tcp_failed: 0,
|
306
313
|
reachability_vmn_xtls_success: 0,
|
307
314
|
reachability_vmn_xtls_failed: 0,
|
315
|
+
natType: this.natType,
|
308
316
|
};
|
309
317
|
|
310
318
|
const updateStats = (clusterType: 'public' | 'vmn', result: ClusterReachabilityResult) => {
|
@@ -963,6 +971,13 @@ export default class Reachability extends EventsScope {
|
|
963
971
|
}
|
964
972
|
);
|
965
973
|
|
974
|
+
this.clusterReachability[key].on(
|
975
|
+
Events.natTypeUpdated,
|
976
|
+
async (data: NatTypeUpdatedEventData) => {
|
977
|
+
this.natType = data.natType;
|
978
|
+
}
|
979
|
+
);
|
980
|
+
|
966
981
|
this.clusterReachability[key].start(); // not awaiting on purpose
|
967
982
|
});
|
968
983
|
}
|
@@ -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
|
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import {Enum} from '../constants';
|
2
|
+
|
3
|
+
export const TurnDiscoverySkipReason = {
|
4
|
+
missingHttpResponse: 'missing http response', // when we asked for the TURN discovery response to be in the http response, but it wasn't there
|
5
|
+
reachability: 'reachability', // when udp reachability to public clusters is ok, so we don't need TURN (this doens't apply when joinWithMedia() is used)
|
6
|
+
alreadyInProgress: 'already in progress', // when we try to start TURN discovery while it's already in progress
|
7
|
+
} as const;
|
8
|
+
|
9
|
+
export type TurnDiscoverySkipReason =
|
10
|
+
| Enum<typeof TurnDiscoverySkipReason> // this is a kind of FYI, because in practice typescript will infer the type of TurnDiscoverySkipReason as a string
|
11
|
+
| string // used in case of errors, contains the error message
|
12
|
+
| undefined; // used when TURN discovery is not skipped
|
13
|
+
|
14
|
+
export type TurnServerInfo = {
|
15
|
+
urls: string[];
|
16
|
+
username: string;
|
17
|
+
password: string;
|
18
|
+
};
|
19
|
+
|
20
|
+
export type TurnDiscoveryResult = {
|
21
|
+
turnServerInfo?: TurnServerInfo;
|
22
|
+
turnDiscoverySkippedReason: TurnDiscoverySkipReason;
|
23
|
+
};
|
@@ -406,6 +406,74 @@ describe('plugin-meetings', () => {
|
|
406
406
|
});
|
407
407
|
});
|
408
408
|
|
409
|
+
describe('canUpdateAnnotation()', () => {
|
410
|
+
beforeEach(() => {
|
411
|
+
sinon.stub(ControlsOptionsUtil, 'hasHints').returns(true);
|
412
|
+
});
|
413
|
+
|
414
|
+
it('should call hasHints() with proper hints when `enabled` is true', () => {
|
415
|
+
ControlsOptionsUtil.canUpdateAnnotation({properties: {enabled: true}}, []);
|
416
|
+
|
417
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
418
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
|
419
|
+
displayHints: [],
|
420
|
+
});
|
421
|
+
});
|
422
|
+
|
423
|
+
it('should call hasHints() with proper hints when `enabled` is false', () => {
|
424
|
+
ControlsOptionsUtil.canUpdateAnnotation({properties: {enabled: false}}, []);
|
425
|
+
|
426
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
427
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
|
428
|
+
displayHints: [],
|
429
|
+
});
|
430
|
+
});
|
431
|
+
|
432
|
+
it('should return the resolution of hasHints()', () => {
|
433
|
+
const expected = 'example-return-value';
|
434
|
+
ControlsOptionsUtil.hasHints.returns(expected);
|
435
|
+
|
436
|
+
const results = ControlsOptionsUtil.canUpdateAnnotation({properties: {}}, []);
|
437
|
+
|
438
|
+
assert.calledOnce(ControlsOptionsUtil.hasHints);
|
439
|
+
assert.equal(results, expected);
|
440
|
+
});
|
441
|
+
});
|
442
|
+
|
443
|
+
describe('canUpdateRemoteDesktopControl()', () => {
|
444
|
+
beforeEach(() => {
|
445
|
+
sinon.stub(ControlsOptionsUtil, 'hasHints').returns(true);
|
446
|
+
});
|
447
|
+
|
448
|
+
it('should call hasHints() with proper hints when `enabled` is true', () => {
|
449
|
+
ControlsOptionsUtil.canUpdateRemoteDesktopControl({properties: {enabled: true}}, []);
|
450
|
+
|
451
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
452
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
|
453
|
+
displayHints: [],
|
454
|
+
});
|
455
|
+
});
|
456
|
+
|
457
|
+
it('should call hasHints() with proper hints when `enabled` is false', () => {
|
458
|
+
ControlsOptionsUtil.canUpdateRemoteDesktopControl({properties: {enabled: false}}, []);
|
459
|
+
|
460
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
461
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
|
462
|
+
displayHints: [],
|
463
|
+
});
|
464
|
+
});
|
465
|
+
|
466
|
+
it('should return the resolution of hasHints()', () => {
|
467
|
+
const expected = 'example-return-value';
|
468
|
+
ControlsOptionsUtil.hasHints.returns(expected);
|
469
|
+
|
470
|
+
const results = ControlsOptionsUtil.canUpdateRemoteDesktopControl({properties: {}}, []);
|
471
|
+
|
472
|
+
assert.calledOnce(ControlsOptionsUtil.hasHints);
|
473
|
+
assert.equal(results, expected);
|
474
|
+
});
|
475
|
+
});
|
476
|
+
|
409
477
|
describe('canUpdate()', () => {
|
410
478
|
const displayHints = [];
|
411
479
|
|
@@ -416,6 +484,8 @@ describe('plugin-meetings', () => {
|
|
416
484
|
ControlsOptionsUtil.canUpdateShareControl = sinon.stub().returns(true);
|
417
485
|
ControlsOptionsUtil.canUpdateVideo = sinon.stub().returns(true);
|
418
486
|
ControlsOptionsUtil.canUpdateViewTheParticipantsList = sinon.stub().returns(true);
|
487
|
+
ControlsOptionsUtil.canUpdateAnnotation = sinon.stub().returns(true);
|
488
|
+
ControlsOptionsUtil.canUpdateRemoteDesktopControl = sinon.stub().returns(true);
|
419
489
|
});
|
420
490
|
|
421
491
|
it('should only call canUpdateAudio() if the scope is audio', () => {
|
@@ -429,6 +499,8 @@ describe('plugin-meetings', () => {
|
|
429
499
|
assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
|
430
500
|
assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
|
431
501
|
assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
|
502
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
|
503
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
|
432
504
|
assert.isTrue(results);
|
433
505
|
});
|
434
506
|
|
@@ -443,6 +515,8 @@ describe('plugin-meetings', () => {
|
|
443
515
|
assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
|
444
516
|
assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
|
445
517
|
assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
|
518
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
|
519
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
|
446
520
|
assert.isTrue(results);
|
447
521
|
});
|
448
522
|
|
@@ -457,6 +531,8 @@ describe('plugin-meetings', () => {
|
|
457
531
|
assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
|
458
532
|
assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
|
459
533
|
assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
|
534
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
|
535
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
|
460
536
|
assert.isTrue(results);
|
461
537
|
});
|
462
538
|
|
@@ -471,6 +547,8 @@ describe('plugin-meetings', () => {
|
|
471
547
|
assert.calledWith(ControlsOptionsUtil.canUpdateShareControl, displayHints);
|
472
548
|
assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
|
473
549
|
assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
|
550
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
|
551
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
|
474
552
|
assert.isTrue(results);
|
475
553
|
});
|
476
554
|
|
@@ -485,6 +563,8 @@ describe('plugin-meetings', () => {
|
|
485
563
|
assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
|
486
564
|
assert.calledWith(ControlsOptionsUtil.canUpdateVideo, control, displayHints);
|
487
565
|
assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
|
566
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
|
567
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
|
488
568
|
assert.isTrue(results);
|
489
569
|
});
|
490
570
|
|
@@ -503,6 +583,44 @@ describe('plugin-meetings', () => {
|
|
503
583
|
control,
|
504
584
|
displayHints
|
505
585
|
);
|
586
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
|
587
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
|
588
|
+
assert.isTrue(results);
|
589
|
+
});
|
590
|
+
|
591
|
+
it('should only call canUpdateAnnotation() if the scope is annotation', () => {
|
592
|
+
const control = {scope: 'annotation'};
|
593
|
+
|
594
|
+
const results = ControlsOptionsUtil.canUpdate(control, displayHints);
|
595
|
+
|
596
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAudio, 0);
|
597
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRaiseHand, 0);
|
598
|
+
assert.callCount(ControlsOptionsUtil.canUpdateReactions, 0);
|
599
|
+
assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
|
600
|
+
assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
|
601
|
+
assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
|
602
|
+
assert.calledWith(ControlsOptionsUtil.canUpdateAnnotation, control, displayHints);
|
603
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
|
604
|
+
assert.isTrue(results);
|
605
|
+
});
|
606
|
+
|
607
|
+
it('should only call canUpdateRemoteDesktopControl() if the scope is rdc', () => {
|
608
|
+
const control = {scope: 'rdc'};
|
609
|
+
|
610
|
+
const results = ControlsOptionsUtil.canUpdate(control, displayHints);
|
611
|
+
|
612
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAudio, 0);
|
613
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRaiseHand, 0);
|
614
|
+
assert.callCount(ControlsOptionsUtil.canUpdateReactions, 0);
|
615
|
+
assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
|
616
|
+
assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
|
617
|
+
assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
|
618
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
|
619
|
+
assert.calledWith(
|
620
|
+
ControlsOptionsUtil.canUpdateRemoteDesktopControl,
|
621
|
+
control,
|
622
|
+
displayHints
|
623
|
+
);
|
506
624
|
assert.isTrue(results);
|
507
625
|
});
|
508
626
|
|
@@ -517,6 +635,8 @@ describe('plugin-meetings', () => {
|
|
517
635
|
assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
|
518
636
|
assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
|
519
637
|
assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
|
638
|
+
assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
|
639
|
+
assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
|
520
640
|
assert.isFalse(results);
|
521
641
|
});
|
522
642
|
});
|