Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

RTCPeerConnection.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. // @flow
  2. import { NativeModules } from 'react-native';
  3. import { RTCPeerConnection, RTCSessionDescription } from 'react-native-webrtc';
  4. const logger = require('jitsi-meet-logger').getLogger(__filename);
  5. /* eslint-disable no-unused-vars */
  6. // Address families.
  7. const AF_INET6 = 30; /* IPv6 */
  8. // Protocols (RFC 1700)
  9. const IPPROTO_TCP = 6; /* tcp */
  10. const IPPROTO_UDP = 17; /* user datagram protocol */
  11. // Protocol families, same as address families for now.
  12. const PF_INET6 = AF_INET6;
  13. const SOCK_DGRAM = 2; /* datagram socket */
  14. const SOCK_STREAM = 1; /* stream socket */
  15. /* eslint-enable no-unused-vars */
  16. // XXX At the time of this writing extending RTCPeerConnection using ES6 'class'
  17. // and 'extends' causes a runtime error related to the attempt to define the
  18. // onaddstream property setter. The error mentions that babelHelpers.set is
  19. // undefined which appears to be a thing inside React Native's packager. As a
  20. // workaround, extend using the pre-ES6 way.
  21. /**
  22. * The RTCPeerConnection provided by react-native-webrtc fires onaddstream
  23. * before it remembers remotedescription (and thus makes it available to API
  24. * clients). Because that appears to be a problem for lib-jitsi-meet which has
  25. * been successfully running on Chrome, Firefox, etc. for a very long
  26. * time, attempt to meet its expectations (by extending RTCPPeerConnection).
  27. *
  28. * @class
  29. */
  30. export default function _RTCPeerConnection(...args: any[]) {
  31. /* eslint-disable indent, no-invalid-this */
  32. RTCPeerConnection.apply(this, args);
  33. this.onaddstream = (...args) => // eslint-disable-line no-shadow
  34. (this._onaddstreamQueue
  35. ? this._queueOnaddstream
  36. : this._invokeOnaddstream)
  37. .apply(this, args);
  38. // Shadow RTCPeerConnection's onaddstream but after _RTCPeerConnection has
  39. // assigned to the property in question. Defining the property on
  40. // _RTCPeerConnection's prototype may (or may not, I don't know) work but I
  41. // don't want to try because the following approach appears to work and I
  42. // understand it.
  43. // $FlowFixMe
  44. Object.defineProperty(this, 'onaddstream', {
  45. configurable: true,
  46. enumerable: true,
  47. get() {
  48. return this._onaddstream;
  49. },
  50. set(value) {
  51. this._onaddstream = value;
  52. }
  53. });
  54. /* eslint-enable indent, no-invalid-this */
  55. }
  56. _RTCPeerConnection.prototype = Object.create(RTCPeerConnection.prototype);
  57. _RTCPeerConnection.prototype.constructor = _RTCPeerConnection;
  58. _RTCPeerConnection.prototype._invokeOnaddstream = function(...args) {
  59. const onaddstream = this._onaddstream;
  60. return onaddstream && onaddstream.apply(this, args);
  61. };
  62. _RTCPeerConnection.prototype._invokeQueuedOnaddstream = function(q) {
  63. q && q.forEach(args => {
  64. try {
  65. this._invokeOnaddstream(...args);
  66. } catch (e) {
  67. // TODO Determine whether the combination of the standard
  68. // setRemoteDescription and onaddstream results in a similar
  69. // swallowing of errors.
  70. _LOGE(e);
  71. }
  72. });
  73. };
  74. _RTCPeerConnection.prototype._queueOnaddstream = function(...args) {
  75. this._onaddstreamQueue.push(Array.from(args));
  76. };
  77. _RTCPeerConnection.prototype.setRemoteDescription = function(
  78. sessionDescription,
  79. successCallback,
  80. errorCallback) {
  81. // If the deprecated callback-based version is used, translate it to the
  82. // Promise-based version.
  83. if (typeof successCallback !== 'undefined'
  84. || typeof errorCallback !== 'undefined') {
  85. // XXX Returning a Promise is not necessary. But I don't see why it'd
  86. // hurt (much).
  87. return (
  88. _RTCPeerConnection.prototype.setRemoteDescription.call(
  89. this,
  90. sessionDescription)
  91. .then(successCallback, errorCallback));
  92. }
  93. return (
  94. _synthesizeIPv6Addresses(sessionDescription)
  95. .catch(reason => {
  96. reason && _LOGE(reason);
  97. return sessionDescription;
  98. })
  99. .then(value => _setRemoteDescription.bind(this)(value)));
  100. };
  101. /**
  102. * Logs at error level.
  103. *
  104. * @private
  105. * @returns {void}
  106. */
  107. function _LOGE(...args) {
  108. logger.error(...args);
  109. }
  110. /**
  111. * Adapts react-native-webrtc's {@link RTCPeerConnection#setRemoteDescription}
  112. * implementation which uses the deprecated, callback-based version to the
  113. * {@code Promise}-based version.
  114. *
  115. * @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
  116. * which specifies the configuration of the remote end of the connection.
  117. * @private
  118. * @private
  119. * @returns {Promise}
  120. */
  121. function _setRemoteDescription(sessionDescription) {
  122. return new Promise((resolve, reject) => {
  123. /* eslint-disable no-invalid-this */
  124. // Ensure I'm not remembering onaddstream invocations from previous
  125. // setRemoteDescription calls. I shouldn't be but... anyway.
  126. this._onaddstreamQueue = [];
  127. RTCPeerConnection.prototype.setRemoteDescription.call(
  128. this,
  129. sessionDescription,
  130. (...args) => {
  131. let q;
  132. try {
  133. resolve(...args);
  134. } finally {
  135. q = this._onaddstreamQueue;
  136. this._onaddstreamQueue = undefined;
  137. }
  138. this._invokeQueuedOnaddstream(q);
  139. },
  140. (...args) => {
  141. this._onaddstreamQueue = undefined;
  142. reject(...args);
  143. });
  144. /* eslint-enable no-invalid-this */
  145. });
  146. }
  147. // XXX The function _synthesizeIPv6FromIPv4Address is not placed relative to the
  148. // other functions in the file according to alphabetical sorting rule of the
  149. // coding style. But eslint wants constants to be defined before they are used.
  150. /**
  151. * Synthesizes an IPv6 address from a specific IPv4 address.
  152. *
  153. * @param {string} ipv4 - The IPv4 address from which an IPv6 address is to be
  154. * synthesized.
  155. * @returns {Promise<?string>} A {@code Promise} which gets resolved with the
  156. * IPv6 address synthesized from the specified {@code ipv4} or a falsy value to
  157. * be treated as inability to synthesize an IPv6 address from the specified
  158. * {@code ipv4}.
  159. */
  160. const _synthesizeIPv6FromIPv4Address: string => Promise<?string> = (function() {
  161. // POSIX.getaddrinfo
  162. const { POSIX } = NativeModules;
  163. if (POSIX) {
  164. const { getaddrinfo } = POSIX;
  165. if (typeof getaddrinfo === 'function') {
  166. return ipv4 =>
  167. getaddrinfo(/* hostname */ ipv4, /* servname */ undefined)
  168. .then(([ { ai_addr: ipv6 } ]) => ipv6);
  169. }
  170. }
  171. // NAT64AddrInfo.getIPv6Address
  172. const { NAT64AddrInfo } = NativeModules;
  173. if (NAT64AddrInfo) {
  174. const { getIPv6Address } = NAT64AddrInfo;
  175. if (typeof getIPv6Address === 'function') {
  176. return getIPv6Address;
  177. }
  178. }
  179. // There's no POSIX.getaddrinfo or NAT64AddrInfo.getIPv6Address.
  180. return () =>
  181. Promise.reject(
  182. 'The impossible just happened! No POSIX.getaddrinfo or'
  183. + ' NAT64AddrInfo.getIPv6Address!');
  184. })();
  185. /**
  186. * Synthesizes IPv6 addresses on iOS in order to support IPv6 NAT64 networks.
  187. *
  188. * @param {RTCSessionDescription} sdp - The RTCSessionDescription which
  189. * specifies the configuration of the remote end of the connection.
  190. * @private
  191. * @returns {Promise}
  192. */
  193. function _synthesizeIPv6Addresses(sdp) {
  194. return (
  195. new Promise(resolve => resolve(_synthesizeIPv6Addresses0(sdp)))
  196. .then(({ ips, lines }) =>
  197. Promise.all(Array.from(ips.values()))
  198. .then(() => _synthesizeIPv6Addresses1(sdp, ips, lines))
  199. ));
  200. }
  201. /* eslint-disable max-depth */
  202. /**
  203. * Begins the asynchronous synthesis of IPv6 addresses.
  204. *
  205. * @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
  206. * for which IPv6 addresses will be synthesized.
  207. * @private
  208. * @returns {{
  209. * ips: Map,
  210. * lines: Array
  211. * }}
  212. */
  213. function _synthesizeIPv6Addresses0(sessionDescription) {
  214. const sdp = sessionDescription.sdp;
  215. let start = 0;
  216. const lines = [];
  217. const ips = new Map();
  218. do {
  219. const end = sdp.indexOf('\r\n', start);
  220. let line;
  221. if (end === -1) {
  222. line = sdp.substring(start);
  223. // Break out of the loop at the end of the iteration.
  224. start = undefined;
  225. } else {
  226. line = sdp.substring(start, end);
  227. start = end + 2;
  228. }
  229. if (line.startsWith('a=candidate:')) {
  230. const candidate = line.split(' ');
  231. if (candidate.length >= 10 && candidate[6] === 'typ') {
  232. const ip4s = [ candidate[4] ];
  233. let abort = false;
  234. for (let i = 8; i < candidate.length; ++i) {
  235. if (candidate[i] === 'raddr') {
  236. ip4s.push(candidate[++i]);
  237. break;
  238. }
  239. }
  240. for (const ip of ip4s) {
  241. if (ip.indexOf(':') === -1) {
  242. ips.has(ip)
  243. || ips.set(ip, new Promise((resolve, reject) => {
  244. const v = ips.get(ip);
  245. if (v && typeof v === 'string') {
  246. resolve(v);
  247. } else {
  248. _synthesizeIPv6FromIPv4Address(ip).then(
  249. value => {
  250. if (!value
  251. || value.indexOf(':') === -1
  252. || value === ips.get(ip)) {
  253. ips.delete(ip);
  254. } else {
  255. ips.set(ip, value);
  256. }
  257. resolve(value);
  258. },
  259. reject);
  260. }
  261. }));
  262. } else {
  263. abort = true;
  264. break;
  265. }
  266. }
  267. if (abort) {
  268. ips.clear();
  269. break;
  270. }
  271. line = candidate;
  272. }
  273. }
  274. lines.push(line);
  275. } while (start);
  276. return {
  277. ips,
  278. lines
  279. };
  280. }
  281. /* eslint-enable max-depth */
  282. /**
  283. * Completes the asynchronous synthesis of IPv6 addresses.
  284. *
  285. * @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
  286. * for which IPv6 addresses are being synthesized.
  287. * @param {Map} ips - A Map of IPv4 addresses found in the specified
  288. * sessionDescription to synthesized IPv6 addresses.
  289. * @param {Array} lines - The lines of the specified sessionDescription.
  290. * @private
  291. * @returns {RTCSessionDescription} A RTCSessionDescription that represents the
  292. * result of the synthesis of IPv6 addresses.
  293. */
  294. function _synthesizeIPv6Addresses1(sessionDescription, ips, lines) {
  295. if (ips.size === 0) {
  296. return sessionDescription;
  297. }
  298. for (let l = 0; l < lines.length; ++l) {
  299. const candidate = lines[l];
  300. if (typeof candidate !== 'string') {
  301. let ip4 = candidate[4];
  302. let ip6 = ips.get(ip4);
  303. ip6 && (candidate[4] = ip6);
  304. for (let i = 8; i < candidate.length; ++i) {
  305. if (candidate[i] === 'raddr') {
  306. ip4 = candidate[++i];
  307. (ip6 = ips.get(ip4)) && (candidate[i] = ip6);
  308. break;
  309. }
  310. }
  311. lines[l] = candidate.join(' ');
  312. }
  313. }
  314. return new RTCSessionDescription({
  315. sdp: lines.join('\r\n'),
  316. type: sessionDescription.type
  317. });
  318. }