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 | |
| parent | c48f2d39f0f62ea8f862ba08234b8a5376a11a2e (diff) | |
more complete DB abstractions
| -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 | ||||
| -rw-r--r-- | extension/manifest.json | 1 | ||||
| -rw-r--r-- | extension/tsconfig.json | 1 | 
10 files changed, 489 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 diff --git a/extension/manifest.json b/extension/manifest.json index 0c30edfd7..478c2a9b0 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -47,6 +47,7 @@        "background/emscriptif.js",        "background/db.js",        "background/query.js", +      "background/messaging.js",        "background/http.js",        "background/wallet.js"      ] diff --git a/extension/tsconfig.json b/extension/tsconfig.json index 565924b43..2b88688d9 100644 --- a/extension/tsconfig.json +++ b/extension/tsconfig.json @@ -9,6 +9,7 @@        "background/db.ts",        "background/query.ts",        "background/http.ts", +      "background/messaging.ts",        "lib/util.ts",        "lib/polyfill-react.ts",        "content_scripts/notify.ts", | 
