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
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2016-02-10 02:03:31 +01:00
|
|
|
/// <reference path="../lib/decl/mithril.d.ts" />
|
2016-01-24 02:29:13 +01:00
|
|
|
/// <reference path="../lib/decl/lodash.d.ts" />
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
2016-02-11 11:29:57 +01:00
|
|
|
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
|
2016-09-12 20:25:56 +02:00
|
|
|
import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent;
|
2016-09-29 01:40:29 +02:00
|
|
|
import {HistoryRecord, HistoryLevel} from "../lib/wallet/wallet";
|
2016-09-12 20:25:56 +02:00
|
|
|
import {AmountJson} from "../lib/wallet/types";
|
2016-02-01 15:10:20 +01:00
|
|
|
|
2016-01-24 02:29:13 +01:00
|
|
|
declare var m: any;
|
|
|
|
declare var i18n: any;
|
|
|
|
|
2016-01-26 17:21:17 +01:00
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function onUpdateNotification(f: () => void) {
|
2016-02-18 23:41:29 +01:00
|
|
|
let port = chrome.runtime.connect({name: "notifications"});
|
|
|
|
port.onMessage.addListener((msg, port) => {
|
|
|
|
f();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-01-26 17:21:17 +01:00
|
|
|
export function main() {
|
|
|
|
console.log("popup main");
|
2016-01-24 02:29:13 +01:00
|
|
|
m.route.mode = "hash";
|
|
|
|
m.route(document.getElementById("content"), "/balance", {
|
|
|
|
"/balance": WalletBalance,
|
|
|
|
"/history": WalletHistory,
|
|
|
|
"/debug": WalletDebug,
|
|
|
|
});
|
|
|
|
m.mount(document.getElementById("nav"), WalletNavBar);
|
2016-01-26 17:21:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
console.log("this is popup");
|
2016-01-24 02:29:13 +01:00
|
|
|
|
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function makeTab(target: string, name: string) {
|
2016-01-24 02:29:13 +01:00
|
|
|
let cssClass = "";
|
|
|
|
if (target == m.route()) {
|
|
|
|
cssClass = "active";
|
|
|
|
}
|
|
|
|
return m("a", {config: m.route, href: target, "class": cssClass}, name);
|
|
|
|
}
|
|
|
|
|
2016-02-18 23:41:29 +01:00
|
|
|
namespace WalletNavBar {
|
|
|
|
export function view() {
|
2016-01-24 02:29:13 +01:00
|
|
|
return m("div#header.nav", [
|
|
|
|
makeTab("/balance", i18n`Balance`),
|
|
|
|
makeTab("/history", i18n`History`),
|
|
|
|
makeTab("/debug", i18n`Debug`),
|
|
|
|
]);
|
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
|
|
|
|
export function controller() {
|
|
|
|
// empty
|
|
|
|
}
|
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function openInExtension(element: HTMLAnchorElement, isInitialized: boolean) {
|
|
|
|
element.addEventListener("click", (e: Event) => {
|
2016-01-24 02:29:13 +01:00
|
|
|
chrome.tabs.create({
|
|
|
|
"url": element.href
|
|
|
|
});
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-09-14 15:20:18 +02:00
|
|
|
|
2016-02-18 23:41:29 +01:00
|
|
|
namespace WalletBalance {
|
|
|
|
export function controller() {
|
|
|
|
return new Controller();
|
|
|
|
}
|
|
|
|
|
|
|
|
class Controller {
|
2016-09-12 20:25:56 +02:00
|
|
|
myWallet: any;
|
2016-03-02 00:47:00 +01:00
|
|
|
gotError = false;
|
2016-02-18 23:41:29 +01:00
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.updateBalance();
|
|
|
|
|
|
|
|
onUpdateNotification(() => this.updateBalance());
|
|
|
|
}
|
|
|
|
|
|
|
|
updateBalance() {
|
|
|
|
m.startComputation();
|
2016-03-02 00:47:00 +01:00
|
|
|
chrome.runtime.sendMessage({type: "balances"}, (resp) => {
|
|
|
|
if (resp.error) {
|
|
|
|
this.gotError = true;
|
|
|
|
console.error("could not retrieve balances", resp);
|
|
|
|
m.endComputation();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.gotError = false;
|
|
|
|
console.log("got wallet", resp);
|
|
|
|
this.myWallet = resp.balances;
|
2016-02-18 23:41:29 +01:00
|
|
|
m.endComputation();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function view(ctrl: Controller) {
|
|
|
|
let wallet = ctrl.myWallet;
|
2016-03-02 00:47:00 +01:00
|
|
|
if (ctrl.gotError) {
|
|
|
|
return i18n`Error: could not retrieve balance information.`;
|
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
if (!wallet) {
|
|
|
|
throw Error("Could not retrieve wallet");
|
|
|
|
}
|
2016-09-12 20:25:56 +02:00
|
|
|
let listing = _.map(wallet, (x: any) => m("p", formatAmount(x)));
|
2016-01-24 02:29:13 +01:00
|
|
|
if (listing.length > 0) {
|
|
|
|
return listing;
|
|
|
|
}
|
2016-09-26 18:20:01 +02:00
|
|
|
let helpLink = m("a",
|
2016-09-29 01:40:29 +02:00
|
|
|
{
|
|
|
|
config: openInExtension,
|
|
|
|
href: chrome.extension.getURL(
|
|
|
|
"pages/help/empty-wallet.html")
|
|
|
|
},
|
|
|
|
i18n`help`);
|
2016-02-23 18:28:12 +01:00
|
|
|
|
2016-04-06 02:06:57 +02:00
|
|
|
return i18n.parts`You have no balance to show. Need some ${helpLink} getting started?`;
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function formatTimestamp(t: number) {
|
2016-01-24 02:29:13 +01:00
|
|
|
let x = new Date(t);
|
|
|
|
return x.toLocaleString();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function formatAmount(amount: AmountJson) {
|
2016-01-24 02:29:13 +01:00
|
|
|
let v = amount.value + amount.fraction / 1e6;
|
|
|
|
return `${v.toFixed(2)} ${amount.currency}`;
|
|
|
|
}
|
|
|
|
|
2016-02-01 15:10:20 +01:00
|
|
|
|
2016-09-29 01:40:29 +02:00
|
|
|
function abbrev(s: string, n: number = 5) {
|
|
|
|
let sAbbrev = s;
|
|
|
|
if (s.length > n) {
|
|
|
|
sAbbrev = s.slice(0, n) + "..";
|
|
|
|
}
|
|
|
|
return m("span.abbrev", {title: s}, sAbbrev);
|
2016-01-24 19:57:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function retryPayment(url: string, contractHash: string) {
|
2016-02-01 15:10:20 +01:00
|
|
|
return function() {
|
|
|
|
chrome.tabs.create({
|
|
|
|
"url": substituteFulfillmentUrl(url,
|
|
|
|
{H_contract: contractHash})
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-28 23:41:34 +02:00
|
|
|
function formatHistoryItem(historyItem: HistoryRecord) {
|
2016-01-24 02:29:13 +01:00
|
|
|
const d = historyItem.detail;
|
|
|
|
const t = historyItem.timestamp;
|
2016-02-22 23:21:41 +01:00
|
|
|
console.log("hist item", historyItem);
|
2016-01-24 02:29:13 +01:00
|
|
|
switch (historyItem.type) {
|
|
|
|
case "create-reserve":
|
|
|
|
return m("p",
|
2016-09-29 01:40:29 +02:00
|
|
|
i18n.parts`Bank requested reserve (${abbrev(d.reservePub)}) for ${formatAmount(
|
|
|
|
d.requestedAmount)}.`);
|
2016-02-22 23:21:41 +01:00
|
|
|
case "confirm-reserve":
|
|
|
|
return m("p",
|
2016-09-29 01:40:29 +02:00
|
|
|
i18n.parts`Started to withdraw from reserve (${abbrev(d.reservePub)}) of ${formatAmount(
|
|
|
|
d.requestedAmount)}.`);
|
2016-01-24 02:29:13 +01:00
|
|
|
case "withdraw":
|
|
|
|
return m("p",
|
|
|
|
i18n`Withdraw at ${formatTimestamp(t)}`);
|
2016-09-29 01:40:29 +02:00
|
|
|
case "offer-contract": {
|
|
|
|
let link = chrome.extension.getURL("view-contract.html");
|
|
|
|
let linkElem = m("a", {href: link}, abbrev(d.contractHash));
|
|
|
|
let merchantElem = m("em", abbrev(d.merchantName, 15));
|
|
|
|
return m("p",
|
|
|
|
i18n.parts`Merchant ${merchantElem} offered contract ${linkElem}.`);
|
|
|
|
}
|
2016-02-22 23:21:41 +01:00
|
|
|
case "depleted-reserve":
|
|
|
|
return m("p",
|
2016-09-29 01:40:29 +02:00
|
|
|
i18n.parts`Withdraw from reserve (${abbrev(d.reservePub)}) of ${formatAmount(
|
|
|
|
d.requestedAmount)} completed.`);
|
|
|
|
case "pay": {
|
2016-02-01 15:10:20 +01:00
|
|
|
let url = substituteFulfillmentUrl(d.fulfillmentUrl,
|
|
|
|
{H_contract: d.contractHash});
|
2016-09-29 01:40:29 +02:00
|
|
|
let merchantElem = m("em", abbrev(d.merchantName, 15));
|
|
|
|
let fulfillmentLinkElem = m(`a`,
|
|
|
|
{href: url, onclick: openTab(url)},
|
|
|
|
"view product");
|
2016-01-24 02:29:13 +01:00
|
|
|
return m("p",
|
2016-09-29 01:40:29 +02:00
|
|
|
i18n.parts`Confirmed payment of ${formatAmount(d.amount)} to merchant ${merchantElem}. (${fulfillmentLinkElem})`);
|
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
default:
|
|
|
|
return m("p", i18n`Unknown event (${historyItem.type})`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-18 23:41:29 +01:00
|
|
|
namespace WalletHistory {
|
|
|
|
export function controller() {
|
|
|
|
return new Controller();
|
|
|
|
}
|
|
|
|
|
|
|
|
class Controller {
|
2016-09-28 23:41:34 +02:00
|
|
|
myHistory: any[];
|
2016-03-02 00:47:00 +01:00
|
|
|
gotError = false;
|
2016-02-18 23:41:29 +01:00
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.update();
|
|
|
|
onUpdateNotification(() => this.update());
|
|
|
|
}
|
|
|
|
|
|
|
|
update() {
|
|
|
|
m.startComputation();
|
|
|
|
chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
|
2016-03-02 00:47:00 +01:00
|
|
|
if (resp.error) {
|
|
|
|
this.gotError = true;
|
|
|
|
console.error("could not retrieve history", resp);
|
|
|
|
m.endComputation();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.gotError = false;
|
|
|
|
console.log("got history", resp.history);
|
|
|
|
this.myHistory = resp.history;
|
2016-02-18 23:41:29 +01:00
|
|
|
m.endComputation();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function view(ctrl: Controller) {
|
2016-09-28 23:41:34 +02:00
|
|
|
let history: HistoryRecord[] = ctrl.myHistory;
|
2016-03-02 00:47:00 +01:00
|
|
|
if (ctrl.gotError) {
|
|
|
|
return i18n`Error: could not retrieve event history`;
|
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
if (!history) {
|
|
|
|
throw Error("Could not retrieve history");
|
|
|
|
}
|
2016-09-28 23:41:34 +02:00
|
|
|
|
|
|
|
let subjectMemo: {[s: string]: boolean} = {};
|
|
|
|
let listing: any[] = [];
|
|
|
|
for (let record of history.reverse()) {
|
2016-09-29 01:40:29 +02:00
|
|
|
if (record.subjectId && subjectMemo[record.subjectId]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (record.level != undefined && record.level < HistoryLevel.User) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-09-28 23:41:34 +02:00
|
|
|
subjectMemo[record.subjectId as string] = true;
|
2016-09-29 01:40:29 +02:00
|
|
|
|
|
|
|
let item = m("div.historyItem", {}, [
|
|
|
|
m("div.historyDate", {}, (new Date(record.timestamp * 1000)).toString()),
|
|
|
|
formatHistoryItem(record)
|
|
|
|
]);
|
|
|
|
|
|
|
|
listing.push(item);
|
2016-09-28 23:41:34 +02:00
|
|
|
}
|
|
|
|
|
2016-01-24 02:29:13 +01:00
|
|
|
if (listing.length > 0) {
|
2016-01-24 19:57:09 +01:00
|
|
|
return m("div.container", listing);
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
|
|
|
return i18n`Your wallet has no events recorded.`;
|
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
|
|
|
|
2016-01-26 17:21:17 +01:00
|
|
|
function reload() {
|
|
|
|
try {
|
|
|
|
chrome.runtime.reload();
|
|
|
|
window.close();
|
|
|
|
} catch (e) {
|
|
|
|
// Functionality missing in firefox, ignore!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-24 02:29:13 +01:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var WalletDebug = {
|
|
|
|
view() {
|
|
|
|
return [
|
2016-01-26 17:21:17 +01:00
|
|
|
m("button",
|
|
|
|
{onclick: openExtensionPage("popup/popup.html")},
|
|
|
|
"wallet tab"),
|
|
|
|
m("button",
|
|
|
|
{onclick: openExtensionPage("pages/show-db.html")},
|
|
|
|
"show db"),
|
|
|
|
m("br"),
|
|
|
|
m("button", {onclick: confirmReset}, "reset"),
|
|
|
|
m("button", {onclick: reload}, "reload chrome extension"),
|
2016-01-24 02:29:13 +01:00
|
|
|
]
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function openExtensionPage(page: string) {
|
2016-01-26 17:21:17 +01:00
|
|
|
return function() {
|
|
|
|
chrome.tabs.create({
|
|
|
|
"url": chrome.extension.getURL(page)
|
|
|
|
});
|
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-01-26 17:21:17 +01:00
|
|
|
|
2016-02-01 15:10:20 +01:00
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function openTab(page: string) {
|
2016-02-01 15:10:20 +01:00
|
|
|
return function() {
|
|
|
|
chrome.tabs.create({
|
|
|
|
"url": page
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|