@webex/plugin-meetings 3.0.0-beta.337 → 3.0.0-beta.339
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 +2 -1
- package/dist/config.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/reachability/clusterReachability.js +356 -0
- package/dist/reachability/clusterReachability.js.map +1 -0
- package/dist/reachability/index.js +167 -451
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/util.js +29 -0
- package/dist/reachability/util.js.map +1 -0
- package/dist/statsAnalyzer/mqaUtil.js +4 -0
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/meetings/index.d.ts +1 -11
- package/dist/types/reachability/clusterReachability.d.ts +109 -0
- package/dist/types/reachability/index.d.ts +32 -121
- package/dist/types/reachability/util.d.ts +8 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +20 -20
- package/src/config.ts +1 -0
- package/src/reachability/clusterReachability.ts +320 -0
- package/src/reachability/index.ts +124 -421
- package/src/reachability/util.ts +24 -0
- package/src/statsAnalyzer/mqaUtil.ts +4 -0
- package/test/unit/spec/reachability/clusterReachability.ts +279 -0
- package/test/unit/spec/reachability/index.ts +159 -226
- package/test/unit/spec/reachability/util.ts +40 -0
- package/test/unit/spec/roap/request.ts +26 -3
- package/test/unit/spec/stats-analyzer/index.js +47 -20
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
3
|
*/
|
|
4
|
+
import ReachabilityRequest from './request';
|
|
5
|
+
import { ClusterReachability, ClusterReachabilityResult } from './clusterReachability';
|
|
4
6
|
export type ReachabilityMetrics = {
|
|
5
7
|
reachability_public_udp_success: number;
|
|
6
8
|
reachability_public_udp_failed: number;
|
|
@@ -11,29 +13,25 @@ export type ReachabilityMetrics = {
|
|
|
11
13
|
reachability_vmn_tcp_success: number;
|
|
12
14
|
reachability_vmn_tcp_failed: number;
|
|
13
15
|
};
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* This is the type that matches what backend expects us to send to them. It is a bit weird, because
|
|
18
|
+
* it uses strings instead of booleans and numbers, but that's what they require.
|
|
19
|
+
*/
|
|
20
|
+
export type TransportResultForBackend = {
|
|
15
21
|
reachable?: 'true' | 'false';
|
|
16
22
|
latencyInMilliseconds?: string;
|
|
17
23
|
clientMediaIPs?: string[];
|
|
18
24
|
untested?: 'true';
|
|
19
25
|
};
|
|
20
|
-
type
|
|
21
|
-
udp:
|
|
22
|
-
tcp:
|
|
23
|
-
xtls:
|
|
24
|
-
untested: 'true';
|
|
25
|
-
};
|
|
26
|
+
export type ReachabilityResultForBackend = {
|
|
27
|
+
udp: TransportResultForBackend;
|
|
28
|
+
tcp: TransportResultForBackend;
|
|
29
|
+
xtls: TransportResultForBackend;
|
|
26
30
|
};
|
|
27
|
-
export type
|
|
28
|
-
type
|
|
31
|
+
export type ReachabilityResultsForBackend = Record<string, ReachabilityResultForBackend>;
|
|
32
|
+
export type ReachabilityResults = Record<string, ClusterReachabilityResult & {
|
|
29
33
|
isVideoMesh?: boolean;
|
|
30
34
|
}>;
|
|
31
|
-
export type ICECandidateResult = {
|
|
32
|
-
clusterId: string;
|
|
33
|
-
isVideoMesh: boolean;
|
|
34
|
-
elapsed?: string | null;
|
|
35
|
-
publicIPs?: string[];
|
|
36
|
-
};
|
|
37
35
|
/**
|
|
38
36
|
* @class Reachability
|
|
39
37
|
* @export
|
|
@@ -41,8 +39,10 @@ export type ICECandidateResult = {
|
|
|
41
39
|
export default class Reachability {
|
|
42
40
|
namespace: string;
|
|
43
41
|
webex: object;
|
|
44
|
-
reachabilityRequest:
|
|
45
|
-
|
|
42
|
+
reachabilityRequest: ReachabilityRequest;
|
|
43
|
+
clusterReachability: {
|
|
44
|
+
[key: string]: ClusterReachability;
|
|
45
|
+
};
|
|
46
46
|
/**
|
|
47
47
|
* Creates an instance of Reachability.
|
|
48
48
|
* @param {object} webex
|
|
@@ -50,13 +50,12 @@ export default class Reachability {
|
|
|
50
50
|
*/
|
|
51
51
|
constructor(webex: object);
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
* @returns {
|
|
53
|
+
* Gets a list of media clusters from the backend and performs reachability checks on all the clusters
|
|
54
|
+
* @returns {Promise<ReachabilityResults>} reachability results
|
|
55
55
|
* @public
|
|
56
|
-
* @async
|
|
57
56
|
* @memberof Reachability
|
|
58
57
|
*/
|
|
59
|
-
gatherReachability(): Promise<
|
|
58
|
+
gatherReachability(): Promise<ReachabilityResults>;
|
|
60
59
|
/**
|
|
61
60
|
* Returns statistics about last reachability results. The returned value is an object
|
|
62
61
|
* with a flat list of properties so that it can be easily sent with metrics
|
|
@@ -64,12 +63,18 @@ export default class Reachability {
|
|
|
64
63
|
* @returns {Promise} Promise with metrics values, it never rejects/throws.
|
|
65
64
|
*/
|
|
66
65
|
getReachabilityMetrics(): Promise<ReachabilityMetrics>;
|
|
66
|
+
/**
|
|
67
|
+
* Maps our internal transport result to the format that backend expects
|
|
68
|
+
* @param {TransportResult} transportResult
|
|
69
|
+
* @returns {TransportResultForBackend}
|
|
70
|
+
*/
|
|
71
|
+
private mapTransportResultToBackendDataFormat;
|
|
67
72
|
/**
|
|
68
73
|
* Reachability results as an object in the format that backend expects
|
|
69
74
|
*
|
|
70
75
|
* @returns {any} reachability results that need to be sent to the backend
|
|
71
76
|
*/
|
|
72
|
-
getReachabilityResults(): Promise<
|
|
77
|
+
getReachabilityResults(): Promise<ReachabilityResultsForBackend | undefined>;
|
|
73
78
|
/**
|
|
74
79
|
* fetches reachability data and checks for cluster reachability
|
|
75
80
|
* @returns {boolean}
|
|
@@ -77,73 +82,13 @@ export default class Reachability {
|
|
|
77
82
|
* @memberof Reachability
|
|
78
83
|
*/
|
|
79
84
|
isAnyPublicClusterReachable(): Promise<boolean>;
|
|
80
|
-
/**
|
|
81
|
-
* Generate peerConnection config settings
|
|
82
|
-
* @param {object} cluster
|
|
83
|
-
* @returns {object} peerConnectionConfig
|
|
84
|
-
* @private
|
|
85
|
-
* @memberof Reachability
|
|
86
|
-
*/
|
|
87
|
-
private buildPeerConnectionConfig;
|
|
88
|
-
/**
|
|
89
|
-
* Creates an RTCPeerConnection
|
|
90
|
-
* @param {object} cluster
|
|
91
|
-
* @returns {RTCPeerConnection} peerConnection
|
|
92
|
-
* @private
|
|
93
|
-
* @memberof Reachability
|
|
94
|
-
*/
|
|
95
|
-
private createPeerConnection;
|
|
96
|
-
/**
|
|
97
|
-
* Gets total elapsed time
|
|
98
|
-
* @param {RTCPeerConnection} peerConnection
|
|
99
|
-
* @returns {Number} Milliseconds
|
|
100
|
-
* @private
|
|
101
|
-
* @memberof Reachability
|
|
102
|
-
*/
|
|
103
|
-
private getElapsedTime;
|
|
104
|
-
/**
|
|
105
|
-
* creates offer and generates localSDP
|
|
106
|
-
* @param {object} clusterList cluster List
|
|
107
|
-
* @returns {Promise} Reachability latency results
|
|
108
|
-
* @private
|
|
109
|
-
* @memberof Reachability
|
|
110
|
-
*/
|
|
111
|
-
private getLocalSDPForClusters;
|
|
112
85
|
/**
|
|
113
86
|
* Get list of all unreachable clusters
|
|
114
87
|
* @returns {array} Unreachable clusters
|
|
115
88
|
* @private
|
|
116
89
|
* @memberof Reachability
|
|
117
90
|
*/
|
|
118
|
-
private
|
|
119
|
-
/**
|
|
120
|
-
* Attach an event handler for the icegatheringstatechange
|
|
121
|
-
* event and measure latency.
|
|
122
|
-
* @param {RTCPeerConnection} peerConnection
|
|
123
|
-
* @returns {undefined}
|
|
124
|
-
* @private
|
|
125
|
-
* @memberof Reachability
|
|
126
|
-
*/
|
|
127
|
-
private handleIceGatheringStateChange;
|
|
128
|
-
/**
|
|
129
|
-
* Attach an event handler for the icecandidate
|
|
130
|
-
* event and measure latency.
|
|
131
|
-
* @param {RTCPeerConnection} peerConnection
|
|
132
|
-
* @returns {undefined}
|
|
133
|
-
* @private
|
|
134
|
-
* @memberof Reachability
|
|
135
|
-
*/
|
|
136
|
-
private handleOnIceCandidate;
|
|
137
|
-
/**
|
|
138
|
-
* An event handler on an RTCPeerConnection when the state of the ICE
|
|
139
|
-
* candidate gathering process changes. Used to measure connection
|
|
140
|
-
* speed.
|
|
141
|
-
* @private
|
|
142
|
-
* @param {RTCPeerConnection} peerConnection
|
|
143
|
-
* @param {boolean} isVideoMesh
|
|
144
|
-
* @returns {Promise}
|
|
145
|
-
*/
|
|
146
|
-
private iceGatheringState;
|
|
91
|
+
private getUnreachableClusters;
|
|
147
92
|
/**
|
|
148
93
|
* Make a log of unreachable clusters.
|
|
149
94
|
* @returns {undefined}
|
|
@@ -152,43 +97,9 @@ export default class Reachability {
|
|
|
152
97
|
*/
|
|
153
98
|
private logUnreachableClusters;
|
|
154
99
|
/**
|
|
155
|
-
*
|
|
156
|
-
* @param {
|
|
157
|
-
* @returns {
|
|
158
|
-
* @protected
|
|
159
|
-
* @memberof Reachability
|
|
160
|
-
*/
|
|
161
|
-
protected parseIceResultsToInternalReachabilityResults(iceResults: Array<ICECandidateResult>): InternalReachabilityResults;
|
|
162
|
-
/**
|
|
163
|
-
* fetches reachability data
|
|
164
|
-
* @param {object} clusterList
|
|
165
|
-
* @returns {Promise<InternalReachabilityResults>} reachability check results
|
|
166
|
-
* @private
|
|
167
|
-
* @memberof Reachability
|
|
168
|
-
*/
|
|
169
|
-
private performReachabilityCheck;
|
|
170
|
-
/**
|
|
171
|
-
* Adds public IP (client media IPs)
|
|
172
|
-
* @param {RTCPeerConnection} peerConnection
|
|
173
|
-
* @param {string} publicIP
|
|
174
|
-
* @returns {void}
|
|
175
|
-
*/
|
|
176
|
-
protected addPublicIP(peerConnection: RTCPeerConnection, publicIP?: string | null): void;
|
|
177
|
-
/**
|
|
178
|
-
* Records latency and closes the peerConnection
|
|
179
|
-
* @param {RTCPeerConnection} peerConnection
|
|
180
|
-
* @param {number} elapsed Latency in milliseconds
|
|
181
|
-
* @returns {undefined}
|
|
182
|
-
* @private
|
|
183
|
-
* @memberof Reachability
|
|
184
|
-
*/
|
|
185
|
-
private setLatencyAndClose;
|
|
186
|
-
/**
|
|
187
|
-
* utility function
|
|
188
|
-
* @returns {undefined}
|
|
189
|
-
* @private
|
|
190
|
-
* @memberof Reachability
|
|
100
|
+
* Performs reachability checks for all clusters
|
|
101
|
+
* @param {ClusterList} clusterList
|
|
102
|
+
* @returns {Promise<ReachabilityResults>} reachability check results
|
|
191
103
|
*/
|
|
192
|
-
private
|
|
104
|
+
private performReachabilityChecks;
|
|
193
105
|
}
|
|
194
|
-
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a stun url to a turn url
|
|
3
|
+
*
|
|
4
|
+
* @param {string} stunUrl url of a stun server
|
|
5
|
+
* @param {'tcp'|'udp'} protocol what protocol to use for the turn server
|
|
6
|
+
* @returns {string} url of a turn server
|
|
7
|
+
*/
|
|
8
|
+
export declare function convertStunUrlToTurn(stunUrl: string, protocol: 'udp' | 'tcp'): string;
|
package/dist/webinar/index.js
CHANGED
|
@@ -62,7 +62,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
|
|
|
62
62
|
updateCanManageWebcast: function updateCanManageWebcast(canManageWebcast) {
|
|
63
63
|
this.set('canManageWebcast', canManageWebcast);
|
|
64
64
|
},
|
|
65
|
-
version: "3.0.0-beta.
|
|
65
|
+
version: "3.0.0-beta.339"
|
|
66
66
|
});
|
|
67
67
|
var _default = Webinar;
|
|
68
68
|
exports.default = _default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/plugin-meetings",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.339",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "Cisco EULA (https://www.cisco.com/c/en/us/products/end-user-license-agreement.html)",
|
|
6
6
|
"contributors": [
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@peculiar/webcrypto": "^1.4.3",
|
|
36
|
-
"@webex/plugin-meetings": "3.0.0-beta.
|
|
37
|
-
"@webex/test-helper-chai": "3.0.0-beta.
|
|
38
|
-
"@webex/test-helper-mocha": "3.0.0-beta.
|
|
39
|
-
"@webex/test-helper-mock-webex": "3.0.0-beta.
|
|
40
|
-
"@webex/test-helper-retry": "3.0.0-beta.
|
|
41
|
-
"@webex/test-helper-test-users": "3.0.0-beta.
|
|
36
|
+
"@webex/plugin-meetings": "3.0.0-beta.339",
|
|
37
|
+
"@webex/test-helper-chai": "3.0.0-beta.339",
|
|
38
|
+
"@webex/test-helper-mocha": "3.0.0-beta.339",
|
|
39
|
+
"@webex/test-helper-mock-webex": "3.0.0-beta.339",
|
|
40
|
+
"@webex/test-helper-retry": "3.0.0-beta.339",
|
|
41
|
+
"@webex/test-helper-test-users": "3.0.0-beta.339",
|
|
42
42
|
"chai": "^4.3.4",
|
|
43
43
|
"chai-as-promised": "^7.1.1",
|
|
44
44
|
"jsdom-global": "3.0.2",
|
|
@@ -47,19 +47,19 @@
|
|
|
47
47
|
"typescript": "^4.7.4"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@webex/common": "3.0.0-beta.
|
|
51
|
-
"@webex/internal-media-core": "2.2.
|
|
52
|
-
"@webex/internal-plugin-conversation": "3.0.0-beta.
|
|
53
|
-
"@webex/internal-plugin-device": "3.0.0-beta.
|
|
54
|
-
"@webex/internal-plugin-llm": "3.0.0-beta.
|
|
55
|
-
"@webex/internal-plugin-mercury": "3.0.0-beta.
|
|
56
|
-
"@webex/internal-plugin-metrics": "3.0.0-beta.
|
|
57
|
-
"@webex/internal-plugin-support": "3.0.0-beta.
|
|
58
|
-
"@webex/internal-plugin-user": "3.0.0-beta.
|
|
59
|
-
"@webex/media-helpers": "3.0.0-beta.
|
|
60
|
-
"@webex/plugin-people": "3.0.0-beta.
|
|
61
|
-
"@webex/plugin-rooms": "3.0.0-beta.
|
|
62
|
-
"@webex/webex-core": "3.0.0-beta.
|
|
50
|
+
"@webex/common": "3.0.0-beta.339",
|
|
51
|
+
"@webex/internal-media-core": "2.2.4",
|
|
52
|
+
"@webex/internal-plugin-conversation": "3.0.0-beta.339",
|
|
53
|
+
"@webex/internal-plugin-device": "3.0.0-beta.339",
|
|
54
|
+
"@webex/internal-plugin-llm": "3.0.0-beta.339",
|
|
55
|
+
"@webex/internal-plugin-mercury": "3.0.0-beta.339",
|
|
56
|
+
"@webex/internal-plugin-metrics": "3.0.0-beta.339",
|
|
57
|
+
"@webex/internal-plugin-support": "3.0.0-beta.339",
|
|
58
|
+
"@webex/internal-plugin-user": "3.0.0-beta.339",
|
|
59
|
+
"@webex/media-helpers": "3.0.0-beta.339",
|
|
60
|
+
"@webex/plugin-people": "3.0.0-beta.339",
|
|
61
|
+
"@webex/plugin-rooms": "3.0.0-beta.339",
|
|
62
|
+
"@webex/webex-core": "3.0.0-beta.339",
|
|
63
63
|
"ampersand-collection": "^2.0.2",
|
|
64
64
|
"bowser": "^2.11.0",
|
|
65
65
|
"btoa": "^1.2.1",
|
package/src/config.ts
CHANGED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import {Defer} from '@webex/common';
|
|
2
|
+
|
|
3
|
+
import LoggerProxy from '../common/logs/logger-proxy';
|
|
4
|
+
import {ClusterNode} from './request';
|
|
5
|
+
import {convertStunUrlToTurn} from './util';
|
|
6
|
+
|
|
7
|
+
import {ICE_GATHERING_STATE, CONNECTION_STATE} from '../constants';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_TIMEOUT = 3000;
|
|
10
|
+
const VIDEO_MESH_TIMEOUT = 1000;
|
|
11
|
+
|
|
12
|
+
// result for a specific transport protocol (like udp or tcp)
|
|
13
|
+
export type TransportResult = {
|
|
14
|
+
result: 'reachable' | 'unreachable' | 'untested';
|
|
15
|
+
latencyInMilliseconds?: number; // amount of time it took to get the first ICE candidate
|
|
16
|
+
clientMediaIPs?: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// reachability result for a specific media cluster
|
|
20
|
+
export type ClusterReachabilityResult = {
|
|
21
|
+
udp: TransportResult;
|
|
22
|
+
tcp: TransportResult;
|
|
23
|
+
xtls: TransportResult;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A class that handles reachability checks for a single cluster.
|
|
28
|
+
*/
|
|
29
|
+
export class ClusterReachability {
|
|
30
|
+
private numUdpUrls: number;
|
|
31
|
+
private numTcpUrls: number;
|
|
32
|
+
private result: ClusterReachabilityResult;
|
|
33
|
+
private pc?: RTCPeerConnection;
|
|
34
|
+
private defer: Defer; // this defer is resolved once reachability checks for this cluster are completed
|
|
35
|
+
private startTimestamp: number;
|
|
36
|
+
public readonly isVideoMesh: boolean;
|
|
37
|
+
public readonly name;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Constructor for ClusterReachability
|
|
41
|
+
* @param {string} name cluster name
|
|
42
|
+
* @param {ClusterNode} clusterInfo information about the media cluster
|
|
43
|
+
*/
|
|
44
|
+
constructor(name: string, clusterInfo: ClusterNode) {
|
|
45
|
+
this.name = name;
|
|
46
|
+
this.isVideoMesh = clusterInfo.isVideoMesh;
|
|
47
|
+
this.numUdpUrls = clusterInfo.udp.length;
|
|
48
|
+
this.numTcpUrls = clusterInfo.tcp.length;
|
|
49
|
+
|
|
50
|
+
this.pc = this.createPeerConnection(clusterInfo);
|
|
51
|
+
|
|
52
|
+
this.defer = new Defer();
|
|
53
|
+
this.result = {
|
|
54
|
+
udp: {
|
|
55
|
+
result: 'untested',
|
|
56
|
+
},
|
|
57
|
+
tcp: {
|
|
58
|
+
result: 'untested',
|
|
59
|
+
},
|
|
60
|
+
xtls: {
|
|
61
|
+
result: 'untested',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Gets total elapsed time, can be called only after start() is called
|
|
68
|
+
* @returns {Number} Milliseconds
|
|
69
|
+
*/
|
|
70
|
+
private getElapsedTime() {
|
|
71
|
+
return Math.round(performance.now() - this.startTimestamp);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Generate peerConnection config settings
|
|
76
|
+
* @param {ClusterNode} cluster
|
|
77
|
+
* @returns {RTCConfiguration} peerConnectionConfig
|
|
78
|
+
*/
|
|
79
|
+
private buildPeerConnectionConfig(cluster: ClusterNode): RTCConfiguration {
|
|
80
|
+
const udpIceServers = cluster.udp.map((url) => ({
|
|
81
|
+
username: '',
|
|
82
|
+
credential: '',
|
|
83
|
+
urls: [url],
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
// STUN servers are contacted only using UDP, so in order to test TCP reachability
|
|
87
|
+
// we pretend that Linus is a TURN server, because we can explicitly say "transport=tcp" in TURN urls.
|
|
88
|
+
// We then check for relay candidates to know if TURN-TCP worked (see registerIceCandidateListener()).
|
|
89
|
+
const tcpIceServers = cluster.tcp.map((urlString: string) => {
|
|
90
|
+
return {
|
|
91
|
+
username: 'webexturnreachuser',
|
|
92
|
+
credential: 'webexturnreachpwd',
|
|
93
|
+
urls: [convertStunUrlToTurn(urlString, 'tcp')],
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
iceServers: [...udpIceServers, ...tcpIceServers],
|
|
99
|
+
iceCandidatePoolSize: 0,
|
|
100
|
+
iceTransportPolicy: 'all',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates an RTCPeerConnection
|
|
106
|
+
* @param {ClusterNode} clusterInfo information about the media cluster
|
|
107
|
+
* @returns {RTCPeerConnection} peerConnection
|
|
108
|
+
*/
|
|
109
|
+
private createPeerConnection(clusterInfo: ClusterNode) {
|
|
110
|
+
try {
|
|
111
|
+
const config = this.buildPeerConnectionConfig(clusterInfo);
|
|
112
|
+
|
|
113
|
+
const peerConnection = new RTCPeerConnection(config);
|
|
114
|
+
|
|
115
|
+
return peerConnection;
|
|
116
|
+
} catch (peerConnectionError) {
|
|
117
|
+
LoggerProxy.logger.warn(
|
|
118
|
+
`Reachability:index#createPeerConnection --> Error creating peerConnection:`,
|
|
119
|
+
peerConnectionError
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @returns {ClusterReachabilityResult} reachability result for this cluster
|
|
128
|
+
*/
|
|
129
|
+
getResult() {
|
|
130
|
+
return this.result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Closes the peerConnection
|
|
135
|
+
*
|
|
136
|
+
* @returns {void}
|
|
137
|
+
*/
|
|
138
|
+
private closePeerConnection() {
|
|
139
|
+
if (this.pc) {
|
|
140
|
+
this.pc.onicecandidate = null;
|
|
141
|
+
this.pc.onicegatheringstatechange = null;
|
|
142
|
+
this.pc.close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolves the defer, indicating that reachability checks for this cluster are completed
|
|
148
|
+
*
|
|
149
|
+
* @returns {void}
|
|
150
|
+
*/
|
|
151
|
+
private finishReachabilityCheck() {
|
|
152
|
+
this.defer.resolve();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Adds public IP (client media IPs)
|
|
157
|
+
* @param {string} protocol
|
|
158
|
+
* @param {string} publicIP
|
|
159
|
+
* @returns {void}
|
|
160
|
+
*/
|
|
161
|
+
private addPublicIP(protocol: 'udp' | 'tcp', publicIP?: string | null) {
|
|
162
|
+
const result = this.result[protocol];
|
|
163
|
+
|
|
164
|
+
if (publicIP) {
|
|
165
|
+
if (result.clientMediaIPs) {
|
|
166
|
+
if (!result.clientMediaIPs.includes(publicIP)) {
|
|
167
|
+
result.clientMediaIPs.push(publicIP);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
result.clientMediaIPs = [publicIP];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Registers a listener for the iceGatheringStateChange event
|
|
177
|
+
*
|
|
178
|
+
* @returns {void}
|
|
179
|
+
*/
|
|
180
|
+
private registerIceGatheringStateChangeListener() {
|
|
181
|
+
this.pc.onicegatheringstatechange = () => {
|
|
182
|
+
const {COMPLETE} = ICE_GATHERING_STATE;
|
|
183
|
+
|
|
184
|
+
if (this.pc.iceConnectionState === COMPLETE) {
|
|
185
|
+
this.closePeerConnection();
|
|
186
|
+
this.finishReachabilityCheck();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Checks if we have the results for all the protocols (UDP and TCP)
|
|
193
|
+
*
|
|
194
|
+
* @returns {boolean} true if we have all results, false otherwise
|
|
195
|
+
*/
|
|
196
|
+
private haveWeGotAllResults(): boolean {
|
|
197
|
+
return ['udp', 'tcp'].every(
|
|
198
|
+
(protocol) =>
|
|
199
|
+
this.result[protocol].result === 'reachable' || this.result[protocol].result === 'untested'
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Stores the latency in the result for the given protocol and marks it as reachable
|
|
205
|
+
*
|
|
206
|
+
* @param {string} protocol
|
|
207
|
+
* @param {number} latency
|
|
208
|
+
* @returns {void}
|
|
209
|
+
*/
|
|
210
|
+
private storeLatencyResult(protocol: 'udp' | 'tcp', latency: number) {
|
|
211
|
+
const result = this.result[protocol];
|
|
212
|
+
|
|
213
|
+
if (result.latencyInMilliseconds === undefined) {
|
|
214
|
+
LoggerProxy.logger.log(
|
|
215
|
+
// @ts-ignore
|
|
216
|
+
`Reachability:index#storeLatencyResult --> Successfully reached ${this.name} over ${protocol}: ${latency}ms`
|
|
217
|
+
);
|
|
218
|
+
result.latencyInMilliseconds = latency;
|
|
219
|
+
result.result = 'reachable';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Registers a listener for the icecandidate event
|
|
225
|
+
*
|
|
226
|
+
* @returns {void}
|
|
227
|
+
*/
|
|
228
|
+
private registerIceCandidateListener() {
|
|
229
|
+
this.pc.onicecandidate = (e) => {
|
|
230
|
+
const CANDIDATE_TYPES = {
|
|
231
|
+
SERVER_REFLEXIVE: 'srflx',
|
|
232
|
+
RELAY: 'relay',
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
if (e.candidate) {
|
|
236
|
+
if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
|
|
237
|
+
this.storeLatencyResult('udp', this.getElapsedTime());
|
|
238
|
+
this.addPublicIP('udp', e.candidate.address);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
|
|
242
|
+
this.storeLatencyResult('tcp', this.getElapsedTime());
|
|
243
|
+
// we don't add public IP for TCP, because in the case of relay candidates
|
|
244
|
+
// e.candidate.address is the TURN server address, not the client's public IP
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (this.haveWeGotAllResults()) {
|
|
248
|
+
this.closePeerConnection();
|
|
249
|
+
this.finishReachabilityCheck();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Starts the process of doing UDP and TCP reachability checks on the media cluster.
|
|
257
|
+
* XTLS reachability checking is not supported.
|
|
258
|
+
*
|
|
259
|
+
* @returns {Promise}
|
|
260
|
+
*/
|
|
261
|
+
async start(): Promise<ClusterReachabilityResult> {
|
|
262
|
+
if (!this.pc) {
|
|
263
|
+
LoggerProxy.logger.warn(
|
|
264
|
+
`Reachability:ClusterReachability#start --> Error: peerConnection is undefined`
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return this.result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Initialize this.result as saying that nothing is reachable.
|
|
271
|
+
// It will get updated as we go along and successfully gather ICE candidates.
|
|
272
|
+
this.result.udp = {
|
|
273
|
+
result: this.numUdpUrls > 0 ? 'unreachable' : 'untested',
|
|
274
|
+
};
|
|
275
|
+
this.result.tcp = {
|
|
276
|
+
result: this.numTcpUrls > 0 ? 'unreachable' : 'untested',
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const offer = await this.pc.createOffer({offerToReceiveAudio: true});
|
|
281
|
+
|
|
282
|
+
this.startTimestamp = performance.now();
|
|
283
|
+
|
|
284
|
+
// not awaiting the next call on purpose, because we're not sending the offer anywhere and there won't be any answer
|
|
285
|
+
// we just need to make this call to trigger the ICE gathering process
|
|
286
|
+
this.pc.setLocalDescription(offer);
|
|
287
|
+
|
|
288
|
+
await this.gatherIceCandidates();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
LoggerProxy.logger.warn(`Reachability:ClusterReachability#start --> Error: `, error);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return this.result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Starts the process of gathering ICE candidates
|
|
298
|
+
*
|
|
299
|
+
* @returns {Promise} promise that's resolved once reachability checks for this cluster are completed or timeout is reached
|
|
300
|
+
*/
|
|
301
|
+
private gatherIceCandidates() {
|
|
302
|
+
const timeout = this.isVideoMesh ? VIDEO_MESH_TIMEOUT : DEFAULT_TIMEOUT;
|
|
303
|
+
|
|
304
|
+
this.registerIceGatheringStateChangeListener();
|
|
305
|
+
this.registerIceCandidateListener();
|
|
306
|
+
|
|
307
|
+
// Set maximum timeout
|
|
308
|
+
setTimeout(() => {
|
|
309
|
+
const {CLOSED} = CONNECTION_STATE;
|
|
310
|
+
|
|
311
|
+
// Close any open peerConnections
|
|
312
|
+
if (this.pc.connectionState !== CLOSED) {
|
|
313
|
+
this.closePeerConnection();
|
|
314
|
+
this.finishReachabilityCheck();
|
|
315
|
+
}
|
|
316
|
+
}, timeout);
|
|
317
|
+
|
|
318
|
+
return this.defer.promise;
|
|
319
|
+
}
|
|
320
|
+
}
|