2016-01-24 02:29:13 +01:00
|
|
|
/*
|
|
|
|
This file is part of TALER
|
|
|
|
(C) 2016 GNUnet e.V.
|
|
|
|
|
|
|
|
TALER is free software; you can redistribute it and/or modify it under the
|
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
|
|
|
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
2016-07-07 17:59:29 +02:00
|
|
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
2016-01-24 02:29:13 +01:00
|
|
|
*/
|
|
|
|
|
2016-03-01 19:46:20 +01:00
|
|
|
/**
|
|
|
|
* Popup shown to the user when they click
|
|
|
|
* the Taler browser action button.
|
|
|
|
*
|
|
|
|
* @author Florian Dold
|
|
|
|
*/
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
/**
|
|
|
|
* Imports.
|
|
|
|
*/
|
2021-05-20 21:58:22 +02:00
|
|
|
import {
|
2020-08-03 09:30:48 +02:00
|
|
|
AmountJson,
|
|
|
|
Amounts,
|
2020-08-12 09:11:00 +02:00
|
|
|
BalancesResponse,
|
|
|
|
Balance,
|
|
|
|
classifyTalerUri,
|
|
|
|
TalerUriType,
|
2020-08-20 08:29:06 +02:00
|
|
|
TransactionsResponse,
|
|
|
|
Transaction,
|
|
|
|
TransactionType,
|
2020-11-18 17:33:02 +01:00
|
|
|
AmountString,
|
|
|
|
Timestamp,
|
2021-03-27 14:35:58 +01:00
|
|
|
amountFractionalBase,
|
|
|
|
} from "@gnu-taler/taler-util";
|
2021-05-07 23:10:27 +02:00
|
|
|
import { Component, ComponentChildren, JSX } from "preact";
|
|
|
|
import { route, Route, Router } from 'preact-router';
|
|
|
|
import { Match } from 'preact-router/match';
|
|
|
|
import { useEffect, useState } from "preact/hooks";
|
|
|
|
import * as i18n from "../i18n";
|
|
|
|
import { PageLink, renderAmount } from "../renderHtml";
|
2017-06-05 03:20:28 +02:00
|
|
|
import * as wxApi from "../wxApi";
|
2020-05-04 13:46:06 +02:00
|
|
|
import { PermissionsCheckbox } from "./welcome";
|
2016-10-10 00:37:08 +02:00
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
interface TabProps {
|
2016-10-10 00:37:08 +02:00
|
|
|
target: string;
|
2021-05-07 23:10:27 +02:00
|
|
|
current?: string;
|
2021-05-07 15:38:28 +02:00
|
|
|
children?: ComponentChildren;
|
2016-10-10 00:37:08 +02:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
2020-04-06 20:02:01 +02:00
|
|
|
function Tab(props: TabProps): JSX.Element {
|
2016-01-24 02:29:13 +01:00
|
|
|
let cssClass = "";
|
2021-05-07 23:10:27 +02:00
|
|
|
if (props.current === props.target) {
|
2016-01-24 02:29:13 +01:00
|
|
|
cssClass = "active";
|
|
|
|
}
|
2016-10-10 00:37:08 +02:00
|
|
|
return (
|
2021-05-07 23:10:27 +02:00
|
|
|
<a href={props.target} className={cssClass}>
|
2016-10-10 00:37:08 +02:00
|
|
|
{props.children}
|
|
|
|
</a>
|
|
|
|
);
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
|
|
|
|
2021-05-07 23:10:27 +02:00
|
|
|
function WalletNavBar({ current }: { current?: string }) {
|
|
|
|
return (
|
|
|
|
<div className="nav" id="header">
|
|
|
|
<Tab target="/popup/balance" current={current}>{i18n.str`Balance`}</Tab>
|
|
|
|
<Tab target="/popup/history" current={current}>{i18n.str`History`}</Tab>
|
|
|
|
<Tab target="/popup/settings" current={current}>{i18n.str`Settings`}</Tab>
|
|
|
|
<Tab target="/popup/debug" current={current}>{i18n.str`Debug`}</Tab>
|
|
|
|
</div>
|
|
|
|
);
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
/**
|
|
|
|
* Render an amount as a large number with a small currency symbol.
|
|
|
|
*/
|
|
|
|
function bigAmount(amount: AmountJson): JSX.Element {
|
2021-03-27 14:35:58 +01:00
|
|
|
const v = amount.value + amount.fraction / amountFractionalBase;
|
2016-11-28 08:19:06 +01:00
|
|
|
return (
|
|
|
|
<span>
|
2020-03-30 12:39:32 +02:00
|
|
|
<span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
|
2016-11-28 08:19:06 +01:00
|
|
|
<span>{amount.currency}</span>
|
2019-09-05 16:10:53 +02:00
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-04-06 20:02:01 +02:00
|
|
|
function EmptyBalanceView(): JSX.Element {
|
2019-09-05 16:10:53 +02:00
|
|
|
return (
|
2020-01-27 14:11:39 +01:00
|
|
|
<i18n.Translate wrap="p">
|
|
|
|
You have no balance to show. Need some{" "}
|
2021-05-07 23:10:27 +02:00
|
|
|
<PageLink pageName="/welcome">help</PageLink> getting started?
|
2020-01-27 14:11:39 +01:00
|
|
|
</i18n.Translate>
|
2016-11-28 08:19:06 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-07 15:38:28 +02:00
|
|
|
class WalletBalanceView extends Component<any, any> {
|
2020-08-12 09:11:00 +02:00
|
|
|
private balance?: BalancesResponse;
|
2017-05-29 15:18:48 +02:00
|
|
|
private gotError = false;
|
|
|
|
private canceler: (() => void) | undefined = undefined;
|
|
|
|
private unmount = false;
|
2020-05-11 18:17:35 +02:00
|
|
|
private updateBalanceRunning = false;
|
2016-09-14 15:20:18 +02:00
|
|
|
|
2020-04-06 20:02:01 +02:00
|
|
|
componentWillMount(): void {
|
2020-05-04 15:22:54 +02:00
|
|
|
this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
|
2016-10-10 00:37:08 +02:00
|
|
|
this.updateBalance();
|
2016-11-13 08:16:12 +01:00
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
|
2020-04-06 20:02:01 +02:00
|
|
|
componentWillUnmount(): void {
|
2016-11-13 08:16:12 +01:00
|
|
|
console.log("component WalletBalanceView will unmount");
|
|
|
|
if (this.canceler) {
|
|
|
|
this.canceler();
|
|
|
|
}
|
|
|
|
this.unmount = true;
|
2016-10-10 00:37:08 +02:00
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
|
2020-04-06 20:02:01 +02:00
|
|
|
async updateBalance(): Promise<void> {
|
2020-05-11 18:17:35 +02:00
|
|
|
if (this.updateBalanceRunning) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.updateBalanceRunning = true;
|
2020-08-12 09:11:00 +02:00
|
|
|
let balance: BalancesResponse;
|
2017-08-14 04:16:12 +02:00
|
|
|
try {
|
|
|
|
balance = await wxApi.getBalance();
|
|
|
|
} catch (e) {
|
2016-11-13 08:16:12 +01:00
|
|
|
if (this.unmount) {
|
|
|
|
return;
|
|
|
|
}
|
2017-08-14 04:16:12 +02:00
|
|
|
this.gotError = true;
|
|
|
|
console.error("could not retrieve balances", e);
|
2016-10-18 02:40:46 +02:00
|
|
|
this.setState({});
|
2017-08-14 04:16:12 +02:00
|
|
|
return;
|
2020-05-11 18:17:35 +02:00
|
|
|
} finally {
|
|
|
|
this.updateBalanceRunning = false;
|
2017-08-14 04:16:12 +02:00
|
|
|
}
|
|
|
|
if (this.unmount) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.gotError = false;
|
|
|
|
console.log("got balance", balance);
|
|
|
|
this.balance = balance;
|
|
|
|
this.setState({});
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
|
|
|
|
2020-08-12 09:11:00 +02:00
|
|
|
formatPending(entry: Balance): JSX.Element {
|
2016-10-19 22:49:03 +02:00
|
|
|
let incoming: JSX.Element | undefined;
|
|
|
|
let payment: JSX.Element | undefined;
|
|
|
|
|
2020-07-28 19:47:12 +02:00
|
|
|
const available = Amounts.parseOrThrow(entry.available);
|
|
|
|
const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
|
|
|
|
const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
|
|
|
|
|
2019-09-05 16:10:53 +02:00
|
|
|
console.log(
|
|
|
|
"available: ",
|
|
|
|
entry.pendingIncoming ? renderAmount(entry.available) : null,
|
|
|
|
);
|
|
|
|
console.log(
|
|
|
|
"incoming: ",
|
|
|
|
entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null,
|
|
|
|
);
|
2016-10-19 22:49:03 +02:00
|
|
|
|
2020-08-03 09:30:48 +02:00
|
|
|
if (!Amounts.isZero(pendingIncoming)) {
|
2016-10-19 22:49:03 +02:00
|
|
|
incoming = (
|
2016-11-23 01:14:45 +01:00
|
|
|
<i18n.Translate wrap="span">
|
2019-09-05 16:10:53 +02:00
|
|
|
<span style={{ color: "darkgreen" }}>
|
2016-10-19 22:49:03 +02:00
|
|
|
{"+"}
|
2017-06-04 17:56:55 +02:00
|
|
|
{renderAmount(entry.pendingIncoming)}
|
2019-09-05 16:10:53 +02:00
|
|
|
</span>{" "}
|
2016-10-19 22:49:03 +02:00
|
|
|
incoming
|
2019-09-05 16:10:53 +02:00
|
|
|
</i18n.Translate>
|
2016-11-23 01:14:45 +01:00
|
|
|
);
|
2016-10-19 22:49:03 +02:00
|
|
|
}
|
|
|
|
|
2020-03-30 12:39:32 +02:00
|
|
|
const l = [incoming, payment].filter((x) => x !== undefined);
|
2017-05-29 15:18:48 +02:00
|
|
|
if (l.length === 0) {
|
2016-10-19 22:49:03 +02:00
|
|
|
return <span />;
|
|
|
|
}
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
if (l.length === 1) {
|
|
|
|
return <span>({l})</span>;
|
2016-10-19 22:49:03 +02:00
|
|
|
}
|
2019-09-05 16:10:53 +02:00
|
|
|
return (
|
|
|
|
<span>
|
|
|
|
({l[0]}, {l[1]})
|
|
|
|
</span>
|
|
|
|
);
|
2016-10-10 03:16:12 +02:00
|
|
|
}
|
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
render(): JSX.Element {
|
2017-05-29 15:18:48 +02:00
|
|
|
const wallet = this.balance;
|
2016-10-10 00:37:08 +02:00
|
|
|
if (this.gotError) {
|
2019-09-05 16:10:53 +02:00
|
|
|
return (
|
2020-03-30 12:39:32 +02:00
|
|
|
<div className="balance">
|
2019-09-05 16:10:53 +02:00
|
|
|
<p>{i18n.str`Error: could not retrieve balance information.`}</p>
|
|
|
|
<p>
|
2019-09-05 16:23:54 +02:00
|
|
|
Click <PageLink pageName="welcome.html">here</PageLink> for help and
|
|
|
|
diagnostics.
|
2019-09-05 16:10:53 +02:00
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
);
|
2016-03-02 00:47:00 +01:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
if (!wallet) {
|
2016-10-20 01:37:00 +02:00
|
|
|
return <span></span>;
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-10-10 00:37:08 +02:00
|
|
|
console.log(wallet);
|
2020-07-28 19:47:12 +02:00
|
|
|
const listing = wallet.balances.map((entry) => {
|
|
|
|
const av = Amounts.parseOrThrow(entry.available);
|
2016-10-19 18:40:29 +02:00
|
|
|
return (
|
2020-07-28 19:47:12 +02:00
|
|
|
<p key={av.currency}>
|
|
|
|
{bigAmount(av)} {this.formatPending(entry)}
|
2016-10-19 18:40:29 +02:00
|
|
|
</p>
|
|
|
|
);
|
2016-10-10 00:37:08 +02:00
|
|
|
});
|
2020-03-30 12:39:32 +02:00
|
|
|
return listing.length > 0 ? (
|
|
|
|
<div className="balance">{listing}</div>
|
|
|
|
) : (
|
|
|
|
<EmptyBalanceView />
|
|
|
|
);
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
2020-11-18 17:33:02 +01:00
|
|
|
interface TransactionAmountProps {
|
|
|
|
debitCreditIndicator: "debit" | "credit" | "unknown";
|
|
|
|
amount: AmountString | "unknown";
|
|
|
|
pending: boolean;
|
2020-01-27 14:11:39 +01:00
|
|
|
}
|
|
|
|
|
2020-11-18 17:33:02 +01:00
|
|
|
function TransactionAmount(props: TransactionAmountProps): JSX.Element {
|
|
|
|
const [currency, amount] = props.amount.split(":");
|
|
|
|
let sign: string;
|
|
|
|
switch (props.debitCreditIndicator) {
|
|
|
|
case "credit":
|
|
|
|
sign = "+";
|
|
|
|
break;
|
|
|
|
case "debit":
|
|
|
|
sign = "-";
|
|
|
|
break;
|
|
|
|
case "unknown":
|
|
|
|
sign = "";
|
|
|
|
}
|
2021-05-07 15:38:28 +02:00
|
|
|
const style: JSX.AllCSSProperties = {
|
2020-11-18 17:33:02 +01:00
|
|
|
marginLeft: "auto",
|
|
|
|
display: "flex",
|
|
|
|
flexDirection: "column",
|
|
|
|
alignItems: "center",
|
|
|
|
alignSelf: "center"
|
|
|
|
};
|
|
|
|
if (props.pending) {
|
|
|
|
style.color = "gray";
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div style={{ ...style }}>
|
|
|
|
<div style={{ fontSize: "x-large" }}>
|
|
|
|
{sign}
|
|
|
|
{amount}
|
|
|
|
</div>
|
|
|
|
<div>{currency}</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TransactionLayoutProps {
|
|
|
|
debitCreditIndicator: "debit" | "credit" | "unknown";
|
|
|
|
amount: AmountString | "unknown";
|
|
|
|
timestamp: Timestamp;
|
|
|
|
title: string;
|
|
|
|
subtitle: string;
|
|
|
|
iconPath: string;
|
|
|
|
pending: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
|
|
|
|
const date = new Date(props.timestamp.t_ms);
|
|
|
|
const dateStr = date.toLocaleString([], {
|
|
|
|
dateStyle: "medium",
|
|
|
|
timeStyle: "short",
|
|
|
|
} as any);
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
display: "flex",
|
|
|
|
flexDirection: "row",
|
|
|
|
border: "1px solid gray",
|
|
|
|
borderRadius: "0.5em",
|
|
|
|
margin: "0.5em 0",
|
|
|
|
justifyContent: "space-between",
|
|
|
|
padding: "0.5em",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<img src={props.iconPath} />
|
|
|
|
<div
|
|
|
|
style={{ display: "flex", flexDirection: "column", marginLeft: "1em" }}
|
|
|
|
>
|
|
|
|
<div style={{ fontSize: "small", color: "gray" }}>{dateStr}</div>
|
|
|
|
<div style={{ fontVariant: "small-caps", fontSize: "x-large" }}>
|
|
|
|
<span>{props.title}</span>
|
|
|
|
{props.pending ? (
|
|
|
|
<span style={{ color: "darkblue" }}> (Pending)</span>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>{props.subtitle}</div>
|
|
|
|
</div>
|
|
|
|
<TransactionAmount
|
|
|
|
pending={props.pending}
|
|
|
|
amount={props.amount}
|
|
|
|
debitCreditIndicator={props.debitCreditIndicator}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
2020-01-27 14:11:39 +01:00
|
|
|
}
|
|
|
|
|
2020-08-20 08:29:06 +02:00
|
|
|
function TransactionItem(props: { tx: Transaction }): JSX.Element {
|
|
|
|
const tx = props.tx;
|
2020-11-18 17:33:02 +01:00
|
|
|
switch (tx.type) {
|
|
|
|
case TransactionType.Withdrawal:
|
|
|
|
return (
|
|
|
|
<TransactionLayout
|
|
|
|
amount={tx.amountEffective}
|
|
|
|
debitCreditIndicator={"credit"}
|
|
|
|
title="Withdrawal"
|
|
|
|
subtitle={`via ${tx.exchangeBaseUrl}`}
|
|
|
|
timestamp={tx.timestamp}
|
|
|
|
iconPath="/static/img/ri-bank-line.svg"
|
|
|
|
pending={tx.pending}
|
|
|
|
></TransactionLayout>
|
|
|
|
);
|
|
|
|
case TransactionType.Payment:
|
|
|
|
return (
|
|
|
|
<TransactionLayout
|
|
|
|
amount={tx.amountEffective}
|
|
|
|
debitCreditIndicator={"debit"}
|
|
|
|
title="Payment"
|
|
|
|
subtitle={tx.info.summary}
|
|
|
|
timestamp={tx.timestamp}
|
|
|
|
iconPath="/static/img/ri-shopping-cart-line.svg"
|
|
|
|
pending={tx.pending}
|
|
|
|
></TransactionLayout>
|
|
|
|
);
|
|
|
|
case TransactionType.Refund:
|
|
|
|
return (
|
|
|
|
<TransactionLayout
|
|
|
|
amount={tx.amountEffective}
|
|
|
|
debitCreditIndicator={"credit"}
|
|
|
|
title="Refund"
|
|
|
|
subtitle={tx.info.summary}
|
|
|
|
timestamp={tx.timestamp}
|
|
|
|
iconPath="/static/img/ri-refund-2-line.svg"
|
|
|
|
pending={tx.pending}
|
|
|
|
></TransactionLayout>
|
|
|
|
);
|
|
|
|
case TransactionType.Tip:
|
|
|
|
return (
|
|
|
|
<TransactionLayout
|
|
|
|
amount={tx.amountEffective}
|
|
|
|
debitCreditIndicator={"credit"}
|
|
|
|
title="Tip"
|
|
|
|
subtitle={`from ${new URL(tx.merchantBaseUrl).hostname}`}
|
|
|
|
timestamp={tx.timestamp}
|
|
|
|
iconPath="/static/img/ri-hand-heart-line.svg"
|
|
|
|
pending={tx.pending}
|
|
|
|
></TransactionLayout>
|
|
|
|
);
|
|
|
|
case TransactionType.Refresh:
|
|
|
|
return (
|
|
|
|
<TransactionLayout
|
|
|
|
amount={tx.amountEffective}
|
|
|
|
debitCreditIndicator={"credit"}
|
|
|
|
title="Refresh"
|
|
|
|
subtitle={`via exchange ${tx.exchangeBaseUrl}`}
|
|
|
|
timestamp={tx.timestamp}
|
|
|
|
iconPath="/static/img/ri-refresh-line.svg"
|
|
|
|
pending={tx.pending}
|
|
|
|
></TransactionLayout>
|
2021-01-18 23:35:41 +01:00
|
|
|
);
|
|
|
|
case TransactionType.Deposit:
|
|
|
|
return (
|
|
|
|
<TransactionLayout
|
|
|
|
amount={tx.amountEffective}
|
|
|
|
debitCreditIndicator={"debit"}
|
|
|
|
title="Refresh"
|
|
|
|
subtitle={`to ${tx.targetPaytoUri}`}
|
|
|
|
timestamp={tx.timestamp}
|
|
|
|
iconPath="/static/img/ri-refresh-line.svg"
|
|
|
|
pending={tx.pending}
|
|
|
|
></TransactionLayout>
|
2020-11-18 17:33:02 +01:00
|
|
|
);
|
|
|
|
}
|
2020-08-20 08:29:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function WalletHistory(props: any): JSX.Element {
|
|
|
|
const [transactions, setTransactions] = useState<
|
|
|
|
TransactionsResponse | undefined
|
2021-05-07 15:38:28 +02:00
|
|
|
>(undefined);
|
2020-08-20 08:29:06 +02:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const fetchData = async (): Promise<void> => {
|
|
|
|
const res = await wxApi.getTransactions();
|
|
|
|
setTransactions(res);
|
|
|
|
};
|
|
|
|
fetchData();
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
if (!transactions) {
|
|
|
|
return <div>Loading ...</div>;
|
|
|
|
}
|
|
|
|
|
2020-11-18 17:33:02 +01:00
|
|
|
const txs = [...transactions.transactions].reverse();
|
|
|
|
|
2020-08-20 08:29:06 +02:00
|
|
|
return (
|
|
|
|
<div>
|
2021-05-07 23:10:27 +02:00
|
|
|
{txs.map((tx, i) => (
|
2021-05-07 15:38:28 +02:00
|
|
|
<TransactionItem key={i} tx={tx} />
|
2020-08-20 08:29:06 +02:00
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2020-01-27 14:11:39 +01:00
|
|
|
|
2021-05-07 15:38:28 +02:00
|
|
|
class WalletSettings extends Component<any, any> {
|
2020-05-04 13:46:06 +02:00
|
|
|
render(): JSX.Element {
|
2020-06-03 13:16:25 +02:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<h2>Permissions</h2>
|
|
|
|
<PermissionsCheckbox />
|
|
|
|
</div>
|
|
|
|
);
|
2020-05-04 13:46:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-06 20:02:01 +02:00
|
|
|
function reload(): void {
|
2016-01-26 17:21:17 +01:00
|
|
|
try {
|
|
|
|
chrome.runtime.reload();
|
|
|
|
window.close();
|
|
|
|
} catch (e) {
|
|
|
|
// Functionality missing in firefox, ignore!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-07 15:38:28 +02:00
|
|
|
async function confirmReset(): Promise<void> {
|
2019-09-05 16:10:53 +02:00
|
|
|
if (
|
|
|
|
confirm(
|
|
|
|
"Do you want to IRREVOCABLY DESTROY everything inside your" +
|
2021-05-07 23:10:27 +02:00
|
|
|
" wallet and LOSE ALL YOUR COINS?",
|
2019-09-05 16:10:53 +02:00
|
|
|
)
|
|
|
|
) {
|
2021-05-07 15:38:28 +02:00
|
|
|
await wxApi.resetDb();
|
2016-01-24 02:29:13 +01:00
|
|
|
window.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-06 20:02:01 +02:00
|
|
|
function WalletDebug(props: any): JSX.Element {
|
2019-09-05 16:10:53 +02:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<p>Debug tools:</p>
|
2020-11-18 17:33:02 +01:00
|
|
|
<button onClick={openExtensionPage("/static/popup.html")}>wallet tab</button>
|
2019-09-05 16:10:53 +02:00
|
|
|
<br />
|
|
|
|
<button onClick={confirmReset}>reset</button>
|
|
|
|
<button onClick={reload}>reload chrome extension</button>
|
|
|
|
</div>
|
|
|
|
);
|
2016-10-10 00:37:08 +02:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function openExtensionPage(page: string) {
|
2017-05-29 15:18:48 +02:00
|
|
|
return () => {
|
2016-01-26 17:21:17 +01:00
|
|
|
chrome.tabs.create({
|
2017-05-29 15:18:48 +02:00
|
|
|
url: chrome.extension.getURL(page),
|
2016-11-26 17:27:33 +01:00
|
|
|
});
|
2017-05-29 15:18:48 +02:00
|
|
|
};
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-01-26 17:21:17 +01:00
|
|
|
|
2021-05-07 15:38:28 +02:00
|
|
|
// function openTab(page: string) {
|
|
|
|
// return (evt: React.SyntheticEvent<any>) => {
|
|
|
|
// evt.preventDefault();
|
|
|
|
// chrome.tabs.create({
|
|
|
|
// url: page,
|
|
|
|
// });
|
|
|
|
// };
|
|
|
|
// }
|
2017-04-20 03:09:25 +02:00
|
|
|
|
2020-05-01 10:46:56 +02:00
|
|
|
function makeExtensionUrlWithParams(
|
|
|
|
url: string,
|
|
|
|
params?: { [name: string]: string | undefined },
|
|
|
|
): string {
|
|
|
|
const innerUrl = new URL(chrome.extension.getURL("/" + url));
|
|
|
|
if (params) {
|
|
|
|
for (const key in params) {
|
|
|
|
const p = params[key];
|
|
|
|
if (p) {
|
|
|
|
innerUrl.searchParams.set(key, p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return innerUrl.href;
|
|
|
|
}
|
|
|
|
|
|
|
|
function actionForTalerUri(talerUri: string): string | undefined {
|
2020-08-12 09:11:00 +02:00
|
|
|
const uriType = classifyTalerUri(talerUri);
|
2020-05-01 10:46:56 +02:00
|
|
|
switch (uriType) {
|
2020-08-12 09:11:00 +02:00
|
|
|
case TalerUriType.TalerWithdraw:
|
2021-05-20 21:58:22 +02:00
|
|
|
return makeExtensionUrlWithParams("static/popup.html#/withdraw", {
|
2020-05-01 10:46:56 +02:00
|
|
|
talerWithdrawUri: talerUri,
|
|
|
|
});
|
2020-08-12 09:11:00 +02:00
|
|
|
case TalerUriType.TalerPay:
|
2021-05-20 21:58:22 +02:00
|
|
|
return makeExtensionUrlWithParams("static/popup.html#/pay", {
|
2020-05-01 10:46:56 +02:00
|
|
|
talerPayUri: talerUri,
|
|
|
|
});
|
2020-08-12 09:11:00 +02:00
|
|
|
case TalerUriType.TalerTip:
|
2021-05-20 21:58:22 +02:00
|
|
|
return makeExtensionUrlWithParams("static/popup.html#/tip", {
|
2020-05-01 10:46:56 +02:00
|
|
|
talerTipUri: talerUri,
|
|
|
|
});
|
2020-08-12 09:11:00 +02:00
|
|
|
case TalerUriType.TalerRefund:
|
2021-05-20 21:58:22 +02:00
|
|
|
return makeExtensionUrlWithParams("static/popup.html#/refund", {
|
2020-05-01 10:46:56 +02:00
|
|
|
talerRefundUri: talerUri,
|
|
|
|
});
|
2020-08-12 09:11:00 +02:00
|
|
|
case TalerUriType.TalerNotifyReserve:
|
2020-05-01 10:46:56 +02:00
|
|
|
// FIXME: implement
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
console.warn(
|
|
|
|
"Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function findTalerUriInActiveTab(): Promise<string | undefined> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
chrome.tabs.executeScript(
|
|
|
|
{
|
|
|
|
code: `
|
|
|
|
(() => {
|
2021-05-20 21:58:22 +02:00
|
|
|
let x = document.querySelector("a[href^='taler://'") || document.querySelector("a[href^='taler+http://'");
|
2020-05-01 10:46:56 +02:00
|
|
|
return x ? x.href.toString() : null;
|
|
|
|
})();
|
|
|
|
`,
|
|
|
|
allFrames: false,
|
|
|
|
},
|
|
|
|
(result) => {
|
|
|
|
if (chrome.runtime.lastError) {
|
|
|
|
console.error(chrome.runtime.lastError);
|
|
|
|
resolve(undefined);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
console.log("got result", result);
|
|
|
|
resolve(result[0]);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-07 23:10:27 +02:00
|
|
|
export function WalletPopup(): JSX.Element {
|
2020-05-01 10:46:56 +02:00
|
|
|
const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
|
|
|
|
undefined,
|
|
|
|
);
|
|
|
|
const [dismissed, setDismissed] = useState(false);
|
|
|
|
useEffect(() => {
|
|
|
|
async function check(): Promise<void> {
|
|
|
|
const talerUri = await findTalerUriInActiveTab();
|
|
|
|
if (talerUri) {
|
|
|
|
const actionUrl = actionForTalerUri(talerUri);
|
|
|
|
setTalerActionUrl(actionUrl);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
check();
|
2021-05-20 21:58:22 +02:00
|
|
|
}, []);
|
2020-05-01 10:46:56 +02:00
|
|
|
if (talerActionUrl && !dismissed) {
|
|
|
|
return (
|
2021-05-20 21:58:22 +02:00
|
|
|
<div style={{ padding: "1em", width: 400 }}>
|
2020-05-01 10:46:56 +02:00
|
|
|
<h1>Taler Action</h1>
|
|
|
|
<p>This page has a Taler action. </p>
|
|
|
|
<p>
|
|
|
|
<button
|
|
|
|
onClick={() => {
|
|
|
|
window.open(talerActionUrl, "_blank");
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
Open
|
|
|
|
</button>
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
<button onClick={() => setDismissed(true)}>Dismiss</button>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2019-09-05 16:23:54 +02:00
|
|
|
return (
|
|
|
|
<div>
|
2021-05-07 23:10:27 +02:00
|
|
|
<Match>{({ path }: any) => <WalletNavBar current={path} />}</Match>
|
2021-05-20 21:58:22 +02:00
|
|
|
<div style={{ margin: "1em", width: 400 }}>
|
2019-09-05 16:23:54 +02:00
|
|
|
<Router>
|
2021-05-07 23:10:27 +02:00
|
|
|
<Route path={Pages.balance} component={WalletBalanceView} />
|
|
|
|
<Route path={Pages.settings} component={WalletSettings} />
|
|
|
|
<Route path={Pages.debug} component={WalletDebug} />
|
|
|
|
<Route path={Pages.history} component={WalletHistory} />
|
2019-09-05 16:23:54 +02:00
|
|
|
</Router>
|
|
|
|
</div>
|
2017-04-20 03:09:25 +02:00
|
|
|
</div>
|
2019-09-05 16:23:54 +02:00
|
|
|
);
|
|
|
|
}
|
2017-04-20 03:09:25 +02:00
|
|
|
|
2021-05-07 23:10:27 +02:00
|
|
|
enum Pages {
|
|
|
|
balance = '/popup/balance',
|
|
|
|
settings = '/popup/settings',
|
|
|
|
debug = '/popup/debug',
|
|
|
|
history = '/popup/history',
|
|
|
|
}
|
|
|
|
|
|
|
|
export function Redirect({ to }: { to: string }): null {
|
|
|
|
useEffect(() => {
|
|
|
|
route(to, true)
|
|
|
|
})
|
|
|
|
return null
|
2020-04-07 10:07:32 +02:00
|
|
|
}
|