You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RTCPeerConnection.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. // @flow
  2. import { NativeModules } from 'react-native';
  3. import { RTCPeerConnection, RTCSessionDescription } from 'react-native-webrtc';
  4. import logger from '../logger';
  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 and others 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(description) {
  78. return (
  79. _synthesizeIPv6Addresses(description)
  80. .catch(reason => {
  81. reason && _LOGE(reason);
  82. return description;
  83. })
  84. .then(value => _setRemoteDescription.bind(this)(value)));
  85. };
  86. /**
  87. * Logs at error level.
  88. *
  89. * @private
  90. * @returns {void}
  91. */
  92. function _LOGE(...args) {
  93. logger.error(...args);
  94. }
  95. /**
  96. * Adapts react-native-webrtc's {@link RTCPeerConnection#setRemoteDescription}
  97. * implementation which uses the deprecated, callback-based version to the
  98. * {@code Promise}-based version.
  99. *
  100. * @param {RTCSessionDescription} description - The RTCSessionDescription
  101. * which specifies the configuration of the remote end of the connection.
  102. * @private
  103. * @private
  104. * @returns {Promise}
  105. */
  106. function _setRemoteDescription(description) {
  107. return new Promise((resolve, reject) => {
  108. /* eslint-disable no-invalid-this */
  109. // Ensure I'm not remembering onaddstream invocations from previous
  110. // setRemoteDescription calls. I shouldn't be but... anyway.
  111. this._onaddstreamQueue = [];
  112. RTCPeerConnection.prototype.setRemoteDescription.call(this, description)
  113. .then((...args) => {
  114. let q;
  115. try {
  116. resolve(...args);
  117. } finally {
  118. q = this._onaddstreamQueue;
  119. this._onaddstreamQueue = undefined;
  120. }
  121. this._invokeQueuedOnaddstream(q);
  122. }, (...args) => {
  123. this._onaddstreamQueue = undefined;
  124. reject(...args);
  125. });
  126. /* eslint-enable no-invalid-this */
  127. });
  128. }
  129. // XXX The function _synthesizeIPv6FromIPv4Address is not placed relative to the
  130. // other functions in the file according to alphabetical sorting rule of the
  131. // coding style. But eslint wants constants to be defined before they are used.
  132. /**
  133. * Synthesizes an IPv6 address from a specific IPv4 address.
  134. *
  135. * @param {string} ipv4 - The IPv4 address from which an IPv6 address is to be
  136. * synthesized.
  137. * @returns {Promise<?string>} A {@code Promise} which gets resolved with the
  138. * IPv6 address synthesized from the specified {@code ipv4} or a falsy value to
  139. * be treated as inability to synthesize an IPv6 address from the specified
  140. * {@code ipv4}.
  141. */
  142. const _synthesizeIPv6FromIPv4Address: string => Promise<?string> = (function() {
  143. // POSIX.getaddrinfo
  144. const { POSIX } = NativeModules;
  145. if (POSIX) {
  146. const { getaddrinfo } = POSIX;
  147. if (typeof getaddrinfo === 'function') {
  148. return ipv4 =>
  149. getaddrinfo(/* hostname */ ipv4, /* servname */ undefined)
  150. .then(([ { ai_addr: ipv6 } ]) => ipv6);
  151. }
  152. }
  153. // NAT64AddrInfo.getIPv6Address
  154. const { NAT64AddrInfo } = NativeModules;
  155. if (NAT64AddrInfo) {
  156. const { getIPv6Address } = NAT64AddrInfo;
  157. if (typeof getIPv6Address === 'function') {
  158. return getIPv6Address;
  159. }
  160. }
  161. // There's no POSIX.getaddrinfo or NAT64AddrInfo.getIPv6Address.
  162. return () =>
  163. Promise.reject(
  164. 'The impossible just happened! No POSIX.getaddrinfo or'
  165. + ' NAT64AddrInfo.getIPv6Address!');
  166. })();
  167. /**
  168. * Synthesizes IPv6 addresses on iOS in order to support IPv6 NAT64 networks.
  169. *
  170. * @param {RTCSessionDescription} sdp - The RTCSessionDescription which
  171. * specifies the configuration of the remote end of the connection.
  172. * @private
  173. * @returns {Promise}
  174. */
  175. function _synthesizeIPv6Addresses(sdp) {
  176. return (
  177. new Promise(resolve => resolve(_synthesizeIPv6Addresses0(sdp)))
  178. .then(({ ips, lines }) =>
  179. Promise.all(Array.from(ips.values()))
  180. .then(() => _synthesizeIPv6Addresses1(sdp, ips, lines))
  181. ));
  182. }
  183. /* eslint-disable max-depth */
  184. /**
  185. * Begins the asynchronous synthesis of IPv6 addresses.
  186. *
  187. * @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
  188. * for which IPv6 addresses will be synthesized.
  189. * @private
  190. * @returns {{
  191. * ips: Map,
  192. * lines: Array
  193. * }}
  194. */
  195. function _synthesizeIPv6Addresses0(sessionDescription) {
  196. const sdp = sessionDescription.sdp;
  197. let start = 0;
  198. const lines = [];
  199. const ips = new Map();
  200. do {
  201. const end = sdp.indexOf('\r\n', start);
  202. let line;
  203. if (end === -1) {
  204. line = sdp.substring(start);
  205. // Break out of the loop at the end of the iteration.
  206. start = undefined;
  207. } else {
  208. line = sdp.substring(start, end);
  209. start = end + 2;
  210. }
  211. if (line.startsWith('a=candidate:')) {
  212. const candidate = line.split(' ');
  213. if (candidate.length >= 10 && candidate[6] === 'typ') {
  214. const ip4s = [ candidate[4] ];
  215. let abort = false;
  216. for (let i = 8; i < candidate.length; ++i) {
  217. if (candidate[i] === 'raddr') {
  218. ip4s.push(candidate[++i]);
  219. break;
  220. }
  221. }
  222. for (const ip of ip4s) {
  223. if (ip.indexOf(':') === -1) {
  224. ips.has(ip)
  225. || ips.set(ip, new Promise((resolve, reject) => {
  226. const v = ips.get(ip);
  227. if (v && typeof v === 'string') {
  228. resolve(v);
  229. } else {
  230. _synthesizeIPv6FromIPv4Address(ip).then(
  231. value => {
  232. if (!value
  233. || value.indexOf(':') === -1
  234. || value === ips.get(ip)) {
  235. ips.delete(ip);
  236. } else {
  237. ips.set(ip, value);
  238. }
  239. resolve(value);
  240. },
  241. reject);
  242. }
  243. }));
  244. } else {
  245. abort = true;
  246. break;
  247. }
  248. }
  249. if (abort) {
  250. ips.clear();
  251. break;
  252. }
  253. line = candidate;
  254. }
  255. }
  256. lines.push(line);
  257. } while (start);
  258. return {
  259. ips,
  260. lines
  261. };
  262. }
  263. /* eslint-enable max-depth */
  264. /**
  265. * Completes the asynchronous synthesis of IPv6 addresses.
  266. *
  267. * @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
  268. * for which IPv6 addresses are being synthesized.
  269. * @param {Map} ips - A Map of IPv4 addresses found in the specified
  270. * sessionDescription to synthesized IPv6 addresses.
  271. * @param {Array} lines - The lines of the specified sessionDescription.
  272. * @private
  273. * @returns {RTCSessionDescription} A RTCSessionDescription that represents the
  274. * result of the synthesis of IPv6 addresses.
  275. */
  276. function _synthesizeIPv6Addresses1(sessionDescription, ips, lines) {
  277. if (ips.size === 0) {
  278. return sessionDescription;
  279. }
  280. for (let l = 0; l < lines.length; ++l) {
  281. const candidate = lines[l];
  282. if (typeof candidate !== 'string') {
  283. let ip4 = candidate[4];
  284. let ip6 = ips.get(ip4);
  285. ip6 && (candidate[4] = ip6);
  286. for (let i = 8; i < candidate.length; ++i) {
  287. if (candidate[i] === 'raddr') {
  288. ip4 = candidate[++i];
  289. (ip6 = ips.get(ip4)) && (candidate[i] = ip6);
  290. break;
  291. }
  292. }
  293. lines[l] = candidate.join(' ');
  294. }
  295. }
  296. return new RTCSessionDescription({
  297. sdp: lines.join('\r\n'),
  298. type: sessionDescription.type
  299. });
  300. }