diff options
| author | Florian Dold <florian.dold@gmail.com> | 2016-02-17 15:56:48 +0100 | 
|---|---|---|
| committer | Florian Dold <florian.dold@gmail.com> | 2016-02-17 15:56:48 +0100 | 
| commit | 874d083ec371441d2f2b31281652fd8f82cc5489 (patch) | |
| tree | 27747e9a675247a75d3f29c89516be717163d463 /extension/lib/wallet | |
| parent | 0c760bc2a1459cc7ec7fd22ae39ca0a8201df9b5 (diff) | |
simplify wallet message dispatching and error handling
Diffstat (limited to 'extension/lib/wallet')
| -rw-r--r-- | extension/lib/wallet/checkable.ts | 40 | ||||
| -rw-r--r-- | extension/lib/wallet/wxmessaging.js | 171 | ||||
| -rw-r--r-- | extension/lib/wallet/wxmessaging.ts | 179 | 
3 files changed, 191 insertions, 199 deletions
| diff --git a/extension/lib/wallet/checkable.ts b/extension/lib/wallet/checkable.ts index 8f89c8669..9a31e4e17 100644 --- a/extension/lib/wallet/checkable.ts +++ b/extension/lib/wallet/checkable.ts @@ -25,12 +25,20 @@   */  export namespace Checkable { +  export function SchemaError(message) { +    this.name = 'SchemaError'; +    this.message = message; +    this.stack = (<any>new Error()).stack; +  } + +  SchemaError.prototype = new Error; +    let chkSym = Symbol("checkable");    function checkNumber(target, prop, path): any {      if ((typeof target) !== "number") { -      throw Error(`expected number for ${path}`); +      throw new SchemaError(`expected number for ${path}`);      }      return target;    } @@ -38,7 +46,7 @@ export namespace Checkable {    function checkString(target, prop, path): any {      if (typeof target !== "string") { -      throw Error(`expected string for ${path}, got ${typeof target} instead`); +      throw new SchemaError(`expected string for ${path}, got ${typeof target} instead`);      }      return target;    } @@ -46,7 +54,7 @@ export namespace Checkable {    function checkAnyObject(target, prop, path): any {      if (typeof target !== "object") { -      throw Error(`expected (any) object for ${path}, got ${typeof target} instead`); +      throw new SchemaError(`expected (any) object for ${path}, got ${typeof target} instead`);      }      return target;    } @@ -59,7 +67,7 @@ export namespace Checkable {    function checkList(target, prop, path): any {      if (!Array.isArray(target)) { -      throw Error(`array expected for ${path}, got ${typeof target} instead`); +      throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`);      }      for (let i = 0; i < target.length; i++) {        let v = target[i]; @@ -76,17 +84,20 @@ export namespace Checkable {      }      let v = target;      if (!v || typeof v !== "object") { -      throw Error(`expected object for ${path}, got ${typeof v} instead`); +      throw new SchemaError(`expected object for ${path}, got ${typeof v} instead`);      }      let props = type.prototype[chkSym].props;      let remainingPropNames = new Set(Object.getOwnPropertyNames(v));      let obj = new type();      for (let prop of props) {        if (!remainingPropNames.has(prop.propertyKey)) { -        throw Error("Property missing: " + prop.propertyKey); +        if (prop.optional) { +          continue; +        } +        throw new SchemaError("Property missing: " + prop.propertyKey);        }        if (!remainingPropNames.delete(prop.propertyKey)) { -        throw Error("assertion failed"); +        throw new SchemaError("assertion failed");        }        let propVal = v[prop.propertyKey];        obj[prop.propertyKey] = prop.checker(propVal, @@ -95,8 +106,8 @@ export namespace Checkable {      }      if (remainingPropNames.size != 0) { -      throw Error("superfluous properties " + JSON.stringify(Array.from( -                    remainingPropNames.values()))); +      throw new SchemaError("superfluous properties " + JSON.stringify(Array.from( +          remainingPropNames.values())));      }      return obj;    } @@ -162,14 +173,21 @@ export namespace Checkable {    export function AnyObject(target: Object,                              propertyKey: string | symbol): void {      let chk = mkChk(target); -    chk.props.push({propertyKey: propertyKey, checker: checkAnyObject}); +    chk.props.push({ +                     propertyKey: propertyKey, +                     checker: checkAnyObject +                   });    }    export function Any(target: Object,                        propertyKey: string | symbol): void {      let chk = mkChk(target); -    chk.props.push({propertyKey: propertyKey, checker: checkAny}); +    chk.props.push({ +                     propertyKey: propertyKey, +                     checker: checkAny, +                     optional: true +                   });    } diff --git a/extension/lib/wallet/wxmessaging.js b/extension/lib/wallet/wxmessaging.js index c70bfb5a0..6310d6cd4 100644 --- a/extension/lib/wallet/wxmessaging.js +++ b/extension/lib/wallet/wxmessaging.js @@ -13,34 +13,20 @@   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/>   */ -System.register(["./wallet", "./db", "./http"], function(exports_1, context_1) { +System.register(["./wallet", "./db", "./http", "./checkable"], function(exports_1, context_1) {      "use strict";      var __moduleName = context_1 && context_1.id; -    var wallet_1, db_1, http_1; +    var wallet_1, db_1, http_1, checkable_1;      var ChromeBadge; -    /** -     * Messaging for the WebExtensions wallet.  Should contain -     * parts that are specific for WebExtensions, but as little business -     * logic as possible. -     * -     * @author Florian Dold -     */ -    function makeHandlers(wallet) { +    function makeHandlers(db, wallet) {          return (_a = {}, -            _a["balances"] = function (db, detail, sendResponse) { -                wallet.getBalances() -                    .then(sendResponse) -                    .catch(function (e) { -                    console.log("exception during 'balances'"); -                    console.error(e.stack); -                }); -                return true; +            _a["balances"] = function (detail) { +                return wallet.getBalances();              }, -            _a["dump-db"] = function (db, detail, sendResponse) { -                db_1.exportDb(db).then(sendResponse); -                return true; +            _a["dump-db"] = function (detail) { +                return db_1.exportDb(db);              }, -            _a["reset"] = function (db, detail, sendResponse) { +            _a["reset"] = function (detail) {                  var tx = db.transaction(db.objectStoreNames, 'readwrite');                  for (var i = 0; i < db.objectStoreNames.length; i++) {                      tx.objectStore(db.objectStoreNames[i]).clear(); @@ -49,108 +35,102 @@ System.register(["./wallet", "./db", "./http"], function(exports_1, context_1) {                  chrome.browserAction.setBadgeText({ text: "" });                  console.log("reset done");                  // Response is synchronous -                return false; +                return Promise.resolve({});              }, -            _a["create-reserve"] = function (db, detail, sendResponse) { +            _a["create-reserve"] = function (detail) {                  var d = {                      mint: detail.mint,                      amount: detail.amount,                  };                  var req = wallet_1.CreateReserveRequest.checked(d); -                wallet.createReserve(req) -                    .then(function (resp) { -                    sendResponse(resp); -                }) -                    .catch(function (e) { -                    sendResponse({ error: "exception" }); -                    console.error("exception during 'create-reserve'"); -                    console.error(e.stack); -                }); -                return true; +                return wallet.createReserve(req);              }, -            _a["confirm-reserve"] = function (db, detail, sendResponse) { +            _a["confirm-reserve"] = function (detail) {                  // TODO: make it a checkable                  var d = {                      reservePub: detail.reservePub                  };                  var req = wallet_1.ConfirmReserveRequest.checked(d); -                wallet.confirmReserve(req) -                    .then(function (resp) { -                    sendResponse(resp); -                }) -                    .catch(function (e) { -                    sendResponse({ error: "exception" }); -                    console.error("exception during 'confirm-reserve'"); -                    console.error(e.stack); -                }); -                return true; +                return wallet.confirmReserve(req);              }, -            _a["confirm-pay"] = function (db, detail, sendResponse) { -                console.log("in confirm-pay handler"); -                var offer = wallet_1.Offer.checked(detail.offer); -                wallet.confirmPay(offer) -                    .then(function (r) { -                    sendResponse(r); -                }) -                    .catch(function (e) { -                    console.error("exception during 'confirm-pay'"); -                    console.error(e.stack); -                    sendResponse({ error: e.message }); -                }); -                return true; +            _a["confirm-pay"] = function (detail) { +                var offer; +                try { +                    offer = wallet_1.Offer.checked(detail.offer); +                } +                catch (e) { +                    if (e instanceof checkable_1.Checkable.SchemaError) { +                        console.error("schema error:", e.message); +                        return Promise.resolve({ error: "invalid contract", hint: e.message }); +                    } +                    else { +                        throw e; +                    } +                } +                return wallet.confirmPay(offer);              }, -            _a["execute-payment"] = function (db, detail, sendResponse) { -                wallet.executePayment(detail.H_contract) -                    .then(function (r) { -                    sendResponse(r); -                }) -                    .catch(function (e) { -                    console.error("exception during 'execute-payment'"); -                    console.error(e.stack); -                    sendResponse({ error: e.message }); -                }); -                // async sendResponse -                return true; +            _a["execute-payment"] = function (detail) { +                return wallet.executePayment(detail.H_contract);              }, -            _a["get-history"] = function (db, detail, sendResponse) { +            _a["get-history"] = function (detail) {                  // 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["error-fatal"] = function (db, detail, sendResponse) { -                console.log("fatal error from page", detail.url); +                return wallet.getHistory();              },              _a          );          var _a;      } +    function dispatch(handlers, db, req, sendResponse) { +        if (req.type in handlers) { +            Promise +                .resolve() +                .then(function () { +                var p = handlers[req.type](db, req.detail); +                return p.then(function (r) { +                    sendResponse(r); +                }); +            }) +                .catch(function (e) { +                console.log("exception during wallet handler'"); +                console.error(e.stack); +                sendResponse({ +                    error: "exception", +                    hint: e.message, +                    stack: e.stack.toString() +                }); +            }); +            // The sendResponse call is async +            return true; +        } +        else { +            console.error("Request type " + JSON.stringify(req) + " unknown, req " + req.type); +            sendResponse({ error: "request unknown" }); +            // The sendResponse call is sync +            return false; +        } +    }      function wxMain() {          chrome.browserAction.setBadgeText({ text: "" }); -        db_1.openTalerDb() +        Promise.resolve() +            .then(function () { +            return db_1.openTalerDb(); +        }) +            .catch(function (e) { +            console.error("could not open database"); +            console.error(e); +        })              .then(function (db) {              var http = new http_1.BrowserHttpLib();              var badge = new ChromeBadge();              var wallet = new wallet_1.Wallet(db, http, badge); -            var handlers = makeHandlers(wallet); -            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); -                onresponse({ error: "request unknown" }); -                return false; +            var handlers = makeHandlers(db, wallet); +            chrome.runtime.onMessage.addListener(function (req, sender, sendResponse) { +                return dispatch(handlers, db, req, sendResponse);              });          })              .catch(function (e) { -            console.error("could not open database:"); -            console.error(e.stack); +            console.error("could not initialize wallet messaging"); +            console.error(e);          });      }      exports_1("wxMain", wxMain); @@ -164,6 +144,9 @@ System.register(["./wallet", "./db", "./http"], function(exports_1, context_1) {              },              function (http_1_1) {                  http_1 = http_1_1; +            }, +            function (checkable_1_1) { +                checkable_1 = checkable_1_1;              }],          execute: function() {              "use strict"; diff --git a/extension/lib/wallet/wxmessaging.ts b/extension/lib/wallet/wxmessaging.ts index ac2594500..34d9d469e 100644 --- a/extension/lib/wallet/wxmessaging.ts +++ b/extension/lib/wallet/wxmessaging.ts @@ -18,6 +18,7 @@  import {Wallet, Offer, Badge, ConfirmReserveRequest, CreateReserveRequest} from "./wallet";  import {deleteDb, exportDb, openTalerDb} from "./db";  import {BrowserHttpLib} from "./http"; +import {Checkable} from "./checkable";  "use strict"; @@ -30,22 +31,18 @@ import {BrowserHttpLib} from "./http";   */ -function makeHandlers(wallet: Wallet) { +type Handler = (detail: any) => Promise<any>; + +function makeHandlers(db: IDBDatabase, +                      wallet: Wallet): {[msg: string]: Handler} {    return { -    ["balances"]: function(db, detail, sendResponse) { -      wallet.getBalances() -            .then(sendResponse) -            .catch((e) => { -              console.log("exception during 'balances'"); -              console.error(e.stack); -            }); -      return true; +    ["balances"]: function(detail) { +      return wallet.getBalances();      }, -    ["dump-db"]: function(db, detail, sendResponse) { -      exportDb(db).then(sendResponse); -      return true; +    ["dump-db"]: function(detail) { +      return exportDb(db);      }, -    ["reset"]: function(db, detail, sendResponse) { +    ["reset"]: function(detail) {        let tx = db.transaction(db.objectStoreNames, 'readwrite');        for (let i = 0; i < db.objectStoreNames.length; i++) {          tx.objectStore(db.objectStoreNames[i]).clear(); @@ -55,84 +52,46 @@ function makeHandlers(wallet: Wallet) {        chrome.browserAction.setBadgeText({text: ""});        console.log("reset done");        // Response is synchronous -      return false; +      return Promise.resolve({});      }, -    ["create-reserve"]: function(db, detail, sendResponse) { +    ["create-reserve"]: function(detail) {        const d = {          mint: detail.mint,          amount: detail.amount,        };        const req = CreateReserveRequest.checked(d); -      wallet.createReserve(req) -            .then((resp) => { -              sendResponse(resp); -            }) -            .catch((e) => { -              sendResponse({error: "exception"}); -              console.error("exception during 'create-reserve'"); -              console.error(e.stack); -            }); -      return true; +      return wallet.createReserve(req);      }, -    ["confirm-reserve"]: function(db, detail, sendResponse) { +    ["confirm-reserve"]: function(detail) {        // TODO: make it a checkable        const d = {          reservePub: detail.reservePub        };        const req = ConfirmReserveRequest.checked(d); -      wallet.confirmReserve(req) -            .then((resp) => { -              sendResponse(resp); -            }) -            .catch((e) => { -              sendResponse({error: "exception"}); -              console.error("exception during 'confirm-reserve'"); -              console.error(e.stack); -            }); -      return true; +      return wallet.confirmReserve(req);      }, -    ["confirm-pay"]: function(db, detail, sendResponse) { -      console.log("in confirm-pay handler"); -      const offer = Offer.checked(detail.offer); -      wallet.confirmPay(offer) -            .then((r) => { -              sendResponse(r) -            }) -            .catch((e) => { -              console.error("exception during 'confirm-pay'"); -              console.error(e.stack); -              sendResponse({error: e.message}); -            }); -      return true; +    ["confirm-pay"]: function(detail) { +      let offer; +      try { +        offer = Offer.checked(detail.offer); +      } catch (e) { +        if (e instanceof Checkable.SchemaError) { +          console.error("schema error:", e.message); +          return Promise.resolve({error: "invalid contract", hint: e.message}); +        } else { +          throw e; +        } +      } + +      return wallet.confirmPay(offer);      }, -    ["execute-payment"]: function(db, detail, sendResponse) { -      wallet.executePayment(detail.H_contract) -            .then((r) => { -              sendResponse(r); -            }) -            .catch((e) => { -              console.error("exception during 'execute-payment'"); -              console.error(e.stack); -              sendResponse({error: e.message}); -            }); -      // async sendResponse -      return true; +    ["execute-payment"]: function(detail) { +      return wallet.executePayment(detail.H_contract);      }, -    ["get-history"]: function(db, detail, sendResponse) { +    ["get-history"]: function(detail) {        // TODO: limit history length -      wallet.getHistory() -            .then((h) => { -              sendResponse(h); -            }) -            .catch((e) => { -              console.error("exception during 'get-history'"); -              console.error(e.stack); -            }); -      return true; +      return wallet.getHistory();      }, -    ["error-fatal"]: function(db, detail, sendResponse) { -      console.log("fatal error from page", detail.url); -    }    };  } @@ -148,27 +107,59 @@ class ChromeBadge implements Badge {  } +function dispatch(handlers, db, req, sendResponse) { +  if (req.type in handlers) { +    Promise +      .resolve() +      .then(() => { +        const p = handlers[req.type](db, req.detail); + +        return p.then((r) => { +          sendResponse(r); +        }) +      }) +      .catch((e) => { +        console.log("exception during wallet handler'"); +        console.error(e.stack); +        sendResponse({ +                       error: "exception", +                       hint: e.message, +                       stack: e.stack.toString() +                     }); +      }); +    // The sendResponse call is async +    return true; +  } else { +    console.error(`Request type ${JSON.stringify(req)} unknown, req ${req.type}`); +    sendResponse({error: "request unknown"}); +    // The sendResponse call is sync +    return false; +  } +} + +  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); -      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}`); -          onresponse({error: "request unknown"}); -          return false; -        }); -    }) -    .catch((e) => { -      console.error("could not open database:"); -      console.error(e.stack); -    }); +  Promise.resolve() +         .then(() => { +           return openTalerDb(); +         }) +         .catch((e) => { +           console.error("could not open database"); +           console.error(e); +         }) +         .then((db) => { +           let http = new BrowserHttpLib(); +           let badge = new ChromeBadge(); +           let wallet = new Wallet(db, http, badge); +           let handlers = makeHandlers(db, wallet); +           chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { +             return dispatch(handlers, db, req, sendResponse) +           }); +         }) +         .catch((e) => { +           console.error("could not initialize wallet messaging"); +           console.error(e); +         });  }
\ No newline at end of file | 
