File: source/ice-connection.js

                      /**
                       * <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 self = this;
                        var givenIceServers = clone(givenConfig.iceServers);
                        var iceServersList = {};
                        var newIceServers = [];
                        // TURN SSL config
                        var useTURNSSLProtocol = false;
                        var useTURNSSLPort = false;
                      
                      
                      
                        if (self._forceTURNSSL) {
                          if (window.webrtcDetectedBrowser === 'chrome' ||
                            window.webrtcDetectedBrowser === 'safari' ||
                            window.webrtcDetectedBrowser === 'IE') {
                            useTURNSSLProtocol = true;
                          } 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 (!self._enableSTUN) {
                              log.warn('Ignoring STUN server provided at index ' + i, clone(server));
                              continue;
                            }
                      
                            if (!self._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 (!self._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
                            // Edge uses 3478 with ?transport=udp for now
                            if (window.webrtcDetectedBrowser === 'edge') {
                              server.url += ':3478';
                            } else 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 (self._TURNTransport === self.TURN_TRANSPORT.ANY) {
                              pushIceServer(username, credential, server.url);
                      
                            } else {
                              var rawUrl = server.url;
                      
                              if (rawUrl.indexOf('?transport=') > 0) {
                                rawUrl = rawUrl.split('?transport=')[0];
                              }
                      
                              if (self._TURNTransport === self.TURN_TRANSPORT.NONE) {
                                pushIceServer(username, credential, rawUrl);
                              } else if (self._TURNTransport === self.TURN_TRANSPORT.UDP) {
                                pushIceServer(username, credential, rawUrl + '?transport=udp');
                              } else if (self._TURNTransport === self.TURN_TRANSPORT.TCP) {
                                pushIceServer(username, credential, rawUrl + '?transport=tcp');
                              } else if (self._TURNTransport === self.TURN_TRANSPORT.ALL) {
                                pushIceServer(username, credential, rawUrl + '?transport=tcp');
                                pushIceServer(username, credential, rawUrl + '?transport=udp');
                              } else {
                                log.warn('Invalid TURN transport option "' + self._TURNTransport +
                                  '". Ignoring TURN server at index' + i, clone(server));
                                continue;
                              }
                            }
                          } else {
                            pushIceServer(username, credential, server.url);
                          }
                        }
                      
                        // add mozilla STUN for firefox
                        if (self._enableSTUN && self._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;
                        }
                      
                        // bowser / edge
                        if (['bowser', 'edge'].indexOf(window.webrtcDetectedBrowser) > -1) {
                          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;
                                  }
                      
                                  // Edge uses 1 url only for now
                                  if (window.webrtcDetectedBrowser === 'edge') {
                                    if (urlsItem.username && urlsItem.credential) {
                                      urlsItem.urls = [urlsItem.urls[0]];
                                      newIceServers.push(urlsItem);
                                      break;
                                    }
                                  } else {
                                    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);
                                  }
                                }
                              }
                            }
                          }
                        }
                      
                        if (self._iceServer) {
                          var nUsername = null, nCredential = null;
                          for (i = 0; i < newIceServers.length; i++) {
                            if (newIceServers[i].username) {
                              nUsername = newIceServers[i].username;
                            }
                            if (newIceServers[i].credential) {
                              nCredential = newIceServers[i].credential;
                            }
                          }
                          newIceServers = [{
                            urls: self._iceServer.urls,
                            username: nUsername,
                            credential: nCredential
                          }];
                        }
                      
                        log.log('Output iceServers configuration:', newIceServers);
                      
                        return {
                          iceServers: newIceServers
                        };
                      };