From ffe6dee6aa50c864cc9a36e816eb95f2f23719b1 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 11 Jan 2016 02:56:32 +0100 Subject: [PATCH] refactor code to be clearer/prettier --- extension/lib/commonHelpers.ts | 6 +- extension/lib/emscripten/emsc.d.ts | 16 +- extension/lib/polyfill-react.ts | 23 ++- extension/lib/wallet/emscriptif.ts | 34 ++-- extension/lib/wallet/http.ts | 5 +- extension/lib/wallet/query.ts | 241 ++++++++++++++++++---------- extension/lib/wallet/timerThread.ts | 10 -- extension/lib/wallet/wallet.ts | 6 +- extension/lib/wallet/wxmessaging.js | 1 - extension/lib/wallet/wxmessaging.ts | 2 +- extension/tsconfig.json | 1 - 11 files changed, 216 insertions(+), 129 deletions(-) delete mode 100644 extension/lib/wallet/timerThread.ts diff --git a/extension/lib/commonHelpers.ts b/extension/lib/commonHelpers.ts index 5c32e47c1..4974778b9 100644 --- a/extension/lib/commonHelpers.ts +++ b/extension/lib/commonHelpers.ts @@ -14,12 +14,12 @@ TALER; see the file COPYING. If not, If not, see */ -Handlebars.registerHelper('prettyAmount', function (amount) { +Handlebars.registerHelper("prettyAmount", function (amount) { let v = amount.value + amount.fraction / 1e6; - return v.toFixed(2) + " " + amount.currency; + return `${v.toFixed(2)} ${amount.currency}`; }); -Handlebars.registerHelper('prettyAmountNoCurrency', function (amount) { +Handlebars.registerHelper("prettyAmountNoCurrency", function (amount) { let v = amount.value + amount.fraction / 1e6; return v.toFixed(2); }); diff --git a/extension/lib/emscripten/emsc.d.ts b/extension/lib/emscripten/emsc.d.ts index 659457ca7..d65bd6dcb 100644 --- a/extension/lib/emscripten/emsc.d.ts +++ b/extension/lib/emscripten/emsc.d.ts @@ -19,23 +19,31 @@ export interface EmscFunGen { ret: string, args: string[]): ((...x: (number|string)[]) => any); (name: string, - ret: 'number', + ret: "number", args: string[]): ((...x: (number|string)[]) => number); (name: string, - ret: 'void', + ret: "void", args: string[]): ((...x: (number|string)[]) => void); (name: string, - ret: 'string', + ret: "string", args: string[]): ((...x: (number|string)[]) => string); } export declare namespace Module { var cwrap: EmscFunGen; + function _free(ptr: number); + function _malloc(n: number): number; + function Pointer_stringify(p: number, len?: number): string; + function getValue(ptr: number, type: string, noSafe?: boolean): number; + function setValue(ptr: number, value: number, type: string, noSafe?: boolean); - function writeStringToMemory(s: string, buffer: number, dontAddNull?: boolean); + + function writeStringToMemory(s: string, + buffer: number, + dontAddNull?: boolean); } \ No newline at end of file diff --git a/extension/lib/polyfill-react.ts b/extension/lib/polyfill-react.ts index 8238093ab..a7fa8c395 100644 --- a/extension/lib/polyfill-react.ts +++ b/extension/lib/polyfill-react.ts @@ -1,3 +1,24 @@ +/* + 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 + */ + +/** + * Implement the "React" namespace so that we can use TSX literals. + * Just returns plain DOM elements, no fancy virtual DOM. + */ + "use strict"; let React = { @@ -7,7 +28,7 @@ let React = { e.setAttribute(k, props[k]); } for (let child of children) { - if ("string" === typeof child || "number" == typeof child) { + if ("string" === typeof child || "number" === typeof child) { child = document.createTextNode(child); } e.appendChild(child); diff --git a/extension/lib/wallet/emscriptif.ts b/extension/lib/wallet/emscriptif.ts index eb6a292a7..223fe7348 100644 --- a/extension/lib/wallet/emscriptif.ts +++ b/extension/lib/wallet/emscriptif.ts @@ -36,7 +36,8 @@ const GNUNET_SYSERR = -1; let Module = EmscWrapper.Module; -let getEmsc: EmscWrapper.EmscFunGen = (...args) => Module.cwrap.apply(null, args); +let getEmsc: EmscWrapper.EmscFunGen = (...args) => Module.cwrap.apply(null, + args); var emsc = { free: (ptr) => Module._free(ptr), @@ -447,8 +448,8 @@ abstract class PackedArenaObject extends ArenaObject { 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(",")); + for (let i = 0; i < bytes.length; i += 8) { + lines.push(bytes.slice(i, i + 8).join(",")); } return lines.join("\n"); } @@ -459,6 +460,7 @@ export class AmountNbo extends PackedArenaObject { size() { return 24; } + toJson(): any { let a = new DefaultArena(); let am = new Amount(null, a); @@ -526,6 +528,7 @@ export class EddsaPublicKey extends PackedArenaObject { size() { return 32; } + static fromCrock: (s: string) => EddsaPublicKey; } mixinStatic(EddsaPublicKey, fromCrock); @@ -543,7 +546,8 @@ function makeFromCrock(decodeFn: (p: number, s: number) => number) { return fromCrock; } -function makeToCrock(encodeFn: (po: number, ps: number) => number): () => string { +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); @@ -553,6 +557,7 @@ function makeToCrock(encodeFn: (po: number, ps: number) => number): () => string res.destroy(); return s; } + return toCrock; } @@ -699,7 +704,7 @@ abstract class SignatureStruct { } - toJson() { + toJson() { let res: any = {}; for (let f of this.fieldTypes()) { let name = f[0]; @@ -857,6 +862,7 @@ function makeEncode(encodeFn) { emsc.free(ptr); return res; } + return encode; } @@ -886,7 +892,7 @@ export class EddsaSignature extends PackedArenaObject { } -export class RsaSignature extends ArenaObject implements Encodeable{ +export class RsaSignature extends ArenaObject implements Encodeable { static fromCrock: (s: string, a?: Arena) => RsaSignature; encode: (arena?: Arena) => ByteArray; @@ -901,9 +907,9 @@ mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode)); export function rsaBlind(hashCode: HashCode, - blindingKey: RsaBlindingKey, - pkey: RsaPublicKey, - arena?: Arena): ByteArray { + blindingKey: RsaBlindingKey, + pkey: RsaPublicKey, + arena?: Arena): ByteArray { let ptr = emscAlloc.malloc(PTR_SIZE); let s = emscAlloc.rsa_blind(hashCode.nativePtr, blindingKey.nativePtr, @@ -914,8 +920,8 @@ export function rsaBlind(hashCode: HashCode, export function eddsaSign(purpose: EccSignaturePurpose, - priv: EddsaPrivateKey, - a?: Arena): EddsaSignature { + priv: EddsaPrivateKey, + a?: Arena): EddsaSignature { let sig = new EddsaSignature(a); sig.alloc(); let res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr); @@ -927,9 +933,9 @@ export function eddsaSign(purpose: EccSignaturePurpose, export function rsaUnblind(sig: RsaSignature, - bk: RsaBlindingKey, - pk: RsaPublicKey, - a?: Arena): RsaSignature { + bk: RsaBlindingKey, + pk: RsaPublicKey, + a?: Arena): RsaSignature { let x = new RsaSignature(a); x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr, bk.nativePtr, diff --git a/extension/lib/wallet/http.ts b/extension/lib/wallet/http.ts index d132857b7..3f7244e40 100644 --- a/extension/lib/wallet/http.ts +++ b/extension/lib/wallet/http.ts @@ -23,7 +23,6 @@ "use strict"; - export interface HttpResponse { status: number; responseText: string; @@ -32,8 +31,8 @@ export interface HttpResponse { export class BrowserHttpLib { req(method: string, - url: string|uri.URI, - options?: any): Promise { + url: string|uri.URI, + options?: any): Promise { let urlString: string; if (url instanceof URI) { urlString = url.href(); diff --git a/extension/lib/wallet/query.ts b/extension/lib/wallet/query.ts index c67ce0193..dda716d07 100644 --- a/extension/lib/wallet/query.ts +++ b/extension/lib/wallet/query.ts @@ -30,9 +30,39 @@ export function Query(db) { return new QueryRoot(db); } +/** + * Stream that can be filtered, reduced or joined + * with indices. + */ +export interface QueryStream { + indexJoin(storeName: string, + indexName: string, + keyFn: (obj: any) => any): QueryStream<[T,S]>; + filter(f: (any) => boolean): QueryStream; + reduce(f: (S, T) => S, acc?: S): Promise; +} -abstract class QueryStreamBase { - abstract subscribe(f: (isDone: boolean, value: any) => void); + +/** + * Get an unresolved promise together with its extracted resolve / reject + * function. + * + * @returns {{resolve: any, reject: any, promise: Promise}} + */ +function openPromise() { + let resolve, reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return {resolve, reject, promise}; +} + + +abstract class QueryStreamBase implements QueryStream { + abstract subscribe(f: (isDone: boolean, + value: any, + tx: IDBTransaction) => void); root: QueryRoot; @@ -40,14 +70,14 @@ abstract class QueryStreamBase { this.root = root; } - indexJoin(storeName: string, indexName: string, key: any): QueryStreamBase { - // join on the source relation's key, which may be - // a path or a transformer function - this.root.stores.add(storeName); + indexJoin(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): QueryStreamBase { + filter(f: (any) => boolean): QueryStream { return new QueryStreamFilter(this, f); } @@ -65,37 +95,39 @@ abstract class QueryStreamBase { acc = f(value, acc); }); - return Promise.resolve().then(() => this.root.finish().then(() => p)); + return Promise.resolve() + .then(() => this.root.finish()) + .then(() => p); } } -class QueryStreamFilter extends QueryStreamBase { - s: QueryStreamBase; +class QueryStreamFilter extends QueryStreamBase { + s: QueryStreamBase; filterFn; - constructor(s: QueryStreamBase, filterFn) { + constructor(s: QueryStreamBase, filterFn) { super(s.root); this.s = s; this.filterFn = filterFn; } subscribe(f) { - this.s.subscribe((isDone, value) => { + this.s.subscribe((isDone, value, tx) => { if (isDone) { - f(true, undefined); + f(true, undefined, tx); return; } if (this.filterFn(value)) { - f(false, value) + f(false, value, tx) } }); } } -class QueryStreamIndexJoin extends QueryStreamBase { - s: QueryStreamBase; +class QueryStreamIndexJoin extends QueryStreamBase { + s: QueryStreamBase; storeName; key; indexName; @@ -109,48 +141,45 @@ class QueryStreamIndexJoin extends QueryStreamBase { } subscribe(f) { - this.s.subscribe((isDone, value) => { + this.s.subscribe((isDone, value, tx) => { if (isDone) { - f(true, undefined); + f(true, undefined, tx); return; } - let s = this.root.tx.objectStore(this.storeName).index(this.indexName); + 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]); + f(false, [value, cursor.value], tx); cursor.continue(); } else { - f(true, undefined); + f(true, undefined, tx); } } }); } - } -class IterQueryStream extends QueryStreamBase { - private qr: QueryRoot; +class IterQueryStream extends QueryStreamBase { private storeName; private options; constructor(qr, storeName, options?) { super(qr); - this.qr = qr; this.options = options; this.storeName = storeName; } subscribe(f) { - function doIt() { + let doIt = (tx) => { let s; if (this.options && this.options.indexName) { - s = this.qr.tx.objectStore(this.storeName) - .index(this.options.indexName); + s = tx.objectStore(this.storeName) + .index(this.options.indexName); } else { - s = this.qr.tx.objectStore(this.storeName); + s = tx.objectStore(this.storeName); } let kr = undefined; if (this.options && ("only" in this.options)) { @@ -160,124 +189,158 @@ class IterQueryStream extends QueryStreamBase { req.onsuccess = (e) => { let cursor: IDBCursorWithValue = req.result; if (cursor) { - f(false, cursor.value); + f(false, cursor.value, tx); cursor.continue(); } else { - f(true, undefined); + f(true, undefined, tx); } } - } + }; - this.qr.work.push(doIt.bind(this)); + this.root.addWork(doIt, null, false); } } class QueryRoot { - work = []; - db: IDBDatabase; - tx: IDBTransaction; - stores = new Set(); - kickoffPromise; + 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(storeName): QueryStreamBase { + iter(storeName): QueryStream { this.stores.add(storeName); return new IterQueryStream(this, storeName); } - iterOnly(storeName, key): QueryStreamBase { + iterOnly(storeName, key): QueryStream { this.stores.add(storeName); return new IterQueryStream(this, storeName, {only: key}); } - iterIndex(storeName, indexName, key) { + + iterIndex(storeName, indexName, key) { this.stores.add(storeName); return new IterQueryStream(this, storeName, {indexName: 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 { - this.stores.add(storeName); - function doPut() { - this.tx.objectStore(storeName).put(val); - } - - this.work.push(doPut.bind(this)); + 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 { - this.stores.add(storeName); - function doPutAll() { - for (let obj of iterable) { - this.tx.objectStore(storeName).put(obj); + const doPutAll = (tx: IDBTransaction) => { + for (const obj of iterable) { + tx.objectStore(storeName).put(obj); } - } - - this.work.push(doPutAll.bind(this)); + }; + 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 { - this.stores.add(storeName); - function doAdd() { - this.tx.objectStore(storeName).add(val); - } - - this.work.push(doAdd.bind(this)); + 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 { - this.stores.add(storeName); - let leakedResolve; - let p = new Promise((resolve, reject) => { - leakedResolve = resolve; - }); - if (!leakedResolve) { - // According to ES6 spec (paragraph 25.4.3.1), this can't happen. - throw Error("assertion failed"); - } - function doGet() { - let req = this.tx.objectStore(storeName).get(key); - req.onsuccess = (r) => { - leakedResolve(req.result); - }; - } + const {resolve, promise} = openPromise(); - this.work.push(doGet.bind(this)); - return Promise.resolve().then(() => { - return this.finish().then(() => p); - }); + 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); } + /** + * Finish the query, and start the query in the first place if necessary. + */ finish(): Promise { if (this.kickoffPromise) { return this.kickoffPromise; } this.kickoffPromise = new Promise((resolve, reject) => { - - this.tx = this.db.transaction(Array.from(this.stores), "readwrite"); - this.tx.oncomplete = () => { + 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(); + w(tx); } }); return this.kickoffPromise; } + /** + * Delete an object by from the given object store. + */ delete(storeName: string, key): QueryRoot { - this.stores.add(storeName); - function doDelete() { - this.tx.objectStore(storeName).delete(key); - } - - this.work.push(doDelete.bind(this)); + 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/timerThread.ts b/extension/lib/wallet/timerThread.ts deleted file mode 100644 index 6635da009..000000000 --- a/extension/lib/wallet/timerThread.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This file should be used as a WebWorker. - * Background pages in the WebExtensions model do - * not allow to schedule callbacks that should be called - * after a timeout. We can emulate this with WebWorkers. - */ - -onmessage = function(e) { - self.setInterval(() => postMessage(true, "timerThread"), e.data.interval); -}; \ No newline at end of file diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts index 46bae70a7..8dbcca044 100644 --- a/extension/lib/wallet/wallet.ts +++ b/extension/lib/wallet/wallet.ts @@ -462,8 +462,10 @@ export class Wallet { .then((mint) => this.updateReserve(reservePub, mint) .then((reserve) => this.depleteReserve(reserve, - mint)) - ); + mint))) + .catch((e) => { + console.error("Failed to deplete reserve", e.stack); + }); return resp; }); }); diff --git a/extension/lib/wallet/wxmessaging.js b/extension/lib/wallet/wxmessaging.js index 7a6f501be..d4df23a08 100644 --- a/extension/lib/wallet/wxmessaging.js +++ b/extension/lib/wallet/wxmessaging.js @@ -137,7 +137,6 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) { }; return ChromeBadge; }()); - wxMain(); } } }); diff --git a/extension/lib/wallet/wxmessaging.ts b/extension/lib/wallet/wxmessaging.ts index c69361c14..123746f4d 100644 --- a/extension/lib/wallet/wxmessaging.ts +++ b/extension/lib/wallet/wxmessaging.ts @@ -132,4 +132,4 @@ export function wxMain() { return false; }); }); -} +} \ No newline at end of file diff --git a/extension/tsconfig.json b/extension/tsconfig.json index e76d322af..7f98b3904 100644 --- a/extension/tsconfig.json +++ b/extension/tsconfig.json @@ -19,7 +19,6 @@ "lib/wallet/types.ts", "lib/commonHelpers.ts", "lib/polyfill-react.ts", - "lib/wallet/timerThread.ts", "content_scripts/notify.ts", "background/main.ts", "popup/balance-overview.tsx",