From 86e02c6ecdde78ed741d89c5a64f6bfb79a2426e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 9 Apr 2024 19:55:45 -0300 Subject: 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(-) (limited to 'packages') 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): string { + const { proto, path } = getUrlInfo(exchangeBaseUrl); + return `${proto}://add-exchange/${path}`; +} + export function stringifyDevExperimentUri({ devExperimentId, }: Omit): 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({ ); + case TalerUriAction.AddExchange: + return ( +
+

+ This page has a add exchange action. +

+ +
+ ); 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 = { 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 { )} /> - + { + const tUri = parseTalerUri(decodeURIComponent(talerUri)) + const baseUrl = tUri?.type === TalerUriAction.AddExchange ? tUri.exchangeBaseUrl : undefined + if (!baseUrl) { + redirectTo(Pages.balanceHistory({})) + return
+ invalid url {talerUri} +
+ } + return + redirectTo(Pages.balanceHistory({}))} + onConfirm={() => redirectTo(Pages.balanceHistory({}))} + /> + + }} + /> {/** * 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 {

{error && {error}}

- - {!active && ( - - )} - - - {value && ( + {uri && ( + - )} - - - Read QR from file - + + )}

+ {!active && ( + +

+ + +

+
+ )} + + + Read QR from file + +
-- cgit v1.2.3