welcome page with error diagnostics / react refactoring
This commit is contained in:
parent
fab4e33896
commit
8144b0f553
4
Makefile
4
Makefile
@ -86,3 +86,7 @@ install: tsc
|
||||
npm install -g --prefix $(prefix) .
|
||||
endif
|
||||
|
||||
.PHONY: watch
|
||||
watch: tsconfig.json
|
||||
|
||||
./node_modules/.bin/webpack --watch
|
||||
|
@ -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) => {
|
||||
|
@ -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.
|
||||
|
@ -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 ""
|
||||
|
@ -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 ""
|
||||
|
@ -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 ""
|
||||
|
@ -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 ""
|
||||
|
@ -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"
|
||||
|
@ -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 ""
|
||||
|
@ -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,
|
||||
}
|
||||
contractTerms: ContractTerms;
|
||||
hasRefund: boolean;
|
||||
totalRefundAmount: AmountJson;
|
||||
totalRefundAndRefreshFees: AmountJson;
|
||||
}
|
||||
|
||||
export interface WalletDiagnostics {
|
||||
walletManifestVersion: string;
|
||||
walletManifestDisplayVersion: string;
|
||||
errors: string[];
|
||||
firefoxIdbProblem: boolean;
|
||||
dbOutdated: boolean;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
@ -205,6 +205,11 @@ export interface MessageMap {
|
||||
request: { talerPayUri: string };
|
||||
response: walletTypes.PreparePayResult;
|
||||
};
|
||||
|
||||
"get-diagnostics": {
|
||||
request: { };
|
||||
response: walletTypes.WalletDiagnostics;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
@ -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}/>;
|
||||
});
|
||||
|
@ -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>
|
@ -20,73 +20,54 @@
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import {
|
||||
ReserveRecord,
|
||||
} from "../../dbTypes";
|
||||
|
||||
import { ImplicitStateComponent, StateHolder } from "../components";
|
||||
import { renderAmount } from "../renderHtml";
|
||||
import {
|
||||
getPaybackReserves,
|
||||
withdrawPaybackReserve,
|
||||
} from "../wxApi";
|
||||
|
||||
import { ReserveRecord } from "../../dbTypes";
|
||||
import { renderAmount, registerMountPage } from "../renderHtml";
|
||||
import { getPaybackReserves, withdrawPaybackReserve } from "../wxApi";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { useState } from "react";
|
||||
|
||||
function Payback() {
|
||||
const [reserves, setReserves] = useState<ReserveRecord[] | null>(null);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
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={() => withdrawPaybackReserve(r.reserve_pub)}>
|
||||
Withdraw again
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function main() {
|
||||
ReactDOM.render(<Payback />, document.getElementById("container")!);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", main);
|
||||
registerMountPage(() => <Payback />);
|
||||
|
@ -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" });
|
||||
});
|
||||
|
@ -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>
|
@ -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);
|
24
src/webex/pages/welcome.html
Normal file
24
src/webex/pages/welcome.html
Normal file
@ -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>
|
113
src/webex/pages/welcome.tsx
Normal file
113
src/webex/pages/welcome.tsx
Normal file
@ -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 />);
|
@ -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">
|
||||
|
@ -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 { 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";
|
||||
|
@ -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>;
|
||||
}
|
@ -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", {});
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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: {
|
||||
|
Loading…
Reference in New Issue
Block a user