diff options
author | Florian Dold <florian.dold@gmail.com> | 2016-01-05 14:20:13 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2016-01-05 14:20:13 +0100 |
commit | b459ffb4241877670fb7f820a67d959fec2d995c (patch) | |
tree | 23277212792e701218100c010dcea66da3b199d2 /extension/background | |
parent | c48f2d39f0f62ea8f862ba08234b8a5376a11a2e (diff) |
more complete DB abstractions
Diffstat (limited to 'extension/background')
-rw-r--r-- | extension/background/db.js | 27 | ||||
-rw-r--r-- | extension/background/db.ts | 48 | ||||
-rw-r--r-- | extension/background/http.ts | 11 | ||||
-rw-r--r-- | extension/background/messaging.ts | 72 | ||||
-rw-r--r-- | extension/background/query.ts | 150 | ||||
-rw-r--r-- | extension/background/timerThread.js | 2 | ||||
-rw-r--r-- | extension/background/wallet.js | 305 | ||||
-rw-r--r-- | extension/background/wallet.ts | 303 |
8 files changed, 487 insertions, 431 deletions
diff --git a/extension/background/db.js b/extension/background/db.js index d9bff8fee..0e4576851 100644 --- a/extension/background/db.js +++ b/extension/background/db.js @@ -47,3 +47,30 @@ function openTalerDb() { }; }); } +function exportDb(db) { + let dump = { + name: db.name, + version: db.version, + stores: {} + }; + return new Promise((resolve, reject) => { + let tx = db.transaction(db.objectStoreNames); + tx.addEventListener('complete', (e) => { + resolve(dump); + }); + for (let i = 0; i < db.objectStoreNames.length; i++) { + let name = db.objectStoreNames[i]; + let storeDump = {}; + dump.stores[name] = storeDump; + let store = tx.objectStore(name) + .openCursor() + .addEventListener('success', (e) => { + let cursor = e.target.result; + if (cursor) { + storeDump[cursor.key] = cursor.value; + cursor.continue(); + } + }); + } + }); +} diff --git a/extension/background/db.ts b/extension/background/db.ts index 92fff47f8..1dd399907 100644 --- a/extension/background/db.ts +++ b/extension/background/db.ts @@ -56,7 +56,7 @@ namespace Db { mintBaseUrl: string; coinValue: AmountJson; } - + export interface Coin { coinPub: string; coinPriv: string; @@ -88,19 +88,51 @@ function openTalerDb(): Promise<IDBDatabase> { }; req.onupgradeneeded = (e) => { let db = req.result; - console.log ("DB: upgrade needed: oldVersion = " + e.oldVersion); + console.log("DB: upgrade needed: oldVersion = " + e.oldVersion); switch (e.oldVersion) { case 0: // DB does not exist yet - let mints = db.createObjectStore("mints", { keyPath: "baseUrl" }); + let mints = db.createObjectStore("mints", {keyPath: "baseUrl"}); mints.createIndex("pubKey", "keys.master_public_key"); - db.createObjectStore("reserves", { keyPath: "reserve_pub"}); - db.createObjectStore("denoms", { keyPath: "denomPub" }); - let coins = db.createObjectStore("coins", { keyPath: "coinPub" }); + db.createObjectStore("reserves", {keyPath: "reserve_pub"}); + db.createObjectStore("denoms", {keyPath: "denomPub"}); + let coins = db.createObjectStore("coins", {keyPath: "coinPub"}); coins.createIndex("mintBaseUrl", "mintBaseUrl"); - db.createObjectStore("transactions", { keyPath: "contractHash" }); - db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true }); + db.createObjectStore("transactions", {keyPath: "contractHash"}); + db.createObjectStore("precoins", + {keyPath: "coinPub", autoIncrement: true}); break; } }; }); } + + +function exportDb(db): Promise<any> { + let dump = { + name: db.name, + version: db.version, + stores: {} + }; + + return new Promise((resolve, reject) => { + + let tx = db.transaction(db.objectStoreNames); + tx.addEventListener('complete', (e) => { + resolve(dump); + }); + for (let i = 0; i < db.objectStoreNames.length; i++) { + let name = db.objectStoreNames[i]; + let storeDump = {}; + dump.stores[name] = storeDump; + let store = tx.objectStore(name) + .openCursor() + .addEventListener('success', (e) => { + let cursor = e.target.result; + if (cursor) { + storeDump[cursor.key] = cursor.value; + cursor.continue(); + } + }); + } + }); +}
\ No newline at end of file diff --git a/extension/background/http.ts b/extension/background/http.ts index da0360dfe..9a064e974 100644 --- a/extension/background/http.ts +++ b/extension/background/http.ts @@ -14,6 +14,8 @@ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ +"use strict"; + interface HttpResponse { status: number; responseText: string; @@ -55,8 +57,13 @@ function httpGet(url: string|uri.URI) { } -function httpPost(url: string|uri.URI, body) { - return httpReq("put", url, {req: JSON.stringify(body)}); +function httpPostJson(url: string|uri.URI, body) { + return httpReq("post", url, {req: JSON.stringify(body)}); +} + + +function httpPostForm(url: string|uri.URI, form) { + return httpReq("post", url, {req: form}); } diff --git a/extension/background/messaging.ts b/extension/background/messaging.ts new file mode 100644 index 000000000..fc513bd04 --- /dev/null +++ b/extension/background/messaging.ts @@ -0,0 +1,72 @@ +/* + 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/> + */ + + +/** + * Messaging for the WebExtensions wallet. Should contain + * parts that are specific for WebExtensions, but as little business + * logic as possible. + * @module Messaging + * @author Florian Dold + */ + +"use strict"; + +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-db"]: 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) { + return confirmReserveHandler(db, detail, sendResponse); + } +}; + + +function wxMain() { + chrome.browserAction.setBadgeText({text: ""}); + + openTalerDb().then((db) => { + updateBadge(db); + chrome.runtime.onMessage.addListener( + function(req, sender, onresponse) { + if (req.type in handlers) { + return handlers[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 diff --git a/extension/background/query.ts b/extension/background/query.ts index bfe3102f3..1a61c66ca 100644 --- a/extension/background/query.ts +++ b/extension/background/query.ts @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2015 GNUnet e.V. + (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 @@ -16,50 +16,147 @@ /// <reference path="../decl/chrome/chrome.d.ts" /> -"use strict"; +/** + * Database query abstractions. + * @module Query + * @author Florian Dold + */ + +"use strict"; function Query(db) { return new QueryRoot(db); } -class QueryStream { - qr: QueryRoot; - storeName; - constructor(qr, storeName) { - this.qr = qr; - this.storeName = storeName; + +abstract class QueryStreamBase { + abstract subscribe(f: (isDone: boolean, value: any) => void); + root: QueryRoot; + + constructor(root: QueryRoot) { + this.root = root; } - join(indexName: string, key: any) { + + indexJoin(storeName: string, indexName: string, key: any): QueryStreamBase { // join on the source relation's key, which may be // a path or a transformer function - throw Error("Not implemented"); + return new QueryStreamIndexJoin(this, storeName, indexName, key); + } + + filter(f: (any) => boolean): QueryStreamBase { + return new QueryStreamFilter(this, f); } - reduce(f, acc): Promise<any> { + + 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(); + this.subscribe((isDone, value) => { + if (isDone) { + leakedResolve(acc); + return; + } + acc = f(value, acc); + }); + + return Promise.resolve().then(() => this.root.finish().then(() => p)); + } +} + + +class QueryStreamFilter extends QueryStreamBase { + s: QueryStreamBase; + filterFn; + + constructor(s: QueryStreamBase, filterFn) { + super(s.root); + this.s = s; + this.filterFn = filterFn; + } + + subscribe(f) { + this.s.subscribe((isDone, value) => { + if (isDone) { + f(true, undefined); + return; + } + if (this.filterFn(value)) { + f(false, value) + } + }); + } +} + + +class QueryStreamIndexJoin extends QueryStreamBase { + s: QueryStreamBase; + storeName; + key; + constructor(s, storeName: string, indexName: string, key: any) { + super(s.root); + this.s = s; + this.storeName = storeName; + this.key = key; + } + + subscribe(f) { + this.s.subscribe((isDone, value) => { + if (isDone) { + f(true, undefined); + return; + } + + let s = this.root.tx.objectStore(this.storeName); + let req = s.openCursor(IDBKeyRange.only(value)); + req.onsuccess = () => { + let cursor = req.result; + if (cursor) { + f(false, [value, cursor.value]); + cursor.continue(); + } else { + f(true, undefined); + } + } + }); + } + +} + + +class IterQueryStream extends QueryStreamBase { + private qr: QueryRoot; + private storeName; + private options; + + constructor(qr, storeName, options?) { + super(qr); + this.qr = qr; + this.options = options; + this.storeName = storeName; + } + + subscribe(f) { + function doIt() { + let s = this.qr.tx.objectStore(this.storeName); + let kr = undefined; + if (this.options && ("only" in this.options)) { + kr = IDBKeyRange.only(this.options.only); + } + let req = s.openCursor(kr); req.onsuccess = (e) => { let cursor: IDBCursorWithValue = req.result; if (cursor) { - acc = f(acc, cursor.value); + f(false, cursor.value); cursor.continue(); } else { - leakedResolve(acc); + f(true, undefined); } } } - - 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)); + this.qr.work.push(doIt.bind(this)); } } @@ -75,9 +172,14 @@ class QueryRoot { this.db = db; } - iter(storeName): QueryStream { + iter(storeName): QueryStreamBase { + this.stores.add(storeName); + return new IterQueryStream(this, storeName); + } + + iterOnly(storeName, key): QueryStreamBase { this.stores.add(storeName); - return new QueryStream(this, storeName); + return new IterQueryStream(this, storeName, {only: key}); } put(storeName, val): QueryRoot { diff --git a/extension/background/timerThread.js b/extension/background/timerThread.js index 7ac66a711..7ade360f3 100644 --- a/extension/background/timerThread.js +++ b/extension/background/timerThread.js @@ -7,4 +7,4 @@ onmessage = function(e) { self.setInterval(() => postMessage(true), e.data.interval); -} +};
\ No newline at end of file diff --git a/extension/background/wallet.js b/extension/background/wallet.js index e97e34028..971da7195 100644 --- a/extension/background/wallet.js +++ b/extension/background/wallet.js @@ -84,86 +84,65 @@ function signDeposit(db, offer, cds) { * @param allowedMints */ function getPossibleMintCoins(db, paymentAmount, depositFeeLimit, allowedMints) { - return new Promise((resolve, reject) => { - let m = {}; - let found = false; - let tx = db.transaction(["mints", "coins"]); - // First pass: Get all coins from acceptable mints. - for (let info of allowedMints) { - let req_mints = tx.objectStore("mints") - .index("pubKey") - .get(info.master_pub); - req_mints.onsuccess = (e) => { - let mint = req_mints.result; - if (!mint) { - // We don't have that mint ... - return; - } - let req_coins = tx.objectStore("coins") - .index("mintBaseUrl") - .openCursor(IDBKeyRange.only(mint.baseUrl)); - req_coins.onsuccess = (e) => { - let cursor = req_coins.result; - if (!cursor) { - return; - } - let value = cursor.value; - let cd = { - coin: cursor.value, - denom: mint.keys.denoms.find((e) => e.denom_pub === value.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); - } - cursor.continue(); - }; - }; + 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)"); } - tx.oncomplete = (e) => { - 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; - } + let x = m[mint.baseUrl]; + if (!x) { + m[mint.baseUrl] = [cd]; + } + else { + x.push(cd); + } + } + let ps = allowedMints.map((info) => { + return Query(db) + .iterOnly("mints", 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; } } - resolve(ret); - }; - tx.onerror = (e) => { - reject(); - }; + } + return ret; }); } function executePay(db, offer, payCoinInfo, merchantBaseUrl, chosenMint) { @@ -187,13 +166,12 @@ function executePay(db, offer, payCoinInfo, merchantBaseUrl, chosenMint) { .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin)) .finish(); } -function confirmPay(db, detail, sendResponse) { +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." }); - // FIXME: does not work like expected here ... return; } let mintUrl = Object.keys(mcs)[0]; @@ -207,7 +185,7 @@ function confirmPay(db, detail, sendResponse) { }); return true; } -function doPayment(db, detail, sendResponse) { +function doPaymentHandler(db, detail, sendResponse) { let H_contract = detail.H_contract; Query(db) .get("transactions", H_contract) @@ -225,7 +203,7 @@ function doPayment(db, detail, sendResponse) { // async sendResponse return true; } -function confirmReserve(db, detail, sendResponse) { +function confirmReserveHandler(db, detail, sendResponse) { let reservePriv = EddsaPrivateKey.create(); let reservePub = reservePriv.getPublicKey(); let form = new FormData(); @@ -234,55 +212,47 @@ function confirmReserve(db, detail, sendResponse) { form.append(detail.field_reserve_pub, reservePub.toCrock()); form.append(detail.field_mint, detail.mint); // XXX: set bank-specified fields. - let myRequest = new XMLHttpRequest(); - myRequest.open('post', detail.post_url); - myRequest.send(form); let mintBaseUrl = canonicalizeBaseUrl(detail.mint); - myRequest.addEventListener('readystatechange', (e) => { - if (myRequest.readyState == XMLHttpRequest.DONE) { - // TODO: extract as interface - let resp = { - status: myRequest.status, - text: myRequest.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 - }; - // XXX: insert into db. - switch (myRequest.status) { - case 200: - resp.success = true; - // We can't show the page directly, so - // we show some generic page from the wallet. - resp.backlink = chrome.extension.getURL("pages/reserve-success.html"); - let tx = db.transaction(['reserves'], 'readwrite'); - tx.objectStore('reserves').add(reserveRecord); - tx.addEventListener('complete', (e) => { - console.log('tx complete, pk was ' + reserveRecord.reserve_pub); - sendResponse(resp); - var mint; - updateMintFromUrl(db, reserveRecord.mint_base_url) - .then((m) => { - mint = m; - return updateReserve(db, reservePub, mint); - }) - .then((reserve) => depleteReserve(db, reserve, mint)); - }); - break; - default: - resp.success = false; - sendResponse(resp); - } + 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 + }; + 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; + }); }); // Allow async response return true; @@ -345,7 +315,7 @@ function withdrawExecute(db, pc) { wd.reserve_sig = pc.withdrawSig; wd.coin_ev = pc.coinEv; let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url); - return httpPost(reqUrl, wd); + return httpPostJson(reqUrl, wd); }) .then(resp => { if (resp.status != 200) { @@ -368,7 +338,7 @@ function withdrawExecute(db, pc) { }); } function updateBadge(db) { - function countNonEmpty(n, c) { + function countNonEmpty(c, n) { if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) { return n + 1; } @@ -449,8 +419,10 @@ function updateReserve(db, reservePub, mint) { throw Error(); } reserve.current_amount = reserveInfo.balance; - let q = Query(db); - return q.put("reserves", reserve).finish().then(() => reserve); + return Query(db) + .put("reserves", reserve) + .finish() + .then(() => reserve); }); }); } @@ -476,45 +448,7 @@ function updateMintFromUrl(db, baseUrl) { return Query(db).put("mints", mint).finish().then(() => mint); }); } -function dumpDb(db, detail, sendResponse) { - let dump = { - name: db.name, - version: db.version, - stores: {} - }; - let tx = db.transaction(db.objectStoreNames); - tx.addEventListener('complete', (e) => { - sendResponse(dump); - }); - for (let i = 0; i < db.objectStoreNames.length; i++) { - let name = db.objectStoreNames[i]; - let storeDump = {}; - dump.stores[name] = storeDump; - let store = tx.objectStore(name) - .openCursor() - .addEventListener('success', (e) => { - let cursor = e.target.result; - if (cursor) { - storeDump[cursor.key] = cursor.value; - cursor.continue(); - } - }); - } - return true; -} -// Just for debugging. -function reset(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; -} -function balances(db, detail, sendResponse) { +function getBalances(db) { function collectBalances(c, byCurrency) { let acc = byCurrency[c.currentAmount.currency]; if (!acc) { @@ -523,32 +457,9 @@ function balances(db, detail, sendResponse) { let am = new Amount(c.currentAmount); am.add(new Amount(acc)); byCurrency[c.currentAmount.currency] = am.toJson(); + return byCurrency; } - Query(db) + return Query(db) .iter("coins") - .reduce(collectBalances, {}) - .then(sendResponse); - return true; -} -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; - }); - }); + .reduce(collectBalances, {}); } -wxMain(); diff --git a/extension/background/wallet.ts b/extension/background/wallet.ts index 4ca4eb802..b3593f682 100644 --- a/extension/background/wallet.ts +++ b/extension/background/wallet.ts @@ -176,90 +176,72 @@ function getPossibleMintCoins(db: IDBDatabase, paymentAmount: AmountJson, depositFeeLimit: AmountJson, allowedMints: MintInfo[]): Promise<MintCoins> { - return new Promise((resolve, reject) => { - let m: MintCoins = {}; - let found = false; - let tx = db.transaction(["mints", "coins"]); - // First pass: Get all coins from acceptable mints. - for (let info of allowedMints) { - let req_mints = tx.objectStore("mints") - .index("pubKey") - .get(info.master_pub); - req_mints.onsuccess = (e) => { - let mint: Db.Mint = req_mints.result; - if (!mint) { - // We don't have that mint ... - return; - } - let req_coins = tx.objectStore("coins") - .index("mintBaseUrl") - .openCursor(IDBKeyRange.only(mint.baseUrl)); - req_coins.onsuccess = (e) => { - let cursor: IDBCursorWithValue = req_coins.result; - if (!cursor) { - return; - } - let value: Db.Coin = cursor.value; - let cd = { - coin: cursor.value, - denom: mint.keys.denoms.find((e) => e.denom_pub === value.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); - } - cursor.continue(); - } - } - } - tx.oncomplete = (e) => { - 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; - } - } - } - resolve(ret); - }; - tx.onerror = (e) => { - reject(); + let m: MintCoins = {}; + + 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(db) + .iterOnly("mints", 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; + } + } + } + return ret; }); } @@ -292,7 +274,7 @@ function executePay(db, } -function confirmPay(db, detail: ConfirmPayRequest, sendResponse) { +function confirmPayHandler(db, detail: ConfirmPayRequest, sendResponse) { let offer: Offer = detail.offer; getPossibleMintCoins(db, offer.contract.amount, @@ -301,7 +283,6 @@ 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]; @@ -317,7 +298,7 @@ function confirmPay(db, detail: ConfirmPayRequest, sendResponse) { } -function doPayment(db, detail, sendResponse) { +function doPaymentHandler(db, detail, sendResponse) { let H_contract = detail.H_contract; Query(db) .get("transactions", H_contract) @@ -337,7 +318,7 @@ function doPayment(db, detail, sendResponse) { } -function confirmReserve(db, detail, sendResponse) { +function confirmReserveHandler(db, detail, sendResponse) { let reservePriv = EddsaPrivateKey.create(); let reservePub = reservePriv.getPublicKey(); let form = new FormData(); @@ -346,16 +327,13 @@ function confirmReserve(db, detail, sendResponse) { form.append(detail.field_reserve_pub, reservePub.toCrock()); form.append(detail.field_mint, detail.mint); // XXX: set bank-specified fields. - let myRequest = new XMLHttpRequest(); - myRequest.open('post', detail.post_url); - myRequest.send(form); let mintBaseUrl = canonicalizeBaseUrl(detail.mint); - myRequest.addEventListener('readystatechange', (e) => { - if (myRequest.readyState == XMLHttpRequest.DONE) { + httpPostForm(detail.post_url, form) + .then((hresp) => { // TODO: extract as interface let resp = { - status: myRequest.status, - text: myRequest.responseText, + status: hresp.status, + text: hresp.responseText, success: undefined, backlink: undefined }; @@ -369,33 +347,31 @@ function confirmReserve(db, detail, sendResponse) { // XXX: set to actual amount initial_amount: null }; - // XXX: insert into db. - switch (myRequest.status) { - case 200: - resp.success = true; - // We can't show the page directly, so - // we show some generic page from the wallet. - resp.backlink = chrome.extension.getURL("pages/reserve-success.html"); - let tx = db.transaction(['reserves'], 'readwrite'); - tx.objectStore('reserves').add(reserveRecord); - tx.addEventListener('complete', (e) => { - console.log('tx complete, pk was ' + reserveRecord.reserve_pub); - sendResponse(resp); - var mint; - updateMintFromUrl(db, reserveRecord.mint_base_url) - .then((m) => { - mint = m; - return updateReserve(db, reservePub, mint); - }) - .then((reserve) => depleteReserve(db, reserve, mint)); - }); - break; - default: - resp.success = false; - sendResponse(resp); + + 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; + }); + }); + // Allow async response return true; } @@ -473,7 +449,7 @@ function withdrawExecute(db, pc: Db.PreCoin): Promise<Db.Coin> { wd.reserve_sig = pc.withdrawSig; wd.coin_ev = pc.coinEv; let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url); - return httpPost(reqUrl, wd); + return httpPostJson(reqUrl, wd); }) .then(resp => { if (resp.status != 200) { @@ -500,7 +476,7 @@ function withdrawExecute(db, pc: Db.PreCoin): Promise<Db.Coin> { function updateBadge(db) { - function countNonEmpty(n, c) { + function countNonEmpty(c, n) { if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) { return n + 1; } @@ -595,8 +571,10 @@ function updateReserve(db: IDBDatabase, throw Error(); } reserve.current_amount = reserveInfo.balance; - let q = Query(db); - return q.put("reserves", reserve).finish().then(() => reserve); + return Query(db) + .put("reserves", reserve) + .finish() + .then(() => reserve); }); }); } @@ -626,49 +604,7 @@ function updateMintFromUrl(db, baseUrl) { } -function dumpDb(db, detail, sendResponse) { - let dump = { - name: db.name, - version: db.version, - stores: {} - }; - let tx = db.transaction(db.objectStoreNames); - tx.addEventListener('complete', (e) => { - sendResponse(dump); - }); - for (let i = 0; i < db.objectStoreNames.length; i++) { - let name = db.objectStoreNames[i]; - let storeDump = {}; - dump.stores[name] = storeDump; - let store = tx.objectStore(name) - .openCursor() - .addEventListener('success', (e) => { - let cursor = e.target.result; - if (cursor) { - storeDump[cursor.key] = cursor.value; - cursor.continue(); - } - }); - } - return true; -} - - -// Just for debugging. -function reset(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; -} - - -function balances(db, detail, sendResponse): boolean { +function getBalances(db): Promise<any> { function collectBalances(c: Db.Coin, byCurrency) { let acc: AmountJson = byCurrency[c.currentAmount.currency]; if (!acc) { @@ -677,41 +613,10 @@ function balances(db, detail, sendResponse): boolean { let am = new Amount(c.currentAmount); am.add(new Amount(acc)); byCurrency[c.currentAmount.currency] = am.toJson(); + return byCurrency; } - Query(db) + return Query(db) .iter("coins") - .reduce(collectBalances, {}) - .then(sendResponse); - return true; + .reduce(collectBalances, {}); } - - -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 |