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