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