From ffd2a62c3f7df94365980302fef3bc3376b48182 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 3 Aug 2020 13:00:48 +0530 Subject: modularize repo, use pnpm, improve typechecking --- src/webex/background.ts | 30 -- src/webex/chromeBadge.ts | 288 ------------------- src/webex/compat.ts | 85 ------ src/webex/i18n-test.tsx | 69 ----- src/webex/i18n.tsx | 250 ---------------- src/webex/pageEntryPoint.ts | 72 ----- src/webex/pages/add-auditor.tsx | 135 --------- src/webex/pages/auditors.tsx | 161 ----------- src/webex/pages/benchmark.tsx | 104 ------- src/webex/pages/pay.tsx | 182 ------------ src/webex/pages/payback.tsx | 30 -- src/webex/pages/popup.tsx | 499 -------------------------------- src/webex/pages/refund.tsx | 89 ------ src/webex/pages/reset-required.tsx | 93 ------ src/webex/pages/return-coins.tsx | 30 -- src/webex/pages/tip.tsx | 103 ------- src/webex/pages/welcome.tsx | 190 ------------ src/webex/pages/withdraw.tsx | 229 --------------- src/webex/permissions.ts | 20 -- src/webex/renderHtml.tsx | 344 ---------------------- src/webex/wxApi.ts | 310 -------------------- src/webex/wxBackend.ts | 575 ------------------------------------- 22 files changed, 3888 deletions(-) delete mode 100644 src/webex/background.ts delete mode 100644 src/webex/chromeBadge.ts delete mode 100644 src/webex/compat.ts delete mode 100644 src/webex/i18n-test.tsx delete mode 100644 src/webex/i18n.tsx delete mode 100644 src/webex/pageEntryPoint.ts delete mode 100644 src/webex/pages/add-auditor.tsx delete mode 100644 src/webex/pages/auditors.tsx delete mode 100644 src/webex/pages/benchmark.tsx delete mode 100644 src/webex/pages/pay.tsx delete mode 100644 src/webex/pages/payback.tsx delete mode 100644 src/webex/pages/popup.tsx delete mode 100644 src/webex/pages/refund.tsx delete mode 100644 src/webex/pages/reset-required.tsx delete mode 100644 src/webex/pages/return-coins.tsx delete mode 100644 src/webex/pages/tip.tsx delete mode 100644 src/webex/pages/welcome.tsx delete mode 100644 src/webex/pages/withdraw.tsx delete mode 100644 src/webex/permissions.ts delete mode 100644 src/webex/renderHtml.tsx delete mode 100644 src/webex/wxApi.ts delete mode 100644 src/webex/wxBackend.ts (limited to 'src/webex') 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 - */ - -/** - * 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 - */ - -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 - */ - -/** - * 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 - */ - -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(str1); - t.is(r.text(), "foo1"); - - const r2 = render( - - str3 {a} / {b} - , - ); - 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 - */ - -/** - * 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 { - 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: - * ``` - * - * Hello. Your score is - * - * ``` - */ -export class Translate extends React.Component { - 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
{result}
; - } - 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: - * ``` - * - * I have {n} apple. - * I have {n} apples. - * - * ``` - */ -export class TranslateSwitch extends React.Component< - TranslateSwitchProps, - void -> { - render(): JSX.Element { - let singular: React.ReactElement | undefined; - let plural: React.ReactElement | 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
{result}
; - } -} - -/** - * 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
{result}
; - } -} 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 - */ - -/** - * Main entry point for extension pages. - * - * @author Florian Dold - */ - -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 - */ - -/** - * 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 => { - 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 ( -
-

- Do you want to let {props.auditorPub} audit the - currency "{props.currency}"? -

- {addDone ? ( -
- Auditor was added! You can also{" "} - view and edit{" "} - auditors. -
- ) : ( -
- - -
- )} -
- ); -} - -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 ; -} 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 - */ - -/** - * 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 { - const currencies = await getCurrencies(); - console.log("currencies: ", currencies); - this.setState({ currencies }); - } - - async confirmRemoveAuditor( - c: CurrencyRecord, - a: AuditorRecord, - ): Promise { - 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 { - 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

No trusted auditors for this currency.

; - } - return ( -
-

Trusted Auditors:

-
    - {c.auditors.map((a) => ( -
  • - {a.baseUrl}{" "} - -
      -
    • valid until {new Date(a.expirationStamp).toString()}
    • -
    • public key {a.auditorPub}
    • -
    -
  • - ))} -
-
- ); - } - - renderExchanges(c: CurrencyRecord): any { - if (c.exchanges.length === 0) { - return

No trusted exchanges for this currency.

; - } - return ( -
-

Trusted Exchanges:

-
    - {c.exchanges.map((e) => ( -
  • - {e.baseUrl}{" "} - -
  • - ))} -
-
- ); - } - - render(): JSX.Element { - const currencies = this.state.currencies; - if (!currencies) { - return ...; - } - return ( -
- {currencies.map((c) => ( -
-

Currency {c.name}

-

Displayed with {c.fractionalDigits} fractional digits.

-

Auditors

-
{this.renderAuditors(c)}
-

Exchanges

-
{this.renderExchanges(c)}
-
- ))} -
- ); - } -} - -export function makeAuditorsPage(): JSX.Element { - return ; -} 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 - */ - -/** - * 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
Waiting for results ...
; - } else { - return
; - } - } - return ( - <> -

Results for {result.repetitions} repetitions

- - - - - - - {Object.keys(result.time) - .sort() - .map((k) => ( - - - - - ))} - -
{i18n.str`Operation`}{i18n.str`time (ms/op)`}
{k}{result.time[k] / result.repetitions}
- - ); -} - -class BenchmarkRunner extends React.Component { - constructor(props: any) { - super(props); - this.state = { - repetitions: 10, - running: false, - }; - } - - async run(): Promise { - this.setState({ result: undefined, running: true }); - const result = await wxApi.benchmarkCrypto(this.state.repetitions); - this.setState({ result, running: false }); - } - - render(): JSX.Element { - return ( -
- - - this.setState({ repetitions: Number.parseInt(evt.target.value) }) - } - />{" "} - - -
- ); - } -} - -export function makeBenchmarkPage(): JSX.Element { - return ; -} 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 - */ - -/** - * 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(); - const [payErrMsg, setPayErrMsg] = useState(""); - const [numTries, setNumTries] = useState(0); - const [loading, setLoading] = useState(false); - let amountEffective: Amounts.AmountJson | undefined = undefined; - - useEffect(() => { - const doFetch = async (): Promise => { - const p = await wxApi.preparePay(talerPayUri); - setPayStatus(p); - }; - doFetch(); - }, [numTries, talerPayUri]); - - if (!payStatus) { - return Loading payment information ...; - } - - 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 ( - - You have already paid for this article. Click{" "} - here to view it again. - - ); - } - - 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 Invalid contract terms.; - } - - if (!contractTerms) { - return ( - - Error: did not get contract terms from merchant or wallet backend. - - ); - } - - let merchantName: React.ReactElement; - if (contractTerms.merchant && contractTerms.merchant.name) { - merchantName = {contractTerms.merchant.name}; - } else { - merchantName = (pub: {contractTerms.merchant_pub}); - } - - const amount = ( - {renderAmount(Amounts.parseOrThrow(contractTerms.amount))} - ); - - const doPayment = async (): Promise => { - 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 ( -
-

- - The merchant {merchantName} offers you to purchase: - -

- {contractTerms.summary} -
- {amountEffective ? ( - - The total price is {amount} - (plus {renderAmount(amountEffective)} fees). - - ) : ( - - The total price is {amount}. - - )} -

- - {insufficientBalance ? ( -
-

- Unable to pay: Your balance is insufficient. -

-
- ) : null} - - {payErrMsg ? ( -
-

Payment failed: {payErrMsg}

- -
- ) : ( -
- doPayment()} - > - {i18n.str`Confirm payment`} - -
- )} -
- ); -} - -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 ; -} 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 - */ - -/** - * View and edit auditors. - * - * @author Florian Dold - */ - -/** - * Imports. - */ -import * as React from "react"; - -export function makePaybackPage(): JSX.Element { - return
not implemented
; -} 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 - */ - -/** - * 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 { - 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
{c}
; - } -} - -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): void => { - Router.setRoute(props.target); - e.preventDefault(); - }; - return ( - - {props.children} - - ); -} - -class WalletNavBar extends React.Component { - 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 ( - - ); - } -} - -/** - * 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 ( - - {v}{" "} - {amount.currency} - - ); -} - -function EmptyBalanceView(): JSX.Element { - return ( - - You have no balance to show. Need some{" "} - help getting started? - - ); -} - -class WalletBalanceView extends React.Component { - 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 { - 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 = ( - - - {"+"} - {renderAmount(entry.pendingIncoming)} - {" "} - incoming - - ); - } - - const l = [incoming, payment].filter((x) => x !== undefined); - if (l.length === 0) { - return ; - } - - if (l.length === 1) { - return ({l}); - } - return ( - - ({l[0]}, {l[1]}) - - ); - } - - render(): JSX.Element { - const wallet = this.balance; - if (this.gotError) { - return ( -
-

{i18n.str`Error: could not retrieve balance information.`}

-

- Click here for help and - diagnostics. -

-
- ); - } - if (!wallet) { - return ; - } - console.log(wallet); - const listing = wallet.balances.map((entry) => { - const av = Amounts.parseOrThrow(entry.available); - return ( -

- {bigAmount(av)} {this.formatPending(entry)} -

- ); - }); - return listing.length > 0 ? ( -
{listing}
- ) : ( - - ); - } -} - -function Icon({ l }: { l: string }): JSX.Element { - return
{l}
; -} - -function formatAndCapitalize(text: string): string { - text = text.replace("-", " "); - text = text.replace(/^./, text[0].toUpperCase()); - return text; -} - -const HistoryComponent = (props: any): JSX.Element => { - return TBD; -}; - -class WalletSettings extends React.Component { - render(): JSX.Element { - return ( -
-

Permissions

- -
- ); - } -} - -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 ( -
-

Debug tools:

- - - - -
- - -
- ); -} - -function openExtensionPage(page: string) { - return () => { - chrome.tabs.create({ - url: chrome.extension.getURL(page), - }); - }; -} - -function openTab(page: string) { - return (evt: React.SyntheticEvent) => { - 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 { - 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( - undefined, - ); - const [dismissed, setDismissed] = useState(false); - useEffect(() => { - async function check(): Promise { - const talerUri = await findTalerUriInActiveTab(); - if (talerUri) { - const actionUrl = actionForTalerUri(talerUri); - setTalerActionUrl(actionUrl); - } - } - check(); - }); - if (talerActionUrl && !dismissed) { - return ( -
-

Taler Action

-

This page has a Taler action.

-

- -

-

- -

-
- ); - } - return ( -
- -
- - - - - -
-
- ); -} - -export function createPopup(): JSX.Element { - return ; -} 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 - */ - -/** - * 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(undefined); - - useEffect(() => { - const doFetch = async (): Promise => { - 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 Error: {errMsg}; - } - - if (!applied || !purchaseDetails) { - return Updating refund status; - } - - return ( - <> -

Refund Status

-

- The product {purchaseDetails.contractTerms.summary} has - received a total refund of{" "} - . -

-

Note that additional fees from the exchange may apply.

- - ); -} - -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 ; -} 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 - */ - -/** - * 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 { - constructor(props: any) { - super(props); - this.state = { checked: false, resetRequired: true }; - setInterval(() => this.update(), 500); - } - async update(): Promise { - const res = await wxApi.checkUpgrade(); - this.setState({ resetRequired: res.dbResetRequired }); - } - render(): JSX.Element { - if (this.state.resetRequired) { - return ( -
-

Manual Reset Reqired

-

- The wallet's database in your browser is incompatible with the{" "} - currently installed wallet. Please reset manually. -

-

- Once the database format has stabilized, we will provide automatic - upgrades. -

- this.setState({ checked: e.target.checked })} - />{" "} - -
- -
- ); - } - return ( -
-

Everything is fine!

A reset is not required anymore, you can - close this page. -
- ); - } -} - -export function createResetRequiredPage(): JSX.Element { - return ; -} 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 - */ - -/** - * Return coins to own bank account. - * - * @author Florian Dold - */ - -/** - * Imports. - */ -import * as React from "react"; - -export function createReturnCoinsPage(): JSX.Element { - return Not implemented yet.; -} 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 - */ - -/** - * 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(undefined); - const [discarded, setDiscarded] = useState(false); - const [loading, setLoading] = useState(false); - const [finished, setFinished] = useState(false); - - useEffect(() => { - const doFetch = async (): Promise => { - const ts = await getTipStatus(props.talerTipUri); - setTipStatus(ts); - }; - doFetch(); - }, [props.talerTipUri]); - - if (discarded) { - return You've discarded the tip.; - } - - if (finished) { - return Tip has been accepted!; - } - - if (!tipStatus) { - return Loading ...; - } - - const discard = (): void => { - setDiscarded(true); - }; - - const accept = async (): Promise => { - setLoading(true); - await acceptTip(tipStatus.tipId); - setFinished(true); - }; - - return ( -
-

Tip Received!

-

- You received a tip of {renderAmount(tipStatus.amount)}{" "} - from - {tipStatus.merchantOrigin}. -

-

- The tip is handled by the exchange{" "} - {tipStatus.exchangeUrl}. This exchange will charge fees - of {renderAmount(tipStatus.totalFees)} for this - operation. -

-
- accept()}> - Accept Tip - {" "} - -
-
- ); -} - -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 ; -} 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 - */ - -/** - * 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( - undefined, - ); - - useEffect(() => { - let gotDiagnostics = false; - setTimeout(() => { - if (!gotDiagnostics) { - console.error("timed out"); - setTimedOut(true); - } - }, 1000); - const doFetch = async (): Promise => { - const d = await getDiagnostics(); - console.log("got diagnostics", d); - gotDiagnostics = true; - setDiagnostics(d); - }; - console.log("fetching diagnostics"); - doFetch(); - }, []); - - if (timedOut) { - return

Diagnostics timed out. Could not talk to the wallet backend.

; - } - - if (diagnostics) { - if (diagnostics.errors.length === 0) { - return null; - } else { - return ( -
-

Problems detected:

-
    - {diagnostics.errors.map((errMsg) => ( -
  1. {errMsg}
  2. - ))} -
- {diagnostics.firefoxIdbProblem ? ( -

- Please check in your about:config settings that you - have IndexedDB enabled (check the preference name{" "} - dom.indexedDB.enabled). -

- ) : null} - {diagnostics.dbOutdated ? ( -

- Your wallet database is outdated. Currently automatic migration is - not supported. Please go{" "} - here to reset - the wallet database. -

- ) : null} -
- ); - } - } - - return

Running diagnostics ...

; -} - -export function PermissionsCheckbox(): JSX.Element { - const [extendedPermissionsEnabled, setExtendedPermissionsEnabled] = useState( - false, - ); - async function handleExtendedPerm(requestedVal: boolean): Promise { - let nextVal: boolean | undefined; - if (requestedVal) { - const granted = await new Promise((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 { - const res = await wxApi.getExtendedPermissions(); - setExtendedPermissionsEnabled(res.newValue); - } - getExtendedPermValue(); - }); - return ( -
- handleExtendedPerm(x.target.checked)} - type="checkbox" - id="checkbox-perm" - style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }} - /> - - - (Enabling this option below will make using the wallet faster, but - requires more permissions from your browser.) - -
- ); -} - -function Welcome(): JSX.Element { - return ( - <> -

Thank you for installing the wallet.

- -

Permissions

- -

Next Steps

- - Try the demo » - - - Learn how to top up your wallet balance » - - - ); -} - -export function createWelcomePage(): JSX.Element { - return ; -} 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 - */ - -/** - * 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(""); - const [errMsg, setErrMsg] = useState(""); - const [updateCounter, setUpdateCounter] = useState(1); - - useEffect(() => { - return onUpdateNotification(() => { - setUpdateCounter(updateCounter + 1); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - const fetchData = async (): Promise => { - // 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 ( -
- - Could not get details for withdraw operation: - -

{errMsg}

-

- { - setSelecting(true); - setErrMsg(undefined); - setSelectedExchange(undefined); - setDetails(undefined); - }} - > - {i18n.str`Chose different exchange provider`} - -

-
- ); - } - - if (!details) { - return Loading...; - } - - if (cancelled) { - return Withdraw operation has been cancelled.; - } - - if (selecting) { - const bankSuggestion = - details && details.bankWithdrawDetails.suggestedExchange; - return ( -
- {i18n.str`Please select an exchange. You can review the details before after your selection.`} - {bankSuggestion && ( -
-

Bank Suggestion

- -
- )} -

Custom Selection

-

- setCustomUrl(e.target.value)} - value={customUrl} - /> -

- -
- ); - } - - const accept = async (): Promise => { - 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 ( -
-

Digital Cash Withdrawal

- - You are about to withdraw{" "} - {renderAmount(details.bankWithdrawDetails.amount)} from - your bank account into your wallet. - - {selectedExchange ? ( -

- The exchange {selectedExchange} will be used as the - Taler payment service provider. -

- ) : null} - -
- -

- setSelecting(true)} - > - {i18n.str`Chose different exchange provider`} - -
- setCancelled(true)} - > - {i18n.str`Cancel withdraw operation`} - -

- - {details.exchangeWithdrawDetails ? ( - - ) : null} -
-
- ); -} - -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 ; -} 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 - */ - -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 - */ - -/** - * 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 (invalid amount); - } - const x = a.value + a.fraction / Amounts.fractionalBase; - return ( - - {x} {a.currency} - - ); -} - -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 ( - - {sAbbrev} - - ); -} - -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 ( -

- - {" "} - {this.props.title} - -

- ); - } - return ( -
-

- - {" "} - {this.props.title} - -

- {this.props.children} -
- ); - } -} - -function WireFee(props: { - s: string; - rci: ExchangeWithdrawDetails; -}): JSX.Element { - return ( - <> - - - Wire Method {props.s} - - - Applies Until - Wire Fee - Closing Fee - - - - {props.rci.wireFees.feesForType[props.s].map((f) => ( - - {stringifyTimestamp(f.endStamp)} - {renderAmount(f.wireFee)} - {renderAmount(f.closingFee)} - - ))} - - - ); -} - -function AuditorDetailsView(props: { - rci: ExchangeWithdrawDetails | null; -}): JSX.Element { - const rci = props.rci; - console.log("rci", rci); - if (!rci) { - return ( -

- Details will be displayed when a valid exchange provider URL is entered. -

- ); - } - if ((rci.exchangeInfo.details?.auditors ?? []).length === 0) { - return

The exchange is not audited by any auditors.

; - } - return ( -
- {(rci.exchangeInfo.details?.auditors ?? []).map((a) => ( -
-

Auditor {a.auditor_url}

-

- Public key: -

-

- Trusted:{" "} - {rci.trustedAuditorPubs.indexOf(a.auditor_pub) >= 0 ? "yes" : "no"} -

-

- Audits {a.denomination_keys.length} of {rci.numOfferedDenoms}{" "} - denominations -

-
- ))} -
- ); -} - -function FeeDetailsView(props: { - rci: ExchangeWithdrawDetails | null; -}): JSX.Element { - const rci = props.rci; - if (!rci) { - return ( -

- Details will be displayed when a valid exchange provider URL is entered. -

- ); - } - - const denoms = rci.selectedDenoms; - const withdrawFee = renderAmount(rci.withdrawFee); - const overhead = renderAmount(rci.overhead); - - return ( -
-

Overview

-

- Public key:{" "} - -

-

- {i18n.str`Withdrawal fees:`} {withdrawFee} -

-

- {i18n.str`Rounding loss:`} {overhead} -

-

{i18n.str`Earliest expiration (for deposit): ${stringifyTimestamp( - rci.earliestDepositExpiration, - )}`}

-

Coin Fees

-
- - - - - - - - - - - - {denoms.selectedDenoms.map((ds) => { - return ( - - - - - - - - ); - })} - -
{i18n.str`# Coins`}{i18n.str`Value`}{i18n.str`Withdraw Fee`}{i18n.str`Refresh Fee`}{i18n.str`Deposit Fee`}
{ds.count + "x"}{renderAmount(ds.denom.value)}{renderAmount(ds.denom.feeWithdraw)}{renderAmount(ds.denom.feeRefresh)}{renderAmount(ds.denom.feeDeposit)}
-
-

Wire Fees

-
- - {Object.keys(rci.wireFees.feesForType).map((s) => ( - - ))} -
-
-
- ); -} - -/** - * Shows details about a withdraw request. - */ -export function WithdrawDetailView(props: { - rci: ExchangeWithdrawDetails | null; -}): JSX.Element { - const rci = props.rci; - return ( -
- - - - - - -
- ); -} - -interface ExpanderTextProps { - text: string; -} - -/** - * Show a heading with a toggle to show/hide the expandable content. - */ -export function ExpanderText({ text }: ExpanderTextProps): JSX.Element { - return {text}; -} - -export interface LoadingButtonProps { - loading: boolean; -} - -export function ProgressButton( - props: React.PropsWithChildren & - React.DetailedHTMLProps< - React.ButtonHTMLAttributes, - HTMLButtonElement - >, -): JSX.Element { - return ( -