Towards payment.

This commit is contained in:
Florian Dold 2015-12-17 22:56:24 +01:00
parent 5f907c13fc
commit 38c947d771
15 changed files with 1135 additions and 450 deletions

View File

@ -1,5 +1,34 @@
"use strict";
const DB_NAME = "taler";
const DB_VERSION = 1;
/** /**
* Declarations and helpers for * Return a promise that resolves
* things that are stored in the wallet's * to the taler wallet db.
* database.
*/ */
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 });
break;
}
};
});
}

View File

@ -0,0 +1,89 @@
"use strict";
/**
* Declarations and helpers for
* things that are stored in the wallet's
* database.
*/
namespace Db {
export interface Mint {
baseUrl: string;
keys: Keys
}
export interface CoinWithDenom {
coin: Coin;
denom: Denomination;
}
export interface Keys {
denoms: { [key: string]: Denomination };
}
export interface Denomination {
value: AmountJson;
denom_pub: string;
fee_withdraw: AmountJson;
fee_deposit: AmountJson;
}
export interface PreCoin {
coinPub: string;
coinPriv: string;
reservePub: string;
denomPub: string;
blindingKey: string;
withdrawSig: string;
coinEv: string;
mintBaseUrl: string;
coinValue: AmountJson;
}
export interface Coin {
coinPub: string;
coinPriv: string;
denomPub: string;
denomSig: string;
currentAmount: AmountJson;
}
}
const DB_NAME = "taler";
const DB_VERSION = 1;
/**
* Return a promise that resolves
* to the taler wallet db.
*/
function openTalerDb(): Promise<IDBDatabase> {
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: // DB does not exist yet
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 });
break;
}
};
});
}

View File

@ -1,19 +1,19 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
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
Foundation; either version 3, or (at your option) any later version. 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 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details. 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 You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
"use strict"; "use strict";
// Size of a native pointer. // Size of a native pointer.
const PTR_SIZE = 4; const PTR_SIZE = 4;
@ -30,6 +30,7 @@ var emsc = {
amount_add: getEmsc('TALER_amount_add', 'number', ['number', 'number', 'number']), amount_add: getEmsc('TALER_amount_add', 'number', ['number', 'number', 'number']),
amount_subtract: getEmsc('TALER_amount_subtract', 'number', ['number', 'number', 'number']), amount_subtract: getEmsc('TALER_amount_subtract', 'number', ['number', 'number', 'number']),
amount_normalize: getEmsc('TALER_amount_normalize', 'void', ['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_cmp: getEmsc('TALER_amount_cmp', 'number', ['number', 'number']),
amount_hton: getEmsc('TALER_amount_hton', 'void', ['number', 'number']), amount_hton: getEmsc('TALER_amount_hton', 'void', ['number', 'number']),
amount_ntoh: getEmsc('TALER_amount_ntoh', 'void', ['number', 'number']), amount_ntoh: getEmsc('TALER_amount_ntoh', 'void', ['number', 'number']),
@ -62,6 +63,7 @@ var emscAlloc = {
var SignaturePurpose; var SignaturePurpose;
(function (SignaturePurpose) { (function (SignaturePurpose) {
SignaturePurpose[SignaturePurpose["RESERVE_WITHDRAW"] = 1200] = "RESERVE_WITHDRAW"; SignaturePurpose[SignaturePurpose["RESERVE_WITHDRAW"] = 1200] = "RESERVE_WITHDRAW";
SignaturePurpose[SignaturePurpose["WALLET_COIN_DEPOSIT"] = 1201] = "WALLET_COIN_DEPOSIT";
})(SignaturePurpose || (SignaturePurpose = {})); })(SignaturePurpose || (SignaturePurpose = {}));
var RandomQuality; var RandomQuality;
(function (RandomQuality) { (function (RandomQuality) {
@ -138,7 +140,7 @@ class SyncArena extends DefaultArena {
super(); super();
let me = this; let me = this;
this.timer = new Worker('background/timerThread.js'); this.timer = new Worker('background/timerThread.js');
this.timer.onmessage = (e) => { this.timer.onmessage = () => {
this.destroy(); this.destroy();
}; };
//this.timer.postMessage({interval: 50}); //this.timer.postMessage({interval: 50});
@ -164,6 +166,14 @@ class Amount extends ArenaObject {
emsc.free(this.nativePtr); 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) { toNbo(a) {
let x = new AmountNbo(a); let x = new AmountNbo(a);
x.alloc(); x.alloc();
@ -260,7 +270,9 @@ class PackedArenaObject extends ArenaObject {
} }
} }
class AmountNbo extends PackedArenaObject { class AmountNbo extends PackedArenaObject {
size() { return 24; } size() {
return 24;
}
} }
class EddsaPrivateKey extends PackedArenaObject { class EddsaPrivateKey extends PackedArenaObject {
static create(a) { static create(a) {
@ -268,30 +280,60 @@ class EddsaPrivateKey extends PackedArenaObject {
obj.nativePtr = emscAlloc.eddsa_key_create(); obj.nativePtr = emscAlloc.eddsa_key_create();
return obj; return obj;
} }
size() { return 32; } size() {
return 32;
}
getPublicKey(a) { getPublicKey(a) {
let obj = new EddsaPublicKey(a); let obj = new EddsaPublicKey(a);
obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr); obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr);
return obj; return obj;
} }
} }
class EddsaPublicKey extends PackedArenaObject { mixinStatic(EddsaPrivateKey, fromCrock);
size() { return 32; } function fromCrock(s) {
let x = new this();
x.alloc();
x.loadCrock(s);
return x;
} }
class RsaBlindingKey extends ArenaObject { function mixin(obj, method, name) {
static create(len, a) { if (!name) {
let o = new RsaBlindingKey(a); name = method.name;
o.nativePtr = emscAlloc.rsa_blinding_key_create(len);
return o;
} }
static fromCrock(s, a) { if (!name) {
throw Error("Mixin needs a name.");
}
console.log("mixing in", name, "into", obj.name);
obj.prototype[method.name] = method;
}
function mixinStatic(obj, method, name) {
if (!name) {
name = method.name;
}
if (!name) {
throw Error("Mixin needs a name.");
}
console.log("mixing in", name, "into", obj.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 obj = new this(a);
let buf = ByteArray.fromCrock(s); let buf = ByteArray.fromCrock(s);
obj.setNative(emscAlloc.rsa_blinding_key_decode(buf.getNative(), buf.size())); obj.setNative(decodeFn(buf.getNative(), buf.size()));
buf.destroy(); buf.destroy();
return obj; return obj;
} }
toCrock() { return fromCrock;
}
function makeToCrock(encodeFn) {
function toCrock() {
let ptr = emscAlloc.malloc(PTR_SIZE); let ptr = emscAlloc.malloc(PTR_SIZE);
let size = emscAlloc.rsa_blinding_key_encode(this.nativePtr, ptr); let size = emscAlloc.rsa_blinding_key_encode(this.nativePtr, ptr);
let res = new ByteArray(size, Module.getValue(ptr, '*')); let res = new ByteArray(size, Module.getValue(ptr, '*'));
@ -300,12 +342,27 @@ class RsaBlindingKey extends ArenaObject {
res.destroy(); res.destroy();
return s; 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() { destroy() {
// TODO // TODO
} }
} }
mixinStatic(RsaBlindingKey, makeFromCrock(emscAlloc.rsa_blinding_key_decode));
class HashCode extends PackedArenaObject { class HashCode extends PackedArenaObject {
size() { return 64; } size() {
return 64;
}
random(qualStr) { random(qualStr) {
let qual; let qual;
switch (qualStr) { switch (qualStr) {
@ -328,6 +385,7 @@ class HashCode extends PackedArenaObject {
emsc.hash_create_random(qual, this.nativePtr); emsc.hash_create_random(qual, this.nativePtr);
} }
} }
mixinStatic(HashCode, fromCrock);
class ByteArray extends PackedArenaObject { class ByteArray extends PackedArenaObject {
constructor(desiredSize, init, a) { constructor(desiredSize, init, a) {
super(a); super(a);
@ -339,7 +397,9 @@ class ByteArray extends PackedArenaObject {
} }
this.allocatedSize = desiredSize; this.allocatedSize = desiredSize;
} }
size() { return this.allocatedSize; } size() {
return this.allocatedSize;
}
static fromString(s, a) { static fromString(s, a) {
let hstr = emscAlloc.malloc(s.length + 1); let hstr = emscAlloc.malloc(s.length + 1);
Module.writeStringToMemory(s, hstr); Module.writeStringToMemory(s, hstr);
@ -364,7 +424,9 @@ class EccSignaturePurpose extends PackedArenaObject {
this.nativePtr = emscAlloc.purpose_create(purpose, payload.nativePtr, payload.size()); this.nativePtr = emscAlloc.purpose_create(purpose, payload.nativePtr, payload.size());
this.payloadSize = payload.size(); this.payloadSize = payload.size();
} }
size() { return this.payloadSize + 8; } size() {
return this.payloadSize + 8;
}
} }
class SignatureStruct { class SignatureStruct {
constructor(x) { constructor(x) {
@ -393,8 +455,7 @@ class SignatureStruct {
ptr += size; ptr += size;
} }
let ba = new ByteArray(totalSize, buf, a); let ba = new ByteArray(totalSize, buf, a);
let x = new EccSignaturePurpose(this.purpose(), ba); return new EccSignaturePurpose(this.purpose(), ba);
return x;
} }
set(name, value) { set(name, value) {
let typemap = {}; let typemap = {};
@ -414,32 +475,68 @@ class WithdrawRequestPS extends SignatureStruct {
constructor(w) { constructor(w) {
super(w); super(w);
} }
purpose() { return SignaturePurpose.RESERVE_WITHDRAW; } purpose() {
return SignaturePurpose.RESERVE_WITHDRAW;
}
fieldTypes() { fieldTypes() {
return [ return [
["reserve_pub", EddsaPublicKey], ["reserve_pub", EddsaPublicKey],
["amount_with_fee", AmountNbo], ["amount_with_fee", AmountNbo],
["withdraw_fee", AmountNbo], ["withdraw_fee", AmountNbo],
["h_denomination_pub", HashCode], ["h_denomination_pub", HashCode],
["h_coin_envelope", HashCode]]; ["h_coin_envelope", HashCode]
];
} }
} }
function encodeWith(obj, fn, arena) { class AbsoluteTimeNbo extends PackedArenaObject {
let ptr = emscAlloc.malloc(PTR_SIZE); static fromTalerString(s) {
let len = fn(obj.getNative(), ptr); throw Error();
let res = new ByteArray(len, null, arena); }
res.setNative(Module.getValue(ptr, '*')); size() {
emsc.free(ptr); return 8;
return res; }
}
class UInt64 extends PackedArenaObject {
static fromNumber(n) {
throw Error();
}
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 { class RsaPublicKey extends ArenaObject {
static fromCrock(s, a) {
let obj = new RsaPublicKey(a);
let buf = ByteArray.fromCrock(s);
obj.nativePtr = emscAlloc.rsa_public_key_decode(buf.nativePtr, buf.size());
buf.destroy();
return obj;
}
toCrock() { toCrock() {
return this.encode().toCrock(); return this.encode().toCrock();
} }
@ -447,38 +544,26 @@ class RsaPublicKey extends ArenaObject {
emsc.rsa_public_key_free(this.nativePtr); emsc.rsa_public_key_free(this.nativePtr);
this.nativePtr = 0; this.nativePtr = 0;
} }
encode(arena) {
let ptr = emscAlloc.malloc(PTR_SIZE);
let len = emscAlloc.rsa_public_key_encode(this.nativePtr, ptr);
let res = new ByteArray(len, Module.getValue(ptr, '*'), arena);
emsc.free(ptr);
return res;
}
} }
mixinStatic(RsaPublicKey, makeFromCrock(emscAlloc.rsa_public_key_decode));
mixin(RsaPublicKey, makeEncode(emscAlloc.rsa_public_key_encode));
class EddsaSignature extends PackedArenaObject { class EddsaSignature extends PackedArenaObject {
size() { return 64; } size() {
return 64;
}
} }
class RsaSignature extends ArenaObject { class RsaSignature extends ArenaObject {
static fromCrock(s, a) {
let obj = new this(a);
let buf = ByteArray.fromCrock(s);
obj.setNative(emscAlloc.rsa_signature_decode(buf.getNative(), buf.size()));
buf.destroy();
return obj;
}
encode(arena) {
return encodeWith(this, emscAlloc.rsa_signature_encode);
}
destroy() { destroy() {
emsc.rsa_signature_free(this.getNative()); emsc.rsa_signature_free(this.getNative());
this.setNative(0); this.setNative(0);
} }
} }
mixinStatic(RsaSignature, makeFromCrock(emscAlloc.rsa_signature_decode));
mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode));
function rsaBlind(hashCode, blindingKey, pkey, arena) { function rsaBlind(hashCode, blindingKey, pkey, arena) {
let ptr = emscAlloc.malloc(PTR_SIZE); let ptr = emscAlloc.malloc(PTR_SIZE);
let s = emscAlloc.rsa_blind(hashCode.nativePtr, blindingKey.nativePtr, pkey.nativePtr, ptr); let s = emscAlloc.rsa_blind(hashCode.nativePtr, blindingKey.nativePtr, pkey.nativePtr, ptr);
let res = new ByteArray(s, Module.getValue(ptr, '*'), arena); return new ByteArray(s, Module.getValue(ptr, '*'), arena);
return res;
} }
function eddsaSign(purpose, priv, a) { function eddsaSign(purpose, priv, a) {
let sig = new EddsaSignature(a); let sig = new EddsaSignature(a);

View File

@ -1,23 +1,23 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
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
Foundation; either version 3, or (at your option) any later version. 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 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details. 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 You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
"use strict"; "use strict";
declare var Module : any; declare var Module: any;
// Size of a native pointer. // Size of a native pointer.
@ -30,10 +30,18 @@ const GNUNET_SYSERR = -1;
interface EmscFunGen { interface EmscFunGen {
(name: string, ret: string, args: string[]): ((...x: (number|string)[]) => any); (name: string,
(name: string, ret: 'number', args: string[]): ((...x: (number|string)[]) => number); ret: string,
(name: string, ret: 'void', args: string[]): ((...x: (number|string)[]) => void); args: string[]): ((...x: (number|string)[]) => any);
(name: string, ret: 'string', args: string[]): ((...x: (number|string)[]) => string); (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);
} }
let getEmsc: EmscFunGen = (...args) => Module.cwrap.apply(null, args); let getEmsc: EmscFunGen = (...args) => Module.cwrap.apply(null, args);
@ -58,15 +66,18 @@ var emsc = {
amount_normalize: getEmsc('TALER_amount_normalize', amount_normalize: getEmsc('TALER_amount_normalize',
'void', 'void',
['number']), ['number']),
amount_get_zero: getEmsc('TALER_amount_get_zero',
'number',
['string', 'number']),
amount_cmp: getEmsc('TALER_amount_cmp', amount_cmp: getEmsc('TALER_amount_cmp',
'number', 'number',
['number', 'number']), ['number', 'number']),
amount_hton: getEmsc('TALER_amount_hton', amount_hton: getEmsc('TALER_amount_hton',
'void', 'void',
['number', 'number']), ['number', 'number']),
amount_ntoh: getEmsc('TALER_amount_ntoh', amount_ntoh: getEmsc('TALER_amount_ntoh',
'void', 'void',
['number', 'number']), ['number', 'number']),
hash: getEmsc('GNUNET_CRYPTO_hash', hash: getEmsc('GNUNET_CRYPTO_hash',
'void', 'void',
['number', 'number', 'number']), ['number', 'number', 'number']),
@ -77,8 +88,8 @@ var emsc = {
'void', 'void',
['number']), ['number']),
rsa_signature_free: getEmsc('GNUNET_CRYPTO_rsa_signature_free', rsa_signature_free: getEmsc('GNUNET_CRYPTO_rsa_signature_free',
'void', 'void',
['number']), ['number']),
string_to_data: getEmsc('GNUNET_STRINGS_string_to_data', string_to_data: getEmsc('GNUNET_STRINGS_string_to_data',
'number', 'number',
['number', 'number', 'number', 'number']), ['number', 'number', 'number', 'number']),
@ -89,8 +100,8 @@ var emsc = {
'void', 'void',
['number', 'number']), ['number', 'number']),
rsa_blinding_key_destroy: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_free', rsa_blinding_key_destroy: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_free',
'void', 'void',
['number']), ['number']),
}; };
var emscAlloc = { var emscAlloc = {
@ -99,9 +110,10 @@ var emscAlloc = {
['number', 'number', 'number', 'string']), ['number', 'number', 'number', 'string']),
eddsa_key_create: getEmsc('GNUNET_CRYPTO_eddsa_key_create', eddsa_key_create: getEmsc('GNUNET_CRYPTO_eddsa_key_create',
'number', []), 'number', []),
eddsa_public_key_from_private: getEmsc('TALER_WRALL_eddsa_public_key_from_private', eddsa_public_key_from_private: getEmsc(
'number', 'TALER_WRALL_eddsa_public_key_from_private',
['number']), 'number',
['number']),
data_to_string_alloc: getEmsc('GNUNET_STRINGS_data_to_string_alloc', data_to_string_alloc: getEmsc('GNUNET_STRINGS_data_to_string_alloc',
'number', 'number',
['number', 'number']), ['number', 'number']),
@ -112,35 +124,36 @@ var emscAlloc = {
'number', 'number',
['number', 'number', 'number', 'number']), ['number', 'number', 'number', 'number']),
rsa_blinding_key_create: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_create', rsa_blinding_key_create: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_create',
'number', 'number',
['number']), ['number']),
rsa_blinding_key_encode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_encode', rsa_blinding_key_encode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_encode',
'number', 'number',
['number', 'number']), ['number', 'number']),
rsa_signature_encode: getEmsc('GNUNET_CRYPTO_rsa_signature_encode', rsa_signature_encode: getEmsc('GNUNET_CRYPTO_rsa_signature_encode',
'number', 'number',
['number', 'number']), ['number', 'number']),
rsa_blinding_key_decode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_decode', rsa_blinding_key_decode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_decode',
'number', 'number',
['number', 'number']), ['number', 'number']),
rsa_public_key_decode: getEmsc('GNUNET_CRYPTO_rsa_public_key_decode', rsa_public_key_decode: getEmsc('GNUNET_CRYPTO_rsa_public_key_decode',
'number', 'number',
['number', 'number']), ['number', 'number']),
rsa_signature_decode: getEmsc('GNUNET_CRYPTO_rsa_signature_decode', rsa_signature_decode: getEmsc('GNUNET_CRYPTO_rsa_signature_decode',
'number', 'number',
['number', 'number']), ['number', 'number']),
rsa_public_key_encode: getEmsc('GNUNET_CRYPTO_rsa_public_key_encode', rsa_public_key_encode: getEmsc('GNUNET_CRYPTO_rsa_public_key_encode',
'number', 'number',
['number', 'number']), ['number', 'number']),
rsa_unblind: getEmsc('GNUNET_CRYPTO_rsa_unblind', rsa_unblind: getEmsc('GNUNET_CRYPTO_rsa_unblind',
'number', 'number',
['number', 'number', 'number']), ['number', 'number', 'number']),
malloc: (size: number) => Module._malloc(size), malloc: (size: number) => Module._malloc(size),
}; };
enum SignaturePurpose { enum SignaturePurpose {
RESERVE_WITHDRAW = 1200 RESERVE_WITHDRAW = 1200,
WALLET_COIN_DEPOSIT = 1201,
} }
enum RandomQuality { enum RandomQuality {
@ -153,6 +166,7 @@ enum RandomQuality {
abstract class ArenaObject { abstract class ArenaObject {
private _nativePtr: number; private _nativePtr: number;
arena: Arena; arena: Arena;
abstract destroy(): void; abstract destroy(): void;
constructor(arena?: Arena) { constructor(arena?: Arena) {
@ -172,7 +186,7 @@ abstract class ArenaObject {
// of native wrappers, but we never want to // of native wrappers, but we never want to
// pass 'undefined' to emscripten. // pass 'undefined' to emscripten.
if (this._nativePtr === undefined) { if (this._nativePtr === undefined) {
throw Error("Native pointer not initialized"); throw Error("Native pointer not initialized");
} }
return this._nativePtr; return this._nativePtr;
} }
@ -198,11 +212,11 @@ abstract class ArenaObject {
this._nativePtr = n; this._nativePtr = n;
} }
set nativePtr (v) { set nativePtr(v) {
this.setNative(v); this.setNative(v);
} }
get nativePtr () { get nativePtr() {
return this.getNative(); return this.getNative();
} }
@ -215,6 +229,7 @@ interface Arena {
class DefaultArena implements Arena { class DefaultArena implements Arena {
heap: Array<ArenaObject>; heap: Array<ArenaObject>;
constructor() { constructor() {
this.heap = []; this.heap = [];
} }
@ -238,31 +253,35 @@ class DefaultArena implements Arena {
*/ */
class SyncArena extends DefaultArena { class SyncArena extends DefaultArena {
timer: Worker; timer: Worker;
constructor() { constructor() {
super(); super();
let me = this; let me = this;
this.timer = new Worker('background/timerThread.js'); this.timer = new Worker('background/timerThread.js');
this.timer.onmessage = (e) => { this.timer.onmessage = () => {
this.destroy(); this.destroy();
}; };
//this.timer.postMessage({interval: 50}); //this.timer.postMessage({interval: 50});
} }
destroy() { destroy() {
super.destroy(); super.destroy();
} }
} }
let arenaStack: Arena[] = []; let arenaStack: Arena[] = [];
arenaStack.push(new SyncArena()); arenaStack.push(new SyncArena());
class Amount extends ArenaObject { class Amount extends ArenaObject {
constructor(args?: any, arena?: Arena) { constructor(args?: AmountJson, arena?: Arena) {
super(arena); super(arena);
if (args) { if (args) {
this.nativePtr = emscAlloc.get_amount(args.value, 0, args.fraction, args.currency); this.nativePtr = emscAlloc.get_amount(args.value,
0,
args.fraction,
args.currency);
} else { } else {
this.nativePtr = emscAlloc.get_amount(0, 0, 0, ""); this.nativePtr = emscAlloc.get_amount(0, 0, 0, "");
} }
@ -274,6 +293,17 @@ class Amount extends ArenaObject {
} }
} }
static getZero(currency: string, a?: Arena) {
let am = new Amount(null, a);
let r = emsc.amount_get_zero(currency, am.getNative());
if (r != GNUNET_OK) {
throw Error("invalid currency");
}
return am;
}
toNbo(a?: Arena): AmountNbo { toNbo(a?: Arena): AmountNbo {
let x = new AmountNbo(a); let x = new AmountNbo(a);
x.alloc(); x.alloc();
@ -362,7 +392,10 @@ abstract class PackedArenaObject extends ArenaObject {
// We need to get the javascript string // We need to get the javascript string
// to the emscripten heap first. // to the emscripten heap first.
let buf = ByteArray.fromString(s); let buf = ByteArray.fromString(s);
let res = emsc.string_to_data(buf.nativePtr, s.length, this.nativePtr, this.size()); let res = emsc.string_to_data(buf.nativePtr,
s.length,
this.nativePtr,
this.size());
buf.destroy(); buf.destroy();
if (res < 1) { if (res < 1) {
throw {error: "wrong encoding"}; throw {error: "wrong encoding"};
@ -390,7 +423,9 @@ abstract class PackedArenaObject extends ArenaObject {
class AmountNbo extends PackedArenaObject { class AmountNbo extends PackedArenaObject {
size() { return 24; } size() {
return 24;
}
} }
@ -401,37 +436,76 @@ class EddsaPrivateKey extends PackedArenaObject {
return obj; return obj;
} }
size() { return 32; } size() {
return 32;
}
getPublicKey(a?: Arena): EddsaPublicKey { getPublicKey(a?: Arena): EddsaPublicKey {
let obj = new EddsaPublicKey(a); let obj = new EddsaPublicKey(a);
obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr); obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr);
return obj; return obj;
} }
static fromCrock: (string) => EddsaPrivateKey;
}
mixinStatic(EddsaPrivateKey, fromCrock);
function fromCrock(s: string) {
let x = new this();
x.alloc();
x.loadCrock(s);
return x;
}
function mixin(obj, method, name?: string) {
if (!name) {
name = method.name;
}
if (!name) {
throw Error("Mixin needs a name.");
}
console.log("mixing in", name, "into", obj.name);
obj.prototype[method.name] = method;
}
function mixinStatic(obj, method, name?: string) {
if (!name) {
name = method.name;
}
if (!name) {
throw Error("Mixin needs a name.");
}
console.log("mixing in", name, "into", obj.name);
obj[method.name] = method;
} }
class EddsaPublicKey extends PackedArenaObject { class EddsaPublicKey extends PackedArenaObject {
size() { return 32; } size() {
} return 32;
class RsaBlindingKey extends ArenaObject {
static create(len: number, a?: Arena) {
let o = new RsaBlindingKey(a);
o.nativePtr = emscAlloc.rsa_blinding_key_create(len);
return o;
} }
static fromCrock(s: string, a?: Arena): RsaBlindingKey { static fromCrock: (s: string) => EddsaPublicKey;
}
mixinStatic(EddsaPublicKey, fromCrock);
function makeFromCrock(decodeFn: (p: number, s: number) => number) {
function fromCrock(s: string, a?: Arena) {
let obj = new this(a); let obj = new this(a);
let buf = ByteArray.fromCrock(s); let buf = ByteArray.fromCrock(s);
obj.setNative(emscAlloc.rsa_blinding_key_decode(buf.getNative(), buf.size())); obj.setNative(decodeFn(buf.getNative(),
buf.size()));
buf.destroy(); buf.destroy();
return obj; return obj;
} }
toCrock(): string { return fromCrock;
}
function makeToCrock(encodeFn: (po: number, ps: number) => number): () => string {
function toCrock() {
let ptr = emscAlloc.malloc(PTR_SIZE); let ptr = emscAlloc.malloc(PTR_SIZE);
let size = emscAlloc.rsa_blinding_key_encode(this.nativePtr, ptr); let size = emscAlloc.rsa_blinding_key_encode(this.nativePtr, ptr);
let res = new ByteArray(size, Module.getValue(ptr, '*')); let res = new ByteArray(size, Module.getValue(ptr, '*'));
@ -440,15 +514,32 @@ class RsaBlindingKey extends ArenaObject {
res.destroy(); res.destroy();
return s; return s;
} }
return toCrock;
}
class RsaBlindingKey extends ArenaObject {
static create(len: number, a?: Arena) {
let o = new RsaBlindingKey(a);
o.nativePtr = emscAlloc.rsa_blinding_key_create(len);
return o;
}
static fromCrock: (s: string, a?: Arena) => RsaBlindingKey;
toCrock = makeToCrock(emscAlloc.rsa_blinding_key_encode);
destroy() { destroy() {
// TODO // TODO
} }
} }
mixinStatic(RsaBlindingKey, makeFromCrock(emscAlloc.rsa_blinding_key_decode));
class HashCode extends PackedArenaObject { class HashCode extends PackedArenaObject {
size() { return 64; } size() {
return 64;
}
static fromCrock: (s: string) => HashCode;
random(qualStr: string) { random(qualStr: string) {
let qual: RandomQuality; let qual: RandomQuality;
@ -472,11 +563,15 @@ class HashCode extends PackedArenaObject {
emsc.hash_create_random(qual, this.nativePtr); emsc.hash_create_random(qual, this.nativePtr);
} }
} }
mixinStatic(HashCode, fromCrock);
class ByteArray extends PackedArenaObject { class ByteArray extends PackedArenaObject {
private allocatedSize: number; private allocatedSize: number;
size() { return this.allocatedSize; }
size() {
return this.allocatedSize;
}
constructor(desiredSize: number, init: number, a?: Arena) { constructor(desiredSize: number, init: number, a?: Arena) {
super(a); super(a);
@ -510,11 +605,19 @@ class ByteArray extends PackedArenaObject {
class EccSignaturePurpose extends PackedArenaObject { class EccSignaturePurpose extends PackedArenaObject {
size() { return this.payloadSize + 8; } size() {
return this.payloadSize + 8;
}
payloadSize: number; payloadSize: number;
constructor(purpose: SignaturePurpose, payload: PackedArenaObject, a?: Arena) {
constructor(purpose: SignaturePurpose,
payload: PackedArenaObject,
a?: Arena) {
super(a); super(a);
this.nativePtr = emscAlloc.purpose_create(purpose, payload.nativePtr, payload.size()); this.nativePtr = emscAlloc.purpose_create(purpose,
payload.nativePtr,
payload.size());
this.payloadSize = payload.size(); this.payloadSize = payload.size();
} }
} }
@ -522,7 +625,9 @@ class EccSignaturePurpose extends PackedArenaObject {
abstract class SignatureStruct { abstract class SignatureStruct {
abstract fieldTypes(): Array<any>; abstract fieldTypes(): Array<any>;
abstract purpose(): SignaturePurpose; abstract purpose(): SignaturePurpose;
private members: any = {}; private members: any = {};
constructor(x: { [name: string]: any }) { constructor(x: { [name: string]: any }) {
@ -552,12 +657,11 @@ abstract class SignatureStruct {
ptr += size; ptr += size;
} }
let ba = new ByteArray(totalSize, buf, a); let ba = new ByteArray(totalSize, buf, a);
let x = new EccSignaturePurpose(this.purpose(), ba); return new EccSignaturePurpose(this.purpose(), ba);
return x;
} }
protected set(name: string, value: PackedArenaObject) { protected set(name: string, value: PackedArenaObject) {
let typemap: any = {} let typemap: any = {};
for (let f of this.fieldTypes()) { for (let f of this.fieldTypes()) {
typemap[f[0]] = f[1]; typemap[f[0]] = f[1];
} }
@ -586,38 +690,104 @@ class WithdrawRequestPS extends SignatureStruct {
constructor(w: WithdrawRequestPS_Args) { constructor(w: WithdrawRequestPS_Args) {
super(w); super(w);
} }
purpose() { return SignaturePurpose.RESERVE_WITHDRAW; }
purpose() {
return SignaturePurpose.RESERVE_WITHDRAW;
}
fieldTypes() { fieldTypes() {
return [ return [
["reserve_pub", EddsaPublicKey], ["reserve_pub", EddsaPublicKey],
["amount_with_fee", AmountNbo], ["amount_with_fee", AmountNbo],
["withdraw_fee", AmountNbo], ["withdraw_fee", AmountNbo],
["h_denomination_pub", HashCode], ["h_denomination_pub", HashCode],
["h_coin_envelope", HashCode]]; ["h_coin_envelope", HashCode]
];
} }
} }
class AbsoluteTimeNbo extends PackedArenaObject {
static fromTalerString(s: string): AbsoluteTimeNbo {
throw Error();
}
function encodeWith(obj, fn, arena?: Arena) { size() {
let ptr = emscAlloc.malloc(PTR_SIZE); return 8;
let len = fn(obj.getNative(), ptr); }
let res = new ByteArray(len, null, arena);
res.setNative(Module.getValue(ptr, '*'));
emsc.free(ptr);
return res;
} }
class RsaPublicKey extends ArenaObject { class UInt64 extends PackedArenaObject {
static fromCrock(s: string, a?: Arena): RsaPublicKey { static fromNumber(n: number): UInt64 {
let obj = new RsaPublicKey(a); throw Error();
let buf = ByteArray.fromCrock(s);
obj.nativePtr = emscAlloc.rsa_public_key_decode(buf.nativePtr, buf.size());
buf.destroy();
return obj;
} }
size() {
return 8;
}
}
// It's redundant, but more type safe.
interface DepositRequestPS_Args {
h_contract: HashCode;
h_wire: HashCode;
timestamp: AbsoluteTimeNbo;
refund_deadline: AbsoluteTimeNbo;
transaction_id: UInt64;
amount_with_fee: AmountNbo;
deposit_fee: AmountNbo;
merchant: EddsaPublicKey;
coin_pub: EddsaPublicKey;
}
class DepositRequestPS extends SignatureStruct {
constructor(w: DepositRequestPS_Args) {
super(w);
}
purpose() {
return SignaturePurpose.WALLET_COIN_DEPOSIT;
}
fieldTypes() {
return [
["h_contract", HashCode],
["h_wire", HashCode],
["timestamp", AbsoluteTimeNbo],
["refund_deadline", AbsoluteTimeNbo],
["transaction_id", UInt64],
["amount_with_fee", AmountNbo],
["deposit_fee", AmountNbo],
["merchant", EddsaPublicKey],
["coin_pub", EddsaPublicKey],
];
}
}
interface Encodeable {
encode(arena?: Arena): ByteArray;
}
function makeEncode(encodeFn) {
function encode(arena?: Arena) {
let ptr = emscAlloc.malloc(PTR_SIZE);
let len = encodeFn(this.getNative(), ptr);
let res = new ByteArray(len, null, arena);
res.setNative(Module.getValue(ptr, '*'));
emsc.free(ptr);
return res;
}
return encode;
}
class RsaPublicKey extends ArenaObject implements Encodeable {
static fromCrock: (s: string, a?: Arena) => RsaPublicKey;
toCrock() { toCrock() {
return this.encode().toCrock(); return this.encode().toCrock();
} }
@ -627,57 +797,49 @@ class RsaPublicKey extends ArenaObject {
this.nativePtr = 0; this.nativePtr = 0;
} }
encode(arena?: Arena): ByteArray { encode: (arena?: Arena) => ByteArray;
let ptr = emscAlloc.malloc(PTR_SIZE);
let len = emscAlloc.rsa_public_key_encode(this.nativePtr, ptr);
let res = new ByteArray(len, Module.getValue(ptr, '*'), arena);
emsc.free(ptr);
return res;
}
} }
mixinStatic(RsaPublicKey, makeFromCrock(emscAlloc.rsa_public_key_decode));
mixin(RsaPublicKey, makeEncode(emscAlloc.rsa_public_key_encode));
class EddsaSignature extends PackedArenaObject { class EddsaSignature extends PackedArenaObject {
size() { return 64; } size() {
return 64;
}
} }
class RsaSignature extends ArenaObject { class RsaSignature extends ArenaObject implements Encodeable{
static fromCrock(s: string, a?: Arena): RsaSignature { static fromCrock: (s: string, a?: Arena) => RsaSignature;
let obj = new this(a);
let buf = ByteArray.fromCrock(s);
obj.setNative(emscAlloc.rsa_signature_decode(buf.getNative(), buf.size()));
buf.destroy();
return obj;
}
encode(arena?: Arena): ByteArray { encode: (arena?: Arena) => ByteArray;
return encodeWith(this, emscAlloc.rsa_signature_encode);
}
destroy() { destroy() {
emsc.rsa_signature_free(this.getNative()); emsc.rsa_signature_free(this.getNative());
this.setNative(0); this.setNative(0);
} }
} }
mixinStatic(RsaSignature, makeFromCrock(emscAlloc.rsa_signature_decode));
mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode));
function rsaBlind(hashCode: HashCode, function rsaBlind(hashCode: HashCode,
blindingKey: RsaBlindingKey, blindingKey: RsaBlindingKey,
pkey: RsaPublicKey, pkey: RsaPublicKey,
arena?: Arena): ByteArray arena?: Arena): ByteArray {
{
let ptr = emscAlloc.malloc(PTR_SIZE); let ptr = emscAlloc.malloc(PTR_SIZE);
let s = emscAlloc.rsa_blind(hashCode.nativePtr, blindingKey.nativePtr, pkey.nativePtr, ptr); let s = emscAlloc.rsa_blind(hashCode.nativePtr,
let res = new ByteArray(s, Module.getValue(ptr, '*'), arena); blindingKey.nativePtr,
return res; pkey.nativePtr,
ptr);
return new ByteArray(s, Module.getValue(ptr, '*'), arena);
} }
function eddsaSign(purpose: EccSignaturePurpose, function eddsaSign(purpose: EccSignaturePurpose,
priv: EddsaPrivateKey, priv: EddsaPrivateKey,
a?: Arena): EddsaSignature a?: Arena): EddsaSignature {
{
let sig = new EddsaSignature(a); let sig = new EddsaSignature(a);
sig.alloc(); sig.alloc();
let res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr); let res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr);
@ -687,13 +849,14 @@ function eddsaSign(purpose: EccSignaturePurpose,
return sig; return sig;
} }
function rsaUnblind(sig: RsaSignature, function rsaUnblind(sig: RsaSignature,
bk: RsaBlindingKey, bk: RsaBlindingKey,
pk: RsaPublicKey, pk: RsaPublicKey,
a?: Arena): RsaSignature { a?: Arena): RsaSignature {
let x = new RsaSignature(a); let x = new RsaSignature(a);
x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr, bk.nativePtr, pk.nativePtr); x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr,
bk.nativePtr,
pk.nativePtr);
return x; return x;
} }

File diff suppressed because one or more lines are too long

View File

@ -1,38 +1,6 @@
/// <reference path="../decl/urijs/URIjs.d.ts" /> /// <reference path="../decl/urijs/URIjs.d.ts" />
/// <reference path="../decl/chrome/chrome.d.ts" /> /// <reference path="../decl/chrome/chrome.d.ts" />
'use strict'; '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:
db.createObjectStore("mints", { keyPath: "baseUrl" });
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 });
break;
}
};
});
}
/** /**
* See http://api.taler.net/wallet.html#general * See http://api.taler.net/wallet.html#general
*/ */
@ -46,8 +14,141 @@ function canonicalizeBaseUrl(url) {
x.query(); x.query();
return x.href(); return x.href();
} }
function grantCoins(db, feeThreshold, paymentAmount, mintBaseUrl) { function signDeposit(db, offer, cds) {
throw "not implemented"; 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.cmp(new Amount(cd.coin.currentAmount)) < 0) {
coinSpend = new Amount(amountRemaining.toJson());
}
else {
coinSpend = new Amount(cd.coin.currentAmount);
}
let d = new DepositRequestPS({
h_contract: HashCode.fromCrock(offer.H_contract),
h_wire: HashCode.fromCrock(offer.contract.H_wire),
amount_with_fee: new Amount(cd.coin.currentAmount).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),
});
amountSpent.add(coinSpend);
let newAmount = new Amount(cd.coin.currentAmount);
newAmount.sub(coinSpend);
cd.coin.currentAmount = newAmount.toJson();
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: amountSpent.toJson(),
};
ret.push({ sig: coinSig, 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 db
* @param paymentAmount
* @param depositFeeLimit
* @param mintKeys
*/
function getPossibleMintCoins(db, paymentAmount, depositFeeLimit, allowedMints) {
return new Promise((resolve, reject) => {
let m = {};
let found = false;
let tx = db.transaction(["mints", "coins"]);
// First pass: Get all coins from acceptable mints.
for (let info of allowedMints) {
let req_mints = tx.objectStore("mints")
.index("pubKey")
.get(info.master_pub);
req_mints.onsuccess = (e) => {
let mint = req_mints.result;
let req_coins = tx.objectStore("coins")
.index("mintBaseUrl")
.openCursor(IDBKeyRange.only(mint.baseUrl));
req_coins.onsuccess = (e) => {
let cursor = req_coins.result;
if (!cursor) {
return;
}
let cd = {
coin: cursor.value,
denom: mint.keys.denoms[cursor.value.denomPub]
};
let x = m[mint.baseUrl];
if (!x) {
m[mint.baseUrl] = [cd];
}
else {
x.push(cd);
}
};
};
}
tx.oncomplete = (e) => {
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 = new Amount(coins[0].c.coin.currentAmount);
for (let i = 0; i < coins.length; i++) {
if (accFee.cmp(maxFee) >= 0) {
continue nextMint;
}
if (accAmount.cmp(minAmount) >= 0) {
ret[key] = m[key];
continue nextMint;
}
accFee.add(coins[i].a);
accFee.add(new Amount(coins[i].c.coin.currentAmount));
}
}
resolve(ret);
};
tx.onerror = (e) => {
reject();
};
});
}
function executePay(db, offer, payCoinInfo, merchantBaseUrl, chosenMint) {
return new Promise((resolve, reject) => {
let reqData = {};
reqData["H_wire"] = offer.contract.H_wire;
reqData["H_contract"] = offer.H_contract;
reqData["transaction_id"] = offer.contract.transaction_id;
reqData["refund_deadline"] = offer.contract.refund_deadline;
reqData["mint"] = chosenMint;
reqData["coins"] = payCoinInfo.map((x) => x.sig);
let payUrl = URI(merchantBaseUrl).absoluteTo(merchantBaseUrl);
console.log("Merchant URL", payUrl);
let req = new XMLHttpRequest();
req.open('post', payUrl.href());
req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
req.addEventListener('readystatechange', (e) => {
if (req.readyState == XMLHttpRequest.DONE) {
resolve();
}
});
});
} }
function confirmPay(db, detail, sendResponse) { function confirmPay(db, detail, sendResponse) {
console.log("confirmPay", JSON.stringify(detail)); console.log("confirmPay", JSON.stringify(detail));
@ -57,10 +158,17 @@ function confirmPay(db, detail, sendResponse) {
contract: detail.offer.contract, contract: detail.offer.contract,
sig: detail.offer sig: detail.offer
}; };
let contract = detail.offer.contract; let offer = detail.offer;
//let chosenCoinPromise = chooseCoins(db, contract.max_fee, contract.amount) getPossibleMintCoins(db, offer.contract.amount, offer.contract.max_fee, offer.contract.mints)
// .then(x => generateDepositPermissions(db, x)) .then((mcs) => {
// .then(executePayment); if (Object.keys(mcs).length == 0) {
sendResponse({ error: "Not enough coins." });
return;
}
let mintUrl = Object.keys(mcs)[0];
let ds = signDeposit(db, offer, mcs[mintUrl]);
return executePay(db, offer, ds, detail.merchantPageUrl, mintUrl);
});
return true; return true;
} }
function confirmReserve(db, detail, sendResponse) { function confirmReserve(db, detail, sendResponse) {
@ -110,7 +218,10 @@ function confirmReserve(db, detail, sendResponse) {
sendResponse(resp); sendResponse(resp);
var mint; var mint;
updateMintFromUrl(db, reserveRecord.mint_base_url) updateMintFromUrl(db, reserveRecord.mint_base_url)
.then((m) => { mint = m; return updateReserve(db, reservePub, mint); }) .then((m) => {
mint = m;
return updateReserve(db, reservePub, mint);
})
.then((reserve) => depleteReserve(db, reserve, mint)); .then((reserve) => depleteReserve(db, reserve, mint));
}); });
break; break;
@ -213,7 +324,7 @@ function withdrawExecute(db, pc) {
console.log("Withdrawal successful"); console.log("Withdrawal successful");
console.log(myRequest.responseText); console.log(myRequest.responseText);
let resp = JSON.parse(myRequest.responseText); let resp = JSON.parse(myRequest.responseText);
let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.coin_ev), RsaBlindingKey.fromCrock(pc.blindingKey), RsaPublicKey.fromCrock(pc.denomPub)); let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.ev_sig), RsaBlindingKey.fromCrock(pc.blindingKey), RsaPublicKey.fromCrock(pc.denomPub));
let coin = { let coin = {
coinPub: pc.coinPub, coinPub: pc.coinPub,
coinPriv: pc.coinPriv, coinPriv: pc.coinPriv,
@ -397,7 +508,9 @@ function dumpDb(db, detail, sendResponse) {
let name = db.objectStoreNames[i]; let name = db.objectStoreNames[i];
let storeDump = {}; let storeDump = {};
dump.stores[name] = storeDump; dump.stores[name] = storeDump;
let store = tx.objectStore(name).openCursor().addEventListener('success', (e) => { let store = tx.objectStore(name)
.openCursor()
.addEventListener('success', (e) => {
let cursor = e.target.result; let cursor = e.target.result;
if (cursor) { if (cursor) {
storeDump[cursor.key] = cursor.value; storeDump[cursor.key] = cursor.value;

View File

@ -2,39 +2,11 @@
/// <reference path="../decl/chrome/chrome.d.ts" /> /// <reference path="../decl/chrome/chrome.d.ts" />
'use strict'; 'use strict';
const DB_NAME = "taler";
const DB_VERSION = 1;
interface AmountJson {
/** value: number;
* Return a promise that resolves fraction: number;
* to the taler wallet db. currency: string;
*/
function openTalerDb(): Promise<IDBDatabase> {
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: // DB does not exist yet
db.createObjectStore("mints", { keyPath: "baseUrl" });
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 });
break;
}
};
});
} }
@ -52,17 +24,214 @@ function canonicalizeBaseUrl(url) {
return x.href() return x.href()
} }
interface ConfirmPayRequest { interface ConfirmPayRequest {
offer: any; merchantPageUrl: string;
selectedMint: any; offer: Offer;
}
interface MintCoins {
[mintUrl: string]: Db.CoinWithDenom[];
}
interface Offer {
contract: Contract;
sig: string;
H_contract: string;
}
interface Contract {
H_wire: string;
amount: AmountJson;
auditors: string[];
expiry: string,
locations: string[];
max_fee: AmountJson;
merchant: any;
merchant_pub: string;
mints: MintInfo[];
pay_url: string;
products: string[];
refund_deadline: string;
timestamp: string;
transaction_id: number;
} }
function grantCoins(db: IDBDatabase, interface CoinPaySig {
feeThreshold: AmountJson, coin_sig: string;
paymentAmount: AmountJson, coin_pub: string;
mintBaseUrl: string): Promise<any> { ub_sig: string;
throw "not implemented"; denom_pub: string;
f: AmountJson;
}
type PayCoinInfo = Array<{ updatedCoin: Db.Coin, sig: CoinPaySig }>;
function signDeposit(db: IDBDatabase,
offer: Offer,
cds: Db.CoinWithDenom[]): PayCoinInfo {
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.cmp(new Amount(cd.coin.currentAmount)) < 0) {
coinSpend = new Amount(amountRemaining.toJson());
} else {
coinSpend = new Amount(cd.coin.currentAmount);
}
let d = new DepositRequestPS({
h_contract: HashCode.fromCrock(offer.H_contract),
h_wire: HashCode.fromCrock(offer.contract.H_wire),
amount_with_fee: new Amount(cd.coin.currentAmount).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),
});
amountSpent.add(coinSpend);
let newAmount = new Amount(cd.coin.currentAmount);
newAmount.sub(coinSpend);
cd.coin.currentAmount = newAmount.toJson();
let coinSig = eddsaSign(d.toPurpose(),
EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
.toCrock();
let s: CoinPaySig = {
coin_sig: coinSig,
coin_pub: cd.coin.coinPub,
ub_sig: cd.coin.denomSig,
denom_pub: cd.coin.denomPub,
f: amountSpent.toJson(),
};
ret.push({sig: coinSig, updatedCoin: cd.coin});
}
return ret;
}
interface MintInfo {
master_pub: string;
url: string;
}
/**
* Get mints and associated coins that are still spendable,
* but only if the sum the coins' remaining value exceeds the payment amount.
* @param db
* @param paymentAmount
* @param depositFeeLimit
* @param mintKeys
*/
function getPossibleMintCoins(db: IDBDatabase,
paymentAmount: AmountJson,
depositFeeLimit: AmountJson,
allowedMints: MintInfo[]): Promise<MintCoins> {
return new Promise((resolve, reject) => {
let m: MintCoins = {};
let found = false;
let tx = db.transaction(["mints", "coins"]);
// First pass: Get all coins from acceptable mints.
for (let info of allowedMints) {
let req_mints = tx.objectStore("mints")
.index("pubKey")
.get(info.master_pub);
req_mints.onsuccess = (e) => {
let mint: Db.Mint = req_mints.result;
let req_coins = tx.objectStore("coins")
.index("mintBaseUrl")
.openCursor(IDBKeyRange.only(mint.baseUrl));
req_coins.onsuccess = (e) => {
let cursor: IDBCursorWithValue = req_coins.result;
if (!cursor) {
return;
}
let cd = {
coin: cursor.value,
denom: mint.keys.denoms[cursor.value.denomPub]
};
let x = m[mint.baseUrl];
if (!x) {
m[mint.baseUrl] = [cd];
} else {
x.push(cd);
}
}
}
}
tx.oncomplete = (e) => {
let ret: MintCoins = {};
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 = new Amount(coins[0].c.coin.currentAmount);
for (let i = 0; i < coins.length; i++) {
if (accFee.cmp(maxFee) >= 0) {
continue nextMint;
}
if (accAmount.cmp(minAmount) >= 0) {
ret[key] = m[key];
continue nextMint;
}
accFee.add(coins[i].a);
accFee.add(new Amount(coins[i].c.coin.currentAmount));
}
}
resolve(ret);
};
tx.onerror = (e) => {
reject();
}
});
}
function executePay(db,
offer: Offer,
payCoinInfo: PayCoinInfo,
merchantBaseUrl: string,
chosenMint: string) {
return new Promise((resolve, reject) => {
let reqData = {};
reqData["H_wire"] = offer.contract.H_wire;
reqData["H_contract"] = offer.H_contract;
reqData["transaction_id"] = offer.contract.transaction_id;
reqData["refund_deadline"] = offer.contract.refund_deadline;
reqData["mint"] = chosenMint;
reqData["coins"] = payCoinInfo.map((x) => x.sig);
let payUrl = URI(merchantBaseUrl).absoluteTo(merchantBaseUrl);
console.log("Merchant URL", payUrl);
let req = new XMLHttpRequest();
req.open('post', payUrl.href());
req.setRequestHeader("Content-Type",
"application/json;charset=UTF-8");
req.addEventListener('readystatechange', (e) => {
if (req.readyState == XMLHttpRequest.DONE) {
resolve()
}
});
});
} }
@ -73,17 +242,26 @@ function confirmPay(db, detail: ConfirmPayRequest, sendResponse) {
contractHash: detail.offer.H_contract, contractHash: detail.offer.H_contract,
contract: detail.offer.contract, contract: detail.offer.contract,
sig: detail.offer sig: detail.offer
} };
let contract = detail.offer.contract;
//let chosenCoinPromise = chooseCoins(db, contract.max_fee, contract.amount)
// .then(x => generateDepositPermissions(db, x))
// .then(executePayment);
let offer: Offer = detail.offer;
getPossibleMintCoins(db,
offer.contract.amount,
offer.contract.max_fee,
offer.contract.mints)
.then((mcs) => {
if (Object.keys(mcs).length == 0) {
sendResponse({error: "Not enough coins."});
return;
}
let mintUrl = Object.keys(mcs)[0];
let ds = signDeposit(db, offer, mcs[mintUrl]);
return executePay(db, offer, ds, detail.merchantPageUrl, mintUrl);
});
return true; return true;
} }
function confirmReserve(db, detail, sendResponse) { function confirmReserve(db, detail, sendResponse) {
let reservePriv = EddsaPrivateKey.create(); let reservePriv = EddsaPrivateKey.create();
let reservePub = reservePriv.getPublicKey(); let reservePub = reservePriv.getPublicKey();
@ -131,8 +309,11 @@ function confirmReserve(db, detail, sendResponse) {
sendResponse(resp); sendResponse(resp);
var mint; var mint;
updateMintFromUrl(db, reserveRecord.mint_base_url) updateMintFromUrl(db, reserveRecord.mint_base_url)
.then((m) => { mint = m; return updateReserve(db, reservePub, mint); }) .then((m) => {
.then((reserve) => depleteReserve(db, reserve, mint)); mint = m;
return updateReserve(db, reservePub, mint);
})
.then((reserve) => depleteReserve(db, reserve, mint));
}); });
break; break;
default: default:
@ -160,8 +341,8 @@ function rankDenom(denom1: any, denom2: any) {
function withdrawPrepare(db: IDBDatabase, function withdrawPrepare(db: IDBDatabase,
denom: Denomination, denom: Db.Denomination,
reserve): Promise<PreCoin> { reserve): Promise<Db.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();
@ -196,7 +377,7 @@ function withdrawPrepare(db: IDBDatabase,
console.log("crypto done, doing request"); console.log("crypto done, doing request");
let preCoin: PreCoin = { let preCoin: Db.PreCoin = {
reservePub: reservePub.toCrock(), reservePub: reservePub.toCrock(),
blindingKey: blindingFactor.toCrock(), blindingKey: blindingFactor.toCrock(),
coinPub: coinPub.toCrock(), coinPub: coinPub.toCrock(),
@ -219,6 +400,7 @@ function withdrawPrepare(db: IDBDatabase,
}); });
} }
function dbGet(db, store: string, key: any): Promise<any> { function dbGet(db, store: string, key: any): Promise<any> {
let tx = db.transaction([store]); let tx = db.transaction([store]);
let req = tx.objectStore(store).get(key); let req = tx.objectStore(store).get(key);
@ -228,48 +410,49 @@ function dbGet(db, store: string, key: any): Promise<any> {
} }
function withdrawExecute(db, pc: PreCoin): Promise<Coin> { function withdrawExecute(db, pc: Db.PreCoin): Promise<Db.Coin> {
return dbGet(db, 'reserves', pc.reservePub) return dbGet(db, 'reserves', pc.reservePub)
.then((r) => new Promise((resolve, reject) => { .then((r) => new Promise((resolve, reject) => {
console.log("loading precoin", JSON.stringify(pc)); console.log("loading precoin", JSON.stringify(pc));
let wd: any = {}; let wd: any = {};
wd.denom_pub = pc.denomPub; wd.denom_pub = pc.denomPub;
wd.reserve_pub = pc.reservePub; wd.reserve_pub = pc.reservePub;
wd.reserve_sig = pc.withdrawSig; wd.reserve_sig = pc.withdrawSig;
wd.coin_ev = pc.coinEv; wd.coin_ev = pc.coinEv;
let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url); let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url);
let myRequest = new XMLHttpRequest(); let myRequest = new XMLHttpRequest();
console.log("making request to " + reqUrl.href()); console.log("making request to " + reqUrl.href());
myRequest.open('post', reqUrl.href()); myRequest.open('post', reqUrl.href());
myRequest.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); myRequest.setRequestHeader("Content-Type",
myRequest.send(JSON.stringify(wd)); "application/json;charset=UTF-8");
myRequest.addEventListener('readystatechange', (e) => { myRequest.send(JSON.stringify(wd));
if (myRequest.readyState == XMLHttpRequest.DONE) { myRequest.addEventListener('readystatechange', (e) => {
if (myRequest.status != 200) { if (myRequest.readyState == XMLHttpRequest.DONE) {
console.log("Withdrawal failed, status ", myRequest.status); if (myRequest.status != 200) {
reject(); console.log("Withdrawal failed, status ", myRequest.status);
return; reject();
} return;
console.log("Withdrawal successful"); }
console.log(myRequest.responseText); console.log("Withdrawal successful");
let resp = JSON.parse(myRequest.responseText); console.log(myRequest.responseText);
let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.coin_ev), let resp = JSON.parse(myRequest.responseText);
RsaBlindingKey.fromCrock(pc.blindingKey), let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.ev_sig),
RsaPublicKey.fromCrock(pc.denomPub)); RsaBlindingKey.fromCrock(pc.blindingKey),
let coin: Coin = { RsaPublicKey.fromCrock(pc.denomPub));
coinPub: pc.coinPub, let coin: Db.Coin = {
coinPriv: pc.coinPriv, coinPub: pc.coinPub,
denomPub: pc.denomPub, coinPriv: pc.coinPriv,
denomSig: denomSig.encode().toCrock(), denomPub: pc.denomPub,
currentAmount: pc.coinValue denomSig: denomSig.encode().toCrock(),
} currentAmount: pc.coinValue
console.log("unblinded coin"); };
resolve(coin); console.log("unblinded coin");
} else { resolve(coin);
console.log("ready state change to", myRequest.status); } else {
} console.log("ready state change to", myRequest.status);
}); }
})); });
}));
} }
@ -284,14 +467,14 @@ function updateBadge(db) {
cursor.continue(); cursor.continue();
} else { } else {
console.log("badge"); console.log("badge");
chrome.browserAction.setBadgeText({text: ""+n}); chrome.browserAction.setBadgeText({text: "" + n});
chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"}); chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"});
} }
} }
} }
function storeCoin(db, coin: Coin) { function storeCoin(db, coin: Db.Coin) {
let tx = db.transaction(['coins', 'precoins'], 'readwrite'); let tx = db.transaction(['coins', 'precoins'], 'readwrite');
tx.objectStore('precoins').delete(coin.coinPub); tx.objectStore('precoins').delete(coin.coinPub);
tx.objectStore('coins').add(coin); tx.objectStore('coins').add(coin);
@ -306,8 +489,8 @@ function storeCoin(db, coin: Coin) {
function withdraw(db, denom, reserve): Promise<void> { function withdraw(db, denom, reserve): Promise<void> {
return withdrawPrepare(db, denom, reserve) return withdrawPrepare(db, denom, reserve)
.then((pc) => withdrawExecute(db, pc)) .then((pc) => withdrawExecute(db, pc))
.then((c) => storeCoin(db, c)); .then((c) => storeCoin(db, c));
} }
@ -344,7 +527,7 @@ function depleteReserve(db, reserve, mint) {
console.log("doing work"); console.log("doing work");
let d = workList.pop(); let d = workList.pop();
withdraw(db, d, reserve) withdraw(db, d, reserve)
.then(() => next()); .then(() => next());
} }
next(); next();
@ -408,7 +591,7 @@ function updateMintFromUrl(db, baseUrl) {
console.log("keys invalid"); console.log("keys invalid");
reject(); reject();
} else { } else {
let mint = { let mint: Db.Mint = {
baseUrl: baseUrl, baseUrl: baseUrl,
keys: mintKeysJson keys: mintKeysJson
}; };
@ -419,7 +602,7 @@ function updateMintFromUrl(db, baseUrl) {
let di = { let di = {
denomPub: d.denom_pub, denomPub: d.denom_pub,
value: d.value value: d.value
} };
tx.objectStore('denoms').put(di); tx.objectStore('denoms').put(di);
} }
tx.oncomplete = (e) => { tx.oncomplete = (e) => {
@ -446,19 +629,21 @@ function dumpDb(db, detail, sendResponse) {
console.log("stores: " + JSON.stringify(db.objectStoreNames)); console.log("stores: " + JSON.stringify(db.objectStoreNames));
let tx = db.transaction(db.objectStoreNames); let tx = db.transaction(db.objectStoreNames);
tx.addEventListener('complete', (e) => { tx.addEventListener('complete', (e) => {
sendResponse(dump); sendResponse(dump);
}); });
for (let i = 0; i < db.objectStoreNames.length; i++) { for (let i = 0; i < db.objectStoreNames.length; i++) {
let name = db.objectStoreNames[i]; let name = db.objectStoreNames[i];
let storeDump = {}; let storeDump = {};
dump.stores[name] = storeDump; dump.stores[name] = storeDump;
let store = tx.objectStore(name).openCursor().addEventListener('success', (e) => { let store = tx.objectStore(name)
let cursor = e.target.result; .openCursor()
if (cursor) { .addEventListener('success', (e) => {
storeDump[cursor.key] = cursor.value; let cursor = e.target.result;
cursor.continue(); if (cursor) {
} storeDump[cursor.key] = cursor.value;
}); cursor.continue();
}
});
} }
return true; return true;
} }
@ -513,7 +698,7 @@ chrome.browserAction.setBadgeText({text: ""});
openTalerDb().then((db) => { openTalerDb().then((db) => {
console.log("db loaded"); console.log("db loaded");
chrome.runtime.onMessage.addListener( chrome.runtime.onMessage.addListener(
function (req, sender, onresponse) { function(req, sender, onresponse) {
let dispatch = { let dispatch = {
"confirm-reserve": confirmReserve, "confirm-reserve": confirmReserve,
"confirm-pay": confirmPay, "confirm-pay": confirmPay,
@ -524,7 +709,9 @@ openTalerDb().then((db) => {
if (req.type in dispatch) { if (req.type in dispatch) {
return dispatch[req.type](db, req.detail, onresponse); return dispatch[req.type](db, req.detail, onresponse);
} }
console.error(format("Request type {1} unknown, req {0}", JSON.stringify(req), req.type)); console.error(format("Request type {1} unknown, req {0}",
JSON.stringify(req),
req.type));
return false; return false;
}); });
}); });

View File

@ -28,3 +28,7 @@ function format(s, ...args) {
s = s.replace(/{([0-9]+)}/g, r); s = s.replace(/{([0-9]+)}/g, r);
return s; return s;
} }
function promiseFinally(p, fn) {
return p.then((x) => { fn(); return x; })
.catch((e) => { fn(); throw e; });
}

View File

@ -32,3 +32,8 @@ function format(s: string, ...args: any[]) {
return s; return s;
} }
function promiseFinally<T>(p: Promise<T>, fn): Promise<T> {
return p.then((x) => { fn(); return x; })
.catch((e) => {fn(); throw e;});
}

View File

@ -26,6 +26,10 @@
<p /> <p />
</script> </script>
<script id="error-template" type="text/x-handlebars-template">
Payment was not successful: {{error}}
</script>
</head> </head>
<body> <body>
@ -33,7 +37,9 @@
<div id="render-contract"></div> <div id="render-contract"></div>
<button id="confirm-purchase">Confirm Purchase!</button> <button id="confirm-pay">Confirm Pay!</button>
<div id="status"></div>
</body> </body>
</html> </html>

View File

@ -23,16 +23,16 @@ document.addEventListener("DOMContentLoaded", (e) => {
let html = template(offer.contract); let html = template(offer.contract);
$_("render-contract").innerHTML = html; $_("render-contract").innerHTML = html;
document.getElementById("confirm-pay").addEventListener("click", (e) => { document.getElementById("confirm-pay").addEventListener("click", (e) => {
let d = clone(query); let d = {
offer: JSON.parse(query.offer)
};
chrome.runtime.sendMessage({ type: 'confirm-pay', detail: d }, (resp) => { chrome.runtime.sendMessage({ type: 'confirm-pay', detail: d }, (resp) => {
if (resp.success === true) { console.log("got response", resp);
document.location.href = resp.backlink; if ("error" in resp) {
} let source = $_("error-template").innerHTML;
else { let template = Handlebars.compile(source);
document.body.innerHTML = $_("status").innerHTML = template(resp);
`Oops, something went wrong. return;
Here is some more info:
<pre>${resp.text}</pre>`;
} }
}); });
}); });

View File

@ -34,15 +34,16 @@ document.addEventListener("DOMContentLoaded", (e) => {
$_("render-contract").innerHTML = html; $_("render-contract").innerHTML = html;
document.getElementById("confirm-pay").addEventListener("click", (e) => { document.getElementById("confirm-pay").addEventListener("click", (e) => {
let d = clone(query); let d = {
offer: JSON.parse(query.offer)
};
chrome.runtime.sendMessage({type:'confirm-pay', detail: d}, (resp) => { chrome.runtime.sendMessage({type:'confirm-pay', detail: d}, (resp) => {
if (resp.success === true) { console.log("got response", resp);
document.location.href = resp.backlink; if ("error" in resp) {
} else { let source = $_("error-template").innerHTML;
document.body.innerHTML = let template = Handlebars.compile(source);
`Oops, something went wrong. $_("status").innerHTML = template(resp);
Here is some more info: return;
<pre>${resp.text}</pre>`;
} }
}); });
}); });

View File

@ -7,5 +7,6 @@
<h1>Debug Pages</h1> <h1>Debug Pages</h1>
<a href="show-db.html">Show DB</a> <br> <a href="show-db.html">Show DB</a> <br>
<a href="../popup/balance-overview.html">Show balance</a> <a href="../popup/balance-overview.html">Show balance</a>
</body> </body>
</html> </html>

View File

@ -20,6 +20,15 @@ document.addEventListener('DOMContentLoaded', (e) => {
let context = document.getElementById("balance-template").innerHTML; let context = document.getElementById("balance-template").innerHTML;
let template = Handlebars.compile(context); let template = Handlebars.compile(context);
document.getElementById("content").innerHTML = template(wallet); document.getElementById("content").innerHTML = template(wallet);
let el = document.getElementById("link-kudos");
if (el) {
el.onclick = (e) => {
let target = e.target;
chrome.tabs.create({
"url": target.href
});
};
}
}); });
document.getElementById("debug").addEventListener("click", (e) => { document.getElementById("debug").addEventListener("click", (e) => {
chrome.tabs.create({ chrome.tabs.create({
@ -29,10 +38,4 @@ document.addEventListener('DOMContentLoaded', (e) => {
document.getElementById("reset").addEventListener("click", (e) => { document.getElementById("reset").addEventListener("click", (e) => {
chrome.runtime.sendMessage({ type: "reset" }); chrome.runtime.sendMessage({ type: "reset" });
}); });
document.getElementById("link-kudos").addEventListener("click", (e) => {
let target = e.target;
chrome.tabs.create({
"url": target.href
});
});
}); });

View File

@ -16,12 +16,22 @@ let React = {
} }
} }
document.addEventListener('DOMContentLoaded', (e) => { document.addEventListener('DOMContentLoaded', (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; let context = document.getElementById("balance-template").innerHTML;
let template = Handlebars.compile(context); let template = Handlebars.compile(context);
document.getElementById("content").innerHTML = template(wallet); document.getElementById("content").innerHTML = template(wallet);
let el = document.getElementById("link-kudos");
if (el) {
el.onclick = (e) => {
let target: any = e.target;
chrome.tabs.create({
"url": target.href
});
};
}
}); });
document.getElementById("debug").addEventListener("click", (e) => { document.getElementById("debug").addEventListener("click", (e) => {
@ -32,10 +42,4 @@ document.addEventListener('DOMContentLoaded', (e) => {
document.getElementById("reset").addEventListener("click", (e) => { document.getElementById("reset").addEventListener("click", (e) => {
chrome.runtime.sendMessage({type: "reset"}); chrome.runtime.sendMessage({type: "reset"});
}); });
document.getElementById("link-kudos").addEventListener("click", (e) => {
let target: any = e.target;
chrome.tabs.create({
"url": target.href
});
});
}); });