aboutsummaryrefslogtreecommitdiff
path: root/extension/background/wallet.ts
diff options
context:
space:
mode:
Diffstat (limited to 'extension/background/wallet.ts')
-rw-r--r--extension/background/wallet.ts580
1 files changed, 269 insertions, 311 deletions
diff --git a/extension/background/wallet.ts b/extension/background/wallet.ts
index 1abd09fd8..c6152253e 100644
--- a/extension/background/wallet.ts
+++ b/extension/background/wallet.ts
@@ -16,6 +16,8 @@
/// <reference path="../decl/urijs/URIjs.d.ts" />
/// <reference path="../decl/chrome/chrome.d.ts" />
+import URIStatic = uri.URIStatic;
+import Request = chrome.devtools.network.Request;
'use strict';
@@ -25,22 +27,6 @@ interface AmountJson {
currency: string;
}
-
-/**
- * See http://api.taler.net/wallet.html#general
- */
-function canonicalizeBaseUrl(url) {
- let x = new URI(url);
- if (!x.protocol()) {
- x.protocol("https");
- }
- x.path(x.path() + "/").normalizePath();
- x.fragment();
- x.query();
- return x.href()
-}
-
-
interface ConfirmPayRequest {
merchantPageUrl: string;
offer: Offer;
@@ -50,6 +36,12 @@ interface MintCoins {
[mintUrl: string]: Db.CoinWithDenom[];
}
+
+interface MintInfo {
+ master_pub: string;
+ url: string;
+}
+
interface Offer {
contract: Contract;
sig: string;
@@ -74,7 +66,6 @@ interface Contract {
transaction_id: number;
}
-
interface CoinPaySig {
coin_sig: string;
coin_pub: string;
@@ -84,9 +75,39 @@ interface CoinPaySig {
}
+interface Transaction {
+ contractHash: string;
+ contract: any;
+ payUrl: string;
+ payReq: any;
+}
+
+
+interface Reserve {
+ mint_base_url: string
+ reserve_priv: string;
+ reserve_pub: string;
+}
+
+
type PayCoinInfo = Array<{ updatedCoin: Db.Coin, sig: CoinPaySig }>;
+/**
+ * See http://api.taler.net/wallet.html#general
+ */
+function canonicalizeBaseUrl(url) {
+ let x = new URI(url);
+ if (!x.protocol()) {
+ x.protocol("https");
+ }
+ x.path(x.path() + "/").normalizePath();
+ x.fragment();
+ x.query();
+ return x.href()
+}
+
+
function signDeposit(db: IDBDatabase,
offer: Offer,
cds: Db.CoinWithDenom[]): PayCoinInfo {
@@ -97,10 +118,7 @@ function signDeposit(db: IDBDatabase,
for (let cd of cds) {
let coinSpend;
- console.log("amount remaining:", amountRemaining.toJson());
-
if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
- console.log("full amount spent");
break;
}
@@ -131,10 +149,6 @@ function signDeposit(db: IDBDatabase,
let d = new DepositRequestPS(args);
- console.log("Deposit request #" + ret.length);
- console.log("DepositRequestPS: \n", d.toJson());
- console.log("DepositRequestPS sig: \n", d.toPurpose().hexdump());
-
let coinSig = eddsaSign(d.toPurpose(),
EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
.toCrock();
@@ -151,11 +165,6 @@ function signDeposit(db: IDBDatabase,
return ret;
}
-interface MintInfo {
- master_pub: string;
- url: string;
-}
-
/**
* Get mints and associated coins that are still spendable,
@@ -228,28 +237,24 @@ function getPossibleMintCoins(db: IDBDatabase,
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;
+ 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;
+ }
}
- 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;
- }
- }
- console.log(format("mint {0}: acc {1} is not enough for {2}",
- key,
- JSON.stringify(accAmount.toJson()),
- JSON.stringify(minAmount.toJson())));
}
resolve(ret);
};
@@ -261,45 +266,31 @@ function getPossibleMintCoins(db: IDBDatabase,
}
-interface Transaction {
- contractHash: string;
- contract: any;
- payUrl: string;
- payReq: any;
-}
-
-
function executePay(db,
offer: Offer,
payCoinInfo: PayCoinInfo,
merchantBaseUrl: string,
- chosenMint: string) {
- return new Promise((resolve, reject) => {
- 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 tx = db.transaction(["transactions", "coins"], "readwrite");
- tx.objectStore('transactions').put(t);
- for (let c of payCoinInfo) {
- tx.objectStore("coins").put(c.updatedCoin);
- }
- tx.oncomplete = (e) => {
- updateBadge(db);
- resolve();
- };
- });
+ 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();
}
@@ -312,16 +303,17 @@ function confirmPay(db, detail: ConfirmPayRequest, sendResponse) {
.then((mcs) => {
if (Object.keys(mcs).length == 0) {
sendResponse({error: "Not enough coins."});
+ // FIXME: does not work like expected here ...
return;
}
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 executePay(db, offer, ds, detail.merchantPageUrl, mintUrl)
+ .then(() => {
+ sendResponse({
+ success: true,
+ });
+ });
});
return true;
}
@@ -329,22 +321,20 @@ function confirmPay(db, detail: ConfirmPayRequest, sendResponse) {
function doPayment(db, detail, sendResponse) {
let H_contract = detail.H_contract;
- let req = db.transaction(['transactions'])
- .objectStore("transactions")
- .get(H_contract);
- console.log("executing contract", H_contract);
- req.onsuccess = (e) => {
- console.log("got db response for existing contract");
- if (!req.result) {
- sendResponse({success: false, error: "contract not found"});
- return;
- }
- sendResponse({
- success: true,
- payUrl: req.result.payUrl,
- payReq: req.result.payReq
- });
- };
+ 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;
}
@@ -359,7 +349,6 @@ function confirmReserve(db, detail, sendResponse) {
form.append(detail.field_mint, detail.mint);
// XXX: set bank-specified fields.
let myRequest = new XMLHttpRequest();
- console.log("making request to " + detail.post_url);
myRequest.open('post', detail.post_url);
myRequest.send(form);
let mintBaseUrl = canonicalizeBaseUrl(detail.mint);
@@ -427,13 +416,6 @@ function rankDenom(denom1: any, denom2: any) {
}
-interface Reserve {
- mint_base_url: string
- reserve_priv: string;
- reserve_pub: string;
-}
-
-
function withdrawPrepare(db: IDBDatabase,
denom: Db.Denomination,
reserve: Reserve): Promise<Db.PreCoin> {
@@ -465,11 +447,7 @@ function withdrawPrepare(db: IDBDatabase,
h_coin_envelope: ev.hash()
});
- console.log("about to sign");
var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv);
- console.log("signed");
-
- console.log("crypto done, doing request");
let preCoin: Db.PreCoin = {
reservePub: reservePub.toCrock(),
@@ -483,104 +461,74 @@ function withdrawPrepare(db: IDBDatabase,
coinValue: denom.value
};
- console.log("storing precoin", JSON.stringify(preCoin));
-
- let tx = db.transaction(['precoins'], 'readwrite');
- tx.objectStore('precoins').add(preCoin);
- return new Promise((resolve, reject) => {
- tx.oncomplete = (e) => {
- resolve(preCoin);
- }
- });
-}
-
-
-function dbGet(db, store: string, key: any): Promise<any> {
- let tx = db.transaction([store]);
- let req = tx.objectStore(store).get(key);
- return new Promise((resolve, reject) => {
- req.onsuccess = (e) => resolve(req.result);
- });
+ return Query(db).put("precoins", preCoin).finish().then(() => preCoin);
}
function withdrawExecute(db, pc: Db.PreCoin): Promise<Db.Coin> {
- return dbGet(db, 'reserves', pc.reservePub)
- .then((r) => new Promise((resolve, reject) => {
- console.log("loading precoin", JSON.stringify(pc));
+ 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);
- let myRequest = new XMLHttpRequest();
- console.log("making request to " + reqUrl.href());
- myRequest.open('post', reqUrl.href());
- myRequest.setRequestHeader("Content-Type",
- "application/json;charset=UTF-8");
- myRequest.send(JSON.stringify(wd));
- myRequest.addEventListener('readystatechange', (e) => {
- if (myRequest.readyState == XMLHttpRequest.DONE) {
- if (myRequest.status != 200) {
- console.log("Withdrawal failed, status ", myRequest.status);
- reject();
- return;
- }
- console.log("Withdrawal successful");
- console.log(myRequest.responseText);
- let resp = JSON.parse(myRequest.responseText);
- let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.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,
- };
- console.log("unblinded coin");
- resolve(coin);
- } else {
- console.log("ready state change to", myRequest.status);
- }
- });
- }));
+ return httpPost(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;
+ });
}
function updateBadge(db) {
- let tx = db.transaction(['coins'], 'readwrite');
- let req = tx.objectStore('coins').openCursor();
- let n = 0;
- req.onsuccess = (e) => {
- let cursor = req.result;
- if (cursor) {
- let c: Db.Coin = cursor.value;
- if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
- n++;
- }
- cursor.continue();
- } else {
- chrome.browserAction.setBadgeText({text: "" + n});
- chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"});
+ function countNonEmpty(n, c) {
+ if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
+ return n + 1;
}
+ return n;
}
+
+ function doBadge(n) {
+ chrome.browserAction.setBadgeText({text: "" + n});
+ chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"});
+ }
+
+ Query(db)
+ .iter("coins")
+ .reduce(countNonEmpty, 0)
+ .then(doBadge);
}
function storeCoin(db, coin: Db.Coin) {
- let tx = db.transaction(['coins', 'precoins'], 'readwrite');
- tx.objectStore('precoins').delete(coin.coinPub);
- tx.objectStore('coins').add(coin);
- return new Promise<void>((resolve, reject) => {
- tx.oncomplete = (e) => {
- resolve();
+ Query(db)
+ .delete("precoins", coin.coinPub)
+ .add("coins", coin)
+ .finish()
+ .then(() => {
updateBadge(db);
- }
- });
+ });
}
@@ -631,88 +579,102 @@ function depleteReserve(db, reserve, mint) {
}
-function updateReserve(db, reservePub: EddsaPublicKey, mint) {
- let reserve;
- return new Promise((resolve, reject) => {
- let tx = db.transaction(['reserves']);
- tx.objectStore('reserves').get(reservePub.toCrock()).onsuccess = (e) => {
- let reserve = e.target.result;
+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': reservePub.toCrock()});
- let myRequest = new XMLHttpRequest();
- console.log("making request to " + reqUrl.href());
- myRequest.open('get', reqUrl.href());
- myRequest.send();
- myRequest.addEventListener('readystatechange', (e) => {
- if (myRequest.readyState == XMLHttpRequest.DONE) {
- if (myRequest.status != 200) {
- reject();
- return;
- }
- let reserveInfo = JSON.parse(myRequest.responseText);
- console.log("got response " + JSON.stringify(reserveInfo));
- reserve.current_amount = reserveInfo.balance;
- let tx = db.transaction(['reserves'], 'readwrite');
- console.log("putting updated reserve " + JSON.stringify(reserve));
- tx.objectStore('reserves').put(reserve);
- tx.oncomplete = (e) => {
- resolve(reserve);
- };
+ 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;
+ let q = Query(db);
+ return q.put("reserves", reserve).finish().then(() => reserve);
});
- };
+ });
+}
+
+
+interface HttpResponse {
+ status: number;
+ responseText: string;
+}
+
+
+function httpReq(method: string,
+ url: string|uri.URI,
+ options?: any): Promise<HttpResponse> {
+ let urlString: string;
+ if (url instanceof URI) {
+ urlString = url.href();
+ } else if (typeof url === "string") {
+ urlString = url;
+ }
+
+ return new Promise((resolve, reject) => {
+ let myRequest = new XMLHttpRequest();
+ myRequest.open(method, urlString);
+ if (options && options.req) {
+ myRequest.send(options.req);
+ }
+ myRequest.addEventListener("readystatechange", (e) => {
+ if (myRequest.readyState == XMLHttpRequest.DONE) {
+ let resp = {
+ status: myRequest.status,
+ responseText: myRequest.responseText
+ };
+ resolve(resp);
+ }
+ });
});
+}
+
+
+function httpGet(url: string|uri.URI) {
+ return httpReq("get", url);
+}
+
+function httpPost(url: string|uri.URI, body) {
+ return httpReq("put", url, {req: JSON.stringify(body)});
}
+class RequestException {
+ constructor(detail) {
+
+ }
+}
+
/**
* 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) {
- console.log("base url is " + baseUrl);
let reqUrl = URI("keys").absoluteTo(baseUrl);
- let myRequest = new XMLHttpRequest();
- myRequest.open('get', reqUrl.href());
- myRequest.send();
- return new Promise((resolve, reject) => {
- myRequest.addEventListener('readystatechange', (e) => {
- console.log("state change to " + myRequest.readyState);
- if (myRequest.readyState == XMLHttpRequest.DONE) {
- if (myRequest.status == 200) {
- console.log("got /keys");
- let mintKeysJson = JSON.parse(myRequest.responseText);
- if (!mintKeysJson) {
- console.log("keys invalid");
- reject();
- } else {
- let mint: Db.Mint = {
- baseUrl: baseUrl,
- keys: mintKeysJson
- };
- let tx = db.transaction(['mints', 'denoms'], 'readwrite');
- tx.objectStore('mints').put(mint);
- for (let d of mintKeysJson.denoms) {
- // TODO: verify and complete
- let di = {
- denomPub: d.denom_pub,
- value: d.value
- };
- tx.objectStore('denoms').put(di);
- }
- tx.oncomplete = (e) => {
- resolve(mint);
- };
- }
- } else {
- console.log("/keys request failed with status " + myRequest.status);
- // XXX: also write last error to DB to show in the UI
- reject();
- }
- }
- });
+ 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(db).put("mints", mint).finish().then(() => mint);
});
}
@@ -723,7 +685,6 @@ function dumpDb(db, detail, sendResponse) {
version: db.version,
stores: {}
};
- console.log("stores: " + JSON.stringify(db.objectStoreNames));
let tx = db.transaction(db.objectStoreNames);
tx.addEventListener('complete', (e) => {
sendResponse(dump);
@@ -755,58 +716,55 @@ function reset(db, detail, sendResponse) {
indexedDB.deleteDatabase(DB_NAME);
chrome.browserAction.setBadgeText({text: ""});
console.log("reset done");
+ // Response is synchronous
return false;
}
-function balances(db, detail, sendResponse) {
- let byCurrency = {};
- let tx = db.transaction(['coins', 'denoms']);
- let req = tx.objectStore('coins').openCursor();
- req.onsuccess = (e) => {
- let cursor = req.result;
- if (cursor) {
- let c: Db.Coin = cursor.value;
- tx.objectStore('denoms').get(c.denomPub).onsuccess = (e2) => {
- let d = e2.target.result;
- let acc = byCurrency[d.value.currency];
- if (!acc) {
- acc = Amount.getZero(c.currentAmount.currency);
- }
- let am = new Amount(c.currentAmount);
- am.add(new Amount(acc));
- byCurrency[d.value.currency] = am.toJson();
- console.log("counting", byCurrency[d.value.currency]);
- };
- cursor.continue();
- } else {
- sendResponse(byCurrency);
+function balances(db, detail, sendResponse): boolean {
+ function collectBalances(c: Db.Coin, byCurrency) {
+ let acc: AmountJson = 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();
+ }
+
+ Query(db)
+ .iter("coins")
+ .reduce(collectBalances, {})
+ .then(sendResponse);
return true;
}
-chrome.browserAction.setBadgeText({text: ""});
-
-openTalerDb().then((db) => {
- console.log("db loaded");
- updateBadge(db);
- chrome.runtime.onMessage.addListener(
- function(req, sender, onresponse) {
- let dispatch = {
- "confirm-reserve": confirmReserve,
- "confirm-pay": confirmPay,
- "dump-db": dumpDb,
- "balances": balances,
- "execute-payment": doPayment,
- "reset": reset
- };
- if (req.type in dispatch) {
- return dispatch[req.type](db, req.detail, onresponse);
- }
- console.error(format("Request type {1} unknown, req {0}",
- JSON.stringify(req),
- req.type));
- return false;
- });
-});
+
+function wxMain() {
+ chrome.browserAction.setBadgeText({text: ""});
+
+ openTalerDb().then((db) => {
+ updateBadge(db);
+ chrome.runtime.onMessage.addListener(
+ function(req, sender, onresponse) {
+ let dispatch = {
+ "confirm-reserve": confirmReserve,
+ "confirm-pay": confirmPay,
+ "dump-db": dumpDb,
+ "balances": balances,
+ "execute-payment": doPayment,
+ "reset": reset
+ };
+ if (req.type in dispatch) {
+ return dispatch[req.type](db, req.detail, onresponse);
+ }
+ console.error(format("Request type {1} unknown, req {0}",
+ JSON.stringify(req),
+ req.type));
+ return false;
+ });
+ });
+}
+
+
+wxMain(); \ No newline at end of file