add linting rules and fix them

This commit is contained in:
Florian Dold 2017-05-28 01:10:54 +02:00
parent 7fff4499fd
commit 08bd3dc0e8
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
27 changed files with 1635 additions and 1515 deletions

View File

@ -52,7 +52,7 @@ coverage: tsc yarn-install
.PHONY: lint .PHONY: lint
lint: tsc yarn-install lint: tsc yarn-install
$(tslint) --type-check --project tsconfig.json -t verbose 'src/**/*.ts' $(tslint) --type-check -e src/i18n/strings.ts --project tsconfig.json -t verbose 'src/**/*.ts'
.PHONY: yarn-install .PHONY: yarn-install
i18n: yarn-install i18n: yarn-install

View File

@ -40,7 +40,7 @@
*/ */
export namespace Checkable { export namespace Checkable {
type Path = (number | string)[]; type Path = Array<number | string>;
interface SchemaErrorConstructor { interface SchemaErrorConstructor {
new (err: string): SchemaError; new (err: string): SchemaError;
@ -67,22 +67,22 @@ export namespace Checkable {
props: Prop[]; props: Prop[];
} }
export let SchemaError = (function SchemaError(this: any, message: string) { export const SchemaError = (function SchemaError(this: any, message: string) {
let that: any = this as any; const that: any = this as any;
that.name = 'SchemaError'; that.name = "SchemaError";
that.message = message; that.message = message;
that.stack = (<any>new Error()).stack; that.stack = (new Error() as any).stack;
}) as any as SchemaErrorConstructor; }) as any as SchemaErrorConstructor;
SchemaError.prototype = new Error; SchemaError.prototype = new Error();
/** /**
* Classes that are checkable are annotated with this * Classes that are checkable are annotated with this
* checkable info symbol, which contains the information necessary * checkable info symbol, which contains the information necessary
* to check if they're valid. * to check if they're valid.
*/ */
let checkableInfoSym = Symbol("checkableInfo"); const checkableInfoSym = Symbol("checkableInfo");
/** /**
* Get the current property list for a checkable type. * Get the current property list for a checkable type.
@ -138,7 +138,7 @@ export namespace Checkable {
throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`); throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`);
} }
for (let i = 0; i < target.length; i++) { for (let i = 0; i < target.length; i++) {
let v = target[i]; const v = target[i];
prop.elementChecker(v, prop.elementProp, path.concat([i])); prop.elementChecker(v, prop.elementProp, path.concat([i]));
} }
return target; return target;
@ -148,9 +148,9 @@ export namespace Checkable {
if (typeof target !== "object") { if (typeof target !== "object") {
throw new SchemaError(`expected object for ${path}, got ${typeof target} instead`); throw new SchemaError(`expected object for ${path}, got ${typeof target} instead`);
} }
for (let key in target) { for (const key in target) {
prop.keyProp.checker(key, prop.keyProp, path.concat([key])); prop.keyProp.checker(key, prop.keyProp, path.concat([key]));
let value = target[key]; const value = target[key];
prop.valueProp.checker(value, prop.valueProp, path.concat([key])); prop.valueProp.checker(value, prop.valueProp, path.concat([key]));
} }
} }
@ -166,35 +166,35 @@ export namespace Checkable {
function checkValue(target: any, prop: Prop, path: Path): any { function checkValue(target: any, prop: Prop, path: Path): any {
let type = prop.type; const type = prop.type;
if (!type) { if (!type) {
throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`); throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`);
} }
let v = target; const v = target;
if (!v || typeof v !== "object") { if (!v || typeof v !== "object") {
throw new SchemaError( throw new SchemaError(
`expected object for ${path.join(".")}, got ${typeof v} instead`); `expected object for ${path.join(".")}, got ${typeof v} instead`);
} }
let props = type.prototype[checkableInfoSym].props; const props = type.prototype[checkableInfoSym].props;
let remainingPropNames = new Set(Object.getOwnPropertyNames(v)); const remainingPropNames = new Set(Object.getOwnPropertyNames(v));
let obj = new type(); const obj = new type();
for (let prop of props) { for (const innerProp of props) {
if (!remainingPropNames.has(prop.propertyKey)) { if (!remainingPropNames.has(innerProp.propertyKey)) {
if (prop.optional) { if (innerProp.optional) {
continue; continue;
} }
throw new SchemaError(`Property ${prop.propertyKey} missing on ${path}`); throw new SchemaError(`Property ${innerProp.propertyKey} missing on ${path}`);
} }
if (!remainingPropNames.delete(prop.propertyKey)) { if (!remainingPropNames.delete(innerProp.propertyKey)) {
throw new SchemaError("assertion failed"); throw new SchemaError("assertion failed");
} }
let propVal = v[prop.propertyKey]; const propVal = v[innerProp.propertyKey];
obj[prop.propertyKey] = prop.checker(propVal, obj[innerProp.propertyKey] = innerProp.checker(propVal,
prop, innerProp,
path.concat([prop.propertyKey])); path.concat([innerProp.propertyKey]));
} }
if (!prop.extraAllowed && remainingPropNames.size != 0) { if (!prop.extraAllowed && remainingPropNames.size !== 0) {
throw new SchemaError("superfluous properties " + JSON.stringify(Array.from( throw new SchemaError("superfluous properties " + JSON.stringify(Array.from(
remainingPropNames.values()))); remainingPropNames.values())));
} }
@ -210,14 +210,14 @@ export namespace Checkable {
export function Class(opts: {extra?: boolean, validate?: boolean} = {}) { export function Class(opts: {extra?: boolean, validate?: boolean} = {}) {
return (target: any) => { return (target: any) => {
target.checked = (v: any) => { target.checked = (v: any) => {
let cv = checkValue(v, { const cv = checkValue(v, {
checker: checkValue,
extraAllowed: !!opts.extra,
propertyKey: "(root)", propertyKey: "(root)",
type: target, type: target,
extraAllowed: !!opts.extra,
checker: checkValue
}, ["(root)"]); }, ["(root)"]);
if (opts.validate) { if (opts.validate) {
let instance = new target(); const instance = new target();
if (typeof instance.validate !== "function") { if (typeof instance.validate !== "function") {
throw Error("invalid Checkable annotion: validate method required"); throw Error("invalid Checkable annotion: validate method required");
} }
@ -227,7 +227,7 @@ export namespace Checkable {
return cv; return cv;
}; };
return target; return target;
} };
} }
@ -238,12 +238,12 @@ export namespace Checkable {
if (!type) { if (!type) {
throw Error("Type does not exist yet (wrong order of definitions?)"); throw Error("Type does not exist yet (wrong order of definitions?)");
} }
function deco(target: Object, propertyKey: string | symbol): void { function deco(target: object, propertyKey: string | symbol): void {
let chk = getCheckableInfo(target); const chk = getCheckableInfo(target);
chk.props.push({ chk.props.push({
propertyKey: propertyKey,
checker: checkValue, checker: checkValue,
type: type propertyKey,
type,
}); });
} }
@ -256,20 +256,20 @@ export namespace Checkable {
* an annotation for a list of strings. * an annotation for a list of strings.
*/ */
export function List(type: any) { export function List(type: any) {
let stub = {}; const stub = {};
type(stub, "(list-element)"); type(stub, "(list-element)");
let elementProp = getCheckableInfo(stub).props[0]; const elementProp = getCheckableInfo(stub).props[0];
let elementChecker = elementProp.checker; const elementChecker = elementProp.checker;
if (!elementChecker) { if (!elementChecker) {
throw Error("assertion failed"); throw Error("assertion failed");
} }
function deco(target: Object, propertyKey: string | symbol): void { function deco(target: object, propertyKey: string | symbol): void {
let chk = getCheckableInfo(target); const chk = getCheckableInfo(target);
chk.props.push({ chk.props.push({
checker: checkList,
elementChecker, elementChecker,
elementProp, elementProp,
propertyKey: propertyKey, propertyKey,
checker: checkList,
}); });
} }
@ -282,25 +282,25 @@ export namespace Checkable {
* one for the key type and one for the value type. * one for the key type and one for the value type.
*/ */
export function Map(keyType: any, valueType: any) { export function Map(keyType: any, valueType: any) {
let keyStub = {}; const keyStub = {};
keyType(keyStub, "(map-key)"); keyType(keyStub, "(map-key)");
let keyProp = getCheckableInfo(keyStub).props[0]; const keyProp = getCheckableInfo(keyStub).props[0];
if (!keyProp) { if (!keyProp) {
throw Error("assertion failed"); throw Error("assertion failed");
} }
let valueStub = {}; const valueStub = {};
valueType(valueStub, "(map-value)"); valueType(valueStub, "(map-value)");
let valueProp = getCheckableInfo(valueStub).props[0]; const valueProp = getCheckableInfo(valueStub).props[0];
if (!valueProp) { if (!valueProp) {
throw Error("assertion failed"); throw Error("assertion failed");
} }
function deco(target: Object, propertyKey: string | symbol): void { function deco(target: object, propertyKey: string | symbol): void {
let chk = getCheckableInfo(target); const chk = getCheckableInfo(target);
chk.props.push({ chk.props.push({
keyProp,
valueProp,
propertyKey: propertyKey,
checker: checkMap, checker: checkMap,
keyProp,
propertyKey,
valueProp,
}); });
} }
@ -312,21 +312,21 @@ export namespace Checkable {
* Makes another annotation optional, for example `@Checkable.Optional(Checkable.Number)`. * Makes another annotation optional, for example `@Checkable.Optional(Checkable.Number)`.
*/ */
export function Optional(type: any) { export function Optional(type: any) {
let stub = {}; const stub = {};
type(stub, "(optional-element)"); type(stub, "(optional-element)");
let elementProp = getCheckableInfo(stub).props[0]; const elementProp = getCheckableInfo(stub).props[0];
let elementChecker = elementProp.checker; const elementChecker = elementProp.checker;
if (!elementChecker) { if (!elementChecker) {
throw Error("assertion failed"); throw Error("assertion failed");
} }
function deco(target: Object, propertyKey: string | symbol): void { function deco(target: object, propertyKey: string | symbol): void {
let chk = getCheckableInfo(target); const chk = getCheckableInfo(target);
chk.props.push({ chk.props.push({
checker: checkOptional,
elementChecker, elementChecker,
elementProp, elementProp,
propertyKey: propertyKey,
checker: checkOptional,
optional: true, optional: true,
propertyKey,
}); });
} }
@ -337,20 +337,20 @@ export namespace Checkable {
/** /**
* Target property must be a number. * Target property must be a number.
*/ */
export function Number(target: Object, propertyKey: string | symbol): void { export function Number(target: object, propertyKey: string | symbol): void {
let chk = getCheckableInfo(target); const chk = getCheckableInfo(target);
chk.props.push({ propertyKey: propertyKey, checker: checkNumber }); chk.props.push({checker: checkNumber, propertyKey});
} }
/** /**
* Target property must be an arbitary object. * Target property must be an arbitary object.
*/ */
export function AnyObject(target: Object, propertyKey: string | symbol): void { export function AnyObject(target: object, propertyKey: string | symbol): void {
let chk = getCheckableInfo(target); const chk = getCheckableInfo(target);
chk.props.push({ chk.props.push({
propertyKey: propertyKey, checker: checkAnyObject,
checker: checkAnyObject propertyKey,
}); });
} }
@ -361,12 +361,12 @@ export namespace Checkable {
* Not useful by itself, but in combination with higher-order annotations * Not useful by itself, but in combination with higher-order annotations
* such as List or Map. * such as List or Map.
*/ */
export function Any(target: Object, propertyKey: string | symbol): void { export function Any(target: object, propertyKey: string | symbol): void {
let chk = getCheckableInfo(target); const chk = getCheckableInfo(target);
chk.props.push({ chk.props.push({
propertyKey: propertyKey,
checker: checkAny, checker: checkAny,
optional: true optional: true,
propertyKey,
}); });
} }
@ -374,16 +374,16 @@ export namespace Checkable {
/** /**
* Target property must be a string. * Target property must be a string.
*/ */
export function String(target: Object, propertyKey: string | symbol): void { export function String(target: object, propertyKey: string | symbol): void {
let chk = getCheckableInfo(target); const chk = getCheckableInfo(target);
chk.props.push({ propertyKey: propertyKey, checker: checkString }); chk.props.push({ checker: checkString, propertyKey });
} }
/** /**
* Target property must be a boolean value. * Target property must be a boolean value.
*/ */
export function Boolean(target: Object, propertyKey: string | symbol): void { export function Boolean(target: object, propertyKey: string | symbol): void {
let chk = getCheckableInfo(target); const chk = getCheckableInfo(target);
chk.props.push({ propertyKey: propertyKey, checker: checkBoolean }); chk.props.push({ checker: checkBoolean, propertyKey });
} }
} }

View File

@ -15,7 +15,7 @@
*/ */
import { import {
Badge Badge,
} from "./wallet"; } from "./wallet";
@ -85,7 +85,7 @@ export class ChromeBadge implements Badge {
constructor(window?: Window) { constructor(window?: Window) {
// Allow injecting another window for testing // Allow injecting another window for testing
let bg = window || chrome.extension.getBackgroundPage(); const bg = window || chrome.extension.getBackgroundPage();
if (!bg) { if (!bg) {
throw Error("no window available"); throw Error("no window available");
} }
@ -130,13 +130,11 @@ export class ChromeBadge implements Badge {
this.ctx.lineWidth = 2.5; this.ctx.lineWidth = 2.5;
if (this.animationRunning) { if (this.animationRunning) {
/* Draw circle around the "T" with an opening of this.gapWidth */ /* Draw circle around the "T" with an opening of this.gapWidth */
this.ctx.arc(0, 0, const aMax = ChromeBadge.rotationAngleMax;
this.canvas.width / 2 - 2, /* radius */ const startAngle = this.rotationAngle / aMax * Math.PI * 2;
this.rotationAngle / ChromeBadge.rotationAngleMax * Math.PI * 2, const stopAngle = ((this.rotationAngle + aMax - this.gapWidth) / aMax) * Math.PI * 2;
((this.rotationAngle + ChromeBadge.rotationAngleMax - this.gapWidth) / ChromeBadge.rotationAngleMax) * Math.PI * 2, this.ctx.arc(0, 0, this.canvas.width / 2 - 2, /* radius */ startAngle, stopAngle, false);
false); } else {
}
else {
/* Draw full circle */ /* Draw full circle */
this.ctx.arc(0, 0, this.ctx.arc(0, 0,
this.canvas.width / 2 - 2, /* radius */ this.canvas.width / 2 - 2, /* radius */
@ -149,9 +147,10 @@ export class ChromeBadge implements Badge {
this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2); this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2);
// Allow running outside the extension for testing // Allow running outside the extension for testing
// tslint:disable-next-line:no-string-literal
if (window["chrome"] && window.chrome["browserAction"]) { if (window["chrome"] && window.chrome["browserAction"]) {
try { try {
let imageData = this.ctx.getImageData(0, const imageData = this.ctx.getImageData(0,
0, 0,
this.canvas.width, this.canvas.width,
this.canvas.height); this.canvas.height);
@ -168,20 +167,20 @@ export class ChromeBadge implements Badge {
return; return;
} }
this.animationRunning = true; this.animationRunning = true;
let start: number|undefined = undefined; let start: number|undefined;
let step = (timestamp: number) => { const step = (timestamp: number) => {
if (!this.animationRunning) { if (!this.animationRunning) {
return; return;
} }
if (!start) { if (!start) {
start = timestamp; start = timestamp;
} }
let delta = (timestamp - start); if (!this.isBusy && 0 === this.gapWidth) {
if (!this.isBusy && 0 == this.gapWidth) {
// stop if we're close enough to origin // stop if we're close enough to origin
this.rotationAngle = 0; this.rotationAngle = 0;
} else { } else {
this.rotationAngle = (this.rotationAngle + (timestamp - start) * ChromeBadge.rotationSpeed) % ChromeBadge.rotationAngleMax; this.rotationAngle = (this.rotationAngle + (timestamp - start) *
ChromeBadge.rotationSpeed) % ChromeBadge.rotationAngleMax;
} }
if (this.isBusy) { if (this.isBusy) {
if (this.gapWidth < ChromeBadge.openMax) { if (this.gapWidth < ChromeBadge.openMax) {
@ -190,15 +189,13 @@ export class ChromeBadge implements Badge {
if (this.gapWidth > ChromeBadge.openMax) { if (this.gapWidth > ChromeBadge.openMax) {
this.gapWidth = ChromeBadge.openMax; this.gapWidth = ChromeBadge.openMax;
} }
} } else {
else {
if (this.gapWidth > 0) { if (this.gapWidth > 0) {
this.gapWidth--; this.gapWidth--;
this.gapWidth *= ChromeBadge.closeSpeed; this.gapWidth *= ChromeBadge.closeSpeed;
} }
} }
if (this.isBusy || this.gapWidth > 0) { if (this.isBusy || this.gapWidth > 0) {
start = timestamp; start = timestamp;
rAF(step); rAF(step);

View File

@ -14,6 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
// tslint:disable:no-unused-expression
/** /**
* Module that is injected into (all!) pages to allow them * Module that is injected into (all!) pages to allow them
@ -55,9 +56,9 @@ interface Handler {
const handlers: Handler[] = []; const handlers: Handler[] = [];
function hashContract(contract: string): Promise<string> { function hashContract(contract: string): Promise<string> {
let walletHashContractMsg = { const walletHashContractMsg = {
detail: {contract},
type: "hash-contract", type: "hash-contract",
detail: {contract}
}; };
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
chrome.runtime.sendMessage(walletHashContractMsg, (resp: any) => { chrome.runtime.sendMessage(walletHashContractMsg, (resp: any) => {
@ -72,8 +73,8 @@ function hashContract(contract: string): Promise<string> {
function queryPayment(url: string): Promise<any> { function queryPayment(url: string): Promise<any> {
const walletMsg = { const walletMsg = {
type: "query-payment",
detail: { url }, detail: { url },
type: "query-payment",
}; };
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(walletMsg, (resp: any) => { chrome.runtime.sendMessage(walletMsg, (resp: any) => {
@ -84,10 +85,10 @@ function queryPayment(url: string): Promise<any> {
function putHistory(historyEntry: any): Promise<void> { function putHistory(historyEntry: any): Promise<void> {
const walletMsg = { const walletMsg = {
type: "put-history-entry",
detail: { detail: {
historyEntry, historyEntry,
}, },
type: "put-history-entry",
}; };
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
chrome.runtime.sendMessage(walletMsg, (resp: any) => { chrome.runtime.sendMessage(walletMsg, (resp: any) => {
@ -98,14 +99,14 @@ function putHistory(historyEntry: any): Promise<void> {
function saveOffer(offer: any): Promise<number> { function saveOffer(offer: any): Promise<number> {
const walletMsg = { const walletMsg = {
type: "save-offer",
detail: { detail: {
offer: { offer: {
H_contract: offer.hash,
contract: offer.data, contract: offer.data,
merchant_sig: offer.sig, merchant_sig: offer.sig,
H_contract: offer.hash, offer_time: new Date().getTime() / 1000,
offer_time: new Date().getTime() / 1000
}, },
type: "save-offer",
}, },
}; };
return new Promise<number>((resolve, reject) => { return new Promise<number>((resolve, reject) => {
@ -120,8 +121,6 @@ function saveOffer(offer: any): Promise<number> {
} }
let sheet: CSSStyleSheet|null; let sheet: CSSStyleSheet|null;
function initStyle() { function initStyle() {
@ -170,7 +169,6 @@ function setStyles(installed: boolean) {
} }
function handlePaymentResponse(walletResp: any) { function handlePaymentResponse(walletResp: any) {
/** /**
* Handle a failed payment. * Handle a failed payment.
@ -185,16 +183,16 @@ function handlePaymentResponse(walletResp: any) {
console.log("pay-failed", {status: r.status, response: r.responseText}); console.log("pay-failed", {status: r.status, response: r.responseText});
} }
function onTimeout() { function onTimeout() {
timeoutHandle = null timeoutHandle = null;
err(); err();
} }
talerPaymentFailed(walletResp.H_contract).then(() => { talerPaymentFailed(walletResp.H_contract).then(() => {
if (timeoutHandle != null) { if (timeoutHandle !== null) {
clearTimeout(timeoutHandle); clearTimeout(timeoutHandle);
timeoutHandle = null; timeoutHandle = null;
} }
err(); err();
}) });
timeoutHandle = window.setTimeout(onTimeout, 200); timeoutHandle = window.setTimeout(onTimeout, 200);
} }
@ -210,7 +208,7 @@ function handlePaymentResponse(walletResp: any) {
r.open("post", walletResp.contract.pay_url); r.open("post", walletResp.contract.pay_url);
r.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); r.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
r.send(JSON.stringify(walletResp.payReq)); r.send(JSON.stringify(walletResp.payReq));
r.onload = function() { r.onload = () => {
if (!r) { if (!r) {
return; return;
} }
@ -219,7 +217,7 @@ function handlePaymentResponse(walletResp: any) {
const merchantResp = JSON.parse(r.responseText); const merchantResp = JSON.parse(r.responseText);
logVerbose && console.log("got success from pay_url"); logVerbose && console.log("got success from pay_url");
talerPaymentSucceeded({H_contract: walletResp.H_contract, merchantSig: merchantResp.sig}).then(() => { talerPaymentSucceeded({H_contract: walletResp.H_contract, merchantSig: merchantResp.sig}).then(() => {
let nextUrl = walletResp.contract.fulfillment_url; const nextUrl = walletResp.contract.fulfillment_url;
logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl); logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl);
window.location.href = nextUrl; window.location.href = nextUrl;
window.location.reload(true); window.location.reload(true);
@ -230,7 +228,7 @@ function handlePaymentResponse(walletResp: any) {
break; break;
} }
r = null; r = null;
if (timeoutHandle != null) { if (timeoutHandle !== null) {
clearTimeout(timeoutHandle!); clearTimeout(timeoutHandle!);
timeoutHandle = null; timeoutHandle = null;
} }
@ -262,7 +260,7 @@ function init() {
initStyle(); initStyle();
setStyles(true); setStyles(true);
}; };
if (document.readyState == "complete") { if (document.readyState === "complete") {
onload(); onload();
} else { } else {
document.addEventListener("DOMContentLoaded", onload); document.addEventListener("DOMContentLoaded", onload);
@ -270,19 +268,19 @@ function init() {
} }
registerHandlers(); registerHandlers();
// Hack to know when the extension is unloaded // Hack to know when the extension is unloaded
let port = chrome.runtime.connect(); const port = chrome.runtime.connect();
port.onDisconnect.addListener(() => { port.onDisconnect.addListener(() => {
logVerbose && console.log("chrome runtime disconnected, removing handlers"); logVerbose && console.log("chrome runtime disconnected, removing handlers");
if (document.documentElement.getAttribute("data-taler-nojs")) { if (document.documentElement.getAttribute("data-taler-nojs")) {
setStyles(false); setStyles(false);
} }
for (let handler of handlers) { for (const handler of handlers) {
document.removeEventListener(handler.type, handler.listener); document.removeEventListener(handler.type, handler.listener);
} }
}); });
if (resp && resp.type == "pay") { if (resp && resp.type === "pay") {
logVerbose && console.log("doing taler.pay with", resp.payDetail); logVerbose && console.log("doing taler.pay with", resp.payDetail);
talerPay(resp.payDetail).then(handlePaymentResponse); talerPay(resp.payDetail).then(handlePaymentResponse);
document.documentElement.style.visibility = "hidden"; document.documentElement.style.visibility = "hidden";
@ -290,9 +288,7 @@ function init() {
}); });
} }
interface HandlerFn { type HandlerFn = (detail: any, sendResponse: (msg: any) => void) => void;
(detail: any, sendResponse: (msg: any) => void): void;
}
function generateNonce(): Promise<string> { function generateNonce(): Promise<string> {
const walletMsg = { const walletMsg = {
@ -306,35 +302,47 @@ function generateNonce(): Promise<string> {
} }
function downloadContract(url: string, nonce: string): Promise<any> { function downloadContract(url: string, nonce: string): Promise<any> {
let parsed_url = new URI(url); const parsed_url = new URI(url);
url = parsed_url.setQuery({nonce}).href(); url = parsed_url.setQuery({nonce}).href();
// FIXME: include and check nonce! // FIXME: include and check nonce!
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const contract_request = new XMLHttpRequest(); const contract_request = new XMLHttpRequest();
console.log("downloading contract from '" + url + "'") console.log("downloading contract from '" + url + "'");
contract_request.open("GET", url, true); contract_request.open("GET", url, true);
contract_request.onload = function (e) { contract_request.onload = (e) => {
if (contract_request.readyState == 4) { if (contract_request.readyState === 4) {
if (contract_request.status == 200) { if (contract_request.status === 200) {
console.log("response text:", console.log("response text:",
contract_request.responseText); contract_request.responseText);
var contract_wrapper = JSON.parse(contract_request.responseText); const contract_wrapper = JSON.parse(contract_request.responseText);
if (!contract_wrapper) { if (!contract_wrapper) {
console.error("response text was invalid json"); console.error("response text was invalid json");
let detail = {hint: "invalid json", status: contract_request.status, body: contract_request.responseText}; const detail = {
body: contract_request.responseText,
hint: "invalid json",
status: contract_request.status,
};
reject(detail); reject(detail);
return; return;
} }
resolve(contract_wrapper); resolve(contract_wrapper);
} else { } else {
let detail = {hint: "contract download failed", status: contract_request.status, body: contract_request.responseText}; const detail = {
body: contract_request.responseText,
hint: "contract download failed",
status: contract_request.status,
};
reject(detail); reject(detail);
return; return;
} }
} }
}; };
contract_request.onerror = function (e) { contract_request.onerror = (e) => {
let detail = {hint: "contract download failed", status: contract_request.status, body: contract_request.responseText}; const detail = {
body: contract_request.responseText,
hint: "contract download failed",
status: contract_request.status,
};
reject(detail); reject(detail);
return; return;
}; };
@ -353,9 +361,9 @@ async function processProposal(proposal: any) {
return; return;
} }
let contractHash = await hashContract(proposal.data); const contractHash = await hashContract(proposal.data);
if (contractHash != proposal.hash) { if (contractHash !== proposal.hash) {
console.error("merchant-supplied contract hash is wrong"); console.error("merchant-supplied contract hash is wrong");
return; return;
} }
@ -367,17 +375,17 @@ async function processProposal(proposal: any) {
// bad contract / name not included // bad contract / name not included
} }
let historyEntry = { const historyEntry = {
timestamp: (new Date).getTime(),
subjectId: `contract-${contractHash}`,
type: "offer-contract",
detail: { detail: {
contractHash, contractHash,
merchantName, merchantName,
} },
subjectId: `contract-${contractHash}`,
timestamp: (new Date()).getTime(),
type: "offer-contract",
}; };
await putHistory(historyEntry); await putHistory(historyEntry);
let offerId = await saveOffer(proposal); const offerId = await saveOffer(proposal);
const uri = new URI(chrome.extension.getURL( const uri = new URI(chrome.extension.getURL(
"/src/pages/confirm-contract.html")); "/src/pages/confirm-contract.html"));
@ -391,17 +399,17 @@ async function processProposal(proposal: any) {
function talerPay(msg: any): Promise<any> { function talerPay(msg: any): Promise<any> {
return new Promise(async(resolve, reject) => { return new Promise(async(resolve, reject) => {
// current URL without fragment // current URL without fragment
let url = new URI(document.location.href).fragment("").href(); const url = new URI(document.location.href).fragment("").href();
let res = await queryPayment(url); const res = await queryPayment(url);
logVerbose && console.log("taler-pay: got response", res); logVerbose && console.log("taler-pay: got response", res);
if (res && res.payReq) { if (res && res.payReq) {
resolve(res); resolve(res);
return; return;
} }
if (msg.contract_url) { if (msg.contract_url) {
let nonce = await generateNonce(); const nonce = await generateNonce();
let proposal = await downloadContract(msg.contract_url, nonce); const proposal = await downloadContract(msg.contract_url, nonce);
if (proposal.data.nonce != nonce) { if (proposal.data.nonce !== nonce) {
console.error("stale contract"); console.error("stale contract");
return; return;
} }
@ -421,10 +429,10 @@ function talerPay(msg: any): Promise<any> {
function talerPaymentFailed(H_contract: string) { function talerPaymentFailed(H_contract: string) {
return new Promise(async(resolve, reject) => { return new Promise(async(resolve, reject) => {
const walletMsg = { const walletMsg = {
type: "payment-failed",
detail: { detail: {
contractHash: H_contract contractHash: H_contract,
}, },
type: "payment-failed",
}; };
chrome.runtime.sendMessage(walletMsg, (resp) => { chrome.runtime.sendMessage(walletMsg, (resp) => {
resolve(); resolve();
@ -444,11 +452,11 @@ function talerPaymentSucceeded(msg: any) {
} }
logVerbose && console.log("got taler-payment-succeeded"); logVerbose && console.log("got taler-payment-succeeded");
const walletMsg = { const walletMsg = {
type: "payment-succeeded",
detail: { detail: {
merchantSig: msg.merchantSig,
contractHash: msg.H_contract, contractHash: msg.H_contract,
merchantSig: msg.merchantSig,
}, },
type: "payment-succeeded",
}; };
chrome.runtime.sendMessage(walletMsg, (resp) => { chrome.runtime.sendMessage(walletMsg, (resp) => {
resolve(); resolve();
@ -463,21 +471,21 @@ function registerHandlers() {
* handles adding sequence numbers to responses. * handles adding sequence numbers to responses.
*/ */
function addHandler(type: string, handler: HandlerFn) { function addHandler(type: string, handler: HandlerFn) {
let handlerWrap = (e: CustomEvent) => { const handlerWrap = (e: CustomEvent) => {
if (e.type != type) { if (e.type !== type) {
throw Error(`invariant violated`); throw Error(`invariant violated`);
} }
let callId: number|undefined = undefined; let callId: number|undefined;
if (e.detail && e.detail.callId != undefined) { if (e.detail && e.detail.callId !== undefined) {
callId = e.detail.callId; callId = e.detail.callId;
} }
let responder = (msg?: any) => { const responder = (msg?: any) => {
let fullMsg = Object.assign({}, msg, {callId}); const fullMsg = Object.assign({}, msg, {callId});
let opts = { detail: fullMsg }; let opts = { detail: fullMsg };
if ("function" == typeof cloneInto) { if ("function" === typeof cloneInto) {
opts = cloneInto(opts, document.defaultView); opts = cloneInto(opts, document.defaultView);
} }
let evt = new CustomEvent(type + "-result", opts); const evt = new CustomEvent(type + "-result", opts);
document.dispatchEvent(evt); document.dispatchEvent(evt);
}; };
handler(e.detail, responder); handler(e.detail, responder);
@ -489,7 +497,7 @@ function registerHandlers() {
addHandler("taler-query-id", (msg: any, sendResponse: any) => { addHandler("taler-query-id", (msg: any, sendResponse: any) => {
// FIXME: maybe include this info in taoer-probe? // FIXME: maybe include this info in taoer-probe?
sendResponse({id: chrome.runtime.id}) sendResponse({id: chrome.runtime.id});
}); });
addHandler("taler-probe", (msg: any, sendResponse: any) => { addHandler("taler-probe", (msg: any, sendResponse: any) => {
@ -497,34 +505,33 @@ function registerHandlers() {
}); });
addHandler("taler-create-reserve", (msg: any) => { addHandler("taler-create-reserve", (msg: any) => {
let params = { const params = {
amount: JSON.stringify(msg.amount), amount: JSON.stringify(msg.amount),
callback_url: new URI(msg.callback_url)
.absoluteTo(document.location.href),
bank_url: document.location.href, bank_url: document.location.href,
wt_types: JSON.stringify(msg.wt_types), callback_url: new URI(msg.callback_url) .absoluteTo(document.location.href),
suggested_exchange_url: msg.suggested_exchange_url, suggested_exchange_url: msg.suggested_exchange_url,
wt_types: JSON.stringify(msg.wt_types),
}; };
let uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html")); const uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html"));
let redirectUrl = uri.query(params).href(); const redirectUrl = uri.query(params).href();
window.location.href = redirectUrl; window.location.href = redirectUrl;
}); });
addHandler("taler-add-auditor", (msg: any) => { addHandler("taler-add-auditor", (msg: any) => {
let params = { const params = {
req: JSON.stringify(msg), req: JSON.stringify(msg),
}; };
let uri = new URI(chrome.extension.getURL("/src/pages/add-auditor.html")); const uri = new URI(chrome.extension.getURL("/src/pages/add-auditor.html"));
let redirectUrl = uri.query(params).href(); const redirectUrl = uri.query(params).href();
window.location.href = redirectUrl; window.location.href = redirectUrl;
}); });
addHandler("taler-confirm-reserve", (msg: any, sendResponse: any) => { addHandler("taler-confirm-reserve", (msg: any, sendResponse: any) => {
let walletMsg = { const walletMsg = {
type: "confirm-reserve",
detail: { detail: {
reservePub: msg.reserve_pub reservePub: msg.reserve_pub,
} },
type: "confirm-reserve",
}; };
chrome.runtime.sendMessage(walletMsg, (resp) => { chrome.runtime.sendMessage(walletMsg, (resp) => {
sendResponse(); sendResponse();
@ -544,7 +551,7 @@ function registerHandlers() {
}); });
addHandler("taler-pay", async(msg: any, sendResponse: any) => { addHandler("taler-pay", async(msg: any, sendResponse: any) => {
let resp = await talerPay(msg); const resp = await talerPay(msg);
sendResponse(resp); sendResponse(resp);
}); });

View File

@ -1,80 +1,104 @@
import {CryptoApi} from "./cryptoApi"; /*
import {ReserveRecord, DenominationRecord, DenominationStatus} from "../types"; This file is part of TALER
(C) 2017 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
// tslint:disable:max-line-length
import {test} from "ava"; import {test} from "ava";
let masterPub1: string = "CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00"; import {
DenominationRecord,
DenominationStatus,
ReserveRecord,
} from "../types";
let denomValid1: DenominationRecord = { import {CryptoApi} from "./cryptoApi";
masterSig: "CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
stampStart: "/Date(1473148381)/", const masterPub1: string = "CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";
stampExpireWithdraw: "/Date(2482300381)/",
stampExpireDeposit: "/Date(1851580381)/", const denomValid1: DenominationRecord = {
denomPub: "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GHS84R3JHHP6GSM2D9Q6514CGT568R32C9J6CWM4DSH64TM4DSM851K0CA48CVKAC1P6H144C2160T46DHK8CVM4HJ274S38C1M6S338D9N6GWM8DT684T3JCT36S13EC9G88R3EGHQ8S0KJGSQ60SKGD216N33AGJ2651K2E9S60TMCD1N75244HHQ6X33EDJ570R3GGJ2651MACA38D130DA560VK4HHJ68WK2CA26GW3ECSH6D13EC9S88VK2GT66WVK8D9G750K0D9R8RRK4DHQ71332GHK8D23GE26710M2H9K6WVK8HJ38MVKEGA66N23AC9H88VKACT58MV3CCSJ6H1K4DT38GRK0C9M8N33CE1R60V4AHA38H1KECSH6S33JH9N8GRKGH1K68S36GH354520818CMG26C1H60R30C935452081918G2J2G0", denomPub: "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GHS84R3JHHP6GSM2D9Q6514CGT568R32C9J6CWM4DSH64TM4DSM851K0CA48CVKAC1P6H144C2160T46DHK8CVM4HJ274S38C1M6S338D9N6GWM8DT684T3JCT36S13EC9G88R3EGHQ8S0KJGSQ60SKGD216N33AGJ2651K2E9S60TMCD1N75244HHQ6X33EDJ570R3GGJ2651MACA38D130DA560VK4HHJ68WK2CA26GW3ECSH6D13EC9S88VK2GT66WVK8D9G750K0D9R8RRK4DHQ71332GHK8D23GE26710M2H9K6WVK8HJ38MVKEGA66N23AC9H88VKACT58MV3CCSJ6H1K4DT38GRK0C9M8N33CE1R60V4AHA38H1KECSH6S33JH9N8GRKGH1K68S36GH354520818CMG26C1H60R30C935452081918G2J2G0",
stampExpireLegal: "/Date(1567756381)/", denomPubHash: "dummy",
value: { exchangeBaseUrl: "https://exchange.example.com/",
"currency": "PUDOS",
"value": 0,
"fraction": 100000
},
feeWithdraw: {
"currency": "PUDOS",
"value": 0,
"fraction": 10000
},
feeDeposit: { feeDeposit: {
"currency": "PUDOS", currency: "PUDOS",
"value": 0, fraction: 10000,
"fraction": 10000 value: 0,
}, },
feeRefresh: { feeRefresh: {
"currency": "PUDOS", currency: "PUDOS",
"value": 0, fraction: 10000,
"fraction": 10000 value: 0,
}, },
feeRefund: { feeRefund: {
"currency": "PUDOS", currency: "PUDOS",
"value": 0, fraction: 10000,
"fraction": 10000 value: 0,
},
feeWithdraw: {
currency: "PUDOS",
fraction: 10000,
value: 0,
}, },
denomPubHash: "dummy",
status: DenominationStatus.Unverified,
isOffered: true, isOffered: true,
exchangeBaseUrl: "https://exchange.example.com/", masterSig: "CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
stampExpireDeposit: "/Date(1851580381)/",
stampExpireLegal: "/Date(1567756381)/",
stampExpireWithdraw: "/Date(2482300381)/",
stampStart: "/Date(1473148381)/",
status: DenominationStatus.Unverified,
value: {
currency: "PUDOS",
fraction: 100000,
value: 0,
},
}; };
let denomInvalid1 = JSON.parse(JSON.stringify(denomValid1)); const denomInvalid1 = JSON.parse(JSON.stringify(denomValid1));
denomInvalid1.value.value += 1; denomInvalid1.value.value += 1;
test("string hashing", async t => { test("string hashing", async (t) => {
let crypto = new CryptoApi(); const crypto = new CryptoApi();
let s = await crypto.hashString("hello taler"); const s = await crypto.hashString("hello taler");
let sh = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR"; const sh = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
t.true(s == sh); t.true(s === sh);
t.pass(); t.pass();
}); });
test("precoin creation", async t => { test("precoin creation", async (t) => {
let crypto = new CryptoApi(); const crypto = new CryptoApi();
let {priv, pub} = await crypto.createEddsaKeypair(); const {priv, pub} = await crypto.createEddsaKeypair();
let r: ReserveRecord = { const r: ReserveRecord = {
reserve_pub: pub,
reserve_priv: priv,
hasPayback: false,
exchange_base_url: "https://example.com/exchange",
created: 0,
requested_amount: {currency: "PUDOS", value: 0, fraction: 0},
precoin_amount: {currency: "PUDOS", value: 0, fraction: 0},
current_amount: null,
confirmed: false, confirmed: false,
created: 0,
current_amount: null,
exchange_base_url: "https://example.com/exchange",
hasPayback: false,
last_query: null, last_query: null,
precoin_amount: {currency: "PUDOS", value: 0, fraction: 0},
requested_amount: {currency: "PUDOS", value: 0, fraction: 0},
reserve_priv: priv,
reserve_pub: pub,
}; };
let precoin = await crypto.createPreCoin(denomValid1, r); const precoin = await crypto.createPreCoin(denomValid1, r);
t.pass(); t.pass();
}); });
test("denom validation", async t => { test("denom validation", async (t) => {
let crypto = new CryptoApi(); const crypto = new CryptoApi();
let v: boolean; let v: boolean;
v = await crypto.isValidDenom(denomValid1, masterPub1); v = await crypto.isValidDenom(denomValid1, masterPub1);
t.true(v); t.true(v);

View File

@ -24,20 +24,22 @@
* Imports. * Imports.
*/ */
import { import {
PreCoinRecord,
CoinRecord,
ReserveRecord,
AmountJson, AmountJson,
CoinRecord,
DenominationRecord, DenominationRecord,
PaybackRequest,
RefreshSessionRecord,
WireFee,
PayCoinInfo, PayCoinInfo,
PaybackRequest,
PreCoinRecord,
RefreshSessionRecord,
ReserveRecord,
WireFee,
} from "../types"; } from "../types";
import { import {
OfferRecord,
CoinWithDenom, CoinWithDenom,
OfferRecord,
} from "../wallet"; } from "../wallet";
import * as timer from "../timer"; import * as timer from "../timer";
import { startWorker } from "./startWorker"; import { startWorker } from "./startWorker";
@ -76,8 +78,6 @@ interface WorkItem {
} }
/** /**
* Number of different priorities. Each priority p * Number of different priorities. Each priority p
* must be 0 <= p < NUM_PRIO. * must be 0 <= p < NUM_PRIO.
@ -97,34 +97,35 @@ export class CryptoApi {
* Start a worker (if not started) and set as busy. * Start a worker (if not started) and set as busy.
*/ */
wake<T>(ws: WorkerState, work: WorkItem): void { wake<T>(ws: WorkerState, work: WorkItem): void {
if (ws.currentWorkItem != null) { if (ws.currentWorkItem !== null) {
throw Error("assertion failed"); throw Error("assertion failed");
} }
ws.currentWorkItem = work; ws.currentWorkItem = work;
this.numBusy++; this.numBusy++;
if (!ws.w) { if (!ws.w) {
let w = startWorker(); const w = startWorker();
w.onmessage = (m: MessageEvent) => this.handleWorkerMessage(ws, m); w.onmessage = (m: MessageEvent) => this.handleWorkerMessage(ws, m);
w.onerror = (e: ErrorEvent) => this.handleWorkerError(ws, e); w.onerror = (e: ErrorEvent) => this.handleWorkerError(ws, e);
ws.w = w; ws.w = w;
} }
let msg: any = { const msg: any = {
operation: work.operation, args: work.args, args: work.args,
id: work.rpcId id: work.rpcId,
operation: work.operation,
}; };
this.resetWorkerTimeout(ws); this.resetWorkerTimeout(ws);
ws.w!.postMessage(msg); ws.w!.postMessage(msg);
} }
resetWorkerTimeout(ws: WorkerState) { resetWorkerTimeout(ws: WorkerState) {
if (ws.terminationTimerHandle != null) { if (ws.terminationTimerHandle !== null) {
ws.terminationTimerHandle.clear(); ws.terminationTimerHandle.clear();
ws.terminationTimerHandle = null; ws.terminationTimerHandle = null;
} }
let destroy = () => { const destroy = () => {
// terminate worker if it's idle // terminate worker if it's idle
if (ws.w && ws.currentWorkItem == null) { if (ws.w && ws.currentWorkItem === null) {
ws.w!.terminate(); ws.w!.terminate();
ws.w = null; ws.w = null;
} }
@ -146,7 +147,7 @@ export class CryptoApi {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
if (ws.currentWorkItem != null) { if (ws.currentWorkItem !== null) {
ws.currentWorkItem.reject(e); ws.currentWorkItem.reject(e);
ws.currentWorkItem = null; ws.currentWorkItem = null;
this.numBusy--; this.numBusy--;
@ -157,9 +158,9 @@ export class CryptoApi {
findWork(ws: WorkerState) { findWork(ws: WorkerState) {
// try to find more work for this worker // try to find more work for this worker
for (let i = 0; i < NUM_PRIO; i++) { for (let i = 0; i < NUM_PRIO; i++) {
let q = this.workQueues[NUM_PRIO - i - 1]; const q = this.workQueues[NUM_PRIO - i - 1];
if (q.length != 0) { if (q.length !== 0) {
let work: WorkItem = q.shift()!; const work: WorkItem = q.shift()!;
this.wake(ws, work); this.wake(ws, work);
return; return;
} }
@ -167,12 +168,12 @@ export class CryptoApi {
} }
handleWorkerMessage(ws: WorkerState, msg: MessageEvent) { handleWorkerMessage(ws: WorkerState, msg: MessageEvent) {
let id = msg.data.id; const id = msg.data.id;
if (typeof id !== "number") { if (typeof id !== "number") {
console.error("rpc id must be number"); console.error("rpc id must be number");
return; return;
} }
let currentWorkItem = ws.currentWorkItem; const currentWorkItem = ws.currentWorkItem;
ws.currentWorkItem = null; ws.currentWorkItem = null;
this.numBusy--; this.numBusy--;
this.findWork(ws); this.findWork(ws);
@ -180,7 +181,7 @@ export class CryptoApi {
console.error("unsolicited response from worker"); console.error("unsolicited response from worker");
return; return;
} }
if (id != currentWorkItem.rpcId) { if (id !== currentWorkItem.rpcId) {
console.error(`RPC with id ${id} has no registry entry`); console.error(`RPC with id ${id} has no registry entry`);
return; return;
} }
@ -191,6 +192,7 @@ export class CryptoApi {
let concurrency = 2; let concurrency = 2;
try { try {
// only works in the browser // only works in the browser
// tslint:disable-next-line:no-string-literal
concurrency = (navigator as any)["hardwareConcurrency"]; concurrency = (navigator as any)["hardwareConcurrency"];
} catch (e) { } catch (e) {
// ignore // ignore
@ -199,9 +201,9 @@ export class CryptoApi {
for (let i = 0; i < this.workers.length; i++) { for (let i = 0; i < this.workers.length; i++) {
this.workers[i] = { this.workers[i] = {
w: null,
terminationTimerHandle: null,
currentWorkItem: null, currentWorkItem: null,
terminationTimerHandle: null,
w: null,
}; };
} }
this.workQueues = []; this.workQueues = [];
@ -212,14 +214,14 @@ export class CryptoApi {
private doRpc<T>(operation: string, priority: number, private doRpc<T>(operation: string, priority: number,
...args: any[]): Promise<T> { ...args: any[]): Promise<T> {
let start = timer.performanceNow(); const start = timer.performanceNow();
let p = new Promise((resolve, reject) => { const p = new Promise((resolve, reject) => {
let rpcId = this.nextRpcId++; const rpcId = this.nextRpcId++;
let workItem: WorkItem = {operation, args, resolve, reject, rpcId}; const workItem: WorkItem = {operation, args, resolve, reject, rpcId};
if (this.numBusy == this.workers.length) { if (this.numBusy === this.workers.length) {
let q = this.workQueues[priority]; const q = this.workQueues[priority];
if (!q) { if (!q) {
throw Error("assertion failed"); throw Error("assertion failed");
} }
@ -227,9 +229,8 @@ export class CryptoApi {
return; return;
} }
for (let i = 0; i < this.workers.length; i++) { for (const ws of this.workers) {
let ws = this.workers[i]; if (ws.currentWorkItem !== null) {
if (ws.currentWorkItem != null) {
continue; continue;
} }

View File

@ -37,12 +37,11 @@ import {
ReserveRecord, ReserveRecord,
WireFee, WireFee,
} from "../types"; } from "../types";
import create = chrome.alarms.create;
import { import {
CoinWithDenom, CoinWithDenom,
OfferRecord, OfferRecord,
} from "../wallet"; } from "../wallet";
import * as native from "./emscInterface";
import { import {
Amount, Amount,
EddsaPublicKey, EddsaPublicKey,
@ -50,6 +49,7 @@ import {
HashContext, HashContext,
RefreshMeltCoinAffirmationPS, RefreshMeltCoinAffirmationPS,
} from "./emscInterface"; } from "./emscInterface";
import * as native from "./emscInterface";
namespace RpcFunctions { namespace RpcFunctions {
@ -60,18 +60,16 @@ namespace RpcFunctions {
*/ */
export function createPreCoin(denom: DenominationRecord, export function createPreCoin(denom: DenominationRecord,
reserve: ReserveRecord): PreCoinRecord { reserve: ReserveRecord): PreCoinRecord {
let reservePriv = new native.EddsaPrivateKey(); const reservePriv = new native.EddsaPrivateKey();
reservePriv.loadCrock(reserve.reserve_priv); reservePriv.loadCrock(reserve.reserve_priv);
let reservePub = new native.EddsaPublicKey(); const reservePub = new native.EddsaPublicKey();
reservePub.loadCrock(reserve.reserve_pub); reservePub.loadCrock(reserve.reserve_pub);
let denomPub = native.RsaPublicKey.fromCrock(denom.denomPub); const denomPub = native.RsaPublicKey.fromCrock(denom.denomPub);
let coinPriv = native.EddsaPrivateKey.create(); const coinPriv = native.EddsaPrivateKey.create();
let coinPub = coinPriv.getPublicKey(); const coinPub = coinPriv.getPublicKey();
let blindingFactor = native.RsaBlindingKeySecret.create(); const blindingFactor = native.RsaBlindingKeySecret.create();
let pubHash: native.HashCode = coinPub.hash(); const pubHash: native.HashCode = coinPub.hash();
let ev = native.rsaBlind(pubHash, const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
blindingFactor,
denomPub);
if (!ev) { if (!ev) {
throw Error("couldn't blind (malicious exchange key?)"); throw Error("couldn't blind (malicious exchange key?)");
@ -81,61 +79,61 @@ namespace RpcFunctions {
throw Error("Field fee_withdraw missing"); throw Error("Field fee_withdraw missing");
} }
let amountWithFee = new native.Amount(denom.value); const amountWithFee = new native.Amount(denom.value);
amountWithFee.add(new native.Amount(denom.feeWithdraw)); amountWithFee.add(new native.Amount(denom.feeWithdraw));
let withdrawFee = new native.Amount(denom.feeWithdraw); const withdrawFee = new native.Amount(denom.feeWithdraw);
// Signature // Signature
let withdrawRequest = new native.WithdrawRequestPS({ const withdrawRequest = new native.WithdrawRequestPS({
reserve_pub: reservePub,
amount_with_fee: amountWithFee.toNbo(), amount_with_fee: amountWithFee.toNbo(),
withdraw_fee: withdrawFee.toNbo(), h_coin_envelope: ev.hash(),
h_denomination_pub: denomPub.encode().hash(), h_denomination_pub: denomPub.encode().hash(),
h_coin_envelope: ev.hash() reserve_pub: reservePub,
withdraw_fee: withdrawFee.toNbo(),
}); });
var sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv); const sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
let preCoin: PreCoinRecord = { const preCoin: PreCoinRecord = {
reservePub: reservePub.toCrock(),
blindingKey: blindingFactor.toCrock(), blindingKey: blindingFactor.toCrock(),
coinPub: coinPub.toCrock(), coinEv: ev.toCrock(),
coinPriv: coinPriv.toCrock(), coinPriv: coinPriv.toCrock(),
coinPub: coinPub.toCrock(),
coinValue: denom.value,
denomPub: denomPub.encode().toCrock(), denomPub: denomPub.encode().toCrock(),
exchangeBaseUrl: reserve.exchange_base_url, exchangeBaseUrl: reserve.exchange_base_url,
reservePub: reservePub.toCrock(),
withdrawSig: sig.toCrock(), withdrawSig: sig.toCrock(),
coinEv: ev.toCrock(),
coinValue: denom.value
}; };
return preCoin; return preCoin;
} }
export function createPaybackRequest(coin: CoinRecord): PaybackRequest { export function createPaybackRequest(coin: CoinRecord): PaybackRequest {
let p = new native.PaybackRequestPS({ const p = new native.PaybackRequestPS({
coin_blind: native.RsaBlindingKeySecret.fromCrock(coin.blindingKey),
coin_pub: native.EddsaPublicKey.fromCrock(coin.coinPub), coin_pub: native.EddsaPublicKey.fromCrock(coin.coinPub),
h_denom_pub: native.RsaPublicKey.fromCrock(coin.denomPub).encode().hash(), h_denom_pub: native.RsaPublicKey.fromCrock(coin.denomPub).encode().hash(),
coin_blind: native.RsaBlindingKeySecret.fromCrock(coin.blindingKey),
}); });
let coinPriv = native.EddsaPrivateKey.fromCrock(coin.coinPriv); const coinPriv = native.EddsaPrivateKey.fromCrock(coin.coinPriv);
let coinSig = native.eddsaSign(p.toPurpose(), coinPriv); const coinSig = native.eddsaSign(p.toPurpose(), coinPriv);
let paybackRequest: PaybackRequest = { const paybackRequest: PaybackRequest = {
denom_pub: coin.denomPub,
denom_sig: coin.denomSig,
coin_blind_key_secret: coin.blindingKey, coin_blind_key_secret: coin.blindingKey,
coin_pub: coin.coinPub, coin_pub: coin.coinPub,
coin_sig: coinSig.toCrock(), coin_sig: coinSig.toCrock(),
denom_pub: coin.denomPub,
denom_sig: coin.denomSig,
}; };
return paybackRequest; return paybackRequest;
} }
export function isValidPaymentSignature(sig: string, contractHash: string, merchantPub: string): boolean { export function isValidPaymentSignature(sig: string, contractHash: string, merchantPub: string): boolean {
let p = new native.PaymentSignaturePS({ const p = new native.PaymentSignaturePS({
contract_hash: native.HashCode.fromCrock(contractHash), contract_hash: native.HashCode.fromCrock(contractHash),
}); });
let nativeSig = new native.EddsaSignature(); const nativeSig = new native.EddsaSignature();
nativeSig.loadCrock(sig); nativeSig.loadCrock(sig);
let nativePub = native.EddsaPublicKey.fromCrock(merchantPub); const nativePub = native.EddsaPublicKey.fromCrock(merchantPub);
return native.eddsaVerify(native.SignaturePurpose.MERCHANT_PAYMENT_OK, return native.eddsaVerify(native.SignaturePurpose.MERCHANT_PAYMENT_OK,
p.toPurpose(), p.toPurpose(),
nativeSig, nativeSig,
@ -143,17 +141,17 @@ namespace RpcFunctions {
} }
export function isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean { export function isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
let p = new native.MasterWireFeePS({ const p = new native.MasterWireFeePS({
closing_fee: (new native.Amount(wf.closingFee)).toNbo(),
end_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.endStamp),
h_wire_method: native.ByteArray.fromStringWithNull(type).hash(), h_wire_method: native.ByteArray.fromStringWithNull(type).hash(),
start_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.startStamp), start_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.startStamp),
end_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.endStamp),
wire_fee: (new native.Amount(wf.wireFee)).toNbo(), wire_fee: (new native.Amount(wf.wireFee)).toNbo(),
closing_fee: (new native.Amount(wf.closingFee)).toNbo(),
}); });
let nativeSig = new native.EddsaSignature(); const nativeSig = new native.EddsaSignature();
nativeSig.loadCrock(wf.sig); nativeSig.loadCrock(wf.sig);
let nativePub = native.EddsaPublicKey.fromCrock(masterPub); const nativePub = native.EddsaPublicKey.fromCrock(masterPub);
return native.eddsaVerify(native.SignaturePurpose.MASTER_WIRE_FEES, return native.eddsaVerify(native.SignaturePurpose.MASTER_WIRE_FEES,
p.toPurpose(), p.toPurpose(),
@ -164,26 +162,24 @@ namespace RpcFunctions {
export function isValidDenom(denom: DenominationRecord, export function isValidDenom(denom: DenominationRecord,
masterPub: string): boolean { masterPub: string): boolean {
let p = new native.DenominationKeyValidityPS({ const p = new native.DenominationKeyValidityPS({
master: native.EddsaPublicKey.fromCrock(masterPub), denom_hash: native.RsaPublicKey.fromCrock(denom.denomPub) .encode() .hash(),
denom_hash: native.RsaPublicKey.fromCrock(denom.denomPub)
.encode()
.hash(),
expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireLegal), expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireLegal),
expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireDeposit), expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireDeposit),
expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireWithdraw), expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireWithdraw),
start: native.AbsoluteTimeNbo.fromTalerString(denom.stampStart),
value: (new native.Amount(denom.value)).toNbo(),
fee_deposit: (new native.Amount(denom.feeDeposit)).toNbo(), fee_deposit: (new native.Amount(denom.feeDeposit)).toNbo(),
fee_refresh: (new native.Amount(denom.feeRefresh)).toNbo(), fee_refresh: (new native.Amount(denom.feeRefresh)).toNbo(),
fee_withdraw: (new native.Amount(denom.feeWithdraw)).toNbo(),
fee_refund: (new native.Amount(denom.feeRefund)).toNbo(), fee_refund: (new native.Amount(denom.feeRefund)).toNbo(),
fee_withdraw: (new native.Amount(denom.feeWithdraw)).toNbo(),
master: native.EddsaPublicKey.fromCrock(masterPub),
start: native.AbsoluteTimeNbo.fromTalerString(denom.stampStart),
value: (new native.Amount(denom.value)).toNbo(),
}); });
let nativeSig = new native.EddsaSignature(); const nativeSig = new native.EddsaSignature();
nativeSig.loadCrock(denom.masterSig); nativeSig.loadCrock(denom.masterSig);
let nativePub = native.EddsaPublicKey.fromCrock(masterPub); const nativePub = native.EddsaPublicKey.fromCrock(masterPub);
return native.eddsaVerify(native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY, return native.eddsaVerify(native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY,
p.toPurpose(), p.toPurpose(),
@ -201,10 +197,10 @@ namespace RpcFunctions {
export function rsaUnblind(sig: string, bk: string, pk: string): string { export function rsaUnblind(sig: string, bk: string, pk: string): string {
let denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig), const denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig),
native.RsaBlindingKeySecret.fromCrock(bk), native.RsaBlindingKeySecret.fromCrock(bk),
native.RsaPublicKey.fromCrock(pk)); native.RsaPublicKey.fromCrock(pk));
return denomSig.encode().toCrock() return denomSig.encode().toCrock();
} }
@ -214,21 +210,21 @@ namespace RpcFunctions {
*/ */
export function signDeposit(offer: OfferRecord, export function signDeposit(offer: OfferRecord,
cds: CoinWithDenom[]): PayCoinInfo { cds: CoinWithDenom[]): PayCoinInfo {
let ret: PayCoinInfo = []; const ret: PayCoinInfo = [];
let feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit); const feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit);
let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList).amount; let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList).amount;
// okay if saturates // okay if saturates
fees = Amounts.sub(fees, offer.contract.max_fee).amount; fees = Amounts.sub(fees, offer.contract.max_fee).amount;
let total = Amounts.add(fees, offer.contract.amount).amount; const total = Amounts.add(fees, offer.contract.amount).amount;
let amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency); const amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
let amountRemaining = new native.Amount(total); const amountRemaining = new native.Amount(total);
for (let cd of cds) { for (const cd of cds) {
let coinSpend: Amount; let coinSpend: Amount;
if (amountRemaining.value == 0 && amountRemaining.fraction == 0) { if (amountRemaining.value === 0 && amountRemaining.fraction === 0) {
break; break;
} }
@ -241,7 +237,7 @@ namespace RpcFunctions {
amountSpent.add(coinSpend); amountSpent.add(coinSpend);
amountRemaining.sub(coinSpend); amountRemaining.sub(coinSpend);
let feeDeposit: Amount = new native.Amount(cd.denom.feeDeposit); const feeDeposit: Amount = new native.Amount(cd.denom.feeDeposit);
// Give the merchant at least the deposit fee, otherwise it'll reject // Give the merchant at least the deposit fee, otherwise it'll reject
// the coin. // the coin.
@ -249,32 +245,32 @@ namespace RpcFunctions {
coinSpend = feeDeposit; coinSpend = feeDeposit;
} }
let newAmount = new native.Amount(cd.coin.currentAmount); const newAmount = new native.Amount(cd.coin.currentAmount);
newAmount.sub(coinSpend); newAmount.sub(coinSpend);
cd.coin.currentAmount = newAmount.toJson(); cd.coin.currentAmount = newAmount.toJson();
cd.coin.status = CoinStatus.TransactionPending; cd.coin.status = CoinStatus.TransactionPending;
let d = new native.DepositRequestPS({ const d = new native.DepositRequestPS({
h_contract: native.HashCode.fromCrock(offer.H_contract),
h_wire: native.HashCode.fromCrock(offer.contract.H_wire),
amount_with_fee: coinSpend.toNbo(), amount_with_fee: coinSpend.toNbo(),
coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub), coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub),
deposit_fee: new native.Amount(cd.denom.feeDeposit).toNbo(), deposit_fee: new native.Amount(cd.denom.feeDeposit).toNbo(),
h_contract: native.HashCode.fromCrock(offer.H_contract),
h_wire: native.HashCode.fromCrock(offer.contract.H_wire),
merchant: native.EddsaPublicKey.fromCrock(offer.contract.merchant_pub), merchant: native.EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
refund_deadline: native.AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline), refund_deadline: native.AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
timestamp: native.AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp), timestamp: native.AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
}); });
let coinSig = native.eddsaSign(d.toPurpose(), const coinSig = native.eddsaSign(d.toPurpose(),
native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv)) native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
.toCrock(); .toCrock();
let s: CoinPaySig = { const s: CoinPaySig = {
coin_sig: coinSig,
coin_pub: cd.coin.coinPub, coin_pub: cd.coin.coinPub,
ub_sig: cd.coin.denomSig, coin_sig: coinSig,
denom_pub: cd.coin.denomPub, denom_pub: cd.coin.denomPub,
f: coinSpend.toJson(), f: coinSpend.toJson(),
ub_sig: cd.coin.denomSig,
}; };
ret.push({sig: s, updatedCoin: cd.coin}); ret.push({sig: s, updatedCoin: cd.coin});
} }
@ -290,7 +286,7 @@ namespace RpcFunctions {
let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency); let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency);
for (let ncd of newCoinDenoms) { for (const ncd of newCoinDenoms) {
valueWithFee = Amounts.add(valueWithFee, valueWithFee = Amounts.add(valueWithFee,
ncd.value, ncd.value,
ncd.feeWithdraw).amount; ncd.feeWithdraw).amount;
@ -299,23 +295,23 @@ namespace RpcFunctions {
// melt fee // melt fee
valueWithFee = Amounts.add(valueWithFee, meltFee).amount; valueWithFee = Amounts.add(valueWithFee, meltFee).amount;
let sessionHc = new HashContext(); const sessionHc = new HashContext();
let transferPubs: string[] = []; const transferPubs: string[] = [];
let transferPrivs: string[] = []; const transferPrivs: string[] = [];
let preCoinsForGammas: RefreshPreCoinRecord[][] = []; const preCoinsForGammas: RefreshPreCoinRecord[][] = [];
for (let i = 0; i < kappa; i++) { for (let i = 0; i < kappa; i++) {
let t = native.EcdhePrivateKey.create(); const t = native.EcdhePrivateKey.create();
let pub = t.getPublicKey(); const pub = t.getPublicKey();
sessionHc.read(pub); sessionHc.read(pub);
transferPrivs.push(t.toCrock()); transferPrivs.push(t.toCrock());
transferPubs.push(pub.toCrock()); transferPubs.push(pub.toCrock());
} }
for (let i = 0; i < newCoinDenoms.length; i++) { for (const denom of newCoinDenoms) {
let r = native.RsaPublicKey.fromCrock(newCoinDenoms[i].denomPub); const r = native.RsaPublicKey.fromCrock(denom.denomPub);
sessionHc.read(r.encode()); sessionHc.read(r.encode());
} }
@ -323,31 +319,31 @@ namespace RpcFunctions {
sessionHc.read((new native.Amount(valueWithFee)).toNbo()); sessionHc.read((new native.Amount(valueWithFee)).toNbo());
for (let i = 0; i < kappa; i++) { for (let i = 0; i < kappa; i++) {
let preCoins: RefreshPreCoinRecord[] = []; const preCoins: RefreshPreCoinRecord[] = [];
for (let j = 0; j < newCoinDenoms.length; j++) { for (let j = 0; j < newCoinDenoms.length; j++) {
let transferPriv = native.EcdhePrivateKey.fromCrock(transferPrivs[i]); const transferPriv = native.EcdhePrivateKey.fromCrock(transferPrivs[i]);
let oldCoinPub = native.EddsaPublicKey.fromCrock(meltCoin.coinPub); const oldCoinPub = native.EddsaPublicKey.fromCrock(meltCoin.coinPub);
let transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub); const transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub);
let fresh = native.setupFreshCoin(transferSecret, j); const fresh = native.setupFreshCoin(transferSecret, j);
let coinPriv = fresh.priv; const coinPriv = fresh.priv;
let coinPub = coinPriv.getPublicKey(); const coinPub = coinPriv.getPublicKey();
let blindingFactor = fresh.blindingKey; const blindingFactor = fresh.blindingKey;
let pubHash: native.HashCode = coinPub.hash(); const pubHash: native.HashCode = coinPub.hash();
let denomPub = native.RsaPublicKey.fromCrock(newCoinDenoms[j].denomPub); const denomPub = native.RsaPublicKey.fromCrock(newCoinDenoms[j].denomPub);
let ev = native.rsaBlind(pubHash, const ev = native.rsaBlind(pubHash,
blindingFactor, blindingFactor,
denomPub); denomPub);
if (!ev) { if (!ev) {
throw Error("couldn't blind (malicious exchange key?)"); throw Error("couldn't blind (malicious exchange key?)");
} }
let preCoin: RefreshPreCoinRecord = { const preCoin: RefreshPreCoinRecord = {
blindingKey: blindingFactor.toCrock(), blindingKey: blindingFactor.toCrock(),
coinEv: ev.toCrock(), coinEv: ev.toCrock(),
publicKey: coinPub.toCrock(),
privateKey: coinPriv.toCrock(), privateKey: coinPriv.toCrock(),
publicKey: coinPub.toCrock(),
}; };
preCoins.push(preCoin); preCoins.push(preCoin);
sessionHc.read(ev); sessionHc.read(ev);
@ -355,40 +351,40 @@ namespace RpcFunctions {
preCoinsForGammas.push(preCoins); preCoinsForGammas.push(preCoins);
} }
let sessionHash = new HashCode(); const sessionHash = new HashCode();
sessionHash.alloc(); sessionHash.alloc();
sessionHc.finish(sessionHash); sessionHc.finish(sessionHash);
let confirmData = new RefreshMeltCoinAffirmationPS({ const confirmData = new RefreshMeltCoinAffirmationPS({
coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub),
amount_with_fee: (new Amount(valueWithFee)).toNbo(), amount_with_fee: (new Amount(valueWithFee)).toNbo(),
coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub),
melt_fee: (new Amount(meltFee)).toNbo(),
session_hash: sessionHash, session_hash: sessionHash,
melt_fee: (new Amount(meltFee)).toNbo()
}); });
let confirmSig: string = native.eddsaSign(confirmData.toPurpose(), const confirmSig: string = native.eddsaSign(confirmData.toPurpose(),
native.EddsaPrivateKey.fromCrock( native.EddsaPrivateKey.fromCrock(
meltCoin.coinPriv)).toCrock(); meltCoin.coinPriv)).toCrock();
let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency); let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency);
for (let denom of newCoinDenoms) { for (const denom of newCoinDenoms) {
valueOutput = Amounts.add(valueOutput, denom.value).amount; valueOutput = Amounts.add(valueOutput, denom.value).amount;
} }
let refreshSession: RefreshSessionRecord = { const refreshSession: RefreshSessionRecord = {
confirmSig,
exchangeBaseUrl,
finished: false,
hash: sessionHash.toCrock(),
meltCoinPub: meltCoin.coinPub, meltCoinPub: meltCoin.coinPub,
newDenoms: newCoinDenoms.map((d) => d.denomPub), newDenoms: newCoinDenoms.map((d) => d.denomPub),
confirmSig,
valueWithFee,
transferPubs,
preCoinsForGammas,
hash: sessionHash.toCrock(),
norevealIndex: undefined, norevealIndex: undefined,
exchangeBaseUrl, preCoinsForGammas,
transferPrivs, transferPrivs,
finished: false, transferPubs,
valueOutput, valueOutput,
valueWithFee,
}; };
return refreshSession; return refreshSession;
@ -408,24 +404,24 @@ namespace RpcFunctions {
} }
let worker: Worker = (self as any) as Worker; const worker: Worker = (self as any) as Worker;
worker.onmessage = (msg: MessageEvent) => { worker.onmessage = (msg: MessageEvent) => {
if (!Array.isArray(msg.data.args)) { if (!Array.isArray(msg.data.args)) {
console.error("args must be array"); console.error("args must be array");
return; return;
} }
if (typeof msg.data.id != "number") { if (typeof msg.data.id !== "number") {
console.error("RPC id must be number"); console.error("RPC id must be number");
} }
if (typeof msg.data.operation != "string") { if (typeof msg.data.operation !== "string") {
console.error("RPC operation must be string"); console.error("RPC operation must be string");
} }
let f = (RpcFunctions as any)[msg.data.operation]; const f = (RpcFunctions as any)[msg.data.operation];
if (!f) { if (!f) {
console.error(`unknown operation: '${msg.data.operation}'`); console.error(`unknown operation: '${msg.data.operation}'`);
return; return;
} }
let res = f(...msg.data.args); const res = f(...msg.data.args);
worker.postMessage({result: res, id: msg.data.id}); worker.postMessage({result: res, id: msg.data.id});
} };

View File

@ -1,101 +1,124 @@
/*
This file is part of TALER
(C) 2017 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
// tslint:disable:max-line-length
import {test} from "ava"; import {test} from "ava";
import * as native from "./emscInterface"; import * as native from "./emscInterface";
test("string hashing", t => { test("string hashing", (t) => {
let x = native.ByteArray.fromStringWithNull("hello taler"); const x = native.ByteArray.fromStringWithNull("hello taler");
let h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR" const h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
let hc = x.hash().toCrock(); const hc = x.hash().toCrock();
console.log(`# hc ${hc}`); console.log(`# hc ${hc}`);
t.true(h === hc, "must equal"); t.true(h === hc, "must equal");
t.pass(); t.pass();
}); });
test("signing", t => {
let x = native.ByteArray.fromStringWithNull("hello taler"); test("signing", (t) => {
let priv = native.EddsaPrivateKey.create(); const x = native.ByteArray.fromStringWithNull("hello taler");
let pub = priv.getPublicKey(); const priv = native.EddsaPrivateKey.create();
let purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x); const pub = priv.getPublicKey();
let sig = native.eddsaSign(purpose, priv); const purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
const sig = native.eddsaSign(purpose, priv);
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub)); t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
t.pass(); t.pass();
}); });
test("signing-fixed-data", t => {
let x = native.ByteArray.fromStringWithNull("hello taler"); test("signing-fixed-data", (t) => {
let purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x); const x = native.ByteArray.fromStringWithNull("hello taler");
const purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
const privStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90"; const privStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
const pubStr = "YHCZB442FQFJ0ET20MWA8YJ53M61EZGJ6QKV1KTJZMRNXDY45WT0"; const pubStr = "YHCZB442FQFJ0ET20MWA8YJ53M61EZGJ6QKV1KTJZMRNXDY45WT0";
const sigStr = "7V6XY4QGC1406GPMT305MZQ1HDCR7R0S5BP02GTGDQFPSXB6YD2YDN5ZS7NJQCNP61Y39MRHXNXQ1Z15JY4CJY4CPDA6CKQ3313WG38"; const sigStr = "7V6XY4QGC1406GPMT305MZQ1HDCR7R0S5BP02GTGDQFPSXB6YD2YDN5ZS7NJQCNP61Y39MRHXNXQ1Z15JY4CJY4CPDA6CKQ3313WG38";
let priv = native.EddsaPrivateKey.fromCrock(privStr); const priv = native.EddsaPrivateKey.fromCrock(privStr);
t.true(privStr == priv.toCrock()) t.true(privStr === priv.toCrock());
let pub = priv.getPublicKey(); const pub = priv.getPublicKey();
t.true(pubStr == pub.toCrock()); t.true(pubStr === pub.toCrock());
let sig = native.EddsaSignature.fromCrock(sigStr); const sig = native.EddsaSignature.fromCrock(sigStr);
t.true(sigStr == sig.toCrock()) t.true(sigStr === sig.toCrock());
let sig2 = native.eddsaSign(purpose, priv); const sig2 = native.eddsaSign(purpose, priv);
t.true(sig.toCrock() == sig2.toCrock()); t.true(sig.toCrock() === sig2.toCrock());
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub)); t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
t.pass(); t.pass();
}); });
const denomPubStr1 = "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30G9R64VK6HHS6MW42DSN8MVKJGHK6WR3CGT18MWMCDSM75138E1K8S0MADSQ68W34DHH6MW4CHA270W4CG9J6GW48DHG8MVK4E9S7523GEA56H0K4E1Q891KCCSG752KGC1M88VMCDSQ6D23CHHG8H33AGHG6MSK8GT26CRKAC1M64V3JCJ56CVKCC228MWMCHA26MS30H1J8MVKEDHJ70TMADHK892KJC1H60TKJDHM710KGGT584T38H9K851KCDHG60W30HJ28CT4CC1G8CR3JGJ28H236DJ28H330H9S890M2D9S8S14AGA369344GA36S248CHS70RKEDSS6MWKGDJ26D136GT465348CSS8S232CHM6GS34C9N8CS3GD9H60W36H1R8MSK2GSQ8MSM6C9R70SKCHHN6MW3ACJ28N0K2CA58RS3GCA26MV42G9P891KAG9Q8N0KGD9M850KEHJ16S130CA27124AE1G852KJCHR6S1KGDSJ8RTKED1S8RR3CCHP68W4CH9Q6GT34GT18GS36EA46N24AGSP6933GCHM60VMAE1S8GV3EHHN74W3GC1J651KEH9N8MSK0CSG6S2KEEA460R32C1M8D144GSR6RWKEC218S0KEGJ4611KEEA36CSKJC2564TM4CSJ6H230E1N74TM8C1P61342CSG60WKCGHH64VK2G9S8CRKAHHK88W30HJ388R3CH1Q6X2K2DHK8GSM4D1Q74WM4HA461146H9S6D33JDJ26D234C9Q6923ECSS60RM6CT46CSKCH1M6S13EH9J8S33GCSN4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00"; const denomPubStr1 = "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30G9R64VK6HHS6MW42DSN8MVKJGHK6WR3CGT18MWMCDSM75138E1K8S0MADSQ68W34DHH6MW4CHA270W4CG9J6GW48DHG8MVK4E9S7523GEA56H0K4E1Q891KCCSG752KGC1M88VMCDSQ6D23CHHG8H33AGHG6MSK8GT26CRKAC1M64V3JCJ56CVKCC228MWMCHA26MS30H1J8MVKEDHJ70TMADHK892KJC1H60TKJDHM710KGGT584T38H9K851KCDHG60W30HJ28CT4CC1G8CR3JGJ28H236DJ28H330H9S890M2D9S8S14AGA369344GA36S248CHS70RKEDSS6MWKGDJ26D136GT465348CSS8S232CHM6GS34C9N8CS3GD9H60W36H1R8MSK2GSQ8MSM6C9R70SKCHHN6MW3ACJ28N0K2CA58RS3GCA26MV42G9P891KAG9Q8N0KGD9M850KEHJ16S130CA27124AE1G852KJCHR6S1KGDSJ8RTKED1S8RR3CCHP68W4CH9Q6GT34GT18GS36EA46N24AGSP6933GCHM60VMAE1S8GV3EHHN74W3GC1J651KEH9N8MSK0CSG6S2KEEA460R32C1M8D144GSR6RWKEC218S0KEGJ4611KEEA36CSKJC2564TM4CSJ6H230E1N74TM8C1P61342CSG60WKCGHH64VK2G9S8CRKAHHK88W30HJ388R3CH1Q6X2K2DHK8GSM4D1Q74WM4HA461146H9S6D33JDJ26D234C9Q6923ECSS60RM6CT46CSKCH1M6S13EH9J8S33GCSN4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00";
test("rsa-encode", t => {
const pubHashStr = "JM63YM5X7X547164QJ3MGJZ4WDD47GEQR5DW5SH35G4JFZXEJBHE5JBNZM5K8XN5C4BRW25BE6GSVAYBF790G2BZZ13VW91D41S4DS0" test("rsa-encode", (t) => {
let denomPub = native.RsaPublicKey.fromCrock(denomPubStr1); const pubHashStr = "JM63YM5X7X547164QJ3MGJZ4WDD47GEQR5DW5SH35G4JFZXEJBHE5JBNZM5K8XN5C4BRW25BE6GSVAYBF790G2BZZ13VW91D41S4DS0";
let pubHash = denomPub.encode().hash(); const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1);
t.true(pubHashStr == pubHash.toCrock()); const pubHash = denomPub.encode().hash();
t.true(pubHashStr === pubHash.toCrock());
t.pass(); t.pass();
}); });
test("withdraw-request", t => { test("withdraw-request", (t) => {
const reservePrivStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90"; const reservePrivStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
const reservePriv = native.EddsaPrivateKey.fromCrock(reservePrivStr); const reservePriv = native.EddsaPrivateKey.fromCrock(reservePrivStr);
const reservePub = reservePriv.getPublicKey(); const reservePub = reservePriv.getPublicKey();
const amountWithFee = new native.Amount({currency: "KUDOS", value: 1, fraction: 10000}); const amountWithFee = new native.Amount({currency: "KUDOS", value: 1, fraction: 10000});
amountWithFee.add(new native.Amount({currency: "KUDOS", value: 0, fraction: 20000})); amountWithFee.add(new native.Amount({currency: "KUDOS", value: 0, fraction: 20000}));
const withdrawFee = new native.Amount({currency: "KUDOS", value: 0, fraction: 20000}) const withdrawFee = new native.Amount({currency: "KUDOS", value: 0, fraction: 20000});
const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1); const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1);
const ev = native.ByteArray.fromStringWithNull("hello, world"); const ev = native.ByteArray.fromStringWithNull("hello, world");
// Signature // Signature
let withdrawRequest = new native.WithdrawRequestPS({ const withdrawRequest = new native.WithdrawRequestPS({
reserve_pub: reservePub,
amount_with_fee: amountWithFee.toNbo(), amount_with_fee: amountWithFee.toNbo(),
withdraw_fee: withdrawFee.toNbo(), h_coin_envelope: ev.hash(),
h_denomination_pub: denomPub.encode().hash(), h_denomination_pub: denomPub.encode().hash(),
h_coin_envelope: ev.hash() reserve_pub: reservePub,
withdraw_fee: withdrawFee.toNbo(),
}); });
var sigStr = "AD3T8W44NV193J19RAN3NAJHPP6RVB0R3NWV7ZK5G8Q946YDK0B6F8YJBNRRBXSPVTKY31S7BVZPJFFTJJRMY61DH51X4JSXK677428"; const sigStr = "AD3T8W44NV193J19RAN3NAJHPP6RVB0R3NWV7ZK5G8Q946YDK0B6F8YJBNRRBXSPVTKY31S7BVZPJFFTJJRMY61DH51X4JSXK677428";
var sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv); const sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
t.true(native.eddsaVerify(native.SignaturePurpose.RESERVE_WITHDRAW, withdrawRequest.toPurpose(), sig, reservePub)); t.true(native.eddsaVerify(native.SignaturePurpose.RESERVE_WITHDRAW, withdrawRequest.toPurpose(), sig, reservePub));
t.true(sig.toCrock() == sigStr); t.true(sig.toCrock() === sigStr);
t.pass(); t.pass();
}); });
test("withdraw-request", t => {
test("withdraw-request", (t) => {
const a1 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000}); const a1 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000});
const a2 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000}); const a2 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000});
a1.add(a2); a1.add(a2);
let x = a1.toJson(); const x = a1.toJson();
t.true(x.currency == "KUDOS"); t.true(x.currency === "KUDOS");
t.true(x.fraction == 0); t.true(x.fraction === 0);
t.true(x.value == 3); t.true(x.value === 3);
t.pass(); t.pass();
}); });
test("ecdsa", t => { test("ecdsa", (t) => {
const priv = native.EcdsaPrivateKey.create(); const priv = native.EcdsaPrivateKey.create();
const pub1 = priv.getPublicKey(); const pub1 = priv.getPublicKey();
t.pass(); t.pass();
}); });
test("ecdhe", t => {
test("ecdhe", (t) => {
const priv = native.EcdhePrivateKey.create(); const priv = native.EcdhePrivateKey.create();
const pub = priv.getPublicKey(); const pub = priv.getPublicKey();
t.pass(); t.pass();

View File

@ -27,8 +27,8 @@
* Imports. * Imports.
*/ */
import {AmountJson} from "../types"; import {AmountJson} from "../types";
import {getLib, EmscFunGen} from "./emscLoader";
import {EmscFunGen, getLib} from "./emscLoader";
const emscLib = getLib(); const emscLib = getLib();
@ -51,7 +51,7 @@ const GNUNET_SYSERR = -1;
const getEmsc: EmscFunGen = (name: string, ret: any, argTypes: any[]) => { const getEmsc: EmscFunGen = (name: string, ret: any, argTypes: any[]) => {
return (...args: any[]) => { return (...args: any[]) => {
return emscLib.ccall(name, ret, argTypes, args); return emscLib.ccall(name, ret, argTypes, args);
} };
}; };
@ -59,84 +59,32 @@ const getEmsc: EmscFunGen = (name: string, ret: any, argTypes: any[]) => {
* Wrapped emscripten functions that do not allocate any memory. * Wrapped emscripten functions that do not allocate any memory.
*/ */
const emsc = { const emsc = {
amount_add: getEmsc("TALER_amount_add", "number", ["number", "number", "number"]),
amount_cmp: getEmsc("TALER_amount_cmp", "number", ["number", "number"]),
amount_get_zero: getEmsc("TALER_amount_get_zero", "number", ["string", "number"]),
amount_hton: getEmsc("TALER_amount_hton", "void", ["number", "number"]),
amount_normalize: getEmsc("TALER_amount_normalize", "void", ["number"]),
amount_ntoh: getEmsc("TALER_amount_ntoh", "void", ["number", "number"]),
amount_subtract: getEmsc("TALER_amount_subtract", "number", ["number", "number", "number"]),
ecdh_eddsa: getEmsc("GNUNET_CRYPTO_ecdh_eddsa", "number", ["number", "number", "number"]),
eddsa_sign: getEmsc("GNUNET_CRYPTO_eddsa_sign", "number", ["number", "number", "number"]),
eddsa_verify: getEmsc("GNUNET_CRYPTO_eddsa_verify", "number", ["number", "number", "number", "number"]),
free: (ptr: number) => emscLib._free(ptr), free: (ptr: number) => emscLib._free(ptr),
get_value: getEmsc("TALER_WR_get_value", get_currency: getEmsc("TALER_WR_get_currency", "string", ["number"]),
"number", get_fraction: getEmsc("TALER_WR_get_fraction", "number", ["number"]),
["number"]), get_value: getEmsc("TALER_WR_get_value", "number", ["number"]),
get_fraction: getEmsc("TALER_WR_get_fraction", hash: getEmsc("GNUNET_CRYPTO_hash", "void", ["number", "number", "number"]),
"number", hash_context_abort: getEmsc("GNUNET_CRYPTO_hash_context_abort", "void", ["number"]),
["number"]), hash_context_finish: getEmsc("GNUNET_CRYPTO_hash_context_finish", "void", ["number", "number"]),
get_currency: getEmsc("TALER_WR_get_currency", hash_context_read: getEmsc("GNUNET_CRYPTO_hash_context_read", "void", ["number", "number", "number"]),
"string", hash_create_random: getEmsc("GNUNET_CRYPTO_hash_create_random", "void", ["number", "number"]),
["number"]), memmove: getEmsc("memmove", "number", ["number", "number", "number"]),
amount_add: getEmsc("TALER_amount_add", random_block: getEmsc("GNUNET_CRYPTO_random_block", "void", ["number", "number", "number"]),
"number", rsa_blinding_key_destroy: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_free", "void", ["number"]),
["number", "number", "number"]), rsa_public_key_free: getEmsc("GNUNET_CRYPTO_rsa_public_key_free", "void", ["number"]),
amount_subtract: getEmsc("TALER_amount_subtract", rsa_signature_free: getEmsc("GNUNET_CRYPTO_rsa_signature_free", "void", ["number"]),
"number", setup_fresh_coin: getEmsc( "TALER_setup_fresh_coin", "void", ["number", "number", "number"]),
["number", "number", "number"]), string_to_data: getEmsc("GNUNET_STRINGS_string_to_data", "number", ["number", "number", "number", "number"]),
amount_normalize: getEmsc("TALER_amount_normalize",
"void",
["number"]),
amount_get_zero: getEmsc("TALER_amount_get_zero",
"number",
["string", "number"]),
amount_cmp: getEmsc("TALER_amount_cmp",
"number",
["number", "number"]),
amount_hton: getEmsc("TALER_amount_hton",
"void",
["number", "number"]),
amount_ntoh: getEmsc("TALER_amount_ntoh",
"void",
["number", "number"]),
hash: getEmsc("GNUNET_CRYPTO_hash",
"void",
["number", "number", "number"]),
memmove: getEmsc("memmove",
"number",
["number", "number", "number"]),
rsa_public_key_free: getEmsc("GNUNET_CRYPTO_rsa_public_key_free",
"void",
["number"]),
rsa_signature_free: getEmsc("GNUNET_CRYPTO_rsa_signature_free",
"void",
["number"]),
string_to_data: getEmsc("GNUNET_STRINGS_string_to_data",
"number",
["number", "number", "number", "number"]),
eddsa_sign: getEmsc("GNUNET_CRYPTO_eddsa_sign",
"number",
["number", "number", "number"]),
eddsa_verify: getEmsc("GNUNET_CRYPTO_eddsa_verify",
"number",
["number", "number", "number", "number"]),
hash_create_random: getEmsc("GNUNET_CRYPTO_hash_create_random",
"void",
["number", "number"]),
rsa_blinding_key_destroy: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_free",
"void",
["number"]),
random_block: getEmsc("GNUNET_CRYPTO_random_block",
"void",
["number", "number", "number"]),
hash_context_abort: getEmsc("GNUNET_CRYPTO_hash_context_abort",
"void",
["number"]),
hash_context_read: getEmsc("GNUNET_CRYPTO_hash_context_read",
"void",
["number", "number", "number"]),
hash_context_finish: getEmsc("GNUNET_CRYPTO_hash_context_finish",
"void",
["number", "number"]),
ecdh_eddsa: getEmsc("GNUNET_CRYPTO_ecdh_eddsa",
"number",
["number", "number", "number"]),
setup_fresh_coin: getEmsc(
"TALER_setup_fresh_coin",
"void",
["number", "number", "number"]),
}; };
@ -144,64 +92,26 @@ const emsc = {
* Emscripten functions that allocate memory. * Emscripten functions that allocate memory.
*/ */
const emscAlloc = { const emscAlloc = {
get_amount: getEmsc("TALER_WRALL_get_amount", data_to_string_alloc: getEmsc("GNUNET_STRINGS_data_to_string_alloc", "number", ["number", "number"]),
"number", ecdhe_key_create: getEmsc("GNUNET_CRYPTO_ecdhe_key_create", "number", []),
["number", "number", "number", "string"]), ecdhe_public_key_from_private: getEmsc( "TALER_WRALL_ecdhe_public_key_from_private", "number", ["number"]),
eddsa_key_create: getEmsc("GNUNET_CRYPTO_eddsa_key_create", ecdsa_key_create: getEmsc("GNUNET_CRYPTO_ecdsa_key_create", "number", []),
"number", []), ecdsa_public_key_from_private: getEmsc( "TALER_WRALL_ecdsa_public_key_from_private", "number", ["number"]),
ecdsa_key_create: getEmsc("GNUNET_CRYPTO_ecdsa_key_create", eddsa_key_create: getEmsc("GNUNET_CRYPTO_eddsa_key_create", "number", []),
"number", []), eddsa_public_key_from_private: getEmsc( "TALER_WRALL_eddsa_public_key_from_private", "number", ["number"]),
ecdhe_key_create: getEmsc("GNUNET_CRYPTO_ecdhe_key_create", get_amount: getEmsc("TALER_WRALL_get_amount", "number", ["number", "number", "number", "string"]),
"number", []), hash_context_start: getEmsc("GNUNET_CRYPTO_hash_context_start", "number", []),
eddsa_public_key_from_private: getEmsc(
"TALER_WRALL_eddsa_public_key_from_private",
"number",
["number"]),
ecdsa_public_key_from_private: getEmsc(
"TALER_WRALL_ecdsa_public_key_from_private",
"number",
["number"]),
ecdhe_public_key_from_private: getEmsc(
"TALER_WRALL_ecdhe_public_key_from_private",
"number",
["number"]),
data_to_string_alloc: getEmsc("GNUNET_STRINGS_data_to_string_alloc",
"number",
["number", "number"]),
purpose_create: getEmsc("TALER_WRALL_purpose_create",
"number",
["number", "number", "number"]),
rsa_blind: getEmsc("GNUNET_CRYPTO_rsa_blind",
"number",
["number", "number", "number", "number", "number"]),
rsa_blinding_key_create: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_create",
"number",
["number"]),
rsa_blinding_key_encode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_encode",
"number",
["number", "number"]),
rsa_signature_encode: getEmsc("GNUNET_CRYPTO_rsa_signature_encode",
"number",
["number", "number"]),
rsa_blinding_key_decode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_decode",
"number",
["number", "number"]),
rsa_public_key_decode: getEmsc("GNUNET_CRYPTO_rsa_public_key_decode",
"number",
["number", "number"]),
rsa_signature_decode: getEmsc("GNUNET_CRYPTO_rsa_signature_decode",
"number",
["number", "number"]),
rsa_public_key_encode: getEmsc("GNUNET_CRYPTO_rsa_public_key_encode",
"number",
["number", "number"]),
rsa_unblind: getEmsc("GNUNET_CRYPTO_rsa_unblind",
"number",
["number", "number", "number"]),
hash_context_start: getEmsc("GNUNET_CRYPTO_hash_context_start",
"number",
[]),
malloc: (size: number) => emscLib._malloc(size), malloc: (size: number) => emscLib._malloc(size),
purpose_create: getEmsc("TALER_WRALL_purpose_create", "number", ["number", "number", "number"]),
rsa_blind: getEmsc("GNUNET_CRYPTO_rsa_blind", "number", ["number", "number", "number", "number", "number"]),
rsa_blinding_key_create: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_create", "number", ["number"]),
rsa_blinding_key_decode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_decode", "number", ["number", "number"]),
rsa_blinding_key_encode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_encode", "number", ["number", "number"]),
rsa_public_key_decode: getEmsc("GNUNET_CRYPTO_rsa_public_key_decode", "number", ["number", "number"]),
rsa_public_key_encode: getEmsc("GNUNET_CRYPTO_rsa_public_key_encode", "number", ["number", "number"]),
rsa_signature_decode: getEmsc("GNUNET_CRYPTO_rsa_signature_decode", "number", ["number", "number"]),
rsa_signature_encode: getEmsc("GNUNET_CRYPTO_rsa_signature_encode", "number", ["number", "number"]),
rsa_unblind: getEmsc("GNUNET_CRYPTO_rsa_unblind", "number", ["number", "number", "number"]),
}; };
@ -226,7 +136,7 @@ export enum SignaturePurpose {
export enum RandomQuality { export enum RandomQuality {
WEAK = 0, WEAK = 0,
STRONG = 1, STRONG = 1,
NONCE = 2 NONCE = 2,
} }
@ -301,8 +211,8 @@ abstract class MallocArenaObject implements ArenaObject {
constructor(arena?: Arena) { constructor(arena?: Arena) {
if (!arena) { if (!arena) {
if (arenaStack.length == 0) { if (arenaStack.length === 0) {
throw Error("No arena available") throw Error("No arena available");
} }
arena = arenaStack[arenaStack.length - 1]; arena = arenaStack[arenaStack.length - 1];
} }
@ -349,7 +259,7 @@ interface Arena {
* Arena that must be manually destroyed. * Arena that must be manually destroyed.
*/ */
class SimpleArena implements Arena { class SimpleArena implements Arena {
heap: Array<ArenaObject>; heap: ArenaObject[];
constructor() { constructor() {
this.heap = []; this.heap = [];
@ -360,10 +270,10 @@ class SimpleArena implements Arena {
} }
destroy() { destroy() {
for (let obj of this.heap) { for (const obj of this.heap) {
obj.destroy(); obj.destroy();
} }
this.heap = [] this.heap = [];
} }
} }
@ -396,7 +306,7 @@ class SyncArena extends SimpleArena {
} }
} }
let arenaStack: Arena[] = []; const arenaStack: Arena[] = [];
arenaStack.push(new SyncArena()); arenaStack.push(new SyncArena());
@ -417,9 +327,9 @@ export class Amount extends MallocArenaObject {
} }
static getZero(currency: string, a?: Arena): Amount { static getZero(currency: string, a?: Arena): Amount {
let am = new Amount(undefined, a); const am = new Amount(undefined, a);
let r = emsc.amount_get_zero(currency, am.nativePtr); const r = emsc.amount_get_zero(currency, am.nativePtr);
if (r != GNUNET_OK) { if (r !== GNUNET_OK) {
throw Error("invalid currency"); throw Error("invalid currency");
} }
return am; return am;
@ -427,7 +337,7 @@ export class Amount extends MallocArenaObject {
toNbo(a?: Arena): AmountNbo { toNbo(a?: Arena): AmountNbo {
let x = new AmountNbo(a); const x = new AmountNbo(a);
x.alloc(); x.alloc();
emsc.amount_hton(x.nativePtr, this.nativePtr); emsc.amount_hton(x.nativePtr, this.nativePtr);
return x; return x;
@ -445,15 +355,15 @@ export class Amount extends MallocArenaObject {
return emsc.get_fraction(this.nativePtr); return emsc.get_fraction(this.nativePtr);
} }
get currency(): String { get currency(): string {
return emsc.get_currency(this.nativePtr); return emsc.get_currency(this.nativePtr);
} }
toJson(): AmountJson { toJson(): AmountJson {
return { return {
value: emsc.get_value(this.nativePtr), currency: emsc.get_currency(this.nativePtr),
fraction: emsc.get_fraction(this.nativePtr), fraction: emsc.get_fraction(this.nativePtr),
currency: emsc.get_currency(this.nativePtr) value: emsc.get_value(this.nativePtr),
}; };
} }
@ -461,7 +371,7 @@ export class Amount extends MallocArenaObject {
* Add an amount to this amount. * Add an amount to this amount.
*/ */
add(a: Amount) { add(a: Amount) {
let res = emsc.amount_add(this.nativePtr, a.nativePtr, this.nativePtr); const res = emsc.amount_add(this.nativePtr, a.nativePtr, this.nativePtr);
if (res < 1) { if (res < 1) {
// Overflow // Overflow
return false; return false;
@ -474,8 +384,8 @@ export class Amount extends MallocArenaObject {
*/ */
sub(a: Amount) { sub(a: Amount) {
// this = this - a // this = this - a
let res = emsc.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr); const res = emsc.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr);
if (res == 0) { if (res === 0) {
// Underflow // Underflow
return false; return false;
} }
@ -503,10 +413,10 @@ export class Amount extends MallocArenaObject {
* Count the UTF-8 characters in a JavaScript string. * Count the UTF-8 characters in a JavaScript string.
*/ */
function countUtf8Bytes(str: string): number { function countUtf8Bytes(str: string): number {
var s = str.length; let s = str.length;
// JavaScript strings are UTF-16 arrays // JavaScript strings are UTF-16 arrays
for (let i = str.length - 1; i >= 0; i--) { for (let i = str.length - 1; i >= 0; i--) {
var code = str.charCodeAt(i); const code = str.charCodeAt(i);
if (code > 0x7f && code <= 0x7ff) { if (code > 0x7f && code <= 0x7ff) {
// We need an extra byte in utf-8 here // We need an extra byte in utf-8 here
s++; s++;
@ -540,8 +450,8 @@ abstract class PackedArenaObject extends MallocArenaObject {
} }
toCrock(): string { toCrock(): string {
var d = emscAlloc.data_to_string_alloc(this.nativePtr, this.size()); const d = emscAlloc.data_to_string_alloc(this.nativePtr, this.size());
var s = emscLib.Pointer_stringify(d); const s = emscLib.Pointer_stringify(d);
emsc.free(d); emsc.free(d);
return s; return s;
} }
@ -557,8 +467,8 @@ abstract class PackedArenaObject extends MallocArenaObject {
this.alloc(); this.alloc();
// We need to get the javascript string // We need to get the javascript string
// to the emscripten heap first. // to the emscripten heap first.
let buf = ByteArray.fromStringWithNull(s); const buf = ByteArray.fromStringWithNull(s);
let res = emsc.string_to_data(buf.nativePtr, const res = emsc.string_to_data(buf.nativePtr,
s.length, s.length,
this.nativePtr, this.nativePtr,
this.size()); this.size());
@ -576,20 +486,20 @@ abstract class PackedArenaObject extends MallocArenaObject {
} }
hash(): HashCode { hash(): HashCode {
var x = new HashCode(); const x = new HashCode();
x.alloc(); x.alloc();
emsc.hash(this.nativePtr, this.size(), x.nativePtr); emsc.hash(this.nativePtr, this.size(), x.nativePtr);
return x; return x;
} }
hexdump() { hexdump() {
let bytes: string[] = []; const bytes: string[] = [];
for (let i = 0; i < this.size(); i++) { for (let i = 0; i < this.size(); i++) {
let b = emscLib.getValue(this.nativePtr + i, "i8"); let b = emscLib.getValue(this.nativePtr + i, "i8");
b = (b + 256) % 256; b = (b + 256) % 256;
bytes.push("0".concat(b.toString(16)).slice(-2)); bytes.push("0".concat(b.toString(16)).slice(-2));
} }
let lines: string[] = []; const lines: string[] = [];
for (let i = 0; i < bytes.length; i += 8) { for (let i = 0; i < bytes.length; i += 8) {
lines.push(bytes.slice(i, i + 8).join(",")); lines.push(bytes.slice(i, i + 8).join(","));
} }
@ -607,10 +517,10 @@ export class AmountNbo extends PackedArenaObject {
} }
toJson(): any { toJson(): any {
let a = new SimpleArena(); const a = new SimpleArena();
let am = new Amount(undefined, a); const am = new Amount(undefined, a);
am.fromNbo(this); am.fromNbo(this);
let json = am.toJson(); const json = am.toJson();
a.destroy(); a.destroy();
return json; return json;
} }
@ -621,7 +531,7 @@ export class AmountNbo extends PackedArenaObject {
* Create a packed arena object from the base32 crockford encoding. * Create a packed arena object from the base32 crockford encoding.
*/ */
function fromCrock<T extends PackedArenaObject>(s: string, ctor: Ctor<T>): T { function fromCrock<T extends PackedArenaObject>(s: string, ctor: Ctor<T>): T {
let x: T = new ctor(); const x: T = new ctor();
x.alloc(); x.alloc();
x.loadCrock(s); x.loadCrock(s);
return x; return x;
@ -632,9 +542,11 @@ function fromCrock<T extends PackedArenaObject>(s: string, ctor: Ctor<T>): T {
* Create a packed arena object from the base32 crockford encoding for objects * Create a packed arena object from the base32 crockford encoding for objects
* that have a special decoding function. * that have a special decoding function.
*/ */
function fromCrockDecoded<T extends MallocArenaObject>(s: string, ctor: Ctor<T>, decodeFn: (p: number, s: number) => number): T { function fromCrockDecoded<T extends MallocArenaObject>(s: string,
let obj = new ctor(); ctor: Ctor<T>,
let buf = ByteArray.fromCrock(s); decodeFn: (p: number, s: number) => number): T {
const obj = new ctor();
const buf = ByteArray.fromCrock(s);
obj.nativePtr = decodeFn(buf.nativePtr, buf.size()); obj.nativePtr = decodeFn(buf.nativePtr, buf.size());
buf.destroy(); buf.destroy();
return obj; return obj;
@ -645,10 +557,10 @@ function fromCrockDecoded<T extends MallocArenaObject>(s: string, ctor: Ctor<T>,
* Encode an object using a special encoding function. * Encode an object using a special encoding function.
*/ */
function encode<T extends MallocArenaObject>(obj: T, encodeFn: any, arena?: Arena): ByteArray { function encode<T extends MallocArenaObject>(obj: T, encodeFn: any, arena?: Arena): ByteArray {
let ptr = emscAlloc.malloc(PTR_SIZE); const ptr = emscAlloc.malloc(PTR_SIZE);
let len = encodeFn(obj.nativePtr, ptr); const len = encodeFn(obj.nativePtr, ptr);
let res = new ByteArray(len, undefined, arena); const res = new ByteArray(len, undefined, arena);
res.nativePtr = emscLib.getValue(ptr, '*'); res.nativePtr = emscLib.getValue(ptr, "*");
emsc.free(ptr); emsc.free(ptr);
return res; return res;
} }
@ -659,7 +571,7 @@ function encode<T extends MallocArenaObject>(obj: T, encodeFn: any, arena?: Aren
*/ */
export class EddsaPrivateKey extends PackedArenaObject { export class EddsaPrivateKey extends PackedArenaObject {
static create(a?: Arena): EddsaPrivateKey { static create(a?: Arena): EddsaPrivateKey {
let obj = new EddsaPrivateKey(a); const obj = new EddsaPrivateKey(a);
obj.nativePtr = emscAlloc.eddsa_key_create(); obj.nativePtr = emscAlloc.eddsa_key_create();
return obj; return obj;
} }
@ -669,7 +581,7 @@ export class EddsaPrivateKey extends PackedArenaObject {
} }
getPublicKey(a?: Arena): EddsaPublicKey { getPublicKey(a?: Arena): EddsaPublicKey {
let obj = new EddsaPublicKey(a); const obj = new EddsaPublicKey(a);
obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr); obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr);
return obj; return obj;
} }
@ -682,7 +594,7 @@ export class EddsaPrivateKey extends PackedArenaObject {
export class EcdsaPrivateKey extends PackedArenaObject { export class EcdsaPrivateKey extends PackedArenaObject {
static create(a?: Arena): EcdsaPrivateKey { static create(a?: Arena): EcdsaPrivateKey {
let obj = new EcdsaPrivateKey(a); const obj = new EcdsaPrivateKey(a);
obj.nativePtr = emscAlloc.ecdsa_key_create(); obj.nativePtr = emscAlloc.ecdsa_key_create();
return obj; return obj;
} }
@ -692,7 +604,7 @@ export class EcdsaPrivateKey extends PackedArenaObject {
} }
getPublicKey(a?: Arena): EcdsaPublicKey { getPublicKey(a?: Arena): EcdsaPublicKey {
let obj = new EcdsaPublicKey(a); const obj = new EcdsaPublicKey(a);
obj.nativePtr = emscAlloc.ecdsa_public_key_from_private(this.nativePtr); obj.nativePtr = emscAlloc.ecdsa_public_key_from_private(this.nativePtr);
return obj; return obj;
} }
@ -705,7 +617,7 @@ export class EcdsaPrivateKey extends PackedArenaObject {
export class EcdhePrivateKey extends PackedArenaObject { export class EcdhePrivateKey extends PackedArenaObject {
static create(a?: Arena): EcdhePrivateKey { static create(a?: Arena): EcdhePrivateKey {
let obj = new EcdhePrivateKey(a); const obj = new EcdhePrivateKey(a);
obj.nativePtr = emscAlloc.ecdhe_key_create(); obj.nativePtr = emscAlloc.ecdhe_key_create();
return obj; return obj;
} }
@ -715,7 +627,7 @@ export class EcdhePrivateKey extends PackedArenaObject {
} }
getPublicKey(a?: Arena): EcdhePublicKey { getPublicKey(a?: Arena): EcdhePublicKey {
let obj = new EcdhePublicKey(a); const obj = new EcdhePublicKey(a);
obj.nativePtr = emscAlloc.ecdhe_public_key_from_private(this.nativePtr); obj.nativePtr = emscAlloc.ecdhe_public_key_from_private(this.nativePtr);
return obj; return obj;
} }
@ -730,7 +642,7 @@ export class EcdhePrivateKey extends PackedArenaObject {
* Constructor for a given type. * Constructor for a given type.
*/ */
interface Ctor<T> { interface Ctor<T> {
new(): T new(): T;
} }
@ -774,7 +686,7 @@ export class RsaBlindingKeySecret extends PackedArenaObject {
* Create a random blinding key secret. * Create a random blinding key secret.
*/ */
static create(a?: Arena): RsaBlindingKeySecret { static create(a?: Arena): RsaBlindingKeySecret {
let o = new RsaBlindingKeySecret(a); const o = new RsaBlindingKeySecret(a);
o.alloc(); o.alloc();
o.randomize(); o.randomize();
return o; return o;
@ -821,16 +733,16 @@ export class ByteArray extends PackedArenaObject {
static fromStringWithoutNull(s: string, a?: Arena): ByteArray { static fromStringWithoutNull(s: string, a?: Arena): ByteArray {
// UTF-8 bytes, including 0-terminator // UTF-8 bytes, including 0-terminator
let terminatedByteLength = countUtf8Bytes(s) + 1; const terminatedByteLength = countUtf8Bytes(s) + 1;
let hstr = emscAlloc.malloc(terminatedByteLength); const hstr = emscAlloc.malloc(terminatedByteLength);
emscLib.stringToUTF8(s, hstr, terminatedByteLength); emscLib.stringToUTF8(s, hstr, terminatedByteLength);
return new ByteArray(terminatedByteLength - 1, hstr, a); return new ByteArray(terminatedByteLength - 1, hstr, a);
} }
static fromStringWithNull(s: string, a?: Arena): ByteArray { static fromStringWithNull(s: string, a?: Arena): ByteArray {
// UTF-8 bytes, including 0-terminator // UTF-8 bytes, including 0-terminator
let terminatedByteLength = countUtf8Bytes(s) + 1; const terminatedByteLength = countUtf8Bytes(s) + 1;
let hstr = emscAlloc.malloc(terminatedByteLength); const hstr = emscAlloc.malloc(terminatedByteLength);
emscLib.stringToUTF8(s, hstr, terminatedByteLength); emscLib.stringToUTF8(s, hstr, terminatedByteLength);
return new ByteArray(terminatedByteLength, hstr, a); return new ByteArray(terminatedByteLength, hstr, a);
} }
@ -838,14 +750,14 @@ export class ByteArray extends PackedArenaObject {
static fromCrock(s: string, a?: Arena): ByteArray { static fromCrock(s: string, a?: Arena): ByteArray {
// this one is a bit more complicated than the other fromCrock functions, // this one is a bit more complicated than the other fromCrock functions,
// since we don't have a fixed size // since we don't have a fixed size
let byteLength = countUtf8Bytes(s); const byteLength = countUtf8Bytes(s);
let hstr = emscAlloc.malloc(byteLength + 1); const hstr = emscAlloc.malloc(byteLength + 1);
emscLib.stringToUTF8(s, hstr, byteLength + 1); emscLib.stringToUTF8(s, hstr, byteLength + 1);
let decodedLen = Math.floor((byteLength * 5) / 8); const decodedLen = Math.floor((byteLength * 5) / 8);
let ba = new ByteArray(decodedLen, undefined, a); const ba = new ByteArray(decodedLen, undefined, a);
let res = emsc.string_to_data(hstr, byteLength, ba.nativePtr, decodedLen); const res = emsc.string_to_data(hstr, byteLength, ba.nativePtr, decodedLen);
emsc.free(hstr); emsc.free(hstr);
if (res != GNUNET_OK) { if (res !== GNUNET_OK) {
throw Error("decoding failed"); throw Error("decoding failed");
} }
return ba; return ba;
@ -877,60 +789,60 @@ export class EccSignaturePurpose extends PackedArenaObject {
abstract class SignatureStruct { abstract class SignatureStruct {
abstract fieldTypes(): Array<any>; abstract fieldTypes(): any[];
abstract purpose(): SignaturePurpose; abstract purpose(): SignaturePurpose;
private members: any = {}; private members: any = {};
constructor(x: { [name: string]: any }) { constructor(x: { [name: string]: any }) {
for (let k in x) { for (const k in x) {
this.set(k, x[k]); this.set(k, x[k]);
} }
} }
toPurpose(a?: Arena): EccSignaturePurpose { toPurpose(a?: Arena): EccSignaturePurpose {
let totalSize = 0; let totalSize = 0;
for (let f of this.fieldTypes()) { for (const f of this.fieldTypes()) {
let name = f[0]; const name = f[0];
let member = this.members[name]; const member = this.members[name];
if (!member) { if (!member) {
throw Error(`Member ${name} not set`); throw Error(`Member ${name} not set`);
} }
totalSize += member.size(); totalSize += member.size();
} }
let buf = emscAlloc.malloc(totalSize); const buf = emscAlloc.malloc(totalSize);
let ptr = buf; let ptr = buf;
for (let f of this.fieldTypes()) { for (const f of this.fieldTypes()) {
let name = f[0]; const name = f[0];
let member = this.members[name]; const member = this.members[name];
let size = member.size(); const size = member.size();
emsc.memmove(ptr, member.nativePtr, size); emsc.memmove(ptr, member.nativePtr, size);
ptr += size; ptr += size;
} }
let ba = new ByteArray(totalSize, buf, a); const ba = new ByteArray(totalSize, buf, a);
return new EccSignaturePurpose(this.purpose(), ba); return new EccSignaturePurpose(this.purpose(), ba);
} }
toJson() { toJson() {
let res: any = {}; const res: any = {};
for (let f of this.fieldTypes()) { for (const f of this.fieldTypes()) {
let name = f[0]; const name = f[0];
let member = this.members[name]; const member = this.members[name];
if (!member) { if (!member) {
throw Error(`Member ${name} not set`); throw Error(`Member ${name} not set`);
} }
res[name] = member.toJson(); res[name] = member.toJson();
} }
res["purpose"] = this.purpose(); res.purpose = this.purpose();
return res; return res;
} }
protected set(name: string, value: PackedArenaObject) { protected set(name: string, value: PackedArenaObject) {
let typemap: any = {}; const typemap: any = {};
for (let f of this.fieldTypes()) { for (const f of this.fieldTypes()) {
typemap[f[0]] = f[1]; typemap[f[0]] = f[1];
} }
if (!(name in typemap)) { if (!(name in typemap)) {
@ -969,7 +881,7 @@ export class WithdrawRequestPS extends SignatureStruct {
["amount_with_fee", AmountNbo], ["amount_with_fee", AmountNbo],
["withdraw_fee", AmountNbo], ["withdraw_fee", AmountNbo],
["h_denomination_pub", HashCode], ["h_denomination_pub", HashCode],
["h_coin_envelope", HashCode] ["h_coin_envelope", HashCode],
]; ];
} }
} }
@ -1023,7 +935,7 @@ export class RefreshMeltCoinAffirmationPS extends SignatureStruct {
["session_hash", HashCode], ["session_hash", HashCode],
["amount_with_fee", AmountNbo], ["amount_with_fee", AmountNbo],
["melt_fee", AmountNbo], ["melt_fee", AmountNbo],
["coin_pub", EddsaPublicKey] ["coin_pub", EddsaPublicKey],
]; ];
} }
} }
@ -1060,21 +972,21 @@ export class MasterWireFeePS extends SignatureStruct {
export class AbsoluteTimeNbo extends PackedArenaObject { export class AbsoluteTimeNbo extends PackedArenaObject {
static fromTalerString(s: string): AbsoluteTimeNbo { static fromTalerString(s: string): AbsoluteTimeNbo {
let x = new AbsoluteTimeNbo(); const x = new AbsoluteTimeNbo();
x.alloc(); x.alloc();
let r = /Date\(([0-9]+)\)/; const r = /Date\(([0-9]+)\)/;
let m = r.exec(s); const m = r.exec(s);
if (!m || m.length != 2) { if (!m || m.length !== 2) {
throw Error(); throw Error();
} }
let n = parseInt(m[1]) * 1000000; const n = parseInt(m[1], 10) * 1000000;
// XXX: This only works up to 54 bit numbers. // XXX: This only works up to 54 bit numbers.
set64(x.nativePtr, n); set64(x.nativePtr, n);
return x; return x;
} }
static fromStampSeconds(stamp: number): AbsoluteTimeNbo { static fromStampSeconds(stamp: number): AbsoluteTimeNbo {
let x = new AbsoluteTimeNbo(); const x = new AbsoluteTimeNbo();
x.alloc(); x.alloc();
// XXX: This only works up to 54 bit numbers. // XXX: This only works up to 54 bit numbers.
set64(x.nativePtr, stamp * 1000000); set64(x.nativePtr, stamp * 1000000);
@ -1107,7 +1019,7 @@ function set32(p: number, n: number) {
export class UInt64 extends PackedArenaObject { export class UInt64 extends PackedArenaObject {
static fromNumber(n: number): UInt64 { static fromNumber(n: number): UInt64 {
let x = new UInt64(); const x = new UInt64();
x.alloc(); x.alloc();
set64(x.nativePtr, n); set64(x.nativePtr, n);
return x; return x;
@ -1121,7 +1033,7 @@ export class UInt64 extends PackedArenaObject {
export class UInt32 extends PackedArenaObject { export class UInt32 extends PackedArenaObject {
static fromNumber(n: number): UInt64 { static fromNumber(n: number): UInt64 {
let x = new UInt32(); const x = new UInt32();
x.alloc(); x.alloc();
set32(x.nativePtr, n); set32(x.nativePtr, n);
return x; return x;
@ -1204,7 +1116,7 @@ export class DenominationKeyValidityPS extends SignatureStruct {
["fee_deposit", AmountNbo], ["fee_deposit", AmountNbo],
["fee_refresh", AmountNbo], ["fee_refresh", AmountNbo],
["fee_refund", AmountNbo], ["fee_refund", AmountNbo],
["denom_hash", HashCode] ["denom_hash", HashCode],
]; ];
} }
} }
@ -1283,18 +1195,18 @@ export function rsaBlind(hashCode: HashCode,
blindingKey: RsaBlindingKeySecret, blindingKey: RsaBlindingKeySecret,
pkey: RsaPublicKey, pkey: RsaPublicKey,
arena?: Arena): ByteArray|null { arena?: Arena): ByteArray|null {
let buf_ptr_out = emscAlloc.malloc(PTR_SIZE); const buf_ptr_out = emscAlloc.malloc(PTR_SIZE);
let buf_size_out = emscAlloc.malloc(PTR_SIZE); const buf_size_out = emscAlloc.malloc(PTR_SIZE);
let res = emscAlloc.rsa_blind(hashCode.nativePtr, const res = emscAlloc.rsa_blind(hashCode.nativePtr,
blindingKey.nativePtr, blindingKey.nativePtr,
pkey.nativePtr, pkey.nativePtr,
buf_ptr_out, buf_ptr_out,
buf_size_out); buf_size_out);
let buf_ptr = emscLib.getValue(buf_ptr_out, '*'); const buf_ptr = emscLib.getValue(buf_ptr_out, "*");
let buf_size = emscLib.getValue(buf_size_out, '*'); const buf_size = emscLib.getValue(buf_size_out, "*");
emsc.free(buf_ptr_out); emsc.free(buf_ptr_out);
emsc.free(buf_size_out); emsc.free(buf_size_out);
if (res != GNUNET_OK) { if (res !== GNUNET_OK) {
// malicious key // malicious key
return null; return null;
} }
@ -1308,9 +1220,9 @@ export function rsaBlind(hashCode: HashCode,
export function eddsaSign(purpose: EccSignaturePurpose, export function eddsaSign(purpose: EccSignaturePurpose,
priv: EddsaPrivateKey, priv: EddsaPrivateKey,
a?: Arena): EddsaSignature { a?: Arena): EddsaSignature {
let sig = new EddsaSignature(a); const sig = new EddsaSignature(a);
sig.alloc(); sig.alloc();
let res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr); const res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr);
if (res < 1) { if (res < 1) {
throw Error("EdDSA signing failed"); throw Error("EdDSA signing failed");
} }
@ -1326,7 +1238,7 @@ export function eddsaVerify(purposeNum: number,
sig: EddsaSignature, sig: EddsaSignature,
pub: EddsaPublicKey, pub: EddsaPublicKey,
a?: Arena): boolean { a?: Arena): boolean {
let r = emsc.eddsa_verify(purposeNum, const r = emsc.eddsa_verify(purposeNum,
verify.nativePtr, verify.nativePtr,
sig.nativePtr, sig.nativePtr,
pub.nativePtr); pub.nativePtr);
@ -1341,7 +1253,7 @@ export function rsaUnblind(sig: RsaSignature,
bk: RsaBlindingKeySecret, bk: RsaBlindingKeySecret,
pk: RsaPublicKey, pk: RsaPublicKey,
a?: Arena): RsaSignature { a?: Arena): RsaSignature {
let x = new RsaSignature(a); const x = new RsaSignature(a);
x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr, x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr,
bk.nativePtr, bk.nativePtr,
pk.nativePtr); pk.nativePtr);
@ -1362,10 +1274,10 @@ export interface FreshCoin {
*/ */
export function ecdhEddsa(priv: EcdhePrivateKey, export function ecdhEddsa(priv: EcdhePrivateKey,
pub: EddsaPublicKey): HashCode { pub: EddsaPublicKey): HashCode {
let h = new HashCode(); const h = new HashCode();
h.alloc(); h.alloc();
let res = emsc.ecdh_eddsa(priv.nativePtr, pub.nativePtr, h.nativePtr); const res = emsc.ecdh_eddsa(priv.nativePtr, pub.nativePtr, h.nativePtr);
if (res != GNUNET_OK) { if (res !== GNUNET_OK) {
throw Error("ecdh_eddsa failed"); throw Error("ecdh_eddsa failed");
} }
return h; return h;
@ -1377,11 +1289,11 @@ export function ecdhEddsa(priv: EcdhePrivateKey,
*/ */
export function setupFreshCoin(secretSeed: TransferSecretP, export function setupFreshCoin(secretSeed: TransferSecretP,
coinIndex: number): FreshCoin { coinIndex: number): FreshCoin {
let priv = new EddsaPrivateKey(); const priv = new EddsaPrivateKey();
priv.isWeak = true; priv.isWeak = true;
let blindingKey = new RsaBlindingKeySecret(); const blindingKey = new RsaBlindingKeySecret();
blindingKey.isWeak = true; blindingKey.isWeak = true;
let buf = new ByteArray(priv.size() + blindingKey.size()); const buf = new ByteArray(priv.size() + blindingKey.size());
emsc.setup_fresh_coin(secretSeed.nativePtr, coinIndex, buf.nativePtr); emsc.setup_fresh_coin(secretSeed.nativePtr, coinIndex, buf.nativePtr);

View File

@ -20,25 +20,25 @@ declare function getLib(): EmscLib;
export interface EmscFunGen { export interface EmscFunGen {
(name: string, (name: string,
ret: string, ret: string,
args: string[]): ((...x: (number|string)[]) => any); args: string[]): ((...x: Array<number|string>) => any);
(name: string, (name: string,
ret: "number", ret: "number",
args: string[]): ((...x: (number|string)[]) => number); args: string[]): ((...x: Array<number|string>) => number);
(name: string, (name: string,
ret: "void", ret: "void",
args: string[]): ((...x: (number|string)[]) => void); args: string[]): ((...x: Array<number|string>) => void);
(name: string, (name: string,
ret: "string", ret: "string",
args: string[]): ((...x: (number|string)[]) => string); args: string[]): ((...x: Array<number|string>) => string);
} }
interface EmscLib { interface EmscLib {
cwrap: EmscFunGen; cwrap: EmscFunGen;
ccall(name: string, ret:"number"|"string", argTypes: any[], args: any[]): any ccall(name: string, ret: "number"|"string", argTypes: any[], args: any[]): any;
stringToUTF8(s: string, addr: number, maxLength: number): void stringToUTF8(s: string, addr: number, maxLength: number): void;
_free(ptr: number): void; _free(ptr: number): void;

View File

@ -14,6 +14,9 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
// tslint:disable:no-var-requires
const path = require("path"); const path = require("path");
const fork = require("child_process").fork; const fork = require("child_process").fork;

View File

@ -15,6 +15,8 @@
*/ */
// tslint:disable:no-var-requires
const fs = require("fs"); const fs = require("fs");
const vm = require("vm"); const vm = require("vm");
@ -22,57 +24,53 @@ process.once("message", (obj: any) => {
const g: any = global as any; const g: any = global as any;
(g as any).self = { (g as any).self = {
addEventListener: (event: "error" | "message", fn: (x: any) => void) => {
if (event === "error") {
g.onerror = fn;
} else if (event === "message") {
g.onmessage = fn;
}
},
close: () => { close: () => {
process.exit(0); process.exit(0);
}, },
postMessage: (msg: any) => {
const str: string = JSON.stringify({data: msg});
if (process.send) {
process.send(str);
}
},
onmessage: undefined,
onerror: (err: any) => { onerror: (err: any) => {
const str: string = JSON.stringify({error: err.message, stack: err.stack}); const str: string = JSON.stringify({error: err.message, stack: err.stack});
if (process.send) { if (process.send) {
process.send(str); process.send(str);
} }
}, },
addEventListener: (event: "error" | "message", fn: (x: any) => void) => { onmessage: undefined,
if (event == "error") { postMessage: (msg: any) => {
g.onerror = fn; const str: string = JSON.stringify({data: msg});
} else if (event == "message") { if (process.send) {
g.onmessage = fn; process.send(str);
} }
}, },
}; };
g.__dirname = obj.cwd; g.__dirname = obj.cwd;
g.__filename = __filename; g.__filename = __filename;
//g.require = require;
//g.module = module;
//g.exports = module.exports;
g.importScripts = (...files: string[]) => { g.importScripts = (...files: string[]) => {
if (files.length > 0) { if (files.length > 0) {
vm.createScript(files.map(file => fs.readFileSync(file, "utf8")).join("\n")).runInThisContext(); vm.createScript(files.map((file) => fs.readFileSync(file, "utf8")).join("\n")).runInThisContext();
} }
}; };
Object.keys(g.self).forEach(key => { Object.keys(g.self).forEach((key) => {
g[key] = g.self[key]; g[key] = g.self[key];
}); });
process.on("message", (msg: any) => { process.on("message", (msg: any) => {
try { try {
(g.onmessage || g.self.onmessage || (() => {}))(JSON.parse(msg)); (g.onmessage || g.self.onmessage || (() => undefined))(JSON.parse(msg));
} catch (err) { } catch (err) {
(g.onerror || g.self.onerror || (() => {}))(err); (g.onerror || g.self.onerror || (() => undefined))(err);
} }
}); });
process.on("error", (err: any) => { process.on("error", (err: any) => {
(g.onerror || g.self.onerror || (() => {}))(err); (g.onerror || g.self.onerror || (() => undefined))(err);
}); });
require(obj.scriptFilename); require(obj.scriptFilename);

View File

@ -1,8 +1,25 @@
/*
This file is part of TALER
(C) 2017 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import {test} from "ava"; import {test} from "ava";
import * as helpers from "./helpers"; import * as helpers from "./helpers";
test("URL canonicalization", t => { test("URL canonicalization", (t) => {
// converts to relative, adds https // converts to relative, adds https
t.is( t.is(
"https://alice.example.com/exchange/", "https://alice.example.com/exchange/",

View File

@ -30,7 +30,7 @@ import URI = require("urijs");
* settings such as significant digits or currency symbols. * settings such as significant digits or currency symbols.
*/ */
export function amountToPretty(amount: AmountJson): string { export function amountToPretty(amount: AmountJson): string {
let x = amount.value + amount.fraction / Amounts.fractionalBase; const x = amount.value + amount.fraction / Amounts.fractionalBase;
return `${x} ${amount.currency}`; return `${x} ${amount.currency}`;
} }
@ -41,14 +41,14 @@ export function amountToPretty(amount: AmountJson): string {
* See http://api.taler.net/wallet.html#general * See http://api.taler.net/wallet.html#general
*/ */
export function canonicalizeBaseUrl(url: string) { export function canonicalizeBaseUrl(url: string) {
let x = new URI(url); const x = new URI(url);
if (!x.protocol()) { if (!x.protocol()) {
x.protocol("https"); x.protocol("https");
} }
x.path(x.path() + "/").normalizePath(); x.path(x.path() + "/").normalizePath();
x.fragment(""); x.fragment("");
x.query(); x.query();
return x.href() return x.href();
} }
@ -59,23 +59,23 @@ export function canonicalizeBaseUrl(url: string) {
export function canonicalJson(obj: any): string { export function canonicalJson(obj: any): string {
// Check for cycles, etc. // Check for cycles, etc.
JSON.stringify(obj); JSON.stringify(obj);
if (typeof obj == "string" || typeof obj == "number" || obj === null) { if (typeof obj === "string" || typeof obj === "number" || obj === null) {
return JSON.stringify(obj) return JSON.stringify(obj);
} }
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
let objs: string[] = obj.map((e) => canonicalJson(e)); const objs: string[] = obj.map((e) => canonicalJson(e));
return `[${objs.join(',')}]`; return `[${objs.join(",")}]`;
} }
let keys: string[] = []; const keys: string[] = [];
for (let key in obj) { for (const key in obj) {
keys.push(key); keys.push(key);
} }
keys.sort(); keys.sort();
let s = "{"; let s = "{";
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
let key = keys[i]; const key = keys[i];
s += JSON.stringify(key) + ":" + canonicalJson(obj[key]); s += JSON.stringify(key) + ":" + canonicalJson(obj[key]);
if (i != keys.length - 1) { if (i !== keys.length - 1) {
s += ","; s += ",";
} }
} }
@ -92,7 +92,7 @@ export function deepEquals(x: any, y: any): boolean {
return false; return false;
} }
var p = Object.keys(x); const p = Object.keys(x);
return Object.keys(y).every((i) => p.indexOf(i) !== -1) && return Object.keys(y).every((i) => p.indexOf(i) !== -1) &&
p.every((i) => deepEquals(x[i], y[i])); p.every((i) => deepEquals(x[i], y[i]));
} }
@ -112,7 +112,7 @@ export function getTalerStampSec(stamp: string): number | null {
if (!m) { if (!m) {
return null; return null;
} }
return parseInt(m[1]); return parseInt(m[1], 10);
} }
@ -121,7 +121,7 @@ export function getTalerStampSec(stamp: string): number | null {
* Returns null if input is not in the right format. * Returns null if input is not in the right format.
*/ */
export function getTalerStampDate(stamp: string): Date | null { export function getTalerStampDate(stamp: string): Date | null {
let sec = getTalerStampSec(stamp); const sec = getTalerStampSec(stamp);
if (sec == null) { if (sec == null) {
return null; return null;
} }

View File

@ -49,7 +49,7 @@ export class BrowserHttpLib {
url: string, url: string,
options?: any): Promise<HttpResponse> { options?: any): Promise<HttpResponse> {
return new Promise<HttpResponse>((resolve, reject) => { return new Promise<HttpResponse>((resolve, reject) => {
let myRequest = new XMLHttpRequest(); const myRequest = new XMLHttpRequest();
myRequest.open(method, url); myRequest.open(method, url);
if (options && options.req) { if (options && options.req) {
myRequest.send(options.req); myRequest.send(options.req);
@ -57,10 +57,10 @@ export class BrowserHttpLib {
myRequest.send(); myRequest.send();
} }
myRequest.addEventListener("readystatechange", (e) => { myRequest.addEventListener("readystatechange", (e) => {
if (myRequest.readyState == XMLHttpRequest.DONE) { if (myRequest.readyState === XMLHttpRequest.DONE) {
let resp = { const resp = {
responseText: myRequest.responseText,
status: myRequest.status, status: myRequest.status,
responseText: myRequest.responseText
}; };
resolve(resp); resolve(resp);
} }

View File

@ -20,7 +20,11 @@
* @author Florian Dold * @author Florian Dold
*/ */
import {Store, QueryRoot, openPromise} from "./query"; import {
QueryRoot,
Store,
openPromise,
} from "./query";
export type Level = "error" | "debug" | "info" | "warn"; export type Level = "error" | "debug" | "info" | "warn";
@ -41,83 +45,92 @@ function makeDebug() {
} }
export async function log(msg: string, level: Level = "info"): Promise<void> { export async function log(msg: string, level: Level = "info"): Promise<void> {
let ci = getCallInfo(2); const ci = getCallInfo(2);
return record(level, msg, undefined, ci.file, ci.line, ci.column); return record(level, msg, undefined, ci.file, ci.line, ci.column);
} }
function getCallInfo(level: number) { function getCallInfo(level: number) {
// see https://github.com/v8/v8/wiki/Stack-Trace-API // see https://github.com/v8/v8/wiki/Stack-Trace-API
let stack = Error().stack; const stack = Error().stack;
if (!stack) { if (!stack) {
return unknownFrame; return unknownFrame;
} }
let lines = stack.split("\n"); const lines = stack.split("\n");
return parseStackLine(lines[level + 1]); return parseStackLine(lines[level + 1]);
} }
interface Frame { interface Frame {
file?: string;
method?: string;
column?: number; column?: number;
file?: string;
line?: number; line?: number;
method?: string;
} }
const unknownFrame: Frame = { const unknownFrame: Frame = {
column: 0,
file: "(unknown)", file: "(unknown)",
method: "(unknown)",
line: 0, line: 0,
column: 0 method: "(unknown)",
}; };
/** /**
* Adapted from https://github.com/errwischt/stacktrace-parser. * Adapted from https://github.com/errwischt/stacktrace-parser.
*/ */
function parseStackLine(stackLine: string): Frame { function parseStackLine(stackLine: string): Frame {
// tslint:disable-next-line:max-line-length
const chrome = /^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i; const chrome = /^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
const gecko = /^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i; const gecko = /^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i;
const node = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i; const node = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i;
let parts; let parts;
if ((parts = gecko.exec(stackLine))) { parts = gecko.exec(stackLine);
let f: Frame = { if (parts) {
file: parts[3], const f: Frame = {
method: parts[1] || "(unknown)",
line: +parts[4],
column: parts[5] ? +parts[5] : undefined, column: parts[5] ? +parts[5] : undefined,
}; file: parts[3],
return f; line: +parts[4],
} else if ((parts = chrome.exec(stackLine))) {
let f: Frame = {
file: parts[2],
method: parts[1] || "(unknown)", method: parts[1] || "(unknown)",
line: +parts[3],
column: parts[4] ? +parts[4] : undefined,
};
return f;
} else if ((parts = node.exec(stackLine))) {
let f: Frame = {
file: parts[2],
method: parts[1] || "(unknown)",
line: +parts[3],
column: parts[4] ? +parts[4] : undefined,
}; };
return f; return f;
} }
parts = chrome.exec(stackLine);
if (parts) {
const f: Frame = {
column: parts[4] ? +parts[4] : undefined,
file: parts[2],
line: +parts[3],
method: parts[1] || "(unknown)",
};
return f;
}
parts = node.exec(stackLine);
if (parts) {
const f: Frame = {
column: parts[4] ? +parts[4] : undefined,
file: parts[2],
line: +parts[3],
method: parts[1] || "(unknown)",
};
return f;
}
return unknownFrame; return unknownFrame;
} }
let db: IDBDatabase|undefined = undefined; let db: IDBDatabase|undefined;
export interface LogEntry { export interface LogEntry {
timestamp: number;
level: string;
msg: string;
detail?: string;
source?: string;
col?: number; col?: number;
line?: number; detail?: string;
id?: number; id?: number;
level: string;
line?: number;
msg: string;
source?: string;
timestamp: number;
} }
export async function getLogs(): Promise<LogEntry[]> { export async function getLogs(): Promise<LogEntry[]> {
@ -140,7 +153,7 @@ export async function recordException(msg: string, e: any): Promise<void> {
try { try {
stack = e.stack; stack = e.stack;
if (stack) { if (stack) {
let lines = stack.split("\n"); const lines = stack.split("\n");
frame = parseStackLine(lines[1]); frame = parseStackLine(lines[1]);
} }
} catch (e) { } catch (e) {
@ -152,7 +165,12 @@ export async function recordException(msg: string, e: any): Promise<void> {
return record("error", e.toString(), stack, frame.file, frame.line, frame.column); return record("error", e.toString(), stack, frame.file, frame.line, frame.column);
} }
export async function record(level: Level, msg: string, detail?: string, source?: string, line?: number, col?: number): Promise<void> { export async function record(level: Level,
msg: string,
detail?: string,
source?: string,
line?: number,
col?: number): Promise<void> {
if (typeof indexedDB === "undefined") { if (typeof indexedDB === "undefined") {
return; return;
} }
@ -160,7 +178,7 @@ export async function record(level: Level, msg: string, detail?: string, source?
let myBarrier: any; let myBarrier: any;
if (barrier) { if (barrier) {
let p = barrier.promise; const p = barrier.promise;
myBarrier = barrier = openPromise(); myBarrier = barrier = openPromise();
await p; await p;
} else { } else {
@ -172,20 +190,20 @@ export async function record(level: Level, msg: string, detail?: string, source?
db = await openLoggingDb(); db = await openLoggingDb();
} }
let count = await new QueryRoot(db).count(logsStore); const count = await new QueryRoot(db).count(logsStore);
if (count > 1000) { if (count > 1000) {
await new QueryRoot(db).deleteIf(logsStore, (e, i) => (i < 200)); await new QueryRoot(db).deleteIf(logsStore, (e, i) => (i < 200));
} }
let entry: LogEntry = { const entry: LogEntry = {
timestamp: new Date().getTime(),
level,
msg,
source,
line,
col, col,
detail, detail,
level,
line,
msg,
source,
timestamp: new Date().getTime(),
}; };
await new QueryRoot(db).put(logsStore, entry); await new QueryRoot(db).put(logsStore, entry);
} finally { } finally {
@ -207,15 +225,15 @@ export function openLoggingDb(): Promise<IDBDatabase> {
resolve(req.result); resolve(req.result);
}; };
req.onupgradeneeded = (e) => { req.onupgradeneeded = (e) => {
const db = req.result; const resDb = req.result;
if (e.oldVersion != 0) { if (e.oldVersion !== 0) {
try { try {
db.deleteObjectStore("logs"); resDb.deleteObjectStore("logs");
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} }
db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}); resDb.createObjectStore("logs", {keyPath: "id", autoIncrement: true});
}; };
}); });
} }

View File

@ -23,32 +23,32 @@
function replacer(match: string, pIndent: string, pKey: string, pVal: string, function replacer(match: string, pIndent: string, pKey: string, pVal: string,
pEnd: string) { pEnd: string) {
var key = '<span class=json-key>'; const key = "<span class=json-key>";
var val = '<span class=json-value>'; const val = "<span class=json-value>";
var str = '<span class=json-string>'; const str = "<span class=json-string>";
var r = pIndent || ''; let r = pIndent || "";
if (pKey) { if (pKey) {
r = r + key + '"' + pKey.replace(/[": ]/g, '') + '":</span> '; r = r + key + '"' + pKey.replace(/[": ]/g, "") + '":</span> ';
} }
if (pVal) { if (pVal) {
r = r + (pVal[0] == '"' ? str : val) + pVal + '</span>'; r = r + (pVal[0] === '"' ? str : val) + pVal + "</span>";
} }
return r + (pEnd || ''); return r + (pEnd || "");
} }
function prettyPrint(obj: any) { function prettyPrint(obj: any) {
var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; const jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg;
return JSON.stringify(obj, null as any, 3) return JSON.stringify(obj, null as any, 3)
.replace(/&/g, '&amp;').replace(/\\"/g, '&quot;') .replace(/&/g, "&amp;").replace(/\\"/g, "&quot;")
.replace(/</g, '&lt;').replace(/>/g, '&gt;') .replace(/</g, "&lt;").replace(/>/g, "&gt;")
.replace(jsonLine, replacer); .replace(jsonLine, replacer);
} }
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
chrome.runtime.sendMessage({type: 'dump-db'}, (resp) => { chrome.runtime.sendMessage({type: "dump-db"}, (resp) => {
const el = document.getElementById('dump'); const el = document.getElementById("dump");
if (!el) { if (!el) {
throw Error(); throw Error();
} }
@ -56,7 +56,7 @@ document.addEventListener("DOMContentLoaded", () => {
document.getElementById("download")!.addEventListener("click", (evt) => { document.getElementById("download")!.addEventListener("click", (evt) => {
console.log("creating download"); console.log("creating download");
let element = document.createElement("a"); const element = document.createElement("a");
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(resp))); element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(resp)));
element.setAttribute("download", "wallet-dump.txt"); element.setAttribute("download", "wallet-dump.txt");
element.style.display = "none"; element.style.display = "none";
@ -67,9 +67,9 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
let fileInput = document.getElementById("fileInput")! as HTMLInputElement; const fileInput = document.getElementById("fileInput")! as HTMLInputElement;
fileInput.onchange = (evt) => { fileInput.onchange = (evt) => {
if (!fileInput.files || fileInput.files.length != 1) { if (!fileInput.files || fileInput.files.length !== 1) {
alert("please select exactly one file to import"); alert("please select exactly one file to import");
return; return;
} }
@ -77,9 +77,9 @@ document.addEventListener("DOMContentLoaded", () => {
const fr = new FileReader(); const fr = new FileReader();
fr.onload = (e: any) => { fr.onload = (e: any) => {
console.log("got file"); console.log("got file");
let dump = JSON.parse(e.target.result); const dump = JSON.parse(e.target.result);
console.log("parsed contents", dump); console.log("parsed contents", dump);
chrome.runtime.sendMessage({ type: 'import-db', detail: { dump } }, (resp) => { chrome.runtime.sendMessage({ type: "import-db", detail: { dump } }, (resp) => {
alert("loaded"); alert("loaded");
}); });
}; };

View File

@ -21,42 +21,42 @@
* @author Florian Dold * @author Florian Dold
*/ */
"use strict";
/**
* Result of an inner join.
*/
export interface JoinResult<L, R> { export interface JoinResult<L, R> {
left: L; left: L;
right: R; right: R;
} }
/**
* Result of a left outer join.
*/
export interface JoinLeftResult<L, R> { export interface JoinLeftResult<L, R> {
left: L; left: L;
right?: R; right?: R;
} }
/**
* Definition of an object store.
*/
export class Store<T> { export class Store<T> {
name: string; constructor(public name: string,
validator?: (v: T) => T; public storeParams?: IDBObjectStoreParameters,
storeParams?: IDBObjectStoreParameters; public validator?: (v: T) => T) {
constructor(name: string, storeParams?: IDBObjectStoreParameters,
validator?: (v: T) => T) {
this.name = name;
this.validator = validator;
this.storeParams = storeParams;
} }
} }
/**
* Definition of an index.
*/
export class Index<S extends IDBValidKey, T> { export class Index<S extends IDBValidKey, T> {
indexName: string;
storeName: string; storeName: string;
keyPath: string | string[];
constructor(s: Store<T>, indexName: string, keyPath: string | string[]) { constructor(s: Store<T>, public indexName: string, public keyPath: string | string[]) {
this.storeName = s.name; this.storeName = s.name;
this.indexName = indexName;
this.keyPath = keyPath;
} }
} }
@ -65,17 +65,58 @@ export class Index<S extends IDBValidKey,T> {
* with indices. * with indices.
*/ */
export interface QueryStream<T> { export interface QueryStream<T> {
indexJoin<S,I extends IDBValidKey>(index: Index<I,S>, /**
keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>>; * Join the current query with values from an index.
* 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>, indexJoinLeft<S, I extends IDBValidKey>(index: Index<I, S>,
keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>>; keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>>;
keyJoin<S,I extends IDBValidKey>(store: Store<S>, /**
keyFn: (obj: T) => I): QueryStream<JoinResult<T,S>>; * Join the current query with values from another object store.
filter(f: (T: any) => boolean): QueryStream<T>; * The left side of the join is extracted via a function over the current query,
* the right side of the join is the key of the object store.
*/
keyJoin<S, I extends IDBValidKey>(store: Store<S>, keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>>;
/**
* Only keep elements in the result stream for which the predicate returns
* true.
*/
filter(f: (x: T) => boolean): QueryStream<T>;
/**
* Reduce the stream, resulting in a single value.
*/
reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>; reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>;
/**
* Map 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<S>(f: (x: T) => S): QueryStream<S>;
/**
* Map each element of the stream to a potentially empty array, and collect
* the result in a stream of the flattened arrays.
*/
flatMap<S>(f: (x: T) => S[]): QueryStream<S>; flatMap<S>(f: (x: T) => S[]): QueryStream<S>;
/**
* Collect the stream into an array and return a promise for it.
*/
toArray(): Promise<T[]>; toArray(): Promise<T[]>;
/**
* Get the first value of the stream.
*/
first(): QueryValue<T>; first(): QueryValue<T>;
then(onfulfill: any, onreject: any): any; then(onfulfill: any, onreject: any): any;
@ -157,7 +198,10 @@ class MapQueryValue<T,S> extends BaseQueryValue<S> {
} }
export let AbortTransaction = Symbol("abort_transaction"); /**
* Exception that should be thrown by client code to abort a transaction.
*/
export const AbortTransaction = Symbol("abort_transaction");
/** /**
* Get an unresolved promise together with its extracted resolve / reject * Get an unresolved promise together with its extracted resolve / reject
@ -193,7 +237,8 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
return new FirstQueryValue(this); return new FirstQueryValue(this);
} }
then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> { then<R>(onfulfilled: (value: void) => R | PromiseLike<R>,
onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
return this.root.then(onfulfilled, onrejected); return this.root.then(onfulfilled, onrejected);
} }
@ -228,8 +273,8 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
} }
toArray(): Promise<T[]> { toArray(): Promise<T[]> {
let {resolve, promise} = openPromise(); const {resolve, promise} = openPromise();
let values: T[] = []; const values: T[] = [];
this.subscribe((isDone, value) => { this.subscribe((isDone, value) => {
if (isDone) { if (isDone) {
@ -245,7 +290,7 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
} }
reduce<A>(f: (x: any, acc?: A) => A, init?: A): Promise<any> { reduce<A>(f: (x: any, acc?: A) => A, init?: A): Promise<any> {
let {resolve, promise} = openPromise(); const {resolve, promise} = openPromise();
let acc = init; let acc = init;
this.subscribe((isDone, value) => { this.subscribe((isDone, value) => {
@ -265,10 +310,7 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
type FilterFn = (e: any) => boolean; type FilterFn = (e: any) => boolean;
type SubscribeFn = (done: boolean, value: any, tx: IDBTransaction) => void; type SubscribeFn = (done: boolean, value: any, tx: IDBTransaction) => void;
type SubscribeOneFn = (value: any, tx: IDBTransaction) => void; type SubscribeOneFn = (value: any, tx: IDBTransaction) => void;
type FlatMapFn<T> = (v: T) => T[];
interface FlatMapFn<T> {
(v: T): T[];
}
class QueryStreamFilter<T> extends QueryStreamBase<T> { class QueryStreamFilter<T> extends QueryStreamBase<T> {
s: QueryStreamBase<T>; s: QueryStreamBase<T>;
@ -310,9 +352,9 @@ class QueryStreamFlatMap<T,S> extends QueryStreamBase<S> {
f(true, undefined, tx); f(true, undefined, tx);
return; return;
} }
let values = this.flatMapFn(value); const values = this.flatMapFn(value);
for (let v in values) { for (const v in values) {
f(false, value, tx) f(false, value, tx);
} }
}); });
} }
@ -335,7 +377,7 @@ class QueryStreamMap<S,T> extends QueryStreamBase<T> {
f(true, undefined, tx); f(true, undefined, tx);
return; return;
} }
let mappedValue = this.mapFn(value); const mappedValue = this.mapFn(value);
f(false, mappedValue, tx); f(false, mappedValue, tx);
}); });
} }
@ -363,15 +405,15 @@ class QueryStreamIndexJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> {
f(true, undefined, tx); f(true, undefined, tx);
return; return;
} }
let s = tx.objectStore(this.storeName).index(this.indexName); const s = tx.objectStore(this.storeName).index(this.indexName);
let req = s.openCursor(IDBKeyRange.only(this.key(value))); const req = s.openCursor(IDBKeyRange.only(this.key(value)));
req.onsuccess = () => { req.onsuccess = () => {
let cursor = req.result; const cursor = req.result;
if (cursor) { if (cursor) {
f(false, {left: value, right: cursor.value}, tx); f(false, {left: value, right: cursor.value}, tx);
cursor.continue(); cursor.continue();
} }
} };
}); });
} }
} }
@ -402,7 +444,7 @@ class QueryStreamIndexJoinLeft<T, S> extends QueryStreamBase<JoinLeftResult<T, S
const req = s.openCursor(IDBKeyRange.only(this.key(value))); const req = s.openCursor(IDBKeyRange.only(this.key(value)));
let gotMatch = false; let gotMatch = false;
req.onsuccess = () => { req.onsuccess = () => {
let cursor = req.result; const cursor = req.result;
if (cursor) { if (cursor) {
gotMatch = true; gotMatch = true;
f(false, {left: value, right: cursor.value}, tx); f(false, {left: value, right: cursor.value}, tx);
@ -412,7 +454,7 @@ class QueryStreamIndexJoinLeft<T, S> extends QueryStreamBase<JoinLeftResult<T, S
f(false, {left: value}, tx); f(false, {left: value}, tx);
} }
} }
} };
}); });
} }
} }
@ -437,17 +479,17 @@ class QueryStreamKeyJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> {
f(true, undefined, tx); f(true, undefined, tx);
return; return;
} }
let s = tx.objectStore(this.storeName); const s = tx.objectStore(this.storeName);
let req = s.openCursor(IDBKeyRange.only(this.key(value))); const req = s.openCursor(IDBKeyRange.only(this.key(value)));
req.onsuccess = () => { req.onsuccess = () => {
let cursor = req.result; const cursor = req.result;
if (cursor) { if (cursor) {
f(false, {left: value, right: cursor.value}, tx); f(false, {left: value, right: cursor.value}, tx);
cursor.continue(); cursor.continue();
} else { } else {
f(true, undefined, tx); f(true, undefined, tx);
} }
} };
}); });
} }
} }
@ -464,7 +506,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
this.storeName = storeName; this.storeName = storeName;
this.subscribers = []; this.subscribers = [];
let doIt = (tx: IDBTransaction) => { const doIt = (tx: IDBTransaction) => {
const {indexName = void 0, only = void 0} = this.options; const {indexName = void 0, only = void 0} = this.options;
let s: any; let s: any;
if (indexName !== void 0) { if (indexName !== void 0) {
@ -473,24 +515,24 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
} else { } else {
s = tx.objectStore(this.storeName); s = tx.objectStore(this.storeName);
} }
let kr: IDBKeyRange | undefined = undefined; let kr: IDBKeyRange | undefined;
if (only !== undefined) { if (only !== undefined) {
kr = IDBKeyRange.only(this.options.only); kr = IDBKeyRange.only(this.options.only);
} }
let req = s.openCursor(kr); const req = s.openCursor(kr);
req.onsuccess = () => { req.onsuccess = () => {
let cursor: IDBCursorWithValue = req.result; const cursor: IDBCursorWithValue = req.result;
if (cursor) { if (cursor) {
for (let f of this.subscribers) { for (const f of this.subscribers) {
f(false, cursor.value, tx); f(false, cursor.value, tx);
} }
cursor.continue(); cursor.continue();
} else { } else {
for (let f of this.subscribers) { for (const f of this.subscribers) {
f(true, undefined, tx); f(true, undefined, tx);
} }
} }
} };
}; };
this.root.addWork(doIt); this.root.addWork(doIt);
@ -503,8 +545,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
export class QueryRoot implements PromiseLike<void> { export class QueryRoot implements PromiseLike<void> {
private work: ((t: IDBTransaction) => void)[] = []; private work: Array<((t: IDBTransaction) => void)> = [];
db: IDBDatabase;
private stores = new Set(); private stores = new Set();
private kickoffPromise: Promise<void>; private kickoffPromise: Promise<void>;
@ -518,20 +559,23 @@ export class QueryRoot implements PromiseLike<void> {
private finished: boolean = false; private finished: boolean = false;
constructor(db: IDBDatabase) { constructor(public db: IDBDatabase) {
this.db = db;
} }
then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> { then<R>(onfulfilled: (value: void) => R | PromiseLike<R>,
onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
return this.finish().then(onfulfilled, onrejected); return this.finish().then(onfulfilled, onrejected);
} }
checkFinished() { private checkFinished() {
if (this.finished) { if (this.finished) {
throw Error("Can't add work to query after it was started"); throw Error("Can't add work to query after it was started");
} }
} }
/**
* Get a stream of all objects in the store.
*/
iter<T>(store: Store<T>): QueryStream<T> { iter<T>(store: Store<T>): QueryStream<T> {
this.checkFinished(); this.checkFinished();
this.stores.add(store.name); this.stores.add(store.name);
@ -539,6 +583,9 @@ export class QueryRoot implements PromiseLike<void> {
return new IterQueryStream<T>(this, store.name, {}); return new IterQueryStream<T>(this, store.name, {});
} }
/**
* Count the number of objects in a store.
*/
count<T>(store: Store<T>): Promise<number> { count<T>(store: Store<T>): Promise<number> {
this.checkFinished(); this.checkFinished();
const {resolve, promise} = openPromise(); const {resolve, promise} = openPromise();
@ -549,7 +596,7 @@ export class QueryRoot implements PromiseLike<void> {
req.onsuccess = () => { req.onsuccess = () => {
resolve(req.result); resolve(req.result);
}; };
} };
this.addWork(doCount, store.name, false); this.addWork(doCount, store.name, false);
return Promise.resolve() return Promise.resolve()
@ -558,6 +605,9 @@ export class QueryRoot implements PromiseLike<void> {
} }
/**
* Delete all objects in a store that match a predicate.
*/
deleteIf<T>(store: Store<T>, predicate: (x: T, n: number) => boolean): QueryRoot { deleteIf<T>(store: Store<T>, predicate: (x: T, n: number) => boolean): QueryRoot {
this.checkFinished(); this.checkFinished();
const doDeleteIf = (tx: IDBTransaction) => { const doDeleteIf = (tx: IDBTransaction) => {
@ -565,14 +615,14 @@ export class QueryRoot implements PromiseLike<void> {
const req = s.openCursor(); const req = s.openCursor();
let n = 0; let n = 0;
req.onsuccess = () => { req.onsuccess = () => {
let cursor: IDBCursorWithValue = req.result; const cursor: IDBCursorWithValue = req.result;
if (cursor) { if (cursor) {
if (predicate(cursor.value, n++)) { if (predicate(cursor.value, n++)) {
cursor.delete(); cursor.delete();
} }
cursor.continue(); cursor.continue();
} }
} };
}; };
this.addWork(doDeleteIf, store.name, true); this.addWork(doDeleteIf, store.name, true);
return this; return this;
@ -584,8 +634,8 @@ export class QueryRoot implements PromiseLike<void> {
this.stores.add(index.storeName); this.stores.add(index.storeName);
this.scheduleFinish(); this.scheduleFinish();
return new IterQueryStream<T>(this, index.storeName, { return new IterQueryStream<T>(this, index.storeName, {
indexName: index.indexName,
only, only,
indexName: index.indexName
}); });
} }
@ -596,7 +646,7 @@ export class QueryRoot implements PromiseLike<void> {
*/ */
put<T>(store: Store<T>, val: T): QueryRoot { put<T>(store: Store<T>, val: T): QueryRoot {
this.checkFinished(); this.checkFinished();
let doPut = (tx: IDBTransaction) => { const doPut = (tx: IDBTransaction) => {
tx.objectStore(store.name).put(val); tx.objectStore(store.name).put(val);
}; };
this.scheduleFinish(); this.scheduleFinish();
@ -608,11 +658,11 @@ export class QueryRoot implements PromiseLike<void> {
putWithResult<T>(store: Store<T>, val: T): Promise<IDBValidKey> { putWithResult<T>(store: Store<T>, val: T): Promise<IDBValidKey> {
this.checkFinished(); this.checkFinished();
const {resolve, promise} = openPromise(); const {resolve, promise} = openPromise();
let doPutWithResult = (tx: IDBTransaction) => { const doPutWithResult = (tx: IDBTransaction) => {
let req = tx.objectStore(store.name).put(val); const req = tx.objectStore(store.name).put(val);
req.onsuccess = () => { req.onsuccess = () => {
resolve(req.result); resolve(req.result);
} };
this.scheduleFinish(); this.scheduleFinish();
}; };
this.addWork(doPutWithResult, store.name, true); this.addWork(doPutWithResult, store.name, true);
@ -622,17 +672,20 @@ export class QueryRoot implements PromiseLike<void> {
} }
/**
* Get, modify and store an element inside a transaction.
*/
mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot { mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot {
this.checkFinished(); this.checkFinished();
let doPut = (tx: IDBTransaction) => { const doPut = (tx: IDBTransaction) => {
let reqGet = tx.objectStore(store.name).get(key); const reqGet = tx.objectStore(store.name).get(key);
reqGet.onsuccess = () => { reqGet.onsuccess = () => {
let r = reqGet.result; const r = reqGet.result;
let m: T; let m: T;
try { try {
m = f(r); m = f(r);
} catch (e) { } catch (e) {
if (e == AbortTransaction) { if (e === AbortTransaction) {
tx.abort(); tx.abort();
return; return;
} }
@ -640,7 +693,7 @@ export class QueryRoot implements PromiseLike<void> {
} }
tx.objectStore(store.name).put(m); tx.objectStore(store.name).put(m);
} };
}; };
this.scheduleFinish(); this.scheduleFinish();
this.addWork(doPut, store.name, true); this.addWork(doPut, store.name, true);
@ -656,7 +709,7 @@ export class QueryRoot implements PromiseLike<void> {
putAll<T>(store: Store<T>, iterable: T[]): QueryRoot { putAll<T>(store: Store<T>, iterable: T[]): QueryRoot {
this.checkFinished(); this.checkFinished();
const doPutAll = (tx: IDBTransaction) => { const doPutAll = (tx: IDBTransaction) => {
for (let obj of iterable) { for (const obj of iterable) {
tx.objectStore(store.name).put(obj); tx.objectStore(store.name).put(obj);
} }
}; };
@ -748,7 +801,7 @@ export class QueryRoot implements PromiseLike<void> {
this.kickoffPromise = new Promise<void>((resolve, reject) => { this.kickoffPromise = new Promise<void>((resolve, reject) => {
// At this point, we can't add any more work // At this point, we can't add any more work
this.finished = true; this.finished = true;
if (this.work.length == 0) { if (this.work.length === 0) {
resolve(); resolve();
return; return;
} }
@ -760,7 +813,7 @@ export class QueryRoot implements PromiseLike<void> {
tx.onabort = () => { tx.onabort = () => {
reject(Error("transaction aborted")); reject(Error("transaction aborted"));
}; };
for (let w of this.work) { for (const w of this.work) {
w(tx); w(tx);
} }
}); });

View File

@ -54,7 +54,7 @@ export let performanceNow = (() => {
return () => { return () => {
const t = process.hrtime(); const t = process.hrtime();
return t[0] * 1e9 + t[1]; return t[0] * 1e9 + t[1];
} };
} else if (typeof "performance" !== "undefined") { } else if (typeof "performance" !== "undefined") {
return () => performance.now(); return () => performance.now();
} else { } else {

View File

@ -1,35 +1,51 @@
/*
This file is part of TALER
(C) 2017 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import {test} from "ava"; import {test} from "ava";
import {Amounts} from "./types"; import {Amounts} from "./types";
import * as types from "./types"; import * as types from "./types";
let amt = (value: number, fraction: number, currency: string): types.AmountJson => ({value, fraction, currency}); const amt = (value: number, fraction: number, currency: string): types.AmountJson => ({value, fraction, currency});
test("amount addition (simple)", t => { test("amount addition (simple)", (t) => {
let a1 = amt(1,0,"EUR"); const a1 = amt(1, 0, "EUR");
let a2 = amt(1,0,"EUR"); const a2 = amt(1, 0, "EUR");
let a3 = amt(2,0,"EUR"); const a3 = amt(2, 0, "EUR");
t.true(0 == types.Amounts.cmp(Amounts.add(a1, a2).amount, a3)); t.true(0 === types.Amounts.cmp(Amounts.add(a1, a2).amount, a3));
t.pass(); t.pass();
}); });
test("amount addition (saturation)", t => { test("amount addition (saturation)", (t) => {
let a1 = amt(1,0,"EUR"); const a1 = amt(1, 0, "EUR");
let res = Amounts.add(Amounts.getMaxAmount("EUR"), a1); const res = Amounts.add(Amounts.getMaxAmount("EUR"), a1);
t.true(res.saturated); t.true(res.saturated);
t.pass(); t.pass();
}); });
test("amount subtraction (simple)", t => { test("amount subtraction (simple)", (t) => {
let a1 = amt(2,5,"EUR"); const a1 = amt(2, 5, "EUR");
let a2 = amt(1,0,"EUR"); const a2 = amt(1, 0, "EUR");
let a3 = amt(1,5,"EUR"); const a3 = amt(1, 5, "EUR");
t.true(0 == types.Amounts.cmp(Amounts.sub(a1, a2).amount, a3)); t.true(0 === types.Amounts.cmp(Amounts.sub(a1, a2).amount, a3));
t.pass(); t.pass();
}); });
test("amount subtraction (saturation)", t => { test("amount subtraction (saturation)", (t) => {
let a1 = amt(0,0,"EUR"); const a1 = amt(0, 0, "EUR");
let a2 = amt(1,0,"EUR"); const a2 = amt(1, 0, "EUR");
let res = Amounts.sub(a1, a2); let res = Amounts.sub(a1, a2);
t.true(res.saturated); t.true(res.saturated);
res = Amounts.sub(a1, a1); res = Amounts.sub(a1, a1);
@ -38,29 +54,29 @@ test("amount subtraction (saturation)", t => {
}); });
test("contract validation", t => { test("contract validation", (t) => {
let c = { const c = {
H_wire: "123", H_wire: "123",
summary: "hello",
amount: amt(1, 2, "EUR"), amount: amt(1, 2, "EUR"),
auditors: [], auditors: [],
pay_deadline: "Date(12346)", exchanges: [{master_pub: "foo", url: "foo"}],
fulfillment_url: "foo",
max_fee: amt(1, 2, "EUR"), max_fee: amt(1, 2, "EUR"),
merchant_pub: "12345", merchant_pub: "12345",
exchanges: [{master_pub: "foo", url: "foo"}], order_id: "test_order",
pay_deadline: "Date(12346)",
pay_url: "https://example.com/pay",
products: [], products: [],
refund_deadline: "Date(12345)", refund_deadline: "Date(12345)",
summary: "hello",
timestamp: "Date(12345)", timestamp: "Date(12345)",
fulfillment_url: "foo",
wire_method: "test", wire_method: "test",
order_id: "test_order",
pay_url: "https://example.com/pay",
}; };
types.Contract.checked(c); types.Contract.checked(c);
let c1 = JSON.parse(JSON.stringify(c)); const c1 = JSON.parse(JSON.stringify(c));
c1.exchanges = [] c1.exchanges = [];
try { try {
types.Contract.checked(c1); types.Contract.checked(c1);
@ -70,5 +86,4 @@ test("contract validation", t => {
} }
t.fail(); t.fail();
}); });

View File

@ -51,20 +51,20 @@ export interface SignedAmountJson {
export interface ReserveRecord { export interface ReserveRecord {
reserve_pub: string; reserve_pub: string;
reserve_priv: string, reserve_priv: string;
exchange_base_url: string, exchange_base_url: string;
created: number, created: number;
last_query: number | null, last_query: number | null;
/** /**
* Current amount left in the reserve * Current amount left in the reserve
*/ */
current_amount: AmountJson | null, current_amount: AmountJson | null;
/** /**
* Amount requested when the reserve was created. * Amount requested when the reserve was created.
* When a reserve is re-used (rare!) the current_amount can * When a reserve is re-used (rare!) the current_amount can
* be higher than the requested_amount * be higher than the requested_amount
*/ */
requested_amount: AmountJson, requested_amount: AmountJson;
/** /**
@ -360,7 +360,7 @@ export interface RefreshSessionRecord {
* How much of the coin's value is melted away * How much of the coin's value is melted away
* with this refresh session? * with this refresh session?
*/ */
valueWithFee: AmountJson valueWithFee: AmountJson;
/** /**
* Sum of the value of denominations we want * Sum of the value of denominations we want
@ -468,7 +468,7 @@ export interface CoinRecord {
* Reserve public key for the reserve we got this coin from, * Reserve public key for the reserve we got this coin from,
* or zero when we got the coin from refresh. * or zero when we got the coin from refresh.
*/ */
reservePub: string|undefined, reservePub: string|undefined;
/** /**
* Status of the coin. * Status of the coin.
@ -528,7 +528,7 @@ interface Merchant {
export class Contract { export class Contract {
validate() { validate() {
if (this.exchanges.length == 0) { if (this.exchanges.length === 0) {
throw Error("no exchanges in contract"); throw Error("no exchanges in contract");
} }
} }
@ -629,27 +629,27 @@ export namespace Amounts {
export function getMaxAmount(currency: string): AmountJson { export function getMaxAmount(currency: string): AmountJson {
return { return {
currency, currency,
value: Number.MAX_SAFE_INTEGER,
fraction: 2 ** 32, fraction: 2 ** 32,
} value: Number.MAX_SAFE_INTEGER,
};
} }
export function getZero(currency: string): AmountJson { export function getZero(currency: string): AmountJson {
return { return {
currency, currency,
value: 0,
fraction: 0, fraction: 0,
} value: 0,
};
} }
export function add(first: AmountJson, ...rest: AmountJson[]): Result { export function add(first: AmountJson, ...rest: AmountJson[]): Result {
let currency = first.currency; const currency = first.currency;
let value = first.value + Math.floor(first.fraction / fractionalBase); let value = first.value + Math.floor(first.fraction / fractionalBase);
if (value > Number.MAX_SAFE_INTEGER) { if (value > Number.MAX_SAFE_INTEGER) {
return { amount: getMaxAmount(currency), saturated: true }; return { amount: getMaxAmount(currency), saturated: true };
} }
let fraction = first.fraction % fractionalBase; let fraction = first.fraction % fractionalBase;
for (let x of rest) { for (const x of rest) {
if (x.currency !== currency) { if (x.currency !== currency) {
throw Error(`Mismatched currency: ${x.currency} and ${currency}`); throw Error(`Mismatched currency: ${x.currency} and ${currency}`);
} }
@ -665,11 +665,11 @@ export namespace Amounts {
export function sub(a: AmountJson, ...rest: AmountJson[]): Result { export function sub(a: AmountJson, ...rest: AmountJson[]): Result {
let currency = a.currency; const currency = a.currency;
let value = a.value; let value = a.value;
let fraction = a.fraction; let fraction = a.fraction;
for (let b of rest) { for (const b of rest) {
if (b.currency !== currency) { if (b.currency !== currency) {
throw Error(`Mismatched currency: ${b.currency} and ${currency}`); throw Error(`Mismatched currency: ${b.currency} and ${currency}`);
} }
@ -695,10 +695,10 @@ export namespace Amounts {
if (a.currency !== b.currency) { if (a.currency !== b.currency) {
throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`); throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
} }
let av = a.value + Math.floor(a.fraction / fractionalBase); const av = a.value + Math.floor(a.fraction / fractionalBase);
let af = a.fraction % fractionalBase; const af = a.fraction % fractionalBase;
let bv = b.value + Math.floor(b.fraction / fractionalBase); const bv = b.value + Math.floor(b.fraction / fractionalBase);
let bf = b.fraction % fractionalBase; const bf = b.fraction % fractionalBase;
switch (true) { switch (true) {
case av < bv: case av < bv:
return -1; return -1;
@ -708,7 +708,7 @@ export namespace Amounts {
return -1; return -1;
case af > bf: case af > bf:
return 1; return 1;
case af == bf: case af === bf:
return 0; return 0;
default: default:
throw Error("assertion failed"); throw Error("assertion failed");
@ -717,25 +717,25 @@ export namespace Amounts {
export function copy(a: AmountJson): AmountJson { export function copy(a: AmountJson): AmountJson {
return { return {
value: a.value,
fraction: a.fraction,
currency: a.currency, currency: a.currency,
} fraction: a.fraction,
value: a.value,
};
} }
export function divide(a: AmountJson, n: number): AmountJson { export function divide(a: AmountJson, n: number): AmountJson {
if (n == 0) { if (n === 0) {
throw Error(`Division by 0`); throw Error(`Division by 0`);
} }
if (n == 1) { if (n === 1) {
return {value: a.value, fraction: a.fraction, currency: a.currency}; return {value: a.value, fraction: a.fraction, currency: a.currency};
} }
let r = a.value % n; const r = a.value % n;
return { return {
currency: a.currency, currency: a.currency,
value: Math.floor(a.value / n),
fraction: Math.floor(((r * fractionalBase) + a.fraction) / n), fraction: Math.floor(((r * fractionalBase) + a.fraction) / n),
} value: Math.floor(a.value / n),
};
} }
export function isNonZero(a: AmountJson) { export function isNonZero(a: AmountJson) {
@ -746,15 +746,15 @@ export namespace Amounts {
* Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct. * Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct.
*/ */
export function parse(s: string): AmountJson|undefined { export function parse(s: string): AmountJson|undefined {
let res = s.match(/([a-zA-Z0-9_*-]+):([0-9])+([.][0-9]+)?/); const res = s.match(/([a-zA-Z0-9_*-]+):([0-9])+([.][0-9]+)?/);
if (!res) { if (!res) {
return undefined; return undefined;
} }
return { return {
currency: res[1], currency: res[1],
value: Number.parseInt(res[2]),
fraction: Math.round(fractionalBase * Number.parseFloat(res[3] || "0")), fraction: Math.round(fractionalBase * Number.parseFloat(res[3] || "0")),
} value: Number.parseInt(res[2]),
};
} }
} }

View File

@ -1,135 +1,152 @@
/*
This file is part of TALER
(C) 2017 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import {test} from "ava"; import {test} from "ava";
import * as types from "./types"; import * as types from "./types";
import * as wallet from "./wallet"; import * as wallet from "./wallet";
function a(x: string): types.AmountJson { function a(x: string): types.AmountJson {
let amt = types.Amounts.parse(x); const amt = types.Amounts.parse(x);
if (!amt) { if (!amt) {
throw Error("invalid amount"); throw Error("invalid amount");
} }
return amt; return amt;
} }
function fakeCwd(current: string, value: string, feeDeposit: string): wallet.CoinWithDenom { function fakeCwd(current: string, value: string, feeDeposit: string): wallet.CoinWithDenom {
return { return {
coin: { coin: {
currentAmount: a(current), blindingKey: "(mock)",
coinPub: "(mock)",
coinPriv: "(mock)", coinPriv: "(mock)",
coinPub: "(mock)",
currentAmount: a(current),
denomPub: "(mock)", denomPub: "(mock)",
denomSig: "(mock)", denomSig: "(mock)",
exchangeBaseUrl: "(mock)", exchangeBaseUrl: "(mock)",
blindingKey: "(mock)",
reservePub: "(mock)", reservePub: "(mock)",
status: types.CoinStatus.Fresh, status: types.CoinStatus.Fresh,
}, },
denom: { denom: {
value: a(value),
feeDeposit: a(feeDeposit),
denomPub: "(mock)", denomPub: "(mock)",
denomPubHash: "(mock)", denomPubHash: "(mock)",
feeWithdraw: a("EUR:0.0"), exchangeBaseUrl: "(mock)",
feeDeposit: a(feeDeposit),
feeRefresh: a("EUR:0.0"), feeRefresh: a("EUR:0.0"),
feeRefund: a("EUR:0.0"), feeRefund: a("EUR:0.0"),
stampStart: "(mock)", feeWithdraw: a("EUR:0.0"),
stampExpireWithdraw: "(mock)",
stampExpireLegal: "(mock)",
stampExpireDeposit: "(mock)",
masterSig: "(mock)",
status: types.DenominationStatus.VerifiedGood,
isOffered: true, isOffered: true,
exchangeBaseUrl: "(mock)", masterSig: "(mock)",
stampExpireDeposit: "(mock)",
stampExpireLegal: "(mock)",
stampExpireWithdraw: "(mock)",
stampStart: "(mock)",
status: types.DenominationStatus.VerifiedGood,
value: a(value),
}, },
} };
} }
test("coin selection 1", (t) => {
test("coin selection 1", t => { const cds: wallet.CoinWithDenom[] = [
let cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.1"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.1"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
]; ];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.1")); const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.1"));
if (!res) { if (!res) {
t.fail(); t.fail();
return; return;
} }
t.true(res.length == 2); t.true(res.length === 2);
t.pass(); t.pass();
}); });
test("coin selection 2", t => { test("coin selection 2", (t) => {
let cds: wallet.CoinWithDenom[] = [ const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
// Merchant covers the fee, this one shouldn't be used // Merchant covers the fee, this one shouldn't be used
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
]; ];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5")); const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
if (!res) { if (!res) {
t.fail(); t.fail();
return; return;
} }
t.true(res.length == 2); t.true(res.length === 2);
t.pass(); t.pass();
}); });
test("coin selection 3", t => { test("coin selection 3", (t) => {
let cds: wallet.CoinWithDenom[] = [ const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
// this coin should be selected instead of previous one with fee // this coin should be selected instead of previous one with fee
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
]; ];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5")); const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
if (!res) { if (!res) {
t.fail(); t.fail();
return; return;
} }
t.true(res.length == 2); t.true(res.length === 2);
t.pass(); t.pass();
}); });
test("coin selection 4", (t) => {
test("coin selection 4", t => { const cds: wallet.CoinWithDenom[] = [
let cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
]; ];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2")); const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
if (!res) { if (!res) {
t.fail(); t.fail();
return; return;
} }
t.true(res.length == 3); t.true(res.length === 3);
t.pass(); t.pass();
}); });
test("coin selection 5", t => { test("coin selection 5", (t) => {
let cds: wallet.CoinWithDenom[] = [ const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
]; ];
let res = wallet.selectCoins(cds, a("EUR:4.0"), a("EUR:0.2")); const res = wallet.selectCoins(cds, a("EUR:4.0"), a("EUR:0.2"));
t.true(!res); t.true(!res);
t.pass(); t.pass();
}); });
test("coin selection 6", t => { test("coin selection 6", (t) => {
let cds: wallet.CoinWithDenom[] = [ const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
]; ];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2")); const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
t.true(!res); t.true(!res);
t.pass(); t.pass();
}); });

File diff suppressed because it is too large Load Diff

View File

@ -17,11 +17,12 @@
import { import {
AmountJson, AmountJson,
CoinRecord, CoinRecord,
CurrencyRecord,
DenominationRecord,
ExchangeRecord,
PreCoinRecord, PreCoinRecord,
ReserveCreationInfo, ReserveCreationInfo,
ExchangeRecord, ReserveRecord,
CurrencyRecord,
ReserveRecord, DenominationRecord
} from "./types"; } from "./types";
/** /**
@ -32,12 +33,12 @@ import {
export function getReserveCreationInfo(baseUrl: string, export function getReserveCreationInfo(baseUrl: string,
amount: AmountJson): Promise<ReserveCreationInfo> { amount: AmountJson): Promise<ReserveCreationInfo> {
let m = { type: "reserve-creation-info", detail: { baseUrl, amount } }; const m = { type: "reserve-creation-info", detail: { baseUrl, amount } };
return new Promise<ReserveCreationInfo>((resolve, reject) => { return new Promise<ReserveCreationInfo>((resolve, reject) => {
chrome.runtime.sendMessage(m, (resp) => { chrome.runtime.sendMessage(m, (resp) => {
if (resp.error) { if (resp.error) {
console.error("error response", resp); console.error("error response", resp);
let e = Error("call to reserve-creation-info failed"); const e = Error("call to reserve-creation-info failed");
(e as any).errorResponse = resp; (e as any).errorResponse = resp;
reject(e); reject(e);
return; return;

View File

@ -24,6 +24,20 @@
/** /**
* Imports. * Imports.
*/ */
import { Checkable } from "./checkable";
import { ChromeBadge } from "./chromeBadge";
import { BrowserHttpLib } from "./http";
import * as logging from "./logging";
import {
Index,
Store,
} from "./query";
import {
AmountJson,
Contract,
Notifier,
} from "./types";
import URI = require("urijs");
import { import {
Badge, Badge,
ConfirmReserveRequest, ConfirmReserveRequest,
@ -32,19 +46,8 @@ import {
Stores, Stores,
Wallet, Wallet,
} from "./wallet"; } from "./wallet";
import {
AmountJson,
Contract,
Notifier,
} from "./types";
import MessageSender = chrome.runtime.MessageSender;
import { ChromeBadge } from "./chromeBadge";
import * as logging from "./logging";
import { Store, Index } from "./query";
import { BrowserHttpLib } from "./http";
import { Checkable } from "./checkable";
import Port = chrome.runtime.Port; import Port = chrome.runtime.Port;
import URI = require("urijs"); import MessageSender = chrome.runtime.MessageSender;
const DB_NAME = "taler"; const DB_NAME = "taler";
@ -62,30 +65,31 @@ type Handler = (detail: any, sender: MessageSender) => Promise<any>;
function makeHandlers(db: IDBDatabase, function makeHandlers(db: IDBDatabase,
wallet: Wallet): { [msg: string]: Handler } { wallet: Wallet): { [msg: string]: Handler } {
return { return {
["balances"]: function (detail, sender) { ["balances"]: (detail, sender) => {
return wallet.getBalances(); return wallet.getBalances();
}, },
["dump-db"]: function (detail, sender) { ["dump-db"]: (detail, sender) => {
return exportDb(db); return exportDb(db);
}, },
["import-db"]: function (detail, sender) { ["import-db"]: (detail, sender) => {
return importDb(db, detail.dump); return importDb(db, detail.dump);
}, },
["get-tab-cookie"]: function (detail, sender) { ["get-tab-cookie"]: (detail, sender) => {
if (!sender || !sender.tab || !sender.tab.id) { if (!sender || !sender.tab || !sender.tab.id) {
return Promise.resolve(); return Promise.resolve();
} }
let id: number = sender.tab.id; const id: number = sender.tab.id;
let info: any = <any> paymentRequestCookies[id]; const info: any = paymentRequestCookies[id] as any;
delete paymentRequestCookies[id]; delete paymentRequestCookies[id];
return Promise.resolve(info); return Promise.resolve(info);
}, },
["ping"]: function (detail, sender) { ["ping"]: (detail, sender) => {
return Promise.resolve(); return Promise.resolve();
}, },
["reset"]: function (detail, sender) { ["reset"]: (detail, sender) => {
if (db) { if (db) {
let tx = db.transaction(Array.from(db.objectStoreNames), 'readwrite'); const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < db.objectStoreNames.length; i++) { for (let i = 0; i < db.objectStoreNames.length; i++) {
tx.objectStore(db.objectStoreNames[i]).clear(); tx.objectStore(db.objectStoreNames[i]).clear();
} }
@ -97,15 +101,15 @@ function makeHandlers(db: IDBDatabase,
// Response is synchronous // Response is synchronous
return Promise.resolve({}); return Promise.resolve({});
}, },
["create-reserve"]: function (detail, sender) { ["create-reserve"]: (detail, sender) => {
const d = { const d = {
exchange: detail.exchange,
amount: detail.amount, amount: detail.amount,
exchange: detail.exchange,
}; };
const req = CreateReserveRequest.checked(d); const req = CreateReserveRequest.checked(d);
return wallet.createReserve(req); return wallet.createReserve(req);
}, },
["confirm-reserve"]: function (detail, sender) { ["confirm-reserve"]: (detail, sender) => {
// TODO: make it a checkable // TODO: make it a checkable
const d = { const d = {
reservePub: detail.reservePub, reservePub: detail.reservePub,
@ -113,10 +117,10 @@ function makeHandlers(db: IDBDatabase,
const req = ConfirmReserveRequest.checked(d); const req = ConfirmReserveRequest.checked(d);
return wallet.confirmReserve(req); return wallet.confirmReserve(req);
}, },
["generate-nonce"]: function (detail, sender) { ["generate-nonce"]: (detail, sender) => {
return wallet.generateNonce(); return wallet.generateNonce();
}, },
["confirm-pay"]: function (detail, sender) { ["confirm-pay"]: (detail, sender) => {
let offer: OfferRecord; let offer: OfferRecord;
try { try {
offer = OfferRecord.checked(detail.offer); offer = OfferRecord.checked(detail.offer);
@ -124,9 +128,9 @@ function makeHandlers(db: IDBDatabase,
if (e instanceof Checkable.SchemaError) { if (e instanceof Checkable.SchemaError) {
console.error("schema error:", e.message); console.error("schema error:", e.message);
return Promise.resolve({ return Promise.resolve({
detail,
error: "invalid contract", error: "invalid contract",
hint: e.message, hint: e.message,
detail: detail,
}); });
} else { } else {
throw e; throw e;
@ -135,7 +139,7 @@ function makeHandlers(db: IDBDatabase,
return wallet.confirmPay(offer); return wallet.confirmPay(offer);
}, },
["check-pay"]: function (detail, sender) { ["check-pay"]: (detail, sender) => {
let offer: OfferRecord; let offer: OfferRecord;
try { try {
offer = OfferRecord.checked(detail.offer); offer = OfferRecord.checked(detail.offer);
@ -143,9 +147,9 @@ function makeHandlers(db: IDBDatabase,
if (e instanceof Checkable.SchemaError) { if (e instanceof Checkable.SchemaError) {
console.error("schema error:", e.message); console.error("schema error:", e.message);
return Promise.resolve({ return Promise.resolve({
detail,
error: "invalid contract", error: "invalid contract",
hint: e.message, hint: e.message,
detail: detail,
}); });
} else { } else {
throw e; throw e;
@ -153,34 +157,34 @@ function makeHandlers(db: IDBDatabase,
} }
return wallet.checkPay(offer); return wallet.checkPay(offer);
}, },
["query-payment"]: function (detail: any, sender: MessageSender) { ["query-payment"]: (detail: any, sender: MessageSender) => {
if (sender.tab && sender.tab.id) { if (sender.tab && sender.tab.id) {
rateLimitCache[sender.tab.id]++; rateLimitCache[sender.tab.id]++;
if (rateLimitCache[sender.tab.id] > 10) { if (rateLimitCache[sender.tab.id] > 10) {
console.warn("rate limit for query-payment exceeded"); console.warn("rate limit for query-payment exceeded");
let msg = { const msg = {
error: "rate limit exceeded for query-payment", error: "rate limit exceeded for query-payment",
rateLimitExceeded: true,
hint: "Check for redirect loops", hint: "Check for redirect loops",
rateLimitExceeded: true,
}; };
return Promise.resolve(msg); return Promise.resolve(msg);
} }
} }
return wallet.queryPayment(detail.url); return wallet.queryPayment(detail.url);
}, },
["exchange-info"]: function (detail) { ["exchange-info"]: (detail) => {
if (!detail.baseUrl) { if (!detail.baseUrl) {
return Promise.resolve({ error: "bad url" }); return Promise.resolve({ error: "bad url" });
} }
return wallet.updateExchangeFromUrl(detail.baseUrl); return wallet.updateExchangeFromUrl(detail.baseUrl);
}, },
["currency-info"]: function (detail) { ["currency-info"]: (detail) => {
if (!detail.name) { if (!detail.name) {
return Promise.resolve({ error: "name missing" }); return Promise.resolve({ error: "name missing" });
} }
return wallet.getCurrencyRecord(detail.name); return wallet.getCurrencyRecord(detail.name);
}, },
["hash-contract"]: function (detail) { ["hash-contract"]: (detail) => {
if (!detail.contract) { if (!detail.contract) {
return Promise.resolve({ error: "contract missing" }); return Promise.resolve({ error: "contract missing" });
} }
@ -188,91 +192,91 @@ function makeHandlers(db: IDBDatabase,
return { hash }; return { hash };
}); });
}, },
["put-history-entry"]: function (detail: any) { ["put-history-entry"]: (detail: any) => {
if (!detail.historyEntry) { if (!detail.historyEntry) {
return Promise.resolve({ error: "historyEntry missing" }); return Promise.resolve({ error: "historyEntry missing" });
} }
return wallet.putHistory(detail.historyEntry); return wallet.putHistory(detail.historyEntry);
}, },
["save-offer"]: function (detail: any) { ["save-offer"]: (detail: any) => {
let offer = detail.offer; const offer = detail.offer;
if (!offer) { if (!offer) {
return Promise.resolve({ error: "offer missing" }); return Promise.resolve({ error: "offer missing" });
} }
console.log("handling safe-offer", detail); console.log("handling safe-offer", detail);
// FIXME: fully migrate to new terminology // FIXME: fully migrate to new terminology
let checkedOffer = OfferRecord.checked(offer); const checkedOffer = OfferRecord.checked(offer);
return wallet.saveOffer(checkedOffer); return wallet.saveOffer(checkedOffer);
}, },
["reserve-creation-info"]: function (detail, sender) { ["reserve-creation-info"]: (detail, sender) => {
if (!detail.baseUrl || typeof detail.baseUrl !== "string") { if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
return Promise.resolve({ error: "bad url" }); return Promise.resolve({ error: "bad url" });
} }
let amount = AmountJson.checked(detail.amount); const amount = AmountJson.checked(detail.amount);
return wallet.getReserveCreationInfo(detail.baseUrl, amount); return wallet.getReserveCreationInfo(detail.baseUrl, amount);
}, },
["get-history"]: function (detail, sender) { ["get-history"]: (detail, sender) => {
// TODO: limit history length // TODO: limit history length
return wallet.getHistory(); return wallet.getHistory();
}, },
["get-offer"]: function (detail, sender) { ["get-offer"]: (detail, sender) => {
return wallet.getOffer(detail.offerId); return wallet.getOffer(detail.offerId);
}, },
["get-exchanges"]: function (detail, sender) { ["get-exchanges"]: (detail, sender) => {
return wallet.getExchanges(); return wallet.getExchanges();
}, },
["get-currencies"]: function (detail, sender) { ["get-currencies"]: (detail, sender) => {
return wallet.getCurrencies(); return wallet.getCurrencies();
}, },
["update-currency"]: function (detail, sender) { ["update-currency"]: (detail, sender) => {
return wallet.updateCurrency(detail.currencyRecord); return wallet.updateCurrency(detail.currencyRecord);
}, },
["get-reserves"]: function (detail, sender) { ["get-reserves"]: (detail, sender) => {
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangeBaseUrl missing")); return Promise.reject(Error("exchangeBaseUrl missing"));
} }
return wallet.getReserves(detail.exchangeBaseUrl); return wallet.getReserves(detail.exchangeBaseUrl);
}, },
["get-payback-reserves"]: function (detail, sender) { ["get-payback-reserves"]: (detail, sender) => {
return wallet.getPaybackReserves(); return wallet.getPaybackReserves();
}, },
["withdraw-payback-reserve"]: function (detail, sender) { ["withdraw-payback-reserve"]: (detail, sender) => {
if (typeof detail.reservePub !== "string") { if (typeof detail.reservePub !== "string") {
return Promise.reject(Error("reservePub missing")); return Promise.reject(Error("reservePub missing"));
} }
return wallet.withdrawPaybackReserve(detail.reservePub); return wallet.withdrawPaybackReserve(detail.reservePub);
}, },
["get-coins"]: function (detail, sender) { ["get-coins"]: (detail, sender) => {
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing")); return Promise.reject(Error("exchangBaseUrl missing"));
} }
return wallet.getCoins(detail.exchangeBaseUrl); return wallet.getCoins(detail.exchangeBaseUrl);
}, },
["get-precoins"]: function (detail, sender) { ["get-precoins"]: (detail, sender) => {
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing")); return Promise.reject(Error("exchangBaseUrl missing"));
} }
return wallet.getPreCoins(detail.exchangeBaseUrl); return wallet.getPreCoins(detail.exchangeBaseUrl);
}, },
["get-denoms"]: function (detail, sender) { ["get-denoms"]: (detail, sender) => {
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing")); return Promise.reject(Error("exchangBaseUrl missing"));
} }
return wallet.getDenoms(detail.exchangeBaseUrl); return wallet.getDenoms(detail.exchangeBaseUrl);
}, },
["refresh-coin"]: function (detail, sender) { ["refresh-coin"]: (detail, sender) => {
if (typeof detail.coinPub !== "string") { if (typeof detail.coinPub !== "string") {
return Promise.reject(Error("coinPub missing")); return Promise.reject(Error("coinPub missing"));
} }
return wallet.refresh(detail.coinPub); return wallet.refresh(detail.coinPub);
}, },
["payback-coin"]: function (detail, sender) { ["payback-coin"]: (detail, sender) => {
if (typeof detail.coinPub !== "string") { if (typeof detail.coinPub !== "string") {
return Promise.reject(Error("coinPub missing")); return Promise.reject(Error("coinPub missing"));
} }
return wallet.payback(detail.coinPub); return wallet.payback(detail.coinPub);
}, },
["payment-failed"]: function (detail, sender) { ["payment-failed"]: (detail, sender) => {
// For now we just update exchanges (maybe the exchange did something // For now we just update exchanges (maybe the exchange did something
// wrong and the keys were messed up). // wrong and the keys were messed up).
// FIXME: in the future we should look at what actually went wrong. // FIXME: in the future we should look at what actually went wrong.
@ -280,9 +284,9 @@ function makeHandlers(db: IDBDatabase,
wallet.updateExchanges(); wallet.updateExchanges();
return Promise.resolve(); return Promise.resolve();
}, },
["payment-succeeded"]: function (detail, sender) { ["payment-succeeded"]: (detail, sender) => {
let contractHash = detail.contractHash; const contractHash = detail.contractHash;
let merchantSig = detail.merchantSig; const merchantSig = detail.merchantSig;
if (!contractHash) { if (!contractHash) {
return Promise.reject(Error("contractHash missing")); return Promise.reject(Error("contractHash missing"));
} }
@ -307,7 +311,7 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any)
try { try {
const p = handlers[req.type](req.detail, sender); const p = handlers[req.type](req.detail, sender);
let r = await p; const r = await p;
try { try {
sendResponse(r); sendResponse(r);
} catch (e) { } catch (e) {
@ -317,7 +321,7 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any)
console.log(`exception during wallet handler for '${req.type}'`); console.log(`exception during wallet handler for '${req.type}'`);
console.log("request", req); console.log("request", req);
console.error(e); console.error(e);
let stack = undefined; let stack;
try { try {
stack = e.stack.toString(); stack = e.stack.toString();
} catch (e) { } catch (e) {
@ -325,9 +329,9 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any)
} }
try { try {
sendResponse({ sendResponse({
stack,
error: "exception", error: "exception",
hint: e.message, hint: e.message,
stack,
}); });
} catch (e) { } catch (e) {
console.log(e); console.log(e);
@ -344,7 +348,7 @@ class ChromeNotifier implements Notifier {
console.log("got connect!"); console.log("got connect!");
this.ports.push(port); this.ports.push(port);
port.onDisconnect.addListener(() => { port.onDisconnect.addListener(() => {
let i = this.ports.indexOf(port); const i = this.ports.indexOf(port);
if (i >= 0) { if (i >= 0) {
this.ports.splice(i, 1); this.ports.splice(i, 1);
} else { } else {
@ -355,7 +359,7 @@ class ChromeNotifier implements Notifier {
} }
notify() { notify() {
for (let p of this.ports) { for (const p of this.ports) {
p.postMessage({ notify: true }); p.postMessage({ notify: true });
} }
} }
@ -365,7 +369,7 @@ class ChromeNotifier implements Notifier {
/** /**
* Mapping from tab ID to payment information (if any). * Mapping from tab ID to payment information (if any).
*/ */
let paymentRequestCookies: { [n: number]: any } = {}; const paymentRequestCookies: { [n: number]: any } = {};
/** /**
@ -376,19 +380,19 @@ let paymentRequestCookies: { [n: number]: any } = {};
*/ */
function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: string, tabId: number): any { function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: string, tabId: number): any {
const headers: { [s: string]: string } = {}; const headers: { [s: string]: string } = {};
for (let kv of headerList) { for (const kv of headerList) {
if (kv.value) { if (kv.value) {
headers[kv.name.toLowerCase()] = kv.value; headers[kv.name.toLowerCase()] = kv.value;
} }
} }
let fields = { const fields = {
contract_url: headers["x-taler-contract-url"],
contract_query: headers["x-taler-contract-query"], contract_query: headers["x-taler-contract-query"],
contract_url: headers["x-taler-contract-url"],
offer_url: headers["x-taler-offer-url"], offer_url: headers["x-taler-offer-url"],
} };
let talerHeaderFound = Object.keys(fields).filter((x: any) => (fields as any)[x]).length !== 0; const talerHeaderFound = Object.keys(fields).filter((x: any) => (fields as any)[x]).length !== 0;
if (!talerHeaderFound) { if (!talerHeaderFound) {
// looks like it's not a taler request, it might be // looks like it's not a taler request, it might be
@ -397,27 +401,26 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
return; return;
} }
let payDetail = { const payDetail = {
contract_url: fields.contract_url, contract_url: fields.contract_url,
offer_url: fields.offer_url, offer_url: fields.offer_url,
}; };
console.log("got pay detail", payDetail) console.log("got pay detail", payDetail);
// This cookie will be read by the injected content script // This cookie will be read by the injected content script
// in the tab that displays the page. // in the tab that displays the page.
paymentRequestCookies[tabId] = { paymentRequestCookies[tabId] = {
type: "pay",
payDetail, payDetail,
type: "pay",
}; };
} }
function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHeader[], function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHeader[],
url: string, tabId: number): any { url: string, tabId: number): any {
const headers: { [s: string]: string } = {}; const headers: { [s: string]: string } = {};
for (let kv of headerList) { for (const kv of headerList) {
if (kv.value) { if (kv.value) {
headers[kv.name.toLowerCase()] = kv.value; headers[kv.name.toLowerCase()] = kv.value;
} }
@ -432,7 +435,7 @@ function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHea
const amount = headers["x-taler-amount"]; const amount = headers["x-taler-amount"];
if (amount) { if (amount) {
let callbackUrl = headers["x-taler-callback-url"]; const callbackUrl = headers["x-taler-callback-url"];
if (!callbackUrl) { if (!callbackUrl) {
console.log("202 not understood (X-Taler-Callback-Url missing)"); console.log("202 not understood (X-Taler-Callback-Url missing)");
return; return;
@ -441,30 +444,29 @@ function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHea
try { try {
amountParsed = JSON.parse(amount); amountParsed = JSON.parse(amount);
} catch (e) { } catch (e) {
let uri = new URI(chrome.extension.getURL("/src/pages/error.html")); const uri = new URI(chrome.extension.getURL("/src/pages/error.html"));
let p = { const p = {
message: `Can't parse amount ("${amount}"): ${e.message}`, message: `Can't parse amount ("${amount}"): ${e.message}`,
}; };
let redirectUrl = uri.query(p).href(); const redirectUrl = uri.query(p).href();
// FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed // FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed
chrome.tabs.update(tabId, {url: redirectUrl}); chrome.tabs.update(tabId, {url: redirectUrl});
return; return;
} }
let wtTypes = headers["x-taler-wt-types"]; const wtTypes = headers["x-taler-wt-types"];
if (!wtTypes) { if (!wtTypes) {
console.log("202 not understood (X-Taler-Wt-Types missing)"); console.log("202 not understood (X-Taler-Wt-Types missing)");
return; return;
} }
let params = { const params = {
amount: amount, amount,
callback_url: new URI(callbackUrl)
.absoluteTo(url),
bank_url: url, bank_url: url,
wt_types: wtTypes, callback_url: new URI(callbackUrl) .absoluteTo(url),
suggested_exchange_url: headers["x-taler-suggested-exchange"], suggested_exchange_url: headers["x-taler-suggested-exchange"],
wt_types: wtTypes,
}; };
let uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html")); const uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html"));
let redirectUrl = uri.query(params).href(); const redirectUrl = uri.query(params).href();
console.log("redirecting to", redirectUrl); console.log("redirecting to", redirectUrl);
// FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed // FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed
chrome.tabs.update(tabId, {url: redirectUrl}); chrome.tabs.update(tabId, {url: redirectUrl});
@ -484,21 +486,21 @@ function clearRateLimitCache() {
export async function wxMain() { export async function wxMain() {
window.onerror = (m, source, lineno, colno, error) => { window.onerror = (m, source, lineno, colno, error) => {
logging.record("error", m + error, undefined, source || "(unknown)", lineno || 0, colno || 0); logging.record("error", m + error, undefined, source || "(unknown)", lineno || 0, colno || 0);
} };
chrome.browserAction.setBadgeText({ text: "" }); chrome.browserAction.setBadgeText({ text: "" });
const badge = new ChromeBadge(); const badge = new ChromeBadge();
chrome.tabs.query({}, function (tabs) { chrome.tabs.query({}, (tabs) => {
for (let tab of tabs) { for (const tab of tabs) {
if (!tab.url || !tab.id) { if (!tab.url || !tab.id) {
return; return;
} }
let uri = new URI(tab.url); const uri = new URI(tab.url);
if (uri.protocol() === "http" || uri.protocol() === "https") { if (uri.protocol() === "http" || uri.protocol() === "https") {
console.log("injecting into existing tab", tab.id); console.log("injecting into existing tab", tab.id);
chrome.tabs.executeScript(tab.id, { file: "/dist/contentScript-bundle.js" }); chrome.tabs.executeScript(tab.id, { file: "/dist/contentScript-bundle.js" });
let code = ` const code = `
if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) { if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) {
document.dispatchEvent(new Event("taler-probe-result")); document.dispatchEvent(new Event("taler-probe-result"));
} }
@ -511,13 +513,13 @@ export async function wxMain() {
const tabTimers: {[n: number]: number[]} = {}; const tabTimers: {[n: number]: number[]} = {};
chrome.tabs.onRemoved.addListener((tabId, changeInfo) => { chrome.tabs.onRemoved.addListener((tabId, changeInfo) => {
let tt = tabTimers[tabId] || []; const tt = tabTimers[tabId] || [];
for (let t of tt) { for (const t of tt) {
chrome.extension.getBackgroundPage().clearTimeout(t); chrome.extension.getBackgroundPage().clearTimeout(t);
} }
}); });
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.status !== 'complete') { if (changeInfo.status !== "complete") {
return; return;
} }
const timers: number[] = []; const timers: number[] = [];
@ -525,7 +527,7 @@ export async function wxMain() {
const addRun = (dt: number) => { const addRun = (dt: number) => {
const id = chrome.extension.getBackgroundPage().setTimeout(run, dt); const id = chrome.extension.getBackgroundPage().setTimeout(run, dt);
timers.push(id); timers.push(id);
} };
const run = () => { const run = () => {
timers.shift(); timers.shift();
@ -536,11 +538,11 @@ export async function wxMain() {
if (!tab.url || !tab.id) { if (!tab.url || !tab.id) {
return; return;
} }
let uri = new URI(tab.url); const uri = new URI(tab.url);
if (!(uri.protocol() === "http" || uri.protocol() === "https")) { if (!(uri.protocol() === "http" || uri.protocol() === "https")) {
return; return;
} }
let code = ` const code = `
if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) { if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) {
document.dispatchEvent(new Event("taler-probe-result")); document.dispatchEvent(new Event("taler-probe-result"));
} }
@ -600,7 +602,6 @@ export async function wxMain() {
} }
/** /**
* Return a promise that resolves * Return a promise that resolves
* to the taler wallet db. * to the taler wallet db.
@ -620,13 +621,13 @@ function openTalerDb(): Promise<IDBDatabase> {
switch (e.oldVersion) { switch (e.oldVersion) {
case 0: // DB does not exist yet case 0: // DB does not exist yet
for (let n in Stores) { for (const n in Stores) {
if ((Stores as any)[n] instanceof Store) { if ((Stores as any)[n] instanceof Store) {
let si: Store<any> = (Stores as any)[n]; const si: Store<any> = (Stores as any)[n];
const s = db.createObjectStore(si.name, si.storeParams); const s = db.createObjectStore(si.name, si.storeParams);
for (let indexName in (si as any)) { for (const indexName in (si as any)) {
if ((si as any)[indexName] instanceof Index) { if ((si as any)[indexName] instanceof Index) {
let ii: Index<any, any> = (si as any)[indexName]; const ii: Index<any, any> = (si as any)[indexName];
s.createIndex(ii.indexName, ii.keyPath); s.createIndex(ii.indexName, ii.keyPath);
} }
} }
@ -649,26 +650,27 @@ function openTalerDb(): Promise<IDBDatabase> {
function exportDb(db: IDBDatabase): Promise<any> { function exportDb(db: IDBDatabase): Promise<any> {
let dump = { const dump = {
name: db.name, name: db.name,
version: db.version,
stores: {} as {[s: string]: any}, stores: {} as {[s: string]: any},
version: db.version,
}; };
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let tx = db.transaction(Array.from(db.objectStoreNames)); const tx = db.transaction(Array.from(db.objectStoreNames));
tx.addEventListener("complete", () => { tx.addEventListener("complete", () => {
resolve(dump); resolve(dump);
}); });
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < db.objectStoreNames.length; i++) { for (let i = 0; i < db.objectStoreNames.length; i++) {
let name = db.objectStoreNames[i]; const name = db.objectStoreNames[i];
let storeDump = {} as {[s: string]: any}; const storeDump = {} as {[s: string]: any};
dump.stores[name] = storeDump; dump.stores[name] = storeDump;
let store = tx.objectStore(name) tx.objectStore(name)
.openCursor() .openCursor()
.addEventListener("success", (e: Event) => { .addEventListener("success", (e: Event) => {
let cursor = (e.target as any).result; const cursor = (e.target as any).result;
if (cursor) { if (cursor) {
storeDump[cursor.key] = cursor.value; storeDump[cursor.key] = cursor.value;
cursor.continue(); cursor.continue();
@ -682,19 +684,22 @@ function exportDb(db: IDBDatabase): Promise<any> {
function importDb(db: IDBDatabase, dump: any): Promise<void> { function importDb(db: IDBDatabase, dump: any): Promise<void> {
console.log("importing db", dump); console.log("importing db", dump);
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
let tx = db.transaction(Array.from(db.objectStoreNames), "readwrite"); const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
for (let storeName in dump.stores) { if (dump.stores) {
let objects = []; for (const storeName in dump.stores) {
for (let key in dump.stores[storeName]) { const objects = [];
objects.push(dump.stores[storeName][key]); const dumpStore = dump.stores[storeName];
for (const key in dumpStore) {
objects.push(dumpStore[key]);
} }
console.log(`importing ${objects.length} records into ${storeName}`); console.log(`importing ${objects.length} records into ${storeName}`);
let store = tx.objectStore(storeName); const store = tx.objectStore(storeName);
let clearReq = store.clear(); const clearReq = store.clear();
for (let obj of objects) { for (const obj of objects) {
store.put(obj); store.put(obj);
} }
} }
}
tx.addEventListener("complete", () => { tx.addEventListener("complete", () => {
resolve(); resolve();
}); });

View File

@ -8,7 +8,26 @@
"max-line-length": { "max-line-length": {
"options": [120] "options": [120]
}, },
"space-before-function-paren": [true, {"named": "never", "anonymous": "always"}] "space-before-function-paren": [true, {"named": "never", "anonymous": "always"}],
"no-console": [false],
"no-consecutive-blank-lines": [true, 2],
"forin": false,
"member-access": false,
"variable-name": false,
"interface-name": false,
"max-classes-per-file": false,
"ordered-imports": [true,
{
"import-sources-order": "lowercase-last",
"named-imports-order": "lowercase-last"
}
],
"no-namespace": false,
"member-ordering": false,
"array-type": [true, "array-simple"],
"class-name": false,
"no-bitwise": false,
"file-header": [true, "GNU General Public License"]
}, },
"rulesDirectory": [] "rulesDirectory": []
} }