Spaces:
Sleeping
Sleeping
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; | |