diff options
| -rw-r--r-- | extension/lib/commonHelpers.ts | 6 | ||||
| -rw-r--r-- | extension/lib/emscripten/emsc.d.ts | 16 | ||||
| -rw-r--r-- | extension/lib/polyfill-react.ts | 23 | ||||
| -rw-r--r-- | extension/lib/wallet/emscriptif.ts | 34 | ||||
| -rw-r--r-- | extension/lib/wallet/http.ts | 5 | ||||
| -rw-r--r-- | extension/lib/wallet/query.ts | 237 | ||||
| -rw-r--r-- | extension/lib/wallet/timerThread.ts | 10 | ||||
| -rw-r--r-- | extension/lib/wallet/wallet.ts | 6 | ||||
| -rw-r--r-- | extension/lib/wallet/wxmessaging.js | 1 | ||||
| -rw-r--r-- | extension/lib/wallet/wxmessaging.ts | 2 | ||||
| -rw-r--r-- | extension/tsconfig.json | 1 | 
11 files changed, 214 insertions, 127 deletions
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 <http://www.gnu.org/licenses/>   */ -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 <http://www.gnu.org/licenses/> + */ + +/** + * 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<HttpResponse> { +      url: string|uri.URI, +      options?: any): Promise<HttpResponse> {      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<T> { +  indexJoin<S>(storeName: string, +               indexName: string, +               keyFn: (obj: any) => any): QueryStream<[T,S]>; +  filter(f: (any) => boolean): QueryStream<T>; +  reduce<S>(f: (S, T) => S, acc?: S): Promise<S>; +} -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<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; @@ -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<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): QueryStreamBase { +  filter(f: (any) => boolean): QueryStream<T> {      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<T> extends QueryStreamBase<T> { +  s: QueryStreamBase<T>;    filterFn; -  constructor(s: QueryStreamBase, filterFn) { +  constructor(s: QueryStreamBase<T>, 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<T> extends QueryStreamBase<T> { +  s: QueryStreamBase<T>;    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<T> extends QueryStreamBase<T> {    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<T>(storeName): QueryStream<T> {      this.stores.add(storeName);      return new IterQueryStream(this, storeName);    } -  iterOnly(storeName, key): QueryStreamBase { +  iterOnly<T>(storeName, key): QueryStream<T> {      this.stores.add(storeName);      return new IterQueryStream(this, storeName, {only: key});    } -  iterIndex(storeName, indexName, key) { + +  iterIndex<T>(storeName, indexName, key) {      this.stores.add(storeName);      return new IterQueryStream(this, storeName, {indexName: indexName});    } -  put(storeName, val): QueryRoot { -    this.stores.add(storeName); -    function doPut() { -      this.tx.objectStore(storeName).put(val); -    } -    this.work.push(doPut.bind(this)); +  /** +   * 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 { -    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<any> { -    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); +    const {resolve, promise} = openPromise(); + +    const doGet = (tx) => { +      const req = tx.objectStore(storeName).get(key);        req.onsuccess = (r) => { -        leakedResolve(req.result); +        resolve(req.result);        }; -    } +    }; -    this.work.push(doGet.bind(this)); -    return Promise.resolve().then(() => { -      return this.finish().then(() => p); -    }); +    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<void> {      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",  | 
