Skip to content

Commit 59c93b5

Browse files
committed
Check TURN servers periodically, and at start of calls
Hopefully this should make our turn-credential checking code a bit more robust (and possibly fix a seconds / ms mismatch).
1 parent c18ef05 commit 59c93b5

File tree

2 files changed

+65
-39
lines changed

2 files changed

+65
-39
lines changed

src/client.js

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {DEHYDRATION_ALGORITHM} from "./crypto/dehydration";
6161
const SCROLLBACK_DELAY_MS = 3000;
6262
export const CRYPTO_ENABLED = isCryptoAvailable();
6363
const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value
64+
const TURN_CHECK_INTERVAL = 10 * 60 * 1000; // poll for turn credentials every 10 minutes
6465

6566
function keysFromRecoverySession(sessions, decryptionKey, roomId) {
6667
const keys = [];
@@ -394,7 +395,8 @@ export function MatrixClient(opts) {
394395
this._clientWellKnownPromise = undefined;
395396

396397
this._turnServers = [];
397-
this._turnServersExpiry = null;
398+
this._turnServersExpiry = 0;
399+
this._checkTurnServersIntervalID = null;
398400

399401
// The SDK doesn't really provide a clean way for events to recalculate the push
400402
// actions for themselves, so we have to kinda help them out when they are encrypted.
@@ -4954,6 +4956,48 @@ MatrixClient.prototype.getTurnServersExpiry = function() {
49544956
return this._turnServersExpiry;
49554957
};
49564958

4959+
MatrixClient.prototype._checkTurnServers = async function() {
4960+
if (!this._supportsVoip) {
4961+
return;
4962+
}
4963+
4964+
let credentialsGood = false;
4965+
const remainingTime = this._turnServersExpiry - Date.now();
4966+
if (remainingTime > TURN_CHECK_INTERVAL) {
4967+
logger.debug("TURN creds are valid for another " + remainingTime + " ms: not fetching new ones.");
4968+
credentialsGood = true;
4969+
} else {
4970+
logger.debug("Fetching new TURN credentials");
4971+
try {
4972+
const res = await this.turnServer();
4973+
if (res.uris) {
4974+
logger.log("Got TURN URIs: " + res.uris + " refresh in " + res.ttl + " secs");
4975+
// map the response to a format that can be fed to RTCPeerConnection
4976+
const servers = {
4977+
urls: res.uris,
4978+
username: res.username,
4979+
credential: res.password,
4980+
};
4981+
this._turnServers = [servers];
4982+
// The TTL is in seconds but we work in ms
4983+
this._turnServersExpiry = Date.now() + (res.ttl * 1000);
4984+
credentialsGood = true;
4985+
}
4986+
} catch (err) {
4987+
logger.error("Failed to get TURN URIs", err);
4988+
// If we get a 403, there's no point in looping forever.
4989+
if (err.httpStatus === 403) {
4990+
logger.info("TURN access unavailable for this account: stopping credentials checks");
4991+
if (this._checkTurnServersIntervalID !== null) global.clearInterval(this._checkTurnServersIntervalID);
4992+
this._checkTurnServersIntervalID = null;
4993+
}
4994+
}
4995+
// otherwise, if we failed for whatever reason, try again the next time we're called.
4996+
}
4997+
4998+
return credentialsGood;
4999+
};
5000+
49575001
/**
49585002
* Set whether to allow a fallback ICE server should be used for negotiating a
49595003
* WebRTC connection if the homeserver doesn't provide any servers. Defaults to
@@ -5106,7 +5150,10 @@ MatrixClient.prototype.startClient = async function(opts) {
51065150
}
51075151

51085152
// periodically poll for turn servers if we support voip
5109-
checkTurnServers(this);
5153+
this._checkTurnServersIntervalID = setInterval(() => {
5154+
this._checkTurnServers();
5155+
}, TURN_CHECK_INTERVAL);
5156+
this._checkTurnServers();
51105157

51115158
if (this._syncApi) {
51125159
// This shouldn't happen since we thought the client was not running
@@ -5218,7 +5265,7 @@ MatrixClient.prototype.stopClient = function() {
52185265
this._callEventHandler = null;
52195266
}
52205267

5221-
global.clearTimeout(this._checkTurnServersTimeoutID);
5268+
global.clearInterval(this._checkTurnServersIntervalID);
52225269
if (this._clientWellKnownIntervalID !== undefined) {
52235270
global.clearInterval(this._clientWellKnownIntervalID);
52245271
}
@@ -5435,42 +5482,6 @@ async function(roomId, eventId, relationType, eventType, opts = {}) {
54355482
};
54365483
};
54375484

5438-
function checkTurnServers(client) {
5439-
if (!client._supportsVoip) {
5440-
return;
5441-
}
5442-
5443-
client.turnServer().then(function(res) {
5444-
if (res.uris) {
5445-
logger.log("Got TURN URIs: " + res.uris + " refresh in " +
5446-
res.ttl + " secs");
5447-
// map the response to a format that can be fed to
5448-
// RTCPeerConnection
5449-
const servers = {
5450-
urls: res.uris,
5451-
username: res.username,
5452-
credential: res.password,
5453-
};
5454-
client._turnServers = [servers];
5455-
client._turnServersExpiry = Date.now() + res.ttl;
5456-
// re-fetch when we're about to reach the TTL
5457-
client._checkTurnServersTimeoutID = setTimeout(() => {
5458-
checkTurnServers(client);
5459-
}, (res.ttl || (60 * 60)) * 1000 * 0.9);
5460-
}
5461-
}, function(err) {
5462-
logger.error("Failed to get TURN URIs");
5463-
// If we get a 403, there's no point in looping forever.
5464-
if (err.httpStatus === 403) {
5465-
logger.info("TURN access unavailable for this account");
5466-
return;
5467-
}
5468-
client._checkTurnServersTimeoutID = setTimeout(function() {
5469-
checkTurnServers(client);
5470-
}, 60000);
5471-
});
5472-
}
5473-
54745485
function _reject(callback, reject, err) {
54755486
if (callback) {
54765487
callback(err);

src/webrtc/call.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,13 @@ export class MatrixCall extends EventEmitter {
527527
const invite = event.getContent();
528528
this.direction = CallDirection.Inbound;
529529

530+
// make sure we have valid turn creds. Unless something's gone wrong, it should
531+
// poll and keep the credentials valid so this should be instant.
532+
const haveTurnCreds = await this.client._checkTurnServers();
533+
if (!haveTurnCreds) {
534+
logger.warn("Failed to get TURN credentials! Proceeding with call anyway...");
535+
}
536+
530537
this.peerConn = this.createPeerConnection();
531538
// we must set the party ID before await-ing on anything: the call event
532539
// handler will start giving us more call events (eg. candidates) so if
@@ -1662,6 +1669,14 @@ export class MatrixCall extends EventEmitter {
16621669
this.setState(CallState.WaitLocalMedia);
16631670
this.direction = CallDirection.Outbound;
16641671
this.config = constraints;
1672+
1673+
// make sure we have valid turn creds. Unless something's gone wrong, it should
1674+
// poll and keep the credentials valid so this should be instant.
1675+
const haveTurnCreds = await this.client._checkTurnServers();
1676+
if (!haveTurnCreds) {
1677+
logger.warn("Failed to get TURN credentials! Proceeding with call anyway...");
1678+
}
1679+
16651680
// It would be really nice if we could start gathering candidates at this point
16661681
// so the ICE agent could be gathering while we open our media devices: we already
16671682
// know the type of the call and therefore what tracks we want to send.

0 commit comments

Comments
 (0)