const TIMEOUT_ERROR = new Error('timeout') const hexRe = /^[0-9A-Fa-f]+$/gu /** * Execute fetch and verify that the response was successful. * * @param request - Request information. * @param options - Fetch options. * @returns The fetch response. */ export async function successfulFetch(request: string, options?: RequestInit) { const response = await fetch(request, options) if (!response.ok) { throw new Error(`Fetch failed with status '${response.status}' for request '${request}'`) } return response } /** * Execute fetch and return object response. * * @param request - The request information. * @param options - The fetch options. * @returns The fetch response JSON data. */ export async function handleFetch(request: string, options?: RequestInit) { const response = await successfulFetch(request, options) const object = await response.json() return object } /** * Execute fetch and return object response, log if known error thrown, otherwise rethrow error. * * @param request - the request options object * @param request.url - The request url to query. * @param request.options - The fetch options. * @param request.timeout - Timeout to fail request * @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context * @returns The fetch response JSON data or undefined (if error occurs). */ export async function fetchWithErrorHandling({ url, options, timeout, errorCodesToCatch, }: { url: string options?: RequestInit timeout?: number errorCodesToCatch?: number[] }) { let result try { if (timeout) { result = Promise.race([ await handleFetch(url, options), new Promise((_, reject) => setTimeout(() => { reject(TIMEOUT_ERROR) }, timeout), ), ]) } else { result = await handleFetch(url, options) } } catch (e) { logOrRethrowError(e, errorCodesToCatch) } return result } /** * Fetch that fails after timeout. * * @param url - Url to fetch. * @param options - Options to send with the request. * @param timeout - Timeout to fail request. * @returns Promise resolving the request. */ export async function timeoutFetch(url: string, options?: RequestInit, timeout = 500): Promise { return Promise.race([ successfulFetch(url, options), new Promise((_, reject) => setTimeout(() => { reject(TIMEOUT_ERROR) }, timeout), ), ]) } /** * Utility method to log if error is a common fetch error and otherwise rethrow it. * * @param error - Caught error that we should either rethrow or log to console * @param codesToCatch - array of error codes for errors we want to catch and log in a particular context */ function logOrRethrowError(error: any, codesToCatch: number[] = []) { if (!error) { return } const includesErrorCodeToCatch = codesToCatch.some(code => error.message.includes(`Fetch failed with status '${code}'`), ) if ( error instanceof Error && (includesErrorCodeToCatch || error.message.includes('Failed to fetch') || error === TIMEOUT_ERROR) ) { console.error(error) } else { throw error } } export function generateHeader() { let random = function (start, end) { return (Math.random() * (end - start) + start) | 0 } let getIp = function () { return `${random(1, 254)}.${random(1, 254)}.${random(1, 254)}.${random(1, 254)}` } let time = Date.now() let useragent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ (70 + Math.random() * 10) | 0 }.0.4324.${(Math.random() * 100) | 0} Safari/537.36` const ip = getIp() return { 'Refresh-Token': (time -= 5000), 'Cache-Control': 'no-cache', 'User-Agent': useragent, 'X-Forwarded-For': ip, 'X-Real-IP': ip, 'Content-Type': 'application/json', } }