/* 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 TALER; see the file COPYING. If not, see */ /** * Popup shown to the user when they click * the Taler browser action button. * * @author Florian Dold */ "use strict"; import {substituteFulfillmentUrl} from "../lib/wallet/helpers"; import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent; import {HistoryRecord, HistoryLevel} from "../lib/wallet/wallet"; import {AmountJson} from "../lib/wallet/types"; declare var i18n: any; function onUpdateNotification(f: () => void) { let port = chrome.runtime.connect({name: "notifications"}); port.onMessage.addListener((msg, port) => { f(); }); } class Router extends preact.Component { 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 { this.routeHandlers.push(f); return () => { let i = this.routeHandlers.indexOf(f); this.routeHandlers = this.routeHandlers.splice(i, 1); } } static routeHandlers: any[] = []; componentWillMount() { console.log("router mounted"); window.onhashchange = () => { this.setState({}); for (let f of Router.routeHandlers) { f(); } } } componentWillUnmount() { console.log("router unmounted"); } render(props: any, state: any): JSX.Element { let route = window.location.hash.substring(1); console.log("rendering route", route); let defaultChild: JSX.Element|null = null; for (let child of props.children) { if (child.attributes["default"]) { defaultChild = child; } if (child.attributes["route"] == route) { return
{child}
; } } if (defaultChild == null) { throw Error("unknown route"); } console.log("rendering default route"); Router.setRoute(defaultChild.attributes["route"]); return
{defaultChild}
; } } export function main() { console.log("popup main"); let el = (
); preact.render(el, document.getElementById("content")!); } interface TabProps extends preact.ComponentProps { target: string; } function Tab(props: TabProps) { let cssClass = ""; if (props.target == Router.getRoute()) { cssClass = "active"; } let onClick = (e: Event) => { Router.setRoute(props.target); e.preventDefault(); }; return ( {props.children} ); } class WalletNavBar extends preact.Component { cancelSubscription: any; componentWillMount() { this.cancelSubscription = Router.onRoute(() => { this.setState({}); }); } componentWillUnmount() { if (this.cancelSubscription) { this.cancelSubscription(); } } render() { console.log("rendering nav bar"); return ( ); } } function ExtensionLink(props: any) { let onClick = (e: Event) => { chrome.tabs.create({ "url": chrome.extension.getURL(props.target) }); e.preventDefault(); }; return ( {props.children} ) } class WalletBalance extends preact.Component { myWallet: any; gotError = false; componentWillMount() { this.updateBalance(); onUpdateNotification(() => this.updateBalance()); } updateBalance() { chrome.runtime.sendMessage({type: "balances"}, (resp) => { if (resp.error) { this.gotError = true; console.error("could not retrieve balances", resp); this.forceUpdate(); return; } this.gotError = false; console.log("got wallet", resp); this.myWallet = resp.balances; this.forceUpdate(); }); } renderEmpty() : JSX.Element { let helpLink = ( help ); return
You have no balance to show. Need some {helpLink} getting started?
; } render(): JSX.Element { let wallet = this.myWallet; if (this.gotError) { return i18n`Error: could not retrieve balance information.`; } if (!wallet) { return this.renderEmpty(); } console.log(wallet); let listing = Object.keys(wallet).map((key) => { return

{formatAmount(wallet[key])}

}); if (listing.length > 0) { return
{listing}
; } return this.renderEmpty(); } } function formatAmount(amount: AmountJson) { let v = amount.value + amount.fraction / 1e6; return `${v.toFixed(2)} ${amount.currency}`; } function abbrev(s: string, n: number = 5) { let sAbbrev = s; if (s.length > n) { sAbbrev = s.slice(0, n) + ".."; } return ( {sAbbrev} ); } function formatHistoryItem(historyItem: HistoryRecord) { const d = historyItem.detail; const t = historyItem.timestamp; console.log("hist item", historyItem); switch (historyItem.type) { case "create-reserve": return (

{i18n.parts`Bank requested reserve (${abbrev(d.reservePub)}) for ${formatAmount( d.requestedAmount)}.`}

); case "confirm-reserve": { // FIXME: eventually remove compat fix let exchange = d.exchangeBaseUrl ? URI(d.exchangeBaseUrl).host() : "??"; let amount = formatAmount(d.requestedAmount); let pub = abbrev(d.reservePub); return (

{i18n.parts`Started to withdraw ${amount} from ${exchange} (${pub}).`}

); } case "offer-contract": { let link = chrome.extension.getURL("view-contract.html"); let linkElem = {abbrev(d.contractHash)}; let merchantElem = {abbrev(d.merchantName, 15)}; return (

{i18n.parts`Merchant ${merchantElem} offered contract ${linkElem}.`}

); } case "depleted-reserve": { let exchange = d.exchangeBaseUrl ? URI(d.exchangeBaseUrl).host() : "??"; let amount = formatAmount(d.requestedAmount); let pub = abbrev(d.reservePub); return (

{i18n.parts`Withdrew ${amount} from ${exchange} (${pub}).`}

); } case "pay": { let url = substituteFulfillmentUrl(d.fulfillmentUrl, {H_contract: d.contractHash}); let merchantElem = {abbrev(d.merchantName, 15)}; let fulfillmentLinkElem = view product; return (

{i18n.parts`Paid ${formatAmount(d.amount)} to merchant ${merchantElem}. (${fulfillmentLinkElem})`}

); } default: return (

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

); } } class WalletHistory extends preact.Component { myHistory: any[]; gotError = false; componentWillMount() { this.update(); onUpdateNotification(() => this.update()); } update() { chrome.runtime.sendMessage({type: "get-history"}, (resp) => { console.log("got history response"); if (resp.error) { this.gotError = true; console.error("could not retrieve history", resp); this.forceUpdate(); return; } this.gotError = false; console.log("got history", resp.history); this.myHistory = resp.history; this.forceUpdate(); }); } render(): JSX.Element { console.log("rendering history"); let history: HistoryRecord[] = this.myHistory; if (this.gotError) { return i18n`Error: could not retrieve event history`; } if (!history) { // We're not ready yet return ; } let subjectMemo: {[s: string]: boolean} = {}; let listing: any[] = []; for (let record of history.reverse()) { if (record.subjectId && subjectMemo[record.subjectId]) { continue; } if (record.level != undefined && record.level < HistoryLevel.User) { continue; } subjectMemo[record.subjectId as string] = true; let item = (
{(new Date(record.timestamp)).toString()}
{formatHistoryItem(record)}
); listing.push(item); } if (listing.length > 0) { return
{listing}
; } return

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

} } function reload() { try { chrome.runtime.reload(); window.close(); } catch (e) { // Functionality missing in firefox, ignore! } } function confirmReset() { if (confirm("Do you want to IRREVOCABLY DESTROY everything inside your" + " wallet and LOSE ALL YOUR COINS?")) { chrome.runtime.sendMessage({type: "reset"}); window.close(); } } function WalletDebug(props: any) { return (

Debug tools:


); } function openExtensionPage(page: string) { return function() { chrome.tabs.create({ "url": chrome.extension.getURL(page) }); } } function openTab(page: string) { return function() { chrome.tabs.create({ "url": page }); } }