move request api to web-util
This commit is contained in:
parent
f7982ed996
commit
be01d1479c
@ -37,7 +37,7 @@
|
|||||||
"preact-render-to-string": "^5.2.6",
|
"preact-render-to-string": "^5.2.6",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"swr": "1.3.0",
|
"swr": "2.0.3",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"ws": "7.4.5"
|
"ws": "7.4.5"
|
||||||
|
43
packages/web-util/src/context/api.ts
Normal file
43
packages/web-util/src/context/api.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
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 { ComponentChildren, createContext, h, VNode } from "preact";
|
||||||
|
import { useContext } from "preact/hooks";
|
||||||
|
import { defaultRequestHandler } from "../utils/request.js";
|
||||||
|
|
||||||
|
interface Type {
|
||||||
|
request: typeof defaultRequestHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Context = createContext<Type>({
|
||||||
|
request: defaultRequestHandler,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useApiContext = (): Type => useContext(Context);
|
||||||
|
export const ApiContextProvider = ({
|
||||||
|
children,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
value: Type;
|
||||||
|
children: ComponentChildren;
|
||||||
|
}): VNode => {
|
||||||
|
return h(Context.Provider, { value, children });
|
||||||
|
};
|
@ -1,5 +1,7 @@
|
|||||||
|
export { ApiContextProvider, useApiContext } from "./api.js";
|
||||||
export {
|
export {
|
||||||
InternationalizationAPI,
|
InternationalizationAPI,
|
||||||
TranslationProvider,
|
TranslationProvider,
|
||||||
useTranslationContext,
|
useTranslationContext
|
||||||
} from "./translation.js";
|
} from "./translation.js";
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export * from "./hooks/index.js";
|
export * from "./hooks/index.js";
|
||||||
|
export * from "./utils/request.js";
|
||||||
export * from "./context/index.js";
|
export * from "./context/index.js";
|
||||||
export * from "./components/index.js";
|
export * from "./components/index.js";
|
||||||
export * as tests from "./tests/index.js";
|
export * as tests from "./tests/index.js";
|
||||||
|
243
packages/web-util/src/utils/base64.ts
Normal file
243
packages/web-util/src/utils/base64.ts
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export function base64encode(str: string): string {
|
||||||
|
return base64EncArr(strToUTF8Arr(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64decode(str: string): string {
|
||||||
|
return UTF8ArrToStr(base64DecToArr(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// from https://developer.mozilla.org/en-US/docs/Glossary/Base64
|
||||||
|
|
||||||
|
// Array of bytes to Base64 string decoding
|
||||||
|
function b64ToUint6(nChr: number): number {
|
||||||
|
return nChr > 64 && nChr < 91
|
||||||
|
? nChr - 65
|
||||||
|
: nChr > 96 && nChr < 123
|
||||||
|
? nChr - 71
|
||||||
|
: nChr > 47 && nChr < 58
|
||||||
|
? nChr + 4
|
||||||
|
: nChr === 43
|
||||||
|
? 62
|
||||||
|
: nChr === 47
|
||||||
|
? 63
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64DecToArr(sBase64: string, nBlocksSize?: number): Uint8Array {
|
||||||
|
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, ""); // Only necessary if the base64 includes whitespace such as line breaks.
|
||||||
|
const nInLen = sB64Enc.length;
|
||||||
|
const nOutLen = nBlocksSize
|
||||||
|
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
|
||||||
|
: (nInLen * 3 + 1) >> 2;
|
||||||
|
const taBytes = new Uint8Array(nOutLen);
|
||||||
|
|
||||||
|
let nMod3;
|
||||||
|
let nMod4;
|
||||||
|
let nUint24 = 0;
|
||||||
|
let nOutIdx = 0;
|
||||||
|
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
|
||||||
|
nMod4 = nInIdx & 3;
|
||||||
|
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4));
|
||||||
|
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||||
|
nMod3 = 0;
|
||||||
|
while (nMod3 < 3 && nOutIdx < nOutLen) {
|
||||||
|
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
|
||||||
|
nMod3++;
|
||||||
|
nOutIdx++;
|
||||||
|
}
|
||||||
|
nUint24 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return taBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base64 string to array encoding */
|
||||||
|
function uint6ToB64(nUint6: number): number {
|
||||||
|
return nUint6 < 26
|
||||||
|
? nUint6 + 65
|
||||||
|
: nUint6 < 52
|
||||||
|
? nUint6 + 71
|
||||||
|
: nUint6 < 62
|
||||||
|
? nUint6 - 4
|
||||||
|
: nUint6 === 62
|
||||||
|
? 43
|
||||||
|
: nUint6 === 63
|
||||||
|
? 47
|
||||||
|
: 65;
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64EncArr(aBytes: Uint8Array): string {
|
||||||
|
let nMod3 = 2;
|
||||||
|
let sB64Enc = "";
|
||||||
|
|
||||||
|
const nLen = aBytes.length;
|
||||||
|
let nUint24 = 0;
|
||||||
|
for (let nIdx = 0; nIdx < nLen; nIdx++) {
|
||||||
|
nMod3 = nIdx % 3;
|
||||||
|
// To break your base64 into several 80-character lines, add:
|
||||||
|
// if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) {
|
||||||
|
// sB64Enc += "\r\n";
|
||||||
|
// }
|
||||||
|
|
||||||
|
nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24);
|
||||||
|
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
|
||||||
|
sB64Enc += String.fromCodePoint(
|
||||||
|
uint6ToB64((nUint24 >>> 18) & 63),
|
||||||
|
uint6ToB64((nUint24 >>> 12) & 63),
|
||||||
|
uint6ToB64((nUint24 >>> 6) & 63),
|
||||||
|
uint6ToB64(nUint24 & 63)
|
||||||
|
);
|
||||||
|
nUint24 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
sB64Enc.substring(0, sB64Enc.length - 2 + nMod3) +
|
||||||
|
(nMod3 === 2 ? "" : nMod3 === 1 ? "=" : "==")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UTF-8 array to JS string and vice versa */
|
||||||
|
|
||||||
|
function UTF8ArrToStr(aBytes: Uint8Array): string {
|
||||||
|
let sView = "";
|
||||||
|
let nPart;
|
||||||
|
const nLen = aBytes.length;
|
||||||
|
for (let nIdx = 0; nIdx < nLen; nIdx++) {
|
||||||
|
nPart = aBytes[nIdx];
|
||||||
|
sView += String.fromCodePoint(
|
||||||
|
nPart > 251 && nPart < 254 && nIdx + 5 < nLen /* six bytes */
|
||||||
|
? /* (nPart - 252 << 30) may be not so safe in ECMAScript! So…: */
|
||||||
|
(nPart - 252) * 1073741824 +
|
||||||
|
((aBytes[++nIdx] - 128) << 24) +
|
||||||
|
((aBytes[++nIdx] - 128) << 18) +
|
||||||
|
((aBytes[++nIdx] - 128) << 12) +
|
||||||
|
((aBytes[++nIdx] - 128) << 6) +
|
||||||
|
aBytes[++nIdx] -
|
||||||
|
128
|
||||||
|
: nPart > 247 && nPart < 252 && nIdx + 4 < nLen /* five bytes */
|
||||||
|
? ((nPart - 248) << 24) +
|
||||||
|
((aBytes[++nIdx] - 128) << 18) +
|
||||||
|
((aBytes[++nIdx] - 128) << 12) +
|
||||||
|
((aBytes[++nIdx] - 128) << 6) +
|
||||||
|
aBytes[++nIdx] -
|
||||||
|
128
|
||||||
|
: nPart > 239 && nPart < 248 && nIdx + 3 < nLen /* four bytes */
|
||||||
|
? ((nPart - 240) << 18) +
|
||||||
|
((aBytes[++nIdx] - 128) << 12) +
|
||||||
|
((aBytes[++nIdx] - 128) << 6) +
|
||||||
|
aBytes[++nIdx] -
|
||||||
|
128
|
||||||
|
: nPart > 223 && nPart < 240 && nIdx + 2 < nLen /* three bytes */
|
||||||
|
? ((nPart - 224) << 12) +
|
||||||
|
((aBytes[++nIdx] - 128) << 6) +
|
||||||
|
aBytes[++nIdx] -
|
||||||
|
128
|
||||||
|
: nPart > 191 && nPart < 224 && nIdx + 1 < nLen /* two bytes */
|
||||||
|
? ((nPart - 192) << 6) + aBytes[++nIdx] - 128
|
||||||
|
: /* nPart < 127 ? */ /* one byte */
|
||||||
|
nPart
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return sView;
|
||||||
|
}
|
||||||
|
|
||||||
|
function strToUTF8Arr(sDOMStr: string): Uint8Array {
|
||||||
|
let nChr;
|
||||||
|
const nStrLen = sDOMStr.length;
|
||||||
|
let nArrLen = 0;
|
||||||
|
|
||||||
|
/* mapping… */
|
||||||
|
for (let nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) {
|
||||||
|
nChr = sDOMStr.codePointAt(nMapIdx);
|
||||||
|
if (nChr === undefined) {
|
||||||
|
throw Error(`No char at ${nMapIdx} on string with length: ${sDOMStr.length}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nChr >= 0x10000) {
|
||||||
|
nMapIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
nArrLen +=
|
||||||
|
nChr < 0x80
|
||||||
|
? 1
|
||||||
|
: nChr < 0x800
|
||||||
|
? 2
|
||||||
|
: nChr < 0x10000
|
||||||
|
? 3
|
||||||
|
: nChr < 0x200000
|
||||||
|
? 4
|
||||||
|
: nChr < 0x4000000
|
||||||
|
? 5
|
||||||
|
: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aBytes = new Uint8Array(nArrLen);
|
||||||
|
|
||||||
|
/* transcription… */
|
||||||
|
let nIdx = 0;
|
||||||
|
let nChrIdx = 0;
|
||||||
|
while (nIdx < nArrLen) {
|
||||||
|
nChr = sDOMStr.codePointAt(nChrIdx);
|
||||||
|
if (nChr === undefined) {
|
||||||
|
throw Error(`No char at ${nChrIdx} on string with length: ${sDOMStr.length}`)
|
||||||
|
}
|
||||||
|
if (nChr < 128) {
|
||||||
|
/* one byte */
|
||||||
|
aBytes[nIdx++] = nChr;
|
||||||
|
} else if (nChr < 0x800) {
|
||||||
|
/* two bytes */
|
||||||
|
aBytes[nIdx++] = 192 + (nChr >>> 6);
|
||||||
|
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||||
|
} else if (nChr < 0x10000) {
|
||||||
|
/* three bytes */
|
||||||
|
aBytes[nIdx++] = 224 + (nChr >>> 12);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 6) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||||
|
} else if (nChr < 0x200000) {
|
||||||
|
/* four bytes */
|
||||||
|
aBytes[nIdx++] = 240 + (nChr >>> 18);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 12) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 6) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||||
|
nChrIdx++;
|
||||||
|
} else if (nChr < 0x4000000) {
|
||||||
|
/* five bytes */
|
||||||
|
aBytes[nIdx++] = 248 + (nChr >>> 24);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 18) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 12) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 6) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||||
|
nChrIdx++;
|
||||||
|
} /* if (nChr <= 0x7fffffff) */ else {
|
||||||
|
/* six bytes */
|
||||||
|
aBytes[nIdx++] = 252 + (nChr >>> 30);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 24) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 18) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 12) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + ((nChr >>> 6) & 63);
|
||||||
|
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||||
|
nChrIdx++;
|
||||||
|
}
|
||||||
|
nChrIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return aBytes;
|
||||||
|
}
|
319
packages/web-util/src/utils/request.ts
Normal file
319
packages/web-util/src/utils/request.ts
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
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 { HttpStatusCode } from "@gnu-taler/taler-util";
|
||||||
|
import { base64encode } from "./base64.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param baseUrl URL where the service is located
|
||||||
|
* @param endpoint endpoint of the service to be called
|
||||||
|
* @param options auth, method and params
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function defaultRequestHandler<T>(
|
||||||
|
baseUrl: string,
|
||||||
|
endpoint: string,
|
||||||
|
options: RequestOptions = {},
|
||||||
|
): Promise<HttpResponseOk<T>> {
|
||||||
|
const requestHeaders: Record<string, string> = {};
|
||||||
|
if (options.token) {
|
||||||
|
requestHeaders.Authorization = `Bearer ${options.token}`
|
||||||
|
} else if (options.basicAuth) {
|
||||||
|
requestHeaders.Authorization = `Basic ${base64encode(`${options.basicAuth.username}:${options.basicAuth.password}`)}`
|
||||||
|
}
|
||||||
|
requestHeaders["Content-Type"] = options.contentType === "json" ? "application/json" : "text/plain"
|
||||||
|
|
||||||
|
const requestMethod = options?.method ?? "GET";
|
||||||
|
const requestBody = options?.data;
|
||||||
|
const requestTimeout = options?.timeout ?? 2 * 1000;
|
||||||
|
const requestParams = options.params ?? {};
|
||||||
|
|
||||||
|
const _url = new URL(`${baseUrl}${endpoint}`);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = await fetch(_url.href, {
|
||||||
|
headers: requestHeaders,
|
||||||
|
method: requestMethod,
|
||||||
|
credentials: "omit",
|
||||||
|
mode: "cors",
|
||||||
|
body: payload,
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
const info: RequestInfo = {
|
||||||
|
payload,
|
||||||
|
url: _url.href,
|
||||||
|
hasToken: !!options.token,
|
||||||
|
status: 0,
|
||||||
|
};
|
||||||
|
const error: HttpResponseUnexpectedError = {
|
||||||
|
info,
|
||||||
|
status: 0,
|
||||||
|
error: ex,
|
||||||
|
message: "Request timeout",
|
||||||
|
};
|
||||||
|
throw new RequestError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.href,
|
||||||
|
payload,
|
||||||
|
!!options.token,
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
const error = await buildRequestFailed(
|
||||||
|
response,
|
||||||
|
_url.href,
|
||||||
|
payload,
|
||||||
|
!!options.token,
|
||||||
|
);
|
||||||
|
throw new RequestError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HttpResponse<T, ErrorDetail> =
|
||||||
|
| HttpResponseOk<T>
|
||||||
|
| HttpResponseLoading<T>
|
||||||
|
| HttpError<ErrorDetail>;
|
||||||
|
|
||||||
|
export type HttpResponsePaginated<T, ErrorDetail> =
|
||||||
|
| HttpResponseOkPaginated<T>
|
||||||
|
| HttpResponseLoading<T>
|
||||||
|
| HttpError<ErrorDetail>;
|
||||||
|
|
||||||
|
export interface RequestInfo {
|
||||||
|
url: string;
|
||||||
|
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<ErrorDetail> =
|
||||||
|
| HttpResponseClientError<ErrorDetail>
|
||||||
|
| HttpResponseServerError<ErrorDetail>
|
||||||
|
| HttpResponseUnexpectedError;
|
||||||
|
|
||||||
|
export interface HttpResponseServerError<ErrorDetail> {
|
||||||
|
ok?: false;
|
||||||
|
loading?: false;
|
||||||
|
clientError?: false;
|
||||||
|
serverError: true;
|
||||||
|
|
||||||
|
error?: ErrorDetail;
|
||||||
|
status: HttpStatusCode;
|
||||||
|
message: string;
|
||||||
|
info?: RequestInfo;
|
||||||
|
}
|
||||||
|
interface HttpResponseClientError<ErrorDetail> {
|
||||||
|
ok?: false;
|
||||||
|
loading?: false;
|
||||||
|
clientError: true;
|
||||||
|
serverError?: false;
|
||||||
|
|
||||||
|
info?: RequestInfo;
|
||||||
|
isUnauthorized: boolean;
|
||||||
|
isNotfound: boolean;
|
||||||
|
status: HttpStatusCode;
|
||||||
|
error?: ErrorDetail;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HttpResponseUnexpectedError {
|
||||||
|
ok?: false;
|
||||||
|
loading?: false;
|
||||||
|
clientError?: false;
|
||||||
|
serverError?: false;
|
||||||
|
|
||||||
|
info?: RequestInfo;
|
||||||
|
status?: HttpStatusCode;
|
||||||
|
error: unknown;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RequestError<ErrorDetail> extends Error {
|
||||||
|
info: HttpError<ErrorDetail>;
|
||||||
|
constructor(d: HttpError<ErrorDetail>) {
|
||||||
|
super(d.message)
|
||||||
|
this.info = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Methods = "GET" | "POST" | "PATCH" | "DELETE" | "PUT";
|
||||||
|
|
||||||
|
export interface RequestOptions {
|
||||||
|
method?: Methods;
|
||||||
|
token?: string;
|
||||||
|
basicAuth?: {
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
}
|
||||||
|
data?: any;
|
||||||
|
params?: unknown;
|
||||||
|
timeout?: number,
|
||||||
|
contentType?: "text" | "json"
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildRequestOk<T>(
|
||||||
|
response: Response,
|
||||||
|
url: string,
|
||||||
|
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<ErrorDetail>(
|
||||||
|
response: Response,
|
||||||
|
url: string,
|
||||||
|
payload: any,
|
||||||
|
hasToken: boolean,
|
||||||
|
): Promise<
|
||||||
|
| HttpResponseClientError<ErrorDetail>
|
||||||
|
| HttpResponseServerError<ErrorDetail>
|
||||||
|
| 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<ErrorDetail> = {
|
||||||
|
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<ErrorDetail> = {
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function isAxiosError<T>(
|
||||||
|
// error: AxiosError | any,
|
||||||
|
// ): error is AxiosError<T> {
|
||||||
|
// return error && error.isAxiosError;
|
||||||
|
// }
|
@ -119,7 +119,7 @@ importers:
|
|||||||
preact-router: 3.2.1
|
preact-router: 3.2.1
|
||||||
qrcode-generator: ^1.4.4
|
qrcode-generator: ^1.4.4
|
||||||
sass: 1.56.1
|
sass: 1.56.1
|
||||||
swr: 1.3.0
|
swr: 2.0.3
|
||||||
typescript: 4.9.4
|
typescript: 4.9.4
|
||||||
dependencies:
|
dependencies:
|
||||||
'@gnu-taler/taler-util': link:../taler-util
|
'@gnu-taler/taler-util': link:../taler-util
|
||||||
@ -130,7 +130,7 @@ importers:
|
|||||||
preact: 10.11.3
|
preact: 10.11.3
|
||||||
preact-router: 3.2.1_preact@10.11.3
|
preact-router: 3.2.1_preact@10.11.3
|
||||||
qrcode-generator: 1.4.4
|
qrcode-generator: 1.4.4
|
||||||
swr: 1.3.0
|
swr: 2.0.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@creativebulma/bulma-tooltip': 1.2.0
|
'@creativebulma/bulma-tooltip': 1.2.0
|
||||||
'@gnu-taler/pogen': link:../pogen
|
'@gnu-taler/pogen': link:../pogen
|
||||||
@ -640,7 +640,7 @@ importers:
|
|||||||
preact-render-to-string: ^5.2.6
|
preact-render-to-string: ^5.2.6
|
||||||
prettier: ^2.5.1
|
prettier: ^2.5.1
|
||||||
rimraf: ^3.0.2
|
rimraf: ^3.0.2
|
||||||
swr: 1.3.0
|
swr: 2.0.3
|
||||||
tslib: ^2.4.0
|
tslib: ^2.4.0
|
||||||
typescript: ^4.9.4
|
typescript: ^4.9.4
|
||||||
ws: 7.4.5
|
ws: 7.4.5
|
||||||
@ -658,7 +658,7 @@ importers:
|
|||||||
preact-render-to-string: 5.2.6_preact@10.11.3
|
preact-render-to-string: 5.2.6_preact@10.11.3
|
||||||
prettier: 2.7.1
|
prettier: 2.7.1
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
swr: 1.3.0
|
swr: 2.0.3
|
||||||
tslib: 2.4.1
|
tslib: 2.4.1
|
||||||
typescript: 4.9.4
|
typescript: 4.9.4
|
||||||
ws: 7.4.5
|
ws: 7.4.5
|
||||||
@ -14646,6 +14646,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==}
|
resolution: {integrity: sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.11.0 || ^17.0.0 || ^18.0.0
|
react: ^16.11.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/swr/2.0.3:
|
||||||
|
resolution: {integrity: sha512-sGvQDok/AHEWTPfhUWXEHBVEXmgGnuahyhmRQbjl9XBYxT/MSlAzvXEKQpyM++bMPaI52vcWS2HiKNaW7+9OFw==}
|
||||||
|
engines: {pnpm: '7'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.11.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
use-sync-external-store: 1.2.0
|
||||||
|
|
||||||
/symbol-tree/3.2.4:
|
/symbol-tree/3.2.4:
|
||||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||||
@ -15301,6 +15310,11 @@ packages:
|
|||||||
querystring: 0.2.0
|
querystring: 0.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/use-sync-external-store/1.2.0:
|
||||||
|
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
|
||||||
/use/3.1.1:
|
/use/3.1.1:
|
||||||
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
|
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
Loading…
Reference in New Issue
Block a user