gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: fix #8494


From: gnunet
Subject: [taler-wallet-core] branch master updated: fix #8494
Date: Wed, 10 Apr 2024 00:55:51 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 86e02c6ec fix #8494
86e02c6ec is described below

commit 86e02c6ecdde78ed741d89c5a64f6bfb79a2426e
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Apr 9 19:55:45 2024 -0300

    fix #8494
---
 packages/taler-util/src/taleruri.test.ts           |  47 ++++++++++
 packages/taler-util/src/taleruri.ts                |  67 +++++++++++++-
 .../src/NavigationBar.tsx                          |   2 +
 .../src/platform/chrome.ts                         |  41 +++++----
 .../src/popup/TalerActionFound.tsx                 |  11 +++
 .../src/wallet/AddExchange/index.ts                |   4 +-
 .../src/wallet/AddExchange/stories.tsx             |   2 -
 .../src/wallet/AddExchange/views.tsx               |   2 +-
 .../src/wallet/Application.tsx                     |  26 +++++-
 .../src/wallet/QrReader.tsx                        | 101 ++++++++++++++++-----
 10 files changed, 254 insertions(+), 49 deletions(-)

diff --git a/packages/taler-util/src/taleruri.test.ts 
b/packages/taler-util/src/taleruri.test.ts
index dbd175fe5..7f10d21fd 100644
--- a/packages/taler-util/src/taleruri.test.ts
+++ b/packages/taler-util/src/taleruri.test.ts
@@ -17,6 +17,7 @@
 import test from "ava";
 import { AmountString } from "./taler-types.js";
 import {
+  parseAddExchangeUri,
   parseDevExperimentUri,
   parsePayPullUri,
   parsePayPushUri,
@@ -26,6 +27,7 @@ import {
   parseRestoreUri,
   parseWithdrawExchangeUri,
   parseWithdrawUri,
+  stringifyAddExchange,
   stringifyDevExperimentUri,
   stringifyPayPullUri,
   stringifyPayPushUri,
@@ -506,6 +508,51 @@ test("taler withdraw exchange URI with amount 
(stringify)", (t) => {
   );
 });
 
+
+/**
+ * 5.13 action: add-exchange 
https://lsd.gnunet.org/lsd0006/#name-action-add-exchange
+ */
+
+test("taler add exchange URI (parse)", (t) => {
+  {
+    const r1 = parseAddExchangeUri(
+      "taler://add-exchange/exchange.example.com/",
+    );
+    if (!r1) {
+      t.fail();
+      return;
+    }
+    t.deepEqual(
+      r1.exchangeBaseUrl,
+      "https://exchange.example.com/";,
+    );
+  }
+  {
+    const r2 = parseAddExchangeUri(
+      "taler://add-exchange/exchanges.example.com/api/",
+    );
+    if (!r2) {
+      t.fail();
+      return;
+    }
+    t.deepEqual(
+      r2.exchangeBaseUrl,
+      "https://exchanges.example.com/api/";,
+    );
+  }
+
+});
+
+test("taler add exchange URI (stringify)", (t) => {
+  const url = stringifyAddExchange({
+    exchangeBaseUrl: "https://exchange.demo.taler.net";,
+  });
+  t.deepEqual(
+    url,
+    "taler://add-exchange/exchange.demo.taler.net/",
+  );
+});
+
 /**
  * wrong uris
  */
diff --git a/packages/taler-util/src/taleruri.ts 
b/packages/taler-util/src/taleruri.ts
index db8a58185..b4f9db6ef 100644
--- a/packages/taler-util/src/taleruri.ts
+++ b/packages/taler-util/src/taleruri.ts
@@ -41,7 +41,8 @@ export type TalerUri =
   | BackupRestoreUri
   | RefundUriResult
   | WithdrawUriResult
-  | WithdrawExchangeUri;
+  | WithdrawExchangeUri
+  | AddExchangeUri;
 
 declare const __action_str: unique symbol;
 export type TalerUriString = string & { [__action_str]: true };
@@ -127,6 +128,11 @@ export interface WithdrawExchangeUri {
   amount?: AmountString;
 }
 
+export interface AddExchangeUri {
+  type: TalerUriAction.AddExchange;
+  exchangeBaseUrl: string;
+}
+
 /**
  * Parse a taler[+http]://withdraw URI.
  * Return undefined if not passed a valid URI.
@@ -176,6 +182,53 @@ export function parseWithdrawUri(s: string): 
WithdrawUriResult | undefined {
   return r.body;
 }
 
+/**
+ * Parse a taler[+http]://withdraw URI.
+ * Return undefined if not passed a valid URI.
+ */
+export function parseAddExchangeUriWithError(s: string) {
+  const pi = parseProtoInfoWithError(s, "add-exchange");
+  if (pi.type === "fail") {
+    return pi;
+  }
+  const parts = pi.body.rest.split("/");
+
+  if (parts.length < 2) {
+    return opKnownTalerFailure(TalerErrorCode.WALLET_TALER_URI_MALFORMED, {
+      code: TalerErrorCode.WALLET_TALER_URI_MALFORMED,
+    });
+  }
+
+  const host = parts[0].toLowerCase();
+  const pathSegments = parts.slice(1, parts.length - 1);
+  /**
+   * The statement below does not tolerate a slash-ended URI.
+   * This results in (1) the withdrawalId being passed as the
+   * empty string, and (2) the bankIntegrationApi ending with the
+   * actual withdrawal operation ID.  That can be fixed by
+   * trimming the parts-list.  FIXME
+   */
+  const p = [host, ...pathSegments].join("/");
+
+  const result: AddExchangeUri = {
+    type: TalerUriAction.AddExchange,
+    exchangeBaseUrl: canonicalizeBaseUrl(
+      `${pi.body.innerProto}://${p}/`,
+    ),
+  };
+  return opFixedSuccess(result);
+}
+
+/**
+ *
+ * @deprecated use parseWithdrawUriWithError
+ */
+export function parseAddExchangeUri(s: string): AddExchangeUri | undefined {
+  const r = parseAddExchangeUriWithError(s);
+  if (r.type === "fail") return undefined;
+  return r.body;
+}
+
 /**
  * @deprecated use TalerUriAction
  */
@@ -203,6 +256,7 @@ export enum TalerUriAction {
   Restore = "restore",
   DevExperiment = "dev-experiment",
   WithdrawExchange = "withdraw-exchange",
+  AddExchange = "add-exchange",
 }
 
 interface TalerUriProtoInfo {
@@ -270,6 +324,7 @@ const parsers: { [A in TalerUriAction]: Parser } = {
   [TalerUriAction.Withdraw]: parseWithdrawUri,
   [TalerUriAction.DevExperiment]: parseDevExperimentUri,
   [TalerUriAction.WithdrawExchange]: parseWithdrawExchangeUri,
+  [TalerUriAction.AddExchange]: parseAddExchangeUri,
 };
 
 export function parseTalerUri(string: string): TalerUri | undefined {
@@ -313,6 +368,9 @@ export function stringifyTalerUri(uri: TalerUri): string {
     case TalerUriAction.WithdrawExchange: {
       return stringifyWithdrawExchange(uri);
     }
+    case TalerUriAction.AddExchange: {
+      return stringifyAddExchange(uri);
+    }
   }
 }
 
@@ -592,6 +650,13 @@ export function stringifyWithdrawExchange({
   return `${proto}://withdraw-exchange/${path}${exchangePub ?? ""}${query}`;
 }
 
+export function stringifyAddExchange({
+  exchangeBaseUrl,
+}: Omit<AddExchangeUri, "type">): string {
+  const { proto, path } = getUrlInfo(exchangeBaseUrl);
+  return `${proto}://add-exchange/${path}`;
+}
+
 export function stringifyDevExperimentUri({
   devExperimentId,
 }: Omit<DevExperimentUri, "type">): string {
diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx 
b/packages/taler-wallet-webextension/src/NavigationBar.tsx
index fc917088d..527600c96 100644
--- a/packages/taler-wallet-webextension/src/NavigationBar.tsx
+++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx
@@ -128,6 +128,7 @@ export const Pages = {
   ctaWithdraw: "/cta/withdraw",
   ctaDeposit: "/cta/deposit",
   ctaExperiment: "/cta/experiment",
+  ctaAddExchange: "/cta/add/exchange",
   ctaInvoiceCreate: pageDefinition<{ amount?: string }>(
     "/cta/invoice/create/:amount?",
   ),
@@ -153,6 +154,7 @@ const talerUriActionToPageName: {
   [TalerUriAction.PayTemplate]: "ctaPayTemplate",
   [TalerUriAction.WithdrawExchange]: "ctaWithdrawManual",
   [TalerUriAction.DevExperiment]: "ctaExperiment",
+  [TalerUriAction.AddExchange]: "ctaAddExchange",
 };
 
 export function getPathnameForTalerURI(talerUri: string): string | undefined {
diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts 
b/packages/taler-wallet-webextension/src/platform/chrome.ts
index ee071347a..6c5510eb6 100644
--- a/packages/taler-wallet-webextension/src/platform/chrome.ts
+++ b/packages/taler-wallet-webextension/src/platform/chrome.ts
@@ -221,6 +221,13 @@ function openWalletURIFromPopup(uri: TalerUri): void {
         )}`,
       );
       break;
+    case TalerUriAction.AddExchange:
+      url = chrome.runtime.getURL(
+        `static/wallet.html#/cta/add/exchange?talerUri=${encodeURIComponent(
+          talerUri,
+        )}`,
+      );
+      break;
     case TalerUriAction.DevExperiment:
       logger.warn(`taler://dev-experiment URIs are not allowed in headers`);
       return;
@@ -474,26 +481,26 @@ function setAlertedIcon(): void {
 
 interface OffscreenCanvasRenderingContext2D
   extends CanvasState,
-    CanvasTransform,
-    CanvasCompositing,
-    CanvasImageSmoothing,
-    CanvasFillStrokeStyles,
-    CanvasShadowStyles,
-    CanvasFilters,
-    CanvasRect,
-    CanvasDrawPath,
-    CanvasUserInterface,
-    CanvasText,
-    CanvasDrawImage,
-    CanvasImageData,
-    CanvasPathDrawingStyles,
-    CanvasTextDrawingStyles,
-    CanvasPath {
+  CanvasTransform,
+  CanvasCompositing,
+  CanvasImageSmoothing,
+  CanvasFillStrokeStyles,
+  CanvasShadowStyles,
+  CanvasFilters,
+  CanvasRect,
+  CanvasDrawPath,
+  CanvasUserInterface,
+  CanvasText,
+  CanvasDrawImage,
+  CanvasImageData,
+  CanvasPathDrawingStyles,
+  CanvasTextDrawingStyles,
+  CanvasPath {
   readonly canvas: OffscreenCanvas;
 }
 declare const OffscreenCanvasRenderingContext2D: {
   prototype: OffscreenCanvasRenderingContext2D;
-  new (): OffscreenCanvasRenderingContext2D;
+  new(): OffscreenCanvasRenderingContext2D;
 };
 
 interface OffscreenCanvas extends EventTarget {
@@ -506,7 +513,7 @@ interface OffscreenCanvas extends EventTarget {
 }
 declare const OffscreenCanvas: {
   prototype: OffscreenCanvas;
-  new (width: number, height: number): OffscreenCanvas;
+  new(width: number, height: number): OffscreenCanvas;
 };
 
 function createCanvas(size: number): OffscreenCanvas {
diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx 
b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
index 11a888412..21373c7cd 100644
--- a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
+++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
@@ -77,6 +77,17 @@ function ContentByUriType({
           </Button>
         </div>
       );
+    case TalerUriAction.AddExchange:
+      return (
+        <div>
+          <p>
+            <i18n.Translate>This page has a add exchange 
action.</i18n.Translate>
+          </p>
+          <Button variant="contained" color="success" onClick={onConfirm}>
+            <i18n.Translate>Open add exchange page</i18n.Translate>
+          </Button>
+        </div>
+      );
 
     case TalerUriAction.DevExperiment:
     case TalerUriAction.PayPull:
diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts
index 3d5a105ec..94b32c157 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts
@@ -21,7 +21,7 @@ import { ErrorAlert } from "../../context/alert.js";
 import { TextFieldHandler } from "../../mui/handlers.js";
 import { StateViewMap, compose } from "../../utils/index.js";
 import { useComponentState } from "./state.js";
-import { ConfirmView, VerifyView } from "./views.js";
+import { ConfirmAddExchangeView, VerifyView } from "./views.js";
 
 export interface Props {
   currency?: string;
@@ -81,7 +81,7 @@ export namespace State {
 const viewMapping: StateViewMap<State> = {
   loading: Loading,
   error: ErrorAlertView,
-  confirm: ConfirmView,
+  confirm: ConfirmAddExchangeView,
   verify: VerifyView,
 };
 
diff --git 
a/packages/taler-wallet-webextension/src/wallet/AddExchange/stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/stories.tsx
index 4e2610743..f205b6415 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddExchange/stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/stories.tsx
@@ -19,8 +19,6 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import * as tests from "@gnu-taler/web-util/testing";
-import { ConfirmView, VerifyView } from "./views.js";
 
 export default {
   title: "example",
diff --git 
a/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx
index 21309fd7b..f6537bc68 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx
@@ -205,7 +205,7 @@ export function VerifyView({
   );
 }
 
-export function ConfirmView({
+export function ConfirmAddExchangeView({
   url,
   onCancel,
   onConfirm,
diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx 
b/packages/taler-wallet-webextension/src/wallet/Application.tsx
index 1fc1e46f4..5c31701e2 100644
--- a/packages/taler-wallet-webextension/src/wallet/Application.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx
@@ -23,7 +23,9 @@
 import {
   Amounts,
   TalerUri,
+  TalerUriAction,
   TranslatedString,
+  parseTalerUri,
   stringifyTalerUri,
 } from "@gnu-taler/taler-util";
 import {
@@ -84,6 +86,7 @@ import { WelcomePage } from "./Welcome.js";
 import { WalletActivity } from "../components/WalletActivity.js";
 import { EnabledBySettings } from "../components/EnabledBySettings.js";
 import { DevExperimentPage } from "../cta/DevExperiment/index.js";
+import { ConfirmAddExchangeView } from "./AddExchange/views.js";
 
 export function Application(): VNode {
   const { i18n } = useTranslationContext();
@@ -510,7 +513,28 @@ export function Application(): VNode {
               </CallToActionTemplate>
             )}
           />
-
+          <Route
+            path={Pages.ctaAddExchange}
+            component={({ talerUri }: { talerUri: string }) => {
+              const tUri = parseTalerUri(decodeURIComponent(talerUri))
+              const baseUrl = tUri?.type === TalerUriAction.AddExchange ? 
tUri.exchangeBaseUrl : undefined
+              if (!baseUrl) {
+                redirectTo(Pages.balanceHistory({}))
+                return <div>
+                  invalid url {talerUri}
+                </div>
+              }
+              return <CallToActionTemplate title={i18n.str`Add exchange`}>
+                <ConfirmAddExchangeView
+                  url={baseUrl}
+                  status="confirm"
+                  error={undefined}
+                  onCancel={() => redirectTo(Pages.balanceHistory({}))}
+                  onConfirm={() => redirectTo(Pages.balanceHistory({}))}
+                />
+              </CallToActionTemplate>
+            }}
+          />
           {/**
            * NOT FOUND
            * all redirects should be at the end
diff --git a/packages/taler-wallet-webextension/src/wallet/QrReader.tsx 
b/packages/taler-wallet-webextension/src/wallet/QrReader.tsx
index 999223fd8..1d18b3993 100644
--- a/packages/taler-wallet-webextension/src/wallet/QrReader.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/QrReader.tsx
@@ -15,8 +15,10 @@
  */
 
 import {
+  assertUnreachable,
   parseTalerUri,
   TalerUri,
+  TalerUriAction,
   TranslatedString,
 } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
@@ -30,6 +32,7 @@ import { Button } from "../mui/Button.js";
 import { Grid } from "../mui/Grid.js";
 import { InputFile } from "../mui/InputFile.js";
 import { TextField } from "../mui/TextField.js";
+import { EnabledBySettings } from "../components/EnabledBySettings.js";
 
 const QrCanvas = css`
   width: 80%;
@@ -211,6 +214,23 @@ export function QrReaderPage({ onDetected }: Props): VNode 
{
 
   const { i18n } = useTranslationContext();
 
+  function onChangeDetect(str: string) {
+    if (!!str) {
+      const uri = parseTalerUri(str)
+      if (!uri) {
+        setError(
+          i18n.str`URI is not valid. Taler URI should start with "taler://"`,
+        );
+      } else {
+        onDetected(uri)
+        setError(undefined);
+      }
+    } else {
+      setError(undefined);
+    }
+    setValue(str);
+  }
+
   function onChange(str: string) {
     if (!!str) {
       if (!parseTalerUri(str)) {
@@ -244,7 +264,7 @@ export function QrReaderPage({ onDetected }: Props): VNode {
     try {
       const code = await createCanvasFromVideo(video, canvasRef.current);
       if (code) {
-        onChange(code);
+        onChangeDetect(code);
         setShow("canvas");
       }
       stream.getTracks().forEach((e) => {
@@ -264,7 +284,7 @@ export function QrReaderPage({ onDetected }: Props): VNode {
     try {
       const code = await createCanvasFromFile(fileContent, canvasRef.current);
       if (code) {
-        onChange(code);
+        onChangeDetect(code);
         setShow("canvas");
       } else {
         setError(i18n.str`Could not found a QR code in the file`);
@@ -273,6 +293,7 @@ export function QrReaderPage({ onDetected }: Props): VNode {
       setError(i18n.str`something unexpected happen: ${error}`);
     }
   }
+  const uri = parseTalerUri(value);
 
   const active = value === "";
   return (
@@ -297,38 +318,46 @@ export function QrReaderPage({ onDetected }: Props): 
VNode {
           <Grid item xs={2}>
             <p>{error && <Alert severity="error">{error}</Alert>}</p>
           </Grid>
-          <Grid item xs={1}>
-            {!active && (
-              <Button
-                variant="contained"
-                onClick={async () => {
-                  setShow("nothing");
-                  onChange("");
-                }}
-                color="error"
-              >
-                <i18n.Translate>Clear</i18n.Translate>
-              </Button>
-            )}
-          </Grid>
-          <Grid item xs={1}>
-            {value && (
+          {uri && (
+            <Grid item xs={2}>
               <Button
                 disabled={!!error}
                 variant="contained"
                 color="success"
                 onClick={async () => {
-                  const uri = parseTalerUri(value);
                   if (uri) onDetected(uri);
                 }}
               >
-                <i18n.Translate>Open</i18n.Translate>
+                {(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>
-            )}
-          </Grid>
-          <Grid item xs={1}>
-            <InputFile onChange={onFileRead}>Read QR from file</InputFile>
-          </Grid>
+            </Grid>
+          )}
           <Grid item xs={1}>
             <p>
               <Button variant="contained" onClick={startVideo}>
@@ -336,6 +365,28 @@ export function QrReaderPage({ onDetected }: Props): VNode 
{
               </Button>
             </p>
           </Grid>
+          {!active && (
+            <Grid item xs={1}>
+              <p>
+
+                <Button
+                  variant="contained"
+                  onClick={async () => {
+                    setShow("nothing");
+                    onChange("");
+                  }}
+                  color="error"
+                >
+                  <i18n.Translate>Clear</i18n.Translate>
+                </Button>
+              </p>
+            </Grid>
+          )}
+          <EnabledBySettings name="advancedMode">
+            <Grid item xs={2}>
+              <InputFile onChange={onFileRead}>Read QR from file</InputFile>
+            </Grid>
+          </EnabledBySettings>
         </Grid>
       </section>
       <div>

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