main.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /*
  2. * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
  3. *
  4. * Use of this source code is governed by a BSD-style license
  5. * that can be found in the LICENSE file in the root of the source
  6. * tree.
  7. */
  8. 'use strict';
  9. const addButton = document.querySelector('button#add');
  10. const candidateTBody = document.querySelector('tbody#candidatesBody');
  11. const gatherButton = document.querySelector('button#gather');
  12. const passwordInput = document.querySelector('input#password');
  13. const removeButton = document.querySelector('button#remove');
  14. const resetButton = document.querySelector('button#reset');
  15. const servers = document.querySelector('select#servers');
  16. const urlInput = document.querySelector('input#url');
  17. const usernameInput = document.querySelector('input#username');
  18. const iceCandidatePoolInput = document.querySelector('input#iceCandidatePool');
  19. addButton.onclick = addServer;
  20. gatherButton.onclick = start;
  21. removeButton.onclick = removeServer;
  22. resetButton.onclick = (e) => {
  23. window.localStorage.clear();
  24. document.querySelectorAll('select#servers option').forEach(option => option.remove());
  25. const serversSelect = document.querySelector('select#servers');
  26. setDefaultServer(serversSelect);
  27. };
  28. iceCandidatePoolInput.onchange = function(e) {
  29. const span = e.target.parentElement.querySelector('span');
  30. span.textContent = e.target.value;
  31. };
  32. let begin;
  33. let pc;
  34. let candidates;
  35. const allServersKey = 'servers';
  36. function setDefaultServer(serversSelect) {
  37. const o = document.createElement('option');
  38. o.value = '{"urls":["turn:turn.wildfirechat.net:3478"],"username":"wfchat","credential":"wfchat1"}';
  39. o.text = 'turn:turn:wildfirechat.cn:3478 [wfchat:wfchat1]';
  40. serversSelect.add(o);
  41. }
  42. function writeServersToLocalStorage() {
  43. const serversSelect = document.querySelector('select#servers');
  44. const allServers = JSON.stringify(Object.values(serversSelect.options).map(o => JSON.parse(o.value)));
  45. window.localStorage.setItem(allServersKey, allServers);
  46. }
  47. function readServersFromLocalStorage() {
  48. document.querySelectorAll('select#servers option').forEach(option => option.remove());
  49. const serversSelect = document.querySelector('select#servers');
  50. const storedServers = window.localStorage.getItem(allServersKey);
  51. if (storedServers === null || storedServers === '') {
  52. setDefaultServer(serversSelect);
  53. } else {
  54. JSON.parse(storedServers).forEach((server, key) => {
  55. const o = document.createElement('option');
  56. o.value = JSON.stringify(server);
  57. o.text = server.urls[0];
  58. o.ondblclick = selectServer;
  59. serversSelect.add(o);
  60. });
  61. }
  62. }
  63. function selectServer(event) {
  64. const option = event.target;
  65. const value = JSON.parse(option.value);
  66. urlInput.value = value.urls[0];
  67. usernameInput.value = value.username || '';
  68. passwordInput.value = value.credential || '';
  69. }
  70. function addServer() {
  71. const scheme = urlInput.value.split(':')[0];
  72. if (scheme !== 'stun' && scheme !== 'turn' && scheme !== 'turns') {
  73. alert(`URI scheme ${scheme} is not valid`);
  74. return;
  75. }
  76. // Store the ICE server as a stringified JSON object in option.value.
  77. const option = document.createElement('option');
  78. const iceServer = {
  79. urls: [urlInput.value],
  80. username: usernameInput.value,
  81. credential: passwordInput.value
  82. };
  83. option.value = JSON.stringify(iceServer);
  84. option.text = `${urlInput.value} `;
  85. const username = usernameInput.value;
  86. const password = passwordInput.value;
  87. if (username || password) {
  88. option.text += (` [${username}:${password}]`);
  89. }
  90. option.ondblclick = selectServer;
  91. servers.add(option);
  92. urlInput.value = usernameInput.value = passwordInput.value = '';
  93. writeServersToLocalStorage();
  94. }
  95. function removeServer() {
  96. for (let i = servers.options.length - 1; i >= 0; --i) {
  97. if (servers.options[i].selected) {
  98. servers.remove(i);
  99. }
  100. }
  101. writeServersToLocalStorage();
  102. }
  103. function start() {
  104. // Clean out the table.
  105. while (candidateTBody.firstChild) {
  106. candidateTBody.removeChild(candidateTBody.firstChild);
  107. }
  108. gatherButton.disabled = true;
  109. // Read the values from the input boxes.
  110. const iceServers = [];
  111. for (let i = 0; i < servers.length; ++i) {
  112. iceServers.push(JSON.parse(servers[i].value));
  113. }
  114. const transports = document.getElementsByName('transports');
  115. let iceTransports;
  116. for (let i = 0; i < transports.length; ++i) {
  117. if (transports[i].checked) {
  118. iceTransports = transports[i].value;
  119. break;
  120. }
  121. }
  122. // Create a PeerConnection with no streams, but force a m=audio line.
  123. const config = {
  124. iceServers: iceServers,
  125. iceTransportPolicy: iceTransports,
  126. iceCandidatePoolSize: iceCandidatePoolInput.value
  127. };
  128. const offerOptions = {offerToReceiveAudio: 1};
  129. // Whether we gather IPv6 candidates.
  130. // Whether we only gather a single set of candidates for RTP and RTCP.
  131. console.log(`Creating new PeerConnection with config=${JSON.stringify(config)}`);
  132. document.getElementById('error').innerText = '';
  133. pc = new RTCPeerConnection(config);
  134. pc.onicecandidate = iceCallback;
  135. pc.onicegatheringstatechange = gatheringStateChange;
  136. pc.onicecandidateerror = iceCandidateError;
  137. pc.createOffer(
  138. offerOptions
  139. ).then(
  140. gotDescription,
  141. noDescription
  142. );
  143. }
  144. function gotDescription(desc) {
  145. begin = window.performance.now();
  146. candidates = [];
  147. pc.setLocalDescription(desc);
  148. }
  149. function noDescription(error) {
  150. console.log('Error creating offer: ', error);
  151. }
  152. // Parse the uint32 PRIORITY field into its constituent parts from RFC 5245,
  153. // type preference, local preference, and (256 - component ID).
  154. // ex: 126 | 32252 | 255 (126 is host preference, 255 is component ID 1)
  155. function formatPriority(priority) {
  156. return [
  157. priority >> 24,
  158. (priority >> 8) & 0xFFFF,
  159. priority & 0xFF
  160. ].join(' | ');
  161. }
  162. function appendCell(row, val, span) {
  163. const cell = document.createElement('td');
  164. cell.textContent = val;
  165. if (span) {
  166. cell.setAttribute('colspan', span);
  167. }
  168. row.appendChild(cell);
  169. }
  170. // Try to determine authentication failures and unreachable TURN
  171. // servers by using heuristics on the candidate types gathered.
  172. function getFinalResult() {
  173. let result = 'Done';
  174. // if more than one server is used, it can not be determined
  175. // which server failed.
  176. if (servers.length === 1) {
  177. const server = JSON.parse(servers[0].value);
  178. // get the candidates types (host, srflx, relay)
  179. const types = candidates.map(function(cand) {
  180. return cand.type;
  181. });
  182. // If the server is a TURN server we should have a relay candidate.
  183. // If we did not get a relay candidate but a srflx candidate
  184. // authentication might have failed.
  185. // If we did not get a relay candidate or a srflx candidate
  186. // we could not reach the TURN server. Either it is not running at
  187. // the target address or the clients access to the port is blocked.
  188. //
  189. // This only works for TURN/UDP since we do not get
  190. // srflx candidates from TURN/TCP.
  191. if (server.urls[0].indexOf('turn:') === 0 &&
  192. server.urls[0].indexOf('?transport=tcp') === -1) {
  193. if (types.indexOf('relay') === -1) {
  194. if (types.indexOf('srflx') > -1) {
  195. // a binding response but no relay candidate suggests auth failure.
  196. result = 'Authentication failed?';
  197. } else {
  198. // either the TURN server is down or the clients access is blocked.
  199. result = 'Not reachable?';
  200. }
  201. }
  202. }
  203. }
  204. return result;
  205. }
  206. function iceCallback(event) {
  207. const elapsed = ((window.performance.now() - begin) / 1000).toFixed(3);
  208. const row = document.createElement('tr');
  209. appendCell(row, elapsed);
  210. if (event.candidate) {
  211. if (event.candidate.candidate === '') {
  212. return;
  213. }
  214. const {candidate} = event;
  215. appendCell(row, candidate.component);
  216. appendCell(row, candidate.type);
  217. appendCell(row, candidate.foundation);
  218. appendCell(row, candidate.protocol);
  219. appendCell(row, candidate.address);
  220. appendCell(row, candidate.port);
  221. appendCell(row, formatPriority(candidate.priority));
  222. candidates.push(candidate);
  223. } else if (!('onicegatheringstatechange' in RTCPeerConnection.prototype)) {
  224. // should not be done if its done in the icegatheringstatechange callback.
  225. appendCell(row, getFinalResult(), 7);
  226. pc.close();
  227. pc = null;
  228. gatherButton.disabled = false;
  229. }
  230. candidateTBody.appendChild(row);
  231. }
  232. function gatheringStateChange() {
  233. if (pc.iceGatheringState !== 'complete') {
  234. return;
  235. }
  236. const elapsed = ((window.performance.now() - begin) / 1000).toFixed(3);
  237. const row = document.createElement('tr');
  238. appendCell(row, elapsed);
  239. appendCell(row, getFinalResult(), 7);
  240. pc.close();
  241. pc = null;
  242. gatherButton.disabled = false;
  243. candidateTBody.appendChild(row);
  244. }
  245. function iceCandidateError(e) {
  246. // The interesting attributes of the error are
  247. // * the url (which allows looking up the server)
  248. // * the errorCode and errorText
  249. document.getElementById('error-note').style.display = 'block';
  250. document.getElementById('error').innerText += 'The server ' + e.url +
  251. ' returned an error with code=' + e.errorCode + ':\n' +
  252. e.errorText + '\n';
  253. }
  254. readServersFromLocalStorage();
  255. // check if we have getUserMedia permissions.
  256. navigator.mediaDevices
  257. .enumerateDevices()
  258. .then(function(devices) {
  259. devices.forEach(function(device) {
  260. if (device.label !== '') {
  261. document.getElementById('getUserMediaPermissions').style.display = 'block';
  262. }
  263. });
  264. });