diff options
author | Florian Dold <florian.dold@gmail.com> | 2016-02-29 18:03:02 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2016-02-29 18:03:02 +0100 |
commit | c962e9402123900c53967c14cf809ea10576cdb8 (patch) | |
tree | e7df9cfdd6fceae30fb99c8ec6be5e07c8b153a8 /extension/lib/wallet | |
parent | 30ee3320c788129b258ed8b42f4fc63d28431e2f (diff) | |
download | wallet-core-c962e9402123900c53967c14cf809ea10576cdb8.tar.xz |
restructure
Diffstat (limited to 'extension/lib/wallet')
-rw-r--r-- | extension/lib/wallet/checkable.ts | 241 | ||||
-rw-r--r-- | extension/lib/wallet/cryptoApi.ts | 93 | ||||
-rw-r--r-- | extension/lib/wallet/cryptoLib.ts | 222 | ||||
-rw-r--r-- | extension/lib/wallet/cryptoWorker.ts | 65 | ||||
-rw-r--r-- | extension/lib/wallet/db.ts | 109 | ||||
-rw-r--r-- | extension/lib/wallet/emscriptif.ts | 1006 | ||||
-rw-r--r-- | extension/lib/wallet/helpers.ts | 65 | ||||
-rw-r--r-- | extension/lib/wallet/http.ts | 84 | ||||
-rw-r--r-- | extension/lib/wallet/query.ts | 360 | ||||
-rw-r--r-- | extension/lib/wallet/types.ts | 269 | ||||
-rw-r--r-- | extension/lib/wallet/wallet.ts | 957 | ||||
-rw-r--r-- | extension/lib/wallet/wxApi.ts | 40 | ||||
-rw-r--r-- | extension/lib/wallet/wxMessaging.ts | 230 |
13 files changed, 0 insertions, 3741 deletions
diff --git a/extension/lib/wallet/checkable.ts b/extension/lib/wallet/checkable.ts deleted file mode 100644 index 27ea9bf74..000000000 --- a/extension/lib/wallet/checkable.ts +++ /dev/null @@ -1,241 +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 <http://www.gnu.org/licenses/> - */ - - -"use strict"; - -/** - * Decorators for type-checking JSON into - * an object. - * @module Checkable - * @author Florian Dold - */ - -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 new SchemaError(`expected number for ${path}`); - } - return target; - } - - - function checkString(target, prop, path): any { - if (typeof target !== "string") { - throw new SchemaError(`expected string for ${path}, got ${typeof target} instead`); - } - return target; - } - - - function checkAnyObject(target, prop, path): any { - if (typeof target !== "object") { - throw new SchemaError(`expected (any) object for ${path}, got ${typeof target} instead`); - } - return target; - } - - - function checkAny(target, prop, path): any { - return target; - } - - - function checkList(target, prop, path): any { - if (!Array.isArray(target)) { - throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`); - } - for (let i = 0; i < target.length; i++) { - let v = target[i]; - prop.elementChecker(v, prop.elementProp, path.concat([i])); - } - return target; - } - - - function checkOptional(target, prop, path): any { - console.assert(prop.propertyKey); - prop.elementChecker(target, - prop.elementProp, - path.concat([prop.propertyKey])); - return target; - } - - - function checkValue(target, prop, path): any { - let type = prop.type; - if (!type) { - throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`); - } - let v = target; - if (!v || typeof v !== "object") { - throw new SchemaError( - `expected object for ${path.join(".")}, 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)) { - if (prop.optional) { - continue; - } - throw new SchemaError("Property missing: " + prop.propertyKey); - } - if (!remainingPropNames.delete(prop.propertyKey)) { - throw new SchemaError("assertion failed"); - } - let propVal = v[prop.propertyKey]; - obj[prop.propertyKey] = prop.checker(propVal, - prop, - path.concat([prop.propertyKey])); - } - - if (remainingPropNames.size != 0) { - throw new SchemaError("superfluous properties " + JSON.stringify(Array.from( - remainingPropNames.values()))); - } - return obj; - } - - - export function Class(target) { - target.checked = (v) => { - return checkValue(v, { - propertyKey: "(root)", - type: target, - checker: checkValue - }, ["(root)"]); - }; - return target; - } - - - export function Value(type) { - if (!type) { - throw Error("Type does not exist yet (wrong order of definitions?)"); - } - function deco(target: Object, propertyKey: string | symbol): void { - let chk = mkChk(target); - chk.props.push({ - propertyKey: propertyKey, - checker: checkValue, - type: type - }); - } - - return deco; - } - - - export function List(type) { - let stub = {}; - type(stub, "(list-element)"); - let elementProp = mkChk(stub).props[0]; - let elementChecker = elementProp.checker; - if (!elementChecker) { - throw Error("assertion failed"); - } - function deco(target: Object, propertyKey: string | symbol): void { - let chk = mkChk(target); - chk.props.push({ - elementChecker, - elementProp, - propertyKey: propertyKey, - checker: checkList, - }); - } - - return deco; - } - - - export function Optional(type) { - let stub = {}; - type(stub, "(optional-element)"); - let elementProp = mkChk(stub).props[0]; - let elementChecker = elementProp.checker; - if (!elementChecker) { - throw Error("assertion failed"); - } - function deco(target: Object, propertyKey: string | symbol): void { - let chk = mkChk(target); - chk.props.push({ - elementChecker, - elementProp, - propertyKey: propertyKey, - checker: checkOptional, - optional: true, - }); - } - - return deco; - } - - - export function Number(target: Object, propertyKey: string | symbol): void { - let chk = mkChk(target); - chk.props.push({propertyKey: propertyKey, checker: checkNumber}); - } - - - export function AnyObject(target: Object, - propertyKey: string | symbol): void { - let chk = mkChk(target); - 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, - optional: true - }); - } - - - export function String(target: Object, propertyKey: string | symbol): void { - let chk = mkChk(target); - chk.props.push({propertyKey: propertyKey, checker: checkString}); - } - - - function mkChk(target) { - let chk = target[chkSym]; - if (!chk) { - chk = {props: []}; - target[chkSym] = chk; - } - return chk; - } -}
\ No newline at end of file diff --git a/extension/lib/wallet/cryptoApi.ts b/extension/lib/wallet/cryptoApi.ts deleted file mode 100644 index 300b928db..000000000 --- a/extension/lib/wallet/cryptoApi.ts +++ /dev/null @@ -1,93 +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 <http://www.gnu.org/licenses/> - */ - - -import {PreCoin} from "./types"; -import {Reserve} from "./types"; -import {Denomination} from "./types"; -import {Offer} from "./wallet"; -import {CoinWithDenom} from "./wallet"; -import {PayCoinInfo} from "./types"; -export class CryptoApi { - private nextRpcId: number = 1; - private rpcRegistry = {}; - private cryptoWorker: Worker; - - - constructor() { - this.cryptoWorker = new Worker("/lib/wallet/cryptoWorker.js"); - - this.cryptoWorker.onmessage = (msg: MessageEvent) => { - let id = msg.data.id; - if (typeof id !== "number") { - console.error("rpc id must be number"); - return; - } - if (!this.rpcRegistry[id]) { - console.error(`RPC with id ${id} has no registry entry`); - return; - } - let {resolve, reject} = this.rpcRegistry[id]; - resolve(msg.data.result); - } - } - - - private registerRpcId(resolve, reject): number { - let id = this.nextRpcId++; - this.rpcRegistry[id] = {resolve, reject}; - return id; - } - - - private doRpc<T>(methodName: string, ...args): Promise<T> { - return new Promise<T>((resolve, reject) => { - let msg = { - operation: methodName, - id: this.registerRpcId(resolve, reject), - args: args, - }; - this.cryptoWorker.postMessage(msg); - }); - } - - - createPreCoin(denom: Denomination, reserve: Reserve): Promise<PreCoin> { - return this.doRpc("createPreCoin", denom, reserve); - } - - hashRsaPub(rsaPub: string): Promise<string> { - return this.doRpc("hashRsaPub", rsaPub); - } - - isValidDenom(denom: Denomination, - masterPub: string): Promise<boolean> { - return this.doRpc("isValidDenom", denom, masterPub); - } - - signDeposit(offer: Offer, - cds: CoinWithDenom[]): Promise<PayCoinInfo> { - return this.doRpc("signDeposit", offer, cds); - } - - createEddsaKeypair(): Promise<{priv: string, pub: string}> { - return this.doRpc("createEddsaKeypair"); - } - - rsaUnblind(sig: string, bk: string, pk: string): Promise<string> { - return this.doRpc("rsaUnblind", sig, bk, pk); - } -}
\ No newline at end of file diff --git a/extension/lib/wallet/cryptoLib.ts b/extension/lib/wallet/cryptoLib.ts deleted file mode 100644 index 869ddbaff..000000000 --- a/extension/lib/wallet/cryptoLib.ts +++ /dev/null @@ -1,222 +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 <http://www.gnu.org/licenses/> - */ - -import {Denomination} from "./types"; -/** - * Web worker for crypto operations. - * @author Florian Dold - */ - -"use strict"; - -import * as native from "./emscriptif"; -import {PreCoin, Reserve, PayCoinInfo} from "./types"; -import create = chrome.alarms.create; -import {Offer} from "./wallet"; -import {CoinWithDenom} from "./wallet"; -import {CoinPaySig} from "./types"; - - -export function main(worker: Worker) { - worker.onmessage = (msg: MessageEvent) => { - console.log("got data", msg.data); - if (!Array.isArray(msg.data.args)) { - console.error("args must be array"); - return; - } - if (typeof msg.data.id != "number") { - console.error("RPC id must be number"); - } - if (typeof msg.data.operation != "string") { - console.error("RPC operation must be string"); - } - let f = RpcFunctions[msg.data.operation]; - if (!f) { - console.error(`unknown operation: '${msg.data.operation}'`); - return; - } - let res = f(...msg.data.args); - worker.postMessage({result: res, id: msg.data.id}); - } -} - -console.log("hello, this is the crypto lib"); - -namespace RpcFunctions { - - /** - * Create a pre-coin of the given denomination to be withdrawn from then given - * reserve. - */ - export function createPreCoin(denom: Denomination, - reserve: Reserve): PreCoin { - let reservePriv = new native.EddsaPrivateKey(); - reservePriv.loadCrock(reserve.reserve_priv); - let reservePub = new native.EddsaPublicKey(); - reservePub.loadCrock(reserve.reserve_pub); - let denomPub = native.RsaPublicKey.fromCrock(denom.denom_pub); - let coinPriv = native.EddsaPrivateKey.create(); - let coinPub = coinPriv.getPublicKey(); - let blindingFactor = native.RsaBlindingKey.create(1024); - let pubHash: native.HashCode = coinPub.hash(); - let ev: native.ByteArray = native.rsaBlind(pubHash, - blindingFactor, - denomPub); - - if (!denom.fee_withdraw) { - throw Error("Field fee_withdraw missing"); - } - - let amountWithFee = new native.Amount(denom.value); - amountWithFee.add(new native.Amount(denom.fee_withdraw)); - let withdrawFee = new native.Amount(denom.fee_withdraw); - - // Signature - let withdrawRequest = new native.WithdrawRequestPS({ - reserve_pub: reservePub, - amount_with_fee: amountWithFee.toNbo(), - withdraw_fee: withdrawFee.toNbo(), - h_denomination_pub: denomPub.encode().hash(), - h_coin_envelope: ev.hash() - }); - - var sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv); - - let preCoin: PreCoin = { - reservePub: reservePub.toCrock(), - blindingKey: blindingFactor.toCrock(), - coinPub: coinPub.toCrock(), - coinPriv: coinPriv.toCrock(), - denomPub: denomPub.encode().toCrock(), - mintBaseUrl: reserve.mint_base_url, - withdrawSig: sig.toCrock(), - coinEv: ev.toCrock(), - coinValue: denom.value - }; - return preCoin; - } - - - export function isValidDenom(denom: Denomination, - masterPub: string): boolean { - let p = new native.DenominationKeyValidityPS({ - master: native.EddsaPublicKey.fromCrock(masterPub), - denom_hash: native.RsaPublicKey.fromCrock(denom.denom_pub) - .encode() - .hash(), - expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_legal), - expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_deposit), - expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_withdraw), - start: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_start), - value: (new native.Amount(denom.value)).toNbo(), - fee_deposit: (new native.Amount(denom.fee_deposit)).toNbo(), - fee_refresh: (new native.Amount(denom.fee_refresh)).toNbo(), - fee_withdraw: (new native.Amount(denom.fee_withdraw)).toNbo(), - }); - - let nativeSig = new native.EddsaSignature(); - nativeSig.loadCrock(denom.master_sig); - - let nativePub = native.EddsaPublicKey.fromCrock(masterPub); - - return native.eddsaVerify(native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY, - p.toPurpose(), - nativeSig, - nativePub); - - } - - - export function hashRsaPub(rsaPub: string): string { - return native.RsaPublicKey.fromCrock(rsaPub) - .encode() - .hash() - .toCrock(); - } - - - export function createEddsaKeypair(): {priv: string, pub: string} { - const priv = native.EddsaPrivateKey.create(); - const pub = priv.getPublicKey(); - return {priv: priv.toCrock(), pub: pub.toCrock()}; - } - - - export function rsaUnblind(sig, bk, pk): string { - let denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig), - native.RsaBlindingKey.fromCrock(bk), - native.RsaPublicKey.fromCrock(pk)); - return denomSig.encode().toCrock() - } - - - /** - * Generate updated coins (to store in the database) - * and deposit permissions for each given coin. - */ - export function signDeposit(offer: Offer, - cds: CoinWithDenom[]): PayCoinInfo { - let ret = []; - let amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency); - let amountRemaining = new native.Amount(offer.contract.amount); - for (let cd of cds) { - let coinSpend; - - if (amountRemaining.value == 0 && amountRemaining.fraction == 0) { - break; - } - - if (amountRemaining.cmp(new native.Amount(cd.coin.currentAmount)) < 0) { - coinSpend = new native.Amount(amountRemaining.toJson()); - } else { - coinSpend = new native.Amount(cd.coin.currentAmount); - } - - amountSpent.add(coinSpend); - amountRemaining.sub(coinSpend); - - let newAmount = new native.Amount(cd.coin.currentAmount); - newAmount.sub(coinSpend); - cd.coin.currentAmount = newAmount.toJson(); - - let d = new native.DepositRequestPS({ - h_contract: native.HashCode.fromCrock(offer.H_contract), - h_wire: native.HashCode.fromCrock(offer.contract.H_wire), - amount_with_fee: coinSpend.toNbo(), - coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub), - deposit_fee: new native.Amount(cd.denom.fee_deposit).toNbo(), - merchant: native.EddsaPublicKey.fromCrock(offer.contract.merchant_pub), - refund_deadline: native.AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline), - timestamp: native.AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp), - transaction_id: native.UInt64.fromNumber(offer.contract.transaction_id), - }); - - let coinSig = native.eddsaSign(d.toPurpose(), - native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv)) - .toCrock(); - - let s: CoinPaySig = { - coin_sig: coinSig, - coin_pub: cd.coin.coinPub, - ub_sig: cd.coin.denomSig, - denom_pub: cd.coin.denomPub, - f: coinSpend.toJson(), - }; - ret.push({sig: s, updatedCoin: cd.coin}); - } - return ret; - } -} diff --git a/extension/lib/wallet/cryptoWorker.ts b/extension/lib/wallet/cryptoWorker.ts deleted file mode 100644 index 958c2de74..000000000 --- a/extension/lib/wallet/cryptoWorker.ts +++ /dev/null @@ -1,65 +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 <http://www.gnu.org/licenses/> - */ - -/** - * Web worker for crypto operations. - * @author Florian Dold - */ - -"use strict"; - - -importScripts("../emscripten/libwrapper.js", - "../vendor/system-csp-production.src.js"); - - -// TypeScript does not allow ".js" extensions in the -// module name, so SystemJS must add it. -System.config({ - defaultJSExtensions: true, - }); - -// We expect that in the manifest, the emscripten js is loaded -// becore the background page. -// Currently it is not possible to use SystemJS to load the emscripten js. -declare var Module: any; -if ("object" !== typeof Module) { - throw Error("emscripten not loaded, no 'Module' defined"); -} - - -// Manually register the emscripten js as a SystemJS, so that -// we can use it from TypeScript by importing it. - -{ - let mod = System.newModule({Module: Module}); - let modName = System.normalizeSync("../emscripten/emsc"); - console.log("registering", modName); - System.set(modName, mod); -} - -System.import("./cryptoLib") - .then((m) => { - m.main(self); - console.log("loaded"); - }) - .catch((e) => { - console.log("crypto worker failed"); - console.error(e.stack); - }); - -console.log("in worker thread"); - diff --git a/extension/lib/wallet/db.ts b/extension/lib/wallet/db.ts deleted file mode 100644 index c7621c5ff..000000000 --- a/extension/lib/wallet/db.ts +++ /dev/null @@ -1,109 +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 <http://www.gnu.org/licenses/> - */ - -"use strict"; - -/** - * Declarations and helpers for - * things that are stored in the wallet's - * database. - * @module Db - * @author Florian Dold - */ - -const DB_NAME = "taler"; -const DB_VERSION = 1; - -/** - * Return a promise that resolves - * to the taler wallet db. - */ -export function openTalerDb(): Promise<IDBDatabase> { - return new Promise((resolve, reject) => { - const req = indexedDB.open(DB_NAME, DB_VERSION); - req.onerror = (e) => { - reject(e); - }; - req.onsuccess = (e) => { - resolve(req.result); - }; - req.onupgradeneeded = (e) => { - let db = req.result; - console.log("DB: upgrade needed: oldVersion = " + e.oldVersion); - switch (e.oldVersion) { - case 0: // DB does not exist yet - const mints = db.createObjectStore("mints", {keyPath: "baseUrl"}); - mints.createIndex("pubKey", "masterPublicKey"); - db.createObjectStore("reserves", {keyPath: "reserve_pub"}); - db.createObjectStore("denoms", {keyPath: "denomPub"}); - const coins = db.createObjectStore("coins", {keyPath: "coinPub"}); - coins.createIndex("mintBaseUrl", "mintBaseUrl"); - const transactions = db.createObjectStore("transactions", - {keyPath: "contractHash"}); - transactions.createIndex("repurchase", - [ - "contract.merchant_pub", - "contract.repurchase_correlation_id" - ]); - - db.createObjectStore("precoins", - {keyPath: "coinPub", autoIncrement: true}); - const history = db.createObjectStore("history", - { - keyPath: "id", - autoIncrement: true - }); - history.createIndex("timestamp", "timestamp"); - break; - } - }; - }); -} - - -export 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(); - } - }); - } - }); -} - -export function deleteDb() { - indexedDB.deleteDatabase(DB_NAME); -}
\ No newline at end of file diff --git a/extension/lib/wallet/emscriptif.ts b/extension/lib/wallet/emscriptif.ts deleted file mode 100644 index b03bc9bc7..000000000 --- a/extension/lib/wallet/emscriptif.ts +++ /dev/null @@ -1,1006 +0,0 @@ -/* - This file is part of TALER - (C) 2015 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/> - */ - -import {AmountJson} from "./types"; -import * as EmscWrapper from "../emscripten/emsc"; - -/** - * High-level interface to emscripten-compiled modules used - * by the wallet. - * @module EmscriptIf - * @author Florian Dold - */ - -"use strict"; - -// Size of a native pointer. -const PTR_SIZE = 4; - -const GNUNET_OK = 1; -const GNUNET_YES = 1; -const GNUNET_NO = 0; -const GNUNET_SYSERR = -1; - -let Module = EmscWrapper.Module; - -let getEmsc: EmscWrapper.EmscFunGen = (...args) => Module.cwrap.apply(null, - args); - -var emsc = { - free: (ptr) => Module._free(ptr), - get_value: getEmsc('TALER_WR_get_value', - 'number', - ['number']), - get_fraction: getEmsc('TALER_WR_get_fraction', - 'number', - ['number']), - get_currency: getEmsc('TALER_WR_get_currency', - 'string', - ['number']), - amount_add: getEmsc('TALER_amount_add', - 'number', - ['number', 'number', 'number']), - amount_subtract: getEmsc('TALER_amount_subtract', - 'number', - ['number', 'number', 'number']), - amount_normalize: getEmsc('TALER_amount_normalize', - 'void', - ['number']), - amount_get_zero: getEmsc('TALER_amount_get_zero', - 'number', - ['string', 'number']), - amount_cmp: getEmsc('TALER_amount_cmp', - 'number', - ['number', 'number']), - amount_hton: getEmsc('TALER_amount_hton', - 'void', - ['number', 'number']), - amount_ntoh: getEmsc('TALER_amount_ntoh', - 'void', - ['number', 'number']), - hash: getEmsc('GNUNET_CRYPTO_hash', - 'void', - ['number', 'number', 'number']), - memmove: getEmsc('memmove', - 'number', - ['number', 'number', 'number']), - rsa_public_key_free: getEmsc('GNUNET_CRYPTO_rsa_public_key_free', - 'void', - ['number']), - rsa_signature_free: getEmsc('GNUNET_CRYPTO_rsa_signature_free', - 'void', - ['number']), - string_to_data: getEmsc('GNUNET_STRINGS_string_to_data', - 'number', - ['number', 'number', 'number', 'number']), - eddsa_sign: getEmsc('GNUNET_CRYPTO_eddsa_sign', - 'number', - ['number', 'number', 'number']), - eddsa_verify: getEmsc('GNUNET_CRYPTO_eddsa_verify', - 'number', - ['number', 'number', 'number', 'number']), - hash_create_random: getEmsc('GNUNET_CRYPTO_hash_create_random', - 'void', - ['number', 'number']), - rsa_blinding_key_destroy: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_free', - 'void', - ['number']), -}; - -var emscAlloc = { - get_amount: getEmsc('TALER_WRALL_get_amount', - 'number', - ['number', 'number', 'number', 'string']), - eddsa_key_create: getEmsc('GNUNET_CRYPTO_eddsa_key_create', - 'number', []), - eddsa_public_key_from_private: getEmsc( - 'TALER_WRALL_eddsa_public_key_from_private', - 'number', - ['number']), - data_to_string_alloc: getEmsc('GNUNET_STRINGS_data_to_string_alloc', - 'number', - ['number', 'number']), - purpose_create: getEmsc('TALER_WRALL_purpose_create', - 'number', - ['number', 'number', 'number']), - rsa_blind: getEmsc('GNUNET_CRYPTO_rsa_blind', - 'number', - ['number', 'number', 'number', 'number']), - rsa_blinding_key_create: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_create', - 'number', - ['number']), - rsa_blinding_key_encode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_encode', - 'number', - ['number', 'number']), - rsa_signature_encode: getEmsc('GNUNET_CRYPTO_rsa_signature_encode', - 'number', - ['number', 'number']), - rsa_blinding_key_decode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_decode', - 'number', - ['number', 'number']), - rsa_public_key_decode: getEmsc('GNUNET_CRYPTO_rsa_public_key_decode', - 'number', - ['number', 'number']), - rsa_signature_decode: getEmsc('GNUNET_CRYPTO_rsa_signature_decode', - 'number', - ['number', 'number']), - rsa_public_key_encode: getEmsc('GNUNET_CRYPTO_rsa_public_key_encode', - 'number', - ['number', 'number']), - rsa_unblind: getEmsc('GNUNET_CRYPTO_rsa_unblind', - 'number', - ['number', 'number', 'number']), - malloc: (size: number) => Module._malloc(size), -}; - - -export enum SignaturePurpose { - RESERVE_WITHDRAW = 1200, - WALLET_COIN_DEPOSIT = 1201, - MASTER_DENOMINATION_KEY_VALIDITY = 1025, -} - -enum RandomQuality { - WEAK = 0, - STRONG = 1, - NONCE = 2 -} - - -abstract class ArenaObject { - private _nativePtr: number; - arena: Arena; - - abstract destroy(): void; - - constructor(arena?: Arena) { - this.nativePtr = null; - if (!arena) { - if (arenaStack.length == 0) { - throw Error("No arena available") - } - arena = arenaStack[arenaStack.length - 1]; - } - arena.put(this); - this.arena = arena; - } - - getNative(): number { - // We want to allow latent allocation - // of native wrappers, but we never want to - // pass 'undefined' to emscripten. - if (this._nativePtr === undefined) { - throw Error("Native pointer not initialized"); - } - return this._nativePtr; - } - - free() { - if (this.nativePtr !== undefined) { - emsc.free(this.nativePtr); - this.nativePtr = undefined; - } - } - - alloc(size: number) { - if (this.nativePtr !== undefined) { - throw Error("Double allocation"); - } - this.nativePtr = emscAlloc.malloc(size); - } - - setNative(n: number) { - if (n === undefined) { - throw Error("Native pointer must be a number or null"); - } - this._nativePtr = n; - } - - set nativePtr(v) { - this.setNative(v); - } - - get nativePtr() { - return this.getNative(); - } - -} - -interface Arena { - put(obj: ArenaObject): void; - destroy(): void; -} - -class DefaultArena implements Arena { - heap: Array<ArenaObject>; - - constructor() { - this.heap = []; - } - - put(obj) { - this.heap.push(obj); - } - - destroy() { - for (let obj of this.heap) { - obj.destroy(); - } - this.heap = [] - } -} - - -function mySetTimeout(ms: number, fn: () => void) { - // We need to use different timeouts, depending on whether - // we run in node or a web extension - if ("function" === typeof setTimeout) { - setTimeout(fn, ms); - } else { - chrome.extension.getBackgroundPage().setTimeout(fn, ms); - } -} - - -/** - * Arena that destroys all its objects once control has returned to the message - * loop and a small interval has passed. - */ -class SyncArena extends DefaultArena { - private isScheduled: boolean; - - constructor() { - super(); - } - - pub(obj) { - super.put(obj); - if (!this.isScheduled) { - this.schedule(); - } - this.heap.push(obj); - } - - destroy() { - super.destroy(); - } - - private schedule() { - this.isScheduled = true; - mySetTimeout(50, () => { - this.isScheduled = false; - this.destroy(); - }); - } -} - -let arenaStack: Arena[] = []; -arenaStack.push(new SyncArena()); - - -export class Amount extends ArenaObject { - constructor(args?: AmountJson, arena?: Arena) { - super(arena); - if (args) { - this.nativePtr = emscAlloc.get_amount(args.value, - 0, - args.fraction, - args.currency); - } else { - this.nativePtr = emscAlloc.get_amount(0, 0, 0, ""); - } - } - - destroy() { - if (this.nativePtr != 0) { - emsc.free(this.nativePtr); - } - } - - - static getZero(currency: string, a?: Arena): Amount { - let am = new Amount(null, a); - let r = emsc.amount_get_zero(currency, am.getNative()); - if (r != GNUNET_OK) { - throw Error("invalid currency"); - } - return am; - } - - - toNbo(a?: Arena): AmountNbo { - let x = new AmountNbo(a); - x.alloc(); - emsc.amount_hton(x.nativePtr, this.nativePtr); - return x; - } - - fromNbo(nbo: AmountNbo): void { - emsc.amount_ntoh(this.nativePtr, nbo.nativePtr); - } - - get value() { - return emsc.get_value(this.nativePtr); - } - - get fraction() { - return emsc.get_fraction(this.nativePtr); - } - - get currency(): String { - return emsc.get_currency(this.nativePtr); - } - - toJson(): AmountJson { - return { - value: emsc.get_value(this.nativePtr), - fraction: emsc.get_fraction(this.nativePtr), - currency: emsc.get_currency(this.nativePtr) - }; - } - - /** - * Add an amount to this amount. - */ - add(a: Amount) { - let res = emsc.amount_add(this.nativePtr, a.nativePtr, this.nativePtr); - if (res < 1) { - // Overflow - return false; - } - return true; - } - - /** - * Perform saturating subtraction on amounts. - */ - sub(a: Amount) { - // this = this - a - let res = emsc.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr); - if (res == 0) { - // Underflow - return false; - } - if (res > 0) { - return true; - } - throw Error("Incompatible currencies"); - } - - cmp(a: Amount) { - // If we don't check this, the c code aborts. - if (this.currency !== a.currency) { - throw Error(`incomparable currencies (${this.currency} and ${a.currency})`); - } - return emsc.amount_cmp(this.nativePtr, a.nativePtr); - } - - normalize() { - emsc.amount_normalize(this.nativePtr); - } -} - - -abstract class PackedArenaObject extends ArenaObject { - abstract size(): number; - - constructor(a?: Arena) { - super(a); - } - - toCrock(): string { - var d = emscAlloc.data_to_string_alloc(this.nativePtr, this.size()); - var s = Module.Pointer_stringify(d); - emsc.free(d); - return s; - } - - toJson(): any { - // Per default, the json encoding of - // packed arena objects is just the crockford encoding. - // Subclasses typically want to override this. - return this.toCrock(); - } - - loadCrock(s: string) { - this.alloc(); - // We need to get the javascript string - // to the emscripten heap first. - let buf = ByteArray.fromString(s); - let res = emsc.string_to_data(buf.nativePtr, - s.length, - this.nativePtr, - this.size()); - buf.destroy(); - if (res < 1) { - throw {error: "wrong encoding"}; - } - } - - alloc() { - if (this.nativePtr === null) { - this.nativePtr = emscAlloc.malloc(this.size()); - } - } - - destroy() { - emsc.free(this.nativePtr); - this.nativePtr = 0; - } - - hash(): HashCode { - var x = new HashCode(); - x.alloc(); - emsc.hash(this.nativePtr, this.size(), x.nativePtr); - return x; - } - - hexdump() { - let bytes: string[] = []; - for (let i = 0; i < this.size(); i++) { - let b = Module.getValue(this.getNative() + i, "i8"); - b = (b + 256) % 256; - bytes.push("0".concat(b.toString(16)).slice(-2)); - } - let lines = []; - for (let i = 0; i < bytes.length; i += 8) { - lines.push(bytes.slice(i, i + 8).join(",")); - } - return lines.join("\n"); - } -} - - -export class AmountNbo extends PackedArenaObject { - size() { - return 24; - } - - toJson(): any { - let a = new DefaultArena(); - let am = new Amount(null, a); - am.fromNbo(this); - let json = am.toJson(); - a.destroy(); - return json; - } -} - - -export class EddsaPrivateKey extends PackedArenaObject { - static create(a?: Arena): EddsaPrivateKey { - let obj = new EddsaPrivateKey(a); - obj.nativePtr = emscAlloc.eddsa_key_create(); - return obj; - } - - size() { - return 32; - } - - getPublicKey(a?: Arena): EddsaPublicKey { - let obj = new EddsaPublicKey(a); - obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr); - return obj; - } - - static fromCrock: (string) => EddsaPrivateKey; -} -mixinStatic(EddsaPrivateKey, fromCrock); - - -function fromCrock(s: string) { - let x = new this(); - x.alloc(); - x.loadCrock(s); - return x; -} - - -function mixin(obj, method, name?: string) { - if (!name) { - name = method.name; - } - if (!name) { - throw Error("Mixin needs a name."); - } - obj.prototype[method.name] = method; -} - - -function mixinStatic(obj, method, name?: string) { - if (!name) { - name = method.name; - } - if (!name) { - throw Error("Mixin needs a name."); - } - obj[method.name] = method; -} - - -export class EddsaPublicKey extends PackedArenaObject { - size() { - return 32; - } - - static fromCrock: (s: string) => EddsaPublicKey; -} -mixinStatic(EddsaPublicKey, fromCrock); - -function makeFromCrock(decodeFn: (p: number, s: number) => number) { - function fromCrock(s: string, a?: Arena) { - let obj = new this(a); - let buf = ByteArray.fromCrock(s); - obj.setNative(decodeFn(buf.getNative(), - buf.size())); - buf.destroy(); - return obj; - } - - return fromCrock; -} - -function makeToCrock(encodeFn: (po: number, - ps: number) => number): () => string { - function toCrock() { - let ptr = emscAlloc.malloc(PTR_SIZE); - let size = emscAlloc.rsa_blinding_key_encode(this.nativePtr, ptr); - let res = new ByteArray(size, Module.getValue(ptr, '*')); - let s = res.toCrock(); - emsc.free(ptr); - res.destroy(); - return s; - } - - return toCrock; -} - -export class RsaBlindingKey extends ArenaObject { - static create(len: number, a?: Arena) { - let o = new RsaBlindingKey(a); - o.nativePtr = emscAlloc.rsa_blinding_key_create(len); - return o; - } - - static fromCrock: (s: string, a?: Arena) => RsaBlindingKey; - toCrock = makeToCrock(emscAlloc.rsa_blinding_key_encode); - - destroy() { - // TODO - } -} -mixinStatic(RsaBlindingKey, makeFromCrock(emscAlloc.rsa_blinding_key_decode)); - - -export class HashCode extends PackedArenaObject { - size() { - return 64; - } - - static fromCrock: (s: string) => HashCode; - - random(qualStr: string) { - let qual: RandomQuality; - switch (qualStr) { - case "weak": - qual = RandomQuality.WEAK; - break; - case "strong": - case null: - case undefined: - qual = RandomQuality.STRONG; - break; - case "nonce": - qual = RandomQuality.NONCE; - break; - default: - throw Error(`unknown crypto quality: ${qual}`); - } - this.alloc(); - emsc.hash_create_random(qual, this.nativePtr); - } -} -mixinStatic(HashCode, fromCrock); - - -export class ByteArray extends PackedArenaObject { - private allocatedSize: number; - - size() { - return this.allocatedSize; - } - - constructor(desiredSize: number, init: number, a?: Arena) { - super(a); - if (init === undefined || init === null) { - this.nativePtr = emscAlloc.malloc(desiredSize); - } else { - this.nativePtr = init; - } - this.allocatedSize = desiredSize; - } - - static fromString(s: string, a?: Arena): ByteArray { - let hstr = emscAlloc.malloc(s.length + 1); - Module.writeStringToMemory(s, hstr); - return new ByteArray(s.length, hstr, a); - } - - static fromCrock(s: string, a?: Arena): ByteArray { - let hstr = emscAlloc.malloc(s.length + 1); - Module.writeStringToMemory(s, hstr); - let decodedLen = Math.floor((s.length * 5) / 8); - let ba = new ByteArray(decodedLen, null, a); - let res = emsc.string_to_data(hstr, s.length, ba.nativePtr, decodedLen); - emsc.free(hstr); - if (res != GNUNET_OK) { - throw Error("decoding failed"); - } - return ba; - } -} - - -export class EccSignaturePurpose extends PackedArenaObject { - size() { - return this.payloadSize + 8; - } - - payloadSize: number; - - constructor(purpose: SignaturePurpose, - payload: PackedArenaObject, - a?: Arena) { - super(a); - this.nativePtr = emscAlloc.purpose_create(purpose, - payload.nativePtr, - payload.size()); - this.payloadSize = payload.size(); - } -} - - -abstract class SignatureStruct { - abstract fieldTypes(): Array<any>; - - abstract purpose(): SignaturePurpose; - - private members: any = {}; - - constructor(x: { [name: string]: any }) { - for (let k in x) { - this.set(k, x[k]); - } - } - - toPurpose(a?: Arena): EccSignaturePurpose { - let totalSize = 0; - for (let f of this.fieldTypes()) { - let name = f[0]; - let member = this.members[name]; - if (!member) { - throw Error(`Member ${name} not set`); - } - totalSize += member.size(); - } - - let buf = emscAlloc.malloc(totalSize); - let ptr = buf; - for (let f of this.fieldTypes()) { - let name = f[0]; - let member = this.members[name]; - let size = member.size(); - emsc.memmove(ptr, member.nativePtr, size); - ptr += size; - } - let ba = new ByteArray(totalSize, buf, a); - return new EccSignaturePurpose(this.purpose(), ba); - } - - - toJson() { - let res: any = {}; - for (let f of this.fieldTypes()) { - let name = f[0]; - let member = this.members[name]; - if (!member) { - throw Error(`Member ${name} not set`); - } - res[name] = member.toJson(); - } - res["purpose"] = this.purpose(); - return res; - } - - protected set(name: string, value: PackedArenaObject) { - let typemap: any = {}; - for (let f of this.fieldTypes()) { - typemap[f[0]] = f[1]; - } - if (!(name in typemap)) { - throw Error(`Key ${name} not found`); - } - if (!(value instanceof typemap[name])) { - throw Error("Wrong type for ${name}"); - } - this.members[name] = value; - } -} - - -// It's redundant, but more type safe. -export interface WithdrawRequestPS_Args { - reserve_pub: EddsaPublicKey; - amount_with_fee: AmountNbo; - withdraw_fee: AmountNbo; - h_denomination_pub: HashCode; - h_coin_envelope: HashCode; -} - - -export class WithdrawRequestPS extends SignatureStruct { - constructor(w: WithdrawRequestPS_Args) { - super(w); - } - - purpose() { - return SignaturePurpose.RESERVE_WITHDRAW; - } - - fieldTypes() { - return [ - ["reserve_pub", EddsaPublicKey], - ["amount_with_fee", AmountNbo], - ["withdraw_fee", AmountNbo], - ["h_denomination_pub", HashCode], - ["h_coin_envelope", HashCode] - ]; - } -} - - -export class AbsoluteTimeNbo extends PackedArenaObject { - static fromTalerString(s: string): AbsoluteTimeNbo { - let x = new AbsoluteTimeNbo(); - x.alloc(); - let r = /Date\(([0-9]+)\)/; - let m = r.exec(s); - if (m.length != 2) { - throw Error(); - } - let n = parseInt(m[1]) * 1000000; - // XXX: This only works up to 54 bit numbers. - set64(x.getNative(), n); - return x; - } - - size() { - return 8; - } -} - - -// XXX: This only works up to 54 bit numbers. -function set64(p: number, n: number) { - for (let i = 0; i < 8; ++i) { - Module.setValue(p + (7 - i), n & 0xFF, "i8"); - n = Math.floor(n / 256); - } - -} - - -export class UInt64 extends PackedArenaObject { - static fromNumber(n: number): UInt64 { - let x = new UInt64(); - x.alloc(); - set64(x.getNative(), n); - return x; - } - - size() { - return 8; - } -} - - -// It's redundant, but more type safe. -export interface DepositRequestPS_Args { - h_contract: HashCode; - h_wire: HashCode; - timestamp: AbsoluteTimeNbo; - refund_deadline: AbsoluteTimeNbo; - transaction_id: UInt64; - amount_with_fee: AmountNbo; - deposit_fee: AmountNbo; - merchant: EddsaPublicKey; - coin_pub: EddsaPublicKey; -} - - -export class DepositRequestPS extends SignatureStruct { - constructor(w: DepositRequestPS_Args) { - super(w); - } - - purpose() { - return SignaturePurpose.WALLET_COIN_DEPOSIT; - } - - fieldTypes() { - return [ - ["h_contract", HashCode], - ["h_wire", HashCode], - ["timestamp", AbsoluteTimeNbo], - ["refund_deadline", AbsoluteTimeNbo], - ["transaction_id", UInt64], - ["amount_with_fee", AmountNbo], - ["deposit_fee", AmountNbo], - ["merchant", EddsaPublicKey], - ["coin_pub", EddsaPublicKey], - ]; - } -} - -export interface DenominationKeyValidityPS_args { - master: EddsaPublicKey; - start: AbsoluteTimeNbo; - expire_withdraw: AbsoluteTimeNbo; - expire_spend: AbsoluteTimeNbo; - expire_legal: AbsoluteTimeNbo; - value: AmountNbo; - fee_withdraw: AmountNbo; - fee_deposit: AmountNbo; - fee_refresh: AmountNbo; - denom_hash: HashCode; -} - -export class DenominationKeyValidityPS extends SignatureStruct { - constructor(w: DenominationKeyValidityPS_args) { - super(w); - } - - purpose() { - return SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY; - } - - fieldTypes() { - return [ - ["master", EddsaPublicKey], - ["start", AbsoluteTimeNbo], - ["expire_withdraw", AbsoluteTimeNbo], - ["expire_spend", AbsoluteTimeNbo], - ["expire_legal", AbsoluteTimeNbo], - ["value", AmountNbo], - ["fee_withdraw", AmountNbo], - ["fee_deposit", AmountNbo], - ["fee_refresh", AmountNbo], - ["denom_hash", HashCode] - ]; - } -} - - -interface Encodeable { - encode(arena?: Arena): ByteArray; -} - -function makeEncode(encodeFn) { - function encode(arena?: Arena) { - let ptr = emscAlloc.malloc(PTR_SIZE); - let len = encodeFn(this.getNative(), ptr); - let res = new ByteArray(len, null, arena); - res.setNative(Module.getValue(ptr, '*')); - emsc.free(ptr); - return res; - } - - return encode; -} - - -export class RsaPublicKey extends ArenaObject implements Encodeable { - static fromCrock: (s: string, a?: Arena) => RsaPublicKey; - - toCrock() { - return this.encode().toCrock(); - } - - destroy() { - emsc.rsa_public_key_free(this.nativePtr); - this.nativePtr = 0; - } - - encode: (arena?: Arena) => ByteArray; -} -mixinStatic(RsaPublicKey, makeFromCrock(emscAlloc.rsa_public_key_decode)); -mixin(RsaPublicKey, makeEncode(emscAlloc.rsa_public_key_encode)); - - -export class EddsaSignature extends PackedArenaObject { - size() { - return 64; - } -} - - -export class RsaSignature extends ArenaObject implements Encodeable { - static fromCrock: (s: string, a?: Arena) => RsaSignature; - - encode: (arena?: Arena) => ByteArray; - - destroy() { - emsc.rsa_signature_free(this.getNative()); - this.setNative(0); - } -} -mixinStatic(RsaSignature, makeFromCrock(emscAlloc.rsa_signature_decode)); -mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode)); - - -export function rsaBlind(hashCode: HashCode, - blindingKey: RsaBlindingKey, - pkey: RsaPublicKey, - arena?: Arena): ByteArray { - let ptr = emscAlloc.malloc(PTR_SIZE); - let s = emscAlloc.rsa_blind(hashCode.nativePtr, - blindingKey.nativePtr, - pkey.nativePtr, - ptr); - return new ByteArray(s, Module.getValue(ptr, '*'), arena); -} - - -export function eddsaSign(purpose: EccSignaturePurpose, - priv: EddsaPrivateKey, - a?: Arena): EddsaSignature { - let sig = new EddsaSignature(a); - sig.alloc(); - let res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr); - if (res < 1) { - throw Error("EdDSA signing failed"); - } - return sig; -} - - -export function eddsaVerify(purposeNum: number, - verify: EccSignaturePurpose, - sig: EddsaSignature, - pub: EddsaPublicKey, - a?: Arena): boolean { - let r = emsc.eddsa_verify(purposeNum, - verify.nativePtr, - sig.nativePtr, - pub.nativePtr); - if (r === GNUNET_OK) { - return true; - } - return false; -} - - -export function rsaUnblind(sig: RsaSignature, - bk: RsaBlindingKey, - pk: RsaPublicKey, - a?: Arena): RsaSignature { - let x = new RsaSignature(a); - x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr, - bk.nativePtr, - pk.nativePtr); - return x; -} diff --git a/extension/lib/wallet/helpers.ts b/extension/lib/wallet/helpers.ts deleted file mode 100644 index 99913e558..000000000 --- a/extension/lib/wallet/helpers.ts +++ /dev/null @@ -1,65 +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 <http://www.gnu.org/licenses/> - */ - - -/** - * Smaller helper functions that do not depend - * on the emscripten machinery. - */ - -import {AmountJson} from "./types"; - -export function substituteFulfillmentUrl(url: string, vars) { - url = url.replace("${H_contract}", vars.H_contract); - url = url.replace("${$}", "$"); - return url; -} - - -export function amountToPretty(amount: AmountJson): string { - let x = amount.value + amount.fraction / 1e6; - return `${x} ${amount.currency}`; -} - - -/** - * Canonicalize a base url, typically for the mint. - * - * See http://api.taler.net/wallet.html#general - */ -export function canonicalizeBaseUrl(url) { - let x = new URI(url); - if (!x.protocol()) { - x.protocol("https"); - } - x.path(x.path() + "/").normalizePath(); - x.fragment(); - x.query(); - return x.href() -} - - -export function parsePrettyAmount(pretty: string): AmountJson { - const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty); - if (!res) { - return null; - } - return { - value: parseInt(res[1], 10), - fraction: res[2] ? (parseFloat(`0.${res[2]}`) * 1e-6) : 0, - currency: res[3] - } -}
\ No newline at end of file diff --git a/extension/lib/wallet/http.ts b/extension/lib/wallet/http.ts deleted file mode 100644 index 3f7244e40..000000000 --- a/extension/lib/wallet/http.ts +++ /dev/null @@ -1,84 +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 <http://www.gnu.org/licenses/> - */ - -/** - * Helpers for doing XMLHttpRequest-s that are based on ES6 promises. - * @module Http - * @author Florian Dold - */ - -"use strict"; - - -export interface HttpResponse { - status: number; - responseText: string; -} - - -export class BrowserHttpLib { - req(method: string, - url: string|uri.URI, - options?: any): Promise<HttpResponse> { - let urlString: string; - if (url instanceof URI) { - urlString = url.href(); - } else if (typeof url === "string") { - urlString = url; - } - - return new Promise((resolve, reject) => { - let myRequest = new XMLHttpRequest(); - myRequest.open(method, urlString); - if (options && options.req) { - myRequest.send(options.req); - } else { - myRequest.send(); - } - myRequest.addEventListener("readystatechange", (e) => { - if (myRequest.readyState == XMLHttpRequest.DONE) { - let resp = { - status: myRequest.status, - responseText: myRequest.responseText - }; - resolve(resp); - } - }); - }); - } - - - get(url: string|uri.URI) { - return this.req("get", url); - } - - - postJson(url: string|uri.URI, body) { - return this.req("post", url, {req: JSON.stringify(body)}); - } - - - postForm(url: string|uri.URI, form) { - return this.req("post", url, {req: form}); - } -} - - -export class RequestException { - constructor(detail) { - - } -}
\ No newline at end of file diff --git a/extension/lib/wallet/query.ts b/extension/lib/wallet/query.ts deleted file mode 100644 index 62411dab3..000000000 --- a/extension/lib/wallet/query.ts +++ /dev/null @@ -1,360 +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 <http://www.gnu.org/licenses/> - */ - - -/** - * Database query abstractions. - * @module Query - * @author Florian Dold - */ - -"use strict"; - - -export function Query(db) { - return new QueryRoot(db); -} - -/** - * Stream that can be filtered, reduced or joined - * with indices. - */ -export interface QueryStream<T> { - indexJoin<S>(storeName: string, - indexName: string, - keyFn: (obj: any) => any): QueryStream<[T,S]>; - filter(f: (any) => boolean): QueryStream<T>; - reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>; -} - - -/** - * Get an unresolved promise together with its extracted resolve / reject - * function. - * - * @returns {{resolve: any, reject: any, promise: Promise<T>}} - */ -function openPromise<T>() { - let resolve, reject; - const promise = new Promise<T>((res, rej) => { - resolve = res; - reject = rej; - }); - return {resolve, reject, promise}; -} - - -abstract class QueryStreamBase<T> implements QueryStream<T> { - abstract subscribe(f: (isDone: boolean, - value: any, - tx: IDBTransaction) => void); - - root: QueryRoot; - - constructor(root: QueryRoot) { - this.root = root; - } - - indexJoin<S>(storeName: string, - indexName: string, - key: any): QueryStream<[T,S]> { - this.root.addWork(null, storeName, false); - return new QueryStreamIndexJoin(this, storeName, indexName, key); - } - - filter(f: (any) => boolean): QueryStream<T> { - return new QueryStreamFilter(this, f); - } - - reduce(f, acc?): Promise<any> { - let leakedResolve; - let p = new Promise((resolve, reject) => { - leakedResolve = resolve; - }); - - this.subscribe((isDone, value) => { - if (isDone) { - leakedResolve(acc); - return; - } - acc = f(value, acc); - }); - - return Promise.resolve() - .then(() => this.root.finish()) - .then(() => p); - } -} - - -class QueryStreamFilter<T> extends QueryStreamBase<T> { - s: QueryStreamBase<T>; - filterFn; - - constructor(s: QueryStreamBase<T>, filterFn) { - super(s.root); - this.s = s; - this.filterFn = filterFn; - } - - subscribe(f) { - this.s.subscribe((isDone, value, tx) => { - if (isDone) { - f(true, undefined, tx); - return; - } - if (this.filterFn(value)) { - f(false, value, tx) - } - }); - } -} - - -class QueryStreamIndexJoin<T> extends QueryStreamBase<T> { - s: QueryStreamBase<T>; - storeName; - key; - indexName; - - constructor(s, storeName: string, indexName: string, key: any) { - super(s.root); - this.s = s; - this.storeName = storeName; - this.key = key; - this.indexName = indexName; - } - - subscribe(f) { - this.s.subscribe((isDone, value, tx) => { - if (isDone) { - f(true, undefined, tx); - return; - } - let s = tx.objectStore(this.storeName).index(this.indexName); - let req = s.openCursor(IDBKeyRange.only(this.key(value))); - req.onsuccess = () => { - let cursor = req.result; - if (cursor) { - f(false, [value, cursor.value], tx); - cursor.continue(); - } else { - f(true, undefined, tx); - } - } - }); - } -} - - -class IterQueryStream<T> extends QueryStreamBase<T> { - private storeName; - private options; - - constructor(qr, storeName, options) { - super(qr); - this.options = options; - this.storeName = storeName; - } - - subscribe(f) { - let doIt = (tx) => { - const {indexName = void 0, only = void 0} = this.options; - let s; - if (indexName !== void 0) { - s = tx.objectStore(this.storeName) - .index(this.options.indexName); - } else { - s = tx.objectStore(this.storeName); - } - let kr = undefined; - if (only !== void 0) { - kr = IDBKeyRange.only(this.options.only); - } - let req = s.openCursor(kr); - req.onsuccess = (e) => { - let cursor: IDBCursorWithValue = req.result; - if (cursor) { - f(false, cursor.value, tx); - cursor.continue(); - } else { - f(true, undefined, tx); - } - } - }; - - this.root.addWork(doIt, null, false); - } -} - - -class QueryRoot { - private work = []; - private db: IDBDatabase; - private stores = new Set(); - private kickoffPromise; - - /** - * Some operations is a write operation, - * and we need to do a "readwrite" transaction/ - */ - private hasWrite; - - constructor(db) { - this.db = db; - } - - iter<T>(storeName, {only = void 0, indexName = void 0} = {}): QueryStream<T> { - this.stores.add(storeName); - return new IterQueryStream(this, storeName, {only, indexName}); - } - - /** - * Put an object into the given object store. - * Overrides if an existing object with the same key exists - * in the store. - */ - put(storeName, val): QueryRoot { - let doPut = (tx: IDBTransaction) => { - tx.objectStore(storeName).put(val); - }; - this.addWork(doPut, storeName, true); - return this; - } - - - /** - * Add all object from an iterable to the given object store. - * Fails if the object's key is already present - * in the object store. - */ - putAll(storeName, iterable): QueryRoot { - const doPutAll = (tx: IDBTransaction) => { - for (const obj of iterable) { - tx.objectStore(storeName).put(obj); - } - }; - this.addWork(doPutAll, storeName, true); - return this; - } - - /** - * Add an object to the given object store. - * Fails if the object's key is already present - * in the object store. - */ - add(storeName, val): QueryRoot { - const doAdd = (tx: IDBTransaction) => { - tx.objectStore(storeName).add(val); - }; - this.addWork(doAdd, storeName, true); - return this; - } - - /** - * Get one object from a store by its key. - */ - get(storeName, key): Promise<any> { - if (key === void 0) { - throw Error("key must not be undefined"); - } - - const {resolve, promise} = openPromise(); - - const doGet = (tx) => { - const req = tx.objectStore(storeName).get(key); - req.onsuccess = (r) => { - resolve(req.result); - }; - }; - - this.addWork(doGet, storeName, false); - return Promise.resolve() - .then(() => this.finish()) - .then(() => promise); - } - - /** - * Get one object from a store by its key. - */ - getIndexed(storeName, indexName, key): Promise<any> { - if (key === void 0) { - throw Error("key must not be undefined"); - } - - const {resolve, promise} = openPromise(); - - const doGetIndexed = (tx) => { - const req = tx.objectStore(storeName).index(indexName).get(key); - req.onsuccess = (r) => { - resolve(req.result); - }; - }; - - this.addWork(doGetIndexed, storeName, false); - return Promise.resolve() - .then(() => this.finish()) - .then(() => promise); - } - - /** - * Finish the query, and start the query in the first place if necessary. - */ - finish(): Promise<void> { - if (this.kickoffPromise) { - return this.kickoffPromise; - } - this.kickoffPromise = new Promise((resolve, reject) => { - const mode = this.hasWrite ? "readwrite" : "readonly"; - const tx = this.db.transaction(Array.from(this.stores), mode); - tx.oncomplete = () => { - resolve(); - }; - for (let w of this.work) { - w(tx); - } - }); - return this.kickoffPromise; - } - - /** - * Delete an object by from the given object store. - */ - delete(storeName: string, key): QueryRoot { - const doDelete = (tx) => { - tx.objectStore(storeName).delete(key); - }; - this.addWork(doDelete, storeName, true); - return this; - } - - /** - * Low-level function to add a task to the internal work queue. - */ - addWork(workFn: (IDBTransaction) => void, - storeName: string, - isWrite: boolean) { - if (storeName) { - this.stores.add(storeName); - } - if (isWrite) { - this.hasWrite = true; - } - if (workFn) { - this.work.push(workFn); - } - } -}
\ No newline at end of file diff --git a/extension/lib/wallet/types.ts b/extension/lib/wallet/types.ts deleted file mode 100644 index 9c7b21b7c..000000000 --- a/extension/lib/wallet/types.ts +++ /dev/null @@ -1,269 +0,0 @@ -/* - This file is part of TALER - (C) 2015 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/> - */ - -/** - * Common types that are used by Taler. - * - * Note most types are defined in wallet.ts, types that - * are defined in types.ts are intended to be used by components - * that do not depend on the whole wallet implementation (which depends on - * emscripten). - */ - -import {Checkable} from "./checkable"; - -@Checkable.Class -export class AmountJson { - @Checkable.Number - value: number; - - @Checkable.Number - fraction: number; - - @Checkable.String - currency: string; - - static checked: (obj: any) => AmountJson; -} - - -@Checkable.Class -export class CreateReserveResponse { - /** - * Mint URL where the bank should create the reserve. - * The URL is canonicalized in the response. - */ - @Checkable.String - mint: string; - - @Checkable.String - reservePub: string; - - static checked: (obj: any) => CreateReserveResponse; -} - - -@Checkable.Class -export class Denomination { - @Checkable.Value(AmountJson) - value: AmountJson; - - @Checkable.String - denom_pub: string; - - @Checkable.Value(AmountJson) - fee_withdraw: AmountJson; - - @Checkable.Value(AmountJson) - fee_deposit: AmountJson; - - @Checkable.Value(AmountJson) - fee_refresh: AmountJson; - - @Checkable.String - stamp_start: string; - - @Checkable.String - stamp_expire_withdraw: string; - - @Checkable.String - stamp_expire_legal: string; - - @Checkable.String - stamp_expire_deposit: string; - - @Checkable.String - master_sig: string; - - @Checkable.Optional(Checkable.String) - pub_hash: string; - - static checked: (obj: any) => Denomination; -} - - -export interface IMintInfo { - baseUrl: string; - masterPublicKey: string; - denoms: Denomination[]; -} - -export interface ReserveCreationInfo { - mintInfo: IMintInfo; - selectedDenoms: Denomination[]; - withdrawFee: AmountJson; - overhead: AmountJson; -} - - -export interface PreCoin { - coinPub: string; - coinPriv: string; - reservePub: string; - denomPub: string; - blindingKey: string; - withdrawSig: string; - coinEv: string; - mintBaseUrl: string; - coinValue: AmountJson; -} - - -export interface Reserve { - mint_base_url: string - reserve_priv: string; - reserve_pub: string; -} - - -export interface CoinPaySig { - coin_sig: string; - coin_pub: string; - ub_sig: string; - denom_pub: string; - f: AmountJson; -} - - -export interface Coin { - coinPub: string; - coinPriv: string; - denomPub: string; - denomSig: string; - currentAmount: AmountJson; - mintBaseUrl: string; -} - - -export type PayCoinInfo = Array<{ updatedCoin: Coin, sig: CoinPaySig }>; - - -export namespace Amounts { - export interface Result { - amount: AmountJson; - // Was there an over-/underflow? - saturated: boolean; - } - - function getMaxAmount(currency: string): AmountJson { - return { - currency, - value: Number.MAX_SAFE_INTEGER, - fraction: 2**32, - } - } - - export function getZero(currency: string): AmountJson { - return { - currency, - value: 0, - fraction: 0, - } - } - - export function add(first: AmountJson, ...rest: AmountJson[]): Result { - let currency = first.currency; - let value = first.value + Math.floor(first.fraction / 1e6); - if (value > Number.MAX_SAFE_INTEGER) { - return {amount: getMaxAmount(currency), saturated: true}; - } - let fraction = first.fraction % 1e6; - for (let x of rest) { - if (x.currency !== currency) { - throw Error(`Mismatched currency: ${x.currency} and ${currency}`); - } - - value = value + x.value + Math.floor((fraction + x.fraction) / 1e6); - fraction = (fraction + x.fraction) % 1e6; - if (value > Number.MAX_SAFE_INTEGER) { - return {amount: getMaxAmount(currency), saturated: true}; - } - } - return {amount: {currency, value, fraction}, saturated: false}; - } - - - export function sub(a: AmountJson, b: AmountJson): Result { - if (a.currency !== b.currency) { - throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`); - } - let currency = a.currency; - let value = a.value; - let fraction = a.fraction; - if (fraction < b.fraction) { - if (value < 1) { - return {amount: {currency, value: 0, fraction: 0}, saturated: true}; - } - value--; - fraction += 1e6; - } - console.assert(fraction >= b.fraction); - fraction -= b.fraction; - if (value < b.value) { - return {amount: {currency, value: 0, fraction: 0}, saturated: true}; - } - value -= b.value; - return {amount: {currency, value, fraction}, saturated: false}; - } - - export function cmp(a: AmountJson, b: AmountJson): number { - if (a.currency !== b.currency) { - throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`); - } - let av = a.value + Math.floor(a.fraction / 1e6); - let af = a.fraction % 1e6; - let bv = b.value + Math.floor(b.fraction / 1e6); - let bf = b.fraction % 1e6; - switch (true) { - case av < bv: - return -1; - case av > bv: - return 1; - case af < bf: - return -1; - case af > bf: - return 1; - case af == bf: - return 0; - default: - throw Error("assertion failed"); - } - } - - export function copy(a: AmountJson): AmountJson { - return { - value: a.value, - fraction: a.fraction, - currency: a.currency, - } - } - - export function isNonZero(a: AmountJson) { - return a.value > 0 || a.fraction > 0; - } -} - - -export interface CheckRepurchaseResult { - isRepurchase: boolean; - existingContractHash?: string; - existingFulfillmentUrl?: string; -} - - -export interface Notifier { - notify(); -}
\ No newline at end of file diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts deleted file mode 100644 index 92fb92a4a..000000000 --- a/extension/lib/wallet/wallet.ts +++ /dev/null @@ -1,957 +0,0 @@ -/* - This file is part of TALER - (C) 2015 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/> - */ - -/** - * High-level wallet operations that should be indepentent from the underlying - * browser extension interface. - * @module Wallet - * @author Florian Dold - */ - -import {AmountJson, CreateReserveResponse, IMintInfo, Denomination, Notifier} from "./types"; -import {HttpResponse, RequestException} from "./http"; -import {Query} from "./query"; -import {Checkable} from "./checkable"; -import {canonicalizeBaseUrl} from "./helpers"; -import {ReserveCreationInfo, Amounts} from "./types"; -import {PreCoin} from "./types"; -import {Reserve} from "./types"; -import {CryptoApi} from "./cryptoApi"; -import {Coin} from "./types"; -import {PayCoinInfo} from "./types"; -import {CheckRepurchaseResult} from "./types"; - -"use strict"; - - -export interface CoinWithDenom { - coin: Coin; - denom: Denomination; -} - - -@Checkable.Class -export class KeysJson { - @Checkable.List(Checkable.Value(Denomination)) - denoms: Denomination[]; - - @Checkable.String - master_public_key: string; - - @Checkable.Any - auditors: any[]; - - @Checkable.String - list_issue_date: string; - - @Checkable.Any - signkeys: any; - - @Checkable.String - eddsa_pub: string; - - @Checkable.String - eddsa_sig: string; - - static checked: (obj: any) => KeysJson; -} - - -class MintInfo implements IMintInfo { - baseUrl: string; - masterPublicKey: string; - denoms: Denomination[]; - - constructor(obj: {baseUrl: string} & any) { - this.baseUrl = obj.baseUrl; - - if (obj.denoms) { - this.denoms = Array.from(<Denomination[]>obj.denoms); - } else { - this.denoms = []; - } - - if (typeof obj.masterPublicKey === "string") { - this.masterPublicKey = obj.masterPublicKey; - } - } - - static fresh(baseUrl: string): MintInfo { - return new MintInfo({baseUrl}); - } - - /** - * Merge new key information into the mint info. - * If the new key information is invalid (missing fields, - * invalid signatures), an exception is thrown, but the - * mint info is updated with the new information up until - * the first error. - */ - mergeKeys(newKeys: KeysJson, cryptoApi: CryptoApi): Promise<void> { - if (!this.masterPublicKey) { - this.masterPublicKey = newKeys.master_public_key; - } - - if (this.masterPublicKey != newKeys.master_public_key) { - throw Error("public keys do not match"); - } - - 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) { - return Promise.resolve(); - } - - return cryptoApi - .isValidDenom(newDenom, this.masterPublicKey) - .then((valid) => { - if (!valid) { - throw Error("signature on denomination invalid"); - } - return cryptoApi.hashRsaPub(newDenom.denom_pub); - }) - .then((h) => { - this.denoms.push(Object.assign({}, newDenom, {pub_hash: h})); - }); - }); - - return Promise.all(ps).then(() => void 0); - } -} - - -@Checkable.Class -export class CreateReserveRequest { - /** - * The initial amount for the reserve. - */ - @Checkable.Value(AmountJson) - amount: AmountJson; - - /** - * Mint URL where the bank should create the reserve. - */ - @Checkable.String - mint: string; - - static checked: (obj: any) => CreateReserveRequest; -} - - -@Checkable.Class -export class ConfirmReserveRequest { - /** - * Public key of then reserve that should be marked - * as confirmed. - */ - @Checkable.String - reservePub: string; - - static checked: (obj: any) => ConfirmReserveRequest; -} - - -@Checkable.Class -export class MintHandle { - @Checkable.String - master_pub: string; - - @Checkable.String - url: string; - - static checked: (obj: any) => MintHandle; -} - - -@Checkable.Class -export class Contract { - @Checkable.String - H_wire: string; - - @Checkable.Value(AmountJson) - amount: AmountJson; - - @Checkable.List(Checkable.AnyObject) - auditors: any[]; - - @Checkable.String - expiry: string; - - @Checkable.Any - locations: any; - - @Checkable.Value(AmountJson) - max_fee: AmountJson; - - @Checkable.Any - merchant: any; - - @Checkable.String - merchant_pub: string; - - @Checkable.List(Checkable.Value(MintHandle)) - mints: MintHandle[]; - - @Checkable.List(Checkable.AnyObject) - products: any[]; - - @Checkable.String - refund_deadline: string; - - @Checkable.String - timestamp: string; - - @Checkable.Number - transaction_id: number; - - @Checkable.String - fulfillment_url: string; - - @Checkable.Optional(Checkable.String) - repurchase_correlation_id: string; - - static checked: (obj: any) => Contract; -} - - -@Checkable.Class -export class Offer { - @Checkable.Value(Contract) - contract: Contract; - - @Checkable.String - merchant_sig: string; - - @Checkable.String - H_contract: string; - - static checked: (obj: any) => Offer; -} - - -interface ConfirmPayRequest { - offer: Offer; -} - -interface MintCoins { - [mintUrl: string]: CoinWithDenom[]; -} - - -interface CoinPaySig { - coin_sig: string; - coin_pub: string; - ub_sig: string; - denom_pub: string; - f: AmountJson; -} - - -interface Transaction { - contractHash: string; - contract: Contract; - payReq: any; - merchantSig: string; -} - - -export interface Badge { - setText(s: string): void; - setColor(c: string): void; -} - - -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) { - return null; - } - return parseInt(m[1]); -} - - -function isWithdrawableDenom(d: Denomination) { - const now_sec = (new Date).getTime() / 1000; - const stamp_withdraw_sec = getTalerStampSec(d.stamp_expire_withdraw); - // Withdraw if still possible to withdraw within a minute - if (stamp_withdraw_sec + 60 > now_sec) { - return true; - } - return false; -} - - -interface HttpRequestLibrary { - req(method: string, - url: string|uri.URI, - options?: any): Promise<HttpResponse>; - - get(url: string|uri.URI): Promise<HttpResponse>; - - postJson(url: string|uri.URI, body): Promise<HttpResponse>; - - postForm(url: string|uri.URI, form): Promise<HttpResponse>; -} - - -function copy(o) { - return JSON.parse(JSON.stringify(o)); -} - - -/** - * Get a list of denominations (with repetitions possible) - * whose total value is as close as possible to the available - * amount, but never larger. - */ -function getWithdrawDenomList(amountAvailable: AmountJson, - denoms: Denomination[]): Denomination[] { - let remaining = Amounts.copy(amountAvailable); - let ds: Denomination[] = []; - - denoms = denoms.filter(isWithdrawableDenom); - denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value)); - - // This is an arbitrary number of coins - // we can withdraw in one go. It's not clear if this limit - // is useful ... - for (let i = 0; i < 1000; i++) { - let found = false; - for (let d of denoms) { - let cost = Amounts.add(d.value, d.fee_withdraw).amount; - if (Amounts.cmp(remaining, cost) < 0) { - continue; - } - found = true; - remaining = Amounts.sub(remaining, cost).amount; - ds.push(d); - break; - } - if (!found) { - break; - } - } - return ds; -} - - -export class Wallet { - private db: IDBDatabase; - private http: HttpRequestLibrary; - private badge: Badge; - private notifier: Notifier; - public cryptoApi: CryptoApi; - - - constructor(db: IDBDatabase, - http: HttpRequestLibrary, - badge: Badge, - notifier: Notifier) { - this.db = db; - this.http = http; - this.badge = badge; - this.notifier = notifier; - this.cryptoApi = new CryptoApi(); - } - - - /** - * Get mints and associated coins that are still spendable, - * but only if the sum the coins' remaining value exceeds the payment amount. - */ - private getPossibleMintCoins(paymentAmount: AmountJson, - depositFeeLimit: AmountJson, - allowedMints: MintHandle[]): Promise<MintCoins> { - // Mapping from mint base URL to list of coins together with their - // denomination - let m: MintCoins = {}; - - function storeMintCoin(mc) { - let mint: IMintInfo = mc[0]; - let coin: Coin = mc[1]; - let cd = { - coin: coin, - denom: mint.denoms.find((e) => e.denom_pub === coin.denomPub) - }; - if (!cd.denom) { - throw Error("denom not found (database inconsistent)"); - } - if (cd.denom.value.currency !== paymentAmount.currency) { - console.warn("same pubkey for different currencies"); - return; - } - let x = m[mint.baseUrl]; - if (!x) { - m[mint.baseUrl] = [cd]; - } else { - x.push(cd); - } - } - - let ps = allowedMints.map((info) => { - console.log("Checking for merchant's mint", JSON.stringify(info)); - return Query(this.db) - .iter("mints", {indexName: "pubKey", only: info.master_pub}) - .indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl) - .reduce(storeMintCoin); - }); - - return Promise.all(ps).then(() => { - let ret: MintCoins = {}; - - if (Object.keys(m).length == 0) { - console.log("not suitable mints found"); - } - - console.dir(m); - - // We try to find the first mint where we have - // enough coins to cover the paymentAmount with fees - // under depositFeeLimit - - nextMint: - for (let key in m) { - let coins = m[key]; - // Sort by ascending deposit fee - coins.sort((o1, o2) => Amounts.cmp(o1.denom.fee_deposit, - o2.denom.fee_deposit)); - let maxFee = Amounts.copy(depositFeeLimit); - let minAmount = Amounts.copy(paymentAmount); - let accFee = Amounts.copy(coins[0].denom.fee_deposit); - let accAmount = Amounts.getZero(coins[0].coin.currentAmount.currency); - let usableCoins: CoinWithDenom[] = []; - nextCoin: - for (let i = 0; i < coins.length; i++) { - let coinAmount = Amounts.copy(coins[i].coin.currentAmount); - let coinFee = coins[i].denom.fee_deposit; - if (Amounts.cmp(coinAmount, coinFee) <= 0) { - continue nextCoin; - } - accFee = Amounts.add(accFee, coinFee).amount; - accAmount = Amounts.add(accAmount, coinAmount).amount; - if (Amounts.cmp(accFee, maxFee) >= 0) { - // FIXME: if the fees are too high, we have - // to cover them ourselves .... - console.log("too much fees"); - continue nextMint; - } - usableCoins.push(coins[i]); - if (Amounts.cmp(accAmount, minAmount) >= 0) { - ret[key] = usableCoins; - continue nextMint; - } - } - } - return ret; - }); - } - - - /** - * Record all information that is necessary to - * pay for a contract in the wallet's database. - */ - private recordConfirmPay(offer: Offer, - payCoinInfo: PayCoinInfo, - chosenMint: string): Promise<void> { - let payReq = {}; - payReq["amount"] = offer.contract.amount; - payReq["coins"] = payCoinInfo.map((x) => x.sig); - payReq["H_contract"] = offer.H_contract; - payReq["max_fee"] = offer.contract.max_fee; - payReq["merchant_sig"] = offer.merchant_sig; - payReq["mint"] = URI(chosenMint).href(); - payReq["refund_deadline"] = offer.contract.refund_deadline; - payReq["timestamp"] = offer.contract.timestamp; - payReq["transaction_id"] = offer.contract.transaction_id; - let t: Transaction = { - contractHash: offer.H_contract, - contract: offer.contract, - payReq: payReq, - merchantSig: offer.merchant_sig, - }; - - console.log("pay request"); - console.dir(payReq); - - let historyEntry = { - type: "pay", - timestamp: (new Date).getTime(), - detail: { - merchantName: offer.contract.merchant.name, - amount: offer.contract.amount, - contractHash: offer.H_contract, - fulfillmentUrl: offer.contract.fulfillment_url - } - }; - - return Query(this.db) - .put("transactions", t) - .put("history", historyEntry) - .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin)) - .finish() - .then(() => { - this.notifier.notify(); - }); - } - - - /** - * Add a contract to the wallet and sign coins, - * but do not send them yet. - */ - confirmPay(offer: Offer): Promise<any> { - console.log("executing confirmPay"); - return Promise.resolve().then(() => { - return this.getPossibleMintCoins(offer.contract.amount, - offer.contract.max_fee, - offer.contract.mints) - }).then((mcs) => { - if (Object.keys(mcs).length == 0) { - console.log("not confirming payment, insufficient coins"); - return { - error: "coins-insufficient", - }; - } - let mintUrl = Object.keys(mcs)[0]; - - return this.cryptoApi.signDeposit(offer, mcs[mintUrl]) - .then((ds) => this.recordConfirmPay(offer, ds, mintUrl)) - .then(() => ({})); - }); - } - - - /** - * Retrieve all necessary information for looking up the contract - * with the given hash. - */ - executePayment(H_contract): Promise<any> { - return Promise.resolve().then(() => { - return Query(this.db) - .get("transactions", H_contract) - .then((t) => { - if (!t) { - return { - success: false, - contractFound: false, - } - } - let resp = { - success: true, - payReq: t.payReq, - contract: t.contract, - }; - return resp; - }); - }); - } - - - /** - * First fetch information requred to withdraw from the reserve, - * then deplete the reserve, withdrawing coins until it is empty. - */ - private initReserve(reserveRecord) { - this.updateMintFromUrl(reserveRecord.mint_base_url) - .then((mint) => - this.updateReserve(reserveRecord.reserve_pub, mint) - .then((reserve) => this.depleteReserve(reserve, - mint))) - .then(() => { - let depleted = { - type: "depleted-reserve", - timestamp: (new Date).getTime(), - detail: { - reservePub: reserveRecord.reserve_pub, - } - }; - return Query(this.db).put("history", depleted).finish(); - }) - .catch((e) => { - console.error("Failed to deplete reserve"); - console.error(e); - }); - } - - - /** - * Create a reserve, but do not flag it as confirmed yet. - */ - createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> { - return this.cryptoApi.createEddsaKeypair().then((keypair) => { - const now = (new Date).getTime(); - const canonMint = canonicalizeBaseUrl(req.mint); - - const reserveRecord = { - reserve_pub: keypair.pub, - reserve_priv: keypair.priv, - mint_base_url: canonMint, - created: now, - last_query: null, - current_amount: null, - requested_amount: req.amount, - confirmed: false, - }; - - - const historyEntry = { - type: "create-reserve", - timestamp: now, - detail: { - requestedAmount: req.amount, - reservePub: reserveRecord.reserve_pub, - } - }; - - return Query(this.db) - .put("reserves", reserveRecord) - .put("history", historyEntry) - .finish() - .then(() => { - let r: CreateReserveResponse = { - mint: canonMint, - reservePub: keypair.pub, - }; - return r; - }); - }); - } - - - /** - * Mark an existing reserve as confirmed. The wallet will start trying - * to withdraw from that reserve. This may not immediately succeed, - * since the mint might not know about the reserve yet, even though the - * bank confirmed its creation. - * - * A confirmed reserve should be shown to the user in the UI, while - * an unconfirmed reserve should be hidden. - */ - confirmReserve(req: ConfirmReserveRequest): Promise<void> { - const now = (new Date).getTime(); - const historyEntry = { - type: "confirm-reserve", - timestamp: now, - detail: { - reservePub: req.reservePub, - } - }; - return Query(this.db) - .get("reserves", req.reservePub) - .then((r) => { - r.confirmed = true; - return Query(this.db) - .put("reserves", r) - .put("history", historyEntry) - .finish() - .then(() => { - // Do this in the background - this.initReserve(r); - }); - }); - } - - - private withdrawExecute(pc: PreCoin): Promise<Coin> { - return Query(this.db) - .get("reserves", pc.reservePub) - .then((r) => { - let wd: any = {}; - wd.denom_pub = pc.denomPub; - wd.reserve_pub = pc.reservePub; - wd.reserve_sig = pc.withdrawSig; - wd.coin_ev = pc.coinEv; - let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url); - return this.http.postJson(reqUrl, wd); - }) - .then(resp => { - if (resp.status != 200) { - throw new RequestException({ - hint: "Withdrawal failed", - status: resp.status - }); - } - let r = JSON.parse(resp.responseText); - return this.cryptoApi.rsaUnblind(r.ev_sig, pc.blindingKey, pc.denomPub) - .then((denomSig) => { - let coin: Coin = { - coinPub: pc.coinPub, - coinPriv: pc.coinPriv, - denomPub: pc.denomPub, - denomSig: denomSig, - currentAmount: pc.coinValue, - mintBaseUrl: pc.mintBaseUrl, - }; - return coin; - - }); - }); - } - - storeCoin(coin: Coin): Promise<void> { - let historyEntry = { - type: "withdraw", - timestamp: (new Date).getTime(), - detail: { - coinPub: coin.coinPub, - } - }; - return Query(this.db) - .delete("precoins", coin.coinPub) - .add("coins", coin) - .add("history", historyEntry) - .finish() - .then(() => { - this.notifier.notify(); - }); - } - - - /** - * Withdraw one coins of the given denomination from the given reserve. - */ - private withdraw(denom: Denomination, reserve: Reserve): Promise<void> { - console.log("creating pre coin at", new Date()); - return this.cryptoApi - .createPreCoin(denom, reserve) - .then((preCoin) => { - return Query(this.db) - .put("precoins", preCoin) - .finish() - .then(() => this.withdrawExecute(preCoin)) - .then((c) => this.storeCoin(c)); - }); - - } - - - /** - * Withdraw coins from a reserve until it is empty. - */ - private depleteReserve(reserve, mint: MintInfo): Promise<void> { - let denomsAvailable: Denomination[] = copy(mint.denoms); - let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount, - denomsAvailable); - - let ps = denomsForWithdraw.map((denom) => { - console.log("withdrawing", JSON.stringify(denom)); - // Do the withdraw asynchronously, so crypto is interleaved - // with requests - return this.withdraw(denom, reserve); - }); - - return Promise.all(ps).then(() => void 0); - } - - - /** - * Update the information about a reserve that is stored in the wallet - * by quering the reserve's mint. - */ - private updateReserve(reservePub: string, mint: MintInfo): Promise<Reserve> { - return Query(this.db) - .get("reserves", reservePub) - .then((reserve) => { - let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl); - reqUrl.query({'reserve_pub': reservePub}); - return this.http.get(reqUrl).then(resp => { - if (resp.status != 200) { - throw Error(); - } - let reserveInfo = JSON.parse(resp.responseText); - if (!reserveInfo) { - throw Error(); - } - let oldAmount = reserve.current_amount; - let newAmount = reserveInfo.balance; - reserve.current_amount = reserveInfo.balance; - let historyEntry = { - type: "reserve-update", - timestamp: (new Date).getTime(), - detail: { - reservePub, - oldAmount, - newAmount - } - }; - return Query(this.db) - .put("reserves", reserve) - .finish() - .then(() => reserve); - }); - }); - } - - - getReserveCreationInfo(baseUrl: string, - amount: AmountJson): Promise<ReserveCreationInfo> { - return this.updateMintFromUrl(baseUrl) - .then((mintInfo: IMintInfo) => { - let selectedDenoms = getWithdrawDenomList(amount, - mintInfo.denoms); - - let acc = Amounts.getZero(amount.currency); - for (let d of selectedDenoms) { - acc = Amounts.add(acc, d.fee_withdraw).amount; - } - let actualCoinCost = selectedDenoms - .map((d: Denomination) => Amounts.add(d.value, - d.fee_withdraw).amount) - .reduce((a, b) => Amounts.add(a, b).amount); - let ret: ReserveCreationInfo = { - mintInfo, - selectedDenoms, - withdrawFee: acc, - overhead: Amounts.sub(amount, actualCoinCost).amount, - }; - return ret; - }); - } - - - /** - * Update or add mint DB entry by fetching the /keys information. - * Optionally link the reserve entry to the new or existing - * mint entry in then DB. - */ - updateMintFromUrl(baseUrl): Promise<MintInfo> { - baseUrl = canonicalizeBaseUrl(baseUrl); - let reqUrl = URI("keys").absoluteTo(baseUrl); - return this.http.get(reqUrl).then((resp) => { - if (resp.status != 200) { - throw Error("/keys request failed"); - } - let mintKeysJson = KeysJson.checked(JSON.parse(resp.responseText)); - - return Query(this.db).get("mints", baseUrl).then((r) => { - let mintInfo; - console.dir(r); - - if (!r) { - mintInfo = MintInfo.fresh(baseUrl); - console.log("making fresh mint"); - } else { - mintInfo = new MintInfo(r); - console.log("using old mint"); - } - - return mintInfo.mergeKeys(mintKeysJson, this.cryptoApi) - .then(() => { - return Query(this.db) - .put("mints", mintInfo) - .finish() - .then(() => mintInfo); - }); - - }); - }); - } - - - /** - * Retrieve a mapping from currency name to the amount - * that is currenctly available for spending in the wallet. - */ - getBalances(): Promise<any> { - function collectBalances(c: Coin, byCurrency) { - let acc: AmountJson = byCurrency[c.currentAmount.currency]; - if (!acc) { - acc = Amounts.getZero(c.currentAmount.currency); - } - byCurrency[c.currentAmount.currency] = Amounts.add(c.currentAmount, - acc).amount; - return byCurrency; - } - - return Query(this.db) - .iter("coins") - .reduce(collectBalances, {}); - } - - - /** - * Retrive the full event history for this wallet. - */ - getHistory(): Promise<any[]> { - function collect(x, acc) { - acc.push(x); - return acc; - } - - return Query(this.db) - .iter("history", {indexName: "timestamp"}) - .reduce(collect, []) - } - - checkRepurchase(contract: Contract): Promise<CheckRepurchaseResult> { - if (!contract.repurchase_correlation_id) { - console.log("no repurchase: no correlation id"); - return Promise.resolve({isRepurchase: false}); - } - return Query(this.db) - .getIndexed("transactions", - "repurchase", - [contract.merchant_pub, contract.repurchase_correlation_id]) - .then((result: Transaction) => { - console.log("db result", result); - let isRepurchase; - if (result) { - console.assert(result.contract.repurchase_correlation_id == contract.repurchase_correlation_id); - return { - isRepurchase: true, - existingContractHash: result.contractHash, - existingFulfillmentUrl: result.contract.fulfillment_url, - }; - } else { - return {isRepurchase: false}; - } - }); - } -}
\ No newline at end of file diff --git a/extension/lib/wallet/wxApi.ts b/extension/lib/wallet/wxApi.ts deleted file mode 100644 index 9871b6e7f..000000000 --- a/extension/lib/wallet/wxApi.ts +++ /dev/null @@ -1,40 +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 <http://www.gnu.org/licenses/> - */ - -import {AmountJson} from "./types"; -import {ReserveCreationInfo} from "./types"; - -/** - * Interface to the wallet through WebExtension messaging. - */ - - -export function getReserveCreationInfo(baseUrl: string, - amount: AmountJson): Promise<ReserveCreationInfo> { - 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 deleted file mode 100644 index 740873d88..000000000 --- a/extension/lib/wallet/wxMessaging.ts +++ /dev/null @@ -1,230 +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 <http://www.gnu.org/licenses/> - */ - - -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"; -import {Contract} from "./wallet"; - -"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<any>; - -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); - }, - ["check-repurchase"]: function(detail) { - let contract = Contract.checked(detail.contract); - return wallet.checkRepurchase(contract); - }, - ["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 for '${req.type}'`); - console.log("request", req); - 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.log("request", req); - 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 |