headless wallet WIP
This commit is contained in:
parent
bcefbd7aab
commit
cc4e8ddc85
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -35,5 +35,6 @@
|
|||||||
"**/*.js.map": true
|
"**/*.js.map": true
|
||||||
},
|
},
|
||||||
"tslint.enable": true,
|
"tslint.enable": true,
|
||||||
"editor.wrappingIndent": "same"
|
"editor.wrappingIndent": "same",
|
||||||
|
"editor.tabSize": 2
|
||||||
}
|
}
|
@ -109,7 +109,6 @@ const tsBaseArgs = {
|
|||||||
noImplicitAny: true,
|
noImplicitAny: true,
|
||||||
allowJs: true,
|
allowJs: true,
|
||||||
checkJs: true,
|
checkJs: true,
|
||||||
noUnusedLocals: true,
|
|
||||||
incremental: true,
|
incremental: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
"@types/react-dom": "^16.0.0",
|
"@types/react-dom": "^16.0.0",
|
||||||
"ava": "^1.4.1",
|
"ava": "^1.4.1",
|
||||||
"awesome-typescript-loader": "^5.2.1",
|
"awesome-typescript-loader": "^5.2.1",
|
||||||
"axios": "^0.18.0",
|
|
||||||
"glob": "^7.1.1",
|
"glob": "^7.1.1",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"gulp-gzip": "^1.2.0",
|
"gulp-gzip": "^1.2.0",
|
||||||
@ -59,6 +58,8 @@
|
|||||||
"webpack-merge": "^4.1.0"
|
"webpack-merge": "^4.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^2.20.0"
|
"axios": "^0.19.0",
|
||||||
|
"commander": "^2.20.0",
|
||||||
|
"source-map-support": "^0.5.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,10 +60,10 @@ export namespace Checkable {
|
|||||||
stringChecker?: (s: string) => boolean;
|
stringChecker?: (s: string) => boolean;
|
||||||
valueProp?: any;
|
valueProp?: any;
|
||||||
optional?: boolean;
|
optional?: boolean;
|
||||||
extraAllowed?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CheckableInfo {
|
interface CheckableInfo {
|
||||||
|
extraAllowed: boolean;
|
||||||
props: Prop[];
|
props: Prop[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ export namespace Checkable {
|
|||||||
function getCheckableInfo(target: any): CheckableInfo {
|
function getCheckableInfo(target: any): CheckableInfo {
|
||||||
let chk = target[checkableInfoSym] as CheckableInfo|undefined;
|
let chk = target[checkableInfoSym] as CheckableInfo|undefined;
|
||||||
if (!chk) {
|
if (!chk) {
|
||||||
chk = { props: [] };
|
chk = { props: [], extraAllowed: false };
|
||||||
target[checkableInfoSym] = chk;
|
target[checkableInfoSym] = chk;
|
||||||
}
|
}
|
||||||
return chk;
|
return chk;
|
||||||
@ -188,7 +188,8 @@ export namespace Checkable {
|
|||||||
throw new SchemaError(
|
throw new SchemaError(
|
||||||
`expected object for ${path.join(".")}, got ${typeof v} instead`);
|
`expected object for ${path.join(".")}, got ${typeof v} instead`);
|
||||||
}
|
}
|
||||||
const props = type.prototype[checkableInfoSym].props;
|
const chk = type.prototype[checkableInfoSym];
|
||||||
|
const props = chk.props;
|
||||||
const remainingPropNames = new Set(Object.getOwnPropertyNames(v));
|
const remainingPropNames = new Set(Object.getOwnPropertyNames(v));
|
||||||
const obj = new type();
|
const obj = new type();
|
||||||
for (const innerProp of props) {
|
for (const innerProp of props) {
|
||||||
@ -207,7 +208,7 @@ export namespace Checkable {
|
|||||||
path.concat([innerProp.propertyKey]));
|
path.concat([innerProp.propertyKey]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prop.extraAllowed && remainingPropNames.size !== 0) {
|
if (!chk.extraAllowed && remainingPropNames.size !== 0) {
|
||||||
const err = `superfluous properties ${JSON.stringify(Array.from(remainingPropNames.values()))} of ${typeName}`;
|
const err = `superfluous properties ${JSON.stringify(Array.from(remainingPropNames.values()))} of ${typeName}`;
|
||||||
throw new SchemaError(err);
|
throw new SchemaError(err);
|
||||||
}
|
}
|
||||||
@ -222,16 +223,16 @@ export namespace Checkable {
|
|||||||
*/
|
*/
|
||||||
export function Class(opts: {extra?: boolean, validate?: boolean} = {}) {
|
export function Class(opts: {extra?: boolean, validate?: boolean} = {}) {
|
||||||
return (target: any) => {
|
return (target: any) => {
|
||||||
|
const chk = getCheckableInfo(target.prototype);
|
||||||
|
chk.extraAllowed = !!opts.extra;
|
||||||
target.checked = (v: any) => {
|
target.checked = (v: any) => {
|
||||||
const cv = checkValue(v, {
|
const cv = checkValue(v, {
|
||||||
checker: checkValue,
|
checker: checkValue,
|
||||||
extraAllowed: !!opts.extra,
|
|
||||||
propertyKey: "(root)",
|
propertyKey: "(root)",
|
||||||
type: target,
|
type: target,
|
||||||
}, ["(root)"]);
|
}, ["(root)"]);
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
if (typeof target.validate !== "function") {
|
if (typeof target.validate !== "function") {
|
||||||
console.error("target", target);
|
|
||||||
throw Error("invalid Checkable annotion: validate method required");
|
throw Error("invalid Checkable annotion: validate method required");
|
||||||
}
|
}
|
||||||
// May throw exception
|
// May throw exception
|
||||||
|
@ -26,10 +26,12 @@ import {
|
|||||||
|
|
||||||
import { CryptoApi } from "./cryptoApi";
|
import { CryptoApi } from "./cryptoApi";
|
||||||
|
|
||||||
const masterPub1: string = "CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";
|
const masterPub1: string =
|
||||||
|
"CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";
|
||||||
|
|
||||||
const denomValid1: DenominationRecord = {
|
const denomValid1: DenominationRecord = {
|
||||||
denomPub: "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GHS84R3JHHP6GSM2D9Q6514CGT568R32C9J6CWM4DSH64TM4DSM851K0CA48CVKAC1P6H144C2160T46DHK8CVM4HJ274S38C1M6S338D9N6GWM8DT684T3JCT36S13EC9G88R3EGHQ8S0KJGSQ60SKGD216N33AGJ2651K2E9S60TMCD1N75244HHQ6X33EDJ570R3GGJ2651MACA38D130DA560VK4HHJ68WK2CA26GW3ECSH6D13EC9S88VK2GT66WVK8D9G750K0D9R8RRK4DHQ71332GHK8D23GE26710M2H9K6WVK8HJ38MVKEGA66N23AC9H88VKACT58MV3CCSJ6H1K4DT38GRK0C9M8N33CE1R60V4AHA38H1KECSH6S33JH9N8GRKGH1K68S36GH354520818CMG26C1H60R30C935452081918G2J2G0",
|
denomPub:
|
||||||
|
"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GHS84R3JHHP6GSM2D9Q6514CGT568R32C9J6CWM4DSH64TM4DSM851K0CA48CVKAC1P6H144C2160T46DHK8CVM4HJ274S38C1M6S338D9N6GWM8DT684T3JCT36S13EC9G88R3EGHQ8S0KJGSQ60SKGD216N33AGJ2651K2E9S60TMCD1N75244HHQ6X33EDJ570R3GGJ2651MACA38D130DA560VK4HHJ68WK2CA26GW3ECSH6D13EC9S88VK2GT66WVK8D9G750K0D9R8RRK4DHQ71332GHK8D23GE26710M2H9K6WVK8HJ38MVKEGA66N23AC9H88VKACT58MV3CCSJ6H1K4DT38GRK0C9M8N33CE1R60V4AHA38H1KECSH6S33JH9N8GRKGH1K68S36GH354520818CMG26C1H60R30C935452081918G2J2G0",
|
||||||
denomPubHash: "dummy",
|
denomPubHash: "dummy",
|
||||||
exchangeBaseUrl: "https://exchange.example.com/",
|
exchangeBaseUrl: "https://exchange.example.com/",
|
||||||
feeDeposit: {
|
feeDeposit: {
|
||||||
@ -53,7 +55,8 @@ const denomValid1: DenominationRecord = {
|
|||||||
value: 0,
|
value: 0,
|
||||||
},
|
},
|
||||||
isOffered: true,
|
isOffered: true,
|
||||||
masterSig: "CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
|
masterSig:
|
||||||
|
"CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
|
||||||
stampExpireDeposit: "/Date(1851580381)/",
|
stampExpireDeposit: "/Date(1851580381)/",
|
||||||
stampExpireLegal: "/Date(1567756381)/",
|
stampExpireLegal: "/Date(1567756381)/",
|
||||||
stampExpireWithdraw: "/Date(2482300381)/",
|
stampExpireWithdraw: "/Date(2482300381)/",
|
||||||
@ -69,15 +72,16 @@ const denomValid1: DenominationRecord = {
|
|||||||
const denomInvalid1 = JSON.parse(JSON.stringify(denomValid1));
|
const denomInvalid1 = JSON.parse(JSON.stringify(denomValid1));
|
||||||
denomInvalid1.value.value += 1;
|
denomInvalid1.value.value += 1;
|
||||||
|
|
||||||
test("string hashing", async (t) => {
|
test("string hashing", async t => {
|
||||||
const crypto = new CryptoApi();
|
const crypto = new CryptoApi();
|
||||||
const s = await crypto.hashString("hello taler");
|
const s = await crypto.hashString("hello taler");
|
||||||
const sh = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
|
const sh =
|
||||||
|
"8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
|
||||||
t.true(s === sh);
|
t.true(s === sh);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("precoin creation", async (t) => {
|
test("precoin creation", async t => {
|
||||||
const crypto = new CryptoApi();
|
const crypto = new CryptoApi();
|
||||||
const { priv, pub } = await crypto.createEddsaKeypair();
|
const { priv, pub } = await crypto.createEddsaKeypair();
|
||||||
const r: ReserveRecord = {
|
const r: ReserveRecord = {
|
||||||
@ -98,7 +102,7 @@ test("precoin creation", async (t) => {
|
|||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("denom validation", async (t) => {
|
test("denom validation", async t => {
|
||||||
const crypto = new CryptoApi();
|
const crypto = new CryptoApi();
|
||||||
let v: boolean;
|
let v: boolean;
|
||||||
v = await crypto.isValidDenom(denomValid1, masterPub1);
|
v = await crypto.isValidDenom(denomValid1, masterPub1);
|
||||||
|
@ -98,6 +98,28 @@ export class CryptoApi {
|
|||||||
*/
|
*/
|
||||||
private numBusy: number = 0;
|
private numBusy: number = 0;
|
||||||
|
|
||||||
|
public enableTracing = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminate all worker threads.
|
||||||
|
*/
|
||||||
|
terminateWorkers() {
|
||||||
|
for (let worker of this.workers) {
|
||||||
|
if (worker.w) {
|
||||||
|
worker.w.terminate();
|
||||||
|
if (worker.terminationTimerHandle) {
|
||||||
|
worker.terminationTimerHandle.clear();
|
||||||
|
worker.terminationTimerHandle = null;
|
||||||
|
}
|
||||||
|
if (worker.currentWorkItem) {
|
||||||
|
worker.currentWorkItem.reject(Error("explicitly terminated"));
|
||||||
|
worker.currentWorkItem = null;
|
||||||
|
}
|
||||||
|
worker.w = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a worker (if not started) and set as busy.
|
* Start a worker (if not started) and set as busy.
|
||||||
*/
|
*/
|
||||||
@ -136,7 +158,7 @@ export class CryptoApi {
|
|||||||
ws.w = null;
|
ws.w = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ws.terminationTimerHandle = timer.after(20 * 1000, destroy);
|
ws.terminationTimerHandle = timer.after(5 * 1000, destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWorkerError(ws: WorkerState, e: ErrorEvent) {
|
handleWorkerError(ws: WorkerState, e: ErrorEvent) {
|
||||||
@ -163,7 +185,7 @@ export class CryptoApi {
|
|||||||
this.findWork(ws);
|
this.findWork(ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
findWork(ws: WorkerState) {
|
private findWork(ws: WorkerState) {
|
||||||
// try to find more work for this worker
|
// try to find more work for this worker
|
||||||
for (let i = 0; i < NUM_PRIO; i++) {
|
for (let i = 0; i < NUM_PRIO; i++) {
|
||||||
const q = this.workQueues[NUM_PRIO - i - 1];
|
const q = this.workQueues[NUM_PRIO - i - 1];
|
||||||
@ -193,7 +215,8 @@ export class CryptoApi {
|
|||||||
console.error(`RPC with id ${id} has no registry entry`);
|
console.error(`RPC with id ${id} has no registry entry`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(
|
|
||||||
|
this.enableTracing && console.log(
|
||||||
`rpc ${currentWorkItem.operation} took ${timer.performanceNow() -
|
`rpc ${currentWorkItem.operation} took ${timer.performanceNow() -
|
||||||
currentWorkItem.startTime}ms`,
|
currentWorkItem.startTime}ms`,
|
||||||
);
|
);
|
||||||
|
@ -56,6 +56,9 @@ import {
|
|||||||
import * as native from "./emscInterface";
|
import * as native from "./emscInterface";
|
||||||
|
|
||||||
namespace RpcFunctions {
|
namespace RpcFunctions {
|
||||||
|
|
||||||
|
export let enableTracing: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a pre-coin of the given denomination to be withdrawn from then given
|
* Create a pre-coin of the given denomination to be withdrawn from then given
|
||||||
* reserve.
|
* reserve.
|
||||||
@ -735,19 +738,25 @@ worker.onmessage = (msg: MessageEvent) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (RpcFunctions.enableTracing) {
|
||||||
console.log("onmessage with", msg.data.operation);
|
console.log("onmessage with", msg.data.operation);
|
||||||
console.log("foo");
|
}
|
||||||
|
|
||||||
emscLoader.getLib().then(p => {
|
emscLoader.getLib().then(p => {
|
||||||
const lib = p.lib;
|
const lib = p.lib;
|
||||||
if (!native.isInitialized()) {
|
if (!native.isInitialized()) {
|
||||||
|
if (RpcFunctions.enableTracing) {
|
||||||
console.log("initializing emscripten for then first time with lib");
|
console.log("initializing emscripten for then first time with lib");
|
||||||
|
}
|
||||||
native.initialize(lib);
|
native.initialize(lib);
|
||||||
}
|
}
|
||||||
|
if (RpcFunctions.enableTracing) {
|
||||||
console.log("about to execute", msg.data.operation);
|
console.log("about to execute", msg.data.operation);
|
||||||
|
}
|
||||||
const res = f(...msg.data.args);
|
const res = f(...msg.data.args);
|
||||||
|
if (RpcFunctions.enableTracing) {
|
||||||
console.log("finished executing", msg.data.operation);
|
console.log("finished executing", msg.data.operation);
|
||||||
|
}
|
||||||
worker.postMessage({ result: res, id: msg.data.id });
|
worker.postMessage({ result: res, id: msg.data.id });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -17,8 +17,14 @@
|
|||||||
// tslint:disable:max-line-length
|
// tslint:disable:max-line-length
|
||||||
|
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
|
import * as emscLoader from "./emscLoader";
|
||||||
import * as native from "./emscInterface";
|
import * as native from "./emscInterface";
|
||||||
|
|
||||||
|
test.before(async () => {
|
||||||
|
const { lib } = await emscLoader.getLib();
|
||||||
|
native.initialize(lib);
|
||||||
|
});
|
||||||
|
|
||||||
test("string hashing", (t) => {
|
test("string hashing", (t) => {
|
||||||
const x = native.ByteArray.fromStringWithNull("hello taler");
|
const x = native.ByteArray.fromStringWithNull("hello taler");
|
||||||
const h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
|
const h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
|
||||||
@ -99,7 +105,7 @@ test("withdraw-request", (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("withdraw-request", (t) => {
|
test("currency-conversion", (t) => {
|
||||||
const a1 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000});
|
const a1 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000});
|
||||||
const a2 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000});
|
const a2 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000});
|
||||||
a1.add(a2);
|
a1.add(a2);
|
||||||
|
@ -43,6 +43,9 @@ export function initialize(lib: EmscLib) {
|
|||||||
if (!lib) {
|
if (!lib) {
|
||||||
throw Error("library must be object");
|
throw Error("library must be object");
|
||||||
}
|
}
|
||||||
|
if (!lib.ccall) {
|
||||||
|
throw Error("sanity check failed: EmscLib does not have 'ccall'");
|
||||||
|
}
|
||||||
if (maybeEmscEnv) {
|
if (maybeEmscEnv) {
|
||||||
throw Error("emsc lib already initialized");
|
throw Error("emsc lib already initialized");
|
||||||
}
|
}
|
||||||
|
@ -25,20 +25,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
let cachedLib = undefined;
|
let cachedLib = undefined;
|
||||||
|
let cachedLibPromise = undefined;
|
||||||
|
|
||||||
|
export let enableTracing = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the taler emscripten lib.
|
* Load the taler emscripten lib.
|
||||||
*
|
*
|
||||||
* If in a WebWorker, importScripts is used. Inside a browser, the module must
|
* If in a WebWorker, importScripts is used. Inside a browser, the module must
|
||||||
* be globally available. Inside node, require is used.
|
* be globally available. Inside node, require is used.
|
||||||
|
*
|
||||||
|
* Returns a Promise<{ lib: EmscLib }>
|
||||||
*/
|
*/
|
||||||
export function getLib() {
|
export function getLib() {
|
||||||
console.log("in getLib");
|
enableTracing && console.log("in getLib");
|
||||||
if (cachedLib) {
|
if (cachedLib) {
|
||||||
console.log("lib is cached");
|
enableTracing && console.log("lib is cached");
|
||||||
return Promise.resolve({ lib: cachedLib });
|
return Promise.resolve({ lib: cachedLib });
|
||||||
}
|
}
|
||||||
|
if (cachedLibPromise) {
|
||||||
|
return cachedLibPromise;
|
||||||
|
}
|
||||||
if (typeof require !== "undefined") {
|
if (typeof require !== "undefined") {
|
||||||
|
enableTracing && console.log("trying to load emscripten lib with 'require'");
|
||||||
// Make sure that TypeScript doesn't try
|
// Make sure that TypeScript doesn't try
|
||||||
// to check the taler-emscripten-lib.
|
// to check the taler-emscripten-lib.
|
||||||
const indirectRequire = require;
|
const indirectRequire = require;
|
||||||
@ -49,17 +58,30 @@ export function getLib() {
|
|||||||
const savedImportScripts = g.importScripts;
|
const savedImportScripts = g.importScripts;
|
||||||
delete g.importScripts;
|
delete g.importScripts;
|
||||||
// Assume that the code is run from the build/ directory.
|
// Assume that the code is run from the build/ directory.
|
||||||
const lib = indirectRequire("../../../emscripten/taler-emscripten-lib.js");
|
const libFn = indirectRequire("../../../emscripten/taler-emscripten-lib.js");
|
||||||
|
const lib = libFn();
|
||||||
g.importScripts = savedImportScripts;
|
g.importScripts = savedImportScripts;
|
||||||
if (lib) {
|
if (lib) {
|
||||||
cachedLib = lib;
|
if (!lib.ccall) {
|
||||||
return Promise.resolve({ lib: cachedLib });
|
throw Error("sanity check failed: taler-emscripten lib does not have 'ccall'");
|
||||||
}
|
}
|
||||||
|
cachedLibPromise = new Promise((resolve, reject) => {
|
||||||
|
lib.onRuntimeInitialized = () => {
|
||||||
|
cachedLib = lib;
|
||||||
|
cachedLibPromise = undefined;
|
||||||
|
resolve({ lib: cachedLib });
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return cachedLibPromise;
|
||||||
|
} else {
|
||||||
// When we're running as a webpack bundle, the above require might
|
// When we're running as a webpack bundle, the above require might
|
||||||
// have failed and returned 'undefined', so we try other ways to import.
|
// have failed and returned 'undefined', so we try other ways to import.
|
||||||
|
console.log("failed to load emscripten lib with 'require', trying alternatives");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof importScripts !== "undefined") {
|
if (typeof importScripts !== "undefined") {
|
||||||
|
console.log("trying to load emscripten lib with 'importScripts'");
|
||||||
self.TalerEmscriptenLib = {};
|
self.TalerEmscriptenLib = {};
|
||||||
importScripts('/emscripten/taler-emscripten-lib.js')
|
importScripts('/emscripten/taler-emscripten-lib.js')
|
||||||
if (!self.TalerEmscriptenLib) {
|
if (!self.TalerEmscriptenLib) {
|
||||||
|
122
src/db.ts
Normal file
122
src/db.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { Stores, WALLET_DB_VERSION } from "./dbTypes";
|
||||||
|
import { Store, Index } from "./query";
|
||||||
|
|
||||||
|
const DB_NAME = "taler";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a promise that resolves
|
||||||
|
* to the taler wallet db.
|
||||||
|
*/
|
||||||
|
export function openTalerDb(
|
||||||
|
idbFactory: IDBFactory,
|
||||||
|
onVersionChange: () => void,
|
||||||
|
onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void,
|
||||||
|
): Promise<IDBDatabase> {
|
||||||
|
return new Promise<IDBDatabase>((resolve, reject) => {
|
||||||
|
const req = idbFactory.open(DB_NAME, WALLET_DB_VERSION);
|
||||||
|
req.onerror = e => {
|
||||||
|
console.log("taler database error", e);
|
||||||
|
reject(e);
|
||||||
|
};
|
||||||
|
req.onsuccess = e => {
|
||||||
|
req.result.onversionchange = (evt: IDBVersionChangeEvent) => {
|
||||||
|
console.log(
|
||||||
|
`handling live db version change from ${evt.oldVersion} to ${
|
||||||
|
evt.newVersion
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
req.result.close();
|
||||||
|
onVersionChange();
|
||||||
|
};
|
||||||
|
resolve(req.result);
|
||||||
|
};
|
||||||
|
req.onupgradeneeded = e => {
|
||||||
|
const db = req.result;
|
||||||
|
console.log(
|
||||||
|
`DB: upgrade needed: oldVersion=${e.oldVersion}, newVersion=${
|
||||||
|
e.newVersion
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
switch (e.oldVersion) {
|
||||||
|
case 0: // DB does not exist yet
|
||||||
|
for (const n in Stores) {
|
||||||
|
if ((Stores as any)[n] instanceof Store) {
|
||||||
|
const si: Store<any> = (Stores as any)[n];
|
||||||
|
const s = db.createObjectStore(si.name, si.storeParams);
|
||||||
|
for (const indexName in si as any) {
|
||||||
|
if ((si as any)[indexName] instanceof Index) {
|
||||||
|
const ii: Index<any, any> = (si as any)[indexName];
|
||||||
|
s.createIndex(ii.indexName, ii.keyPath, ii.options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (e.oldVersion !== WALLET_DB_VERSION) {
|
||||||
|
onUpgradeUnsupported(e.oldVersion, WALLET_DB_VERSION);
|
||||||
|
throw Error("incompatible DB");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exportDb(db: IDBDatabase): Promise<any> {
|
||||||
|
const dump = {
|
||||||
|
name: db.name,
|
||||||
|
stores: {} as { [s: string]: any },
|
||||||
|
version: db.version,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(Array.from(db.objectStoreNames));
|
||||||
|
tx.addEventListener("complete", () => {
|
||||||
|
resolve(dump);
|
||||||
|
});
|
||||||
|
// tslint:disable-next-line:prefer-for-of
|
||||||
|
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
||||||
|
const name = db.objectStoreNames[i];
|
||||||
|
const storeDump = {} as { [s: string]: any };
|
||||||
|
dump.stores[name] = storeDump;
|
||||||
|
tx.objectStore(name)
|
||||||
|
.openCursor()
|
||||||
|
.addEventListener("success", (e: Event) => {
|
||||||
|
const cursor = (e.target as any).result;
|
||||||
|
if (cursor) {
|
||||||
|
storeDump[cursor.key] = cursor.value;
|
||||||
|
cursor.continue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function importDb(db: IDBDatabase, dump: any): Promise<void> {
|
||||||
|
console.log("importing db", dump);
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
|
||||||
|
if (dump.stores) {
|
||||||
|
for (const storeName in dump.stores) {
|
||||||
|
const objects = [];
|
||||||
|
const dumpStore = dump.stores[storeName];
|
||||||
|
for (const key in dumpStore) {
|
||||||
|
objects.push(dumpStore[key]);
|
||||||
|
}
|
||||||
|
console.log(`importing ${objects.length} records into ${storeName}`);
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
for (const obj of objects) {
|
||||||
|
store.put(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.addEventListener("complete", () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteDb(idbFactory: IDBFactory) {
|
||||||
|
idbFactory.deleteDatabase(DB_NAME);
|
||||||
|
}
|
233
src/headless/taler-wallet-cli.ts
Normal file
233
src/headless/taler-wallet-cli.ts
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge";
|
||||||
|
import { Wallet } from "../wallet";
|
||||||
|
import { Notifier, Badge } from "../walletTypes";
|
||||||
|
import { openTalerDb, exportDb } from "../db";
|
||||||
|
import { HttpRequestLibrary } from "../http";
|
||||||
|
import * as amounts from "../amounts";
|
||||||
|
import Axios from "axios";
|
||||||
|
|
||||||
|
import URI = require("urijs");
|
||||||
|
|
||||||
|
import querystring = require("querystring");
|
||||||
|
|
||||||
|
class ConsoleNotifier implements Notifier {
|
||||||
|
notify(): void {
|
||||||
|
// nothing to do.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleBadge implements Badge {
|
||||||
|
startBusy(): void {
|
||||||
|
console.log("NOTIFICATION: busy");
|
||||||
|
}
|
||||||
|
stopBusy(): void {
|
||||||
|
console.log("NOTIFICATION: busy end");
|
||||||
|
}
|
||||||
|
showNotification(): void {
|
||||||
|
console.log("NOTIFICATION: show");
|
||||||
|
}
|
||||||
|
clearNotification(): void {
|
||||||
|
console.log("NOTIFICATION: cleared");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeHttpLib implements HttpRequestLibrary {
|
||||||
|
async get(url: string): Promise<import("../http").HttpResponse> {
|
||||||
|
console.log("making GET request to", url);
|
||||||
|
const resp = await Axios({
|
||||||
|
method: "get",
|
||||||
|
url: url,
|
||||||
|
responseType: "json",
|
||||||
|
});
|
||||||
|
console.log("got response", resp.data);
|
||||||
|
console.log("resp type", typeof resp.data);
|
||||||
|
return {
|
||||||
|
responseJson: resp.data,
|
||||||
|
status: resp.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async postJson(
|
||||||
|
url: string,
|
||||||
|
body: any,
|
||||||
|
): Promise<import("../http").HttpResponse> {
|
||||||
|
console.log("making POST request to", url);
|
||||||
|
const resp = await Axios({
|
||||||
|
method: "post",
|
||||||
|
url: url,
|
||||||
|
responseType: "json",
|
||||||
|
data: body,
|
||||||
|
});
|
||||||
|
console.log("got response", resp.data);
|
||||||
|
console.log("resp type", typeof resp.data);
|
||||||
|
return {
|
||||||
|
responseJson: resp.data,
|
||||||
|
status: resp.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async postForm(
|
||||||
|
url: string,
|
||||||
|
form: any,
|
||||||
|
): Promise<import("../http").HttpResponse> {
|
||||||
|
console.log("making POST request to", url);
|
||||||
|
const resp = await Axios({
|
||||||
|
method: "post",
|
||||||
|
url: url,
|
||||||
|
data: querystring.stringify(form),
|
||||||
|
responseType: "json",
|
||||||
|
});
|
||||||
|
console.log("got response", resp.data);
|
||||||
|
console.log("resp type", typeof resp.data);
|
||||||
|
return {
|
||||||
|
responseJson: resp.data,
|
||||||
|
status: resp.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BankUser {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeId(length: number): string {
|
||||||
|
let result = "";
|
||||||
|
const characters =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerBankUser(
|
||||||
|
bankBaseUrl: string,
|
||||||
|
httpLib: HttpRequestLibrary,
|
||||||
|
): Promise<BankUser> {
|
||||||
|
const reqUrl = new URI("register").absoluteTo(bankBaseUrl).href();
|
||||||
|
const randId = makeId(8);
|
||||||
|
const bankUser: BankUser = {
|
||||||
|
username: `testuser-${randId}`,
|
||||||
|
password: `testpw-${randId}`,
|
||||||
|
};
|
||||||
|
const result = await httpLib.postForm(reqUrl, bankUser);
|
||||||
|
if (result.status != 200) {
|
||||||
|
throw Error("could not register bank user");
|
||||||
|
}
|
||||||
|
return bankUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createBankReserve(
|
||||||
|
bankBaseUrl: string,
|
||||||
|
bankUser: BankUser,
|
||||||
|
amount: string,
|
||||||
|
reservePub: string,
|
||||||
|
exchangePaytoUri: string,
|
||||||
|
httpLib: HttpRequestLibrary,
|
||||||
|
) {
|
||||||
|
const reqUrl = new URI("taler/withdraw").absoluteTo(bankBaseUrl).href();
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
auth: { type: "basic" },
|
||||||
|
username: bankUser,
|
||||||
|
amount,
|
||||||
|
reserve_pub: reservePub,
|
||||||
|
exchange_wire_detail: exchangePaytoUri,
|
||||||
|
};
|
||||||
|
|
||||||
|
const resp = await Axios({
|
||||||
|
method: "post",
|
||||||
|
url: reqUrl,
|
||||||
|
data: body,
|
||||||
|
responseType: "json",
|
||||||
|
headers: {
|
||||||
|
"X-Taler-Bank-Username": bankUser.username,
|
||||||
|
"X-Taler-Bank-Password": bankUser.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp.status != 200) {
|
||||||
|
throw Error("failed to create bank reserve");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const myNotifier = new ConsoleNotifier();
|
||||||
|
|
||||||
|
const myBadge = new ConsoleBadge();
|
||||||
|
|
||||||
|
const myBackend = new MemoryBackend();
|
||||||
|
|
||||||
|
myBackend.enableTracing = false;
|
||||||
|
|
||||||
|
BridgeIDBFactory.enableTracing = false;
|
||||||
|
|
||||||
|
const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
|
||||||
|
const myIdbFactory: IDBFactory = (myBridgeIdbFactory as any) as IDBFactory;
|
||||||
|
|
||||||
|
const myHttpLib = new NodeHttpLib();
|
||||||
|
|
||||||
|
const myVersionChange = () => {
|
||||||
|
console.error("version change requested, should not happen");
|
||||||
|
throw Error();
|
||||||
|
};
|
||||||
|
|
||||||
|
const myUnsupportedUpgrade = () => {
|
||||||
|
console.error("unsupported database migration");
|
||||||
|
throw Error();
|
||||||
|
};
|
||||||
|
|
||||||
|
shimIndexedDB(myBridgeIdbFactory);
|
||||||
|
|
||||||
|
const exchangeBaseUrl = "https://exchange.test.taler.net/";
|
||||||
|
const bankBaseUrl = "https://bank.test.taler.net/";
|
||||||
|
|
||||||
|
const myDb = await openTalerDb(
|
||||||
|
myIdbFactory,
|
||||||
|
myVersionChange,
|
||||||
|
myUnsupportedUpgrade,
|
||||||
|
);
|
||||||
|
|
||||||
|
const myWallet = new Wallet(myDb, myHttpLib, myBadge, myNotifier);
|
||||||
|
|
||||||
|
const reserveResponse = await myWallet.createReserve({
|
||||||
|
amount: amounts.parseOrThrow("TESTKUDOS:10.0"),
|
||||||
|
exchange: exchangeBaseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bankUser = await registerBankUser(bankBaseUrl, myHttpLib);
|
||||||
|
|
||||||
|
console.log("bank user", bankUser);
|
||||||
|
|
||||||
|
const exchangePaytoUri = await myWallet.getExchangePaytoUri(
|
||||||
|
"https://exchange.test.taler.net/",
|
||||||
|
["x-taler-bank"],
|
||||||
|
);
|
||||||
|
|
||||||
|
await createBankReserve(
|
||||||
|
bankBaseUrl,
|
||||||
|
bankUser,
|
||||||
|
"TESTKUDOS:10.0",
|
||||||
|
reserveResponse.reservePub,
|
||||||
|
exchangePaytoUri,
|
||||||
|
myHttpLib,
|
||||||
|
);
|
||||||
|
|
||||||
|
await myWallet.confirmReserve({ reservePub: reserveResponse.reservePub });
|
||||||
|
|
||||||
|
//await myWallet.waitForReserveDrained(reserveResponse.reservePub);
|
||||||
|
|
||||||
|
//myWallet.clearNotification();
|
||||||
|
|
||||||
|
//myWallet.stop();
|
||||||
|
|
||||||
|
const dbContents = await exportDb(myDb);
|
||||||
|
|
||||||
|
console.log("db:", JSON.stringify(dbContents, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error("Failed with exception:");
|
||||||
|
console.error(err);
|
||||||
|
});
|
@ -24,7 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
export interface HttpResponse {
|
export interface HttpResponse {
|
||||||
status: number;
|
status: number;
|
||||||
responseText: string;
|
responseJson: object & any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -58,8 +58,12 @@ export class BrowserHttpLib implements HttpRequestLibrary {
|
|||||||
}
|
}
|
||||||
myRequest.addEventListener("readystatechange", (e) => {
|
myRequest.addEventListener("readystatechange", (e) => {
|
||||||
if (myRequest.readyState === XMLHttpRequest.DONE) {
|
if (myRequest.readyState === XMLHttpRequest.DONE) {
|
||||||
|
const responseJson = JSON.parse(myRequest.responseText);
|
||||||
|
if (responseJson === null || typeof responseJson !== "object") {
|
||||||
|
reject(Error("Invalid JSON from HTTP response"));
|
||||||
|
}
|
||||||
const resp = {
|
const resp = {
|
||||||
responseText: myRequest.responseText,
|
responseJson: responseJson,
|
||||||
status: myRequest.status,
|
status: myRequest.status,
|
||||||
};
|
};
|
||||||
resolve(resp);
|
resolve(resp);
|
||||||
|
@ -852,7 +852,7 @@ export class WireFeesJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Checkable.Class()
|
@Checkable.Class({extra: true})
|
||||||
export class AccountInfo {
|
export class AccountInfo {
|
||||||
@Checkable.String()
|
@Checkable.String()
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -33,7 +33,7 @@ class IntervalHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
clearTimeout(this.h);
|
clearInterval(this.h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1335,6 +1335,7 @@ export class Wallet {
|
|||||||
this.processReserve(reserve);
|
this.processReserve(reserve);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async withdrawExecute(pc: PreCoinRecord): Promise<CoinRecord> {
|
private async withdrawExecute(pc: PreCoinRecord): Promise<CoinRecord> {
|
||||||
const wd: any = {};
|
const wd: any = {};
|
||||||
wd.denom_pub_hash = pc.denomPubHash;
|
wd.denom_pub_hash = pc.denomPubHash;
|
||||||
@ -1350,7 +1351,7 @@ export class Wallet {
|
|||||||
status: resp.status,
|
status: resp.status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const r = JSON.parse(resp.responseText);
|
const r = resp.responseJson;
|
||||||
const denomSig = await this.cryptoApi.rsaUnblind(
|
const denomSig = await this.cryptoApi.rsaUnblind(
|
||||||
r.ev_sig,
|
r.ev_sig,
|
||||||
pc.blindingKey,
|
pc.blindingKey,
|
||||||
@ -1462,7 +1463,7 @@ export class Wallet {
|
|||||||
if (resp.status !== 200) {
|
if (resp.status !== 200) {
|
||||||
throw Error();
|
throw Error();
|
||||||
}
|
}
|
||||||
const reserveInfo = ReserveStatus.checked(JSON.parse(resp.responseText));
|
const reserveInfo = ReserveStatus.checked(resp.responseJson);
|
||||||
if (!reserveInfo) {
|
if (!reserveInfo) {
|
||||||
throw Error();
|
throw Error();
|
||||||
}
|
}
|
||||||
@ -1486,7 +1487,7 @@ export class Wallet {
|
|||||||
throw Error("/wire request failed");
|
throw Error("/wire request failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
const wiJson = JSON.parse(resp.responseText);
|
const wiJson = resp.responseJson;
|
||||||
if (!wiJson) {
|
if (!wiJson) {
|
||||||
throw Error("/wire response malformed");
|
throw Error("/wire response malformed");
|
||||||
}
|
}
|
||||||
@ -1745,6 +1746,17 @@ export class Wallet {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getExchangePaytoUri(exchangeBaseUrl: string, supportedTargetTypes: string[]): Promise<string> {
|
||||||
|
const wireInfo = await this.getWireInfo(exchangeBaseUrl);
|
||||||
|
for (let account of wireInfo.accounts) {
|
||||||
|
const paytoUri = new URI(account.url);
|
||||||
|
if (supportedTargetTypes.includes(paytoUri.authority())) {
|
||||||
|
return account.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Error("no matching exchange account found");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update or add exchange DB entry by fetching the /keys information.
|
* Update or add exchange DB entry by fetching the /keys information.
|
||||||
* Optionally link the reserve entry to the new or existing
|
* Optionally link the reserve entry to the new or existing
|
||||||
@ -1757,9 +1769,7 @@ export class Wallet {
|
|||||||
if (keysResp.status !== 200) {
|
if (keysResp.status !== 200) {
|
||||||
throw Error("/keys request failed");
|
throw Error("/keys request failed");
|
||||||
}
|
}
|
||||||
const exchangeKeysJson = KeysJson.checked(
|
const exchangeKeysJson = KeysJson.checked(keysResp.responseJson);
|
||||||
JSON.parse(keysResp.responseText),
|
|
||||||
);
|
|
||||||
const exchangeWire = await this.getWireInfo(baseUrl);
|
const exchangeWire = await this.getWireInfo(baseUrl);
|
||||||
return this.updateExchangeFromJson(baseUrl, exchangeKeysJson, exchangeWire);
|
return this.updateExchangeFromJson(baseUrl, exchangeKeysJson, exchangeWire);
|
||||||
}
|
}
|
||||||
@ -2291,18 +2301,14 @@ export class Wallet {
|
|||||||
console.log("melt request:", meltReq);
|
console.log("melt request:", meltReq);
|
||||||
const resp = await this.http.postJson(reqUrl.href(), meltReq);
|
const resp = await this.http.postJson(reqUrl.href(), meltReq);
|
||||||
|
|
||||||
console.log("melt response:", resp.responseText);
|
console.log("melt response:", resp.responseJson);
|
||||||
|
|
||||||
if (resp.status !== 200) {
|
if (resp.status !== 200) {
|
||||||
console.error(resp.responseText);
|
console.error(resp.responseJson);
|
||||||
throw Error("refresh failed");
|
throw Error("refresh failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
const respJson = JSON.parse(resp.responseText);
|
const respJson = resp.responseJson;
|
||||||
|
|
||||||
if (!respJson) {
|
|
||||||
throw Error("exchange responded with garbage");
|
|
||||||
}
|
|
||||||
|
|
||||||
const norevealIndex = respJson.noreveal_index;
|
const norevealIndex = respJson.noreveal_index;
|
||||||
|
|
||||||
@ -2376,7 +2382,7 @@ export class Wallet {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const respJson = JSON.parse(resp.responseText);
|
const respJson = resp.responseJson;
|
||||||
|
|
||||||
if (!respJson.ev_sigs || !Array.isArray(respJson.ev_sigs)) {
|
if (!respJson.ev_sigs || !Array.isArray(respJson.ev_sigs)) {
|
||||||
console.log("/refresh/reveal did not contain ev_sigs");
|
console.log("/refresh/reveal did not contain ev_sigs");
|
||||||
@ -2647,9 +2653,7 @@ export class Wallet {
|
|||||||
if (resp.status !== 200) {
|
if (resp.status !== 200) {
|
||||||
throw Error();
|
throw Error();
|
||||||
}
|
}
|
||||||
const paybackConfirmation = PaybackConfirmation.checked(
|
const paybackConfirmation = PaybackConfirmation.checked(resp.responseJson);
|
||||||
JSON.parse(resp.responseText),
|
|
||||||
);
|
|
||||||
if (paybackConfirmation.reserve_pub !== coin.reservePub) {
|
if (paybackConfirmation.reserve_pub !== coin.reservePub) {
|
||||||
throw Error(`Coin's reserve doesn't match reserve on payback`);
|
throw Error(`Coin's reserve doesn't match reserve on payback`);
|
||||||
}
|
}
|
||||||
@ -2710,6 +2714,7 @@ export class Wallet {
|
|||||||
*/
|
*/
|
||||||
stop() {
|
stop() {
|
||||||
this.timerGroup.stopCurrentAndFutureTimers();
|
this.timerGroup.stopCurrentAndFutureTimers();
|
||||||
|
this.cryptoApi.terminateWorkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSenderWireInfos(): Promise<SenderWireInfos> {
|
async getSenderWireInfos(): Promise<SenderWireInfos> {
|
||||||
@ -2857,7 +2862,7 @@ export class Wallet {
|
|||||||
console.error("deposit failed due to status code", resp);
|
console.error("deposit failed due to status code", resp);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const respJson = JSON.parse(resp.responseText);
|
const respJson = resp.responseJson;
|
||||||
if (respJson.status !== "DEPOSIT_OK") {
|
if (respJson.status !== "DEPOSIT_OK") {
|
||||||
console.error("deposit failed", resp);
|
console.error("deposit failed", resp);
|
||||||
continue;
|
continue;
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"noUnusedLocals": true,
|
|
||||||
"incremental": true
|
"incremental": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
45
yarn.lock
45
yarn.lock
@ -1057,13 +1057,13 @@ awesome-typescript-loader@^5.2.1:
|
|||||||
source-map-support "^0.5.3"
|
source-map-support "^0.5.3"
|
||||||
webpack-log "^1.2.0"
|
webpack-log "^1.2.0"
|
||||||
|
|
||||||
axios@^0.18.0:
|
axios@^0.19.0:
|
||||||
version "0.18.0"
|
version "0.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8"
|
||||||
integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=
|
integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects "^1.3.0"
|
follow-redirects "1.5.10"
|
||||||
is-buffer "^1.1.5"
|
is-buffer "^2.0.2"
|
||||||
|
|
||||||
babel-code-frame@^6.22.0:
|
babel-code-frame@^6.22.0:
|
||||||
version "6.26.0"
|
version "6.26.0"
|
||||||
@ -1975,12 +1975,12 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@^3.2.6:
|
debug@=3.1.0:
|
||||||
version "3.2.6"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@^4.1.0, debug@^4.1.1:
|
debug@^4.1.0, debug@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
@ -2650,12 +2650,12 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
|
|||||||
inherits "^2.0.3"
|
inherits "^2.0.3"
|
||||||
readable-stream "^2.3.6"
|
readable-stream "^2.3.6"
|
||||||
|
|
||||||
follow-redirects@^1.3.0:
|
follow-redirects@1.5.10:
|
||||||
version "1.7.0"
|
version "1.5.10"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
|
||||||
integrity sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==
|
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^3.2.6"
|
debug "=3.1.0"
|
||||||
|
|
||||||
for-in@^1.0.1, for-in@^1.0.2:
|
for-in@^1.0.1, for-in@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
@ -3343,6 +3343,11 @@ is-buffer@^1.1.5:
|
|||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||||
|
|
||||||
|
is-buffer@^2.0.2:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
|
||||||
|
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
|
||||||
|
|
||||||
is-ci@^1.0.10:
|
is-ci@^1.0.10:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
|
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
|
||||||
@ -5760,6 +5765,14 @@ source-map-support@^0.5.11, source-map-support@^0.5.3, source-map-support@~0.5.1
|
|||||||
buffer-from "^1.0.0"
|
buffer-from "^1.0.0"
|
||||||
source-map "^0.6.0"
|
source-map "^0.6.0"
|
||||||
|
|
||||||
|
source-map-support@^0.5.12:
|
||||||
|
version "0.5.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
|
||||||
|
integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==
|
||||||
|
dependencies:
|
||||||
|
buffer-from "^1.0.0"
|
||||||
|
source-map "^0.6.0"
|
||||||
|
|
||||||
source-map-url@^0.4.0:
|
source-map-url@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
|
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
|
||||||
|
Loading…
Reference in New Issue
Block a user