summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-04-09 19:55:45 -0300
committerSebastian <sebasjm@gmail.com>2024-04-09 19:55:45 -0300
commit86e02c6ecdde78ed741d89c5a64f6bfb79a2426e (patch)
tree27d30041774d6043b079950b23917def1f335e7e
parent9dec13056fa7af00e6ba16e6e57d527e3aa25185 (diff)
downloadwallet-core-86e02c6ecdde78ed741d89c5a64f6bfb79a2426e.tar.gz
wallet-core-86e02c6ecdde78ed741d89c5a64f6bfb79a2426e.tar.bz2
wallet-core-86e02c6ecdde78ed741d89c5a64f6bfb79a2426e.zip
fix #8494
-rw-r--r--packages/taler-util/src/taleruri.test.ts47
-rw-r--r--packages/taler-util/src/taleruri.ts67
-rw-r--r--packages/taler-wallet-webextension/src/NavigationBar.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/platform/chrome.ts41
-rw-r--r--packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx11
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts4
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddExchange/stories.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Application.tsx26
-rw-r--r--packages/taler-wallet-webextension/src/wallet/QrReader.tsx101
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.
@@ -177,6 +183,53 @@ export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
}
/**
+ * 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
*/
export enum TalerUriType {
@@ -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>