aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--src/db.ts2
-rw-r--r--src/dbTypes.ts2
-rw-r--r--src/i18n/de.po16
-rw-r--r--src/i18n/en-US.po16
-rw-r--r--src/i18n/fr.po16
-rw-r--r--src/i18n/it.po16
-rw-r--r--src/i18n/sv.po16
-rw-r--r--src/i18n/taler-wallet-webex.pot16
-rw-r--r--src/walletTypes.ts18
-rw-r--r--src/webex/components.ts63
-rw-r--r--src/webex/messages.ts5
-rw-r--r--src/webex/pages/add-auditor.tsx119
-rw-r--r--src/webex/pages/help/empty-wallet.html30
-rw-r--r--src/webex/pages/payback.tsx87
-rw-r--r--src/webex/pages/popup.tsx255
-rw-r--r--src/webex/pages/tree.html27
-rw-r--r--src/webex/pages/tree.tsx402
-rw-r--r--src/webex/pages/welcome.html24
-rw-r--r--src/webex/pages/welcome.tsx113
-rw-r--r--src/webex/pages/withdraw.html2
-rw-r--r--src/webex/pages/withdraw.tsx10
-rw-r--r--src/webex/renderHtml.tsx82
-rw-r--r--src/webex/wxApi.ts8
-rw-r--r--src/webex/wxBackend.ts88
-rw-r--r--tsconfig.json3
-rw-r--r--webpack.config.js2
27 files changed, 535 insertions, 907 deletions
diff --git a/Makefile b/Makefile
index f29f8108f..b33ac71bd 100644
--- a/Makefile
+++ b/Makefile
@@ -86,3 +86,7 @@ install: tsc
npm install -g --prefix $(prefix) .
endif
+.PHONY: watch
+watch: tsconfig.json
+
+ ./node_modules/.bin/webpack --watch
diff --git a/src/db.ts b/src/db.ts
index eaac22eb0..00eac4320 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -17,7 +17,7 @@ export function openTalerDb(
const req = idbFactory.open(DB_NAME, WALLET_DB_VERSION);
req.onerror = e => {
console.log("taler database error", e);
- reject(e);
+ reject(new Error("database error"));
};
req.onsuccess = e => {
req.result.onversionchange = (evt: IDBVersionChangeEvent) => {
diff --git a/src/dbTypes.ts b/src/dbTypes.ts
index 0ca2b6262..ef79ae193 100644
--- a/src/dbTypes.ts
+++ b/src/dbTypes.ts
@@ -43,7 +43,7 @@ import { Index, Store } from "./query";
* In the future we might consider adding migration functions for
* each version increment.
*/
-export const WALLET_DB_VERSION = 26;
+export const WALLET_DB_VERSION = 27;
/**
* A reserve record as stored in the wallet's database.
diff --git a/src/i18n/de.po b/src/i18n/de.po
index 6a27556c7..9d0687909 100644
--- a/src/i18n/de.po
+++ b/src/i18n/de.po
@@ -250,42 +250,42 @@ msgstr ""
msgid "Cancel withdraw operation"
msgstr ""
-#: src/webex/renderHtml.tsx:225
+#: src/webex/renderHtml.tsx:226
#, fuzzy, c-format
msgid "Withdrawal fees:"
msgstr "Abheben bei"
-#: src/webex/renderHtml.tsx:226
+#: src/webex/renderHtml.tsx:227
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/renderHtml.tsx:227
+#: src/webex/renderHtml.tsx:228
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/renderHtml.tsx:233
+#: src/webex/renderHtml.tsx:234
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/renderHtml.tsx:234
+#: src/webex/renderHtml.tsx:235
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/renderHtml.tsx:235
+#: src/webex/renderHtml.tsx:236
#, fuzzy, c-format
msgid "Withdraw Fee"
msgstr "Abheben bei %1$s"
-#: src/webex/renderHtml.tsx:236
+#: src/webex/renderHtml.tsx:237
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/renderHtml.tsx:237
+#: src/webex/renderHtml.tsx:238
#, c-format
msgid "Deposit Fee"
msgstr ""
diff --git a/src/i18n/en-US.po b/src/i18n/en-US.po
index 558ec7177..0f73aa141 100644
--- a/src/i18n/en-US.po
+++ b/src/i18n/en-US.po
@@ -241,42 +241,42 @@ msgstr ""
msgid "Cancel withdraw operation"
msgstr ""
-#: src/webex/renderHtml.tsx:225
+#: src/webex/renderHtml.tsx:226
#, c-format
msgid "Withdrawal fees:"
msgstr ""
-#: src/webex/renderHtml.tsx:226
+#: src/webex/renderHtml.tsx:227
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/renderHtml.tsx:227
+#: src/webex/renderHtml.tsx:228
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/renderHtml.tsx:233
+#: src/webex/renderHtml.tsx:234
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/renderHtml.tsx:234
+#: src/webex/renderHtml.tsx:235
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/renderHtml.tsx:235
+#: src/webex/renderHtml.tsx:236
#, c-format
msgid "Withdraw Fee"
msgstr ""
-#: src/webex/renderHtml.tsx:236
+#: src/webex/renderHtml.tsx:237
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/renderHtml.tsx:237
+#: src/webex/renderHtml.tsx:238
#, c-format
msgid "Deposit Fee"
msgstr ""
diff --git a/src/i18n/fr.po b/src/i18n/fr.po
index ba7f9ecff..ee6b66eb0 100644
--- a/src/i18n/fr.po
+++ b/src/i18n/fr.po
@@ -241,42 +241,42 @@ msgstr ""
msgid "Cancel withdraw operation"
msgstr ""
-#: src/webex/renderHtml.tsx:225
+#: src/webex/renderHtml.tsx:226
#, c-format
msgid "Withdrawal fees:"
msgstr ""
-#: src/webex/renderHtml.tsx:226
+#: src/webex/renderHtml.tsx:227
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/renderHtml.tsx:227
+#: src/webex/renderHtml.tsx:228
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/renderHtml.tsx:233
+#: src/webex/renderHtml.tsx:234
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/renderHtml.tsx:234
+#: src/webex/renderHtml.tsx:235
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/renderHtml.tsx:235
+#: src/webex/renderHtml.tsx:236
#, c-format
msgid "Withdraw Fee"
msgstr ""
-#: src/webex/renderHtml.tsx:236
+#: src/webex/renderHtml.tsx:237
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/renderHtml.tsx:237
+#: src/webex/renderHtml.tsx:238
#, c-format
msgid "Deposit Fee"
msgstr ""
diff --git a/src/i18n/it.po b/src/i18n/it.po
index ba7f9ecff..ee6b66eb0 100644
--- a/src/i18n/it.po
+++ b/src/i18n/it.po
@@ -241,42 +241,42 @@ msgstr ""
msgid "Cancel withdraw operation"
msgstr ""
-#: src/webex/renderHtml.tsx:225
+#: src/webex/renderHtml.tsx:226
#, c-format
msgid "Withdrawal fees:"
msgstr ""
-#: src/webex/renderHtml.tsx:226
+#: src/webex/renderHtml.tsx:227
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/renderHtml.tsx:227
+#: src/webex/renderHtml.tsx:228
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/renderHtml.tsx:233
+#: src/webex/renderHtml.tsx:234
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/renderHtml.tsx:234
+#: src/webex/renderHtml.tsx:235
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/renderHtml.tsx:235
+#: src/webex/renderHtml.tsx:236
#, c-format
msgid "Withdraw Fee"
msgstr ""
-#: src/webex/renderHtml.tsx:236
+#: src/webex/renderHtml.tsx:237
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/renderHtml.tsx:237
+#: src/webex/renderHtml.tsx:238
#, c-format
msgid "Deposit Fee"
msgstr ""
diff --git a/src/i18n/sv.po b/src/i18n/sv.po
index 23e1a3645..97e510f44 100644
--- a/src/i18n/sv.po
+++ b/src/i18n/sv.po
@@ -245,42 +245,42 @@ msgstr "Acceptera avgifter och utbetala"
msgid "Cancel withdraw operation"
msgstr ""
-#: src/webex/renderHtml.tsx:225
+#: src/webex/renderHtml.tsx:226
#, c-format
msgid "Withdrawal fees:"
msgstr "Utbetalnings avgifter:"
-#: src/webex/renderHtml.tsx:226
+#: src/webex/renderHtml.tsx:227
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/renderHtml.tsx:227
+#: src/webex/renderHtml.tsx:228
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/renderHtml.tsx:233
+#: src/webex/renderHtml.tsx:234
#, c-format
msgid "# Coins"
msgstr "# Mynt"
-#: src/webex/renderHtml.tsx:234
+#: src/webex/renderHtml.tsx:235
#, c-format
msgid "Value"
msgstr "Värde"
-#: src/webex/renderHtml.tsx:235
+#: src/webex/renderHtml.tsx:236
#, c-format
msgid "Withdraw Fee"
msgstr "Utbetalnings avgift"
-#: src/webex/renderHtml.tsx:236
+#: src/webex/renderHtml.tsx:237
#, c-format
msgid "Refresh Fee"
msgstr "Återhämtnings avgift"
-#: src/webex/renderHtml.tsx:237
+#: src/webex/renderHtml.tsx:238
#, c-format
msgid "Deposit Fee"
msgstr "Depostitions avgift"
diff --git a/src/i18n/taler-wallet-webex.pot b/src/i18n/taler-wallet-webex.pot
index ba7f9ecff..ee6b66eb0 100644
--- a/src/i18n/taler-wallet-webex.pot
+++ b/src/i18n/taler-wallet-webex.pot
@@ -241,42 +241,42 @@ msgstr ""
msgid "Cancel withdraw operation"
msgstr ""
-#: src/webex/renderHtml.tsx:225
+#: src/webex/renderHtml.tsx:226
#, c-format
msgid "Withdrawal fees:"
msgstr ""
-#: src/webex/renderHtml.tsx:226
+#: src/webex/renderHtml.tsx:227
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/renderHtml.tsx:227
+#: src/webex/renderHtml.tsx:228
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/renderHtml.tsx:233
+#: src/webex/renderHtml.tsx:234
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/renderHtml.tsx:234
+#: src/webex/renderHtml.tsx:235
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/renderHtml.tsx:235
+#: src/webex/renderHtml.tsx:236
#, c-format
msgid "Withdraw Fee"
msgstr ""
-#: src/webex/renderHtml.tsx:236
+#: src/webex/renderHtml.tsx:237
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/renderHtml.tsx:237
+#: src/webex/renderHtml.tsx:238
#, c-format
msgid "Deposit Fee"
msgstr ""
diff --git a/src/walletTypes.ts b/src/walletTypes.ts
index 0d18e4a9b..47360c660 100644
--- a/src/walletTypes.ts
+++ b/src/walletTypes.ts
@@ -507,8 +507,16 @@ export interface AcceptWithdrawalResponse {
* Details about a purchase, including refund status.
*/
export interface PurchaseDetails {
- contractTerms: ContractTerms,
- hasRefund: boolean,
- totalRefundAmount: AmountJson,
- totalRefundAndRefreshFees: AmountJson,
-} \ No newline at end of file
+ contractTerms: ContractTerms;
+ hasRefund: boolean;
+ totalRefundAmount: AmountJson;
+ totalRefundAndRefreshFees: AmountJson;
+}
+
+export interface WalletDiagnostics {
+ walletManifestVersion: string;
+ walletManifestDisplayVersion: string;
+ errors: string[];
+ firefoxIdbProblem: boolean;
+ dbOutdated: boolean;
+}
diff --git a/src/webex/components.ts b/src/webex/components.ts
deleted file mode 100644
index 1f5d18731..000000000
--- a/src/webex/components.ts
+++ /dev/null
@@ -1,63 +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/>
- */
-
-
-/**
- * General helper React components.
- */
-
-
-/**
- * Imports.
- */
-import * as React from "react";
-
-/**
- * Wrapper around state that will cause updates to the
- * containing component.
- */
-export interface StateHolder<T> {
- (): T;
- (newState: T): void;
-}
-
-/**
- * Component that doesn't hold its state in one object,
- * but has multiple state holders.
- */
-export abstract class ImplicitStateComponent<PropType> extends React.Component<PropType, any> {
- private _implicit = {needsUpdate: false, didMount: false};
- componentDidMount() {
- this._implicit.didMount = true;
- if (this._implicit.needsUpdate) {
- this.setState({} as any);
- }
- }
- makeState<StateType>(initial: StateType): StateHolder<StateType> {
- let state: StateType = initial;
- return (s?: StateType): StateType => {
- if (s !== undefined) {
- state = s;
- if (this._implicit.didMount) {
- this.setState({} as any);
- } else {
- this._implicit.needsUpdate = true;
- }
- }
- return state;
- };
- }
-}
diff --git a/src/webex/messages.ts b/src/webex/messages.ts
index 7e99cfc77..27d85a1f3 100644
--- a/src/webex/messages.ts
+++ b/src/webex/messages.ts
@@ -205,6 +205,11 @@ export interface MessageMap {
request: { talerPayUri: string };
response: walletTypes.PreparePayResult;
};
+
+ "get-diagnostics": {
+ request: { };
+ response: walletTypes.WalletDiagnostics;
+ };
}
diff --git a/src/webex/pages/add-auditor.tsx b/src/webex/pages/add-auditor.tsx
index 1ab6fdf9c..7e3e06322 100644
--- a/src/webex/pages/add-auditor.tsx
+++ b/src/webex/pages/add-auditor.tsx
@@ -20,20 +20,11 @@
* @author Florian Dold
*/
-
-import {
- CurrencyRecord,
-} from "../../dbTypes";
-
-import { ImplicitStateComponent, StateHolder } from "../components";
-import {
- getCurrencies,
- updateCurrency,
-} from "../wxApi";
-
-import * as React from "react";
-import * as ReactDOM from "react-dom";
+import { CurrencyRecord } from "../../dbTypes";
+import { getCurrencies, updateCurrency } from "../wxApi";
+import React, { useState } from "react";
import URI = require("urijs");
+import { registerMountPage } from "../renderHtml";
interface ConfirmAuditorProps {
url: string;
@@ -42,36 +33,39 @@ interface ConfirmAuditorProps {
expirationStamp: number;
}
-class ConfirmAuditor extends ImplicitStateComponent<ConfirmAuditorProps> {
- private addDone: StateHolder<boolean> = this.makeState(false);
- constructor(props: ConfirmAuditorProps) {
- super(props);
- }
+function ConfirmAuditor(props: ConfirmAuditorProps) {
+ const [addDone, setAddDone] = useState(false);
+
- async add() {
+ const add = async() => {
const currencies = await getCurrencies();
- let currency: CurrencyRecord|undefined;
+ let currency: CurrencyRecord | undefined;
for (const c of currencies) {
- if (c.name === this.props.currency) {
+ if (c.name === props.currency) {
currency = c;
}
}
if (!currency) {
- currency = { name: this.props.currency, auditors: [], fractionalDigits: 2, exchanges: [] };
+ currency = {
+ name: props.currency,
+ auditors: [],
+ fractionalDigits: 2,
+ exchanges: [],
+ };
}
const newAuditor = {
- auditorPub: this.props.auditorPub,
- baseUrl: this.props.url,
- expirationStamp: this.props.expirationStamp,
+ 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 === this.props.url) {
+ if (a.baseUrl === props.url) {
auditorFound = true;
// Update auditor if already found by URL.
currency.auditors[idx] = newAuditor;
@@ -84,47 +78,54 @@ class ConfirmAuditor extends ImplicitStateComponent<ConfirmAuditorProps> {
await updateCurrency(currency);
- this.addDone(true);
+ setAddDone(true);
}
- back() {
+ const back = () => {
window.history.back();
- }
-
- render(): JSX.Element {
- return (
- <div id="main">
- <p>Do you want to let <strong>{this.props.auditorPub}</strong> audit the currency "{this.props.currency}"?</p>
- {this.addDone() ?
- (
- <div>
- Auditor was added! You can also{" "}
- <a href={chrome.extension.getURL("/src/webex/pages/auditors.html")}>view and edit</a>{" "}
- auditors.
- </div>
- )
- :
- (
- <div>
- <button onClick={() => this.add()} className="pure-button pure-button-primary">Yes</button>
- <button onClick={() => this.back()} className="pure-button">No</button>
- </div>
- )
- }
- </div>
- );
- }
+ };
+
+ return (
+ <div id="main">
+ <p>
+ Do you want to let <strong>{props.auditorPub}</strong> audit the
+ currency "{props.currency}"?
+ </p>
+ {addDone ? (
+ <div>
+ Auditor was added! You can also{" "}
+ <a href={chrome.extension.getURL("/src/webex/pages/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>
+ );
}
-function main() {
+
+registerMountPage(() => {
const walletPageUrl = new URI(document.location.href);
- const query: any = JSON.parse((URI.parseQuery(walletPageUrl.query()) as any).req);
+ const query: any = JSON.parse(
+ (URI.parseQuery(walletPageUrl.query()) as any).req,
+ );
const url = query.url;
const currency: string = query.currency;
const auditorPub: string = query.auditorPub;
const expirationStamp = Number.parseInt(query.expirationStamp);
const args = { url, currency, auditorPub, expirationStamp };
- ReactDOM.render(<ConfirmAuditor {...args} />, document.getElementById("container")!);
-}
-
-document.addEventListener("DOMContentLoaded", main);
+ return <ConfirmAuditor {...args}/>;
+});
diff --git a/src/webex/pages/help/empty-wallet.html b/src/webex/pages/help/empty-wallet.html
deleted file mode 100644
index dd29d9689..000000000
--- a/src/webex/pages/help/empty-wallet.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset="utf-8">
- <title>GNU Taler Help - Empty Wallet</title>
- <link rel="icon" href="/img/icon.png">
- <meta name="description" content="">
- <link rel="stylesheet" type="text/css" href="/src/style/wallet.css">
- </head>
- <body>
- <div class="container" id="main">
- <div class="row">
- <div class="col-lg-12">
- <h2 lang="en">Your wallet is empty!</h2>
- <p lang="en">You have succeeded with installing the Taler wallet. However, before
- you can buy articles using the Taler wallet, you must withdraw electronic coins.
- This is typically done by visiting your bank's online banking Web site. There,
- you instruct your bank to transfer the funds to a Taler exchange operator. In
- return, your wallet will be allowed to withdraw electronic coins.</p>
- <p lang="en">At this stage, we are not aware of any regular exchange operators issuing
- coins in well-known currencies. However, to see how Taler would work, you
- can visit our "fake" bank at
- <a href="https://bank.demo.taler.net/">bank.demo.taler.net</a> to
- withdraw coins in the "KUDOS" currency that we created just for
- demonstrating the system.</p>
- </div>
- </div>
- </div>
- </body>
-</html>
diff --git a/src/webex/pages/payback.tsx b/src/webex/pages/payback.tsx
index f69a33493..934c28c0a 100644
--- a/src/webex/pages/payback.tsx
+++ b/src/webex/pages/payback.tsx
@@ -20,73 +20,54 @@
* @author Florian Dold
*/
-
/**
* Imports.
*/
-import {
- ReserveRecord,
-} from "../../dbTypes";
+import { ReserveRecord } from "../../dbTypes";
+import { renderAmount, registerMountPage } from "../renderHtml";
+import { getPaybackReserves, withdrawPaybackReserve } from "../wxApi";
+import * as React from "react";
+import { useState } from "react";
-import { ImplicitStateComponent, StateHolder } from "../components";
-import { renderAmount } from "../renderHtml";
-import {
- getPaybackReserves,
- withdrawPaybackReserve,
-} from "../wxApi";
+function Payback() {
+ const [reserves, setReserves] = useState<ReserveRecord[] | null>(null);
-import * as React from "react";
-import * as ReactDOM from "react-dom";
+ useState(() => {
+ const update = async () => {
+ const r = await getPaybackReserves();
+ setReserves(r);
+ };
-class Payback extends ImplicitStateComponent<{}> {
- private reserves: StateHolder<ReserveRecord[]|null> = this.makeState(null);
- constructor(props: {}) {
- super(props);
const port = chrome.runtime.connect();
port.onMessage.addListener((msg: any) => {
if (msg.notify) {
console.log("got notified");
- this.update();
+ update();
}
});
- this.update();
- }
+ });
- async update() {
- const reserves = await getPaybackReserves();
- this.reserves(reserves);
+ if (!reserves) {
+ return <span>loading ...</span>;
}
-
- withdrawPayback(pub: string) {
- withdrawPaybackReserve(pub);
+ if (reserves.length === 0) {
+ return <span>No reserves with payback available.</span>;
}
-
- render(): JSX.Element {
- const reserves = this.reserves();
- if (!reserves) {
- return <span>loading ...</span>;
- }
- if (reserves.length === 0) {
- return <span>No reserves with payback available.</span>;
- }
- return (
- <div>
- {reserves.map((r) => (
- <div>
- <h2>Reserve for ${renderAmount(r.current_amount!)}</h2>
- <ul>
- <li>Exchange: ${r.exchange_base_url}</li>
- </ul>
- <button onClick={() => this.withdrawPayback(r.reserve_pub)}>Withdraw again</button>
- </div>
- ))}
- </div>
- );
- }
-}
-
-function main() {
- ReactDOM.render(<Payback />, document.getElementById("container")!);
+ return (
+ <div>
+ {reserves.map(r => (
+ <div>
+ <h2>Reserve for ${renderAmount(r.current_amount!)}</h2>
+ <ul>
+ <li>Exchange: ${r.exchange_base_url}</li>
+ </ul>
+ <button onClick={() => withdrawPaybackReserve(r.reserve_pub)}>
+ Withdraw again
+ </button>
+ </div>
+ ))}
+ </div>
+ );
}
-document.addEventListener("DOMContentLoaded", main);
+registerMountPage(() => <Payback />);
diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx
index 2cdfd8235..91ab515e4 100644
--- a/src/webex/pages/popup.tsx
+++ b/src/webex/pages/popup.tsx
@@ -14,7 +14,6 @@
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.
@@ -38,7 +37,7 @@ import {
WalletBalanceEntry,
} from "../../walletTypes";
-import { abbrev, renderAmount } from "../renderHtml";
+import { abbrev, renderAmount, PageLink } from "../renderHtml";
import * as wxApi from "../wxApi";
import * as React from "react";
@@ -47,7 +46,7 @@ import * as ReactDOM from "react-dom";
import URI = require("urijs");
function onUpdateNotification(f: () => void): () => void {
- const port = chrome.runtime.connect({name: "notifications"});
+ const port = chrome.runtime.connect({ name: "notifications" });
const listener = () => {
f();
};
@@ -57,7 +56,6 @@ function onUpdateNotification(f: () => void): () => void {
};
}
-
class Router extends React.Component<any, any> {
static setRoute(s: string): void {
window.location.hash = s;
@@ -92,13 +90,12 @@ class Router extends React.Component<any, any> {
console.log("router unmounted");
}
-
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) => {
+ 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;
@@ -119,7 +116,6 @@ class Router extends React.Component<any, any> {
}
}
-
interface TabProps {
target: string;
children?: React.ReactNode;
@@ -141,7 +137,6 @@ function Tab(props: TabProps) {
);
}
-
class WalletNavBar extends React.Component<any, any> {
private cancelSubscription: any;
@@ -161,20 +156,14 @@ class WalletNavBar extends React.Component<any, any> {
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="/debug">
- {i18n.str`Debug`}
- </Tab>
- </div>);
+ <Tab target="/balance">{i18n.str`Balance`}</Tab>
+ <Tab target="/history">{i18n.str`History`}</Tab>
+ <Tab target="/debug">{i18n.str`Debug`}</Tab>
+ </div>
+ );
}
}
-
function ExtensionLink(props: any) {
const onClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
chrome.tabs.create({
@@ -189,7 +178,6 @@ function ExtensionLink(props: any) {
);
}
-
/**
* Render an amount as a large number with a small currency symbol.
*/
@@ -197,10 +185,21 @@ function bigAmount(amount: AmountJson): JSX.Element {
const v = amount.value + amount.fraction / Amounts.fractionalBase;
return (
<span>
- <span style={{fontSize: "300%"}}>{v}</span>
- {" "}
+ <span style={{ fontSize: "300%" }}>{v}</span>{" "}
<span>{amount.currency}</span>
- </span>
+ </span>
+ );
+}
+
+function EmptyBalanceView() {
+ return (
+ <div>
+ <i18n.Translate wrap="p">
+ You have no balance to show. Need some{" "}
+ <PageLink pageName="welcome.html">help</PageLink> getting
+ started?
+ </i18n.Translate>
+ </div>
);
}
@@ -245,57 +244,44 @@ class WalletBalanceView extends React.Component<any, any> {
this.setState({});
}
- renderEmpty(): JSX.Element {
- const helpLink = (
- <ExtensionLink target="/src/webex/pages/help/empty-wallet.html">
- {i18n.str`help`}
- </ExtensionLink>
- );
- return (
- <div>
- <i18n.Translate wrap="p">
- You have no balance to show. Need some
- {" "}<span>{helpLink}</span>{" "}
- getting started?
- </i18n.Translate>
- </div>
- );
- }
-
formatPending(entry: WalletBalanceEntry): JSX.Element {
let incoming: JSX.Element | undefined;
let payment: JSX.Element | undefined;
- console.log("available: ", entry.pendingIncoming ? renderAmount(entry.available) : null);
- console.log("incoming: ", entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null);
+ console.log(
+ "available: ",
+ entry.pendingIncoming ? renderAmount(entry.available) : null,
+ );
+ console.log(
+ "incoming: ",
+ entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null,
+ );
if (Amounts.isNonZero(entry.pendingIncoming)) {
incoming = (
<i18n.Translate wrap="span">
- <span style={{color: "darkgreen"}}>
+ <span style={{ color: "darkgreen" }}>
{"+"}
{renderAmount(entry.pendingIncoming)}
- </span>
- {" "}
+ </span>{" "}
incoming
- </i18n.Translate>
+ </i18n.Translate>
);
}
if (Amounts.isNonZero(entry.pendingPayment)) {
payment = (
<i18n.Translate wrap="span">
- <span style={{color: "red"}}>
+ <span style={{ color: "red" }}>
{"-"}
{renderAmount(entry.pendingPayment)}
- </span>
- {" "}
+ </span>{" "}
being spent
</i18n.Translate>
);
}
- const l = [incoming, payment].filter((x) => x !== undefined);
+ const l = [incoming, payment].filter(x => x !== undefined);
if (l.length === 0) {
return <span />;
}
@@ -303,49 +289,41 @@ class WalletBalanceView extends React.Component<any, any> {
if (l.length === 1) {
return <span>({l})</span>;
}
- return <span>({l[0]}, {l[1]})</span>;
-
+ return (
+ <span>
+ ({l[0]}, {l[1]})
+ </span>
+ );
}
render(): JSX.Element {
const wallet = this.balance;
if (this.gotError) {
- return i18n.str`Error: could not retrieve balance information.`;
+ return (
+ <div>
+ <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);
- let paybackAvailable = false;
- const listing = Object.keys(wallet.byCurrency).map((key) => {
+ const listing = Object.keys(wallet.byCurrency).map(key => {
const entry: WalletBalanceEntry = wallet.byCurrency[key];
- if (entry.paybackAmount.value !== 0 || entry.paybackAmount.fraction !== 0) {
- paybackAvailable = true;
- }
return (
<p>
- {bigAmount(entry.available)}
- {" "}
- {this.formatPending(entry)}
+ {bigAmount(entry.available)} {this.formatPending(entry)}
</p>
);
});
- const makeLink = (page: string, name: string) => {
- const url = chrome.extension.getURL(`/src/webex/pages/${page}`);
- return <div><a className="actionLink" href={url} target="_blank">{name}</a></div>;
- };
- return (
- <div>
- {listing.length > 0 ? listing : this.renderEmpty()}
- {paybackAvailable && makeLink("payback", i18n.str`Payback`)}
- {makeLink("return-coins.html#dissolve", i18n.str`Return Electronic Cash to Bank Account`)}
- {makeLink("auditors.html", i18n.str`Manage Trusted Auditors and Exchanges`)}
- </div>
- );
+ return <div>{listing.length > 0 ? listing : <EmptyBalanceView />}</div>;
}
}
-
function formatHistoryItem(historyItem: HistoryRecord) {
const d = historyItem.detail;
console.log("hist item", historyItem);
@@ -353,13 +331,12 @@ function formatHistoryItem(historyItem: HistoryRecord) {
case "create-reserve":
return (
<i18n.Translate wrap="p">
- Bank requested reserve (<span>{abbrev(d.reservePub)}</span>) for
- {" "}
+ Bank requested reserve (<span>{abbrev(d.reservePub)}</span>) for{" "}
<span>{renderAmount(d.requestedAmount)}</span>.
</i18n.Translate>
);
case "confirm-reserve": {
- const exchange = (new URI(d.exchangeBaseUrl)).host();
+ const exchange = new URI(d.exchangeBaseUrl).host();
const pub = abbrev(d.reservePub);
return (
<i18n.Translate wrap="p">
@@ -372,30 +349,37 @@ function formatHistoryItem(historyItem: HistoryRecord) {
case "offer-contract": {
return (
<i18n.Translate wrap="p">
- Merchant <em>{abbrev(d.merchantName, 15)}</em> offered
- contract <span>{abbrev(d.contractTermsHash)}</span>.
+ Merchant <em>{abbrev(d.merchantName, 15)}</em> offered contract{" "}
+ <span>{abbrev(d.contractTermsHash)}</span>.
</i18n.Translate>
);
}
case "depleted-reserve": {
- const exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??";
+ const exchange = d.exchangeBaseUrl
+ ? new URI(d.exchangeBaseUrl).host()
+ : "??";
const amount = renderAmount(d.requestedAmount);
const pub = abbrev(d.reservePub);
return (
<i18n.Translate wrap="p">
- Withdrew <span>{amount}</span> from <span>{exchange}</span> (<span>{pub}</span>).
+ Withdrew <span>{amount}</span> from <span>{exchange}</span> (
+ <span>{pub}</span>).
</i18n.Translate>
);
}
case "pay": {
const url = d.fulfillmentUrl;
const merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
- const fulfillmentLinkElem = <a href={url} onClick={openTab(url)}>view product</a>;
+ const fulfillmentLinkElem = (
+ <a href={url} onClick={openTab(url)}>
+ view product
+ </a>
+ );
return (
<i18n.Translate wrap="p">
- Paid <span>{renderAmount(d.amount)}</span> to merchant <span>{merchantElem}</span>.
- <span> </span>
- (<span>{fulfillmentLinkElem}</span>)
+ Paid <span>{renderAmount(d.amount)}</span> to merchant{" "}
+ <span>{merchantElem}</span>.<span> </span>(
+ <span>{fulfillmentLinkElem}</span>)
</i18n.Translate>
);
}
@@ -403,12 +387,15 @@ function formatHistoryItem(historyItem: HistoryRecord) {
const merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
return (
<i18n.Translate wrap="p">
- Merchant <span>{merchantElem}</span> gave a refund over <span>{renderAmount(d.refundAmount)}</span>.
+ Merchant <span>{merchantElem}</span> gave a refund over{" "}
+ <span>{renderAmount(d.refundAmount)}</span>.
</i18n.Translate>
);
}
case "tip": {
- const tipPageUrl = new URI(chrome.extension.getURL("/src/webex/pages/tip.html"));
+ const tipPageUrl = new URI(
+ chrome.extension.getURL("/src/webex/pages/tip.html"),
+ );
const params = { tip_id: d.tipId, merchant_domain: d.merchantDomain };
const url = tipPageUrl.query(params).href();
const tipLink = <a href={url} onClick={openTab(url)}>{i18n.str`tip`}</a>;
@@ -416,19 +403,23 @@ function formatHistoryItem(historyItem: HistoryRecord) {
return (
<>
<i18n.Translate wrap="p">
- Merchant <span>{d.merchantDomain}</span> gave
- a <span>{tipLink}</span> of <span>{renderAmount(d.amount)}</span>.
+ Merchant <span>{d.merchantDomain}</span> gave a{" "}
+ <span>{tipLink}</span> of <span>{renderAmount(d.amount)}</span>.
</i18n.Translate>
- <span> { d.accepted ? null : <i18n.Translate>You did not accept the tip yet.</i18n.Translate> }</span>
+ <span>
+ {" "}
+ {d.accepted ? null : (
+ <i18n.Translate>You did not accept the tip yet.</i18n.Translate>
+ )}
+ </span>
</>
);
}
default:
- return (<p>{i18n.str`Unknown event (${historyItem.type})`}</p>);
+ return <p>{i18n.str`Unknown event (${historyItem.type})`}</p>;
}
}
-
class WalletHistory extends React.Component<any, any> {
private myHistory: any[];
private gotError = false;
@@ -445,7 +436,7 @@ class WalletHistory extends React.Component<any, any> {
}
update() {
- chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
+ chrome.runtime.sendMessage({ type: "get-history" }, resp => {
if (this.unmounted) {
return;
}
@@ -480,7 +471,7 @@ class WalletHistory extends React.Component<any, any> {
const item = (
<div className="historyItem">
<div className="historyDate">
- {(new Date(record.timestamp)).toString()}
+ {new Date(record.timestamp).toString()}
</div>
{formatHistoryItem(record)}
</div>
@@ -494,10 +485,8 @@ class WalletHistory extends React.Component<any, any> {
}
return <p>{i18n.str`Your wallet has no events recorded.`}</p>;
}
-
}
-
function reload() {
try {
chrome.runtime.reload();
@@ -508,43 +497,43 @@ function reload() {
}
function confirmReset() {
- if (confirm("Do you want to IRREVOCABLY DESTROY everything inside your" +
- " wallet and LOSE ALL YOUR COINS?")) {
+ 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) {
- return (<div>
- <p>Debug tools:</p>
- <button onClick={openExtensionPage("/src/webex/pages/popup.html")}>
- wallet tab
- </button>
- <button onClick={openExtensionPage("/src/webex/pages/benchmark.html")}>
- benchmark
- </button>
- <button onClick={openExtensionPage("/src/webex/pages/show-db.html")}>
- show db
- </button>
- <button onClick={openExtensionPage("/src/webex/pages/tree.html")}>
- show tree
- </button>
- <button onClick={openExtensionPage("/src/webex/pages/logs.html")}>
- show logs
- </button>
- <br />
- <button onClick={confirmReset}>
- reset
- </button>
- <button onClick={reload}>
- reload chrome extension
- </button>
- </div>);
+ return (
+ <div>
+ <p>Debug tools:</p>
+ <button onClick={openExtensionPage("/src/webex/pages/popup.html")}>
+ wallet tab
+ </button>
+ <button onClick={openExtensionPage("/src/webex/pages/benchmark.html")}>
+ benchmark
+ </button>
+ <button onClick={openExtensionPage("/src/webex/pages/show-db.html")}>
+ show db
+ </button>
+ <button onClick={openExtensionPage("/src/webex/pages/tree.html")}>
+ show tree
+ </button>
+ <button onClick={openExtensionPage("/src/webex/pages/logs.html")}>
+ show logs
+ </button>
+ <br />
+ <button onClick={confirmReset}>reset</button>
+ <button onClick={reload}>reload chrome extension</button>
+ </div>
+ );
}
-
function openExtensionPage(page: string) {
return () => {
chrome.tabs.create({
@@ -553,7 +542,6 @@ function openExtensionPage(page: string) {
};
}
-
function openTab(page: string) {
return (evt: React.SyntheticEvent<any>) => {
evt.preventDefault();
@@ -563,15 +551,14 @@ function openTab(page: string) {
};
}
-
const el = (
<div>
<WalletNavBar />
- <div style={{margin: "1em"}}>
+ <div style={{ margin: "1em" }}>
<Router>
- <WalletBalanceView route="/balance" default/>
- <WalletHistory route="/history"/>
- <WalletDebug route="/debug"/>
+ <WalletBalanceView route="/balance" default />
+ <WalletHistory route="/history" />
+ <WalletDebug route="/debug" />
</Router>
</div>
</div>
@@ -581,5 +568,5 @@ runOnceWhenReady(() => {
ReactDOM.render(el, document.getElementById("content")!);
// Will be used by the backend to detect when the popup gets closed,
// so we can clear notifications
- chrome.runtime.connect({name: "popup"});
+ chrome.runtime.connect({ name: "popup" });
});
diff --git a/src/webex/pages/tree.html b/src/webex/pages/tree.html
deleted file mode 100644
index 0c0a368b3..000000000
--- a/src/webex/pages/tree.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
- <meta charset="UTF-8">
- <title>Taler Wallet: Tree View</title>
-
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
-
- <link rel="icon" href="/img/icon.png">
-
- <script src="/dist/page-common-bundle.js"></script>
- <script src="/dist/tree-bundle.js"></script>
-
- <style>
- .tree-item {
- margin: 2em;
- border-radius: 5px;
- border: 1px solid gray;
- padding: 1em;
- }
- </style>
-
- <body>
- <div id="container"></div>
- </body>
-</html>
diff --git a/src/webex/pages/tree.tsx b/src/webex/pages/tree.tsx
deleted file mode 100644
index 67e58a1df..000000000
--- a/src/webex/pages/tree.tsx
+++ /dev/null
@@ -1,402 +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/>
- */
-
-/**
- * Show contents of the wallet as a tree.
- *
- * @author Florian Dold
- */
-
-
-import { getTalerStampDate } from "../../helpers";
-
-import {
- CoinRecord,
- CoinStatus,
- DenominationRecord,
- ExchangeRecord,
- PreCoinRecord,
- ReserveRecord,
-} from "../../dbTypes";
-
-import { ImplicitStateComponent, StateHolder } from "../components";
-import {
- getCoins,
- getDenoms,
- getExchanges,
- getPreCoins,
- getReserves,
- payback,
- refresh,
-} from "../wxApi";
-
-import { ExpanderText, renderAmount } from "../renderHtml";
-
-import * as React from "react";
-import * as ReactDOM from "react-dom";
-
-interface ReserveViewProps {
- reserve: ReserveRecord;
-}
-
-class ReserveView extends React.Component<ReserveViewProps, {}> {
- render(): JSX.Element {
- const r: ReserveRecord = this.props.reserve;
- return (
- <div className="tree-item">
- <ul>
- <li>Key: {r.reserve_pub}</li>
- <li>Created: {(new Date(r.created * 1000).toString())}</li>
- <li>Current: {r.current_amount ? renderAmount(r.current_amount!) : "null"}</li>
- <li>Requested: {renderAmount(r.requested_amount)}</li>
- <li>Confirmed: {r.timestamp_confirmed}</li>
- </ul>
- </div>
- );
- }
-}
-
-interface ReserveListProps {
- exchangeBaseUrl: string;
-}
-
-interface ToggleProps {
- expanded: StateHolder<boolean>;
-}
-
-class Toggle extends ImplicitStateComponent<ToggleProps> {
- renderButton() {
- const show = () => {
- this.props.expanded(true);
- this.setState({});
- };
- const hide = () => {
- this.props.expanded(false);
- this.setState({});
- };
- if (this.props.expanded()) {
- return <button onClick={hide}>hide</button>;
- }
- return <button onClick={show}>show</button>;
-
- }
- render() {
- return (
- <div style={{display: "inline"}}>
- {this.renderButton()}
- {this.props.expanded() ? this.props.children : []}
- </div>);
- }
-}
-
-
-interface CoinViewProps {
- coin: CoinRecord;
-}
-
-interface RefreshDialogProps {
- coin: CoinRecord;
-}
-
-class RefreshDialog extends ImplicitStateComponent<RefreshDialogProps> {
- private refreshRequested = this.makeState<boolean>(false);
- render(): JSX.Element {
- if (!this.refreshRequested()) {
- return (
- <div style={{display: "inline"}}>
- <button onClick={() => this.refreshRequested(true)}>refresh</button>
- </div>
- );
- }
- return (
- <div>
- Refresh amount: <input type="text" size={10} />
- <button onClick={() => refresh(this.props.coin.coinPub)}>ok</button>
- <button onClick={() => this.refreshRequested(false)}>cancel</button>
- </div>
- );
- }
-}
-
-class CoinView extends React.Component<CoinViewProps, {}> {
- render() {
- const c = this.props.coin;
- return (
- <div className="tree-item">
- <ul>
- <li>Key: {c.coinPub}</li>
- <li>Current amount: {renderAmount(c.currentAmount)}</li>
- <li>Denomination: <ExpanderText text={c.denomPub} /></li>
- <li>Suspended: {(c.suspended || false).toString()}</li>
- <li>Status: {CoinStatus[c.status]}</li>
- <li><RefreshDialog coin={c} /></li>
- <li><button onClick={() => payback(c.coinPub)}>Payback</button></li>
- </ul>
- </div>
- );
- }
-}
-
-
-interface PreCoinViewProps {
- precoin: PreCoinRecord;
-}
-
-class PreCoinView extends React.Component<PreCoinViewProps, {}> {
- render() {
- const c = this.props.precoin;
- return (
- <div className="tree-item">
- <ul>
- <li>Key: {c.coinPub}</li>
- </ul>
- </div>
- );
- }
-}
-
-interface CoinListProps {
- exchangeBaseUrl: string;
-}
-
-class CoinList extends ImplicitStateComponent<CoinListProps> {
- private coins = this.makeState<CoinRecord[] | null>(null);
- private expanded = this.makeState<boolean>(false);
-
- constructor(props: CoinListProps) {
- super(props);
- this.update(props);
- }
-
- async update(props: CoinListProps) {
- const coins = await getCoins(props.exchangeBaseUrl);
- this.coins(coins);
- }
-
- componentWillReceiveProps(newProps: CoinListProps) {
- this.update(newProps);
- }
-
- render(): JSX.Element {
- if (!this.coins()) {
- return <div>...</div>;
- }
- return (
- <div className="tree-item">
- Coins ({this.coins() !.length.toString()})
- {" "}
- <Toggle expanded={this.expanded}>
- {this.coins() !.map((c) => <CoinView coin={c} />)}
- </Toggle>
- </div>
- );
- }
-}
-
-
-interface PreCoinListProps {
- exchangeBaseUrl: string;
-}
-
-class PreCoinList extends ImplicitStateComponent<PreCoinListProps> {
- private precoins = this.makeState<PreCoinRecord[] | null>(null);
- private expanded = this.makeState<boolean>(false);
-
- constructor(props: PreCoinListProps) {
- super(props);
- this.update();
- }
-
- async update() {
- const precoins = await getPreCoins(this.props.exchangeBaseUrl);
- this.precoins(precoins);
- }
-
- render(): JSX.Element {
- if (!this.precoins()) {
- return <div>...</div>;
- }
- return (
- <div className="tree-item">
- Planchets ({this.precoins() !.length.toString()})
- {" "}
- <Toggle expanded={this.expanded}>
- {this.precoins() !.map((c) => <PreCoinView precoin={c} />)}
- </Toggle>
- </div>
- );
- }
-}
-
-interface DenominationListProps {
- exchange: ExchangeRecord;
-}
-
-class DenominationList extends ImplicitStateComponent<DenominationListProps> {
- private expanded = this.makeState<boolean>(false);
- private denoms = this.makeState<undefined|DenominationRecord[]>(undefined);
-
- constructor(props: DenominationListProps) {
- super(props);
- this.update();
- }
-
- async update() {
- const d = await getDenoms(this.props.exchange.baseUrl);
- this.denoms(d);
- }
-
- renderDenom(d: DenominationRecord) {
- return (
- <div className="tree-item">
- <ul>
- <li>Offered: {d.isOffered ? "yes" : "no"}</li>
- <li>Value: {renderAmount(d.value)}</li>
- <li>Withdraw fee: {renderAmount(d.feeWithdraw)}</li>
- <li>Refresh fee: {renderAmount(d.feeRefresh)}</li>
- <li>Deposit fee: {renderAmount(d.feeDeposit)}</li>
- <li>Refund fee: {renderAmount(d.feeRefund)}</li>
- <li>Start: {getTalerStampDate(d.stampStart)!.toString()}</li>
- <li>Withdraw expiration: {getTalerStampDate(d.stampExpireWithdraw)!.toString()}</li>
- <li>Legal expiration: {getTalerStampDate(d.stampExpireLegal)!.toString()}</li>
- <li>Deposit expiration: {getTalerStampDate(d.stampExpireDeposit)!.toString()}</li>
- <li>Denom pub: <ExpanderText text={d.denomPub} /></li>
- </ul>
- </div>
- );
- }
-
- render(): JSX.Element {
- const denoms = this.denoms();
- if (!denoms) {
- return (
- <div className="tree-item">
- Denominations (...)
- {" "}
- <Toggle expanded={this.expanded}>
- ...
- </Toggle>
- </div>
- );
- }
- return (
- <div className="tree-item">
- Denominations ({denoms.length.toString()})
- {" "}
- <Toggle expanded={this.expanded}>
- {denoms.map((d) => this.renderDenom(d))}
- </Toggle>
- </div>
- );
- }
-}
-
-
-class ReserveList extends ImplicitStateComponent<ReserveListProps> {
- private reserves = this.makeState<ReserveRecord[] | null>(null);
- private expanded = this.makeState<boolean>(false);
-
- constructor(props: ReserveListProps) {
- super(props);
- this.update();
- }
-
- async update() {
- const reserves = await getReserves(this.props.exchangeBaseUrl);
- this.reserves(reserves);
- }
-
- render(): JSX.Element {
- if (!this.reserves()) {
- return <div>...</div>;
- }
- return (
- <div className="tree-item">
- Reserves ({this.reserves() !.length.toString()})
- {" "}
- <Toggle expanded={this.expanded}>
- {this.reserves() !.map((r) => <ReserveView reserve={r} />)}
- </Toggle>
- </div>
- );
- }
-}
-
-interface ExchangeProps {
- exchange: ExchangeRecord;
-}
-
-class ExchangeView extends React.Component<ExchangeProps, {}> {
- render(): JSX.Element {
- const e = this.props.exchange;
- return (
- <div className="tree-item">
- <ul>
- <li>Exchange Base Url: {this.props.exchange.baseUrl}</li>
- <li>Master public key: <ExpanderText text={this.props.exchange.masterPublicKey} /></li>
- </ul>
- <DenominationList exchange={e} />
- <ReserveList exchangeBaseUrl={this.props.exchange.baseUrl} />
- <CoinList exchangeBaseUrl={this.props.exchange.baseUrl} />
- <PreCoinList exchangeBaseUrl={this.props.exchange.baseUrl} />
- </div>
- );
- }
-}
-
-interface ExchangesListState {
- exchanges?: ExchangeRecord[];
-}
-
-class ExchangesList extends React.Component<{}, ExchangesListState> {
- 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() {
- const exchanges = await getExchanges();
- console.log("exchanges: ", exchanges);
- this.setState({ exchanges });
- }
-
- render(): JSX.Element {
- const exchanges = this.state.exchanges;
- if (!exchanges) {
- return <span>...</span>;
- }
- return (
- <div className="tree-item">
- Exchanges ({exchanges.length.toString()}):
- {exchanges.map((e) => <ExchangeView exchange={e} />)}
- </div>
- );
- }
-}
-
-function main() {
- ReactDOM.render(<ExchangesList />, document.getElementById("container")!);
-}
-
-document.addEventListener("DOMContentLoaded", main);
diff --git a/src/webex/pages/welcome.html b/src/webex/pages/welcome.html
new file mode 100644
index 000000000..9a96d04a7
--- /dev/null
+++ b/src/webex/pages/welcome.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="UTF-8">
+ <title>Taler Wallet: Withdraw</title>
+
+ <link rel="icon" href="/img/icon.png">
+ <link rel="stylesheet" type="text/css" href="../style/pure.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/welcome-bundle.js"></script>
+
+</head>
+
+<body>
+ <section id="main">
+ <h1>GNU Taler Wallet Installed!</h1>
+ <div id="container">Loading...</div>
+ </section>
+</body>
+
+</html>
diff --git a/src/webex/pages/welcome.tsx b/src/webex/pages/welcome.tsx
new file mode 100644
index 000000000..1026e6e6e
--- /dev/null
+++ b/src/webex/pages/welcome.tsx
@@ -0,0 +1,113 @@
+/*
+ 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 { registerMountPage, PageLink } from "../renderHtml";
+import { WalletDiagnostics } from "../../walletTypes";
+
+function Diagnostics() {
+ 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 () => {
+ 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 <p>Running diagnostics ... everything looks fine.</p>;
+ } 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>{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>;
+}
+
+function Welcome() {
+ return (
+ <>
+ <p>Thank you for installing the wallet.</p>
+ <h2>First Steps</h2>
+ <p>
+ Check out <a href="https://demo.taler.net/">demo.taler.net</a> for a
+ demo.
+ </p>
+ <h2>Troubleshooting</h2>
+ <Diagnostics />
+ </>
+ );
+}
+
+registerMountPage(() => <Welcome />);
diff --git a/src/webex/pages/withdraw.html b/src/webex/pages/withdraw.html
index 8b1e59b1d..e5c527275 100644
--- a/src/webex/pages/withdraw.html
+++ b/src/webex/pages/withdraw.html
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
- <title>Taler Wallet: Select Taler Provider</title>
+ <title>Taler Wallet: Withdraw</title>
<link rel="icon" href="/img/icon.png">
<link rel="stylesheet" type="text/css" href="../style/pure.css">
diff --git a/src/webex/pages/withdraw.tsx b/src/webex/pages/withdraw.tsx
index 66617373b..39b27f2d8 100644
--- a/src/webex/pages/withdraw.tsx
+++ b/src/webex/pages/withdraw.tsx
@@ -21,21 +21,13 @@
* @author Florian Dold
*/
-import { canonicalizeBaseUrl } from "../../helpers";
-import * as i18n from "../../i18n";
-import { AmountJson } from "../../amounts";
-import * as Amounts from "../../amounts";
+import * as i18n from "../../i18n";
-import { CurrencyRecord } from "../../dbTypes";
import {
- CreateReserveResponse,
- ReserveCreationInfo,
WithdrawDetails,
} from "../../walletTypes";
-import { ImplicitStateComponent, StateHolder } from "../components";
-
import { WithdrawDetailView, renderAmount } from "../renderHtml";
import React, { useState, useEffect } from "react";
diff --git a/src/webex/renderHtml.tsx b/src/webex/renderHtml.tsx
index 1c50aa1ad..0f736d1b6 100644
--- a/src/webex/renderHtml.tsx
+++ b/src/webex/renderHtml.tsx
@@ -26,22 +26,16 @@
*/
import { AmountJson } from "../amounts";
import * as Amounts from "../amounts";
-
import {
DenominationRecord,
} from "../dbTypes";
import {
ReserveCreationInfo,
} from "../walletTypes";
-
-
-import { ImplicitStateComponent } from "./components";
-
import * as moment from "moment";
-
import * as i18n from "../i18n";
-
-import * as React from "react";
+import React from "react";
+import ReactDOM from "react-dom";
/**
@@ -274,49 +268,16 @@ interface ExpanderTextProps {
text: string;
}
+
/**
* Show a heading with a toggle to show/hide the expandable content.
*/
-export class ExpanderText extends ImplicitStateComponent<ExpanderTextProps> {
- private expanded = this.makeState<boolean>(false);
- private textArea: any = undefined;
-
- componentDidUpdate() {
- if (this.expanded() && this.textArea) {
- this.textArea.focus();
- this.textArea.scrollTop = 0;
- }
- }
-
- render(): JSX.Element {
- if (!this.expanded()) {
- return (
- <span onClick={() => { this.expanded(true); }}>
- {(this.props.text.length <= 10)
- ? this.props.text
- : (
- <span>
- {this.props.text.substring(0, 10)}
- <span style={{textDecoration: "underline"}}>...</span>
- </span>
- )
- }
- </span>
- );
- }
- return (
- <textarea
- readOnly
- style={{display: "block"}}
- onBlur={() => this.expanded(false)}
- ref={(e) => this.textArea = e}>
- {this.props.text}
- </textarea>
- );
- }
+export function ExpanderText({ text }: ExpanderTextProps) {
+ return <span>{text}</span>;
}
+
export interface LoadingButtonProps {
loading: boolean;
}
@@ -340,4 +301,35 @@ export function ProgressButton(
{props.children}
</button>
);
+}
+
+export function registerMountPage(mainFn: () => React.ReactElement) {
+ async function main() {
+ try {
+ const mainElement = mainFn();
+ const container = document.getElementById("container");
+ if (!container) {
+ throw Error("container not found, can't mount page contents");
+ }
+ ReactDOM.render(
+ mainElement,
+ container,
+ );
+ } catch (e) {
+ document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`;
+ console.error("got error", e);
+ }
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", main);
+ return;
+ } else {
+ main();
+ }
+}
+
+export function PageLink(props: React.PropsWithChildren<{pageName: string}>) {
+ const url = chrome.extension.getURL(`/src/webex/pages/${props.pageName}`);
+ return <a className="actionLink" href={url} target="_blank">{props.children}</a>;
} \ No newline at end of file
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
index 7e4d17e37..61dc2ca69 100644
--- a/src/webex/wxApi.ts
+++ b/src/webex/wxApi.ts
@@ -42,6 +42,7 @@ import {
TipStatus,
WalletBalance,
PurchaseDetails,
+ WalletDiagnostics,
} from "../walletTypes";
import {
@@ -396,3 +397,10 @@ export function preparePay(talerPayUri: string) {
export function acceptWithdrawal(talerWithdrawUri: string, selectedExchange: string) {
return callBackend("accept-withdrawal", { talerWithdrawUri, selectedExchange });
}
+
+/**
+ * Get diagnostics information
+ */
+export function getDiagnostics(): Promise<WalletDiagnostics> {
+ return callBackend("get-diagnostics", {});
+}
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index ea43f65c2..0cfaf2346 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -25,40 +25,34 @@
*/
import { BrowserHttpLib } from "../http";
import * as logging from "../logging";
-
import { AmountJson } from "../amounts";
-
import {
ConfirmReserveRequest,
CreateReserveRequest,
Notifier,
ReturnCoinsRequest,
+ WalletDiagnostics,
} from "../walletTypes";
-
import { Wallet } from "../wallet";
-
import { isFirefox } from "./compat";
-
-import { PurchaseRecord, WALLET_DB_VERSION } from "../dbTypes";
-
+import { WALLET_DB_VERSION } from "../dbTypes";
import { openTalerDb, exportDb, importDb, deleteDb } from "../db";
-
import { ChromeBadge } from "./chromeBadge";
import { MessageType } from "./messages";
import * as wxApi from "./wxApi";
-
import URI = require("urijs");
import Port = chrome.runtime.Port;
import MessageSender = chrome.runtime.MessageSender;
import { BrowserCryptoWorkerFactory } from "../crypto/cryptoApi";
+import { OpenedPromise, openPromise } from "../promiseUtils";
const NeedsWallet = Symbol("NeedsWallet");
-function handleMessage(
+async function handleMessage(
sender: MessageSender,
type: MessageType,
detail: any,
-): any {
+): Promise<any> {
function assertNotFound(t: never): never {
console.error(`Request type ${t as string} unknown`);
console.error(`Request detail was ${detail}`);
@@ -251,7 +245,7 @@ function handleMessage(
const resp: wxApi.UpgradeResponse = {
currentDbVersion: WALLET_DB_VERSION.toString(),
dbResetRequired,
- oldDbVersion: (oldDbVersion || "unknown").toString(),
+ oldDbVersion: (outdatedDbVersion || "unknown").toString(),
};
return resp;
}
@@ -314,6 +308,39 @@ function handleMessage(
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().preparePay(detail.talerPayUri);
default:
@@ -351,7 +378,7 @@ async function dispatch(
error: {
message: e.message,
stack,
- }
+ },
});
} catch (e) {
console.log(e);
@@ -441,26 +468,24 @@ function makeSyncWalletRedirect(
return { redirectUrl: outerUrl.href() };
}
-// Rate limit cache for executePayment operations, to break redirect loops
-let rateLimitCache: { [n: number]: number } = {};
-
-function clearRateLimitCache() {
- rateLimitCache = {};
-}
-
/**
* 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 oldDbVersion: number | undefined;
+let outdatedDbVersion: number | undefined;
+
+let walletInit: OpenedPromise<void> = openPromise<void>();
function handleUpgradeUnsupported(oldDbVersion: number, newDbVersion: number) {
console.log("DB migration not supported");
+ outdatedDbVersion = oldDbVersion;
chrome.tabs.create({
url: chrome.extension.getURL("/src/webex/pages/reset-required.html"),
});
@@ -473,20 +498,25 @@ async function reinitWallet() {
currentWallet.stop();
currentWallet = undefined;
}
+ currentDatabase = undefined;
setBadgeText({ text: "" });
const badge = new ChromeBadge();
- let db: IDBDatabase;
try {
- db = await openTalerDb(indexedDB, reinitWallet, handleUpgradeUnsupported);
+ currentDatabase = await openTalerDb(
+ indexedDB,
+ reinitWallet,
+ handleUpgradeUnsupported,
+ );
} catch (e) {
console.error("could not open database", e);
+ walletInit.reject(e);
return;
}
const http = new BrowserHttpLib();
const notifier = new ChromeNotifier();
console.log("setting wallet");
const wallet = new Wallet(
- db,
+ currentDatabase,
http,
badge,
notifier,
@@ -495,6 +525,7 @@ async function reinitWallet() {
// Useful for debugging in the background page.
(window as any).talerWallet = wallet;
currentWallet = wallet;
+ walletInit.resolve();
}
/**
@@ -528,6 +559,13 @@ function injectScript(
* Sets up all event handlers and other machinery.
*/
export async function wxMain() {
+ chrome.runtime.onInstalled.addListener(details => {
+ if (details.reason === "install") {
+ const url = chrome.extension.getURL("/src/webex/pages/welcome.html");
+ chrome.tabs.create({ active: true, url: url });
+ }
+ });
+
// 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 => {
@@ -630,8 +668,6 @@ export async function wxMain() {
tabTimers[tabId] = timers;
});
- chrome.extension.getBackgroundPage()!.setInterval(clearRateLimitCache, 5000);
-
reinitWallet();
// Handlers for messages coming directly from the content
diff --git a/tsconfig.json b/tsconfig.json
index 7aca07913..307918760 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -66,7 +66,6 @@
"src/webex/background.ts",
"src/webex/chromeBadge.ts",
"src/webex/compat.ts",
- "src/webex/components.ts",
"src/webex/messages.ts",
"src/webex/notify.ts",
"src/webex/pages/add-auditor.tsx",
@@ -84,7 +83,7 @@
"src/webex/pages/return-coins.tsx",
"src/webex/pages/show-db.ts",
"src/webex/pages/tip.tsx",
- "src/webex/pages/tree.tsx",
+ "src/webex/pages/welcome.tsx",
"src/webex/pages/withdraw.tsx",
"src/webex/renderHtml.tsx",
"src/webex/wxApi.ts",
diff --git a/webpack.config.js b/webpack.config.js
index df9ebbcf4..096455887 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -79,6 +79,7 @@ module.exports = function (env) {
"benchmark": "./src/webex/pages/benchmark.tsx",
"pay": "./src/webex/pages/pay.tsx",
"withdraw": "./src/webex/pages/withdraw.tsx",
+ "welcome": "./src/webex/pages/welcome.tsx",
"error": "./src/webex/pages/error.tsx",
"logs": "./src/webex/pages/logs.tsx",
"payback": "./src/webex/pages/payback.tsx",
@@ -88,7 +89,6 @@ module.exports = function (env) {
"refund": "./src/webex/pages/refund.tsx",
"show-db": "./src/webex/pages/show-db.ts",
"tip": "./src/webex/pages/tip.tsx",
- "tree": "./src/webex/pages/tree.tsx",
},
name: "pages",
optimization: {