summaryrefslogtreecommitdiff
path: root/src/webex
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-08-03 13:00:48 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-08-03 13:01:05 +0530
commitffd2a62c3f7df94365980302fef3bc3376b48182 (patch)
tree270af6f16b4cc7f5da2afdba55c8bc9dbea5eca5 /src/webex
parentaa481e42675fb7c4dcbbeec0ba1c61e1953b9596 (diff)
downloadwallet-core-ffd2a62c3f7df94365980302fef3bc3376b48182.tar.gz
wallet-core-ffd2a62c3f7df94365980302fef3bc3376b48182.tar.bz2
wallet-core-ffd2a62c3f7df94365980302fef3bc3376b48182.zip
modularize repo, use pnpm, improve typechecking
Diffstat (limited to 'src/webex')
-rw-r--r--src/webex/background.ts30
-rw-r--r--src/webex/chromeBadge.ts288
-rw-r--r--src/webex/compat.ts85
-rw-r--r--src/webex/i18n-test.tsx69
-rw-r--r--src/webex/i18n.tsx250
-rw-r--r--src/webex/pageEntryPoint.ts72
-rw-r--r--src/webex/pages/add-auditor.tsx135
-rw-r--r--src/webex/pages/auditors.tsx161
-rw-r--r--src/webex/pages/benchmark.tsx104
-rw-r--r--src/webex/pages/pay.tsx182
-rw-r--r--src/webex/pages/payback.tsx30
-rw-r--r--src/webex/pages/popup.tsx499
-rw-r--r--src/webex/pages/refund.tsx89
-rw-r--r--src/webex/pages/reset-required.tsx93
-rw-r--r--src/webex/pages/return-coins.tsx30
-rw-r--r--src/webex/pages/tip.tsx103
-rw-r--r--src/webex/pages/welcome.tsx190
-rw-r--r--src/webex/pages/withdraw.tsx229
-rw-r--r--src/webex/permissions.ts20
-rw-r--r--src/webex/renderHtml.tsx344
-rw-r--r--src/webex/wxApi.ts310
-rw-r--r--src/webex/wxBackend.ts575
22 files changed, 0 insertions, 3888 deletions
diff --git a/src/webex/background.ts b/src/webex/background.ts
deleted file mode 100644
index dbc540df4..000000000
--- a/src/webex/background.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Entry point for the background page.
- *
- * @author Florian Dold
- */
-
-/**
- * Imports.
- */
-import { wxMain } from "./wxBackend";
-
-window.addEventListener("load", () => {
- wxMain();
-});
diff --git a/src/webex/chromeBadge.ts b/src/webex/chromeBadge.ts
deleted file mode 100644
index 7bc5d368d..000000000
--- a/src/webex/chromeBadge.ts
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 INRIA
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { isFirefox } from "./compat";
-
-/**
- * Polyfill for requestAnimationFrame, which
- * doesn't work from a background page.
- */
-function rAF(cb: (ts: number) => void): void {
- window.setTimeout(() => {
- cb(performance.now());
- }, 100 /* 100 ms delay between frames */);
-}
-
-/**
- * Badge for Chrome that renders a Taler logo with a rotating ring if some
- * background activity is happening.
- */
-export class ChromeBadge {
- private canvas: HTMLCanvasElement;
- private ctx: CanvasRenderingContext2D;
- /**
- * True if animation running. The animation
- * might still be running even if we're not busy anymore,
- * just to transition to the "normal" state in a animated way.
- */
- private animationRunning = false;
-
- /**
- * Is the wallet still busy? Note that we do not stop the
- * animation immediately when the wallet goes idle, but
- * instead slowly close the gap.
- */
- private isBusy = false;
-
- /**
- * Current rotation angle, ranges from 0 to rotationAngleMax.
- */
- private rotationAngle = 0;
-
- /**
- * While animating, how wide is the current gap in the circle?
- * Ranges from 0 to openMax.
- */
- private gapWidth = 0;
-
- /**
- * Should we show the notification dot?
- */
- private hasNotification = false;
-
- /**
- * Maximum value for our rotationAngle, corresponds to 2 Pi.
- */
- static rotationAngleMax = 1000;
-
- /**
- * How fast do we rotate? Given in rotation angle (relative to rotationAngleMax) per millisecond.
- */
- static rotationSpeed = 0.5;
-
- /**
- * How fast to we open? Given in rotation angle (relative to rotationAngleMax) per millisecond.
- */
- static openSpeed = 0.15;
-
- /**
- * How fast to we close? Given as a multiplication factor per frame update.
- */
- static closeSpeed = 0.7;
-
- /**
- * How far do we open? Given relative to rotationAngleMax.
- */
- static openMax = 100;
-
- constructor(window?: Window) {
- // Allow injecting another window for testing
- const bg = window || chrome.extension.getBackgroundPage();
- if (!bg) {
- throw Error("no window available");
- }
- this.canvas = bg.document.createElement("canvas");
- // Note: changing the width here means changing the font
- // size in draw() as well!
- this.canvas.width = 32;
- this.canvas.height = 32;
- const ctx = this.canvas.getContext("2d");
- if (!ctx) {
- throw Error("unable to get canvas context");
- }
- this.ctx = ctx;
- this.draw();
- }
-
- /**
- * Draw the badge based on the current state.
- */
- private draw(): void {
- this.ctx.setTransform(1, 0, 0, 1, 0, 0);
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
-
- this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2);
-
- this.ctx.beginPath();
- this.ctx.arc(0, 0, this.canvas.width / 2 - 2, 0, 2 * Math.PI);
- this.ctx.fillStyle = "white";
- this.ctx.fill();
-
- // move into the center, off by 2 for aligning the "T" with the bottom
- // of the circle.
- this.ctx.translate(0, 2);
-
- // pick sans-serif font; note: 14px is based on the 32px width above!
- this.ctx.font = "bold 24px sans-serif";
- // draw the "T" perfectly centered (x and y) to the current position
- this.ctx.textAlign = "center";
- this.ctx.textBaseline = "middle";
- this.ctx.fillStyle = "black";
- this.ctx.fillText("T", 0, 0);
- // now move really into the center
- this.ctx.translate(0, -2);
- // start drawing the (possibly open) circle
- this.ctx.beginPath();
- this.ctx.lineWidth = 2.5;
- if (this.animationRunning) {
- /* Draw circle around the "T" with an opening of this.gapWidth */
- const aMax = ChromeBadge.rotationAngleMax;
- const startAngle = (this.rotationAngle / aMax) * Math.PI * 2;
- const stopAngle =
- ((this.rotationAngle + aMax - this.gapWidth) / aMax) * Math.PI * 2;
- this.ctx.arc(
- 0,
- 0,
- this.canvas.width / 2 - 2,
- /* radius */ startAngle,
- stopAngle,
- false,
- );
- } else {
- /* Draw full circle */
- this.ctx.arc(
- 0,
- 0,
- this.canvas.width / 2 - 2 /* radius */,
- 0,
- Math.PI * 2,
- false,
- );
- }
- this.ctx.stroke();
- // go back to the origin
- this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2);
-
- if (this.hasNotification) {
- // We draw a circle with a soft border in the
- // lower right corner.
- const r = 8;
- const cw = this.canvas.width;
- const ch = this.canvas.height;
- this.ctx.beginPath();
- this.ctx.arc(cw - r, ch - r, r, 0, 2 * Math.PI, false);
- const gradient = this.ctx.createRadialGradient(
- cw - r,
- ch - r,
- r,
- cw - r,
- ch - r,
- 5,
- );
- gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
- gradient.addColorStop(1, "blue");
- this.ctx.fillStyle = gradient;
- this.ctx.fill();
- }
-
- // Allow running outside the extension for testing
- // tslint:disable-next-line:no-string-literal
- if (window["chrome"] && window.chrome["browserAction"]) {
- try {
- const imageData = this.ctx.getImageData(
- 0,
- 0,
- this.canvas.width,
- this.canvas.height,
- );
- chrome.browserAction.setIcon({ imageData });
- } catch (e) {
- // Might fail if browser has over-eager canvas fingerprinting countermeasures.
- // There's nothing we can do then ...
- }
- }
- }
-
- private animate(): void {
- if (this.animationRunning) {
- return;
- }
- if (isFirefox()) {
- // Firefox does not support badge animations properly
- return;
- }
- this.animationRunning = true;
- let start: number | undefined;
- const step = (timestamp: number): void => {
- if (!this.animationRunning) {
- return;
- }
- if (!start) {
- start = timestamp;
- }
- if (!this.isBusy && 0 === this.gapWidth) {
- // stop if we're close enough to origin
- this.rotationAngle = 0;
- } else {
- this.rotationAngle =
- (this.rotationAngle +
- (timestamp - start) * ChromeBadge.rotationSpeed) %
- ChromeBadge.rotationAngleMax;
- }
- if (this.isBusy) {
- if (this.gapWidth < ChromeBadge.openMax) {
- this.gapWidth += ChromeBadge.openSpeed * (timestamp - start);
- }
- if (this.gapWidth > ChromeBadge.openMax) {
- this.gapWidth = ChromeBadge.openMax;
- }
- } else {
- if (this.gapWidth > 0) {
- this.gapWidth--;
- this.gapWidth *= ChromeBadge.closeSpeed;
- }
- }
-
- if (this.isBusy || this.gapWidth > 0) {
- start = timestamp;
- rAF(step);
- } else {
- this.animationRunning = false;
- }
- this.draw();
- };
- rAF(step);
- }
-
- /**
- * Draw the badge such that it shows the
- * user that something happened (balance changed).
- */
- showNotification(): void {
- this.hasNotification = true;
- this.draw();
- }
-
- /**
- * Draw the badge without the notification mark.
- */
- clearNotification(): void {
- this.hasNotification = false;
- this.draw();
- }
-
- startBusy(): void {
- if (this.isBusy) {
- return;
- }
- this.isBusy = true;
- this.animate();
- }
-
- stopBusy(): void {
- this.isBusy = false;
- }
-}
diff --git a/src/webex/compat.ts b/src/webex/compat.ts
deleted file mode 100644
index 4635abd80..000000000
--- a/src/webex/compat.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 INRIA
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Compatibility helpers needed for browsers that don't implement
- * WebExtension APIs consistently.
- */
-
-export function isFirefox(): boolean {
- const rt = chrome.runtime as any;
- if (typeof rt.getBrowserInfo === "function") {
- return true;
- }
- return false;
-}
-
-/**
- * Check if we are running under nodejs.
- */
-export function isNode(): boolean {
- return typeof process !== "undefined" && process.release.name === "node";
-}
-
-/**
- * Compatibility API that works on multiple browsers.
- */
-export interface CrossBrowserPermissionsApi {
- contains(
- permissions: chrome.permissions.Permissions,
- callback: (result: boolean) => void,
- ): void;
-
- addPermissionsListener(
- callback: (permissions: chrome.permissions.Permissions) => void,
- ): void;
-
- request(
- permissions: chrome.permissions.Permissions,
- callback?: (granted: boolean) => void,
- ): void;
-
- remove(
- permissions: chrome.permissions.Permissions,
- callback?: (removed: boolean) => void,
- ): void;
-}
-
-export function getPermissionsApi(): CrossBrowserPermissionsApi {
- const myBrowser = (globalThis as any).browser;
- if (
- typeof myBrowser === "object" &&
- typeof myBrowser.permissions === "object"
- ) {
- return {
- addPermissionsListener: () => {
- // Not supported yet.
- },
- contains: myBrowser.permissions.contains,
- request: myBrowser.permissions.request,
- remove: myBrowser.permissions.remove,
- };
- } else {
- return {
- addPermissionsListener: chrome.permissions.onAdded.addListener.bind(
- chrome.permissions.onAdded,
- ),
- contains: chrome.permissions.contains,
- request: chrome.permissions.request,
- remove: chrome.permissions.remove,
- };
- }
-}
diff --git a/src/webex/i18n-test.tsx b/src/webex/i18n-test.tsx
deleted file mode 100644
index 4a1c40254..000000000
--- a/src/webex/i18n-test.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems SA
-
- 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 test from "ava";
-import { internalSetStrings, str, Translate } from "./i18n";
-import { strings } from "../i18n/strings";
-import React from "react";
-import { render } from "enzyme";
-import { configure } from "enzyme";
-import Adapter from "enzyme-adapter-react-16";
-
-configure({ adapter: new Adapter() });
-
-const testStrings = {
- domain: "messages",
- locale_data: {
- messages: {
- str1: ["foo1"],
- str2: [""],
- "str3 %1$s / %2$s": ["foo3 %2$s ; %1$s"],
- "": {
- domain: "messages",
- plural_forms: "nplurals=2; plural=(n != 1);",
- lang: "",
- },
- },
- },
-};
-
-test("str translation", (t) => {
- // Alias, so we nly use the function for lookups, not for string extranction.
- const strAlias = str;
- const TranslateAlias = Translate;
- internalSetStrings(testStrings);
- t.is(strAlias`str1`, "foo1");
- t.is(strAlias`str2`, "str2");
- const a = "a";
- const b = "b";
- t.is(strAlias`str3 ${a} / ${b}`, "foo3 b ; a");
- const r = render(<TranslateAlias>str1</TranslateAlias>);
- t.is(r.text(), "foo1");
-
- const r2 = render(
- <TranslateAlias>
- str3 <span>{a}</span> / <span>{b}</span>
- </TranslateAlias>,
- );
- t.is(r2.text(), "foo3 b ; a");
-
- t.pass();
-});
-
-test("existing str translation", (t) => {
- internalSetStrings(strings);
- t.pass();
-});
diff --git a/src/webex/i18n.tsx b/src/webex/i18n.tsx
deleted file mode 100644
index 6b5c2318d..000000000
--- a/src/webex/i18n.tsx
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Translation helpers for React components and template literals.
- */
-
-/**
- * Imports.
- */
-import { strings } from "../i18n/strings";
-
-// @ts-ignore: no type decl for this library
-import * as jedLib from "jed";
-
-import * as React from "react";
-
-let jed = setupJed();
-
-const enableTracing = false;
-
-/**
- * Set up jed library for internationalization,
- * based on browser language settings.
- */
-function setupJed(): any {
- let lang: string;
- try {
- lang = chrome.i18n.getUILanguage();
- // Chrome gives e.g. "en-US", but Firefox gives us "en_US"
- lang = lang.replace("_", "-");
- } catch (e) {
- lang = "en";
- console.warn("i18n default language not available");
- }
-
- if (!strings[lang]) {
- lang = "en-US";
- console.log(`language ${lang} not found, defaulting to english`);
- }
- return new jedLib.Jed(strings[lang]);
-}
-
-/**
- * Use different translations for testing. Should not be used outside
- * of test cases.
- */
-export function internalSetStrings(langStrings: any): void {
- jed = new jedLib.Jed(langStrings);
-}
-
-/**
- * Convert template strings to a msgid
- */
-function toI18nString(stringSeq: ReadonlyArray<string>): string {
- let s = "";
- for (let i = 0; i < stringSeq.length; i++) {
- s += stringSeq[i];
- if (i < stringSeq.length - 1) {
- s += `%${i + 1}$s`;
- }
- }
- return s;
-}
-
-/**
- * Internationalize a string template with arbitrary serialized values.
- */
-export function str(stringSeq: TemplateStringsArray, ...values: any[]): string {
- const s = toI18nString(stringSeq);
- const tr = jed
- .translate(s)
- .ifPlural(1, s)
- .fetch(...values);
- return tr;
-}
-
-interface TranslateSwitchProps {
- target: number;
-}
-
-function stringifyChildren(children: any): string {
- let n = 1;
- const ss = React.Children.map(children, (c) => {
- if (typeof c === "string") {
- return c;
- }
- return `%${n++}$s`;
- });
- const s = ss.join("").replace(/ +/g, " ").trim();
- enableTracing && console.log("translation lookup", JSON.stringify(s));
- return s;
-}
-
-interface TranslateProps {
- /**
- * Component that the translated element should be wrapped in.
- * Defaults to "div".
- */
- wrap?: any;
-
- /**
- * Props to give to the wrapped component.
- */
- wrapProps?: any;
-}
-
-function getTranslatedChildren(
- translation: string,
- children: React.ReactNode,
-): React.ReactNode[] {
- const tr = translation.split(/%(\d+)\$s/);
- const childArray = React.Children.toArray(children);
- // Merge consecutive string children.
- const placeholderChildren = [];
- for (let i = 0; i < childArray.length; i++) {
- const x = childArray[i];
- if (x === undefined) {
- continue;
- } else if (typeof x === "string") {
- continue;
- } else {
- placeholderChildren.push(x);
- }
- }
- const result = [];
- for (let i = 0; i < tr.length; i++) {
- if (i % 2 == 0) {
- // Text
- result.push(tr[i]);
- } else {
- const childIdx = Number.parseInt(tr[i]) - 1;
- result.push(placeholderChildren[childIdx]);
- }
- }
- return result;
-}
-
-/**
- * Translate text node children of this component.
- * If a child component might produce a text node, it must be wrapped
- * in a another non-text element.
- *
- * Example:
- * ```
- * <Translate>
- * Hello. Your score is <span><PlayerScore player={player} /></span>
- * </Translate>
- * ```
- */
-export class Translate extends React.Component<TranslateProps, {}> {
- render(): JSX.Element {
- const s = stringifyChildren(this.props.children);
- const translation: string = jed.ngettext(s, s, 1);
- const result = getTranslatedChildren(translation, this.props.children);
- if (!this.props.wrap) {
- return <div>{result}</div>;
- }
- return React.createElement(this.props.wrap, this.props.wrapProps, result);
- }
-}
-
-/**
- * Switch translation based on singular or plural based on the target prop.
- * Should only contain TranslateSingular and TransplatePlural as children.
- *
- * Example:
- * ```
- * <TranslateSwitch target={n}>
- * <TranslateSingular>I have {n} apple.</TranslateSingular>
- * <TranslatePlural>I have {n} apples.</TranslatePlural>
- * </TranslateSwitch>
- * ```
- */
-export class TranslateSwitch extends React.Component<
- TranslateSwitchProps,
- void
-> {
- render(): JSX.Element {
- let singular: React.ReactElement<TranslationPluralProps> | undefined;
- let plural: React.ReactElement<TranslationPluralProps> | undefined;
- const children = this.props.children;
- if (children) {
- React.Children.forEach(children, (child: any) => {
- if (child.type === TranslatePlural) {
- plural = child;
- }
- if (child.type === TranslateSingular) {
- singular = child;
- }
- });
- }
- if (!singular || !plural) {
- console.error("translation not found");
- return React.createElement("span", {}, ["translation not found"]);
- }
- singular.props.target = this.props.target;
- plural.props.target = this.props.target;
- // We're looking up the translation based on the
- // singular, even if we must use the plural form.
- return singular;
- }
-}
-
-interface TranslationPluralProps {
- target: number;
-}
-
-/**
- * See [[TranslateSwitch]].
- */
-export class TranslatePlural extends React.Component<
- TranslationPluralProps,
- void
-> {
- render(): JSX.Element {
- const s = stringifyChildren(this.props.children);
- const translation = jed.ngettext(s, s, 1);
- const result = getTranslatedChildren(translation, this.props.children);
- return <div>{result}</div>;
- }
-}
-
-/**
- * See [[TranslateSwitch]].
- */
-export class TranslateSingular extends React.Component<
- TranslationPluralProps,
- void
-> {
- render(): JSX.Element {
- const s = stringifyChildren(this.props.children);
- const translation = jed.ngettext(s, s, this.props.target);
- const result = getTranslatedChildren(translation, this.props.children);
- return <div>{result}</div>;
- }
-}
diff --git a/src/webex/pageEntryPoint.ts b/src/webex/pageEntryPoint.ts
deleted file mode 100644
index 9fd1d36f1..000000000
--- a/src/webex/pageEntryPoint.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 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/>
- */
-
-/**
- * Main entry point for extension pages.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-import ReactDOM from "react-dom";
-import { createPopup } from "./pages/popup";
-import { createWithdrawPage } from "./pages/withdraw";
-import { createWelcomePage } from "./pages/welcome";
-import { createPayPage } from "./pages/pay";
-import { createRefundPage } from "./pages/refund";
-
-function main(): void {
- try {
- let mainElement;
- const m = location.pathname.match(/([^/]+)$/);
- if (!m) {
- throw Error("can't parse page URL");
- }
- const page = m[1];
- switch (page) {
- case "popup.html":
- mainElement = createPopup();
- break;
- case "withdraw.html":
- mainElement = createWithdrawPage();
- break;
- case "welcome.html":
- mainElement = createWelcomePage();
- break;
- case "pay.html":
- mainElement = createPayPage();
- break;
- case "refund.html":
- mainElement = createRefundPage();
- break;
- default:
- throw Error(`page '${page}' not implemented`);
- }
- const container = document.getElementById("container");
- if (!container) {
- throw Error("container not found, can't mount page contents");
- }
- ReactDOM.render(mainElement, container);
- } catch (e) {
- console.error("got error", e);
- document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`;
- }
-}
-
-if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", main);
-} else {
- main();
-}
diff --git a/src/webex/pages/add-auditor.tsx b/src/webex/pages/add-auditor.tsx
deleted file mode 100644
index c28d15cad..000000000
--- a/src/webex/pages/add-auditor.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 Inria
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * View and edit auditors.
- *
- * @author Florian Dold
- */
-
-import { CurrencyRecord } from "../../types/dbTypes";
-import { getCurrencies, updateCurrency } from "../wxApi";
-import React, { useState } from "react";
-
-interface ConfirmAuditorProps {
- url: string;
- currency: string;
- auditorPub: string;
- expirationStamp: number;
-}
-
-function ConfirmAuditor(props: ConfirmAuditorProps): JSX.Element {
- const [addDone, setAddDone] = useState(false);
-
- const add = async (): Promise<void> => {
- const currencies = await getCurrencies();
- let currency: CurrencyRecord | undefined;
-
- for (const c of currencies) {
- if (c.name === props.currency) {
- currency = c;
- }
- }
-
- if (!currency) {
- currency = {
- name: props.currency,
- auditors: [],
- fractionalDigits: 2,
- exchanges: [],
- };
- }
-
- const newAuditor = {
- auditorPub: props.auditorPub,
- baseUrl: props.url,
- expirationStamp: props.expirationStamp,
- };
-
- let auditorFound = false;
- for (const idx in currency.auditors) {
- const a = currency.auditors[idx];
- if (a.baseUrl === props.url) {
- auditorFound = true;
- // Update auditor if already found by URL.
- currency.auditors[idx] = newAuditor;
- }
- }
-
- if (!auditorFound) {
- currency.auditors.push(newAuditor);
- }
-
- await updateCurrency(currency);
-
- setAddDone(true);
- };
-
- const back = (): void => {
- window.history.back();
- };
-
- return (
- <div id="main">
- <p>
- Do you want to let <strong>{props.auditorPub}</strong> audit the
- currency &quot;{props.currency}&quot;?
- </p>
- {addDone ? (
- <div>
- Auditor was added! You can also{" "}
- <a href={chrome.extension.getURL("/auditors.html")}>view and edit</a>{" "}
- auditors.
- </div>
- ) : (
- <div>
- <button
- onClick={() => add()}
- className="pure-button pure-button-primary"
- >
- Yes
- </button>
- <button onClick={() => back()} className="pure-button">
- No
- </button>
- </div>
- )}
- </div>
- );
-}
-
-export function makeAddAuditorPage(): JSX.Element {
- const walletPageUrl = new URL(document.location.href);
- const url = walletPageUrl.searchParams.get("url");
- if (!url) {
- throw Error("missign parameter (url)");
- }
- const currency = walletPageUrl.searchParams.get("currency");
- if (!currency) {
- throw Error("missing parameter (currency)");
- }
- const auditorPub = walletPageUrl.searchParams.get("auditorPub");
- if (!auditorPub) {
- throw Error("missing parameter (auditorPub)");
- }
- const auditorStampStr = walletPageUrl.searchParams.get("expirationStamp");
- if (!auditorStampStr) {
- throw Error("missing parameter (auditorStampStr)");
- }
- const expirationStamp = Number.parseInt(auditorStampStr);
- const args = { url, currency, auditorPub, expirationStamp };
- return <ConfirmAuditor {...args} />;
-}
diff --git a/src/webex/pages/auditors.tsx b/src/webex/pages/auditors.tsx
deleted file mode 100644
index ac93afd31..000000000
--- a/src/webex/pages/auditors.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 Inria
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * View and edit auditors.
- *
- * @author Florian Dold
- */
-
-import {
- AuditorRecord,
- CurrencyRecord,
- ExchangeForCurrencyRecord,
-} from "../../types/dbTypes";
-
-import { getCurrencies, updateCurrency } from "../wxApi";
-
-import * as React from "react";
-
-interface CurrencyListState {
- currencies?: CurrencyRecord[];
-}
-
-class CurrencyList extends React.Component<{}, CurrencyListState> {
- constructor(props: {}) {
- super(props);
- const port = chrome.runtime.connect();
- port.onMessage.addListener((msg: any) => {
- if (msg.notify) {
- console.log("got notified");
- this.update();
- }
- });
- this.update();
- this.state = {} as any;
- }
-
- async update(): Promise<void> {
- const currencies = await getCurrencies();
- console.log("currencies: ", currencies);
- this.setState({ currencies });
- }
-
- async confirmRemoveAuditor(
- c: CurrencyRecord,
- a: AuditorRecord,
- ): Promise<void> {
- if (
- window.confirm(
- `Do you really want to remove auditor ${a.baseUrl} for currency ${c.name}?`,
- )
- ) {
- c.auditors = c.auditors.filter((x) => x.auditorPub !== a.auditorPub);
- await updateCurrency(c);
- }
- }
-
- async confirmRemoveExchange(
- c: CurrencyRecord,
- e: ExchangeForCurrencyRecord,
- ): Promise<void> {
- if (
- window.confirm(
- `Do you really want to remove exchange ${e.baseUrl} for currency ${c.name}?`,
- )
- ) {
- c.exchanges = c.exchanges.filter((x) => x.baseUrl !== e.baseUrl);
- await updateCurrency(c);
- }
- }
-
- renderAuditors(c: CurrencyRecord): any {
- if (c.auditors.length === 0) {
- return <p>No trusted auditors for this currency.</p>;
- }
- return (
- <div>
- <p>Trusted Auditors:</p>
- <ul>
- {c.auditors.map((a) => (
- <li key={a.baseUrl}>
- {a.baseUrl}{" "}
- <button
- className="pure-button button-destructive"
- onClick={() => this.confirmRemoveAuditor(c, a)}
- >
- Remove
- </button>
- <ul>
- <li>valid until {new Date(a.expirationStamp).toString()}</li>
- <li>public key {a.auditorPub}</li>
- </ul>
- </li>
- ))}
- </ul>
- </div>
- );
- }
-
- renderExchanges(c: CurrencyRecord): any {
- if (c.exchanges.length === 0) {
- return <p>No trusted exchanges for this currency.</p>;
- }
- return (
- <div>
- <p>Trusted Exchanges:</p>
- <ul>
- {c.exchanges.map((e) => (
- <li key={e.baseUrl}>
- {e.baseUrl}{" "}
- <button
- className="pure-button button-destructive"
- onClick={() => this.confirmRemoveExchange(c, e)}
- >
- Remove
- </button>
- </li>
- ))}
- </ul>
- </div>
- );
- }
-
- render(): JSX.Element {
- const currencies = this.state.currencies;
- if (!currencies) {
- return <span>...</span>;
- }
- return (
- <div id="main">
- {currencies.map((c) => (
- <div key={c.name}>
- <h1>Currency {c.name}</h1>
- <p>Displayed with {c.fractionalDigits} fractional digits.</p>
- <h2>Auditors</h2>
- <div>{this.renderAuditors(c)}</div>
- <h2>Exchanges</h2>
- <div>{this.renderExchanges(c)}</div>
- </div>
- ))}
- </div>
- );
- }
-}
-
-export function makeAuditorsPage(): JSX.Element {
- return <CurrencyList />;
-}
diff --git a/src/webex/pages/benchmark.tsx b/src/webex/pages/benchmark.tsx
deleted file mode 100644
index eb7193e0c..000000000
--- a/src/webex/pages/benchmark.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Benchmarks for the wallet.
- *
- * @author Florian Dold
- */
-
-import * as i18n from "../i18n";
-
-import { BenchmarkResult } from "../../types/walletTypes";
-
-import * as wxApi from "../wxApi";
-
-import * as React from "react";
-
-interface BenchmarkRunnerState {
- repetitions: number;
- result?: BenchmarkResult;
- running: boolean;
-}
-
-function BenchmarkDisplay(props: BenchmarkRunnerState): JSX.Element {
- const result = props.result;
- if (!result) {
- if (props.running) {
- return <div>Waiting for results ...</div>;
- } else {
- return <div></div>;
- }
- }
- return (
- <>
- <h2>Results for {result.repetitions} repetitions</h2>
- <table className="pure-table">
- <thead>
- <tr>
- <th>{i18n.str`Operation`}</th>
- <th>{i18n.str`time (ms/op)`}</th>
- </tr>
- {Object.keys(result.time)
- .sort()
- .map((k) => (
- <tr key={k}>
- <td>{k}</td>
- <td>{result.time[k] / result.repetitions}</td>
- </tr>
- ))}
- </thead>
- </table>
- </>
- );
-}
-
-class BenchmarkRunner extends React.Component<any, BenchmarkRunnerState> {
- constructor(props: any) {
- super(props);
- this.state = {
- repetitions: 10,
- running: false,
- };
- }
-
- async run(): Promise<void> {
- this.setState({ result: undefined, running: true });
- const result = await wxApi.benchmarkCrypto(this.state.repetitions);
- this.setState({ result, running: false });
- }
-
- render(): JSX.Element {
- return (
- <div>
- <label>Repetitions:</label>
- <input
- type="number"
- value={this.state.repetitions}
- onChange={(evt) =>
- this.setState({ repetitions: Number.parseInt(evt.target.value) })
- }
- />{" "}
- <button onClick={() => this.run()}>Run</button>
- <BenchmarkDisplay {...this.state} />
- </div>
- );
- }
-}
-
-export function makeBenchmarkPage(): JSX.Element {
- return <BenchmarkRunner />;
-}
diff --git a/src/webex/pages/pay.tsx b/src/webex/pages/pay.tsx
deleted file mode 100644
index ce44c0040..000000000
--- a/src/webex/pages/pay.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Page shown to the user to confirm entering
- * a contract.
- */
-
-/**
- * Imports.
- */
-import * as i18n from "../i18n";
-
-import { PreparePayResult, PreparePayResultType } from "../../types/walletTypes";
-
-import { renderAmount, ProgressButton } from "../renderHtml";
-import * as wxApi from "../wxApi";
-
-import React, { useState, useEffect } from "react";
-
-import * as Amounts from "../../util/amounts";
-import { codecForContractTerms, ContractTerms } from "../../types/talerTypes";
-
-function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element {
- const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>();
- const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
- const [numTries, setNumTries] = useState(0);
- const [loading, setLoading] = useState(false);
- let amountEffective: Amounts.AmountJson | undefined = undefined;
-
- useEffect(() => {
- const doFetch = async (): Promise<void> => {
- const p = await wxApi.preparePay(talerPayUri);
- setPayStatus(p);
- };
- doFetch();
- }, [numTries, talerPayUri]);
-
- if (!payStatus) {
- return <span>Loading payment information ...</span>;
- }
-
- let insufficientBalance = false;
- if (payStatus.status == "insufficient-balance") {
- insufficientBalance = true;
- }
-
- if (payStatus.status === "payment-possible") {
- amountEffective = Amounts.parseOrThrow(payStatus.amountEffective);
- }
-
- if (payStatus.status === PreparePayResultType.AlreadyConfirmed && numTries === 0) {
- return (
- <span>
- You have already paid for this article. Click{" "}
- <a href={payStatus.nextUrl}>here</a> to view it again.
- </span>
- );
- }
-
- let contractTerms: ContractTerms;
-
- try {
- contractTerms = codecForContractTerms().decode(payStatus.contractTerms);
- } catch (e) {
- // This should never happen, as the wallet is supposed to check the contract terms
- // before storing them.
- console.error(e);
- console.log("raw contract terms were", payStatus.contractTerms);
- return <span>Invalid contract terms.</span>;
- }
-
- if (!contractTerms) {
- return (
- <span>
- Error: did not get contract terms from merchant or wallet backend.
- </span>
- );
- }
-
- let merchantName: React.ReactElement;
- if (contractTerms.merchant && contractTerms.merchant.name) {
- merchantName = <strong>{contractTerms.merchant.name}</strong>;
- } else {
- merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
- }
-
- const amount = (
- <strong>{renderAmount(Amounts.parseOrThrow(contractTerms.amount))}</strong>
- );
-
- const doPayment = async (): Promise<void> => {
- if (payStatus.status !== "payment-possible") {
- throw Error(`invalid state: ${payStatus.status}`);
- }
- const proposalId = payStatus.proposalId;
- setNumTries(numTries + 1);
- try {
- setLoading(true);
- const res = await wxApi.confirmPay(proposalId, undefined);
- document.location.href = res.nextUrl;
- } catch (e) {
- console.error(e);
- setPayErrMsg(e.message);
- }
- };
-
- return (
- <div>
- <p>
- <i18n.Translate wrap="p">
- The merchant <span>{merchantName}</span> offers you to purchase:
- </i18n.Translate>
- <div style={{ textAlign: "center" }}>
- <strong>{contractTerms.summary}</strong>
- </div>
- {amountEffective ? (
- <i18n.Translate wrap="p">
- The total price is <span>{amount} </span>
- (plus <span>{renderAmount(amountEffective)}</span> fees).
- </i18n.Translate>
- ) : (
- <i18n.Translate wrap="p">
- The total price is <span>{amount}</span>.
- </i18n.Translate>
- )}
- </p>
-
- {insufficientBalance ? (
- <div>
- <p style={{ color: "red", fontWeight: "bold" }}>
- Unable to pay: Your balance is insufficient.
- </p>
- </div>
- ) : null}
-
- {payErrMsg ? (
- <div>
- <p>Payment failed: {payErrMsg}</p>
- <button
- className="pure-button button-success"
- onClick={() => doPayment()}
- >
- {i18n.str`Retry`}
- </button>
- </div>
- ) : (
- <div>
- <ProgressButton
- loading={loading}
- disabled={insufficientBalance}
- onClick={() => doPayment()}
- >
- {i18n.str`Confirm payment`}
- </ProgressButton>
- </div>
- )}
- </div>
- );
-}
-
-export function createPayPage(): JSX.Element {
- const url = new URL(document.location.href);
- const talerPayUri = url.searchParams.get("talerPayUri");
- if (!talerPayUri) {
- throw Error("invalid parameter");
- }
- return <TalerPayDialog talerPayUri={talerPayUri} />;
-}
diff --git a/src/webex/pages/payback.tsx b/src/webex/pages/payback.tsx
deleted file mode 100644
index 5d42f5f47..000000000
--- a/src/webex/pages/payback.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 Inria
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * View and edit auditors.
- *
- * @author Florian Dold
- */
-
-/**
- * Imports.
- */
-import * as React from "react";
-
-export function makePaybackPage(): JSX.Element {
- return <div>not implemented</div>;
-}
diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx
deleted file mode 100644
index 8a99a6d90..000000000
--- a/src/webex/pages/popup.tsx
+++ /dev/null
@@ -1,499 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Popup shown to the user when they click
- * the Taler browser action button.
- *
- * @author Florian Dold
- */
-
-/**
- * Imports.
- */
-import * as i18n from "../i18n";
-
-import { AmountJson } from "../../util/amounts";
-import * as Amounts from "../../util/amounts";
-
-import { abbrev, renderAmount, PageLink } from "../renderHtml";
-import * as wxApi from "../wxApi";
-
-import React, { Fragment, useState, useEffect } from "react";
-
-import moment from "moment";
-import { Timestamp } from "../../util/time";
-import { classifyTalerUri, TalerUriType } from "../../util/taleruri";
-import { PermissionsCheckbox } from "./welcome";
-import { BalancesResponse, Balance } from "../../types/walletTypes";
-
-// FIXME: move to newer react functions
-/* eslint-disable react/no-deprecated */
-
-class Router extends React.Component<any, any> {
- static setRoute(s: string): void {
- window.location.hash = s;
- }
-
- static getRoute(): string {
- // Omit the '#' at the beginning
- return window.location.hash.substring(1);
- }
-
- static onRoute(f: any): () => void {
- Router.routeHandlers.push(f);
- return () => {
- const i = Router.routeHandlers.indexOf(f);
- this.routeHandlers = this.routeHandlers.splice(i, 1);
- };
- }
-
- private static routeHandlers: any[] = [];
-
- componentWillMount(): void {
- console.log("router mounted");
- window.onhashchange = () => {
- this.setState({});
- for (const f of Router.routeHandlers) {
- f();
- }
- };
- }
-
- render(): JSX.Element {
- const route = window.location.hash.substring(1);
- console.log("rendering route", route);
- let defaultChild: React.ReactChild | null = null;
- let foundChild: React.ReactChild | null = null;
- React.Children.forEach(this.props.children, (child) => {
- const childProps: any = (child as any).props;
- if (!childProps) {
- return;
- }
- if (childProps.default) {
- defaultChild = child as React.ReactChild;
- }
- if (childProps.route === route) {
- foundChild = child as React.ReactChild;
- }
- });
- const c: React.ReactChild | null = foundChild || defaultChild;
- if (!c) {
- throw Error("unknown route");
- }
- Router.setRoute((c as any).props.route);
- return <div>{c}</div>;
- }
-}
-
-interface TabProps {
- target: string;
- children?: React.ReactNode;
-}
-
-function Tab(props: TabProps): JSX.Element {
- let cssClass = "";
- if (props.target === Router.getRoute()) {
- cssClass = "active";
- }
- const onClick = (e: React.MouseEvent<HTMLAnchorElement>): void => {
- Router.setRoute(props.target);
- e.preventDefault();
- };
- return (
- <a onClick={onClick} href={props.target} className={cssClass}>
- {props.children}
- </a>
- );
-}
-
-class WalletNavBar extends React.Component<any, any> {
- private cancelSubscription: any;
-
- componentWillMount(): void {
- this.cancelSubscription = Router.onRoute(() => {
- this.setState({});
- });
- }
-
- componentWillUnmount(): void {
- if (this.cancelSubscription) {
- this.cancelSubscription();
- }
- }
-
- render(): JSX.Element {
- console.log("rendering nav bar");
- return (
- <div className="nav" id="header">
- <Tab target="/balance">{i18n.str`Balance`}</Tab>
- <Tab target="/history">{i18n.str`History`}</Tab>
- <Tab target="/settings">{i18n.str`Settings`}</Tab>
- <Tab target="/debug">{i18n.str`Debug`}</Tab>
- </div>
- );
- }
-}
-
-/**
- * Render an amount as a large number with a small currency symbol.
- */
-function bigAmount(amount: AmountJson): JSX.Element {
- const v = amount.value + amount.fraction / Amounts.fractionalBase;
- return (
- <span>
- <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
- <span>{amount.currency}</span>
- </span>
- );
-}
-
-function EmptyBalanceView(): JSX.Element {
- return (
- <i18n.Translate wrap="p">
- You have no balance to show. Need some{" "}
- <PageLink pageName="welcome.html">help</PageLink> getting started?
- </i18n.Translate>
- );
-}
-
-class WalletBalanceView extends React.Component<any, any> {
- private balance: BalancesResponse;
- private gotError = false;
- private canceler: (() => void) | undefined = undefined;
- private unmount = false;
- private updateBalanceRunning = false;
-
- componentWillMount(): void {
- this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
- this.updateBalance();
- }
-
- componentWillUnmount(): void {
- console.log("component WalletBalanceView will unmount");
- if (this.canceler) {
- this.canceler();
- }
- this.unmount = true;
- }
-
- async updateBalance(): Promise<void> {
- if (this.updateBalanceRunning) {
- return;
- }
- this.updateBalanceRunning = true;
- let balance: BalancesResponse;
- try {
- balance = await wxApi.getBalance();
- } catch (e) {
- if (this.unmount) {
- return;
- }
- this.gotError = true;
- console.error("could not retrieve balances", e);
- this.setState({});
- return;
- } finally {
- this.updateBalanceRunning = false;
- }
- if (this.unmount) {
- return;
- }
- this.gotError = false;
- console.log("got balance", balance);
- this.balance = balance;
- this.setState({});
- }
-
- formatPending(entry: Balance): JSX.Element {
- let incoming: JSX.Element | undefined;
- let payment: JSX.Element | undefined;
-
- const available = Amounts.parseOrThrow(entry.available);
- const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
- const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
-
- console.log(
- "available: ",
- entry.pendingIncoming ? renderAmount(entry.available) : null,
- );
- console.log(
- "incoming: ",
- entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null,
- );
-
- if (Amounts.isNonZero(pendingIncoming)) {
- incoming = (
- <i18n.Translate wrap="span">
- <span style={{ color: "darkgreen" }}>
- {"+"}
- {renderAmount(entry.pendingIncoming)}
- </span>{" "}
- incoming
- </i18n.Translate>
- );
- }
-
- const l = [incoming, payment].filter((x) => x !== undefined);
- if (l.length === 0) {
- return <span />;
- }
-
- if (l.length === 1) {
- return <span>({l})</span>;
- }
- return (
- <span>
- ({l[0]}, {l[1]})
- </span>
- );
- }
-
- render(): JSX.Element {
- const wallet = this.balance;
- if (this.gotError) {
- return (
- <div className="balance">
- <p>{i18n.str`Error: could not retrieve balance information.`}</p>
- <p>
- Click <PageLink pageName="welcome.html">here</PageLink> for help and
- diagnostics.
- </p>
- </div>
- );
- }
- if (!wallet) {
- return <span></span>;
- }
- console.log(wallet);
- const listing = wallet.balances.map((entry) => {
- const av = Amounts.parseOrThrow(entry.available);
- return (
- <p key={av.currency}>
- {bigAmount(av)} {this.formatPending(entry)}
- </p>
- );
- });
- return listing.length > 0 ? (
- <div className="balance">{listing}</div>
- ) : (
- <EmptyBalanceView />
- );
- }
-}
-
-function Icon({ l }: { l: string }): JSX.Element {
- return <div className={"icon"}>{l}</div>;
-}
-
-function formatAndCapitalize(text: string): string {
- text = text.replace("-", " ");
- text = text.replace(/^./, text[0].toUpperCase());
- return text;
-}
-
-const HistoryComponent = (props: any): JSX.Element => {
- return <span>TBD</span>;
-};
-
-class WalletSettings extends React.Component<any, any> {
- render(): JSX.Element {
- return (
- <div>
- <h2>Permissions</h2>
- <PermissionsCheckbox />
- </div>
- );
- }
-}
-
-function reload(): void {
- try {
- chrome.runtime.reload();
- window.close();
- } catch (e) {
- // Functionality missing in firefox, ignore!
- }
-}
-
-function confirmReset(): void {
- if (
- confirm(
- "Do you want to IRREVOCABLY DESTROY everything inside your" +
- " wallet and LOSE ALL YOUR COINS?",
- )
- ) {
- wxApi.resetDb();
- window.close();
- }
-}
-
-function WalletDebug(props: any): JSX.Element {
- return (
- <div>
- <p>Debug tools:</p>
- <button onClick={openExtensionPage("/popup.html")}>wallet tab</button>
- <button onClick={openExtensionPage("/benchmark.html")}>benchmark</button>
- <button onClick={openExtensionPage("/show-db.html")}>show db</button>
- <button onClick={openExtensionPage("/tree.html")}>show tree</button>
- <br />
- <button onClick={confirmReset}>reset</button>
- <button onClick={reload}>reload chrome extension</button>
- </div>
- );
-}
-
-function openExtensionPage(page: string) {
- return () => {
- chrome.tabs.create({
- url: chrome.extension.getURL(page),
- });
- };
-}
-
-function openTab(page: string) {
- return (evt: React.SyntheticEvent<any>) => {
- evt.preventDefault();
- chrome.tabs.create({
- url: page,
- });
- };
-}
-
-function makeExtensionUrlWithParams(
- url: string,
- params?: { [name: string]: string | undefined },
-): string {
- const innerUrl = new URL(chrome.extension.getURL("/" + url));
- if (params) {
- for (const key in params) {
- const p = params[key];
- if (p) {
- innerUrl.searchParams.set(key, p);
- }
- }
- }
- return innerUrl.href;
-}
-
-function actionForTalerUri(talerUri: string): string | undefined {
- const uriType = classifyTalerUri(talerUri);
- switch (uriType) {
- case TalerUriType.TalerWithdraw:
- return makeExtensionUrlWithParams("withdraw.html", {
- talerWithdrawUri: talerUri,
- });
- case TalerUriType.TalerPay:
- return makeExtensionUrlWithParams("pay.html", {
- talerPayUri: talerUri,
- });
- case TalerUriType.TalerTip:
- return makeExtensionUrlWithParams("tip.html", {
- talerTipUri: talerUri,
- });
- case TalerUriType.TalerRefund:
- return makeExtensionUrlWithParams("refund.html", {
- talerRefundUri: talerUri,
- });
- case TalerUriType.TalerNotifyReserve:
- // FIXME: implement
- break;
- default:
- console.warn(
- "Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
- );
- break;
- }
- return undefined;
-}
-
-async function findTalerUriInActiveTab(): Promise<string | undefined> {
- return new Promise((resolve, reject) => {
- chrome.tabs.executeScript(
- {
- code: `
- (() => {
- let x = document.querySelector("a[href^='taler://'");
- return x ? x.href.toString() : null;
- })();
- `,
- allFrames: false,
- },
- (result) => {
- if (chrome.runtime.lastError) {
- console.error(chrome.runtime.lastError);
- resolve(undefined);
- return;
- }
- console.log("got result", result);
- resolve(result[0]);
- },
- );
- });
-}
-
-function WalletPopup(): JSX.Element {
- const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
- undefined,
- );
- const [dismissed, setDismissed] = useState(false);
- useEffect(() => {
- async function check(): Promise<void> {
- const talerUri = await findTalerUriInActiveTab();
- if (talerUri) {
- const actionUrl = actionForTalerUri(talerUri);
- setTalerActionUrl(actionUrl);
- }
- }
- check();
- });
- if (talerActionUrl && !dismissed) {
- return (
- <div style={{ padding: "1em" }}>
- <h1>Taler Action</h1>
- <p>This page has a Taler action. </p>
- <p>
- <button
- onClick={() => {
- window.open(talerActionUrl, "_blank");
- }}
- >
- Open
- </button>
- </p>
- <p>
- <button onClick={() => setDismissed(true)}>Dismiss</button>
- </p>
- </div>
- );
- }
- return (
- <div>
- <WalletNavBar />
- <div style={{ margin: "1em" }}>
- <Router>
- <WalletBalanceView route="/balance" default />
- <WalletSettings route="/settings" />
- <WalletDebug route="/debug" />
- </Router>
- </div>
- </div>
- );
-}
-
-export function createPopup(): JSX.Element {
- return <WalletPopup />;
-}
diff --git a/src/webex/pages/refund.tsx b/src/webex/pages/refund.tsx
deleted file mode 100644
index c5d6a00df..000000000
--- a/src/webex/pages/refund.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015-2016 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Page that shows refund status for purchases.
- *
- * @author Florian Dold
- */
-
-import React, { useEffect, useState } from "react";
-
-import * as wxApi from "../wxApi";
-import { PurchaseDetails } from "../../types/walletTypes";
-import { AmountView } from "../renderHtml";
-
-function RefundStatusView(props: { talerRefundUri: string }): JSX.Element {
- const [applied, setApplied] = useState(false);
- const [purchaseDetails, setPurchaseDetails] = useState<
- PurchaseDetails | undefined
- >(undefined);
- const [errMsg, setErrMsg] = useState<string | undefined>(undefined);
-
- useEffect(() => {
- const doFetch = async (): Promise<void> => {
- try {
- const result = await wxApi.applyRefund(props.talerRefundUri);
- setApplied(true);
- const r = await wxApi.getPurchaseDetails(result.proposalId);
- setPurchaseDetails(r);
- } catch (e) {
- console.error(e);
- setErrMsg(e.message);
- console.log("err message", e.message);
- }
- };
- doFetch();
- }, [props.talerRefundUri]);
-
- console.log("rendering");
-
- if (errMsg) {
- return <span>Error: {errMsg}</span>;
- }
-
- if (!applied || !purchaseDetails) {
- return <span>Updating refund status</span>;
- }
-
- return (
- <>
- <h2>Refund Status</h2>
- <p>
- The product <em>{purchaseDetails.contractTerms.summary}</em> has
- received a total refund of{" "}
- <AmountView amount={purchaseDetails.totalRefundAmount} />.
- </p>
- <p>Note that additional fees from the exchange may apply.</p>
- </>
- );
-}
-
-export function createRefundPage(): JSX.Element {
- const url = new URL(document.location.href);
-
- const container = document.getElementById("container");
- if (!container) {
- throw Error("fatal: can't mount component, container missing");
- }
-
- const talerRefundUri = url.searchParams.get("talerRefundUri");
- if (!talerRefundUri) {
- throw Error("taler refund URI requred");
- }
-
- return <RefundStatusView talerRefundUri={talerRefundUri} />;
-}
diff --git a/src/webex/pages/reset-required.tsx b/src/webex/pages/reset-required.tsx
deleted file mode 100644
index 9e40e7981..000000000
--- a/src/webex/pages/reset-required.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Page to inform the user when a database reset is required.
- *
- * @author Florian Dold
- */
-
-import * as React from "react";
-
-import * as wxApi from "../wxApi";
-
-class State {
- /**
- * Did the user check the confirmation check box?
- */
- checked: boolean;
-
- /**
- * Do we actually need to reset the db?
- */
- resetRequired: boolean;
-}
-
-class ResetNotification extends React.Component<any, State> {
- constructor(props: any) {
- super(props);
- this.state = { checked: false, resetRequired: true };
- setInterval(() => this.update(), 500);
- }
- async update(): Promise<void> {
- const res = await wxApi.checkUpgrade();
- this.setState({ resetRequired: res.dbResetRequired });
- }
- render(): JSX.Element {
- if (this.state.resetRequired) {
- return (
- <div>
- <h1>Manual Reset Reqired</h1>
- <p>
- The wallet&apos;s database in your browser is incompatible with the{" "}
- currently installed wallet. Please reset manually.
- </p>
- <p>
- Once the database format has stabilized, we will provide automatic
- upgrades.
- </p>
- <input
- id="check"
- type="checkbox"
- checked={this.state.checked}
- onChange={(e) => this.setState({ checked: e.target.checked })}
- />{" "}
- <label htmlFor="check">
- I understand that I will lose all my data
- </label>
- <br />
- <button
- className="pure-button"
- disabled={!this.state.checked}
- onClick={() => wxApi.resetDb()}
- >
- Reset
- </button>
- </div>
- );
- }
- return (
- <div>
- <h1>Everything is fine!</h1>A reset is not required anymore, you can
- close this page.
- </div>
- );
- }
-}
-
-export function createResetRequiredPage(): JSX.Element {
- return <ResetNotification />;
-}
diff --git a/src/webex/pages/return-coins.tsx b/src/webex/pages/return-coins.tsx
deleted file mode 100644
index e8cf8c9dd..000000000
--- a/src/webex/pages/return-coins.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 Inria
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Return coins to own bank account.
- *
- * @author Florian Dold
- */
-
-/**
- * Imports.
- */
-import * as React from "react";
-
-export function createReturnCoinsPage(): JSX.Element {
- return <span>Not implemented yet.</span>;
-}
diff --git a/src/webex/pages/tip.tsx b/src/webex/pages/tip.tsx
deleted file mode 100644
index 4a1d3743a..000000000
--- a/src/webex/pages/tip.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Page shown to the user to confirm creation
- * of a reserve, usually requested by the bank.
- *
- * @author Florian Dold
- */
-
-import * as React from "react";
-
-import { acceptTip, getTipStatus } from "../wxApi";
-
-import { renderAmount, ProgressButton } from "../renderHtml";
-
-import { useState, useEffect } from "react";
-import { TipStatus } from "../../types/walletTypes";
-
-function TipDisplay(props: { talerTipUri: string }): JSX.Element {
- const [tipStatus, setTipStatus] = useState<TipStatus | undefined>(undefined);
- const [discarded, setDiscarded] = useState(false);
- const [loading, setLoading] = useState(false);
- const [finished, setFinished] = useState(false);
-
- useEffect(() => {
- const doFetch = async (): Promise<void> => {
- const ts = await getTipStatus(props.talerTipUri);
- setTipStatus(ts);
- };
- doFetch();
- }, [props.talerTipUri]);
-
- if (discarded) {
- return <span>You&apos;ve discarded the tip.</span>;
- }
-
- if (finished) {
- return <span>Tip has been accepted!</span>;
- }
-
- if (!tipStatus) {
- return <span>Loading ...</span>;
- }
-
- const discard = (): void => {
- setDiscarded(true);
- };
-
- const accept = async (): Promise<void> => {
- setLoading(true);
- await acceptTip(tipStatus.tipId);
- setFinished(true);
- };
-
- return (
- <div>
- <h2>Tip Received!</h2>
- <p>
- You received a tip of <strong>{renderAmount(tipStatus.amount)}</strong>{" "}
- from <span> </span>
- <strong>{tipStatus.merchantOrigin}</strong>.
- </p>
- <p>
- The tip is handled by the exchange{" "}
- <strong>{tipStatus.exchangeUrl}</strong>. This exchange will charge fees
- of <strong>{renderAmount(tipStatus.totalFees)}</strong> for this
- operation.
- </p>
- <form className="pure-form">
- <ProgressButton loading={loading} onClick={() => accept()}>
- Accept Tip
- </ProgressButton>{" "}
- <button className="pure-button" type="button" onClick={() => discard()}>
- Discard tip
- </button>
- </form>
- </div>
- );
-}
-
-export function createTipPage(): JSX.Element {
- const url = new URL(document.location.href);
- const talerTipUri = url.searchParams.get("talerTipUri");
- if (typeof talerTipUri !== "string") {
- throw Error("talerTipUri must be a string");
- }
-
- return <TipDisplay talerTipUri={talerTipUri} />;
-}
diff --git a/src/webex/pages/welcome.tsx b/src/webex/pages/welcome.tsx
deleted file mode 100644
index a7c24d659..000000000
--- a/src/webex/pages/welcome.tsx
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 Taler Systems SA
-
- 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/>
- */
-
-/**
- * Welcome page, shown on first installs.
- *
- * @author Florian Dold
- */
-
-import React, { useState, useEffect } from "react";
-import { getDiagnostics } from "../wxApi";
-import { PageLink } from "../renderHtml";
-import { WalletDiagnostics } from "../../types/walletTypes";
-import * as wxApi from "../wxApi";
-import { getPermissionsApi } from "../compat";
-import { extendedPermissions } from "../permissions";
-
-function Diagnostics(): JSX.Element | null {
- const [timedOut, setTimedOut] = useState(false);
- const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | undefined>(
- undefined,
- );
-
- useEffect(() => {
- let gotDiagnostics = false;
- setTimeout(() => {
- if (!gotDiagnostics) {
- console.error("timed out");
- setTimedOut(true);
- }
- }, 1000);
- const doFetch = async (): Promise<void> => {
- const d = await getDiagnostics();
- console.log("got diagnostics", d);
- gotDiagnostics = true;
- setDiagnostics(d);
- };
- console.log("fetching diagnostics");
- doFetch();
- }, []);
-
- if (timedOut) {
- return <p>Diagnostics timed out. Could not talk to the wallet backend.</p>;
- }
-
- if (diagnostics) {
- if (diagnostics.errors.length === 0) {
- return null;
- } else {
- return (
- <div
- style={{
- borderLeft: "0.5em solid red",
- paddingLeft: "1em",
- paddingTop: "0.2em",
- paddingBottom: "0.2em",
- }}
- >
- <p>Problems detected:</p>
- <ol>
- {diagnostics.errors.map((errMsg) => (
- <li key={errMsg}>{errMsg}</li>
- ))}
- </ol>
- {diagnostics.firefoxIdbProblem ? (
- <p>
- Please check in your <code>about:config</code> settings that you
- have IndexedDB enabled (check the preference name{" "}
- <code>dom.indexedDB.enabled</code>).
- </p>
- ) : null}
- {diagnostics.dbOutdated ? (
- <p>
- Your wallet database is outdated. Currently automatic migration is
- not supported. Please go{" "}
- <PageLink pageName="reset-required.html">here</PageLink> to reset
- the wallet database.
- </p>
- ) : null}
- </div>
- );
- }
- }
-
- return <p>Running diagnostics ...</p>;
-}
-
-export function PermissionsCheckbox(): JSX.Element {
- const [extendedPermissionsEnabled, setExtendedPermissionsEnabled] = useState(
- false,
- );
- async function handleExtendedPerm(requestedVal: boolean): Promise<void> {
- let nextVal: boolean | undefined;
- if (requestedVal) {
- const granted = await new Promise<boolean>((resolve, reject) => {
- // We set permissions here, since apparently FF wants this to be done
- // as the result of an input event ...
- getPermissionsApi().request(extendedPermissions, (granted: boolean) => {
- if (chrome.runtime.lastError) {
- console.error("error requesting permissions");
- console.error(chrome.runtime.lastError);
- reject(chrome.runtime.lastError);
- return;
- }
- console.log("permissions granted:", granted);
- resolve(granted);
- });
- });
- const res = await wxApi.setExtendedPermissions(granted);
- console.log(res);
- nextVal = res.newValue;
- } else {
- const res = await wxApi.setExtendedPermissions(false);
- console.log(res);
- nextVal = res.newValue;
- }
- console.log("new permissions applied:", nextVal);
- setExtendedPermissionsEnabled(nextVal ?? false);
- }
- useEffect(() => {
- async function getExtendedPermValue(): Promise<void> {
- const res = await wxApi.getExtendedPermissions();
- setExtendedPermissionsEnabled(res.newValue);
- }
- getExtendedPermValue();
- });
- return (
- <div>
- <input
- checked={extendedPermissionsEnabled}
- onChange={(x) => handleExtendedPerm(x.target.checked)}
- type="checkbox"
- id="checkbox-perm"
- style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }}
- />
- <label
- htmlFor="checkbox-perm"
- style={{ marginLeft: "0.5em", fontWeight: "bold" }}
- >
- Automatically open wallet based on page content
- </label>
- <span
- style={{
- color: "#383838",
- fontSize: "smaller",
- display: "block",
- marginLeft: "2em",
- }}
- >
- (Enabling this option below will make using the wallet faster, but
- requires more permissions from your browser.)
- </span>
- </div>
- );
-}
-
-function Welcome(): JSX.Element {
- return (
- <>
- <p>Thank you for installing the wallet.</p>
- <Diagnostics />
- <h2>Permissions</h2>
- <PermissionsCheckbox />
- <h2>Next Steps</h2>
- <a href="https://demo.taler.net/" style={{ display: "block" }}>
- Try the demo »
- </a>
- <a href="https://demo.taler.net/" style={{ display: "block" }}>
- Learn how to top up your wallet balance »
- </a>
- </>
- );
-}
-
-export function createWelcomePage(): JSX.Element {
- return <Welcome />;
-}
diff --git a/src/webex/pages/withdraw.tsx b/src/webex/pages/withdraw.tsx
deleted file mode 100644
index 4a92704b3..000000000
--- a/src/webex/pages/withdraw.tsx
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015-2016 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Page shown to the user to confirm creation
- * of a reserve, usually requested by the bank.
- *
- * @author Florian Dold
- */
-
-import * as i18n from "../i18n";
-
-import { WithdrawDetailView, renderAmount } from "../renderHtml";
-
-import React, { useState, useEffect } from "react";
-import {
- acceptWithdrawal,
- onUpdateNotification,
-} from "../wxApi";
-
-function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
- const [details, setDetails] = useState<
- any | undefined
- >();
- const [selectedExchange, setSelectedExchange] = useState<
- string | undefined
- >();
- const talerWithdrawUri = props.talerWithdrawUri;
- const [cancelled, setCancelled] = useState(false);
- const [selecting, setSelecting] = useState(false);
- const [customUrl, setCustomUrl] = useState<string>("");
- const [errMsg, setErrMsg] = useState<string | undefined>("");
- const [updateCounter, setUpdateCounter] = useState(1);
-
- useEffect(() => {
- return onUpdateNotification(() => {
- setUpdateCounter(updateCounter + 1);
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- useEffect(() => {
- const fetchData = async (): Promise<void> => {
- // FIXME: re-implement with new API
- // console.log("getting from", talerWithdrawUri);
- // let d: WithdrawalDetailsResponse | undefined = undefined;
- // try {
- // d = await getWithdrawDetails(talerWithdrawUri, selectedExchange);
- // } catch (e) {
- // console.error(
- // `error getting withdraw details for uri ${talerWithdrawUri}, exchange ${selectedExchange}`,
- // e,
- // );
- // setErrMsg(e.message);
- // return;
- // }
- // console.log("got withdrawDetails", d);
- // if (!selectedExchange && d.bankWithdrawDetails.suggestedExchange) {
- // console.log("setting selected exchange");
- // setSelectedExchange(d.bankWithdrawDetails.suggestedExchange);
- // }
- // setDetails(d);
- };
- fetchData();
- }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]);
-
- if (errMsg) {
- return (
- <div>
- <i18n.Translate wrap="p">
- Could not get details for withdraw operation:
- </i18n.Translate>
- <p style={{ color: "red" }}>{errMsg}</p>
- <p>
- <span
- role="button"
- tabIndex={0}
- style={{ textDecoration: "underline", cursor: "pointer" }}
- onClick={() => {
- setSelecting(true);
- setErrMsg(undefined);
- setSelectedExchange(undefined);
- setDetails(undefined);
- }}
- >
- {i18n.str`Chose different exchange provider`}
- </span>
- </p>
- </div>
- );
- }
-
- if (!details) {
- return <span>Loading...</span>;
- }
-
- if (cancelled) {
- return <span>Withdraw operation has been cancelled.</span>;
- }
-
- if (selecting) {
- const bankSuggestion =
- details && details.bankWithdrawDetails.suggestedExchange;
- return (
- <div>
- {i18n.str`Please select an exchange. You can review the details before after your selection.`}
- {bankSuggestion && (
- <div>
- <h2>Bank Suggestion</h2>
- <button
- className="pure-button button-success"
- onClick={() => {
- setDetails(undefined);
- setSelectedExchange(bankSuggestion);
- setSelecting(false);
- }}
- >
- <i18n.Translate wrap="span">
- Select <strong>{bankSuggestion}</strong>
- </i18n.Translate>
- </button>
- </div>
- )}
- <h2>Custom Selection</h2>
- <p>
- <input
- type="text"
- onChange={(e) => setCustomUrl(e.target.value)}
- value={customUrl}
- />
- </p>
- <button
- className="pure-button button-success"
- onClick={() => {
- setDetails(undefined);
- setSelectedExchange(customUrl);
- setSelecting(false);
- }}
- >
- <i18n.Translate wrap="span">Select custom exchange</i18n.Translate>
- </button>
- </div>
- );
- }
-
- const accept = async (): Promise<void> => {
- if (!selectedExchange) {
- throw Error("can't accept, no exchange selected");
- }
- console.log("accepting exchange", selectedExchange);
- const res = await acceptWithdrawal(talerWithdrawUri, selectedExchange);
- console.log("accept withdrawal response", res);
- if (res.confirmTransferUrl) {
- document.location.href = res.confirmTransferUrl;
- }
- };
-
- return (
- <div>
- <h1>Digital Cash Withdrawal</h1>
- <i18n.Translate wrap="p">
- You are about to withdraw{" "}
- <strong>{renderAmount(details.bankWithdrawDetails.amount)}</strong> from
- your bank account into your wallet.
- </i18n.Translate>
- {selectedExchange ? (
- <p>
- The exchange <strong>{selectedExchange}</strong> will be used as the
- Taler payment service provider.
- </p>
- ) : null}
-
- <div>
- <button
- className="pure-button button-success"
- disabled={!selectedExchange}
- onClick={() => accept()}
- >
- {i18n.str`Accept fees and withdraw`}
- </button>
- <p>
- <span
- role="button"
- tabIndex={0}
- style={{ textDecoration: "underline", cursor: "pointer" }}
- onClick={() => setSelecting(true)}
- >
- {i18n.str`Chose different exchange provider`}
- </span>
- <br />
- <span
- role="button"
- tabIndex={0}
- style={{ textDecoration: "underline", cursor: "pointer" }}
- onClick={() => setCancelled(true)}
- >
- {i18n.str`Cancel withdraw operation`}
- </span>
- </p>
-
- {details.exchangeWithdrawDetails ? (
- <WithdrawDetailView rci={details.exchangeWithdrawDetails} />
- ) : null}
- </div>
- </div>
- );
-}
-
-export function createWithdrawPage(): JSX.Element {
- const url = new URL(document.location.href);
- const talerWithdrawUri = url.searchParams.get("talerWithdrawUri");
- if (!talerWithdrawUri) {
- throw Error("withdraw URI required");
- }
- return <WithdrawalDialog talerWithdrawUri={talerWithdrawUri} />;
-}
diff --git a/src/webex/permissions.ts b/src/webex/permissions.ts
deleted file mode 100644
index bcd357fd6..000000000
--- a/src/webex/permissions.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 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 const extendedPermissions = {
- permissions: ["webRequest", "webRequestBlocking"],
- origins: ["http://*/*", "https://*/*"],
-};
diff --git a/src/webex/renderHtml.tsx b/src/webex/renderHtml.tsx
deleted file mode 100644
index 39ff470a2..000000000
--- a/src/webex/renderHtml.tsx
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 INRIA
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Helpers functions to render Taler-related data structures to HTML.
- *
- * @author Florian Dold
- */
-
-/**
- * Imports.
- */
-import { AmountJson } from "../util/amounts";
-import * as Amounts from "../util/amounts";
-import { ExchangeWithdrawDetails } from "../types/walletTypes";
-import * as i18n from "./i18n";
-import React from "react";
-import { stringifyTimestamp } from "../util/time";
-
-/**
- * Render amount as HTML, which non-breaking space between
- * decimal value and currency.
- */
-export function renderAmount(amount: AmountJson | string): JSX.Element {
- let a;
- if (typeof amount === "string") {
- a = Amounts.parse(amount);
- } else {
- a = amount;
- }
- if (!a) {
- return <span>(invalid amount)</span>;
- }
- const x = a.value + a.fraction / Amounts.fractionalBase;
- return (
- <span>
- {x}&nbsp;{a.currency}
- </span>
- );
-}
-
-export const AmountView = ({
- amount,
-}: {
- amount: AmountJson | string;
-}): JSX.Element => renderAmount(amount);
-
-/**
- * Abbreviate a string to a given length, and show the full
- * string on hover as a tooltip.
- */
-export function abbrev(s: string, n = 5): JSX.Element {
- let sAbbrev = s;
- if (s.length > n) {
- sAbbrev = s.slice(0, n) + "..";
- }
- return (
- <span className="abbrev" title={s}>
- {sAbbrev}
- </span>
- );
-}
-
-interface CollapsibleState {
- collapsed: boolean;
-}
-
-interface CollapsibleProps {
- initiallyCollapsed: boolean;
- title: string;
-}
-
-/**
- * Component that shows/hides its children when clicking
- * a heading.
- */
-export class Collapsible extends React.Component<
- CollapsibleProps,
- CollapsibleState
-> {
- constructor(props: CollapsibleProps) {
- super(props);
- this.state = { collapsed: props.initiallyCollapsed };
- }
- render(): JSX.Element {
- const doOpen = (e: any): void => {
- this.setState({ collapsed: false });
- e.preventDefault();
- };
- const doClose = (e: any): void => {
- this.setState({ collapsed: true });
- e.preventDefault();
- };
- if (this.state.collapsed) {
- return (
- <h2>
- <a className="opener opener-collapsed" href="#" onClick={doOpen}>
- {" "}
- {this.props.title}
- </a>
- </h2>
- );
- }
- return (
- <div>
- <h2>
- <a className="opener opener-open" href="#" onClick={doClose}>
- {" "}
- {this.props.title}
- </a>
- </h2>
- {this.props.children}
- </div>
- );
- }
-}
-
-function WireFee(props: {
- s: string;
- rci: ExchangeWithdrawDetails;
-}): JSX.Element {
- return (
- <>
- <thead>
- <tr>
- <th colSpan={3}>Wire Method {props.s}</th>
- </tr>
- <tr>
- <th>Applies Until</th>
- <th>Wire Fee</th>
- <th>Closing Fee</th>
- </tr>
- </thead>
- <tbody>
- {props.rci.wireFees.feesForType[props.s].map((f) => (
- <tr key={f.sig}>
- <td>{stringifyTimestamp(f.endStamp)}</td>
- <td>{renderAmount(f.wireFee)}</td>
- <td>{renderAmount(f.closingFee)}</td>
- </tr>
- ))}
- </tbody>
- </>
- );
-}
-
-function AuditorDetailsView(props: {
- rci: ExchangeWithdrawDetails | null;
-}): JSX.Element {
- const rci = props.rci;
- console.log("rci", rci);
- if (!rci) {
- return (
- <p>
- Details will be displayed when a valid exchange provider URL is entered.
- </p>
- );
- }
- if ((rci.exchangeInfo.details?.auditors ?? []).length === 0) {
- return <p>The exchange is not audited by any auditors.</p>;
- }
- return (
- <div>
- {(rci.exchangeInfo.details?.auditors ?? []).map((a) => (
- <div key={a.auditor_pub}>
- <h3>Auditor {a.auditor_url}</h3>
- <p>
- Public key: <ExpanderText text={a.auditor_pub} />
- </p>
- <p>
- Trusted:{" "}
- {rci.trustedAuditorPubs.indexOf(a.auditor_pub) >= 0 ? "yes" : "no"}
- </p>
- <p>
- Audits {a.denomination_keys.length} of {rci.numOfferedDenoms}{" "}
- denominations
- </p>
- </div>
- ))}
- </div>
- );
-}
-
-function FeeDetailsView(props: {
- rci: ExchangeWithdrawDetails | null;
-}): JSX.Element {
- const rci = props.rci;
- if (!rci) {
- return (
- <p>
- Details will be displayed when a valid exchange provider URL is entered.
- </p>
- );
- }
-
- const denoms = rci.selectedDenoms;
- const withdrawFee = renderAmount(rci.withdrawFee);
- const overhead = renderAmount(rci.overhead);
-
- return (
- <div>
- <h3>Overview</h3>
- <p>
- Public key:{" "}
- <ExpanderText
- text={rci.exchangeInfo.details?.masterPublicKey ?? "??"}
- />
- </p>
- <p>
- {i18n.str`Withdrawal fees:`} {withdrawFee}
- </p>
- <p>
- {i18n.str`Rounding loss:`} {overhead}
- </p>
- <p>{i18n.str`Earliest expiration (for deposit): ${stringifyTimestamp(
- rci.earliestDepositExpiration,
- )}`}</p>
- <h3>Coin Fees</h3>
- <div style={{ overflow: "auto" }}>
- <table className="pure-table">
- <thead>
- <tr>
- <th>{i18n.str`# Coins`}</th>
- <th>{i18n.str`Value`}</th>
- <th>{i18n.str`Withdraw Fee`}</th>
- <th>{i18n.str`Refresh Fee`}</th>
- <th>{i18n.str`Deposit Fee`}</th>
- </tr>
- </thead>
- <tbody>
- {denoms.selectedDenoms.map((ds) => {
- return (
- <tr key={ds.denom.denomPub}>
- <td>{ds.count + "x"}</td>
- <td>{renderAmount(ds.denom.value)}</td>
- <td>{renderAmount(ds.denom.feeWithdraw)}</td>
- <td>{renderAmount(ds.denom.feeRefresh)}</td>
- <td>{renderAmount(ds.denom.feeDeposit)}</td>
- </tr>
- );
- })}
- </tbody>
- </table>
- </div>
- <h3>Wire Fees</h3>
- <div style={{ overflow: "auto" }}>
- <table className="pure-table">
- {Object.keys(rci.wireFees.feesForType).map((s) => (
- <WireFee key={s} s={s} rci={rci} />
- ))}
- </table>
- </div>
- </div>
- );
-}
-
-/**
- * Shows details about a withdraw request.
- */
-export function WithdrawDetailView(props: {
- rci: ExchangeWithdrawDetails | null;
-}): JSX.Element {
- const rci = props.rci;
- return (
- <div>
- <Collapsible initiallyCollapsed={true} title="Fee and Spending Details">
- <FeeDetailsView rci={rci} />
- </Collapsible>
- <Collapsible initiallyCollapsed={true} title="Auditor Details">
- <AuditorDetailsView rci={rci} />
- </Collapsible>
- </div>
- );
-}
-
-interface ExpanderTextProps {
- text: string;
-}
-
-/**
- * Show a heading with a toggle to show/hide the expandable content.
- */
-export function ExpanderText({ text }: ExpanderTextProps): JSX.Element {
- return <span>{text}</span>;
-}
-
-export interface LoadingButtonProps {
- loading: boolean;
-}
-
-export function ProgressButton(
- props: React.PropsWithChildren<LoadingButtonProps> &
- React.DetailedHTMLProps<
- React.ButtonHTMLAttributes<HTMLButtonElement>,
- HTMLButtonElement
- >,
-): JSX.Element {
- return (
- <button
- className="pure-button pure-button-primary"
- type="button"
- {...props}
- >
- {props.loading ? (
- <span>
- <object
- className="svg-icon svg-baseline"
- data="/img/spinner-bars.svg"
- />
- </span>
- ) : null}{" "}
- {props.children}
- </button>
- );
-}
-
-export function PageLink(
- props: React.PropsWithChildren<{ pageName: string }>,
-): JSX.Element {
- const url = chrome.extension.getURL(`/${props.pageName}`);
- return (
- <a
- className="actionLink"
- href={url}
- target="_blank"
- rel="noopener noreferrer"
- >
- {props.children}
- </a>
- );
-}
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
deleted file mode 100644
index 4e11463d6..000000000
--- a/src/webex/wxApi.ts
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Interface to the wallet through WebExtension messaging.
- */
-
-/**
- * Imports.
- */
-import { AmountJson } from "../util/amounts";
-import {
- CoinRecord,
- CurrencyRecord,
- DenominationRecord,
- ExchangeRecord,
- ReserveRecord,
-} from "../types/dbTypes";
-import {
- BenchmarkResult,
- ConfirmPayResult,
- SenderWireInfos,
- TipStatus,
- PurchaseDetails,
- WalletDiagnostics,
- PreparePayResult,
- AcceptWithdrawalResponse,
- ExtendedPermissionsResponse,
- BalancesResponse,
-} from "../types/walletTypes";
-
-/**
- * Response with information about available version upgrades.
- */
-export interface UpgradeResponse {
- /**
- * Is a reset required because of a new DB version
- * that can't be atomatically upgraded?
- */
- dbResetRequired: boolean;
-
- /**
- * Current database version.
- */
- currentDbVersion: string;
-
- /**
- * Old db version (if applicable).
- */
- oldDbVersion: string;
-}
-
-/**
- * Error thrown when the function from the backend (via RPC) threw an error.
- */
-export class WalletApiError extends Error {
- constructor(message: string, public detail: any) {
- super(message);
- // restore prototype chain
- Object.setPrototypeOf(this, new.target.prototype);
- }
-}
-
-async function callBackend(
- type: string,
- detail: any,
-): Promise<any> {
- return new Promise<any>((resolve, reject) => {
- chrome.runtime.sendMessage({ type, detail }, (resp) => {
- if (chrome.runtime.lastError) {
- console.log("Error calling backend");
- reject(
- new Error(
- `Error contacting backend: chrome.runtime.lastError.message`,
- ),
- );
- }
- if (typeof resp === "object" && resp && resp.error) {
- console.warn("response error:", resp);
- const e = new WalletApiError(resp.error.message, resp.error);
- reject(e);
- } else {
- resolve(resp);
- }
- });
- });
-}
-
-/**
- * Get all exchanges the wallet knows about.
- */
-export function getExchanges(): Promise<ExchangeRecord[]> {
- return callBackend("get-exchanges", {});
-}
-
-/**
- * Get all currencies the exchange knows about.
- */
-export function getCurrencies(): Promise<CurrencyRecord[]> {
- return callBackend("get-currencies", {});
-}
-
-/**
- * Get information about a specific exchange.
- */
-export function getExchangeInfo(baseUrl: string): Promise<ExchangeRecord> {
- return callBackend("exchange-info", { baseUrl });
-}
-
-/**
- * Replace an existing currency record with the one given. The currency to
- * replace is specified inside the currency record.
- */
-export function updateCurrency(currencyRecord: CurrencyRecord): Promise<void> {
- return callBackend("update-currency", { currencyRecord });
-}
-
-/**
- * Get all reserves the wallet has at an exchange.
- */
-export function getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
- return callBackend("get-reserves", { exchangeBaseUrl });
-}
-
-/**
- * Get all coins withdrawn from the given exchange.
- */
-export function getCoins(exchangeBaseUrl: string): Promise<CoinRecord[]> {
- return callBackend("get-coins", { exchangeBaseUrl });
-}
-
-/**
- * Get all denoms offered by the given exchange.
- */
-export function getDenoms(
- exchangeBaseUrl: string,
-): Promise<DenominationRecord[]> {
- return callBackend("get-denoms", { exchangeBaseUrl });
-}
-
-/**
- * Start refreshing a coin.
- */
-export function refresh(coinPub: string): Promise<void> {
- return callBackend("refresh-coin", { coinPub });
-}
-
-/**
- * Pay for a proposal.
- */
-export function confirmPay(
- proposalId: string,
- sessionId: string | undefined,
-): Promise<ConfirmPayResult> {
- return callBackend("confirm-pay", { proposalId, sessionId });
-}
-
-/**
- * Check upgrade information
- */
-export function checkUpgrade(): Promise<UpgradeResponse> {
- return callBackend("check-upgrade", {});
-}
-
-/**
- * Reset database
- */
-export function resetDb(): Promise<void> {
- return callBackend("reset-db", {});
-}
-
-/**
- * Get balances for all currencies/exchanges.
- */
-export function getBalance(): Promise<BalancesResponse> {
- return callBackend("balances", {});
-}
-
-/**
- * Get possible sender wire infos for getting money
- * wired from an exchange.
- */
-export function getSenderWireInfos(): Promise<SenderWireInfos> {
- return callBackend("get-sender-wire-infos", {});
-}
-
-/**
- * Return coins to a bank account.
- */
-export function returnCoins(args: {
- amount: AmountJson;
- exchange: string;
- senderWire: string;
-}): Promise<void> {
- return callBackend("return-coins", args);
-}
-
-/**
- * Look up a purchase in the wallet database from
- * the contract terms hash.
- */
-export function getPurchaseDetails(
- proposalId: string,
-): Promise<PurchaseDetails> {
- return callBackend("get-purchase-details", { proposalId });
-}
-
-/**
- * Get the status of processing a tip.
- */
-export function getTipStatus(talerTipUri: string): Promise<TipStatus> {
- return callBackend("get-tip-status", { talerTipUri });
-}
-
-/**
- * Mark a tip as accepted by the user.
- */
-export function acceptTip(talerTipUri: string): Promise<void> {
- return callBackend("accept-tip", { talerTipUri });
-}
-
-/**
- * Download a refund and accept it.
- */
-export function applyRefund(
- refundUrl: string,
-): Promise<{ contractTermsHash: string; proposalId: string }> {
- return callBackend("accept-refund", { refundUrl });
-}
-
-/**
- * Abort a failed payment and try to get a refund.
- */
-export function abortFailedPayment(contractTermsHash: string): Promise<void> {
- return callBackend("abort-failed-payment", { contractTermsHash });
-}
-
-/**
- * Abort a failed payment and try to get a refund.
- */
-export function benchmarkCrypto(repetitions: number): Promise<BenchmarkResult> {
- return callBackend("benchmark-crypto", { repetitions });
-}
-
-/**
- * Get details about a pay operation.
- */
-export function preparePay(talerPayUri: string): Promise<PreparePayResult> {
- return callBackend("prepare-pay", { talerPayUri });
-}
-
-/**
- * Get details about a withdraw operation.
- */
-export function acceptWithdrawal(
- talerWithdrawUri: string,
- selectedExchange: string,
-): Promise<AcceptWithdrawalResponse> {
- return callBackend("accept-withdrawal", {
- talerWithdrawUri,
- selectedExchange,
- });
-}
-
-/**
- * Get diagnostics information
- */
-export function getDiagnostics(): Promise<WalletDiagnostics> {
- return callBackend("get-diagnostics", {});
-}
-
-/**
- * Get diagnostics information
- */
-export function setExtendedPermissions(
- value: boolean,
-): Promise<ExtendedPermissionsResponse> {
- return callBackend("set-extended-permissions", { value });
-}
-
-/**
- * Get diagnostics information
- */
-export function getExtendedPermissions(): Promise<ExtendedPermissionsResponse> {
- return callBackend("get-extended-permissions", {});
-}
-
-export function onUpdateNotification(f: () => void): () => void {
- const port = chrome.runtime.connect({ name: "notifications" });
- const listener = (): void => {
- f();
- };
- port.onMessage.addListener(listener);
- return () => {
- port.onMessage.removeListener(listener);
- };
-}
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
deleted file mode 100644
index 39fcf899e..000000000
--- a/src/webex/wxBackend.ts
+++ /dev/null
@@ -1,575 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Messaging for the WebExtensions wallet. Should contain
- * parts that are specific for WebExtensions, but as little business
- * logic as possible.
- */
-
-/**
- * Imports.
- */
-import { BrowserCryptoWorkerFactory } from "../crypto/workers/cryptoApi";
-import {
- deleteTalerDatabase,
- openTalerDatabase,
- WALLET_DB_MINOR_VERSION,
-} from "../db";
-import { ReturnCoinsRequest, WalletDiagnostics } from "../types/walletTypes";
-import { BrowserHttpLib } from "../util/http";
-import { OpenedPromise, openPromise } from "../util/promiseUtils";
-import { classifyTalerUri, TalerUriType } from "../util/taleruri";
-import { Wallet } from "../wallet";
-import { isFirefox, getPermissionsApi } from "./compat";
-import * as wxApi from "./wxApi";
-import MessageSender = chrome.runtime.MessageSender;
-import { Database } from "../util/query";
-import { extendedPermissions } from "./permissions";
-
-const NeedsWallet = Symbol("NeedsWallet");
-
-/**
- * Currently active wallet instance. Might be unloaded and
- * re-instantiated when the database is reset.
- */
-let currentWallet: Wallet | undefined;
-
-let currentDatabase: IDBDatabase | undefined;
-
-/**
- * Last version if an outdated DB, if applicable.
- */
-let outdatedDbVersion: number | undefined;
-
-const walletInit: OpenedPromise<void> = openPromise<void>();
-
-const notificationPorts: chrome.runtime.Port[] = [];
-
-async function handleMessage(
- sender: MessageSender,
- type: string,
- detail: any,
-): Promise<any> {
- function needsWallet(): Wallet {
- if (!currentWallet) {
- throw NeedsWallet;
- }
- return currentWallet;
- }
- switch (type) {
- case "balances": {
- return needsWallet().getBalances();
- }
- case "dump-db": {
- const db = needsWallet().db;
- return db.exportDatabase();
- }
- case "import-db": {
- const db = needsWallet().db;
- return db.importDatabase(detail.dump);
- }
- case "ping": {
- return Promise.resolve();
- }
- case "reset-db": {
- deleteTalerDatabase(indexedDB);
- setBadgeText({ text: "" });
- console.log("reset done");
- if (!currentWallet) {
- reinitWallet();
- }
- return Promise.resolve({});
- }
- case "confirm-pay": {
- if (typeof detail.proposalId !== "string") {
- throw Error("proposalId must be string");
- }
- return needsWallet().confirmPay(detail.proposalId, detail.sessionId);
- }
- case "exchange-info": {
- if (!detail.baseUrl) {
- return Promise.resolve({ error: "bad url" });
- }
- return needsWallet().updateExchangeFromUrl(detail.baseUrl);
- }
- case "get-exchanges": {
- return needsWallet().getExchangeRecords();
- }
- case "get-currencies": {
- return needsWallet().getCurrencies();
- }
- case "update-currency": {
- return needsWallet().updateCurrency(detail.currencyRecord);
- }
- case "get-reserves": {
- if (typeof detail.exchangeBaseUrl !== "string") {
- return Promise.reject(Error("exchangeBaseUrl missing"));
- }
- return needsWallet().getReserves(detail.exchangeBaseUrl);
- }
- case "get-coins": {
- if (typeof detail.exchangeBaseUrl !== "string") {
- return Promise.reject(Error("exchangBaseUrl missing"));
- }
- return needsWallet().getCoinsForExchange(detail.exchangeBaseUrl);
- }
- case "get-denoms": {
- if (typeof detail.exchangeBaseUrl !== "string") {
- return Promise.reject(Error("exchangBaseUrl missing"));
- }
- return needsWallet().getDenoms(detail.exchangeBaseUrl);
- }
- case "refresh-coin": {
- if (typeof detail.coinPub !== "string") {
- return Promise.reject(Error("coinPub missing"));
- }
- return needsWallet().refresh(detail.coinPub);
- }
- case "get-sender-wire-infos": {
- return needsWallet().getSenderWireInfos();
- }
- case "return-coins": {
- const d = {
- amount: detail.amount,
- exchange: detail.exchange,
- senderWire: detail.senderWire,
- };
- const req = ReturnCoinsRequest.checked(d);
- return needsWallet().returnCoins(req);
- }
- case "check-upgrade": {
- let dbResetRequired = false;
- if (!currentWallet) {
- dbResetRequired = true;
- }
- const resp: wxApi.UpgradeResponse = {
- currentDbVersion: WALLET_DB_MINOR_VERSION.toString(),
- dbResetRequired,
- oldDbVersion: (outdatedDbVersion || "unknown").toString(),
- };
- return resp;
- }
- case "get-purchase-details": {
- const proposalId = detail.proposalId;
- if (!proposalId) {
- throw Error("proposalId missing");
- }
- if (typeof proposalId !== "string") {
- throw Error("proposalId must be a string");
- }
- return needsWallet().getPurchaseDetails(proposalId);
- }
- case "accept-refund":
- return needsWallet().applyRefund(detail.refundUrl);
- case "get-tip-status": {
- return needsWallet().getTipStatus(detail.talerTipUri);
- }
- case "accept-tip": {
- return needsWallet().acceptTip(detail.talerTipUri);
- }
- case "abort-failed-payment": {
- if (!detail.contractTermsHash) {
- throw Error("contracTermsHash not given");
- }
- return needsWallet().abortFailedPayment(detail.contractTermsHash);
- }
- case "benchmark-crypto": {
- if (!detail.repetitions) {
- throw Error("repetitions not given");
- }
- return needsWallet().benchmarkCrypto(detail.repetitions);
- }
- case "accept-withdrawal": {
- return needsWallet().acceptWithdrawal(
- detail.talerWithdrawUri,
- detail.selectedExchange,
- );
- }
- case "get-diagnostics": {
- const manifestData = chrome.runtime.getManifest();
- const errors: string[] = [];
- let firefoxIdbProblem = false;
- let dbOutdated = false;
- try {
- await walletInit.promise;
- } catch (e) {
- errors.push("Error during wallet initialization: " + e);
- if (
- currentDatabase === undefined &&
- outdatedDbVersion === undefined &&
- isFirefox()
- ) {
- firefoxIdbProblem = true;
- }
- }
- if (!currentWallet) {
- errors.push("Could not create wallet backend.");
- }
- if (!currentDatabase) {
- errors.push("Could not open database");
- }
- if (outdatedDbVersion !== undefined) {
- errors.push(`Outdated DB version: ${outdatedDbVersion}`);
- dbOutdated = true;
- }
- const diagnostics: WalletDiagnostics = {
- walletManifestDisplayVersion:
- manifestData.version_name || "(undefined)",
- walletManifestVersion: manifestData.version,
- errors,
- firefoxIdbProblem,
- dbOutdated,
- };
- return diagnostics;
- }
- case "prepare-pay":
- return needsWallet().preparePayForUri(detail.talerPayUri);
- case "set-extended-permissions": {
- const newVal = detail.value;
- console.log("new extended permissions value", newVal);
- if (newVal) {
- setupHeaderListener();
- return { newValue: true };
- } else {
- await new Promise((resolve, reject) => {
- getPermissionsApi().remove(extendedPermissions, (rem) => {
- console.log("permissions removed:", rem);
- resolve();
- });
- });
- return { newVal: false };
- }
- }
- case "get-extended-permissions": {
- const res = await new Promise((resolve, reject) => {
- getPermissionsApi().contains(extendedPermissions, (result: boolean) => {
- resolve(result);
- });
- });
- return { newValue: res };
- }
- default:
- console.error(`Request type ${type} unknown`);
- console.error(`Request detail was ${detail}`);
- return {
- error: {
- message: `request type ${type} unknown`,
- requestType: type,
- },
- };
- }
-}
-
-async function dispatch(
- req: any,
- sender: any,
- sendResponse: any,
-): Promise<void> {
- try {
- const p = handleMessage(sender, req.type, req.detail);
- const r = await p;
- try {
- sendResponse(r);
- } catch (e) {
- // might fail if tab disconnected
- }
- } catch (e) {
- console.log(`exception during wallet handler for '${req.type}'`);
- console.log("request", req);
- console.error(e);
- let stack;
- try {
- stack = e.stack.toString();
- } catch (e) {
- // might fail
- }
- try {
- sendResponse({
- error: {
- message: e.message,
- stack,
- },
- });
- } catch (e) {
- console.log(e);
- // might fail if tab disconnected
- }
- }
-}
-
-function getTab(tabId: number): Promise<chrome.tabs.Tab> {
- return new Promise((resolve, reject) => {
- chrome.tabs.get(tabId, (tab: chrome.tabs.Tab) => resolve(tab));
- });
-}
-
-function setBadgeText(options: chrome.browserAction.BadgeTextDetails): void {
- // not supported by all browsers ...
- if (chrome && chrome.browserAction && chrome.browserAction.setBadgeText) {
- chrome.browserAction.setBadgeText(options);
- } else {
- console.warn("can't set badge text, not supported", options);
- }
-}
-
-function waitMs(timeoutMs: number): Promise<void> {
- return new Promise((resolve, reject) => {
- const bgPage = chrome.extension.getBackgroundPage();
- if (!bgPage) {
- reject("fatal: no background page");
- return;
- }
- bgPage.setTimeout(() => resolve(), timeoutMs);
- });
-}
-
-function makeSyncWalletRedirect(
- url: string,
- tabId: number,
- oldUrl: string,
- params?: { [name: string]: string | undefined },
-): Record<string, unknown> {
- const innerUrl = new URL(chrome.extension.getURL("/" + url));
- if (params) {
- for (const key in params) {
- const p = params[key];
- if (p) {
- innerUrl.searchParams.set(key, p);
- }
- }
- }
- if (isFirefox()) {
- // Some platforms don't support the sync redirect (yet), so fall back to
- // async redirect after a timeout.
- const doit = async (): Promise<void> => {
- await waitMs(150);
- const tab = await getTab(tabId);
- if (tab.url === oldUrl) {
- chrome.tabs.update(tabId, { url: innerUrl.href });
- }
- };
- doit();
- }
- console.log("redirecting to", innerUrl.href);
- chrome.tabs.update(tabId, { url: innerUrl.href });
- return { redirectUrl: innerUrl.href };
-}
-
-async function reinitWallet(): Promise<void> {
- if (currentWallet) {
- currentWallet.stop();
- currentWallet = undefined;
- }
- currentDatabase = undefined;
- setBadgeText({ text: "" });
- try {
- currentDatabase = await openTalerDatabase(indexedDB, reinitWallet);
- } catch (e) {
- console.error("could not open database", e);
- walletInit.reject(e);
- return;
- }
- const http = new BrowserHttpLib();
- console.log("setting wallet");
- const wallet = new Wallet(
- new Database(currentDatabase),
- http,
- new BrowserCryptoWorkerFactory(),
- );
- wallet.addNotificationListener((x) => {
- for (const x of notificationPorts) {
- try {
- x.postMessage({ type: "notification" });
- } catch (e) {
- console.error(e);
- }
- }
- });
- wallet.runRetryLoop().catch((e) => {
- console.log("error during wallet retry loop", e);
- });
- // Useful for debugging in the background page.
- (window as any).talerWallet = wallet;
- currentWallet = wallet;
- walletInit.resolve();
-}
-
-try {
- // This needs to be outside of main, as Firefox won't fire the event if
- // the listener isn't created synchronously on loading the backend.
- chrome.runtime.onInstalled.addListener((details) => {
- console.log("onInstalled with reason", details.reason);
- if (details.reason === "install") {
- const url = chrome.extension.getURL("/welcome.html");
- chrome.tabs.create({ active: true, url: url });
- }
- });
-} catch (e) {
- console.error(e);
-}
-
-function headerListener(
- details: chrome.webRequest.WebResponseHeadersDetails,
-): chrome.webRequest.BlockingResponse | undefined {
- console.log("header listener");
- if (chrome.runtime.lastError) {
- console.error(chrome.runtime.lastError);
- return;
- }
- const wallet = currentWallet;
- if (!wallet) {
- console.warn("wallet not available while handling header");
- return;
- }
- console.log("in header listener");
- if (details.statusCode === 402 || details.statusCode === 202) {
- console.log(`got 402/202 from ${details.url}`);
- for (const header of details.responseHeaders || []) {
- if (header.name.toLowerCase() === "taler") {
- const talerUri = header.value || "";
- const uriType = classifyTalerUri(talerUri);
- switch (uriType) {
- case TalerUriType.TalerWithdraw:
- return makeSyncWalletRedirect(
- "withdraw.html",
- details.tabId,
- details.url,
- {
- talerWithdrawUri: talerUri,
- },
- );
- case TalerUriType.TalerPay:
- return makeSyncWalletRedirect(
- "pay.html",
- details.tabId,
- details.url,
- {
- talerPayUri: talerUri,
- },
- );
- case TalerUriType.TalerTip:
- return makeSyncWalletRedirect(
- "tip.html",
- details.tabId,
- details.url,
- {
- talerTipUri: talerUri,
- },
- );
- case TalerUriType.TalerRefund:
- return makeSyncWalletRedirect(
- "refund.html",
- details.tabId,
- details.url,
- {
- talerRefundUri: talerUri,
- },
- );
- case TalerUriType.TalerNotifyReserve:
- Promise.resolve().then(() => {
- const w = currentWallet;
- if (!w) {
- return;
- }
- w.handleNotifyReserve();
- });
- break;
- default:
- console.warn(
- "Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
- );
- break;
- }
- }
- }
- }
- return;
-}
-
-function setupHeaderListener(): void {
- console.log("setting up header listener");
- // Handlers for catching HTTP requests
- getPermissionsApi().contains(extendedPermissions, (result: boolean) => {
- if (
- chrome.webRequest.onHeadersReceived &&
- chrome.webRequest.onHeadersReceived.hasListener(headerListener)
- ) {
- chrome.webRequest.onHeadersReceived.removeListener(headerListener);
- }
- if (result) {
- console.log("actually adding listener");
- chrome.webRequest.onHeadersReceived.addListener(
- headerListener,
- { urls: ["<all_urls>"] },
- ["responseHeaders", "blocking"],
- );
- }
- chrome.webRequest.handlerBehaviorChanged(() => {
- if (chrome.runtime.lastError) {
- console.error(chrome.runtime.lastError);
- }
- });
- });
-}
-
-/**
- * Main function to run for the WebExtension backend.
- *
- * Sets up all event handlers and other machinery.
- */
-export async function wxMain(): Promise<void> {
- // Explicitly unload the extension page as soon as an update is available,
- // so the update gets installed as soon as possible.
- chrome.runtime.onUpdateAvailable.addListener((details) => {
- console.log("update available:", details);
- chrome.runtime.reload();
- });
- reinitWallet();
-
- // Handlers for messages coming directly from the content
- // script on the page
- chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
- dispatch(req, sender, sendResponse);
- return true;
- });
-
- chrome.runtime.onConnect.addListener((port) => {
- notificationPorts.push(port);
- port.onDisconnect.addListener((discoPort) => {
- const idx = notificationPorts.indexOf(discoPort);
- if (idx >= 0) {
- notificationPorts.splice(idx, 1);
- }
- });
- });
-
- try {
- setupHeaderListener();
- } catch (e) {
- console.log(e);
- }
-
- // On platforms that support it, also listen to external
- // modification of permissions.
- getPermissionsApi().addPermissionsListener((perm) => {
- if (chrome.runtime.lastError) {
- console.error(chrome.runtime.lastError);
- return;
- }
- setupHeaderListener();
- });
-}