From 8144b0f5535c3d00c1e508cddce3cd88a153a581 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 5 Sep 2019 16:10:53 +0200 Subject: welcome page with error diagnostics / react refactoring --- Makefile | 4 + src/db.ts | 2 +- src/dbTypes.ts | 2 +- src/i18n/de.po | 16 +- src/i18n/en-US.po | 16 +- src/i18n/fr.po | 16 +- src/i18n/it.po | 16 +- src/i18n/sv.po | 16 +- src/i18n/taler-wallet-webex.pot | 16 +- src/walletTypes.ts | 18 +- src/webex/components.ts | 63 ------ src/webex/messages.ts | 5 + src/webex/pages/add-auditor.tsx | 119 +++++----- src/webex/pages/help/empty-wallet.html | 30 --- src/webex/pages/payback.tsx | 87 +++---- src/webex/pages/popup.tsx | 255 ++++++++++----------- src/webex/pages/tree.html | 27 --- src/webex/pages/tree.tsx | 402 --------------------------------- src/webex/pages/welcome.html | 24 ++ src/webex/pages/welcome.tsx | 113 +++++++++ src/webex/pages/withdraw.html | 2 +- src/webex/pages/withdraw.tsx | 10 +- src/webex/renderHtml.tsx | 82 +++---- src/webex/wxApi.ts | 8 + src/webex/wxBackend.ts | 88 +++++--- tsconfig.json | 3 +- webpack.config.js | 2 +- 27 files changed, 535 insertions(+), 907 deletions(-) delete mode 100644 src/webex/components.ts delete mode 100644 src/webex/pages/help/empty-wallet.html delete mode 100644 src/webex/pages/tree.html delete mode 100644 src/webex/pages/tree.tsx create mode 100644 src/webex/pages/welcome.html create mode 100644 src/webex/pages/welcome.tsx 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 - */ - - -/** - * 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; - (newState: T): void; -} - -/** - * Component that doesn't hold its state in one object, - * but has multiple state holders. - */ -export abstract class ImplicitStateComponent extends React.Component { - private _implicit = {needsUpdate: false, didMount: false}; - componentDidMount() { - this._implicit.didMount = true; - if (this._implicit.needsUpdate) { - this.setState({} as any); - } - } - makeState(initial: StateType): StateHolder { - 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 { - private addDone: StateHolder = 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 { await updateCurrency(currency); - this.addDone(true); + setAddDone(true); } - back() { + const back = () => { window.history.back(); - } - - render(): JSX.Element { - return ( -
-

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

- {this.addDone() ? - ( -
- Auditor was added! You can also{" "} - view and edit{" "} - auditors. -
- ) - : - ( -
- - -
- ) - } -
- ); - } + }; + + return ( +
+

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

+ {addDone ? ( +
+ Auditor was added! You can also{" "} + + view and edit + {" "} + auditors. +
+ ) : ( +
+ + +
+ )} +
+ ); } -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(, document.getElementById("container")!); -} - -document.addEventListener("DOMContentLoaded", main); + return ; +}); 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 @@ - - - - - GNU Taler Help - Empty Wallet - - - - - -
-
-
-

Your wallet is empty!

-

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.

-

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 - bank.demo.taler.net to - withdraw coins in the "KUDOS" currency that we created just for - demonstrating the system.

-
-
-
- - 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(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 = 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 loading ...; } - - withdrawPayback(pub: string) { - withdrawPaybackReserve(pub); + if (reserves.length === 0) { + return No reserves with payback available.; } - - render(): JSX.Element { - const reserves = this.reserves(); - if (!reserves) { - return loading ...; - } - if (reserves.length === 0) { - return No reserves with payback available.; - } - return ( -
- {reserves.map((r) => ( -
-

Reserve for ${renderAmount(r.current_amount!)}

-
    -
  • Exchange: ${r.exchange_base_url}
  • -
- -
- ))} -
- ); - } -} - -function main() { - ReactDOM.render(, document.getElementById("container")!); + return ( +
+ {reserves.map(r => ( +
+

Reserve for ${renderAmount(r.current_amount!)}

+
    +
  • Exchange: ${r.exchange_base_url}
  • +
+ +
+ ))} +
+ ); } -document.addEventListener("DOMContentLoaded", main); +registerMountPage(() => ); 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 */ - /** * 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 { static setRoute(s: string): void { window.location.hash = s; @@ -92,13 +90,12 @@ class Router extends React.Component { 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 { } } - interface TabProps { target: string; children?: React.ReactNode; @@ -141,7 +137,6 @@ function Tab(props: TabProps) { ); } - class WalletNavBar extends React.Component { private cancelSubscription: any; @@ -161,20 +156,14 @@ class WalletNavBar extends React.Component { console.log("rendering nav bar"); return ( ); + {i18n.str`Balance`} + {i18n.str`History`} + {i18n.str`Debug`} + + ); } } - function ExtensionLink(props: any) { const onClick = (e: React.MouseEvent) => { 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 ( - {v} - {" "} + {v}{" "} {amount.currency} - + + ); +} + +function EmptyBalanceView() { + return ( +
+ + You have no balance to show. Need some{" "} + help getting + started? + +
); } @@ -245,57 +244,44 @@ class WalletBalanceView extends React.Component { this.setState({}); } - renderEmpty(): JSX.Element { - const helpLink = ( - - {i18n.str`help`} - - ); - return ( -
- - You have no balance to show. Need some - {" "}{helpLink}{" "} - getting started? - -
- ); - } - 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 = ( - + {"+"} {renderAmount(entry.pendingIncoming)} - - {" "} + {" "} incoming - + ); } if (Amounts.isNonZero(entry.pendingPayment)) { payment = ( - + {"-"} {renderAmount(entry.pendingPayment)} - - {" "} + {" "} being spent ); } - const l = [incoming, payment].filter((x) => x !== undefined); + const l = [incoming, payment].filter(x => x !== undefined); if (l.length === 0) { return ; } @@ -303,49 +289,41 @@ class WalletBalanceView extends React.Component { if (l.length === 1) { return ({l}); } - return ({l[0]}, {l[1]}); - + return ( + + ({l[0]}, {l[1]}) + + ); } render(): JSX.Element { const wallet = this.balance; if (this.gotError) { - return i18n.str`Error: could not retrieve balance information.`; + return ( +
+

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

+

+ Click here for help and diagnostics. +

+
+ ); } if (!wallet) { return ; } 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 (

- {bigAmount(entry.available)} - {" "} - {this.formatPending(entry)} + {bigAmount(entry.available)} {this.formatPending(entry)}

); }); - const makeLink = (page: string, name: string) => { - const url = chrome.extension.getURL(`/src/webex/pages/${page}`); - return ; - }; - return ( -
- {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`)} -
- ); + return
{listing.length > 0 ? listing : }
; } } - 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 ( - Bank requested reserve ({abbrev(d.reservePub)}) for - {" "} + Bank requested reserve ({abbrev(d.reservePub)}) for{" "} {renderAmount(d.requestedAmount)}. ); case "confirm-reserve": { - const exchange = (new URI(d.exchangeBaseUrl)).host(); + const exchange = new URI(d.exchangeBaseUrl).host(); const pub = abbrev(d.reservePub); return ( @@ -372,30 +349,37 @@ function formatHistoryItem(historyItem: HistoryRecord) { case "offer-contract": { return ( - Merchant {abbrev(d.merchantName, 15)} offered - contract {abbrev(d.contractTermsHash)}. + Merchant {abbrev(d.merchantName, 15)} offered contract{" "} + {abbrev(d.contractTermsHash)}. ); } 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 ( - Withdrew {amount} from {exchange} ({pub}). + Withdrew {amount} from {exchange} ( + {pub}). ); } case "pay": { const url = d.fulfillmentUrl; const merchantElem = {abbrev(d.merchantName, 15)}; - const fulfillmentLinkElem = view product; + const fulfillmentLinkElem = ( + + view product + + ); return ( - Paid {renderAmount(d.amount)} to merchant {merchantElem}. - - ({fulfillmentLinkElem}) + Paid {renderAmount(d.amount)} to merchant{" "} + {merchantElem}. ( + {fulfillmentLinkElem}) ); } @@ -403,12 +387,15 @@ function formatHistoryItem(historyItem: HistoryRecord) { const merchantElem = {abbrev(d.merchantName, 15)}; return ( - Merchant {merchantElem} gave a refund over {renderAmount(d.refundAmount)}. + Merchant {merchantElem} gave a refund over{" "} + {renderAmount(d.refundAmount)}. ); } 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 = {i18n.str`tip`}; @@ -416,19 +403,23 @@ function formatHistoryItem(historyItem: HistoryRecord) { return ( <> - Merchant {d.merchantDomain} gave - a {tipLink} of {renderAmount(d.amount)}. + Merchant {d.merchantDomain} gave a{" "} + {tipLink} of {renderAmount(d.amount)}. - { d.accepted ? null : You did not accept the tip yet. } + + {" "} + {d.accepted ? null : ( + You did not accept the tip yet. + )} + ); } default: - return (

{i18n.str`Unknown event (${historyItem.type})`}

); + return

{i18n.str`Unknown event (${historyItem.type})`}

; } } - class WalletHistory extends React.Component { private myHistory: any[]; private gotError = false; @@ -445,7 +436,7 @@ class WalletHistory extends React.Component { } 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 { const item = (
- {(new Date(record.timestamp)).toString()} + {new Date(record.timestamp).toString()}
{formatHistoryItem(record)}
@@ -494,10 +485,8 @@ class WalletHistory extends React.Component { } return

{i18n.str`Your wallet has no events recorded.`}

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

Debug tools:

- - - - - -
- - -
); + return ( +
+

Debug tools:

+ + + + + +
+ + +
+ ); } - function openExtensionPage(page: string) { return () => { chrome.tabs.create({ @@ -553,7 +542,6 @@ function openExtensionPage(page: string) { }; } - function openTab(page: string) { return (evt: React.SyntheticEvent) => { evt.preventDefault(); @@ -563,15 +551,14 @@ function openTab(page: string) { }; } - const el = (
-
+
- - - + + +
@@ -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 @@ - - - - - - Taler Wallet: Tree View - - - - - - - - - - - -
- - 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 - */ - -/** - * 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 { - render(): JSX.Element { - const r: ReserveRecord = this.props.reserve; - return ( -
-
    -
  • Key: {r.reserve_pub}
  • -
  • Created: {(new Date(r.created * 1000).toString())}
  • -
  • Current: {r.current_amount ? renderAmount(r.current_amount!) : "null"}
  • -
  • Requested: {renderAmount(r.requested_amount)}
  • -
  • Confirmed: {r.timestamp_confirmed}
  • -
-
- ); - } -} - -interface ReserveListProps { - exchangeBaseUrl: string; -} - -interface ToggleProps { - expanded: StateHolder; -} - -class Toggle extends ImplicitStateComponent { - renderButton() { - const show = () => { - this.props.expanded(true); - this.setState({}); - }; - const hide = () => { - this.props.expanded(false); - this.setState({}); - }; - if (this.props.expanded()) { - return ; - } - return ; - - } - render() { - return ( -
- {this.renderButton()} - {this.props.expanded() ? this.props.children : []} -
); - } -} - - -interface CoinViewProps { - coin: CoinRecord; -} - -interface RefreshDialogProps { - coin: CoinRecord; -} - -class RefreshDialog extends ImplicitStateComponent { - private refreshRequested = this.makeState(false); - render(): JSX.Element { - if (!this.refreshRequested()) { - return ( -
- -
- ); - } - return ( -
- Refresh amount: - - -
- ); - } -} - -class CoinView extends React.Component { - render() { - const c = this.props.coin; - return ( -
-
    -
  • Key: {c.coinPub}
  • -
  • Current amount: {renderAmount(c.currentAmount)}
  • -
  • Denomination:
  • -
  • Suspended: {(c.suspended || false).toString()}
  • -
  • Status: {CoinStatus[c.status]}
  • -
  • -
  • -
-
- ); - } -} - - -interface PreCoinViewProps { - precoin: PreCoinRecord; -} - -class PreCoinView extends React.Component { - render() { - const c = this.props.precoin; - return ( -
-
    -
  • Key: {c.coinPub}
  • -
-
- ); - } -} - -interface CoinListProps { - exchangeBaseUrl: string; -} - -class CoinList extends ImplicitStateComponent { - private coins = this.makeState(null); - private expanded = this.makeState(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
...
; - } - return ( -
- Coins ({this.coins() !.length.toString()}) - {" "} - - {this.coins() !.map((c) => )} - -
- ); - } -} - - -interface PreCoinListProps { - exchangeBaseUrl: string; -} - -class PreCoinList extends ImplicitStateComponent { - private precoins = this.makeState(null); - private expanded = this.makeState(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
...
; - } - return ( -
- Planchets ({this.precoins() !.length.toString()}) - {" "} - - {this.precoins() !.map((c) => )} - -
- ); - } -} - -interface DenominationListProps { - exchange: ExchangeRecord; -} - -class DenominationList extends ImplicitStateComponent { - private expanded = this.makeState(false); - private denoms = this.makeState(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 ( -
-
    -
  • Offered: {d.isOffered ? "yes" : "no"}
  • -
  • Value: {renderAmount(d.value)}
  • -
  • Withdraw fee: {renderAmount(d.feeWithdraw)}
  • -
  • Refresh fee: {renderAmount(d.feeRefresh)}
  • -
  • Deposit fee: {renderAmount(d.feeDeposit)}
  • -
  • Refund fee: {renderAmount(d.feeRefund)}
  • -
  • Start: {getTalerStampDate(d.stampStart)!.toString()}
  • -
  • Withdraw expiration: {getTalerStampDate(d.stampExpireWithdraw)!.toString()}
  • -
  • Legal expiration: {getTalerStampDate(d.stampExpireLegal)!.toString()}
  • -
  • Deposit expiration: {getTalerStampDate(d.stampExpireDeposit)!.toString()}
  • -
  • Denom pub:
  • -
-
- ); - } - - render(): JSX.Element { - const denoms = this.denoms(); - if (!denoms) { - return ( -
- Denominations (...) - {" "} - - ... - -
- ); - } - return ( -
- Denominations ({denoms.length.toString()}) - {" "} - - {denoms.map((d) => this.renderDenom(d))} - -
- ); - } -} - - -class ReserveList extends ImplicitStateComponent { - private reserves = this.makeState(null); - private expanded = this.makeState(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
...
; - } - return ( -
- Reserves ({this.reserves() !.length.toString()}) - {" "} - - {this.reserves() !.map((r) => )} - -
- ); - } -} - -interface ExchangeProps { - exchange: ExchangeRecord; -} - -class ExchangeView extends React.Component { - render(): JSX.Element { - const e = this.props.exchange; - return ( -
-
    -
  • Exchange Base Url: {this.props.exchange.baseUrl}
  • -
  • Master public key:
  • -
- - - - -
- ); - } -} - -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 ...; - } - return ( -
- Exchanges ({exchanges.length.toString()}): - {exchanges.map((e) => )} -
- ); - } -} - -function main() { - ReactDOM.render(, 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 @@ + + + + + + Taler Wallet: Withdraw + + + + + + + + + + + +
+

GNU Taler Wallet Installed!

+
Loading...
+
+ + + 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 + */ + +/** + * 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( + 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

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

; + } + + if (diagnostics) { + if (diagnostics.errors.length === 0) { + return

Running diagnostics ... everything looks fine.

; + } 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 ...

; +} + +function Welcome() { + return ( + <> +

Thank you for installing the wallet.

+

First Steps

+

+ Check out demo.taler.net for a + demo. +

+

Troubleshooting

+ + + ); +} + +registerMountPage(() => ); 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 @@ - Taler Wallet: Select Taler Provider + Taler Wallet: Withdraw 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 { - private expanded = this.makeState(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 ( - { this.expanded(true); }}> - {(this.props.text.length <= 10) - ? this.props.text - : ( - - {this.props.text.substring(0, 10)} - ... - - ) - } - - ); - } - return ( - - ); - } +export function ExpanderText({ text }: ExpanderTextProps) { + return {text}; } + export interface LoadingButtonProps { loading: boolean; } @@ -340,4 +301,35 @@ export function ProgressButton( {props.children} ); +} + +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 {props.children}; } \ 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 { + 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 { 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 = openPromise(); 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: { -- cgit v1.2.3