add linting rules and fix them
This commit is contained in:
parent
7fff4499fd
commit
08bd3dc0e8
2
Makefile
2
Makefile
@ -52,7 +52,7 @@ coverage: tsc yarn-install
|
|||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: tsc yarn-install
|
lint: tsc yarn-install
|
||||||
$(tslint) --type-check --project tsconfig.json -t verbose 'src/**/*.ts'
|
$(tslint) --type-check -e src/i18n/strings.ts --project tsconfig.json -t verbose 'src/**/*.ts'
|
||||||
|
|
||||||
.PHONY: yarn-install
|
.PHONY: yarn-install
|
||||||
i18n: yarn-install
|
i18n: yarn-install
|
||||||
|
148
src/checkable.ts
148
src/checkable.ts
@ -40,7 +40,7 @@
|
|||||||
*/
|
*/
|
||||||
export namespace Checkable {
|
export namespace Checkable {
|
||||||
|
|
||||||
type Path = (number | string)[];
|
type Path = Array<number | string>;
|
||||||
|
|
||||||
interface SchemaErrorConstructor {
|
interface SchemaErrorConstructor {
|
||||||
new (err: string): SchemaError;
|
new (err: string): SchemaError;
|
||||||
@ -67,22 +67,22 @@ export namespace Checkable {
|
|||||||
props: Prop[];
|
props: Prop[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export let SchemaError = (function SchemaError(this: any, message: string) {
|
export const SchemaError = (function SchemaError(this: any, message: string) {
|
||||||
let that: any = this as any;
|
const that: any = this as any;
|
||||||
that.name = 'SchemaError';
|
that.name = "SchemaError";
|
||||||
that.message = message;
|
that.message = message;
|
||||||
that.stack = (<any>new Error()).stack;
|
that.stack = (new Error() as any).stack;
|
||||||
}) as any as SchemaErrorConstructor;
|
}) as any as SchemaErrorConstructor;
|
||||||
|
|
||||||
|
|
||||||
SchemaError.prototype = new Error;
|
SchemaError.prototype = new Error();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classes that are checkable are annotated with this
|
* Classes that are checkable are annotated with this
|
||||||
* checkable info symbol, which contains the information necessary
|
* checkable info symbol, which contains the information necessary
|
||||||
* to check if they're valid.
|
* to check if they're valid.
|
||||||
*/
|
*/
|
||||||
let checkableInfoSym = Symbol("checkableInfo");
|
const checkableInfoSym = Symbol("checkableInfo");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current property list for a checkable type.
|
* Get the current property list for a checkable type.
|
||||||
@ -138,7 +138,7 @@ export namespace Checkable {
|
|||||||
throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`);
|
throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`);
|
||||||
}
|
}
|
||||||
for (let i = 0; i < target.length; i++) {
|
for (let i = 0; i < target.length; i++) {
|
||||||
let v = target[i];
|
const v = target[i];
|
||||||
prop.elementChecker(v, prop.elementProp, path.concat([i]));
|
prop.elementChecker(v, prop.elementProp, path.concat([i]));
|
||||||
}
|
}
|
||||||
return target;
|
return target;
|
||||||
@ -148,9 +148,9 @@ export namespace Checkable {
|
|||||||
if (typeof target !== "object") {
|
if (typeof target !== "object") {
|
||||||
throw new SchemaError(`expected object for ${path}, got ${typeof target} instead`);
|
throw new SchemaError(`expected object for ${path}, got ${typeof target} instead`);
|
||||||
}
|
}
|
||||||
for (let key in target) {
|
for (const key in target) {
|
||||||
prop.keyProp.checker(key, prop.keyProp, path.concat([key]));
|
prop.keyProp.checker(key, prop.keyProp, path.concat([key]));
|
||||||
let value = target[key];
|
const value = target[key];
|
||||||
prop.valueProp.checker(value, prop.valueProp, path.concat([key]));
|
prop.valueProp.checker(value, prop.valueProp, path.concat([key]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,35 +166,35 @@ export namespace Checkable {
|
|||||||
|
|
||||||
|
|
||||||
function checkValue(target: any, prop: Prop, path: Path): any {
|
function checkValue(target: any, prop: Prop, path: Path): any {
|
||||||
let type = prop.type;
|
const type = prop.type;
|
||||||
if (!type) {
|
if (!type) {
|
||||||
throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`);
|
throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`);
|
||||||
}
|
}
|
||||||
let v = target;
|
const v = target;
|
||||||
if (!v || typeof v !== "object") {
|
if (!v || typeof v !== "object") {
|
||||||
throw new SchemaError(
|
throw new SchemaError(
|
||||||
`expected object for ${path.join(".")}, got ${typeof v} instead`);
|
`expected object for ${path.join(".")}, got ${typeof v} instead`);
|
||||||
}
|
}
|
||||||
let props = type.prototype[checkableInfoSym].props;
|
const props = type.prototype[checkableInfoSym].props;
|
||||||
let remainingPropNames = new Set(Object.getOwnPropertyNames(v));
|
const remainingPropNames = new Set(Object.getOwnPropertyNames(v));
|
||||||
let obj = new type();
|
const obj = new type();
|
||||||
for (let prop of props) {
|
for (const innerProp of props) {
|
||||||
if (!remainingPropNames.has(prop.propertyKey)) {
|
if (!remainingPropNames.has(innerProp.propertyKey)) {
|
||||||
if (prop.optional) {
|
if (innerProp.optional) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw new SchemaError(`Property ${prop.propertyKey} missing on ${path}`);
|
throw new SchemaError(`Property ${innerProp.propertyKey} missing on ${path}`);
|
||||||
}
|
}
|
||||||
if (!remainingPropNames.delete(prop.propertyKey)) {
|
if (!remainingPropNames.delete(innerProp.propertyKey)) {
|
||||||
throw new SchemaError("assertion failed");
|
throw new SchemaError("assertion failed");
|
||||||
}
|
}
|
||||||
let propVal = v[prop.propertyKey];
|
const propVal = v[innerProp.propertyKey];
|
||||||
obj[prop.propertyKey] = prop.checker(propVal,
|
obj[innerProp.propertyKey] = innerProp.checker(propVal,
|
||||||
prop,
|
innerProp,
|
||||||
path.concat([prop.propertyKey]));
|
path.concat([innerProp.propertyKey]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prop.extraAllowed && remainingPropNames.size != 0) {
|
if (!prop.extraAllowed && remainingPropNames.size !== 0) {
|
||||||
throw new SchemaError("superfluous properties " + JSON.stringify(Array.from(
|
throw new SchemaError("superfluous properties " + JSON.stringify(Array.from(
|
||||||
remainingPropNames.values())));
|
remainingPropNames.values())));
|
||||||
}
|
}
|
||||||
@ -210,14 +210,14 @@ 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) => {
|
||||||
target.checked = (v: any) => {
|
target.checked = (v: any) => {
|
||||||
let cv = checkValue(v, {
|
const cv = checkValue(v, {
|
||||||
|
checker: checkValue,
|
||||||
|
extraAllowed: !!opts.extra,
|
||||||
propertyKey: "(root)",
|
propertyKey: "(root)",
|
||||||
type: target,
|
type: target,
|
||||||
extraAllowed: !!opts.extra,
|
|
||||||
checker: checkValue
|
|
||||||
}, ["(root)"]);
|
}, ["(root)"]);
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
let instance = new target();
|
const instance = new target();
|
||||||
if (typeof instance.validate !== "function") {
|
if (typeof instance.validate !== "function") {
|
||||||
throw Error("invalid Checkable annotion: validate method required");
|
throw Error("invalid Checkable annotion: validate method required");
|
||||||
}
|
}
|
||||||
@ -227,7 +227,7 @@ export namespace Checkable {
|
|||||||
return cv;
|
return cv;
|
||||||
};
|
};
|
||||||
return target;
|
return target;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -238,12 +238,12 @@ export namespace Checkable {
|
|||||||
if (!type) {
|
if (!type) {
|
||||||
throw Error("Type does not exist yet (wrong order of definitions?)");
|
throw Error("Type does not exist yet (wrong order of definitions?)");
|
||||||
}
|
}
|
||||||
function deco(target: Object, propertyKey: string | symbol): void {
|
function deco(target: object, propertyKey: string | symbol): void {
|
||||||
let chk = getCheckableInfo(target);
|
const chk = getCheckableInfo(target);
|
||||||
chk.props.push({
|
chk.props.push({
|
||||||
propertyKey: propertyKey,
|
|
||||||
checker: checkValue,
|
checker: checkValue,
|
||||||
type: type
|
propertyKey,
|
||||||
|
type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,20 +256,20 @@ export namespace Checkable {
|
|||||||
* an annotation for a list of strings.
|
* an annotation for a list of strings.
|
||||||
*/
|
*/
|
||||||
export function List(type: any) {
|
export function List(type: any) {
|
||||||
let stub = {};
|
const stub = {};
|
||||||
type(stub, "(list-element)");
|
type(stub, "(list-element)");
|
||||||
let elementProp = getCheckableInfo(stub).props[0];
|
const elementProp = getCheckableInfo(stub).props[0];
|
||||||
let elementChecker = elementProp.checker;
|
const elementChecker = elementProp.checker;
|
||||||
if (!elementChecker) {
|
if (!elementChecker) {
|
||||||
throw Error("assertion failed");
|
throw Error("assertion failed");
|
||||||
}
|
}
|
||||||
function deco(target: Object, propertyKey: string | symbol): void {
|
function deco(target: object, propertyKey: string | symbol): void {
|
||||||
let chk = getCheckableInfo(target);
|
const chk = getCheckableInfo(target);
|
||||||
chk.props.push({
|
chk.props.push({
|
||||||
|
checker: checkList,
|
||||||
elementChecker,
|
elementChecker,
|
||||||
elementProp,
|
elementProp,
|
||||||
propertyKey: propertyKey,
|
propertyKey,
|
||||||
checker: checkList,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,25 +282,25 @@ export namespace Checkable {
|
|||||||
* one for the key type and one for the value type.
|
* one for the key type and one for the value type.
|
||||||
*/
|
*/
|
||||||
export function Map(keyType: any, valueType: any) {
|
export function Map(keyType: any, valueType: any) {
|
||||||
let keyStub = {};
|
const keyStub = {};
|
||||||
keyType(keyStub, "(map-key)");
|
keyType(keyStub, "(map-key)");
|
||||||
let keyProp = getCheckableInfo(keyStub).props[0];
|
const keyProp = getCheckableInfo(keyStub).props[0];
|
||||||
if (!keyProp) {
|
if (!keyProp) {
|
||||||
throw Error("assertion failed");
|
throw Error("assertion failed");
|
||||||
}
|
}
|
||||||
let valueStub = {};
|
const valueStub = {};
|
||||||
valueType(valueStub, "(map-value)");
|
valueType(valueStub, "(map-value)");
|
||||||
let valueProp = getCheckableInfo(valueStub).props[0];
|
const valueProp = getCheckableInfo(valueStub).props[0];
|
||||||
if (!valueProp) {
|
if (!valueProp) {
|
||||||
throw Error("assertion failed");
|
throw Error("assertion failed");
|
||||||
}
|
}
|
||||||
function deco(target: Object, propertyKey: string | symbol): void {
|
function deco(target: object, propertyKey: string | symbol): void {
|
||||||
let chk = getCheckableInfo(target);
|
const chk = getCheckableInfo(target);
|
||||||
chk.props.push({
|
chk.props.push({
|
||||||
keyProp,
|
|
||||||
valueProp,
|
|
||||||
propertyKey: propertyKey,
|
|
||||||
checker: checkMap,
|
checker: checkMap,
|
||||||
|
keyProp,
|
||||||
|
propertyKey,
|
||||||
|
valueProp,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,21 +312,21 @@ export namespace Checkable {
|
|||||||
* Makes another annotation optional, for example `@Checkable.Optional(Checkable.Number)`.
|
* Makes another annotation optional, for example `@Checkable.Optional(Checkable.Number)`.
|
||||||
*/
|
*/
|
||||||
export function Optional(type: any) {
|
export function Optional(type: any) {
|
||||||
let stub = {};
|
const stub = {};
|
||||||
type(stub, "(optional-element)");
|
type(stub, "(optional-element)");
|
||||||
let elementProp = getCheckableInfo(stub).props[0];
|
const elementProp = getCheckableInfo(stub).props[0];
|
||||||
let elementChecker = elementProp.checker;
|
const elementChecker = elementProp.checker;
|
||||||
if (!elementChecker) {
|
if (!elementChecker) {
|
||||||
throw Error("assertion failed");
|
throw Error("assertion failed");
|
||||||
}
|
}
|
||||||
function deco(target: Object, propertyKey: string | symbol): void {
|
function deco(target: object, propertyKey: string | symbol): void {
|
||||||
let chk = getCheckableInfo(target);
|
const chk = getCheckableInfo(target);
|
||||||
chk.props.push({
|
chk.props.push({
|
||||||
|
checker: checkOptional,
|
||||||
elementChecker,
|
elementChecker,
|
||||||
elementProp,
|
elementProp,
|
||||||
propertyKey: propertyKey,
|
|
||||||
checker: checkOptional,
|
|
||||||
optional: true,
|
optional: true,
|
||||||
|
propertyKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,20 +337,20 @@ export namespace Checkable {
|
|||||||
/**
|
/**
|
||||||
* Target property must be a number.
|
* Target property must be a number.
|
||||||
*/
|
*/
|
||||||
export function Number(target: Object, propertyKey: string | symbol): void {
|
export function Number(target: object, propertyKey: string | symbol): void {
|
||||||
let chk = getCheckableInfo(target);
|
const chk = getCheckableInfo(target);
|
||||||
chk.props.push({ propertyKey: propertyKey, checker: checkNumber });
|
chk.props.push({checker: checkNumber, propertyKey});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Target property must be an arbitary object.
|
* Target property must be an arbitary object.
|
||||||
*/
|
*/
|
||||||
export function AnyObject(target: Object, propertyKey: string | symbol): void {
|
export function AnyObject(target: object, propertyKey: string | symbol): void {
|
||||||
let chk = getCheckableInfo(target);
|
const chk = getCheckableInfo(target);
|
||||||
chk.props.push({
|
chk.props.push({
|
||||||
propertyKey: propertyKey,
|
checker: checkAnyObject,
|
||||||
checker: checkAnyObject
|
propertyKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,12 +361,12 @@ export namespace Checkable {
|
|||||||
* Not useful by itself, but in combination with higher-order annotations
|
* Not useful by itself, but in combination with higher-order annotations
|
||||||
* such as List or Map.
|
* such as List or Map.
|
||||||
*/
|
*/
|
||||||
export function Any(target: Object, propertyKey: string | symbol): void {
|
export function Any(target: object, propertyKey: string | symbol): void {
|
||||||
let chk = getCheckableInfo(target);
|
const chk = getCheckableInfo(target);
|
||||||
chk.props.push({
|
chk.props.push({
|
||||||
propertyKey: propertyKey,
|
|
||||||
checker: checkAny,
|
checker: checkAny,
|
||||||
optional: true
|
optional: true,
|
||||||
|
propertyKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,16 +374,16 @@ export namespace Checkable {
|
|||||||
/**
|
/**
|
||||||
* Target property must be a string.
|
* Target property must be a string.
|
||||||
*/
|
*/
|
||||||
export function String(target: Object, propertyKey: string | symbol): void {
|
export function String(target: object, propertyKey: string | symbol): void {
|
||||||
let chk = getCheckableInfo(target);
|
const chk = getCheckableInfo(target);
|
||||||
chk.props.push({ propertyKey: propertyKey, checker: checkString });
|
chk.props.push({ checker: checkString, propertyKey });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Target property must be a boolean value.
|
* Target property must be a boolean value.
|
||||||
*/
|
*/
|
||||||
export function Boolean(target: Object, propertyKey: string | symbol): void {
|
export function Boolean(target: object, propertyKey: string | symbol): void {
|
||||||
let chk = getCheckableInfo(target);
|
const chk = getCheckableInfo(target);
|
||||||
chk.props.push({ propertyKey: propertyKey, checker: checkBoolean });
|
chk.props.push({ checker: checkBoolean, propertyKey });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Badge
|
Badge,
|
||||||
} from "./wallet";
|
} from "./wallet";
|
||||||
|
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ export class ChromeBadge implements Badge {
|
|||||||
|
|
||||||
constructor(window?: Window) {
|
constructor(window?: Window) {
|
||||||
// Allow injecting another window for testing
|
// Allow injecting another window for testing
|
||||||
let bg = window || chrome.extension.getBackgroundPage();
|
const bg = window || chrome.extension.getBackgroundPage();
|
||||||
if (!bg) {
|
if (!bg) {
|
||||||
throw Error("no window available");
|
throw Error("no window available");
|
||||||
}
|
}
|
||||||
@ -130,13 +130,11 @@ export class ChromeBadge implements Badge {
|
|||||||
this.ctx.lineWidth = 2.5;
|
this.ctx.lineWidth = 2.5;
|
||||||
if (this.animationRunning) {
|
if (this.animationRunning) {
|
||||||
/* Draw circle around the "T" with an opening of this.gapWidth */
|
/* Draw circle around the "T" with an opening of this.gapWidth */
|
||||||
this.ctx.arc(0, 0,
|
const aMax = ChromeBadge.rotationAngleMax;
|
||||||
this.canvas.width / 2 - 2, /* radius */
|
const startAngle = this.rotationAngle / aMax * Math.PI * 2;
|
||||||
this.rotationAngle / ChromeBadge.rotationAngleMax * Math.PI * 2,
|
const stopAngle = ((this.rotationAngle + aMax - this.gapWidth) / aMax) * Math.PI * 2;
|
||||||
((this.rotationAngle + ChromeBadge.rotationAngleMax - this.gapWidth) / ChromeBadge.rotationAngleMax) * Math.PI * 2,
|
this.ctx.arc(0, 0, this.canvas.width / 2 - 2, /* radius */ startAngle, stopAngle, false);
|
||||||
false);
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Draw full circle */
|
/* Draw full circle */
|
||||||
this.ctx.arc(0, 0,
|
this.ctx.arc(0, 0,
|
||||||
this.canvas.width / 2 - 2, /* radius */
|
this.canvas.width / 2 - 2, /* radius */
|
||||||
@ -149,9 +147,10 @@ export class ChromeBadge implements Badge {
|
|||||||
this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2);
|
this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2);
|
||||||
|
|
||||||
// Allow running outside the extension for testing
|
// Allow running outside the extension for testing
|
||||||
|
// tslint:disable-next-line:no-string-literal
|
||||||
if (window["chrome"] && window.chrome["browserAction"]) {
|
if (window["chrome"] && window.chrome["browserAction"]) {
|
||||||
try {
|
try {
|
||||||
let imageData = this.ctx.getImageData(0,
|
const imageData = this.ctx.getImageData(0,
|
||||||
0,
|
0,
|
||||||
this.canvas.width,
|
this.canvas.width,
|
||||||
this.canvas.height);
|
this.canvas.height);
|
||||||
@ -168,20 +167,20 @@ export class ChromeBadge implements Badge {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.animationRunning = true;
|
this.animationRunning = true;
|
||||||
let start: number|undefined = undefined;
|
let start: number|undefined;
|
||||||
let step = (timestamp: number) => {
|
const step = (timestamp: number) => {
|
||||||
if (!this.animationRunning) {
|
if (!this.animationRunning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!start) {
|
if (!start) {
|
||||||
start = timestamp;
|
start = timestamp;
|
||||||
}
|
}
|
||||||
let delta = (timestamp - start);
|
if (!this.isBusy && 0 === this.gapWidth) {
|
||||||
if (!this.isBusy && 0 == this.gapWidth) {
|
|
||||||
// stop if we're close enough to origin
|
// stop if we're close enough to origin
|
||||||
this.rotationAngle = 0;
|
this.rotationAngle = 0;
|
||||||
} else {
|
} else {
|
||||||
this.rotationAngle = (this.rotationAngle + (timestamp - start) * ChromeBadge.rotationSpeed) % ChromeBadge.rotationAngleMax;
|
this.rotationAngle = (this.rotationAngle + (timestamp - start) *
|
||||||
|
ChromeBadge.rotationSpeed) % ChromeBadge.rotationAngleMax;
|
||||||
}
|
}
|
||||||
if (this.isBusy) {
|
if (this.isBusy) {
|
||||||
if (this.gapWidth < ChromeBadge.openMax) {
|
if (this.gapWidth < ChromeBadge.openMax) {
|
||||||
@ -190,15 +189,13 @@ export class ChromeBadge implements Badge {
|
|||||||
if (this.gapWidth > ChromeBadge.openMax) {
|
if (this.gapWidth > ChromeBadge.openMax) {
|
||||||
this.gapWidth = ChromeBadge.openMax;
|
this.gapWidth = ChromeBadge.openMax;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (this.gapWidth > 0) {
|
if (this.gapWidth > 0) {
|
||||||
this.gapWidth--;
|
this.gapWidth--;
|
||||||
this.gapWidth *= ChromeBadge.closeSpeed;
|
this.gapWidth *= ChromeBadge.closeSpeed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (this.isBusy || this.gapWidth > 0) {
|
if (this.isBusy || this.gapWidth > 0) {
|
||||||
start = timestamp;
|
start = timestamp;
|
||||||
rAF(step);
|
rAF(step);
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
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/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// tslint:disable:no-unused-expression
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that is injected into (all!) pages to allow them
|
* Module that is injected into (all!) pages to allow them
|
||||||
@ -55,9 +56,9 @@ interface Handler {
|
|||||||
const handlers: Handler[] = [];
|
const handlers: Handler[] = [];
|
||||||
|
|
||||||
function hashContract(contract: string): Promise<string> {
|
function hashContract(contract: string): Promise<string> {
|
||||||
let walletHashContractMsg = {
|
const walletHashContractMsg = {
|
||||||
|
detail: {contract},
|
||||||
type: "hash-contract",
|
type: "hash-contract",
|
||||||
detail: {contract}
|
|
||||||
};
|
};
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
chrome.runtime.sendMessage(walletHashContractMsg, (resp: any) => {
|
chrome.runtime.sendMessage(walletHashContractMsg, (resp: any) => {
|
||||||
@ -72,8 +73,8 @@ function hashContract(contract: string): Promise<string> {
|
|||||||
|
|
||||||
function queryPayment(url: string): Promise<any> {
|
function queryPayment(url: string): Promise<any> {
|
||||||
const walletMsg = {
|
const walletMsg = {
|
||||||
type: "query-payment",
|
|
||||||
detail: { url },
|
detail: { url },
|
||||||
|
type: "query-payment",
|
||||||
};
|
};
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
|
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
|
||||||
@ -84,10 +85,10 @@ function queryPayment(url: string): Promise<any> {
|
|||||||
|
|
||||||
function putHistory(historyEntry: any): Promise<void> {
|
function putHistory(historyEntry: any): Promise<void> {
|
||||||
const walletMsg = {
|
const walletMsg = {
|
||||||
type: "put-history-entry",
|
|
||||||
detail: {
|
detail: {
|
||||||
historyEntry,
|
historyEntry,
|
||||||
},
|
},
|
||||||
|
type: "put-history-entry",
|
||||||
};
|
};
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
|
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
|
||||||
@ -98,14 +99,14 @@ function putHistory(historyEntry: any): Promise<void> {
|
|||||||
|
|
||||||
function saveOffer(offer: any): Promise<number> {
|
function saveOffer(offer: any): Promise<number> {
|
||||||
const walletMsg = {
|
const walletMsg = {
|
||||||
type: "save-offer",
|
|
||||||
detail: {
|
detail: {
|
||||||
offer: {
|
offer: {
|
||||||
|
H_contract: offer.hash,
|
||||||
contract: offer.data,
|
contract: offer.data,
|
||||||
merchant_sig: offer.sig,
|
merchant_sig: offer.sig,
|
||||||
H_contract: offer.hash,
|
offer_time: new Date().getTime() / 1000,
|
||||||
offer_time: new Date().getTime() / 1000
|
|
||||||
},
|
},
|
||||||
|
type: "save-offer",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return new Promise<number>((resolve, reject) => {
|
return new Promise<number>((resolve, reject) => {
|
||||||
@ -120,8 +121,6 @@ function saveOffer(offer: any): Promise<number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let sheet: CSSStyleSheet|null;
|
let sheet: CSSStyleSheet|null;
|
||||||
|
|
||||||
function initStyle() {
|
function initStyle() {
|
||||||
@ -170,7 +169,6 @@ function setStyles(installed: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function handlePaymentResponse(walletResp: any) {
|
function handlePaymentResponse(walletResp: any) {
|
||||||
/**
|
/**
|
||||||
* Handle a failed payment.
|
* Handle a failed payment.
|
||||||
@ -185,16 +183,16 @@ function handlePaymentResponse(walletResp: any) {
|
|||||||
console.log("pay-failed", {status: r.status, response: r.responseText});
|
console.log("pay-failed", {status: r.status, response: r.responseText});
|
||||||
}
|
}
|
||||||
function onTimeout() {
|
function onTimeout() {
|
||||||
timeoutHandle = null
|
timeoutHandle = null;
|
||||||
err();
|
err();
|
||||||
}
|
}
|
||||||
talerPaymentFailed(walletResp.H_contract).then(() => {
|
talerPaymentFailed(walletResp.H_contract).then(() => {
|
||||||
if (timeoutHandle != null) {
|
if (timeoutHandle !== null) {
|
||||||
clearTimeout(timeoutHandle);
|
clearTimeout(timeoutHandle);
|
||||||
timeoutHandle = null;
|
timeoutHandle = null;
|
||||||
}
|
}
|
||||||
err();
|
err();
|
||||||
})
|
});
|
||||||
timeoutHandle = window.setTimeout(onTimeout, 200);
|
timeoutHandle = window.setTimeout(onTimeout, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +208,7 @@ function handlePaymentResponse(walletResp: any) {
|
|||||||
r.open("post", walletResp.contract.pay_url);
|
r.open("post", walletResp.contract.pay_url);
|
||||||
r.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
r.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||||
r.send(JSON.stringify(walletResp.payReq));
|
r.send(JSON.stringify(walletResp.payReq));
|
||||||
r.onload = function() {
|
r.onload = () => {
|
||||||
if (!r) {
|
if (!r) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -219,7 +217,7 @@ function handlePaymentResponse(walletResp: any) {
|
|||||||
const merchantResp = JSON.parse(r.responseText);
|
const merchantResp = JSON.parse(r.responseText);
|
||||||
logVerbose && console.log("got success from pay_url");
|
logVerbose && console.log("got success from pay_url");
|
||||||
talerPaymentSucceeded({H_contract: walletResp.H_contract, merchantSig: merchantResp.sig}).then(() => {
|
talerPaymentSucceeded({H_contract: walletResp.H_contract, merchantSig: merchantResp.sig}).then(() => {
|
||||||
let nextUrl = walletResp.contract.fulfillment_url;
|
const nextUrl = walletResp.contract.fulfillment_url;
|
||||||
logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl);
|
logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl);
|
||||||
window.location.href = nextUrl;
|
window.location.href = nextUrl;
|
||||||
window.location.reload(true);
|
window.location.reload(true);
|
||||||
@ -230,7 +228,7 @@ function handlePaymentResponse(walletResp: any) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
r = null;
|
r = null;
|
||||||
if (timeoutHandle != null) {
|
if (timeoutHandle !== null) {
|
||||||
clearTimeout(timeoutHandle!);
|
clearTimeout(timeoutHandle!);
|
||||||
timeoutHandle = null;
|
timeoutHandle = null;
|
||||||
}
|
}
|
||||||
@ -262,7 +260,7 @@ function init() {
|
|||||||
initStyle();
|
initStyle();
|
||||||
setStyles(true);
|
setStyles(true);
|
||||||
};
|
};
|
||||||
if (document.readyState == "complete") {
|
if (document.readyState === "complete") {
|
||||||
onload();
|
onload();
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener("DOMContentLoaded", onload);
|
document.addEventListener("DOMContentLoaded", onload);
|
||||||
@ -270,19 +268,19 @@ function init() {
|
|||||||
}
|
}
|
||||||
registerHandlers();
|
registerHandlers();
|
||||||
// Hack to know when the extension is unloaded
|
// Hack to know when the extension is unloaded
|
||||||
let port = chrome.runtime.connect();
|
const port = chrome.runtime.connect();
|
||||||
|
|
||||||
port.onDisconnect.addListener(() => {
|
port.onDisconnect.addListener(() => {
|
||||||
logVerbose && console.log("chrome runtime disconnected, removing handlers");
|
logVerbose && console.log("chrome runtime disconnected, removing handlers");
|
||||||
if (document.documentElement.getAttribute("data-taler-nojs")) {
|
if (document.documentElement.getAttribute("data-taler-nojs")) {
|
||||||
setStyles(false);
|
setStyles(false);
|
||||||
}
|
}
|
||||||
for (let handler of handlers) {
|
for (const handler of handlers) {
|
||||||
document.removeEventListener(handler.type, handler.listener);
|
document.removeEventListener(handler.type, handler.listener);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resp && resp.type == "pay") {
|
if (resp && resp.type === "pay") {
|
||||||
logVerbose && console.log("doing taler.pay with", resp.payDetail);
|
logVerbose && console.log("doing taler.pay with", resp.payDetail);
|
||||||
talerPay(resp.payDetail).then(handlePaymentResponse);
|
talerPay(resp.payDetail).then(handlePaymentResponse);
|
||||||
document.documentElement.style.visibility = "hidden";
|
document.documentElement.style.visibility = "hidden";
|
||||||
@ -290,9 +288,7 @@ function init() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HandlerFn {
|
type HandlerFn = (detail: any, sendResponse: (msg: any) => void) => void;
|
||||||
(detail: any, sendResponse: (msg: any) => void): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateNonce(): Promise<string> {
|
function generateNonce(): Promise<string> {
|
||||||
const walletMsg = {
|
const walletMsg = {
|
||||||
@ -306,35 +302,47 @@ function generateNonce(): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function downloadContract(url: string, nonce: string): Promise<any> {
|
function downloadContract(url: string, nonce: string): Promise<any> {
|
||||||
let parsed_url = new URI(url);
|
const parsed_url = new URI(url);
|
||||||
url = parsed_url.setQuery({nonce}).href();
|
url = parsed_url.setQuery({nonce}).href();
|
||||||
// FIXME: include and check nonce!
|
// FIXME: include and check nonce!
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const contract_request = new XMLHttpRequest();
|
const contract_request = new XMLHttpRequest();
|
||||||
console.log("downloading contract from '" + url + "'")
|
console.log("downloading contract from '" + url + "'");
|
||||||
contract_request.open("GET", url, true);
|
contract_request.open("GET", url, true);
|
||||||
contract_request.onload = function (e) {
|
contract_request.onload = (e) => {
|
||||||
if (contract_request.readyState == 4) {
|
if (contract_request.readyState === 4) {
|
||||||
if (contract_request.status == 200) {
|
if (contract_request.status === 200) {
|
||||||
console.log("response text:",
|
console.log("response text:",
|
||||||
contract_request.responseText);
|
contract_request.responseText);
|
||||||
var contract_wrapper = JSON.parse(contract_request.responseText);
|
const contract_wrapper = JSON.parse(contract_request.responseText);
|
||||||
if (!contract_wrapper) {
|
if (!contract_wrapper) {
|
||||||
console.error("response text was invalid json");
|
console.error("response text was invalid json");
|
||||||
let detail = {hint: "invalid json", status: contract_request.status, body: contract_request.responseText};
|
const detail = {
|
||||||
|
body: contract_request.responseText,
|
||||||
|
hint: "invalid json",
|
||||||
|
status: contract_request.status,
|
||||||
|
};
|
||||||
reject(detail);
|
reject(detail);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolve(contract_wrapper);
|
resolve(contract_wrapper);
|
||||||
} else {
|
} else {
|
||||||
let detail = {hint: "contract download failed", status: contract_request.status, body: contract_request.responseText};
|
const detail = {
|
||||||
|
body: contract_request.responseText,
|
||||||
|
hint: "contract download failed",
|
||||||
|
status: contract_request.status,
|
||||||
|
};
|
||||||
reject(detail);
|
reject(detail);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
contract_request.onerror = function (e) {
|
contract_request.onerror = (e) => {
|
||||||
let detail = {hint: "contract download failed", status: contract_request.status, body: contract_request.responseText};
|
const detail = {
|
||||||
|
body: contract_request.responseText,
|
||||||
|
hint: "contract download failed",
|
||||||
|
status: contract_request.status,
|
||||||
|
};
|
||||||
reject(detail);
|
reject(detail);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -353,9 +361,9 @@ async function processProposal(proposal: any) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let contractHash = await hashContract(proposal.data);
|
const contractHash = await hashContract(proposal.data);
|
||||||
|
|
||||||
if (contractHash != proposal.hash) {
|
if (contractHash !== proposal.hash) {
|
||||||
console.error("merchant-supplied contract hash is wrong");
|
console.error("merchant-supplied contract hash is wrong");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -367,17 +375,17 @@ async function processProposal(proposal: any) {
|
|||||||
// bad contract / name not included
|
// bad contract / name not included
|
||||||
}
|
}
|
||||||
|
|
||||||
let historyEntry = {
|
const historyEntry = {
|
||||||
timestamp: (new Date).getTime(),
|
|
||||||
subjectId: `contract-${contractHash}`,
|
|
||||||
type: "offer-contract",
|
|
||||||
detail: {
|
detail: {
|
||||||
contractHash,
|
contractHash,
|
||||||
merchantName,
|
merchantName,
|
||||||
}
|
},
|
||||||
|
subjectId: `contract-${contractHash}`,
|
||||||
|
timestamp: (new Date()).getTime(),
|
||||||
|
type: "offer-contract",
|
||||||
};
|
};
|
||||||
await putHistory(historyEntry);
|
await putHistory(historyEntry);
|
||||||
let offerId = await saveOffer(proposal);
|
const offerId = await saveOffer(proposal);
|
||||||
|
|
||||||
const uri = new URI(chrome.extension.getURL(
|
const uri = new URI(chrome.extension.getURL(
|
||||||
"/src/pages/confirm-contract.html"));
|
"/src/pages/confirm-contract.html"));
|
||||||
@ -391,17 +399,17 @@ async function processProposal(proposal: any) {
|
|||||||
function talerPay(msg: any): Promise<any> {
|
function talerPay(msg: any): Promise<any> {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
// current URL without fragment
|
// current URL without fragment
|
||||||
let url = new URI(document.location.href).fragment("").href();
|
const url = new URI(document.location.href).fragment("").href();
|
||||||
let res = await queryPayment(url);
|
const res = await queryPayment(url);
|
||||||
logVerbose && console.log("taler-pay: got response", res);
|
logVerbose && console.log("taler-pay: got response", res);
|
||||||
if (res && res.payReq) {
|
if (res && res.payReq) {
|
||||||
resolve(res);
|
resolve(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg.contract_url) {
|
if (msg.contract_url) {
|
||||||
let nonce = await generateNonce();
|
const nonce = await generateNonce();
|
||||||
let proposal = await downloadContract(msg.contract_url, nonce);
|
const proposal = await downloadContract(msg.contract_url, nonce);
|
||||||
if (proposal.data.nonce != nonce) {
|
if (proposal.data.nonce !== nonce) {
|
||||||
console.error("stale contract");
|
console.error("stale contract");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -421,10 +429,10 @@ function talerPay(msg: any): Promise<any> {
|
|||||||
function talerPaymentFailed(H_contract: string) {
|
function talerPaymentFailed(H_contract: string) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
const walletMsg = {
|
const walletMsg = {
|
||||||
type: "payment-failed",
|
|
||||||
detail: {
|
detail: {
|
||||||
contractHash: H_contract
|
contractHash: H_contract,
|
||||||
},
|
},
|
||||||
|
type: "payment-failed",
|
||||||
};
|
};
|
||||||
chrome.runtime.sendMessage(walletMsg, (resp) => {
|
chrome.runtime.sendMessage(walletMsg, (resp) => {
|
||||||
resolve();
|
resolve();
|
||||||
@ -444,11 +452,11 @@ function talerPaymentSucceeded(msg: any) {
|
|||||||
}
|
}
|
||||||
logVerbose && console.log("got taler-payment-succeeded");
|
logVerbose && console.log("got taler-payment-succeeded");
|
||||||
const walletMsg = {
|
const walletMsg = {
|
||||||
type: "payment-succeeded",
|
|
||||||
detail: {
|
detail: {
|
||||||
merchantSig: msg.merchantSig,
|
|
||||||
contractHash: msg.H_contract,
|
contractHash: msg.H_contract,
|
||||||
|
merchantSig: msg.merchantSig,
|
||||||
},
|
},
|
||||||
|
type: "payment-succeeded",
|
||||||
};
|
};
|
||||||
chrome.runtime.sendMessage(walletMsg, (resp) => {
|
chrome.runtime.sendMessage(walletMsg, (resp) => {
|
||||||
resolve();
|
resolve();
|
||||||
@ -463,21 +471,21 @@ function registerHandlers() {
|
|||||||
* handles adding sequence numbers to responses.
|
* handles adding sequence numbers to responses.
|
||||||
*/
|
*/
|
||||||
function addHandler(type: string, handler: HandlerFn) {
|
function addHandler(type: string, handler: HandlerFn) {
|
||||||
let handlerWrap = (e: CustomEvent) => {
|
const handlerWrap = (e: CustomEvent) => {
|
||||||
if (e.type != type) {
|
if (e.type !== type) {
|
||||||
throw Error(`invariant violated`);
|
throw Error(`invariant violated`);
|
||||||
}
|
}
|
||||||
let callId: number|undefined = undefined;
|
let callId: number|undefined;
|
||||||
if (e.detail && e.detail.callId != undefined) {
|
if (e.detail && e.detail.callId !== undefined) {
|
||||||
callId = e.detail.callId;
|
callId = e.detail.callId;
|
||||||
}
|
}
|
||||||
let responder = (msg?: any) => {
|
const responder = (msg?: any) => {
|
||||||
let fullMsg = Object.assign({}, msg, {callId});
|
const fullMsg = Object.assign({}, msg, {callId});
|
||||||
let opts = { detail: fullMsg };
|
let opts = { detail: fullMsg };
|
||||||
if ("function" == typeof cloneInto) {
|
if ("function" === typeof cloneInto) {
|
||||||
opts = cloneInto(opts, document.defaultView);
|
opts = cloneInto(opts, document.defaultView);
|
||||||
}
|
}
|
||||||
let evt = new CustomEvent(type + "-result", opts);
|
const evt = new CustomEvent(type + "-result", opts);
|
||||||
document.dispatchEvent(evt);
|
document.dispatchEvent(evt);
|
||||||
};
|
};
|
||||||
handler(e.detail, responder);
|
handler(e.detail, responder);
|
||||||
@ -489,7 +497,7 @@ function registerHandlers() {
|
|||||||
|
|
||||||
addHandler("taler-query-id", (msg: any, sendResponse: any) => {
|
addHandler("taler-query-id", (msg: any, sendResponse: any) => {
|
||||||
// FIXME: maybe include this info in taoer-probe?
|
// FIXME: maybe include this info in taoer-probe?
|
||||||
sendResponse({id: chrome.runtime.id})
|
sendResponse({id: chrome.runtime.id});
|
||||||
});
|
});
|
||||||
|
|
||||||
addHandler("taler-probe", (msg: any, sendResponse: any) => {
|
addHandler("taler-probe", (msg: any, sendResponse: any) => {
|
||||||
@ -497,34 +505,33 @@ function registerHandlers() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addHandler("taler-create-reserve", (msg: any) => {
|
addHandler("taler-create-reserve", (msg: any) => {
|
||||||
let params = {
|
const params = {
|
||||||
amount: JSON.stringify(msg.amount),
|
amount: JSON.stringify(msg.amount),
|
||||||
callback_url: new URI(msg.callback_url)
|
|
||||||
.absoluteTo(document.location.href),
|
|
||||||
bank_url: document.location.href,
|
bank_url: document.location.href,
|
||||||
wt_types: JSON.stringify(msg.wt_types),
|
callback_url: new URI(msg.callback_url) .absoluteTo(document.location.href),
|
||||||
suggested_exchange_url: msg.suggested_exchange_url,
|
suggested_exchange_url: msg.suggested_exchange_url,
|
||||||
|
wt_types: JSON.stringify(msg.wt_types),
|
||||||
};
|
};
|
||||||
let uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html"));
|
const uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html"));
|
||||||
let redirectUrl = uri.query(params).href();
|
const redirectUrl = uri.query(params).href();
|
||||||
window.location.href = redirectUrl;
|
window.location.href = redirectUrl;
|
||||||
});
|
});
|
||||||
|
|
||||||
addHandler("taler-add-auditor", (msg: any) => {
|
addHandler("taler-add-auditor", (msg: any) => {
|
||||||
let params = {
|
const params = {
|
||||||
req: JSON.stringify(msg),
|
req: JSON.stringify(msg),
|
||||||
};
|
};
|
||||||
let uri = new URI(chrome.extension.getURL("/src/pages/add-auditor.html"));
|
const uri = new URI(chrome.extension.getURL("/src/pages/add-auditor.html"));
|
||||||
let redirectUrl = uri.query(params).href();
|
const redirectUrl = uri.query(params).href();
|
||||||
window.location.href = redirectUrl;
|
window.location.href = redirectUrl;
|
||||||
});
|
});
|
||||||
|
|
||||||
addHandler("taler-confirm-reserve", (msg: any, sendResponse: any) => {
|
addHandler("taler-confirm-reserve", (msg: any, sendResponse: any) => {
|
||||||
let walletMsg = {
|
const walletMsg = {
|
||||||
type: "confirm-reserve",
|
|
||||||
detail: {
|
detail: {
|
||||||
reservePub: msg.reserve_pub
|
reservePub: msg.reserve_pub,
|
||||||
}
|
},
|
||||||
|
type: "confirm-reserve",
|
||||||
};
|
};
|
||||||
chrome.runtime.sendMessage(walletMsg, (resp) => {
|
chrome.runtime.sendMessage(walletMsg, (resp) => {
|
||||||
sendResponse();
|
sendResponse();
|
||||||
@ -544,7 +551,7 @@ function registerHandlers() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addHandler("taler-pay", async(msg: any, sendResponse: any) => {
|
addHandler("taler-pay", async(msg: any, sendResponse: any) => {
|
||||||
let resp = await talerPay(msg);
|
const resp = await talerPay(msg);
|
||||||
sendResponse(resp);
|
sendResponse(resp);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,80 +1,104 @@
|
|||||||
import {CryptoApi} from "./cryptoApi";
|
/*
|
||||||
import {ReserveRecord, DenominationRecord, DenominationStatus} from "../types";
|
This file is part of TALER
|
||||||
|
(C) 2017 Inria and GNUnet e.V.
|
||||||
|
|
||||||
|
TALER is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
// tslint:disable:max-line-length
|
||||||
|
|
||||||
import {test} from "ava";
|
import {test} from "ava";
|
||||||
|
|
||||||
let masterPub1: string = "CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";
|
import {
|
||||||
|
DenominationRecord,
|
||||||
|
DenominationStatus,
|
||||||
|
ReserveRecord,
|
||||||
|
} from "../types";
|
||||||
|
|
||||||
let denomValid1: DenominationRecord = {
|
import {CryptoApi} from "./cryptoApi";
|
||||||
masterSig: "CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
|
|
||||||
stampStart: "/Date(1473148381)/",
|
const masterPub1: string = "CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";
|
||||||
stampExpireWithdraw: "/Date(2482300381)/",
|
|
||||||
stampExpireDeposit: "/Date(1851580381)/",
|
const denomValid1: DenominationRecord = {
|
||||||
denomPub: "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GHS84R3JHHP6GSM2D9Q6514CGT568R32C9J6CWM4DSH64TM4DSM851K0CA48CVKAC1P6H144C2160T46DHK8CVM4HJ274S38C1M6S338D9N6GWM8DT684T3JCT36S13EC9G88R3EGHQ8S0KJGSQ60SKGD216N33AGJ2651K2E9S60TMCD1N75244HHQ6X33EDJ570R3GGJ2651MACA38D130DA560VK4HHJ68WK2CA26GW3ECSH6D13EC9S88VK2GT66WVK8D9G750K0D9R8RRK4DHQ71332GHK8D23GE26710M2H9K6WVK8HJ38MVKEGA66N23AC9H88VKACT58MV3CCSJ6H1K4DT38GRK0C9M8N33CE1R60V4AHA38H1KECSH6S33JH9N8GRKGH1K68S36GH354520818CMG26C1H60R30C935452081918G2J2G0",
|
denomPub: "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GHS84R3JHHP6GSM2D9Q6514CGT568R32C9J6CWM4DSH64TM4DSM851K0CA48CVKAC1P6H144C2160T46DHK8CVM4HJ274S38C1M6S338D9N6GWM8DT684T3JCT36S13EC9G88R3EGHQ8S0KJGSQ60SKGD216N33AGJ2651K2E9S60TMCD1N75244HHQ6X33EDJ570R3GGJ2651MACA38D130DA560VK4HHJ68WK2CA26GW3ECSH6D13EC9S88VK2GT66WVK8D9G750K0D9R8RRK4DHQ71332GHK8D23GE26710M2H9K6WVK8HJ38MVKEGA66N23AC9H88VKACT58MV3CCSJ6H1K4DT38GRK0C9M8N33CE1R60V4AHA38H1KECSH6S33JH9N8GRKGH1K68S36GH354520818CMG26C1H60R30C935452081918G2J2G0",
|
||||||
stampExpireLegal: "/Date(1567756381)/",
|
denomPubHash: "dummy",
|
||||||
value: {
|
exchangeBaseUrl: "https://exchange.example.com/",
|
||||||
"currency": "PUDOS",
|
|
||||||
"value": 0,
|
|
||||||
"fraction": 100000
|
|
||||||
},
|
|
||||||
feeWithdraw: {
|
|
||||||
"currency": "PUDOS",
|
|
||||||
"value": 0,
|
|
||||||
"fraction": 10000
|
|
||||||
},
|
|
||||||
feeDeposit: {
|
feeDeposit: {
|
||||||
"currency": "PUDOS",
|
currency: "PUDOS",
|
||||||
"value": 0,
|
fraction: 10000,
|
||||||
"fraction": 10000
|
value: 0,
|
||||||
},
|
},
|
||||||
feeRefresh: {
|
feeRefresh: {
|
||||||
"currency": "PUDOS",
|
currency: "PUDOS",
|
||||||
"value": 0,
|
fraction: 10000,
|
||||||
"fraction": 10000
|
value: 0,
|
||||||
},
|
},
|
||||||
feeRefund: {
|
feeRefund: {
|
||||||
"currency": "PUDOS",
|
currency: "PUDOS",
|
||||||
"value": 0,
|
fraction: 10000,
|
||||||
"fraction": 10000
|
value: 0,
|
||||||
|
},
|
||||||
|
feeWithdraw: {
|
||||||
|
currency: "PUDOS",
|
||||||
|
fraction: 10000,
|
||||||
|
value: 0,
|
||||||
},
|
},
|
||||||
denomPubHash: "dummy",
|
|
||||||
status: DenominationStatus.Unverified,
|
|
||||||
isOffered: true,
|
isOffered: true,
|
||||||
exchangeBaseUrl: "https://exchange.example.com/",
|
masterSig: "CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
|
||||||
|
stampExpireDeposit: "/Date(1851580381)/",
|
||||||
|
stampExpireLegal: "/Date(1567756381)/",
|
||||||
|
stampExpireWithdraw: "/Date(2482300381)/",
|
||||||
|
stampStart: "/Date(1473148381)/",
|
||||||
|
status: DenominationStatus.Unverified,
|
||||||
|
value: {
|
||||||
|
currency: "PUDOS",
|
||||||
|
fraction: 100000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let 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) => {
|
||||||
let crypto = new CryptoApi();
|
const crypto = new CryptoApi();
|
||||||
let s = await crypto.hashString("hello taler");
|
const s = await crypto.hashString("hello taler");
|
||||||
let 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) => {
|
||||||
let crypto = new CryptoApi();
|
const crypto = new CryptoApi();
|
||||||
let {priv, pub} = await crypto.createEddsaKeypair();
|
const {priv, pub} = await crypto.createEddsaKeypair();
|
||||||
let r: ReserveRecord = {
|
const r: ReserveRecord = {
|
||||||
reserve_pub: pub,
|
|
||||||
reserve_priv: priv,
|
|
||||||
hasPayback: false,
|
|
||||||
exchange_base_url: "https://example.com/exchange",
|
|
||||||
created: 0,
|
|
||||||
requested_amount: {currency: "PUDOS", value: 0, fraction: 0},
|
|
||||||
precoin_amount: {currency: "PUDOS", value: 0, fraction: 0},
|
|
||||||
current_amount: null,
|
|
||||||
confirmed: false,
|
confirmed: false,
|
||||||
|
created: 0,
|
||||||
|
current_amount: null,
|
||||||
|
exchange_base_url: "https://example.com/exchange",
|
||||||
|
hasPayback: false,
|
||||||
last_query: null,
|
last_query: null,
|
||||||
|
precoin_amount: {currency: "PUDOS", value: 0, fraction: 0},
|
||||||
|
requested_amount: {currency: "PUDOS", value: 0, fraction: 0},
|
||||||
|
reserve_priv: priv,
|
||||||
|
reserve_pub: pub,
|
||||||
};
|
};
|
||||||
|
|
||||||
let precoin = await crypto.createPreCoin(denomValid1, r);
|
const precoin = await crypto.createPreCoin(denomValid1, r);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("denom validation", async t => {
|
test("denom validation", async (t) => {
|
||||||
let 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);
|
||||||
t.true(v);
|
t.true(v);
|
||||||
|
@ -24,20 +24,22 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
PreCoinRecord,
|
|
||||||
CoinRecord,
|
|
||||||
ReserveRecord,
|
|
||||||
AmountJson,
|
AmountJson,
|
||||||
|
CoinRecord,
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
PaybackRequest,
|
|
||||||
RefreshSessionRecord,
|
|
||||||
WireFee,
|
|
||||||
PayCoinInfo,
|
PayCoinInfo,
|
||||||
|
PaybackRequest,
|
||||||
|
PreCoinRecord,
|
||||||
|
RefreshSessionRecord,
|
||||||
|
ReserveRecord,
|
||||||
|
WireFee,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OfferRecord,
|
|
||||||
CoinWithDenom,
|
CoinWithDenom,
|
||||||
|
OfferRecord,
|
||||||
} from "../wallet";
|
} from "../wallet";
|
||||||
|
|
||||||
import * as timer from "../timer";
|
import * as timer from "../timer";
|
||||||
|
|
||||||
import { startWorker } from "./startWorker";
|
import { startWorker } from "./startWorker";
|
||||||
@ -76,8 +78,6 @@ interface WorkItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of different priorities. Each priority p
|
* Number of different priorities. Each priority p
|
||||||
* must be 0 <= p < NUM_PRIO.
|
* must be 0 <= p < NUM_PRIO.
|
||||||
@ -97,34 +97,35 @@ export class CryptoApi {
|
|||||||
* Start a worker (if not started) and set as busy.
|
* Start a worker (if not started) and set as busy.
|
||||||
*/
|
*/
|
||||||
wake<T>(ws: WorkerState, work: WorkItem): void {
|
wake<T>(ws: WorkerState, work: WorkItem): void {
|
||||||
if (ws.currentWorkItem != null) {
|
if (ws.currentWorkItem !== null) {
|
||||||
throw Error("assertion failed");
|
throw Error("assertion failed");
|
||||||
}
|
}
|
||||||
ws.currentWorkItem = work;
|
ws.currentWorkItem = work;
|
||||||
this.numBusy++;
|
this.numBusy++;
|
||||||
if (!ws.w) {
|
if (!ws.w) {
|
||||||
let w = startWorker();
|
const w = startWorker();
|
||||||
w.onmessage = (m: MessageEvent) => this.handleWorkerMessage(ws, m);
|
w.onmessage = (m: MessageEvent) => this.handleWorkerMessage(ws, m);
|
||||||
w.onerror = (e: ErrorEvent) => this.handleWorkerError(ws, e);
|
w.onerror = (e: ErrorEvent) => this.handleWorkerError(ws, e);
|
||||||
ws.w = w;
|
ws.w = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg: any = {
|
const msg: any = {
|
||||||
operation: work.operation, args: work.args,
|
args: work.args,
|
||||||
id: work.rpcId
|
id: work.rpcId,
|
||||||
|
operation: work.operation,
|
||||||
};
|
};
|
||||||
this.resetWorkerTimeout(ws);
|
this.resetWorkerTimeout(ws);
|
||||||
ws.w!.postMessage(msg);
|
ws.w!.postMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetWorkerTimeout(ws: WorkerState) {
|
resetWorkerTimeout(ws: WorkerState) {
|
||||||
if (ws.terminationTimerHandle != null) {
|
if (ws.terminationTimerHandle !== null) {
|
||||||
ws.terminationTimerHandle.clear();
|
ws.terminationTimerHandle.clear();
|
||||||
ws.terminationTimerHandle = null;
|
ws.terminationTimerHandle = null;
|
||||||
}
|
}
|
||||||
let destroy = () => {
|
const destroy = () => {
|
||||||
// terminate worker if it's idle
|
// terminate worker if it's idle
|
||||||
if (ws.w && ws.currentWorkItem == null) {
|
if (ws.w && ws.currentWorkItem === null) {
|
||||||
ws.w!.terminate();
|
ws.w!.terminate();
|
||||||
ws.w = null;
|
ws.w = null;
|
||||||
}
|
}
|
||||||
@ -146,7 +147,7 @@ export class CryptoApi {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
if (ws.currentWorkItem != null) {
|
if (ws.currentWorkItem !== null) {
|
||||||
ws.currentWorkItem.reject(e);
|
ws.currentWorkItem.reject(e);
|
||||||
ws.currentWorkItem = null;
|
ws.currentWorkItem = null;
|
||||||
this.numBusy--;
|
this.numBusy--;
|
||||||
@ -157,9 +158,9 @@ export class CryptoApi {
|
|||||||
findWork(ws: WorkerState) {
|
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++) {
|
||||||
let q = this.workQueues[NUM_PRIO - i - 1];
|
const q = this.workQueues[NUM_PRIO - i - 1];
|
||||||
if (q.length != 0) {
|
if (q.length !== 0) {
|
||||||
let work: WorkItem = q.shift()!;
|
const work: WorkItem = q.shift()!;
|
||||||
this.wake(ws, work);
|
this.wake(ws, work);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -167,12 +168,12 @@ export class CryptoApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleWorkerMessage(ws: WorkerState, msg: MessageEvent) {
|
handleWorkerMessage(ws: WorkerState, msg: MessageEvent) {
|
||||||
let id = msg.data.id;
|
const id = msg.data.id;
|
||||||
if (typeof id !== "number") {
|
if (typeof id !== "number") {
|
||||||
console.error("rpc id must be number");
|
console.error("rpc id must be number");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let currentWorkItem = ws.currentWorkItem;
|
const currentWorkItem = ws.currentWorkItem;
|
||||||
ws.currentWorkItem = null;
|
ws.currentWorkItem = null;
|
||||||
this.numBusy--;
|
this.numBusy--;
|
||||||
this.findWork(ws);
|
this.findWork(ws);
|
||||||
@ -180,7 +181,7 @@ export class CryptoApi {
|
|||||||
console.error("unsolicited response from worker");
|
console.error("unsolicited response from worker");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (id != currentWorkItem.rpcId) {
|
if (id !== currentWorkItem.rpcId) {
|
||||||
console.error(`RPC with id ${id} has no registry entry`);
|
console.error(`RPC with id ${id} has no registry entry`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -191,6 +192,7 @@ export class CryptoApi {
|
|||||||
let concurrency = 2;
|
let concurrency = 2;
|
||||||
try {
|
try {
|
||||||
// only works in the browser
|
// only works in the browser
|
||||||
|
// tslint:disable-next-line:no-string-literal
|
||||||
concurrency = (navigator as any)["hardwareConcurrency"];
|
concurrency = (navigator as any)["hardwareConcurrency"];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
@ -199,9 +201,9 @@ export class CryptoApi {
|
|||||||
|
|
||||||
for (let i = 0; i < this.workers.length; i++) {
|
for (let i = 0; i < this.workers.length; i++) {
|
||||||
this.workers[i] = {
|
this.workers[i] = {
|
||||||
w: null,
|
|
||||||
terminationTimerHandle: null,
|
|
||||||
currentWorkItem: null,
|
currentWorkItem: null,
|
||||||
|
terminationTimerHandle: null,
|
||||||
|
w: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.workQueues = [];
|
this.workQueues = [];
|
||||||
@ -212,14 +214,14 @@ export class CryptoApi {
|
|||||||
|
|
||||||
private doRpc<T>(operation: string, priority: number,
|
private doRpc<T>(operation: string, priority: number,
|
||||||
...args: any[]): Promise<T> {
|
...args: any[]): Promise<T> {
|
||||||
let start = timer.performanceNow();
|
const start = timer.performanceNow();
|
||||||
|
|
||||||
let p = new Promise((resolve, reject) => {
|
const p = new Promise((resolve, reject) => {
|
||||||
let rpcId = this.nextRpcId++;
|
const rpcId = this.nextRpcId++;
|
||||||
let workItem: WorkItem = {operation, args, resolve, reject, rpcId};
|
const workItem: WorkItem = {operation, args, resolve, reject, rpcId};
|
||||||
|
|
||||||
if (this.numBusy == this.workers.length) {
|
if (this.numBusy === this.workers.length) {
|
||||||
let q = this.workQueues[priority];
|
const q = this.workQueues[priority];
|
||||||
if (!q) {
|
if (!q) {
|
||||||
throw Error("assertion failed");
|
throw Error("assertion failed");
|
||||||
}
|
}
|
||||||
@ -227,9 +229,8 @@ export class CryptoApi {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < this.workers.length; i++) {
|
for (const ws of this.workers) {
|
||||||
let ws = this.workers[i];
|
if (ws.currentWorkItem !== null) {
|
||||||
if (ws.currentWorkItem != null) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +37,11 @@ import {
|
|||||||
ReserveRecord,
|
ReserveRecord,
|
||||||
WireFee,
|
WireFee,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import create = chrome.alarms.create;
|
|
||||||
import {
|
import {
|
||||||
CoinWithDenom,
|
CoinWithDenom,
|
||||||
OfferRecord,
|
OfferRecord,
|
||||||
} from "../wallet";
|
} from "../wallet";
|
||||||
import * as native from "./emscInterface";
|
|
||||||
import {
|
import {
|
||||||
Amount,
|
Amount,
|
||||||
EddsaPublicKey,
|
EddsaPublicKey,
|
||||||
@ -50,6 +49,7 @@ import {
|
|||||||
HashContext,
|
HashContext,
|
||||||
RefreshMeltCoinAffirmationPS,
|
RefreshMeltCoinAffirmationPS,
|
||||||
} from "./emscInterface";
|
} from "./emscInterface";
|
||||||
|
import * as native from "./emscInterface";
|
||||||
|
|
||||||
|
|
||||||
namespace RpcFunctions {
|
namespace RpcFunctions {
|
||||||
@ -60,18 +60,16 @@ namespace RpcFunctions {
|
|||||||
*/
|
*/
|
||||||
export function createPreCoin(denom: DenominationRecord,
|
export function createPreCoin(denom: DenominationRecord,
|
||||||
reserve: ReserveRecord): PreCoinRecord {
|
reserve: ReserveRecord): PreCoinRecord {
|
||||||
let reservePriv = new native.EddsaPrivateKey();
|
const reservePriv = new native.EddsaPrivateKey();
|
||||||
reservePriv.loadCrock(reserve.reserve_priv);
|
reservePriv.loadCrock(reserve.reserve_priv);
|
||||||
let reservePub = new native.EddsaPublicKey();
|
const reservePub = new native.EddsaPublicKey();
|
||||||
reservePub.loadCrock(reserve.reserve_pub);
|
reservePub.loadCrock(reserve.reserve_pub);
|
||||||
let denomPub = native.RsaPublicKey.fromCrock(denom.denomPub);
|
const denomPub = native.RsaPublicKey.fromCrock(denom.denomPub);
|
||||||
let coinPriv = native.EddsaPrivateKey.create();
|
const coinPriv = native.EddsaPrivateKey.create();
|
||||||
let coinPub = coinPriv.getPublicKey();
|
const coinPub = coinPriv.getPublicKey();
|
||||||
let blindingFactor = native.RsaBlindingKeySecret.create();
|
const blindingFactor = native.RsaBlindingKeySecret.create();
|
||||||
let pubHash: native.HashCode = coinPub.hash();
|
const pubHash: native.HashCode = coinPub.hash();
|
||||||
let ev = native.rsaBlind(pubHash,
|
const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
|
||||||
blindingFactor,
|
|
||||||
denomPub);
|
|
||||||
|
|
||||||
if (!ev) {
|
if (!ev) {
|
||||||
throw Error("couldn't blind (malicious exchange key?)");
|
throw Error("couldn't blind (malicious exchange key?)");
|
||||||
@ -81,61 +79,61 @@ namespace RpcFunctions {
|
|||||||
throw Error("Field fee_withdraw missing");
|
throw Error("Field fee_withdraw missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
let amountWithFee = new native.Amount(denom.value);
|
const amountWithFee = new native.Amount(denom.value);
|
||||||
amountWithFee.add(new native.Amount(denom.feeWithdraw));
|
amountWithFee.add(new native.Amount(denom.feeWithdraw));
|
||||||
let withdrawFee = new native.Amount(denom.feeWithdraw);
|
const withdrawFee = new native.Amount(denom.feeWithdraw);
|
||||||
|
|
||||||
// Signature
|
// Signature
|
||||||
let withdrawRequest = new native.WithdrawRequestPS({
|
const withdrawRequest = new native.WithdrawRequestPS({
|
||||||
reserve_pub: reservePub,
|
|
||||||
amount_with_fee: amountWithFee.toNbo(),
|
amount_with_fee: amountWithFee.toNbo(),
|
||||||
withdraw_fee: withdrawFee.toNbo(),
|
h_coin_envelope: ev.hash(),
|
||||||
h_denomination_pub: denomPub.encode().hash(),
|
h_denomination_pub: denomPub.encode().hash(),
|
||||||
h_coin_envelope: ev.hash()
|
reserve_pub: reservePub,
|
||||||
|
withdraw_fee: withdrawFee.toNbo(),
|
||||||
});
|
});
|
||||||
|
|
||||||
var sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
|
const sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
|
||||||
|
|
||||||
let preCoin: PreCoinRecord = {
|
const preCoin: PreCoinRecord = {
|
||||||
reservePub: reservePub.toCrock(),
|
|
||||||
blindingKey: blindingFactor.toCrock(),
|
blindingKey: blindingFactor.toCrock(),
|
||||||
coinPub: coinPub.toCrock(),
|
coinEv: ev.toCrock(),
|
||||||
coinPriv: coinPriv.toCrock(),
|
coinPriv: coinPriv.toCrock(),
|
||||||
|
coinPub: coinPub.toCrock(),
|
||||||
|
coinValue: denom.value,
|
||||||
denomPub: denomPub.encode().toCrock(),
|
denomPub: denomPub.encode().toCrock(),
|
||||||
exchangeBaseUrl: reserve.exchange_base_url,
|
exchangeBaseUrl: reserve.exchange_base_url,
|
||||||
|
reservePub: reservePub.toCrock(),
|
||||||
withdrawSig: sig.toCrock(),
|
withdrawSig: sig.toCrock(),
|
||||||
coinEv: ev.toCrock(),
|
|
||||||
coinValue: denom.value
|
|
||||||
};
|
};
|
||||||
return preCoin;
|
return preCoin;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPaybackRequest(coin: CoinRecord): PaybackRequest {
|
export function createPaybackRequest(coin: CoinRecord): PaybackRequest {
|
||||||
let p = new native.PaybackRequestPS({
|
const p = new native.PaybackRequestPS({
|
||||||
|
coin_blind: native.RsaBlindingKeySecret.fromCrock(coin.blindingKey),
|
||||||
coin_pub: native.EddsaPublicKey.fromCrock(coin.coinPub),
|
coin_pub: native.EddsaPublicKey.fromCrock(coin.coinPub),
|
||||||
h_denom_pub: native.RsaPublicKey.fromCrock(coin.denomPub).encode().hash(),
|
h_denom_pub: native.RsaPublicKey.fromCrock(coin.denomPub).encode().hash(),
|
||||||
coin_blind: native.RsaBlindingKeySecret.fromCrock(coin.blindingKey),
|
|
||||||
});
|
});
|
||||||
let coinPriv = native.EddsaPrivateKey.fromCrock(coin.coinPriv);
|
const coinPriv = native.EddsaPrivateKey.fromCrock(coin.coinPriv);
|
||||||
let coinSig = native.eddsaSign(p.toPurpose(), coinPriv);
|
const coinSig = native.eddsaSign(p.toPurpose(), coinPriv);
|
||||||
let paybackRequest: PaybackRequest = {
|
const paybackRequest: PaybackRequest = {
|
||||||
denom_pub: coin.denomPub,
|
|
||||||
denom_sig: coin.denomSig,
|
|
||||||
coin_blind_key_secret: coin.blindingKey,
|
coin_blind_key_secret: coin.blindingKey,
|
||||||
coin_pub: coin.coinPub,
|
coin_pub: coin.coinPub,
|
||||||
coin_sig: coinSig.toCrock(),
|
coin_sig: coinSig.toCrock(),
|
||||||
|
denom_pub: coin.denomPub,
|
||||||
|
denom_sig: coin.denomSig,
|
||||||
};
|
};
|
||||||
return paybackRequest;
|
return paybackRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function isValidPaymentSignature(sig: string, contractHash: string, merchantPub: string): boolean {
|
export function isValidPaymentSignature(sig: string, contractHash: string, merchantPub: string): boolean {
|
||||||
let p = new native.PaymentSignaturePS({
|
const p = new native.PaymentSignaturePS({
|
||||||
contract_hash: native.HashCode.fromCrock(contractHash),
|
contract_hash: native.HashCode.fromCrock(contractHash),
|
||||||
});
|
});
|
||||||
let nativeSig = new native.EddsaSignature();
|
const nativeSig = new native.EddsaSignature();
|
||||||
nativeSig.loadCrock(sig);
|
nativeSig.loadCrock(sig);
|
||||||
let nativePub = native.EddsaPublicKey.fromCrock(merchantPub);
|
const nativePub = native.EddsaPublicKey.fromCrock(merchantPub);
|
||||||
return native.eddsaVerify(native.SignaturePurpose.MERCHANT_PAYMENT_OK,
|
return native.eddsaVerify(native.SignaturePurpose.MERCHANT_PAYMENT_OK,
|
||||||
p.toPurpose(),
|
p.toPurpose(),
|
||||||
nativeSig,
|
nativeSig,
|
||||||
@ -143,17 +141,17 @@ namespace RpcFunctions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
|
export function isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
|
||||||
let p = new native.MasterWireFeePS({
|
const p = new native.MasterWireFeePS({
|
||||||
|
closing_fee: (new native.Amount(wf.closingFee)).toNbo(),
|
||||||
|
end_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.endStamp),
|
||||||
h_wire_method: native.ByteArray.fromStringWithNull(type).hash(),
|
h_wire_method: native.ByteArray.fromStringWithNull(type).hash(),
|
||||||
start_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.startStamp),
|
start_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.startStamp),
|
||||||
end_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.endStamp),
|
|
||||||
wire_fee: (new native.Amount(wf.wireFee)).toNbo(),
|
wire_fee: (new native.Amount(wf.wireFee)).toNbo(),
|
||||||
closing_fee: (new native.Amount(wf.closingFee)).toNbo(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let nativeSig = new native.EddsaSignature();
|
const nativeSig = new native.EddsaSignature();
|
||||||
nativeSig.loadCrock(wf.sig);
|
nativeSig.loadCrock(wf.sig);
|
||||||
let nativePub = native.EddsaPublicKey.fromCrock(masterPub);
|
const nativePub = native.EddsaPublicKey.fromCrock(masterPub);
|
||||||
|
|
||||||
return native.eddsaVerify(native.SignaturePurpose.MASTER_WIRE_FEES,
|
return native.eddsaVerify(native.SignaturePurpose.MASTER_WIRE_FEES,
|
||||||
p.toPurpose(),
|
p.toPurpose(),
|
||||||
@ -164,26 +162,24 @@ namespace RpcFunctions {
|
|||||||
|
|
||||||
export function isValidDenom(denom: DenominationRecord,
|
export function isValidDenom(denom: DenominationRecord,
|
||||||
masterPub: string): boolean {
|
masterPub: string): boolean {
|
||||||
let p = new native.DenominationKeyValidityPS({
|
const p = new native.DenominationKeyValidityPS({
|
||||||
master: native.EddsaPublicKey.fromCrock(masterPub),
|
denom_hash: native.RsaPublicKey.fromCrock(denom.denomPub) .encode() .hash(),
|
||||||
denom_hash: native.RsaPublicKey.fromCrock(denom.denomPub)
|
|
||||||
.encode()
|
|
||||||
.hash(),
|
|
||||||
expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireLegal),
|
expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireLegal),
|
||||||
expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireDeposit),
|
expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireDeposit),
|
||||||
expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireWithdraw),
|
expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireWithdraw),
|
||||||
start: native.AbsoluteTimeNbo.fromTalerString(denom.stampStart),
|
|
||||||
value: (new native.Amount(denom.value)).toNbo(),
|
|
||||||
fee_deposit: (new native.Amount(denom.feeDeposit)).toNbo(),
|
fee_deposit: (new native.Amount(denom.feeDeposit)).toNbo(),
|
||||||
fee_refresh: (new native.Amount(denom.feeRefresh)).toNbo(),
|
fee_refresh: (new native.Amount(denom.feeRefresh)).toNbo(),
|
||||||
fee_withdraw: (new native.Amount(denom.feeWithdraw)).toNbo(),
|
|
||||||
fee_refund: (new native.Amount(denom.feeRefund)).toNbo(),
|
fee_refund: (new native.Amount(denom.feeRefund)).toNbo(),
|
||||||
|
fee_withdraw: (new native.Amount(denom.feeWithdraw)).toNbo(),
|
||||||
|
master: native.EddsaPublicKey.fromCrock(masterPub),
|
||||||
|
start: native.AbsoluteTimeNbo.fromTalerString(denom.stampStart),
|
||||||
|
value: (new native.Amount(denom.value)).toNbo(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let nativeSig = new native.EddsaSignature();
|
const nativeSig = new native.EddsaSignature();
|
||||||
nativeSig.loadCrock(denom.masterSig);
|
nativeSig.loadCrock(denom.masterSig);
|
||||||
|
|
||||||
let nativePub = native.EddsaPublicKey.fromCrock(masterPub);
|
const nativePub = native.EddsaPublicKey.fromCrock(masterPub);
|
||||||
|
|
||||||
return native.eddsaVerify(native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY,
|
return native.eddsaVerify(native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY,
|
||||||
p.toPurpose(),
|
p.toPurpose(),
|
||||||
@ -201,10 +197,10 @@ namespace RpcFunctions {
|
|||||||
|
|
||||||
|
|
||||||
export function rsaUnblind(sig: string, bk: string, pk: string): string {
|
export function rsaUnblind(sig: string, bk: string, pk: string): string {
|
||||||
let denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig),
|
const denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig),
|
||||||
native.RsaBlindingKeySecret.fromCrock(bk),
|
native.RsaBlindingKeySecret.fromCrock(bk),
|
||||||
native.RsaPublicKey.fromCrock(pk));
|
native.RsaPublicKey.fromCrock(pk));
|
||||||
return denomSig.encode().toCrock()
|
return denomSig.encode().toCrock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -214,21 +210,21 @@ namespace RpcFunctions {
|
|||||||
*/
|
*/
|
||||||
export function signDeposit(offer: OfferRecord,
|
export function signDeposit(offer: OfferRecord,
|
||||||
cds: CoinWithDenom[]): PayCoinInfo {
|
cds: CoinWithDenom[]): PayCoinInfo {
|
||||||
let ret: PayCoinInfo = [];
|
const ret: PayCoinInfo = [];
|
||||||
|
|
||||||
|
|
||||||
let feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit);
|
const feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit);
|
||||||
let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList).amount;
|
let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList).amount;
|
||||||
// okay if saturates
|
// okay if saturates
|
||||||
fees = Amounts.sub(fees, offer.contract.max_fee).amount;
|
fees = Amounts.sub(fees, offer.contract.max_fee).amount;
|
||||||
let total = Amounts.add(fees, offer.contract.amount).amount;
|
const total = Amounts.add(fees, offer.contract.amount).amount;
|
||||||
|
|
||||||
let amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
|
const amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
|
||||||
let amountRemaining = new native.Amount(total);
|
const amountRemaining = new native.Amount(total);
|
||||||
for (let cd of cds) {
|
for (const cd of cds) {
|
||||||
let coinSpend: Amount;
|
let coinSpend: Amount;
|
||||||
|
|
||||||
if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
|
if (amountRemaining.value === 0 && amountRemaining.fraction === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +237,7 @@ namespace RpcFunctions {
|
|||||||
amountSpent.add(coinSpend);
|
amountSpent.add(coinSpend);
|
||||||
amountRemaining.sub(coinSpend);
|
amountRemaining.sub(coinSpend);
|
||||||
|
|
||||||
let feeDeposit: Amount = new native.Amount(cd.denom.feeDeposit);
|
const feeDeposit: Amount = new native.Amount(cd.denom.feeDeposit);
|
||||||
|
|
||||||
// Give the merchant at least the deposit fee, otherwise it'll reject
|
// Give the merchant at least the deposit fee, otherwise it'll reject
|
||||||
// the coin.
|
// the coin.
|
||||||
@ -249,32 +245,32 @@ namespace RpcFunctions {
|
|||||||
coinSpend = feeDeposit;
|
coinSpend = feeDeposit;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newAmount = new native.Amount(cd.coin.currentAmount);
|
const newAmount = new native.Amount(cd.coin.currentAmount);
|
||||||
newAmount.sub(coinSpend);
|
newAmount.sub(coinSpend);
|
||||||
cd.coin.currentAmount = newAmount.toJson();
|
cd.coin.currentAmount = newAmount.toJson();
|
||||||
cd.coin.status = CoinStatus.TransactionPending;
|
cd.coin.status = CoinStatus.TransactionPending;
|
||||||
|
|
||||||
let d = new native.DepositRequestPS({
|
const d = new native.DepositRequestPS({
|
||||||
h_contract: native.HashCode.fromCrock(offer.H_contract),
|
|
||||||
h_wire: native.HashCode.fromCrock(offer.contract.H_wire),
|
|
||||||
amount_with_fee: coinSpend.toNbo(),
|
amount_with_fee: coinSpend.toNbo(),
|
||||||
coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub),
|
coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub),
|
||||||
deposit_fee: new native.Amount(cd.denom.feeDeposit).toNbo(),
|
deposit_fee: new native.Amount(cd.denom.feeDeposit).toNbo(),
|
||||||
|
h_contract: native.HashCode.fromCrock(offer.H_contract),
|
||||||
|
h_wire: native.HashCode.fromCrock(offer.contract.H_wire),
|
||||||
merchant: native.EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
|
merchant: native.EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
|
||||||
refund_deadline: native.AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
|
refund_deadline: native.AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
|
||||||
timestamp: native.AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
|
timestamp: native.AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
|
||||||
});
|
});
|
||||||
|
|
||||||
let coinSig = native.eddsaSign(d.toPurpose(),
|
const coinSig = native.eddsaSign(d.toPurpose(),
|
||||||
native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
|
native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
|
||||||
.toCrock();
|
.toCrock();
|
||||||
|
|
||||||
let s: CoinPaySig = {
|
const s: CoinPaySig = {
|
||||||
coin_sig: coinSig,
|
|
||||||
coin_pub: cd.coin.coinPub,
|
coin_pub: cd.coin.coinPub,
|
||||||
ub_sig: cd.coin.denomSig,
|
coin_sig: coinSig,
|
||||||
denom_pub: cd.coin.denomPub,
|
denom_pub: cd.coin.denomPub,
|
||||||
f: coinSpend.toJson(),
|
f: coinSpend.toJson(),
|
||||||
|
ub_sig: cd.coin.denomSig,
|
||||||
};
|
};
|
||||||
ret.push({sig: s, updatedCoin: cd.coin});
|
ret.push({sig: s, updatedCoin: cd.coin});
|
||||||
}
|
}
|
||||||
@ -290,7 +286,7 @@ namespace RpcFunctions {
|
|||||||
|
|
||||||
let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency);
|
let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency);
|
||||||
|
|
||||||
for (let ncd of newCoinDenoms) {
|
for (const ncd of newCoinDenoms) {
|
||||||
valueWithFee = Amounts.add(valueWithFee,
|
valueWithFee = Amounts.add(valueWithFee,
|
||||||
ncd.value,
|
ncd.value,
|
||||||
ncd.feeWithdraw).amount;
|
ncd.feeWithdraw).amount;
|
||||||
@ -299,23 +295,23 @@ namespace RpcFunctions {
|
|||||||
// melt fee
|
// melt fee
|
||||||
valueWithFee = Amounts.add(valueWithFee, meltFee).amount;
|
valueWithFee = Amounts.add(valueWithFee, meltFee).amount;
|
||||||
|
|
||||||
let sessionHc = new HashContext();
|
const sessionHc = new HashContext();
|
||||||
|
|
||||||
let transferPubs: string[] = [];
|
const transferPubs: string[] = [];
|
||||||
let transferPrivs: string[] = [];
|
const transferPrivs: string[] = [];
|
||||||
|
|
||||||
let preCoinsForGammas: RefreshPreCoinRecord[][] = [];
|
const preCoinsForGammas: RefreshPreCoinRecord[][] = [];
|
||||||
|
|
||||||
for (let i = 0; i < kappa; i++) {
|
for (let i = 0; i < kappa; i++) {
|
||||||
let t = native.EcdhePrivateKey.create();
|
const t = native.EcdhePrivateKey.create();
|
||||||
let pub = t.getPublicKey();
|
const pub = t.getPublicKey();
|
||||||
sessionHc.read(pub);
|
sessionHc.read(pub);
|
||||||
transferPrivs.push(t.toCrock());
|
transferPrivs.push(t.toCrock());
|
||||||
transferPubs.push(pub.toCrock());
|
transferPubs.push(pub.toCrock());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < newCoinDenoms.length; i++) {
|
for (const denom of newCoinDenoms) {
|
||||||
let r = native.RsaPublicKey.fromCrock(newCoinDenoms[i].denomPub);
|
const r = native.RsaPublicKey.fromCrock(denom.denomPub);
|
||||||
sessionHc.read(r.encode());
|
sessionHc.read(r.encode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,31 +319,31 @@ namespace RpcFunctions {
|
|||||||
sessionHc.read((new native.Amount(valueWithFee)).toNbo());
|
sessionHc.read((new native.Amount(valueWithFee)).toNbo());
|
||||||
|
|
||||||
for (let i = 0; i < kappa; i++) {
|
for (let i = 0; i < kappa; i++) {
|
||||||
let preCoins: RefreshPreCoinRecord[] = [];
|
const preCoins: RefreshPreCoinRecord[] = [];
|
||||||
for (let j = 0; j < newCoinDenoms.length; j++) {
|
for (let j = 0; j < newCoinDenoms.length; j++) {
|
||||||
|
|
||||||
let transferPriv = native.EcdhePrivateKey.fromCrock(transferPrivs[i]);
|
const transferPriv = native.EcdhePrivateKey.fromCrock(transferPrivs[i]);
|
||||||
let oldCoinPub = native.EddsaPublicKey.fromCrock(meltCoin.coinPub);
|
const oldCoinPub = native.EddsaPublicKey.fromCrock(meltCoin.coinPub);
|
||||||
let transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub);
|
const transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub);
|
||||||
|
|
||||||
let fresh = native.setupFreshCoin(transferSecret, j);
|
const fresh = native.setupFreshCoin(transferSecret, j);
|
||||||
|
|
||||||
let coinPriv = fresh.priv;
|
const coinPriv = fresh.priv;
|
||||||
let coinPub = coinPriv.getPublicKey();
|
const coinPub = coinPriv.getPublicKey();
|
||||||
let blindingFactor = fresh.blindingKey;
|
const blindingFactor = fresh.blindingKey;
|
||||||
let pubHash: native.HashCode = coinPub.hash();
|
const pubHash: native.HashCode = coinPub.hash();
|
||||||
let denomPub = native.RsaPublicKey.fromCrock(newCoinDenoms[j].denomPub);
|
const denomPub = native.RsaPublicKey.fromCrock(newCoinDenoms[j].denomPub);
|
||||||
let ev = native.rsaBlind(pubHash,
|
const ev = native.rsaBlind(pubHash,
|
||||||
blindingFactor,
|
blindingFactor,
|
||||||
denomPub);
|
denomPub);
|
||||||
if (!ev) {
|
if (!ev) {
|
||||||
throw Error("couldn't blind (malicious exchange key?)");
|
throw Error("couldn't blind (malicious exchange key?)");
|
||||||
}
|
}
|
||||||
let preCoin: RefreshPreCoinRecord = {
|
const preCoin: RefreshPreCoinRecord = {
|
||||||
blindingKey: blindingFactor.toCrock(),
|
blindingKey: blindingFactor.toCrock(),
|
||||||
coinEv: ev.toCrock(),
|
coinEv: ev.toCrock(),
|
||||||
publicKey: coinPub.toCrock(),
|
|
||||||
privateKey: coinPriv.toCrock(),
|
privateKey: coinPriv.toCrock(),
|
||||||
|
publicKey: coinPub.toCrock(),
|
||||||
};
|
};
|
||||||
preCoins.push(preCoin);
|
preCoins.push(preCoin);
|
||||||
sessionHc.read(ev);
|
sessionHc.read(ev);
|
||||||
@ -355,40 +351,40 @@ namespace RpcFunctions {
|
|||||||
preCoinsForGammas.push(preCoins);
|
preCoinsForGammas.push(preCoins);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sessionHash = new HashCode();
|
const sessionHash = new HashCode();
|
||||||
sessionHash.alloc();
|
sessionHash.alloc();
|
||||||
sessionHc.finish(sessionHash);
|
sessionHc.finish(sessionHash);
|
||||||
|
|
||||||
let confirmData = new RefreshMeltCoinAffirmationPS({
|
const confirmData = new RefreshMeltCoinAffirmationPS({
|
||||||
coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub),
|
|
||||||
amount_with_fee: (new Amount(valueWithFee)).toNbo(),
|
amount_with_fee: (new Amount(valueWithFee)).toNbo(),
|
||||||
|
coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub),
|
||||||
|
melt_fee: (new Amount(meltFee)).toNbo(),
|
||||||
session_hash: sessionHash,
|
session_hash: sessionHash,
|
||||||
melt_fee: (new Amount(meltFee)).toNbo()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let confirmSig: string = native.eddsaSign(confirmData.toPurpose(),
|
const confirmSig: string = native.eddsaSign(confirmData.toPurpose(),
|
||||||
native.EddsaPrivateKey.fromCrock(
|
native.EddsaPrivateKey.fromCrock(
|
||||||
meltCoin.coinPriv)).toCrock();
|
meltCoin.coinPriv)).toCrock();
|
||||||
|
|
||||||
let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency);
|
let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency);
|
||||||
for (let denom of newCoinDenoms) {
|
for (const denom of newCoinDenoms) {
|
||||||
valueOutput = Amounts.add(valueOutput, denom.value).amount;
|
valueOutput = Amounts.add(valueOutput, denom.value).amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
let refreshSession: RefreshSessionRecord = {
|
const refreshSession: RefreshSessionRecord = {
|
||||||
|
confirmSig,
|
||||||
|
exchangeBaseUrl,
|
||||||
|
finished: false,
|
||||||
|
hash: sessionHash.toCrock(),
|
||||||
meltCoinPub: meltCoin.coinPub,
|
meltCoinPub: meltCoin.coinPub,
|
||||||
newDenoms: newCoinDenoms.map((d) => d.denomPub),
|
newDenoms: newCoinDenoms.map((d) => d.denomPub),
|
||||||
confirmSig,
|
|
||||||
valueWithFee,
|
|
||||||
transferPubs,
|
|
||||||
preCoinsForGammas,
|
|
||||||
hash: sessionHash.toCrock(),
|
|
||||||
norevealIndex: undefined,
|
norevealIndex: undefined,
|
||||||
exchangeBaseUrl,
|
preCoinsForGammas,
|
||||||
transferPrivs,
|
transferPrivs,
|
||||||
finished: false,
|
transferPubs,
|
||||||
valueOutput,
|
valueOutput,
|
||||||
|
valueWithFee,
|
||||||
};
|
};
|
||||||
|
|
||||||
return refreshSession;
|
return refreshSession;
|
||||||
@ -408,24 +404,24 @@ namespace RpcFunctions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let worker: Worker = (self as any) as Worker;
|
const worker: Worker = (self as any) as Worker;
|
||||||
|
|
||||||
worker.onmessage = (msg: MessageEvent) => {
|
worker.onmessage = (msg: MessageEvent) => {
|
||||||
if (!Array.isArray(msg.data.args)) {
|
if (!Array.isArray(msg.data.args)) {
|
||||||
console.error("args must be array");
|
console.error("args must be array");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof msg.data.id != "number") {
|
if (typeof msg.data.id !== "number") {
|
||||||
console.error("RPC id must be number");
|
console.error("RPC id must be number");
|
||||||
}
|
}
|
||||||
if (typeof msg.data.operation != "string") {
|
if (typeof msg.data.operation !== "string") {
|
||||||
console.error("RPC operation must be string");
|
console.error("RPC operation must be string");
|
||||||
}
|
}
|
||||||
let f = (RpcFunctions as any)[msg.data.operation];
|
const f = (RpcFunctions as any)[msg.data.operation];
|
||||||
if (!f) {
|
if (!f) {
|
||||||
console.error(`unknown operation: '${msg.data.operation}'`);
|
console.error(`unknown operation: '${msg.data.operation}'`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let res = f(...msg.data.args);
|
const res = f(...msg.data.args);
|
||||||
worker.postMessage({result: res, id: msg.data.id});
|
worker.postMessage({result: res, id: msg.data.id});
|
||||||
}
|
};
|
||||||
|
@ -1,101 +1,124 @@
|
|||||||
|
/*
|
||||||
|
This file is part of TALER
|
||||||
|
(C) 2017 Inria and GNUnet e.V.
|
||||||
|
|
||||||
|
TALER is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
// tslint:disable:max-line-length
|
||||||
|
|
||||||
import {test} from "ava";
|
import {test} from "ava";
|
||||||
import * as native from "./emscInterface";
|
import * as native from "./emscInterface";
|
||||||
|
|
||||||
test("string hashing", t => {
|
test("string hashing", (t) => {
|
||||||
let x = native.ByteArray.fromStringWithNull("hello taler");
|
const x = native.ByteArray.fromStringWithNull("hello taler");
|
||||||
let h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR"
|
const h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
|
||||||
let hc = x.hash().toCrock();
|
const hc = x.hash().toCrock();
|
||||||
console.log(`# hc ${hc}`);
|
console.log(`# hc ${hc}`);
|
||||||
t.true(h === hc, "must equal");
|
t.true(h === hc, "must equal");
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("signing", t => {
|
|
||||||
let x = native.ByteArray.fromStringWithNull("hello taler");
|
test("signing", (t) => {
|
||||||
let priv = native.EddsaPrivateKey.create();
|
const x = native.ByteArray.fromStringWithNull("hello taler");
|
||||||
let pub = priv.getPublicKey();
|
const priv = native.EddsaPrivateKey.create();
|
||||||
let purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
|
const pub = priv.getPublicKey();
|
||||||
let sig = native.eddsaSign(purpose, priv);
|
const purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
|
||||||
|
const sig = native.eddsaSign(purpose, priv);
|
||||||
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
|
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("signing-fixed-data", t => {
|
|
||||||
let x = native.ByteArray.fromStringWithNull("hello taler");
|
test("signing-fixed-data", (t) => {
|
||||||
let purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
|
const x = native.ByteArray.fromStringWithNull("hello taler");
|
||||||
|
const purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
|
||||||
const privStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
|
const privStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
|
||||||
const pubStr = "YHCZB442FQFJ0ET20MWA8YJ53M61EZGJ6QKV1KTJZMRNXDY45WT0";
|
const pubStr = "YHCZB442FQFJ0ET20MWA8YJ53M61EZGJ6QKV1KTJZMRNXDY45WT0";
|
||||||
const sigStr = "7V6XY4QGC1406GPMT305MZQ1HDCR7R0S5BP02GTGDQFPSXB6YD2YDN5ZS7NJQCNP61Y39MRHXNXQ1Z15JY4CJY4CPDA6CKQ3313WG38";
|
const sigStr = "7V6XY4QGC1406GPMT305MZQ1HDCR7R0S5BP02GTGDQFPSXB6YD2YDN5ZS7NJQCNP61Y39MRHXNXQ1Z15JY4CJY4CPDA6CKQ3313WG38";
|
||||||
let priv = native.EddsaPrivateKey.fromCrock(privStr);
|
const priv = native.EddsaPrivateKey.fromCrock(privStr);
|
||||||
t.true(privStr == priv.toCrock())
|
t.true(privStr === priv.toCrock());
|
||||||
let pub = priv.getPublicKey();
|
const pub = priv.getPublicKey();
|
||||||
t.true(pubStr == pub.toCrock());
|
t.true(pubStr === pub.toCrock());
|
||||||
let sig = native.EddsaSignature.fromCrock(sigStr);
|
const sig = native.EddsaSignature.fromCrock(sigStr);
|
||||||
t.true(sigStr == sig.toCrock())
|
t.true(sigStr === sig.toCrock());
|
||||||
let sig2 = native.eddsaSign(purpose, priv);
|
const sig2 = native.eddsaSign(purpose, priv);
|
||||||
t.true(sig.toCrock() == sig2.toCrock());
|
t.true(sig.toCrock() === sig2.toCrock());
|
||||||
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
|
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const denomPubStr1 = "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30G9R64VK6HHS6MW42DSN8MVKJGHK6WR3CGT18MWMCDSM75138E1K8S0MADSQ68W34DHH6MW4CHA270W4CG9J6GW48DHG8MVK4E9S7523GEA56H0K4E1Q891KCCSG752KGC1M88VMCDSQ6D23CHHG8H33AGHG6MSK8GT26CRKAC1M64V3JCJ56CVKCC228MWMCHA26MS30H1J8MVKEDHJ70TMADHK892KJC1H60TKJDHM710KGGT584T38H9K851KCDHG60W30HJ28CT4CC1G8CR3JGJ28H236DJ28H330H9S890M2D9S8S14AGA369344GA36S248CHS70RKEDSS6MWKGDJ26D136GT465348CSS8S232CHM6GS34C9N8CS3GD9H60W36H1R8MSK2GSQ8MSM6C9R70SKCHHN6MW3ACJ28N0K2CA58RS3GCA26MV42G9P891KAG9Q8N0KGD9M850KEHJ16S130CA27124AE1G852KJCHR6S1KGDSJ8RTKED1S8RR3CCHP68W4CH9Q6GT34GT18GS36EA46N24AGSP6933GCHM60VMAE1S8GV3EHHN74W3GC1J651KEH9N8MSK0CSG6S2KEEA460R32C1M8D144GSR6RWKEC218S0KEGJ4611KEEA36CSKJC2564TM4CSJ6H230E1N74TM8C1P61342CSG60WKCGHH64VK2G9S8CRKAHHK88W30HJ388R3CH1Q6X2K2DHK8GSM4D1Q74WM4HA461146H9S6D33JDJ26D234C9Q6923ECSS60RM6CT46CSKCH1M6S13EH9J8S33GCSN4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00";
|
const denomPubStr1 = "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30G9R64VK6HHS6MW42DSN8MVKJGHK6WR3CGT18MWMCDSM75138E1K8S0MADSQ68W34DHH6MW4CHA270W4CG9J6GW48DHG8MVK4E9S7523GEA56H0K4E1Q891KCCSG752KGC1M88VMCDSQ6D23CHHG8H33AGHG6MSK8GT26CRKAC1M64V3JCJ56CVKCC228MWMCHA26MS30H1J8MVKEDHJ70TMADHK892KJC1H60TKJDHM710KGGT584T38H9K851KCDHG60W30HJ28CT4CC1G8CR3JGJ28H236DJ28H330H9S890M2D9S8S14AGA369344GA36S248CHS70RKEDSS6MWKGDJ26D136GT465348CSS8S232CHM6GS34C9N8CS3GD9H60W36H1R8MSK2GSQ8MSM6C9R70SKCHHN6MW3ACJ28N0K2CA58RS3GCA26MV42G9P891KAG9Q8N0KGD9M850KEHJ16S130CA27124AE1G852KJCHR6S1KGDSJ8RTKED1S8RR3CCHP68W4CH9Q6GT34GT18GS36EA46N24AGSP6933GCHM60VMAE1S8GV3EHHN74W3GC1J651KEH9N8MSK0CSG6S2KEEA460R32C1M8D144GSR6RWKEC218S0KEGJ4611KEEA36CSKJC2564TM4CSJ6H230E1N74TM8C1P61342CSG60WKCGHH64VK2G9S8CRKAHHK88W30HJ388R3CH1Q6X2K2DHK8GSM4D1Q74WM4HA461146H9S6D33JDJ26D234C9Q6923ECSS60RM6CT46CSKCH1M6S13EH9J8S33GCSN4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00";
|
||||||
|
|
||||||
test("rsa-encode", t => {
|
|
||||||
const pubHashStr = "JM63YM5X7X547164QJ3MGJZ4WDD47GEQR5DW5SH35G4JFZXEJBHE5JBNZM5K8XN5C4BRW25BE6GSVAYBF790G2BZZ13VW91D41S4DS0"
|
test("rsa-encode", (t) => {
|
||||||
let denomPub = native.RsaPublicKey.fromCrock(denomPubStr1);
|
const pubHashStr = "JM63YM5X7X547164QJ3MGJZ4WDD47GEQR5DW5SH35G4JFZXEJBHE5JBNZM5K8XN5C4BRW25BE6GSVAYBF790G2BZZ13VW91D41S4DS0";
|
||||||
let pubHash = denomPub.encode().hash();
|
const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1);
|
||||||
t.true(pubHashStr == pubHash.toCrock());
|
const pubHash = denomPub.encode().hash();
|
||||||
|
t.true(pubHashStr === pubHash.toCrock());
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("withdraw-request", t => {
|
test("withdraw-request", (t) => {
|
||||||
const reservePrivStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
|
const reservePrivStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
|
||||||
const reservePriv = native.EddsaPrivateKey.fromCrock(reservePrivStr);
|
const reservePriv = native.EddsaPrivateKey.fromCrock(reservePrivStr);
|
||||||
const reservePub = reservePriv.getPublicKey();
|
const reservePub = reservePriv.getPublicKey();
|
||||||
const amountWithFee = new native.Amount({currency: "KUDOS", value: 1, fraction: 10000});
|
const amountWithFee = new native.Amount({currency: "KUDOS", value: 1, fraction: 10000});
|
||||||
amountWithFee.add(new native.Amount({currency: "KUDOS", value: 0, fraction: 20000}));
|
amountWithFee.add(new native.Amount({currency: "KUDOS", value: 0, fraction: 20000}));
|
||||||
const withdrawFee = new native.Amount({currency: "KUDOS", value: 0, fraction: 20000})
|
const withdrawFee = new native.Amount({currency: "KUDOS", value: 0, fraction: 20000});
|
||||||
const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1);
|
const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1);
|
||||||
const ev = native.ByteArray.fromStringWithNull("hello, world");
|
const ev = native.ByteArray.fromStringWithNull("hello, world");
|
||||||
|
|
||||||
|
|
||||||
// Signature
|
// Signature
|
||||||
let withdrawRequest = new native.WithdrawRequestPS({
|
const withdrawRequest = new native.WithdrawRequestPS({
|
||||||
reserve_pub: reservePub,
|
|
||||||
amount_with_fee: amountWithFee.toNbo(),
|
amount_with_fee: amountWithFee.toNbo(),
|
||||||
withdraw_fee: withdrawFee.toNbo(),
|
h_coin_envelope: ev.hash(),
|
||||||
h_denomination_pub: denomPub.encode().hash(),
|
h_denomination_pub: denomPub.encode().hash(),
|
||||||
h_coin_envelope: ev.hash()
|
reserve_pub: reservePub,
|
||||||
|
withdraw_fee: withdrawFee.toNbo(),
|
||||||
});
|
});
|
||||||
|
|
||||||
var sigStr = "AD3T8W44NV193J19RAN3NAJHPP6RVB0R3NWV7ZK5G8Q946YDK0B6F8YJBNRRBXSPVTKY31S7BVZPJFFTJJRMY61DH51X4JSXK677428";
|
const sigStr = "AD3T8W44NV193J19RAN3NAJHPP6RVB0R3NWV7ZK5G8Q946YDK0B6F8YJBNRRBXSPVTKY31S7BVZPJFFTJJRMY61DH51X4JSXK677428";
|
||||||
|
|
||||||
var sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
|
const sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
|
||||||
t.true(native.eddsaVerify(native.SignaturePurpose.RESERVE_WITHDRAW, withdrawRequest.toPurpose(), sig, reservePub));
|
t.true(native.eddsaVerify(native.SignaturePurpose.RESERVE_WITHDRAW, withdrawRequest.toPurpose(), sig, reservePub));
|
||||||
t.true(sig.toCrock() == sigStr);
|
t.true(sig.toCrock() === sigStr);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("withdraw-request", t => {
|
|
||||||
|
test("withdraw-request", (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);
|
||||||
let x = a1.toJson();
|
const x = a1.toJson();
|
||||||
t.true(x.currency == "KUDOS");
|
t.true(x.currency === "KUDOS");
|
||||||
t.true(x.fraction == 0);
|
t.true(x.fraction === 0);
|
||||||
t.true(x.value == 3);
|
t.true(x.value === 3);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("ecdsa", t => {
|
test("ecdsa", (t) => {
|
||||||
const priv = native.EcdsaPrivateKey.create();
|
const priv = native.EcdsaPrivateKey.create();
|
||||||
const pub1 = priv.getPublicKey();
|
const pub1 = priv.getPublicKey();
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("ecdhe", t => {
|
|
||||||
|
test("ecdhe", (t) => {
|
||||||
const priv = native.EcdhePrivateKey.create();
|
const priv = native.EcdhePrivateKey.create();
|
||||||
const pub = priv.getPublicKey();
|
const pub = priv.getPublicKey();
|
||||||
t.pass();
|
t.pass();
|
||||||
|
@ -27,8 +27,8 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import {AmountJson} from "../types";
|
import {AmountJson} from "../types";
|
||||||
import {getLib, EmscFunGen} from "./emscLoader";
|
|
||||||
|
|
||||||
|
import {EmscFunGen, getLib} from "./emscLoader";
|
||||||
|
|
||||||
const emscLib = getLib();
|
const emscLib = getLib();
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ const GNUNET_SYSERR = -1;
|
|||||||
const getEmsc: EmscFunGen = (name: string, ret: any, argTypes: any[]) => {
|
const getEmsc: EmscFunGen = (name: string, ret: any, argTypes: any[]) => {
|
||||||
return (...args: any[]) => {
|
return (...args: any[]) => {
|
||||||
return emscLib.ccall(name, ret, argTypes, args);
|
return emscLib.ccall(name, ret, argTypes, args);
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -59,84 +59,32 @@ const getEmsc: EmscFunGen = (name: string, ret: any, argTypes: any[]) => {
|
|||||||
* Wrapped emscripten functions that do not allocate any memory.
|
* Wrapped emscripten functions that do not allocate any memory.
|
||||||
*/
|
*/
|
||||||
const emsc = {
|
const emsc = {
|
||||||
|
amount_add: getEmsc("TALER_amount_add", "number", ["number", "number", "number"]),
|
||||||
|
amount_cmp: getEmsc("TALER_amount_cmp", "number", ["number", "number"]),
|
||||||
|
amount_get_zero: getEmsc("TALER_amount_get_zero", "number", ["string", "number"]),
|
||||||
|
amount_hton: getEmsc("TALER_amount_hton", "void", ["number", "number"]),
|
||||||
|
amount_normalize: getEmsc("TALER_amount_normalize", "void", ["number"]),
|
||||||
|
amount_ntoh: getEmsc("TALER_amount_ntoh", "void", ["number", "number"]),
|
||||||
|
amount_subtract: getEmsc("TALER_amount_subtract", "number", ["number", "number", "number"]),
|
||||||
|
ecdh_eddsa: getEmsc("GNUNET_CRYPTO_ecdh_eddsa", "number", ["number", "number", "number"]),
|
||||||
|
eddsa_sign: getEmsc("GNUNET_CRYPTO_eddsa_sign", "number", ["number", "number", "number"]),
|
||||||
|
eddsa_verify: getEmsc("GNUNET_CRYPTO_eddsa_verify", "number", ["number", "number", "number", "number"]),
|
||||||
free: (ptr: number) => emscLib._free(ptr),
|
free: (ptr: number) => emscLib._free(ptr),
|
||||||
get_value: getEmsc("TALER_WR_get_value",
|
get_currency: getEmsc("TALER_WR_get_currency", "string", ["number"]),
|
||||||
"number",
|
get_fraction: getEmsc("TALER_WR_get_fraction", "number", ["number"]),
|
||||||
["number"]),
|
get_value: getEmsc("TALER_WR_get_value", "number", ["number"]),
|
||||||
get_fraction: getEmsc("TALER_WR_get_fraction",
|
hash: getEmsc("GNUNET_CRYPTO_hash", "void", ["number", "number", "number"]),
|
||||||
"number",
|
hash_context_abort: getEmsc("GNUNET_CRYPTO_hash_context_abort", "void", ["number"]),
|
||||||
["number"]),
|
hash_context_finish: getEmsc("GNUNET_CRYPTO_hash_context_finish", "void", ["number", "number"]),
|
||||||
get_currency: getEmsc("TALER_WR_get_currency",
|
hash_context_read: getEmsc("GNUNET_CRYPTO_hash_context_read", "void", ["number", "number", "number"]),
|
||||||
"string",
|
hash_create_random: getEmsc("GNUNET_CRYPTO_hash_create_random", "void", ["number", "number"]),
|
||||||
["number"]),
|
memmove: getEmsc("memmove", "number", ["number", "number", "number"]),
|
||||||
amount_add: getEmsc("TALER_amount_add",
|
random_block: getEmsc("GNUNET_CRYPTO_random_block", "void", ["number", "number", "number"]),
|
||||||
"number",
|
rsa_blinding_key_destroy: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_free", "void", ["number"]),
|
||||||
["number", "number", "number"]),
|
rsa_public_key_free: getEmsc("GNUNET_CRYPTO_rsa_public_key_free", "void", ["number"]),
|
||||||
amount_subtract: getEmsc("TALER_amount_subtract",
|
rsa_signature_free: getEmsc("GNUNET_CRYPTO_rsa_signature_free", "void", ["number"]),
|
||||||
"number",
|
setup_fresh_coin: getEmsc( "TALER_setup_fresh_coin", "void", ["number", "number", "number"]),
|
||||||
["number", "number", "number"]),
|
string_to_data: getEmsc("GNUNET_STRINGS_string_to_data", "number", ["number", "number", "number", "number"]),
|
||||||
amount_normalize: getEmsc("TALER_amount_normalize",
|
|
||||||
"void",
|
|
||||||
["number"]),
|
|
||||||
amount_get_zero: getEmsc("TALER_amount_get_zero",
|
|
||||||
"number",
|
|
||||||
["string", "number"]),
|
|
||||||
amount_cmp: getEmsc("TALER_amount_cmp",
|
|
||||||
"number",
|
|
||||||
["number", "number"]),
|
|
||||||
amount_hton: getEmsc("TALER_amount_hton",
|
|
||||||
"void",
|
|
||||||
["number", "number"]),
|
|
||||||
amount_ntoh: getEmsc("TALER_amount_ntoh",
|
|
||||||
"void",
|
|
||||||
["number", "number"]),
|
|
||||||
hash: getEmsc("GNUNET_CRYPTO_hash",
|
|
||||||
"void",
|
|
||||||
["number", "number", "number"]),
|
|
||||||
memmove: getEmsc("memmove",
|
|
||||||
"number",
|
|
||||||
["number", "number", "number"]),
|
|
||||||
rsa_public_key_free: getEmsc("GNUNET_CRYPTO_rsa_public_key_free",
|
|
||||||
"void",
|
|
||||||
["number"]),
|
|
||||||
rsa_signature_free: getEmsc("GNUNET_CRYPTO_rsa_signature_free",
|
|
||||||
"void",
|
|
||||||
["number"]),
|
|
||||||
string_to_data: getEmsc("GNUNET_STRINGS_string_to_data",
|
|
||||||
"number",
|
|
||||||
["number", "number", "number", "number"]),
|
|
||||||
eddsa_sign: getEmsc("GNUNET_CRYPTO_eddsa_sign",
|
|
||||||
"number",
|
|
||||||
["number", "number", "number"]),
|
|
||||||
eddsa_verify: getEmsc("GNUNET_CRYPTO_eddsa_verify",
|
|
||||||
"number",
|
|
||||||
["number", "number", "number", "number"]),
|
|
||||||
hash_create_random: getEmsc("GNUNET_CRYPTO_hash_create_random",
|
|
||||||
"void",
|
|
||||||
["number", "number"]),
|
|
||||||
rsa_blinding_key_destroy: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_free",
|
|
||||||
"void",
|
|
||||||
["number"]),
|
|
||||||
random_block: getEmsc("GNUNET_CRYPTO_random_block",
|
|
||||||
"void",
|
|
||||||
["number", "number", "number"]),
|
|
||||||
hash_context_abort: getEmsc("GNUNET_CRYPTO_hash_context_abort",
|
|
||||||
"void",
|
|
||||||
["number"]),
|
|
||||||
hash_context_read: getEmsc("GNUNET_CRYPTO_hash_context_read",
|
|
||||||
"void",
|
|
||||||
["number", "number", "number"]),
|
|
||||||
hash_context_finish: getEmsc("GNUNET_CRYPTO_hash_context_finish",
|
|
||||||
"void",
|
|
||||||
["number", "number"]),
|
|
||||||
ecdh_eddsa: getEmsc("GNUNET_CRYPTO_ecdh_eddsa",
|
|
||||||
"number",
|
|
||||||
["number", "number", "number"]),
|
|
||||||
|
|
||||||
setup_fresh_coin: getEmsc(
|
|
||||||
"TALER_setup_fresh_coin",
|
|
||||||
"void",
|
|
||||||
["number", "number", "number"]),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -144,64 +92,26 @@ const emsc = {
|
|||||||
* Emscripten functions that allocate memory.
|
* Emscripten functions that allocate memory.
|
||||||
*/
|
*/
|
||||||
const emscAlloc = {
|
const emscAlloc = {
|
||||||
get_amount: getEmsc("TALER_WRALL_get_amount",
|
data_to_string_alloc: getEmsc("GNUNET_STRINGS_data_to_string_alloc", "number", ["number", "number"]),
|
||||||
"number",
|
ecdhe_key_create: getEmsc("GNUNET_CRYPTO_ecdhe_key_create", "number", []),
|
||||||
["number", "number", "number", "string"]),
|
ecdhe_public_key_from_private: getEmsc( "TALER_WRALL_ecdhe_public_key_from_private", "number", ["number"]),
|
||||||
eddsa_key_create: getEmsc("GNUNET_CRYPTO_eddsa_key_create",
|
ecdsa_key_create: getEmsc("GNUNET_CRYPTO_ecdsa_key_create", "number", []),
|
||||||
"number", []),
|
ecdsa_public_key_from_private: getEmsc( "TALER_WRALL_ecdsa_public_key_from_private", "number", ["number"]),
|
||||||
ecdsa_key_create: getEmsc("GNUNET_CRYPTO_ecdsa_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", "number", ["number"]),
|
||||||
ecdhe_key_create: getEmsc("GNUNET_CRYPTO_ecdhe_key_create",
|
get_amount: getEmsc("TALER_WRALL_get_amount", "number", ["number", "number", "number", "string"]),
|
||||||
"number", []),
|
hash_context_start: getEmsc("GNUNET_CRYPTO_hash_context_start", "number", []),
|
||||||
eddsa_public_key_from_private: getEmsc(
|
|
||||||
"TALER_WRALL_eddsa_public_key_from_private",
|
|
||||||
"number",
|
|
||||||
["number"]),
|
|
||||||
ecdsa_public_key_from_private: getEmsc(
|
|
||||||
"TALER_WRALL_ecdsa_public_key_from_private",
|
|
||||||
"number",
|
|
||||||
["number"]),
|
|
||||||
ecdhe_public_key_from_private: getEmsc(
|
|
||||||
"TALER_WRALL_ecdhe_public_key_from_private",
|
|
||||||
"number",
|
|
||||||
["number"]),
|
|
||||||
data_to_string_alloc: getEmsc("GNUNET_STRINGS_data_to_string_alloc",
|
|
||||||
"number",
|
|
||||||
["number", "number"]),
|
|
||||||
purpose_create: getEmsc("TALER_WRALL_purpose_create",
|
|
||||||
"number",
|
|
||||||
["number", "number", "number"]),
|
|
||||||
rsa_blind: getEmsc("GNUNET_CRYPTO_rsa_blind",
|
|
||||||
"number",
|
|
||||||
["number", "number", "number", "number", "number"]),
|
|
||||||
rsa_blinding_key_create: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_create",
|
|
||||||
"number",
|
|
||||||
["number"]),
|
|
||||||
rsa_blinding_key_encode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_encode",
|
|
||||||
"number",
|
|
||||||
["number", "number"]),
|
|
||||||
rsa_signature_encode: getEmsc("GNUNET_CRYPTO_rsa_signature_encode",
|
|
||||||
"number",
|
|
||||||
["number", "number"]),
|
|
||||||
rsa_blinding_key_decode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_decode",
|
|
||||||
"number",
|
|
||||||
["number", "number"]),
|
|
||||||
rsa_public_key_decode: getEmsc("GNUNET_CRYPTO_rsa_public_key_decode",
|
|
||||||
"number",
|
|
||||||
["number", "number"]),
|
|
||||||
rsa_signature_decode: getEmsc("GNUNET_CRYPTO_rsa_signature_decode",
|
|
||||||
"number",
|
|
||||||
["number", "number"]),
|
|
||||||
rsa_public_key_encode: getEmsc("GNUNET_CRYPTO_rsa_public_key_encode",
|
|
||||||
"number",
|
|
||||||
["number", "number"]),
|
|
||||||
rsa_unblind: getEmsc("GNUNET_CRYPTO_rsa_unblind",
|
|
||||||
"number",
|
|
||||||
["number", "number", "number"]),
|
|
||||||
hash_context_start: getEmsc("GNUNET_CRYPTO_hash_context_start",
|
|
||||||
"number",
|
|
||||||
[]),
|
|
||||||
malloc: (size: number) => emscLib._malloc(size),
|
malloc: (size: number) => emscLib._malloc(size),
|
||||||
|
purpose_create: getEmsc("TALER_WRALL_purpose_create", "number", ["number", "number", "number"]),
|
||||||
|
rsa_blind: getEmsc("GNUNET_CRYPTO_rsa_blind", "number", ["number", "number", "number", "number", "number"]),
|
||||||
|
rsa_blinding_key_create: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_create", "number", ["number"]),
|
||||||
|
rsa_blinding_key_decode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_decode", "number", ["number", "number"]),
|
||||||
|
rsa_blinding_key_encode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_encode", "number", ["number", "number"]),
|
||||||
|
rsa_public_key_decode: getEmsc("GNUNET_CRYPTO_rsa_public_key_decode", "number", ["number", "number"]),
|
||||||
|
rsa_public_key_encode: getEmsc("GNUNET_CRYPTO_rsa_public_key_encode", "number", ["number", "number"]),
|
||||||
|
rsa_signature_decode: getEmsc("GNUNET_CRYPTO_rsa_signature_decode", "number", ["number", "number"]),
|
||||||
|
rsa_signature_encode: getEmsc("GNUNET_CRYPTO_rsa_signature_encode", "number", ["number", "number"]),
|
||||||
|
rsa_unblind: getEmsc("GNUNET_CRYPTO_rsa_unblind", "number", ["number", "number", "number"]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -226,7 +136,7 @@ export enum SignaturePurpose {
|
|||||||
export enum RandomQuality {
|
export enum RandomQuality {
|
||||||
WEAK = 0,
|
WEAK = 0,
|
||||||
STRONG = 1,
|
STRONG = 1,
|
||||||
NONCE = 2
|
NONCE = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -301,8 +211,8 @@ abstract class MallocArenaObject implements ArenaObject {
|
|||||||
|
|
||||||
constructor(arena?: Arena) {
|
constructor(arena?: Arena) {
|
||||||
if (!arena) {
|
if (!arena) {
|
||||||
if (arenaStack.length == 0) {
|
if (arenaStack.length === 0) {
|
||||||
throw Error("No arena available")
|
throw Error("No arena available");
|
||||||
}
|
}
|
||||||
arena = arenaStack[arenaStack.length - 1];
|
arena = arenaStack[arenaStack.length - 1];
|
||||||
}
|
}
|
||||||
@ -349,7 +259,7 @@ interface Arena {
|
|||||||
* Arena that must be manually destroyed.
|
* Arena that must be manually destroyed.
|
||||||
*/
|
*/
|
||||||
class SimpleArena implements Arena {
|
class SimpleArena implements Arena {
|
||||||
heap: Array<ArenaObject>;
|
heap: ArenaObject[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.heap = [];
|
this.heap = [];
|
||||||
@ -360,10 +270,10 @@ class SimpleArena implements Arena {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
for (let obj of this.heap) {
|
for (const obj of this.heap) {
|
||||||
obj.destroy();
|
obj.destroy();
|
||||||
}
|
}
|
||||||
this.heap = []
|
this.heap = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,7 +306,7 @@ class SyncArena extends SimpleArena {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let arenaStack: Arena[] = [];
|
const arenaStack: Arena[] = [];
|
||||||
arenaStack.push(new SyncArena());
|
arenaStack.push(new SyncArena());
|
||||||
|
|
||||||
|
|
||||||
@ -417,9 +327,9 @@ export class Amount extends MallocArenaObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getZero(currency: string, a?: Arena): Amount {
|
static getZero(currency: string, a?: Arena): Amount {
|
||||||
let am = new Amount(undefined, a);
|
const am = new Amount(undefined, a);
|
||||||
let r = emsc.amount_get_zero(currency, am.nativePtr);
|
const r = emsc.amount_get_zero(currency, am.nativePtr);
|
||||||
if (r != GNUNET_OK) {
|
if (r !== GNUNET_OK) {
|
||||||
throw Error("invalid currency");
|
throw Error("invalid currency");
|
||||||
}
|
}
|
||||||
return am;
|
return am;
|
||||||
@ -427,7 +337,7 @@ export class Amount extends MallocArenaObject {
|
|||||||
|
|
||||||
|
|
||||||
toNbo(a?: Arena): AmountNbo {
|
toNbo(a?: Arena): AmountNbo {
|
||||||
let x = new AmountNbo(a);
|
const x = new AmountNbo(a);
|
||||||
x.alloc();
|
x.alloc();
|
||||||
emsc.amount_hton(x.nativePtr, this.nativePtr);
|
emsc.amount_hton(x.nativePtr, this.nativePtr);
|
||||||
return x;
|
return x;
|
||||||
@ -445,15 +355,15 @@ export class Amount extends MallocArenaObject {
|
|||||||
return emsc.get_fraction(this.nativePtr);
|
return emsc.get_fraction(this.nativePtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
get currency(): String {
|
get currency(): string {
|
||||||
return emsc.get_currency(this.nativePtr);
|
return emsc.get_currency(this.nativePtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson(): AmountJson {
|
toJson(): AmountJson {
|
||||||
return {
|
return {
|
||||||
value: emsc.get_value(this.nativePtr),
|
currency: emsc.get_currency(this.nativePtr),
|
||||||
fraction: emsc.get_fraction(this.nativePtr),
|
fraction: emsc.get_fraction(this.nativePtr),
|
||||||
currency: emsc.get_currency(this.nativePtr)
|
value: emsc.get_value(this.nativePtr),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,7 +371,7 @@ export class Amount extends MallocArenaObject {
|
|||||||
* Add an amount to this amount.
|
* Add an amount to this amount.
|
||||||
*/
|
*/
|
||||||
add(a: Amount) {
|
add(a: Amount) {
|
||||||
let res = emsc.amount_add(this.nativePtr, a.nativePtr, this.nativePtr);
|
const res = emsc.amount_add(this.nativePtr, a.nativePtr, this.nativePtr);
|
||||||
if (res < 1) {
|
if (res < 1) {
|
||||||
// Overflow
|
// Overflow
|
||||||
return false;
|
return false;
|
||||||
@ -474,8 +384,8 @@ export class Amount extends MallocArenaObject {
|
|||||||
*/
|
*/
|
||||||
sub(a: Amount) {
|
sub(a: Amount) {
|
||||||
// this = this - a
|
// this = this - a
|
||||||
let res = emsc.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr);
|
const res = emsc.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr);
|
||||||
if (res == 0) {
|
if (res === 0) {
|
||||||
// Underflow
|
// Underflow
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -503,10 +413,10 @@ export class Amount extends MallocArenaObject {
|
|||||||
* Count the UTF-8 characters in a JavaScript string.
|
* Count the UTF-8 characters in a JavaScript string.
|
||||||
*/
|
*/
|
||||||
function countUtf8Bytes(str: string): number {
|
function countUtf8Bytes(str: string): number {
|
||||||
var s = str.length;
|
let s = str.length;
|
||||||
// JavaScript strings are UTF-16 arrays
|
// JavaScript strings are UTF-16 arrays
|
||||||
for (let i = str.length - 1; i >= 0; i--) {
|
for (let i = str.length - 1; i >= 0; i--) {
|
||||||
var code = str.charCodeAt(i);
|
const code = str.charCodeAt(i);
|
||||||
if (code > 0x7f && code <= 0x7ff) {
|
if (code > 0x7f && code <= 0x7ff) {
|
||||||
// We need an extra byte in utf-8 here
|
// We need an extra byte in utf-8 here
|
||||||
s++;
|
s++;
|
||||||
@ -540,8 +450,8 @@ abstract class PackedArenaObject extends MallocArenaObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toCrock(): string {
|
toCrock(): string {
|
||||||
var d = emscAlloc.data_to_string_alloc(this.nativePtr, this.size());
|
const d = emscAlloc.data_to_string_alloc(this.nativePtr, this.size());
|
||||||
var s = emscLib.Pointer_stringify(d);
|
const s = emscLib.Pointer_stringify(d);
|
||||||
emsc.free(d);
|
emsc.free(d);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@ -557,8 +467,8 @@ abstract class PackedArenaObject extends MallocArenaObject {
|
|||||||
this.alloc();
|
this.alloc();
|
||||||
// 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.fromStringWithNull(s);
|
const buf = ByteArray.fromStringWithNull(s);
|
||||||
let res = emsc.string_to_data(buf.nativePtr,
|
const res = emsc.string_to_data(buf.nativePtr,
|
||||||
s.length,
|
s.length,
|
||||||
this.nativePtr,
|
this.nativePtr,
|
||||||
this.size());
|
this.size());
|
||||||
@ -576,20 +486,20 @@ abstract class PackedArenaObject extends MallocArenaObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hash(): HashCode {
|
hash(): HashCode {
|
||||||
var x = new HashCode();
|
const x = new HashCode();
|
||||||
x.alloc();
|
x.alloc();
|
||||||
emsc.hash(this.nativePtr, this.size(), x.nativePtr);
|
emsc.hash(this.nativePtr, this.size(), x.nativePtr);
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
hexdump() {
|
hexdump() {
|
||||||
let bytes: string[] = [];
|
const bytes: string[] = [];
|
||||||
for (let i = 0; i < this.size(); i++) {
|
for (let i = 0; i < this.size(); i++) {
|
||||||
let b = emscLib.getValue(this.nativePtr + i, "i8");
|
let b = emscLib.getValue(this.nativePtr + i, "i8");
|
||||||
b = (b + 256) % 256;
|
b = (b + 256) % 256;
|
||||||
bytes.push("0".concat(b.toString(16)).slice(-2));
|
bytes.push("0".concat(b.toString(16)).slice(-2));
|
||||||
}
|
}
|
||||||
let lines: string[] = [];
|
const lines: string[] = [];
|
||||||
for (let i = 0; i < bytes.length; i += 8) {
|
for (let i = 0; i < bytes.length; i += 8) {
|
||||||
lines.push(bytes.slice(i, i + 8).join(","));
|
lines.push(bytes.slice(i, i + 8).join(","));
|
||||||
}
|
}
|
||||||
@ -607,10 +517,10 @@ export class AmountNbo extends PackedArenaObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJson(): any {
|
toJson(): any {
|
||||||
let a = new SimpleArena();
|
const a = new SimpleArena();
|
||||||
let am = new Amount(undefined, a);
|
const am = new Amount(undefined, a);
|
||||||
am.fromNbo(this);
|
am.fromNbo(this);
|
||||||
let json = am.toJson();
|
const json = am.toJson();
|
||||||
a.destroy();
|
a.destroy();
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@ -621,7 +531,7 @@ export class AmountNbo extends PackedArenaObject {
|
|||||||
* Create a packed arena object from the base32 crockford encoding.
|
* Create a packed arena object from the base32 crockford encoding.
|
||||||
*/
|
*/
|
||||||
function fromCrock<T extends PackedArenaObject>(s: string, ctor: Ctor<T>): T {
|
function fromCrock<T extends PackedArenaObject>(s: string, ctor: Ctor<T>): T {
|
||||||
let x: T = new ctor();
|
const x: T = new ctor();
|
||||||
x.alloc();
|
x.alloc();
|
||||||
x.loadCrock(s);
|
x.loadCrock(s);
|
||||||
return x;
|
return x;
|
||||||
@ -632,9 +542,11 @@ function fromCrock<T extends PackedArenaObject>(s: string, ctor: Ctor<T>): T {
|
|||||||
* Create a packed arena object from the base32 crockford encoding for objects
|
* Create a packed arena object from the base32 crockford encoding for objects
|
||||||
* that have a special decoding function.
|
* that have a special decoding function.
|
||||||
*/
|
*/
|
||||||
function fromCrockDecoded<T extends MallocArenaObject>(s: string, ctor: Ctor<T>, decodeFn: (p: number, s: number) => number): T {
|
function fromCrockDecoded<T extends MallocArenaObject>(s: string,
|
||||||
let obj = new ctor();
|
ctor: Ctor<T>,
|
||||||
let buf = ByteArray.fromCrock(s);
|
decodeFn: (p: number, s: number) => number): T {
|
||||||
|
const obj = new ctor();
|
||||||
|
const buf = ByteArray.fromCrock(s);
|
||||||
obj.nativePtr = decodeFn(buf.nativePtr, buf.size());
|
obj.nativePtr = decodeFn(buf.nativePtr, buf.size());
|
||||||
buf.destroy();
|
buf.destroy();
|
||||||
return obj;
|
return obj;
|
||||||
@ -645,10 +557,10 @@ function fromCrockDecoded<T extends MallocArenaObject>(s: string, ctor: Ctor<T>,
|
|||||||
* Encode an object using a special encoding function.
|
* Encode an object using a special encoding function.
|
||||||
*/
|
*/
|
||||||
function encode<T extends MallocArenaObject>(obj: T, encodeFn: any, arena?: Arena): ByteArray {
|
function encode<T extends MallocArenaObject>(obj: T, encodeFn: any, arena?: Arena): ByteArray {
|
||||||
let ptr = emscAlloc.malloc(PTR_SIZE);
|
const ptr = emscAlloc.malloc(PTR_SIZE);
|
||||||
let len = encodeFn(obj.nativePtr, ptr);
|
const len = encodeFn(obj.nativePtr, ptr);
|
||||||
let res = new ByteArray(len, undefined, arena);
|
const res = new ByteArray(len, undefined, arena);
|
||||||
res.nativePtr = emscLib.getValue(ptr, '*');
|
res.nativePtr = emscLib.getValue(ptr, "*");
|
||||||
emsc.free(ptr);
|
emsc.free(ptr);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -659,7 +571,7 @@ function encode<T extends MallocArenaObject>(obj: T, encodeFn: any, arena?: Aren
|
|||||||
*/
|
*/
|
||||||
export class EddsaPrivateKey extends PackedArenaObject {
|
export class EddsaPrivateKey extends PackedArenaObject {
|
||||||
static create(a?: Arena): EddsaPrivateKey {
|
static create(a?: Arena): EddsaPrivateKey {
|
||||||
let obj = new EddsaPrivateKey(a);
|
const obj = new EddsaPrivateKey(a);
|
||||||
obj.nativePtr = emscAlloc.eddsa_key_create();
|
obj.nativePtr = emscAlloc.eddsa_key_create();
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -669,7 +581,7 @@ export class EddsaPrivateKey extends PackedArenaObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPublicKey(a?: Arena): EddsaPublicKey {
|
getPublicKey(a?: Arena): EddsaPublicKey {
|
||||||
let obj = new EddsaPublicKey(a);
|
const 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;
|
||||||
}
|
}
|
||||||
@ -682,7 +594,7 @@ export class EddsaPrivateKey extends PackedArenaObject {
|
|||||||
|
|
||||||
export class EcdsaPrivateKey extends PackedArenaObject {
|
export class EcdsaPrivateKey extends PackedArenaObject {
|
||||||
static create(a?: Arena): EcdsaPrivateKey {
|
static create(a?: Arena): EcdsaPrivateKey {
|
||||||
let obj = new EcdsaPrivateKey(a);
|
const obj = new EcdsaPrivateKey(a);
|
||||||
obj.nativePtr = emscAlloc.ecdsa_key_create();
|
obj.nativePtr = emscAlloc.ecdsa_key_create();
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -692,7 +604,7 @@ export class EcdsaPrivateKey extends PackedArenaObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPublicKey(a?: Arena): EcdsaPublicKey {
|
getPublicKey(a?: Arena): EcdsaPublicKey {
|
||||||
let obj = new EcdsaPublicKey(a);
|
const obj = new EcdsaPublicKey(a);
|
||||||
obj.nativePtr = emscAlloc.ecdsa_public_key_from_private(this.nativePtr);
|
obj.nativePtr = emscAlloc.ecdsa_public_key_from_private(this.nativePtr);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -705,7 +617,7 @@ export class EcdsaPrivateKey extends PackedArenaObject {
|
|||||||
|
|
||||||
export class EcdhePrivateKey extends PackedArenaObject {
|
export class EcdhePrivateKey extends PackedArenaObject {
|
||||||
static create(a?: Arena): EcdhePrivateKey {
|
static create(a?: Arena): EcdhePrivateKey {
|
||||||
let obj = new EcdhePrivateKey(a);
|
const obj = new EcdhePrivateKey(a);
|
||||||
obj.nativePtr = emscAlloc.ecdhe_key_create();
|
obj.nativePtr = emscAlloc.ecdhe_key_create();
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -715,7 +627,7 @@ export class EcdhePrivateKey extends PackedArenaObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPublicKey(a?: Arena): EcdhePublicKey {
|
getPublicKey(a?: Arena): EcdhePublicKey {
|
||||||
let obj = new EcdhePublicKey(a);
|
const obj = new EcdhePublicKey(a);
|
||||||
obj.nativePtr = emscAlloc.ecdhe_public_key_from_private(this.nativePtr);
|
obj.nativePtr = emscAlloc.ecdhe_public_key_from_private(this.nativePtr);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -730,7 +642,7 @@ export class EcdhePrivateKey extends PackedArenaObject {
|
|||||||
* Constructor for a given type.
|
* Constructor for a given type.
|
||||||
*/
|
*/
|
||||||
interface Ctor<T> {
|
interface Ctor<T> {
|
||||||
new(): T
|
new(): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -774,7 +686,7 @@ export class RsaBlindingKeySecret extends PackedArenaObject {
|
|||||||
* Create a random blinding key secret.
|
* Create a random blinding key secret.
|
||||||
*/
|
*/
|
||||||
static create(a?: Arena): RsaBlindingKeySecret {
|
static create(a?: Arena): RsaBlindingKeySecret {
|
||||||
let o = new RsaBlindingKeySecret(a);
|
const o = new RsaBlindingKeySecret(a);
|
||||||
o.alloc();
|
o.alloc();
|
||||||
o.randomize();
|
o.randomize();
|
||||||
return o;
|
return o;
|
||||||
@ -821,16 +733,16 @@ export class ByteArray extends PackedArenaObject {
|
|||||||
|
|
||||||
static fromStringWithoutNull(s: string, a?: Arena): ByteArray {
|
static fromStringWithoutNull(s: string, a?: Arena): ByteArray {
|
||||||
// UTF-8 bytes, including 0-terminator
|
// UTF-8 bytes, including 0-terminator
|
||||||
let terminatedByteLength = countUtf8Bytes(s) + 1;
|
const terminatedByteLength = countUtf8Bytes(s) + 1;
|
||||||
let hstr = emscAlloc.malloc(terminatedByteLength);
|
const hstr = emscAlloc.malloc(terminatedByteLength);
|
||||||
emscLib.stringToUTF8(s, hstr, terminatedByteLength);
|
emscLib.stringToUTF8(s, hstr, terminatedByteLength);
|
||||||
return new ByteArray(terminatedByteLength - 1, hstr, a);
|
return new ByteArray(terminatedByteLength - 1, hstr, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromStringWithNull(s: string, a?: Arena): ByteArray {
|
static fromStringWithNull(s: string, a?: Arena): ByteArray {
|
||||||
// UTF-8 bytes, including 0-terminator
|
// UTF-8 bytes, including 0-terminator
|
||||||
let terminatedByteLength = countUtf8Bytes(s) + 1;
|
const terminatedByteLength = countUtf8Bytes(s) + 1;
|
||||||
let hstr = emscAlloc.malloc(terminatedByteLength);
|
const hstr = emscAlloc.malloc(terminatedByteLength);
|
||||||
emscLib.stringToUTF8(s, hstr, terminatedByteLength);
|
emscLib.stringToUTF8(s, hstr, terminatedByteLength);
|
||||||
return new ByteArray(terminatedByteLength, hstr, a);
|
return new ByteArray(terminatedByteLength, hstr, a);
|
||||||
}
|
}
|
||||||
@ -838,14 +750,14 @@ export class ByteArray extends PackedArenaObject {
|
|||||||
static fromCrock(s: string, a?: Arena): ByteArray {
|
static fromCrock(s: string, a?: Arena): ByteArray {
|
||||||
// this one is a bit more complicated than the other fromCrock functions,
|
// this one is a bit more complicated than the other fromCrock functions,
|
||||||
// since we don't have a fixed size
|
// since we don't have a fixed size
|
||||||
let byteLength = countUtf8Bytes(s);
|
const byteLength = countUtf8Bytes(s);
|
||||||
let hstr = emscAlloc.malloc(byteLength + 1);
|
const hstr = emscAlloc.malloc(byteLength + 1);
|
||||||
emscLib.stringToUTF8(s, hstr, byteLength + 1);
|
emscLib.stringToUTF8(s, hstr, byteLength + 1);
|
||||||
let decodedLen = Math.floor((byteLength * 5) / 8);
|
const decodedLen = Math.floor((byteLength * 5) / 8);
|
||||||
let ba = new ByteArray(decodedLen, undefined, a);
|
const ba = new ByteArray(decodedLen, undefined, a);
|
||||||
let res = emsc.string_to_data(hstr, byteLength, ba.nativePtr, decodedLen);
|
const res = emsc.string_to_data(hstr, byteLength, ba.nativePtr, decodedLen);
|
||||||
emsc.free(hstr);
|
emsc.free(hstr);
|
||||||
if (res != GNUNET_OK) {
|
if (res !== GNUNET_OK) {
|
||||||
throw Error("decoding failed");
|
throw Error("decoding failed");
|
||||||
}
|
}
|
||||||
return ba;
|
return ba;
|
||||||
@ -877,60 +789,60 @@ export class EccSignaturePurpose extends PackedArenaObject {
|
|||||||
|
|
||||||
|
|
||||||
abstract class SignatureStruct {
|
abstract class SignatureStruct {
|
||||||
abstract fieldTypes(): Array<any>;
|
abstract fieldTypes(): any[];
|
||||||
|
|
||||||
abstract purpose(): SignaturePurpose;
|
abstract purpose(): SignaturePurpose;
|
||||||
|
|
||||||
private members: any = {};
|
private members: any = {};
|
||||||
|
|
||||||
constructor(x: { [name: string]: any }) {
|
constructor(x: { [name: string]: any }) {
|
||||||
for (let k in x) {
|
for (const k in x) {
|
||||||
this.set(k, x[k]);
|
this.set(k, x[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toPurpose(a?: Arena): EccSignaturePurpose {
|
toPurpose(a?: Arena): EccSignaturePurpose {
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
for (let f of this.fieldTypes()) {
|
for (const f of this.fieldTypes()) {
|
||||||
let name = f[0];
|
const name = f[0];
|
||||||
let member = this.members[name];
|
const member = this.members[name];
|
||||||
if (!member) {
|
if (!member) {
|
||||||
throw Error(`Member ${name} not set`);
|
throw Error(`Member ${name} not set`);
|
||||||
}
|
}
|
||||||
totalSize += member.size();
|
totalSize += member.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
let buf = emscAlloc.malloc(totalSize);
|
const buf = emscAlloc.malloc(totalSize);
|
||||||
let ptr = buf;
|
let ptr = buf;
|
||||||
for (let f of this.fieldTypes()) {
|
for (const f of this.fieldTypes()) {
|
||||||
let name = f[0];
|
const name = f[0];
|
||||||
let member = this.members[name];
|
const member = this.members[name];
|
||||||
let size = member.size();
|
const size = member.size();
|
||||||
emsc.memmove(ptr, member.nativePtr, size);
|
emsc.memmove(ptr, member.nativePtr, size);
|
||||||
ptr += size;
|
ptr += size;
|
||||||
}
|
}
|
||||||
let ba = new ByteArray(totalSize, buf, a);
|
const ba = new ByteArray(totalSize, buf, a);
|
||||||
return new EccSignaturePurpose(this.purpose(), ba);
|
return new EccSignaturePurpose(this.purpose(), ba);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
let res: any = {};
|
const res: any = {};
|
||||||
for (let f of this.fieldTypes()) {
|
for (const f of this.fieldTypes()) {
|
||||||
let name = f[0];
|
const name = f[0];
|
||||||
let member = this.members[name];
|
const member = this.members[name];
|
||||||
if (!member) {
|
if (!member) {
|
||||||
throw Error(`Member ${name} not set`);
|
throw Error(`Member ${name} not set`);
|
||||||
}
|
}
|
||||||
res[name] = member.toJson();
|
res[name] = member.toJson();
|
||||||
}
|
}
|
||||||
res["purpose"] = this.purpose();
|
res.purpose = this.purpose();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected set(name: string, value: PackedArenaObject) {
|
protected set(name: string, value: PackedArenaObject) {
|
||||||
let typemap: any = {};
|
const typemap: any = {};
|
||||||
for (let f of this.fieldTypes()) {
|
for (const f of this.fieldTypes()) {
|
||||||
typemap[f[0]] = f[1];
|
typemap[f[0]] = f[1];
|
||||||
}
|
}
|
||||||
if (!(name in typemap)) {
|
if (!(name in typemap)) {
|
||||||
@ -969,7 +881,7 @@ export class WithdrawRequestPS extends SignatureStruct {
|
|||||||
["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],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1023,7 +935,7 @@ export class RefreshMeltCoinAffirmationPS extends SignatureStruct {
|
|||||||
["session_hash", HashCode],
|
["session_hash", HashCode],
|
||||||
["amount_with_fee", AmountNbo],
|
["amount_with_fee", AmountNbo],
|
||||||
["melt_fee", AmountNbo],
|
["melt_fee", AmountNbo],
|
||||||
["coin_pub", EddsaPublicKey]
|
["coin_pub", EddsaPublicKey],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1060,21 +972,21 @@ export class MasterWireFeePS extends SignatureStruct {
|
|||||||
|
|
||||||
export class AbsoluteTimeNbo extends PackedArenaObject {
|
export class AbsoluteTimeNbo extends PackedArenaObject {
|
||||||
static fromTalerString(s: string): AbsoluteTimeNbo {
|
static fromTalerString(s: string): AbsoluteTimeNbo {
|
||||||
let x = new AbsoluteTimeNbo();
|
const x = new AbsoluteTimeNbo();
|
||||||
x.alloc();
|
x.alloc();
|
||||||
let r = /Date\(([0-9]+)\)/;
|
const r = /Date\(([0-9]+)\)/;
|
||||||
let m = r.exec(s);
|
const m = r.exec(s);
|
||||||
if (!m || m.length != 2) {
|
if (!m || m.length !== 2) {
|
||||||
throw Error();
|
throw Error();
|
||||||
}
|
}
|
||||||
let n = parseInt(m[1]) * 1000000;
|
const n = parseInt(m[1], 10) * 1000000;
|
||||||
// XXX: This only works up to 54 bit numbers.
|
// XXX: This only works up to 54 bit numbers.
|
||||||
set64(x.nativePtr, n);
|
set64(x.nativePtr, n);
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromStampSeconds(stamp: number): AbsoluteTimeNbo {
|
static fromStampSeconds(stamp: number): AbsoluteTimeNbo {
|
||||||
let x = new AbsoluteTimeNbo();
|
const x = new AbsoluteTimeNbo();
|
||||||
x.alloc();
|
x.alloc();
|
||||||
// XXX: This only works up to 54 bit numbers.
|
// XXX: This only works up to 54 bit numbers.
|
||||||
set64(x.nativePtr, stamp * 1000000);
|
set64(x.nativePtr, stamp * 1000000);
|
||||||
@ -1107,7 +1019,7 @@ function set32(p: number, n: number) {
|
|||||||
|
|
||||||
export class UInt64 extends PackedArenaObject {
|
export class UInt64 extends PackedArenaObject {
|
||||||
static fromNumber(n: number): UInt64 {
|
static fromNumber(n: number): UInt64 {
|
||||||
let x = new UInt64();
|
const x = new UInt64();
|
||||||
x.alloc();
|
x.alloc();
|
||||||
set64(x.nativePtr, n);
|
set64(x.nativePtr, n);
|
||||||
return x;
|
return x;
|
||||||
@ -1121,7 +1033,7 @@ export class UInt64 extends PackedArenaObject {
|
|||||||
|
|
||||||
export class UInt32 extends PackedArenaObject {
|
export class UInt32 extends PackedArenaObject {
|
||||||
static fromNumber(n: number): UInt64 {
|
static fromNumber(n: number): UInt64 {
|
||||||
let x = new UInt32();
|
const x = new UInt32();
|
||||||
x.alloc();
|
x.alloc();
|
||||||
set32(x.nativePtr, n);
|
set32(x.nativePtr, n);
|
||||||
return x;
|
return x;
|
||||||
@ -1204,7 +1116,7 @@ export class DenominationKeyValidityPS extends SignatureStruct {
|
|||||||
["fee_deposit", AmountNbo],
|
["fee_deposit", AmountNbo],
|
||||||
["fee_refresh", AmountNbo],
|
["fee_refresh", AmountNbo],
|
||||||
["fee_refund", AmountNbo],
|
["fee_refund", AmountNbo],
|
||||||
["denom_hash", HashCode]
|
["denom_hash", HashCode],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1283,18 +1195,18 @@ export function rsaBlind(hashCode: HashCode,
|
|||||||
blindingKey: RsaBlindingKeySecret,
|
blindingKey: RsaBlindingKeySecret,
|
||||||
pkey: RsaPublicKey,
|
pkey: RsaPublicKey,
|
||||||
arena?: Arena): ByteArray|null {
|
arena?: Arena): ByteArray|null {
|
||||||
let buf_ptr_out = emscAlloc.malloc(PTR_SIZE);
|
const buf_ptr_out = emscAlloc.malloc(PTR_SIZE);
|
||||||
let buf_size_out = emscAlloc.malloc(PTR_SIZE);
|
const buf_size_out = emscAlloc.malloc(PTR_SIZE);
|
||||||
let res = emscAlloc.rsa_blind(hashCode.nativePtr,
|
const res = emscAlloc.rsa_blind(hashCode.nativePtr,
|
||||||
blindingKey.nativePtr,
|
blindingKey.nativePtr,
|
||||||
pkey.nativePtr,
|
pkey.nativePtr,
|
||||||
buf_ptr_out,
|
buf_ptr_out,
|
||||||
buf_size_out);
|
buf_size_out);
|
||||||
let buf_ptr = emscLib.getValue(buf_ptr_out, '*');
|
const buf_ptr = emscLib.getValue(buf_ptr_out, "*");
|
||||||
let buf_size = emscLib.getValue(buf_size_out, '*');
|
const buf_size = emscLib.getValue(buf_size_out, "*");
|
||||||
emsc.free(buf_ptr_out);
|
emsc.free(buf_ptr_out);
|
||||||
emsc.free(buf_size_out);
|
emsc.free(buf_size_out);
|
||||||
if (res != GNUNET_OK) {
|
if (res !== GNUNET_OK) {
|
||||||
// malicious key
|
// malicious key
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -1308,9 +1220,9 @@ export function rsaBlind(hashCode: HashCode,
|
|||||||
export function eddsaSign(purpose: EccSignaturePurpose,
|
export function eddsaSign(purpose: EccSignaturePurpose,
|
||||||
priv: EddsaPrivateKey,
|
priv: EddsaPrivateKey,
|
||||||
a?: Arena): EddsaSignature {
|
a?: Arena): EddsaSignature {
|
||||||
let sig = new EddsaSignature(a);
|
const sig = new EddsaSignature(a);
|
||||||
sig.alloc();
|
sig.alloc();
|
||||||
let res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr);
|
const res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr);
|
||||||
if (res < 1) {
|
if (res < 1) {
|
||||||
throw Error("EdDSA signing failed");
|
throw Error("EdDSA signing failed");
|
||||||
}
|
}
|
||||||
@ -1326,7 +1238,7 @@ export function eddsaVerify(purposeNum: number,
|
|||||||
sig: EddsaSignature,
|
sig: EddsaSignature,
|
||||||
pub: EddsaPublicKey,
|
pub: EddsaPublicKey,
|
||||||
a?: Arena): boolean {
|
a?: Arena): boolean {
|
||||||
let r = emsc.eddsa_verify(purposeNum,
|
const r = emsc.eddsa_verify(purposeNum,
|
||||||
verify.nativePtr,
|
verify.nativePtr,
|
||||||
sig.nativePtr,
|
sig.nativePtr,
|
||||||
pub.nativePtr);
|
pub.nativePtr);
|
||||||
@ -1341,7 +1253,7 @@ export function rsaUnblind(sig: RsaSignature,
|
|||||||
bk: RsaBlindingKeySecret,
|
bk: RsaBlindingKeySecret,
|
||||||
pk: RsaPublicKey,
|
pk: RsaPublicKey,
|
||||||
a?: Arena): RsaSignature {
|
a?: Arena): RsaSignature {
|
||||||
let x = new RsaSignature(a);
|
const x = new RsaSignature(a);
|
||||||
x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr,
|
x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr,
|
||||||
bk.nativePtr,
|
bk.nativePtr,
|
||||||
pk.nativePtr);
|
pk.nativePtr);
|
||||||
@ -1362,10 +1274,10 @@ export interface FreshCoin {
|
|||||||
*/
|
*/
|
||||||
export function ecdhEddsa(priv: EcdhePrivateKey,
|
export function ecdhEddsa(priv: EcdhePrivateKey,
|
||||||
pub: EddsaPublicKey): HashCode {
|
pub: EddsaPublicKey): HashCode {
|
||||||
let h = new HashCode();
|
const h = new HashCode();
|
||||||
h.alloc();
|
h.alloc();
|
||||||
let res = emsc.ecdh_eddsa(priv.nativePtr, pub.nativePtr, h.nativePtr);
|
const res = emsc.ecdh_eddsa(priv.nativePtr, pub.nativePtr, h.nativePtr);
|
||||||
if (res != GNUNET_OK) {
|
if (res !== GNUNET_OK) {
|
||||||
throw Error("ecdh_eddsa failed");
|
throw Error("ecdh_eddsa failed");
|
||||||
}
|
}
|
||||||
return h;
|
return h;
|
||||||
@ -1377,11 +1289,11 @@ export function ecdhEddsa(priv: EcdhePrivateKey,
|
|||||||
*/
|
*/
|
||||||
export function setupFreshCoin(secretSeed: TransferSecretP,
|
export function setupFreshCoin(secretSeed: TransferSecretP,
|
||||||
coinIndex: number): FreshCoin {
|
coinIndex: number): FreshCoin {
|
||||||
let priv = new EddsaPrivateKey();
|
const priv = new EddsaPrivateKey();
|
||||||
priv.isWeak = true;
|
priv.isWeak = true;
|
||||||
let blindingKey = new RsaBlindingKeySecret();
|
const blindingKey = new RsaBlindingKeySecret();
|
||||||
blindingKey.isWeak = true;
|
blindingKey.isWeak = true;
|
||||||
let buf = new ByteArray(priv.size() + blindingKey.size());
|
const buf = new ByteArray(priv.size() + blindingKey.size());
|
||||||
|
|
||||||
emsc.setup_fresh_coin(secretSeed.nativePtr, coinIndex, buf.nativePtr);
|
emsc.setup_fresh_coin(secretSeed.nativePtr, coinIndex, buf.nativePtr);
|
||||||
|
|
||||||
|
12
src/crypto/emscLoader.d.ts
vendored
12
src/crypto/emscLoader.d.ts
vendored
@ -20,25 +20,25 @@ declare function getLib(): EmscLib;
|
|||||||
export interface EmscFunGen {
|
export interface EmscFunGen {
|
||||||
(name: string,
|
(name: string,
|
||||||
ret: string,
|
ret: string,
|
||||||
args: string[]): ((...x: (number|string)[]) => any);
|
args: string[]): ((...x: Array<number|string>) => any);
|
||||||
(name: string,
|
(name: string,
|
||||||
ret: "number",
|
ret: "number",
|
||||||
args: string[]): ((...x: (number|string)[]) => number);
|
args: string[]): ((...x: Array<number|string>) => number);
|
||||||
(name: string,
|
(name: string,
|
||||||
ret: "void",
|
ret: "void",
|
||||||
args: string[]): ((...x: (number|string)[]) => void);
|
args: string[]): ((...x: Array<number|string>) => void);
|
||||||
(name: string,
|
(name: string,
|
||||||
ret: "string",
|
ret: "string",
|
||||||
args: string[]): ((...x: (number|string)[]) => string);
|
args: string[]): ((...x: Array<number|string>) => string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface EmscLib {
|
interface EmscLib {
|
||||||
cwrap: EmscFunGen;
|
cwrap: EmscFunGen;
|
||||||
|
|
||||||
ccall(name: string, ret:"number"|"string", argTypes: any[], args: any[]): any
|
ccall(name: string, ret: "number"|"string", argTypes: any[], args: any[]): any;
|
||||||
|
|
||||||
stringToUTF8(s: string, addr: number, maxLength: number): void
|
stringToUTF8(s: string, addr: number, maxLength: number): void;
|
||||||
|
|
||||||
_free(ptr: number): void;
|
_free(ptr: number): void;
|
||||||
|
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
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/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// tslint:disable:no-var-requires
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fork = require("child_process").fork;
|
const fork = require("child_process").fork;
|
||||||
|
|
||||||
@ -49,7 +52,7 @@ export class Worker {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.child.send({scriptFilename,cwd: process.cwd()});
|
this.child.send({scriptFilename, cwd: process.cwd()});
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener(event: "message" | "error", fn: (x: any) => void): void {
|
addEventListener(event: "message" | "error", fn: (x: any) => void): void {
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// tslint:disable:no-var-requires
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const vm = require("vm");
|
const vm = require("vm");
|
||||||
|
|
||||||
@ -22,57 +24,53 @@ process.once("message", (obj: any) => {
|
|||||||
const g: any = global as any;
|
const g: any = global as any;
|
||||||
|
|
||||||
(g as any).self = {
|
(g as any).self = {
|
||||||
|
addEventListener: (event: "error" | "message", fn: (x: any) => void) => {
|
||||||
|
if (event === "error") {
|
||||||
|
g.onerror = fn;
|
||||||
|
} else if (event === "message") {
|
||||||
|
g.onmessage = fn;
|
||||||
|
}
|
||||||
|
},
|
||||||
close: () => {
|
close: () => {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
},
|
},
|
||||||
postMessage: (msg: any) => {
|
|
||||||
const str: string = JSON.stringify({data: msg});
|
|
||||||
if (process.send) {
|
|
||||||
process.send(str);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmessage: undefined,
|
|
||||||
onerror: (err: any) => {
|
onerror: (err: any) => {
|
||||||
const str: string = JSON.stringify({error: err.message, stack: err.stack});
|
const str: string = JSON.stringify({error: err.message, stack: err.stack});
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send(str);
|
process.send(str);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addEventListener: (event: "error" | "message", fn: (x: any) => void) => {
|
onmessage: undefined,
|
||||||
if (event == "error") {
|
postMessage: (msg: any) => {
|
||||||
g.onerror = fn;
|
const str: string = JSON.stringify({data: msg});
|
||||||
} else if (event == "message") {
|
if (process.send) {
|
||||||
g.onmessage = fn;
|
process.send(str);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
g.__dirname = obj.cwd;
|
g.__dirname = obj.cwd;
|
||||||
g.__filename = __filename;
|
g.__filename = __filename;
|
||||||
//g.require = require;
|
|
||||||
//g.module = module;
|
|
||||||
//g.exports = module.exports;
|
|
||||||
|
|
||||||
g.importScripts = (...files: string[]) => {
|
g.importScripts = (...files: string[]) => {
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
vm.createScript(files.map(file => fs.readFileSync(file, "utf8")).join("\n")).runInThisContext();
|
vm.createScript(files.map((file) => fs.readFileSync(file, "utf8")).join("\n")).runInThisContext();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.keys(g.self).forEach(key => {
|
Object.keys(g.self).forEach((key) => {
|
||||||
g[key] = g.self[key];
|
g[key] = g.self[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("message", (msg: any) => {
|
process.on("message", (msg: any) => {
|
||||||
try {
|
try {
|
||||||
(g.onmessage || g.self.onmessage || (() => {}))(JSON.parse(msg));
|
(g.onmessage || g.self.onmessage || (() => undefined))(JSON.parse(msg));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
(g.onerror || g.self.onerror || (() => {}))(err);
|
(g.onerror || g.self.onerror || (() => undefined))(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("error", (err: any) => {
|
process.on("error", (err: any) => {
|
||||||
(g.onerror || g.self.onerror || (() => {}))(err);
|
(g.onerror || g.self.onerror || (() => undefined))(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
require(obj.scriptFilename);
|
require(obj.scriptFilename);
|
||||||
|
@ -1,8 +1,25 @@
|
|||||||
|
/*
|
||||||
|
This file is part of TALER
|
||||||
|
(C) 2017 Inria and GNUnet e.V.
|
||||||
|
|
||||||
|
TALER is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {test} from "ava";
|
import {test} from "ava";
|
||||||
import * as helpers from "./helpers";
|
import * as helpers from "./helpers";
|
||||||
|
|
||||||
|
|
||||||
test("URL canonicalization", t => {
|
test("URL canonicalization", (t) => {
|
||||||
// converts to relative, adds https
|
// converts to relative, adds https
|
||||||
t.is(
|
t.is(
|
||||||
"https://alice.example.com/exchange/",
|
"https://alice.example.com/exchange/",
|
||||||
|
@ -30,7 +30,7 @@ import URI = require("urijs");
|
|||||||
* settings such as significant digits or currency symbols.
|
* settings such as significant digits or currency symbols.
|
||||||
*/
|
*/
|
||||||
export function amountToPretty(amount: AmountJson): string {
|
export function amountToPretty(amount: AmountJson): string {
|
||||||
let x = amount.value + amount.fraction / Amounts.fractionalBase;
|
const x = amount.value + amount.fraction / Amounts.fractionalBase;
|
||||||
return `${x} ${amount.currency}`;
|
return `${x} ${amount.currency}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,14 +41,14 @@ export function amountToPretty(amount: AmountJson): string {
|
|||||||
* See http://api.taler.net/wallet.html#general
|
* See http://api.taler.net/wallet.html#general
|
||||||
*/
|
*/
|
||||||
export function canonicalizeBaseUrl(url: string) {
|
export function canonicalizeBaseUrl(url: string) {
|
||||||
let x = new URI(url);
|
const x = new URI(url);
|
||||||
if (!x.protocol()) {
|
if (!x.protocol()) {
|
||||||
x.protocol("https");
|
x.protocol("https");
|
||||||
}
|
}
|
||||||
x.path(x.path() + "/").normalizePath();
|
x.path(x.path() + "/").normalizePath();
|
||||||
x.fragment("");
|
x.fragment("");
|
||||||
x.query();
|
x.query();
|
||||||
return x.href()
|
return x.href();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -59,23 +59,23 @@ export function canonicalizeBaseUrl(url: string) {
|
|||||||
export function canonicalJson(obj: any): string {
|
export function canonicalJson(obj: any): string {
|
||||||
// Check for cycles, etc.
|
// Check for cycles, etc.
|
||||||
JSON.stringify(obj);
|
JSON.stringify(obj);
|
||||||
if (typeof obj == "string" || typeof obj == "number" || obj === null) {
|
if (typeof obj === "string" || typeof obj === "number" || obj === null) {
|
||||||
return JSON.stringify(obj)
|
return JSON.stringify(obj);
|
||||||
}
|
}
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
let objs: string[] = obj.map((e) => canonicalJson(e));
|
const objs: string[] = obj.map((e) => canonicalJson(e));
|
||||||
return `[${objs.join(',')}]`;
|
return `[${objs.join(",")}]`;
|
||||||
}
|
}
|
||||||
let keys: string[] = [];
|
const keys: string[] = [];
|
||||||
for (let key in obj) {
|
for (const key in obj) {
|
||||||
keys.push(key);
|
keys.push(key);
|
||||||
}
|
}
|
||||||
keys.sort();
|
keys.sort();
|
||||||
let s = "{";
|
let s = "{";
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
let key = keys[i];
|
const key = keys[i];
|
||||||
s += JSON.stringify(key) + ":" + canonicalJson(obj[key]);
|
s += JSON.stringify(key) + ":" + canonicalJson(obj[key]);
|
||||||
if (i != keys.length - 1) {
|
if (i !== keys.length - 1) {
|
||||||
s += ",";
|
s += ",";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ export function deepEquals(x: any, y: any): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var p = Object.keys(x);
|
const p = Object.keys(x);
|
||||||
return Object.keys(y).every((i) => p.indexOf(i) !== -1) &&
|
return Object.keys(y).every((i) => p.indexOf(i) !== -1) &&
|
||||||
p.every((i) => deepEquals(x[i], y[i]));
|
p.every((i) => deepEquals(x[i], y[i]));
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ export function getTalerStampSec(stamp: string): number | null {
|
|||||||
if (!m) {
|
if (!m) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return parseInt(m[1]);
|
return parseInt(m[1], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ export function getTalerStampSec(stamp: string): number | null {
|
|||||||
* Returns null if input is not in the right format.
|
* Returns null if input is not in the right format.
|
||||||
*/
|
*/
|
||||||
export function getTalerStampDate(stamp: string): Date | null {
|
export function getTalerStampDate(stamp: string): Date | null {
|
||||||
let sec = getTalerStampSec(stamp);
|
const sec = getTalerStampSec(stamp);
|
||||||
if (sec == null) {
|
if (sec == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ export class BrowserHttpLib {
|
|||||||
url: string,
|
url: string,
|
||||||
options?: any): Promise<HttpResponse> {
|
options?: any): Promise<HttpResponse> {
|
||||||
return new Promise<HttpResponse>((resolve, reject) => {
|
return new Promise<HttpResponse>((resolve, reject) => {
|
||||||
let myRequest = new XMLHttpRequest();
|
const myRequest = new XMLHttpRequest();
|
||||||
myRequest.open(method, url);
|
myRequest.open(method, url);
|
||||||
if (options && options.req) {
|
if (options && options.req) {
|
||||||
myRequest.send(options.req);
|
myRequest.send(options.req);
|
||||||
@ -57,10 +57,10 @@ export class BrowserHttpLib {
|
|||||||
myRequest.send();
|
myRequest.send();
|
||||||
}
|
}
|
||||||
myRequest.addEventListener("readystatechange", (e) => {
|
myRequest.addEventListener("readystatechange", (e) => {
|
||||||
if (myRequest.readyState == XMLHttpRequest.DONE) {
|
if (myRequest.readyState === XMLHttpRequest.DONE) {
|
||||||
let resp = {
|
const resp = {
|
||||||
|
responseText: myRequest.responseText,
|
||||||
status: myRequest.status,
|
status: myRequest.status,
|
||||||
responseText: myRequest.responseText
|
|
||||||
};
|
};
|
||||||
resolve(resp);
|
resolve(resp);
|
||||||
}
|
}
|
||||||
|
116
src/logging.ts
116
src/logging.ts
@ -20,7 +20,11 @@
|
|||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Store, QueryRoot, openPromise} from "./query";
|
import {
|
||||||
|
QueryRoot,
|
||||||
|
Store,
|
||||||
|
openPromise,
|
||||||
|
} from "./query";
|
||||||
|
|
||||||
export type Level = "error" | "debug" | "info" | "warn";
|
export type Level = "error" | "debug" | "info" | "warn";
|
||||||
|
|
||||||
@ -41,83 +45,92 @@ function makeDebug() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function log(msg: string, level: Level = "info"): Promise<void> {
|
export async function log(msg: string, level: Level = "info"): Promise<void> {
|
||||||
let ci = getCallInfo(2);
|
const ci = getCallInfo(2);
|
||||||
return record(level, msg, undefined, ci.file, ci.line, ci.column);
|
return record(level, msg, undefined, ci.file, ci.line, ci.column);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCallInfo(level: number) {
|
function getCallInfo(level: number) {
|
||||||
// see https://github.com/v8/v8/wiki/Stack-Trace-API
|
// see https://github.com/v8/v8/wiki/Stack-Trace-API
|
||||||
let stack = Error().stack;
|
const stack = Error().stack;
|
||||||
if (!stack) {
|
if (!stack) {
|
||||||
return unknownFrame;
|
return unknownFrame;
|
||||||
}
|
}
|
||||||
let lines = stack.split("\n");
|
const lines = stack.split("\n");
|
||||||
return parseStackLine(lines[level + 1]);
|
return parseStackLine(lines[level + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Frame {
|
interface Frame {
|
||||||
file?: string;
|
|
||||||
method?: string;
|
|
||||||
column?: number;
|
column?: number;
|
||||||
|
file?: string;
|
||||||
line?: number;
|
line?: number;
|
||||||
|
method?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unknownFrame: Frame = {
|
const unknownFrame: Frame = {
|
||||||
|
column: 0,
|
||||||
file: "(unknown)",
|
file: "(unknown)",
|
||||||
method: "(unknown)",
|
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 0
|
method: "(unknown)",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapted from https://github.com/errwischt/stacktrace-parser.
|
* Adapted from https://github.com/errwischt/stacktrace-parser.
|
||||||
*/
|
*/
|
||||||
function parseStackLine(stackLine: string): Frame {
|
function parseStackLine(stackLine: string): Frame {
|
||||||
|
// tslint:disable-next-line:max-line-length
|
||||||
const chrome = /^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
|
const chrome = /^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
|
||||||
const gecko = /^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i;
|
const gecko = /^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i;
|
||||||
const node = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i;
|
const node = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i;
|
||||||
let parts;
|
let parts;
|
||||||
|
|
||||||
if ((parts = gecko.exec(stackLine))) {
|
parts = gecko.exec(stackLine);
|
||||||
let f: Frame = {
|
if (parts) {
|
||||||
file: parts[3],
|
const f: Frame = {
|
||||||
method: parts[1] || "(unknown)",
|
|
||||||
line: +parts[4],
|
|
||||||
column: parts[5] ? +parts[5] : undefined,
|
column: parts[5] ? +parts[5] : undefined,
|
||||||
};
|
file: parts[3],
|
||||||
return f;
|
line: +parts[4],
|
||||||
} else if ((parts = chrome.exec(stackLine))) {
|
|
||||||
let f: Frame = {
|
|
||||||
file: parts[2],
|
|
||||||
method: parts[1] || "(unknown)",
|
method: parts[1] || "(unknown)",
|
||||||
line: +parts[3],
|
|
||||||
column: parts[4] ? +parts[4] : undefined,
|
|
||||||
};
|
|
||||||
return f;
|
|
||||||
} else if ((parts = node.exec(stackLine))) {
|
|
||||||
let f: Frame = {
|
|
||||||
file: parts[2],
|
|
||||||
method: parts[1] || "(unknown)",
|
|
||||||
line: +parts[3],
|
|
||||||
column: parts[4] ? +parts[4] : undefined,
|
|
||||||
};
|
};
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parts = chrome.exec(stackLine);
|
||||||
|
if (parts) {
|
||||||
|
const f: Frame = {
|
||||||
|
column: parts[4] ? +parts[4] : undefined,
|
||||||
|
file: parts[2],
|
||||||
|
line: +parts[3],
|
||||||
|
method: parts[1] || "(unknown)",
|
||||||
|
};
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = node.exec(stackLine);
|
||||||
|
if (parts) {
|
||||||
|
const f: Frame = {
|
||||||
|
column: parts[4] ? +parts[4] : undefined,
|
||||||
|
file: parts[2],
|
||||||
|
line: +parts[3],
|
||||||
|
method: parts[1] || "(unknown)",
|
||||||
|
};
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
return unknownFrame;
|
return unknownFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let db: IDBDatabase|undefined = undefined;
|
let db: IDBDatabase|undefined;
|
||||||
|
|
||||||
export interface LogEntry {
|
export interface LogEntry {
|
||||||
timestamp: number;
|
|
||||||
level: string;
|
|
||||||
msg: string;
|
|
||||||
detail?: string;
|
|
||||||
source?: string;
|
|
||||||
col?: number;
|
col?: number;
|
||||||
line?: number;
|
detail?: string;
|
||||||
id?: number;
|
id?: number;
|
||||||
|
level: string;
|
||||||
|
line?: number;
|
||||||
|
msg: string;
|
||||||
|
source?: string;
|
||||||
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLogs(): Promise<LogEntry[]> {
|
export async function getLogs(): Promise<LogEntry[]> {
|
||||||
@ -140,7 +153,7 @@ export async function recordException(msg: string, e: any): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
stack = e.stack;
|
stack = e.stack;
|
||||||
if (stack) {
|
if (stack) {
|
||||||
let lines = stack.split("\n");
|
const lines = stack.split("\n");
|
||||||
frame = parseStackLine(lines[1]);
|
frame = parseStackLine(lines[1]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -152,7 +165,12 @@ export async function recordException(msg: string, e: any): Promise<void> {
|
|||||||
return record("error", e.toString(), stack, frame.file, frame.line, frame.column);
|
return record("error", e.toString(), stack, frame.file, frame.line, frame.column);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function record(level: Level, msg: string, detail?: string, source?: string, line?: number, col?: number): Promise<void> {
|
export async function record(level: Level,
|
||||||
|
msg: string,
|
||||||
|
detail?: string,
|
||||||
|
source?: string,
|
||||||
|
line?: number,
|
||||||
|
col?: number): Promise<void> {
|
||||||
if (typeof indexedDB === "undefined") {
|
if (typeof indexedDB === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -160,7 +178,7 @@ export async function record(level: Level, msg: string, detail?: string, source?
|
|||||||
let myBarrier: any;
|
let myBarrier: any;
|
||||||
|
|
||||||
if (barrier) {
|
if (barrier) {
|
||||||
let p = barrier.promise;
|
const p = barrier.promise;
|
||||||
myBarrier = barrier = openPromise();
|
myBarrier = barrier = openPromise();
|
||||||
await p;
|
await p;
|
||||||
} else {
|
} else {
|
||||||
@ -172,20 +190,20 @@ export async function record(level: Level, msg: string, detail?: string, source?
|
|||||||
db = await openLoggingDb();
|
db = await openLoggingDb();
|
||||||
}
|
}
|
||||||
|
|
||||||
let count = await new QueryRoot(db).count(logsStore);
|
const count = await new QueryRoot(db).count(logsStore);
|
||||||
|
|
||||||
if (count > 1000) {
|
if (count > 1000) {
|
||||||
await new QueryRoot(db).deleteIf(logsStore, (e, i) => (i < 200));
|
await new QueryRoot(db).deleteIf(logsStore, (e, i) => (i < 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry: LogEntry = {
|
const entry: LogEntry = {
|
||||||
timestamp: new Date().getTime(),
|
|
||||||
level,
|
|
||||||
msg,
|
|
||||||
source,
|
|
||||||
line,
|
|
||||||
col,
|
col,
|
||||||
detail,
|
detail,
|
||||||
|
level,
|
||||||
|
line,
|
||||||
|
msg,
|
||||||
|
source,
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
};
|
};
|
||||||
await new QueryRoot(db).put(logsStore, entry);
|
await new QueryRoot(db).put(logsStore, entry);
|
||||||
} finally {
|
} finally {
|
||||||
@ -207,15 +225,15 @@ export function openLoggingDb(): Promise<IDBDatabase> {
|
|||||||
resolve(req.result);
|
resolve(req.result);
|
||||||
};
|
};
|
||||||
req.onupgradeneeded = (e) => {
|
req.onupgradeneeded = (e) => {
|
||||||
const db = req.result;
|
const resDb = req.result;
|
||||||
if (e.oldVersion != 0) {
|
if (e.oldVersion !== 0) {
|
||||||
try {
|
try {
|
||||||
db.deleteObjectStore("logs");
|
resDb.deleteObjectStore("logs");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.createObjectStore("logs", {keyPath: "id", autoIncrement: true});
|
resDb.createObjectStore("logs", {keyPath: "id", autoIncrement: true});
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -23,32 +23,32 @@
|
|||||||
|
|
||||||
function replacer(match: string, pIndent: string, pKey: string, pVal: string,
|
function replacer(match: string, pIndent: string, pKey: string, pVal: string,
|
||||||
pEnd: string) {
|
pEnd: string) {
|
||||||
var key = '<span class=json-key>';
|
const key = "<span class=json-key>";
|
||||||
var val = '<span class=json-value>';
|
const val = "<span class=json-value>";
|
||||||
var str = '<span class=json-string>';
|
const str = "<span class=json-string>";
|
||||||
var r = pIndent || '';
|
let r = pIndent || "";
|
||||||
if (pKey) {
|
if (pKey) {
|
||||||
r = r + key + '"' + pKey.replace(/[": ]/g, '') + '":</span> ';
|
r = r + key + '"' + pKey.replace(/[": ]/g, "") + '":</span> ';
|
||||||
}
|
}
|
||||||
if (pVal) {
|
if (pVal) {
|
||||||
r = r + (pVal[0] == '"' ? str : val) + pVal + '</span>';
|
r = r + (pVal[0] === '"' ? str : val) + pVal + "</span>";
|
||||||
}
|
}
|
||||||
return r + (pEnd || '');
|
return r + (pEnd || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function prettyPrint(obj: any) {
|
function prettyPrint(obj: any) {
|
||||||
var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg;
|
const jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg;
|
||||||
return JSON.stringify(obj, null as any, 3)
|
return JSON.stringify(obj, null as any, 3)
|
||||||
.replace(/&/g, '&').replace(/\\"/g, '"')
|
.replace(/&/g, "&").replace(/\\"/g, """)
|
||||||
.replace(/</g, '<').replace(/>/g, '>')
|
.replace(/</g, "<").replace(/>/g, ">")
|
||||||
.replace(jsonLine, replacer);
|
.replace(jsonLine, replacer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
chrome.runtime.sendMessage({type: 'dump-db'}, (resp) => {
|
chrome.runtime.sendMessage({type: "dump-db"}, (resp) => {
|
||||||
const el = document.getElementById('dump');
|
const el = document.getElementById("dump");
|
||||||
if (!el) {
|
if (!el) {
|
||||||
throw Error();
|
throw Error();
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
document.getElementById("download")!.addEventListener("click", (evt) => {
|
document.getElementById("download")!.addEventListener("click", (evt) => {
|
||||||
console.log("creating download");
|
console.log("creating download");
|
||||||
let element = document.createElement("a");
|
const element = document.createElement("a");
|
||||||
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(resp)));
|
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(resp)));
|
||||||
element.setAttribute("download", "wallet-dump.txt");
|
element.setAttribute("download", "wallet-dump.txt");
|
||||||
element.style.display = "none";
|
element.style.display = "none";
|
||||||
@ -67,9 +67,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let fileInput = document.getElementById("fileInput")! as HTMLInputElement;
|
const fileInput = document.getElementById("fileInput")! as HTMLInputElement;
|
||||||
fileInput.onchange = (evt) => {
|
fileInput.onchange = (evt) => {
|
||||||
if (!fileInput.files || fileInput.files.length != 1) {
|
if (!fileInput.files || fileInput.files.length !== 1) {
|
||||||
alert("please select exactly one file to import");
|
alert("please select exactly one file to import");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -77,9 +77,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const fr = new FileReader();
|
const fr = new FileReader();
|
||||||
fr.onload = (e: any) => {
|
fr.onload = (e: any) => {
|
||||||
console.log("got file");
|
console.log("got file");
|
||||||
let dump = JSON.parse(e.target.result);
|
const dump = JSON.parse(e.target.result);
|
||||||
console.log("parsed contents", dump);
|
console.log("parsed contents", dump);
|
||||||
chrome.runtime.sendMessage({ type: 'import-db', detail: { dump } }, (resp) => {
|
chrome.runtime.sendMessage({ type: "import-db", detail: { dump } }, (resp) => {
|
||||||
alert("loaded");
|
alert("loaded");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
229
src/query.ts
229
src/query.ts
@ -21,42 +21,42 @@
|
|||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
|
/**
|
||||||
export interface JoinResult<L,R> {
|
* Result of an inner join.
|
||||||
|
*/
|
||||||
|
export interface JoinResult<L, R> {
|
||||||
left: L;
|
left: L;
|
||||||
right: R;
|
right: R;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JoinLeftResult<L,R> {
|
/**
|
||||||
|
* Result of a left outer join.
|
||||||
|
*/
|
||||||
|
export interface JoinLeftResult<L, R> {
|
||||||
left: L;
|
left: L;
|
||||||
right?: R;
|
right?: R;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definition of an object store.
|
||||||
|
*/
|
||||||
export class Store<T> {
|
export class Store<T> {
|
||||||
name: string;
|
constructor(public name: string,
|
||||||
validator?: (v: T) => T;
|
public storeParams?: IDBObjectStoreParameters,
|
||||||
storeParams?: IDBObjectStoreParameters;
|
public validator?: (v: T) => T) {
|
||||||
|
|
||||||
constructor(name: string, storeParams?: IDBObjectStoreParameters,
|
|
||||||
validator?: (v: T) => T) {
|
|
||||||
this.name = name;
|
|
||||||
this.validator = validator;
|
|
||||||
this.storeParams = storeParams;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Index<S extends IDBValidKey,T> {
|
/**
|
||||||
indexName: string;
|
* Definition of an index.
|
||||||
|
*/
|
||||||
|
export class Index<S extends IDBValidKey, T> {
|
||||||
storeName: string;
|
storeName: string;
|
||||||
keyPath: string | string[];
|
|
||||||
|
|
||||||
constructor(s: Store<T>, indexName: string, keyPath: string | string[]) {
|
constructor(s: Store<T>, public indexName: string, public keyPath: string | string[]) {
|
||||||
this.storeName = s.name;
|
this.storeName = s.name;
|
||||||
this.indexName = indexName;
|
|
||||||
this.keyPath = keyPath;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,17 +65,58 @@ export class Index<S extends IDBValidKey,T> {
|
|||||||
* with indices.
|
* with indices.
|
||||||
*/
|
*/
|
||||||
export interface QueryStream<T> {
|
export interface QueryStream<T> {
|
||||||
indexJoin<S,I extends IDBValidKey>(index: Index<I,S>,
|
/**
|
||||||
keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>>;
|
* Join the current query with values from an index.
|
||||||
indexJoinLeft<S,I extends IDBValidKey>(index: Index<I,S>,
|
* The left side of the join is extracted via a function from the stream's
|
||||||
|
* result, the right side of the join is the key of the index.
|
||||||
|
*/
|
||||||
|
indexJoin<S, I extends IDBValidKey>(index: Index<I, S>, keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>>;
|
||||||
|
/**
|
||||||
|
* Join the current query with values from an index, and keep values in the
|
||||||
|
* current stream that don't have a match. The left side of the join is
|
||||||
|
* extracted via a function from the stream's result, the right side of the
|
||||||
|
* join is the key of the index.
|
||||||
|
*/
|
||||||
|
indexJoinLeft<S, I extends IDBValidKey>(index: Index<I, S>,
|
||||||
keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>>;
|
keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>>;
|
||||||
keyJoin<S,I extends IDBValidKey>(store: Store<S>,
|
/**
|
||||||
keyFn: (obj: T) => I): QueryStream<JoinResult<T,S>>;
|
* Join the current query with values from another object store.
|
||||||
filter(f: (T: any) => boolean): QueryStream<T>;
|
* The left side of the join is extracted via a function over the current query,
|
||||||
|
* the right side of the join is the key of the object store.
|
||||||
|
*/
|
||||||
|
keyJoin<S, I extends IDBValidKey>(store: Store<S>, keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only keep elements in the result stream for which the predicate returns
|
||||||
|
* true.
|
||||||
|
*/
|
||||||
|
filter(f: (x: T) => boolean): QueryStream<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduce the stream, resulting in a single value.
|
||||||
|
*/
|
||||||
reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>;
|
reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>;
|
||||||
map<S>(f: (x:T) => S): QueryStream<S>;
|
|
||||||
|
/**
|
||||||
|
* Map each element of the stream using a function, resulting in another
|
||||||
|
* stream of a different type.
|
||||||
|
*/
|
||||||
|
map<S>(f: (x: T) => S): QueryStream<S>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map each element of the stream to a potentially empty array, and collect
|
||||||
|
* the result in a stream of the flattened arrays.
|
||||||
|
*/
|
||||||
flatMap<S>(f: (x: T) => S[]): QueryStream<S>;
|
flatMap<S>(f: (x: T) => S[]): QueryStream<S>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect the stream into an array and return a promise for it.
|
||||||
|
*/
|
||||||
toArray(): Promise<T[]>;
|
toArray(): Promise<T[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first value of the stream.
|
||||||
|
*/
|
||||||
first(): QueryValue<T>;
|
first(): QueryValue<T>;
|
||||||
|
|
||||||
then(onfulfill: any, onreject: any): any;
|
then(onfulfill: any, onreject: any): any;
|
||||||
@ -99,7 +140,7 @@ abstract class BaseQueryValue<T> implements QueryValue<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
map<S>(f: (x: T) => S): QueryValue<S> {
|
map<S>(f: (x: T) => S): QueryValue<S> {
|
||||||
return new MapQueryValue<T,S>(this, f);
|
return new MapQueryValue<T, S>(this, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
cond<R>(f: (x: T) => boolean, onTrue: (r: QueryRoot) => R, onFalse: (r: QueryRoot) => R): Promise<void> {
|
cond<R>(f: (x: T) => boolean, onTrue: (r: QueryRoot) => R, onFalse: (r: QueryRoot) => R): Promise<void> {
|
||||||
@ -141,7 +182,7 @@ class FirstQueryValue<T> extends BaseQueryValue<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapQueryValue<T,S> extends BaseQueryValue<S> {
|
class MapQueryValue<T, S> extends BaseQueryValue<S> {
|
||||||
mapFn: (x: T) => S;
|
mapFn: (x: T) => S;
|
||||||
v: BaseQueryValue<T>;
|
v: BaseQueryValue<T>;
|
||||||
|
|
||||||
@ -157,7 +198,10 @@ class MapQueryValue<T,S> extends BaseQueryValue<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export let AbortTransaction = Symbol("abort_transaction");
|
/**
|
||||||
|
* Exception that should be thrown by client code to abort a transaction.
|
||||||
|
*/
|
||||||
|
export const AbortTransaction = Symbol("abort_transaction");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an unresolved promise together with its extracted resolve / reject
|
* Get an unresolved promise together with its extracted resolve / reject
|
||||||
@ -193,25 +237,26 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
|
|||||||
return new FirstQueryValue(this);
|
return new FirstQueryValue(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
|
then<R>(onfulfilled: (value: void) => R | PromiseLike<R>,
|
||||||
|
onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
|
||||||
return this.root.then(onfulfilled, onrejected);
|
return this.root.then(onfulfilled, onrejected);
|
||||||
}
|
}
|
||||||
|
|
||||||
flatMap<S>(f: (x: T) => S[]): QueryStream<S> {
|
flatMap<S>(f: (x: T) => S[]): QueryStream<S> {
|
||||||
return new QueryStreamFlatMap<T,S>(this, f);
|
return new QueryStreamFlatMap<T, S>(this, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
map<S>(f: (x: T) => S): QueryStream<S> {
|
map<S>(f: (x: T) => S): QueryStream<S> {
|
||||||
return new QueryStreamMap(this, f);
|
return new QueryStreamMap(this, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
indexJoin<S,I extends IDBValidKey>(index: Index<I,S>,
|
indexJoin<S, I extends IDBValidKey>(index: Index<I, S>,
|
||||||
keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> {
|
keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> {
|
||||||
this.root.addStoreAccess(index.storeName, false);
|
this.root.addStoreAccess(index.storeName, false);
|
||||||
return new QueryStreamIndexJoin<T, S>(this, index.storeName, index.indexName, keyFn);
|
return new QueryStreamIndexJoin<T, S>(this, index.storeName, index.indexName, keyFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
indexJoinLeft<S,I extends IDBValidKey>(index: Index<I,S>,
|
indexJoinLeft<S, I extends IDBValidKey>(index: Index<I, S>,
|
||||||
keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>> {
|
keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>> {
|
||||||
this.root.addStoreAccess(index.storeName, false);
|
this.root.addStoreAccess(index.storeName, false);
|
||||||
return new QueryStreamIndexJoinLeft<T, S>(this, index.storeName, index.indexName, keyFn);
|
return new QueryStreamIndexJoinLeft<T, S>(this, index.storeName, index.indexName, keyFn);
|
||||||
@ -228,8 +273,8 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toArray(): Promise<T[]> {
|
toArray(): Promise<T[]> {
|
||||||
let {resolve, promise} = openPromise();
|
const {resolve, promise} = openPromise();
|
||||||
let values: T[] = [];
|
const values: T[] = [];
|
||||||
|
|
||||||
this.subscribe((isDone, value) => {
|
this.subscribe((isDone, value) => {
|
||||||
if (isDone) {
|
if (isDone) {
|
||||||
@ -245,7 +290,7 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduce<A>(f: (x: any, acc?: A) => A, init?: A): Promise<any> {
|
reduce<A>(f: (x: any, acc?: A) => A, init?: A): Promise<any> {
|
||||||
let {resolve, promise} = openPromise();
|
const {resolve, promise} = openPromise();
|
||||||
let acc = init;
|
let acc = init;
|
||||||
|
|
||||||
this.subscribe((isDone, value) => {
|
this.subscribe((isDone, value) => {
|
||||||
@ -265,10 +310,7 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
|
|||||||
type FilterFn = (e: any) => boolean;
|
type FilterFn = (e: any) => boolean;
|
||||||
type SubscribeFn = (done: boolean, value: any, tx: IDBTransaction) => void;
|
type SubscribeFn = (done: boolean, value: any, tx: IDBTransaction) => void;
|
||||||
type SubscribeOneFn = (value: any, tx: IDBTransaction) => void;
|
type SubscribeOneFn = (value: any, tx: IDBTransaction) => void;
|
||||||
|
type FlatMapFn<T> = (v: T) => T[];
|
||||||
interface FlatMapFn<T> {
|
|
||||||
(v: T): T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
class QueryStreamFilter<T> extends QueryStreamBase<T> {
|
class QueryStreamFilter<T> extends QueryStreamBase<T> {
|
||||||
s: QueryStreamBase<T>;
|
s: QueryStreamBase<T>;
|
||||||
@ -294,7 +336,7 @@ class QueryStreamFilter<T> extends QueryStreamBase<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class QueryStreamFlatMap<T,S> extends QueryStreamBase<S> {
|
class QueryStreamFlatMap<T, S> extends QueryStreamBase<S> {
|
||||||
s: QueryStreamBase<T>;
|
s: QueryStreamBase<T>;
|
||||||
flatMapFn: (v: T) => S[];
|
flatMapFn: (v: T) => S[];
|
||||||
|
|
||||||
@ -310,16 +352,16 @@ class QueryStreamFlatMap<T,S> extends QueryStreamBase<S> {
|
|||||||
f(true, undefined, tx);
|
f(true, undefined, tx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let values = this.flatMapFn(value);
|
const values = this.flatMapFn(value);
|
||||||
for (let v in values) {
|
for (const v in values) {
|
||||||
f(false, value, tx)
|
f(false, value, tx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class QueryStreamMap<S,T> extends QueryStreamBase<T> {
|
class QueryStreamMap<S, T> extends QueryStreamBase<T> {
|
||||||
s: QueryStreamBase<S>;
|
s: QueryStreamBase<S>;
|
||||||
mapFn: (v: S) => T;
|
mapFn: (v: S) => T;
|
||||||
|
|
||||||
@ -335,7 +377,7 @@ class QueryStreamMap<S,T> extends QueryStreamBase<T> {
|
|||||||
f(true, undefined, tx);
|
f(true, undefined, tx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mappedValue = this.mapFn(value);
|
const mappedValue = this.mapFn(value);
|
||||||
f(false, mappedValue, tx);
|
f(false, mappedValue, tx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -363,15 +405,15 @@ class QueryStreamIndexJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> {
|
|||||||
f(true, undefined, tx);
|
f(true, undefined, tx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let s = tx.objectStore(this.storeName).index(this.indexName);
|
const s = tx.objectStore(this.storeName).index(this.indexName);
|
||||||
let req = s.openCursor(IDBKeyRange.only(this.key(value)));
|
const req = s.openCursor(IDBKeyRange.only(this.key(value)));
|
||||||
req.onsuccess = () => {
|
req.onsuccess = () => {
|
||||||
let cursor = req.result;
|
const cursor = req.result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
f(false, {left: value, right: cursor.value}, tx);
|
f(false, {left: value, right: cursor.value}, tx);
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -402,7 +444,7 @@ class QueryStreamIndexJoinLeft<T, S> extends QueryStreamBase<JoinLeftResult<T, S
|
|||||||
const req = s.openCursor(IDBKeyRange.only(this.key(value)));
|
const req = s.openCursor(IDBKeyRange.only(this.key(value)));
|
||||||
let gotMatch = false;
|
let gotMatch = false;
|
||||||
req.onsuccess = () => {
|
req.onsuccess = () => {
|
||||||
let cursor = req.result;
|
const cursor = req.result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
gotMatch = true;
|
gotMatch = true;
|
||||||
f(false, {left: value, right: cursor.value}, tx);
|
f(false, {left: value, right: cursor.value}, tx);
|
||||||
@ -412,7 +454,7 @@ class QueryStreamIndexJoinLeft<T, S> extends QueryStreamBase<JoinLeftResult<T, S
|
|||||||
f(false, {left: value}, tx);
|
f(false, {left: value}, tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -437,17 +479,17 @@ class QueryStreamKeyJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> {
|
|||||||
f(true, undefined, tx);
|
f(true, undefined, tx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let s = tx.objectStore(this.storeName);
|
const s = tx.objectStore(this.storeName);
|
||||||
let req = s.openCursor(IDBKeyRange.only(this.key(value)));
|
const req = s.openCursor(IDBKeyRange.only(this.key(value)));
|
||||||
req.onsuccess = () => {
|
req.onsuccess = () => {
|
||||||
let cursor = req.result;
|
const cursor = req.result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
f(false, {left:value, right: cursor.value}, tx);
|
f(false, {left: value, right: cursor.value}, tx);
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
} else {
|
} else {
|
||||||
f(true, undefined, tx);
|
f(true, undefined, tx);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -464,7 +506,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
|
|||||||
this.storeName = storeName;
|
this.storeName = storeName;
|
||||||
this.subscribers = [];
|
this.subscribers = [];
|
||||||
|
|
||||||
let doIt = (tx: IDBTransaction) => {
|
const doIt = (tx: IDBTransaction) => {
|
||||||
const {indexName = void 0, only = void 0} = this.options;
|
const {indexName = void 0, only = void 0} = this.options;
|
||||||
let s: any;
|
let s: any;
|
||||||
if (indexName !== void 0) {
|
if (indexName !== void 0) {
|
||||||
@ -473,24 +515,24 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
|
|||||||
} else {
|
} else {
|
||||||
s = tx.objectStore(this.storeName);
|
s = tx.objectStore(this.storeName);
|
||||||
}
|
}
|
||||||
let kr: IDBKeyRange | undefined = undefined;
|
let kr: IDBKeyRange | undefined;
|
||||||
if (only !== undefined) {
|
if (only !== undefined) {
|
||||||
kr = IDBKeyRange.only(this.options.only);
|
kr = IDBKeyRange.only(this.options.only);
|
||||||
}
|
}
|
||||||
let req = s.openCursor(kr);
|
const req = s.openCursor(kr);
|
||||||
req.onsuccess = () => {
|
req.onsuccess = () => {
|
||||||
let cursor: IDBCursorWithValue = req.result;
|
const cursor: IDBCursorWithValue = req.result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
for (let f of this.subscribers) {
|
for (const f of this.subscribers) {
|
||||||
f(false, cursor.value, tx);
|
f(false, cursor.value, tx);
|
||||||
}
|
}
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
} else {
|
} else {
|
||||||
for (let f of this.subscribers) {
|
for (const f of this.subscribers) {
|
||||||
f(true, undefined, tx);
|
f(true, undefined, tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
this.root.addWork(doIt);
|
this.root.addWork(doIt);
|
||||||
@ -503,8 +545,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
|
|||||||
|
|
||||||
|
|
||||||
export class QueryRoot implements PromiseLike<void> {
|
export class QueryRoot implements PromiseLike<void> {
|
||||||
private work: ((t: IDBTransaction) => void)[] = [];
|
private work: Array<((t: IDBTransaction) => void)> = [];
|
||||||
db: IDBDatabase;
|
|
||||||
private stores = new Set();
|
private stores = new Set();
|
||||||
private kickoffPromise: Promise<void>;
|
private kickoffPromise: Promise<void>;
|
||||||
|
|
||||||
@ -518,20 +559,23 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
|
|
||||||
private finished: boolean = false;
|
private finished: boolean = false;
|
||||||
|
|
||||||
constructor(db: IDBDatabase) {
|
constructor(public db: IDBDatabase) {
|
||||||
this.db = db;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
|
then<R>(onfulfilled: (value: void) => R | PromiseLike<R>,
|
||||||
|
onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
|
||||||
return this.finish().then(onfulfilled, onrejected);
|
return this.finish().then(onfulfilled, onrejected);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkFinished() {
|
private checkFinished() {
|
||||||
if (this.finished) {
|
if (this.finished) {
|
||||||
throw Error("Can't add work to query after it was started");
|
throw Error("Can't add work to query after it was started");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a stream of all objects in the store.
|
||||||
|
*/
|
||||||
iter<T>(store: Store<T>): QueryStream<T> {
|
iter<T>(store: Store<T>): QueryStream<T> {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
this.stores.add(store.name);
|
this.stores.add(store.name);
|
||||||
@ -539,6 +583,9 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
return new IterQueryStream<T>(this, store.name, {});
|
return new IterQueryStream<T>(this, store.name, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of objects in a store.
|
||||||
|
*/
|
||||||
count<T>(store: Store<T>): Promise<number> {
|
count<T>(store: Store<T>): Promise<number> {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
const {resolve, promise} = openPromise();
|
const {resolve, promise} = openPromise();
|
||||||
@ -549,7 +596,7 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
req.onsuccess = () => {
|
req.onsuccess = () => {
|
||||||
resolve(req.result);
|
resolve(req.result);
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
this.addWork(doCount, store.name, false);
|
this.addWork(doCount, store.name, false);
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
@ -558,6 +605,9 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all objects in a store that match a predicate.
|
||||||
|
*/
|
||||||
deleteIf<T>(store: Store<T>, predicate: (x: T, n: number) => boolean): QueryRoot {
|
deleteIf<T>(store: Store<T>, predicate: (x: T, n: number) => boolean): QueryRoot {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
const doDeleteIf = (tx: IDBTransaction) => {
|
const doDeleteIf = (tx: IDBTransaction) => {
|
||||||
@ -565,27 +615,27 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
const req = s.openCursor();
|
const req = s.openCursor();
|
||||||
let n = 0;
|
let n = 0;
|
||||||
req.onsuccess = () => {
|
req.onsuccess = () => {
|
||||||
let cursor: IDBCursorWithValue = req.result;
|
const cursor: IDBCursorWithValue = req.result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
if (predicate(cursor.value, n++)) {
|
if (predicate(cursor.value, n++)) {
|
||||||
cursor.delete();
|
cursor.delete();
|
||||||
}
|
}
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
this.addWork(doDeleteIf, store.name, true);
|
this.addWork(doDeleteIf, store.name, true);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
iterIndex<S extends IDBValidKey,T>(index: Index<S,T>,
|
iterIndex<S extends IDBValidKey, T>(index: Index<S, T>,
|
||||||
only?: S): QueryStream<T> {
|
only?: S): QueryStream<T> {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
this.stores.add(index.storeName);
|
this.stores.add(index.storeName);
|
||||||
this.scheduleFinish();
|
this.scheduleFinish();
|
||||||
return new IterQueryStream<T>(this, index.storeName, {
|
return new IterQueryStream<T>(this, index.storeName, {
|
||||||
|
indexName: index.indexName,
|
||||||
only,
|
only,
|
||||||
indexName: index.indexName
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,7 +646,7 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
*/
|
*/
|
||||||
put<T>(store: Store<T>, val: T): QueryRoot {
|
put<T>(store: Store<T>, val: T): QueryRoot {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
let doPut = (tx: IDBTransaction) => {
|
const doPut = (tx: IDBTransaction) => {
|
||||||
tx.objectStore(store.name).put(val);
|
tx.objectStore(store.name).put(val);
|
||||||
};
|
};
|
||||||
this.scheduleFinish();
|
this.scheduleFinish();
|
||||||
@ -608,11 +658,11 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
putWithResult<T>(store: Store<T>, val: T): Promise<IDBValidKey> {
|
putWithResult<T>(store: Store<T>, val: T): Promise<IDBValidKey> {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
const {resolve, promise} = openPromise();
|
const {resolve, promise} = openPromise();
|
||||||
let doPutWithResult = (tx: IDBTransaction) => {
|
const doPutWithResult = (tx: IDBTransaction) => {
|
||||||
let req = tx.objectStore(store.name).put(val);
|
const req = tx.objectStore(store.name).put(val);
|
||||||
req.onsuccess = () => {
|
req.onsuccess = () => {
|
||||||
resolve(req.result);
|
resolve(req.result);
|
||||||
}
|
};
|
||||||
this.scheduleFinish();
|
this.scheduleFinish();
|
||||||
};
|
};
|
||||||
this.addWork(doPutWithResult, store.name, true);
|
this.addWork(doPutWithResult, store.name, true);
|
||||||
@ -622,17 +672,20 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get, modify and store an element inside a transaction.
|
||||||
|
*/
|
||||||
mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot {
|
mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
let doPut = (tx: IDBTransaction) => {
|
const doPut = (tx: IDBTransaction) => {
|
||||||
let reqGet = tx.objectStore(store.name).get(key);
|
const reqGet = tx.objectStore(store.name).get(key);
|
||||||
reqGet.onsuccess = () => {
|
reqGet.onsuccess = () => {
|
||||||
let r = reqGet.result;
|
const r = reqGet.result;
|
||||||
let m: T;
|
let m: T;
|
||||||
try {
|
try {
|
||||||
m = f(r);
|
m = f(r);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e == AbortTransaction) {
|
if (e === AbortTransaction) {
|
||||||
tx.abort();
|
tx.abort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -640,7 +693,7 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tx.objectStore(store.name).put(m);
|
tx.objectStore(store.name).put(m);
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
this.scheduleFinish();
|
this.scheduleFinish();
|
||||||
this.addWork(doPut, store.name, true);
|
this.addWork(doPut, store.name, true);
|
||||||
@ -656,7 +709,7 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
putAll<T>(store: Store<T>, iterable: T[]): QueryRoot {
|
putAll<T>(store: Store<T>, iterable: T[]): QueryRoot {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
const doPutAll = (tx: IDBTransaction) => {
|
const doPutAll = (tx: IDBTransaction) => {
|
||||||
for (let obj of iterable) {
|
for (const obj of iterable) {
|
||||||
tx.objectStore(store.name).put(obj);
|
tx.objectStore(store.name).put(obj);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -707,7 +760,7 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
/**
|
/**
|
||||||
* Get one object from a store by its key.
|
* Get one object from a store by its key.
|
||||||
*/
|
*/
|
||||||
getIndexed<I extends IDBValidKey,T>(index: Index<I,T>,
|
getIndexed<I extends IDBValidKey, T>(index: Index<I, T>,
|
||||||
key: I): Promise<T|undefined> {
|
key: I): Promise<T|undefined> {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
if (key === void 0) {
|
if (key === void 0) {
|
||||||
@ -748,7 +801,7 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
this.kickoffPromise = new Promise<void>((resolve, reject) => {
|
this.kickoffPromise = new Promise<void>((resolve, reject) => {
|
||||||
// At this point, we can't add any more work
|
// At this point, we can't add any more work
|
||||||
this.finished = true;
|
this.finished = true;
|
||||||
if (this.work.length == 0) {
|
if (this.work.length === 0) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -760,7 +813,7 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
tx.onabort = () => {
|
tx.onabort = () => {
|
||||||
reject(Error("transaction aborted"));
|
reject(Error("transaction aborted"));
|
||||||
};
|
};
|
||||||
for (let w of this.work) {
|
for (const w of this.work) {
|
||||||
w(tx);
|
w(tx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -54,7 +54,7 @@ export let performanceNow = (() => {
|
|||||||
return () => {
|
return () => {
|
||||||
const t = process.hrtime();
|
const t = process.hrtime();
|
||||||
return t[0] * 1e9 + t[1];
|
return t[0] * 1e9 + t[1];
|
||||||
}
|
};
|
||||||
} else if (typeof "performance" !== "undefined") {
|
} else if (typeof "performance" !== "undefined") {
|
||||||
return () => performance.now();
|
return () => performance.now();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,35 +1,51 @@
|
|||||||
|
/*
|
||||||
|
This file is part of TALER
|
||||||
|
(C) 2017 Inria and GNUnet e.V.
|
||||||
|
|
||||||
|
TALER is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
import {test} from "ava";
|
import {test} from "ava";
|
||||||
import {Amounts} from "./types";
|
import {Amounts} from "./types";
|
||||||
import * as types from "./types";
|
import * as types from "./types";
|
||||||
|
|
||||||
let amt = (value: number, fraction: number, currency: string): types.AmountJson => ({value, fraction, currency});
|
const amt = (value: number, fraction: number, currency: string): types.AmountJson => ({value, fraction, currency});
|
||||||
|
|
||||||
test("amount addition (simple)", t => {
|
test("amount addition (simple)", (t) => {
|
||||||
let a1 = amt(1,0,"EUR");
|
const a1 = amt(1, 0, "EUR");
|
||||||
let a2 = amt(1,0,"EUR");
|
const a2 = amt(1, 0, "EUR");
|
||||||
let a3 = amt(2,0,"EUR");
|
const a3 = amt(2, 0, "EUR");
|
||||||
t.true(0 == types.Amounts.cmp(Amounts.add(a1, a2).amount, a3));
|
t.true(0 === types.Amounts.cmp(Amounts.add(a1, a2).amount, a3));
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("amount addition (saturation)", t => {
|
test("amount addition (saturation)", (t) => {
|
||||||
let a1 = amt(1,0,"EUR");
|
const a1 = amt(1, 0, "EUR");
|
||||||
let res = Amounts.add(Amounts.getMaxAmount("EUR"), a1);
|
const res = Amounts.add(Amounts.getMaxAmount("EUR"), a1);
|
||||||
t.true(res.saturated);
|
t.true(res.saturated);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("amount subtraction (simple)", t => {
|
test("amount subtraction (simple)", (t) => {
|
||||||
let a1 = amt(2,5,"EUR");
|
const a1 = amt(2, 5, "EUR");
|
||||||
let a2 = amt(1,0,"EUR");
|
const a2 = amt(1, 0, "EUR");
|
||||||
let a3 = amt(1,5,"EUR");
|
const a3 = amt(1, 5, "EUR");
|
||||||
t.true(0 == types.Amounts.cmp(Amounts.sub(a1, a2).amount, a3));
|
t.true(0 === types.Amounts.cmp(Amounts.sub(a1, a2).amount, a3));
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("amount subtraction (saturation)", t => {
|
test("amount subtraction (saturation)", (t) => {
|
||||||
let a1 = amt(0,0,"EUR");
|
const a1 = amt(0, 0, "EUR");
|
||||||
let a2 = amt(1,0,"EUR");
|
const a2 = amt(1, 0, "EUR");
|
||||||
let res = Amounts.sub(a1, a2);
|
let res = Amounts.sub(a1, a2);
|
||||||
t.true(res.saturated);
|
t.true(res.saturated);
|
||||||
res = Amounts.sub(a1, a1);
|
res = Amounts.sub(a1, a1);
|
||||||
@ -38,29 +54,29 @@ test("amount subtraction (saturation)", t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("contract validation", t => {
|
test("contract validation", (t) => {
|
||||||
let c = {
|
const c = {
|
||||||
H_wire: "123",
|
H_wire: "123",
|
||||||
summary: "hello",
|
amount: amt(1, 2, "EUR"),
|
||||||
amount: amt(1,2,"EUR"),
|
|
||||||
auditors: [],
|
auditors: [],
|
||||||
pay_deadline: "Date(12346)",
|
|
||||||
max_fee: amt(1,2,"EUR"),
|
|
||||||
merchant_pub: "12345",
|
|
||||||
exchanges: [{master_pub: "foo", url: "foo"}],
|
exchanges: [{master_pub: "foo", url: "foo"}],
|
||||||
|
fulfillment_url: "foo",
|
||||||
|
max_fee: amt(1, 2, "EUR"),
|
||||||
|
merchant_pub: "12345",
|
||||||
|
order_id: "test_order",
|
||||||
|
pay_deadline: "Date(12346)",
|
||||||
|
pay_url: "https://example.com/pay",
|
||||||
products: [],
|
products: [],
|
||||||
refund_deadline: "Date(12345)",
|
refund_deadline: "Date(12345)",
|
||||||
|
summary: "hello",
|
||||||
timestamp: "Date(12345)",
|
timestamp: "Date(12345)",
|
||||||
fulfillment_url: "foo",
|
|
||||||
wire_method: "test",
|
wire_method: "test",
|
||||||
order_id: "test_order",
|
|
||||||
pay_url: "https://example.com/pay",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
types.Contract.checked(c);
|
types.Contract.checked(c);
|
||||||
|
|
||||||
let c1 = JSON.parse(JSON.stringify(c));
|
const c1 = JSON.parse(JSON.stringify(c));
|
||||||
c1.exchanges = []
|
c1.exchanges = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
types.Contract.checked(c1);
|
types.Contract.checked(c1);
|
||||||
@ -70,5 +86,4 @@ test("contract validation", t => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.fail();
|
t.fail();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
66
src/types.ts
66
src/types.ts
@ -51,20 +51,20 @@ export interface SignedAmountJson {
|
|||||||
|
|
||||||
export interface ReserveRecord {
|
export interface ReserveRecord {
|
||||||
reserve_pub: string;
|
reserve_pub: string;
|
||||||
reserve_priv: string,
|
reserve_priv: string;
|
||||||
exchange_base_url: string,
|
exchange_base_url: string;
|
||||||
created: number,
|
created: number;
|
||||||
last_query: number | null,
|
last_query: number | null;
|
||||||
/**
|
/**
|
||||||
* Current amount left in the reserve
|
* Current amount left in the reserve
|
||||||
*/
|
*/
|
||||||
current_amount: AmountJson | null,
|
current_amount: AmountJson | null;
|
||||||
/**
|
/**
|
||||||
* Amount requested when the reserve was created.
|
* Amount requested when the reserve was created.
|
||||||
* When a reserve is re-used (rare!) the current_amount can
|
* When a reserve is re-used (rare!) the current_amount can
|
||||||
* be higher than the requested_amount
|
* be higher than the requested_amount
|
||||||
*/
|
*/
|
||||||
requested_amount: AmountJson,
|
requested_amount: AmountJson;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -360,7 +360,7 @@ export interface RefreshSessionRecord {
|
|||||||
* How much of the coin's value is melted away
|
* How much of the coin's value is melted away
|
||||||
* with this refresh session?
|
* with this refresh session?
|
||||||
*/
|
*/
|
||||||
valueWithFee: AmountJson
|
valueWithFee: AmountJson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sum of the value of denominations we want
|
* Sum of the value of denominations we want
|
||||||
@ -468,7 +468,7 @@ export interface CoinRecord {
|
|||||||
* Reserve public key for the reserve we got this coin from,
|
* Reserve public key for the reserve we got this coin from,
|
||||||
* or zero when we got the coin from refresh.
|
* or zero when we got the coin from refresh.
|
||||||
*/
|
*/
|
||||||
reservePub: string|undefined,
|
reservePub: string|undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of the coin.
|
* Status of the coin.
|
||||||
@ -528,7 +528,7 @@ interface Merchant {
|
|||||||
export class Contract {
|
export class Contract {
|
||||||
|
|
||||||
validate() {
|
validate() {
|
||||||
if (this.exchanges.length == 0) {
|
if (this.exchanges.length === 0) {
|
||||||
throw Error("no exchanges in contract");
|
throw Error("no exchanges in contract");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -629,27 +629,27 @@ export namespace Amounts {
|
|||||||
export function getMaxAmount(currency: string): AmountJson {
|
export function getMaxAmount(currency: string): AmountJson {
|
||||||
return {
|
return {
|
||||||
currency,
|
currency,
|
||||||
value: Number.MAX_SAFE_INTEGER,
|
|
||||||
fraction: 2 ** 32,
|
fraction: 2 ** 32,
|
||||||
}
|
value: Number.MAX_SAFE_INTEGER,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getZero(currency: string): AmountJson {
|
export function getZero(currency: string): AmountJson {
|
||||||
return {
|
return {
|
||||||
currency,
|
currency,
|
||||||
value: 0,
|
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
}
|
value: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function add(first: AmountJson, ...rest: AmountJson[]): Result {
|
export function add(first: AmountJson, ...rest: AmountJson[]): Result {
|
||||||
let currency = first.currency;
|
const currency = first.currency;
|
||||||
let value = first.value + Math.floor(first.fraction / fractionalBase);
|
let value = first.value + Math.floor(first.fraction / fractionalBase);
|
||||||
if (value > Number.MAX_SAFE_INTEGER) {
|
if (value > Number.MAX_SAFE_INTEGER) {
|
||||||
return { amount: getMaxAmount(currency), saturated: true };
|
return { amount: getMaxAmount(currency), saturated: true };
|
||||||
}
|
}
|
||||||
let fraction = first.fraction % fractionalBase;
|
let fraction = first.fraction % fractionalBase;
|
||||||
for (let x of rest) {
|
for (const x of rest) {
|
||||||
if (x.currency !== currency) {
|
if (x.currency !== currency) {
|
||||||
throw Error(`Mismatched currency: ${x.currency} and ${currency}`);
|
throw Error(`Mismatched currency: ${x.currency} and ${currency}`);
|
||||||
}
|
}
|
||||||
@ -665,11 +665,11 @@ export namespace Amounts {
|
|||||||
|
|
||||||
|
|
||||||
export function sub(a: AmountJson, ...rest: AmountJson[]): Result {
|
export function sub(a: AmountJson, ...rest: AmountJson[]): Result {
|
||||||
let currency = a.currency;
|
const currency = a.currency;
|
||||||
let value = a.value;
|
let value = a.value;
|
||||||
let fraction = a.fraction;
|
let fraction = a.fraction;
|
||||||
|
|
||||||
for (let b of rest) {
|
for (const b of rest) {
|
||||||
if (b.currency !== currency) {
|
if (b.currency !== currency) {
|
||||||
throw Error(`Mismatched currency: ${b.currency} and ${currency}`);
|
throw Error(`Mismatched currency: ${b.currency} and ${currency}`);
|
||||||
}
|
}
|
||||||
@ -695,10 +695,10 @@ export namespace Amounts {
|
|||||||
if (a.currency !== b.currency) {
|
if (a.currency !== b.currency) {
|
||||||
throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
|
throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
|
||||||
}
|
}
|
||||||
let av = a.value + Math.floor(a.fraction / fractionalBase);
|
const av = a.value + Math.floor(a.fraction / fractionalBase);
|
||||||
let af = a.fraction % fractionalBase;
|
const af = a.fraction % fractionalBase;
|
||||||
let bv = b.value + Math.floor(b.fraction / fractionalBase);
|
const bv = b.value + Math.floor(b.fraction / fractionalBase);
|
||||||
let bf = b.fraction % fractionalBase;
|
const bf = b.fraction % fractionalBase;
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case av < bv:
|
case av < bv:
|
||||||
return -1;
|
return -1;
|
||||||
@ -708,7 +708,7 @@ export namespace Amounts {
|
|||||||
return -1;
|
return -1;
|
||||||
case af > bf:
|
case af > bf:
|
||||||
return 1;
|
return 1;
|
||||||
case af == bf:
|
case af === bf:
|
||||||
return 0;
|
return 0;
|
||||||
default:
|
default:
|
||||||
throw Error("assertion failed");
|
throw Error("assertion failed");
|
||||||
@ -717,25 +717,25 @@ export namespace Amounts {
|
|||||||
|
|
||||||
export function copy(a: AmountJson): AmountJson {
|
export function copy(a: AmountJson): AmountJson {
|
||||||
return {
|
return {
|
||||||
value: a.value,
|
|
||||||
fraction: a.fraction,
|
|
||||||
currency: a.currency,
|
currency: a.currency,
|
||||||
}
|
fraction: a.fraction,
|
||||||
|
value: a.value,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function divide(a: AmountJson, n: number): AmountJson {
|
export function divide(a: AmountJson, n: number): AmountJson {
|
||||||
if (n == 0) {
|
if (n === 0) {
|
||||||
throw Error(`Division by 0`);
|
throw Error(`Division by 0`);
|
||||||
}
|
}
|
||||||
if (n == 1) {
|
if (n === 1) {
|
||||||
return {value: a.value, fraction: a.fraction, currency: a.currency};
|
return {value: a.value, fraction: a.fraction, currency: a.currency};
|
||||||
}
|
}
|
||||||
let r = a.value % n;
|
const r = a.value % n;
|
||||||
return {
|
return {
|
||||||
currency: a.currency,
|
currency: a.currency,
|
||||||
value: Math.floor(a.value / n),
|
|
||||||
fraction: Math.floor(((r * fractionalBase) + a.fraction) / n),
|
fraction: Math.floor(((r * fractionalBase) + a.fraction) / n),
|
||||||
}
|
value: Math.floor(a.value / n),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNonZero(a: AmountJson) {
|
export function isNonZero(a: AmountJson) {
|
||||||
@ -746,15 +746,15 @@ export namespace Amounts {
|
|||||||
* Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct.
|
* Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct.
|
||||||
*/
|
*/
|
||||||
export function parse(s: string): AmountJson|undefined {
|
export function parse(s: string): AmountJson|undefined {
|
||||||
let res = s.match(/([a-zA-Z0-9_*-]+):([0-9])+([.][0-9]+)?/);
|
const res = s.match(/([a-zA-Z0-9_*-]+):([0-9])+([.][0-9]+)?/);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
currency: res[1],
|
currency: res[1],
|
||||||
value: Number.parseInt(res[2]),
|
|
||||||
fraction: Math.round(fractionalBase * Number.parseFloat(res[3] || "0")),
|
fraction: Math.round(fractionalBase * Number.parseFloat(res[3] || "0")),
|
||||||
}
|
value: Number.parseInt(res[2]),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,135 +1,152 @@
|
|||||||
|
/*
|
||||||
|
This file is part of TALER
|
||||||
|
(C) 2017 Inria and GNUnet e.V.
|
||||||
|
|
||||||
|
TALER is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {test} from "ava";
|
import {test} from "ava";
|
||||||
import * as types from "./types";
|
import * as types from "./types";
|
||||||
import * as wallet from "./wallet";
|
import * as wallet from "./wallet";
|
||||||
|
|
||||||
|
|
||||||
function a(x: string): types.AmountJson {
|
function a(x: string): types.AmountJson {
|
||||||
let amt = types.Amounts.parse(x);
|
const amt = types.Amounts.parse(x);
|
||||||
if (!amt) {
|
if (!amt) {
|
||||||
throw Error("invalid amount");
|
throw Error("invalid amount");
|
||||||
}
|
}
|
||||||
return amt;
|
return amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function fakeCwd(current: string, value: string, feeDeposit: string): wallet.CoinWithDenom {
|
function fakeCwd(current: string, value: string, feeDeposit: string): wallet.CoinWithDenom {
|
||||||
return {
|
return {
|
||||||
coin: {
|
coin: {
|
||||||
currentAmount: a(current),
|
blindingKey: "(mock)",
|
||||||
coinPub: "(mock)",
|
|
||||||
coinPriv: "(mock)",
|
coinPriv: "(mock)",
|
||||||
|
coinPub: "(mock)",
|
||||||
|
currentAmount: a(current),
|
||||||
denomPub: "(mock)",
|
denomPub: "(mock)",
|
||||||
denomSig: "(mock)",
|
denomSig: "(mock)",
|
||||||
exchangeBaseUrl: "(mock)",
|
exchangeBaseUrl: "(mock)",
|
||||||
blindingKey: "(mock)",
|
|
||||||
reservePub: "(mock)",
|
reservePub: "(mock)",
|
||||||
status: types.CoinStatus.Fresh,
|
status: types.CoinStatus.Fresh,
|
||||||
},
|
},
|
||||||
denom: {
|
denom: {
|
||||||
value: a(value),
|
|
||||||
feeDeposit: a(feeDeposit),
|
|
||||||
denomPub: "(mock)",
|
denomPub: "(mock)",
|
||||||
denomPubHash: "(mock)",
|
denomPubHash: "(mock)",
|
||||||
feeWithdraw: a("EUR:0.0"),
|
exchangeBaseUrl: "(mock)",
|
||||||
|
feeDeposit: a(feeDeposit),
|
||||||
feeRefresh: a("EUR:0.0"),
|
feeRefresh: a("EUR:0.0"),
|
||||||
feeRefund: a("EUR:0.0"),
|
feeRefund: a("EUR:0.0"),
|
||||||
stampStart: "(mock)",
|
feeWithdraw: a("EUR:0.0"),
|
||||||
stampExpireWithdraw: "(mock)",
|
|
||||||
stampExpireLegal: "(mock)",
|
|
||||||
stampExpireDeposit: "(mock)",
|
|
||||||
masterSig: "(mock)",
|
|
||||||
status: types.DenominationStatus.VerifiedGood,
|
|
||||||
isOffered: true,
|
isOffered: true,
|
||||||
exchangeBaseUrl: "(mock)",
|
masterSig: "(mock)",
|
||||||
|
stampExpireDeposit: "(mock)",
|
||||||
|
stampExpireLegal: "(mock)",
|
||||||
|
stampExpireWithdraw: "(mock)",
|
||||||
|
stampStart: "(mock)",
|
||||||
|
status: types.DenominationStatus.VerifiedGood,
|
||||||
|
value: a(value),
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test("coin selection 1", (t) => {
|
||||||
test("coin selection 1", t => {
|
const cds: wallet.CoinWithDenom[] = [
|
||||||
let cds: wallet.CoinWithDenom[] = [
|
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.1"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.1"),
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
|
||||||
];
|
];
|
||||||
|
|
||||||
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.1"));
|
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.1"));
|
||||||
if (!res) {
|
if (!res) {
|
||||||
t.fail();
|
t.fail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
t.true(res.length == 2);
|
t.true(res.length === 2);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("coin selection 2", t => {
|
test("coin selection 2", (t) => {
|
||||||
let cds: wallet.CoinWithDenom[] = [
|
const cds: wallet.CoinWithDenom[] = [
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
|
||||||
// Merchant covers the fee, this one shouldn't be used
|
// Merchant covers the fee, this one shouldn't be used
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
|
||||||
];
|
];
|
||||||
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
|
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
|
||||||
if (!res) {
|
if (!res) {
|
||||||
t.fail();
|
t.fail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
t.true(res.length == 2);
|
t.true(res.length === 2);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("coin selection 3", t => {
|
test("coin selection 3", (t) => {
|
||||||
let cds: wallet.CoinWithDenom[] = [
|
const cds: wallet.CoinWithDenom[] = [
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
// this coin should be selected instead of previous one with fee
|
// this coin should be selected instead of previous one with fee
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
|
||||||
];
|
];
|
||||||
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
|
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
|
||||||
if (!res) {
|
if (!res) {
|
||||||
t.fail();
|
t.fail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
t.true(res.length == 2);
|
t.true(res.length === 2);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("coin selection 4", (t) => {
|
||||||
test("coin selection 4", t => {
|
const cds: wallet.CoinWithDenom[] = [
|
||||||
let cds: wallet.CoinWithDenom[] = [
|
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
];
|
];
|
||||||
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
|
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
|
||||||
if (!res) {
|
if (!res) {
|
||||||
t.fail();
|
t.fail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
t.true(res.length == 3);
|
t.true(res.length === 3);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("coin selection 5", t => {
|
test("coin selection 5", (t) => {
|
||||||
let cds: wallet.CoinWithDenom[] = [
|
const cds: wallet.CoinWithDenom[] = [
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
];
|
];
|
||||||
let res = wallet.selectCoins(cds, a("EUR:4.0"), a("EUR:0.2"));
|
const res = wallet.selectCoins(cds, a("EUR:4.0"), a("EUR:0.2"));
|
||||||
t.true(!res);
|
t.true(!res);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("coin selection 6", t => {
|
test("coin selection 6", (t) => {
|
||||||
let cds: wallet.CoinWithDenom[] = [
|
const cds: wallet.CoinWithDenom[] = [
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
|
||||||
];
|
];
|
||||||
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
|
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
|
||||||
t.true(!res);
|
t.true(!res);
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
844
src/wallet.ts
844
src/wallet.ts
File diff suppressed because it is too large
Load Diff
11
src/wxApi.ts
11
src/wxApi.ts
@ -17,11 +17,12 @@
|
|||||||
import {
|
import {
|
||||||
AmountJson,
|
AmountJson,
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
|
CurrencyRecord,
|
||||||
|
DenominationRecord,
|
||||||
|
ExchangeRecord,
|
||||||
PreCoinRecord,
|
PreCoinRecord,
|
||||||
ReserveCreationInfo,
|
ReserveCreationInfo,
|
||||||
ExchangeRecord,
|
ReserveRecord,
|
||||||
CurrencyRecord,
|
|
||||||
ReserveRecord, DenominationRecord
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,12 +33,12 @@ import {
|
|||||||
|
|
||||||
export function getReserveCreationInfo(baseUrl: string,
|
export function getReserveCreationInfo(baseUrl: string,
|
||||||
amount: AmountJson): Promise<ReserveCreationInfo> {
|
amount: AmountJson): Promise<ReserveCreationInfo> {
|
||||||
let m = { type: "reserve-creation-info", detail: { baseUrl, amount } };
|
const m = { type: "reserve-creation-info", detail: { baseUrl, amount } };
|
||||||
return new Promise<ReserveCreationInfo>((resolve, reject) => {
|
return new Promise<ReserveCreationInfo>((resolve, reject) => {
|
||||||
chrome.runtime.sendMessage(m, (resp) => {
|
chrome.runtime.sendMessage(m, (resp) => {
|
||||||
if (resp.error) {
|
if (resp.error) {
|
||||||
console.error("error response", resp);
|
console.error("error response", resp);
|
||||||
let e = Error("call to reserve-creation-info failed");
|
const e = Error("call to reserve-creation-info failed");
|
||||||
(e as any).errorResponse = resp;
|
(e as any).errorResponse = resp;
|
||||||
reject(e);
|
reject(e);
|
||||||
return;
|
return;
|
||||||
|
239
src/wxBackend.ts
239
src/wxBackend.ts
@ -24,6 +24,20 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
|
import { Checkable } from "./checkable";
|
||||||
|
import { ChromeBadge } from "./chromeBadge";
|
||||||
|
import { BrowserHttpLib } from "./http";
|
||||||
|
import * as logging from "./logging";
|
||||||
|
import {
|
||||||
|
Index,
|
||||||
|
Store,
|
||||||
|
} from "./query";
|
||||||
|
import {
|
||||||
|
AmountJson,
|
||||||
|
Contract,
|
||||||
|
Notifier,
|
||||||
|
} from "./types";
|
||||||
|
import URI = require("urijs");
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
ConfirmReserveRequest,
|
ConfirmReserveRequest,
|
||||||
@ -32,19 +46,8 @@ import {
|
|||||||
Stores,
|
Stores,
|
||||||
Wallet,
|
Wallet,
|
||||||
} from "./wallet";
|
} from "./wallet";
|
||||||
import {
|
|
||||||
AmountJson,
|
|
||||||
Contract,
|
|
||||||
Notifier,
|
|
||||||
} from "./types";
|
|
||||||
import MessageSender = chrome.runtime.MessageSender;
|
|
||||||
import { ChromeBadge } from "./chromeBadge";
|
|
||||||
import * as logging from "./logging";
|
|
||||||
import { Store, Index } from "./query";
|
|
||||||
import { BrowserHttpLib } from "./http";
|
|
||||||
import { Checkable } from "./checkable";
|
|
||||||
import Port = chrome.runtime.Port;
|
import Port = chrome.runtime.Port;
|
||||||
import URI = require("urijs");
|
import MessageSender = chrome.runtime.MessageSender;
|
||||||
|
|
||||||
|
|
||||||
const DB_NAME = "taler";
|
const DB_NAME = "taler";
|
||||||
@ -62,30 +65,31 @@ type Handler = (detail: any, sender: MessageSender) => Promise<any>;
|
|||||||
function makeHandlers(db: IDBDatabase,
|
function makeHandlers(db: IDBDatabase,
|
||||||
wallet: Wallet): { [msg: string]: Handler } {
|
wallet: Wallet): { [msg: string]: Handler } {
|
||||||
return {
|
return {
|
||||||
["balances"]: function (detail, sender) {
|
["balances"]: (detail, sender) => {
|
||||||
return wallet.getBalances();
|
return wallet.getBalances();
|
||||||
},
|
},
|
||||||
["dump-db"]: function (detail, sender) {
|
["dump-db"]: (detail, sender) => {
|
||||||
return exportDb(db);
|
return exportDb(db);
|
||||||
},
|
},
|
||||||
["import-db"]: function (detail, sender) {
|
["import-db"]: (detail, sender) => {
|
||||||
return importDb(db, detail.dump);
|
return importDb(db, detail.dump);
|
||||||
},
|
},
|
||||||
["get-tab-cookie"]: function (detail, sender) {
|
["get-tab-cookie"]: (detail, sender) => {
|
||||||
if (!sender || !sender.tab || !sender.tab.id) {
|
if (!sender || !sender.tab || !sender.tab.id) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
let id: number = sender.tab.id;
|
const id: number = sender.tab.id;
|
||||||
let info: any = <any> paymentRequestCookies[id];
|
const info: any = paymentRequestCookies[id] as any;
|
||||||
delete paymentRequestCookies[id];
|
delete paymentRequestCookies[id];
|
||||||
return Promise.resolve(info);
|
return Promise.resolve(info);
|
||||||
},
|
},
|
||||||
["ping"]: function (detail, sender) {
|
["ping"]: (detail, sender) => {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
["reset"]: function (detail, sender) {
|
["reset"]: (detail, sender) => {
|
||||||
if (db) {
|
if (db) {
|
||||||
let tx = db.transaction(Array.from(db.objectStoreNames), 'readwrite');
|
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
|
||||||
|
// tslint:disable-next-line:prefer-for-of
|
||||||
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
||||||
tx.objectStore(db.objectStoreNames[i]).clear();
|
tx.objectStore(db.objectStoreNames[i]).clear();
|
||||||
}
|
}
|
||||||
@ -97,15 +101,15 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
// Response is synchronous
|
// Response is synchronous
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
},
|
},
|
||||||
["create-reserve"]: function (detail, sender) {
|
["create-reserve"]: (detail, sender) => {
|
||||||
const d = {
|
const d = {
|
||||||
exchange: detail.exchange,
|
|
||||||
amount: detail.amount,
|
amount: detail.amount,
|
||||||
|
exchange: detail.exchange,
|
||||||
};
|
};
|
||||||
const req = CreateReserveRequest.checked(d);
|
const req = CreateReserveRequest.checked(d);
|
||||||
return wallet.createReserve(req);
|
return wallet.createReserve(req);
|
||||||
},
|
},
|
||||||
["confirm-reserve"]: function (detail, sender) {
|
["confirm-reserve"]: (detail, sender) => {
|
||||||
// TODO: make it a checkable
|
// TODO: make it a checkable
|
||||||
const d = {
|
const d = {
|
||||||
reservePub: detail.reservePub,
|
reservePub: detail.reservePub,
|
||||||
@ -113,10 +117,10 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
const req = ConfirmReserveRequest.checked(d);
|
const req = ConfirmReserveRequest.checked(d);
|
||||||
return wallet.confirmReserve(req);
|
return wallet.confirmReserve(req);
|
||||||
},
|
},
|
||||||
["generate-nonce"]: function (detail, sender) {
|
["generate-nonce"]: (detail, sender) => {
|
||||||
return wallet.generateNonce();
|
return wallet.generateNonce();
|
||||||
},
|
},
|
||||||
["confirm-pay"]: function (detail, sender) {
|
["confirm-pay"]: (detail, sender) => {
|
||||||
let offer: OfferRecord;
|
let offer: OfferRecord;
|
||||||
try {
|
try {
|
||||||
offer = OfferRecord.checked(detail.offer);
|
offer = OfferRecord.checked(detail.offer);
|
||||||
@ -124,9 +128,9 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
if (e instanceof Checkable.SchemaError) {
|
if (e instanceof Checkable.SchemaError) {
|
||||||
console.error("schema error:", e.message);
|
console.error("schema error:", e.message);
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
detail,
|
||||||
error: "invalid contract",
|
error: "invalid contract",
|
||||||
hint: e.message,
|
hint: e.message,
|
||||||
detail: detail,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
@ -135,7 +139,7 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
|
|
||||||
return wallet.confirmPay(offer);
|
return wallet.confirmPay(offer);
|
||||||
},
|
},
|
||||||
["check-pay"]: function (detail, sender) {
|
["check-pay"]: (detail, sender) => {
|
||||||
let offer: OfferRecord;
|
let offer: OfferRecord;
|
||||||
try {
|
try {
|
||||||
offer = OfferRecord.checked(detail.offer);
|
offer = OfferRecord.checked(detail.offer);
|
||||||
@ -143,9 +147,9 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
if (e instanceof Checkable.SchemaError) {
|
if (e instanceof Checkable.SchemaError) {
|
||||||
console.error("schema error:", e.message);
|
console.error("schema error:", e.message);
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
detail,
|
||||||
error: "invalid contract",
|
error: "invalid contract",
|
||||||
hint: e.message,
|
hint: e.message,
|
||||||
detail: detail,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
@ -153,34 +157,34 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
}
|
}
|
||||||
return wallet.checkPay(offer);
|
return wallet.checkPay(offer);
|
||||||
},
|
},
|
||||||
["query-payment"]: function (detail: any, sender: MessageSender) {
|
["query-payment"]: (detail: any, sender: MessageSender) => {
|
||||||
if (sender.tab && sender.tab.id) {
|
if (sender.tab && sender.tab.id) {
|
||||||
rateLimitCache[sender.tab.id]++;
|
rateLimitCache[sender.tab.id]++;
|
||||||
if (rateLimitCache[sender.tab.id] > 10) {
|
if (rateLimitCache[sender.tab.id] > 10) {
|
||||||
console.warn("rate limit for query-payment exceeded");
|
console.warn("rate limit for query-payment exceeded");
|
||||||
let msg = {
|
const msg = {
|
||||||
error: "rate limit exceeded for query-payment",
|
error: "rate limit exceeded for query-payment",
|
||||||
rateLimitExceeded: true,
|
|
||||||
hint: "Check for redirect loops",
|
hint: "Check for redirect loops",
|
||||||
|
rateLimitExceeded: true,
|
||||||
};
|
};
|
||||||
return Promise.resolve(msg);
|
return Promise.resolve(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return wallet.queryPayment(detail.url);
|
return wallet.queryPayment(detail.url);
|
||||||
},
|
},
|
||||||
["exchange-info"]: function (detail) {
|
["exchange-info"]: (detail) => {
|
||||||
if (!detail.baseUrl) {
|
if (!detail.baseUrl) {
|
||||||
return Promise.resolve({ error: "bad url" });
|
return Promise.resolve({ error: "bad url" });
|
||||||
}
|
}
|
||||||
return wallet.updateExchangeFromUrl(detail.baseUrl);
|
return wallet.updateExchangeFromUrl(detail.baseUrl);
|
||||||
},
|
},
|
||||||
["currency-info"]: function (detail) {
|
["currency-info"]: (detail) => {
|
||||||
if (!detail.name) {
|
if (!detail.name) {
|
||||||
return Promise.resolve({ error: "name missing" });
|
return Promise.resolve({ error: "name missing" });
|
||||||
}
|
}
|
||||||
return wallet.getCurrencyRecord(detail.name);
|
return wallet.getCurrencyRecord(detail.name);
|
||||||
},
|
},
|
||||||
["hash-contract"]: function (detail) {
|
["hash-contract"]: (detail) => {
|
||||||
if (!detail.contract) {
|
if (!detail.contract) {
|
||||||
return Promise.resolve({ error: "contract missing" });
|
return Promise.resolve({ error: "contract missing" });
|
||||||
}
|
}
|
||||||
@ -188,91 +192,91 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
return { hash };
|
return { hash };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
["put-history-entry"]: function (detail: any) {
|
["put-history-entry"]: (detail: any) => {
|
||||||
if (!detail.historyEntry) {
|
if (!detail.historyEntry) {
|
||||||
return Promise.resolve({ error: "historyEntry missing" });
|
return Promise.resolve({ error: "historyEntry missing" });
|
||||||
}
|
}
|
||||||
return wallet.putHistory(detail.historyEntry);
|
return wallet.putHistory(detail.historyEntry);
|
||||||
},
|
},
|
||||||
["save-offer"]: function (detail: any) {
|
["save-offer"]: (detail: any) => {
|
||||||
let offer = detail.offer;
|
const offer = detail.offer;
|
||||||
if (!offer) {
|
if (!offer) {
|
||||||
return Promise.resolve({ error: "offer missing" });
|
return Promise.resolve({ error: "offer missing" });
|
||||||
}
|
}
|
||||||
console.log("handling safe-offer", detail);
|
console.log("handling safe-offer", detail);
|
||||||
// FIXME: fully migrate to new terminology
|
// FIXME: fully migrate to new terminology
|
||||||
let checkedOffer = OfferRecord.checked(offer);
|
const checkedOffer = OfferRecord.checked(offer);
|
||||||
return wallet.saveOffer(checkedOffer);
|
return wallet.saveOffer(checkedOffer);
|
||||||
},
|
},
|
||||||
["reserve-creation-info"]: function (detail, sender) {
|
["reserve-creation-info"]: (detail, sender) => {
|
||||||
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
|
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
|
||||||
return Promise.resolve({ error: "bad url" });
|
return Promise.resolve({ error: "bad url" });
|
||||||
}
|
}
|
||||||
let amount = AmountJson.checked(detail.amount);
|
const amount = AmountJson.checked(detail.amount);
|
||||||
return wallet.getReserveCreationInfo(detail.baseUrl, amount);
|
return wallet.getReserveCreationInfo(detail.baseUrl, amount);
|
||||||
},
|
},
|
||||||
["get-history"]: function (detail, sender) {
|
["get-history"]: (detail, sender) => {
|
||||||
// TODO: limit history length
|
// TODO: limit history length
|
||||||
return wallet.getHistory();
|
return wallet.getHistory();
|
||||||
},
|
},
|
||||||
["get-offer"]: function (detail, sender) {
|
["get-offer"]: (detail, sender) => {
|
||||||
return wallet.getOffer(detail.offerId);
|
return wallet.getOffer(detail.offerId);
|
||||||
},
|
},
|
||||||
["get-exchanges"]: function (detail, sender) {
|
["get-exchanges"]: (detail, sender) => {
|
||||||
return wallet.getExchanges();
|
return wallet.getExchanges();
|
||||||
},
|
},
|
||||||
["get-currencies"]: function (detail, sender) {
|
["get-currencies"]: (detail, sender) => {
|
||||||
return wallet.getCurrencies();
|
return wallet.getCurrencies();
|
||||||
},
|
},
|
||||||
["update-currency"]: function (detail, sender) {
|
["update-currency"]: (detail, sender) => {
|
||||||
return wallet.updateCurrency(detail.currencyRecord);
|
return wallet.updateCurrency(detail.currencyRecord);
|
||||||
},
|
},
|
||||||
["get-reserves"]: function (detail, sender) {
|
["get-reserves"]: (detail, sender) => {
|
||||||
if (typeof detail.exchangeBaseUrl !== "string") {
|
if (typeof detail.exchangeBaseUrl !== "string") {
|
||||||
return Promise.reject(Error("exchangeBaseUrl missing"));
|
return Promise.reject(Error("exchangeBaseUrl missing"));
|
||||||
}
|
}
|
||||||
return wallet.getReserves(detail.exchangeBaseUrl);
|
return wallet.getReserves(detail.exchangeBaseUrl);
|
||||||
},
|
},
|
||||||
["get-payback-reserves"]: function (detail, sender) {
|
["get-payback-reserves"]: (detail, sender) => {
|
||||||
return wallet.getPaybackReserves();
|
return wallet.getPaybackReserves();
|
||||||
},
|
},
|
||||||
["withdraw-payback-reserve"]: function (detail, sender) {
|
["withdraw-payback-reserve"]: (detail, sender) => {
|
||||||
if (typeof detail.reservePub !== "string") {
|
if (typeof detail.reservePub !== "string") {
|
||||||
return Promise.reject(Error("reservePub missing"));
|
return Promise.reject(Error("reservePub missing"));
|
||||||
}
|
}
|
||||||
return wallet.withdrawPaybackReserve(detail.reservePub);
|
return wallet.withdrawPaybackReserve(detail.reservePub);
|
||||||
},
|
},
|
||||||
["get-coins"]: function (detail, sender) {
|
["get-coins"]: (detail, sender) => {
|
||||||
if (typeof detail.exchangeBaseUrl !== "string") {
|
if (typeof detail.exchangeBaseUrl !== "string") {
|
||||||
return Promise.reject(Error("exchangBaseUrl missing"));
|
return Promise.reject(Error("exchangBaseUrl missing"));
|
||||||
}
|
}
|
||||||
return wallet.getCoins(detail.exchangeBaseUrl);
|
return wallet.getCoins(detail.exchangeBaseUrl);
|
||||||
},
|
},
|
||||||
["get-precoins"]: function (detail, sender) {
|
["get-precoins"]: (detail, sender) => {
|
||||||
if (typeof detail.exchangeBaseUrl !== "string") {
|
if (typeof detail.exchangeBaseUrl !== "string") {
|
||||||
return Promise.reject(Error("exchangBaseUrl missing"));
|
return Promise.reject(Error("exchangBaseUrl missing"));
|
||||||
}
|
}
|
||||||
return wallet.getPreCoins(detail.exchangeBaseUrl);
|
return wallet.getPreCoins(detail.exchangeBaseUrl);
|
||||||
},
|
},
|
||||||
["get-denoms"]: function (detail, sender) {
|
["get-denoms"]: (detail, sender) => {
|
||||||
if (typeof detail.exchangeBaseUrl !== "string") {
|
if (typeof detail.exchangeBaseUrl !== "string") {
|
||||||
return Promise.reject(Error("exchangBaseUrl missing"));
|
return Promise.reject(Error("exchangBaseUrl missing"));
|
||||||
}
|
}
|
||||||
return wallet.getDenoms(detail.exchangeBaseUrl);
|
return wallet.getDenoms(detail.exchangeBaseUrl);
|
||||||
},
|
},
|
||||||
["refresh-coin"]: function (detail, sender) {
|
["refresh-coin"]: (detail, sender) => {
|
||||||
if (typeof detail.coinPub !== "string") {
|
if (typeof detail.coinPub !== "string") {
|
||||||
return Promise.reject(Error("coinPub missing"));
|
return Promise.reject(Error("coinPub missing"));
|
||||||
}
|
}
|
||||||
return wallet.refresh(detail.coinPub);
|
return wallet.refresh(detail.coinPub);
|
||||||
},
|
},
|
||||||
["payback-coin"]: function (detail, sender) {
|
["payback-coin"]: (detail, sender) => {
|
||||||
if (typeof detail.coinPub !== "string") {
|
if (typeof detail.coinPub !== "string") {
|
||||||
return Promise.reject(Error("coinPub missing"));
|
return Promise.reject(Error("coinPub missing"));
|
||||||
}
|
}
|
||||||
return wallet.payback(detail.coinPub);
|
return wallet.payback(detail.coinPub);
|
||||||
},
|
},
|
||||||
["payment-failed"]: function (detail, sender) {
|
["payment-failed"]: (detail, sender) => {
|
||||||
// For now we just update exchanges (maybe the exchange did something
|
// For now we just update exchanges (maybe the exchange did something
|
||||||
// wrong and the keys were messed up).
|
// wrong and the keys were messed up).
|
||||||
// FIXME: in the future we should look at what actually went wrong.
|
// FIXME: in the future we should look at what actually went wrong.
|
||||||
@ -280,9 +284,9 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
wallet.updateExchanges();
|
wallet.updateExchanges();
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
["payment-succeeded"]: function (detail, sender) {
|
["payment-succeeded"]: (detail, sender) => {
|
||||||
let contractHash = detail.contractHash;
|
const contractHash = detail.contractHash;
|
||||||
let merchantSig = detail.merchantSig;
|
const merchantSig = detail.merchantSig;
|
||||||
if (!contractHash) {
|
if (!contractHash) {
|
||||||
return Promise.reject(Error("contractHash missing"));
|
return Promise.reject(Error("contractHash missing"));
|
||||||
}
|
}
|
||||||
@ -307,7 +311,7 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const p = handlers[req.type](req.detail, sender);
|
const p = handlers[req.type](req.detail, sender);
|
||||||
let r = await p;
|
const r = await p;
|
||||||
try {
|
try {
|
||||||
sendResponse(r);
|
sendResponse(r);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -317,7 +321,7 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any)
|
|||||||
console.log(`exception during wallet handler for '${req.type}'`);
|
console.log(`exception during wallet handler for '${req.type}'`);
|
||||||
console.log("request", req);
|
console.log("request", req);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
let stack = undefined;
|
let stack;
|
||||||
try {
|
try {
|
||||||
stack = e.stack.toString();
|
stack = e.stack.toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -325,9 +329,9 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any)
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
sendResponse({
|
sendResponse({
|
||||||
stack,
|
|
||||||
error: "exception",
|
error: "exception",
|
||||||
hint: e.message,
|
hint: e.message,
|
||||||
|
stack,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
@ -344,7 +348,7 @@ class ChromeNotifier implements Notifier {
|
|||||||
console.log("got connect!");
|
console.log("got connect!");
|
||||||
this.ports.push(port);
|
this.ports.push(port);
|
||||||
port.onDisconnect.addListener(() => {
|
port.onDisconnect.addListener(() => {
|
||||||
let i = this.ports.indexOf(port);
|
const i = this.ports.indexOf(port);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
this.ports.splice(i, 1);
|
this.ports.splice(i, 1);
|
||||||
} else {
|
} else {
|
||||||
@ -355,7 +359,7 @@ class ChromeNotifier implements Notifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notify() {
|
notify() {
|
||||||
for (let p of this.ports) {
|
for (const p of this.ports) {
|
||||||
p.postMessage({ notify: true });
|
p.postMessage({ notify: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,7 +369,7 @@ class ChromeNotifier implements Notifier {
|
|||||||
/**
|
/**
|
||||||
* Mapping from tab ID to payment information (if any).
|
* Mapping from tab ID to payment information (if any).
|
||||||
*/
|
*/
|
||||||
let paymentRequestCookies: { [n: number]: any } = {};
|
const paymentRequestCookies: { [n: number]: any } = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -376,19 +380,19 @@ let paymentRequestCookies: { [n: number]: any } = {};
|
|||||||
*/
|
*/
|
||||||
function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: string, tabId: number): any {
|
function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: string, tabId: number): any {
|
||||||
const headers: { [s: string]: string } = {};
|
const headers: { [s: string]: string } = {};
|
||||||
for (let kv of headerList) {
|
for (const kv of headerList) {
|
||||||
if (kv.value) {
|
if (kv.value) {
|
||||||
headers[kv.name.toLowerCase()] = kv.value;
|
headers[kv.name.toLowerCase()] = kv.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let fields = {
|
const fields = {
|
||||||
contract_url: headers["x-taler-contract-url"],
|
|
||||||
contract_query: headers["x-taler-contract-query"],
|
contract_query: headers["x-taler-contract-query"],
|
||||||
|
contract_url: headers["x-taler-contract-url"],
|
||||||
offer_url: headers["x-taler-offer-url"],
|
offer_url: headers["x-taler-offer-url"],
|
||||||
}
|
};
|
||||||
|
|
||||||
let talerHeaderFound = Object.keys(fields).filter((x: any) => (fields as any)[x]).length !== 0;
|
const talerHeaderFound = Object.keys(fields).filter((x: any) => (fields as any)[x]).length !== 0;
|
||||||
|
|
||||||
if (!talerHeaderFound) {
|
if (!talerHeaderFound) {
|
||||||
// looks like it's not a taler request, it might be
|
// looks like it's not a taler request, it might be
|
||||||
@ -397,27 +401,26 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let payDetail = {
|
const payDetail = {
|
||||||
contract_url: fields.contract_url,
|
contract_url: fields.contract_url,
|
||||||
offer_url: fields.offer_url,
|
offer_url: fields.offer_url,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("got pay detail", payDetail)
|
console.log("got pay detail", payDetail);
|
||||||
|
|
||||||
// This cookie will be read by the injected content script
|
// This cookie will be read by the injected content script
|
||||||
// in the tab that displays the page.
|
// in the tab that displays the page.
|
||||||
paymentRequestCookies[tabId] = {
|
paymentRequestCookies[tabId] = {
|
||||||
type: "pay",
|
|
||||||
payDetail,
|
payDetail,
|
||||||
|
type: "pay",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHeader[],
|
function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHeader[],
|
||||||
url: string, tabId: number): any {
|
url: string, tabId: number): any {
|
||||||
const headers: { [s: string]: string } = {};
|
const headers: { [s: string]: string } = {};
|
||||||
for (let kv of headerList) {
|
for (const kv of headerList) {
|
||||||
if (kv.value) {
|
if (kv.value) {
|
||||||
headers[kv.name.toLowerCase()] = kv.value;
|
headers[kv.name.toLowerCase()] = kv.value;
|
||||||
}
|
}
|
||||||
@ -432,7 +435,7 @@ function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHea
|
|||||||
|
|
||||||
const amount = headers["x-taler-amount"];
|
const amount = headers["x-taler-amount"];
|
||||||
if (amount) {
|
if (amount) {
|
||||||
let callbackUrl = headers["x-taler-callback-url"];
|
const callbackUrl = headers["x-taler-callback-url"];
|
||||||
if (!callbackUrl) {
|
if (!callbackUrl) {
|
||||||
console.log("202 not understood (X-Taler-Callback-Url missing)");
|
console.log("202 not understood (X-Taler-Callback-Url missing)");
|
||||||
return;
|
return;
|
||||||
@ -441,30 +444,29 @@ function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHea
|
|||||||
try {
|
try {
|
||||||
amountParsed = JSON.parse(amount);
|
amountParsed = JSON.parse(amount);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let uri = new URI(chrome.extension.getURL("/src/pages/error.html"));
|
const uri = new URI(chrome.extension.getURL("/src/pages/error.html"));
|
||||||
let p = {
|
const p = {
|
||||||
message: `Can't parse amount ("${amount}"): ${e.message}`,
|
message: `Can't parse amount ("${amount}"): ${e.message}`,
|
||||||
};
|
};
|
||||||
let redirectUrl = uri.query(p).href();
|
const redirectUrl = uri.query(p).href();
|
||||||
// FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed
|
// FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed
|
||||||
chrome.tabs.update(tabId, {url: redirectUrl});
|
chrome.tabs.update(tabId, {url: redirectUrl});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let wtTypes = headers["x-taler-wt-types"];
|
const wtTypes = headers["x-taler-wt-types"];
|
||||||
if (!wtTypes) {
|
if (!wtTypes) {
|
||||||
console.log("202 not understood (X-Taler-Wt-Types missing)");
|
console.log("202 not understood (X-Taler-Wt-Types missing)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let params = {
|
const params = {
|
||||||
amount: amount,
|
amount,
|
||||||
callback_url: new URI(callbackUrl)
|
|
||||||
.absoluteTo(url),
|
|
||||||
bank_url: url,
|
bank_url: url,
|
||||||
wt_types: wtTypes,
|
callback_url: new URI(callbackUrl) .absoluteTo(url),
|
||||||
suggested_exchange_url: headers["x-taler-suggested-exchange"],
|
suggested_exchange_url: headers["x-taler-suggested-exchange"],
|
||||||
|
wt_types: wtTypes,
|
||||||
};
|
};
|
||||||
let uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html"));
|
const uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html"));
|
||||||
let redirectUrl = uri.query(params).href();
|
const redirectUrl = uri.query(params).href();
|
||||||
console.log("redirecting to", redirectUrl);
|
console.log("redirecting to", redirectUrl);
|
||||||
// FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed
|
// FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed
|
||||||
chrome.tabs.update(tabId, {url: redirectUrl});
|
chrome.tabs.update(tabId, {url: redirectUrl});
|
||||||
@ -484,21 +486,21 @@ function clearRateLimitCache() {
|
|||||||
export async function wxMain() {
|
export async function wxMain() {
|
||||||
window.onerror = (m, source, lineno, colno, error) => {
|
window.onerror = (m, source, lineno, colno, error) => {
|
||||||
logging.record("error", m + error, undefined, source || "(unknown)", lineno || 0, colno || 0);
|
logging.record("error", m + error, undefined, source || "(unknown)", lineno || 0, colno || 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
chrome.browserAction.setBadgeText({ text: "" });
|
chrome.browserAction.setBadgeText({ text: "" });
|
||||||
const badge = new ChromeBadge();
|
const badge = new ChromeBadge();
|
||||||
|
|
||||||
chrome.tabs.query({}, function (tabs) {
|
chrome.tabs.query({}, (tabs) => {
|
||||||
for (let tab of tabs) {
|
for (const tab of tabs) {
|
||||||
if (!tab.url || !tab.id) {
|
if (!tab.url || !tab.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let uri = new URI(tab.url);
|
const uri = new URI(tab.url);
|
||||||
if (uri.protocol() === "http" || uri.protocol() === "https") {
|
if (uri.protocol() === "http" || uri.protocol() === "https") {
|
||||||
console.log("injecting into existing tab", tab.id);
|
console.log("injecting into existing tab", tab.id);
|
||||||
chrome.tabs.executeScript(tab.id, { file: "/dist/contentScript-bundle.js" });
|
chrome.tabs.executeScript(tab.id, { file: "/dist/contentScript-bundle.js" });
|
||||||
let code = `
|
const code = `
|
||||||
if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) {
|
if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) {
|
||||||
document.dispatchEvent(new Event("taler-probe-result"));
|
document.dispatchEvent(new Event("taler-probe-result"));
|
||||||
}
|
}
|
||||||
@ -511,13 +513,13 @@ export async function wxMain() {
|
|||||||
const tabTimers: {[n: number]: number[]} = {};
|
const tabTimers: {[n: number]: number[]} = {};
|
||||||
|
|
||||||
chrome.tabs.onRemoved.addListener((tabId, changeInfo) => {
|
chrome.tabs.onRemoved.addListener((tabId, changeInfo) => {
|
||||||
let tt = tabTimers[tabId] || [];
|
const tt = tabTimers[tabId] || [];
|
||||||
for (let t of tt) {
|
for (const t of tt) {
|
||||||
chrome.extension.getBackgroundPage().clearTimeout(t);
|
chrome.extension.getBackgroundPage().clearTimeout(t);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
||||||
if (changeInfo.status !== 'complete') {
|
if (changeInfo.status !== "complete") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const timers: number[] = [];
|
const timers: number[] = [];
|
||||||
@ -525,7 +527,7 @@ export async function wxMain() {
|
|||||||
const addRun = (dt: number) => {
|
const addRun = (dt: number) => {
|
||||||
const id = chrome.extension.getBackgroundPage().setTimeout(run, dt);
|
const id = chrome.extension.getBackgroundPage().setTimeout(run, dt);
|
||||||
timers.push(id);
|
timers.push(id);
|
||||||
}
|
};
|
||||||
|
|
||||||
const run = () => {
|
const run = () => {
|
||||||
timers.shift();
|
timers.shift();
|
||||||
@ -536,11 +538,11 @@ export async function wxMain() {
|
|||||||
if (!tab.url || !tab.id) {
|
if (!tab.url || !tab.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let uri = new URI(tab.url);
|
const uri = new URI(tab.url);
|
||||||
if (!(uri.protocol() === "http" || uri.protocol() === "https")) {
|
if (!(uri.protocol() === "http" || uri.protocol() === "https")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let code = `
|
const code = `
|
||||||
if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) {
|
if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) {
|
||||||
document.dispatchEvent(new Event("taler-probe-result"));
|
document.dispatchEvent(new Event("taler-probe-result"));
|
||||||
}
|
}
|
||||||
@ -600,7 +602,6 @@ export async function wxMain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a promise that resolves
|
* Return a promise that resolves
|
||||||
* to the taler wallet db.
|
* to the taler wallet db.
|
||||||
@ -620,13 +621,13 @@ function openTalerDb(): Promise<IDBDatabase> {
|
|||||||
switch (e.oldVersion) {
|
switch (e.oldVersion) {
|
||||||
case 0: // DB does not exist yet
|
case 0: // DB does not exist yet
|
||||||
|
|
||||||
for (let n in Stores) {
|
for (const n in Stores) {
|
||||||
if ((Stores as any)[n] instanceof Store) {
|
if ((Stores as any)[n] instanceof Store) {
|
||||||
let si: Store<any> = (Stores as any)[n];
|
const si: Store<any> = (Stores as any)[n];
|
||||||
const s = db.createObjectStore(si.name, si.storeParams);
|
const s = db.createObjectStore(si.name, si.storeParams);
|
||||||
for (let indexName in (si as any)) {
|
for (const indexName in (si as any)) {
|
||||||
if ((si as any)[indexName] instanceof Index) {
|
if ((si as any)[indexName] instanceof Index) {
|
||||||
let ii: Index<any, any> = (si as any)[indexName];
|
const ii: Index<any, any> = (si as any)[indexName];
|
||||||
s.createIndex(ii.indexName, ii.keyPath);
|
s.createIndex(ii.indexName, ii.keyPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -649,26 +650,27 @@ function openTalerDb(): Promise<IDBDatabase> {
|
|||||||
|
|
||||||
|
|
||||||
function exportDb(db: IDBDatabase): Promise<any> {
|
function exportDb(db: IDBDatabase): Promise<any> {
|
||||||
let dump = {
|
const dump = {
|
||||||
name: db.name,
|
name: db.name,
|
||||||
version: db.version,
|
|
||||||
stores: {} as {[s: string]: any},
|
stores: {} as {[s: string]: any},
|
||||||
|
version: db.version,
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
let tx = db.transaction(Array.from(db.objectStoreNames));
|
const tx = db.transaction(Array.from(db.objectStoreNames));
|
||||||
tx.addEventListener("complete", () => {
|
tx.addEventListener("complete", () => {
|
||||||
resolve(dump);
|
resolve(dump);
|
||||||
});
|
});
|
||||||
|
// tslint:disable-next-line:prefer-for-of
|
||||||
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
||||||
let name = db.objectStoreNames[i];
|
const name = db.objectStoreNames[i];
|
||||||
let storeDump = {} as {[s: string]: any};
|
const storeDump = {} as {[s: string]: any};
|
||||||
dump.stores[name] = storeDump;
|
dump.stores[name] = storeDump;
|
||||||
let store = tx.objectStore(name)
|
tx.objectStore(name)
|
||||||
.openCursor()
|
.openCursor()
|
||||||
.addEventListener("success", (e: Event) => {
|
.addEventListener("success", (e: Event) => {
|
||||||
let cursor = (e.target as any).result;
|
const cursor = (e.target as any).result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
storeDump[cursor.key] = cursor.value;
|
storeDump[cursor.key] = cursor.value;
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
@ -682,19 +684,22 @@ function exportDb(db: IDBDatabase): Promise<any> {
|
|||||||
function importDb(db: IDBDatabase, dump: any): Promise<void> {
|
function importDb(db: IDBDatabase, dump: any): Promise<void> {
|
||||||
console.log("importing db", dump);
|
console.log("importing db", dump);
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
let tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
|
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
|
||||||
for (let storeName in dump.stores) {
|
if (dump.stores) {
|
||||||
let objects = [];
|
for (const storeName in dump.stores) {
|
||||||
for (let key in dump.stores[storeName]) {
|
const objects = [];
|
||||||
objects.push(dump.stores[storeName][key]);
|
const dumpStore = dump.stores[storeName];
|
||||||
|
for (const key in dumpStore) {
|
||||||
|
objects.push(dumpStore[key]);
|
||||||
}
|
}
|
||||||
console.log(`importing ${objects.length} records into ${storeName}`);
|
console.log(`importing ${objects.length} records into ${storeName}`);
|
||||||
let store = tx.objectStore(storeName);
|
const store = tx.objectStore(storeName);
|
||||||
let clearReq = store.clear();
|
const clearReq = store.clear();
|
||||||
for (let obj of objects) {
|
for (const obj of objects) {
|
||||||
store.put(obj);
|
store.put(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
tx.addEventListener("complete", () => {
|
tx.addEventListener("complete", () => {
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
21
tslint.json
21
tslint.json
@ -8,7 +8,26 @@
|
|||||||
"max-line-length": {
|
"max-line-length": {
|
||||||
"options": [120]
|
"options": [120]
|
||||||
},
|
},
|
||||||
"space-before-function-paren": [true, {"named": "never", "anonymous": "always"}]
|
"space-before-function-paren": [true, {"named": "never", "anonymous": "always"}],
|
||||||
|
"no-console": [false],
|
||||||
|
"no-consecutive-blank-lines": [true, 2],
|
||||||
|
"forin": false,
|
||||||
|
"member-access": false,
|
||||||
|
"variable-name": false,
|
||||||
|
"interface-name": false,
|
||||||
|
"max-classes-per-file": false,
|
||||||
|
"ordered-imports": [true,
|
||||||
|
{
|
||||||
|
"import-sources-order": "lowercase-last",
|
||||||
|
"named-imports-order": "lowercase-last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-namespace": false,
|
||||||
|
"member-ordering": false,
|
||||||
|
"array-type": [true, "array-simple"],
|
||||||
|
"class-name": false,
|
||||||
|
"no-bitwise": false,
|
||||||
|
"file-header": [true, "GNU General Public License"]
|
||||||
},
|
},
|
||||||
"rulesDirectory": []
|
"rulesDirectory": []
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user