s4s-packager / src /common /request.js
soiz1's picture
Upload 225 files
7aec436 verified
import {HTTPError, UnknownNetworkError} from './errors';
const clampProgress = (n) => Math.max(0, Math.min(1, n));
const DO_NOT_USE_FALLBACK_URL_ERRORS = [
// If we make a request with eg. an invalid project ID, do not use fallback URLs
400,
// If we make a request with eg. an unshared project ID, do not use fallback URLs
404,
];
const request = async (options) => {
const {
type,
progressCallback,
timeout,
estimatedSize,
abortTarget
} = options;
const requestURL = (url) => new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
cleanup();
if (xhr.status === 200) {
resolve(xhr.response);
} else {
reject(new HTTPError(`Couldn't fetch ${url}: status code ${xhr.status}`, xhr.status));
}
};
xhr.onerror = () => {
cleanup();
reject(new UnknownNetworkError(url));
};
if (progressCallback) {
xhr.onprogress = (e) => {
if (e.lengthComputable) {
progressCallback(clampProgress(e.loaded / e.total));
} else if (estimatedSize) {
progressCallback(clampProgress(e.loaded / estimatedSize));
}
};
}
xhr.responseType = type;
xhr.open('GET', url);
xhr.send();
const cleanup = () => {
if (cleanupAbortCallback) {
cleanupAbortCallback();
}
if (timeoutId) {
clearTimeout(timeoutId);
}
};
let cleanupAbortCallback;
if (abortTarget) {
const abortCallback = () => {
xhr.abort();
cleanup();
reject(new Error(`Couldn't fetch ${url}: aborted`));
};
abortTarget.addEventListener('abort', abortCallback);
cleanupAbortCallback = () => {
abortTarget.removeEventListener('abort', abortCallback);
};
}
let timeoutId;
if (timeout) {
timeoutId = setTimeout(() => {
xhr.abort();
cleanup();
reject(new Error(`Couldn't fetch ${url}: timed out`));
}, timeout);
}
});
const urls = Array.isArray(options.url) ? options.url : [options.url];
if (urls.length === 0) {
throw new Error('no URLs');
}
let errorToThrow;
for (const url of urls) {
try {
return await requestURL(url);
} catch (e) {
if (e instanceof HTTPError && DO_NOT_USE_FALLBACK_URL_ERRORS.includes(e.status)) {
throw e;
}
// We'll record this error if this is the first error, or if the current error provides more information than
// the old error. This is useful because:
// trampoline.turbowarp.org/... -> blocked by filter (appears to us as generic network error)
// trampoline.turbowarp.xyz/... -> returns status 500
// should return the HTTP error, not the generic network error.
if (!errorToThrow || (errorToThrow instanceof UnknownNetworkError && !(e instanceof UnknownNetworkError))) {
errorToThrow = e;
}
}
}
throw errorToThrow;
};
export default request;