Nicer DB interface, various refactoring.

This commit is contained in:
Florian Dold 2016-01-05 01:10:31 +01:00
parent ba4d272cc4
commit c9fc0c31ef
7 changed files with 607 additions and 556 deletions

View File

@ -401,7 +401,6 @@ class HashCode extends PackedArenaObject {
case "nonce":
qual = RandomQuality.NONCE;
break;
break;
default:
throw Error(format("unknown crypto quality: {0}", qual));
}

View File

@ -302,7 +302,7 @@ class Amount extends ArenaObject {
}
static getZero(currency: string, a?: Arena) {
static getZero(currency: string, a?: Arena): Amount {
let am = new Amount(null, a);
let r = emsc.amount_get_zero(currency, am.getNative());
if (r != GNUNET_OK) {
@ -590,7 +590,6 @@ class HashCode extends PackedArenaObject {
case "nonce":
qual = RandomQuality.NONCE;
break;
break;
default:
throw Error(format("unknown crypto quality: {0}", qual));
}

View File

@ -0,0 +1,157 @@
/*
This file is part of TALER
(C) 2015 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/>
*/
/// <reference path="../decl/chrome/chrome.d.ts" />
"use strict";
function Query(db) {
return new QueryRoot(db);
}
class QueryStream {
qr: QueryRoot;
storeName;
constructor(qr, storeName) {
this.qr = qr;
this.storeName = storeName;
}
join(indexName: string, key: any) {
// join on the source relation's key, which may be
// a path or a transformer function
throw Error("Not implemented");
}
reduce(f, acc): Promise<any> {
let leakedResolve;
let p = new Promise((resolve, reject) => {
leakedResolve = resolve;
});
let qr = this.qr;
let storeName = this.storeName;
function doReduce() {
let req = qr.tx.objectStore(storeName).openCursor();
req.onsuccess = (e) => {
let cursor: IDBCursorWithValue = req.result;
if (cursor) {
acc = f(acc, cursor.value);
cursor.continue();
} else {
leakedResolve(acc);
}
}
}
this.qr.work.push(doReduce);
// We need this one level of indirection so that the kickoff
// is run asynchronously.
return Promise.resolve().then(() => this.qr.finish().then(() => p));
}
}
class QueryRoot {
work = [];
db: IDBDatabase;
tx: IDBTransaction;
stores = new Set();
kickoffPromise;
constructor(db) {
this.db = db;
}
iter(storeName): QueryStream {
this.stores.add(storeName);
return new QueryStream(this, storeName);
}
put(storeName, val): QueryRoot {
this.stores.add(storeName);
function doPut() {
this.tx.objectStore(storeName).put(val);
}
this.work.push(doPut);
return this;
}
putAll(storeName, iterable): QueryRoot {
this.stores.add(storeName);
function doPutAll() {
for (let obj of iterable) {
this.tx.objectStore(storeName).put(obj);
}
}
this.work.push(doPutAll);
return this;
}
add(storeName, val): QueryRoot {
this.stores.add(storeName);
function doAdd() {
this.tx.objectStore(storeName).add(val);
}
this.work.push(doAdd);
return this;
}
get(storeName, key): Promise<any> {
this.stores.add(storeName);
let leakedResolve;
let p = new Promise((resolve, reject) => {
leakedResolve = resolve;
});
if (!leakedResolve) {
// According to ES6 spec (paragraph 25.4.3.1), this can't happen.
throw Error("assertion failed");
}
function doGet() {
let req = this.tx.objectStore(storeName).get(key);
req.onsuccess = (r) => {
leakedResolve(r);
};
}
this.work.push(doGet);
return p;
}
finish(): Promise<void> {
if (this.kickoffPromise) {
return this.kickoffPromise;
}
this.kickoffPromise = new Promise((resolve, reject) => {
this.tx = this.db.transaction(Array.from(this.stores), "readwrite");
this.tx.oncomplete = () => {
resolve();
};
for (let w of this.work) {
w();
}
});
return this.kickoffPromise;
}
delete(storeName: string, key): QueryRoot {
this.stores.add(storeName);
function doDelete() {
this.tx.objectStore(storeName).delete(key);
}
this.work.push(doDelete);
return this;
}
}

View File

@ -13,8 +13,6 @@
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/>
*/
/// <reference path="../decl/urijs/URIjs.d.ts" />
/// <reference path="../decl/chrome/chrome.d.ts" />
'use strict';
/**
* See http://api.taler.net/wallet.html#general
@ -36,9 +34,7 @@ function signDeposit(db, offer, cds) {
cds = copy(cds);
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;
}
if (amountRemaining.cmp(new Amount(cd.coin.currentAmount)) < 0) {
@ -64,9 +60,6 @@ function signDeposit(db, offer, cds) {
transaction_id: UInt64.fromNumber(offer.contract.transaction_id),
};
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();
let s = {
@ -163,7 +156,6 @@ function getPossibleMintCoins(db, paymentAmount, depositFeeLimit, allowedMints)
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);
};
@ -173,7 +165,6 @@ function getPossibleMintCoins(db, paymentAmount, depositFeeLimit, allowedMints)
});
}
function executePay(db, offer, payCoinInfo, merchantBaseUrl, chosenMint) {
return new Promise((resolve, reject) => {
let payReq = {};
payReq["H_wire"] = offer.contract.H_wire;
payReq["H_contract"] = offer.H_contract;
@ -189,16 +180,10 @@ function executePay(db, offer, payCoinInfo, merchantBaseUrl, chosenMint) {
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();
};
});
return Query(db)
.put("transactions", t)
.putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
.finish();
}
function confirmPay(db, detail, sendResponse) {
let offer = detail.offer;
@ -206,37 +191,36 @@ function confirmPay(db, detail, 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);
})
return executePay(db, offer, ds, detail.merchantPageUrl, mintUrl)
.then(() => {
sendResponse({
success: true,
});
});
});
return true;
}
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) {
Query(db)
.get("transactions", H_contract)
.then((r) => {
if (!r) {
sendResponse({ success: false, error: "contract not found" });
return;
}
sendResponse({
success: true,
payUrl: req.result.payUrl,
payReq: req.result.payReq
payUrl: r.payUrl,
payReq: r.payReq
});
};
});
// async sendResponse
return true;
}
function confirmReserve(db, detail, sendResponse) {
@ -249,7 +233,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);
@ -336,10 +319,7 @@ function withdrawPrepare(db, denom, reserve) {
h_denomination_pub: denomPub.encode().hash(),
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 = {
reservePub: reservePub.toCrock(),
blindingKey: blindingFactor.toCrock(),
@ -351,48 +331,29 @@ function withdrawPrepare(db, denom, reserve) {
coinEv: ev.toCrock(),
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, key) {
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) {
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 = {};
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;
return httpPost(reqUrl, wd);
})
.then(resp => {
if (resp.status != 200) {
throw new RequestException({
hint: "Withdrawal failed",
status: resp.status
});
}
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 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,
@ -401,43 +362,32 @@ function withdrawExecute(db, pc) {
currentAmount: pc.coinValue,
mintBaseUrl: pc.mintBaseUrl,
};
console.log("unblinded coin");
resolve(coin);
}
else {
console.log("ready state change to", myRequest.status);
}
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 = cursor.value;
function countNonEmpty(n, c) {
if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
n++;
return n + 1;
}
cursor.continue();
return n;
}
else {
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) {
let tx = db.transaction(['coins', 'precoins'], 'readwrite');
tx.objectStore('precoins').delete(coin.coinPub);
tx.objectStore('coins').add(coin);
return new Promise((resolve, reject) => {
tx.oncomplete = (e) => {
resolve();
Query(db)
.delete("precoins", coin.coinPub)
.add("coins", coin)
.finish()
.then(() => {
updateBadge(db);
};
});
}
function withdraw(db, denom, reserve) {
@ -482,86 +432,81 @@ function depleteReserve(db, reserve, mint) {
next();
}
function updateReserve(db, reservePub, 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;
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;
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();
}
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);
let q = Query(db);
return q.put("reserves", reserve).finish().then(() => reserve);
});
});
}
function httpReq(method, url, options) {
let urlString;
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) {
return httpReq("get", url);
}
function httpPost(url, 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();
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" });
}
else {
let 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 Query(db).put("mints", mint).finish().then(() => mint);
});
}
function dumpDb(db, detail, sendResponse) {
@ -570,7 +515,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);
@ -600,38 +544,28 @@ 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 = cursor.value;
tx.objectStore('denoms').get(c.denomPub).onsuccess = (e2) => {
let d = e2.target.result;
let acc = byCurrency[d.value.currency];
function collectBalances(c, byCurrency) {
let acc = byCurrency[c.currentAmount.currency];
if (!acc) {
acc = Amount.getZero(c.currentAmount.currency);
acc = Amount.getZero(c.currentAmount.currency).toJson();
}
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();
byCurrency[c.currentAmount.currency] = am.toJson();
}
else {
sendResponse(byCurrency);
}
};
Query(db)
.iter("coins")
.reduce(collectBalances, {})
.then(sendResponse);
return true;
}
chrome.browserAction.setBadgeText({ text: "" });
openTalerDb().then((db) => {
console.log("db loaded");
function wxMain() {
chrome.browserAction.setBadgeText({ text: "" });
openTalerDb().then((db) => {
updateBadge(db);
chrome.runtime.onMessage.addListener(function (req, sender, onresponse) {
let dispatch = {
@ -648,4 +582,6 @@ openTalerDb().then((db) => {
console.error(format("Request type {1} unknown, req {0}", JSON.stringify(req), req.type));
return false;
});
});
});
}
wxMain();

View File

@ -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,
@ -246,10 +255,6 @@ function getPossibleMintCoins(db: IDBDatabase,
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,20 +266,11 @@ 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) => {
chosenMint: string): Promise<void> {
let payReq = {};
payReq["H_wire"] = offer.contract.H_wire;
payReq["H_contract"] = offer.H_contract;
@ -290,16 +286,11 @@ function executePay(db,
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();
};
});
return Query(db)
.put("transactions", t)
.putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
.finish();
}
@ -312,39 +303,38 @@ 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);
})
return executePay(db, offer, ds, detail.merchantPageUrl, mintUrl)
.then(() => {
sendResponse({
success: true,
});
});
});
return true;
}
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) {
Query(db)
.get("transactions", H_contract)
.then((r) => {
if (!r) {
sendResponse({success: false, error: "contract not found"});
return;
}
sendResponse({
success: true,
payUrl: req.result.payUrl,
payReq: req.result.payReq
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,54 +461,31 @@ 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;
return httpPost(reqUrl, wd);
})
.then(resp => {
if (resp.status != 200) {
throw new RequestException({
hint: "Withdrawal failed",
status: resp.status
});
}
console.log("Withdrawal successful");
console.log(myRequest.responseText);
let resp = JSON.parse(myRequest.responseText);
let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.ev_sig),
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 = {
@ -541,45 +496,38 @@ function withdrawExecute(db, pc: Db.PreCoin): Promise<Db.Coin> {
currentAmount: pc.coinValue,
mintBaseUrl: pc.mintBaseUrl,
};
console.log("unblinded coin");
resolve(coin);
} else {
console.log("ready state change to", myRequest.status);
}
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;
function countNonEmpty(n, c) {
if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
n++;
return n + 1;
}
cursor.continue();
} else {
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;
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();
}
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);
};
}
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);
return httpGet(reqUrl).then((resp) => {
if (resp.status != 200) {
throw Error("/keys request failed");
}
let mintKeysJson = JSON.parse(resp.responseText);
if (!mintKeysJson) {
console.log("keys invalid");
reject();
} else {
throw new RequestException({url: reqUrl, hint: "keys invalid"});
}
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 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,41 +716,34 @@ 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];
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);
acc = Amount.getZero(c.currentAmount.currency).toJson();
}
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);
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");
function wxMain() {
chrome.browserAction.setBadgeText({text: ""});
openTalerDb().then((db) => {
updateBadge(db);
chrome.runtime.onMessage.addListener(
function(req, sender, onresponse) {
@ -809,4 +763,8 @@ openTalerDb().then((db) => {
req.type));
return false;
});
});
});
}
wxMain();

View File

@ -46,6 +46,7 @@
"background/libwrapper.js",
"background/emscriptif.js",
"background/db.js",
"background/query.js",
"background/wallet.js"
]
},

View File

@ -7,6 +7,7 @@
"background/wallet.ts",
"background/emscriptif.ts",
"background/db.ts",
"background/query.ts",
"lib/util.ts",
"lib/polyfill-react.ts",
"content_scripts/notify.ts",