The great modularization.

Use ES6 module syntax and SystemJS modules for everything.

Some testing stubs were added as well.
This commit is contained in:
Florian Dold 2016-01-10 20:07:42 +01:00
parent dd19e0ecbe
commit 473503a246
50 changed files with 24152 additions and 1557 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.zip
*.xpi

View File

@ -1,3 +1,7 @@
node_modules/ node_modules/
*.js.map *.js.map
background/*.js background/*.js
lib/wallet/*.js
lib/*.js
popup/*.js
test/tests/*.js

View File

@ -1,77 +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/>
*/
"use strict";
const DB_NAME = "taler";
const DB_VERSION = 1;
/**
* Return a promise that resolves
* to the taler wallet db.
*/
function openTalerDb() {
return new Promise((resolve, reject) => {
let 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:
let mints = db.createObjectStore("mints", { keyPath: "baseUrl" });
mints.createIndex("pubKey", "keys.master_public_key");
db.createObjectStore("reserves", { keyPath: "reserve_pub" });
db.createObjectStore("denoms", { keyPath: "denomPub" });
let coins = db.createObjectStore("coins", { keyPath: "coinPub" });
coins.createIndex("mintBaseUrl", "mintBaseUrl");
db.createObjectStore("transactions", { keyPath: "contractHash" });
db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true });
db.createObjectStore("history", { keyPath: "id", autoIncrement: true });
break;
}
};
});
}
function exportDb(db) {
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();
}
});
}
});
}

View File

@ -1,643 +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 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 getEmsc = (...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']),
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) => Module._malloc(size),
};
var SignaturePurpose;
(function (SignaturePurpose) {
SignaturePurpose[SignaturePurpose["RESERVE_WITHDRAW"] = 1200] = "RESERVE_WITHDRAW";
SignaturePurpose[SignaturePurpose["WALLET_COIN_DEPOSIT"] = 1201] = "WALLET_COIN_DEPOSIT";
})(SignaturePurpose || (SignaturePurpose = {}));
var RandomQuality;
(function (RandomQuality) {
RandomQuality[RandomQuality["WEAK"] = 0] = "WEAK";
RandomQuality[RandomQuality["STRONG"] = 1] = "STRONG";
RandomQuality[RandomQuality["NONCE"] = 2] = "NONCE";
})(RandomQuality || (RandomQuality = {}));
class ArenaObject {
constructor(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() {
// 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) {
if (this.nativePtr !== undefined) {
throw Error("Double allocation");
}
this.nativePtr = emscAlloc.malloc(size);
}
setNative(n) {
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();
}
}
class DefaultArena {
constructor() {
this.heap = [];
}
put(obj) {
this.heap.push(obj);
}
destroy() {
for (let obj of this.heap) {
obj.destroy();
}
this.heap = [];
}
}
/**
* Arena that destroys all its objects once control has returned to the message
* loop and a small interval has passed.
*/
class SyncArena extends DefaultArena {
constructor() {
super();
let me = this;
this.timer = new Worker('background/timerThread.js');
this.timer.onmessage = () => {
this.destroy();
};
//this.timer.postMessage({interval: 50});
}
destroy() {
super.destroy();
}
}
let arenaStack = [];
arenaStack.push(new SyncArena());
class Amount extends ArenaObject {
constructor(args, 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, a) {
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) {
let x = new AmountNbo(a);
x.alloc();
emsc.amount_hton(x.nativePtr, this.nativePtr);
return x;
}
fromNbo(nbo) {
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() {
return emsc.get_currency(this.nativePtr);
}
toJson() {
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) {
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) {
// 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) {
return emsc.amount_cmp(this.nativePtr, a.nativePtr);
}
normalize() {
emsc.amount_normalize(this.nativePtr);
}
}
class PackedArenaObject extends ArenaObject {
constructor(a) {
super(a);
}
toCrock() {
var d = emscAlloc.data_to_string_alloc(this.nativePtr, this.size());
var s = Module.Pointer_stringify(d);
emsc.free(d);
return s;
}
toJson() {
// 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) {
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() {
var x = new HashCode();
x.alloc();
emsc.hash(this.nativePtr, this.size(), x.nativePtr);
return x;
}
hexdump() {
let bytes = [];
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");
}
}
class AmountNbo extends PackedArenaObject {
size() {
return 24;
}
toJson() {
let a = new DefaultArena();
let am = new Amount(null, a);
am.fromNbo(this);
let json = am.toJson();
a.destroy();
return json;
}
}
class EddsaPrivateKey extends PackedArenaObject {
static create(a) {
let obj = new EddsaPrivateKey(a);
obj.nativePtr = emscAlloc.eddsa_key_create();
return obj;
}
size() {
return 32;
}
getPublicKey(a) {
let obj = new EddsaPublicKey(a);
obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr);
return obj;
}
}
mixinStatic(EddsaPrivateKey, fromCrock);
function fromCrock(s) {
let x = new this();
x.alloc();
x.loadCrock(s);
return x;
}
function mixin(obj, method, name) {
if (!name) {
name = method.name;
}
if (!name) {
throw Error("Mixin needs a name.");
}
obj.prototype[method.name] = method;
}
function mixinStatic(obj, method, name) {
if (!name) {
name = method.name;
}
if (!name) {
throw Error("Mixin needs a name.");
}
obj[method.name] = method;
}
class EddsaPublicKey extends PackedArenaObject {
size() {
return 32;
}
}
mixinStatic(EddsaPublicKey, fromCrock);
function makeFromCrock(decodeFn) {
function fromCrock(s, a) {
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) {
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;
}
class RsaBlindingKey extends ArenaObject {
constructor(...args) {
super(...args);
this.toCrock = makeToCrock(emscAlloc.rsa_blinding_key_encode);
}
static create(len, a) {
let o = new RsaBlindingKey(a);
o.nativePtr = emscAlloc.rsa_blinding_key_create(len);
return o;
}
destroy() {
// TODO
}
}
mixinStatic(RsaBlindingKey, makeFromCrock(emscAlloc.rsa_blinding_key_decode));
class HashCode extends PackedArenaObject {
size() {
return 64;
}
random(qualStr) {
let qual;
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(format("unknown crypto quality: {0}", qual));
}
this.alloc();
emsc.hash_create_random(qual, this.nativePtr);
}
}
mixinStatic(HashCode, fromCrock);
class ByteArray extends PackedArenaObject {
constructor(desiredSize, init, a) {
super(a);
if (init === undefined || init === null) {
this.nativePtr = emscAlloc.malloc(desiredSize);
}
else {
this.nativePtr = init;
}
this.allocatedSize = desiredSize;
}
size() {
return this.allocatedSize;
}
static fromString(s, a) {
let hstr = emscAlloc.malloc(s.length + 1);
Module.writeStringToMemory(s, hstr);
return new ByteArray(s.length, hstr, a);
}
static fromCrock(s, a) {
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;
}
}
class EccSignaturePurpose extends PackedArenaObject {
constructor(purpose, payload, a) {
super(a);
this.nativePtr = emscAlloc.purpose_create(purpose, payload.nativePtr, payload.size());
this.payloadSize = payload.size();
}
size() {
return this.payloadSize + 8;
}
}
class SignatureStruct {
constructor(x) {
this.members = {};
for (let k in x) {
this.set(k, x[k]);
}
}
toPurpose(a) {
let totalSize = 0;
for (let f of this.fieldTypes()) {
let name = f[0];
let member = this.members[name];
if (!member) {
throw Error(format("Member {0} not set", name));
}
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 = {};
for (let f of this.fieldTypes()) {
let name = f[0];
let member = this.members[name];
if (!member) {
throw Error(format("Member {0} not set", name));
}
res[name] = member.toJson();
}
res["purpose"] = this.purpose();
return res;
}
set(name, value) {
let typemap = {};
for (let f of this.fieldTypes()) {
typemap[f[0]] = f[1];
}
if (!(name in typemap)) {
throw Error(format("Key {0} not found", name));
}
if (!(value instanceof typemap[name])) {
throw Error(format("Wrong type for {0}", name));
}
this.members[name] = value;
}
}
class WithdrawRequestPS extends SignatureStruct {
constructor(w) {
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]
];
}
}
class AbsoluteTimeNbo extends PackedArenaObject {
static fromTalerString(s) {
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, n) {
for (let i = 0; i < 8; ++i) {
Module.setValue(p + (7 - i), n & 0xFF, "i8");
n = Math.floor(n / 256);
}
}
class UInt64 extends PackedArenaObject {
static fromNumber(n) {
let x = new UInt64();
x.alloc();
set64(x.getNative(), n);
return x;
}
size() {
return 8;
}
}
class DepositRequestPS extends SignatureStruct {
constructor(w) {
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],
];
}
}
function makeEncode(encodeFn) {
function encode(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;
}
class RsaPublicKey extends ArenaObject {
toCrock() {
return this.encode().toCrock();
}
destroy() {
emsc.rsa_public_key_free(this.nativePtr);
this.nativePtr = 0;
}
}
mixinStatic(RsaPublicKey, makeFromCrock(emscAlloc.rsa_public_key_decode));
mixin(RsaPublicKey, makeEncode(emscAlloc.rsa_public_key_encode));
class EddsaSignature extends PackedArenaObject {
size() {
return 64;
}
}
class RsaSignature extends ArenaObject {
destroy() {
emsc.rsa_signature_free(this.getNative());
this.setNative(0);
}
}
mixinStatic(RsaSignature, makeFromCrock(emscAlloc.rsa_signature_decode));
mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode));
function rsaBlind(hashCode, blindingKey, pkey, arena) {
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);
}
function eddsaSign(purpose, priv, a) {
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;
}
function rsaUnblind(sig, bk, pk, a) {
let x = new RsaSignature(a);
x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr, bk.nativePtr, pk.nativePtr);
return x;
}

View File

@ -0,0 +1,42 @@
/*
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/>
*/
// Entry point for the background page.
"use strict";
System.config({
defaultJSExtensions: true,
});
var Module: any;
if ("object" !== typeof Module) {
throw Error("emscripten not loaded, no 'Module' defined");
}
let mod = System.newModule({Module: Module});
let modName = System.normalizeSync("../lib/emscripten/emsc");
console.log("registering", modName);
System.set(modName, mod);
System.import("../lib/wallet/wxmessaging")
.then((wxmessaging) => {
wxmessaging.wxMain();
})
.catch((e) => {
console.error("import failed", e.stack);
});

View File

@ -1,502 +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
*/
/// <reference path="../decl/urijs/URIjs.d.ts" />
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
let AmountJson = class {
};
__decorate([
Checkable.Number
], AmountJson.prototype, "value", void 0);
__decorate([
Checkable.Number
], AmountJson.prototype, "fraction", void 0);
__decorate([
Checkable.String
], AmountJson.prototype, "currency", void 0);
AmountJson = __decorate([
Checkable.Class
], AmountJson);
let CoinPaySig = class {
};
__decorate([
Checkable.String
], CoinPaySig.prototype, "coin_sig", void 0);
__decorate([
Checkable.String
], CoinPaySig.prototype, "coin_pub", void 0);
__decorate([
Checkable.String
], CoinPaySig.prototype, "ub_sig", void 0);
__decorate([
Checkable.String
], CoinPaySig.prototype, "denom_pub", void 0);
__decorate([
Checkable.Value(AmountJson)
], CoinPaySig.prototype, "f", void 0);
CoinPaySig = __decorate([
Checkable.Class
], CoinPaySig);
/**
* See http://api.taler.net/wallet.html#general
*/
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();
}
function copy(o) {
return JSON.parse(JSON.stringify(o));
}
function rankDenom(denom1, denom2) {
// Slow ... we should find a better way than to convert it evert time.
let v1 = new Amount(denom1.value);
let v2 = new Amount(denom2.value);
return (-1) * v1.cmp(v2);
}
class Wallet {
constructor(db, http, badge) {
this.db = db;
this.http = http;
this.badge = badge;
}
static signDeposit(offer, cds) {
let ret = [];
let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency);
let amountRemaining = new Amount(offer.contract.amount);
cds = copy(cds);
for (let cd of cds) {
let coinSpend;
if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
break;
}
if (amountRemaining.cmp(new Amount(cd.coin.currentAmount)) < 0) {
coinSpend = new Amount(amountRemaining.toJson());
}
else {
coinSpend = new Amount(cd.coin.currentAmount);
}
amountSpent.add(coinSpend);
amountRemaining.sub(coinSpend);
let newAmount = new Amount(cd.coin.currentAmount);
newAmount.sub(coinSpend);
cd.coin.currentAmount = newAmount.toJson();
let args = {
h_contract: HashCode.fromCrock(offer.H_contract),
h_wire: HashCode.fromCrock(offer.contract.H_wire),
amount_with_fee: coinSpend.toNbo(),
coin_pub: EddsaPublicKey.fromCrock(cd.coin.coinPub),
deposit_fee: new Amount(cd.denom.fee_deposit).toNbo(),
merchant: EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
refund_deadline: AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
timestamp: AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
transaction_id: UInt64.fromNumber(offer.contract.transaction_id),
};
let d = new DepositRequestPS(args);
let coinSig = eddsaSign(d.toPurpose(), EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
.toCrock();
let s = {
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;
}
/**
* Get mints and associated coins that are still spendable,
* but only if the sum the coins' remaining value exceeds the payment amount.
* @param paymentAmount
* @param depositFeeLimit
* @param allowedMints
*/
getPossibleMintCoins(paymentAmount, depositFeeLimit, allowedMints) {
let m = {};
function storeMintCoin(mc) {
let mint = mc[0];
let coin = mc[1];
let cd = {
coin: coin,
denom: mint.keys.denoms.find((e) => e.denom_pub === coin.denomPub)
};
if (!cd.denom) {
throw Error("denom not found (database inconsistent)");
}
let x = m[mint.baseUrl];
if (!x) {
m[mint.baseUrl] = [cd];
}
else {
x.push(cd);
}
}
let ps = allowedMints.map((info) => {
return Query(this.db)
.iterIndex("mints", "pubKey", info.master_pub)
.indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
.reduce(storeMintCoin);
});
return Promise.all(ps).then(() => {
let ret = {};
nextMint: for (let key in m) {
let coins = m[key].map((x) => ({
a: new Amount(x.denom.fee_deposit),
c: x
}));
// Sort by ascending deposit fee
coins.sort((o1, o2) => o1.a.cmp(o2.a));
let maxFee = new Amount(depositFeeLimit);
let minAmount = new Amount(paymentAmount);
let accFee = new Amount(coins[0].c.denom.fee_deposit);
let accAmount = Amount.getZero(coins[0].c.coin.currentAmount.currency);
let usableCoins = [];
nextCoin: for (let i = 0; i < coins.length; i++) {
let coinAmount = new Amount(coins[i].c.coin.currentAmount);
let coinFee = coins[i].a;
if (coinAmount.cmp(coinFee) <= 0) {
continue nextCoin;
}
accFee.add(coinFee);
accAmount.add(coinAmount);
if (accFee.cmp(maxFee) >= 0) {
console.log("too much fees");
continue nextMint;
}
usableCoins.push(coins[i].c);
if (accAmount.cmp(minAmount) >= 0) {
ret[key] = usableCoins;
continue nextMint;
}
}
}
return ret;
});
}
executePay(offer, payCoinInfo, merchantBaseUrl, chosenMint) {
let payReq = {};
payReq["H_wire"] = offer.contract.H_wire;
payReq["H_contract"] = offer.H_contract;
payReq["transaction_id"] = offer.contract.transaction_id;
payReq["refund_deadline"] = offer.contract.refund_deadline;
payReq["mint"] = URI(chosenMint).href();
payReq["coins"] = payCoinInfo.map((x) => x.sig);
payReq["timestamp"] = offer.contract.timestamp;
let payUrl = URI(offer.pay_url).absoluteTo(merchantBaseUrl);
let t = {
contractHash: offer.H_contract,
contract: offer.contract,
payUrl: payUrl.href(),
payReq: payReq
};
return Query(this.db)
.put("transactions", t)
.putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
.finish();
}
confirmPay(offer, merchantPageUrl) {
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) {
throw Error("Not enough coins.");
}
let mintUrl = Object.keys(mcs)[0];
let ds = Wallet.signDeposit(offer, mcs[mintUrl]);
return this.executePay(offer, ds, merchantPageUrl, mintUrl);
});
}
doPayment(H_contract) {
return Promise.resolve().then(() => {
return Query(this.db)
.get("transactions", H_contract)
.then((t) => {
if (!t) {
throw Error("contract not found");
}
let resp = {
payUrl: t.payUrl,
payReq: t.payReq
};
return resp;
});
});
}
confirmReserve(req) {
let reservePriv = EddsaPrivateKey.create();
let reservePub = reservePriv.getPublicKey();
let form = new FormData();
let now = (new Date()).toString();
form.append(req.field_amount, req.amount_str);
form.append(req.field_reserve_pub, reservePub.toCrock());
form.append(req.field_mint, req.mint);
// TODO: set bank-specified fields.
let mintBaseUrl = canonicalizeBaseUrl(req.mint);
return this.http.postForm(req.post_url, form)
.then((hresp) => {
let resp = {
status: hresp.status,
text: hresp.responseText,
success: undefined,
backlink: undefined
};
let reserveRecord = {
reserve_pub: reservePub.toCrock(),
reserve_priv: reservePriv.toCrock(),
mint_base_url: mintBaseUrl,
created: now,
last_query: null,
current_amount: null,
// XXX: set to actual amount
initial_amount: null
};
if (hresp.status != 200) {
resp.success = false;
return resp;
}
resp.success = true;
// We can't show the page directly, so
// we show some generic page from the wallet.
resp.backlink = null;
return Query(this.db)
.put("reserves", reserveRecord)
.finish()
.then(() => {
// Do this in the background
this.updateMintFromUrl(reserveRecord.mint_base_url)
.then((mint) => this.updateReserve(reservePub, mint)
.then((reserve) => this.depleteReserve(reserve, mint)));
return resp;
});
});
}
withdrawPrepare(denom, reserve) {
let reservePriv = new EddsaPrivateKey();
reservePriv.loadCrock(reserve.reserve_priv);
let reservePub = new EddsaPublicKey();
reservePub.loadCrock(reserve.reserve_pub);
let denomPub = RsaPublicKey.fromCrock(denom.denom_pub);
let coinPriv = EddsaPrivateKey.create();
let coinPub = coinPriv.getPublicKey();
let blindingFactor = RsaBlindingKey.create(1024);
let pubHash = coinPub.hash();
let ev = rsaBlind(pubHash, blindingFactor, denomPub);
if (!denom.fee_withdraw) {
throw Error("Field fee_withdraw missing");
}
let amountWithFee = new Amount(denom.value);
amountWithFee.add(new Amount(denom.fee_withdraw));
let withdrawFee = new Amount(denom.fee_withdraw);
// Signature
let withdrawRequest = new 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 = eddsaSign(withdrawRequest.toPurpose(), reservePriv);
let 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 Query(this.db).put("precoins", preCoin).finish().then(() => preCoin);
}
withdrawExecute(pc) {
return Query(this.db)
.get("reserves", pc.reservePub)
.then((r) => {
let wd = {};
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);
let denomSig = rsaUnblind(RsaSignature.fromCrock(r.ev_sig), RsaBlindingKey.fromCrock(pc.blindingKey), RsaPublicKey.fromCrock(pc.denomPub));
let coin = {
coinPub: pc.coinPub,
coinPriv: pc.coinPriv,
denomPub: pc.denomPub,
denomSig: denomSig.encode().toCrock(),
currentAmount: pc.coinValue,
mintBaseUrl: pc.mintBaseUrl,
};
return coin;
});
}
updateBadge() {
function countNonEmpty(c, n) {
if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
return n + 1;
}
return n;
}
function doBadge(n) {
this.badge.setText(n.toString());
this.badge.setColor("#0F0");
}
Query(this.db)
.iter("coins")
.reduce(countNonEmpty, 0)
.then(doBadge.bind(this));
}
storeCoin(coin) {
Query(this.db)
.delete("precoins", coin.coinPub)
.add("coins", coin)
.finish()
.then(() => {
this.updateBadge();
});
}
withdraw(denom, reserve) {
return this.withdrawPrepare(denom, reserve)
.then((pc) => this.withdrawExecute(pc))
.then((c) => this.storeCoin(c));
}
/**
* Withdraw coins from a reserve until it is empty.
*/
depleteReserve(reserve, mint) {
let denoms = copy(mint.keys.denoms);
let remaining = new Amount(reserve.current_amount);
denoms.sort(rankDenom);
let workList = [];
for (let i = 0; i < 1000; i++) {
let found = false;
for (let d of denoms) {
let cost = new Amount(d.value);
cost.add(new Amount(d.fee_withdraw));
if (remaining.cmp(cost) < 0) {
continue;
}
found = true;
remaining.sub(cost);
workList.push(d);
}
if (!found) {
console.log("did not find coins for remaining ", remaining.toJson());
break;
}
}
// Do the request one by one.
let next = () => {
if (workList.length == 0) {
return;
}
let d = workList.pop();
this.withdraw(d, reserve)
.then(() => next());
};
// Asynchronous recursion
next();
}
updateReserve(reservePub, mint) {
let reservePubStr = reservePub.toCrock();
return Query(this.db)
.get("reserves", reservePubStr)
.then((reserve) => {
let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl);
reqUrl.query({ 'reserve_pub': reservePubStr });
return this.http.get(reqUrl).then(resp => {
if (resp.status != 200) {
throw Error();
}
let reserveInfo = JSON.parse(resp.responseText);
if (!reserveInfo) {
throw Error();
}
reserve.current_amount = reserveInfo.balance;
return Query(this.db)
.put("reserves", reserve)
.finish()
.then(() => reserve);
});
});
}
/**
* 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) {
let reqUrl = URI("keys").absoluteTo(baseUrl);
return this.http.get(reqUrl).then((resp) => {
if (resp.status != 200) {
throw Error("/keys request failed");
}
let mintKeysJson = JSON.parse(resp.responseText);
if (!mintKeysJson) {
throw new RequestException({ url: reqUrl, hint: "keys invalid" });
}
let mint = {
baseUrl: baseUrl,
keys: mintKeysJson
};
return Query(this.db).put("mints", mint).finish().then(() => mint);
});
}
getBalances() {
function collectBalances(c, byCurrency) {
let acc = byCurrency[c.currentAmount.currency];
if (!acc) {
acc = Amount.getZero(c.currentAmount.currency).toJson();
}
let am = new Amount(c.currentAmount);
am.add(new Amount(acc));
byCurrency[c.currentAmount.currency] = am.toJson();
return byCurrency;
}
return Query(this.db)
.iter("coins")
.reduce(collectBalances, {});
}
}

View File

@ -15,29 +15,30 @@
*/ */
// Script that is injected into pages in order to allow merchants pages to // Script that is injected into pages in order to allow merchants pages to
// query the availability of Taler. // query the availability of Taler.
/// <reference path="../lib/decl/chrome/chrome.d.ts" />
"use strict"; "use strict";
document.addEventListener("taler-checkout-probe", function (e) { document.addEventListener("taler-checkout-probe", function (e) {
let evt = new Event("taler-wallet-present"); var evt = new Event("taler-wallet-present");
document.dispatchEvent(evt); document.dispatchEvent(evt);
console.log("merchant handshake done"); console.log("merchant handshake done");
}); });
document.addEventListener("taler-wire-probe", function (e) { document.addEventListener("taler-wire-probe", function (e) {
let evt = new Event("taler-wallet-present"); var evt = new Event("taler-wallet-present");
document.dispatchEvent(evt); document.dispatchEvent(evt);
console.log("bank handshake done"); console.log("bank handshake done");
}); });
document.addEventListener("taler-checkout-probe", function (e) { document.addEventListener("taler-checkout-probe", function (e) {
let evt = new Event("taler-wallet-present"); var evt = new Event("taler-wallet-present");
document.dispatchEvent(evt); document.dispatchEvent(evt);
console.log("merchant handshake done"); console.log("merchant handshake done");
}); });
document.addEventListener("taler-create-reserve", function (e) { document.addEventListener("taler-create-reserve", function (e) {
let $ = (x) => document.getElementById(x); var $ = function (x) { return document.getElementById(x); };
console.log("taler-create-reserve with " + JSON.stringify(e.detail)); console.log("taler-create-reserve with " + JSON.stringify(e.detail));
let form_uri = $(e.detail.form_id).action; var form_uri = $(e.detail.form_id).action;
// TODO: validate event fields // TODO: validate event fields
// TODO: also send extra bank-defined form fields // TODO: also send extra bank-defined form fields
let params = { var params = {
post_url: URI(form_uri).absoluteTo(document.location.href).href(), post_url: URI(form_uri).absoluteTo(document.location.href).href(),
// TODO: This should change in the future, we should not deal with the // TODO: This should change in the future, we should not deal with the
// amount as a bank-specific string here. // amount as a bank-specific string here.
@ -47,14 +48,14 @@ document.addEventListener("taler-create-reserve", function (e) {
field_reserve_pub: $(e.detail.input_pub).name, field_reserve_pub: $(e.detail.input_pub).name,
field_mint: $(e.detail.mint_rcv).name, field_mint: $(e.detail.mint_rcv).name,
}; };
let uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html")); var uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
document.location.href = uri.query(params).href(); document.location.href = uri.query(params).href();
}); });
document.addEventListener("taler-contract", function (e) { document.addEventListener("taler-contract", function (e) {
// XXX: the merchant should just give us the parsed data ... // XXX: the merchant should just give us the parsed data ...
let offer = JSON.parse(e.detail); var offer = JSON.parse(e.detail);
let uri = URI(chrome.extension.getURL("pages/confirm-contract.html")); var uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
let params = { var params = {
offer: JSON.stringify(offer), offer: JSON.stringify(offer),
merchantPageUrl: document.location.href, merchantPageUrl: document.location.href,
cookie: document.cookie, cookie: document.cookie,
@ -63,23 +64,23 @@ document.addEventListener("taler-contract", function (e) {
}); });
document.addEventListener('taler-execute-payment', function (e) { document.addEventListener('taler-execute-payment', function (e) {
console.log("got taler-execute-payment in content page"); console.log("got taler-execute-payment in content page");
let msg = { var msg = {
type: "execute-payment", type: "execute-payment",
detail: { detail: {
H_contract: e.detail.H_contract H_contract: e.detail.H_contract
}, },
}; };
chrome.runtime.sendMessage(msg, (resp) => { chrome.runtime.sendMessage(msg, function (resp) {
if (!resp.success) { if (!resp.success) {
console.log("failure!"); console.log("failure!");
return; return;
} }
console.log("Making request to ", resp.payUrl); console.log("Making request to ", resp.payUrl);
let r = new XMLHttpRequest(); var r = new XMLHttpRequest();
r.open('post', resp.payUrl); r.open('post', resp.payUrl);
r.send(JSON.stringify(resp.payReq)); r.send(JSON.stringify(resp.payReq));
let detail = {}; var detail = {};
r.onload = (e) => { r.onload = function (e) {
switch (r.status) { switch (r.status) {
case 200: case 200:
detail.success = true; detail.success = true;
@ -101,3 +102,4 @@ document.addEventListener('taler-execute-payment', function (e) {
}; };
}); });
}); });
//# sourceMappingURL=notify.js.map

View File

@ -17,6 +17,8 @@
// Script that is injected into pages in order to allow merchants pages to // Script that is injected into pages in order to allow merchants pages to
// query the availability of Taler. // query the availability of Taler.
/// <reference path="../lib/decl/chrome/chrome.d.ts" />
"use strict"; "use strict";
document.addEventListener("taler-checkout-probe", function(e) { document.addEventListener("taler-checkout-probe", function(e) {

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
(C) 2015 GNUnet e.V. (C) 2016 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU General Public License as published by the Free Software
@ -14,16 +14,14 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
"use strict";
Handlebars.registerHelper('prettyAmount', function (amount) { Handlebars.registerHelper('prettyAmount', function (amount) {
let v = amount.value + amount.fraction / 1e6; 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; let v = amount.value + amount.fraction / 1e6;
return v.toFixed(2); return v.toFixed(2);
}); });
Handlebars.registerHelper('objectStringifier', function (o) { Handlebars.registerHelper('objectStringifier', function (o) {

18634
extension/lib/decl/lib.es6.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
interface System {
import(name: string): Promise<any>;
defined: any;
amdDefine: () => void;
amdRequire: () => void;
baseURL: string;
paths: { [key: string]: string };
meta: { [key: string]: Object };
config: any;
newModule(obj: Object): any;
normalizeSync(name: string): string;
set(moduleName: string, module: any)
}
declare var System: System;
declare module "systemjs" {
export = System;
}

41
extension/lib/emscripten/emsc.d.ts vendored Normal file
View File

@ -0,0 +1,41 @@
/*
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/>
*/
export interface EmscFunGen {
(name: string,
ret: string,
args: string[]): ((...x: (number|string)[]) => any);
(name: string,
ret: 'number',
args: string[]): ((...x: (number|string)[]) => number);
(name: string,
ret: 'void',
args: string[]): ((...x: (number|string)[]) => void);
(name: 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);
}

File diff suppressed because one or more lines are too long

View File

@ -1,16 +0,0 @@
"use strict";
let React = {
createElement: function (tag, props, ...children) {
let e = document.createElement(tag);
for (let k in props) {
e.setAttribute(k, props[k]);
}
for (let child of children) {
if ("string" === typeof child || "number" == typeof child) {
child = document.createTextNode(child);
}
e.appendChild(child);
}
return e;
}
};

6
extension/lib/refs.ts Normal file
View File

@ -0,0 +1,6 @@
// Help the TypeScript compiler find declarations.
/// <reference path="decl/lib.es6.d.ts" />
/// <reference path="decl/urijs/URIjs.d.ts" />
/// <reference path="decl/systemjs/systemjs.d.ts" />

View File

@ -1,49 +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/>
*/
'use strict';
/**
* Parse an amount that is specified like '5.42 EUR'.
* Returns a {currency,value,fraction} object or null
* if the input is invalid.
*/
function amount_parse_pretty(s) {
let pattern = /(\d+)(.\d+)?\s*([a-zA-Z]+)/;
let matches = pattern.exec(s);
if (null == matches) {
return null;
}
return {
// Always succeeds due to regex
value: parseInt(matches[1]),
// Should we warn / fail on lost precision?
fraction: Math.round(parseFloat(matches[2] || "0") * 1000000),
currency: matches[3],
};
}
function format(s, ...args) {
function r(m, n) {
let i = parseInt(n);
return args[i];
}
s = s.replace(/{{/g, '{');
s = s.replace(/}}/g, '}');
s = s.replace(/{([0-9]+)}/g, r);
return s;
}
function promiseFinally(p, fn) {
return p.then((x) => { fn(); return x; })
.catch((e) => { fn(); throw e; });
}

4843
extension/lib/vendor/system.src.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
"use strict"; "use strict";
/** /**
@ -23,8 +24,7 @@
* @author Florian Dold * @author Florian Dold
*/ */
export namespace Checkable {
namespace Checkable {
let chkSym = Symbol("checkable"); let chkSym = Symbol("checkable");
function checkNumber(target, prop): any { function checkNumber(target, prop): any {

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
(C) 2015 GNUnet e.V. (C) 2016 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU General Public License as published by the Free Software
@ -24,51 +24,6 @@
* @author Florian Dold * @author Florian Dold
*/ */
namespace Db {
export interface Mint {
baseUrl: string;
keys: Keys
}
export interface CoinWithDenom {
coin: Coin;
denom: Denomination;
}
export interface Keys {
denoms: Denomination[];
}
export interface Denomination {
value: AmountJson_interface;
denom_pub: string;
fee_withdraw: AmountJson_interface;
fee_deposit: AmountJson_interface;
}
export interface PreCoin {
coinPub: string;
coinPriv: string;
reservePub: string;
denomPub: string;
blindingKey: string;
withdrawSig: string;
coinEv: string;
mintBaseUrl: string;
coinValue: AmountJson_interface;
}
export interface Coin {
coinPub: string;
coinPriv: string;
denomPub: string;
denomSig: string;
currentAmount: AmountJson_interface;
mintBaseUrl: string;
}
}
const DB_NAME = "taler"; const DB_NAME = "taler";
const DB_VERSION = 1; const DB_VERSION = 1;
@ -76,7 +31,7 @@ const DB_VERSION = 1;
* Return a promise that resolves * Return a promise that resolves
* to the taler wallet db. * to the taler wallet db.
*/ */
function openTalerDb(): Promise<IDBDatabase> { export function openTalerDb(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let req = indexedDB.open(DB_NAME, DB_VERSION); let req = indexedDB.open(DB_NAME, DB_VERSION);
req.onerror = (e) => { req.onerror = (e) => {
@ -107,7 +62,7 @@ function openTalerDb(): Promise<IDBDatabase> {
} }
function exportDb(db): Promise<any> { export function exportDb(db): Promise<any> {
let dump = { let dump = {
name: db.name, name: db.name,
version: db.version, version: db.version,
@ -136,3 +91,7 @@ function exportDb(db): Promise<any> {
} }
}); });
} }
export function deleteDb() {
indexedDB.deleteDatabase(DB_NAME);
}

View File

@ -14,6 +14,9 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
import {AmountJson_interface} from "./types";
import * as EmscWrapper from "../emscripten/emsc";
/** /**
* High-level interface to emscripten-compiled modules used * High-level interface to emscripten-compiled modules used
* by the wallet. * by the wallet.
@ -21,37 +24,8 @@
* @author Florian Dold * @author Florian Dold
*/ */
"use strict"; "use strict";
declare var Module: EmscModule;
interface EmscModule {
cwrap: EmscFunGen;
_free(ptr: number);
_malloc(n: number): number;
Pointer_stringify(p: number, len?: number): string;
getValue(ptr: number, type: string, noSafe?: boolean): number;
setValue(ptr: number, value: number, type: string, noSafe?: boolean);
writeStringToMemory(s: string, buffer: number, dontAddNull?: boolean);
}
interface EmscFunGen {
(name: string,
ret: string,
args: string[]): ((...x: (number|string)[]) => any);
(name: string,
ret: 'number',
args: string[]): ((...x: (number|string)[]) => number);
(name: string,
ret: 'void',
args: string[]): ((...x: (number|string)[]) => void);
(name: string,
ret: 'string',
args: string[]): ((...x: (number|string)[]) => string);
}
// Size of a native pointer. // Size of a native pointer.
const PTR_SIZE = 4; const PTR_SIZE = 4;
@ -60,8 +34,9 @@ const GNUNET_YES = 1;
const GNUNET_NO = 0; const GNUNET_NO = 0;
const GNUNET_SYSERR = -1; const GNUNET_SYSERR = -1;
let Module = EmscWrapper.Module;
let getEmsc: EmscFunGen = (...args) => Module.cwrap.apply(null, args); let getEmsc: EmscWrapper.EmscFunGen = (...args) => Module.cwrap.apply(null, args);
var emsc = { var emsc = {
free: (ptr) => Module._free(ptr), free: (ptr) => Module._free(ptr),
@ -264,33 +239,54 @@ class DefaultArena implements Arena {
} }
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 * Arena that destroys all its objects once control has returned to the message
* loop and a small interval has passed. * loop and a small interval has passed.
*/ */
class SyncArena extends DefaultArena { class SyncArena extends DefaultArena {
timer: Worker; private isScheduled: boolean;
constructor() { constructor() {
super(); super();
let me = this; }
this.timer = new Worker('background/timerThread.js');
this.timer.onmessage = () => { pub(obj) {
this.destroy(); super.put(obj);
}; if (!this.isScheduled) {
//this.timer.postMessage({interval: 50}); this.schedule();
}
this.heap.push(obj);
} }
destroy() { destroy() {
super.destroy(); super.destroy();
} }
private schedule() {
this.isScheduled = true;
mySetTimeout(50, () => {
this.isScheduled = false;
this.destroy();
});
}
} }
let arenaStack: Arena[] = []; let arenaStack: Arena[] = [];
arenaStack.push(new SyncArena()); arenaStack.push(new SyncArena());
class Amount extends ArenaObject { export class Amount extends ArenaObject {
constructor(args?: AmountJson_interface, arena?: Arena) { constructor(args?: AmountJson_interface, arena?: Arena) {
super(arena); super(arena);
if (args) { if (args) {
@ -459,7 +455,7 @@ abstract class PackedArenaObject extends ArenaObject {
} }
class AmountNbo extends PackedArenaObject { export class AmountNbo extends PackedArenaObject {
size() { size() {
return 24; return 24;
} }
@ -474,7 +470,7 @@ class AmountNbo extends PackedArenaObject {
} }
class EddsaPrivateKey extends PackedArenaObject { export class EddsaPrivateKey extends PackedArenaObject {
static create(a?: Arena): EddsaPrivateKey { static create(a?: Arena): EddsaPrivateKey {
let obj = new EddsaPrivateKey(a); let obj = new EddsaPrivateKey(a);
obj.nativePtr = emscAlloc.eddsa_key_create(); obj.nativePtr = emscAlloc.eddsa_key_create();
@ -526,7 +522,7 @@ function mixinStatic(obj, method, name?: string) {
} }
class EddsaPublicKey extends PackedArenaObject { export class EddsaPublicKey extends PackedArenaObject {
size() { size() {
return 32; return 32;
} }
@ -560,7 +556,7 @@ function makeToCrock(encodeFn: (po: number, ps: number) => number): () => string
return toCrock; return toCrock;
} }
class RsaBlindingKey extends ArenaObject { export class RsaBlindingKey extends ArenaObject {
static create(len: number, a?: Arena) { static create(len: number, a?: Arena) {
let o = new RsaBlindingKey(a); let o = new RsaBlindingKey(a);
o.nativePtr = emscAlloc.rsa_blinding_key_create(len); o.nativePtr = emscAlloc.rsa_blinding_key_create(len);
@ -577,7 +573,7 @@ class RsaBlindingKey extends ArenaObject {
mixinStatic(RsaBlindingKey, makeFromCrock(emscAlloc.rsa_blinding_key_decode)); mixinStatic(RsaBlindingKey, makeFromCrock(emscAlloc.rsa_blinding_key_decode));
class HashCode extends PackedArenaObject { export class HashCode extends PackedArenaObject {
size() { size() {
return 64; return 64;
} }
@ -608,7 +604,7 @@ class HashCode extends PackedArenaObject {
mixinStatic(HashCode, fromCrock); mixinStatic(HashCode, fromCrock);
class ByteArray extends PackedArenaObject { export class ByteArray extends PackedArenaObject {
private allocatedSize: number; private allocatedSize: number;
size() { size() {
@ -646,7 +642,7 @@ class ByteArray extends PackedArenaObject {
} }
class EccSignaturePurpose extends PackedArenaObject { export class EccSignaturePurpose extends PackedArenaObject {
size() { size() {
return this.payloadSize + 8; return this.payloadSize + 8;
} }
@ -734,7 +730,7 @@ abstract class SignatureStruct {
// It's redundant, but more type safe. // It's redundant, but more type safe.
interface WithdrawRequestPS_Args { export interface WithdrawRequestPS_Args {
reserve_pub: EddsaPublicKey; reserve_pub: EddsaPublicKey;
amount_with_fee: AmountNbo; amount_with_fee: AmountNbo;
withdraw_fee: AmountNbo; withdraw_fee: AmountNbo;
@ -743,7 +739,7 @@ interface WithdrawRequestPS_Args {
} }
class WithdrawRequestPS extends SignatureStruct { export class WithdrawRequestPS extends SignatureStruct {
constructor(w: WithdrawRequestPS_Args) { constructor(w: WithdrawRequestPS_Args) {
super(w); super(w);
} }
@ -764,7 +760,7 @@ class WithdrawRequestPS extends SignatureStruct {
} }
class AbsoluteTimeNbo extends PackedArenaObject { export class AbsoluteTimeNbo extends PackedArenaObject {
static fromTalerString(s: string): AbsoluteTimeNbo { static fromTalerString(s: string): AbsoluteTimeNbo {
let x = new AbsoluteTimeNbo(); let x = new AbsoluteTimeNbo();
x.alloc(); x.alloc();
@ -795,7 +791,7 @@ function set64(p: number, n: number) {
} }
class UInt64 extends PackedArenaObject { export class UInt64 extends PackedArenaObject {
static fromNumber(n: number): UInt64 { static fromNumber(n: number): UInt64 {
let x = new UInt64(); let x = new UInt64();
x.alloc(); x.alloc();
@ -810,7 +806,7 @@ class UInt64 extends PackedArenaObject {
// It's redundant, but more type safe. // It's redundant, but more type safe.
interface DepositRequestPS_Args { export interface DepositRequestPS_Args {
h_contract: HashCode; h_contract: HashCode;
h_wire: HashCode; h_wire: HashCode;
timestamp: AbsoluteTimeNbo; timestamp: AbsoluteTimeNbo;
@ -823,7 +819,7 @@ interface DepositRequestPS_Args {
} }
class DepositRequestPS extends SignatureStruct { export class DepositRequestPS extends SignatureStruct {
constructor(w: DepositRequestPS_Args) { constructor(w: DepositRequestPS_Args) {
super(w); super(w);
} }
@ -865,7 +861,7 @@ function makeEncode(encodeFn) {
} }
class RsaPublicKey extends ArenaObject implements Encodeable { export class RsaPublicKey extends ArenaObject implements Encodeable {
static fromCrock: (s: string, a?: Arena) => RsaPublicKey; static fromCrock: (s: string, a?: Arena) => RsaPublicKey;
toCrock() { toCrock() {
@ -883,14 +879,14 @@ mixinStatic(RsaPublicKey, makeFromCrock(emscAlloc.rsa_public_key_decode));
mixin(RsaPublicKey, makeEncode(emscAlloc.rsa_public_key_encode)); mixin(RsaPublicKey, makeEncode(emscAlloc.rsa_public_key_encode));
class EddsaSignature extends PackedArenaObject { export class EddsaSignature extends PackedArenaObject {
size() { size() {
return 64; return 64;
} }
} }
class RsaSignature extends ArenaObject implements Encodeable{ export class RsaSignature extends ArenaObject implements Encodeable{
static fromCrock: (s: string, a?: Arena) => RsaSignature; static fromCrock: (s: string, a?: Arena) => RsaSignature;
encode: (arena?: Arena) => ByteArray; encode: (arena?: Arena) => ByteArray;
@ -904,7 +900,7 @@ mixinStatic(RsaSignature, makeFromCrock(emscAlloc.rsa_signature_decode));
mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode)); mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode));
function rsaBlind(hashCode: HashCode, export function rsaBlind(hashCode: HashCode,
blindingKey: RsaBlindingKey, blindingKey: RsaBlindingKey,
pkey: RsaPublicKey, pkey: RsaPublicKey,
arena?: Arena): ByteArray { arena?: Arena): ByteArray {
@ -917,7 +913,7 @@ function rsaBlind(hashCode: HashCode,
} }
function eddsaSign(purpose: EccSignaturePurpose, export function eddsaSign(purpose: EccSignaturePurpose,
priv: EddsaPrivateKey, priv: EddsaPrivateKey,
a?: Arena): EddsaSignature { a?: Arena): EddsaSignature {
let sig = new EddsaSignature(a); let sig = new EddsaSignature(a);
@ -930,7 +926,7 @@ function eddsaSign(purpose: EccSignaturePurpose,
} }
function rsaUnblind(sig: RsaSignature, export function rsaUnblind(sig: RsaSignature,
bk: RsaBlindingKey, bk: RsaBlindingKey,
pk: RsaPublicKey, pk: RsaPublicKey,
a?: Arena): RsaSignature { a?: Arena): RsaSignature {

View File

@ -22,13 +22,15 @@
"use strict"; "use strict";
interface HttpResponse {
export interface HttpResponse {
status: number; status: number;
responseText: string; responseText: string;
} }
class BrowserHttpLib { export class BrowserHttpLib {
req(method: string, req(method: string,
url: string|uri.URI, url: string|uri.URI,
options?: any): Promise<HttpResponse> { options?: any): Promise<HttpResponse> {
@ -76,7 +78,7 @@ class BrowserHttpLib {
} }
class RequestException { export class RequestException {
constructor(detail) { constructor(detail) {
} }

View File

@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
/// <reference path="../decl/chrome/chrome.d.ts" /> /// <reference path="../decl/urijs/URIjs.d.ts" />
/** /**
@ -26,7 +26,7 @@
"use strict"; "use strict";
function Query(db) { export function Query(db) {
return new QueryRoot(db); return new QueryRoot(db);
} }

View File

@ -6,5 +6,5 @@
*/ */
onmessage = function(e) { onmessage = function(e) {
self.setInterval(() => postMessage(true), e.data.interval); self.setInterval(() => postMessage(true, "timerThread"), e.data.interval);
}; };

View File

@ -0,0 +1,109 @@
/*
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";
// TODO: factor into multiple files
export interface Mint {
baseUrl: string;
keys: Keys
}
export interface CoinWithDenom {
coin: Coin;
denom: Denomination;
}
export interface Keys {
denoms: Denomination[];
}
export interface Denomination {
value: AmountJson_interface;
denom_pub: string;
fee_withdraw: AmountJson_interface;
fee_deposit: AmountJson_interface;
}
export interface PreCoin {
coinPub: string;
coinPriv: string;
reservePub: string;
denomPub: string;
blindingKey: string;
withdrawSig: string;
coinEv: string;
mintBaseUrl: string;
coinValue: AmountJson_interface;
}
export interface Coin {
coinPub: string;
coinPriv: string;
denomPub: string;
denomSig: string;
currentAmount: AmountJson_interface;
mintBaseUrl: string;
}
export interface AmountJson_interface {
value: number;
fraction: number
currency: string;
}
export interface ConfirmReserveRequest {
/**
* Name of the form field for the amount.
*/
field_amount;
/**
* Name of the form field for the reserve public key.
*/
field_reserve_pub;
/**
* Name of the form field for the reserve public key.
*/
field_mint;
/**
* The actual amount in string form.
* TODO: where is this format specified?
*/
amount_str;
/**
* Target URL for the reserve creation request.
*/
post_url;
/**
* Mint URL where the bank should create the reserve.
*/
mint;
}
export interface ConfirmReserveResponse {
backlink: string;
success: boolean;
status: number;
text: string;
}

View File

@ -21,8 +21,35 @@
* @author Florian Dold * @author Florian Dold
*/ */
import {Amount} from "./emscriptif"
import {AmountJson_interface} from "./types";
import {CoinWithDenom} from "./types";
import {DepositRequestPS_Args} from "./emscriptif";
import {HashCode} from "./emscriptif";
import {EddsaPublicKey} from "./emscriptif";
import {Coin} from "./types";
import {AbsoluteTimeNbo} from "./emscriptif";
import {UInt64} from "./emscriptif";
import {DepositRequestPS} from "./emscriptif";
import {eddsaSign} from "./emscriptif";
import {EddsaPrivateKey} from "./emscriptif";
import {ConfirmReserveRequest} from "./types";
import {ConfirmReserveResponse} from "./types";
import {RsaPublicKey} from "./emscriptif";
import {Denomination} from "./types";
import {RsaBlindingKey} from "./emscriptif";
import {ByteArray} from "./emscriptif";
import {rsaBlind} from "./emscriptif";
import {WithdrawRequestPS} from "./emscriptif";
import {PreCoin} from "./types";
import {rsaUnblind} from "./emscriptif";
import {RsaSignature} from "./emscriptif";
import {Mint} from "./types";
import {Checkable} from "./checkable";
import {HttpResponse} from "./http";
import {RequestException} from "./http";
import {Query} from "./query";
/// <reference path="../decl/urijs/URIjs.d.ts" />
"use strict"; "use strict";
@Checkable.Class @Checkable.Class
@ -60,11 +87,6 @@ class CoinPaySig {
static check: (v: any) => CoinPaySig; static check: (v: any) => CoinPaySig;
} }
interface AmountJson_interface {
value: number;
fraction: number
currency: string;
}
interface ConfirmPayRequest { interface ConfirmPayRequest {
merchantPageUrl: string; merchantPageUrl: string;
@ -72,7 +94,7 @@ interface ConfirmPayRequest {
} }
interface MintCoins { interface MintCoins {
[mintUrl: string]: Db.CoinWithDenom[]; [mintUrl: string]: CoinWithDenom[];
} }
@ -136,49 +158,13 @@ interface PaymentResponse {
} }
interface ConfirmReserveRequest { export interface Badge {
/** setText(s: string): void;
* Name of the form field for the amount. setColor(c: string): void;
*/
field_amount;
/**
* Name of the form field for the reserve public key.
*/
field_reserve_pub;
/**
* Name of the form field for the reserve public key.
*/
field_mint;
/**
* The actual amount in string form.
* TODO: where is this format specified?
*/
amount_str;
/**
* Target URL for the reserve creation request.
*/
post_url;
/**
* Mint URL where the bank should create the reserve.
*/
mint;
} }
interface ConfirmReserveResponse { type PayCoinInfo = Array<{ updatedCoin: Coin, sig: CoinPaySig_interface }>;
backlink: string;
success: boolean;
status: number;
text: string;
}
type PayCoinInfo = Array<{ updatedCoin: Db.Coin, sig: CoinPaySig_interface }>;
/** /**
@ -208,11 +194,6 @@ interface HttpRequestLibrary {
postForm(url: string|uri.URI, form): Promise<HttpResponse>; postForm(url: string|uri.URI, form): Promise<HttpResponse>;
} }
interface Badge {
setText(s: string): void;
setColor(c: string): void;
}
function copy(o) { function copy(o) {
return JSON.parse(JSON.stringify(o)); return JSON.parse(JSON.stringify(o));
@ -227,7 +208,7 @@ function rankDenom(denom1: any, denom2: any) {
} }
class Wallet { export class Wallet {
private db: IDBDatabase; private db: IDBDatabase;
private http: HttpRequestLibrary; private http: HttpRequestLibrary;
private badge: Badge; private badge: Badge;
@ -239,7 +220,7 @@ class Wallet {
} }
static signDeposit(offer: Offer, static signDeposit(offer: Offer,
cds: Db.CoinWithDenom[]): PayCoinInfo { cds: CoinWithDenom[]): PayCoinInfo {
let ret = []; let ret = [];
let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency); let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency);
let amountRemaining = new Amount(offer.contract.amount); let amountRemaining = new Amount(offer.contract.amount);
@ -349,7 +330,7 @@ class Wallet {
let minAmount = new Amount(paymentAmount); let minAmount = new Amount(paymentAmount);
let accFee = new Amount(coins[0].c.denom.fee_deposit); let accFee = new Amount(coins[0].c.denom.fee_deposit);
let accAmount = Amount.getZero(coins[0].c.coin.currentAmount.currency); let accAmount = Amount.getZero(coins[0].c.coin.currentAmount.currency);
let usableCoins: Db.CoinWithDenom[] = []; let usableCoins: CoinWithDenom[] = [];
nextCoin: nextCoin:
for (let i = 0; i < coins.length; i++) { for (let i = 0; i < coins.length; i++) {
let coinAmount = new Amount(coins[i].c.coin.currentAmount); let coinAmount = new Amount(coins[i].c.coin.currentAmount);
@ -488,8 +469,8 @@ class Wallet {
}); });
} }
withdrawPrepare(denom: Db.Denomination, withdrawPrepare(denom: Denomination,
reserve: Reserve): Promise<Db.PreCoin> { reserve: Reserve): Promise<PreCoin> {
let reservePriv = new EddsaPrivateKey(); let reservePriv = new EddsaPrivateKey();
reservePriv.loadCrock(reserve.reserve_priv); reservePriv.loadCrock(reserve.reserve_priv);
let reservePub = new EddsaPublicKey(); let reservePub = new EddsaPublicKey();
@ -520,7 +501,7 @@ class Wallet {
var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv); var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv);
let preCoin: Db.PreCoin = { let preCoin: PreCoin = {
reservePub: reservePub.toCrock(), reservePub: reservePub.toCrock(),
blindingKey: blindingFactor.toCrock(), blindingKey: blindingFactor.toCrock(),
coinPub: coinPub.toCrock(), coinPub: coinPub.toCrock(),
@ -536,7 +517,7 @@ class Wallet {
} }
withdrawExecute(pc: Db.PreCoin): Promise<Db.Coin> { withdrawExecute(pc: PreCoin): Promise<Coin> {
return Query(this.db) return Query(this.db)
.get("reserves", pc.reservePub) .get("reserves", pc.reservePub)
.then((r) => { .then((r) => {
@ -559,7 +540,7 @@ class Wallet {
let denomSig = rsaUnblind(RsaSignature.fromCrock(r.ev_sig), let denomSig = rsaUnblind(RsaSignature.fromCrock(r.ev_sig),
RsaBlindingKey.fromCrock(pc.blindingKey), RsaBlindingKey.fromCrock(pc.blindingKey),
RsaPublicKey.fromCrock(pc.denomPub)); RsaPublicKey.fromCrock(pc.denomPub));
let coin: Db.Coin = { let coin: Coin = {
coinPub: pc.coinPub, coinPub: pc.coinPub,
coinPriv: pc.coinPriv, coinPriv: pc.coinPriv,
denomPub: pc.denomPub, denomPub: pc.denomPub,
@ -591,7 +572,7 @@ class Wallet {
.then(doBadge.bind(this)); .then(doBadge.bind(this));
} }
storeCoin(coin: Db.Coin) { storeCoin(coin: Coin) {
Query(this.db) Query(this.db)
.delete("precoins", coin.coinPub) .delete("precoins", coin.coinPub)
.add("coins", coin) .add("coins", coin)
@ -688,7 +669,7 @@ class Wallet {
if (!mintKeysJson) { if (!mintKeysJson) {
throw new RequestException({url: reqUrl, hint: "keys invalid"}); throw new RequestException({url: reqUrl, hint: "keys invalid"});
} }
let mint: Db.Mint = { let mint: Mint = {
baseUrl: baseUrl, baseUrl: baseUrl,
keys: mintKeysJson keys: mintKeysJson
}; };
@ -698,7 +679,7 @@ class Wallet {
getBalances(): Promise<any> { getBalances(): Promise<any> {
function collectBalances(c: Db.Coin, byCurrency) { function collectBalances(c: Coin, byCurrency) {
let acc: AmountJson_interface = byCurrency[c.currentAmount.currency]; let acc: AmountJson_interface = byCurrency[c.currentAmount.currency];
if (!acc) { if (!acc) {
acc = Amount.getZero(c.currentAmount.currency).toJson(); acc = Amount.getZero(c.currentAmount.currency).toJson();

View File

@ -0,0 +1,144 @@
/*
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/>
*/
System.register(["./wallet", "./db", "./http"], function(exports_1) {
"use strict";
var wallet_1, db_1, db_2, db_3, http_1;
var ChromeBadge;
function makeHandlers(wallet) {
return (_a = {},
_a["balances"] = function (db, detail, sendResponse) {
wallet.getBalances().then(sendResponse);
return true;
},
_a["dump-db"] = function (db, detail, sendResponse) {
db_1.exportDb(db).then(sendResponse);
return true;
},
_a["reset"] = function (db, detail, sendResponse) {
var tx = db.transaction(db.objectStoreNames, 'readwrite');
for (var i = 0; i < db.objectStoreNames.length; i++) {
tx.objectStore(db.objectStoreNames[i]).clear();
}
db_2.deleteDb();
chrome.browserAction.setBadgeText({ text: "" });
console.log("reset done");
// Response is synchronous
return false;
},
_a["confirm-reserve"] = function (db, detail, sendResponse) {
// TODO: make it a checkable
var req = {
field_amount: detail.field_amount,
field_mint: detail.field_mint,
field_reserve_pub: detail.field_reserve_pub,
post_url: detail.post_url,
mint: detail.mint,
amount_str: detail.amount_str
};
wallet.confirmReserve(req)
.then(function (resp) {
if (resp.success) {
resp.backlink = chrome.extension.getURL("pages/reserve-success.html");
}
sendResponse(resp);
});
return true;
},
_a["confirm-pay"] = function (db, detail, sendResponse) {
wallet.confirmPay(detail.offer, detail.merchantPageUrl)
.then(function () {
sendResponse({ success: true });
})
.catch(function (e) {
sendResponse({ error: e.message });
});
return true;
},
_a["execute-payment"] = function (db, detail, sendResponse) {
wallet.doPayment(detail.H_contract)
.then(function (r) {
sendResponse({
success: true,
payUrl: r.payUrl,
payReq: r.payReq
});
})
.catch(function (e) {
sendResponse({ success: false, error: e.message });
});
// async sendResponse
return true;
},
_a
);
var _a;
}
function wxMain() {
chrome.browserAction.setBadgeText({ text: "" });
db_3.openTalerDb().then(function (db) {
var http = new http_1.BrowserHttpLib();
var badge = new ChromeBadge();
var wallet = new wallet_1.Wallet(db, http, badge);
var handlers = makeHandlers(wallet);
wallet.updateBadge();
chrome.runtime.onMessage.addListener(function (req, sender, onresponse) {
if (req.type in handlers) {
return handlers[req.type](db, req.detail, onresponse);
}
console.error(format("Request type {1} unknown, req {0}", JSON.stringify(req), req.type));
return false;
});
});
}
exports_1("wxMain", wxMain);
return {
setters:[
function (wallet_1_1) {
wallet_1 = wallet_1_1;
},
function (db_1_1) {
db_1 = db_1_1;
db_2 = db_1_1;
db_3 = db_1_1;
},
function (http_1_1) {
http_1 = http_1_1;
}],
execute: function() {
/**
* Messaging for the WebExtensions wallet. Should contain
* parts that are specific for WebExtensions, but as little business
* logic as possible.
* @module Messaging
* @author Florian Dold
*/
"use strict";
ChromeBadge = (function () {
function ChromeBadge() {
}
ChromeBadge.prototype.setText = function (s) {
chrome.browserAction.setBadgeText({ text: s });
};
ChromeBadge.prototype.setColor = function (c) {
chrome.browserAction.setBadgeBackgroundColor({ color: c });
};
return ChromeBadge;
}());
wxMain();
}
}
});
//# sourceMappingURL=wxmessaging.js.map

View File

@ -15,6 +15,13 @@
*/ */
import {ConfirmReserveRequest} from "./types";
import {Wallet} from "./wallet";
import {exportDb} from "./db";
import {deleteDb} from "./db";
import {openTalerDb} from "./db";
import {BrowserHttpLib} from "./http";
import {Badge} from "./wallet";
/** /**
* Messaging for the WebExtensions wallet. Should contain * Messaging for the WebExtensions wallet. Should contain
* parts that are specific for WebExtensions, but as little business * parts that are specific for WebExtensions, but as little business
@ -40,7 +47,8 @@ function makeHandlers(wallet) {
for (let i = 0; i < db.objectStoreNames.length; i++) { for (let i = 0; i < db.objectStoreNames.length; i++) {
tx.objectStore(db.objectStoreNames[i]).clear(); tx.objectStore(db.objectStoreNames[i]).clear();
} }
indexedDB.deleteDatabase(DB_NAME); deleteDb();
chrome.browserAction.setBadgeText({text: ""}); chrome.browserAction.setBadgeText({text: ""});
console.log("reset done"); console.log("reset done");
// Response is synchronous // Response is synchronous
@ -94,7 +102,7 @@ function makeHandlers(wallet) {
}; };
} }
class ChromeBadge { class ChromeBadge implements Badge {
setText(s: string) { setText(s: string) {
chrome.browserAction.setBadgeText({text: s}); chrome.browserAction.setBadgeText({text: s});
} }
@ -105,7 +113,7 @@ class ChromeBadge {
} }
function wxMain() { export function wxMain() {
chrome.browserAction.setBadgeText({text: ""}); chrome.browserAction.setBadgeText({text: ""});
openTalerDb().then((db) => { openTalerDb().then((db) => {

View File

@ -26,9 +26,10 @@
{ {
"matches": ["*://*/*"], "matches": ["*://*/*"],
"js": [ "js": [
"content_scripts/notify.js", "lib/vendor/system.src.js",
"lib/URI.js", "lib/vendor/URI.js",
"lib/util.js" "lib/util.js",
"content_scripts/notify.js"
], ],
"run_at": "document_start" "run_at": "document_start"
} }
@ -41,16 +42,12 @@
"background": { "background": {
"scripts": [ "scripts": [
"lib/vendor/URI.js",
"lib/vendor/handlebars-v4.0.5.js",
"lib/util.js", "lib/util.js",
"lib/URI.js", "lib/emscripten/libwrapper.js",
"background/checkable.js", "lib/vendor/system.src.js",
"background/libwrapper.js", "background/main.js"
"background/emscriptif.js",
"background/db.js",
"background/query.js",
"background/messaging.js",
"background/http.js",
"background/wallet.js"
] ]
}, },

20
extension/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "taler-wallet",
"version": "0.0.0",
"description": "",
"main": "wxwallet.js",
"scripts": {
"test": "mocha --delay"
},
"repository": {
"type": "git",
"url": "git://git.taler.net/wallet.git"
},
"author": "",
"license": "GPL-3.0",
"devDependencies": {
"better-assert": "^1.0.2",
"mocha": "^2.3.4",
"systemjs": "^0.19.14"
}
}

View File

@ -3,8 +3,8 @@
<html> <html>
<head> <head>
<title>Taler Wallet: Confirm Reserve Creation</title> <title>Taler Wallet: Confirm Reserve Creation</title>
<script src="../lib/URI.js"></script> <script src="../lib/vendor/URI.js"></script>
<script src="../lib/handlebars-v4.0.5.js"></script> <script src="../lib/vendor/handlebars-v4.0.5.js"></script>
<script src="../lib/commonHelpers.js"></script> <script src="../lib/commonHelpers.js"></script>
<script src="confirm-contract.js"></script> <script src="confirm-contract.js"></script>
<link rel="stylesheet" type="text/css" href="../style/wallet.css"> <link rel="stylesheet" type="text/css" href="../style/wallet.css">

View File

@ -13,13 +13,13 @@
You should have received a copy of the GNU General Public License along with 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/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
/// <reference path="../decl/handlebars/handlebars.d.ts" /> /// <reference path="../lib/decl/handlebars/handlebars.d.ts" />
"use strict"; "use strict";
let url = URI(document.location.href); var url = URI(document.location.href);
let query = URI.parseQuery(url.query()); var query = URI.parseQuery(url.query());
let $_ = (x) => document.getElementById(x); var $_ = function (x) { return document.getElementById(x); };
function renderContract(contract) { function renderContract(contract) {
let showAmount = document.getElementById("show-amount"); var showAmount = document.getElementById("show-amount");
$_('merchant-name').innerText = contract.merchant.name; $_('merchant-name').innerText = contract.merchant.name;
} }
function clone(obj) { function clone(obj) {
@ -27,26 +27,26 @@ function clone(obj) {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
Handlebars.registerHelper('prettyAmount', function (amount) { Handlebars.registerHelper('prettyAmount', function (amount) {
let v = amount.value + amount.fraction / 10e6; var v = amount.value + amount.fraction / 10e6;
return v.toFixed(2) + " " + amount.currency; return v.toFixed(2) + " " + amount.currency;
}); });
document.addEventListener("DOMContentLoaded", (e) => { document.addEventListener("DOMContentLoaded", function (e) {
let offer = JSON.parse(query.offer); var offer = JSON.parse(query.offer);
console.dir(offer); console.dir(offer);
let source = $_("contract-template").innerHTML; var source = $_("contract-template").innerHTML;
let template = Handlebars.compile(source); var template = Handlebars.compile(source);
$_("render-contract").innerHTML = template(offer.contract); $_("render-contract").innerHTML = template(offer.contract);
document.getElementById("confirm-pay").addEventListener("click", (e) => { document.getElementById("confirm-pay").addEventListener("click", function (e) {
console.log("Query:", JSON.stringify(query)); console.log("Query:", JSON.stringify(query));
let d = { var d = {
offer: JSON.parse(query.offer), offer: JSON.parse(query.offer),
merchantPageUrl: query.merchantPageUrl merchantPageUrl: query.merchantPageUrl
}; };
chrome.runtime.sendMessage({ type: 'confirm-pay', detail: d }, (resp) => { chrome.runtime.sendMessage({ type: 'confirm-pay', detail: d }, function (resp) {
if (!resp.success) { if (!resp.success) {
let source = $_("error-template").innerHTML; var source_1 = $_("error-template").innerHTML;
let template = Handlebars.compile(source); var template_1 = Handlebars.compile(source_1);
$_("status").innerHTML = template(resp); $_("status").innerHTML = template_1(resp);
return; return;
} }
document.location.href = URI(d.offer.exec_url) document.location.href = URI(d.offer.exec_url)
@ -56,3 +56,4 @@ document.addEventListener("DOMContentLoaded", (e) => {
}); });
}); });
}); });
//# sourceMappingURL=confirm-contract.js.map

View File

@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
/// <reference path="../decl/handlebars/handlebars.d.ts" /> /// <reference path="../lib/decl/handlebars/handlebars.d.ts" />
"use strict"; "use strict";
let url = URI(document.location.href); let url = URI(document.location.href);

View File

@ -3,7 +3,7 @@
<html> <html>
<head> <head>
<title>Taler Wallet: Select Taler Provider</title> <title>Taler Wallet: Select Taler Provider</title>
<script src="../lib/URI.js"></script> <script src="../lib/vendor/URI.js"></script>
<script src="../lib/polyfill-react.js"></script> <script src="../lib/polyfill-react.js"></script>
<script src="confirm-create-reserve.js"></script> <script src="confirm-create-reserve.js"></script>
<link rel="stylesheet" type="text/css" href="../style/wallet.css"> <link rel="stylesheet" type="text/css" href="../style/wallet.css">

View File

@ -16,12 +16,12 @@
"use strict"; "use strict";
var ConfirmCreateReserve; var ConfirmCreateReserve;
(function (ConfirmCreateReserve) { (function (ConfirmCreateReserve) {
let url = URI(document.location.href); var url = URI(document.location.href);
let query = URI.parseQuery(url.query()); var query = URI.parseQuery(url.query());
function updateAmount() { function updateAmount() {
let showAmount = document.getElementById("show-amount"); var showAmount = document.getElementById("show-amount");
console.log("Query is " + JSON.stringify(query)); console.log("Query is " + JSON.stringify(query));
let s = query.amount_str; var s = query.amount_str;
if (!s) { if (!s) {
document.body.innerHTML = "Oops, something went wrong."; document.body.innerHTML = "Oops, something went wrong.";
return; return;
@ -32,25 +32,21 @@ var ConfirmCreateReserve;
// This is faster than it looks ... // This is faster than it looks ...
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
document.addEventListener("DOMContentLoaded", (e) => { document.addEventListener("DOMContentLoaded", function (e) {
updateAmount(); updateAmount();
document.getElementById("confirm").addEventListener("click", (e) => { document.getElementById("confirm").addEventListener("click", function (e) {
let d = clone(query); var d = clone(query);
d.mint = document.getElementById('mint-url').value; d.mint = document.getElementById('mint-url').value;
chrome.runtime.sendMessage({ type: 'confirm-reserve', detail: d }, (resp) => { chrome.runtime.sendMessage({ type: 'confirm-reserve', detail: d }, function (resp) {
if (resp.success === true) { if (resp.success === true) {
document.location.href = resp.backlink; document.location.href = resp.backlink;
} }
else { else {
document.body.innerHTML = document.body.innerHTML =
` "\n Oops, something went wrong.\n The bank responded with HTTP status code " + resp.status + ".\n Here is some more info:\n <pre>" + resp.text + "</pre>\n </div>";
Oops, something went wrong.
The bank responded with HTTP status code ${resp.status}.
Here is some more info:
<pre>${resp.text}</pre>
</div>`;
} }
}); });
}); });
}); });
})(ConfirmCreateReserve || (ConfirmCreateReserve = {})); })(ConfirmCreateReserve || (ConfirmCreateReserve = {}));
//# sourceMappingURL=confirm-create-reserve.js.map

View File

@ -5,8 +5,8 @@
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" href="popup.css" type="text/css"> <link rel="stylesheet" href="popup.css" type="text/css">
<script src="../lib/util.js" type="text/javascript"></script> <script src="../lib/util.js" type="text/javascript"></script>
<script src="../lib/handlebars-v4.0.5.js"></script> <script src="../lib/vendor/handlebars-v4.0.5.js" type="text/javascript"></script>
<script src="../lib/commonHelpers.js"></script> <script src="../lib/commonHelpers.js" type="text/javascript"></script>
<script src="balance-overview.js" type="text/javascript"></script> <script src="balance-overview.js" type="text/javascript"></script>
<script id="balance-template" type="text/x-handlebars-template"> <script id="balance-template" type="text/x-handlebars-template">

View File

@ -14,30 +14,31 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/ */
"use strict"; "use strict";
document.addEventListener("DOMContentLoaded", (e) => { document.addEventListener("DOMContentLoaded", function (e) {
console.log("content loaded"); console.log("content loaded");
chrome.runtime.sendMessage({ type: "balances" }, function (wallet) { chrome.runtime.sendMessage({ type: "balances" }, function (wallet) {
let context = document.getElementById("balance-template").innerHTML; var context = document.getElementById("balance-template").innerHTML;
let template = Handlebars.compile(context); var template = Handlebars.compile(context);
document.getElementById("content").innerHTML = template(wallet); document.getElementById("content").innerHTML = template(wallet);
console.log("got wallet", JSON.stringify(wallet)); console.log("got wallet", JSON.stringify(wallet));
let el = document.getElementById("link-kudos"); var el = document.getElementById("link-kudos");
if (el) { if (el) {
el.onclick = (e) => { el.onclick = function (e) {
let target = e.target; var target = e.target;
chrome.tabs.create({ chrome.tabs.create({
"url": target.href "url": target.href
}); });
}; };
} }
}); });
document.getElementById("debug").addEventListener("click", (e) => { document.getElementById("debug").addEventListener("click", function (e) {
chrome.tabs.create({ chrome.tabs.create({
"url": chrome.extension.getURL("pages/debug.html") "url": chrome.extension.getURL("pages/debug.html")
}); });
}); });
document.getElementById("reset").addEventListener("click", (e) => { document.getElementById("reset").addEventListener("click", function (e) {
chrome.runtime.sendMessage({ type: "reset" }); chrome.runtime.sendMessage({ type: "reset" });
window.close(); window.close();
}); });
}); });
//# sourceMappingURL=balance-overview.js.map

View File

@ -0,0 +1,55 @@
/**
* Bridge between the mocha test runner / nodejs
* and the typescript / the wallet's module system.
*
* The test cases use better-assert as assert library
* with mocha's bdd UI.
*/
"use strict";
let assert = require("better-assert");
let vm = require("vm");
let fs = require("fs");
if ("function" !== typeof run) {
throw Error("test must be run with 'mocha --delay ...'");
}
console.log("typeof require (here)", typeof require);
// We might need thins in the future ...
global.nodeRequire = function (modulePath) {
return require(modulePath);
};
global.require = global.nodeRequire;
let data = fs.readFileSync("lib/emscripten/libwrapper.js");
vm.runInThisContext(data);
// Do it here, since it breaks 'require''
let System = require("systemjs");
System.config({
defaultJSExtensions: true
});
let mod = System.newModule({Module: Module});
let modName = System.normalizeSync(__dirname + "/../lib/emscripten/emsc");
console.log("registering", modName);
System.set(modName, mod);
System.import("./test/tests/taler.js")
.then((t) => {
t.declareTests(assert, context, it);
run();
})
.catch((e) => {
console.error("failed to load module", e.stack);
});

View File

@ -0,0 +1,10 @@
import * as Emsc from '../../lib/wallet/emscriptif';
export function declareTests(assert, context, it) {
it("works!", function() {
let x = new Emsc.Amount({value: 42, fraction:42, currency: "EUR"});
let j = x.toJson();
assert("value" in j);
});
}

View File

@ -1,23 +1,32 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es6", "target": "es5",
"jsx": "react", "jsx": "react",
"experimentalDecorators": true "experimentalDecorators": true,
"module": "system",
"noLib": true,
"sourceMap": true
}, },
"files": [ "files": [
"background/wallet.ts", "lib/refs.ts",
"background/emscriptif.ts", "lib/wallet/wallet.ts",
"background/db.ts", "lib/wallet/emscriptif.ts",
"background/query.ts", "lib/wallet/db.ts",
"background/http.ts", "lib/wallet/query.ts",
"background/checkable.ts", "lib/wallet/http.ts",
"background/messaging.ts", "lib/wallet/checkable.ts",
"lib/wallet/wxmessaging.ts",
"lib/wallet/types.ts",
"lib/util.ts", "lib/util.ts",
"lib/commonHelpers.ts",
"lib/polyfill-react.ts", "lib/polyfill-react.ts",
"lib/wallet/timerThread.ts",
"content_scripts/notify.ts", "content_scripts/notify.ts",
"background/main.ts",
"popup/balance-overview.tsx", "popup/balance-overview.tsx",
"popup/history.tsx", "popup/history.tsx",
"pages/confirm-contract.tsx", "pages/confirm-contract.tsx",
"pages/confirm-create-reserve.tsx" "pages/confirm-create-reserve.tsx",
"test/tests/taler.ts"
] ]
} }