diff options
Diffstat (limited to 'extension/lib/wallet')
-rw-r--r-- | extension/lib/wallet/db.ts | 9 | ||||
-rw-r--r-- | extension/lib/wallet/query.ts | 25 | ||||
-rw-r--r-- | extension/lib/wallet/wallet.ts | 83 | ||||
-rw-r--r-- | extension/lib/wallet/wxmessaging.js | 34 | ||||
-rw-r--r-- | extension/lib/wallet/wxmessaging.ts | 62 |
5 files changed, 167 insertions, 46 deletions
diff --git a/extension/lib/wallet/db.ts b/extension/lib/wallet/db.ts index a208f0923..8c331ef6f 100644 --- a/extension/lib/wallet/db.ts +++ b/extension/lib/wallet/db.ts @@ -33,7 +33,7 @@ const DB_VERSION = 1; */ export function openTalerDb(): Promise<IDBDatabase> { return new Promise((resolve, reject) => { - let req = indexedDB.open(DB_NAME, DB_VERSION); + const req = indexedDB.open(DB_NAME, DB_VERSION); req.onerror = (e) => { reject(e); }; @@ -45,16 +45,17 @@ export function openTalerDb(): Promise<IDBDatabase> { 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"}); + const 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"}); + const coins = db.createObjectStore("coins", {keyPath: "coinPub"}); coins.createIndex("mintBaseUrl", "mintBaseUrl"); db.createObjectStore("transactions", {keyPath: "contractHash"}); db.createObjectStore("precoins", {keyPath: "coinPub", autoIncrement: true}); - db.createObjectStore("history", {keyPath: "id", autoIncrement: true}); + const history = db.createObjectStore("history", {keyPath: "id", autoIncrement: true}); + history.createIndex("timestamp", "timestamp"); break; } }; diff --git a/extension/lib/wallet/query.ts b/extension/lib/wallet/query.ts index dda716d07..375816193 100644 --- a/extension/lib/wallet/query.ts +++ b/extension/lib/wallet/query.ts @@ -39,7 +39,7 @@ export interface QueryStream<T> { indexName: string, keyFn: (obj: any) => any): QueryStream<[T,S]>; filter(f: (any) => boolean): QueryStream<T>; - reduce<S>(f: (S, T) => S, acc?: S): Promise<S>; + reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>; } @@ -166,7 +166,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> { private storeName; private options; - constructor(qr, storeName, options?) { + constructor(qr, storeName, options) { super(qr); this.options = options; this.storeName = storeName; @@ -174,15 +174,16 @@ class IterQueryStream<T> extends QueryStreamBase<T> { subscribe(f) { let doIt = (tx) => { + const {indexName = void 0, only = void 0} = this.options; let s; - if (this.options && this.options.indexName) { + if (indexName !== void 0) { s = tx.objectStore(this.storeName) .index(this.options.indexName); } else { s = tx.objectStore(this.storeName); } let kr = undefined; - if (this.options && ("only" in this.options)) { + if (only !== void 0) { kr = IDBKeyRange.only(this.options.only); } let req = s.openCursor(kr); @@ -218,23 +219,11 @@ class QueryRoot { this.db = db; } - iter<T>(storeName): QueryStream<T> { + iter<T>(storeName, {only = void 0, indexName = void 0} = {}): QueryStream<T> { this.stores.add(storeName); - return new IterQueryStream(this, storeName); + return new IterQueryStream(this, storeName, {only, indexName}); } - iterOnly<T>(storeName, key): QueryStream<T> { - this.stores.add(storeName); - return new IterQueryStream(this, storeName, {only: key}); - } - - - iterIndex<T>(storeName, indexName, key) { - this.stores.add(storeName); - return new IterQueryStream(this, storeName, {indexName: indexName}); - } - - /** * Put an object into the given object store. * Overrides if an existing object with the same key exists diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts index 8dbcca044..2a931ae33 100644 --- a/extension/lib/wallet/wallet.ts +++ b/extension/lib/wallet/wallet.ts @@ -181,6 +181,18 @@ function canonicalizeBaseUrl(url) { return x.href() } +function parsePrettyAmount(pretty: string): AmountJson_interface { + const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty); + if (!res) { + return null; + } + return { + value: parseInt(res[1], 10), + fraction: res[2] ? (parseFloat(`0.${res[2]}`) * 1e-6) : 0, + currency: res[3] + } +} + interface HttpRequestLibrary { req(method: string, @@ -310,7 +322,7 @@ export class Wallet { let ps = allowedMints.map((info) => { return Query(this.db) - .iterIndex("mints", "pubKey", info.master_pub) + .iter("mints", {indexName: "pubKey", only: info.master_pub}) .indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl) .reduce(storeMintCoin); }); @@ -376,8 +388,20 @@ export class Wallet { payReq: payReq }; + + let historyEntry = { + type: "pay", + timestamp: (new Date).getTime(), + detail: { + merchantName: offer.contract.merchant.name, + amount: offer.contract.amount, + contractHash: offer.H_contract + } + }; + return Query(this.db) .put("transactions", t) + .put("history", historyEntry) .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin)) .finish(); } @@ -418,12 +442,17 @@ export class Wallet { let reservePriv = EddsaPrivateKey.create(); let reservePub = reservePriv.getPublicKey(); let form = new FormData(); - let now = (new Date()).toString(); + let now: number = (new Date).getTime(); 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); + let requestedAmount = parsePrettyAmount(req.amount_str); + + if (!requestedAmount) { + throw Error(`unrecognized amount ${req.amount_str}.`); + } return this.http.postForm(req.post_url, form) .then((hresp) => { @@ -441,7 +470,7 @@ export class Wallet { last_query: null, current_amount: null, // XXX: set to actual amount - initial_amount: null + requested_amount: null }; if (hresp.status != 200) { @@ -449,12 +478,22 @@ export class Wallet { return resp; } + let historyEntry = { + type: "create-reserve", + timestamp: now, + detail: { + requestedAmount, + reservePub: reserveRecord.reserve_pub, + } + }; + 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) + .put("history", historyEntry) .finish() .then(() => { // Do this in the background @@ -574,10 +613,18 @@ export class Wallet { .then(doBadge.bind(this)); } - storeCoin(coin: Coin) { - Query(this.db) + storeCoin(coin: Coin): Promise<void> { + let historyEntry = { + type: "withdraw", + timestamp: (new Date).getTime(), + detail: { + coinPub: coin.coinPub, + } + }; + return Query(this.db) .delete("precoins", coin.coinPub) .add("coins", coin) + .add("history", historyEntry) .finish() .then(() => { this.updateBadge(); @@ -624,7 +671,10 @@ export class Wallet { } let d = workList.pop(); this.withdraw(d, reserve) - .then(() => next()); + .then(() => next()) + .catch((e) => { + console.log("Failed to withdraw coin", e.stack); + }); }; // Asynchronous recursion @@ -647,7 +697,18 @@ export class Wallet { if (!reserveInfo) { throw Error(); } + let oldAmount = reserve.current_amount; + let newAmount = reserveInfo.balance; reserve.current_amount = reserveInfo.balance; + let historyEntry = { + type: "reserve-update", + timestamp: (new Date).getTime(), + detail: { + reservePub, + oldAmount, + newAmount + } + }; return Query(this.db) .put("reserves", reserve) .finish() @@ -696,4 +757,14 @@ export class Wallet { .iter("coins") .reduce(collectBalances, {}); } + + getHistory() { + function collect(x, acc) { + acc.push(x); + return acc; + } + return Query(this.db) + .iter("history", {indexName: "timestamp"}) + .reduce(collect, []) + } } diff --git a/extension/lib/wallet/wxmessaging.js b/extension/lib/wallet/wxmessaging.js index d4df23a08..93ffd8bb7 100644 --- a/extension/lib/wallet/wxmessaging.js +++ b/extension/lib/wallet/wxmessaging.js @@ -20,7 +20,12 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) { function makeHandlers(wallet) { return (_a = {}, _a["balances"] = function (db, detail, sendResponse) { - wallet.getBalances().then(sendResponse); + wallet.getBalances() + .then(sendResponse) + .catch(function (e) { + console.log("exception during 'balances'"); + console.error(e.stack); + }); return true; }, _a["dump-db"] = function (db, detail, sendResponse) { @@ -54,6 +59,10 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) { resp.backlink = chrome.extension.getURL("pages/reserve-success.html"); } sendResponse(resp); + }) + .catch(function (e) { + console.error("exception during 'confirm-reserve'"); + console.error(e.stack); }); return true; }, @@ -63,6 +72,8 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) { sendResponse({ success: true }); }) .catch(function (e) { + console.error("exception during 'confirm-pay'"); + console.error(e.stack); sendResponse({ error: e.message }); }); return true; @@ -77,18 +88,33 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) { }); }) .catch(function (e) { + console.error("exception during 'execute-payment'"); + console.error(e.stack); sendResponse({ success: false, error: e.message }); }); // async sendResponse return true; }, + _a["get-history"] = function (db, detail, sendResponse) { + // TODO: limit history length + wallet.getHistory() + .then(function (h) { + sendResponse(h); + }) + .catch(function (e) { + console.error("exception during 'get-history'"); + console.error(e.stack); + }); + return true; + }, _a ); var _a; } function wxMain() { chrome.browserAction.setBadgeText({ text: "" }); - db_3.openTalerDb().then(function (db) { + db_3.openTalerDb() + .then(function (db) { var http = new http_1.BrowserHttpLib(); var badge = new ChromeBadge(); var wallet = new wallet_1.Wallet(db, http, badge); @@ -101,6 +127,10 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) { console.error("Request type " + JSON.stringify(req) + " unknown, req " + req.type); return false; }); + }) + .catch(function (e) { + console.error("could not open database:"); + console.error(e.stack); }); } exports_1("wxMain", wxMain); diff --git a/extension/lib/wallet/wxmessaging.ts b/extension/lib/wallet/wxmessaging.ts index 123746f4d..de9d5907c 100644 --- a/extension/lib/wallet/wxmessaging.ts +++ b/extension/lib/wallet/wxmessaging.ts @@ -36,7 +36,12 @@ import {Badge} from "./wallet"; function makeHandlers(wallet) { return { ["balances"]: function(db, detail, sendResponse) { - wallet.getBalances().then(sendResponse); + wallet.getBalances() + .then(sendResponse) + .catch((e) => { + console.log("exception during 'balances'"); + console.error(e.stack); + }); return true; }, ["dump-db"]: function(db, detail, sendResponse) { @@ -72,6 +77,10 @@ function makeHandlers(wallet) { "pages/reserve-success.html"); } sendResponse(resp); + }) + .catch((e) => { + console.error("exception during 'confirm-reserve'"); + console.error(e.stack); }); return true; }, @@ -81,6 +90,8 @@ function makeHandlers(wallet) { sendResponse({success: true}) }) .catch((e) => { + console.error("exception during 'confirm-pay'"); + console.error(e.stack); sendResponse({error: e.message}); }); return true; @@ -95,10 +106,24 @@ function makeHandlers(wallet) { }); }) .catch((e) => { + console.error("exception during 'execute-payment'"); + console.error(e.stack); sendResponse({success: false, error: e.message}); }); // async sendResponse return true; + }, + ["get-history"]: function(db, detail, sendResponse) { + // TODO: limit history length + wallet.getHistory() + .then((h) => { + sendResponse(h); + }) + .catch((e) => { + console.error("exception during 'get-history'"); + console.error(e.stack); + }); + return true; } }; } @@ -117,19 +142,24 @@ class ChromeBadge implements Badge { export function wxMain() { chrome.browserAction.setBadgeText({text: ""}); - openTalerDb().then((db) => { - let http = new BrowserHttpLib(); - let badge = new ChromeBadge(); - let wallet = new Wallet(db, http, badge); - let handlers = makeHandlers(wallet); - wallet.updateBadge(); - chrome.runtime.onMessage.addListener( - function(req, sender, onresponse) { - if (req.type in handlers) { - return handlers[req.type](db, req.detail, onresponse); - } - console.error(`Request type ${JSON.stringify(req)} unknown, req ${req.type}`); - return false; - }); - }); + openTalerDb() + .then((db) => { + let http = new BrowserHttpLib(); + let badge = new ChromeBadge(); + let wallet = new Wallet(db, http, badge); + let handlers = makeHandlers(wallet); + wallet.updateBadge(); + chrome.runtime.onMessage.addListener( + function(req, sender, onresponse) { + if (req.type in handlers) { + return handlers[req.type](db, req.detail, onresponse); + } + console.error(`Request type ${JSON.stringify(req)} unknown, req ${req.type}`); + return false; + }); + }) + .catch((e) => { + console.error("could not open database:"); + console.error(e.stack); + }); }
\ No newline at end of file |