diff options
author | Christian Grothoff <christian@grothoff.org> | 2016-09-22 15:09:18 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2016-09-22 15:09:18 +0200 |
commit | 4974dd19c02778f0c58fad6cd12e839c9164e00d (patch) | |
tree | 69198fc1d53cb595ed1cb393e86ac7ef0038a309 /lib/wallet | |
parent | 903bb780dea2a4c538d2ffde3ba5a0a7f4497050 (diff) | |
parent | fca125a0da491e1753d2902d21a672559936922b (diff) | |
download | wallet-core-4974dd19c02778f0c58fad6cd12e839c9164e00d.tar.xz |
Merge branch 'master' of git+ssh://taler.net/var/git/wallet-webex
Diffstat (limited to 'lib/wallet')
-rw-r--r-- | lib/wallet/checkable.ts | 51 | ||||
-rw-r--r-- | lib/wallet/cryptoApi.ts | 7 | ||||
-rw-r--r-- | lib/wallet/cryptoLib.ts | 9 | ||||
-rw-r--r-- | lib/wallet/db.ts | 15 | ||||
-rw-r--r-- | lib/wallet/emscriptif.ts | 55 | ||||
-rw-r--r-- | lib/wallet/helpers.ts | 8 | ||||
-rw-r--r-- | lib/wallet/http.ts | 6 | ||||
-rw-r--r-- | lib/wallet/query.ts | 140 | ||||
-rw-r--r-- | lib/wallet/types.ts | 2 | ||||
-rw-r--r-- | lib/wallet/wallet.ts | 69 | ||||
-rw-r--r-- | lib/wallet/wxMessaging.ts | 92 |
11 files changed, 284 insertions, 170 deletions
diff --git a/lib/wallet/checkable.ts b/lib/wallet/checkable.ts index a35634e81..9fd816578 100644 --- a/lib/wallet/checkable.ts +++ b/lib/wallet/checkable.ts @@ -25,18 +25,39 @@ */ export namespace Checkable { - export function SchemaError(message) { + + type Path = (number|string)[]; + + interface SchemaErrorConstructor { + new (err: string): SchemaError; + } + + interface SchemaError { + name: string; + message: string; + } + + interface Prop { + propertyKey: any; + checker: any; + type: any; + elementChecker?: any; + elementProp?: any; + } + + export let SchemaError = (function SchemaError(message: string) { this.name = 'SchemaError'; this.message = message; this.stack = (<any>new Error()).stack; - } + }) as any as SchemaErrorConstructor; + SchemaError.prototype = new Error; let chkSym = Symbol("checkable"); - function checkNumber(target, prop, path): any { + function checkNumber(target: any, prop: Prop, path: Path): any { if ((typeof target) !== "number") { throw new SchemaError(`expected number for ${path}`); } @@ -44,7 +65,7 @@ export namespace Checkable { } - function checkString(target, prop, path): any { + function checkString(target: any, prop: Prop, path: Path): any { if (typeof target !== "string") { throw new SchemaError(`expected string for ${path}, got ${typeof target} instead`); } @@ -52,7 +73,7 @@ export namespace Checkable { } - function checkAnyObject(target, prop, path): any { + function checkAnyObject(target: any, prop: Prop, path: Path): any { if (typeof target !== "object") { throw new SchemaError(`expected (any) object for ${path}, got ${typeof target} instead`); } @@ -60,12 +81,12 @@ export namespace Checkable { } - function checkAny(target, prop, path): any { + function checkAny(target: any, prop: Prop, path: Path): any { return target; } - function checkList(target, prop, path): any { + function checkList(target: any, prop: Prop, path: Path): any { if (!Array.isArray(target)) { throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`); } @@ -77,7 +98,7 @@ export namespace Checkable { } - function checkOptional(target, prop, path): any { + function checkOptional(target: any, prop: Prop, path: Path): any { console.assert(prop.propertyKey); prop.elementChecker(target, prop.elementProp, @@ -86,7 +107,7 @@ export namespace Checkable { } - function checkValue(target, prop, path): any { + function checkValue(target: any, prop: Prop, path: Path): any { let type = prop.type; if (!type) { throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`); @@ -123,8 +144,8 @@ export namespace Checkable { } - export function Class(target) { - target.checked = (v) => { + export function Class(target: any) { + target.checked = (v: any) => { return checkValue(v, { propertyKey: "(root)", type: target, @@ -135,7 +156,7 @@ export namespace Checkable { } - export function Value(type) { + export function Value(type: any) { if (!type) { throw Error("Type does not exist yet (wrong order of definitions?)"); } @@ -152,7 +173,7 @@ export namespace Checkable { } - export function List(type) { + export function List(type: any) { let stub = {}; type(stub, "(list-element)"); let elementProp = mkChk(stub).props[0]; @@ -174,7 +195,7 @@ export namespace Checkable { } - export function Optional(type) { + export function Optional(type: any) { let stub = {}; type(stub, "(optional-element)"); let elementProp = mkChk(stub).props[0]; @@ -230,7 +251,7 @@ export namespace Checkable { } - function mkChk(target) { + function mkChk(target: any) { let chk = target[chkSym]; if (!chk) { chk = {props: []}; diff --git a/lib/wallet/cryptoApi.ts b/lib/wallet/cryptoApi.ts index a0113b2ea..2a2a7d319 100644 --- a/lib/wallet/cryptoApi.ts +++ b/lib/wallet/cryptoApi.ts @@ -27,9 +27,10 @@ import {Denomination} from "./types"; import {Offer} from "./wallet"; import {CoinWithDenom} from "./wallet"; import {PayCoinInfo} from "./types"; +type RegistryEntry = {resolve: any; reject: any}; export class CryptoApi { private nextRpcId: number = 1; - private rpcRegistry = {}; + private rpcRegistry: {[n: number]: RegistryEntry} = {}; private cryptoWorker: Worker; @@ -52,14 +53,14 @@ export class CryptoApi { } - private registerRpcId(resolve, reject): number { + private registerRpcId(resolve: any, reject: any): number { let id = this.nextRpcId++; this.rpcRegistry[id] = {resolve, reject}; return id; } - private doRpc<T>(methodName: string, ...args): Promise<T> { + private doRpc<T>(methodName: string, ...args: any[]): Promise<T> { return new Promise<T>((resolve, reject) => { let msg = { operation: methodName, diff --git a/lib/wallet/cryptoLib.ts b/lib/wallet/cryptoLib.ts index 1acbdaa16..1ca7b856f 100644 --- a/lib/wallet/cryptoLib.ts +++ b/lib/wallet/cryptoLib.ts @@ -28,6 +28,7 @@ import {Offer} from "./wallet"; import {CoinWithDenom} from "./wallet"; import {CoinPaySig} from "./types"; import {Denomination} from "./types"; +import {Amount} from "./emscriptif"; export function main(worker: Worker) { @@ -43,7 +44,7 @@ export function main(worker: Worker) { if (typeof msg.data.operation != "string") { console.error("RPC operation must be string"); } - let f = RpcFunctions[msg.data.operation]; + let f = (RpcFunctions as any)[msg.data.operation]; if (!f) { console.error(`unknown operation: '${msg.data.operation}'`); return; @@ -156,7 +157,7 @@ namespace RpcFunctions { } - export function rsaUnblind(sig, bk, pk): string { + export function rsaUnblind(sig: string, bk: string, pk: string): string { let denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig), native.RsaBlindingKeySecret.fromCrock(bk), native.RsaPublicKey.fromCrock(pk)); @@ -170,11 +171,11 @@ namespace RpcFunctions { */ export function signDeposit(offer: Offer, cds: CoinWithDenom[]): PayCoinInfo { - let ret = []; + let ret: PayCoinInfo = []; 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; + let coinSpend: Amount; if (amountRemaining.value == 0 && amountRemaining.fraction == 0) { break; diff --git a/lib/wallet/db.ts b/lib/wallet/db.ts index 6cd25ee27..5104f28fb 100644 --- a/lib/wallet/db.ts +++ b/lib/wallet/db.ts @@ -15,6 +15,7 @@ */ "use strict"; +import Dictionary = _.Dictionary; /** * Declarations and helpers for @@ -83,27 +84,27 @@ export function openTalerDb(): Promise<IDBDatabase> { } -export function exportDb(db): Promise<any> { +export function exportDb(db: IDBDatabase): Promise<any> { let dump = { name: db.name, version: db.version, - stores: {} + stores: {} as Dictionary<any>, }; return new Promise((resolve, reject) => { - let tx = db.transaction(db.objectStoreNames); - tx.addEventListener("complete", (e) => { + let tx = db.transaction(Array.from(db.objectStoreNames)); + tx.addEventListener("complete", () => { resolve(dump); }); for (let i = 0; i < db.objectStoreNames.length; i++) { let name = db.objectStoreNames[i]; - let storeDump = {}; + let storeDump = {} as Dictionary<any>; dump.stores[name] = storeDump; let store = tx.objectStore(name) .openCursor() - .addEventListener("success", (e) => { - let cursor = e.target.result; + .addEventListener("success", (e: Event) => { + let cursor = (e.target as any).result; if (cursor) { storeDump[cursor.key] = cursor.value; cursor.continue(); diff --git a/lib/wallet/emscriptif.ts b/lib/wallet/emscriptif.ts index 8540d15d0..1e5fb0283 100644 --- a/lib/wallet/emscriptif.ts +++ b/lib/wallet/emscriptif.ts @@ -36,11 +36,11 @@ const GNUNET_SYSERR = -1; let Module = EmscWrapper.Module; -let getEmsc: EmscWrapper.EmscFunGen = (...args) => Module.cwrap.apply(null, +let getEmsc: EmscWrapper.EmscFunGen = (...args: any[]) => Module.cwrap.apply(null, args); var emsc = { - free: (ptr) => Module._free(ptr), + free: (ptr: number) => Module._free(ptr), get_value: getEmsc('TALER_WR_get_value', 'number', ['number']), @@ -164,13 +164,12 @@ enum RandomQuality { abstract class ArenaObject { - private _nativePtr: number; + protected _nativePtr: number | undefined = undefined; arena: Arena; abstract destroy(): void; constructor(arena?: Arena) { - this.nativePtr = null; if (!arena) { if (arenaStack.length == 0) { throw Error("No arena available") @@ -192,14 +191,14 @@ abstract class ArenaObject { } free() { - if (this.nativePtr !== undefined) { + if (this.nativePtr) { emsc.free(this.nativePtr); - this.nativePtr = undefined; + this._nativePtr = undefined; } } alloc(size: number) { - if (this.nativePtr !== undefined) { + if (this._nativePtr !== undefined) { throw Error("Double allocation"); } this.nativePtr = emscAlloc.malloc(size); @@ -212,21 +211,22 @@ abstract class ArenaObject { this._nativePtr = n; } - set nativePtr(v) { + set nativePtr(v: number) { this.setNative(v); } get nativePtr() { return this.getNative(); } - } + interface Arena { put(obj: ArenaObject): void; destroy(): void; } + class DefaultArena implements Arena { heap: Array<ArenaObject>; @@ -234,7 +234,7 @@ class DefaultArena implements Arena { this.heap = []; } - put(obj) { + put(obj: ArenaObject) { this.heap.push(obj); } @@ -269,7 +269,7 @@ class SyncArena extends DefaultArena { super(); } - pub(obj) { + pub(obj: ArenaObject) { super.put(obj); if (!this.isScheduled) { this.schedule(); @@ -308,14 +308,12 @@ export class Amount extends ArenaObject { } destroy() { - if (this.nativePtr != 0) { - emsc.free(this.nativePtr); - } + super.free(); } static getZero(currency: string, a?: Arena): Amount { - let am = new Amount(null, a); + let am = new Amount(undefined, a); let r = emsc.amount_get_zero(currency, am.getNative()); if (r != GNUNET_OK) { throw Error("invalid currency"); @@ -442,7 +440,8 @@ abstract class PackedArenaObject extends ArenaObject { } alloc() { - if (this.nativePtr === null) { + // FIXME: should the client be allowed to call alloc multiple times? + if (!this._nativePtr) { this.nativePtr = emscAlloc.malloc(this.size()); } } @@ -466,7 +465,7 @@ abstract class PackedArenaObject extends ArenaObject { b = (b + 256) % 256; bytes.push("0".concat(b.toString(16)).slice(-2)); } - let lines = []; + let lines: string[] = []; for (let i = 0; i < bytes.length; i += 8) { lines.push(bytes.slice(i, i + 8).join(",")); } @@ -482,7 +481,7 @@ export class AmountNbo extends PackedArenaObject { toJson(): any { let a = new DefaultArena(); - let am = new Amount(null, a); + let am = new Amount(undefined, a); am.fromNbo(this); let json = am.toJson(); a.destroy(); @@ -508,7 +507,7 @@ export class EddsaPrivateKey extends PackedArenaObject { return obj; } - static fromCrock: (string) => EddsaPrivateKey; + static fromCrock: (s: string) => EddsaPrivateKey; } mixinStatic(EddsaPrivateKey, fromCrock); @@ -521,7 +520,7 @@ function fromCrock(s: string) { } -function mixin(obj, method, name?: string) { +function mixin(obj: any, method: any, name?: string) { if (!name) { name = method.name; } @@ -532,7 +531,7 @@ function mixin(obj, method, name?: string) { } -function mixinStatic(obj, method, name?: string) { +function mixinStatic(obj: any, method: any, name?: string) { if (!name) { name = method.name; } @@ -595,7 +594,7 @@ export class RsaBlindingKeySecret extends PackedArenaObject { return o; } - static fromCrock: (string) => RsaBlindingKeySecret; + static fromCrock: (s: string) => RsaBlindingKeySecret; } mixinStatic(RsaBlindingKeySecret, fromCrock); @@ -622,9 +621,9 @@ export class ByteArray extends PackedArenaObject { return this.allocatedSize; } - constructor(desiredSize: number, init: number, a?: Arena) { + constructor(desiredSize: number, init?: number, a?: Arena) { super(a); - if (init === undefined || init === null) { + if (init === undefined) { this.nativePtr = emscAlloc.malloc(desiredSize); } else { this.nativePtr = init; @@ -642,7 +641,7 @@ export class ByteArray extends PackedArenaObject { 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 ba = new ByteArray(decodedLen, undefined, a); let res = emsc.string_to_data(hstr, s.length, ba.nativePtr, decodedLen); emsc.free(hstr); if (res != GNUNET_OK) { @@ -777,7 +776,7 @@ export class AbsoluteTimeNbo extends PackedArenaObject { x.alloc(); let r = /Date\(([0-9]+)\)/; let m = r.exec(s); - if (m.length != 2) { + if (!m || m.length != 2) { throw Error(); } let n = parseInt(m[1]) * 1000000; @@ -899,11 +898,11 @@ interface Encodeable { encode(arena?: Arena): ByteArray; } -function makeEncode(encodeFn) { +function makeEncode(encodeFn: any) { function encode(arena?: Arena) { let ptr = emscAlloc.malloc(PTR_SIZE); let len = encodeFn(this.getNative(), ptr); - let res = new ByteArray(len, null, arena); + let res = new ByteArray(len, undefined, arena); res.setNative(Module.getValue(ptr, '*')); emsc.free(ptr); return res; diff --git a/lib/wallet/helpers.ts b/lib/wallet/helpers.ts index fd1650758..5d231fe64 100644 --- a/lib/wallet/helpers.ts +++ b/lib/wallet/helpers.ts @@ -24,7 +24,7 @@ import {AmountJson} from "./types"; -export function substituteFulfillmentUrl(url: string, vars) { +export function substituteFulfillmentUrl(url: string, vars: any) { url = url.replace("${H_contract}", vars.H_contract); url = url.replace("${$}", "$"); return url; @@ -42,7 +42,7 @@ export function amountToPretty(amount: AmountJson): string { * * See http://api.taler.net/wallet.html#general */ -export function canonicalizeBaseUrl(url) { +export function canonicalizeBaseUrl(url: string) { let x = new URI(url); if (!x.protocol()) { x.protocol("https"); @@ -54,10 +54,10 @@ export function canonicalizeBaseUrl(url) { } -export function parsePrettyAmount(pretty: string): AmountJson { +export function parsePrettyAmount(pretty: string): AmountJson|undefined { const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty); if (!res) { - return null; + return undefined; } return { value: parseInt(res[1], 10), diff --git a/lib/wallet/http.ts b/lib/wallet/http.ts index 60f388e4b..8f82ceaff 100644 --- a/lib/wallet/http.ts +++ b/lib/wallet/http.ts @@ -66,19 +66,19 @@ export class BrowserHttpLib { } - postJson(url: string|uri.URI, body) { + postJson(url: string|uri.URI, body: any) { return this.req("post", url, {req: JSON.stringify(body)}); } - postForm(url: string|uri.URI, form) { + postForm(url: string|uri.URI, form: any) { return this.req("post", url, {req: form}); } } export class RequestException { - constructor(detail) { + constructor(detail: any) { } } diff --git a/lib/wallet/query.ts b/lib/wallet/query.ts index 4eccb696b..c7420a3f7 100644 --- a/lib/wallet/query.ts +++ b/lib/wallet/query.ts @@ -24,7 +24,7 @@ "use strict"; -export function Query(db) { +export function Query(db: IDBDatabase) { return new QueryRoot(db); } @@ -36,24 +36,27 @@ export interface QueryStream<T> { indexJoin<S>(storeName: string, indexName: string, keyFn: (obj: any) => any): QueryStream<[T,S]>; - filter(f: (any) => boolean): QueryStream<T>; + filter(f: (x: any) => boolean): QueryStream<T>; reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>; - flatMap(f: (T) => T[]): QueryStream<T>; + flatMap(f: (x: T) => T[]): QueryStream<T>; } /** * 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; + let resolve: ((value?: T | PromiseLike<T>) => void) | null = null; + let reject: ((reason?: any) => void) | null = null; const promise = new Promise<T>((res, rej) => { resolve = res; reject = rej; }); + if (!(resolve && reject)) { + // Never happens, unless JS implementation is broken + throw Error(); + } return {resolve, reject, promise}; } @@ -61,7 +64,7 @@ function openPromise<T>() { abstract class QueryStreamBase<T> implements QueryStream<T> { abstract subscribe(f: (isDone: boolean, value: any, - tx: IDBTransaction) => void); + tx: IDBTransaction) => void): void; root: QueryRoot; @@ -69,30 +72,28 @@ abstract class QueryStreamBase<T> implements QueryStream<T> { this.root = root; } - flatMap(f: (T) => T[]): QueryStream<T> { + flatMap(f: (x: T) => T[]): QueryStream<T> { return new QueryStreamFlatMap(this, f); } indexJoin<S>(storeName: string, indexName: string, key: any): QueryStream<[T,S]> { - this.root.addWork(null, storeName, false); + this.root.addStoreAccess(storeName, false); return new QueryStreamIndexJoin(this, storeName, indexName, key); } - filter(f: (any) => boolean): QueryStream<T> { + filter(f: (x: any) => boolean): QueryStream<T> { return new QueryStreamFilter(this, f); } - reduce(f, acc?): Promise<any> { - let leakedResolve; - let p = new Promise((resolve, reject) => { - leakedResolve = resolve; - }); + reduce<A>(f: (x: any, acc?: A) => A, init?: A): Promise<any> { + let {resolve, promise} = openPromise(); + let acc = init; this.subscribe((isDone, value) => { if (isDone) { - leakedResolve(acc); + resolve(acc); return; } acc = f(value, acc); @@ -100,22 +101,28 @@ abstract class QueryStreamBase<T> implements QueryStream<T> { return Promise.resolve() .then(() => this.root.finish()) - .then(() => p); + .then(() => promise); } } +type FilterFn = (e: any) => boolean; +type SubscribeFn = (done: boolean, value: any, tx: IDBTransaction) => void; + +interface FlatMapFn<T> { + (v: T): T[]; +} class QueryStreamFilter<T> extends QueryStreamBase<T> { s: QueryStreamBase<T>; - filterFn; + filterFn: FilterFn; - constructor(s: QueryStreamBase<T>, filterFn) { + constructor(s: QueryStreamBase<T>, filterFn: FilterFn) { super(s.root); this.s = s; this.filterFn = filterFn; } - subscribe(f) { + subscribe(f: SubscribeFn) { this.s.subscribe((isDone, value, tx) => { if (isDone) { f(true, undefined, tx); @@ -131,15 +138,15 @@ class QueryStreamFilter<T> extends QueryStreamBase<T> { class QueryStreamFlatMap<T> extends QueryStreamBase<T> { s: QueryStreamBase<T>; - flatMapFn; + flatMapFn: (v: T) => T[]; - constructor(s: QueryStreamBase<T>, flatMapFn) { + constructor(s: QueryStreamBase<T>, flatMapFn: (v: T) => T[]) { super(s.root); this.s = s; - this.flatMap = flatMapFn; + this.flatMapFn = flatMapFn; } - subscribe(f) { + subscribe(f: SubscribeFn) { this.s.subscribe((isDone, value, tx) => { if (isDone) { f(true, undefined, tx); @@ -154,13 +161,13 @@ class QueryStreamFlatMap<T> extends QueryStreamBase<T> { } -class QueryStreamIndexJoin<T> extends QueryStreamBase<T> { +class QueryStreamIndexJoin<T,S> extends QueryStreamBase<[T, S]> { s: QueryStreamBase<T>; - storeName; - key; - indexName; + storeName: string; + key: any; + indexName: string; - constructor(s, storeName: string, indexName: string, key: any) { + constructor(s: QueryStreamBase<T>, storeName: string, indexName: string, key: any) { super(s.root); this.s = s; this.storeName = storeName; @@ -168,7 +175,7 @@ class QueryStreamIndexJoin<T> extends QueryStreamBase<T> { this.indexName = indexName; } - subscribe(f) { + subscribe(f: SubscribeFn) { this.s.subscribe((isDone, value, tx) => { if (isDone) { f(true, undefined, tx); @@ -192,31 +199,31 @@ class QueryStreamIndexJoin<T> extends QueryStreamBase<T> { class IterQueryStream<T> extends QueryStreamBase<T> { - private storeName; - private options; - private subscribers; + private storeName: string; + private options: any; + private subscribers: SubscribeFn[]; - constructor(qr, storeName, options) { + constructor(qr: QueryRoot, storeName: string, options: any) { super(qr); this.options = options; this.storeName = storeName; this.subscribers = []; - let doIt = (tx) => { + let doIt = (tx: IDBTransaction) => { const {indexName = void 0, only = void 0} = this.options; - let s; + let s: any; 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) { + let kr: IDBKeyRange|undefined = undefined; + if (only !== undefined) { kr = IDBKeyRange.only(this.options.only); } let req = s.openCursor(kr); - req.onsuccess = (e) => { + req.onsuccess = () => { let cursor: IDBCursorWithValue = req.result; if (cursor) { for (let f of this.subscribers) { @@ -231,32 +238,33 @@ class IterQueryStream<T> extends QueryStreamBase<T> { } }; - this.root.addWork(doIt, null, false); + this.root.addWork(doIt); } - subscribe(f) { + subscribe(f: SubscribeFn) { this.subscribers.push(f); } } class QueryRoot { - private work = []; + private work: ((t: IDBTransaction) => void)[] = []; private db: IDBDatabase; private stores = new Set(); - private kickoffPromise; + private kickoffPromise: Promise<void>; /** * Some operations is a write operation, * and we need to do a "readwrite" transaction/ */ - private hasWrite; + private hasWrite: boolean; - constructor(db) { + constructor(db: IDBDatabase) { this.db = db; } - iter<T>(storeName, {only = void 0, indexName = void 0} = {}): QueryStream<T> { + iter<T>(storeName: string, + {only = <string|undefined>undefined, indexName = <string|undefined>undefined} = {}): QueryStream<T> { this.stores.add(storeName); return new IterQueryStream(this, storeName, {only, indexName}); } @@ -266,7 +274,7 @@ class QueryRoot { * Overrides if an existing object with the same key exists * in the store. */ - put(storeName, val): QueryRoot { + put(storeName: string, val: any): QueryRoot { let doPut = (tx: IDBTransaction) => { tx.objectStore(storeName).put(val); }; @@ -280,7 +288,7 @@ class QueryRoot { * Fails if the object's key is already present * in the object store. */ - putAll(storeName, iterable): QueryRoot { + putAll(storeName: string, iterable: any[]): QueryRoot { const doPutAll = (tx: IDBTransaction) => { for (const obj of iterable) { tx.objectStore(storeName).put(obj); @@ -295,7 +303,7 @@ class QueryRoot { * Fails if the object's key is already present * in the object store. */ - add(storeName, val): QueryRoot { + add(storeName: string, val: any): QueryRoot { const doAdd = (tx: IDBTransaction) => { tx.objectStore(storeName).add(val); }; @@ -306,16 +314,16 @@ class QueryRoot { /** * Get one object from a store by its key. */ - get(storeName, key): Promise<any> { + get(storeName: any, key: any): Promise<any> { if (key === void 0) { throw Error("key must not be undefined"); } const {resolve, promise} = openPromise(); - const doGet = (tx) => { + const doGet = (tx: IDBTransaction) => { const req = tx.objectStore(storeName).get(key); - req.onsuccess = (r) => { + req.onsuccess = () => { resolve(req.result); }; }; @@ -329,16 +337,16 @@ class QueryRoot { /** * Get one object from a store by its key. */ - getIndexed(storeName, indexName, key): Promise<any> { + getIndexed(storeName: string, indexName: string, key: any): Promise<any> { if (key === void 0) { throw Error("key must not be undefined"); } const {resolve, promise} = openPromise(); - const doGetIndexed = (tx) => { + const doGetIndexed = (tx: IDBTransaction) => { const req = tx.objectStore(storeName).index(indexName).get(key); - req.onsuccess = (r) => { + req.onsuccess = () => { resolve(req.result); }; }; @@ -356,7 +364,7 @@ class QueryRoot { if (this.kickoffPromise) { return this.kickoffPromise; } - this.kickoffPromise = new Promise((resolve, reject) => { + this.kickoffPromise = new Promise<void>((resolve, reject) => { if (this.work.length == 0) { resolve(); return; @@ -376,8 +384,8 @@ class QueryRoot { /** * Delete an object by from the given object store. */ - delete(storeName: string, key): QueryRoot { - const doDelete = (tx) => { + delete(storeName: string, key: any): QueryRoot { + const doDelete = (tx: IDBTransaction) => { tx.objectStore(storeName).delete(key); }; this.addWork(doDelete, storeName, true); @@ -387,17 +395,21 @@ class QueryRoot { /** * Low-level function to add a task to the internal work queue. */ - addWork(workFn: (IDBTransaction) => void, - storeName: string, - isWrite: boolean) { + addWork(workFn: (t: IDBTransaction) => void, + storeName?: string, + isWrite?: boolean) { + this.work.push(workFn); + if (storeName) { + this.addStoreAccess(storeName, isWrite); + } + } + + addStoreAccess(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/lib/wallet/types.ts b/lib/wallet/types.ts index 2434dca32..e8b7a1e39 100644 --- a/lib/wallet/types.ts +++ b/lib/wallet/types.ts @@ -392,5 +392,5 @@ export interface CheckRepurchaseResult { export interface Notifier { - notify(); + notify(): void; } diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts index 9edad2c5c..367c9cbcd 100644 --- a/lib/wallet/wallet.ts +++ b/lib/wallet/wallet.ts @@ -154,12 +154,12 @@ interface Transaction { export interface Badge { setText(s: string): void; setColor(c: string): void; - startBusy(); - stopBusy(); + startBusy(): void; + stopBusy(): void; } -function deepEquals(x, y) { +function deepEquals(x: any, y: any): boolean { if (x === y) { return true; } @@ -179,7 +179,7 @@ function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] { } -function getTalerStampSec(stamp: string): number { +function getTalerStampSec(stamp: string): number|null { const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/); if (!m) { return null; @@ -188,7 +188,7 @@ function getTalerStampSec(stamp: string): number { } -function setTimeout(f, t) { +function setTimeout(f: any, t: number) { return chrome.extension.getBackgroundPage().setTimeout(f, t); } @@ -211,13 +211,13 @@ interface HttpRequestLibrary { get(url: string|uri.URI): Promise<HttpResponse>; - postJson(url: string|uri.URI, body): Promise<HttpResponse>; + postJson(url: string|uri.URI, body: any): Promise<HttpResponse>; - postForm(url: string|uri.URI, form): Promise<HttpResponse>; + postForm(url: string|uri.URI, form: any): Promise<HttpResponse>; } -function copy(o) { +function copy(o: any) { return JSON.parse(JSON.stringify(o)); } @@ -240,11 +240,18 @@ interface KeyUpdateInfo { function getWithdrawDenomList(amountAvailable: AmountJson, denoms: Denomination[]): Denomination[] { let remaining = Amounts.copy(amountAvailable); - let ds: Denomination[] = []; + const ds: Denomination[] = []; + + console.log("available denoms"); + console.log(denoms); denoms = denoms.filter(isWithdrawableDenom); denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value)); + console.log("withdrawable denoms"); + console.log(denoms); + + // This is an arbitrary number of coins // we can withdraw in one go. It's not clear if this limit // is useful ... @@ -355,7 +362,7 @@ export class Wallet { let x: number; - function storeExchangeCoin(mc, url) { + function storeExchangeCoin(mc: any, url: string) { let exchange: IExchangeInfo = mc[0]; console.log("got coin for exchange", url); let coin: Coin = mc[1]; @@ -366,18 +373,16 @@ export class Wallet { exchange.baseUrl); return; } - let cd = { - coin: coin, - denom: exchange.active_denoms.find((e) => e.denom_pub === coin.denomPub) - }; - if (!cd.denom) { + let denom = exchange.active_denoms.find((e) => e.denom_pub === coin.denomPub); + if (!denom) { console.warn("denom not found (database inconsistent)"); return; } - if (cd.denom.value.currency !== paymentAmount.currency) { + if (denom.value.currency !== paymentAmount.currency) { console.warn("same pubkey for different currencies"); return; } + let cd = {coin, denom}; let x = m[url]; if (!x) { m[url] = [cd]; @@ -464,7 +469,7 @@ export class Wallet { private recordConfirmPay(offer: Offer, payCoinInfo: PayCoinInfo, chosenExchange: string): Promise<void> { - let payReq = {}; + let payReq: any = {}; payReq["amount"] = offer.contract.amount; payReq["coins"] = payCoinInfo.map((x) => x.sig); payReq["H_contract"] = offer.H_contract; @@ -581,7 +586,7 @@ export class Wallet { * Retrieve all necessary information for looking up the contract * with the given hash. */ - executePayment(H_contract): Promise<any> { + executePayment(H_contract: string): Promise<any> { return Promise.resolve().then(() => { return Query(this.db) .get("transactions", H_contract) @@ -607,7 +612,7 @@ export class Wallet { * First fetch information requred to withdraw from the reserve, * then deplete the reserve, withdrawing coins until it is empty. */ - private processReserve(reserveRecord): void { + private processReserve(reserveRecord: any): void { let retryDelayMs = 100; const opId = "reserve-" + reserveRecord.reserve_pub; this.startOperation(opId); @@ -637,7 +642,7 @@ export class Wallet { } - private processPreCoin(preCoin, retryDelayMs = 100): void { + private processPreCoin(preCoin: any, retryDelayMs = 100): void { this.withdrawExecute(preCoin) .then((c) => this.storeCoin(c)) .catch((e) => { @@ -803,7 +808,7 @@ export class Wallet { /** * Withdraw coins from a reserve until it is empty. */ - private depleteReserve(reserve, exchange: IExchangeInfo): Promise<void> { + private depleteReserve(reserve: any, exchange: IExchangeInfo): Promise<void> { let denomsAvailable: Denomination[] = copy(exchange.active_denoms); let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount, denomsAvailable); @@ -912,7 +917,7 @@ export class Wallet { * Optionally link the reserve entry to the new or existing * exchange entry in then DB. */ - updateExchangeFromUrl(baseUrl): Promise<IExchangeInfo> { + updateExchangeFromUrl(baseUrl: string): Promise<IExchangeInfo> { baseUrl = canonicalizeBaseUrl(baseUrl); let reqUrl = URI("keys").absoluteTo(baseUrl); return this.http.get(reqUrl).then((resp) => { @@ -927,8 +932,8 @@ export class Wallet { private updateExchangeFromJson(baseUrl: string, exchangeKeysJson: KeysJson): Promise<IExchangeInfo> { - let updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date); - if (!updateTimeSec) { + const updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date); + if (updateTimeSec === null) { throw Error("invalid update time"); } @@ -947,7 +952,7 @@ export class Wallet { console.log("making fresh exchange"); } else { if (updateTimeSec < r.last_update_time) { - console.log("outdated /keys, not updating") + console.log("outdated /keys, not updating"); return Promise.resolve(r); } exchangeInfo = r; @@ -966,9 +971,9 @@ export class Wallet { {indexName: "exchangeBaseUrl", only: baseUrl}) .reduce((coin: Coin, suspendedCoins: Coin[]) => { if (!updatedExchangeInfo.active_denoms.find((c) => c.denom_pub == coin.denomPub)) { - return [].concat(suspendedCoins, [coin]); + return Array.prototype.concat(suspendedCoins, [coin]); } - return [].concat(suspendedCoins); + return Array.prototype.concat(suspendedCoins); }, []) .then((suspendedCoins: Coin[]) => { let q = Query(this.db); @@ -999,8 +1004,8 @@ export class Wallet { let found = false; for (let oldDenom of exchangeInfo.all_denoms) { if (oldDenom.denom_pub === newDenom.denom_pub) { - let a = Object.assign({}, oldDenom); - let b = Object.assign({}, newDenom); + let a: any = Object.assign({}, oldDenom); + let b: any = Object.assign({}, newDenom); // pub hash is only there for convenience in the wallet delete a["pub_hash"]; delete b["pub_hash"]; @@ -1048,7 +1053,7 @@ export class Wallet { * that is currenctly available for spending in the wallet. */ getBalances(): Promise<any> { - function collectBalances(c: Coin, byCurrency) { + function collectBalances(c: Coin, byCurrency: any) { if (c.suspended) { return byCurrency; } @@ -1074,7 +1079,7 @@ export class Wallet { * Retrive the full event history for this wallet. */ getHistory(): Promise<any> { - function collect(x, acc) { + function collect(x: any, acc: any) { acc.push(x); return acc; } @@ -1099,7 +1104,7 @@ export class Wallet { [contract.merchant_pub, contract.repurchase_correlation_id]) .then((result: Transaction) => { console.log("db result", result); - let isRepurchase; + let isRepurchase: boolean; if (result) { console.assert(result.contract.repurchase_correlation_id == contract.repurchase_correlation_id); return { diff --git a/lib/wallet/wxMessaging.ts b/lib/wallet/wxMessaging.ts index a8dc27524..9c08b20ca 100644 --- a/lib/wallet/wxMessaging.ts +++ b/lib/wallet/wxMessaging.ts @@ -15,7 +15,13 @@ */ -import {Wallet, Offer, Badge, ConfirmReserveRequest, CreateReserveRequest} from "./wallet"; +import { + Wallet, + Offer, + Badge, + ConfirmReserveRequest, + CreateReserveRequest +} from "./wallet"; import {deleteDb, exportDb, openTalerDb} from "./db"; import {BrowserHttpLib} from "./http"; import {Checkable} from "./checkable"; @@ -48,11 +54,17 @@ function makeHandlers(db: IDBDatabase, return exportDb(db); }, ["ping"]: function(detail, sender) { - return Promise.resolve({}); + if (!sender || !sender.tab || !sender.tab.id) { + return Promise.resolve(); + } + let id: number = sender.tab.id; + let info: any = <any>paymentRequestCookies[id]; + delete paymentRequestCookies[id]; + return Promise.resolve(info); }, ["reset"]: function(detail, sender) { if (db) { - let tx = db.transaction(db.objectStoreNames, 'readwrite'); + let tx = db.transaction(Array.from(db.objectStoreNames), 'readwrite'); for (let i = 0; i < db.objectStoreNames.length; i++) { tx.objectStore(db.objectStoreNames[i]).clear(); } @@ -81,7 +93,7 @@ function makeHandlers(db: IDBDatabase, return wallet.confirmReserve(req); }, ["confirm-pay"]: function(detail, sender) { - let offer; + let offer: Offer; try { offer = Offer.checked(detail.offer); } catch (e) { @@ -100,7 +112,7 @@ function makeHandlers(db: IDBDatabase, return wallet.confirmPay(offer); }, ["check-pay"]: function(detail, sender) { - let offer; + let offer: Offer; try { offer = Offer.checked(detail.offer); } catch (e) { @@ -173,14 +185,14 @@ class ChromeBadge implements Badge { } -function dispatch(handlers, req, sender, sendResponse) { +function dispatch(handlers: any, req: any, sender: any, sendResponse: any) { if (req.type in handlers) { Promise .resolve() .then(() => { const p = handlers[req.type](req.detail, sender); - return p.then((r) => { + return p.then((r: any) => { sendResponse(r); }) }) @@ -231,12 +243,58 @@ class ChromeNotifier implements Notifier { } +/** + * Mapping from tab ID to payment information (if any). + */ +let paymentRequestCookies: {[n: number]: any} = {}; + +function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], + url: string, tabId: number): any { + const headers: {[s: string]: string} = {}; + for (let kv of headerList) { + if (kv.value) { + headers[kv.name.toLowerCase()] = kv.value; + } + } + + const contractUrl = headers["x-taler-contract-url"]; + if (contractUrl !== undefined) { + paymentRequestCookies[tabId] = {type: "fetch", contractUrl}; + return; + } + + const contractHash = headers["x-taler-contract-hash"]; + + if (contractHash !== undefined) { + const payUrl = headers["x-taler-pay-url"]; + if (payUrl === undefined) { + console.log("malformed 402, X-Taler-Pay-Url missing"); + return; + } + + // Offer URL is optional + const offerUrl = headers["x-taler-offer-url"]; + paymentRequestCookies[tabId] = { + type: "execute", + offerUrl, + payUrl, + contractHash + }; + return; + } + + // looks like it's not a taler request, it might be + // for a different payment system (or the shop is buggy) + console.log("ignoring non-taler 402 response"); +} + + export function wxMain() { chrome.browserAction.setBadgeText({text: ""}); chrome.tabs.query({}, function(tabs) { for (let tab of tabs) { - if (!tab.url) { + if (!tab.url || !tab.id) { return; } let uri = URI(tab.url); @@ -255,11 +313,14 @@ export function wxMain() { console.error("could not open database"); console.error(e); }) - .then((db) => { + .then((db: IDBDatabase) => { let http = new BrowserHttpLib(); let badge = new ChromeBadge(); let notifier = new ChromeNotifier(); let wallet = new Wallet(db, http, badge, notifier); + + // Handlers for messages coming directly from the content + // script on the page let handlers = makeHandlers(db, wallet); chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { try { @@ -276,6 +337,19 @@ export function wxMain() { return false; } }); + + // Handlers for catching HTTP requests + chrome.webRequest.onHeadersReceived.addListener((details) => { + if (details.statusCode != 402) { + return; + } + console.log(`got 402 from ${details.url}`); + return handleHttpPayment(details.responseHeaders || [], + details.url, + details.tabId); + }, {urls: ["<all_urls>"]}, ["responseHeaders", "blocking"]); + + }) .catch((e) => { console.error("could not initialize wallet messaging"); |