gnunet-svn
[Top][All Lists]
Advanced

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

[taler-typescript-core] branch master updated: fix #9535


From: Admin
Subject: [taler-typescript-core] branch master updated: fix #9535
Date: Fri, 14 Feb 2025 20:51:22 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 2bc77f355 fix #9535
2bc77f355 is described below

commit 2bc77f355f74bd98c6ba748c5db1822a243aaa36
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Feb 14 16:51:05 2025 -0300

    fix #9535
---
 .../taler-wallet-webextension/src/utils/index.ts   |   7 +-
 .../src/wallet/QrReader.tsx                        | 360 +++++++++++++++++----
 2 files changed, 309 insertions(+), 58 deletions(-)

diff --git a/packages/taler-wallet-webextension/src/utils/index.ts 
b/packages/taler-wallet-webextension/src/utils/index.ts
index d83e6f472..5905b90ab 100644
--- a/packages/taler-wallet-webextension/src/utils/index.ts
+++ b/packages/taler-wallet-webextension/src/utils/index.ts
@@ -15,7 +15,7 @@
  */
 
 import { createElement, VNode } from "preact";
-import { useCallback, useMemo } from "preact/hooks";
+import { useMemo } from "preact/hooks";
 
 function getJsonIfOk(r: Response): Promise<any> {
   if (r.ok) {
@@ -27,7 +27,8 @@ function getJsonIfOk(r: Response): Promise<any> {
   }
 
   throw new Error(
-    `Try another server: (${r.status}) ${r.statusText || "internal server 
error"
+    `Try another server: (${r.status}) ${
+      r.statusText || "internal server error"
     }`,
   );
 }
@@ -104,7 +105,7 @@ export function compose<SType extends { status: string }, 
PType>(
     // TheComponent.name = `${name}`;
 
     return useMemo(() => {
-      return TheComponent
+      return TheComponent;
     }, [stateHook]);
   }
 
diff --git a/packages/taler-wallet-webextension/src/wallet/QrReader.tsx 
b/packages/taler-wallet-webextension/src/wallet/QrReader.tsx
index 9635cd077..42abc5c69 100644
--- a/packages/taler-wallet-webextension/src/wallet/QrReader.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/QrReader.tsx
@@ -16,12 +16,21 @@
 
 import {
   assertUnreachable,
+  HttpStatusCode,
   parseTalerUri,
+  TalerCoreBankHttpClient,
+  TalerError,
+  TalerExchangeHttpClient,
+  TalerMerchantInstanceHttpClient,
   TalerUri,
   TalerUriAction,
   TranslatedString,
 } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+  BrowserFetchHttpLib,
+  InternationalizationAPI,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { css } from "@linaria/core";
 import { styled } from "@linaria/react";
 import jsQR, * as pr from "jsqr";
@@ -205,6 +214,29 @@ async function waitUntilReady(video: HTMLVideoElement): 
Promise<void> {
   });
 }
 
+function debounce(func: typeof testValidUri, wait = 500): typeof testValidUri {
+  let timeout: ReturnType<typeof setTimeout>;
+  type Args = Parameters<typeof testValidUri>;
+  type Ret = ReturnType<typeof testValidUri>;
+
+  function debounced(...args: Args): Promise<Ret> {
+    clearTimeout(timeout);
+    return new Promise<Ret>((res, rej) => {
+      timeout = setTimeout(() => {
+        func(...args)
+          .then((msg) => {
+            res(Promise.resolve(msg));
+          })
+          .catch(rej);
+      }, wait);
+    });
+  }
+
+  return debounced as any;
+}
+
+const testValidUriDebounced = debounce(testValidUri);
+
 export function QrReaderPage({ onDetected }: Props): VNode {
   const videoRef = useRef<HTMLVideoElement>(null);
   const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -214,36 +246,55 @@ export function QrReaderPage({ onDetected }: Props): 
VNode {
 
   const { i18n } = useTranslationContext();
 
-  function onChangeDetect(str: string) {
+  async function onChangeDetect(str: string) {
     if (str) {
       const uri = parseTalerUri(str.toLowerCase());
       if (!uri) {
         setError(
           i18n.str`URI is not valid. Taler URI should start with "taler://"`,
         );
-      } else {
-        onDetected(uri);
-        setError(undefined);
+        setValue(str);
+        return;
       }
+      setError(i18n.str`checking...`);
+      const errorMsg = await testValidUriDebounced(uri, i18n);
+      if (errorMsg) {
+        setError(errorMsg);
+        setValue(str);
+        return;
+      }
+      onDetected(uri);
+      setError(undefined);
+      setValue(str);
     } else {
       setError(undefined);
+      setValue(str);
     }
-    setValue(str);
   }
 
   function onChange(str: string) {
     if (str) {
-      if (!parseTalerUri(str.toLowerCase())) {
+      const uri = parseTalerUri(str.toLowerCase());
+      if (!uri) {
         setError(
           i18n.str`URI is not valid. Taler URI should start with "taler://"`,
         );
+        setValue(str);
       } else {
-        setError(undefined);
+        setError(i18n.str`checking...`);
+        setValue(str);
+        testValidUriDebounced(uri, i18n).then((errorMsg) => {
+          if (errorMsg) {
+            setError(errorMsg);
+            return;
+          }
+          setError(undefined);
+        });
       }
     } else {
       setError(undefined);
+      setValue(str);
     }
-    setValue(str);
   }
 
   async function startVideo() {
@@ -264,14 +315,14 @@ export function QrReaderPage({ onDetected }: Props): 
VNode {
     try {
       const code = await createCanvasFromVideo(video, canvasRef.current);
       if (code) {
-        onChangeDetect(code);
+        await onChangeDetect(code);
         setShow("canvas");
       }
       stream.getTracks().forEach((e) => {
         e.stop();
       });
     } catch (error) {
-      setError(i18n.str`something unexpected happen: ${error}`);
+      setError(i18n.str`Unexpected error happen reading the camera: ${error}`);
     }
   }
 
@@ -284,13 +335,13 @@ export function QrReaderPage({ onDetected }: Props): 
VNode {
     try {
       const code = await createCanvasFromFile(fileContent, canvasRef.current);
       if (code) {
-        onChangeDetect(code);
+        await onChangeDetect(code);
         setShow("canvas");
       } else {
         setError(i18n.str`Could not found a QR code in the file`);
       }
     } catch (error) {
-      setError(i18n.str`something unexpected happen: ${error}`);
+      setError(i18n.str`Unexpected error happen reading the file: ${error}`);
     }
   }
   const uri = parseTalerUri(value.toLowerCase());
@@ -313,53 +364,61 @@ export function QrReaderPage({ onDetected }: Props): 
VNode {
               onChange={onChange}
             />
           </div>
-          {uri && (
-            <Button
-              disabled={!!error}
-              variant="contained"
-              color="success"
-              onClick={async () => {
-                if (uri) onDetected(uri);
-              }}
-            >
-              {(function (talerUri: TalerUri): VNode {
-                switch (talerUri.type) {
-                  case TalerUriAction.Pay:
-                    return <i18n.Translate>Pay invoice</i18n.Translate>;
-                  case TalerUriAction.Withdraw:
-                    return (
-                      <i18n.Translate>Withdrawal from bank</i18n.Translate>
-                    );
-                  case TalerUriAction.Refund:
-                    return <i18n.Translate>Claim refund</i18n.Translate>;
-                  case TalerUriAction.PayPull:
-                    return <i18n.Translate>Pay invoice</i18n.Translate>;
-                  case TalerUriAction.PayPush:
-                    return <i18n.Translate>Accept payment</i18n.Translate>;
-                  case TalerUriAction.PayTemplate:
-                    return <i18n.Translate>Complete order</i18n.Translate>;
-                  case TalerUriAction.Restore:
-                    return <i18n.Translate>Restore wallet</i18n.Translate>;
-                  case TalerUriAction.DevExperiment:
-                    return <i18n.Translate>Enable experiment</i18n.Translate>;
-                  case TalerUriAction.WithdrawExchange:
-                    return (
-                      <i18n.Translate>Withdraw from exchange</i18n.Translate>
-                    );
-                  case TalerUriAction.AddExchange:
-                    return <i18n.Translate>Add exchange</i18n.Translate>;
-                  default: {
-                    assertUnreachable(talerUri);
-                  }
-                }
-              })(uri)}
-            </Button>
-          )}
         </div>
         <Grid container justifyContent="space-around" columns={2}>
           <Grid item xs={2}>
             <p>{error && <Alert severity="error">{error}</Alert>}</p>
           </Grid>
+          {uri && (
+            <Grid item xs={2}>
+              <p>
+                <Button
+                  disabled={!!error}
+                  variant="contained"
+                  color="success"
+                  onClick={async () => {
+                    if (uri) onDetected(uri);
+                  }}
+                >
+                  {(function (talerUri: TalerUri): VNode {
+                    switch (talerUri.type) {
+                      case TalerUriAction.Pay:
+                        return <i18n.Translate>Pay invoice</i18n.Translate>;
+                      case TalerUriAction.Withdraw:
+                        return (
+                          <i18n.Translate>Withdrawal from bank</i18n.Translate>
+                        );
+                      case TalerUriAction.Refund:
+                        return <i18n.Translate>Claim refund</i18n.Translate>;
+                      case TalerUriAction.PayPull:
+                        return <i18n.Translate>Pay invoice</i18n.Translate>;
+                      case TalerUriAction.PayPush:
+                        return <i18n.Translate>Accept payment</i18n.Translate>;
+                      case TalerUriAction.PayTemplate:
+                        return <i18n.Translate>Complete order</i18n.Translate>;
+                      case TalerUriAction.Restore:
+                        return <i18n.Translate>Restore wallet</i18n.Translate>;
+                      case TalerUriAction.DevExperiment:
+                        return (
+                          <i18n.Translate>Enable experiment</i18n.Translate>
+                        );
+                      case TalerUriAction.WithdrawExchange:
+                        return (
+                          <i18n.Translate>
+                            Withdraw from exchange
+                          </i18n.Translate>
+                        );
+                      case TalerUriAction.AddExchange:
+                        return <i18n.Translate>Add exchange</i18n.Translate>;
+                      default: {
+                        assertUnreachable(talerUri);
+                      }
+                    }
+                  })(uri)}
+                </Button>
+              </p>
+            </Grid>
+          )}
           <Grid item xs={2}>
             <p>
               <Button variant="contained" onClick={startVideo}>
@@ -390,3 +449,194 @@ export function QrReaderPage({ onDetected }: Props): 
VNode {
     </Container>
   );
 }
+const httpFetch: any = new BrowserFetchHttpLib();
+
+async function testValidUri(
+  uri: TalerUri,
+  i18n: InternationalizationAPI,
+): Promise<TranslatedString | undefined> {
+  switch (uri.type) {
+    case TalerUriAction.Pay: {
+      const errorExchange = await checkMerchantUrl(uri.merchantBaseUrl, i18n);
+      if (errorExchange) {
+        return errorExchange;
+      }
+      return undefined;
+    }
+    case TalerUriAction.Withdraw: {
+      const errorExchange = await checkBankUrl(
+        uri.bankIntegrationApiBaseUrl,
+        i18n,
+      );
+      if (errorExchange) {
+        return errorExchange;
+      }
+      return undefined;
+    }
+    case TalerUriAction.Refund: {
+      const errorExchange = await checkMerchantUrl(uri.merchantBaseUrl, i18n);
+      if (errorExchange) {
+        return errorExchange;
+      }
+      return undefined;
+    }
+    case TalerUriAction.PayTemplate: {
+      const errorExchange = await checkMerchantUrl(uri.merchantBaseUrl, i18n);
+      if (errorExchange) {
+        return errorExchange;
+      }
+      return undefined;
+    }
+    case TalerUriAction.Restore: {
+      return undefined;
+    }
+    case TalerUriAction.DevExperiment: {
+      return undefined;
+    }
+    case TalerUriAction.PayPull: {
+      const errorExchange = await checkExchangeUrl(uri.exchangeBaseUrl, i18n);
+      if (errorExchange) {
+        return errorExchange;
+      }
+      return undefined;
+    }
+    case TalerUriAction.PayPush: {
+      const errorExchange = await checkExchangeUrl(uri.exchangeBaseUrl, i18n);
+      if (errorExchange) {
+        return errorExchange;
+      }
+      return undefined;
+    }
+    case TalerUriAction.AddExchange: {
+      const errorExchange = await checkExchangeUrl(uri.exchangeBaseUrl, i18n);
+      if (errorExchange) {
+        return errorExchange;
+      }
+      return undefined;
+    }
+    case TalerUriAction.WithdrawExchange: {
+      const errorExchange = await checkExchangeUrl(uri.exchangeBaseUrl, i18n);
+      if (errorExchange) {
+        return errorExchange;
+      }
+      return undefined;
+    }
+    default: {
+      assertUnreachable(uri);
+    }
+  }
+}
+
+async function checkExchangeUrl(
+  baseUrl: string,
+  i18n: InternationalizationAPI,
+): Promise<TranslatedString | undefined> {
+  let url: URL | undefined = undefined;
+  try {
+    url = new URL("./", baseUrl);
+  } catch (e) {
+    return i18n.str`The exchange URL is invalid.`;
+  }
+  if (!baseUrl.endsWith("/")) {
+    return i18n.str`The exchange URL should end with '/'`;
+  }
+  try {
+    const config = await new TalerExchangeHttpClient(
+      url.href,
+      httpFetch,
+    ).getConfig();
+    if (config.type === "ok") {
+      return undefined;
+    } else {
+      switch (config.case) {
+        case HttpStatusCode.NotFound: {
+          return i18n.str`Couldn't found an exchange in the URL specified.`;
+        }
+        default: {
+          assertUnreachable(config.case);
+        }
+      }
+    }
+  } catch (e) {
+    if (e instanceof TalerError && e.errorDetail.detail) {
+      return i18n.str`HTTP request failed to ${url.href}: 
${e.errorDetail.detail}`;
+    }
+    return i18n.str`HTTP request failed to ${url.href}`;
+  }
+}
+
+async function checkMerchantUrl(
+  baseUrl: string,
+  i18n: InternationalizationAPI,
+): Promise<TranslatedString | undefined> {
+  let url: URL | undefined = undefined;
+  try {
+    url = new URL("./", baseUrl);
+  } catch (e) {
+    return i18n.str`The merchant URL is invalid.`;
+  }
+  if (!baseUrl.endsWith("/")) {
+    return i18n.str`The merchant URL should end with '/'`;
+  }
+  try {
+    const config = await new TalerMerchantInstanceHttpClient(
+      url.href,
+      httpFetch,
+    ).getConfig();
+    if (config.type === "ok") {
+      return undefined;
+    } else {
+      switch (config.case) {
+        case HttpStatusCode.NotFound: {
+          return i18n.str`Couldn't found an merchant in the URL specified.`;
+        }
+        default: {
+          assertUnreachable(config.case);
+        }
+      }
+    }
+  } catch (e) {
+    if (e instanceof TalerError && e.errorDetail.detail) {
+      return i18n.str`HTTP request failed to ${url.href}: 
${e.errorDetail.detail}`;
+    }
+    return i18n.str`HTTP request failed to ${url.href}`;
+  }
+}
+
+async function checkBankUrl(
+  baseUrl: string,
+  i18n: InternationalizationAPI,
+): Promise<TranslatedString | undefined> {
+  let url: URL | undefined = undefined;
+  try {
+    url = new URL("./", baseUrl);
+  } catch (e) {
+    return i18n.str`The bank URL is invalid.`;
+  }
+  if (!baseUrl.endsWith("/")) {
+    return i18n.str`The bank URL should end with '/'`;
+  }
+  try {
+    const config = await new TalerCoreBankHttpClient(
+      url.href,
+      httpFetch,
+    ).getConfig();
+    if (config.type === "ok") {
+      return undefined;
+    } else {
+      switch (config.case) {
+        case HttpStatusCode.NotFound: {
+          return i18n.str`Couldn't found an bank in the URL specified.`;
+        }
+        default: {
+          assertUnreachable(config.case);
+        }
+      }
+    }
+  } catch (e) {
+    if (e instanceof TalerError && e.errorDetail.detail) {
+      return i18n.str`HTTP request failed to ${url.href}: 
${e.errorDetail.detail}`;
+    }
+    return i18n.str`HTTP request failed to ${url.href}`;
+  }
+}

-- 
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]