wallet-core/src/webex/pages/popup.tsx

890 lines
23 KiB
TypeScript
Raw Normal View History

/*
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-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.
*/
import * as i18n from "../i18n";
import { AmountJson } from "../../util/amounts";
import * as Amounts from "../../util/amounts";
2020-03-30 12:39:32 +02:00
import { WalletBalance, WalletBalanceEntry } from "../../types/walletTypes";
2020-04-07 10:07:32 +02:00
import { abbrev, renderAmount, PageLink } from "../renderHtml";
2017-06-05 03:20:28 +02:00
import * as wxApi from "../wxApi";
import React, { Fragment, useState, useEffect } from "react";
import { HistoryEvent } from "../../types/history";
import moment from "moment";
import { Timestamp } from "../../util/time";
import { classifyTalerUri, TalerUriType } from "../../util/taleruri";
2020-04-07 10:28:55 +02:00
// FIXME: move to newer react functions
/* eslint-disable react/no-deprecated */
function onUpdateNotification(f: () => void): () => void {
const port = chrome.runtime.connect({ name: "notifications" });
2020-04-06 20:02:01 +02:00
const listener = (): void => {
2016-02-18 23:41:29 +01:00
f();
};
port.onMessage.addListener(listener);
return () => {
port.onMessage.removeListener(listener);
2017-05-29 15:18:48 +02:00
};
2016-02-18 23:41:29 +01:00
}
2017-05-29 15:18:48 +02:00
class Router extends React.Component<any, any> {
2016-10-10 00:37:08 +02:00
static setRoute(s: string): void {
window.location.hash = s;
}
static getRoute(): string {
// Omit the '#' at the beginning
return window.location.hash.substring(1);
}
static onRoute(f: any): () => void {
2016-10-13 02:36:33 +02:00
Router.routeHandlers.push(f);
2016-10-10 00:37:08 +02:00
return () => {
2017-05-29 15:18:48 +02:00
const i = Router.routeHandlers.indexOf(f);
2016-10-10 00:37:08 +02:00
this.routeHandlers = this.routeHandlers.splice(i, 1);
2017-05-29 15:18:48 +02:00
};
2016-10-10 00:37:08 +02:00
}
2017-05-29 15:18:48 +02:00
private static routeHandlers: any[] = [];
2016-10-10 00:37:08 +02:00
2020-04-06 20:02:01 +02:00
componentWillMount(): void {
2016-10-10 00:37:08 +02:00
console.log("router mounted");
window.onhashchange = () => {
2016-10-10 02:36:12 +02:00
this.setState({});
2017-05-29 15:18:48 +02:00
for (const f of Router.routeHandlers) {
2016-10-10 00:37:08 +02:00
f();
}
2017-05-29 15:18:48 +02:00
};
2016-10-10 00:37:08 +02:00
}
render(): JSX.Element {
2017-05-29 15:18:48 +02:00
const route = window.location.hash.substring(1);
2016-10-10 00:37:08 +02:00
console.log("rendering route", route);
let defaultChild: React.ReactChild | null = null;
let foundChild: React.ReactChild | null = null;
2020-03-30 12:39:32 +02:00
React.Children.forEach(this.props.children, (child) => {
2017-05-29 15:18:48 +02:00
const childProps: any = (child as any).props;
if (!childProps) {
return;
}
2017-05-29 15:18:48 +02:00
if (childProps.default) {
2019-05-07 23:46:50 +02:00
defaultChild = child as React.ReactChild;
2016-10-10 00:37:08 +02:00
}
2017-05-29 15:18:48 +02:00
if (childProps.route === route) {
2019-05-07 23:46:50 +02:00
foundChild = child as React.ReactChild;
2016-10-10 00:37:08 +02:00
}
2017-05-29 15:18:48 +02:00
});
2017-10-15 19:28:35 +02:00
const c: React.ReactChild | null = foundChild || defaultChild;
if (!c) {
2016-10-10 00:37:08 +02:00
throw Error("unknown route");
}
2017-10-15 19:28:35 +02:00
Router.setRoute((c as any).props.route);
return <div>{c}</div>;
2016-10-10 00:37:08 +02:00
}
}
interface TabProps {
2016-10-10 00:37:08 +02:00
target: string;
children?: React.ReactNode;
2016-10-10 00:37:08 +02:00
}
2020-04-06 20:02:01 +02:00
function Tab(props: TabProps): JSX.Element {
let cssClass = "";
2017-05-29 15:18:48 +02:00
if (props.target === Router.getRoute()) {
cssClass = "active";
}
2020-04-06 20:02:01 +02:00
const onClick = (e: React.MouseEvent<HTMLAnchorElement>): void => {
2016-10-10 00:37:08 +02:00
Router.setRoute(props.target);
e.preventDefault();
};
return (
<a onClick={onClick} href={props.target} className={cssClass}>
{props.children}
</a>
);
}
2017-05-29 15:18:48 +02:00
class WalletNavBar extends React.Component<any, any> {
private cancelSubscription: any;
2016-10-10 00:37:08 +02:00
2020-04-06 20:02:01 +02:00
componentWillMount(): void {
2016-10-10 00:37:08 +02:00
this.cancelSubscription = Router.onRoute(() => {
this.setState({});
});
}
2016-02-18 23:41:29 +01:00
2020-04-06 20:02:01 +02:00
componentWillUnmount(): void {
2016-10-10 00:37:08 +02:00
if (this.cancelSubscription) {
this.cancelSubscription();
}
}
2020-04-06 20:02:01 +02:00
render(): JSX.Element {
2016-10-10 00:37:08 +02:00
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>
);
2016-02-18 23:41:29 +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 {
const v = amount.value + amount.fraction / Amounts.fractionalBase;
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>
</span>
);
}
2020-04-06 20:02:01 +02:00
function EmptyBalanceView(): JSX.Element {
return (
<i18n.Translate wrap="p">
You have no balance to show. Need some{" "}
<PageLink pageName="welcome.html">help</PageLink> getting started?
</i18n.Translate>
2016-11-28 08:19:06 +01:00
);
}
class WalletBalanceView extends React.Component<any, any> {
2017-05-29 15:18:48 +02:00
private balance: WalletBalance;
private gotError = false;
private canceler: (() => void) | undefined = undefined;
private unmount = false;
2016-09-14 15:20:18 +02:00
2020-04-06 20:02:01 +02:00
componentWillMount(): void {
this.canceler = onUpdateNotification(() => this.updateBalance());
2016-10-10 00:37:08 +02:00
this.updateBalance();
}
2016-02-18 23:41:29 +01:00
2020-04-06 20:02:01 +02:00
componentWillUnmount(): void {
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> {
let balance: WalletBalance;
try {
balance = await wxApi.getBalance();
} catch (e) {
if (this.unmount) {
return;
}
this.gotError = true;
console.error("could not retrieve balances", e);
2016-10-18 02:40:46 +02:00
this.setState({});
return;
}
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
}
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,
);
if (Amounts.isNonZero(entry.pendingIncoming)) {
incoming = (
2016-11-23 01:14:45 +01:00
<i18n.Translate wrap="span">
<span style={{ color: "darkgreen" }}>
{"+"}
2017-06-04 17:56:55 +02:00
{renderAmount(entry.pendingIncoming)}
</span>{" "}
incoming
</i18n.Translate>
2016-11-23 01:14:45 +01:00
);
}
if (Amounts.isNonZero(entry.pendingPayment)) {
payment = (
2016-11-23 01:14:45 +01:00
<i18n.Translate wrap="span">
<span style={{ color: "red" }}>
2018-02-05 13:26:06 +01:00
{"-"}
2017-06-04 17:56:55 +02:00
{renderAmount(entry.pendingPayment)}
</span>{" "}
being spent
2016-11-23 01:14:45 +01:00
</i18n.Translate>
);
}
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) {
return <span />;
}
2017-05-29 15:18:48 +02:00
if (l.length === 1) {
return <span>({l})</span>;
}
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) {
return (
2020-03-30 12:39:32 +02:00
<div className="balance">
<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.
</p>
</div>
);
2016-03-02 00:47:00 +01:00
}
if (!wallet) {
2016-10-20 01:37:00 +02:00
return <span></span>;
}
2016-10-10 00:37:08 +02:00
console.log(wallet);
2020-03-30 12:39:32 +02:00
const listing = Object.keys(wallet.byCurrency).map((key) => {
const entry: WalletBalanceEntry = wallet.byCurrency[key];
2016-10-19 18:40:29 +02:00
return (
2020-04-07 10:28:55 +02:00
<p key={key}>
{bigAmount(entry.available)} {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-02-18 23:41:29 +01:00
}
2020-04-06 20:02:01 +02:00
function Icon({ l }: { l: string }): JSX.Element {
return <div className={"icon"}>{l}</div>;
}
2020-04-06 20:02:01 +02:00
function formatAndCapitalize(text: string): string {
text = text.replace("-", " ");
text = text.replace(/^./, text[0].toUpperCase());
return text;
}
type HistoryItemProps = {
title?: string | JSX.Element;
text?: string | JSX.Element;
small?: string | JSX.Element;
amount?: string | AmountJson;
fees?: string | AmountJson;
invalid?: string | AmountJson;
icon?: string;
timestamp: Timestamp;
negative?: boolean;
};
function HistoryItem({
title,
text,
small,
amount,
fees,
invalid,
timestamp,
icon,
2020-03-30 12:39:32 +02:00
negative = false,
2020-04-06 20:02:01 +02:00
}: HistoryItemProps): JSX.Element {
function formatDate(timestamp: number | "never"): string | null {
if (timestamp !== "never") {
const itemDate = moment(timestamp);
if (itemDate.isBetween(moment().subtract(2, "days"), moment())) {
return itemDate.fromNow();
}
return itemDate.format("lll");
}
return null;
}
let invalidElement, amountElement, feesElement;
if (amount) {
amountElement = renderAmount(amount);
}
if (fees) {
fees = typeof fees === "string" ? Amounts.parse(fees) : fees;
if (fees && Amounts.isNonZero(fees)) {
feesElement = renderAmount(fees);
}
}
if (invalid) {
invalid = typeof invalid === "string" ? Amounts.parse(invalid) : invalid;
if (invalid && Amounts.isNonZero(invalid)) {
invalidElement = renderAmount(invalid);
}
}
return (
<div className="historyItem">
{icon ? <Icon l={icon} /> : null}
<div className="historyContent">
{title ? <div className={"historyTitle"}>{title}</div> : null}
{text ? <div className={"historyText"}>{text}</div> : null}
{small ? <div className={"historySmall"}>{small}</div> : null}
</div>
<div className={"historyLeft"}>
<div className={"historyAmount"}>
{amountElement ? (
<div className={`${negative ? "negative" : "positive"}`}>
{amountElement}
</div>
) : null}
{invalidElement ? (
<div className={"secondary"}>
{i18n.str`Invalid `}{" "}
<span className={"negative"}>{invalidElement}</span>
</div>
) : null}
{feesElement ? (
<div className={"secondary"}>
{i18n.str`Fees `}{" "}
<span className={"negative"}>{feesElement}</span>
</div>
) : null}
</div>
<div className="historyDate">{formatDate(timestamp.t_ms)}</div>
</div>
</div>
);
}
function amountDiff(
total: string | Amounts.AmountJson,
2020-03-30 12:39:32 +02:00
partial: string | Amounts.AmountJson,
): Amounts.AmountJson | string {
2020-04-06 17:45:41 +02:00
const a = typeof total === "string" ? Amounts.parse(total) : total;
const b = typeof partial === "string" ? Amounts.parse(partial) : partial;
if (a && b) {
return Amounts.sub(a, b).amount;
} else {
return total;
}
}
2020-04-07 10:28:55 +02:00
function parseSummary(summary: string): { item: string; merchant: string } {
2020-04-06 17:45:41 +02:00
const parsed = summary.split(/: (.+)/);
return {
merchant: parsed[0],
2020-03-30 12:39:32 +02:00
item: parsed[1],
};
}
2020-04-06 20:02:01 +02:00
function formatHistoryItem(historyItem: HistoryEvent): JSX.Element {
switch (historyItem.type) {
case "refreshed": {
2016-10-10 00:37:08 +02:00
return (
<HistoryItem
timestamp={historyItem.timestamp}
small={i18n.str`Refresh sessions has completed`}
fees={amountDiff(
historyItem.amountRefreshedRaw,
2020-03-30 12:39:32 +02:00
historyItem.amountRefreshedEffective,
)}
/>
);
}
case "order-refused": {
const { merchant, item } = parseSummary(
2020-03-30 12:39:32 +02:00
historyItem.orderShortInfo.summary,
2016-10-10 00:37:08 +02:00
);
return (
<HistoryItem
icon={"X"}
timestamp={historyItem.timestamp}
small={i18n.str`Order Refused`}
title={merchant}
text={abbrev(item, 30)}
/>
2016-10-10 00:37:08 +02:00
);
2016-10-10 03:16:12 +02:00
}
case "order-redirected": {
const { merchant, item } = parseSummary(
2020-03-30 12:39:32 +02:00
historyItem.newOrderShortInfo.summary,
);
2016-10-10 00:37:08 +02:00
return (
<HistoryItem
icon={"⟲"}
small={i18n.str`Order redirected`}
text={abbrev(item, 40)}
timestamp={historyItem.timestamp}
title={merchant}
/>
2016-10-10 00:37:08 +02:00
);
2016-09-29 01:40:29 +02:00
}
case "payment-aborted": {
const { merchant, item } = parseSummary(
2020-03-30 12:39:32 +02:00
historyItem.orderShortInfo.summary,
);
2016-11-23 01:14:45 +01:00
return (
<HistoryItem
amount={historyItem.orderShortInfo.amount}
fees={historyItem.amountLost}
icon={"P"}
small={i18n.str`Payment aborted`}
text={abbrev(item, 40)}
timestamp={historyItem.timestamp}
title={merchant}
/>
2016-11-23 01:14:45 +01:00
);
2016-10-10 03:16:12 +02:00
}
case "payment-sent": {
const url = historyItem.orderShortInfo.fulfillmentUrl;
const { merchant, item } = parseSummary(
2020-03-30 12:39:32 +02:00
historyItem.orderShortInfo.summary,
);
const fees = amountDiff(
historyItem.amountPaidWithFees,
2020-03-30 12:39:32 +02:00
historyItem.orderShortInfo.amount,
);
const fulfillmentLinkElem = (
<Fragment>
2020-03-30 12:39:32 +02:00
<a href={url} onClick={openTab(url)}>
{item ? abbrev(item, 30) : null}
</a>
</Fragment>
);
2016-10-10 00:37:08 +02:00
return (
<HistoryItem
amount={historyItem.orderShortInfo.amount}
fees={fees}
icon={"P"}
negative={true}
small={i18n.str`Payment Sent`}
text={fulfillmentLinkElem}
timestamp={historyItem.timestamp}
title={merchant}
/>
);
}
case "order-accepted": {
const url = historyItem.orderShortInfo.fulfillmentUrl;
const { merchant, item } = parseSummary(
2020-03-30 12:39:32 +02:00
historyItem.orderShortInfo.summary,
);
const fulfillmentLinkElem = (
<Fragment>
2020-03-30 12:39:32 +02:00
<a href={url} onClick={openTab(url)}>
{item ? abbrev(item, 40) : null}
</a>
</Fragment>
);
return (
<HistoryItem
negative={true}
amount={historyItem.orderShortInfo.amount}
icon={"P"}
small={i18n.str`Order accepted`}
text={fulfillmentLinkElem}
timestamp={historyItem.timestamp}
title={merchant}
/>
);
}
case "reserve-balance-updated": {
return (
<HistoryItem
timestamp={historyItem.timestamp}
small={i18n.str`Reserve balance updated`}
/>
);
2016-09-29 01:40:29 +02:00
}
case "refund": {
const merchantElem = (
<em>{abbrev(historyItem.orderShortInfo.summary, 25)}</em>
);
return (
<HistoryItem
icon={"R"}
timestamp={historyItem.timestamp}
small={i18n.str`Payment refund`}
text={merchantElem}
amount={historyItem.amountRefundedRaw}
invalid={historyItem.amountRefundedInvalid}
fees={amountDiff(
amountDiff(
historyItem.amountRefundedRaw,
2020-03-30 12:39:32 +02:00
historyItem.amountRefundedInvalid,
),
2020-03-30 12:39:32 +02:00
historyItem.amountRefundedEffective,
)}
/>
);
}
case "withdrawn": {
const exchange = new URL(historyItem.exchangeBaseUrl).host;
const fees = amountDiff(
historyItem.amountWithdrawnRaw,
2020-03-30 12:39:32 +02:00
historyItem.amountWithdrawnEffective,
);
return (
<HistoryItem
amount={historyItem.amountWithdrawnRaw}
fees={fees}
icon={"w"}
small={i18n.str`Withdrawn`}
title={exchange}
timestamp={historyItem.timestamp}
/>
);
}
case "tip-accepted": {
return (
<HistoryItem
icon={"T"}
negative={true}
timestamp={historyItem.timestamp}
title={<i18n.Translate wrap={Fragment}>Tip Accepted</i18n.Translate>}
amount={historyItem.tipAmountRaw}
/>
);
}
case "tip-declined": {
2017-12-12 15:38:03 +01:00
return (
<HistoryItem
icon={"T"}
timestamp={historyItem.timestamp}
title={<i18n.Translate wrap={Fragment}>Tip Declined</i18n.Translate>}
amount={historyItem.tipAmountRaw}
/>
2017-12-12 15:38:03 +01:00
);
}
default:
return (
<HistoryItem
timestamp={historyItem.timestamp}
small={i18n.str`${formatAndCapitalize(historyItem.type)}`}
/>
);
}
}
2020-04-06 20:02:01 +02:00
const HistoryComponent = (props: any): JSX.Element => {
const record = props.record;
return formatHistoryItem(record);
};
class WalletHistory extends React.Component<any, any> {
2017-05-29 15:18:48 +02:00
private myHistory: any[];
private gotError = false;
private unmounted = false;
private hidenTypes: string[] = [
"order-accepted",
"order-redirected",
"refreshed",
"reserve-balance-updated",
"exchange-updated",
2020-03-30 12:39:32 +02:00
"exchange-added",
];
2020-03-30 12:39:32 +02:00
2020-04-06 20:02:01 +02:00
componentWillMount(): void {
2016-10-10 00:37:08 +02:00
this.update();
this.setState({ filter: true });
2016-10-10 00:37:08 +02:00
onUpdateNotification(() => this.update());
}
2016-02-18 23:41:29 +01:00
2020-04-06 20:02:01 +02:00
componentWillUnmount(): void {
console.log("history component unmounted");
this.unmounted = true;
}
2020-04-06 20:02:01 +02:00
update(): void {
2020-03-30 12:39:32 +02:00
chrome.runtime.sendMessage({ type: "get-history" }, (resp) => {
if (this.unmounted) {
return;
}
2016-10-10 00:37:08 +02:00
console.log("got history response");
if (resp.error) {
this.gotError = true;
console.error("could not retrieve history", resp);
2016-10-18 02:58:46 +02:00
this.setState({});
2016-10-10 00:37:08 +02:00
return;
}
this.gotError = false;
console.log("got history", resp.history);
this.myHistory = resp.history;
2016-10-18 02:58:46 +02:00
this.setState({});
2016-10-10 00:37:08 +02:00
});
2016-02-18 23:41:29 +01:00
}
2016-10-10 00:37:08 +02:00
render(): JSX.Element {
console.log("rendering history");
2019-11-21 23:09:43 +01:00
const history: HistoryEvent[] = this.myHistory;
2016-10-10 00:37:08 +02:00
if (this.gotError) {
2020-04-07 10:07:32 +02:00
return <span>i18n.str`Error: could not retrieve event history`</span>;
2016-03-02 00:47:00 +01:00
}
2016-10-10 00:37:08 +02:00
if (!history) {
2016-10-10 00:37:08 +02:00
// We're not ready yet
return <span />;
}
2016-09-28 23:41:34 +02:00
2017-05-29 15:18:48 +02:00
const listing: any[] = [];
2020-03-30 12:39:32 +02:00
const messages = history.reverse().filter((hEvent) => {
if (!this.state.filter) return true;
return this.hidenTypes.indexOf(hEvent.type) === -1;
});
for (const record of messages) {
2020-03-30 12:39:32 +02:00
const item = <HistoryComponent key={record.eventId} record={record} />;
2016-09-29 01:40:29 +02:00
listing.push(item);
2016-09-28 23:41:34 +02:00
}
if (listing.length > 0) {
return (
<div>
<div className="container">{listing}</div>
<div className="option">
Filtered list{" "}
<input
type="checkbox"
checked={this.state.filter}
onChange={() => this.setState({ filter: !this.state.filter })}
/>
</div>
</div>
);
}
2017-05-29 15:18:48 +02:00
return <p>{i18n.str`Your wallet has no events recorded.`}</p>;
}
2016-02-18 23:41:29 +01: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!
}
}
2020-04-06 20:02:01 +02:00
function confirmReset(): void {
if (
confirm(
"Do you want to IRREVOCABLY DESTROY everything inside your" +
" wallet and LOSE ALL YOUR COINS?",
)
) {
2017-06-05 03:20:28 +02:00
wxApi.resetDb();
window.close();
}
}
2020-04-06 20:02:01 +02:00
function WalletDebug(props: any): JSX.Element {
return (
<div>
<p>Debug tools:</p>
2020-04-07 10:07:32 +02:00
<button onClick={openExtensionPage("/popup.html")}>wallet tab</button>
<button onClick={openExtensionPage("/benchmark.html")}>benchmark</button>
<button onClick={openExtensionPage("/show-db.html")}>show db</button>
<button onClick={openExtensionPage("/tree.html")}>show tree</button>
<br />
<button onClick={confirmReset}>reset</button>
<button onClick={reload}>reload chrome extension</button>
</div>
);
2016-10-10 00:37:08 +02: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),
});
2017-05-29 15:18:48 +02:00
};
}
2016-01-26 17:21:17 +01:00
2016-09-12 20:25:56 +02:00
function openTab(page: string) {
2017-06-05 02:00:03 +02:00
return (evt: React.SyntheticEvent<any>) => {
2017-06-05 00:52:22 +02:00
evt.preventDefault();
2016-02-01 15:10:20 +01:00
chrome.tabs.create({
2017-05-29 15:18:48 +02:00
url: page,
});
2017-05-29 15:18:48 +02:00
};
2016-02-01 15:10:20 +01: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 {
const uriType = classifyTalerUri(talerUri);
switch (uriType) {
case TalerUriType.TalerWithdraw:
return makeExtensionUrlWithParams("withdraw.html", {
talerWithdrawUri: talerUri,
});
case TalerUriType.TalerPay:
return makeExtensionUrlWithParams("pay.html", {
talerPayUri: talerUri,
});
case TalerUriType.TalerTip:
return makeExtensionUrlWithParams("tip.html", {
talerTipUri: talerUri,
});
case TalerUriType.TalerRefund:
return makeExtensionUrlWithParams("refund.html", {
talerRefundUri: talerUri,
});
case TalerUriType.TalerNotifyReserve:
// FIXME: implement
break;
default:
console.warn(
"Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
);
break;
}
return undefined;
}
async function findTalerUriInActiveTab(): Promise<string | undefined> {
return new Promise((resolve, reject) => {
chrome.tabs.executeScript(
{
code: `
(() => {
let x = document.querySelector("a[href^='taler://'");
return x ? x.href.toString() : null;
})();
`,
allFrames: false,
},
(result) => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
resolve(undefined);
return;
}
console.log("got result", result);
resolve(result[0]);
},
);
});
}
2020-04-06 20:02:01 +02:00
function WalletPopup(): JSX.Element {
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();
});
if (talerActionUrl && !dismissed) {
return (
<div style={{ padding: "1em" }}>
<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>
<WalletNavBar />
<div style={{ margin: "1em" }}>
<Router>
<WalletBalanceView route="/balance" default />
<WalletHistory route="/history" />
<WalletDebug route="/debug" />
</Router>
</div>
</div>
2019-09-05 16:23:54 +02:00
);
}
2020-04-06 20:02:01 +02:00
export function createPopup(): JSX.Element {
//chrome.runtime.connect({ name: "popup" });
2020-03-30 12:39:32 +02:00
return <WalletPopup />;
2020-04-07 10:07:32 +02:00
}