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(passedIceServers) {
  var self = this;
  var iceServerName = null;
  var iceServerProtocol = 'stun';
  var iceServerPorts = {
    udp: [3478, 19302, 19303, 19304],
    tcp: [80, 443],
    both: [19305, 19306, 19307, 19308]
  };
  var iceServers = [
    // Public
    { urls: [] },
    // Private
    { urls: [] }
  ];

  // Note: Provide only 1 single TURN! turn:xxxx.io for custom TURN servers. Ignores custom ports.
  passedIceServers.forEach(function (server) {
    if (server.url.indexOf('stun:') === 0) {
      if (server.url.indexOf('temasys') > 0) {
        // server[?transport=xxx]
        iceServerName = (server.url.split(':')[1] || '').split('?')[0] || null;
      } else {
        iceServers[0].urls.push(server.url);
      }

    } else if (server.url.indexOf('turn:') === 0 && server.url.indexOf('@') > 0 && server.credential &&
      !(iceServers[1].username || iceServers[1].credential)) {
      var parts = server.url.split(':');
      var urlParts = (parts[1] || '').split('@');

      // server[?transport=xxx]
      iceServerName = (urlParts[1] || '').split('?')[0];
      iceServers[1].username = urlParts[0];
      iceServers[1].credential = server.credential;
      iceServerProtocol = 'turn';
    }
  });

  if (self._iceServer) {
    iceServers = [{
      urls: self._iceServer.urls,
      username: iceServers[1].username,
      credential: iceServers[1].credential
    }];

  } else {
    iceServerName = iceServerName || 'turn.temasys.io';

    if (AdapterJS.webrtcDetectedBrowser === 'edge') {
      iceServerPorts.udp = [3478];
      iceServerPorts.tcp = [];
      iceServerPorts.both = [];
      iceServerProtocol = 'turn';

    } else if (self._forceTURNSSL) {
      if (AdapterJS.webrtcDetectedBrowser === 'firefox' && AdapterJS.webrtcDetectedVersion < 53) {
        iceServerPorts.udp = [];
        iceServerPorts.tcp = [443];
        iceServerPorts.both = [];
        iceServerProtocol = 'turn';

      } else {
        iceServerPorts.udp = [];
        iceServerProtocol = 'turns';
      }

    // Limit the number of ports..
    } else if (AdapterJS.webrtcDetectedBrowser === 'firefox') {
      iceServerPorts.udp = [3478];
      iceServerPorts.tcp = [443, 80];
    }

    if (self._TURNTransport === self.TURN_TRANSPORT.UDP && !self._forceTURNSSL) {
      iceServerPorts.udp = iceServerPorts.udp.concat(iceServerPorts.both);
      iceServerPorts.tcp = [];
      iceServerPorts.both = [];

    } else if (self._TURNTransport === self.TURN_TRANSPORT.TCP) {
      iceServerPorts.tcp = iceServerPorts.tcp.concat(iceServerPorts.both);
      iceServerPorts.udp = [];
      iceServerPorts.both = [];

    } else if (self._TURNTransport === self.TURN_TRANSPORT.NONE) {
      iceServerPorts.tcp = [];
      iceServerPorts.udp = [];
    }

    if (iceServerProtocol === 'stun') {
      iceServerPorts.tcp = [];
    }

    iceServerPorts.tcp.forEach(function (tcpPort) {
      iceServers[1].urls.push(iceServerProtocol + ':' + iceServerName + ':' + tcpPort + '?transport=tcp');
    });

    iceServerPorts.udp.forEach(function (udpPort) {
      iceServers[1].urls.push(iceServerProtocol + ':' + iceServerName + ':' + udpPort + '?transport=udp');
    });

    iceServerPorts.both.forEach(function (bothPort) {
      iceServers[1].urls.push(iceServerProtocol + ':' + iceServerName + ':' + bothPort);
    });
  }

  if (!self._usePublicSTUN) {
    iceServers.splice(0, 1);
  }

  log.log('Output iceServers configuration:', iceServers);  

  return {
    iceServers: iceServers
  };
};