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
lint: tsc yarn-install
$(tslint) --type-check --project tsconfig.json -t verbose 'src/**/*.ts'
$(tslint) --type-check -e src/i18n/strings.ts --project tsconfig.json -t verbose 'src/**/*.ts'
.PHONY: yarn-install
i18n: yarn-install

View File

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

View File

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

View File

@ -17,7 +17,7 @@
/**
* General helper React components.
*
*
* @author Florian Dold
*/

View File

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

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";
let masterPub1: string = "CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";
import {
DenominationRecord,
DenominationStatus,
ReserveRecord,
} from "../types";
let denomValid1: DenominationRecord = {
masterSig: "CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
stampStart: "/Date(1473148381)/",
stampExpireWithdraw: "/Date(2482300381)/",
stampExpireDeposit: "/Date(1851580381)/",
import {CryptoApi} from "./cryptoApi";
const masterPub1: string = "CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";
const denomValid1: DenominationRecord = {
denomPub: "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GHS84R3JHHP6GSM2D9Q6514CGT568R32C9J6CWM4DSH64TM4DSM851K0CA48CVKAC1P6H144C2160T46DHK8CVM4HJ274S38C1M6S338D9N6GWM8DT684T3JCT36S13EC9G88R3EGHQ8S0KJGSQ60SKGD216N33AGJ2651K2E9S60TMCD1N75244HHQ6X33EDJ570R3GGJ2651MACA38D130DA560VK4HHJ68WK2CA26GW3ECSH6D13EC9S88VK2GT66WVK8D9G750K0D9R8RRK4DHQ71332GHK8D23GE26710M2H9K6WVK8HJ38MVKEGA66N23AC9H88VKACT58MV3CCSJ6H1K4DT38GRK0C9M8N33CE1R60V4AHA38H1KECSH6S33JH9N8GRKGH1K68S36GH354520818CMG26C1H60R30C935452081918G2J2G0",
stampExpireLegal: "/Date(1567756381)/",
value: {
"currency": "PUDOS",
"value": 0,
"fraction": 100000
},
feeWithdraw: {
"currency": "PUDOS",
"value": 0,
"fraction": 10000
},
denomPubHash: "dummy",
exchangeBaseUrl: "https://exchange.example.com/",
feeDeposit: {
"currency": "PUDOS",
"value": 0,
"fraction": 10000
currency: "PUDOS",
fraction: 10000,
value: 0,
},
feeRefresh: {
"currency": "PUDOS",
"value": 0,
"fraction": 10000
currency: "PUDOS",
fraction: 10000,
value: 0,
},
feeRefund: {
"currency": "PUDOS",
"value": 0,
"fraction": 10000
currency: "PUDOS",
fraction: 10000,
value: 0,
},
feeWithdraw: {
currency: "PUDOS",
fraction: 10000,
value: 0,
},
denomPubHash: "dummy",
status: DenominationStatus.Unverified,
isOffered: true,
exchangeBaseUrl: "https://exchange.example.com/",
masterSig: "CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
stampExpireDeposit: "/Date(1851580381)/",
stampExpireLegal: "/Date(1567756381)/",
stampExpireWithdraw: "/Date(2482300381)/",
stampStart: "/Date(1473148381)/",
status: DenominationStatus.Unverified,
value: {
currency: "PUDOS",
fraction: 100000,
value: 0,
},
};
let denomInvalid1 = JSON.parse(JSON.stringify(denomValid1));
const denomInvalid1 = JSON.parse(JSON.stringify(denomValid1));
denomInvalid1.value.value += 1;
test("string hashing", async t => {
let crypto = new CryptoApi();
let s = await crypto.hashString("hello taler");
let sh = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
t.true(s == sh);
test("string hashing", async (t) => {
const crypto = new CryptoApi();
const s = await crypto.hashString("hello taler");
const sh = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
t.true(s === sh);
t.pass();
});
test("precoin creation", async t => {
let crypto = new CryptoApi();
let {priv, pub} = await crypto.createEddsaKeypair();
let r: ReserveRecord = {
reserve_pub: pub,
reserve_priv: priv,
hasPayback: false,
exchange_base_url: "https://example.com/exchange",
created: 0,
requested_amount: {currency: "PUDOS", value: 0, fraction: 0},
precoin_amount: {currency: "PUDOS", value: 0, fraction: 0},
current_amount: null,
test("precoin creation", async (t) => {
const crypto = new CryptoApi();
const {priv, pub} = await crypto.createEddsaKeypair();
const r: ReserveRecord = {
confirmed: false,
created: 0,
current_amount: null,
exchange_base_url: "https://example.com/exchange",
hasPayback: false,
last_query: null,
precoin_amount: {currency: "PUDOS", value: 0, fraction: 0},
requested_amount: {currency: "PUDOS", value: 0, fraction: 0},
reserve_priv: priv,
reserve_pub: pub,
};
let precoin = await crypto.createPreCoin(denomValid1, r);
const precoin = await crypto.createPreCoin(denomValid1, r);
t.pass();
});
test("denom validation", async t => {
let crypto = new CryptoApi();
test("denom validation", async (t) => {
const crypto = new CryptoApi();
let v: boolean;
v = await crypto.isValidDenom(denomValid1, masterPub1);
t.true(v);

View File

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

View File

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

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 * as native from "./emscInterface";
test("string hashing", t => {
let x = native.ByteArray.fromStringWithNull("hello taler");
let h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR"
let hc = x.hash().toCrock();
test("string hashing", (t) => {
const x = native.ByteArray.fromStringWithNull("hello taler");
const h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
const hc = x.hash().toCrock();
console.log(`# hc ${hc}`);
t.true(h === hc, "must equal");
t.pass();
});
test("signing", t => {
let x = native.ByteArray.fromStringWithNull("hello taler");
let priv = native.EddsaPrivateKey.create();
let pub = priv.getPublicKey();
let purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
let sig = native.eddsaSign(purpose, priv);
test("signing", (t) => {
const x = native.ByteArray.fromStringWithNull("hello taler");
const priv = native.EddsaPrivateKey.create();
const pub = priv.getPublicKey();
const purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
const sig = native.eddsaSign(purpose, priv);
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
t.pass();
});
test("signing-fixed-data", t => {
let x = native.ByteArray.fromStringWithNull("hello taler");
let purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
test("signing-fixed-data", (t) => {
const x = native.ByteArray.fromStringWithNull("hello taler");
const purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
const privStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
const pubStr = "YHCZB442FQFJ0ET20MWA8YJ53M61EZGJ6QKV1KTJZMRNXDY45WT0";
const sigStr = "7V6XY4QGC1406GPMT305MZQ1HDCR7R0S5BP02GTGDQFPSXB6YD2YDN5ZS7NJQCNP61Y39MRHXNXQ1Z15JY4CJY4CPDA6CKQ3313WG38";
let priv = native.EddsaPrivateKey.fromCrock(privStr);
t.true(privStr == priv.toCrock())
let pub = priv.getPublicKey();
t.true(pubStr == pub.toCrock());
let sig = native.EddsaSignature.fromCrock(sigStr);
t.true(sigStr == sig.toCrock())
let sig2 = native.eddsaSign(purpose, priv);
t.true(sig.toCrock() == sig2.toCrock());
const priv = native.EddsaPrivateKey.fromCrock(privStr);
t.true(privStr === priv.toCrock());
const pub = priv.getPublicKey();
t.true(pubStr === pub.toCrock());
const sig = native.EddsaSignature.fromCrock(sigStr);
t.true(sigStr === sig.toCrock());
const sig2 = native.eddsaSign(purpose, priv);
t.true(sig.toCrock() === sig2.toCrock());
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
t.pass();
});
const denomPubStr1 = "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30G9R64VK6HHS6MW42DSN8MVKJGHK6WR3CGT18MWMCDSM75138E1K8S0MADSQ68W34DHH6MW4CHA270W4CG9J6GW48DHG8MVK4E9S7523GEA56H0K4E1Q891KCCSG752KGC1M88VMCDSQ6D23CHHG8H33AGHG6MSK8GT26CRKAC1M64V3JCJ56CVKCC228MWMCHA26MS30H1J8MVKEDHJ70TMADHK892KJC1H60TKJDHM710KGGT584T38H9K851KCDHG60W30HJ28CT4CC1G8CR3JGJ28H236DJ28H330H9S890M2D9S8S14AGA369344GA36S248CHS70RKEDSS6MWKGDJ26D136GT465348CSS8S232CHM6GS34C9N8CS3GD9H60W36H1R8MSK2GSQ8MSM6C9R70SKCHHN6MW3ACJ28N0K2CA58RS3GCA26MV42G9P891KAG9Q8N0KGD9M850KEHJ16S130CA27124AE1G852KJCHR6S1KGDSJ8RTKED1S8RR3CCHP68W4CH9Q6GT34GT18GS36EA46N24AGSP6933GCHM60VMAE1S8GV3EHHN74W3GC1J651KEH9N8MSK0CSG6S2KEEA460R32C1M8D144GSR6RWKEC218S0KEGJ4611KEEA36CSKJC2564TM4CSJ6H230E1N74TM8C1P61342CSG60WKCGHH64VK2G9S8CRKAHHK88W30HJ388R3CH1Q6X2K2DHK8GSM4D1Q74WM4HA461146H9S6D33JDJ26D234C9Q6923ECSS60RM6CT46CSKCH1M6S13EH9J8S33GCSN4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00";
test("rsa-encode", t => {
const pubHashStr = "JM63YM5X7X547164QJ3MGJZ4WDD47GEQR5DW5SH35G4JFZXEJBHE5JBNZM5K8XN5C4BRW25BE6GSVAYBF790G2BZZ13VW91D41S4DS0"
let denomPub = native.RsaPublicKey.fromCrock(denomPubStr1);
let pubHash = denomPub.encode().hash();
t.true(pubHashStr == pubHash.toCrock());
test("rsa-encode", (t) => {
const pubHashStr = "JM63YM5X7X547164QJ3MGJZ4WDD47GEQR5DW5SH35G4JFZXEJBHE5JBNZM5K8XN5C4BRW25BE6GSVAYBF790G2BZZ13VW91D41S4DS0";
const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1);
const pubHash = denomPub.encode().hash();
t.true(pubHashStr === pubHash.toCrock());
t.pass();
});
test("withdraw-request", t => {
test("withdraw-request", (t) => {
const reservePrivStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
const reservePriv = native.EddsaPrivateKey.fromCrock(reservePrivStr);
const reservePub = reservePriv.getPublicKey();
const amountWithFee = new native.Amount({currency: "KUDOS", value: 1, fraction: 10000});
amountWithFee.add(new native.Amount({currency: "KUDOS", value: 0, fraction: 20000}));
const withdrawFee = new native.Amount({currency: "KUDOS", value: 0, fraction: 20000})
const withdrawFee = new native.Amount({currency: "KUDOS", value: 0, fraction: 20000});
const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1);
const ev = native.ByteArray.fromStringWithNull("hello, world");
// Signature
let withdrawRequest = new native.WithdrawRequestPS({
reserve_pub: reservePub,
const withdrawRequest = new native.WithdrawRequestPS({
amount_with_fee: amountWithFee.toNbo(),
withdraw_fee: withdrawFee.toNbo(),
h_coin_envelope: ev.hash(),
h_denomination_pub: denomPub.encode().hash(),
h_coin_envelope: ev.hash()
reserve_pub: reservePub,
withdraw_fee: withdrawFee.toNbo(),
});
var sigStr = "AD3T8W44NV193J19RAN3NAJHPP6RVB0R3NWV7ZK5G8Q946YDK0B6F8YJBNRRBXSPVTKY31S7BVZPJFFTJJRMY61DH51X4JSXK677428";
const sigStr = "AD3T8W44NV193J19RAN3NAJHPP6RVB0R3NWV7ZK5G8Q946YDK0B6F8YJBNRRBXSPVTKY31S7BVZPJFFTJJRMY61DH51X4JSXK677428";
var sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
const sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
t.true(native.eddsaVerify(native.SignaturePurpose.RESERVE_WITHDRAW, withdrawRequest.toPurpose(), sig, reservePub));
t.true(sig.toCrock() == sigStr);
t.true(sig.toCrock() === sigStr);
t.pass();
});
test("withdraw-request", t => {
test("withdraw-request", (t) => {
const a1 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000});
const a2 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000});
a1.add(a2);
let x = a1.toJson();
t.true(x.currency == "KUDOS");
t.true(x.fraction == 0);
t.true(x.value == 3);
const x = a1.toJson();
t.true(x.currency === "KUDOS");
t.true(x.fraction === 0);
t.true(x.value === 3);
t.pass();
});
test("ecdsa", t => {
test("ecdsa", (t) => {
const priv = native.EcdsaPrivateKey.create();
const pub1 = priv.getPublicKey();
t.pass();
});
test("ecdhe", t => {
test("ecdhe", (t) => {
const priv = native.EcdhePrivateKey.create();
const pub = priv.getPublicKey();
t.pass();

View File

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

View File

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

View File

@ -14,6 +14,9 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
// tslint:disable:no-var-requires
const path = require("path");
const fork = require("child_process").fork;
@ -49,7 +52,7 @@ export class Worker {
}
});
this.child.send({scriptFilename,cwd: process.cwd()});
this.child.send({scriptFilename, cwd: process.cwd()});
}
addEventListener(event: "message" | "error", fn: (x: any) => void): void {

View File

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

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 * as helpers from "./helpers";
test("URL canonicalization", t => {
test("URL canonicalization", (t) => {
// converts to relative, adds https
t.is(
"https://alice.example.com/exchange/",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 {Amounts} from "./types";
import * as types from "./types";
let amt = (value: number, fraction: number, currency: string): types.AmountJson => ({value, fraction, currency});
const amt = (value: number, fraction: number, currency: string): types.AmountJson => ({value, fraction, currency});
test("amount addition (simple)", t => {
let a1 = amt(1,0,"EUR");
let a2 = amt(1,0,"EUR");
let a3 = amt(2,0,"EUR");
t.true(0 == types.Amounts.cmp(Amounts.add(a1, a2).amount, a3));
test("amount addition (simple)", (t) => {
const a1 = amt(1, 0, "EUR");
const a2 = amt(1, 0, "EUR");
const a3 = amt(2, 0, "EUR");
t.true(0 === types.Amounts.cmp(Amounts.add(a1, a2).amount, a3));
t.pass();
});
test("amount addition (saturation)", t => {
let a1 = amt(1,0,"EUR");
let res = Amounts.add(Amounts.getMaxAmount("EUR"), a1);
test("amount addition (saturation)", (t) => {
const a1 = amt(1, 0, "EUR");
const res = Amounts.add(Amounts.getMaxAmount("EUR"), a1);
t.true(res.saturated);
t.pass();
});
test("amount subtraction (simple)", t => {
let a1 = amt(2,5,"EUR");
let a2 = amt(1,0,"EUR");
let a3 = amt(1,5,"EUR");
t.true(0 == types.Amounts.cmp(Amounts.sub(a1, a2).amount, a3));
test("amount subtraction (simple)", (t) => {
const a1 = amt(2, 5, "EUR");
const a2 = amt(1, 0, "EUR");
const a3 = amt(1, 5, "EUR");
t.true(0 === types.Amounts.cmp(Amounts.sub(a1, a2).amount, a3));
t.pass();
});
test("amount subtraction (saturation)", t => {
let a1 = amt(0,0,"EUR");
let a2 = amt(1,0,"EUR");
test("amount subtraction (saturation)", (t) => {
const a1 = amt(0, 0, "EUR");
const a2 = amt(1, 0, "EUR");
let res = Amounts.sub(a1, a2);
t.true(res.saturated);
res = Amounts.sub(a1, a1);
@ -38,29 +54,29 @@ test("amount subtraction (saturation)", t => {
});
test("contract validation", t => {
let c = {
test("contract validation", (t) => {
const c = {
H_wire: "123",
summary: "hello",
amount: amt(1,2,"EUR"),
amount: amt(1, 2, "EUR"),
auditors: [],
pay_deadline: "Date(12346)",
max_fee: amt(1,2,"EUR"),
merchant_pub: "12345",
exchanges: [{master_pub: "foo", url: "foo"}],
fulfillment_url: "foo",
max_fee: amt(1, 2, "EUR"),
merchant_pub: "12345",
order_id: "test_order",
pay_deadline: "Date(12346)",
pay_url: "https://example.com/pay",
products: [],
refund_deadline: "Date(12345)",
summary: "hello",
timestamp: "Date(12345)",
fulfillment_url: "foo",
wire_method: "test",
order_id: "test_order",
pay_url: "https://example.com/pay",
};
types.Contract.checked(c);
let c1 = JSON.parse(JSON.stringify(c));
c1.exchanges = []
const c1 = JSON.parse(JSON.stringify(c));
c1.exchanges = [];
try {
types.Contract.checked(c1);
@ -70,5 +86,4 @@ test("contract validation", t => {
}
t.fail();
});

View File

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

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 * as types from "./types";
import * as wallet from "./wallet";
function a(x: string): types.AmountJson {
let amt = types.Amounts.parse(x);
const amt = types.Amounts.parse(x);
if (!amt) {
throw Error("invalid amount");
}
return amt;
}
function fakeCwd(current: string, value: string, feeDeposit: string): wallet.CoinWithDenom {
return {
coin: {
currentAmount: a(current),
coinPub: "(mock)",
blindingKey: "(mock)",
coinPriv: "(mock)",
coinPub: "(mock)",
currentAmount: a(current),
denomPub: "(mock)",
denomSig: "(mock)",
exchangeBaseUrl: "(mock)",
blindingKey: "(mock)",
reservePub: "(mock)",
status: types.CoinStatus.Fresh,
},
denom: {
value: a(value),
feeDeposit: a(feeDeposit),
denomPub: "(mock)",
denomPubHash: "(mock)",
feeWithdraw: a("EUR:0.0"),
exchangeBaseUrl: "(mock)",
feeDeposit: a(feeDeposit),
feeRefresh: a("EUR:0.0"),
feeRefund: a("EUR:0.0"),
stampStart: "(mock)",
stampExpireWithdraw: "(mock)",
stampExpireLegal: "(mock)",
stampExpireDeposit: "(mock)",
masterSig: "(mock)",
status: types.DenominationStatus.VerifiedGood,
feeWithdraw: a("EUR:0.0"),
isOffered: true,
exchangeBaseUrl: "(mock)",
masterSig: "(mock)",
stampExpireDeposit: "(mock)",
stampExpireLegal: "(mock)",
stampExpireWithdraw: "(mock)",
stampStart: "(mock)",
status: types.DenominationStatus.VerifiedGood,
value: a(value),
},
}
};
}
test("coin selection 1", t => {
let cds: wallet.CoinWithDenom[] = [
test("coin selection 1", (t) => {
const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.1"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.1"));
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.1"));
if (!res) {
t.fail();
return;
}
t.true(res.length == 2);
t.true(res.length === 2);
t.pass();
});
test("coin selection 2", t => {
let cds: wallet.CoinWithDenom[] = [
test("coin selection 2", (t) => {
const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
// Merchant covers the fee, this one shouldn't be used
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
if (!res) {
t.fail();
return;
}
t.true(res.length == 2);
t.true(res.length === 2);
t.pass();
});
test("coin selection 3", t => {
let cds: wallet.CoinWithDenom[] = [
test("coin selection 3", (t) => {
const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
// this coin should be selected instead of previous one with fee
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"),
];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5"));
if (!res) {
t.fail();
return;
}
t.true(res.length == 2);
t.true(res.length === 2);
t.pass();
});
test("coin selection 4", t => {
let cds: wallet.CoinWithDenom[] = [
test("coin selection 4", (t) => {
const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
if (!res) {
t.fail();
return;
}
t.true(res.length == 3);
t.true(res.length === 3);
t.pass();
});
test("coin selection 5", t => {
let cds: wallet.CoinWithDenom[] = [
test("coin selection 5", (t) => {
const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
];
let res = wallet.selectCoins(cds, a("EUR:4.0"), a("EUR:0.2"));
const res = wallet.selectCoins(cds, a("EUR:4.0"), a("EUR:0.2"));
t.true(!res);
t.pass();
});
test("coin selection 6", t => {
let cds: wallet.CoinWithDenom[] = [
test("coin selection 6", (t) => {
const cds: wallet.CoinWithDenom[] = [
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"),
];
let res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2"));
t.true(!res);
t.pass();
});

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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