@webex/plugin-meetings 2.60.0-next.9 → 2.60.1-next.1
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.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/index.d.ts +2 -0
- package/dist/interceptors/index.js +15 -0
- package/dist/interceptors/index.js.map +1 -0
- package/dist/interceptors/locusRetry.d.ts +27 -0
- package/dist/interceptors/locusRetry.js +94 -0
- package/dist/interceptors/locusRetry.js.map +1 -0
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +6 -2
- package/dist/meeting/index.js.map +1 -1
- package/dist/meetings/index.d.ts +1 -11
- package/dist/meetings/index.js +4 -3
- package/dist/meetings/index.js.map +1 -1
- package/dist/reachability/clusterReachability.d.ts +109 -0
- package/dist/reachability/clusterReachability.js +357 -0
- package/dist/reachability/clusterReachability.js.map +1 -0
- package/dist/reachability/index.d.ts +32 -121
- package/dist/reachability/index.js +173 -459
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/util.d.ts +8 -0
- package/dist/reachability/util.js +29 -0
- package/dist/reachability/util.js.map +1 -0
- package/dist/statsAnalyzer/index.d.ts +22 -0
- package/dist/statsAnalyzer/index.js +60 -0
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +4 -0
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +25 -22
- package/src/config.ts +1 -0
- package/src/index.ts +4 -0
- package/src/interceptors/index.ts +3 -0
- package/src/interceptors/locusRetry.ts +67 -0
- package/src/meeting/index.ts +10 -2
- package/src/meetings/index.ts +4 -3
- package/src/reachability/clusterReachability.ts +320 -0
- package/src/reachability/index.ts +124 -421
- package/src/reachability/util.ts +24 -0
- package/src/statsAnalyzer/index.ts +64 -1
- package/src/statsAnalyzer/mqaUtil.ts +4 -0
- package/test/unit/spec/interceptors/locusRetry.ts +131 -0
- package/test/unit/spec/meeting/index.js +8 -1
- package/test/unit/spec/meetings/index.js +28 -25
- 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 +100 -20
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/* eslint-disable class-methods-use-this */
|
|
6
|
-
|
|
7
|
-
import {uniq, mapValues, pick} from 'lodash';
|
|
6
|
+
import {mapValues} from 'lodash';
|
|
8
7
|
|
|
9
8
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
10
9
|
import MeetingUtil from '../meeting/util';
|
|
11
10
|
|
|
12
|
-
import {
|
|
11
|
+
import {REACHABILITY} from '../constants';
|
|
13
12
|
|
|
14
|
-
import ReachabilityRequest from './request';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
import ReachabilityRequest, {ClusterList} from './request';
|
|
14
|
+
import {
|
|
15
|
+
ClusterReachability,
|
|
16
|
+
ClusterReachabilityResult,
|
|
17
|
+
TransportResult,
|
|
18
|
+
} from './clusterReachability';
|
|
18
19
|
|
|
19
20
|
export type ReachabilityMetrics = {
|
|
20
21
|
reachability_public_udp_success: number;
|
|
@@ -27,39 +28,34 @@ export type ReachabilityMetrics = {
|
|
|
27
28
|
reachability_vmn_tcp_failed: number;
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* This is the type that matches what backend expects us to send to them. It is a bit weird, because
|
|
33
|
+
* it uses strings instead of booleans and numbers, but that's what they require.
|
|
34
|
+
*/
|
|
35
|
+
export type TransportResultForBackend = {
|
|
32
36
|
reachable?: 'true' | 'false';
|
|
33
37
|
latencyInMilliseconds?: string;
|
|
34
38
|
clientMediaIPs?: string[];
|
|
35
39
|
untested?: 'true';
|
|
36
40
|
};
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
xtls: {
|
|
43
|
-
untested: 'true';
|
|
44
|
-
};
|
|
42
|
+
export type ReachabilityResultForBackend = {
|
|
43
|
+
udp: TransportResultForBackend;
|
|
44
|
+
tcp: TransportResultForBackend;
|
|
45
|
+
xtls: TransportResultForBackend;
|
|
45
46
|
};
|
|
47
|
+
|
|
46
48
|
// this is the type that is required by the backend when we send them reachability results
|
|
47
|
-
export type
|
|
49
|
+
export type ReachabilityResultsForBackend = Record<string, ReachabilityResultForBackend>;
|
|
48
50
|
|
|
49
51
|
// this is the type used by Reachability class internally and stored in local storage
|
|
50
|
-
type
|
|
52
|
+
export type ReachabilityResults = Record<
|
|
51
53
|
string,
|
|
52
|
-
|
|
54
|
+
ClusterReachabilityResult & {
|
|
53
55
|
isVideoMesh?: boolean;
|
|
54
56
|
}
|
|
55
57
|
>;
|
|
56
58
|
|
|
57
|
-
export type ICECandidateResult = {
|
|
58
|
-
clusterId: string;
|
|
59
|
-
isVideoMesh: boolean;
|
|
60
|
-
elapsed?: string | null;
|
|
61
|
-
publicIPs?: string[];
|
|
62
|
-
};
|
|
63
59
|
/**
|
|
64
60
|
* @class Reachability
|
|
65
61
|
* @export
|
|
@@ -67,8 +63,10 @@ export type ICECandidateResult = {
|
|
|
67
63
|
export default class Reachability {
|
|
68
64
|
namespace = REACHABILITY.namespace;
|
|
69
65
|
webex: object;
|
|
70
|
-
reachabilityRequest:
|
|
71
|
-
|
|
66
|
+
reachabilityRequest: ReachabilityRequest;
|
|
67
|
+
clusterReachability: {
|
|
68
|
+
[key: string]: ClusterReachability;
|
|
69
|
+
};
|
|
72
70
|
|
|
73
71
|
/**
|
|
74
72
|
* Creates an instance of Reachability.
|
|
@@ -87,26 +85,16 @@ export default class Reachability {
|
|
|
87
85
|
*/
|
|
88
86
|
this.reachabilityRequest = new ReachabilityRequest(this.webex);
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
* internal object of clusters latency results
|
|
92
|
-
* @instance
|
|
93
|
-
* @type {object}
|
|
94
|
-
* @private
|
|
95
|
-
* @memberof Reachability
|
|
96
|
-
*/
|
|
97
|
-
this.clusterLatencyResults = {};
|
|
88
|
+
this.clusterReachability = {};
|
|
98
89
|
}
|
|
99
90
|
|
|
100
91
|
/**
|
|
101
|
-
*
|
|
102
|
-
* @returns {
|
|
92
|
+
* Gets a list of media clusters from the backend and performs reachability checks on all the clusters
|
|
93
|
+
* @returns {Promise<ReachabilityResults>} reachability results
|
|
103
94
|
* @public
|
|
104
|
-
* @async
|
|
105
95
|
* @memberof Reachability
|
|
106
96
|
*/
|
|
107
|
-
public async gatherReachability(): Promise<
|
|
108
|
-
this.setup();
|
|
109
|
-
|
|
97
|
+
public async gatherReachability(): Promise<ReachabilityResults> {
|
|
110
98
|
// Remove stored reachability results to ensure no stale data
|
|
111
99
|
// @ts-ignore
|
|
112
100
|
await this.webex.boundedStorage.del(this.namespace, REACHABILITY.localStorageResult);
|
|
@@ -120,7 +108,7 @@ export default class Reachability {
|
|
|
120
108
|
);
|
|
121
109
|
|
|
122
110
|
// Perform Reachability Check
|
|
123
|
-
const results = await this.
|
|
111
|
+
const results = await this.performReachabilityChecks(clusters);
|
|
124
112
|
|
|
125
113
|
// @ts-ignore
|
|
126
114
|
await this.webex.boundedStorage.put(
|
|
@@ -140,10 +128,8 @@ export default class Reachability {
|
|
|
140
128
|
);
|
|
141
129
|
|
|
142
130
|
return results;
|
|
143
|
-
} catch (
|
|
144
|
-
LoggerProxy.logger.error(
|
|
145
|
-
`Reachability:index#gatherReachability --> Error in calling getClusters(): ${getClusterError}`
|
|
146
|
-
);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
LoggerProxy.logger.error(`Reachability:index#gatherReachability --> Error:`, error);
|
|
147
133
|
|
|
148
134
|
return {};
|
|
149
135
|
}
|
|
@@ -167,13 +153,13 @@ export default class Reachability {
|
|
|
167
153
|
reachability_vmn_tcp_failed: 0,
|
|
168
154
|
};
|
|
169
155
|
|
|
170
|
-
const updateStats = (clusterType: 'public' | 'vmn', result:
|
|
171
|
-
if (result.udp
|
|
172
|
-
const outcome = result.udp.
|
|
156
|
+
const updateStats = (clusterType: 'public' | 'vmn', result: ClusterReachabilityResult) => {
|
|
157
|
+
if (result.udp && result.udp.result !== 'untested') {
|
|
158
|
+
const outcome = result.udp.result === 'reachable' ? 'success' : 'failed';
|
|
173
159
|
stats[`reachability_${clusterType}_udp_${outcome}`] += 1;
|
|
174
160
|
}
|
|
175
|
-
if (result.tcp
|
|
176
|
-
const outcome = result.tcp.
|
|
161
|
+
if (result.tcp && result.tcp.result !== 'untested') {
|
|
162
|
+
const outcome = result.tcp.result === 'reachable' ? 'success' : 'failed';
|
|
177
163
|
stats[`reachability_${clusterType}_tcp_${outcome}`] += 1;
|
|
178
164
|
}
|
|
179
165
|
};
|
|
@@ -185,9 +171,9 @@ export default class Reachability {
|
|
|
185
171
|
REACHABILITY.localStorageResult
|
|
186
172
|
);
|
|
187
173
|
|
|
188
|
-
const
|
|
174
|
+
const results: ReachabilityResults = JSON.parse(resultsJson);
|
|
189
175
|
|
|
190
|
-
Object.values(
|
|
176
|
+
Object.values(results).forEach((result) => {
|
|
191
177
|
updateStats(result.isVideoMesh ? 'vmn' : 'public', result);
|
|
192
178
|
});
|
|
193
179
|
} catch (e) {
|
|
@@ -201,16 +187,49 @@ export default class Reachability {
|
|
|
201
187
|
return stats;
|
|
202
188
|
}
|
|
203
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Maps our internal transport result to the format that backend expects
|
|
192
|
+
* @param {TransportResult} transportResult
|
|
193
|
+
* @returns {TransportResultForBackend}
|
|
194
|
+
*/
|
|
195
|
+
private mapTransportResultToBackendDataFormat(
|
|
196
|
+
transportResult: TransportResult
|
|
197
|
+
): TransportResultForBackend {
|
|
198
|
+
const output: TransportResultForBackend = {};
|
|
199
|
+
|
|
200
|
+
for (const [key, value] of Object.entries(transportResult)) {
|
|
201
|
+
switch (key) {
|
|
202
|
+
case 'result':
|
|
203
|
+
switch (value) {
|
|
204
|
+
case 'reachable':
|
|
205
|
+
output.reachable = 'true';
|
|
206
|
+
break;
|
|
207
|
+
case 'unreachable':
|
|
208
|
+
output.reachable = 'false';
|
|
209
|
+
break;
|
|
210
|
+
case 'untested':
|
|
211
|
+
output.untested = 'true';
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
case 'latencyInMilliseconds':
|
|
216
|
+
output.latencyInMilliseconds = value.toString();
|
|
217
|
+
break;
|
|
218
|
+
default:
|
|
219
|
+
output[key] = value;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return output;
|
|
224
|
+
}
|
|
225
|
+
|
|
204
226
|
/**
|
|
205
227
|
* Reachability results as an object in the format that backend expects
|
|
206
228
|
*
|
|
207
229
|
* @returns {any} reachability results that need to be sent to the backend
|
|
208
230
|
*/
|
|
209
|
-
async getReachabilityResults(): Promise<
|
|
210
|
-
let results:
|
|
211
|
-
|
|
212
|
-
// these are the only props that backend needs in the reachability results:
|
|
213
|
-
const reachabilityResultsProps: Array<keyof ReachabilityResult> = ['udp', 'tcp', 'xtls'];
|
|
231
|
+
async getReachabilityResults(): Promise<ReachabilityResultsForBackend | undefined> {
|
|
232
|
+
let results: ReachabilityResultsForBackend;
|
|
214
233
|
|
|
215
234
|
try {
|
|
216
235
|
// @ts-ignore
|
|
@@ -219,9 +238,15 @@ export default class Reachability {
|
|
|
219
238
|
REACHABILITY.localStorageResult
|
|
220
239
|
);
|
|
221
240
|
|
|
222
|
-
const
|
|
241
|
+
const allClusterResults: ReachabilityResults = JSON.parse(resultsJson);
|
|
223
242
|
|
|
224
|
-
results = mapValues(
|
|
243
|
+
results = mapValues(allClusterResults, (clusterResult) => ({
|
|
244
|
+
udp: this.mapTransportResultToBackendDataFormat(clusterResult.udp || {result: 'untested'}),
|
|
245
|
+
tcp: this.mapTransportResultToBackendDataFormat(clusterResult.tcp || {result: 'untested'}),
|
|
246
|
+
xtls: this.mapTransportResultToBackendDataFormat(
|
|
247
|
+
clusterResult.xtls || {result: 'untested'}
|
|
248
|
+
),
|
|
249
|
+
}));
|
|
225
250
|
} catch (e) {
|
|
226
251
|
// empty storage, that's ok
|
|
227
252
|
LoggerProxy.logger.warn(
|
|
@@ -248,12 +273,12 @@ export default class Reachability {
|
|
|
248
273
|
|
|
249
274
|
if (reachabilityData) {
|
|
250
275
|
try {
|
|
251
|
-
const reachabilityResults:
|
|
276
|
+
const reachabilityResults: ReachabilityResults = JSON.parse(reachabilityData);
|
|
252
277
|
|
|
253
278
|
reachable = Object.values(reachabilityResults).some(
|
|
254
279
|
(result) =>
|
|
255
280
|
!result.isVideoMesh &&
|
|
256
|
-
(result.udp?.
|
|
281
|
+
(result.udp?.result === 'reachable' || result.tcp?.result === 'reachable')
|
|
257
282
|
);
|
|
258
283
|
} catch (e) {
|
|
259
284
|
LoggerProxy.logger.error(
|
|
@@ -265,246 +290,29 @@ export default class Reachability {
|
|
|
265
290
|
return reachable;
|
|
266
291
|
}
|
|
267
292
|
|
|
268
|
-
/**
|
|
269
|
-
* Generate peerConnection config settings
|
|
270
|
-
* @param {object} cluster
|
|
271
|
-
* @returns {object} peerConnectionConfig
|
|
272
|
-
* @private
|
|
273
|
-
* @memberof Reachability
|
|
274
|
-
*/
|
|
275
|
-
private buildPeerConnectionConfig(cluster: any) {
|
|
276
|
-
const iceServers = uniq(cluster.udp).map((url) => ({
|
|
277
|
-
username: '',
|
|
278
|
-
credential: '',
|
|
279
|
-
urls: [url],
|
|
280
|
-
}));
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
iceServers: [...iceServers],
|
|
284
|
-
iceCandidatePoolSize: '0',
|
|
285
|
-
iceTransportPolicy: 'all',
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Creates an RTCPeerConnection
|
|
291
|
-
* @param {object} cluster
|
|
292
|
-
* @returns {RTCPeerConnection} peerConnection
|
|
293
|
-
* @private
|
|
294
|
-
* @memberof Reachability
|
|
295
|
-
*/
|
|
296
|
-
private createPeerConnection(cluster: any) {
|
|
297
|
-
const {key, config} = cluster;
|
|
298
|
-
|
|
299
|
-
try {
|
|
300
|
-
const peerConnection = new window.RTCPeerConnection(config);
|
|
301
|
-
|
|
302
|
-
// @ts-ignore
|
|
303
|
-
peerConnection.key = key;
|
|
304
|
-
|
|
305
|
-
return peerConnection;
|
|
306
|
-
} catch (peerConnectionError) {
|
|
307
|
-
LoggerProxy.logger.log(
|
|
308
|
-
`Reachability:index#createPeerConnection --> Error creating peerConnection: ${peerConnectionError}`
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Gets total elapsed time
|
|
317
|
-
* @param {RTCPeerConnection} peerConnection
|
|
318
|
-
* @returns {Number} Milliseconds
|
|
319
|
-
* @private
|
|
320
|
-
* @memberof Reachability
|
|
321
|
-
*/
|
|
322
|
-
private getElapsedTime(peerConnection: any) {
|
|
323
|
-
const startTime = peerConnection.begin;
|
|
324
|
-
|
|
325
|
-
delete peerConnection.begin;
|
|
326
|
-
|
|
327
|
-
return Date.now() - startTime;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* creates offer and generates localSDP
|
|
332
|
-
* @param {object} clusterList cluster List
|
|
333
|
-
* @returns {Promise} Reachability latency results
|
|
334
|
-
* @private
|
|
335
|
-
* @memberof Reachability
|
|
336
|
-
*/
|
|
337
|
-
private getLocalSDPForClusters(clusterList: object): Promise<InternalReachabilityResults> {
|
|
338
|
-
let clusters: any[] = [...Object.keys(clusterList)];
|
|
339
|
-
|
|
340
|
-
clusters = clusters.map(async (key) => {
|
|
341
|
-
const cluster = clusterList[key];
|
|
342
|
-
const config = this.buildPeerConnectionConfig(cluster);
|
|
343
|
-
const peerConnection = this.createPeerConnection({key, config});
|
|
344
|
-
const description = await peerConnection.createOffer({offerToReceiveAudio: true});
|
|
345
|
-
|
|
346
|
-
// @ts-ignore
|
|
347
|
-
peerConnection.begin = Date.now();
|
|
348
|
-
peerConnection.setLocalDescription(description);
|
|
349
|
-
|
|
350
|
-
return this.iceGatheringState(peerConnection, cluster.isVideoMesh).catch(
|
|
351
|
-
(iceGatheringStateError) => {
|
|
352
|
-
LoggerProxy.logger.log(
|
|
353
|
-
`Reachability:index#getLocalSDPForClusters --> Error in getLocalSDP : ${iceGatheringStateError}`
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
return Promise.all(clusters)
|
|
360
|
-
.then(this.parseIceResultsToInternalReachabilityResults)
|
|
361
|
-
.then((reachabilityLatencyResults) => {
|
|
362
|
-
this.logUnreachableClusters();
|
|
363
|
-
|
|
364
|
-
// return results
|
|
365
|
-
return reachabilityLatencyResults;
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
293
|
/**
|
|
370
294
|
* Get list of all unreachable clusters
|
|
371
295
|
* @returns {array} Unreachable clusters
|
|
372
296
|
* @private
|
|
373
297
|
* @memberof Reachability
|
|
374
298
|
*/
|
|
375
|
-
private
|
|
299
|
+
private getUnreachableClusters(): Array<{name: string; protocol: string}> {
|
|
376
300
|
const unreachableList = [];
|
|
377
|
-
const clusters = this.clusterLatencyResults;
|
|
378
301
|
|
|
379
|
-
Object.
|
|
380
|
-
const
|
|
302
|
+
Object.entries(this.clusterReachability).forEach(([key, clusterReachability]) => {
|
|
303
|
+
const result = clusterReachability.getResult();
|
|
381
304
|
|
|
382
|
-
if (
|
|
383
|
-
unreachableList.push(key);
|
|
305
|
+
if (result.udp.result === 'unreachable') {
|
|
306
|
+
unreachableList.push({name: key, protocol: 'udp'});
|
|
307
|
+
}
|
|
308
|
+
if (result.tcp.result === 'unreachable') {
|
|
309
|
+
unreachableList.push({name: key, protocol: 'tcp'});
|
|
384
310
|
}
|
|
385
311
|
});
|
|
386
312
|
|
|
387
313
|
return unreachableList;
|
|
388
314
|
}
|
|
389
315
|
|
|
390
|
-
/**
|
|
391
|
-
* Attach an event handler for the icegatheringstatechange
|
|
392
|
-
* event and measure latency.
|
|
393
|
-
* @param {RTCPeerConnection} peerConnection
|
|
394
|
-
* @returns {undefined}
|
|
395
|
-
* @private
|
|
396
|
-
* @memberof Reachability
|
|
397
|
-
*/
|
|
398
|
-
private handleIceGatheringStateChange(peerConnection: RTCPeerConnection) {
|
|
399
|
-
peerConnection.onicegatheringstatechange = () => {
|
|
400
|
-
const {COMPLETE} = ICE_GATHERING_STATE;
|
|
401
|
-
|
|
402
|
-
if (peerConnection.iceConnectionState === COMPLETE) {
|
|
403
|
-
const elapsed = this.getElapsedTime(peerConnection);
|
|
404
|
-
|
|
405
|
-
// @ts-ignore
|
|
406
|
-
LoggerProxy.logger.log(
|
|
407
|
-
// @ts-ignore
|
|
408
|
-
`Reachability:index#onIceGatheringStateChange --> Successfully pinged ${peerConnection.key}:`,
|
|
409
|
-
elapsed
|
|
410
|
-
);
|
|
411
|
-
this.setLatencyAndClose(peerConnection, elapsed);
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Attach an event handler for the icecandidate
|
|
418
|
-
* event and measure latency.
|
|
419
|
-
* @param {RTCPeerConnection} peerConnection
|
|
420
|
-
* @returns {undefined}
|
|
421
|
-
* @private
|
|
422
|
-
* @memberof Reachability
|
|
423
|
-
*/
|
|
424
|
-
private handleOnIceCandidate(peerConnection: RTCPeerConnection) {
|
|
425
|
-
peerConnection.onicecandidate = (e) => {
|
|
426
|
-
const SERVER_REFLEXIVE = 'srflx';
|
|
427
|
-
|
|
428
|
-
if (e.candidate && String(e.candidate.type).toLowerCase() === SERVER_REFLEXIVE) {
|
|
429
|
-
const elapsed = this.getElapsedTime(peerConnection);
|
|
430
|
-
|
|
431
|
-
LoggerProxy.logger.log(
|
|
432
|
-
// @ts-ignore
|
|
433
|
-
`Reachability:index#onIceCandidate --> Successfully pinged ${peerConnection.key}:`,
|
|
434
|
-
elapsed
|
|
435
|
-
);
|
|
436
|
-
// order is important
|
|
437
|
-
this.addPublicIP(peerConnection, e.candidate.address);
|
|
438
|
-
this.setLatencyAndClose(peerConnection, elapsed);
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* An event handler on an RTCPeerConnection when the state of the ICE
|
|
445
|
-
* candidate gathering process changes. Used to measure connection
|
|
446
|
-
* speed.
|
|
447
|
-
* @private
|
|
448
|
-
* @param {RTCPeerConnection} peerConnection
|
|
449
|
-
* @param {boolean} isVideoMesh
|
|
450
|
-
* @returns {Promise}
|
|
451
|
-
*/
|
|
452
|
-
private iceGatheringState(peerConnection: RTCPeerConnection, isVideoMesh: boolean) {
|
|
453
|
-
const ELAPSED = 'elapsed';
|
|
454
|
-
|
|
455
|
-
const timeout = isVideoMesh ? VIDEO_MESH_TIMEOUT : DEFAULT_TIMEOUT;
|
|
456
|
-
|
|
457
|
-
return new Promise<ICECandidateResult>((resolve) => {
|
|
458
|
-
const peerConnectionProxy = new window.Proxy(peerConnection, {
|
|
459
|
-
// eslint-disable-next-line require-jsdoc
|
|
460
|
-
get(target, property) {
|
|
461
|
-
const targetMember = target[property];
|
|
462
|
-
|
|
463
|
-
if (typeof targetMember === 'function') {
|
|
464
|
-
return targetMember.bind(target);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return targetMember;
|
|
468
|
-
},
|
|
469
|
-
set: (target, property, value) => {
|
|
470
|
-
// only intercept elapsed property
|
|
471
|
-
if (property === ELAPSED) {
|
|
472
|
-
resolve({
|
|
473
|
-
// @ts-ignore
|
|
474
|
-
clusterId: peerConnection.key,
|
|
475
|
-
isVideoMesh,
|
|
476
|
-
// @ts-ignore
|
|
477
|
-
publicIPs: target.publicIPs,
|
|
478
|
-
elapsed: value,
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
return true;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// pass thru
|
|
485
|
-
return window.Reflect.set(target, property, value);
|
|
486
|
-
},
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
// Using peerConnection proxy so handle functions below
|
|
490
|
-
// won't be coupled to our promise implementation
|
|
491
|
-
this.handleIceGatheringStateChange(peerConnectionProxy);
|
|
492
|
-
this.handleOnIceCandidate(peerConnectionProxy);
|
|
493
|
-
|
|
494
|
-
// Set maximum timeout
|
|
495
|
-
window.setTimeout(() => {
|
|
496
|
-
const {CLOSED} = CONNECTION_STATE;
|
|
497
|
-
|
|
498
|
-
// Close any open peerConnections
|
|
499
|
-
if (peerConnectionProxy.connectionState !== CLOSED) {
|
|
500
|
-
// order is important
|
|
501
|
-
this.addPublicIP(peerConnectionProxy, null);
|
|
502
|
-
this.setLatencyAndClose(peerConnectionProxy, null);
|
|
503
|
-
}
|
|
504
|
-
}, timeout);
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
|
|
508
316
|
/**
|
|
509
317
|
* Make a log of unreachable clusters.
|
|
510
318
|
* @returns {undefined}
|
|
@@ -512,160 +320,55 @@ export default class Reachability {
|
|
|
512
320
|
* @memberof Reachability
|
|
513
321
|
*/
|
|
514
322
|
private logUnreachableClusters() {
|
|
515
|
-
const list = this.
|
|
323
|
+
const list = this.getUnreachableClusters();
|
|
516
324
|
|
|
517
|
-
list.forEach((
|
|
325
|
+
list.forEach(({name, protocol}) => {
|
|
518
326
|
LoggerProxy.logger.log(
|
|
519
|
-
`Reachability:index#logUnreachableClusters -->
|
|
327
|
+
`Reachability:index#logUnreachableClusters --> failed to reach ${name} over ${protocol}`
|
|
520
328
|
);
|
|
521
329
|
});
|
|
522
330
|
}
|
|
523
331
|
|
|
524
332
|
/**
|
|
525
|
-
*
|
|
526
|
-
* @param {
|
|
527
|
-
* @returns {
|
|
528
|
-
* @protected
|
|
529
|
-
* @memberof Reachability
|
|
333
|
+
* Performs reachability checks for all clusters
|
|
334
|
+
* @param {ClusterList} clusterList
|
|
335
|
+
* @returns {Promise<ReachabilityResults>} reachability check results
|
|
530
336
|
*/
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
): InternalReachabilityResults {
|
|
534
|
-
const reachabilityMap = {};
|
|
535
|
-
|
|
536
|
-
iceResults.forEach(({clusterId, isVideoMesh, elapsed, publicIPs}) => {
|
|
537
|
-
const latencyResult = {};
|
|
538
|
-
|
|
539
|
-
if (!elapsed) {
|
|
540
|
-
Object.assign(latencyResult, {reachable: 'false'});
|
|
541
|
-
} else {
|
|
542
|
-
Object.assign(latencyResult, {
|
|
543
|
-
reachable: 'true',
|
|
544
|
-
latencyInMilliseconds: elapsed.toString(),
|
|
545
|
-
});
|
|
546
|
-
}
|
|
337
|
+
private async performReachabilityChecks(clusterList: ClusterList): Promise<ReachabilityResults> {
|
|
338
|
+
const results: ReachabilityResults = {};
|
|
547
339
|
|
|
548
|
-
if (publicIPs) {
|
|
549
|
-
Object.assign(latencyResult, {
|
|
550
|
-
clientMediaIPs: publicIPs,
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
reachabilityMap[clusterId] = {
|
|
555
|
-
udp: latencyResult,
|
|
556
|
-
tcp: {untested: 'true'},
|
|
557
|
-
xtls: {untested: 'true'},
|
|
558
|
-
isVideoMesh,
|
|
559
|
-
};
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
return reachabilityMap;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* fetches reachability data
|
|
567
|
-
* @param {object} clusterList
|
|
568
|
-
* @returns {Promise<InternalReachabilityResults>} reachability check results
|
|
569
|
-
* @private
|
|
570
|
-
* @memberof Reachability
|
|
571
|
-
*/
|
|
572
|
-
private performReachabilityCheck(clusterList: object): Promise<InternalReachabilityResults> {
|
|
573
340
|
if (!clusterList || !Object.keys(clusterList).length) {
|
|
574
|
-
return Promise.resolve(
|
|
341
|
+
return Promise.resolve(results);
|
|
575
342
|
}
|
|
576
343
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
.then((localSDPData) => {
|
|
580
|
-
if (!localSDPData || !Object.keys(localSDPData).length) {
|
|
581
|
-
// TODO: handle the error condition properly and try retry
|
|
582
|
-
LoggerProxy.logger.log(
|
|
583
|
-
'Reachability:index#performReachabilityCheck --> Local SDP is empty or has missing elements..returning'
|
|
584
|
-
);
|
|
585
|
-
resolve({});
|
|
586
|
-
} else {
|
|
587
|
-
resolve(localSDPData);
|
|
588
|
-
}
|
|
589
|
-
})
|
|
590
|
-
.catch((error) => {
|
|
591
|
-
LoggerProxy.logger.error(
|
|
592
|
-
`Reachability:index#performReachabilityCheck --> Error in getLocalSDPForClusters: ${error}`
|
|
593
|
-
);
|
|
594
|
-
resolve({});
|
|
595
|
-
});
|
|
596
|
-
});
|
|
597
|
-
}
|
|
344
|
+
// @ts-ignore
|
|
345
|
+
const includeTcpReachability = this.webex.config.meetings.experimental.enableTcpReachability;
|
|
598
346
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
*/
|
|
605
|
-
protected addPublicIP(peerConnection: RTCPeerConnection, publicIP?: string | null) {
|
|
606
|
-
const modifiedPeerConnection: RTCPeerConnection & {publicIPs?: string[]} = peerConnection;
|
|
607
|
-
const {CLOSED} = CONNECTION_STATE;
|
|
347
|
+
LoggerProxy.logger.log(
|
|
348
|
+
`Reachability:index#performReachabilityChecks --> doing UDP${
|
|
349
|
+
includeTcpReachability ? ' and TCP' : ''
|
|
350
|
+
} reachability checks`
|
|
351
|
+
);
|
|
608
352
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
`Reachability:index#addPublicIP --> Attempting to set publicIP of ${publicIP} on closed peerConnection.`
|
|
612
|
-
);
|
|
613
|
-
}
|
|
353
|
+
const clusterReachabilityChecks = Object.keys(clusterList).map((key) => {
|
|
354
|
+
const cluster = clusterList[key];
|
|
614
355
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
modifiedPeerConnection.publicIPs.push(publicIP);
|
|
618
|
-
} else {
|
|
619
|
-
modifiedPeerConnection.publicIPs = [publicIP];
|
|
356
|
+
if (!includeTcpReachability) {
|
|
357
|
+
cluster.tcp = [];
|
|
620
358
|
}
|
|
621
|
-
} else {
|
|
622
|
-
modifiedPeerConnection.publicIPs = null;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
359
|
|
|
626
|
-
|
|
627
|
-
* Records latency and closes the peerConnection
|
|
628
|
-
* @param {RTCPeerConnection} peerConnection
|
|
629
|
-
* @param {number} elapsed Latency in milliseconds
|
|
630
|
-
* @returns {undefined}
|
|
631
|
-
* @private
|
|
632
|
-
* @memberof Reachability
|
|
633
|
-
*/
|
|
634
|
-
private setLatencyAndClose(peerConnection: RTCPeerConnection, elapsed: number) {
|
|
635
|
-
const REACHABLE = 'reachable';
|
|
636
|
-
const UNREACHABLE = 'unreachable';
|
|
637
|
-
const {CLOSED} = CONNECTION_STATE;
|
|
638
|
-
// @ts-ignore
|
|
639
|
-
const {key} = peerConnection;
|
|
640
|
-
const resultKey = elapsed === null ? UNREACHABLE : REACHABLE;
|
|
641
|
-
const intialState = {[REACHABLE]: 0, [UNREACHABLE]: 0};
|
|
360
|
+
this.clusterReachability[key] = new ClusterReachability(key, cluster);
|
|
642
361
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
362
|
+
return this.clusterReachability[key].start().then((result) => {
|
|
363
|
+
results[key] = result;
|
|
364
|
+
results[key].isVideoMesh = cluster.isVideoMesh;
|
|
365
|
+
});
|
|
366
|
+
});
|
|
650
367
|
|
|
651
|
-
|
|
652
|
-
this.clusterLatencyResults[key][resultKey] += 1;
|
|
368
|
+
await Promise.all(clusterReachabilityChecks);
|
|
653
369
|
|
|
654
|
-
|
|
655
|
-
// an event other than onIceCandidate
|
|
656
|
-
peerConnection.onicecandidate = null;
|
|
657
|
-
peerConnection.close();
|
|
658
|
-
// @ts-ignore
|
|
659
|
-
peerConnection.elapsed = elapsed;
|
|
660
|
-
}
|
|
370
|
+
this.logUnreachableClusters();
|
|
661
371
|
|
|
662
|
-
|
|
663
|
-
* utility function
|
|
664
|
-
* @returns {undefined}
|
|
665
|
-
* @private
|
|
666
|
-
* @memberof Reachability
|
|
667
|
-
*/
|
|
668
|
-
private setup() {
|
|
669
|
-
this.clusterLatencyResults = {};
|
|
372
|
+
return results;
|
|
670
373
|
}
|
|
671
374
|
}
|