aboutsummaryrefslogtreecommitdiff
path: root/extension
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-01-06 15:39:22 +0100
committerFlorian Dold <florian.dold@gmail.com>2016-01-06 15:39:22 +0100
commitabf15268acafe588191fffb7ca6ddb963244bb0f (patch)
tree0f582386d09eccd550c414e62337a7f630b2ddb1 /extension
parent2f8aa00595ab40292019ca739041296c84703899 (diff)
downloadwallet-core-abf15268acafe588191fffb7ca6ddb963244bb0f.tar.xz
Refactor wallet into logic and extension interface.
Diffstat (limited to 'extension')
-rw-r--r--extension/background/checkable.ts31
-rw-r--r--extension/background/db.js1
-rw-r--r--extension/background/db.ts6
-rw-r--r--extension/background/messaging.ts101
-rw-r--r--extension/background/wallet.js803
-rw-r--r--extension/background/wallet.ts947
-rw-r--r--extension/content_scripts/notify.js2
-rw-r--r--extension/content_scripts/notify.ts2
-rw-r--r--extension/manifest.json2
-rw-r--r--extension/popup/balance-overview.html2
-rw-r--r--extension/popup/balance-overview.js17
-rw-r--r--extension/popup/balance-overview.tsx26
-rw-r--r--extension/popup/history.html32
-rw-r--r--extension/popup/history.tsx22
-rw-r--r--extension/popup/reserve-create-sepa.html2
-rw-r--r--extension/popup/reserve-create.html2
-rw-r--r--extension/popup/reserves.html2
-rw-r--r--extension/popup/transactions.html62
-rw-r--r--extension/popup/transactions.js39
-rw-r--r--extension/tsconfig.json1
20 files changed, 1081 insertions, 1021 deletions
diff --git a/extension/background/checkable.ts b/extension/background/checkable.ts
index f7e99df92..7cf50318a 100644
--- a/extension/background/checkable.ts
+++ b/extension/background/checkable.ts
@@ -41,6 +41,13 @@ namespace Checkable {
return target;
}
+ function checkAnyObject(target, prop): any {
+ if (typeof target !== "object") {
+ throw Error("object expected for " + prop.propertyKey);
+ }
+ return target;
+ }
+
function checkValue(target, prop): any {
let type = prop.type;
if (!type) {
@@ -84,11 +91,7 @@ namespace Checkable {
export function Value(type) {
function deco(target: Object, propertyKey: string | symbol): void {
- let chk = target[chkSym];
- if (!chk) {
- chk = {props: []};
- target[chkSym] = chk;
- }
+ let chk = mkChk(target);
chk.props.push({
propertyKey: propertyKey,
checker: checkValue,
@@ -108,20 +111,26 @@ namespace Checkable {
}
export function Number(target: Object, propertyKey: string | symbol): void {
- let chk = target[chkSym];
- if (!chk) {
- chk = {props: []};
- target[chkSym] = chk;
- }
+ let chk = mkChk(target);
chk.props.push({propertyKey: propertyKey, checker: checkNumber});
}
+ export function AnyObject(target: Object, propertyKey: string | symbol): void {
+ let chk = mkChk(target);
+ chk.props.push({propertyKey: propertyKey, checker: checkAnyObject});
+ }
+
export function String(target: Object, propertyKey: string | symbol): void {
+ let chk = mkChk(target);
+ chk.props.push({propertyKey: propertyKey, checker: checkString});
+ }
+
+ function mkChk(target) {
let chk = target[chkSym];
if (!chk) {
chk = {props: []};
target[chkSym] = chk;
}
- chk.props.push({propertyKey: propertyKey, checker: checkString});
+ return chk;
}
}
diff --git a/extension/background/db.js b/extension/background/db.js
index 4b81555da..8abf56b48 100644
--- a/extension/background/db.js
+++ b/extension/background/db.js
@@ -42,6 +42,7 @@ function openTalerDb() {
coins.createIndex("mintBaseUrl", "mintBaseUrl");
db.createObjectStore("transactions", { keyPath: "contractHash" });
db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true });
+ db.createObjectStore("history", { keyPath: "id", autoIncrement: true });
break;
}
};
diff --git a/extension/background/db.ts b/extension/background/db.ts
index d3c6e9182..2807eb185 100644
--- a/extension/background/db.ts
+++ b/extension/background/db.ts
@@ -67,11 +67,8 @@ namespace Db {
currentAmount: AmountJson_interface;
mintBaseUrl: string;
}
-
-
}
-
const DB_NAME = "taler";
const DB_VERSION = 1;
@@ -102,6 +99,7 @@ function openTalerDb(): Promise<IDBDatabase> {
db.createObjectStore("transactions", {keyPath: "contractHash"});
db.createObjectStore("precoins",
{keyPath: "coinPub", autoIncrement: true});
+ db.createObjectStore("history", {keyPath: "id", autoIncrement: true});
break;
}
};
@@ -137,4 +135,4 @@ function exportDb(db): Promise<any> {
});
}
});
-} \ No newline at end of file
+}
diff --git a/extension/background/messaging.ts b/extension/background/messaging.ts
index 6d444f95d..8cde06262 100644
--- a/extension/background/messaging.ts
+++ b/extension/background/messaging.ts
@@ -25,45 +25,82 @@
"use strict";
-// FIXME: none of these handlers should pass on the sendResponse.
-
-let handlers = {
- ["balances"]: function(db, detail, sendResponse) {
- getBalances(db).then(sendResponse);
- return true;
- },
- ["dump-db"]: function(db, detail, sendResponse) {
- exportDb(db).then(sendResponse);
- return true;
- },
- ["reset"]: function(db, detail, sendResponse) {
- let tx = db.transaction(db.objectStoreNames, 'readwrite');
- for (let i = 0; i < db.objectStoreNames.length; i++) {
- tx.objectStore(db.objectStoreNames[i]).clear();
+function makeHandlers(wallet) {
+ return {
+ ["balances"]: function(db, detail, sendResponse) {
+ wallet.getBalances().then(sendResponse);
+ return true;
+ },
+ ["dump-db"]: function(db, detail, sendResponse) {
+ exportDb(db).then(sendResponse);
+ return true;
+ },
+ ["reset"]: function(db, detail, sendResponse) {
+ let tx = db.transaction(db.objectStoreNames, 'readwrite');
+ for (let i = 0; i < db.objectStoreNames.length; i++) {
+ tx.objectStore(db.objectStoreNames[i]).clear();
+ }
+ indexedDB.deleteDatabase(DB_NAME);
+ chrome.browserAction.setBadgeText({text: ""});
+ console.log("reset done");
+ // Response is synchronous
+ return false;
+ },
+ ["confirm-reserve"]: function(db, detail, sendResponse) {
+ // TODO: make it a checkable
+ let req: ConfirmReserveRequest = {
+ field_amount: detail.field_amount,
+ field_mint: detail.field_mint,
+ field_reserve_pub: detail.field_reserve_pub,
+ post_url: detail.post_url,
+ mint: detail.mint,
+ amount_str: detail.amount_str
+ };
+ wallet.confirmReserve(req)
+ .then((resp) => {
+ if (resp.success) {
+ resp.backlink = chrome.extension.getURL("pages/reserve-success.html");
+ }
+ sendResponse(resp);
+ });
+ return true;
+ },
+ ["confirm-pay"]: function(db, detail, sendResponse) {
+ wallet.confirmPay(detail.offer, detail.merchantPageUrl)
+ .then(() => {
+ sendResponse({success: true})
+ })
+ .catch((e) => {
+ sendResponse({error: e.message});
+ });
+ return true;
+ },
+ ["execute-payment"]: function(db, detail, sendResponse) {
+ wallet.doPayment(detail.H_contract)
+ .then((r) => {
+ sendResponse({
+ success: true,
+ payUrl: r.payUrl,
+ payReq: r.payReq
+ });
+ })
+ .catch((e) => {
+ sendResponse({success: false, error: e.message});
+ });
+ // async sendResponse
+ return true;
}
- indexedDB.deleteDatabase(DB_NAME);
- chrome.browserAction.setBadgeText({text: ""});
- console.log("reset done");
- // Response is synchronous
- return false;
- },
- ["confirm-reserve"]: function(db, detail, sendResponse) {
- return confirmReserveHandler(db, detail, sendResponse);
- },
- ["confirm-pay"]: function(db, detail, sendResponse) {
- return confirmPayHandler(db, detail, sendResponse);
- },
- ["execute-payment"]: function(db, detail, sendResponse) {
- return doPaymentHandler(db, detail, sendResponse);
- }
-};
+ };
+}
function wxMain() {
chrome.browserAction.setBadgeText({text: ""});
openTalerDb().then((db) => {
- updateBadge(db);
+ let wallet = new Wallet(db, undefined, undefined);
+ let handlers = makeHandlers(wallet);
+ wallet.updateBadge();
chrome.runtime.onMessage.addListener(
function(req, sender, onresponse) {
if (req.type in handlers) {
diff --git a/extension/background/wallet.js b/extension/background/wallet.js
index 0962961d6..f0337818c 100644
--- a/extension/background/wallet.js
+++ b/extension/background/wallet.js
@@ -75,438 +75,429 @@ function canonicalizeBaseUrl(url) {
x.query();
return x.href();
}
-function signDeposit(db, offer, cds) {
- let ret = [];
- let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency);
- let amountRemaining = new Amount(offer.contract.amount);
- cds = copy(cds);
- for (let cd of cds) {
- let coinSpend;
- if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
- break;
- }
- if (amountRemaining.cmp(new Amount(cd.coin.currentAmount)) < 0) {
- coinSpend = new Amount(amountRemaining.toJson());
- }
- else {
- coinSpend = new Amount(cd.coin.currentAmount);
- }
- amountSpent.add(coinSpend);
- amountRemaining.sub(coinSpend);
- let newAmount = new Amount(cd.coin.currentAmount);
- newAmount.sub(coinSpend);
- cd.coin.currentAmount = newAmount.toJson();
- let args = {
- h_contract: HashCode.fromCrock(offer.H_contract),
- h_wire: HashCode.fromCrock(offer.contract.H_wire),
- amount_with_fee: coinSpend.toNbo(),
- coin_pub: EddsaPublicKey.fromCrock(cd.coin.coinPub),
- deposit_fee: new Amount(cd.denom.fee_deposit).toNbo(),
- merchant: EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
- refund_deadline: AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
- timestamp: AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
- transaction_id: UInt64.fromNumber(offer.contract.transaction_id),
- };
- let d = new DepositRequestPS(args);
- let coinSig = eddsaSign(d.toPurpose(), EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
- .toCrock();
- let s = {
- coin_sig: coinSig,
- coin_pub: cd.coin.coinPub,
- ub_sig: cd.coin.denomSig,
- denom_pub: cd.coin.denomPub,
- f: coinSpend.toJson(),
- };
- ret.push({ sig: s, updatedCoin: cd.coin });
- }
- return ret;
+function copy(o) {
+ return JSON.parse(JSON.stringify(o));
}
-/**
- * Get mints and associated coins that are still spendable,
- * but only if the sum the coins' remaining value exceeds the payment amount.
- * @param db
- * @param paymentAmount
- * @param depositFeeLimit
- * @param allowedMints
- */
-function getPossibleMintCoins(db, paymentAmount, depositFeeLimit, allowedMints) {
- let m = {};
- function storeMintCoin(mc) {
- let mint = mc[0];
- let coin = mc[1];
- let cd = {
- coin: coin,
- denom: mint.keys.denoms.find((e) => e.denom_pub === coin.denomPub)
- };
- if (!cd.denom) {
- throw Error("denom not found (database inconsistent)");
- }
- let x = m[mint.baseUrl];
- if (!x) {
- m[mint.baseUrl] = [cd];
- }
- else {
- x.push(cd);
- }
+function rankDenom(denom1, denom2) {
+ // Slow ... we should find a better way than to convert it evert time.
+ let v1 = new Amount(denom1.value);
+ let v2 = new Amount(denom2.value);
+ return (-1) * v1.cmp(v2);
+}
+class Wallet {
+ constructor(db, http, badge) {
+ this.db = db;
+ this.http = http;
+ this.badge = badge;
}
- let ps = allowedMints.map((info) => {
- return Query(db)
- .iterIndex("mints", "pubKey", info.master_pub)
- .indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
- .reduce(storeMintCoin);
- });
- return Promise.all(ps).then(() => {
- let ret = {};
- nextMint: for (let key in m) {
- let coins = m[key].map((x) => ({
- a: new Amount(x.denom.fee_deposit),
- c: x
- }));
- // Sort by ascending deposit fee
- coins.sort((o1, o2) => o1.a.cmp(o2.a));
- let maxFee = new Amount(depositFeeLimit);
- let minAmount = new Amount(paymentAmount);
- let accFee = new Amount(coins[0].c.denom.fee_deposit);
- let accAmount = Amount.getZero(coins[0].c.coin.currentAmount.currency);
- let usableCoins = [];
- nextCoin: for (let i = 0; i < coins.length; i++) {
- let coinAmount = new Amount(coins[i].c.coin.currentAmount);
- let coinFee = coins[i].a;
- if (coinAmount.cmp(coinFee) <= 0) {
- continue nextCoin;
- }
- accFee.add(coinFee);
- accAmount.add(coinAmount);
- if (accFee.cmp(maxFee) >= 0) {
- console.log("too much fees");
- continue nextMint;
- }
- usableCoins.push(coins[i].c);
- if (accAmount.cmp(minAmount) >= 0) {
- ret[key] = usableCoins;
- continue nextMint;
- }
+ static signDeposit(offer, cds) {
+ let ret = [];
+ let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency);
+ let amountRemaining = new Amount(offer.contract.amount);
+ cds = copy(cds);
+ for (let cd of cds) {
+ let coinSpend;
+ if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
+ break;
}
+ if (amountRemaining.cmp(new Amount(cd.coin.currentAmount)) < 0) {
+ coinSpend = new Amount(amountRemaining.toJson());
+ }
+ else {
+ coinSpend = new Amount(cd.coin.currentAmount);
+ }
+ amountSpent.add(coinSpend);
+ amountRemaining.sub(coinSpend);
+ let newAmount = new Amount(cd.coin.currentAmount);
+ newAmount.sub(coinSpend);
+ cd.coin.currentAmount = newAmount.toJson();
+ let args = {
+ h_contract: HashCode.fromCrock(offer.H_contract),
+ h_wire: HashCode.fromCrock(offer.contract.H_wire),
+ amount_with_fee: coinSpend.toNbo(),
+ coin_pub: EddsaPublicKey.fromCrock(cd.coin.coinPub),
+ deposit_fee: new Amount(cd.denom.fee_deposit).toNbo(),
+ merchant: EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
+ refund_deadline: AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
+ timestamp: AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
+ transaction_id: UInt64.fromNumber(offer.contract.transaction_id),
+ };
+ let d = new DepositRequestPS(args);
+ let coinSig = eddsaSign(d.toPurpose(), EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
+ .toCrock();
+ let s = {
+ coin_sig: coinSig,
+ coin_pub: cd.coin.coinPub,
+ ub_sig: cd.coin.denomSig,
+ denom_pub: cd.coin.denomPub,
+ f: coinSpend.toJson(),
+ };
+ ret.push({ sig: s, updatedCoin: cd.coin });
}
return ret;
- });
-}
-function executePay(db, offer, payCoinInfo, merchantBaseUrl, chosenMint) {
- let payReq = {};
- payReq["H_wire"] = offer.contract.H_wire;
- payReq["H_contract"] = offer.H_contract;
- payReq["transaction_id"] = offer.contract.transaction_id;
- payReq["refund_deadline"] = offer.contract.refund_deadline;
- payReq["mint"] = URI(chosenMint).href();
- payReq["coins"] = payCoinInfo.map((x) => x.sig);
- payReq["timestamp"] = offer.contract.timestamp;
- let payUrl = URI(offer.pay_url).absoluteTo(merchantBaseUrl);
- let t = {
- contractHash: offer.H_contract,
- contract: offer.contract,
- payUrl: payUrl.href(),
- payReq: payReq
- };
- return Query(db)
- .put("transactions", t)
- .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
- .finish();
-}
-function confirmPayHandler(db, detail, sendResponse) {
- let offer = detail.offer;
- getPossibleMintCoins(db, offer.contract.amount, offer.contract.max_fee, offer.contract.mints)
- .then((mcs) => {
- if (Object.keys(mcs).length == 0) {
- sendResponse({ error: "Not enough coins." });
- return;
+ }
+ /**
+ * Get mints and associated coins that are still spendable,
+ * but only if the sum the coins' remaining value exceeds the payment amount.
+ * @param paymentAmount
+ * @param depositFeeLimit
+ * @param allowedMints
+ */
+ getPossibleMintCoins(paymentAmount, depositFeeLimit, allowedMints) {
+ let m = {};
+ function storeMintCoin(mc) {
+ let mint = mc[0];
+ let coin = mc[1];
+ let cd = {
+ coin: coin,
+ denom: mint.keys.denoms.find((e) => e.denom_pub === coin.denomPub)
+ };
+ if (!cd.denom) {
+ throw Error("denom not found (database inconsistent)");
+ }
+ let x = m[mint.baseUrl];
+ if (!x) {
+ m[mint.baseUrl] = [cd];
+ }
+ else {
+ x.push(cd);
+ }
}
- let mintUrl = Object.keys(mcs)[0];
- let ds = signDeposit(db, offer, mcs[mintUrl]);
- return executePay(db, offer, ds, detail.merchantPageUrl, mintUrl)
- .then(() => {
- sendResponse({
- success: true,
- });
+ let ps = allowedMints.map((info) => {
+ return Query(this.db)
+ .iterIndex("mints", "pubKey", info.master_pub)
+ .indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
+ .reduce(storeMintCoin);
});
- });
- return true;
-}
-function doPaymentHandler(db, detail, sendResponse) {
- let H_contract = detail.H_contract;
- Query(db)
- .get("transactions", H_contract)
- .then((r) => {
- if (!r) {
- sendResponse({ success: false, error: "contract not found" });
- return;
- }
- sendResponse({
- success: true,
- payUrl: r.payUrl,
- payReq: r.payReq
+ return Promise.all(ps).then(() => {
+ let ret = {};
+ nextMint: for (let key in m) {
+ let coins = m[key].map((x) => ({
+ a: new Amount(x.denom.fee_deposit),
+ c: x
+ }));
+ // Sort by ascending deposit fee
+ coins.sort((o1, o2) => o1.a.cmp(o2.a));
+ let maxFee = new Amount(depositFeeLimit);
+ let minAmount = new Amount(paymentAmount);
+ let accFee = new Amount(coins[0].c.denom.fee_deposit);
+ let accAmount = Amount.getZero(coins[0].c.coin.currentAmount.currency);
+ let usableCoins = [];
+ nextCoin: for (let i = 0; i < coins.length; i++) {
+ let coinAmount = new Amount(coins[i].c.coin.currentAmount);
+ let coinFee = coins[i].a;
+ if (coinAmount.cmp(coinFee) <= 0) {
+ continue nextCoin;
+ }
+ accFee.add(coinFee);
+ accAmount.add(coinAmount);
+ if (accFee.cmp(maxFee) >= 0) {
+ console.log("too much fees");
+ continue nextMint;
+ }
+ usableCoins.push(coins[i].c);
+ if (accAmount.cmp(minAmount) >= 0) {
+ ret[key] = usableCoins;
+ continue nextMint;
+ }
+ }
+ }
+ return ret;
});
- });
- // async sendResponse
- return true;
-}
-function confirmReserveHandler(db, detail, sendResponse) {
- let reservePriv = EddsaPrivateKey.create();
- let reservePub = reservePriv.getPublicKey();
- let form = new FormData();
- let now = (new Date()).toString();
- form.append(detail.field_amount, detail.amount_str);
- form.append(detail.field_reserve_pub, reservePub.toCrock());
- form.append(detail.field_mint, detail.mint);
- // XXX: set bank-specified fields.
- let mintBaseUrl = canonicalizeBaseUrl(detail.mint);
- httpPostForm(detail.post_url, form)
- .then((hresp) => {
- // TODO: extract as interface
- let resp = {
- status: hresp.status,
- text: hresp.responseText,
- success: undefined,
- backlink: undefined
- };
- let reserveRecord = {
- reserve_pub: reservePub.toCrock(),
- reserve_priv: reservePriv.toCrock(),
- mint_base_url: mintBaseUrl,
- created: now,
- last_query: null,
- current_amount: null,
- // XXX: set to actual amount
- initial_amount: null
+ }
+ executePay(offer, payCoinInfo, merchantBaseUrl, chosenMint) {
+ let payReq = {};
+ payReq["H_wire"] = offer.contract.H_wire;
+ payReq["H_contract"] = offer.H_contract;
+ payReq["transaction_id"] = offer.contract.transaction_id;
+ payReq["refund_deadline"] = offer.contract.refund_deadline;
+ payReq["mint"] = URI(chosenMint).href();
+ payReq["coins"] = payCoinInfo.map((x) => x.sig);
+ payReq["timestamp"] = offer.contract.timestamp;
+ let payUrl = URI(offer.pay_url).absoluteTo(merchantBaseUrl);
+ let t = {
+ contractHash: offer.H_contract,
+ contract: offer.contract,
+ payUrl: payUrl.href(),
+ payReq: payReq
};
- if (hresp.status != 200) {
- resp.success = false;
- return resp;
- }
- resp.success = true;
- // We can't show the page directly, so
- // we show some generic page from the wallet.
- // TODO: this should not be webextensions-specific
- resp.backlink = chrome.extension.getURL("pages/reserve-success.html");
- return Query(db)
- .put("reserves", reserveRecord)
- .finish()
- .then(() => {
- // Do this in the background
- updateMintFromUrl(db, reserveRecord.mint_base_url)
- .then((mint) => updateReserve(db, reservePub, mint)
- .then((reserve) => depleteReserve(db, reserve, mint)));
- return resp;
+ return Query(this.db)
+ .put("transactions", t)
+ .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
+ .finish();
+ }
+ confirmPay(offer, merchantPageUrl) {
+ return Promise.resolve().then(() => {
+ return this.getPossibleMintCoins(offer.contract.amount, offer.contract.max_fee, offer.contract.mints);
+ }).then((mcs) => {
+ if (Object.keys(mcs).length == 0) {
+ throw Error("Not enough coins.");
+ }
+ let mintUrl = Object.keys(mcs)[0];
+ let ds = Wallet.signDeposit(offer, mcs[mintUrl]);
+ return this.executePay(offer, ds, merchantPageUrl, mintUrl);
});
- })
- .then((resp) => {
- sendResponse(resp);
- });
- // Allow async response
- return true;
-}
-function copy(o) {
- return JSON.parse(JSON.stringify(o));
-}
-function rankDenom(denom1, denom2) {
- // Slow ... we should find a better way than to convert it evert time.
- let v1 = new Amount(denom1.value);
- let v2 = new Amount(denom2.value);
- return (-1) * v1.cmp(v2);
-}
-function withdrawPrepare(db, denom, reserve) {
- let reservePriv = new EddsaPrivateKey();
- reservePriv.loadCrock(reserve.reserve_priv);
- let reservePub = new EddsaPublicKey();
- reservePub.loadCrock(reserve.reserve_pub);
- let denomPub = RsaPublicKey.fromCrock(denom.denom_pub);
- let coinPriv = EddsaPrivateKey.create();
- let coinPub = coinPriv.getPublicKey();
- let blindingFactor = RsaBlindingKey.create(1024);
- let pubHash = coinPub.hash();
- let ev = rsaBlind(pubHash, blindingFactor, denomPub);
- if (!denom.fee_withdraw) {
- throw Error("Field fee_withdraw missing");
}
- let amountWithFee = new Amount(denom.value);
- amountWithFee.add(new Amount(denom.fee_withdraw));
- let withdrawFee = new Amount(denom.fee_withdraw);
- // Signature
- let withdrawRequest = new WithdrawRequestPS({
- reserve_pub: reservePub,
- amount_with_fee: amountWithFee.toNbo(),
- withdraw_fee: withdrawFee.toNbo(),
- h_denomination_pub: denomPub.encode().hash(),
- h_coin_envelope: ev.hash()
- });
- var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv);
- let preCoin = {
- reservePub: reservePub.toCrock(),
- blindingKey: blindingFactor.toCrock(),
- coinPub: coinPub.toCrock(),
- coinPriv: coinPriv.toCrock(),
- denomPub: denomPub.encode().toCrock(),
- mintBaseUrl: reserve.mint_base_url,
- withdrawSig: sig.toCrock(),
- coinEv: ev.toCrock(),
- coinValue: denom.value
- };
- return Query(db).put("precoins", preCoin).finish().then(() => preCoin);
-}
-function withdrawExecute(db, pc) {
- return Query(db)
- .get("reserves", pc.reservePub)
- .then((r) => {
- let wd = {};
- wd.denom_pub = pc.denomPub;
- wd.reserve_pub = pc.reservePub;
- wd.reserve_sig = pc.withdrawSig;
- wd.coin_ev = pc.coinEv;
- let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url);
- return httpPostJson(reqUrl, wd);
- })
- .then(resp => {
- if (resp.status != 200) {
- throw new RequestException({
- hint: "Withdrawal failed",
- status: resp.status
+ doPayment(H_contract) {
+ return Promise.resolve().then(() => {
+ return Query(this.db)
+ .get("transactions", H_contract)
+ .then((t) => {
+ if (!t) {
+ throw Error("contract not found");
+ }
+ let resp = {
+ payUrl: t.payUrl,
+ payReq: t.payReq
+ };
+ return resp;
});
+ });
+ }
+ confirmReserve(req) {
+ let reservePriv = EddsaPrivateKey.create();
+ let reservePub = reservePriv.getPublicKey();
+ let form = new FormData();
+ let now = (new Date()).toString();
+ form.append(req.field_amount, req.amount_str);
+ form.append(req.field_reserve_pub, reservePub.toCrock());
+ form.append(req.field_mint, req.mint);
+ // TODO: set bank-specified fields.
+ let mintBaseUrl = canonicalizeBaseUrl(req.mint);
+ return httpPostForm(req.post_url, form)
+ .then((hresp) => {
+ let resp = {
+ status: hresp.status,
+ text: hresp.responseText,
+ success: undefined,
+ backlink: undefined
+ };
+ let reserveRecord = {
+ reserve_pub: reservePub.toCrock(),
+ reserve_priv: reservePriv.toCrock(),
+ mint_base_url: mintBaseUrl,
+ created: now,
+ last_query: null,
+ current_amount: null,
+ // XXX: set to actual amount
+ initial_amount: null
+ };
+ if (hresp.status != 200) {
+ resp.success = false;
+ return resp;
+ }
+ resp.success = true;
+ // We can't show the page directly, so
+ // we show some generic page from the wallet.
+ resp.backlink = null;
+ return Query(this.db)
+ .put("reserves", reserveRecord)
+ .finish()
+ .then(() => {
+ // Do this in the background
+ this.updateMintFromUrl(reserveRecord.mint_base_url)
+ .then((mint) => this.updateReserve(reservePub, mint)
+ .then((reserve) => this.depleteReserve(reserve, mint)));
+ return resp;
+ });
+ });
+ }
+ withdrawPrepare(denom, reserve) {
+ let reservePriv = new EddsaPrivateKey();
+ reservePriv.loadCrock(reserve.reserve_priv);
+ let reservePub = new EddsaPublicKey();
+ reservePub.loadCrock(reserve.reserve_pub);
+ let denomPub = RsaPublicKey.fromCrock(denom.denom_pub);
+ let coinPriv = EddsaPrivateKey.create();
+ let coinPub = coinPriv.getPublicKey();
+ let blindingFactor = RsaBlindingKey.create(1024);
+ let pubHash = coinPub.hash();
+ let ev = rsaBlind(pubHash, blindingFactor, denomPub);
+ if (!denom.fee_withdraw) {
+ throw Error("Field fee_withdraw missing");
}
- let r = JSON.parse(resp.responseText);
- let denomSig = rsaUnblind(RsaSignature.fromCrock(r.ev_sig), RsaBlindingKey.fromCrock(pc.blindingKey), RsaPublicKey.fromCrock(pc.denomPub));
- let coin = {
- coinPub: pc.coinPub,
- coinPriv: pc.coinPriv,
- denomPub: pc.denomPub,
- denomSig: denomSig.encode().toCrock(),
- currentAmount: pc.coinValue,
- mintBaseUrl: pc.mintBaseUrl,
+ let amountWithFee = new Amount(denom.value);
+ amountWithFee.add(new Amount(denom.fee_withdraw));
+ let withdrawFee = new Amount(denom.fee_withdraw);
+ // Signature
+ let withdrawRequest = new WithdrawRequestPS({
+ reserve_pub: reservePub,
+ amount_with_fee: amountWithFee.toNbo(),
+ withdraw_fee: withdrawFee.toNbo(),
+ h_denomination_pub: denomPub.encode().hash(),
+ h_coin_envelope: ev.hash()
+ });
+ var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv);
+ let preCoin = {
+ reservePub: reservePub.toCrock(),
+ blindingKey: blindingFactor.toCrock(),
+ coinPub: coinPub.toCrock(),
+ coinPriv: coinPriv.toCrock(),
+ denomPub: denomPub.encode().toCrock(),
+ mintBaseUrl: reserve.mint_base_url,
+ withdrawSig: sig.toCrock(),
+ coinEv: ev.toCrock(),
+ coinValue: denom.value
};
- return coin;
- });
-}
-function updateBadge(db) {
- function countNonEmpty(c, n) {
- if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
- return n + 1;
- }
- return n;
+ return Query(this.db).put("precoins", preCoin).finish().then(() => preCoin);
}
- function doBadge(n) {
- chrome.browserAction.setBadgeText({ text: "" + n });
- chrome.browserAction.setBadgeBackgroundColor({ color: "#0F0" });
+ withdrawExecute(pc) {
+ return Query(this.db)
+ .get("reserves", pc.reservePub)
+ .then((r) => {
+ let wd = {};
+ wd.denom_pub = pc.denomPub;
+ wd.reserve_pub = pc.reservePub;
+ wd.reserve_sig = pc.withdrawSig;
+ wd.coin_ev = pc.coinEv;
+ let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url);
+ return httpPostJson(reqUrl, wd);
+ })
+ .then(resp => {
+ if (resp.status != 200) {
+ throw new RequestException({
+ hint: "Withdrawal failed",
+ status: resp.status
+ });
+ }
+ let r = JSON.parse(resp.responseText);
+ let denomSig = rsaUnblind(RsaSignature.fromCrock(r.ev_sig), RsaBlindingKey.fromCrock(pc.blindingKey), RsaPublicKey.fromCrock(pc.denomPub));
+ let coin = {
+ coinPub: pc.coinPub,
+ coinPriv: pc.coinPriv,
+ denomPub: pc.denomPub,
+ denomSig: denomSig.encode().toCrock(),
+ currentAmount: pc.coinValue,
+ mintBaseUrl: pc.mintBaseUrl,
+ };
+ return coin;
+ });
}
- Query(db)
- .iter("coins")
- .reduce(countNonEmpty, 0)
- .then(doBadge);
-}
-function storeCoin(db, coin) {
- Query(db)
- .delete("precoins", coin.coinPub)
- .add("coins", coin)
- .finish()
- .then(() => {
- updateBadge(db);
- });
-}
-function withdraw(db, denom, reserve) {
- return withdrawPrepare(db, denom, reserve)
- .then((pc) => withdrawExecute(db, pc))
- .then((c) => storeCoin(db, c));
-}
-/**
- * Withdraw coins from a reserve until it is empty.
- */
-function depleteReserve(db, reserve, mint) {
- let denoms = copy(mint.keys.denoms);
- let remaining = new Amount(reserve.current_amount);
- denoms.sort(rankDenom);
- let workList = [];
- for (let i = 0; i < 1000; i++) {
- let found = false;
- for (let d of denoms) {
- let cost = new Amount(d.value);
- cost.add(new Amount(d.fee_withdraw));
- if (remaining.cmp(cost) < 0) {
- continue;
+ updateBadge() {
+ function countNonEmpty(c, n) {
+ if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
+ return n + 1;
}
- found = true;
- remaining.sub(cost);
- workList.push(d);
+ return n;
}
- if (!found) {
- console.log("did not find coins for remaining ", remaining.toJson());
- break;
+ function doBadge(n) {
+ chrome.browserAction.setBadgeText({ text: "" + n });
+ chrome.browserAction.setBadgeBackgroundColor({ color: "#0F0" });
}
+ Query(this.db)
+ .iter("coins")
+ .reduce(countNonEmpty, 0)
+ .then(doBadge);
+ }
+ storeCoin(coin) {
+ Query(this.db)
+ .delete("precoins", coin.coinPub)
+ .add("coins", coin)
+ .finish()
+ .then(() => {
+ this.updateBadge();
+ });
+ }
+ withdraw(denom, reserve) {
+ return this.withdrawPrepare(denom, reserve)
+ .then((pc) => this.withdrawExecute(pc))
+ .then((c) => this.storeCoin(c));
}
- // Do the request one by one.
- function next() {
- if (workList.length == 0) {
- return;
+ /**
+ * Withdraw coins from a reserve until it is empty.
+ */
+ depleteReserve(reserve, mint) {
+ let denoms = copy(mint.keys.denoms);
+ let remaining = new Amount(reserve.current_amount);
+ denoms.sort(rankDenom);
+ let workList = [];
+ for (let i = 0; i < 1000; i++) {
+ let found = false;
+ for (let d of denoms) {
+ let cost = new Amount(d.value);
+ cost.add(new Amount(d.fee_withdraw));
+ if (remaining.cmp(cost) < 0) {
+ continue;
+ }
+ found = true;
+ remaining.sub(cost);
+ workList.push(d);
+ }
+ if (!found) {
+ console.log("did not find coins for remaining ", remaining.toJson());
+ break;
+ }
}
- let d = workList.pop();
- withdraw(db, d, reserve)
- .then(() => next());
+ // Do the request one by one.
+ let next = () => {
+ if (workList.length == 0) {
+ return;
+ }
+ let d = workList.pop();
+ this.withdraw(d, reserve)
+ .then(() => next());
+ };
+ // Asynchronous recursion
+ next();
}
- next();
-}
-function updateReserve(db, reservePub, mint) {
- let reservePubStr = reservePub.toCrock();
- return Query(db)
- .get("reserves", reservePubStr)
- .then((reserve) => {
- let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl);
- reqUrl.query({ 'reserve_pub': reservePubStr });
- return httpGet(reqUrl).then(resp => {
+ updateReserve(reservePub, mint) {
+ let reservePubStr = reservePub.toCrock();
+ return Query(this.db)
+ .get("reserves", reservePubStr)
+ .then((reserve) => {
+ let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl);
+ reqUrl.query({ 'reserve_pub': reservePubStr });
+ return httpGet(reqUrl).then(resp => {
+ if (resp.status != 200) {
+ throw Error();
+ }
+ let reserveInfo = JSON.parse(resp.responseText);
+ if (!reserveInfo) {
+ throw Error();
+ }
+ reserve.current_amount = reserveInfo.balance;
+ return Query(this.db)
+ .put("reserves", reserve)
+ .finish()
+ .then(() => reserve);
+ });
+ });
+ }
+ /**
+ * Update or add mint DB entry by fetching the /keys information.
+ * Optionally link the reserve entry to the new or existing
+ * mint entry in then DB.
+ */
+ updateMintFromUrl(baseUrl) {
+ let reqUrl = URI("keys").absoluteTo(baseUrl);
+ return httpGet(reqUrl).then((resp) => {
if (resp.status != 200) {
- throw Error();
+ throw Error("/keys request failed");
}
- let reserveInfo = JSON.parse(resp.responseText);
- if (!reserveInfo) {
- throw Error();
+ let mintKeysJson = JSON.parse(resp.responseText);
+ if (!mintKeysJson) {
+ throw new RequestException({ url: reqUrl, hint: "keys invalid" });
}
- reserve.current_amount = reserveInfo.balance;
- return Query(db)
- .put("reserves", reserve)
- .finish()
- .then(() => reserve);
+ let mint = {
+ baseUrl: baseUrl,
+ keys: mintKeysJson
+ };
+ return Query(this.db).put("mints", mint).finish().then(() => mint);
});
- });
-}
-/**
- * Update or add mint DB entry by fetching the /keys information.
- * Optionally link the reserve entry to the new or existing
- * mint entry in then DB.
- */
-function updateMintFromUrl(db, baseUrl) {
- let reqUrl = URI("keys").absoluteTo(baseUrl);
- return httpGet(reqUrl).then((resp) => {
- if (resp.status != 200) {
- throw Error("/keys request failed");
- }
- let mintKeysJson = JSON.parse(resp.responseText);
- if (!mintKeysJson) {
- throw new RequestException({ url: reqUrl, hint: "keys invalid" });
- }
- let mint = {
- baseUrl: baseUrl,
- keys: mintKeysJson
- };
- return Query(db).put("mints", mint).finish().then(() => mint);
- });
-}
-function getBalances(db) {
- function collectBalances(c, byCurrency) {
- let acc = byCurrency[c.currentAmount.currency];
- if (!acc) {
- acc = Amount.getZero(c.currentAmount.currency).toJson();
+ }
+ getBalances() {
+ function collectBalances(c, byCurrency) {
+ let acc = byCurrency[c.currentAmount.currency];
+ if (!acc) {
+ acc = Amount.getZero(c.currentAmount.currency).toJson();
+ }
+ let am = new Amount(c.currentAmount);
+ am.add(new Amount(acc));
+ byCurrency[c.currentAmount.currency] = am.toJson();
+ return byCurrency;
}
- let am = new Amount(c.currentAmount);
- am.add(new Amount(acc));
- byCurrency[c.currentAmount.currency] = am.toJson();
- return byCurrency;
+ return Query(this.db)
+ .iter("coins")
+ .reduce(collectBalances, {});
}
- return Query(db)
- .iter("coins")
- .reduce(collectBalances, {});
}
diff --git a/extension/background/wallet.ts b/extension/background/wallet.ts
index d9187f14a..6479b961a 100644
--- a/extension/background/wallet.ts
+++ b/extension/background/wallet.ts
@@ -131,6 +131,54 @@ interface Reserve {
}
+interface PaymentResponse {
+ payUrl: string;
+ payReq: any;
+}
+
+
+interface ConfirmReserveRequest {
+ /**
+ * Name of the form field for the amount.
+ */
+ field_amount;
+
+ /**
+ * Name of the form field for the reserve public key.
+ */
+ field_reserve_pub;
+
+ /**
+ * Name of the form field for the reserve public key.
+ */
+ field_mint;
+
+ /**
+ * The actual amount in string form.
+ * TODO: where is this format specified?
+ */
+ amount_str;
+
+ /**
+ * Target URL for the reserve creation request.
+ */
+ post_url;
+
+ /**
+ * Mint URL where the bank should create the reserve.
+ */
+ mint;
+}
+
+
+interface ConfirmReserveResponse {
+ backlink: string;
+ success: boolean;
+ status: number;
+ text: string;
+}
+
+
type PayCoinInfo = Array<{ updatedCoin: Db.Coin, sig: CoinPaySig_interface }>;
@@ -148,521 +196,512 @@ function canonicalizeBaseUrl(url) {
return x.href()
}
+interface HttpRequestLibrary {
-function signDeposit(db: IDBDatabase,
- offer: Offer,
- cds: Db.CoinWithDenom[]): PayCoinInfo {
- let ret = [];
- let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency);
- let amountRemaining = new Amount(offer.contract.amount);
- cds = copy(cds);
- for (let cd of cds) {
- let coinSpend;
-
- if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
- break;
- }
-
- if (amountRemaining.cmp(new Amount(cd.coin.currentAmount)) < 0) {
- coinSpend = new Amount(amountRemaining.toJson());
- } else {
- coinSpend = new Amount(cd.coin.currentAmount);
- }
+}
- amountSpent.add(coinSpend);
- amountRemaining.sub(coinSpend);
-
- let newAmount = new Amount(cd.coin.currentAmount);
- newAmount.sub(coinSpend);
- cd.coin.currentAmount = newAmount.toJson();
-
- let args: DepositRequestPS_Args = {
- h_contract: HashCode.fromCrock(offer.H_contract),
- h_wire: HashCode.fromCrock(offer.contract.H_wire),
- amount_with_fee: coinSpend.toNbo(),
- coin_pub: EddsaPublicKey.fromCrock(cd.coin.coinPub),
- deposit_fee: new Amount(cd.denom.fee_deposit).toNbo(),
- merchant: EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
- refund_deadline: AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
- timestamp: AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
- transaction_id: UInt64.fromNumber(offer.contract.transaction_id),
- };
+interface Badge {
- let d = new DepositRequestPS(args);
+}
- let coinSig = eddsaSign(d.toPurpose(),
- EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
- .toCrock();
- let s: CoinPaySig_interface = {
- coin_sig: coinSig,
- coin_pub: cd.coin.coinPub,
- ub_sig: cd.coin.denomSig,
- denom_pub: cd.coin.denomPub,
- f: coinSpend.toJson(),
- };
- ret.push({sig: s, updatedCoin: cd.coin});
- }
- return ret;
+function copy(o) {
+ return JSON.parse(JSON.stringify(o));
}
-/**
- * Get mints and associated coins that are still spendable,
- * but only if the sum the coins' remaining value exceeds the payment amount.
- * @param db
- * @param paymentAmount
- * @param depositFeeLimit
- * @param allowedMints
- */
-function getPossibleMintCoins(db: IDBDatabase,
- paymentAmount: AmountJson_interface,
- depositFeeLimit: AmountJson_interface,
- allowedMints: MintInfo[]): Promise<MintCoins> {
+function rankDenom(denom1: any, denom2: any) {
+ // Slow ... we should find a better way than to convert it evert time.
+ let v1 = new Amount(denom1.value);
+ let v2 = new Amount(denom2.value);
+ return (-1) * v1.cmp(v2);
+}
- let m: MintCoins = {};
+class Wallet {
+ private db: IDBDatabase;
+ private http: HttpRequestLibrary;
+ private badge: Badge;
- function storeMintCoin(mc) {
- let mint = mc[0];
- let coin = mc[1];
- let cd = {
- coin: coin,
- denom: mint.keys.denoms.find((e) => e.denom_pub === coin.denomPub)
- };
- if (!cd.denom) {
- throw Error("denom not found (database inconsistent)");
- }
- let x = m[mint.baseUrl];
- if (!x) {
- m[mint.baseUrl] = [cd];
- } else {
- x.push(cd);
- }
+ constructor(db: IDBDatabase, http: HttpRequestLibrary, badge: Badge) {
+ this.db = db;
+ this.http = http;
+ this.badge = badge;
}
- let ps = allowedMints.map((info) => {
- return Query(db)
- .iterIndex("mints", "pubKey", info.master_pub)
- .indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
- .reduce(storeMintCoin);
- });
-
- return Promise.all(ps).then(() => {
- let ret: MintCoins = {};
-
- nextMint:
- for (let key in m) {
- let coins = m[key].map((x) => ({
- a: new Amount(x.denom.fee_deposit),
- c: x
- }));
- // Sort by ascending deposit fee
- coins.sort((o1, o2) => o1.a.cmp(o2.a));
- let maxFee = new Amount(depositFeeLimit);
- let minAmount = new Amount(paymentAmount);
- let accFee = new Amount(coins[0].c.denom.fee_deposit);
- let accAmount = Amount.getZero(coins[0].c.coin.currentAmount.currency);
- let usableCoins: Db.CoinWithDenom[] = [];
- nextCoin:
- for (let i = 0; i < coins.length; i++) {
- let coinAmount = new Amount(coins[i].c.coin.currentAmount);
- let coinFee = coins[i].a;
- if (coinAmount.cmp(coinFee) <= 0) {
- continue nextCoin;
- }
- accFee.add(coinFee);
- accAmount.add(coinAmount);
- if (accFee.cmp(maxFee) >= 0) {
- console.log("too much fees");
- continue nextMint;
- }
- usableCoins.push(coins[i].c);
- if (accAmount.cmp(minAmount) >= 0) {
- ret[key] = usableCoins;
- continue nextMint;
- }
- }
+ static signDeposit(offer: Offer,
+ cds: Db.CoinWithDenom[]): PayCoinInfo {
+ let ret = [];
+ let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency);
+ let amountRemaining = new Amount(offer.contract.amount);
+ cds = copy(cds);
+ for (let cd of cds) {
+ let coinSpend;
+
+ if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
+ break;
}
- return ret;
- });
-}
-
-function executePay(db,
- offer: Offer,
- payCoinInfo: PayCoinInfo,
- merchantBaseUrl: string,
- chosenMint: string): Promise<void> {
- let payReq = {};
- payReq["H_wire"] = offer.contract.H_wire;
- payReq["H_contract"] = offer.H_contract;
- payReq["transaction_id"] = offer.contract.transaction_id;
- payReq["refund_deadline"] = offer.contract.refund_deadline;
- payReq["mint"] = URI(chosenMint).href();
- payReq["coins"] = payCoinInfo.map((x) => x.sig);
- payReq["timestamp"] = offer.contract.timestamp;
- let payUrl = URI(offer.pay_url).absoluteTo(merchantBaseUrl);
- let t: Transaction = {
- contractHash: offer.H_contract,
- contract: offer.contract,
- payUrl: payUrl.href(),
- payReq: payReq
- };
-
- return Query(db)
- .put("transactions", t)
- .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
- .finish();
-}
-
-
-function confirmPayHandler(db, detail: ConfirmPayRequest, sendResponse) {
- let offer: Offer = detail.offer;
- getPossibleMintCoins(db,
- offer.contract.amount,
- offer.contract.max_fee,
- offer.contract.mints)
- .then((mcs) => {
- if (Object.keys(mcs).length == 0) {
- sendResponse({error: "Not enough coins."});
- return;
+ if (amountRemaining.cmp(new Amount(cd.coin.currentAmount)) < 0) {
+ coinSpend = new Amount(amountRemaining.toJson());
+ } else {
+ coinSpend = new Amount(cd.coin.currentAmount);
}
- let mintUrl = Object.keys(mcs)[0];
- let ds = signDeposit(db, offer, mcs[mintUrl]);
- return executePay(db, offer, ds, detail.merchantPageUrl, mintUrl)
- .then(() => {
- sendResponse({
- success: true,
- });
- });
- });
- return true;
-}
+ amountSpent.add(coinSpend);
+ amountRemaining.sub(coinSpend);
+
+ let newAmount = new Amount(cd.coin.currentAmount);
+ newAmount.sub(coinSpend);
+ cd.coin.currentAmount = newAmount.toJson();
+
+ let args: DepositRequestPS_Args = {
+ h_contract: HashCode.fromCrock(offer.H_contract),
+ h_wire: HashCode.fromCrock(offer.contract.H_wire),
+ amount_with_fee: coinSpend.toNbo(),
+ coin_pub: EddsaPublicKey.fromCrock(cd.coin.coinPub),
+ deposit_fee: new Amount(cd.denom.fee_deposit).toNbo(),
+ merchant: EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
+ refund_deadline: AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
+ timestamp: AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
+ transaction_id: UInt64.fromNumber(offer.contract.transaction_id),
+ };
-function doPaymentHandler(db, detail, sendResponse) {
- let H_contract = detail.H_contract;
- Query(db)
- .get("transactions", H_contract)
- .then((r) => {
- if (!r) {
- sendResponse({success: false, error: "contract not found"});
- return;
- }
- sendResponse({
- success: true,
- payUrl: r.payUrl,
- payReq: r.payReq
- });
- });
- // async sendResponse
- return true;
-}
+ let d = new DepositRequestPS(args);
+ let coinSig = eddsaSign(d.toPurpose(),
+ EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
+ .toCrock();
-function confirmReserveHandler(db, detail, sendResponse) {
- let reservePriv = EddsaPrivateKey.create();
- let reservePub = reservePriv.getPublicKey();
- let form = new FormData();
- let now = (new Date()).toString();
- form.append(detail.field_amount, detail.amount_str);
- form.append(detail.field_reserve_pub, reservePub.toCrock());
- form.append(detail.field_mint, detail.mint);
- // XXX: set bank-specified fields.
- let mintBaseUrl = canonicalizeBaseUrl(detail.mint);
- httpPostForm(detail.post_url, form)
- .then((hresp) => {
- // TODO: extract as interface
- let resp = {
- status: hresp.status,
- text: hresp.responseText,
- success: undefined,
- backlink: undefined
- };
- let reserveRecord = {
- reserve_pub: reservePub.toCrock(),
- reserve_priv: reservePriv.toCrock(),
- mint_base_url: mintBaseUrl,
- created: now,
- last_query: null,
- current_amount: null,
- // XXX: set to actual amount
- initial_amount: null
+ let s: CoinPaySig_interface = {
+ coin_sig: coinSig,
+ coin_pub: cd.coin.coinPub,
+ ub_sig: cd.coin.denomSig,
+ denom_pub: cd.coin.denomPub,
+ f: coinSpend.toJson(),
};
+ ret.push({sig: s, updatedCoin: cd.coin});
+ }
+ return ret;
+ }
- if (hresp.status != 200) {
- resp.success = false;
- return resp;
- }
- resp.success = true;
- // We can't show the page directly, so
- // we show some generic page from the wallet.
- // TODO: this should not be webextensions-specific
- resp.backlink = chrome.extension.getURL("pages/reserve-success.html");
- return Query(db)
- .put("reserves", reserveRecord)
- .finish()
- .then(() => {
- // Do this in the background
- updateMintFromUrl(db, reserveRecord.mint_base_url)
- .then((mint) =>
- updateReserve(db, reservePub, mint)
- .then((reserve) => depleteReserve(db, reserve, mint))
- );
- return resp;
- });
- })
- .then((resp) => {
- sendResponse(resp);
- });
+ /**
+ * Get mints and associated coins that are still spendable,
+ * but only if the sum the coins' remaining value exceeds the payment amount.
+ * @param paymentAmount
+ * @param depositFeeLimit
+ * @param allowedMints
+ */
+ getPossibleMintCoins(paymentAmount: AmountJson_interface,
+ depositFeeLimit: AmountJson_interface,
+ allowedMints: MintInfo[]): Promise<MintCoins> {
- // Allow async response
- return true;
-}
+ let m: MintCoins = {};
-function copy(o) {
- return JSON.parse(JSON.stringify(o));
-}
+ function storeMintCoin(mc) {
+ let mint = mc[0];
+ let coin = mc[1];
+ let cd = {
+ coin: coin,
+ denom: mint.keys.denoms.find((e) => e.denom_pub === coin.denomPub)
+ };
+ if (!cd.denom) {
+ throw Error("denom not found (database inconsistent)");
+ }
+ let x = m[mint.baseUrl];
+ if (!x) {
+ m[mint.baseUrl] = [cd];
+ } else {
+ x.push(cd);
+ }
+ }
+ let ps = allowedMints.map((info) => {
+ return Query(this.db)
+ .iterIndex("mints", "pubKey", info.master_pub)
+ .indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
+ .reduce(storeMintCoin);
+ });
-function rankDenom(denom1: any, denom2: any) {
- // Slow ... we should find a better way than to convert it evert time.
- let v1 = new Amount(denom1.value);
- let v2 = new Amount(denom2.value);
- return (-1) * v1.cmp(v2);
-}
+ return Promise.all(ps).then(() => {
+ let ret: MintCoins = {};
+
+ nextMint:
+ for (let key in m) {
+ let coins = m[key].map((x) => ({
+ a: new Amount(x.denom.fee_deposit),
+ c: x
+ }));
+ // Sort by ascending deposit fee
+ coins.sort((o1, o2) => o1.a.cmp(o2.a));
+ let maxFee = new Amount(depositFeeLimit);
+ let minAmount = new Amount(paymentAmount);
+ let accFee = new Amount(coins[0].c.denom.fee_deposit);
+ let accAmount = Amount.getZero(coins[0].c.coin.currentAmount.currency);
+ let usableCoins: Db.CoinWithDenom[] = [];
+ nextCoin:
+ for (let i = 0; i < coins.length; i++) {
+ let coinAmount = new Amount(coins[i].c.coin.currentAmount);
+ let coinFee = coins[i].a;
+ if (coinAmount.cmp(coinFee) <= 0) {
+ continue nextCoin;
+ }
+ accFee.add(coinFee);
+ accAmount.add(coinAmount);
+ if (accFee.cmp(maxFee) >= 0) {
+ console.log("too much fees");
+ continue nextMint;
+ }
+ usableCoins.push(coins[i].c);
+ if (accAmount.cmp(minAmount) >= 0) {
+ ret[key] = usableCoins;
+ continue nextMint;
+ }
+ }
+ }
+ return ret;
+ });
+ }
-function withdrawPrepare(db: IDBDatabase,
- denom: Db.Denomination,
- reserve: Reserve): Promise<Db.PreCoin> {
- let reservePriv = new EddsaPrivateKey();
- reservePriv.loadCrock(reserve.reserve_priv);
- let reservePub = new EddsaPublicKey();
- reservePub.loadCrock(reserve.reserve_pub);
- let denomPub = RsaPublicKey.fromCrock(denom.denom_pub);
- let coinPriv = EddsaPrivateKey.create();
- let coinPub = coinPriv.getPublicKey();
- let blindingFactor = RsaBlindingKey.create(1024);
- let pubHash: HashCode = coinPub.hash();
- let ev: ByteArray = rsaBlind(pubHash, blindingFactor, denomPub);
-
- if (!denom.fee_withdraw) {
- throw Error("Field fee_withdraw missing");
- }
+ executePay(offer: Offer,
+ payCoinInfo: PayCoinInfo,
+ merchantBaseUrl: string,
+ chosenMint: string): Promise<void> {
+ let payReq = {};
+ payReq["H_wire"] = offer.contract.H_wire;
+ payReq["H_contract"] = offer.H_contract;
+ payReq["transaction_id"] = offer.contract.transaction_id;
+ payReq["refund_deadline"] = offer.contract.refund_deadline;
+ payReq["mint"] = URI(chosenMint).href();
+ payReq["coins"] = payCoinInfo.map((x) => x.sig);
+ payReq["timestamp"] = offer.contract.timestamp;
+ let payUrl = URI(offer.pay_url).absoluteTo(merchantBaseUrl);
+ let t: Transaction = {
+ contractHash: offer.H_contract,
+ contract: offer.contract,
+ payUrl: payUrl.href(),
+ payReq: payReq
+ };
- let amountWithFee = new Amount(denom.value);
- amountWithFee.add(new Amount(denom.fee_withdraw));
- let withdrawFee = new Amount(denom.fee_withdraw);
-
- // Signature
- let withdrawRequest = new WithdrawRequestPS({
- reserve_pub: reservePub,
- amount_with_fee: amountWithFee.toNbo(),
- withdraw_fee: withdrawFee.toNbo(),
- h_denomination_pub: denomPub.encode().hash(),
- h_coin_envelope: ev.hash()
- });
-
- var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv);
-
- let preCoin: Db.PreCoin = {
- reservePub: reservePub.toCrock(),
- blindingKey: blindingFactor.toCrock(),
- coinPub: coinPub.toCrock(),
- coinPriv: coinPriv.toCrock(),
- denomPub: denomPub.encode().toCrock(),
- mintBaseUrl: reserve.mint_base_url,
- withdrawSig: sig.toCrock(),
- coinEv: ev.toCrock(),
- coinValue: denom.value
- };
-
- return Query(db).put("precoins", preCoin).finish().then(() => preCoin);
-}
+ return Query(this.db)
+ .put("transactions", t)
+ .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
+ .finish();
+ }
+ confirmPay(offer: Offer, merchantPageUrl: string): Promise<any> {
+ return Promise.resolve().then(() => {
+ return this.getPossibleMintCoins(offer.contract.amount,
+ offer.contract.max_fee,
+ offer.contract.mints)
+ }).then((mcs) => {
+ if (Object.keys(mcs).length == 0) {
+ throw Error("Not enough coins.");
+ }
+ let mintUrl = Object.keys(mcs)[0];
+ let ds = Wallet.signDeposit(offer, mcs[mintUrl]);
+ return this.executePay(offer, ds, merchantPageUrl, mintUrl);
+ });
+ }
-function withdrawExecute(db, pc: Db.PreCoin): Promise<Db.Coin> {
- return Query(db)
- .get("reserves", pc.reservePub)
- .then((r) => {
- let wd: any = {};
- wd.denom_pub = pc.denomPub;
- wd.reserve_pub = pc.reservePub;
- wd.reserve_sig = pc.withdrawSig;
- wd.coin_ev = pc.coinEv;
- let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url);
- return httpPostJson(reqUrl, wd);
- })
- .then(resp => {
- if (resp.status != 200) {
- throw new RequestException({
- hint: "Withdrawal failed",
- status: resp.status
+ doPayment(H_contract): Promise<PaymentResponse> {
+ return Promise.resolve().then(() => {
+ return Query(this.db)
+ .get("transactions", H_contract)
+ .then((t) => {
+ if (!t) {
+ throw Error("contract not found");
+ }
+ let resp: PaymentResponse = {
+ payUrl: t.payUrl,
+ payReq: t.payReq
+ };
+ return resp;
});
- }
- let r = JSON.parse(resp.responseText);
- let denomSig = rsaUnblind(RsaSignature.fromCrock(r.ev_sig),
- RsaBlindingKey.fromCrock(pc.blindingKey),
- RsaPublicKey.fromCrock(pc.denomPub));
- let coin: Db.Coin = {
- coinPub: pc.coinPub,
- coinPriv: pc.coinPriv,
- denomPub: pc.denomPub,
- denomSig: denomSig.encode().toCrock(),
- currentAmount: pc.coinValue,
- mintBaseUrl: pc.mintBaseUrl,
- };
- return coin;
});
-}
+ }
+ confirmReserve(req: ConfirmReserveRequest): Promise<ConfirmReserveResponse> {
+ let reservePriv = EddsaPrivateKey.create();
+ let reservePub = reservePriv.getPublicKey();
+ let form = new FormData();
+ let now = (new Date()).toString();
+ form.append(req.field_amount, req.amount_str);
+ form.append(req.field_reserve_pub, reservePub.toCrock());
+ form.append(req.field_mint, req.mint);
+ // TODO: set bank-specified fields.
+ let mintBaseUrl = canonicalizeBaseUrl(req.mint);
+
+ return httpPostForm(req.post_url, form)
+ .then((hresp) => {
+ let resp: ConfirmReserveResponse = {
+ status: hresp.status,
+ text: hresp.responseText,
+ success: undefined,
+ backlink: undefined
+ };
+ let reserveRecord = {
+ reserve_pub: reservePub.toCrock(),
+ reserve_priv: reservePriv.toCrock(),
+ mint_base_url: mintBaseUrl,
+ created: now,
+ last_query: null,
+ current_amount: null,
+ // XXX: set to actual amount
+ initial_amount: null
+ };
+
+ if (hresp.status != 200) {
+ resp.success = false;
+ return resp;
+ }
-function updateBadge(db) {
- function countNonEmpty(c, n) {
- if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
- return n + 1;
- }
- return n;
+ resp.success = true;
+ // We can't show the page directly, so
+ // we show some generic page from the wallet.
+ resp.backlink = null;
+ return Query(this.db)
+ .put("reserves", reserveRecord)
+ .finish()
+ .then(() => {
+ // Do this in the background
+ this.updateMintFromUrl(reserveRecord.mint_base_url)
+ .then((mint) =>
+ this.updateReserve(reservePub, mint)
+ .then((reserve) => this.depleteReserve(reserve,
+ mint))
+ );
+ return resp;
+ });
+ });
}
- function doBadge(n) {
- chrome.browserAction.setBadgeText({text: "" + n});
- chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"});
- }
+ withdrawPrepare(denom: Db.Denomination,
+ reserve: Reserve): Promise<Db.PreCoin> {
+ let reservePriv = new EddsaPrivateKey();
+ reservePriv.loadCrock(reserve.reserve_priv);
+ let reservePub = new EddsaPublicKey();
+ reservePub.loadCrock(reserve.reserve_pub);
+ let denomPub = RsaPublicKey.fromCrock(denom.denom_pub);
+ let coinPriv = EddsaPrivateKey.create();
+ let coinPub = coinPriv.getPublicKey();
+ let blindingFactor = RsaBlindingKey.create(1024);
+ let pubHash: HashCode = coinPub.hash();
+ let ev: ByteArray = rsaBlind(pubHash, blindingFactor, denomPub);
+
+ if (!denom.fee_withdraw) {
+ throw Error("Field fee_withdraw missing");
+ }
- Query(db)
- .iter("coins")
- .reduce(countNonEmpty, 0)
- .then(doBadge);
-}
+ let amountWithFee = new Amount(denom.value);
+ amountWithFee.add(new Amount(denom.fee_withdraw));
+ let withdrawFee = new Amount(denom.fee_withdraw);
+
+ // Signature
+ let withdrawRequest = new WithdrawRequestPS({
+ reserve_pub: reservePub,
+ amount_with_fee: amountWithFee.toNbo(),
+ withdraw_fee: withdrawFee.toNbo(),
+ h_denomination_pub: denomPub.encode().hash(),
+ h_coin_envelope: ev.hash()
+ });
+ var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv);
+
+ let preCoin: Db.PreCoin = {
+ reservePub: reservePub.toCrock(),
+ blindingKey: blindingFactor.toCrock(),
+ coinPub: coinPub.toCrock(),
+ coinPriv: coinPriv.toCrock(),
+ denomPub: denomPub.encode().toCrock(),
+ mintBaseUrl: reserve.mint_base_url,
+ withdrawSig: sig.toCrock(),
+ coinEv: ev.toCrock(),
+ coinValue: denom.value
+ };
-function storeCoin(db, coin: Db.Coin) {
- Query(db)
- .delete("precoins", coin.coinPub)
- .add("coins", coin)
- .finish()
- .then(() => {
- updateBadge(db);
- });
-}
+ return Query(this.db).put("precoins", preCoin).finish().then(() => preCoin);
+ }
-function withdraw(db, denom, reserve): Promise<void> {
- return withdrawPrepare(db, denom, reserve)
- .then((pc) => withdrawExecute(db, pc))
- .then((c) => storeCoin(db, c));
-}
+ withdrawExecute(pc: Db.PreCoin): Promise<Db.Coin> {
+ return Query(this.db)
+ .get("reserves", pc.reservePub)
+ .then((r) => {
+ let wd: any = {};
+ wd.denom_pub = pc.denomPub;
+ wd.reserve_pub = pc.reservePub;
+ wd.reserve_sig = pc.withdrawSig;
+ wd.coin_ev = pc.coinEv;
+ let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url);
+ return httpPostJson(reqUrl, wd);
+ })
+ .then(resp => {
+ if (resp.status != 200) {
+ throw new RequestException({
+ hint: "Withdrawal failed",
+ status: resp.status
+ });
+ }
+ let r = JSON.parse(resp.responseText);
+ let denomSig = rsaUnblind(RsaSignature.fromCrock(r.ev_sig),
+ RsaBlindingKey.fromCrock(pc.blindingKey),
+ RsaPublicKey.fromCrock(pc.denomPub));
+ let coin: Db.Coin = {
+ coinPub: pc.coinPub,
+ coinPriv: pc.coinPriv,
+ denomPub: pc.denomPub,
+ denomSig: denomSig.encode().toCrock(),
+ currentAmount: pc.coinValue,
+ mintBaseUrl: pc.mintBaseUrl,
+ };
+ return coin;
+ });
+ }
-/**
- * Withdraw coins from a reserve until it is empty.
- */
-function depleteReserve(db, reserve, mint): void {
- let denoms = copy(mint.keys.denoms);
- let remaining = new Amount(reserve.current_amount);
- denoms.sort(rankDenom);
- let workList = [];
- for (let i = 0; i < 1000; i++) {
- let found = false;
- for (let d of denoms) {
- let cost = new Amount(d.value);
- cost.add(new Amount(d.fee_withdraw));
- if (remaining.cmp(cost) < 0) {
- continue;
+ updateBadge() {
+ function countNonEmpty(c, n) {
+ if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
+ return n + 1;
}
- found = true;
- remaining.sub(cost);
- workList.push(d);
+ return n;
}
- if (!found) {
- console.log("did not find coins for remaining ", remaining.toJson());
- break;
+
+ function doBadge(n) {
+ chrome.browserAction.setBadgeText({text: "" + n});
+ chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"});
}
+
+ Query(this.db)
+ .iter("coins")
+ .reduce(countNonEmpty, 0)
+ .then(doBadge);
}
- // Do the request one by one.
- function next(): void {
- if (workList.length == 0) {
- return;
- }
- let d = workList.pop();
- withdraw(db, d, reserve)
- .then(() => next());
+ storeCoin(coin: Db.Coin) {
+ Query(this.db)
+ .delete("precoins", coin.coinPub)
+ .add("coins", coin)
+ .finish()
+ .then(() => {
+ this.updateBadge();
+ });
}
- next();
-}
+ withdraw(denom, reserve): Promise<void> {
+ return this.withdrawPrepare(denom, reserve)
+ .then((pc) => this.withdrawExecute(pc))
+ .then((c) => this.storeCoin(c));
+ }
-function updateReserve(db: IDBDatabase,
- reservePub: EddsaPublicKey,
- mint): Promise<Reserve> {
- let reservePubStr = reservePub.toCrock();
- return Query(db)
- .get("reserves", reservePubStr)
- .then((reserve) => {
- let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl);
- reqUrl.query({'reserve_pub': reservePubStr});
- return httpGet(reqUrl).then(resp => {
- if (resp.status != 200) {
- throw Error();
- }
- let reserveInfo = JSON.parse(resp.responseText);
- if (!reserveInfo) {
- throw Error();
+ /**
+ * Withdraw coins from a reserve until it is empty.
+ */
+ depleteReserve(reserve, mint): void {
+ let denoms = copy(mint.keys.denoms);
+ let remaining = new Amount(reserve.current_amount);
+ denoms.sort(rankDenom);
+ let workList = [];
+ for (let i = 0; i < 1000; i++) {
+ let found = false;
+ for (let d of denoms) {
+ let cost = new Amount(d.value);
+ cost.add(new Amount(d.fee_withdraw));
+ if (remaining.cmp(cost) < 0) {
+ continue;
}
- reserve.current_amount = reserveInfo.balance;
- return Query(db)
- .put("reserves", reserve)
- .finish()
- .then(() => reserve);
+ found = true;
+ remaining.sub(cost);
+ workList.push(d);
+ }
+ if (!found) {
+ console.log("did not find coins for remaining ", remaining.toJson());
+ break;
+ }
+ }
+
+ // Do the request one by one.
+ let next = () => {
+ if (workList.length == 0) {
+ return;
+ }
+ let d = workList.pop();
+ this.withdraw(d, reserve)
+ .then(() => next());
+ };
+
+ // Asynchronous recursion
+ next();
+ }
+
+ updateReserve(reservePub: EddsaPublicKey,
+ mint): Promise<Reserve> {
+ let reservePubStr = reservePub.toCrock();
+ return Query(this.db)
+ .get("reserves", reservePubStr)
+ .then((reserve) => {
+ let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl);
+ reqUrl.query({'reserve_pub': reservePubStr});
+ return httpGet(reqUrl).then(resp => {
+ if (resp.status != 200) {
+ throw Error();
+ }
+ let reserveInfo = JSON.parse(resp.responseText);
+ if (!reserveInfo) {
+ throw Error();
+ }
+ reserve.current_amount = reserveInfo.balance;
+ return Query(this.db)
+ .put("reserves", reserve)
+ .finish()
+ .then(() => reserve);
+ });
});
+ }
+
+ /**
+ * Update or add mint DB entry by fetching the /keys information.
+ * Optionally link the reserve entry to the new or existing
+ * mint entry in then DB.
+ */
+ updateMintFromUrl(baseUrl) {
+ let reqUrl = URI("keys").absoluteTo(baseUrl);
+ return httpGet(reqUrl).then((resp) => {
+ if (resp.status != 200) {
+ throw Error("/keys request failed");
+ }
+ let mintKeysJson = JSON.parse(resp.responseText);
+ if (!mintKeysJson) {
+ throw new RequestException({url: reqUrl, hint: "keys invalid"});
+ }
+ let mint: Db.Mint = {
+ baseUrl: baseUrl,
+ keys: mintKeysJson
+ };
+ return Query(this.db).put("mints", mint).finish().then(() => mint);
});
-}
+ }
-/**
- * Update or add mint DB entry by fetching the /keys information.
- * Optionally link the reserve entry to the new or existing
- * mint entry in then DB.
- */
-function updateMintFromUrl(db, baseUrl) {
- let reqUrl = URI("keys").absoluteTo(baseUrl);
- return httpGet(reqUrl).then((resp) => {
- if (resp.status != 200) {
- throw Error("/keys request failed");
- }
- let mintKeysJson = JSON.parse(resp.responseText);
- if (!mintKeysJson) {
- throw new RequestException({url: reqUrl, hint: "keys invalid"});
+ getBalances(): Promise<any> {
+ function collectBalances(c: Db.Coin, byCurrency) {
+ let acc: AmountJson_interface = byCurrency[c.currentAmount.currency];
+ if (!acc) {
+ acc = Amount.getZero(c.currentAmount.currency).toJson();
+ }
+ let am = new Amount(c.currentAmount);
+ am.add(new Amount(acc));
+ byCurrency[c.currentAmount.currency] = am.toJson();
+ return byCurrency;
}
- let mint: Db.Mint = {
- baseUrl: baseUrl,
- keys: mintKeysJson
- };
- return Query(db).put("mints", mint).finish().then(() => mint);
- });
-}
-
-function getBalances(db): Promise<any> {
- function collectBalances(c: Db.Coin, byCurrency) {
- let acc: AmountJson_interface = byCurrency[c.currentAmount.currency];
- if (!acc) {
- acc = Amount.getZero(c.currentAmount.currency).toJson();
- }
- let am = new Amount(c.currentAmount);
- am.add(new Amount(acc));
- byCurrency[c.currentAmount.currency] = am.toJson();
- return byCurrency;
+ return Query(this.db)
+ .iter("coins")
+ .reduce(collectBalances, {});
}
-
- return Query(db)
- .iter("coins")
- .reduce(collectBalances, {});
}
diff --git a/extension/content_scripts/notify.js b/extension/content_scripts/notify.js
index 57d32135d..47c839799 100644
--- a/extension/content_scripts/notify.js
+++ b/extension/content_scripts/notify.js
@@ -50,7 +50,7 @@ document.addEventListener("taler-create-reserve", function (e) {
let uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
document.location.href = uri.query(params).href();
});
-document.addEventListener('taler-contract', function (e) {
+document.addEventListener("taler-contract", function (e) {
// XXX: the merchant should just give us the parsed data ...
let offer = JSON.parse(e.detail);
let uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
diff --git a/extension/content_scripts/notify.ts b/extension/content_scripts/notify.ts
index a75ab09cf..e2ffaefa9 100644
--- a/extension/content_scripts/notify.ts
+++ b/extension/content_scripts/notify.ts
@@ -57,7 +57,7 @@ document.addEventListener("taler-create-reserve", function(e: CustomEvent) {
document.location.href = uri.query(params).href();
});
-document.addEventListener('taler-contract', function(e: CustomEvent) {
+document.addEventListener("taler-contract", function(e: CustomEvent) {
// XXX: the merchant should just give us the parsed data ...
let offer = JSON.parse(e.detail);
let uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
diff --git a/extension/manifest.json b/extension/manifest.json
index 7c0f295b4..c913eafed 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -43,12 +43,12 @@
"scripts": [
"lib/util.js",
"lib/URI.js",
+ "background/checkable.js",
"background/libwrapper.js",
"background/emscriptif.js",
"background/db.js",
"background/query.js",
"background/messaging.js",
- "background/checkable.js",
"background/http.js",
"background/wallet.js"
]
diff --git a/extension/popup/balance-overview.html b/extension/popup/balance-overview.html
index 1bc80d97e..f6adc521d 100644
--- a/extension/popup/balance-overview.html
+++ b/extension/popup/balance-overview.html
@@ -22,7 +22,7 @@
<body>
<div id="header" class="nav">
<a href="balance-overview.html" class="active">Wallet</a>
- <a href="transactions.html">Transactions</a>
+ <a href="history.html">History</a>
<a href="reserves.html">Reserves</a>
<button id="debug">Debug!</button>
<button id="reset">Reset!</button>
diff --git a/extension/popup/balance-overview.js b/extension/popup/balance-overview.js
index 6b6aaf794..08ad465f4 100644
--- a/extension/popup/balance-overview.js
+++ b/extension/popup/balance-overview.js
@@ -1,5 +1,20 @@
+/*
+ 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, If not, see <http://www.gnu.org/licenses/>
+ */
"use strict";
-document.addEventListener('DOMContentLoaded', (e) => {
+document.addEventListener("DOMContentLoaded", (e) => {
console.log("content loaded");
chrome.runtime.sendMessage({ type: "balances" }, function (wallet) {
let context = document.getElementById("balance-template").innerHTML;
diff --git a/extension/popup/balance-overview.tsx b/extension/popup/balance-overview.tsx
index a78111bb1..8a278d6a1 100644
--- a/extension/popup/balance-overview.tsx
+++ b/extension/popup/balance-overview.tsx
@@ -1,6 +1,22 @@
+/*
+ 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, If not, see <http://www.gnu.org/licenses/>
+ */
+
"use strict";
-document.addEventListener('DOMContentLoaded', (e) => {
+document.addEventListener("DOMContentLoaded", (e) => {
console.log("content loaded");
chrome.runtime.sendMessage({type: "balances"}, function(wallet) {
let context = document.getElementById("balance-template").innerHTML;
@@ -12,16 +28,16 @@ document.addEventListener('DOMContentLoaded', (e) => {
el.onclick = (e) => {
let target: any = e.target;
chrome.tabs.create({
- "url": target.href
- });
+ "url": target.href
+ });
};
}
});
document.getElementById("debug").addEventListener("click", (e) => {
chrome.tabs.create({
- "url": chrome.extension.getURL("pages/debug.html")
- });
+ "url": chrome.extension.getURL("pages/debug.html")
+ });
});
document.getElementById("reset").addEventListener("click", (e) => {
chrome.runtime.sendMessage({type: "reset"});
diff --git a/extension/popup/history.html b/extension/popup/history.html
new file mode 100644
index 000000000..dccc84605
--- /dev/null
+++ b/extension/popup/history.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="popup.css" type="text/css">
+ <script src="../lib/util.js" type="text/javascript"></script>
+ <script src="history.js" type="text/javascript"></script>
+
+ <script id="balance-template" type="text/x-handlebars-template">
+ {{#each transactions}}
+ <p>bla</p>
+ {{else}}
+ There's nothing here. Go to
+ our <a href="http://demo.taler.net">demo site</a> to try GNU Taler.
+ {{/each}}
+ </script>
+</head>
+
+<body>
+<div id="header" class="nav">
+ <a href="balance-overview.html">Wallet</a>
+ <a href="history.html" class="active">Transactions</a>
+ <a href="reserves.html">Reserves</a>
+</div>
+
+<div id="content">
+ (Loading...)
+</div>
+
+</body>
+</html>
diff --git a/extension/popup/history.tsx b/extension/popup/history.tsx
new file mode 100644
index 000000000..387f19c80
--- /dev/null
+++ b/extension/popup/history.tsx
@@ -0,0 +1,22 @@
+/*
+ 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, If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+"use strict";
+
+document.addEventListener("DOMContentLoaded", (e) => {
+
+});
diff --git a/extension/popup/reserve-create-sepa.html b/extension/popup/reserve-create-sepa.html
index d360f05ed..12d02b832 100644
--- a/extension/popup/reserve-create-sepa.html
+++ b/extension/popup/reserve-create-sepa.html
@@ -9,7 +9,7 @@
<body>
<div id="header" class="nav">
<a href="balance-overview.html">Wallet</a>
- <a href="transactions.html">Transactions</a>
+ <a href="history.html">Transactions</a>
<a href="reserves.html" class="active">Reserves</a>
</div>
diff --git a/extension/popup/reserve-create.html b/extension/popup/reserve-create.html
index 423e519c1..165496ab6 100644
--- a/extension/popup/reserve-create.html
+++ b/extension/popup/reserve-create.html
@@ -9,7 +9,7 @@
<body>
<div id="header" class="nav">
<a href="balance-overview.html">Wallet</a>
- <a href="transactions.html">Transactions</a>
+ <a href="history.html">Transactions</a>
<a href="reserves.html" class="active">Reserves</a>
</div>
diff --git a/extension/popup/reserves.html b/extension/popup/reserves.html
index 0785cb5ca..424f59d3c 100644
--- a/extension/popup/reserves.html
+++ b/extension/popup/reserves.html
@@ -9,7 +9,7 @@
<body>
<div id="header" class="nav">
<a href="balance-overview.html">Wallet</a>
- <a href="transactions.html">Transactions</a>
+ <a href="history.html">Transactions</a>
<a href="reserves.html" class="active">Reserves</a>
</div>
diff --git a/extension/popup/transactions.html b/extension/popup/transactions.html
deleted file mode 100644
index e575f0227..000000000
--- a/extension/popup/transactions.html
+++ /dev/null
@@ -1,62 +0,0 @@
-<!DOCTYPE html>
-
-<html>
-<head>
- <meta charset="utf-8">
- <link rel="stylesheet" href="popup.css" type="text/css">
- <script src="../lib/util.js" type="text/javascript"></script>
- <script src="transactions.js" type="text/javascript"></script>
-
- <script id="balance-template" type="text/x-handlebars-template">
- {{#each transactions}}
- bla
- {{else}}
- Looks like you didn't make any transactions. Get some
- coins and <a>donate</a> something.
- {{/each}}
- </script>
-
-</head>
-
-<body>
-<div id="header" class="nav">
- <a href="balance-overview.html">Wallet</a>
- <a href="transactions.html" class="active">Transactions</a>
- <a href="reserves.html">Reserves</a>
-</div>
-
-<div id="content">
- <table id="transactions-table" class="hidden">
- <thead>
- <tr>
- <th>Date</th>
- <th>Amount</th>
- <th>Status</th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <!--
- <tr>
- <td class="date">2015-12-21 13:37</td>
- <td class="amount">42 EUR</td>
- <td class="status">Completed</td>
- <td class="contract"><button>Contract</button></td>
- </tr>
- <tr>
- <td class="date">2015-12-22 10:01</td>
- <td class="amount">23 USD</td>
- <td class="status">Pending</td>
- <td class="contract"><button>Contract</button></td>
- </tr>
- -->
- </tbody>
- </table>
-
- <p id="no-transactions">
- There are no transactions to show.
- </p>
-</div>
-
-</body>
-</html>
diff --git a/extension/popup/transactions.js b/extension/popup/transactions.js
deleted file mode 100644
index fbd578114..000000000
--- a/extension/popup/transactions.js
+++ /dev/null
@@ -1,39 +0,0 @@
-'use strict';
-
-function add_transaction (date, currency, amount, status, contract)
-{
- let table = document.getElementById('transactions-table');
- table.className = table.className.replace(/\bhidden\b/, '');
- let tr = document.createElement('tr');
- table.appendChild(tr);
-
- let td_date = document.createElement('td');
- td_date.className = 'date';
- let text_date = document.createTextNode(date_format (date));
- tr.appendChild(td_date).appendChild(text_date);
-
- let td_amount = document.createElement('td');
- td_amount.className = 'amount';
- let text_amount = document.createTextNode(amount +' '+ currency);
- tr.appendChild(td_amount).appendChild(text_amount);
-
- let td_status = document.createElement('td');
- td_status.className = 'status';
- let text_status = document.createTextNode(status);
- tr.appendChild(td_status).appendChild(text_status);
-
- let td_contract = document.createElement('td');
- td_contract.className = 'contract';
- let btn_contract = document.createElement('button');
- btn_contract.appendChild(document.createTextNode('Contract'));
- tr.appendChild(td_contract).appendChild(btn_contract);
-}
-
-document.addEventListener('DOMContentLoaded', function () {
- let no = document.getElementById('no-transactions');
-
- // FIXME
- no.className += ' hidden';
- add_transaction (new Date('2015-12-21 13:37'), 'EUR', 42, 'Completed', {});
- add_transaction (new Date('2015-12-22 10:01'), 'USD', 23, 'Pending', {});
-});
diff --git a/extension/tsconfig.json b/extension/tsconfig.json
index 5586d78bc..68b437671 100644
--- a/extension/tsconfig.json
+++ b/extension/tsconfig.json
@@ -16,6 +16,7 @@
"lib/polyfill-react.ts",
"content_scripts/notify.ts",
"popup/balance-overview.tsx",
+ "popup/history.tsx",
"pages/confirm-contract.tsx",
"pages/confirm-create-reserve.tsx"
]