diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/utils/request.ts')
-rw-r--r-- | packages/merchant-backoffice-ui/src/utils/request.ts | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/packages/merchant-backoffice-ui/src/utils/request.ts b/packages/merchant-backoffice-ui/src/utils/request.ts new file mode 100644 index 000000000..32b31a557 --- /dev/null +++ b/packages/merchant-backoffice-ui/src/utils/request.ts @@ -0,0 +1,282 @@ +/* + This file is part of GNU Taler + (C) 2021-2023 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +// import axios, { AxiosError, AxiosResponse } from "axios"; +import { MerchantBackend } from "../declaration.js"; + +export async function defaultRequestHandler<T>( + base: string, + path: string, + options: RequestOptions = {}, +): Promise<HttpResponseOk<T>> { + const requestHeaders = options.token + ? { Authorization: `Bearer ${options.token}` } + : undefined; + + const requestMethod = options?.method ?? "GET"; + const requestBody = options?.data; + const requestTimeout = 2 * 1000; + const requestParams = options.params ?? {}; + + const _url = new URL(`${base}${path}`); + + Object.entries(requestParams).forEach(([key, value]) => { + _url.searchParams.set(key, String(value)); + }); + + let payload: BodyInit | undefined = undefined; + if (requestBody != null) { + if (typeof requestBody === "string") { + payload = requestBody; + } else if (requestBody instanceof ArrayBuffer) { + payload = requestBody; + } else if (ArrayBuffer.isView(requestBody)) { + payload = requestBody; + } else if (typeof requestBody === "object") { + payload = JSON.stringify(requestBody); + } else { + throw Error("unsupported request body type"); + } + } + + const controller = new AbortController(); + const timeoutId = setTimeout(() => { + controller.abort("HTTP_REQUEST_TIMEOUT"); + }, requestTimeout); + + const response = await fetch(_url.href, { + headers: { + ...requestHeaders, + "Content-Type": "text/plain", + }, + method: requestMethod, + credentials: "omit", + mode: "cors", + body: payload, + signal: controller.signal, + }); + + if (timeoutId) { + clearTimeout(timeoutId); + } + const headerMap = new Headers(); + response.headers.forEach((value, key) => { + headerMap.set(key, value); + }); + + if (response.ok) { + const result = await buildRequestOk<T>( + response, + _url, + payload, + !!options.token, + ); + return result; + } else { + const error = await buildRequestFailed( + response, + _url, + payload, + !!options.token, + ); + throw error; + } +} + +export type HttpResponse<T> = + | HttpResponseOk<T> + | HttpResponseLoading<T> + | HttpError; +export type HttpResponsePaginated<T> = + | HttpResponseOkPaginated<T> + | HttpResponseLoading<T> + | HttpError; + +export interface RequestInfo { + url: URL; + hasToken: boolean; + payload: any; + status: number; +} + +interface HttpResponseLoading<T> { + ok?: false; + loading: true; + clientError?: false; + serverError?: false; + + data?: T; +} +export interface HttpResponseOk<T> { + ok: true; + loading?: false; + clientError?: false; + serverError?: false; + + data: T; + info?: RequestInfo; +} + +export type HttpResponseOkPaginated<T> = HttpResponseOk<T> & WithPagination; + +export interface WithPagination { + loadMore: () => void; + loadMorePrev: () => void; + isReachingEnd?: boolean; + isReachingStart?: boolean; +} + +export type HttpError = + | HttpResponseClientError + | HttpResponseServerError + | HttpResponseUnexpectedError; +export interface SwrError { + info: unknown; + status: number; + message: string; +} +export interface HttpResponseServerError { + ok?: false; + loading?: false; + clientError?: false; + serverError: true; + + error?: MerchantBackend.ErrorDetail; + status: number; + message: string; + info?: RequestInfo; +} +interface HttpResponseClientError { + ok?: false; + loading?: false; + clientError: true; + serverError?: false; + + info?: RequestInfo; + isUnauthorized: boolean; + isNotfound: boolean; + status: number; + error?: MerchantBackend.ErrorDetail; + message: string; +} + +interface HttpResponseUnexpectedError { + ok?: false; + loading?: false; + clientError?: false; + serverError?: false; + + info?: RequestInfo; + status?: number; + error: unknown; + message: string; +} + +type Methods = "GET" | "POST" | "PATCH" | "DELETE" | "PUT"; + +export interface RequestOptions { + method?: Methods; + token?: string; + data?: any; + params?: unknown; +} + +async function buildRequestOk<T>( + response: Response, + url: URL, + payload: any, + hasToken: boolean, +): Promise<HttpResponseOk<T>> { + const dataTxt = await response.text(); + const data = dataTxt ? JSON.parse(dataTxt) : undefined + return { + ok: true, + data, + info: { + payload, + url, + hasToken, + status: response.status, + }, + }; +} + +async function buildRequestFailed( + response: Response, + url: URL, + payload: any, + hasToken: boolean, +): Promise< + | HttpResponseClientError + | HttpResponseServerError + | HttpResponseUnexpectedError +> { + const status = response?.status; + + const info: RequestInfo = { + payload, + url, + hasToken, + status: status || 0, + }; + + try { + const dataTxt = await response.text(); + const data = dataTxt ? JSON.parse(dataTxt) : undefined + if (status && status >= 400 && status < 500) { + const error: HttpResponseClientError = { + clientError: true, + isNotfound: status === 404, + isUnauthorized: status === 401, + status, + info, + message: data?.hint, + error: data, + }; + return error; + } + if (status && status >= 500 && status < 600) { + const error: HttpResponseServerError = { + serverError: true, + status, + info, + message: `${data?.hint} (code ${data?.code})`, + error: data, + }; + return error; + } + return { + info, + status, + error: {}, + message: "NOT DEFINED", + }; + } catch (ex) { + const error: HttpResponseUnexpectedError = { + info, + status, + error: ex, + message: "NOT DEFINED", + }; + + throw error; + } +} + +// export function isAxiosError<T>( +// error: AxiosError | any, +// ): error is AxiosError<T> { +// return error && error.isAxiosError; +// } |