22 changed files with 493 additions and 41 deletions
|
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
/* |
||||
* 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/>.
|
||||
* |
||||
*/ |
||||
|
||||
package channelling |
||||
|
||||
import ( |
||||
"log" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/strukturag/spreed-turnservicecli/turnservicecli" |
||||
) |
||||
|
||||
type TURNServiceManager interface { |
||||
TurnDataCreator |
||||
} |
||||
|
||||
type turnServiceManager struct { |
||||
sync.Mutex |
||||
pleaders map[uint64]Sender // Mapping of clients waiting to receive TURN data.
|
||||
|
||||
uri string |
||||
accessToken string |
||||
clientID string |
||||
turnService *turnservicecli.TURNService |
||||
} |
||||
|
||||
func NewTURNServiceManager(uri string, accessToken string, clientID string) TURNServiceManager { |
||||
turnService := turnservicecli.NewTURNService(uri, 0, nil) |
||||
mgr := &turnServiceManager{ |
||||
uri: uri, |
||||
accessToken: accessToken, |
||||
clientID: clientID, |
||||
|
||||
turnService: turnService, |
||||
pleaders: make(map[uint64]Sender), |
||||
} |
||||
|
||||
turnService.Open(accessToken, clientID, "") |
||||
turnService.BindOnCredentials(mgr.onCredentials) |
||||
log.Println("Fetching TURN credentials from service") |
||||
go func() { |
||||
//time.Sleep(10000 * time.Millisecond)
|
||||
turnService.Autorefresh(true) |
||||
}() |
||||
// Wait a bit, to give TURN service some time to populate credentials, so
|
||||
// we avoid to have send them as an update for fast reconnecting clients.
|
||||
time.Sleep(500 * time.Millisecond) |
||||
if mgr.turnService.Credentials(false) == nil { |
||||
log.Println("No TURN credentials from service on startup - extra traffic for clients connecting before credentials have been received") |
||||
} |
||||
|
||||
return mgr |
||||
} |
||||
|
||||
func (mgr *turnServiceManager) CreateTurnData(sender Sender, session *Session) *DataTurn { |
||||
credentials := mgr.turnService.Credentials(false) |
||||
turn, err := mgr.turnData(credentials) |
||||
if err != nil || turn.Ttl == 0 { |
||||
// When no data was return from service, refresh quickly.
|
||||
mgr.Lock() |
||||
mgr.pleaders[sender.Index()] = sender |
||||
mgr.Unlock() |
||||
|
||||
// Have client come back early.
|
||||
turn.Ttl = 300 |
||||
} |
||||
|
||||
return turn |
||||
} |
||||
|
||||
func (mgr *turnServiceManager) turnData(credentials *turnservicecli.CachedCredentialsData) (*DataTurn, error) { |
||||
turn := &DataTurn{} |
||||
if credentials != nil { |
||||
ttl := credentials.TTL() |
||||
if ttl > 0 { |
||||
turn.Username = credentials.Turn.Username |
||||
turn.Password = credentials.Turn.Password |
||||
turn.Servers = credentials.Turn.Servers |
||||
turn.Ttl = int(ttl) |
||||
turn.GeoURI = credentials.Turn.GeoURI |
||||
} |
||||
} |
||||
|
||||
return turn, nil |
||||
} |
||||
|
||||
func (mgr *turnServiceManager) onCredentials(credentials *turnservicecli.CachedCredentialsData, err error) { |
||||
if err != nil { |
||||
log.Printf("TURN credentials service error: %s\n", err.Error()) |
||||
return |
||||
} |
||||
|
||||
log.Println("Received TURN credentials from service", credentials.Turn.Username) |
||||
|
||||
mgr.Lock() |
||||
for _, sender := range mgr.pleaders { |
||||
if turn, err := mgr.turnData(credentials); err == nil { |
||||
sender.Outgoing(&DataTurnUpdate{ |
||||
Type: "TurnUpdate", |
||||
Turn: turn, |
||||
}) |
||||
} |
||||
} |
||||
mgr.pleaders = make(map[uint64]Sender) // Clear.
|
||||
mgr.Unlock() |
||||
} |
||||
@ -0,0 +1,180 @@
@@ -0,0 +1,180 @@
|
||||
/* |
||||
* 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.
|
||||
|
||||
// turnData
|
||||
return ["$timeout", "$http", "api", "randomGen", "appData", function($timeout, $http, api, randomGen, appData) { |
||||
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.
|
||||
if (!turn.serverMap) { |
||||
var servers = {}; |
||||
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]; |
||||
if (turn.geo_uri) { |
||||
turn.servers.unshift({ |
||||
"id": "auto" |
||||
}) |
||||
} |
||||
turn.serverMap = servers; |
||||
} |
||||
var urls; |
||||
if (turn.preferred) { |
||||
for (var 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, 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) { |
||||
ttlTimeout = $timeout(function() { |
||||
console.log("TURN TTL reached - sending refresh request."); |
||||
api.sendSelf(); |
||||
}, turn.ttl * 0.01 * refreshPercentile * 1000); |
||||
} |
||||
}; |
||||
|
||||
service.cancel = function() { |
||||
$timeout.cancel(ttlTimeout); |
||||
} |
||||
|
||||
appData.e.on("userSettingsLoaded", service.refresh); |
||||
|
||||
return service; |
||||
}] |
||||
}) |
||||
Loading…
Reference in new issue