aboutsummaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'packages/merchant-backoffice-ui/src/utils')
-rw-r--r--packages/merchant-backoffice-ui/src/utils/regex.test.ts88
-rw-r--r--packages/merchant-backoffice-ui/src/utils/request.ts282
-rw-r--r--packages/merchant-backoffice-ui/src/utils/switchableAxios.ts73
3 files changed, 370 insertions, 73 deletions
diff --git a/packages/merchant-backoffice-ui/src/utils/regex.test.ts b/packages/merchant-backoffice-ui/src/utils/regex.test.ts
new file mode 100644
index 000000000..41f0156f5
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/utils/regex.test.ts
@@ -0,0 +1,88 @@
+/*
+ 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/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { expect } from "chai";
+import { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/utils/constants.js";
+
+describe('payto uri format', () => {
+ const valids = [
+ 'payto://iban/DE75512108001245126199?amount=EUR:200.0&message=hello',
+ 'payto://ach/122000661/1234',
+ 'payto://upi/alice@example.com?receiver-name=Alice&amount=INR:200',
+ 'payto://void/?amount=EUR:10.5',
+ 'payto://ilp/g.acme.bob'
+ ]
+
+ it('should be valid', () => {
+ valids.forEach(v => expect(v).match(PAYTO_REGEX))
+ });
+
+ const invalids = [
+ // has two question marks
+ 'payto://iban/DE75?512108001245126199?amount=EUR:200.0&message=hello',
+ // has a space
+ 'payto://ach /122000661/1234',
+ // has a space
+ 'payto://upi/alice@ example.com?receiver-name=Alice&amount=INR:200',
+ // invalid field name (mount instead of amount)
+ 'payto://void/?mount=EUR:10.5',
+ // payto:// is incomplete
+ 'payto: //ilp/g.acme.bob'
+ ]
+
+ it('should not be valid', () => {
+ invalids.forEach(v => expect(v).not.match(PAYTO_REGEX))
+ });
+})
+
+describe('amount format', () => {
+ const valids = [
+ 'ARS:10',
+ 'COL:10.2',
+ 'UY:1,000.2',
+ 'ARS:10.123,123',
+ 'ARS:1,000,000',
+ 'ARSCOL:10',
+ 'THISISTHEMOTHERCOIN:1,000,000.123,123',
+ ]
+
+ it('should be valid', () => {
+ valids.forEach(v => expect(v).match(AMOUNT_REGEX))
+ });
+
+ const invalids = [
+ //no currency name
+ ':10',
+ //use . instead of ,
+ 'ARS:1.000.000',
+ //currency name with numbers
+ '1ARS:10',
+ //currency name with numbers
+ 'AR5:10',
+ //missing value
+ 'USD:',
+ ]
+
+ it('should not be valid', () => {
+ invalids.forEach(v => expect(v).not.match(AMOUNT_REGEX))
+ });
+
+}) \ No newline at end of file
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;
+// }
diff --git a/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts b/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts
deleted file mode 100644
index 20ce7043e..000000000
--- a/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- 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, { AxiosPromise, AxiosRequestConfig } from "axios";
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-export let removeAxiosCancelToken = false;
-
-export let axiosHandler = function doAxiosRequest(
- config: AxiosRequestConfig,
-): AxiosPromise<any> {
- return axios(config);
-};
-
-/**
- * Set this backend library to testing mode.
- * Instead of calling the axios library the @handler will be called
- *
- * @param handler callback that will mock axios
- */
-export function setAxiosRequestAsTestingEnvironment(
- handler: AxiosHandler,
-): void {
- removeAxiosCancelToken = true;
- axiosHandler = function defaultTestingHandler(config) {
- const currentHanlder = listOfHandlersToUseOnce.shift();
- if (!currentHanlder) {
- return handler(config);
- }
-
- return currentHanlder(config);
- };
-}
-
-type AxiosHandler = (config: AxiosRequestConfig) => AxiosPromise<any>;
-type AxiosArguments = { args: AxiosRequestConfig | undefined };
-
-const listOfHandlersToUseOnce = new Array<AxiosHandler>();
-
-/**
- *
- * @param handler mock function
- * @returns savedArgs
- */
-export function mockAxiosOnce(handler: AxiosHandler): {
- args: AxiosRequestConfig | undefined;
-} {
- const savedArgs: AxiosArguments = { args: undefined };
- listOfHandlersToUseOnce.push(
- (config: AxiosRequestConfig): AxiosPromise<any> => {
- savedArgs.args = config;
- return handler(config);
- },
- );
- return savedArgs;
-}