WebRTC audio/video call and conferencing server.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

213 lines
6.2 KiB

/*
* Spreed WebRTC.
* Copyright (C) 2013-2016 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
"use strict";
define(["jquery"], function($) {
var geoRequestTimeout = 30000; // Timeout for geo requests in milliseconds.
var geoFastRetryTimeout = 45000; // Refresh timer in milliseconds, after which GEO requests should be retried if failed before.
var refreshPercentile = 90; // Percent of the TTL when TURN credentials should be refreshed.
var refreshMinimumInterval = 30000; // Minimal TURN refresh interval in milliseconds.
// turnData
return ["$timeout", "$http", "api", "randomGen", "appData", "translationLanguage", function($timeout, $http, api, randomGen, appData, translationLanguage) {
var ttlTimeout = null;
var geoRefresh = null;
var geoPreferred = null;
var service = this;
service.e = $({});
service.data = {};
service.apply = function() {
var turn = service.data;
var turnData = {
"username": turn.username,
"password": turn.password,
"ttl": turn.ttl
};
if (turn && turn.servers) {
// Multiple options, need to sort and use settings.
var i;
if (!turn.serverMap) {
var servers = {};
var serversSelectable = [];
// Sort for prio.
turn.servers.sort(function(a, b) {
servers[a.id] = a;
servers[b.id] = b;
return (a.prio > b.prio) ? 1 : ((a.prio < b.prio) ? -1 : 0);
});
turn.first = turn.servers[0];
// Create selectable servers.
var lang = translationLanguage.lang;
for (i=0; i<turn.servers.length; i++) {
var label = turn.servers[i].label;
if (turn.servers[i].i18n && turn.servers[i].i18n[lang]) {
label = turn.servers[i].label = turn.servers[i].i18n[lang];
}
if (label === "hidden") {
continue;
}
if (!label) {
// Use id as label.
label = turn.servers[i].label = turn.servers[i].id;
}
if (!label) {
// Use index as label.
label = turn.servers[i].label = ''+i;
}
serversSelectable.push(turn.servers[i]);
}
// Add auto zone if geo URI and available zones.
if (turn.geo_uri && turn.servers.length > 0) {
serversSelectable.unshift({
"id": "auto",
"label": "auto"
})
}
// Make created data available.
turn.serverMap = servers;
turn.serversSelectable = serversSelectable;
}
var urls;
if (turn.preferred) {
for (i=0; i<turn.preferred.length; i++) {
if (turn.serverMap.hasOwnProperty(turn.preferred[i])) {
urls = turn.serverMap[turn.preferred[i]].urns;
break;
}
}
}
if (!urls && turn.first) {
urls = turn.first.urns;
}
turnData.urls = urls;
} else if (turn && turn.urls) {
// Simple case, single region.
turnData.urls = turn.urls
} else {
// Unknown data.
turnData.urls = [];
}
console.log("TURN servers selected: ", turnData.urls, turnData.ttl, turn.preferred || null);
service.e.triggerHandler("apply", [turnData]);
return turnData;
};
service.refresh = function(withGeo) {
$timeout.cancel(geoRefresh);
var turn = service.data;
service.e.triggerHandler("refresh", [turn]);
if (turn.selected === "auto" && turn.geo_uri) {
if (geoPreferred !== null) {
// Use existing data.
turn.preferred = geoPreferred;
} else {
if (!withGeo) {
// Avoid triggering spurious GEO request for fast updates.
geoRefresh = $timeout(function() {
service.refresh(true);
}, 1000);
return;
}
// Run Geo request.
var nonce = randomGen.random({hex: true});
$http({
method: "POST",
url: turn.geo_uri,
headers: {"Content-Type": "application/x-www-form-urlencoded"},
data: "nonce="+encodeURIComponent(nonce)+"&username="+encodeURIComponent(turn.username)+"&password="+encodeURIComponent(turn.password),
timeout: geoRequestTimeout
}).then(function(response) {
// success
if (turn !== service.data) {
// No longer our data.
return;
}
if (response.status === 200) {
var data = response.data;
if (data.success && data.nonce === nonce) {
geoPreferred = turn.preferred = data.geo.prefer;
console.log("TURN GEO auto selected: ", turn.preferred);
service.apply();
}
}
}, function(response) {
// failed
if (turn !== service.data) {
// No longer our data.
return;
}
console.warn("TURN GEO failed:", response.status, response);
$timeout.cancel(ttlTimeout);
ttlTimeout = $timeout(function() {
// Fast retry.
console.warn("TURN GEO failed - refreshing early.")
api.sendSelf();
}, geoFastRetryTimeout)
})
}
} else {
// Set directly.
turn.preferred = [];
if (turn.selected) {
turn.preferred.push(turn.selected);
}
}
service.apply();
};
service.update = function(turn) {
$timeout.cancel(ttlTimeout);
if (service.data && service.data.preferred) {
// Keep preferred list if there is one.
turn.preferred = service.data.preferred;
}
service.data = turn;
service.refresh()
// Support to refresh TURN data when ttl was reached.
if (turn.ttl) {
var timer = turn.ttl * 0.01 * refreshPercentile * 1000;
if (timer < refreshMinimumInterval) {
timer = refreshMinimumInterval;
}
ttlTimeout = $timeout(function() {
console.log("TURN TTL reached - sending refresh request.");
api.sendSelf();
}, timer);
}
};
service.cancel = function() {
$timeout.cancel(ttlTimeout);
}
appData.e.on("userSettingsLoaded", service.refresh);
return service;
}]
})