@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.
@@ -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
- export type TransportResult = {
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 ReachabilityResult = {
21
- udp: TransportResult;
22
- tcp: TransportResult;
23
- xtls: {
24
- untested: 'true';
25
- };
26
+ export type ReachabilityResultForBackend = {
27
+ udp: TransportResultForBackend;
28
+ tcp: TransportResultForBackend;
29
+ xtls: TransportResultForBackend;
26
30
  };
27
- export type ReachabilityResults = Record<string, ReachabilityResult>;
28
- type InternalReachabilityResults = Record<string, ReachabilityResult & {
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: any;
45
- clusterLatencyResults: any;
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
- * fetches reachability data
54
- * @returns {Object} reachability data
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<InternalReachabilityResults>;
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<ReachabilityResults | undefined>;
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 getUnreachablClusters;
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
- * Calculates time to establish connection
156
- * @param {Array<ICECandidateResult>} iceResults iceResults
157
- * @returns {object} reachabilityMap
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 setup;
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;
@@ -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.337"
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.337",
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.337",
37
- "@webex/test-helper-chai": "3.0.0-beta.337",
38
- "@webex/test-helper-mocha": "3.0.0-beta.337",
39
- "@webex/test-helper-mock-webex": "3.0.0-beta.337",
40
- "@webex/test-helper-retry": "3.0.0-beta.337",
41
- "@webex/test-helper-test-users": "3.0.0-beta.337",
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.337",
51
- "@webex/internal-media-core": "2.2.3",
52
- "@webex/internal-plugin-conversation": "3.0.0-beta.337",
53
- "@webex/internal-plugin-device": "3.0.0-beta.337",
54
- "@webex/internal-plugin-llm": "3.0.0-beta.337",
55
- "@webex/internal-plugin-mercury": "3.0.0-beta.337",
56
- "@webex/internal-plugin-metrics": "3.0.0-beta.337",
57
- "@webex/internal-plugin-support": "3.0.0-beta.337",
58
- "@webex/internal-plugin-user": "3.0.0-beta.337",
59
- "@webex/media-helpers": "3.0.0-beta.337",
60
- "@webex/plugin-people": "3.0.0-beta.337",
61
- "@webex/plugin-rooms": "3.0.0-beta.337",
62
- "@webex/webex-core": "3.0.0-beta.337",
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
@@ -88,6 +88,7 @@ export default {
88
88
  enableMediaNegotiatedEvent: false,
89
89
  enableUnifiedMeetings: true,
90
90
  enableAdhocMeetings: true,
91
+ enableTcpReachability: false,
91
92
  },
92
93
  degradationPreferences: {
93
94
  maxMacroblocksLimit: 8192,
@@ -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
+ }