From 29dfafaa1282d6b1e03bd7211ce598a09c22bae7 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 19 Feb 2016 04:23:00 +0100 Subject: error handling --- extension/lib/wallet/wallet.ts | 103 +++++++++-------- extension/lib/wallet/wxApi.ts | 40 +++++++ extension/lib/wallet/wxMessaging.ts | 223 ++++++++++++++++++++++++++++++++++++ extension/lib/wallet/wxmessaging.ts | 223 ------------------------------------ 4 files changed, 321 insertions(+), 268 deletions(-) create mode 100644 extension/lib/wallet/wxApi.ts create mode 100644 extension/lib/wallet/wxMessaging.ts delete mode 100644 extension/lib/wallet/wxmessaging.ts (limited to 'extension/lib') diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts index ed719211f..448714254 100644 --- a/extension/lib/wallet/wallet.ts +++ b/extension/lib/wallet/wallet.ts @@ -108,60 +108,55 @@ class MintInfo implements IMintInfo { * the first error. */ mergeKeys(newKeys: KeysJson, wallet: Wallet): Promise { - return Promise.resolve().then(() => { - if (!this.masterPublicKey) { - this.masterPublicKey = newKeys.master_public_key; - } + if (!this.masterPublicKey) { + this.masterPublicKey = newKeys.master_public_key; + } - if (this.masterPublicKey != newKeys.master_public_key) { - throw Error("public keys do not match"); - } + if (this.masterPublicKey != newKeys.master_public_key) { + throw Error("public keys do not match"); + } - for (let newDenom of newKeys.denoms) { - let found = false; - for (let oldDenom of this.denoms) { - if (oldDenom.denom_pub === newDenom.denom_pub) { - let a = Object.assign({}, oldDenom); - let b = Object.assign({}, newDenom); - // pub hash is only there for convenience in the wallet - delete a["pub_hash"]; - delete b["pub_hash"]; - if (!_.isEqual(a, b)) { - console.log("old/new:"); - console.dir(a); - console.dir(b); - throw Error("denomination modified"); - } - // TODO: check if info still matches - found = true; - break; + let ps = newKeys.denoms.map((newDenom) => { + let found = false; + for (let oldDenom of this.denoms) { + if (oldDenom.denom_pub === newDenom.denom_pub) { + let a = Object.assign({}, oldDenom); + let b = Object.assign({}, newDenom); + // pub hash is only there for convenience in the wallet + delete a["pub_hash"]; + delete b["pub_hash"]; + if (!deepEquals(a, b)) { + console.log("old/new:"); + console.dir(a); + console.dir(b); + throw Error("denomination modified"); } + found = true; + break; } + } - if (found) { - continue; - } - - console.log("validating denomination"); - - return wallet.isValidDenom(newDenom, this.masterPublicKey) - .then((valid) => { - if (!valid) { - throw Error("signature on denomination invalid"); - } + if (found) { + return Promise.resolve(); + } - let d: Denomination = Object.assign({}, newDenom); - d.pub_hash = native.RsaPublicKey.fromCrock(d.denom_pub) - .encode() - .hash() - .toCrock(); - this.denoms.push(d); + return wallet.isValidDenom(newDenom, this.masterPublicKey) + .then((valid) => { + if (!valid) { + throw Error("signature on denomination invalid"); + } - }); + let d: Denomination = Object.assign({}, newDenom); + d.pub_hash = native.RsaPublicKey.fromCrock(d.denom_pub) + .encode() + .hash() + .toCrock(); + this.denoms.push(d); - } - return; + }); }); + + return Promise.all(ps).then(() => void 0); } } @@ -308,6 +303,21 @@ export interface Badge { type PayCoinInfo = Array<{ updatedCoin: Coin, sig: CoinPaySig }>; +function deepEquals(x, y) { + if (x === y) { + return true; + } + + if (Array.isArray(x) && x.length !== y.length) { + return false; + } + + var p = Object.keys(x); + return Object.keys(y).every((i) => p.indexOf(i) !== -1) && + p.every((i) => deepEquals(x[i], y[i])); +} + + function getTalerStampSec(stamp: string) { const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/); if (!m) { @@ -603,6 +613,9 @@ export class Wallet { payReq: payReq, }; + console.log("pay request"); + console.dir(payReq); + let historyEntry = { type: "pay", timestamp: (new Date).getTime(), diff --git a/extension/lib/wallet/wxApi.ts b/extension/lib/wallet/wxApi.ts new file mode 100644 index 000000000..9871b6e7f --- /dev/null +++ b/extension/lib/wallet/wxApi.ts @@ -0,0 +1,40 @@ +/* + 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 + */ + +import {AmountJson} from "./types"; +import {ReserveCreationInfo} from "./types"; + +/** + * Interface to the wallet through WebExtension messaging. + */ + + +export function getReserveCreationInfo(baseUrl: string, + amount: AmountJson): Promise { + let m = {type: "reserve-creation-info", detail: {baseUrl, amount}}; + return new Promise((resolve, reject) => { + chrome.runtime.sendMessage(m, (resp) => { + if (resp.error) { + console.error("error response", resp); + let e = Error("call to reserve-creation-info failed"); + (e as any).errorResponse = resp; + reject(e); + return; + } + resolve(resp); + }); + }); +} \ No newline at end of file diff --git a/extension/lib/wallet/wxMessaging.ts b/extension/lib/wallet/wxMessaging.ts new file mode 100644 index 000000000..df79648ab --- /dev/null +++ b/extension/lib/wallet/wxMessaging.ts @@ -0,0 +1,223 @@ +/* + 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 + */ + + +import {Wallet, Offer, Badge, ConfirmReserveRequest, CreateReserveRequest} from "./wallet"; +import {deleteDb, exportDb, openTalerDb} from "./db"; +import {BrowserHttpLib} from "./http"; +import {Checkable} from "./checkable"; +import {AmountJson} from "./types"; +import Port = chrome.runtime.Port; +import {Notifier} from "./types"; + +"use strict"; + +/** + * Messaging for the WebExtensions wallet. Should contain + * parts that are specific for WebExtensions, but as little business + * logic as possible. + * + * @author Florian Dold + */ + + +type Handler = (detail: any) => Promise; + +function makeHandlers(db: IDBDatabase, + wallet: Wallet): {[msg: string]: Handler} { + return { + ["balances"]: function(detail) { + return wallet.getBalances(); + }, + ["dump-db"]: function(detail) { + return exportDb(db); + }, + ["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(); + } + deleteDb(); + + chrome.browserAction.setBadgeText({text: ""}); + console.log("reset done"); + // Response is synchronous + return Promise.resolve({}); + }, + ["create-reserve"]: function(detail) { + const d = { + mint: detail.mint, + amount: detail.amount, + }; + const req = CreateReserveRequest.checked(d); + return wallet.createReserve(req); + }, + ["confirm-reserve"]: function(detail) { + // TODO: make it a checkable + const d = { + reservePub: detail.reservePub + }; + const req = ConfirmReserveRequest.checked(d); + return wallet.confirmReserve(req); + }, + ["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, + detail: detail + }); + } else { + throw e; + } + } + + return wallet.confirmPay(offer); + }, + ["execute-payment"]: function(detail) { + return wallet.executePayment(detail.H_contract); + }, + ["mint-info"]: function(detail) { + if (!detail.baseUrl) { + return Promise.resolve({error: "bad url"}); + } + return wallet.updateMintFromUrl(detail.baseUrl); + }, + ["reserve-creation-info"]: function(detail) { + if (!detail.baseUrl || typeof detail.baseUrl !== "string") { + return Promise.resolve({error: "bad url"}); + } + let amount = AmountJson.checked(detail.amount); + return wallet.getReserveCreationInfo(detail.baseUrl, amount); + }, + ["get-history"]: function(detail) { + // TODO: limit history length + return wallet.getHistory(); + }, + }; +} + + +class ChromeBadge implements Badge { + setText(s: string) { + chrome.browserAction.setBadgeText({text: s}); + } + + setColor(c: string) { + chrome.browserAction.setBadgeBackgroundColor({color: c}); + } +} + + +function dispatch(handlers, req, sendResponse) { + if (req.type in handlers) { + Promise + .resolve() + .then(() => { + const p = handlers[req.type](req.detail); + + return p.then((r) => { + sendResponse(r); + }) + }) + .catch((e) => { + console.log("exception during wallet handler"); + console.error(e); + 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; + } +} + +class ChromeNotifier implements Notifier { + ports: Port[] = []; + + constructor() { + chrome.runtime.onConnect.addListener((port) => { + console.log("got connect!"); + this.ports.push(port); + port.onDisconnect.addListener(() => { + let i = this.ports.indexOf(port); + if (i >= 0) { + this.ports.splice(i, 1); + } else { + console.error("port already removed"); + } + }); + }); + } + + notify() { + console.log("notifying all ports"); + for (let p of this.ports) { + p.postMessage({notify: true}); + } + } +} + + +export function wxMain() { + chrome.browserAction.setBadgeText({text: ""}); + + 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 notifier = new ChromeNotifier(); + let wallet = new Wallet(db, http, badge, notifier); + let handlers = makeHandlers(db, wallet); + chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { + try { + return dispatch(handlers, req, sendResponse) + } catch (e) { + console.log("exception during wallet handler (dispatch)"); + console.error(e); + sendResponse({ + error: "exception", + hint: e.message, + stack: e.stack.toString() + }); + return false; + } + }); + }) + .catch((e) => { + console.error("could not initialize wallet messaging"); + console.error(e); + }); +} \ No newline at end of file diff --git a/extension/lib/wallet/wxmessaging.ts b/extension/lib/wallet/wxmessaging.ts deleted file mode 100644 index fc99a2054..000000000 --- a/extension/lib/wallet/wxmessaging.ts +++ /dev/null @@ -1,223 +0,0 @@ -/* - 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 - */ - - -import {Wallet, Offer, Badge, ConfirmReserveRequest, CreateReserveRequest} from "./wallet"; -import {deleteDb, exportDb, openTalerDb} from "./db"; -import {BrowserHttpLib} from "./http"; -import {Checkable} from "./checkable"; -import {AmountJson} from "./types"; -import Port = chrome.runtime.Port; -import {Notifier} from "./types"; - -"use strict"; - -/** - * Messaging for the WebExtensions wallet. Should contain - * parts that are specific for WebExtensions, but as little business - * logic as possible. - * - * @author Florian Dold - */ - - -type Handler = (detail: any) => Promise; - -function makeHandlers(db: IDBDatabase, - wallet: Wallet): {[msg: string]: Handler} { - return { - ["balances"]: function(detail) { - return wallet.getBalances(); - }, - ["dump-db"]: function(detail) { - return exportDb(db); - }, - ["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(); - } - deleteDb(); - - chrome.browserAction.setBadgeText({text: ""}); - console.log("reset done"); - // Response is synchronous - return Promise.resolve({}); - }, - ["create-reserve"]: function(detail) { - const d = { - mint: detail.mint, - amount: detail.amount, - }; - const req = CreateReserveRequest.checked(d); - return wallet.createReserve(req); - }, - ["confirm-reserve"]: function(detail) { - // TODO: make it a checkable - const d = { - reservePub: detail.reservePub - }; - const req = ConfirmReserveRequest.checked(d); - return wallet.confirmReserve(req); - }, - ["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, - detail: detail - }); - } else { - throw e; - } - } - - return wallet.confirmPay(offer); - }, - ["execute-payment"]: function(detail) { - return wallet.executePayment(detail.H_contract); - }, - ["mint-info"]: function(detail) { - if (!detail.baseUrl) { - return Promise.resolve({error: "bad url"}); - } - return wallet.updateMintFromUrl(detail.baseUrl); - }, - ["reserve-creation-info"]: function(detail) { - if (!detail.baseUrl || typeof detail.baseUrl !== "string") { - return Promise.resolve({error: "bad url"}); - } - let amount = AmountJson.checked(detail.amount); - return wallet.getReserveCreationInfo(detail.baseUrl, amount); - }, - ["get-history"]: function(detail) { - // TODO: limit history length - return wallet.getHistory(); - }, - }; -} - - -class ChromeBadge implements Badge { - setText(s: string) { - chrome.browserAction.setBadgeText({text: s}); - } - - setColor(c: string) { - chrome.browserAction.setBadgeBackgroundColor({color: c}); - } -} - - -function dispatch(handlers, req, sendResponse) { - if (req.type in handlers) { - Promise - .resolve() - .then(() => { - const p = handlers[req.type](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; - } -} - -class ChromeNotifier implements Notifier { - ports: Port[] = []; - - constructor() { - chrome.runtime.onConnect.addListener((port) => { - console.log("got connect!"); - this.ports.push(port); - port.onDisconnect.addListener(() => { - let i = this.ports.indexOf(port); - if (i >= 0) { - this.ports.splice(i, 1); - } else { - console.error("port already removed"); - } - }); - }); - } - - notify() { - console.log("notifying all ports"); - for (let p of this.ports) { - p.postMessage({notify: true}); - } - } -} - - -export function wxMain() { - chrome.browserAction.setBadgeText({text: ""}); - - 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 notifier = new ChromeNotifier(); - let wallet = new Wallet(db, http, badge, notifier); - let handlers = makeHandlers(db, wallet); - chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { - try { - return dispatch(handlers, req, sendResponse) - } catch (e) { - console.log("exception during wallet handler (dispatch)"); - console.error(e.stack); - sendResponse({ - error: "exception", - hint: e.message, - stack: e.stack.toString() - }); - return false; - } - }); - }) - .catch((e) => { - console.error("could not initialize wallet messaging"); - console.error(e); - }); -} \ No newline at end of file -- cgit v1.2.3