react-native
Version:
A framework for building native apps using React
153 lines (140 loc) • 4.14 kB
JavaScript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import Networking from '../../Network/RCTNetworking';
import DevLoadingView from '../../Utilities/DevLoadingView';
import HMRClient from '../../Utilities/HMRClient';
import getDevServer from './getDevServer';
declare var global: {globalEvalWithSourceUrl?: (string, string) => mixed, ...};
let pendingRequests = 0;
const cachedPromisesByUrl = new Map<string, Promise<void>>();
function asyncRequest(
url: string,
): Promise<{body: string, headers: {[string]: string}}> {
let id = null;
let responseText = null;
let headers = null;
let dataListener;
let completeListener;
let responseListener;
let incrementalDataListener;
return new Promise<{body: string, headers: {[string]: string}}>(
(resolve, reject) => {
dataListener = Networking.addListener(
'didReceiveNetworkData',
([requestId, response]) => {
if (requestId === id) {
responseText = response;
}
},
);
incrementalDataListener = Networking.addListener(
'didReceiveNetworkIncrementalData',
([requestId, data]) => {
if (requestId === id) {
if (responseText != null) {
responseText += data;
} else {
responseText = data;
}
}
},
);
responseListener = Networking.addListener(
'didReceiveNetworkResponse',
([requestId, status, responseHeaders]) => {
if (requestId === id) {
headers = responseHeaders;
}
},
);
completeListener = Networking.addListener(
'didCompleteNetworkResponse',
([requestId, error]) => {
if (requestId === id) {
if (error) {
reject(error);
} else {
//$FlowFixMe[incompatible-call]
resolve({body: responseText, headers});
}
}
},
);
Networking.sendRequest(
'GET',
'asyncRequest',
url,
{},
'',
'text',
true,
0,
requestId => {
id = requestId;
},
true,
);
},
).finally(() => {
dataListener?.remove();
completeListener?.remove();
responseListener?.remove();
incrementalDataListener?.remove();
});
}
function buildUrlForBundle(bundlePathAndQuery: string) {
const {url: serverUrl} = getDevServer();
return (
serverUrl.replace(/\/+$/, '') + '/' + bundlePathAndQuery.replace(/^\/+/, '')
);
}
module.exports = function (bundlePathAndQuery: string): Promise<void> {
const requestUrl = buildUrlForBundle(bundlePathAndQuery);
let loadPromise = cachedPromisesByUrl.get(requestUrl);
if (loadPromise) {
return loadPromise;
}
DevLoadingView.showMessage('Downloading...', 'load');
++pendingRequests;
loadPromise = asyncRequest(requestUrl)
.then<void>(({body, headers}) => {
if (
headers['Content-Type'] != null &&
headers['Content-Type'].indexOf('application/json') >= 0
) {
// Errors are returned as JSON.
throw new Error(
JSON.parse(body).message ||
`Unknown error fetching '${bundlePathAndQuery}'`,
);
}
HMRClient.registerBundle(requestUrl);
// Some engines do not support `sourceURL` as a comment. We expose a
// `globalEvalWithSourceUrl` function to handle updates in that case.
if (global.globalEvalWithSourceUrl) {
global.globalEvalWithSourceUrl(body, requestUrl);
} else {
// eslint-disable-next-line no-eval
eval(body);
}
})
.catch<void>(e => {
cachedPromisesByUrl.delete(requestUrl);
throw e;
})
.finally(() => {
if (!--pendingRequests) {
DevLoadingView.hide();
}
});
cachedPromisesByUrl.set(requestUrl, loadPromise);
return loadPromise;
};