From 874d083ec371441d2f2b31281652fd8f82cc5489 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 17 Feb 2016 15:56:48 +0100 Subject: [PATCH] simplify wallet message dispatching and error handling --- extension/lib/wallet/checkable.ts | 40 +++-- extension/lib/wallet/wxmessaging.js | 171 +++++++++----------- extension/lib/wallet/wxmessaging.ts | 179 ++++++++++----------- extension/pages/confirm-create-reserve.js | 3 + extension/pages/confirm-create-reserve.tsx | 9 ++ 5 files changed, 203 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 = (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 */ -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; + +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 diff --git a/extension/pages/confirm-create-reserve.js b/extension/pages/confirm-create-reserve.js index 0ca5fc236..4cb8c03fb 100644 --- a/extension/pages/confirm-create-reserve.js +++ b/extension/pages/confirm-create-reserve.js @@ -48,6 +48,9 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril"], fun return mithril_1.default("div", controls); var _a; } + function probeMint(mintBaseUrl) { + throw Error("not implemented"); + } function getSuggestedMint(currency) { // TODO: make this request go to the wallet backend // Right now, this is a stub. diff --git a/extension/pages/confirm-create-reserve.tsx b/extension/pages/confirm-create-reserve.tsx index b3086c63e..4be934d37 100644 --- a/extension/pages/confirm-create-reserve.tsx +++ b/extension/pages/confirm-create-reserve.tsx @@ -200,6 +200,15 @@ function view(ctrl: Controller) { } +interface MintProbeResult { + keyInfo?: any; +} + +function probeMint(mintBaseUrl: string): Promise { + throw Error("not implemented"); +} + + function getSuggestedMint(currency: string): Promise { // TODO: make this request go to the wallet backend // Right now, this is a stub.