Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

RTCPeerConnection.js 11KB

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