@webex/plugin-meetings 3.3.1-next.2 → 3.3.1-next.21
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 +7 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/media/MediaConnectionAwaiter.js +50 -13
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/mediaQualityMetrics/config.js +16 -6
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/connectionStateHandler.js +67 -0
- package/dist/meeting/connectionStateHandler.js.map +1 -0
- package/dist/meeting/index.js +98 -46
- package/dist/meeting/index.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +57 -0
- package/dist/metrics/index.js.map +1 -1
- package/dist/reachability/clusterReachability.js +108 -53
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +415 -56
- package/dist/reachability/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +81 -27
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +36 -10
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/types/media/MediaConnectionAwaiter.d.ts +18 -4
- package/dist/types/mediaQualityMetrics/config.d.ts +11 -0
- package/dist/types/meeting/connectionStateHandler.d.ts +30 -0
- package/dist/types/meeting/index.d.ts +2 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/metrics/index.d.ts +15 -0
- package/dist/types/reachability/clusterReachability.d.ts +31 -3
- package/dist/types/reachability/index.d.ts +93 -2
- package/dist/types/statsAnalyzer/index.d.ts +15 -6
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +17 -4
- package/dist/webinar/index.js +1 -1
- package/package.json +23 -22
- package/src/breakouts/index.ts +7 -1
- package/src/media/MediaConnectionAwaiter.ts +66 -11
- package/src/mediaQualityMetrics/config.ts +14 -3
- package/src/meeting/connectionStateHandler.ts +65 -0
- package/src/meeting/index.ts +72 -14
- package/src/metrics/constants.ts +1 -0
- package/src/metrics/index.ts +44 -0
- package/src/reachability/clusterReachability.ts +86 -25
- package/src/reachability/index.ts +316 -27
- package/src/statsAnalyzer/index.ts +85 -24
- package/src/statsAnalyzer/mqaUtil.ts +55 -7
- package/test/unit/spec/breakouts/index.ts +51 -32
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +90 -32
- package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
- package/test/unit/spec/meeting/index.js +158 -36
- package/test/unit/spec/metrics/index.js +126 -0
- package/test/unit/spec/reachability/clusterReachability.ts +116 -22
- package/test/unit/spec/reachability/index.ts +1153 -84
- package/test/unit/spec/stats-analyzer/index.js +647 -319
package/src/meeting/index.ts
CHANGED
|
@@ -153,6 +153,7 @@ import RecordingController from '../recording-controller';
|
|
|
153
153
|
import ControlsOptionsManager from '../controls-options-manager';
|
|
154
154
|
import PermissionError from '../common/errors/permission';
|
|
155
155
|
import {LocusMediaRequest} from './locusMediaRequest';
|
|
156
|
+
import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
|
|
156
157
|
|
|
157
158
|
const logRequest = (request: any, {logText = ''}) => {
|
|
158
159
|
LoggerProxy.logger.info(`${logText} - sending request`);
|
|
@@ -680,6 +681,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
680
681
|
private sdpResponseTimer?: ReturnType<typeof setTimeout>;
|
|
681
682
|
private hasMediaConnectionConnectedAtLeastOnce: boolean;
|
|
682
683
|
private joinWithMediaRetryInfo?: {isRetry: boolean; prevJoinResponse?: any};
|
|
684
|
+
private connectionStateHandler?: ConnectionStateHandler;
|
|
685
|
+
private iceCandidateErrors: Map<string, number>;
|
|
683
686
|
|
|
684
687
|
/**
|
|
685
688
|
* @param {Object} attrs
|
|
@@ -1470,6 +1473,24 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1470
1473
|
* @memberof Meeting
|
|
1471
1474
|
*/
|
|
1472
1475
|
this.joinWithMediaRetryInfo = {isRetry: false, prevJoinResponse: undefined};
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* Connection state handler
|
|
1479
|
+
* @instance
|
|
1480
|
+
* @type {ConnectionStateHandler}
|
|
1481
|
+
* @private
|
|
1482
|
+
* @memberof Meeting
|
|
1483
|
+
*/
|
|
1484
|
+
this.connectionStateHandler = undefined;
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* ICE Candidates errors map
|
|
1488
|
+
* @instance
|
|
1489
|
+
* @type {Map<[number, string], number>}
|
|
1490
|
+
* @private
|
|
1491
|
+
* @memberof Meeting
|
|
1492
|
+
*/
|
|
1493
|
+
this.iceCandidateErrors = new Map();
|
|
1473
1494
|
}
|
|
1474
1495
|
|
|
1475
1496
|
/**
|
|
@@ -5233,8 +5254,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5233
5254
|
|
|
5234
5255
|
// @ts-ignore - Fix type
|
|
5235
5256
|
if (this.webex.internal.llm.isConnected()) {
|
|
5236
|
-
|
|
5237
|
-
|
|
5257
|
+
if (
|
|
5258
|
+
// @ts-ignore - Fix type
|
|
5259
|
+
url === this.webex.internal.llm.getLocusUrl() &&
|
|
5260
|
+
// @ts-ignore - Fix type
|
|
5261
|
+
datachannelUrl === this.webex.internal.llm.getDatachannelUrl() &&
|
|
5262
|
+
isJoined
|
|
5263
|
+
) {
|
|
5238
5264
|
return undefined;
|
|
5239
5265
|
}
|
|
5240
5266
|
// @ts-ignore - Fix type
|
|
@@ -5846,7 +5872,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5846
5872
|
}
|
|
5847
5873
|
});
|
|
5848
5874
|
|
|
5849
|
-
this.
|
|
5875
|
+
this.connectionStateHandler = new ConnectionStateHandler(
|
|
5876
|
+
this.mediaProperties.webrtcMediaConnection
|
|
5877
|
+
);
|
|
5878
|
+
|
|
5879
|
+
this.connectionStateHandler.on(ConnectionStateEvent.stateChanged, (event) => {
|
|
5850
5880
|
const connectionFailed = () => {
|
|
5851
5881
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_FAILURE, {
|
|
5852
5882
|
correlation_id: this.correlationId,
|
|
@@ -5990,6 +6020,32 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5990
6020
|
);
|
|
5991
6021
|
}
|
|
5992
6022
|
);
|
|
6023
|
+
|
|
6024
|
+
this.iceCandidateErrors.clear();
|
|
6025
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ICE_CANDIDATE_ERROR, (event) => {
|
|
6026
|
+
const {errorCode} = event.error;
|
|
6027
|
+
let {errorText} = event.error;
|
|
6028
|
+
|
|
6029
|
+
if (
|
|
6030
|
+
errorCode === 600 &&
|
|
6031
|
+
errorText === 'Address not associated with the desired network interface.'
|
|
6032
|
+
) {
|
|
6033
|
+
return;
|
|
6034
|
+
}
|
|
6035
|
+
|
|
6036
|
+
if (errorText.endsWith('.')) {
|
|
6037
|
+
errorText = errorText.slice(0, -1);
|
|
6038
|
+
}
|
|
6039
|
+
|
|
6040
|
+
errorText = errorText.toLowerCase();
|
|
6041
|
+
errorText = errorText.replace(/ /g, '_');
|
|
6042
|
+
|
|
6043
|
+
const error = `${errorCode}_${errorText}`;
|
|
6044
|
+
|
|
6045
|
+
const count = this.iceCandidateErrors.get(error) || 0;
|
|
6046
|
+
|
|
6047
|
+
this.iceCandidateErrors.set(error, count + 1);
|
|
6048
|
+
});
|
|
5993
6049
|
};
|
|
5994
6050
|
|
|
5995
6051
|
/**
|
|
@@ -6264,6 +6320,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6264
6320
|
try {
|
|
6265
6321
|
await this.mediaProperties.waitForMediaConnectionConnected();
|
|
6266
6322
|
} catch (error) {
|
|
6323
|
+
const {iceConnected} = error;
|
|
6324
|
+
|
|
6267
6325
|
if (!this.hasMediaConnectionConnectedAtLeastOnce) {
|
|
6268
6326
|
// Only send CA event for join flow if we haven't successfully connected media yet
|
|
6269
6327
|
// @ts-ignore
|
|
@@ -6283,12 +6341,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6283
6341
|
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
|
|
6284
6342
|
?.signalingState ||
|
|
6285
6343
|
'unknown',
|
|
6286
|
-
|
|
6287
|
-
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
6288
|
-
?.iceConnectionState ||
|
|
6289
|
-
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
|
|
6290
|
-
?.iceConnectionState ||
|
|
6291
|
-
'unknown',
|
|
6344
|
+
iceConnected,
|
|
6292
6345
|
turnServerUsed: this.turnServerUsed,
|
|
6293
6346
|
}),
|
|
6294
6347
|
}
|
|
@@ -6317,12 +6370,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6317
6370
|
if (this.config.stats.enableStatsAnalyzer) {
|
|
6318
6371
|
// @ts-ignore - config coming from registerPlugin
|
|
6319
6372
|
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
6320
|
-
this.statsAnalyzer = new StatsAnalyzer(
|
|
6373
|
+
this.statsAnalyzer = new StatsAnalyzer({
|
|
6321
6374
|
// @ts-ignore - config coming from registerPlugin
|
|
6322
|
-
this.config.stats,
|
|
6323
|
-
(ssrc: number) => this.receiveSlotManager.findReceiveSlotBySsrc(ssrc),
|
|
6324
|
-
this.networkQualityMonitor
|
|
6325
|
-
|
|
6375
|
+
config: this.config.stats,
|
|
6376
|
+
receiveSlotCallback: (ssrc: number) => this.receiveSlotManager.findReceiveSlotBySsrc(ssrc),
|
|
6377
|
+
networkQualityMonitor: this.networkQualityMonitor,
|
|
6378
|
+
isMultistream: this.isMultistream,
|
|
6379
|
+
});
|
|
6326
6380
|
this.setupStatsAnalyzerEventHandlers();
|
|
6327
6381
|
this.networkQualityMonitor.on(
|
|
6328
6382
|
EVENT_TRIGGERS.NETWORK_QUALITY,
|
|
@@ -6830,6 +6884,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6830
6884
|
const {selectedCandidatePairChanges, numTransports} =
|
|
6831
6885
|
await this.mediaProperties.getCurrentConnectionInfo();
|
|
6832
6886
|
|
|
6887
|
+
const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
|
|
6888
|
+
|
|
6833
6889
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
6834
6890
|
correlation_id: this.correlationId,
|
|
6835
6891
|
locus_id: this.locusUrl.split('/').pop(),
|
|
@@ -6859,6 +6915,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6859
6915
|
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
|
|
6860
6916
|
'unknown',
|
|
6861
6917
|
...reachabilityMetrics,
|
|
6918
|
+
...iceCandidateErrors,
|
|
6862
6919
|
});
|
|
6863
6920
|
|
|
6864
6921
|
await this.cleanUpOnAddMediaFailure();
|
|
@@ -7896,6 +7953,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7896
7953
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
|
|
7897
7954
|
correlationId: this.correlationId,
|
|
7898
7955
|
muted,
|
|
7956
|
+
encoderImplementation: this.statsAnalyzer?.shareVideoEncoderImplementation,
|
|
7899
7957
|
});
|
|
7900
7958
|
};
|
|
7901
7959
|
|
package/src/metrics/constants.ts
CHANGED
|
@@ -69,6 +69,7 @@ const BEHAVIORAL_METRICS = {
|
|
|
69
69
|
ROAP_OFFER_TO_ANSWER_LATENCY: 'js_sdk_roap_offer_to_answer_latency',
|
|
70
70
|
ROAP_HTTP_RESPONSE_MISSING: 'js_sdk_roap_http_response_missing',
|
|
71
71
|
TURN_DISCOVERY_REQUIRES_OK: 'js_sdk_turn_discovery_requires_ok',
|
|
72
|
+
REACHABILITY_COMPLETED: 'js_sdk_reachability_completed',
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
export {BEHAVIORAL_METRICS as default};
|
package/src/metrics/index.ts
CHANGED
|
@@ -65,6 +65,50 @@ class Metrics {
|
|
|
65
65
|
tags: metricTags,
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Flattens an object into one that has no nested properties. Each level of nesting is represented
|
|
71
|
+
* by "_" in the flattened object property names.
|
|
72
|
+
* This function is needed, because Amplitude doesn't allow passing nested objects as metricFields.
|
|
73
|
+
* Use this function for metricFields before calling sendBehavioralMetric() if you want to send
|
|
74
|
+
* nested objects in your metrics.
|
|
75
|
+
*
|
|
76
|
+
* If the function is called with a literal, it returns an object with a single property "value"
|
|
77
|
+
* and the literal value in it.
|
|
78
|
+
*
|
|
79
|
+
* @param {any} payload object you want to flatten
|
|
80
|
+
* @param {string} prefix string prefix prepended to any property names in flatten object
|
|
81
|
+
* @returns {Object}
|
|
82
|
+
*/
|
|
83
|
+
prepareMetricFields(payload: any = {}, prefix = '') {
|
|
84
|
+
let output = {};
|
|
85
|
+
|
|
86
|
+
if (Array.isArray(payload)) {
|
|
87
|
+
payload.forEach((item, index) => {
|
|
88
|
+
const propName = prefix.length > 0 ? `${prefix}_${index}` : `${index}`;
|
|
89
|
+
|
|
90
|
+
output = {...output, ...this.prepareMetricFields(item, propName)};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return output;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof payload !== 'object' || payload === null) {
|
|
97
|
+
if (prefix.length > 0) {
|
|
98
|
+
return {[prefix]: payload};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {value: payload};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Object.entries(payload).forEach(([key, value]) => {
|
|
105
|
+
const propName = prefix.length > 0 ? `${prefix}_${key}` : key;
|
|
106
|
+
|
|
107
|
+
output = {...output, ...this.prepareMetricFields(value, propName)};
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return output;
|
|
111
|
+
}
|
|
68
112
|
}
|
|
69
113
|
|
|
70
114
|
// Export Metrics singleton ---------------------------------------------------
|
|
@@ -3,11 +3,9 @@ import {Defer} from '@webex/common';
|
|
|
3
3
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
4
4
|
import {ClusterNode} from './request';
|
|
5
5
|
import {convertStunUrlToTurn, convertStunUrlToTurnTls} from './util';
|
|
6
|
+
import EventsScope from '../common/events/events-scope';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const DEFAULT_TIMEOUT = 3000;
|
|
10
|
-
const VIDEO_MESH_TIMEOUT = 1000;
|
|
8
|
+
import {CONNECTION_STATE, Enum, ICE_GATHERING_STATE} from '../constants';
|
|
11
9
|
|
|
12
10
|
// result for a specific transport protocol (like udp or tcp)
|
|
13
11
|
export type TransportResult = {
|
|
@@ -23,10 +21,32 @@ export type ClusterReachabilityResult = {
|
|
|
23
21
|
xtls: TransportResult;
|
|
24
22
|
};
|
|
25
23
|
|
|
24
|
+
// data for the Events.resultReady event
|
|
25
|
+
export type ResultEventData = {
|
|
26
|
+
protocol: 'udp' | 'tcp' | 'xtls';
|
|
27
|
+
result: 'reachable' | 'unreachable' | 'untested';
|
|
28
|
+
latencyInMilliseconds: number; // amount of time it took to get the ICE candidate
|
|
29
|
+
clientMediaIPs?: string[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// data for the Events.clientMediaIpsUpdated event
|
|
33
|
+
export type ClientMediaIpsUpdatedEventData = {
|
|
34
|
+
protocol: 'udp' | 'tcp' | 'xtls';
|
|
35
|
+
clientMediaIPs: string[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Events = {
|
|
39
|
+
resultReady: 'resultReady', // emitted when a cluster is reached successfully using specific protocol
|
|
40
|
+
clientMediaIpsUpdated: 'clientMediaIpsUpdated', // emitted when more public IPs are found after resultReady was already sent for a given protocol
|
|
41
|
+
} as const;
|
|
42
|
+
|
|
43
|
+
export type Events = Enum<typeof Events>;
|
|
44
|
+
|
|
26
45
|
/**
|
|
27
46
|
* A class that handles reachability checks for a single cluster.
|
|
47
|
+
* It emits events from Events enum
|
|
28
48
|
*/
|
|
29
|
-
export class ClusterReachability {
|
|
49
|
+
export class ClusterReachability extends EventsScope {
|
|
30
50
|
private numUdpUrls: number;
|
|
31
51
|
private numTcpUrls: number;
|
|
32
52
|
private numXTlsUrls: number;
|
|
@@ -43,6 +63,7 @@ export class ClusterReachability {
|
|
|
43
63
|
* @param {ClusterNode} clusterInfo information about the media cluster
|
|
44
64
|
*/
|
|
45
65
|
constructor(name: string, clusterInfo: ClusterNode) {
|
|
66
|
+
super();
|
|
46
67
|
this.name = name;
|
|
47
68
|
this.isVideoMesh = clusterInfo.isVideoMesh;
|
|
48
69
|
this.numUdpUrls = clusterInfo.udp.length;
|
|
@@ -162,23 +183,54 @@ export class ClusterReachability {
|
|
|
162
183
|
this.defer.resolve();
|
|
163
184
|
}
|
|
164
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Aborts the cluster reachability checks by closing the peer connection
|
|
188
|
+
*
|
|
189
|
+
* @returns {void}
|
|
190
|
+
*/
|
|
191
|
+
public abort() {
|
|
192
|
+
const {CLOSED} = CONNECTION_STATE;
|
|
193
|
+
|
|
194
|
+
if (this.pc.connectionState !== CLOSED) {
|
|
195
|
+
this.closePeerConnection();
|
|
196
|
+
this.finishReachabilityCheck();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
165
200
|
/**
|
|
166
201
|
* Adds public IP (client media IPs)
|
|
167
202
|
* @param {string} protocol
|
|
168
203
|
* @param {string} publicIP
|
|
169
204
|
* @returns {void}
|
|
170
205
|
*/
|
|
171
|
-
private addPublicIP(protocol: 'udp' | 'tcp', publicIP?: string | null) {
|
|
206
|
+
private addPublicIP(protocol: 'udp' | 'tcp' | 'xtls', publicIP?: string | null) {
|
|
172
207
|
const result = this.result[protocol];
|
|
173
208
|
|
|
174
209
|
if (publicIP) {
|
|
210
|
+
let ipAdded = false;
|
|
211
|
+
|
|
175
212
|
if (result.clientMediaIPs) {
|
|
176
213
|
if (!result.clientMediaIPs.includes(publicIP)) {
|
|
177
214
|
result.clientMediaIPs.push(publicIP);
|
|
215
|
+
ipAdded = true;
|
|
178
216
|
}
|
|
179
217
|
} else {
|
|
180
218
|
result.clientMediaIPs = [publicIP];
|
|
219
|
+
ipAdded = true;
|
|
181
220
|
}
|
|
221
|
+
|
|
222
|
+
if (ipAdded)
|
|
223
|
+
this.emit(
|
|
224
|
+
{
|
|
225
|
+
file: 'clusterReachability',
|
|
226
|
+
function: 'addPublicIP',
|
|
227
|
+
},
|
|
228
|
+
Events.clientMediaIpsUpdated,
|
|
229
|
+
{
|
|
230
|
+
protocol,
|
|
231
|
+
clientMediaIPs: result.clientMediaIPs,
|
|
232
|
+
}
|
|
233
|
+
);
|
|
182
234
|
}
|
|
183
235
|
}
|
|
184
236
|
|
|
@@ -211,22 +263,43 @@ export class ClusterReachability {
|
|
|
211
263
|
}
|
|
212
264
|
|
|
213
265
|
/**
|
|
214
|
-
*
|
|
266
|
+
* Saves the latency in the result for the given protocol and marks it as reachable,
|
|
267
|
+
* emits the "resultReady" event if this is the first result for that protocol,
|
|
268
|
+
* emits the "clientMediaIpsUpdated" event if we already had a result and only found
|
|
269
|
+
* a new client IP
|
|
215
270
|
*
|
|
216
271
|
* @param {string} protocol
|
|
217
272
|
* @param {number} latency
|
|
273
|
+
* @param {string|null} [publicIp]
|
|
218
274
|
* @returns {void}
|
|
219
275
|
*/
|
|
220
|
-
private
|
|
276
|
+
private saveResult(protocol: 'udp' | 'tcp' | 'xtls', latency: number, publicIp?: string | null) {
|
|
221
277
|
const result = this.result[protocol];
|
|
222
278
|
|
|
223
279
|
if (result.latencyInMilliseconds === undefined) {
|
|
224
280
|
LoggerProxy.logger.log(
|
|
225
281
|
// @ts-ignore
|
|
226
|
-
`Reachability:index#
|
|
282
|
+
`Reachability:index#saveResult --> Successfully reached ${this.name} over ${protocol}: ${latency}ms`
|
|
227
283
|
);
|
|
228
284
|
result.latencyInMilliseconds = latency;
|
|
229
285
|
result.result = 'reachable';
|
|
286
|
+
if (publicIp) {
|
|
287
|
+
result.clientMediaIPs = [publicIp];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.emit(
|
|
291
|
+
{
|
|
292
|
+
file: 'clusterReachability',
|
|
293
|
+
function: 'saveResult',
|
|
294
|
+
},
|
|
295
|
+
Events.resultReady,
|
|
296
|
+
{
|
|
297
|
+
protocol,
|
|
298
|
+
...result,
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
} else {
|
|
302
|
+
this.addPublicIP(protocol, publicIp);
|
|
230
303
|
}
|
|
231
304
|
}
|
|
232
305
|
|
|
@@ -243,15 +316,16 @@ export class ClusterReachability {
|
|
|
243
316
|
RELAY: 'relay',
|
|
244
317
|
};
|
|
245
318
|
|
|
319
|
+
const latencyInMilliseconds = this.getElapsedTime();
|
|
320
|
+
|
|
246
321
|
if (e.candidate) {
|
|
247
322
|
if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
|
|
248
|
-
this.
|
|
249
|
-
this.addPublicIP('udp', e.candidate.address);
|
|
323
|
+
this.saveResult('udp', latencyInMilliseconds, e.candidate.address);
|
|
250
324
|
}
|
|
251
325
|
|
|
252
326
|
if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
|
|
253
327
|
const protocol = e.candidate.port === TURN_TLS_PORT ? 'xtls' : 'tcp';
|
|
254
|
-
this.
|
|
328
|
+
this.saveResult(protocol, latencyInMilliseconds);
|
|
255
329
|
// we don't add public IP for TCP, because in the case of relay candidates
|
|
256
330
|
// e.candidate.address is the TURN server address, not the client's public IP
|
|
257
331
|
}
|
|
@@ -314,22 +388,9 @@ export class ClusterReachability {
|
|
|
314
388
|
* @returns {Promise} promise that's resolved once reachability checks for this cluster are completed or timeout is reached
|
|
315
389
|
*/
|
|
316
390
|
private gatherIceCandidates() {
|
|
317
|
-
const timeout = this.isVideoMesh ? VIDEO_MESH_TIMEOUT : DEFAULT_TIMEOUT;
|
|
318
|
-
|
|
319
391
|
this.registerIceGatheringStateChangeListener();
|
|
320
392
|
this.registerIceCandidateListener();
|
|
321
393
|
|
|
322
|
-
// Set maximum timeout
|
|
323
|
-
setTimeout(() => {
|
|
324
|
-
const {CLOSED} = CONNECTION_STATE;
|
|
325
|
-
|
|
326
|
-
// Close any open peerConnections
|
|
327
|
-
if (this.pc.connectionState !== CLOSED) {
|
|
328
|
-
this.closePeerConnection();
|
|
329
|
-
this.finishReachabilityCheck();
|
|
330
|
-
}
|
|
331
|
-
}, timeout);
|
|
332
|
-
|
|
333
394
|
return this.defer.promise;
|
|
334
395
|
}
|
|
335
396
|
}
|