/**
* <blockquote class="info">
* Learn more about how ICE works in this
* <a href="https://temasys.com.sg/ice-what-is-this-sorcery/">article here</a>.
* </blockquote>
* The list of Peer connection ICE connection states.
* @attribute ICE_CONNECTION_STATE
* @param {String} CHECKING <small>Value <code>"checking"</code></small>
* The value of the state when Peer connection is checking for a suitable matching pair of
* ICE candidates to establish ICE connection.
* <small>Exchanging of ICE candidates happens during <a href="#event_candidateGenerationState">
* <code>candidateGenerationState</code> event</a>.</small>
* @param {String} CONNECTED <small>Value <code>"connected"</code></small>
* The value of the state when Peer connection has found a suitable matching pair of
* ICE candidates to establish ICE connection but is still checking for a better
* suitable matching pair of ICE candidates for the best ICE connectivity.
* <small>At this state, ICE connection is already established and audio, video and
* data streaming has already started.</small>
* @param {String} COMPLETED <small>Value <code>"completed"</code></small>
* The value of the state when Peer connection has found the best suitable matching pair
* of ICE candidates to establish ICE connection and checking has stopped.
* <small>At this state, ICE connection is already established and audio, video and
* data streaming has already started. This may happpen after <code>CONNECTED</code>.</small>
* @param {String} FAILED <small>Value <code>"failed"</code></small>
* The value of the state when Peer connection ICE connection has failed.
* @param {String} DISCONNECTED <small>Value <code>"disconnected"</code></small>
* The value of the state when Peer connection ICE connection is disconnected.
* <small>At this state, the Peer connection may attempt to revive the ICE connection.
* This may happen due to flaky network conditions.</small>
* @param {String} CLOSED <small>Value <code>"closed"</code></small>
* The value of the state when Peer connection ICE connection has closed.
* <small>This happens when Peer connection is closed and no streaming can occur at this stage.</small>
* @param {String} TRICKLE_FAILED <small>Value <code>"trickeFailed"</code></small>
* The value of the state when Peer connection ICE connection has failed during trickle ICE.
* <small>Trickle ICE is enabled in <a href="#method_init"><code>init()</code> method</a>
* <code>enableIceTrickle</code> option.</small>
* @type JSON
* @readOnly
* @for Skylink
* @since 0.1.0
*/
Skylink.prototype.ICE_CONNECTION_STATE = {
STARTING: 'starting',
CHECKING: 'checking',
CONNECTED: 'connected',
COMPLETED: 'completed',
CLOSED: 'closed',
FAILED: 'failed',
TRICKLE_FAILED: 'trickleFailed',
DISCONNECTED: 'disconnected'
};
/**
* <blockquote class="info">
* Note that configuring the protocol may not necessarily result in the desired network transports protocol
* used in the actual TURN network traffic as it depends which protocol the browser selects and connects with.
* This simply configures the TURN ICE server urls <code?transport=(protocol)</code> query option when constructing
* the Peer connection. When all protocols are selected, the ICE servers urls are duplicated with all protocols.
* </blockquote>
* The list of TURN network transport protocols options when constructing Peer connections
* configured in the <a href="#method_init"><code>init()</code> method</a>.
* <small>Example <code>.urls</code> inital input: [<code>"turn:server.com?transport=tcp"</code>,
* <code>"turn:server1.com:3478"</code>, <code>"turn:server.com?transport=udp"</code>]</small>
* @attribute TURN_TRANSPORT
* @param {String} TCP <small>Value <code>"tcp"</code></small>
* The value of the option to configure using only TCP network transport protocol.
* <small>Example <code>.urls</code> output: [<code>"turn:server.com?transport=tcp"</code>,
* <code>"turn:server1.com:3478?transport=tcp"</code>]</small>
* @param {String} UDP <small>Value <code>"udp"</code></small>
* The value of the option to configure using only UDP network transport protocol.
* <small>Example <code>.urls</code> output: [<code>"turn:server.com?transport=udp"</code>,
* <code>"turn:server1.com:3478?transport=udp"</code>]</small>
* @param {String} ANY <small>Value <code>"any"</code></small>
* The value of the option to configure using any network transport protocols configured from the Signaling server.
* <small>Example <code>.urls</code> output: [<code>"turn:server.com?transport=tcp"</code>,
* <code>"turn:server1.com:3478"</code>, <code>"turn:server.com?transport=udp"</code>]</small>
* @param {String} NONE <small>Value <code>"none"</code></small>
* The value of the option to not configure using any network transport protocols.
* <small>Example <code>.urls</code> output: [<code>"turn:server.com"</code>, <code>"turn:server1.com:3478"</code>]</small>
* <small>Configuring this does not mean that no protocols will be used, but
* rather removing <code>?transport=(protocol)</code> query option in
* the TURN ICE server <code>.urls</code> when constructing the Peer connection.</small>
* @param {String} ALL <small>Value <code>"all"</code></small>
* The value of the option to configure using both TCP and UDP network transport protocols.
* <small>Example <code>.urls</code> output: [<code>"turn:server.com?transport=tcp"</code>,
* <code>"turn:server.com?transport=udp"</code>, <code>"turn:server1.com:3478?transport=tcp"</code>,
* <code>"turn:server1.com:3478?transport=udp"</code>]</small>
* @type JSON
* @readOnly
* @for Skylink
* @since 0.5.4
*/
Skylink.prototype.TURN_TRANSPORT = {
UDP: 'udp',
TCP: 'tcp',
ANY: 'any',
NONE: 'none',
ALL: 'all'
};
/**
* Function that filters and configures the ICE servers received from Signaling
* based on the <code>init()</code> configuration and returns the updated
* list of ICE servers to be used when constructing Peer connection.
* @method _setIceServers
* @private
* @for Skylink
* @since 0.5.4
*/
Skylink.prototype._setIceServers = function(givenConfig) {
var givenIceServers = clone(givenConfig.iceServers);
var iceServersList = {};
var newIceServers = [];
// TURN SSL config
var useTURNSSLProtocol = false;
var useTURNSSLPort = false;
if (this._forceTURNSSL) {
if (window.webrtcDetectedBrowser === 'chrome' ||
window.webrtcDetectedBrowser === 'safari' ||
window.webrtcDetectedBrowser === 'IE') {
useTURNSSLProtocol = true;
useTURNSSLPort = false;
} else {
useTURNSSLPort = true;
}
}
log.log('TURN server connections SSL configuration', {
useTURNSSLProtocol: useTURNSSLProtocol,
useTURNSSLPort: useTURNSSLPort
});
var pushIceServer = function (username, credential, url, index) {
if (!iceServersList[username]) {
iceServersList[username] = {};
}
if (!iceServersList[username][credential]) {
iceServersList[username][credential] = [];
}
if (iceServersList[username][credential].indexOf(url) === -1) {
if (typeof index === 'number') {
iceServersList[username][credential].splice(index, 0, url);
} else {
iceServersList[username][credential].push(url);
}
}
};
var i, serverItem;
for (i = 0; i < givenIceServers.length; i++) {
var server = givenIceServers[i];
if (typeof server.url !== 'string') {
log.warn('Ignoring ICE server provided at index ' + i, clone(server));
continue;
}
if (server.url.indexOf('stun') === 0) {
if (!this._enableSTUN) {
log.warn('Ignoring STUN server provided at index ' + i, clone(server));
continue;
}
if (!this._usePublicSTUN && server.url.indexOf('temasys') === -1) {
log.warn('Ignoring public STUN server provided at index ' + i, clone(server));
continue;
}
} else if (server.url.indexOf('turn') === 0) {
if (!this._enableTURN) {
log.warn('Ignoring TURN server provided at index ' + i, clone(server));
continue;
}
if (server.url.indexOf(':443') === -1 && useTURNSSLPort) {
log.log('Ignoring TURN Server (non-SSL port) provided at index ' + i, clone(server));
continue;
}
if (useTURNSSLProtocol) {
var parts = server.url.split(':');
parts[0] = 'turns';
server.url = parts.join(':');
}
}
// parse "@" settings
if (server.url.indexOf('@') > 0) {
var protocolParts = server.url.split(':');
var urlParts = protocolParts[1].split('@');
server.username = urlParts[0];
server.url = protocolParts[0] + ':' + urlParts[1];
// add the ICE server port
if (protocolParts[2]) {
server.url += ':' + protocolParts[2];
}
}
var username = typeof server.username === 'string' ? server.username : 'none';
var credential = typeof server.credential === 'string' ? server.credential : 'none';
if (server.url.indexOf('turn') === 0) {
if (this._TURNTransport === this.TURN_TRANSPORT.ANY) {
pushIceServer(username, credential, server.url);
} else {
var rawUrl = server.url;
if (rawUrl.indexOf('?transport=') > 0) {
rawUrl = rawUrl.split('?transport=')[0];
}
if (this._TURNTransport === this.TURN_TRANSPORT.NONE) {
pushIceServer(username, credential, rawUrl);
} else if (this._TURNTransport === this.TURN_TRANSPORT.UDP) {
pushIceServer(username, credential, rawUrl + '?transport=udp');
} else if (this._TURNTransport === this.TURN_TRANSPORT.TCP) {
pushIceServer(username, credential, rawUrl + '?transport=tcp');
} else if (this._TURNTransport === this.TURN_TRANSPORT.ALL) {
pushIceServer(username, credential, rawUrl + '?transport=tcp');
pushIceServer(username, credential, rawUrl + '?transport=udp');
} else {
log.warn('Invalid TURN transport option "' + this._TURNTransport +
'". Ignoring TURN server at index' + i, clone(server));
continue;
}
}
} else {
pushIceServer(username, credential, server.url);
}
}
// add mozilla STUN for firefox
if (this._enableSTUN && this._usePublicSTUN && window.webrtcDetectedBrowser === 'firefox') {
pushIceServer('none', 'none', 'stun:stun.services.mozilla.com', 0);
}
var hasUrlsSupport = false;
if (window.webrtcDetectedBrowser === 'chrome' && window.webrtcDetectedVersion > 34) {
hasUrlsSupport = true;
}
if (window.webrtcDetectedBrowser === 'firefox' && window.webrtcDetectedVersion > 38) {
hasUrlsSupport = true;
}
if (window.webrtcDetectedBrowser === 'opera' && window.webrtcDetectedVersion > 31) {
hasUrlsSupport = true;
}
// plugin supports .urls
if (window.webrtcDetectedBrowser === 'safari' || window.webrtcDetectedBrowser === 'IE') {
hasUrlsSupport = true;
}
for (var serverUsername in iceServersList) {
if (iceServersList.hasOwnProperty(serverUsername)) {
for (var serverCred in iceServersList[serverUsername]) {
if (iceServersList[serverUsername].hasOwnProperty(serverCred)) {
if (hasUrlsSupport) {
var urlsItem = {
urls: iceServersList[serverUsername][serverCred]
};
if (serverUsername !== 'none') {
urlsItem.username = serverUsername;
}
if (serverCred !== 'none') {
urlsItem.credential = serverCred;
}
newIceServers.push(urlsItem);
} else {
for (var j = 0; j < iceServersList[serverUsername][serverCred].length; j++) {
var urlItem = {
url: iceServersList[serverUsername][serverCred][j]
};
if (serverUsername !== 'none') {
urlItem.username = serverUsername;
}
if (serverCred !== 'none') {
urlItem.credential = serverCred;
}
newIceServers.push(urlItem);
}
}
}
}
}
}
log.log('Output iceServers configuration:', newIceServers);
return {
iceServers: newIceServers
};
};