gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 01/04: move request api to web-util


From: gnunet
Subject: [taler-wallet-core] 01/04: move request api to web-util
Date: Wed, 08 Feb 2023 21:41:34 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

commit be01d1479cf650fe8eb0c8e567620abfa4544e1e
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Feb 8 17:36:26 2023 -0300

    move request api to web-util
---
 packages/web-util/package.json         |   2 +-
 packages/web-util/src/context/api.ts   |  43 +++++
 packages/web-util/src/context/index.ts |   4 +-
 packages/web-util/src/index.browser.ts |   1 +
 packages/web-util/src/utils/base64.ts  | 243 +++++++++++++++++++++++++
 packages/web-util/src/utils/request.ts | 319 +++++++++++++++++++++++++++++++++
 pnpm-lock.yaml                         |  22 ++-
 7 files changed, 628 insertions(+), 6 deletions(-)

diff --git a/packages/web-util/package.json b/packages/web-util/package.json
index ad44ed67f..1d3dcfca6 100644
--- a/packages/web-util/package.json
+++ b/packages/web-util/package.json
@@ -37,7 +37,7 @@
     "preact-render-to-string": "^5.2.6",
     "prettier": "^2.5.1",
     "rimraf": "^3.0.2",
-    "swr": "1.3.0",
+    "swr": "2.0.3",
     "tslib": "^2.4.0",
     "typescript": "^4.9.4",
     "ws": "7.4.5"
diff --git a/packages/web-util/src/context/api.ts 
b/packages/web-util/src/context/api.ts
new file mode 100644
index 000000000..81586bd35
--- /dev/null
+++ b/packages/web-util/src/context/api.ts
@@ -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 });
+};
diff --git a/packages/web-util/src/context/index.ts 
b/packages/web-util/src/context/index.ts
index 4bc1b22f2..9ed3ef645 100644
--- a/packages/web-util/src/context/index.ts
+++ b/packages/web-util/src/context/index.ts
@@ -1,5 +1,7 @@
+export { ApiContextProvider, useApiContext } from "./api.js";
 export {
   InternationalizationAPI,
   TranslationProvider,
-  useTranslationContext,
+  useTranslationContext
 } from "./translation.js";
+
diff --git a/packages/web-util/src/index.browser.ts 
b/packages/web-util/src/index.browser.ts
index d3aeae168..2ae3f2a0b 100644
--- a/packages/web-util/src/index.browser.ts
+++ b/packages/web-util/src/index.browser.ts
@@ -1,4 +1,5 @@
 export * from "./hooks/index.js";
+export * from "./utils/request.js";
 export * from "./context/index.js";
 export * from "./components/index.js";
 export * as tests from "./tests/index.js";
diff --git a/packages/web-util/src/utils/base64.ts 
b/packages/web-util/src/utils/base64.ts
new file mode 100644
index 000000000..0e075880f
--- /dev/null
+++ b/packages/web-util/src/utils/base64.ts
@@ -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;
+}
diff --git a/packages/web-util/src/utils/request.ts 
b/packages/web-util/src/utils/request.ts
new file mode 100644
index 000000000..24342bb80
--- /dev/null
+++ b/packages/web-util/src/utils/request.ts
@@ -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;
+// }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index eef15f25e..4058ca82a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -119,7 +119,7 @@ importers:
       preact-router: 3.2.1
       qrcode-generator: ^1.4.4
       sass: 1.56.1
-      swr: 1.3.0
+      swr: 2.0.3
       typescript: 4.9.4
     dependencies:
       '@gnu-taler/taler-util': link:../taler-util
@@ -130,7 +130,7 @@ importers:
       preact: 10.11.3
       preact-router: 3.2.1_preact@10.11.3
       qrcode-generator: 1.4.4
-      swr: 1.3.0
+      swr: 2.0.3
     devDependencies:
       '@creativebulma/bulma-tooltip': 1.2.0
       '@gnu-taler/pogen': link:../pogen
@@ -640,7 +640,7 @@ importers:
       preact-render-to-string: ^5.2.6
       prettier: ^2.5.1
       rimraf: ^3.0.2
-      swr: 1.3.0
+      swr: 2.0.3
       tslib: ^2.4.0
       typescript: ^4.9.4
       ws: 7.4.5
@@ -658,7 +658,7 @@ importers:
       preact-render-to-string: 5.2.6_preact@10.11.3
       prettier: 2.7.1
       rimraf: 3.0.2
-      swr: 1.3.0
+      swr: 2.0.3
       tslib: 2.4.1
       typescript: 4.9.4
       ws: 7.4.5
@@ -14646,6 +14646,15 @@ packages:
     resolution: {integrity: 
sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==}
     peerDependencies:
       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:
     resolution: {integrity: 
sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
@@ -15301,6 +15310,11 @@ packages:
       querystring: 0.2.0
     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:
     resolution: {integrity: 
sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
     engines: {node: '>=0.10.0'}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]