File: source/ice-connection.js

  1. /**
  2. * <blockquote class="info">
  3. * Learn more about how ICE works in this
  4. * <a href="https://temasys.com.sg/ice-what-is-this-sorcery/">article here</a>.
  5. * </blockquote>
  6. * The list of Peer connection ICE connection states.
  7. * @attribute ICE_CONNECTION_STATE
  8. * @param {String} CHECKING <small>Value <code>"checking"</code></small>
  9. * The value of the state when Peer connection is checking for a suitable matching pair of
  10. * ICE candidates to establish ICE connection.
  11. * <small>Exchanging of ICE candidates happens during <a href="#event_candidateGenerationState">
  12. * <code>candidateGenerationState</code> event</a>.</small>
  13. * @param {String} CONNECTED <small>Value <code>"connected"</code></small>
  14. * The value of the state when Peer connection has found a suitable matching pair of
  15. * ICE candidates to establish ICE connection but is still checking for a better
  16. * suitable matching pair of ICE candidates for the best ICE connectivity.
  17. * <small>At this state, ICE connection is already established and audio, video and
  18. * data streaming has already started.</small>
  19. * @param {String} COMPLETED <small>Value <code>"completed"</code></small>
  20. * The value of the state when Peer connection has found the best suitable matching pair
  21. * of ICE candidates to establish ICE connection and checking has stopped.
  22. * <small>At this state, ICE connection is already established and audio, video and
  23. * data streaming has already started. This may happpen after <code>CONNECTED</code>.</small>
  24. * @param {String} FAILED <small>Value <code>"failed"</code></small>
  25. * The value of the state when Peer connection ICE connection has failed.
  26. * @param {String} DISCONNECTED <small>Value <code>"disconnected"</code></small>
  27. * The value of the state when Peer connection ICE connection is disconnected.
  28. * <small>At this state, the Peer connection may attempt to revive the ICE connection.
  29. * This may happen due to flaky network conditions.</small>
  30. * @param {String} CLOSED <small>Value <code>"closed"</code></small>
  31. * The value of the state when Peer connection ICE connection has closed.
  32. * <small>This happens when Peer connection is closed and no streaming can occur at this stage.</small>
  33. * @param {String} TRICKLE_FAILED <small>Value <code>"trickeFailed"</code></small>
  34. * The value of the state when Peer connection ICE connection has failed during trickle ICE.
  35. * <small>Trickle ICE is enabled in <a href="#method_init"><code>init()</code> method</a>
  36. * <code>enableIceTrickle</code> option.</small>
  37. * @type JSON
  38. * @readOnly
  39. * @for Skylink
  40. * @since 0.1.0
  41. */
  42. Skylink.prototype.ICE_CONNECTION_STATE = {
  43. STARTING: 'starting',
  44. CHECKING: 'checking',
  45. CONNECTED: 'connected',
  46. COMPLETED: 'completed',
  47. CLOSED: 'closed',
  48. FAILED: 'failed',
  49. TRICKLE_FAILED: 'trickleFailed',
  50. DISCONNECTED: 'disconnected'
  51. };
  52.  
  53. /**
  54. * <blockquote class="info">
  55. * Note that configuring the protocol may not necessarily result in the desired network transports protocol
  56. * used in the actual TURN network traffic as it depends which protocol the browser selects and connects with.
  57. * This simply configures the TURN ICE server urls <code?transport=(protocol)</code> query option when constructing
  58. * the Peer connection. When all protocols are selected, the ICE servers urls are duplicated with all protocols.
  59. * </blockquote>
  60. * The list of TURN network transport protocols options when constructing Peer connections
  61. * configured in the <a href="#method_init"><code>init()</code> method</a>.
  62. * <small>Example <code>.urls</code> inital input: [<code>"turn:server.com?transport=tcp"</code>,
  63. * <code>"turn:server1.com:3478"</code>, <code>"turn:server.com?transport=udp"</code>]</small>
  64. * @attribute TURN_TRANSPORT
  65. * @param {String} TCP <small>Value <code>"tcp"</code></small>
  66. * The value of the option to configure using only TCP network transport protocol.
  67. * <small>Example <code>.urls</code> output: [<code>"turn:server.com?transport=tcp"</code>,
  68. * <code>"turn:server1.com:3478?transport=tcp"</code>]</small>
  69. * @param {String} UDP <small>Value <code>"udp"</code></small>
  70. * The value of the option to configure using only UDP network transport protocol.
  71. * <small>Example <code>.urls</code> output: [<code>"turn:server.com?transport=udp"</code>,
  72. * <code>"turn:server1.com:3478?transport=udp"</code>]</small>
  73. * @param {String} ANY <small>Value <code>"any"</code></small>
  74. * The value of the option to configure using any network transport protocols configured from the Signaling server.
  75. * <small>Example <code>.urls</code> output: [<code>"turn:server.com?transport=tcp"</code>,
  76. * <code>"turn:server1.com:3478"</code>, <code>"turn:server.com?transport=udp"</code>]</small>
  77. * @param {String} NONE <small>Value <code>"none"</code></small>
  78. * The value of the option to not configure using any network transport protocols.
  79. * <small>Example <code>.urls</code> output: [<code>"turn:server.com"</code>, <code>"turn:server1.com:3478"</code>]</small>
  80. * <small>Configuring this does not mean that no protocols will be used, but
  81. * rather removing <code>?transport=(protocol)</code> query option in
  82. * the TURN ICE server <code>.urls</code> when constructing the Peer connection.</small>
  83. * @param {String} ALL <small>Value <code>"all"</code></small>
  84. * The value of the option to configure using both TCP and UDP network transport protocols.
  85. * <small>Example <code>.urls</code> output: [<code>"turn:server.com?transport=tcp"</code>,
  86. * <code>"turn:server.com?transport=udp"</code>, <code>"turn:server1.com:3478?transport=tcp"</code>,
  87. * <code>"turn:server1.com:3478?transport=udp"</code>]</small>
  88. * @type JSON
  89. * @readOnly
  90. * @for Skylink
  91. * @since 0.5.4
  92. */
  93. Skylink.prototype.TURN_TRANSPORT = {
  94. UDP: 'udp',
  95. TCP: 'tcp',
  96. ANY: 'any',
  97. NONE: 'none',
  98. ALL: 'all'
  99. };
  100.  
  101. /**
  102. * Function that filters and configures the ICE servers received from Signaling
  103. * based on the <code>init()</code> configuration and returns the updated
  104. * list of ICE servers to be used when constructing Peer connection.
  105. * @method _setIceServers
  106. * @private
  107. * @for Skylink
  108. * @since 0.5.4
  109. */
  110. Skylink.prototype._setIceServers = function(givenConfig) {
  111. var self = this;
  112. var givenIceServers = clone(givenConfig.iceServers);
  113. var iceServersList = {};
  114. var newIceServers = [];
  115. // TURN SSL config
  116. var useTURNSSLProtocol = false;
  117. var useTURNSSLPort = false;
  118.  
  119.  
  120.  
  121. if (self._forceTURNSSL) {
  122. if (window.webrtcDetectedBrowser === 'chrome' ||
  123. window.webrtcDetectedBrowser === 'safari' ||
  124. window.webrtcDetectedBrowser === 'IE') {
  125. useTURNSSLProtocol = true;
  126. } else {
  127. useTURNSSLPort = true;
  128. }
  129. }
  130.  
  131. log.log('TURN server connections SSL configuration', {
  132. useTURNSSLProtocol: useTURNSSLProtocol,
  133. useTURNSSLPort: useTURNSSLPort
  134. });
  135.  
  136. var pushIceServer = function (username, credential, url, index) {
  137. if (!iceServersList[username]) {
  138. iceServersList[username] = {};
  139. }
  140.  
  141. if (!iceServersList[username][credential]) {
  142. iceServersList[username][credential] = [];
  143. }
  144.  
  145. if (iceServersList[username][credential].indexOf(url) === -1) {
  146. if (typeof index === 'number') {
  147. iceServersList[username][credential].splice(index, 0, url);
  148. } else {
  149. iceServersList[username][credential].push(url);
  150. }
  151. }
  152. };
  153.  
  154. var i, serverItem;
  155.  
  156. for (i = 0; i < givenIceServers.length; i++) {
  157. var server = givenIceServers[i];
  158.  
  159. if (typeof server.url !== 'string') {
  160. log.warn('Ignoring ICE server provided at index ' + i, clone(server));
  161. continue;
  162. }
  163.  
  164. if (server.url.indexOf('stun') === 0) {
  165. if (!self._enableSTUN) {
  166. log.warn('Ignoring STUN server provided at index ' + i, clone(server));
  167. continue;
  168. }
  169.  
  170. if (!self._usePublicSTUN && server.url.indexOf('temasys') === -1) {
  171. log.warn('Ignoring public STUN server provided at index ' + i, clone(server));
  172. continue;
  173. }
  174.  
  175. } else if (server.url.indexOf('turn') === 0) {
  176. if (!self._enableTURN) {
  177. log.warn('Ignoring TURN server provided at index ' + i, clone(server));
  178. continue;
  179. }
  180.  
  181. if (server.url.indexOf(':443') === -1 && useTURNSSLPort) {
  182. log.log('Ignoring TURN Server (non-SSL port) provided at index ' + i, clone(server));
  183. continue;
  184. }
  185.  
  186. if (useTURNSSLProtocol) {
  187. var parts = server.url.split(':');
  188. parts[0] = 'turns';
  189. server.url = parts.join(':');
  190. }
  191. }
  192.  
  193. // parse "@" settings
  194. if (server.url.indexOf('@') > 0) {
  195. var protocolParts = server.url.split(':');
  196. var urlParts = protocolParts[1].split('@');
  197. server.username = urlParts[0];
  198. server.url = protocolParts[0] + ':' + urlParts[1];
  199.  
  200. // add the ICE server port
  201. // Edge uses 3478 with ?transport=udp for now
  202. if (window.webrtcDetectedBrowser === 'edge') {
  203. server.url += ':3478';
  204. } else if (protocolParts[2]) {
  205. server.url += ':' + protocolParts[2];
  206. }
  207. }
  208.  
  209. var username = typeof server.username === 'string' ? server.username : 'none';
  210. var credential = typeof server.credential === 'string' ? server.credential : 'none';
  211.  
  212. if (server.url.indexOf('turn') === 0) {
  213. if (self._TURNTransport === self.TURN_TRANSPORT.ANY) {
  214. pushIceServer(username, credential, server.url);
  215.  
  216. } else {
  217. var rawUrl = server.url;
  218.  
  219. if (rawUrl.indexOf('?transport=') > 0) {
  220. rawUrl = rawUrl.split('?transport=')[0];
  221. }
  222.  
  223. if (self._TURNTransport === self.TURN_TRANSPORT.NONE) {
  224. pushIceServer(username, credential, rawUrl);
  225. } else if (self._TURNTransport === self.TURN_TRANSPORT.UDP) {
  226. pushIceServer(username, credential, rawUrl + '?transport=udp');
  227. } else if (self._TURNTransport === self.TURN_TRANSPORT.TCP) {
  228. pushIceServer(username, credential, rawUrl + '?transport=tcp');
  229. } else if (self._TURNTransport === self.TURN_TRANSPORT.ALL) {
  230. pushIceServer(username, credential, rawUrl + '?transport=tcp');
  231. pushIceServer(username, credential, rawUrl + '?transport=udp');
  232. } else {
  233. log.warn('Invalid TURN transport option "' + self._TURNTransport +
  234. '". Ignoring TURN server at index' + i, clone(server));
  235. continue;
  236. }
  237. }
  238. } else {
  239. pushIceServer(username, credential, server.url);
  240. }
  241. }
  242.  
  243. // add mozilla STUN for firefox
  244. if (self._enableSTUN && self._usePublicSTUN && window.webrtcDetectedBrowser === 'firefox') {
  245. pushIceServer('none', 'none', 'stun:stun.services.mozilla.com', 0);
  246. }
  247.  
  248. var hasUrlsSupport = false;
  249.  
  250. if (window.webrtcDetectedBrowser === 'chrome' && window.webrtcDetectedVersion > 34) {
  251. hasUrlsSupport = true;
  252. }
  253.  
  254. if (window.webrtcDetectedBrowser === 'firefox' && window.webrtcDetectedVersion > 38) {
  255. hasUrlsSupport = true;
  256. }
  257.  
  258. if (window.webrtcDetectedBrowser === 'opera' && window.webrtcDetectedVersion > 31) {
  259. hasUrlsSupport = true;
  260. }
  261.  
  262. // plugin supports .urls
  263. if (window.webrtcDetectedBrowser === 'safari' || window.webrtcDetectedBrowser === 'IE') {
  264. hasUrlsSupport = true;
  265. }
  266.  
  267. // bowser / edge
  268. if (['bowser', 'edge'].indexOf(window.webrtcDetectedBrowser) > -1) {
  269. hasUrlsSupport = true;
  270. }
  271.  
  272. for (var serverUsername in iceServersList) {
  273. if (iceServersList.hasOwnProperty(serverUsername)) {
  274. for (var serverCred in iceServersList[serverUsername]) {
  275. if (iceServersList[serverUsername].hasOwnProperty(serverCred)) {
  276. if (hasUrlsSupport) {
  277. var urlsItem = {
  278. urls: iceServersList[serverUsername][serverCred]
  279. };
  280. if (serverUsername !== 'none') {
  281. urlsItem.username = serverUsername;
  282. }
  283. if (serverCred !== 'none') {
  284. urlsItem.credential = serverCred;
  285. }
  286.  
  287. // Edge uses 1 url only for now
  288. if (window.webrtcDetectedBrowser === 'edge') {
  289. if (urlsItem.username && urlsItem.credential) {
  290. urlsItem.urls = [urlsItem.urls[0]];
  291. newIceServers.push(urlsItem);
  292. break;
  293. }
  294. } else {
  295. newIceServers.push(urlsItem);
  296. }
  297. } else {
  298. for (var j = 0; j < iceServersList[serverUsername][serverCred].length; j++) {
  299. var urlItem = {
  300. url: iceServersList[serverUsername][serverCred][j]
  301. };
  302. if (serverUsername !== 'none') {
  303. urlItem.username = serverUsername;
  304. }
  305. if (serverCred !== 'none') {
  306. urlItem.credential = serverCred;
  307. }
  308. newIceServers.push(urlItem);
  309. }
  310. }
  311. }
  312. }
  313. }
  314. }
  315.  
  316. if (self._iceServer) {
  317. var nUsername = null, nCredential = null;
  318. for (i = 0; i < newIceServers.length; i++) {
  319. if (newIceServers[i].username) {
  320. nUsername = newIceServers[i].username;
  321. }
  322. if (newIceServers[i].credential) {
  323. nCredential = newIceServers[i].credential;
  324. }
  325. }
  326. newIceServers = [{
  327. urls: self._iceServer.urls,
  328. username: nUsername,
  329. credential: nCredential
  330. }];
  331. }
  332.  
  333. log.log('Output iceServers configuration:', newIceServers);
  334.  
  335. return {
  336. iceServers: newIceServers
  337. };
  338. };