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.

loadScript.native.js 3.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. // @flow
  2. import { timeoutPromise } from './timeoutPromise';
  3. /**
  4. * Loads a script from a specific URL. React Native cannot load a JS
  5. * file/resource/URL via a <script> HTML element, so the implementation
  6. * fetches the specified {@code url} as plain text using {@link fetch()} and
  7. * then evaluates the fetched string as JavaScript code (using {@link eval()}).
  8. *
  9. * @param {string} url - The absolute URL from which the script is to be
  10. * (down)loaded.
  11. * @param {number} [timeout] - The timeout in millisecnods after which the
  12. * loading of the specified {@code url} is to be aborted/rejected (if not
  13. * settled yet).
  14. * @returns {void}
  15. */
  16. export function loadScript(url: string, timeout: ?number): Promise<void> {
  17. return new Promise((resolve, reject) => {
  18. // XXX The implementation of fetch on Android will throw an Exception on
  19. // the Java side which will break the app if the URL is invalid (which
  20. // the implementation of fetch on Android calls 'unexpected url'). In
  21. // order to try to prevent the breakage of the app, try to fail on an
  22. // invalid URL as soon as possible.
  23. const { hostname, pathname, protocol } = new URL(url);
  24. // XXX The standard URL implementation should throw an Error if the
  25. // specified URL is relative. Unfortunately, the polyfill used on
  26. // react-native does not.
  27. if (!hostname || !pathname || !protocol) {
  28. reject(`unexpected url: ${url}`);
  29. return;
  30. }
  31. let fetch_ = fetch(url, { method: 'GET' });
  32. // The implementation of fetch provided by react-native is based on
  33. // XMLHttpRequest. Which defines timeout as an unsigned long with
  34. // default value 0, which means there is no timeout.
  35. if (timeout) {
  36. // FIXME I don't like the approach with timeoutPromise because:
  37. //
  38. // * It merely abandons the underlying XHR and, consequently, opens
  39. // us to potential issues with NetworkActivityIndicator which
  40. // tracks XHRs.
  41. //
  42. // * @paweldomas also reported that timeouts seem to be respected by
  43. // the XHR implementation on iOS. Given that we have
  44. // implementation of loadScript based on fetch and XHR (in an
  45. // earlier revision), I don't see why we're not using an XHR
  46. // directly on iOS.
  47. //
  48. // * The approach of timeoutPromise I found on the Internet is to
  49. // directly use XHR instead of fetch and abort the XHR on timeout.
  50. // Which may deal with the NetworkActivityIndicator at least.
  51. fetch_ = timeoutPromise(fetch_, timeout);
  52. }
  53. fetch_
  54. .then(response => {
  55. switch (response.status) {
  56. case 200:
  57. return response.responseText || response.text();
  58. default:
  59. throw response.statusText;
  60. }
  61. })
  62. .then(responseText => {
  63. eval.call(window, responseText); // eslint-disable-line no-eval
  64. })
  65. .then(resolve, reject);
  66. });
  67. }