UNPKG

@hyper-fetch/core

Version:

Cache, Queue and Persist your requests no matter if you are online or offline!

489 lines (420 loc) 12.4 kB
import { getErrorMessage, ResponseSuccessType, ResponseErrorType, ProgressDataType, AdapterInstance, RequestProcessingError, } from "adapter"; import { LoggerMethods } from "managers"; import { RequestInstance, getProgressData, ProgressEventType } from "request"; import { ExtractResponseType, ExtractErrorType, ExtractPayloadType, ExtractAdapterOptionsType, ExtractAdapterStatusType, ExtractAdapterExtraType, } from "types"; export const getAdapterBindings = async <T extends AdapterInstance>({ request: baseRequest, requestId, resolve, onStartTime, internalErrorMapping, }: { request: RequestInstance; requestId: string; resolve: (value: ResponseSuccessType<any, T> | ResponseErrorType<any, T>) => void; onStartTime: (timestamp: number) => void; internalErrorMapping: (error: ReturnType<typeof getErrorMessage>) => any; }) => { const { requestManager, loggerManager } = baseRequest.client; // eslint-disable-next-line @typescript-eslint/naming-convention const { unstable_payloadMapper } = baseRequest.client.adapter; const logger = loggerManager.initialize(baseRequest.client, "Adapter"); let requestStartTimestamp: null | number = null; let responseStartTimestamp: null | number = null; let request = baseRequest; // Progress let requestTotal = 1; let responseTotal = 1; let previousRequestTotal = 0; let previousResponseTotal = 0; // Pre request modifications logger.debug({ title: `Running middleware callbacks`, type: "request", extra: { request, requestId, }, }); request = await request.client.unstable_modifyRequest(request); if (request.auth) { request = await request.client.unstable_modifyAuth(request); } if (request.unstable_requestMapper) { request = await request.unstable_requestMapper(request, requestId); } // Request Setup const { client, abortKey } = request; // eslint-disable-next-line prefer-destructuring let payload = request.payload; if (request.unstable_payloadMapper) { payload = await request.unstable_payloadMapper<ExtractPayloadType<RequestInstance>>(request.payload); } else if (unstable_payloadMapper) { payload = await unstable_payloadMapper(payload); } const adapterOptions = request.options as ExtractAdapterOptionsType<T> | undefined; const startTime = +new Date(); onStartTime(startTime); const getRequestStartTimestamp = () => { return requestStartTimestamp; }; const getResponseStartTimestamp = () => { return responseStartTimestamp; }; // Progress const getTotal = (previousTotal: number, progress?: ProgressDataType) => { if (!progress) return previousTotal; const total = Number(progress.total || 0); const loaded = Number(progress.loaded || 0); return Math.max(total, loaded, previousTotal); }; const handleRequestProgress = ( startTimestamp: number, progressTimestamp: number, progressEvent: ProgressEventType, ) => { const progress = getProgressData(new Date(startTimestamp), new Date(progressTimestamp), progressEvent); if (previousRequestTotal !== 100) { previousRequestTotal = progress.total; requestManager.events.emitUploadProgress({ ...progress, requestId, request }); } }; const handleResponseProgress = ( startTimestamp: number, progressTimestamp: number, progressEvent: ProgressEventType, ) => { const progress = getProgressData(new Date(startTimestamp), new Date(progressTimestamp), progressEvent); if (previousResponseTotal !== 100) { previousResponseTotal = progress.total; requestManager.events.emitDownloadProgress({ ...progress, requestId, request }); } }; // Pre-request const onBeforeRequest = () => { logger.debug({ title: `Request about to be sent`, type: "request", extra: { request, requestId, }, }); client.triggerPlugins("onRequestTrigger", { request }); }; // Request const onRequestStart = (progress?: ProgressDataType) => { logger.info({ title: `Request start`, type: "request", extra: { request, requestId, }, }); client.triggerPlugins("onRequestStart", { request }); if (progress?.total) { requestTotal = getTotal(requestTotal, progress); } const initialPayload = { total: requestTotal, loaded: progress?.loaded || 0, }; requestStartTimestamp = +new Date(); handleRequestProgress(requestStartTimestamp, requestStartTimestamp, initialPayload); requestManager.events.emitRequestStart({ requestId, request }); return requestStartTimestamp; }; const onRequestProgress = (progress: ProgressDataType) => { if (!requestStartTimestamp) { requestStartTimestamp = +new Date(); } requestTotal = getTotal(requestTotal, progress); const progressTimestamp = +new Date(); handleRequestProgress(requestStartTimestamp, progressTimestamp, { total: requestTotal, loaded: progress.loaded || 0, }); return progressTimestamp; }; const onRequestEnd = () => { if (!requestStartTimestamp) { requestStartTimestamp = +new Date(); } const progressTimestamp = +new Date(); handleRequestProgress(requestStartTimestamp, progressTimestamp, { total: requestTotal, loaded: requestTotal, }); return progressTimestamp; }; // Response const onResponseStart = (progress?: ProgressDataType) => { responseStartTimestamp = +new Date(); responseTotal = getTotal(responseTotal, progress); const initialPayload = { total: responseTotal, loaded: progress?.loaded || 0, }; handleResponseProgress(responseStartTimestamp, responseStartTimestamp, initialPayload); requestManager.events.emitResponseStart({ requestId, request }); return responseStartTimestamp; }; const onResponseProgress = (progress: ProgressDataType) => { if (!responseStartTimestamp) { responseStartTimestamp = +new Date(); } const progressTimestamp = +new Date(); responseTotal = getTotal(responseTotal, progress); handleResponseProgress(responseStartTimestamp, progressTimestamp, { total: progress.total || responseTotal, loaded: progress.loaded || 0, }); return progressTimestamp; }; const onResponseEnd = () => { if (!responseStartTimestamp) { responseStartTimestamp = +new Date(); } const progressTimestamp = +new Date(); handleResponseProgress(responseStartTimestamp, progressTimestamp, { total: responseTotal, loaded: responseTotal, }); return progressTimestamp; }; // Success const onSuccess = async ({ data, error, status, extra, }: { data: any; status: ExtractAdapterStatusType<T>; extra: ExtractAdapterExtraType<T>; error?: ExtractErrorType<T>; }): Promise<ResponseSuccessType<ExtractResponseType<T>, T>> => { let response: ResponseSuccessType<ExtractResponseType<T>, T> = { data, error: error ?? null, success: true, status, extra, requestTimestamp: startTime, responseTimestamp: +new Date(), }; response = (await request.client.unstable_modifyResponse?.(response, request)) as typeof data; response = (await request.client.unstable_modifySuccessResponse?.(response, request)) as typeof data; client.triggerPlugins("onRequestSuccess", { response, request }); client.triggerPlugins("onRequestFinished", { response, request }); resolve(response); logger.info({ title: `Response success`, type: "response", extra: { request, requestId, response, }, }); return response; }; // Errors // eslint-disable-next-line @typescript-eslint/no-use-before-define const onError = getAdapterOnError({ request, requestId, startTime, logger, resolve, }); const onAbortError = ({ status, extra, }: { status: ExtractAdapterStatusType<T>; extra: ExtractAdapterExtraType<T>; }) => { logger.error({ title: `Abort error`, type: "request", extra: { request, requestId, }, }); const error = internalErrorMapping(getErrorMessage("abort")); return onError({ error, status, extra }); }; const onTimeoutError = ({ status, extra, }: { status: ExtractAdapterStatusType<T>; extra: ExtractAdapterExtraType<T>; }) => { logger.error({ title: `Timeout error`, type: "request", extra: { request, requestId, }, }); const error = getErrorMessage("timeout"); return onError({ error, status, extra }); }; const onUnexpectedError = ({ status, extra, }: { status: ExtractAdapterStatusType<T>; extra: ExtractAdapterExtraType<T>; }) => { logger.error({ title: `Unexpected error`, type: "request", extra: { request, requestId, }, }); const error = getErrorMessage(); return onError({ error, status, extra }); }; // Abort const getAbortController = () => { return requestManager.getAbortController(abortKey, requestId); }; const createAbortListener = ({ status, extra, onAbort = () => {}, }: { status: ExtractAdapterStatusType<T>; extra: ExtractAdapterExtraType<T>; onAbort?: () => void; }) => { const controller = getAbortController(); if (!controller) { throw new RequestProcessingError("Controller is not found"); } const abort = () => { onAbortError({ status, extra }); onAbort(); requestManager.events.emitAbort({ requestId, request }); }; // Instant abort when we stack many requests triggered at once, and we receive aborted controller if (controller.signal.aborted) { abort(); } // Abort during the request controller.signal.addEventListener("abort", abort); return () => controller.signal.removeEventListener("abort", abort); }; logger.debug({ title: `Mounted adapter bindings`, type: "request", extra: { request, requestId, payload, adapterOptions, }, }); const queryParams = baseRequest.client.adapter.unstable_queryParamsMapper(request.queryParams); const endpoint = baseRequest.client.adapter.unstable_endpointMapper(request.endpoint); const headers = baseRequest.client.adapter.unstable_headerMapper(request); const { url } = baseRequest.client; return { request, requestId, url, endpoint, queryParams, payload, headers, adapter: baseRequest.client.adapter, adapterOptions, getAbortController, getRequestStartTimestamp, getResponseStartTimestamp, createAbortListener, onBeforeRequest, onRequestStart, onRequestProgress, onRequestEnd, onResponseStart, onResponseProgress, onResponseEnd, onSuccess, onAbortError, onTimeoutError, onUnexpectedError, onError, }; }; export function getAdapterOnError<T extends AdapterInstance>({ request, requestId, startTime, resolve, logger, }: { request: RequestInstance; requestId: string; startTime: number; logger: LoggerMethods; resolve: (value: ResponseSuccessType<any, T> | ResponseErrorType<any, T>) => void; }) { const { client } = request; return async ({ error, status, extra, }: { error: any; status: ExtractAdapterStatusType<T>; extra: ExtractAdapterExtraType<T>; }): Promise<ResponseErrorType<any, T>> => { let response: ResponseErrorType<any, T> = { data: null, status, error, success: false, extra, requestTimestamp: startTime, responseTimestamp: +new Date(), }; response = (await client.unstable_modifyResponse(response, request)) as typeof response; response = (await client.unstable_modifyErrorResponse(response, request)) as typeof response; client.triggerPlugins("onRequestError", { response, request }); client.triggerPlugins("onRequestFinished", { response, request }); resolve(response); logger.error({ title: `Request error`, type: "request", extra: { request, requestId, response, }, }); return response; }; }