diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/checkable.ts | 146 | ||||
-rw-r--r-- | src/chromeBadge.ts | 39 | ||||
-rw-r--r-- | src/components.ts | 2 | ||||
-rw-r--r-- | src/content_scripts/notify.ts | 155 | ||||
-rw-r--r-- | src/crypto/cryptoApi-test.ts | 126 | ||||
-rw-r--r-- | src/crypto/cryptoApi.ts | 67 | ||||
-rw-r--r-- | src/crypto/cryptoWorker.ts | 214 | ||||
-rw-r--r-- | src/crypto/emscInterface-test.ts | 109 | ||||
-rw-r--r-- | src/crypto/emscInterface.ts | 384 | ||||
-rw-r--r-- | src/crypto/emscLoader.d.ts | 12 | ||||
-rw-r--r-- | src/crypto/nodeWorker.ts | 5 | ||||
-rw-r--r-- | src/crypto/nodeWorkerEntry.ts | 38 | ||||
-rw-r--r-- | src/helpers-test.ts | 19 | ||||
-rw-r--r-- | src/helpers.ts | 28 | ||||
-rw-r--r-- | src/http.ts | 12 | ||||
-rw-r--r-- | src/logging.ts | 96 | ||||
-rw-r--r-- | src/pages/show-db.ts | 34 | ||||
-rw-r--r-- | src/query.ts | 241 | ||||
-rw-r--r-- | src/timer.ts | 2 | ||||
-rw-r--r-- | src/types-test.ts | 75 | ||||
-rw-r--r-- | src/types.ts | 66 | ||||
-rw-r--r-- | src/wallet-test.ts | 93 | ||||
-rw-r--r-- | src/wallet.ts | 842 | ||||
-rw-r--r-- | src/wxApi.ts | 13 | ||||
-rw-r--r-- | src/wxBackend.ts | 263 |
25 files changed, 1591 insertions, 1490 deletions
diff --git a/src/checkable.ts b/src/checkable.ts index 1b6e371f6..802d8f32d 100644 --- a/src/checkable.ts +++ b/src/checkable.ts @@ -40,7 +40,7 @@ */ export namespace Checkable { - type Path = (number | string)[]; + type Path = Array<number | string>; interface SchemaErrorConstructor { new (err: string): SchemaError; @@ -67,22 +67,22 @@ export namespace Checkable { props: Prop[]; } - export let SchemaError = (function SchemaError(this: any, message: string) { - let that: any = this as any; - that.name = 'SchemaError'; + export const SchemaError = (function SchemaError(this: any, message: string) { + const that: any = this as any; + that.name = "SchemaError"; that.message = message; - that.stack = (<any>new Error()).stack; + that.stack = (new Error() as any).stack; }) as any as SchemaErrorConstructor; - SchemaError.prototype = new Error; + SchemaError.prototype = new Error(); /** * Classes that are checkable are annotated with this * checkable info symbol, which contains the information necessary * to check if they're valid. */ - let checkableInfoSym = Symbol("checkableInfo"); + const checkableInfoSym = Symbol("checkableInfo"); /** * Get the current property list for a checkable type. @@ -138,7 +138,7 @@ export namespace Checkable { throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`); } for (let i = 0; i < target.length; i++) { - let v = target[i]; + const v = target[i]; prop.elementChecker(v, prop.elementProp, path.concat([i])); } return target; @@ -148,9 +148,9 @@ export namespace Checkable { if (typeof target !== "object") { throw new SchemaError(`expected object for ${path}, got ${typeof target} instead`); } - for (let key in target) { + for (const key in target) { prop.keyProp.checker(key, prop.keyProp, path.concat([key])); - let value = target[key]; + const value = target[key]; prop.valueProp.checker(value, prop.valueProp, path.concat([key])); } } @@ -166,35 +166,35 @@ export namespace Checkable { function checkValue(target: any, prop: Prop, path: Path): any { - let type = prop.type; + const type = prop.type; if (!type) { throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`); } - let v = target; + const v = target; if (!v || typeof v !== "object") { throw new SchemaError( `expected object for ${path.join(".")}, got ${typeof v} instead`); } - let props = type.prototype[checkableInfoSym].props; - let remainingPropNames = new Set(Object.getOwnPropertyNames(v)); - let obj = new type(); - for (let prop of props) { - if (!remainingPropNames.has(prop.propertyKey)) { - if (prop.optional) { + const props = type.prototype[checkableInfoSym].props; + const remainingPropNames = new Set(Object.getOwnPropertyNames(v)); + const obj = new type(); + for (const innerProp of props) { + if (!remainingPropNames.has(innerProp.propertyKey)) { + if (innerProp.optional) { continue; } - throw new SchemaError(`Property ${prop.propertyKey} missing on ${path}`); + throw new SchemaError(`Property ${innerProp.propertyKey} missing on ${path}`); } - if (!remainingPropNames.delete(prop.propertyKey)) { + if (!remainingPropNames.delete(innerProp.propertyKey)) { throw new SchemaError("assertion failed"); } - let propVal = v[prop.propertyKey]; - obj[prop.propertyKey] = prop.checker(propVal, - prop, - path.concat([prop.propertyKey])); + const propVal = v[innerProp.propertyKey]; + obj[innerProp.propertyKey] = innerProp.checker(propVal, + innerProp, + path.concat([innerProp.propertyKey])); } - if (!prop.extraAllowed && remainingPropNames.size != 0) { + if (!prop.extraAllowed && remainingPropNames.size !== 0) { throw new SchemaError("superfluous properties " + JSON.stringify(Array.from( remainingPropNames.values()))); } @@ -210,14 +210,14 @@ export namespace Checkable { export function Class(opts: {extra?: boolean, validate?: boolean} = {}) { return (target: any) => { target.checked = (v: any) => { - let cv = checkValue(v, { + const cv = checkValue(v, { + checker: checkValue, + extraAllowed: !!opts.extra, propertyKey: "(root)", type: target, - extraAllowed: !!opts.extra, - checker: checkValue }, ["(root)"]); if (opts.validate) { - let instance = new target(); + const instance = new target(); if (typeof instance.validate !== "function") { throw Error("invalid Checkable annotion: validate method required"); } @@ -227,7 +227,7 @@ export namespace Checkable { return cv; }; return target; - } + }; } @@ -238,12 +238,12 @@ export namespace Checkable { if (!type) { throw Error("Type does not exist yet (wrong order of definitions?)"); } - function deco(target: Object, propertyKey: string | symbol): void { - let chk = getCheckableInfo(target); + function deco(target: object, propertyKey: string | symbol): void { + const chk = getCheckableInfo(target); chk.props.push({ - propertyKey: propertyKey, checker: checkValue, - type: type + propertyKey, + type, }); } @@ -256,20 +256,20 @@ export namespace Checkable { * an annotation for a list of strings. */ export function List(type: any) { - let stub = {}; + const stub = {}; type(stub, "(list-element)"); - let elementProp = getCheckableInfo(stub).props[0]; - let elementChecker = elementProp.checker; + const elementProp = getCheckableInfo(stub).props[0]; + const elementChecker = elementProp.checker; if (!elementChecker) { throw Error("assertion failed"); } - function deco(target: Object, propertyKey: string | symbol): void { - let chk = getCheckableInfo(target); + function deco(target: object, propertyKey: string | symbol): void { + const chk = getCheckableInfo(target); chk.props.push({ + checker: checkList, elementChecker, elementProp, - propertyKey: propertyKey, - checker: checkList, + propertyKey, }); } @@ -282,25 +282,25 @@ export namespace Checkable { * one for the key type and one for the value type. */ export function Map(keyType: any, valueType: any) { - let keyStub = {}; + const keyStub = {}; keyType(keyStub, "(map-key)"); - let keyProp = getCheckableInfo(keyStub).props[0]; + const keyProp = getCheckableInfo(keyStub).props[0]; if (!keyProp) { throw Error("assertion failed"); } - let valueStub = {}; + const valueStub = {}; valueType(valueStub, "(map-value)"); - let valueProp = getCheckableInfo(valueStub).props[0]; + const valueProp = getCheckableInfo(valueStub).props[0]; if (!valueProp) { throw Error("assertion failed"); } - function deco(target: Object, propertyKey: string | symbol): void { - let chk = getCheckableInfo(target); + function deco(target: object, propertyKey: string | symbol): void { + const chk = getCheckableInfo(target); chk.props.push({ + checker: checkMap, keyProp, + propertyKey, valueProp, - propertyKey: propertyKey, - checker: checkMap, }); } @@ -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 }); } } diff --git a/src/chromeBadge.ts b/src/chromeBadge.ts index 369a95227..13716a64a 100644 --- a/src/chromeBadge.ts +++ b/src/chromeBadge.ts @@ -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); diff --git a/src/components.ts b/src/components.ts index bef3cff42..9d1127e99 100644 --- a/src/components.ts +++ b/src/components.ts @@ -17,7 +17,7 @@ /** * General helper React components. - * + * * @author Florian Dold */ diff --git a/src/content_scripts/notify.ts b/src/content_scripts/notify.ts index 64414e0b9..733367a59 100644 --- a/src/content_scripts/notify.ts +++ b/src/content_scripts/notify.ts @@ -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); }); diff --git a/src/crypto/cryptoApi-test.ts b/src/crypto/cryptoApi-test.ts index cc5d1156e..60bd44c90 100644 --- a/src/crypto/cryptoApi-test.ts +++ b/src/crypto/cryptoApi-test.ts @@ -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", +test("precoin creation", async (t) => { + const crypto = new CryptoApi(); + const {priv, pub} = await crypto.createEddsaKeypair(); + const r: ReserveRecord = { + confirmed: false, created: 0, - requested_amount: {currency: "PUDOS", value: 0, fraction: 0}, - precoin_amount: {currency: "PUDOS", value: 0, fraction: 0}, current_amount: null, - confirmed: false, + 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); diff --git a/src/crypto/cryptoApi.ts b/src/crypto/cryptoApi.ts index b291cd17d..57f035a83 100644 --- a/src/crypto/cryptoApi.ts +++ b/src/crypto/cryptoApi.ts @@ -24,20 +24,22 @@ * Imports. */ import { - PreCoinRecord, - CoinRecord, - ReserveRecord, AmountJson, + CoinRecord, DenominationRecord, + PayCoinInfo, PaybackRequest, + PreCoinRecord, RefreshSessionRecord, + ReserveRecord, WireFee, - PayCoinInfo, } 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; } diff --git a/src/crypto/cryptoWorker.ts b/src/crypto/cryptoWorker.ts index 36b3b924a..9541b7442 100644 --- a/src/crypto/cryptoWorker.ts +++ b/src/crypto/cryptoWorker.ts @@ -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 = { - meltCoinPub: meltCoin.coinPub, - newDenoms: newCoinDenoms.map((d) => d.denomPub), + const refreshSession: RefreshSessionRecord = { confirmSig, - valueWithFee, - transferPubs, - preCoinsForGammas, + exchangeBaseUrl, + finished: false, hash: sessionHash.toCrock(), + meltCoinPub: meltCoin.coinPub, + newDenoms: newCoinDenoms.map((d) => d.denomPub), 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}); -} +}; diff --git a/src/crypto/emscInterface-test.ts b/src/crypto/emscInterface-test.ts index dc98308fb..789ac2509 100644 --- a/src/crypto/emscInterface-test.ts +++ b/src/crypto/emscInterface-test.ts @@ -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(); diff --git a/src/crypto/emscInterface.ts b/src/crypto/emscInterface.ts index 52c6c965e..e00e67a84 100644 --- a/src/crypto/emscInterface.ts +++ b/src/crypto/emscInterface.ts @@ -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); diff --git a/src/crypto/emscLoader.d.ts b/src/crypto/emscLoader.d.ts index e46ed7f13..aac4116b5 100644 --- a/src/crypto/emscLoader.d.ts +++ b/src/crypto/emscLoader.d.ts @@ -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; diff --git a/src/crypto/nodeWorker.ts b/src/crypto/nodeWorker.ts index afa2930a8..4352b66c2 100644 --- a/src/crypto/nodeWorker.ts +++ b/src/crypto/nodeWorker.ts @@ -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 { diff --git a/src/crypto/nodeWorkerEntry.ts b/src/crypto/nodeWorkerEntry.ts index aa6f57599..ff7f8766b 100644 --- a/src/crypto/nodeWorkerEntry.ts +++ b/src/crypto/nodeWorkerEntry.ts @@ -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}); + onerror: (err: any) => { + const str: string = JSON.stringify({error: err.message, stack: err.stack}); if (process.send) { process.send(str); } }, onmessage: undefined, - onerror: (err: any) => { - const str: string = JSON.stringify({error: err.message, stack: err.stack}); + postMessage: (msg: any) => { + const str: string = JSON.stringify({data: msg}); 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; - } - }, }; 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); diff --git a/src/helpers-test.ts b/src/helpers-test.ts index 4e9b994d6..8ce3c8e2d 100644 --- a/src/helpers-test.ts +++ b/src/helpers-test.ts @@ -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/", diff --git a/src/helpers.ts b/src/helpers.ts index 5e3ef06d5..e5eb40211 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -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; } diff --git a/src/http.ts b/src/http.ts index 965a44a97..895b10973 100644 --- a/src/http.ts +++ b/src/http.ts @@ -46,10 +46,10 @@ export interface HttpRequestLibrary { */ export class BrowserHttpLib { private req(method: string, - url: string, - options?: any): Promise<HttpResponse> { + url: string, + options?: any): Promise<HttpResponse> { return new Promise<HttpResponse>((resolve, reject) => { - let myRequest = new XMLHttpRequest(); + const myRequest = new XMLHttpRequest(); myRequest.open(method, url); if (options && options.req) { myRequest.send(options.req); @@ -57,10 +57,10 @@ export class BrowserHttpLib { myRequest.send(); } myRequest.addEventListener("readystatechange", (e) => { - if (myRequest.readyState == XMLHttpRequest.DONE) { - let resp = { + if (myRequest.readyState === XMLHttpRequest.DONE) { + const resp = { + responseText: myRequest.responseText, status: myRequest.status, - responseText: myRequest.responseText }; resolve(resp); } diff --git a/src/logging.ts b/src/logging.ts index d5d6debf4..19dd2f76c 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -20,7 +20,11 @@ * @author Florian Dold */ -import {Store, QueryRoot, openPromise} from "./query"; +import { + QueryRoot, + Store, + openPromise, +} from "./query"; export type Level = "error" | "debug" | "info" | "warn"; @@ -41,83 +45,92 @@ function makeDebug() { } export async function log(msg: string, level: Level = "info"): Promise<void> { - let ci = getCallInfo(2); + const ci = getCallInfo(2); return record(level, msg, undefined, ci.file, ci.line, ci.column); } function getCallInfo(level: number) { // see https://github.com/v8/v8/wiki/Stack-Trace-API - let stack = Error().stack; + const stack = Error().stack; if (!stack) { return unknownFrame; } - let lines = stack.split("\n"); + const lines = stack.split("\n"); return parseStackLine(lines[level + 1]); } interface Frame { - file?: string; - method?: string; column?: number; + file?: string; line?: number; + method?: string; } const unknownFrame: Frame = { + column: 0, file: "(unknown)", - method: "(unknown)", line: 0, - column: 0 + method: "(unknown)", }; /** * Adapted from https://github.com/errwischt/stacktrace-parser. */ function parseStackLine(stackLine: string): Frame { + // tslint:disable-next-line:max-line-length const chrome = /^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i; const gecko = /^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i; const node = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i; let parts; - if ((parts = gecko.exec(stackLine))) { - let f: Frame = { + parts = gecko.exec(stackLine); + if (parts) { + const f: Frame = { + column: parts[5] ? +parts[5] : undefined, file: parts[3], - method: parts[1] || "(unknown)", line: +parts[4], - column: parts[5] ? +parts[5] : undefined, + method: parts[1] || "(unknown)", }; return f; - } else if ((parts = chrome.exec(stackLine))) { - let f: Frame = { + } + + parts = chrome.exec(stackLine); + if (parts) { + const f: Frame = { + column: parts[4] ? +parts[4] : undefined, file: parts[2], - method: parts[1] || "(unknown)", line: +parts[3], - column: parts[4] ? +parts[4] : undefined, + method: parts[1] || "(unknown)", }; return f; - } else if ((parts = node.exec(stackLine))) { - let f: Frame = { + } + + parts = node.exec(stackLine); + if (parts) { + const f: Frame = { + column: parts[4] ? +parts[4] : undefined, file: parts[2], - method: parts[1] || "(unknown)", line: +parts[3], - column: parts[4] ? +parts[4] : undefined, + method: parts[1] || "(unknown)", }; return f; } + return unknownFrame; } -let db: IDBDatabase|undefined = undefined; +let db: IDBDatabase|undefined; export interface LogEntry { - timestamp: number; + col?: number; + detail?: string; + id?: number; level: string; + line?: number; msg: string; - detail?: string; source?: string; - col?: number; - line?: number; - id?: number; + 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(), + const entry: LogEntry = { + col, + detail, level, + line, msg, source, - line, - col, - detail, + 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}); }; }); } diff --git a/src/pages/show-db.ts b/src/pages/show-db.ts index 955253680..d95951385 100644 --- a/src/pages/show-db.ts +++ b/src/pages/show-db.ts @@ -23,32 +23,32 @@ function replacer(match: string, pIndent: string, pKey: string, pVal: string, pEnd: string) { - var key = '<span class=json-key>'; - var val = '<span class=json-value>'; - var str = '<span class=json-string>'; - var r = pIndent || ''; + const key = "<span class=json-key>"; + const val = "<span class=json-value>"; + const str = "<span class=json-string>"; + let r = pIndent || ""; if (pKey) { - r = r + key + '"' + pKey.replace(/[": ]/g, '') + '":</span> '; + r = r + key + '"' + pKey.replace(/[": ]/g, "") + '":</span> '; } if (pVal) { - r = r + (pVal[0] == '"' ? str : val) + pVal + '</span>'; + r = r + (pVal[0] === '"' ? str : val) + pVal + "</span>"; } - return r + (pEnd || ''); + return r + (pEnd || ""); } function prettyPrint(obj: any) { - var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; + const jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; return JSON.stringify(obj, null as any, 3) - .replace(/&/g, '&').replace(/\\"/g, '"') - .replace(/</g, '<').replace(/>/g, '>') + .replace(/&/g, "&").replace(/\\"/g, """) + .replace(/</g, "<").replace(/>/g, ">") .replace(jsonLine, replacer); } document.addEventListener("DOMContentLoaded", () => { - chrome.runtime.sendMessage({type: 'dump-db'}, (resp) => { - const el = document.getElementById('dump'); + chrome.runtime.sendMessage({type: "dump-db"}, (resp) => { + const el = document.getElementById("dump"); if (!el) { throw Error(); } @@ -56,7 +56,7 @@ document.addEventListener("DOMContentLoaded", () => { document.getElementById("download")!.addEventListener("click", (evt) => { console.log("creating download"); - let element = document.createElement("a"); + const element = document.createElement("a"); element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(resp))); element.setAttribute("download", "wallet-dump.txt"); element.style.display = "none"; @@ -67,9 +67,9 @@ document.addEventListener("DOMContentLoaded", () => { }); - let fileInput = document.getElementById("fileInput")! as HTMLInputElement; + const fileInput = document.getElementById("fileInput")! as HTMLInputElement; fileInput.onchange = (evt) => { - if (!fileInput.files || fileInput.files.length != 1) { + if (!fileInput.files || fileInput.files.length !== 1) { alert("please select exactly one file to import"); return; } @@ -77,9 +77,9 @@ document.addEventListener("DOMContentLoaded", () => { const fr = new FileReader(); fr.onload = (e: any) => { console.log("got file"); - let dump = JSON.parse(e.target.result); + const dump = JSON.parse(e.target.result); console.log("parsed contents", dump); - chrome.runtime.sendMessage({ type: 'import-db', detail: { dump } }, (resp) => { + chrome.runtime.sendMessage({ type: "import-db", detail: { dump } }, (resp) => { alert("loaded"); }); }; diff --git a/src/query.ts b/src/query.ts index c593f061e..78b810371 100644 --- a/src/query.ts +++ b/src/query.ts @@ -21,42 +21,42 @@ * @author Florian Dold */ -"use strict"; - -export interface JoinResult<L,R> { +/** + * Result of an inner join. + */ +export interface JoinResult<L, R> { left: L; right: R; } -export interface JoinLeftResult<L,R> { +/** + * Result of a left outer join. + */ +export interface JoinLeftResult<L, R> { left: L; right?: R; } +/** + * Definition of an object store. + */ export class Store<T> { - name: string; - validator?: (v: T) => T; - storeParams?: IDBObjectStoreParameters; - - constructor(name: string, storeParams?: IDBObjectStoreParameters, - validator?: (v: T) => T) { - this.name = name; - this.validator = validator; - this.storeParams = storeParams; + constructor(public name: string, + public storeParams?: IDBObjectStoreParameters, + public validator?: (v: T) => T) { } } -export class Index<S extends IDBValidKey,T> { - indexName: string; +/** + * Definition of an index. + */ +export class Index<S extends IDBValidKey, T> { storeName: string; - keyPath: string | string[]; - constructor(s: Store<T>, indexName: string, keyPath: string | string[]) { + constructor(s: Store<T>, public indexName: string, public keyPath: string | string[]) { this.storeName = s.name; - this.indexName = indexName; - this.keyPath = keyPath; } } @@ -65,17 +65,58 @@ export class Index<S extends IDBValidKey,T> { * with indices. */ export interface QueryStream<T> { - indexJoin<S,I extends IDBValidKey>(index: Index<I,S>, - keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>>; - indexJoinLeft<S,I extends IDBValidKey>(index: Index<I,S>, - keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>>; - keyJoin<S,I extends IDBValidKey>(store: Store<S>, - keyFn: (obj: T) => I): QueryStream<JoinResult<T,S>>; - filter(f: (T: any) => boolean): QueryStream<T>; + /** + * Join the current query with values from an index. + * The left side of the join is extracted via a function from the stream's + * result, the right side of the join is the key of the index. + */ + indexJoin<S, I extends IDBValidKey>(index: Index<I, S>, keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>>; + /** + * Join the current query with values from an index, and keep values in the + * current stream that don't have a match. The left side of the join is + * extracted via a function from the stream's result, the right side of the + * join is the key of the index. + */ + indexJoinLeft<S, I extends IDBValidKey>(index: Index<I, S>, + keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>>; + /** + * Join the current query with values from another object store. + * The left side of the join is extracted via a function over the current query, + * the right side of the join is the key of the object store. + */ + keyJoin<S, I extends IDBValidKey>(store: Store<S>, keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>>; + + /** + * Only keep elements in the result stream for which the predicate returns + * true. + */ + filter(f: (x: T) => boolean): QueryStream<T>; + + /** + * Reduce the stream, resulting in a single value. + */ reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>; - map<S>(f: (x:T) => S): QueryStream<S>; + + /** + * Map each element of the stream using a function, resulting in another + * stream of a different type. + */ + map<S>(f: (x: T) => S): QueryStream<S>; + + /** + * Map each element of the stream to a potentially empty array, and collect + * the result in a stream of the flattened arrays. + */ flatMap<S>(f: (x: T) => S[]): QueryStream<S>; + + /** + * Collect the stream into an array and return a promise for it. + */ toArray(): Promise<T[]>; + + /** + * Get the first value of the stream. + */ first(): QueryValue<T>; then(onfulfill: any, onreject: any): any; @@ -99,7 +140,7 @@ abstract class BaseQueryValue<T> implements QueryValue<T> { } map<S>(f: (x: T) => S): QueryValue<S> { - return new MapQueryValue<T,S>(this, f); + return new MapQueryValue<T, S>(this, f); } cond<R>(f: (x: T) => boolean, onTrue: (r: QueryRoot) => R, onFalse: (r: QueryRoot) => R): Promise<void> { @@ -141,7 +182,7 @@ class FirstQueryValue<T> extends BaseQueryValue<T> { } } -class MapQueryValue<T,S> extends BaseQueryValue<S> { +class MapQueryValue<T, S> extends BaseQueryValue<S> { mapFn: (x: T) => S; v: BaseQueryValue<T>; @@ -157,7 +198,10 @@ class MapQueryValue<T,S> extends BaseQueryValue<S> { } -export let AbortTransaction = Symbol("abort_transaction"); +/** + * Exception that should be thrown by client code to abort a transaction. + */ +export const AbortTransaction = Symbol("abort_transaction"); /** * Get an unresolved promise together with its extracted resolve / reject @@ -193,26 +237,27 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> { return new FirstQueryValue(this); } - then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> { + then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, + onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> { return this.root.then(onfulfilled, onrejected); } flatMap<S>(f: (x: T) => S[]): QueryStream<S> { - return new QueryStreamFlatMap<T,S>(this, f); + return new QueryStreamFlatMap<T, S>(this, f); } map<S>(f: (x: T) => S): QueryStream<S> { return new QueryStreamMap(this, f); } - indexJoin<S,I extends IDBValidKey>(index: Index<I,S>, - keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> { + indexJoin<S, I extends IDBValidKey>(index: Index<I, S>, + keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> { this.root.addStoreAccess(index.storeName, false); return new QueryStreamIndexJoin<T, S>(this, index.storeName, index.indexName, keyFn); } - indexJoinLeft<S,I extends IDBValidKey>(index: Index<I,S>, - keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>> { + indexJoinLeft<S, I extends IDBValidKey>(index: Index<I, S>, + keyFn: (obj: T) => I): QueryStream<JoinLeftResult<T, S>> { this.root.addStoreAccess(index.storeName, false); return new QueryStreamIndexJoinLeft<T, S>(this, index.storeName, index.indexName, keyFn); } @@ -228,8 +273,8 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> { } toArray(): Promise<T[]> { - let {resolve, promise} = openPromise(); - let values: T[] = []; + const {resolve, promise} = openPromise(); + const values: T[] = []; this.subscribe((isDone, value) => { if (isDone) { @@ -245,7 +290,7 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> { } reduce<A>(f: (x: any, acc?: A) => A, init?: A): Promise<any> { - let {resolve, promise} = openPromise(); + const {resolve, promise} = openPromise(); let acc = init; this.subscribe((isDone, value) => { @@ -265,10 +310,7 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> { type FilterFn = (e: any) => boolean; type SubscribeFn = (done: boolean, value: any, tx: IDBTransaction) => void; type SubscribeOneFn = (value: any, tx: IDBTransaction) => void; - -interface FlatMapFn<T> { - (v: T): T[]; -} +type FlatMapFn<T> = (v: T) => T[]; class QueryStreamFilter<T> extends QueryStreamBase<T> { s: QueryStreamBase<T>; @@ -294,7 +336,7 @@ class QueryStreamFilter<T> extends QueryStreamBase<T> { } -class QueryStreamFlatMap<T,S> extends QueryStreamBase<S> { +class QueryStreamFlatMap<T, S> extends QueryStreamBase<S> { s: QueryStreamBase<T>; flatMapFn: (v: T) => S[]; @@ -310,16 +352,16 @@ class QueryStreamFlatMap<T,S> extends QueryStreamBase<S> { f(true, undefined, tx); return; } - let values = this.flatMapFn(value); - for (let v in values) { - f(false, value, tx) + const values = this.flatMapFn(value); + for (const v in values) { + f(false, value, tx); } }); } } -class QueryStreamMap<S,T> extends QueryStreamBase<T> { +class QueryStreamMap<S, T> extends QueryStreamBase<T> { s: QueryStreamBase<S>; mapFn: (v: S) => T; @@ -335,7 +377,7 @@ class QueryStreamMap<S,T> extends QueryStreamBase<T> { f(true, undefined, tx); return; } - let mappedValue = this.mapFn(value); + const mappedValue = this.mapFn(value); f(false, mappedValue, tx); }); } @@ -363,15 +405,15 @@ class QueryStreamIndexJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> { f(true, undefined, tx); return; } - let s = tx.objectStore(this.storeName).index(this.indexName); - let req = s.openCursor(IDBKeyRange.only(this.key(value))); + const s = tx.objectStore(this.storeName).index(this.indexName); + const req = s.openCursor(IDBKeyRange.only(this.key(value))); req.onsuccess = () => { - let cursor = req.result; + const cursor = req.result; if (cursor) { f(false, {left: value, right: cursor.value}, tx); cursor.continue(); } - } + }; }); } } @@ -402,7 +444,7 @@ class QueryStreamIndexJoinLeft<T, S> extends QueryStreamBase<JoinLeftResult<T, S const req = s.openCursor(IDBKeyRange.only(this.key(value))); let gotMatch = false; req.onsuccess = () => { - let cursor = req.result; + const cursor = req.result; if (cursor) { gotMatch = true; f(false, {left: value, right: cursor.value}, tx); @@ -412,7 +454,7 @@ class QueryStreamIndexJoinLeft<T, S> extends QueryStreamBase<JoinLeftResult<T, S f(false, {left: value}, tx); } } - } + }; }); } } @@ -437,17 +479,17 @@ class QueryStreamKeyJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> { f(true, undefined, tx); return; } - let s = tx.objectStore(this.storeName); - let req = s.openCursor(IDBKeyRange.only(this.key(value))); + const s = tx.objectStore(this.storeName); + const req = s.openCursor(IDBKeyRange.only(this.key(value))); req.onsuccess = () => { - let cursor = req.result; + const cursor = req.result; if (cursor) { - f(false, {left:value, right: cursor.value}, tx); + f(false, {left: value, right: cursor.value}, tx); cursor.continue(); } else { f(true, undefined, tx); } - } + }; }); } } @@ -464,7 +506,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> { this.storeName = storeName; this.subscribers = []; - let doIt = (tx: IDBTransaction) => { + const doIt = (tx: IDBTransaction) => { const {indexName = void 0, only = void 0} = this.options; let s: any; if (indexName !== void 0) { @@ -473,24 +515,24 @@ class IterQueryStream<T> extends QueryStreamBase<T> { } else { s = tx.objectStore(this.storeName); } - let kr: IDBKeyRange | undefined = undefined; + let kr: IDBKeyRange | undefined; if (only !== undefined) { kr = IDBKeyRange.only(this.options.only); } - let req = s.openCursor(kr); + const req = s.openCursor(kr); req.onsuccess = () => { - let cursor: IDBCursorWithValue = req.result; + const cursor: IDBCursorWithValue = req.result; if (cursor) { - for (let f of this.subscribers) { + for (const f of this.subscribers) { f(false, cursor.value, tx); } cursor.continue(); } else { - for (let f of this.subscribers) { + for (const f of this.subscribers) { f(true, undefined, tx); } } - } + }; }; this.root.addWork(doIt); @@ -503,8 +545,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> { export class QueryRoot implements PromiseLike<void> { - private work: ((t: IDBTransaction) => void)[] = []; - db: IDBDatabase; + private work: Array<((t: IDBTransaction) => void)> = []; private stores = new Set(); private kickoffPromise: Promise<void>; @@ -518,20 +559,23 @@ export class QueryRoot implements PromiseLike<void> { private finished: boolean = false; - constructor(db: IDBDatabase) { - this.db = db; + constructor(public db: IDBDatabase) { } - then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> { + then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, + onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> { return this.finish().then(onfulfilled, onrejected); } - checkFinished() { + private checkFinished() { if (this.finished) { throw Error("Can't add work to query after it was started"); } } + /** + * Get a stream of all objects in the store. + */ iter<T>(store: Store<T>): QueryStream<T> { this.checkFinished(); this.stores.add(store.name); @@ -539,6 +583,9 @@ export class QueryRoot implements PromiseLike<void> { return new IterQueryStream<T>(this, store.name, {}); } + /** + * Count the number of objects in a store. + */ count<T>(store: Store<T>): Promise<number> { this.checkFinished(); const {resolve, promise} = openPromise(); @@ -549,7 +596,7 @@ export class QueryRoot implements PromiseLike<void> { req.onsuccess = () => { resolve(req.result); }; - } + }; this.addWork(doCount, store.name, false); return Promise.resolve() @@ -558,6 +605,9 @@ export class QueryRoot implements PromiseLike<void> { } + /** + * Delete all objects in a store that match a predicate. + */ deleteIf<T>(store: Store<T>, predicate: (x: T, n: number) => boolean): QueryRoot { this.checkFinished(); const doDeleteIf = (tx: IDBTransaction) => { @@ -565,27 +615,27 @@ export class QueryRoot implements PromiseLike<void> { const req = s.openCursor(); let n = 0; req.onsuccess = () => { - let cursor: IDBCursorWithValue = req.result; + const cursor: IDBCursorWithValue = req.result; if (cursor) { if (predicate(cursor.value, n++)) { cursor.delete(); } cursor.continue(); - } - } + } + }; }; this.addWork(doDeleteIf, store.name, true); return this; } - iterIndex<S extends IDBValidKey,T>(index: Index<S,T>, - only?: S): QueryStream<T> { + iterIndex<S extends IDBValidKey, T>(index: Index<S, T>, + only?: S): QueryStream<T> { this.checkFinished(); this.stores.add(index.storeName); this.scheduleFinish(); return new IterQueryStream<T>(this, index.storeName, { + indexName: index.indexName, only, - indexName: index.indexName }); } @@ -596,7 +646,7 @@ export class QueryRoot implements PromiseLike<void> { */ put<T>(store: Store<T>, val: T): QueryRoot { this.checkFinished(); - let doPut = (tx: IDBTransaction) => { + const doPut = (tx: IDBTransaction) => { tx.objectStore(store.name).put(val); }; this.scheduleFinish(); @@ -608,11 +658,11 @@ export class QueryRoot implements PromiseLike<void> { putWithResult<T>(store: Store<T>, val: T): Promise<IDBValidKey> { this.checkFinished(); const {resolve, promise} = openPromise(); - let doPutWithResult = (tx: IDBTransaction) => { - let req = tx.objectStore(store.name).put(val); + const doPutWithResult = (tx: IDBTransaction) => { + const req = tx.objectStore(store.name).put(val); req.onsuccess = () => { resolve(req.result); - } + }; this.scheduleFinish(); }; this.addWork(doPutWithResult, store.name, true); @@ -622,17 +672,20 @@ export class QueryRoot implements PromiseLike<void> { } + /** + * Get, modify and store an element inside a transaction. + */ mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot { this.checkFinished(); - let doPut = (tx: IDBTransaction) => { - let reqGet = tx.objectStore(store.name).get(key); + const doPut = (tx: IDBTransaction) => { + const reqGet = tx.objectStore(store.name).get(key); reqGet.onsuccess = () => { - let r = reqGet.result; + const r = reqGet.result; let m: T; try { m = f(r); } catch (e) { - if (e == AbortTransaction) { + if (e === AbortTransaction) { tx.abort(); return; } @@ -640,7 +693,7 @@ export class QueryRoot implements PromiseLike<void> { } tx.objectStore(store.name).put(m); - } + }; }; this.scheduleFinish(); this.addWork(doPut, store.name, true); @@ -656,7 +709,7 @@ export class QueryRoot implements PromiseLike<void> { putAll<T>(store: Store<T>, iterable: T[]): QueryRoot { this.checkFinished(); const doPutAll = (tx: IDBTransaction) => { - for (let obj of iterable) { + for (const obj of iterable) { tx.objectStore(store.name).put(obj); } }; @@ -707,8 +760,8 @@ export class QueryRoot implements PromiseLike<void> { /** * Get one object from a store by its key. */ - getIndexed<I extends IDBValidKey,T>(index: Index<I,T>, - key: I): Promise<T|undefined> { + getIndexed<I extends IDBValidKey, T>(index: Index<I, T>, + key: I): Promise<T|undefined> { this.checkFinished(); if (key === void 0) { throw Error("key must not be undefined"); @@ -748,7 +801,7 @@ export class QueryRoot implements PromiseLike<void> { this.kickoffPromise = new Promise<void>((resolve, reject) => { // At this point, we can't add any more work this.finished = true; - if (this.work.length == 0) { + if (this.work.length === 0) { resolve(); return; } @@ -760,7 +813,7 @@ export class QueryRoot implements PromiseLike<void> { tx.onabort = () => { reject(Error("transaction aborted")); }; - for (let w of this.work) { + for (const w of this.work) { w(tx); } }); diff --git a/src/timer.ts b/src/timer.ts index 105fc8c7a..2fc2ade04 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -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 { diff --git a/src/types-test.ts b/src/types-test.ts index 88f9f2723..a84bdaec8 100644 --- a/src/types-test.ts +++ b/src/types-test.ts @@ -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(); - }); diff --git a/src/types.ts b/src/types.ts index 53f98948e..91a61bc4b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -51,20 +51,20 @@ export interface SignedAmountJson { export interface ReserveRecord { reserve_pub: string; - reserve_priv: string, - exchange_base_url: string, - created: number, - last_query: number | null, + reserve_priv: string; + exchange_base_url: string; + created: number; + last_query: number | null; /** * Current amount left in the reserve */ - current_amount: AmountJson | null, + current_amount: AmountJson | null; /** * Amount requested when the reserve was created. * When a reserve is re-used (rare!) the current_amount can * be higher than the requested_amount */ - requested_amount: AmountJson, + requested_amount: AmountJson; /** @@ -360,7 +360,7 @@ export interface RefreshSessionRecord { * How much of the coin's value is melted away * with this refresh session? */ - valueWithFee: AmountJson + valueWithFee: AmountJson; /** * Sum of the value of denominations we want @@ -468,7 +468,7 @@ export interface CoinRecord { * Reserve public key for the reserve we got this coin from, * or zero when we got the coin from refresh. */ - reservePub: string|undefined, + reservePub: string|undefined; /** * Status of the coin. @@ -528,7 +528,7 @@ interface Merchant { export class Contract { validate() { - if (this.exchanges.length == 0) { + if (this.exchanges.length === 0) { throw Error("no exchanges in contract"); } } @@ -629,27 +629,27 @@ export namespace Amounts { export function getMaxAmount(currency: string): AmountJson { return { currency, - value: Number.MAX_SAFE_INTEGER, fraction: 2 ** 32, - } + value: Number.MAX_SAFE_INTEGER, + }; } export function getZero(currency: string): AmountJson { return { currency, - value: 0, fraction: 0, - } + value: 0, + }; } export function add(first: AmountJson, ...rest: AmountJson[]): Result { - let currency = first.currency; + const currency = first.currency; let value = first.value + Math.floor(first.fraction / fractionalBase); if (value > Number.MAX_SAFE_INTEGER) { return { amount: getMaxAmount(currency), saturated: true }; } let fraction = first.fraction % fractionalBase; - for (let x of rest) { + for (const x of rest) { if (x.currency !== currency) { throw Error(`Mismatched currency: ${x.currency} and ${currency}`); } @@ -665,11 +665,11 @@ export namespace Amounts { export function sub(a: AmountJson, ...rest: AmountJson[]): Result { - let currency = a.currency; + const currency = a.currency; let value = a.value; let fraction = a.fraction; - for (let b of rest) { + for (const b of rest) { if (b.currency !== currency) { throw Error(`Mismatched currency: ${b.currency} and ${currency}`); } @@ -695,10 +695,10 @@ export namespace Amounts { if (a.currency !== b.currency) { throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`); } - let av = a.value + Math.floor(a.fraction / fractionalBase); - let af = a.fraction % fractionalBase; - let bv = b.value + Math.floor(b.fraction / fractionalBase); - let bf = b.fraction % fractionalBase; + const av = a.value + Math.floor(a.fraction / fractionalBase); + const af = a.fraction % fractionalBase; + const bv = b.value + Math.floor(b.fraction / fractionalBase); + const bf = b.fraction % fractionalBase; switch (true) { case av < bv: return -1; @@ -708,7 +708,7 @@ export namespace Amounts { return -1; case af > bf: return 1; - case af == bf: + case af === bf: return 0; default: throw Error("assertion failed"); @@ -717,25 +717,25 @@ export namespace Amounts { export function copy(a: AmountJson): AmountJson { return { - value: a.value, - fraction: a.fraction, currency: a.currency, - } + fraction: a.fraction, + value: a.value, + }; } export function divide(a: AmountJson, n: number): AmountJson { - if (n == 0) { + if (n === 0) { throw Error(`Division by 0`); } - if (n == 1) { + if (n === 1) { return {value: a.value, fraction: a.fraction, currency: a.currency}; } - let r = a.value % n; + const r = a.value % n; return { currency: a.currency, - value: Math.floor(a.value / n), fraction: Math.floor(((r * fractionalBase) + a.fraction) / n), - } + value: Math.floor(a.value / n), + }; } export function isNonZero(a: AmountJson) { @@ -746,15 +746,15 @@ export namespace Amounts { * Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct. */ export function parse(s: string): AmountJson|undefined { - let res = s.match(/([a-zA-Z0-9_*-]+):([0-9])+([.][0-9]+)?/); + const res = s.match(/([a-zA-Z0-9_*-]+):([0-9])+([.][0-9]+)?/); if (!res) { return undefined; } return { currency: res[1], - value: Number.parseInt(res[2]), fraction: Math.round(fractionalBase * Number.parseFloat(res[3] || "0")), - } + value: Number.parseInt(res[2]), + }; } } diff --git a/src/wallet-test.ts b/src/wallet-test.ts index 2c3ed52d0..51a8497b7 100644 --- a/src/wallet-test.ts +++ b/src/wallet-test.ts @@ -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)", + feeWithdraw: a("EUR:0.0"), + isOffered: true, masterSig: "(mock)", + stampExpireDeposit: "(mock)", + stampExpireLegal: "(mock)", + stampExpireWithdraw: "(mock)", + stampStart: "(mock)", status: types.DenominationStatus.VerifiedGood, - isOffered: true, - exchangeBaseUrl: "(mock)", + 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(); }); diff --git a/src/wallet.ts b/src/wallet.ts index a2a9e83f5..b21cdbd96 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -22,57 +22,58 @@ /** * Imports. */ +import {Checkable} from "./checkable"; +import {CryptoApi} from "./crypto/cryptoApi"; +import { + amountToPretty, + canonicalJson, + canonicalizeBaseUrl, + deepEquals, + flatMap, + getTalerStampSec, +} from "./helpers"; +import { + HttpRequestLibrary, + HttpResponse, + RequestException, +} from "./http"; +import { + AbortTransaction, + Index, + JoinLeftResult, + JoinResult, + QueryRoot, + Store, +} from "./query"; import { AmountJson, Amounts, - CoinRecord, + Auditor, + AuditorRecord, CoinPaySig, + CoinRecord, + CoinStatus, Contract, CreateReserveResponse, + CurrencyRecord, Denomination, + DenominationRecord, + DenominationStatus, ExchangeHandle, ExchangeRecord, + ExchangeWireFeesRecord, Notifier, PayCoinInfo, + PaybackConfirmation, PreCoinRecord, RefreshSessionRecord, ReserveCreationInfo, ReserveRecord, - CurrencyRecord, - Auditor, - AuditorRecord, WalletBalance, WalletBalanceEntry, WireFee, - ExchangeWireFeesRecord, WireInfo, - DenominationRecord, - DenominationStatus, - CoinStatus, - PaybackConfirmation, } from "./types"; -import { - HttpRequestLibrary, - HttpResponse, - RequestException, -} from "./http"; -import { - AbortTransaction, - Index, - JoinResult, - QueryRoot, - Store, JoinLeftResult, -} from "./query"; -import {Checkable} from "./checkable"; -import { - amountToPretty, - canonicalizeBaseUrl, - canonicalJson, - deepEquals, - flatMap, - getTalerStampSec, -} from "./helpers"; -import {CryptoApi} from "./crypto/cryptoApi"; import URI = require("urijs"); @@ -272,33 +273,31 @@ export interface ConfigRecord { } - const builtinCurrencies: CurrencyRecord[] = [ { - name: "KUDOS", - fractionalDigits: 2, auditors: [ { + auditorPub: "XN9KMN5G2KGPCAN0E89MM5HE8FV4WBWA9KDTMTDR817MWBCYA7H0", baseUrl: "https://auditor.demo.taler.net/", expirationStamp: (new Date(2027, 1)).getTime(), - auditorPub: "XN9KMN5G2KGPCAN0E89MM5HE8FV4WBWA9KDTMTDR817MWBCYA7H0", }, ], exchanges: [], + fractionalDigits: 2, + name: "KUDOS", }, { - name: "PUDOS", - fractionalDigits: 2, auditors: [ ], exchanges: [ { baseUrl: "https://exchange.test.taler.net/", priority: 0 }, ], + fractionalDigits: 2, + name: "PUDOS", }, ]; - // FIXME: these functions should be dependency-injected // into the wallet, as this is chrome specific => bad @@ -312,17 +311,17 @@ function setInterval(f: any, t: number) { function isWithdrawableDenom(d: DenominationRecord) { - const now_sec = (new Date).getTime() / 1000; - const stamp_withdraw_sec = getTalerStampSec(d.stampExpireWithdraw); - if (stamp_withdraw_sec == null) { + const nowSec = (new Date()).getTime() / 1000; + const stampWithdrawSec = getTalerStampSec(d.stampExpireWithdraw); + if (stampWithdrawSec === null) { return false; } - const stamp_start_sec = getTalerStampSec(d.stampStart); - if (stamp_start_sec == null) { + const stampStartSec = getTalerStampSec(d.stampStart); + if (stampStartSec === null) { return false; } // Withdraw if still possible to withdraw within a minute - if ((stamp_withdraw_sec + 60 > now_sec) && (now_sec >= stamp_start_sec)) { + if ((stampWithdrawSec + 60 > nowSec) && (nowSec >= stampStartSec)) { return true; } return false; @@ -333,31 +332,30 @@ export type CoinSelectionResult = {exchangeUrl: string, cds: CoinWithDenom[]}|un export function selectCoins(cds: CoinWithDenom[], paymentAmount: AmountJson, depositFeeLimit: AmountJson): CoinWithDenom[]|undefined { - if (cds.length == 0) { + if (cds.length === 0) { return undefined; } // Sort by ascending deposit fee cds.sort((o1, o2) => Amounts.cmp(o1.denom.feeDeposit, o2.denom.feeDeposit)); - let currency = cds[0].denom.value.currency; - let cdsResult: CoinWithDenom[] = []; + const currency = cds[0].denom.value.currency; + const cdsResult: CoinWithDenom[] = []; let accFee: AmountJson = Amounts.getZero(currency); let accAmount: AmountJson = Amounts.getZero(currency); let isBelowFee = false; let coversAmount = false; let coversAmountWithFee = false; - for (let i = 0; i < cds.length; i++) { - let {coin, denom} = cds[i]; + for (const {coin, denom} of cds) { if (coin.suspended) { continue; } - if (coin.status != CoinStatus.Fresh) { + if (coin.status !== CoinStatus.Fresh) { continue; } if (Amounts.cmp(denom.feeDeposit, coin.currentAmount) >= 0) { continue; } - cdsResult.push(cds[i]); + cdsResult.push({coin, denom}); accFee = Amounts.add(denom.feeDeposit, accFee).amount; accAmount = Amounts.add(coin.currentAmount, accAmount).amount; coversAmount = Amounts.cmp(accAmount, paymentAmount) >= 0; @@ -391,8 +389,8 @@ function getWithdrawDenomList(amountAvailable: AmountJson, // is useful ... for (let i = 0; i < 1000; i++) { let found = false; - for (let d of denoms) { - let cost = Amounts.add(d.value, d.feeWithdraw).amount; + for (const d of denoms) { + const cost = Amounts.add(d.value, d.feeWithdraw).amount; if (Amounts.cmp(remaining, cost) < 0) { continue; } @@ -415,7 +413,7 @@ export namespace Stores { super("exchanges", {keyPath: "baseUrl"}); } - pubKeyIndex = new Index<string,ExchangeRecord>(this, "pubKey", "masterPublicKey"); + pubKeyIndex = new Index<string, ExchangeRecord>(this, "pubKey", "masterPublicKey"); } class NonceStore extends Store<NonceRecord> { @@ -429,26 +427,26 @@ export namespace Stores { super("coins", {keyPath: "coinPub"}); } - exchangeBaseUrlIndex = new Index<string,CoinRecord>(this, "exchangeBaseUrl", "exchangeBaseUrl"); - denomPubIndex = new Index<string,CoinRecord>(this, "denomPub", "denomPub"); + exchangeBaseUrlIndex = new Index<string, CoinRecord>(this, "exchangeBaseUrl", "exchangeBaseUrl"); + denomPubIndex = new Index<string, CoinRecord>(this, "denomPub", "denomPub"); } class HistoryStore extends Store<HistoryRecord> { constructor() { super("history", { + autoIncrement: true, keyPath: "id", - autoIncrement: true }); } - timestampIndex = new Index<number,HistoryRecord>(this, "timestamp", "timestamp"); + timestampIndex = new Index<number, HistoryRecord>(this, "timestamp", "timestamp"); } class OffersStore extends Store<OfferRecord> { constructor() { super("offers", { + autoIncrement: true, keyPath: "id", - autoIncrement: true }); } } @@ -458,8 +456,8 @@ export namespace Stores { super("transactions", {keyPath: "contractHash"}); } - fulfillmentUrlIndex = new Index<string,TransactionRecord>(this, "fulfillment_url", "contract.fulfillment_url"); - orderIdIndex = new Index<string,TransactionRecord>(this, "order_id", "contract.order_id"); + fulfillmentUrlIndex = new Index<string, TransactionRecord>(this, "fulfillment_url", "contract.fulfillment_url"); + orderIdIndex = new Index<string, TransactionRecord>(this, "order_id", "contract.order_id"); } class DenominationsStore extends Store<DenominationRecord> { @@ -469,7 +467,7 @@ export namespace Stores { {keyPath: ["exchangeBaseUrl", "denomPub"] as any as IDBKeyPath}); } - denomPubHashIndex = new Index<string,DenominationRecord>(this, "denomPubHash", "denomPubHash"); + denomPubHashIndex = new Index<string, DenominationRecord>(this, "denomPubHash", "denomPubHash"); exchangeBaseUrlIndex = new Index<string, DenominationRecord>(this, "exchangeBaseUrl", "exchangeBaseUrl"); denomPubIndex = new Index<string, DenominationRecord>(this, "denomPub", "denomPub"); } @@ -491,19 +489,31 @@ export namespace Stores { super("exchangeWireFees", {keyPath: "exchangeBaseUrl"}); } } - export const exchanges: ExchangeStore = new ExchangeStore(); - export const exchangeWireFees: ExchangeWireFeesStore = new ExchangeWireFeesStore(); - export const nonces: NonceStore = new NonceStore(); - export const transactions: TransactionsStore = new TransactionsStore(); - export const reserves: Store<ReserveRecord> = new Store<ReserveRecord>("reserves", {keyPath: "reserve_pub"}); - export const coins: CoinsStore = new CoinsStore(); - export const refresh: Store<RefreshSessionRecord> = new Store<RefreshSessionRecord>("refresh", {keyPath: "meltCoinPub"}); - export const history: HistoryStore = new HistoryStore(); - export const offers: OffersStore = new OffersStore(); - export const precoins: Store<PreCoinRecord> = new Store<PreCoinRecord>("precoins", {keyPath: "coinPub"}); - export const denominations: DenominationsStore = new DenominationsStore(); - export const currencies: CurrenciesStore = new CurrenciesStore(); - export const config: ConfigStore = new ConfigStore(); + export const exchanges = new ExchangeStore(); + export const exchangeWireFees = new ExchangeWireFeesStore(); + export const nonces = new NonceStore(); + export const transactions = new TransactionsStore(); + export const reserves = new Store<ReserveRecord>("reserves", {keyPath: "reserve_pub"}); + export const coins = new CoinsStore(); + export const refresh = new Store<RefreshSessionRecord>("refresh", {keyPath: "meltCoinPub"}); + export const history = new HistoryStore(); + export const offers = new OffersStore(); + export const precoins = new Store<PreCoinRecord>("precoins", {keyPath: "coinPub"}); + export const denominations = new DenominationsStore(); + export const currencies = new CurrenciesStore(); + export const config = new ConfigStore(); +} + + +interface CoinsForPaymentArgs { + allowedAuditors: Auditor[]; + allowedExchanges: ExchangeHandle[]; + depositFeeLimit: AmountJson; + paymentAmount: AmountJson; + wireFeeAmortization: number; + wireFeeLimit: AmountJson; + wireFeeTime: number; + wireMethod: string; } @@ -543,10 +553,10 @@ export class Wallet { } private async fillDefaults() { - let onTrue = (r: QueryRoot) => { + const onTrue = (r: QueryRoot) => { console.log("defaults already applied"); }; - let onFalse = (r: QueryRoot) => { + const onFalse = (r: QueryRoot) => { console.log("applying defaults"); r.put(Stores.config, {key: "currencyDefaultsApplied", value: true}) .putAll(Stores.currencies, builtinCurrencies) @@ -555,7 +565,7 @@ export class Wallet { await ( this.q() .iter(Stores.config) - .filter(x => x.key == "currencyDefaultsApplied") + .filter((x) => x.key === "currencyDefaultsApplied") .first() .cond((x) => x && x.value, onTrue, onFalse) ); @@ -569,7 +579,7 @@ export class Wallet { private stopOperation(operationId: string) { this.runningOperations.delete(operationId); - if (this.runningOperations.size == 0) { + if (this.runningOperations.size === 0) { this.badge.stopBusy(); } } @@ -577,12 +587,12 @@ export class Wallet { async updateExchanges(): Promise<void> { console.log("updating exchanges"); - let exchangesUrls = await this.q() - .iter(Stores.exchanges) - .map((e) => e.baseUrl) - .toArray(); + const exchangesUrls = await this.q() + .iter(Stores.exchanges) + .map((e) => e.baseUrl) + .toArray(); - for (let url of exchangesUrls) { + for (const url of exchangesUrls) { this.updateExchangeFromUrl(url) .catch((e) => { console.error("updating exchange failed", e); @@ -621,7 +631,7 @@ export class Wallet { this.q() .iter(Stores.coins) .reduce((c: CoinRecord) => { - if (c.status == CoinStatus.Dirty) { + if (c.status === CoinStatus.Dirty) { console.log("resuming pending refresh for coin", c); this.refresh(c.coinPub); } @@ -633,23 +643,28 @@ export class Wallet { * Get exchanges and associated coins that are still spendable, * but only if the sum the coins' remaining value exceeds the payment amount. */ - private async getCoinsForPayment(paymentAmount: AmountJson, - wireMethod: string, - wireFeeTime: number, - depositFeeLimit: AmountJson, - wireFeeLimit: AmountJson, - wireFeeAmortization: number, - allowedExchanges: ExchangeHandle[], - allowedAuditors: Auditor[]): Promise<CoinSelectionResult> { - - let exchanges = await this.q().iter(Stores.exchanges).toArray(); - - for (let exchange of exchanges) { + private async getCoinsForPayment(args: CoinsForPaymentArgs): Promise<CoinSelectionResult> { + const { + allowedAuditors, + allowedExchanges, + depositFeeLimit, + paymentAmount, + wireFeeAmortization, + wireFeeLimit, + wireFeeTime, + wireMethod, + } = args; + + let remainingAmount = paymentAmount; + + const exchanges = await this.q().iter(Stores.exchanges).toArray(); + + for (const exchange of exchanges) { let isOkay: boolean = false; // is the exchange explicitly allowed? - for (let allowedExchange of allowedExchanges) { - if (allowedExchange.master_pub == exchange.masterPublicKey) { + for (const allowedExchange of allowedExchanges) { + if (allowedExchange.master_pub === exchange.masterPublicKey) { isOkay = true; break; } @@ -657,9 +672,9 @@ export class Wallet { // is the exchange allowed because of one of its auditors? if (!isOkay) { - for (let allowedAuditor of allowedAuditors) { - for (let auditor of exchange.auditors) { - if (auditor.auditor_pub == allowedAuditor.auditor_pub) { + for (const allowedAuditor of allowedAuditors) { + for (const auditor of exchange.auditors) { + if (auditor.auditor_pub === allowedAuditor.auditor_pub) { isOkay = true; break; } @@ -674,53 +689,52 @@ export class Wallet { continue; } - let coins: CoinRecord[] = await this.q() + const coins: CoinRecord[] = await this.q() .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl) .toArray(); - if (!coins || coins.length == 0) { + if (!coins || coins.length === 0) { continue; } // Denomination of the first coin, we assume that all other // coins have the same currency - let firstDenom = await this.q().get(Stores.denominations, - [ - exchange.baseUrl, - coins[0].denomPub - ]); + const firstDenom = await this.q().get(Stores.denominations, + [ + exchange.baseUrl, + coins[0].denomPub, + ]); if (!firstDenom) { throw Error("db inconsistent"); } - let currency = firstDenom.value.currency; - let cds: CoinWithDenom[] = []; - for (let i = 0; i < coins.length; i++) { - let coin = coins[i]; - let denom = await this.q().get(Stores.denominations, + const currency = firstDenom.value.currency; + const cds: CoinWithDenom[] = []; + for (const coin of coins) { + const denom = await this.q().get(Stores.denominations, [exchange.baseUrl, coin.denomPub]); if (!denom) { throw Error("db inconsistent"); } - if (denom.value.currency != currency) { + if (denom.value.currency !== currency) { console.warn(`same pubkey for different currencies at exchange ${exchange.baseUrl}`); continue; } if (coin.suspended) { continue; } - if (coin.status != CoinStatus.Fresh) { + if (coin.status !== CoinStatus.Fresh) { continue; } cds.push({coin, denom}); } - let fees = await this.q().get(Stores.exchangeWireFees, exchange.baseUrl); + const fees = await this.q().get(Stores.exchangeWireFees, exchange.baseUrl); if (!fees) { console.error("no fees found for exchange", exchange); continue; } - let wireFee: AmountJson|undefined = undefined; - for (let fee of (fees.feesForType[wireMethod] || [])) { + let wireFee: AmountJson|undefined; + for (const fee of (fees.feesForType[wireMethod] || [])) { if (fee.startStamp >= wireFeeTime && fee.endStamp <= wireFeeTime) { wireFee = fee.wireFee; break; @@ -728,18 +742,18 @@ export class Wallet { } if (wireFee) { - let amortizedWireFee = Amounts.divide(wireFee, wireFeeAmortization); + const amortizedWireFee = Amounts.divide(wireFee, wireFeeAmortization); if (Amounts.cmp(wireFeeLimit, amortizedWireFee) < 0) { - paymentAmount = Amounts.add(amortizedWireFee, paymentAmount).amount; + remainingAmount = Amounts.add(amortizedWireFee, remainingAmount).amount; } } - let res = selectCoins(cds, paymentAmount, depositFeeLimit); + const res = selectCoins(cds, remainingAmount, depositFeeLimit); if (res) { return { - exchangeUrl: exchange.baseUrl, cds: res, - } + exchangeUrl: exchange.baseUrl, + }; } } return undefined; @@ -753,31 +767,31 @@ export class Wallet { private async recordConfirmPay(offer: OfferRecord, payCoinInfo: PayCoinInfo, chosenExchange: string): Promise<void> { - let payReq: PayReq = { + const payReq: PayReq = { coins: payCoinInfo.map((x) => x.sig), + exchange: chosenExchange, merchant_pub: offer.contract.merchant_pub, order_id: offer.contract.order_id, - exchange: chosenExchange, }; - let t: TransactionRecord = { - contractHash: offer.H_contract, + const t: TransactionRecord = { contract: offer.contract, - payReq: payReq, - merchantSig: offer.merchant_sig, + contractHash: offer.H_contract, finished: false, + merchantSig: offer.merchant_sig, + payReq, }; - let historyEntry: HistoryRecord = { - type: "pay", - timestamp: (new Date).getTime(), - subjectId: `contract-${offer.H_contract}`, + const historyEntry: HistoryRecord = { detail: { - merchantName: offer.contract.merchant.name, amount: offer.contract.amount, contractHash: offer.H_contract, fulfillmentUrl: offer.contract.fulfillment_url, + merchantName: offer.contract.merchant.name, }, - level: HistoryLevel.User + level: HistoryLevel.User, + subjectId: `contract-${offer.H_contract}`, + timestamp: (new Date()).getTime(), + type: "pay", }; await this.q() @@ -798,7 +812,7 @@ export class Wallet { async saveOffer(offer: OfferRecord): Promise<number> { console.log(`saving offer in wallet.ts`); - let id = await this.q().putWithResult(Stores.offers, offer); + const id = await this.q().putWithResult(Stores.offers, offer); this.notifier.notify(); console.log(`saved offer with id ${id}`); if (typeof id !== "number") { @@ -815,21 +829,23 @@ export class Wallet { async confirmPay(offer: OfferRecord): Promise<any> { console.log("executing confirmPay"); - let transaction = await this.q().get(Stores.transactions, offer.H_contract); + const transaction = await this.q().get(Stores.transactions, offer.H_contract); if (transaction) { // Already payed ... return {}; } - let res = await this.getCoinsForPayment(offer.contract.amount, - offer.contract.wire_method, - getTalerStampSec(offer.contract.timestamp) || 0, - offer.contract.max_fee, - offer.contract.max_wire_fee || Amounts.getZero(offer.contract.amount.currency), - offer.contract.wire_fee_amortization || 1, - offer.contract.exchanges, - offer.contract.auditors); + const res = await this.getCoinsForPayment({ + allowedAuditors: offer.contract.auditors, + allowedExchanges: offer.contract.exchanges, + depositFeeLimit: offer.contract.max_fee, + paymentAmount: offer.contract.amount, + wireFeeAmortization: offer.contract.wire_fee_amortization || 1, + wireFeeLimit: offer.contract.max_wire_fee || Amounts.getZero(offer.contract.amount.currency), + wireFeeTime: getTalerStampSec(offer.contract.timestamp) || 0, + wireMethod: offer.contract.wire_method, + }); console.log("max_fee", offer.contract.max_fee); console.log("coin selection result", res); @@ -840,9 +856,9 @@ export class Wallet { error: "coins-insufficient", }; } - let {exchangeUrl, cds} = res; + const {exchangeUrl, cds} = res; - let ds = await this.cryptoApi.signDeposit(offer, cds); + const ds = await this.cryptoApi.signDeposit(offer, cds); await this.recordConfirmPay(offer, ds, exchangeUrl); @@ -856,20 +872,22 @@ export class Wallet { */ async checkPay(offer: OfferRecord): Promise<any> { // First check if we already payed for it. - let transaction = await this.q().get(Stores.transactions, offer.H_contract); + const transaction = await this.q().get(Stores.transactions, offer.H_contract); if (transaction) { return {isPayed: true}; } // If not already payed, check if we could pay for it. - let res = await this.getCoinsForPayment(offer.contract.amount, - offer.contract.wire_method, - getTalerStampSec(offer.contract.timestamp) || 0, - offer.contract.max_fee, - offer.contract.max_wire_fee || Amounts.getZero(offer.contract.amount.currency), - offer.contract.wire_fee_amortization || 1, - offer.contract.exchanges, - offer.contract.auditors); + const res = await this.getCoinsForPayment({ + allowedAuditors: offer.contract.auditors, + allowedExchanges: offer.contract.exchanges, + depositFeeLimit: offer.contract.max_fee, + paymentAmount: offer.contract.amount, + wireFeeAmortization: offer.contract.wire_fee_amortization || 1, + wireFeeLimit: offer.contract.max_wire_fee || Amounts.getZero(offer.contract.amount.currency), + wireFeeTime: getTalerStampSec(offer.contract.timestamp) || 0, + wireMethod: offer.contract.wire_method, + }); if (!res) { console.log("not confirming payment, insufficient coins"); @@ -894,14 +912,14 @@ export class Wallet { console.log("query for payment failed"); return { success: false, - } + }; } console.log("query for payment succeeded:", t); - let resp = { - success: true, - payReq: t.payReq, + const resp = { H_contract: t.contractHash, contract: t.contract, + payReq: t.payReq, + success: true, }; return resp; } @@ -917,29 +935,28 @@ export class Wallet { this.startOperation(opId); try { - let exchange = await this.updateExchangeFromUrl(reserveRecord.exchange_base_url); - let reserve = await this.updateReserve(reserveRecord.reserve_pub); - let n = await this.depleteReserve(reserve); + const exchange = await this.updateExchangeFromUrl(reserveRecord.exchange_base_url); + const reserve = await this.updateReserve(reserveRecord.reserve_pub); + const n = await this.depleteReserve(reserve); - if (n != 0) { - let depleted: HistoryRecord = { - type: "depleted-reserve", - subjectId: `reserve-progress-${reserveRecord.reserve_pub}`, - timestamp: (new Date).getTime(), + if (n !== 0) { + const depleted: HistoryRecord = { detail: { + currentAmount: reserveRecord.current_amount, exchangeBaseUrl: reserveRecord.exchange_base_url, - reservePub: reserveRecord.reserve_pub, requestedAmount: reserveRecord.requested_amount, - currentAmount: reserveRecord.current_amount, + reservePub: reserveRecord.reserve_pub, }, - level: HistoryLevel.User + level: HistoryLevel.User, + subjectId: `reserve-progress-${reserveRecord.reserve_pub}`, + timestamp: (new Date()).getTime(), + type: "depleted-reserve", }; await this.q().put(Stores.history, depleted).finish(); } } catch (e) { // random, exponential backoff truncated at 3 minutes - let nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), - 3000 * 60); + const nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), 3000 * 60); console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`); setTimeout(() => this.processReserve(reserveRecord, nextDelay), retryDelayMs); @@ -980,9 +997,7 @@ export class Wallet { console.log(`before committing coin: current ${amountToPretty(r.current_amount!)}, precoin: ${amountToPretty( r.precoin_amount)})}`); - let x = Amounts.sub(r.precoin_amount, - preCoin.coinValue, - denom.feeWithdraw); + const x = Amounts.sub(r.precoin_amount, preCoin.coinValue, denom.feeWithdraw); if (x.saturated) { console.error("database inconsistent"); throw AbortTransaction; @@ -992,12 +1007,12 @@ export class Wallet { }; const historyEntry: HistoryRecord = { - type: "withdraw", - timestamp: (new Date).getTime(), - level: HistoryLevel.Expert, detail: { coinPub: coin.coinPub, - } + }, + level: HistoryLevel.Expert, + timestamp: (new Date()).getTime(), + type: "withdraw", }; await this.q() @@ -1013,10 +1028,12 @@ export class Wallet { retryDelayMs, "ms", e); // exponential backoff truncated at one minute - let nextRetryDelayMs = Math.min(retryDelayMs * 2, 5 * 60 * 1000); + const nextRetryDelayMs = Math.min(retryDelayMs * 2, 5 * 60 * 1000); setTimeout(() => this.processPreCoin(preCoin, nextRetryDelayMs), retryDelayMs); - this.processPreCoinThrottle[preCoin.exchangeBaseUrl] = (this.processPreCoinThrottle[preCoin.exchangeBaseUrl] || 0) + 1; + + const currentThrottle = this.processPreCoinThrottle[preCoin.exchangeBaseUrl] || 0; + this.processPreCoinThrottle[preCoin.exchangeBaseUrl] = currentThrottle + 1; setTimeout(() => {this.processPreCoinThrottle[preCoin.exchangeBaseUrl]--; }, retryDelayMs); } finally { this.processPreCoinConcurrent--; @@ -1031,44 +1048,44 @@ export class Wallet { * audited nor trusted already. */ async createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> { - let keypair = await this.cryptoApi.createEddsaKeypair(); - const now = (new Date).getTime(); + const keypair = await this.cryptoApi.createEddsaKeypair(); + const now = (new Date()).getTime(); const canonExchange = canonicalizeBaseUrl(req.exchange); const reserveRecord: ReserveRecord = { - hasPayback: false, - reserve_pub: keypair.pub, - reserve_priv: keypair.priv, - exchange_base_url: canonExchange, + confirmed: false, created: now, - last_query: null, current_amount: null, - requested_amount: req.amount, - confirmed: false, + exchange_base_url: canonExchange, + hasPayback: false, + last_query: null, precoin_amount: Amounts.getZero(req.amount.currency), + requested_amount: req.amount, + reserve_priv: keypair.priv, + reserve_pub: keypair.pub, }; const historyEntry = { - type: "create-reserve", - level: HistoryLevel.Expert, - timestamp: now, - subjectId: `reserve-progress-${reserveRecord.reserve_pub}`, detail: { requestedAmount: req.amount, reservePub: reserveRecord.reserve_pub, - } + }, + level: HistoryLevel.Expert, + subjectId: `reserve-progress-${reserveRecord.reserve_pub}`, + timestamp: now, + type: "create-reserve", }; - let exchangeInfo = await this.updateExchangeFromUrl(req.exchange); - let {isAudited, isTrusted} = await this.getExchangeTrust(exchangeInfo); + const exchangeInfo = await this.updateExchangeFromUrl(req.exchange); + const {isAudited, isTrusted} = await this.getExchangeTrust(exchangeInfo); let currencyRecord = await this.q().get(Stores.currencies, exchangeInfo.currency); if (!currencyRecord) { currencyRecord = { - name: exchangeInfo.currency, - fractionalDigits: 2, - exchanges: [], auditors: [], - } + exchanges: [], + fractionalDigits: 2, + name: exchangeInfo.currency, + }; } if (!isAudited && !isTrusted) { @@ -1081,7 +1098,7 @@ export class Wallet { .put(Stores.history, historyEntry) .finish(); - let r: CreateReserveResponse = { + const r: CreateReserveResponse = { exchange: canonExchange, reservePub: keypair.pub, }; @@ -1099,8 +1116,8 @@ export class Wallet { * an unconfirmed reserve should be hidden. */ async confirmReserve(req: ConfirmReserveRequest): Promise<void> { - const now = (new Date).getTime(); - let reserve: ReserveRecord|undefined = await ( + const now = (new Date()).getTime(); + const reserve: ReserveRecord|undefined = await ( this.q().get<ReserveRecord>(Stores.reserves, req.reservePub)); if (!reserve) { @@ -1109,15 +1126,15 @@ export class Wallet { } console.log("reserve confirmed"); const historyEntry: HistoryRecord = { - type: "confirm-reserve", - timestamp: now, - subjectId: `reserve-progress-${reserve.reserve_pub}`, detail: { exchangeBaseUrl: reserve.exchange_base_url, - reservePub: req.reservePub, requestedAmount: reserve.requested_amount, + reservePub: req.reservePub, }, level: HistoryLevel.User, + subjectId: `reserve-progress-${reserve.reserve_pub}`, + timestamp: now, + type: "confirm-reserve", }; reserve.confirmed = true; await this.q() @@ -1131,40 +1148,40 @@ export class Wallet { private async withdrawExecute(pc: PreCoinRecord): Promise<CoinRecord> { - let reserve = await this.q().get<ReserveRecord>(Stores.reserves, + const reserve = await this.q().get<ReserveRecord>(Stores.reserves, pc.reservePub); if (!reserve) { throw Error("db inconsistent"); } - let wd: any = {}; + const wd: any = {}; wd.denom_pub = pc.denomPub; wd.reserve_pub = pc.reservePub; wd.reserve_sig = pc.withdrawSig; wd.coin_ev = pc.coinEv; - let reqUrl = (new URI("reserve/withdraw")).absoluteTo(reserve.exchange_base_url); - let resp = await this.http.postJson(reqUrl.href(), wd); + const reqUrl = (new URI("reserve/withdraw")).absoluteTo(reserve.exchange_base_url); + const resp = await this.http.postJson(reqUrl.href(), wd); - if (resp.status != 200) { + if (resp.status !== 200) { throw new RequestException({ hint: "Withdrawal failed", - status: resp.status + status: resp.status, }); } - let r = JSON.parse(resp.responseText); - let denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig, + const r = JSON.parse(resp.responseText); + const denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig, pc.blindingKey, pc.denomPub); - let coin: CoinRecord = { - reservePub: pc.reservePub, - coinPub: pc.coinPub, - coinPriv: pc.coinPriv, - denomPub: pc.denomPub, - denomSig: denomSig, + const coin: CoinRecord = { blindingKey: pc.blindingKey, + coinPriv: pc.coinPriv, + coinPub: pc.coinPub, currentAmount: pc.coinValue, + denomPub: pc.denomPub, + denomSig, exchangeBaseUrl: pc.exchangeBaseUrl, + reservePub: pc.reservePub, status: CoinStatus.Fresh, }; return coin; @@ -1179,25 +1196,24 @@ export class Wallet { if (!reserve.current_amount) { throw Error("can't withdraw when amount is unknown"); } - let currentAmount = reserve.current_amount; - if (!currentAmount) { + const withdrawAmount = reserve.current_amount; + if (!withdrawAmount) { throw Error("can't withdraw when amount is unknown"); } - let denomsForWithdraw = await this.getVerifiedWithdrawDenomList(reserve.exchange_base_url, - currentAmount); + const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(reserve.exchange_base_url, withdrawAmount); console.log(`withdrawing ${denomsForWithdraw.length} coins`); - let ps = denomsForWithdraw.map(async(denom) => { + const ps = denomsForWithdraw.map(async(denom) => { function mutateReserve(r: ReserveRecord): ReserveRecord { - let currentAmount = r.current_amount; + const currentAmount = r.current_amount; if (!currentAmount) { throw Error("can't withdraw when amount is unknown"); } r.precoin_amount = Amounts.add(r.precoin_amount, denom.value, denom.feeWithdraw).amount; - let result = Amounts.sub(currentAmount, + const result = Amounts.sub(currentAmount, denom.value, denom.feeWithdraw); if (result.saturated) { @@ -1212,7 +1228,7 @@ export class Wallet { return r; } - let preCoin = await this.cryptoApi + const preCoin = await this.cryptoApi .createPreCoin(denom, reserve); await this.q() .put(Stores.precoins, preCoin) @@ -1230,34 +1246,34 @@ export class Wallet { * by quering the reserve's exchange. */ private async updateReserve(reservePub: string): Promise<ReserveRecord> { - let reserve = await this.q() + const reserve = await this.q() .get<ReserveRecord>(Stores.reserves, reservePub); if (!reserve) { throw Error("reserve not in db"); } - let reqUrl = new URI("reserve/status").absoluteTo(reserve.exchange_base_url); - reqUrl.query({'reserve_pub': reservePub}); - let resp = await this.http.get(reqUrl.href()); - if (resp.status != 200) { + const reqUrl = new URI("reserve/status").absoluteTo(reserve.exchange_base_url); + reqUrl.query({reserve_pub: reservePub}); + const resp = await this.http.get(reqUrl.href()); + if (resp.status !== 200) { throw Error(); } - let reserveInfo = JSON.parse(resp.responseText); + const reserveInfo = JSON.parse(resp.responseText); if (!reserveInfo) { throw Error(); } - let oldAmount = reserve.current_amount; - let newAmount = reserveInfo.balance; + const oldAmount = reserve.current_amount; + const newAmount = reserveInfo.balance; reserve.current_amount = reserveInfo.balance; - let historyEntry = { - type: "reserve-update", - timestamp: (new Date).getTime(), - subjectId: `reserve-progress-${reserve.reserve_pub}`, + const historyEntry = { detail: { - reservePub, - requestedAmount: reserve.requested_amount, + newAmount, oldAmount, - newAmount - } + requestedAmount: reserve.requested_amount, + reservePub, + }, + subjectId: `reserve-progress-${reserve.reserve_pub}`, + timestamp: (new Date()).getTime(), + type: "reserve-update", }; await this.q() .put(Stores.reserves, reserve) @@ -1272,16 +1288,16 @@ export class Wallet { */ async getWireInfo(exchangeBaseUrl: string): Promise<WireInfo> { exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); - let reqUrl = new URI("wire").absoluteTo(exchangeBaseUrl); - let resp = await this.http.get(reqUrl.href()); + const reqUrl = new URI("wire").absoluteTo(exchangeBaseUrl); + const resp = await this.http.get(reqUrl.href()); - if (resp.status != 200) { + if (resp.status !== 200) { throw Error("/wire request failed"); } - let wiJson = JSON.parse(resp.responseText); + const wiJson = JSON.parse(resp.responseText); if (!wiJson) { - throw Error("/wire response malformed") + throw Error("/wire response malformed"); } return wiJson; } @@ -1290,7 +1306,7 @@ export class Wallet { return ( this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeBaseUrl) - .filter((d) => d.status == DenominationStatus.Unverified || d.status == DenominationStatus.VerifiedGood) + .filter((d) => d.status === DenominationStatus.Unverified || d.status === DenominationStatus.VerifiedGood) .toArray() ); } @@ -1312,7 +1328,7 @@ export class Wallet { const possibleDenoms = await ( this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl) - .filter((d) => d.status == DenominationStatus.Unverified || d.status == DenominationStatus.VerifiedGood) + .filter((d) => d.status === DenominationStatus.Unverified || d.status === DenominationStatus.VerifiedGood) .toArray() ); @@ -1323,12 +1339,12 @@ export class Wallet { do { allValid = true; - let nextPossibleDenoms = []; + const nextPossibleDenoms = []; selectedDenoms = getWithdrawDenomList(amount, possibleDenoms); - for (let denom of selectedDenoms || []) { - if (denom.status == DenominationStatus.Unverified) { + for (const denom of selectedDenoms || []) { + if (denom.status === DenominationStatus.Unverified) { console.log(`verifying denom ${denom.denomPub.substr(0, 15)}`); - let valid = await this.cryptoApi.isValidDenom(denom, + const valid = await this.cryptoApi.isValidDenom(denom, exchange.masterPublicKey); if (!valid) { denom.status = DenominationStatus.VerifiedBad; @@ -1349,24 +1365,23 @@ export class Wallet { } - /** * Check if and how an exchange is trusted and/or audited. */ async getExchangeTrust(exchangeInfo: ExchangeRecord): Promise<{isTrusted: boolean, isAudited: boolean}> { let isTrusted = false; let isAudited = false; - let currencyRecord = await this.q().get(Stores.currencies, exchangeInfo.currency); + const currencyRecord = await this.q().get(Stores.currencies, exchangeInfo.currency); if (currencyRecord) { - for (let trustedExchange of currencyRecord.exchanges) { - if (trustedExchange.baseUrl == exchangeInfo.baseUrl) { + for (const trustedExchange of currencyRecord.exchanges) { + if (trustedExchange.baseUrl === exchangeInfo.baseUrl) { isTrusted = true; break; } } - for (let trustedAuditor of currencyRecord.auditors) { - for (let exchangeAuditor of exchangeInfo.auditors) { - if (trustedAuditor.baseUrl == exchangeAuditor.url) { + for (const trustedAuditor of currencyRecord.auditors) { + for (const exchangeAuditor of exchangeInfo.auditors) { + if (trustedAuditor.baseUrl === exchangeAuditor.url) { isAudited = true; break; } @@ -1378,47 +1393,47 @@ export class Wallet { async getReserveCreationInfo(baseUrl: string, amount: AmountJson): Promise<ReserveCreationInfo> { - let exchangeInfo = await this.updateExchangeFromUrl(baseUrl); + const exchangeInfo = await this.updateExchangeFromUrl(baseUrl); - let selectedDenoms = await this.getVerifiedWithdrawDenomList(baseUrl, + const selectedDenoms = await this.getVerifiedWithdrawDenomList(baseUrl, amount); let acc = Amounts.getZero(amount.currency); - for (let d of selectedDenoms) { + for (const d of selectedDenoms) { acc = Amounts.add(acc, d.feeWithdraw).amount; } - let actualCoinCost = selectedDenoms + const actualCoinCost = selectedDenoms .map((d: DenominationRecord) => Amounts.add(d.value, d.feeWithdraw).amount) .reduce((a, b) => Amounts.add(a, b).amount); - let wireInfo = await this.getWireInfo(baseUrl); + const wireInfo = await this.getWireInfo(baseUrl); - let wireFees = await this.q().get(Stores.exchangeWireFees, baseUrl); + const wireFees = await this.q().get(Stores.exchangeWireFees, baseUrl); if (!wireFees) { // should never happen unless DB is inconsistent throw Error(`no wire fees found for exchange ${baseUrl}`); } - let {isTrusted, isAudited} = await this.getExchangeTrust(exchangeInfo); + const {isTrusted, isAudited} = await this.getExchangeTrust(exchangeInfo); - let earliestDepositExpiration = Infinity;; - for (let denom of selectedDenoms) { - let expireDeposit = getTalerStampSec(denom.stampExpireDeposit)!; + let earliestDepositExpiration = Infinity; + for (const denom of selectedDenoms) { + const expireDeposit = getTalerStampSec(denom.stampExpireDeposit)!; if (expireDeposit < earliestDepositExpiration) { earliestDepositExpiration = expireDeposit; } } - let ret: ReserveCreationInfo = { + const ret: ReserveCreationInfo = { + earliestDepositExpiration, exchangeInfo, - selectedDenoms, - wireInfo, - wireFees, isAudited, isTrusted, - withdrawFee: acc, - earliestDepositExpiration, overhead: Amounts.sub(amount, actualCoinCost).amount, + selectedDenoms, + wireFees, + wireInfo, + withdrawFee: acc, }; return ret; } @@ -1431,24 +1446,24 @@ export class Wallet { */ async updateExchangeFromUrl(baseUrl: string): Promise<ExchangeRecord> { baseUrl = canonicalizeBaseUrl(baseUrl); - let keysUrl = new URI("keys").absoluteTo(baseUrl); - let wireUrl = new URI("wire").absoluteTo(baseUrl); - let keysResp = await this.http.get(keysUrl.href()); - if (keysResp.status != 200) { + const keysUrl = new URI("keys").absoluteTo(baseUrl); + const wireUrl = new URI("wire").absoluteTo(baseUrl); + const keysResp = await this.http.get(keysUrl.href()); + if (keysResp.status !== 200) { throw Error("/keys request failed"); } - let wireResp = await this.http.get(wireUrl.href()); - if (wireResp.status != 200) { + const wireResp = await this.http.get(wireUrl.href()); + if (wireResp.status !== 200) { throw Error("/wire request failed"); } - let exchangeKeysJson = KeysJson.checked(JSON.parse(keysResp.responseText)); - let wireRespJson = JSON.parse(wireResp.responseText); + const exchangeKeysJson = KeysJson.checked(JSON.parse(keysResp.responseText)); + const wireRespJson = JSON.parse(wireResp.responseText); if (typeof wireRespJson !== "object") { throw Error("/wire response is not an object"); } console.log("exchange wire", wireRespJson); - let wireMethodDetails: WireDetailJson[] = []; - for (let methodName in wireRespJson) { + const wireMethodDetails: WireDetailJson[] = []; + for (const methodName in wireRespJson) { wireMethodDetails.push(WireDetailJson.checked(wireRespJson[methodName])); } return this.updateExchangeFromJson(baseUrl, exchangeKeysJson, wireMethodDetails); @@ -1456,12 +1471,12 @@ export class Wallet { private async suspendCoins(exchangeInfo: ExchangeRecord): Promise<void> { - let suspendedCoins = await ( + const resultSuspendedCoins = await ( this.q() .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchangeInfo.baseUrl) .indexJoinLeft(Stores.denominations.exchangeBaseUrlIndex, (e) => e.exchangeBaseUrl) - .reduce((cd: JoinLeftResult<CoinRecord,DenominationRecord>, + .reduce((cd: JoinLeftResult<CoinRecord, DenominationRecord>, suspendedCoins: CoinRecord[]) => { if ((!cd.right) || (!cd.right.isOffered)) { return Array.prototype.concat(suspendedCoins, [cd.left]); @@ -1469,8 +1484,8 @@ export class Wallet { return Array.prototype.concat(suspendedCoins); }, [])); - let q = this.q(); - suspendedCoins.map((c) => { + const q = this.q(); + resultSuspendedCoins.map((c) => { console.log("suspending coin", c); c.suspended = true; q.put(Stores.coins, c); @@ -1489,7 +1504,7 @@ export class Wallet { throw Error("invalid update time"); } - if (exchangeKeysJson.denoms.length == 0) { + if (exchangeKeysJson.denoms.length === 0) { throw Error("exchange doesn't offer any denominations"); } @@ -1499,24 +1514,24 @@ export class Wallet { if (!r) { exchangeInfo = { + auditors: exchangeKeysJson.auditors, baseUrl, + currency: exchangeKeysJson.denoms[0].value.currency, lastUpdateTime: updateTimeSec, masterPublicKey: exchangeKeysJson.master_public_key, - auditors: exchangeKeysJson.auditors, - currency: exchangeKeysJson.denoms[0].value.currency, }; console.log("making fresh exchange"); } else { if (updateTimeSec < r.lastUpdateTime) { console.log("outdated /keys, not updating"); - return r + return r; } exchangeInfo = r; exchangeInfo.lastUpdateTime = updateTimeSec; console.log("updating old exchange"); } - let updatedExchangeInfo = await this.updateExchangeInfo(exchangeInfo, + const updatedExchangeInfo = await this.updateExchangeInfo(exchangeInfo, exchangeKeysJson); await this.suspendCoins(updatedExchangeInfo); @@ -1532,37 +1547,37 @@ export class Wallet { }; } - for (let detail of wireMethodDetails) { + for (const detail of wireMethodDetails) { let latestFeeStamp = 0; - let fees = oldWireFees.feesForType[detail.type] || []; + const fees = oldWireFees.feesForType[detail.type] || []; oldWireFees.feesForType[detail.type] = fees; - for (let oldFee of fees) { + for (const oldFee of fees) { if (oldFee.endStamp > latestFeeStamp) { latestFeeStamp = oldFee.endStamp; } } - for (let fee of detail.fees) { - let start = getTalerStampSec(fee.start_date); - if (start == null) { + for (const fee of detail.fees) { + const start = getTalerStampSec(fee.start_date); + if (start === null) { console.error("invalid start stamp in fee", fee); continue; } if (start < latestFeeStamp) { continue; } - let end = getTalerStampSec(fee.end_date); - if (end == null) { + const end = getTalerStampSec(fee.end_date); + if (end === null) { console.error("invalid end stamp in fee", fee); continue; } - let wf: WireFee = { - wireFee: fee.wire_fee, + const wf: WireFee = { closingFee: fee.closing_fee, + endStamp: end, sig: fee.sig, startStamp: start, - endStamp: end, - } - let valid: boolean = await this.cryptoApi.isValidWireFee(detail.type, wf, exchangeInfo.masterPublicKey); + wireFee: fee.wire_fee, + }; + const valid: boolean = await this.cryptoApi.isValidWireFee(detail.type, wf, exchangeInfo.masterPublicKey); if (!valid) { console.error("fee signature invalid", fee); throw Error("fee signature invalid"); @@ -1574,14 +1589,14 @@ export class Wallet { await this.q().put(Stores.exchangeWireFees, oldWireFees); if (exchangeKeysJson.payback) { - for (let payback of exchangeKeysJson.payback) { - let denom = await this.q().getIndexed(Stores.denominations.denomPubHashIndex, payback.h_denom_pub); + for (const payback of exchangeKeysJson.payback) { + const denom = await this.q().getIndexed(Stores.denominations.denomPubHashIndex, payback.h_denom_pub); if (!denom) { continue; } console.log(`cashing back denom`, denom); - let coins = await this.q().iterIndex(Stores.coins.denomPubIndex, denom.denomPub).toArray(); - for (let coin of coins) { + const coins = await this.q().iterIndex(Stores.coins.denomPubIndex, denom.denomPub).toArray(); + for (const coin of coins) { this.payback(coin.coinPub); } } @@ -1593,7 +1608,7 @@ export class Wallet { private async updateExchangeInfo(exchangeInfo: ExchangeRecord, newKeys: KeysJson): Promise<ExchangeRecord> { - if (exchangeInfo.masterPublicKey != newKeys.master_public_key) { + if (exchangeInfo.masterPublicKey !== newKeys.master_public_key) { throw Error("public keys do not match"); } @@ -1608,17 +1623,17 @@ export class Wallet { const newDenoms: typeof existingDenoms = {}; const newAndUnseenDenoms: typeof existingDenoms = {}; - for (let d of newKeys.denoms) { - let dr = await this.denominationRecordFromKeys(exchangeInfo.baseUrl, d); + for (const d of newKeys.denoms) { + const dr = await this.denominationRecordFromKeys(exchangeInfo.baseUrl, d); if (!(d.denom_pub in existingDenoms)) { newAndUnseenDenoms[dr.denomPub] = dr; } newDenoms[dr.denomPub] = dr; } - for (let oldDenomPub in existingDenoms) { + for (const oldDenomPub in existingDenoms) { if (!(oldDenomPub in newDenoms)) { - let d = existingDenoms[oldDenomPub]; + const d = existingDenoms[oldDenomPub]; d.isOffered = false; } } @@ -1640,13 +1655,13 @@ export class Wallet { async getBalances(): Promise<WalletBalance> { function ensureEntry(balance: WalletBalance, currency: string) { let entry: WalletBalanceEntry|undefined = balance[currency]; - let z = Amounts.getZero(currency); + const z = Amounts.getZero(currency); if (!entry) { balance[currency] = entry = { available: z, + paybackAmount: z, pendingIncoming: z, pendingPayment: z, - paybackAmount: z, }; } return entry; @@ -1656,11 +1671,11 @@ export class Wallet { if (c.suspended) { return balance; } - if (!(c.status == CoinStatus.Dirty || c.status == CoinStatus.Fresh)) { + if (!(c.status === CoinStatus.Dirty || c.status === CoinStatus.Fresh)) { return balance; } - let currency = c.currentAmount.currency; - let entry = ensureEntry(balance, currency); + const currency = c.currentAmount.currency; + const entry = ensureEntry(balance, currency); entry.available = Amounts.add(entry.available, c.currentAmount).amount; return balance; } @@ -1669,7 +1684,7 @@ export class Wallet { if (!r.confirmed) { return balance; } - let entry = ensureEntry(balance, r.requested_amount.currency); + const entry = ensureEntry(balance, r.requested_amount.currency); let amount = r.current_amount; if (!amount) { amount = r.requested_amount; @@ -1686,7 +1701,7 @@ export class Wallet { if (!r.hasPayback) { return balance; } - let entry = ensureEntry(balance, r.requested_amount.currency); + const entry = ensureEntry(balance, r.requested_amount.currency); if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], r.current_amount!) < 0) { entry.paybackAmount = Amounts.add(entry.paybackAmount, r.current_amount!).amount; } @@ -1700,7 +1715,7 @@ export class Wallet { if (r.finished) { return balance; } - let entry = ensureEntry(balance, r.valueWithFee.currency); + const entry = ensureEntry(balance, r.valueWithFee.currency); entry.pendingIncoming = Amounts.add(entry.pendingIncoming, r.valueOutput).amount; @@ -1711,7 +1726,7 @@ export class Wallet { if (t.finished) { return balance; } - let entry = ensureEntry(balance, t.contract.amount.currency); + const entry = ensureEntry(balance, t.contract.amount.currency); entry.pendingPayment = Amounts.add(entry.pendingPayment, t.contract.amount).amount; @@ -1721,7 +1736,7 @@ export class Wallet { function collectSmallestWithdraw(e: JoinResult<ExchangeRecord, DenominationRecord>, sw: any) { let min = sw[e.left.baseUrl]; - let v = Amounts.add(e.right.value, e.right.feeWithdraw).amount; + const v = Amounts.add(e.right.value, e.right.feeWithdraw).amount; if (!min) { min = v; } else if (Amounts.cmp(v, min) < 0) { @@ -1731,7 +1746,7 @@ export class Wallet { return sw; } - let balance = {}; + const balance = {}; // Mapping from exchange pub to smallest // possible amount we can withdraw let smallestWithdraw: {[baseUrl: string]: AmountJson} = {}; @@ -1742,7 +1757,7 @@ export class Wallet { (x) => x.baseUrl) .reduce(collectSmallestWithdraw, {})); - let tx = this.q(); + const tx = this.q(); tx.iter(Stores.coins) .reduce(collectBalances, balance); tx.iter(Stores.refresh) @@ -1760,52 +1775,52 @@ export class Wallet { async createRefreshSession(oldCoinPub: string): Promise<RefreshSessionRecord|undefined> { - let coin = await this.q().get<CoinRecord>(Stores.coins, oldCoinPub); + const coin = await this.q().get<CoinRecord>(Stores.coins, oldCoinPub); if (!coin) { throw Error("coin not found"); } - if (coin.currentAmount.value == 0 && coin.currentAmount.fraction == 0) { + if (coin.currentAmount.value === 0 && coin.currentAmount.fraction === 0) { return undefined; } - let exchange = await this.updateExchangeFromUrl(coin.exchangeBaseUrl); + const exchange = await this.updateExchangeFromUrl(coin.exchangeBaseUrl); if (!exchange) { throw Error("db inconsistent"); } - let oldDenom = await this.q().get(Stores.denominations, + const oldDenom = await this.q().get(Stores.denominations, [exchange.baseUrl, coin.denomPub]); if (!oldDenom) { throw Error("db inconsistent"); } - let availableDenoms: DenominationRecord[] = await ( + const availableDenoms: DenominationRecord[] = await ( this.q() .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl) .toArray() ); - let availableAmount = Amounts.sub(coin.currentAmount, + const availableAmount = Amounts.sub(coin.currentAmount, oldDenom.feeRefresh).amount; - let newCoinDenoms = getWithdrawDenomList(availableAmount, + const newCoinDenoms = getWithdrawDenomList(availableAmount, availableDenoms); console.log("refreshing coin", coin); console.log("refreshing into", newCoinDenoms); - if (newCoinDenoms.length == 0) { + if (newCoinDenoms.length === 0) { console.log(`not refreshing, available amount ${amountToPretty(availableAmount)} too small`); return undefined; } - let refreshSession: RefreshSessionRecord = await ( + const refreshSession: RefreshSessionRecord = await ( this.cryptoApi.createRefreshSession(exchange.baseUrl, 3, coin, @@ -1813,7 +1828,7 @@ export class Wallet { oldDenom.feeRefresh)); function mutateCoin(c: CoinRecord): CoinRecord { - let r = Amounts.sub(c.currentAmount, + const r = Amounts.sub(c.currentAmount, refreshSession.valueWithFee); if (r.saturated) { // Something else must have written the coin value @@ -1837,7 +1852,7 @@ export class Wallet { async refresh(oldCoinPub: string): Promise<void> { let refreshSession: RefreshSessionRecord|undefined; - let oldSession = await this.q().get(Stores.refresh, oldCoinPub); + const oldSession = await this.q().get(Stores.refresh, oldCoinPub); if (oldSession) { console.log("got old session for", oldCoinPub); console.log(oldSession); @@ -1858,9 +1873,9 @@ export class Wallet { return; } if (typeof refreshSession.norevealIndex !== "number") { - let coinPub = refreshSession.meltCoinPub; + const coinPub = refreshSession.meltCoinPub; await this.refreshMelt(refreshSession); - let r = await this.q().get<RefreshSessionRecord>(Stores.refresh, coinPub); + const r = await this.q().get<RefreshSessionRecord>(Stores.refresh, coinPub); if (!r) { throw Error("refresh session does not exist anymore"); } @@ -1872,53 +1887,53 @@ export class Wallet { async refreshMelt(refreshSession: RefreshSessionRecord): Promise<void> { - if (refreshSession.norevealIndex != undefined) { + if (refreshSession.norevealIndex !== undefined) { console.error("won't melt again"); return; } - let coin = await this.q().get<CoinRecord>(Stores.coins, + const coin = await this.q().get<CoinRecord>(Stores.coins, refreshSession.meltCoinPub); if (!coin) { console.error("can't melt coin, it does not exist"); return; } - let reqUrl = new URI("refresh/melt").absoluteTo(refreshSession.exchangeBaseUrl); - let meltCoin = { + const reqUrl = new URI("refresh/melt").absoluteTo(refreshSession.exchangeBaseUrl); + const meltCoin = { coin_pub: coin.coinPub, + confirm_sig: refreshSession.confirmSig, denom_pub: coin.denomPub, denom_sig: coin.denomSig, - confirm_sig: refreshSession.confirmSig, value_with_fee: refreshSession.valueWithFee, }; - let coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv)); - let req = { - "new_denoms": refreshSession.newDenoms, - "melt_coin": meltCoin, - "transfer_pubs": refreshSession.transferPubs, - "coin_evs": coinEvs, + const coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv)); + const req = { + coin_evs: coinEvs, + melt_coin: meltCoin, + new_denoms: refreshSession.newDenoms, + transfer_pubs: refreshSession.transferPubs, }; console.log("melt request:", req); - let resp = await this.http.postJson(reqUrl.href(), req); + const resp = await this.http.postJson(reqUrl.href(), req); console.log("melt request:", req); console.log("melt response:", resp.responseText); - if (resp.status != 200) { + if (resp.status !== 200) { console.error(resp.responseText); throw Error("refresh failed"); } - let respJson = JSON.parse(resp.responseText); + const respJson = JSON.parse(resp.responseText); if (!respJson) { throw Error("exchange responded with garbage"); } - let norevealIndex = respJson.noreveal_index; + const norevealIndex = respJson.noreveal_index; - if (typeof norevealIndex != "number") { + if (typeof norevealIndex !== "number") { throw Error("invalid response"); } @@ -1929,71 +1944,70 @@ export class Wallet { async refreshReveal(refreshSession: RefreshSessionRecord): Promise<void> { - let norevealIndex = refreshSession.norevealIndex; - if (norevealIndex == undefined) { + const norevealIndex = refreshSession.norevealIndex; + if (norevealIndex === undefined) { throw Error("can't reveal without melting first"); } - let privs = Array.from(refreshSession.transferPrivs); + const privs = Array.from(refreshSession.transferPrivs); privs.splice(norevealIndex, 1); - let req = { - "session_hash": refreshSession.hash, - "transfer_privs": privs, + const req = { + session_hash: refreshSession.hash, + transfer_privs: privs, }; - let reqUrl = new URI("refresh/reveal") - .absoluteTo(refreshSession.exchangeBaseUrl); + const reqUrl = new URI("refresh/reveal") .absoluteTo(refreshSession.exchangeBaseUrl); console.log("reveal request:", req); - let resp = await this.http.postJson(reqUrl.href(), req); + const resp = await this.http.postJson(reqUrl.href(), req); console.log("session:", refreshSession); console.log("reveal response:", resp); - if (resp.status != 200) { + if (resp.status !== 200) { console.log("error: /refresh/reveal returned status " + resp.status); return; } - let respJson = JSON.parse(resp.responseText); + const respJson = JSON.parse(resp.responseText); if (!respJson.ev_sigs || !Array.isArray(respJson.ev_sigs)) { console.log("/refresh/reveal did not contain ev_sigs"); } - let exchange = await this.q().get<ExchangeRecord>(Stores.exchanges, + const exchange = await this.q().get<ExchangeRecord>(Stores.exchanges, refreshSession.exchangeBaseUrl); if (!exchange) { console.error(`exchange ${refreshSession.exchangeBaseUrl} not found`); return; } - let coins: CoinRecord[] = []; + const coins: CoinRecord[] = []; for (let i = 0; i < respJson.ev_sigs.length; i++) { - let denom = await ( + const denom = await ( this.q() .get(Stores.denominations, [ refreshSession.exchangeBaseUrl, - refreshSession.newDenoms[i] + refreshSession.newDenoms[i], ])); if (!denom) { console.error("denom not found"); continue; } - let pc = refreshSession.preCoinsForGammas[refreshSession.norevealIndex!][i]; - let denomSig = await this.cryptoApi.rsaUnblind(respJson.ev_sigs[i].ev_sig, + const pc = refreshSession.preCoinsForGammas[refreshSession.norevealIndex!][i]; + const denomSig = await this.cryptoApi.rsaUnblind(respJson.ev_sigs[i].ev_sig, pc.blindingKey, denom.denomPub); - let coin: CoinRecord = { - reservePub: undefined, + const coin: CoinRecord = { blindingKey: pc.blindingKey, - coinPub: pc.publicKey, coinPriv: pc.privateKey, - denomPub: denom.denomPub, - denomSig: denomSig, + coinPub: pc.publicKey, currentAmount: denom.value, + denomPub: denom.denomPub, + denomSig, exchangeBaseUrl: refreshSession.exchangeBaseUrl, + reservePub: undefined, status: CoinStatus.Fresh, }; @@ -2018,7 +2032,7 @@ export class Wallet { return acc; } - let history = await ( + const history = await ( this.q() .iterIndex(Stores.history.timestampIndex) .reduce(collect, [])); @@ -2027,12 +2041,12 @@ export class Wallet { } async getDenoms(exchangeUrl: string): Promise<DenominationRecord[]> { - let denoms = await this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeUrl).toArray(); + const denoms = await this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeUrl).toArray(); return denoms; } async getOffer(offerId: number): Promise<any> { - let offer = await this.q() .get(Stores.offers, offerId); + const offer = await this.q() .get(Stores.offers, offerId); return offer; } @@ -2089,7 +2103,7 @@ export class Wallet { * Store the private key in our DB, so we can prove ownership. */ async generateNonce(): Promise<string> { - let {priv, pub} = await this.cryptoApi.createEddsaKeypair(); + const {priv, pub} = await this.cryptoApi.createEddsaKeypair(); await this.q() .put(Stores.nonces, {priv, pub}) .finish(); @@ -2103,23 +2117,23 @@ export class Wallet { async paymentSucceeded(contractHash: string, merchantSig: string): Promise<any> { const doPaymentSucceeded = async() => { - let t = await this.q().get<TransactionRecord>(Stores.transactions, + const t = await this.q().get<TransactionRecord>(Stores.transactions, contractHash); if (!t) { console.error("contract not found"); return; } - let merchantPub = t.contract.merchant_pub; - let valid = this.cryptoApi.isValidPaymentSignature(merchantSig, contractHash, merchantPub); + const merchantPub = t.contract.merchant_pub; + const valid = this.cryptoApi.isValidPaymentSignature(merchantSig, contractHash, merchantPub); if (!valid) { console.error("merchant payment signature invalid"); // FIXME: properly display error return; } t.finished = true; - let modifiedCoins: CoinRecord[] = []; - for (let pc of t.payReq.coins) { - let c = await this.q().get<CoinRecord>(Stores.coins, pc.coin_pub); + const modifiedCoins: CoinRecord[] = []; + for (const pc of t.payReq.coins) { + const c = await this.q().get<CoinRecord>(Stores.coins, pc.coin_pub); if (!c) { console.error("coin not found"); return; @@ -2132,7 +2146,7 @@ export class Wallet { .putAll(Stores.coins, modifiedCoins) .put(Stores.transactions, t) .finish(); - for (let c of t.payReq.coins) { + for (const c of t.payReq.coins) { this.refresh(c.coin_pub); } }; @@ -2145,11 +2159,11 @@ export class Wallet { if (!coin) { throw Error(`Coin ${coinPub} not found, can't request payback`); } - let reservePub = coin.reservePub; + const reservePub = coin.reservePub; if (!reservePub) { throw Error(`Can't request payback for a refreshed coin`); } - let reserve = await this.q().get(Stores.reserves, reservePub); + const reserve = await this.q().get(Stores.reserves, reservePub); if (!reserve) { throw Error(`Reserve of coin ${coinPub} not found`); } @@ -2167,14 +2181,14 @@ export class Wallet { reserve.hasPayback = true; await this.q().put(Stores.coins, coin).put(Stores.reserves, reserve); - let paybackRequest = await this.cryptoApi.createPaybackRequest(coin); - let reqUrl = new URI("payback").absoluteTo(coin.exchangeBaseUrl); - let resp = await this.http.postJson(reqUrl.href(), paybackRequest); - if (resp.status != 200) { + const paybackRequest = await this.cryptoApi.createPaybackRequest(coin); + const reqUrl = new URI("payback").absoluteTo(coin.exchangeBaseUrl); + const resp = await this.http.postJson(reqUrl.href(), paybackRequest); + if (resp.status !== 200) { throw Error(); } - let paybackConfirmation = PaybackConfirmation.checked(JSON.parse(resp.responseText)); - if (paybackConfirmation.reserve_pub != coin.reservePub) { + const paybackConfirmation = PaybackConfirmation.checked(JSON.parse(resp.responseText)); + if (paybackConfirmation.reserve_pub !== coin.reservePub) { throw Error(`Coin's reserve doesn't match reserve on payback`); } coin = await this.q().get(Stores.coins, coinPub); @@ -2188,29 +2202,29 @@ export class Wallet { async denominationRecordFromKeys(exchangeBaseUrl: string, denomIn: Denomination): Promise<DenominationRecord> { - let denomPubHash = await this.cryptoApi.hashDenomPub(denomIn.denom_pub); - let d: DenominationRecord = { - denomPubHash, + const denomPubHash = await this.cryptoApi.hashDenomPub(denomIn.denom_pub); + const d: DenominationRecord = { denomPub: denomIn.denom_pub, - exchangeBaseUrl: exchangeBaseUrl, + denomPubHash, + exchangeBaseUrl, feeDeposit: denomIn.fee_deposit, - masterSig: denomIn.master_sig, - feeRefund: denomIn.fee_refund, feeRefresh: denomIn.fee_refresh, + feeRefund: denomIn.fee_refund, feeWithdraw: denomIn.fee_withdraw, + isOffered: true, + masterSig: denomIn.master_sig, stampExpireDeposit: denomIn.stamp_expire_deposit, stampExpireLegal: denomIn.stamp_expire_legal, stampExpireWithdraw: denomIn.stamp_expire_withdraw, stampStart: denomIn.stamp_start, status: DenominationStatus.Unverified, - isOffered: true, value: denomIn.value, }; return d; } async withdrawPaybackReserve(reservePub: string): Promise<void> { - let reserve = await this.q().get(Stores.reserves, reservePub); + const reserve = await this.q().get(Stores.reserves, reservePub); if (!reserve) { throw Error(`Reserve ${reservePub} does not exist`); } @@ -2220,7 +2234,7 @@ export class Wallet { } async getPaybackReserves(): Promise<ReserveRecord[]> { - return await this.q().iter(Stores.reserves).filter(r => r.hasPayback).toArray() + return await this.q().iter(Stores.reserves).filter((r) => r.hasPayback).toArray(); } } diff --git a/src/wxApi.ts b/src/wxApi.ts index b74cb9eb1..1f7d08fb3 100644 --- a/src/wxApi.ts +++ b/src/wxApi.ts @@ -17,11 +17,12 @@ import { AmountJson, CoinRecord, + CurrencyRecord, + DenominationRecord, + ExchangeRecord, PreCoinRecord, ReserveCreationInfo, - ExchangeRecord, - CurrencyRecord, - ReserveRecord, DenominationRecord + ReserveRecord, } from "./types"; /** @@ -31,13 +32,13 @@ import { export function getReserveCreationInfo(baseUrl: string, - amount: AmountJson): Promise<ReserveCreationInfo> { - let m = { type: "reserve-creation-info", detail: { baseUrl, amount } }; + amount: AmountJson): Promise<ReserveCreationInfo> { + const m = { type: "reserve-creation-info", detail: { baseUrl, amount } }; return new Promise<ReserveCreationInfo>((resolve, reject) => { chrome.runtime.sendMessage(m, (resp) => { if (resp.error) { console.error("error response", resp); - let e = Error("call to reserve-creation-info failed"); + const e = Error("call to reserve-creation-info failed"); (e as any).errorResponse = resp; reject(e); return; diff --git a/src/wxBackend.ts b/src/wxBackend.ts index 26020c7fe..6b9601572 100644 --- a/src/wxBackend.ts +++ b/src/wxBackend.ts @@ -24,6 +24,20 @@ /** * Imports. */ +import { Checkable } from "./checkable"; +import { ChromeBadge } from "./chromeBadge"; +import { BrowserHttpLib } from "./http"; +import * as logging from "./logging"; +import { + Index, + Store, +} from "./query"; +import { + AmountJson, + Contract, + Notifier, +} from "./types"; +import URI = require("urijs"); import { Badge, ConfirmReserveRequest, @@ -32,19 +46,8 @@ import { Stores, Wallet, } from "./wallet"; -import { - AmountJson, - Contract, - Notifier, -} from "./types"; -import MessageSender = chrome.runtime.MessageSender; -import { ChromeBadge } from "./chromeBadge"; -import * as logging from "./logging"; -import { Store, Index } from "./query"; -import { BrowserHttpLib } from "./http"; -import { Checkable } from "./checkable"; import Port = chrome.runtime.Port; -import URI = require("urijs"); +import MessageSender = chrome.runtime.MessageSender; const DB_NAME = "taler"; @@ -60,32 +63,33 @@ const DB_VERSION = 17; type Handler = (detail: any, sender: MessageSender) => Promise<any>; function makeHandlers(db: IDBDatabase, - wallet: Wallet): { [msg: string]: Handler } { + wallet: Wallet): { [msg: string]: Handler } { return { - ["balances"]: function (detail, sender) { + ["balances"]: (detail, sender) => { return wallet.getBalances(); }, - ["dump-db"]: function (detail, sender) { + ["dump-db"]: (detail, sender) => { return exportDb(db); }, - ["import-db"]: function (detail, sender) { + ["import-db"]: (detail, sender) => { return importDb(db, detail.dump); }, - ["get-tab-cookie"]: function (detail, sender) { + ["get-tab-cookie"]: (detail, sender) => { if (!sender || !sender.tab || !sender.tab.id) { return Promise.resolve(); } - let id: number = sender.tab.id; - let info: any = <any> paymentRequestCookies[id]; + const id: number = sender.tab.id; + const info: any = paymentRequestCookies[id] as any; delete paymentRequestCookies[id]; return Promise.resolve(info); }, - ["ping"]: function (detail, sender) { + ["ping"]: (detail, sender) => { return Promise.resolve(); }, - ["reset"]: function (detail, sender) { + ["reset"]: (detail, sender) => { if (db) { - let tx = db.transaction(Array.from(db.objectStoreNames), 'readwrite'); + const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite"); + // tslint:disable-next-line:prefer-for-of for (let i = 0; i < db.objectStoreNames.length; i++) { tx.objectStore(db.objectStoreNames[i]).clear(); } @@ -97,15 +101,15 @@ function makeHandlers(db: IDBDatabase, // Response is synchronous return Promise.resolve({}); }, - ["create-reserve"]: function (detail, sender) { + ["create-reserve"]: (detail, sender) => { const d = { - exchange: detail.exchange, amount: detail.amount, + exchange: detail.exchange, }; const req = CreateReserveRequest.checked(d); return wallet.createReserve(req); }, - ["confirm-reserve"]: function (detail, sender) { + ["confirm-reserve"]: (detail, sender) => { // TODO: make it a checkable const d = { reservePub: detail.reservePub, @@ -113,10 +117,10 @@ function makeHandlers(db: IDBDatabase, const req = ConfirmReserveRequest.checked(d); return wallet.confirmReserve(req); }, - ["generate-nonce"]: function (detail, sender) { + ["generate-nonce"]: (detail, sender) => { return wallet.generateNonce(); }, - ["confirm-pay"]: function (detail, sender) { + ["confirm-pay"]: (detail, sender) => { let offer: OfferRecord; try { offer = OfferRecord.checked(detail.offer); @@ -124,9 +128,9 @@ function makeHandlers(db: IDBDatabase, if (e instanceof Checkable.SchemaError) { console.error("schema error:", e.message); return Promise.resolve({ + detail, error: "invalid contract", hint: e.message, - detail: detail, }); } else { throw e; @@ -135,7 +139,7 @@ function makeHandlers(db: IDBDatabase, return wallet.confirmPay(offer); }, - ["check-pay"]: function (detail, sender) { + ["check-pay"]: (detail, sender) => { let offer: OfferRecord; try { offer = OfferRecord.checked(detail.offer); @@ -143,9 +147,9 @@ function makeHandlers(db: IDBDatabase, if (e instanceof Checkable.SchemaError) { console.error("schema error:", e.message); return Promise.resolve({ + detail, error: "invalid contract", hint: e.message, - detail: detail, }); } else { throw e; @@ -153,34 +157,34 @@ function makeHandlers(db: IDBDatabase, } return wallet.checkPay(offer); }, - ["query-payment"]: function (detail: any, sender: MessageSender) { + ["query-payment"]: (detail: any, sender: MessageSender) => { if (sender.tab && sender.tab.id) { rateLimitCache[sender.tab.id]++; if (rateLimitCache[sender.tab.id] > 10) { console.warn("rate limit for query-payment exceeded"); - let msg = { + const msg = { error: "rate limit exceeded for query-payment", - rateLimitExceeded: true, hint: "Check for redirect loops", + rateLimitExceeded: true, }; return Promise.resolve(msg); } } return wallet.queryPayment(detail.url); }, - ["exchange-info"]: function (detail) { + ["exchange-info"]: (detail) => { if (!detail.baseUrl) { return Promise.resolve({ error: "bad url" }); } return wallet.updateExchangeFromUrl(detail.baseUrl); }, - ["currency-info"]: function (detail) { + ["currency-info"]: (detail) => { if (!detail.name) { return Promise.resolve({ error: "name missing" }); } return wallet.getCurrencyRecord(detail.name); }, - ["hash-contract"]: function (detail) { + ["hash-contract"]: (detail) => { if (!detail.contract) { return Promise.resolve({ error: "contract missing" }); } @@ -188,91 +192,91 @@ function makeHandlers(db: IDBDatabase, return { hash }; }); }, - ["put-history-entry"]: function (detail: any) { + ["put-history-entry"]: (detail: any) => { if (!detail.historyEntry) { return Promise.resolve({ error: "historyEntry missing" }); } return wallet.putHistory(detail.historyEntry); }, - ["save-offer"]: function (detail: any) { - let offer = detail.offer; + ["save-offer"]: (detail: any) => { + const offer = detail.offer; if (!offer) { return Promise.resolve({ error: "offer missing" }); } console.log("handling safe-offer", detail); // FIXME: fully migrate to new terminology - let checkedOffer = OfferRecord.checked(offer); + const checkedOffer = OfferRecord.checked(offer); return wallet.saveOffer(checkedOffer); }, - ["reserve-creation-info"]: function (detail, sender) { + ["reserve-creation-info"]: (detail, sender) => { if (!detail.baseUrl || typeof detail.baseUrl !== "string") { return Promise.resolve({ error: "bad url" }); } - let amount = AmountJson.checked(detail.amount); + const amount = AmountJson.checked(detail.amount); return wallet.getReserveCreationInfo(detail.baseUrl, amount); }, - ["get-history"]: function (detail, sender) { + ["get-history"]: (detail, sender) => { // TODO: limit history length return wallet.getHistory(); }, - ["get-offer"]: function (detail, sender) { + ["get-offer"]: (detail, sender) => { return wallet.getOffer(detail.offerId); }, - ["get-exchanges"]: function (detail, sender) { + ["get-exchanges"]: (detail, sender) => { return wallet.getExchanges(); }, - ["get-currencies"]: function (detail, sender) { + ["get-currencies"]: (detail, sender) => { return wallet.getCurrencies(); }, - ["update-currency"]: function (detail, sender) { + ["update-currency"]: (detail, sender) => { return wallet.updateCurrency(detail.currencyRecord); }, - ["get-reserves"]: function (detail, sender) { + ["get-reserves"]: (detail, sender) => { if (typeof detail.exchangeBaseUrl !== "string") { return Promise.reject(Error("exchangeBaseUrl missing")); } return wallet.getReserves(detail.exchangeBaseUrl); }, - ["get-payback-reserves"]: function (detail, sender) { + ["get-payback-reserves"]: (detail, sender) => { return wallet.getPaybackReserves(); }, - ["withdraw-payback-reserve"]: function (detail, sender) { + ["withdraw-payback-reserve"]: (detail, sender) => { if (typeof detail.reservePub !== "string") { return Promise.reject(Error("reservePub missing")); } return wallet.withdrawPaybackReserve(detail.reservePub); }, - ["get-coins"]: function (detail, sender) { + ["get-coins"]: (detail, sender) => { if (typeof detail.exchangeBaseUrl !== "string") { return Promise.reject(Error("exchangBaseUrl missing")); } return wallet.getCoins(detail.exchangeBaseUrl); }, - ["get-precoins"]: function (detail, sender) { + ["get-precoins"]: (detail, sender) => { if (typeof detail.exchangeBaseUrl !== "string") { return Promise.reject(Error("exchangBaseUrl missing")); } return wallet.getPreCoins(detail.exchangeBaseUrl); }, - ["get-denoms"]: function (detail, sender) { + ["get-denoms"]: (detail, sender) => { if (typeof detail.exchangeBaseUrl !== "string") { return Promise.reject(Error("exchangBaseUrl missing")); } return wallet.getDenoms(detail.exchangeBaseUrl); }, - ["refresh-coin"]: function (detail, sender) { + ["refresh-coin"]: (detail, sender) => { if (typeof detail.coinPub !== "string") { return Promise.reject(Error("coinPub missing")); } return wallet.refresh(detail.coinPub); }, - ["payback-coin"]: function (detail, sender) { + ["payback-coin"]: (detail, sender) => { if (typeof detail.coinPub !== "string") { return Promise.reject(Error("coinPub missing")); } return wallet.payback(detail.coinPub); }, - ["payment-failed"]: function (detail, sender) { + ["payment-failed"]: (detail, sender) => { // For now we just update exchanges (maybe the exchange did something // wrong and the keys were messed up). // FIXME: in the future we should look at what actually went wrong. @@ -280,9 +284,9 @@ function makeHandlers(db: IDBDatabase, wallet.updateExchanges(); return Promise.resolve(); }, - ["payment-succeeded"]: function (detail, sender) { - let contractHash = detail.contractHash; - let merchantSig = detail.merchantSig; + ["payment-succeeded"]: (detail, sender) => { + const contractHash = detail.contractHash; + const merchantSig = detail.merchantSig; if (!contractHash) { return Promise.reject(Error("contractHash missing")); } @@ -307,7 +311,7 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any) try { const p = handlers[req.type](req.detail, sender); - let r = await p; + const r = await p; try { sendResponse(r); } catch (e) { @@ -317,7 +321,7 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any) console.log(`exception during wallet handler for '${req.type}'`); console.log("request", req); console.error(e); - let stack = undefined; + let stack; try { stack = e.stack.toString(); } catch (e) { @@ -325,9 +329,9 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any) } try { sendResponse({ - stack, error: "exception", hint: e.message, + stack, }); } catch (e) { console.log(e); @@ -344,7 +348,7 @@ class ChromeNotifier implements Notifier { console.log("got connect!"); this.ports.push(port); port.onDisconnect.addListener(() => { - let i = this.ports.indexOf(port); + const i = this.ports.indexOf(port); if (i >= 0) { this.ports.splice(i, 1); } else { @@ -355,7 +359,7 @@ class ChromeNotifier implements Notifier { } notify() { - for (let p of this.ports) { + for (const p of this.ports) { p.postMessage({ notify: true }); } } @@ -365,7 +369,7 @@ class ChromeNotifier implements Notifier { /** * Mapping from tab ID to payment information (if any). */ -let paymentRequestCookies: { [n: number]: any } = {}; +const paymentRequestCookies: { [n: number]: any } = {}; /** @@ -376,19 +380,19 @@ let paymentRequestCookies: { [n: number]: any } = {}; */ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: string, tabId: number): any { const headers: { [s: string]: string } = {}; - for (let kv of headerList) { + for (const kv of headerList) { if (kv.value) { headers[kv.name.toLowerCase()] = kv.value; } } - let fields = { - contract_url: headers["x-taler-contract-url"], + const fields = { contract_query: headers["x-taler-contract-query"], + contract_url: headers["x-taler-contract-url"], offer_url: headers["x-taler-offer-url"], - } + }; - let talerHeaderFound = Object.keys(fields).filter((x: any) => (fields as any)[x]).length !== 0; + const talerHeaderFound = Object.keys(fields).filter((x: any) => (fields as any)[x]).length !== 0; if (!talerHeaderFound) { // looks like it's not a taler request, it might be @@ -397,27 +401,26 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri return; } - let payDetail = { + const payDetail = { contract_url: fields.contract_url, offer_url: fields.offer_url, }; - console.log("got pay detail", payDetail) + console.log("got pay detail", payDetail); // This cookie will be read by the injected content script // in the tab that displays the page. paymentRequestCookies[tabId] = { - type: "pay", payDetail, + type: "pay", }; } - function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHeader[], - url: string, tabId: number): any { + url: string, tabId: number): any { const headers: { [s: string]: string } = {}; - for (let kv of headerList) { + for (const kv of headerList) { if (kv.value) { headers[kv.name.toLowerCase()] = kv.value; } @@ -432,7 +435,7 @@ function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHea const amount = headers["x-taler-amount"]; if (amount) { - let callbackUrl = headers["x-taler-callback-url"]; + const callbackUrl = headers["x-taler-callback-url"]; if (!callbackUrl) { console.log("202 not understood (X-Taler-Callback-Url missing)"); return; @@ -441,30 +444,29 @@ function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHea try { amountParsed = JSON.parse(amount); } catch (e) { - let uri = new URI(chrome.extension.getURL("/src/pages/error.html")); - let p = { + const uri = new URI(chrome.extension.getURL("/src/pages/error.html")); + const p = { message: `Can't parse amount ("${amount}"): ${e.message}`, }; - let redirectUrl = uri.query(p).href(); + const redirectUrl = uri.query(p).href(); // FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed chrome.tabs.update(tabId, {url: redirectUrl}); return; } - let wtTypes = headers["x-taler-wt-types"]; + const wtTypes = headers["x-taler-wt-types"]; if (!wtTypes) { console.log("202 not understood (X-Taler-Wt-Types missing)"); return; } - let params = { - amount: amount, - callback_url: new URI(callbackUrl) - .absoluteTo(url), + const params = { + amount, bank_url: url, - wt_types: wtTypes, + callback_url: new URI(callbackUrl) .absoluteTo(url), suggested_exchange_url: headers["x-taler-suggested-exchange"], + wt_types: wtTypes, }; - let uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html")); - let redirectUrl = uri.query(params).href(); + const uri = new URI(chrome.extension.getURL("/src/pages/confirm-create-reserve.html")); + const redirectUrl = uri.query(params).href(); console.log("redirecting to", redirectUrl); // FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed chrome.tabs.update(tabId, {url: redirectUrl}); @@ -484,21 +486,21 @@ function clearRateLimitCache() { export async function wxMain() { window.onerror = (m, source, lineno, colno, error) => { logging.record("error", m + error, undefined, source || "(unknown)", lineno || 0, colno || 0); - } + }; chrome.browserAction.setBadgeText({ text: "" }); const badge = new ChromeBadge(); - chrome.tabs.query({}, function (tabs) { - for (let tab of tabs) { + chrome.tabs.query({}, (tabs) => { + for (const tab of tabs) { if (!tab.url || !tab.id) { return; } - let uri = new URI(tab.url); + const uri = new URI(tab.url); if (uri.protocol() === "http" || uri.protocol() === "https") { console.log("injecting into existing tab", tab.id); chrome.tabs.executeScript(tab.id, { file: "/dist/contentScript-bundle.js" }); - let code = ` + const code = ` if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) { document.dispatchEvent(new Event("taler-probe-result")); } @@ -511,13 +513,13 @@ export async function wxMain() { const tabTimers: {[n: number]: number[]} = {}; chrome.tabs.onRemoved.addListener((tabId, changeInfo) => { - let tt = tabTimers[tabId] || []; - for (let t of tt) { + const tt = tabTimers[tabId] || []; + for (const t of tt) { chrome.extension.getBackgroundPage().clearTimeout(t); } }); chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { - if (changeInfo.status !== 'complete') { + if (changeInfo.status !== "complete") { return; } const timers: number[] = []; @@ -525,7 +527,7 @@ export async function wxMain() { const addRun = (dt: number) => { const id = chrome.extension.getBackgroundPage().setTimeout(run, dt); timers.push(id); - } + }; const run = () => { timers.shift(); @@ -536,11 +538,11 @@ export async function wxMain() { if (!tab.url || !tab.id) { return; } - let uri = new URI(tab.url); + const uri = new URI(tab.url); if (!(uri.protocol() === "http" || uri.protocol() === "https")) { return; } - let code = ` + const code = ` if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) { document.dispatchEvent(new Event("taler-probe-result")); } @@ -600,7 +602,6 @@ export async function wxMain() { } - /** * Return a promise that resolves * to the taler wallet db. @@ -620,13 +621,13 @@ function openTalerDb(): Promise<IDBDatabase> { switch (e.oldVersion) { case 0: // DB does not exist yet - for (let n in Stores) { + for (const n in Stores) { if ((Stores as any)[n] instanceof Store) { - let si: Store<any> = (Stores as any)[n]; + const si: Store<any> = (Stores as any)[n]; const s = db.createObjectStore(si.name, si.storeParams); - for (let indexName in (si as any)) { + for (const indexName in (si as any)) { if ((si as any)[indexName] instanceof Index) { - let ii: Index<any, any> = (si as any)[indexName]; + const ii: Index<any, any> = (si as any)[indexName]; s.createIndex(ii.indexName, ii.keyPath); } } @@ -649,31 +650,32 @@ function openTalerDb(): Promise<IDBDatabase> { function exportDb(db: IDBDatabase): Promise<any> { - let dump = { + const dump = { name: db.name, - version: db.version, stores: {} as {[s: string]: any}, + version: db.version, }; return new Promise((resolve, reject) => { - let tx = db.transaction(Array.from(db.objectStoreNames)); + const tx = db.transaction(Array.from(db.objectStoreNames)); tx.addEventListener("complete", () => { resolve(dump); }); + // tslint:disable-next-line:prefer-for-of for (let i = 0; i < db.objectStoreNames.length; i++) { - let name = db.objectStoreNames[i]; - let storeDump = {} as {[s: string]: any}; + const name = db.objectStoreNames[i]; + const storeDump = {} as {[s: string]: any}; dump.stores[name] = storeDump; - let store = tx.objectStore(name) - .openCursor() - .addEventListener("success", (e: Event) => { - let cursor = (e.target as any).result; - if (cursor) { - storeDump[cursor.key] = cursor.value; - cursor.continue(); - } - }); + tx.objectStore(name) + .openCursor() + .addEventListener("success", (e: Event) => { + const cursor = (e.target as any).result; + if (cursor) { + storeDump[cursor.key] = cursor.value; + cursor.continue(); + } + }); } }); } @@ -682,17 +684,20 @@ function exportDb(db: IDBDatabase): Promise<any> { function importDb(db: IDBDatabase, dump: any): Promise<void> { console.log("importing db", dump); return new Promise<void>((resolve, reject) => { - let tx = db.transaction(Array.from(db.objectStoreNames), "readwrite"); - for (let storeName in dump.stores) { - let objects = []; - for (let key in dump.stores[storeName]) { - objects.push(dump.stores[storeName][key]); - } - console.log(`importing ${objects.length} records into ${storeName}`); - let store = tx.objectStore(storeName); - let clearReq = store.clear(); - for (let obj of objects) { - store.put(obj); + const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite"); + if (dump.stores) { + for (const storeName in dump.stores) { + const objects = []; + const dumpStore = dump.stores[storeName]; + for (const key in dumpStore) { + objects.push(dumpStore[key]); + } + console.log(`importing ${objects.length} records into ${storeName}`); + const store = tx.objectStore(storeName); + const clearReq = store.clear(); + for (const obj of objects) { + store.put(obj); + } } } tx.addEventListener("complete", () => { |