aboutsummaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/RequestThrottler.ts128
-rw-r--r--src/util/amounts-test.ts140
-rw-r--r--src/util/amounts.ts383
-rw-r--r--src/util/assertUnreachable.ts19
-rw-r--r--src/util/asyncMemo.ts87
-rw-r--r--src/util/codec-test.ts78
-rw-r--r--src/util/codec.ts410
-rw-r--r--src/util/helpers-test.ts46
-rw-r--r--src/util/helpers.ts147
-rw-r--r--src/util/http.ts362
-rw-r--r--src/util/libtoolVersion-test.ts48
-rw-r--r--src/util/libtoolVersion.ts88
-rw-r--r--src/util/logging.ts87
-rw-r--r--src/util/payto-test.ts31
-rw-r--r--src/util/payto.ts69
-rw-r--r--src/util/promiseUtils.ts60
-rw-r--r--src/util/query.ts575
-rw-r--r--src/util/reserveHistoryUtil-test.ts285
-rw-r--r--src/util/reserveHistoryUtil.ts360
-rw-r--r--src/util/talerconfig.ts120
-rw-r--r--src/util/taleruri-test.ts193
-rw-r--r--src/util/taleruri.ts211
-rw-r--r--src/util/time.ts198
-rw-r--r--src/util/timer.ts160
-rw-r--r--src/util/wire.ts51
25 files changed, 0 insertions, 4336 deletions
diff --git a/src/util/RequestThrottler.ts b/src/util/RequestThrottler.ts
deleted file mode 100644
index d979fbfcf..000000000
--- a/src/util/RequestThrottler.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Implementation of token bucket throttling.
- */
-
-/**
- * Imports.
- */
-import { getTimestampNow, timestampDifference } from "../util/time";
-
-/**
- * Maximum request per second, per origin.
- */
-const MAX_PER_SECOND = 50;
-
-/**
- * Maximum request per minute, per origin.
- */
-const MAX_PER_MINUTE = 100;
-
-/**
- * Maximum request per hour, per origin.
- */
-const MAX_PER_HOUR = 1000;
-
-/**
- * Throttling state for one origin.
- */
-class OriginState {
- private tokensSecond: number = MAX_PER_SECOND;
- private tokensMinute: number = MAX_PER_MINUTE;
- private tokensHour: number = MAX_PER_HOUR;
- private lastUpdate = getTimestampNow();
-
- private refill(): void {
- const now = getTimestampNow();
- const d = timestampDifference(now, this.lastUpdate);
- if (d.d_ms === "forever") {
- throw Error("assertion failed");
- }
- const d_s = d.d_ms / 1000;
- this.tokensSecond = Math.min(
- MAX_PER_SECOND,
- this.tokensSecond + d_s / 1000,
- );
- this.tokensMinute = Math.min(
- MAX_PER_MINUTE,
- this.tokensMinute + (d_s / 1000) * 60,
- );
- this.tokensHour = Math.min(
- MAX_PER_HOUR,
- this.tokensHour + (d_s / 1000) * 60 * 60,
- );
- this.lastUpdate = now;
- }
-
- /**
- * Return true if the request for this origin should be throttled.
- * Otherwise, take a token out of the respective buckets.
- */
- applyThrottle(): boolean {
- this.refill();
- if (this.tokensSecond < 1) {
- console.log("request throttled (per second limit exceeded)");
- return true;
- }
- if (this.tokensMinute < 1) {
- console.log("request throttled (per minute limit exceeded)");
- return true;
- }
- if (this.tokensHour < 1) {
- console.log("request throttled (per hour limit exceeded)");
- return true;
- }
- this.tokensSecond--;
- this.tokensMinute--;
- this.tokensHour--;
- return false;
- }
-}
-
-/**
- * Request throttler, used as a "last layer of defense" when some
- * other part of the re-try logic is broken and we're sending too
- * many requests to the same exchange/bank/merchant.
- */
-export class RequestThrottler {
- private perOriginInfo: { [origin: string]: OriginState } = {};
-
- /**
- * Get the throttling state for an origin, or
- * initialize if no state is associated with the
- * origin yet.
- */
- private getState(origin: string): OriginState {
- const s = this.perOriginInfo[origin];
- if (s) {
- return s;
- }
- const ns = (this.perOriginInfo[origin] = new OriginState());
- return ns;
- }
-
- /**
- * Apply throttling to a request.
- *
- * @returns whether the request should be throttled.
- */
- applyThrottle(requestUrl: string): boolean {
- const origin = new URL(requestUrl).origin;
- return this.getState(origin).applyThrottle();
- }
-}
diff --git a/src/util/amounts-test.ts b/src/util/amounts-test.ts
deleted file mode 100644
index afd8caa51..000000000
--- a/src/util/amounts-test.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems S.A.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import test from "ava";
-
-import { Amounts, AmountJson } from "../util/amounts";
-
-const jAmt = (
- value: number,
- fraction: number,
- currency: string,
-): AmountJson => ({ value, fraction, currency });
-
-const sAmt = (s: string): AmountJson => Amounts.parseOrThrow(s);
-
-test("amount addition (simple)", (t) => {
- const a1 = jAmt(1, 0, "EUR");
- const a2 = jAmt(1, 0, "EUR");
- const a3 = jAmt(2, 0, "EUR");
- t.true(0 === Amounts.cmp(Amounts.add(a1, a2).amount, a3));
- t.pass();
-});
-
-test("amount addition (saturation)", (t) => {
- const a1 = jAmt(1, 0, "EUR");
- const res = Amounts.add(jAmt(Amounts.maxAmountValue, 0, "EUR"), a1);
- t.true(res.saturated);
- t.pass();
-});
-
-test("amount subtraction (simple)", (t) => {
- const a1 = jAmt(2, 5, "EUR");
- const a2 = jAmt(1, 0, "EUR");
- const a3 = jAmt(1, 5, "EUR");
- t.true(0 === Amounts.cmp(Amounts.sub(a1, a2).amount, a3));
- t.pass();
-});
-
-test("amount subtraction (saturation)", (t) => {
- const a1 = jAmt(0, 0, "EUR");
- const a2 = jAmt(1, 0, "EUR");
- let res = Amounts.sub(a1, a2);
- t.true(res.saturated);
- res = Amounts.sub(a1, a1);
- t.true(!res.saturated);
- t.pass();
-});
-
-test("amount comparison", (t) => {
- t.is(Amounts.cmp(jAmt(1, 0, "EUR"), jAmt(1, 0, "EUR")), 0);
- t.is(Amounts.cmp(jAmt(1, 1, "EUR"), jAmt(1, 0, "EUR")), 1);
- t.is(Amounts.cmp(jAmt(1, 1, "EUR"), jAmt(1, 2, "EUR")), -1);
- t.is(Amounts.cmp(jAmt(1, 0, "EUR"), jAmt(0, 0, "EUR")), 1);
- t.is(Amounts.cmp(jAmt(0, 0, "EUR"), jAmt(1, 0, "EUR")), -1);
- t.is(Amounts.cmp(jAmt(1, 0, "EUR"), jAmt(0, 100000000, "EUR")), 0);
- t.throws(() => Amounts.cmp(jAmt(1, 0, "FOO"), jAmt(1, 0, "BAR")));
- t.pass();
-});
-
-test("amount parsing", (t) => {
- t.is(
- Amounts.cmp(Amounts.parseOrThrow("TESTKUDOS:0"), jAmt(0, 0, "TESTKUDOS")),
- 0,
- );
- t.is(
- Amounts.cmp(Amounts.parseOrThrow("TESTKUDOS:10"), jAmt(10, 0, "TESTKUDOS")),
- 0,
- );
- t.is(
- Amounts.cmp(
- Amounts.parseOrThrow("TESTKUDOS:0.1"),
- jAmt(0, 10000000, "TESTKUDOS"),
- ),
- 0,
- );
- t.is(
- Amounts.cmp(
- Amounts.parseOrThrow("TESTKUDOS:0.00000001"),
- jAmt(0, 1, "TESTKUDOS"),
- ),
- 0,
- );
- t.is(
- Amounts.cmp(
- Amounts.parseOrThrow("TESTKUDOS:4503599627370496.99999999"),
- jAmt(4503599627370496, 99999999, "TESTKUDOS"),
- ),
- 0,
- );
- t.throws(() => Amounts.parseOrThrow("foo:"));
- t.throws(() => Amounts.parseOrThrow("1.0"));
- t.throws(() => Amounts.parseOrThrow("42"));
- t.throws(() => Amounts.parseOrThrow(":1.0"));
- t.throws(() => Amounts.parseOrThrow(":42"));
- t.throws(() => Amounts.parseOrThrow("EUR:.42"));
- t.throws(() => Amounts.parseOrThrow("EUR:42."));
- t.throws(() => Amounts.parseOrThrow("TESTKUDOS:4503599627370497.99999999"));
- t.is(
- Amounts.cmp(
- Amounts.parseOrThrow("TESTKUDOS:0.99999999"),
- jAmt(0, 99999999, "TESTKUDOS"),
- ),
- 0,
- );
- t.throws(() => Amounts.parseOrThrow("TESTKUDOS:0.999999991"));
- t.pass();
-});
-
-test("amount stringification", (t) => {
- t.is(Amounts.stringify(jAmt(0, 0, "TESTKUDOS")), "TESTKUDOS:0");
- t.is(Amounts.stringify(jAmt(4, 94000000, "TESTKUDOS")), "TESTKUDOS:4.94");
- t.is(Amounts.stringify(jAmt(0, 10000000, "TESTKUDOS")), "TESTKUDOS:0.1");
- t.is(Amounts.stringify(jAmt(0, 1, "TESTKUDOS")), "TESTKUDOS:0.00000001");
- t.is(Amounts.stringify(jAmt(5, 0, "TESTKUDOS")), "TESTKUDOS:5");
- // denormalized
- t.is(Amounts.stringify(jAmt(1, 100000000, "TESTKUDOS")), "TESTKUDOS:2");
- t.pass();
-});
-
-test("amount multiplication", (t) => {
- t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 0).amount), "EUR:0");
- t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 1).amount), "EUR:1.11");
- t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 2).amount), "EUR:2.22");
- t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 3).amount), "EUR:3.33");
- t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 4).amount), "EUR:4.44");
- t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 5).amount), "EUR:5.55");
-});
diff --git a/src/util/amounts.ts b/src/util/amounts.ts
deleted file mode 100644
index 1e7f192f4..000000000
--- a/src/util/amounts.ts
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 Taler Systems S.A.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Types and helper functions for dealing with Taler amounts.
- */
-
-/**
- * Imports.
- */
-import {
- makeCodecForObject,
- codecForString,
- codecForNumber,
- Codec,
-} from "./codec";
-
-/**
- * Number of fractional units that one value unit represents.
- */
-export const fractionalBase = 1e8;
-
-/**
- * How many digits behind the comma are required to represent the
- * fractional value in human readable decimal format? Must match
- * lg(fractionalBase)
- */
-export const fractionalLength = 8;
-
-/**
- * Maximum allowed value field of an amount.
- */
-export const maxAmountValue = 2 ** 52;
-
-/**
- * Non-negative financial amount. Fractional values are expressed as multiples
- * of 1e-8.
- */
-export interface AmountJson {
- /**
- * Value, must be an integer.
- */
- readonly value: number;
-
- /**
- * Fraction, must be an integer. Represent 1/1e8 of a unit.
- */
- readonly fraction: number;
-
- /**
- * Currency of the amount.
- */
- readonly currency: string;
-}
-
-export const codecForAmountJson = (): Codec<AmountJson> =>
- makeCodecForObject<AmountJson>()
- .property("currency", codecForString)
- .property("value", codecForNumber)
- .property("fraction", codecForNumber)
- .build("AmountJson");
-
-/**
- * Result of a possibly overflowing operation.
- */
-export interface Result {
- /**
- * Resulting, possibly saturated amount.
- */
- amount: AmountJson;
- /**
- * Was there an over-/underflow?
- */
- saturated: boolean;
-}
-
-/**
- * Get an amount that represents zero units of a currency.
- */
-export function getZero(currency: string): AmountJson {
- return {
- currency,
- fraction: 0,
- value: 0,
- };
-}
-
-export function sum(amounts: AmountJson[]): Result {
- if (amounts.length <= 0) {
- throw Error("can't sum zero amounts");
- }
- return add(amounts[0], ...amounts.slice(1));
-}
-
-/**
- * Add two amounts. Return the result and whether
- * the addition overflowed. The overflow is always handled
- * by saturating and never by wrapping.
- *
- * Throws when currencies don't match.
- */
-export function add(first: AmountJson, ...rest: AmountJson[]): Result {
- const currency = first.currency;
- let value = first.value + Math.floor(first.fraction / fractionalBase);
- if (value > maxAmountValue) {
- return {
- amount: { currency, value: maxAmountValue, fraction: fractionalBase - 1 },
- saturated: true,
- };
- }
- let fraction = first.fraction % fractionalBase;
- for (const x of rest) {
- if (x.currency !== currency) {
- throw Error(`Mismatched currency: ${x.currency} and ${currency}`);
- }
-
- value =
- value + x.value + Math.floor((fraction + x.fraction) / fractionalBase);
- fraction = Math.floor((fraction + x.fraction) % fractionalBase);
- if (value > maxAmountValue) {
- return {
- amount: {
- currency,
- value: maxAmountValue,
- fraction: fractionalBase - 1,
- },
- saturated: true,
- };
- }
- }
- return { amount: { currency, value, fraction }, saturated: false };
-}
-
-/**
- * Subtract two amounts. Return the result and whether
- * the subtraction overflowed. The overflow is always handled
- * by saturating and never by wrapping.
- *
- * Throws when currencies don't match.
- */
-export function sub(a: AmountJson, ...rest: AmountJson[]): Result {
- const currency = a.currency;
- let value = a.value;
- let fraction = a.fraction;
-
- for (const b of rest) {
- if (b.currency !== currency) {
- throw Error(`Mismatched currency: ${b.currency} and ${currency}`);
- }
- if (fraction < b.fraction) {
- if (value < 1) {
- return { amount: { currency, value: 0, fraction: 0 }, saturated: true };
- }
- value--;
- fraction += fractionalBase;
- }
- console.assert(fraction >= b.fraction);
- fraction -= b.fraction;
- if (value < b.value) {
- return { amount: { currency, value: 0, fraction: 0 }, saturated: true };
- }
- value -= b.value;
- }
-
- return { amount: { currency, value, fraction }, saturated: false };
-}
-
-/**
- * Compare two amounts. Returns 0 when equal, -1 when a < b
- * and +1 when a > b. Throws when currencies don't match.
- */
-export function cmp(a: AmountJson, b: AmountJson): -1 | 0 | 1 {
- if (a.currency !== b.currency) {
- throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
- }
- 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;
- case av > bv:
- return 1;
- case af < bf:
- return -1;
- case af > bf:
- return 1;
- case af === bf:
- return 0;
- default:
- throw Error("assertion failed");
- }
-}
-
-/**
- * Create a copy of an amount.
- */
-export function copy(a: AmountJson): AmountJson {
- return {
- currency: a.currency,
- fraction: a.fraction,
- value: a.value,
- };
-}
-
-/**
- * Divide an amount. Throws on division by zero.
- */
-export function divide(a: AmountJson, n: number): AmountJson {
- if (n === 0) {
- throw Error(`Division by 0`);
- }
- if (n === 1) {
- return { value: a.value, fraction: a.fraction, currency: a.currency };
- }
- const r = a.value % n;
- return {
- currency: a.currency,
- fraction: Math.floor((r * fractionalBase + a.fraction) / n),
- value: Math.floor(a.value / n),
- };
-}
-
-/**
- * Check if an amount is non-zero.
- */
-export function isNonZero(a: AmountJson): boolean {
- return a.value > 0 || a.fraction > 0;
-}
-
-export function isZero(a: AmountJson): boolean {
- return a.value === 0 && a.fraction === 0;
-}
-
-/**
- * Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct.
- */
-export function parse(s: string): AmountJson | undefined {
- const res = s.match(/^([a-zA-Z0-9_*-]+):([0-9]+)([.][0-9]+)?$/);
- if (!res) {
- return undefined;
- }
- const tail = res[3] || ".0";
- if (tail.length > fractionalLength + 1) {
- return undefined;
- }
- const value = Number.parseInt(res[2]);
- if (value > maxAmountValue) {
- return undefined;
- }
- return {
- currency: res[1],
- fraction: Math.round(fractionalBase * Number.parseFloat(tail)),
- value,
- };
-}
-
-/**
- * Parse amount in standard string form (like 'EUR:20.5'),
- * throw if the input is not a valid amount.
- */
-export function parseOrThrow(s: string): AmountJson {
- const res = parse(s);
- if (!res) {
- throw Error(`Can't parse amount: "${s}"`);
- }
- return res;
-}
-
-/**
- * Convert a float to a Taler amount.
- * Loss of precision possible.
- */
-export function fromFloat(floatVal: number, currency: string): AmountJson {
- return {
- currency,
- fraction: Math.floor((floatVal - Math.floor(floatVal)) * fractionalBase),
- value: Math.floor(floatVal),
- };
-}
-
-/**
- * Convert to standard human-readable string representation that's
- * also used in JSON formats.
- */
-export function stringify(a: AmountJson): string {
- const av = a.value + Math.floor(a.fraction / fractionalBase);
- const af = a.fraction % fractionalBase;
- let s = av.toString();
-
- if (af) {
- s = s + ".";
- let n = af;
- for (let i = 0; i < fractionalLength; i++) {
- if (!n) {
- break;
- }
- s = s + Math.floor((n / fractionalBase) * 10).toString();
- n = (n * 10) % fractionalBase;
- }
- }
-
- return `${a.currency}:${s}`;
-}
-
-/**
- * Check if the argument is a valid amount in string form.
- */
-function check(a: any): boolean {
- if (typeof a !== "string") {
- return false;
- }
- try {
- const parsedAmount = parse(a);
- return !!parsedAmount;
- } catch {
- return false;
- }
-}
-
-function mult(a: AmountJson, n: number): Result {
- if (!Number.isInteger(n)) {
- throw Error("amount can only be multipied by an integer");
- }
- if (n < 0) {
- throw Error("amount can only be multiplied by a positive integer");
- }
- if (n == 0) {
- return { amount: getZero(a.currency), saturated: false };
- }
- let x = a;
- let acc = getZero(a.currency);
- while (n > 1) {
- if (n % 2 == 0) {
- n = n / 2;
- } else {
- n = (n - 1) / 2;
- const r2 = add(acc, x);
- if (r2.saturated) {
- return r2;
- }
- acc = r2.amount;
- }
- const r2 = add(x, x);
- if (r2.saturated) {
- return r2;
- }
- x = r2.amount;
- }
- return add(acc, x);
-}
-
-// Export all amount-related functions here for better IDE experience.
-export const Amounts = {
- stringify: stringify,
- parse: parse,
- parseOrThrow: parseOrThrow,
- cmp: cmp,
- add: add,
- sum: sum,
- sub: sub,
- mult: mult,
- check: check,
- getZero: getZero,
- isZero: isZero,
- maxAmountValue: maxAmountValue,
- fromFloat: fromFloat,
- copy: copy,
-};
diff --git a/src/util/assertUnreachable.ts b/src/util/assertUnreachable.ts
deleted file mode 100644
index ffdf88f04..000000000
--- a/src/util/assertUnreachable.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-export function assertUnreachable(x: never): never {
- throw new Error("Didn't expect to get here");
-}
diff --git a/src/util/asyncMemo.ts b/src/util/asyncMemo.ts
deleted file mode 100644
index 6e88081b6..000000000
--- a/src/util/asyncMemo.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-interface MemoEntry<T> {
- p: Promise<T>;
- t: number;
- n: number;
-}
-
-export class AsyncOpMemoMap<T> {
- private n = 0;
- private memoMap: { [k: string]: MemoEntry<T> } = {};
-
- private cleanUp(key: string, n: number): void {
- const r = this.memoMap[key];
- if (r && r.n === n) {
- delete this.memoMap[key];
- }
- }
-
- memo(key: string, pg: () => Promise<T>): Promise<T> {
- const res = this.memoMap[key];
- if (res) {
- return res.p;
- }
- const n = this.n++;
- // Wrap the operation in case it immediately throws
- const p = Promise.resolve().then(() => pg());
- this.memoMap[key] = {
- p,
- n,
- t: new Date().getTime(),
- };
- return p.finally(() => {
- this.cleanUp(key, n);
- });
- }
- clear(): void {
- this.memoMap = {};
- }
-}
-
-export class AsyncOpMemoSingle<T> {
- private n = 0;
- private memoEntry: MemoEntry<T> | undefined;
-
- private cleanUp(n: number): void {
- if (this.memoEntry && this.memoEntry.n === n) {
- this.memoEntry = undefined;
- }
- }
-
- memo(pg: () => Promise<T>): Promise<T> {
- const res = this.memoEntry;
- if (res) {
- return res.p;
- }
- const n = this.n++;
- // Wrap the operation in case it immediately throws
- const p = Promise.resolve().then(() => pg());
- p.finally(() => {
- this.cleanUp(n);
- });
- this.memoEntry = {
- p,
- n,
- t: new Date().getTime(),
- };
- return p;
- }
- clear(): void {
- this.memoEntry = undefined;
- }
-}
diff --git a/src/util/codec-test.ts b/src/util/codec-test.ts
deleted file mode 100644
index b429c318c..000000000
--- a/src/util/codec-test.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2018-2019 GNUnet e.V.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Type-safe codecs for converting from/to JSON.
- */
-
-import test from "ava";
-import {
- Codec,
- makeCodecForObject,
- makeCodecForConstString,
- codecForString,
- makeCodecForUnion,
-} from "./codec";
-
-interface MyObj {
- foo: string;
-}
-
-interface AltOne {
- type: "one";
- foo: string;
-}
-
-interface AltTwo {
- type: "two";
- bar: string;
-}
-
-type MyUnion = AltOne | AltTwo;
-
-test("basic codec", (t) => {
- const myObjCodec = makeCodecForObject<MyObj>()
- .property("foo", codecForString)
- .build("MyObj");
- const res = myObjCodec.decode({ foo: "hello" });
- t.assert(res.foo === "hello");
-
- t.throws(() => {
- myObjCodec.decode({ foo: 123 });
- });
-});
-
-test("union", (t) => {
- const altOneCodec: Codec<AltOne> = makeCodecForObject<AltOne>()
- .property("type", makeCodecForConstString("one"))
- .property("foo", codecForString)
- .build("AltOne");
- const altTwoCodec: Codec<AltTwo> = makeCodecForObject<AltTwo>()
- .property("type", makeCodecForConstString("two"))
- .property("bar", codecForString)
- .build("AltTwo");
- const myUnionCodec: Codec<MyUnion> = makeCodecForUnion<MyUnion>()
- .discriminateOn("type")
- .alternative("one", altOneCodec)
- .alternative("two", altTwoCodec)
- .build<MyUnion>("MyUnion");
-
- const res = myUnionCodec.decode({ type: "one", foo: "bla" });
- t.is(res.type, "one");
- if (res.type == "one") {
- t.is(res.foo, "bla");
- }
-});
diff --git a/src/util/codec.ts b/src/util/codec.ts
deleted file mode 100644
index 383a2d99e..000000000
--- a/src/util/codec.ts
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2018-2019 GNUnet e.V.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Type-safe codecs for converting from/to JSON.
- */
-
- /* eslint-disable @typescript-eslint/ban-types */
-
-/**
- * Error thrown when decoding fails.
- */
-export class DecodingError extends Error {
- constructor(message: string) {
- super(message);
- Object.setPrototypeOf(this, DecodingError.prototype);
- this.name = "DecodingError";
- }
-}
-
-/**
- * Context information to show nicer error messages when decoding fails.
- */
-export interface Context {
- readonly path?: string[];
-}
-
-export function renderContext(c?: Context): string {
- const p = c?.path;
- if (p) {
- return p.join(".");
- } else {
- return "(unknown)";
- }
-}
-
-function joinContext(c: Context | undefined, part: string): Context {
- const path = c?.path ?? [];
- return {
- path: path.concat([part]),
- };
-}
-
-/**
- * A codec converts untyped JSON to a typed object.
- */
-export interface Codec<V> {
- /**
- * Decode untyped JSON to an object of type [[V]].
- */
- readonly decode: (x: any, c?: Context) => V;
-}
-
-type SingletonRecord<K extends keyof any, V> = { [Y in K]: V };
-
-interface Prop {
- name: string;
- codec: Codec<any>;
-}
-
-interface Alternative {
- tagValue: any;
- codec: Codec<any>;
-}
-
-class ObjectCodecBuilder<OutputType, PartialOutputType> {
- private propList: Prop[] = [];
-
- /**
- * Define a property for the object.
- */
- property<K extends keyof OutputType & string, V extends OutputType[K]>(
- x: K,
- codec: Codec<V>,
- ): ObjectCodecBuilder<OutputType, PartialOutputType & SingletonRecord<K, V>> {
- if (!codec) {
- throw Error("inner codec must be defined");
- }
- this.propList.push({ name: x, codec: codec });
- return this as any;
- }
-
- /**
- * Return the built codec.
- *
- * @param objectDisplayName name of the object that this codec operates on,
- * used in error messages.
- */
- build(objectDisplayName: string): Codec<PartialOutputType> {
- const propList = this.propList;
- return {
- decode(x: any, c?: Context): PartialOutputType {
- if (!c) {
- c = {
- path: [`(${objectDisplayName})`],
- };
- }
- if (typeof x !== "object") {
- throw new DecodingError(
- `expected object for ${objectDisplayName} at ${renderContext(
- c,
- )} but got ${typeof x}`,
- );
- }
- const obj: any = {};
- for (const prop of propList) {
- const propRawVal = x[prop.name];
- const propVal = prop.codec.decode(
- propRawVal,
- joinContext(c, prop.name),
- );
- obj[prop.name] = propVal;
- }
- return obj as PartialOutputType;
- },
- };
- }
-}
-
-class UnionCodecBuilder<
- TargetType,
- TagPropertyLabel extends keyof TargetType,
- CommonBaseType,
- PartialTargetType
-> {
- private alternatives = new Map<any, Alternative>();
-
- constructor(
- private discriminator: TagPropertyLabel,
- private baseCodec?: Codec<CommonBaseType>,
- ) {}
-
- /**
- * Define a property for the object.
- */
- alternative<V>(
- tagValue: TargetType[TagPropertyLabel],
- codec: Codec<V>,
- ): UnionCodecBuilder<
- TargetType,
- TagPropertyLabel,
- CommonBaseType,
- PartialTargetType | V
- > {
- if (!codec) {
- throw Error("inner codec must be defined");
- }
- this.alternatives.set(tagValue, { codec, tagValue });
- return this as any;
- }
-
- /**
- * Return the built codec.
- *
- * @param objectDisplayName name of the object that this codec operates on,
- * used in error messages.
- */
- build<R extends PartialTargetType & CommonBaseType = never>(
- objectDisplayName: string,
- ): Codec<R> {
- const alternatives = this.alternatives;
- const discriminator = this.discriminator;
- const baseCodec = this.baseCodec;
- return {
- decode(x: any, c?: Context): R {
- if (!c) {
- c = {
- path: [`(${objectDisplayName})`],
- };
- }
- const d = x[discriminator];
- if (d === undefined) {
- throw new DecodingError(
- `expected tag for ${objectDisplayName} at ${renderContext(
- c,
- )}.${discriminator}`,
- );
- }
- const alt = alternatives.get(d);
- if (!alt) {
- throw new DecodingError(
- `unknown tag for ${objectDisplayName} ${d} at ${renderContext(
- c,
- )}.${discriminator}`,
- );
- }
- const altDecoded = alt.codec.decode(x);
- if (baseCodec) {
- const baseDecoded = baseCodec.decode(x, c);
- return { ...baseDecoded, ...altDecoded };
- } else {
- return altDecoded;
- }
- },
- };
- }
-}
-
-export class UnionCodecPreBuilder<T> {
- discriminateOn<D extends keyof T, B = {}>(
- discriminator: D,
- baseCodec?: Codec<B>,
- ): UnionCodecBuilder<T, D, B, never> {
- return new UnionCodecBuilder<T, D, B, never>(discriminator, baseCodec);
- }
-}
-
-/**
- * Return a builder for a codec that decodes an object with properties.
- */
-export function makeCodecForObject<T>(): ObjectCodecBuilder<T, {}> {
- return new ObjectCodecBuilder<T, {}>();
-}
-
-export function makeCodecForUnion<T>(): UnionCodecPreBuilder<T> {
- return new UnionCodecPreBuilder<T>();
-}
-
-/**
- * Return a codec for a mapping from a string to values described by the inner codec.
- */
-export function makeCodecForMap<T>(
- innerCodec: Codec<T>,
-): Codec<{ [x: string]: T }> {
- if (!innerCodec) {
- throw Error("inner codec must be defined");
- }
- return {
- decode(x: any, c?: Context): { [x: string]: T } {
- const map: { [x: string]: T } = {};
- if (typeof x !== "object") {
- throw new DecodingError(`expected object at ${renderContext(c)}`);
- }
- for (const i in x) {
- map[i] = innerCodec.decode(x[i], joinContext(c, `[${i}]`));
- }
- return map;
- },
- };
-}
-
-/**
- * Return a codec for a list, containing values described by the inner codec.
- */
-export function makeCodecForList<T>(innerCodec: Codec<T>): Codec<T[]> {
- if (!innerCodec) {
- throw Error("inner codec must be defined");
- }
- return {
- decode(x: any, c?: Context): T[] {
- const arr: T[] = [];
- if (!Array.isArray(x)) {
- throw new DecodingError(`expected array at ${renderContext(c)}`);
- }
- for (const i in x) {
- arr.push(innerCodec.decode(x[i], joinContext(c, `[${i}]`)));
- }
- return arr;
- },
- };
-}
-
-/**
- * Return a codec for a value that must be a number.
- */
-export const codecForNumber: Codec<number> = {
- decode(x: any, c?: Context): number {
- if (typeof x === "number") {
- return x;
- }
- throw new DecodingError(
- `expected number at ${renderContext(c)} but got ${typeof x}`,
- );
- },
-};
-
-/**
- * Return a codec for a value that must be a number.
- */
-export const codecForBoolean: Codec<boolean> = {
- decode(x: any, c?: Context): boolean {
- if (typeof x === "boolean") {
- return x;
- }
- throw new DecodingError(
- `expected boolean at ${renderContext(c)} but got ${typeof x}`,
- );
- },
-};
-
-/**
- * Return a codec for a value that must be a string.
- */
-export const codecForString: Codec<string> = {
- decode(x: any, c?: Context): string {
- if (typeof x === "string") {
- return x;
- }
- throw new DecodingError(
- `expected string at ${renderContext(c)} but got ${typeof x}`,
- );
- },
-};
-
-/**
- * Codec that allows any value.
- */
-export const codecForAny: Codec<any> = {
- decode(x: any, c?: Context): any {
- return x;
- },
-};
-
-/**
- * Return a codec for a value that must be a string.
- */
-export function makeCodecForConstString<V extends string>(s: V): Codec<V> {
- return {
- decode(x: any, c?: Context): V {
- if (x === s) {
- return x;
- }
- throw new DecodingError(
- `expected string constant "${s}" at ${renderContext(
- c,
- )} but got ${typeof x}`,
- );
- },
- };
-}
-
-/**
- * Return a codec for a boolean true constant.
- */
-export function makeCodecForConstTrue(): Codec<true> {
- return {
- decode(x: any, c?: Context): true {
- if (x === true) {
- return x;
- }
- throw new DecodingError(
- `expected boolean true at ${renderContext(
- c,
- )} but got ${typeof x}`,
- );
- },
- };
-}
-
-/**
- * Return a codec for a boolean true constant.
- */
-export function makeCodecForConstFalse(): Codec<false> {
- return {
- decode(x: any, c?: Context): false {
- if (x === false) {
- return x;
- }
- throw new DecodingError(
- `expected boolean false at ${renderContext(
- c,
- )} but got ${typeof x}`,
- );
- },
- };
-}
-
-/**
- * Return a codec for a value that must be a constant number.
- */
-export function makeCodecForConstNumber<V extends number>(n: V): Codec<V> {
- return {
- decode(x: any, c?: Context): V {
- if (x === n) {
- return x;
- }
- throw new DecodingError(
- `expected number constant "${n}" at ${renderContext(
- c,
- )} but got ${typeof x}`,
- );
- },
- };
-}
-
-export function makeCodecOptional<V>(
- innerCodec: Codec<V>,
-): Codec<V | undefined> {
- return {
- decode(x: any, c?: Context): V | undefined {
- if (x === undefined || x === null) {
- return undefined;
- }
- return innerCodec.decode(x, c);
- },
- };
-}
diff --git a/src/util/helpers-test.ts b/src/util/helpers-test.ts
deleted file mode 100644
index dbecf14b8..000000000
--- a/src/util/helpers-test.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- 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) => {
- // converts to relative, adds https
- t.is(
- "https://alice.example.com/exchange/",
- helpers.canonicalizeBaseUrl("alice.example.com/exchange"),
- );
-
- // keeps http, adds trailing slash
- t.is(
- "http://alice.example.com/exchange/",
- helpers.canonicalizeBaseUrl("http://alice.example.com/exchange"),
- );
-
- // keeps http, adds trailing slash
- t.is(
- "http://alice.example.com/exchange/",
- helpers.canonicalizeBaseUrl("http://alice.example.com/exchange#foobar"),
- );
-
- // Remove search component
- t.is(
- "http://alice.example.com/exchange/",
- helpers.canonicalizeBaseUrl("http://alice.example.com/exchange?foo=bar"),
- );
-
- t.pass();
-});
diff --git a/src/util/helpers.ts b/src/util/helpers.ts
deleted file mode 100644
index 7cd9e4234..000000000
--- a/src/util/helpers.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 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/>
- */
-
-/**
- * Small helper functions that don't fit anywhere else.
- */
-
-/**
- * Imports.
- */
-import { AmountJson } from "./amounts";
-import * as Amounts from "./amounts";
-
-/**
- * Show an amount in a form suitable for the user.
- * FIXME: In the future, this should consider currency-specific
- * settings such as significant digits or currency symbols.
- */
-export function amountToPretty(amount: AmountJson): string {
- const x = amount.value + amount.fraction / Amounts.fractionalBase;
- return `${x} ${amount.currency}`;
-}
-
-/**
- * Canonicalize a base url, typically for the exchange.
- *
- * See http://api.taler.net/wallet.html#general
- */
-export function canonicalizeBaseUrl(url: string): string {
- if (!url.startsWith("http") && !url.startsWith("https")) {
- url = "https://" + url;
- }
- const x = new URL(url);
- if (!x.pathname.endsWith("/")) {
- x.pathname = x.pathname + "/";
- }
- x.search = "";
- x.hash = "";
- return x.href;
-}
-
-/**
- * Convert object to JSON with canonical ordering of keys
- * and whitespace omitted.
- */
-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 (Array.isArray(obj)) {
- const objs: string[] = obj.map((e) => canonicalJson(e));
- return `[${objs.join(",")}]`;
- }
- const keys: string[] = [];
- for (const key in obj) {
- keys.push(key);
- }
- keys.sort();
- let s = "{";
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i];
- s += JSON.stringify(key) + ":" + canonicalJson(obj[key]);
- if (i !== keys.length - 1) {
- s += ",";
- }
- }
- return s + "}";
-}
-
-/**
- * Check for deep equality of two objects.
- * Only arrays, objects and primitives are supported.
- */
-export function deepEquals(x: any, y: any): boolean {
- if (x === y) {
- return true;
- }
-
- if (Array.isArray(x) && x.length !== y.length) {
- return false;
- }
-
- const p = Object.keys(x);
- return (
- Object.keys(y).every((i) => p.indexOf(i) !== -1) &&
- p.every((i) => deepEquals(x[i], y[i]))
- );
-}
-
-export function deepCopy(x: any): any {
- // FIXME: this has many issues ...
- return JSON.parse(JSON.stringify(x));
-}
-
-/**
- * Map from a collection to a list or results and then
- * concatenate the results.
- */
-export function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] {
- return xs.reduce((acc: U[], next: T) => [...f(next), ...acc], []);
-}
-
-/**
- * Compute the hash function of a JSON object.
- */
-export function hash(val: any): number {
- const str = canonicalJson(val);
- // https://github.com/darkskyapp/string-hash
- let h = 5381;
- let i = str.length;
- while (i) {
- h = (h * 33) ^ str.charCodeAt(--i);
- }
-
- /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
- * integers. Since we want the results to be always positive, convert the
- * signed int to an unsigned by doing an unsigned bitshift. */
- return h >>> 0;
-}
-
-/**
- * Lexically compare two strings.
- */
-export function strcmp(s1: string, s2: string): number {
- if (s1 < s2) {
- return -1;
- }
- if (s1 > s2) {
- return 1;
- }
- return 0;
-}
diff --git a/src/util/http.ts b/src/util/http.ts
deleted file mode 100644
index 38892491b..000000000
--- a/src/util/http.ts
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 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/>
- */
-
-/**
- * Helpers for doing XMLHttpRequest-s that are based on ES6 promises.
- * Allows for easy mocking for test cases.
- */
-
-/**
- * Imports
- */
-import { Codec } from "./codec";
-import { OperationFailedError, makeErrorDetails } from "../operations/errors";
-import { TalerErrorCode } from "../TalerErrorCode";
-import { Logger } from "./logging";
-
-const logger = new Logger("http.ts");
-
-/**
- * An HTTP response that is returned by all request methods of this library.
- */
-export interface HttpResponse {
- requestUrl: string;
- status: number;
- headers: Headers;
- json(): Promise<any>;
- text(): Promise<string>;
-}
-
-export interface HttpRequestOptions {
- headers?: { [name: string]: string };
-}
-
-export enum HttpResponseStatus {
- Ok = 200,
- Gone = 210,
-}
-
-/**
- * Headers, roughly modeled after the fetch API's headers object.
- */
-export class Headers {
- private headerMap = new Map<string, string>();
-
- get(name: string): string | null {
- const r = this.headerMap.get(name.toLowerCase());
- if (r) {
- return r;
- }
- return null;
- }
-
- set(name: string, value: string): void {
- const normalizedName = name.toLowerCase();
- const existing = this.headerMap.get(normalizedName);
- if (existing !== undefined) {
- this.headerMap.set(normalizedName, existing + "," + value);
- } else {
- this.headerMap.set(normalizedName, value);
- }
- }
-}
-
-/**
- * Interface for the HTTP request library used by the wallet.
- *
- * The request library is bundled into an interface to make mocking and
- * request tunneling easy.
- */
-export interface HttpRequestLibrary {
- /**
- * Make an HTTP GET request.
- */
- get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse>;
-
- /**
- * Make an HTTP POST request with a JSON body.
- */
- postJson(
- url: string,
- body: any,
- opt?: HttpRequestOptions,
- ): Promise<HttpResponse>;
-}
-
-/**
- * An implementation of the [[HttpRequestLibrary]] using the
- * browser's XMLHttpRequest.
- */
-export class BrowserHttpLib implements HttpRequestLibrary {
- private req(
- method: string,
- url: string,
- requestBody?: any,
- options?: HttpRequestOptions,
- ): Promise<HttpResponse> {
- return new Promise<HttpResponse>((resolve, reject) => {
- const myRequest = new XMLHttpRequest();
- myRequest.open(method, url);
- if (options?.headers) {
- for (const headerName in options.headers) {
- myRequest.setRequestHeader(headerName, options.headers[headerName]);
- }
- }
- myRequest.setRequestHeader;
- if (requestBody) {
- myRequest.send(requestBody);
- } else {
- myRequest.send();
- }
-
- myRequest.onerror = (e) => {
- logger.error("http request error");
- reject(
- OperationFailedError.fromCode(
- TalerErrorCode.WALLET_NETWORK_ERROR,
- "Could not make request",
- {
- requestUrl: url,
- },
- ),
- );
- };
-
- myRequest.addEventListener("readystatechange", (e) => {
- if (myRequest.readyState === XMLHttpRequest.DONE) {
- if (myRequest.status === 0) {
- const exc = OperationFailedError.fromCode(
- TalerErrorCode.WALLET_NETWORK_ERROR,
- "HTTP request failed (status 0, maybe URI scheme was wrong?)",
- {
- requestUrl: url,
- },
- );
- reject(exc);
- return;
- }
- const makeJson = async (): Promise<any> => {
- let responseJson;
- try {
- responseJson = JSON.parse(myRequest.responseText);
- } catch (e) {
- throw OperationFailedError.fromCode(
- TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
- "Invalid JSON from HTTP response",
- {
- requestUrl: url,
- httpStatusCode: myRequest.status,
- },
- );
- }
- if (responseJson === null || typeof responseJson !== "object") {
- throw OperationFailedError.fromCode(
- TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
- "Invalid JSON from HTTP response",
- {
- requestUrl: url,
- httpStatusCode: myRequest.status,
- },
- );
- }
- return responseJson;
- };
-
- const headers = myRequest.getAllResponseHeaders();
- const arr = headers.trim().split(/[\r\n]+/);
-
- // Create a map of header names to values
- const headerMap = new Headers();
- arr.forEach(function (line) {
- const parts = line.split(": ");
- const headerName = parts.shift();
- if (!headerName) {
- logger.warn("skipping invalid header");
- return;
- }
- const value = parts.join(": ");
- headerMap.set(headerName, value);
- });
- const resp: HttpResponse = {
- requestUrl: url,
- status: myRequest.status,
- headers: headerMap,
- json: makeJson,
- text: async () => myRequest.responseText,
- };
- resolve(resp);
- }
- });
- });
- }
-
- get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
- return this.req("get", url, undefined, opt);
- }
-
- postJson(
- url: string,
- body: unknown,
- opt?: HttpRequestOptions,
- ): Promise<HttpResponse> {
- return this.req("post", url, JSON.stringify(body), opt);
- }
-
- stop(): void {
- // Nothing to do
- }
-}
-
-type TalerErrorResponse = {
- code: number;
-} & unknown;
-
-type ResponseOrError<T> =
- | { isError: false; response: T }
- | { isError: true; talerErrorResponse: TalerErrorResponse };
-
-export async function readSuccessResponseJsonOrErrorCode<T>(
- httpResponse: HttpResponse,
- codec: Codec<T>,
-): Promise<ResponseOrError<T>> {
- if (!(httpResponse.status >= 200 && httpResponse.status < 300)) {
- const errJson = await httpResponse.json();
- const talerErrorCode = errJson.code;
- if (typeof talerErrorCode !== "number") {
- throw new OperationFailedError(
- makeErrorDetails(
- TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
- "Error response did not contain error code",
- {
- requestUrl: httpResponse.requestUrl,
- },
- ),
- );
- }
- return {
- isError: true,
- talerErrorResponse: errJson,
- };
- }
- const respJson = await httpResponse.json();
- let parsedResponse: T;
- try {
- parsedResponse = codec.decode(respJson);
- } catch (e) {
- throw OperationFailedError.fromCode(
- TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
- "Response invalid",
- {
- requestUrl: httpResponse.requestUrl,
- httpStatusCode: httpResponse.status,
- validationError: e.toString(),
- },
- );
- }
- return {
- isError: false,
- response: parsedResponse,
- };
-}
-
-export function throwUnexpectedRequestError(
- httpResponse: HttpResponse,
- talerErrorResponse: TalerErrorResponse,
-): never {
- throw new OperationFailedError(
- makeErrorDetails(
- TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
- "Unexpected error code in response",
- {
- requestUrl: httpResponse.requestUrl,
- httpStatusCode: httpResponse.status,
- errorResponse: talerErrorResponse,
- },
- ),
- );
-}
-
-export async function readSuccessResponseJsonOrThrow<T>(
- httpResponse: HttpResponse,
- codec: Codec<T>,
-): Promise<T> {
- const r = await readSuccessResponseJsonOrErrorCode(httpResponse, codec);
- if (!r.isError) {
- return r.response;
- }
- throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
-}
-
-
-export async function readSuccessResponseTextOrErrorCode<T>(
- httpResponse: HttpResponse,
-): Promise<ResponseOrError<string>> {
- if (!(httpResponse.status >= 200 && httpResponse.status < 300)) {
- const errJson = await httpResponse.json();
- const talerErrorCode = errJson.code;
- if (typeof talerErrorCode !== "number") {
- throw new OperationFailedError(
- makeErrorDetails(
- TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
- "Error response did not contain error code",
- {
- requestUrl: httpResponse.requestUrl,
- },
- ),
- );
- }
- return {
- isError: true,
- talerErrorResponse: errJson,
- };
- }
- const respJson = await httpResponse.text();
- return {
- isError: false,
- response: respJson,
- };
-}
-
-export async function checkSuccessResponseOrThrow(
- httpResponse: HttpResponse,
-): Promise<void> {
- if (!(httpResponse.status >= 200 && httpResponse.status < 300)) {
- const errJson = await httpResponse.json();
- const talerErrorCode = errJson.code;
- if (typeof talerErrorCode !== "number") {
- throw new OperationFailedError(
- makeErrorDetails(
- TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
- "Error response did not contain error code",
- {
- requestUrl: httpResponse.requestUrl,
- },
- ),
- );
- }
- throwUnexpectedRequestError(httpResponse, errJson);
- }
-}
-
-export async function readSuccessResponseTextOrThrow<T>(
- httpResponse: HttpResponse,
-): Promise<string> {
- const r = await readSuccessResponseTextOrErrorCode(httpResponse);
- if (!r.isError) {
- return r.response;
- }
- throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
-}
diff --git a/src/util/libtoolVersion-test.ts b/src/util/libtoolVersion-test.ts
deleted file mode 100644
index e58e94759..000000000
--- a/src/util/libtoolVersion-test.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 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 * as LibtoolVersion from "./libtoolVersion";
-
-import test from "ava";
-
-test("version comparison", (t) => {
- t.deepEqual(LibtoolVersion.compare("0:0:0", "0:0:0"), {
- compatible: true,
- currentCmp: 0,
- });
- t.deepEqual(LibtoolVersion.compare("0:0:0", ""), undefined);
- t.deepEqual(LibtoolVersion.compare("foo", "0:0:0"), undefined);
- t.deepEqual(LibtoolVersion.compare("0:0:0", "1:0:1"), {
- compatible: true,
- currentCmp: -1,
- });
- t.deepEqual(LibtoolVersion.compare("0:0:0", "1:5:1"), {
- compatible: true,
- currentCmp: -1,
- });
- t.deepEqual(LibtoolVersion.compare("0:0:0", "1:5:0"), {
- compatible: false,
- currentCmp: -1,
- });
- t.deepEqual(LibtoolVersion.compare("1:0:0", "0:5:0"), {
- compatible: false,
- currentCmp: 1,
- });
- t.deepEqual(LibtoolVersion.compare("1:0:1", "1:5:1"), {
- compatible: true,
- currentCmp: 0,
- });
-});
diff --git a/src/util/libtoolVersion.ts b/src/util/libtoolVersion.ts
deleted file mode 100644
index 5e9d0b74e..000000000
--- a/src/util/libtoolVersion.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 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/>
- */
-
-/**
- * Semantic versioning, but libtool-style.
- * See https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
- */
-
-/**
- * Result of comparing two libtool versions.
- */
-export interface VersionMatchResult {
- /**
- * Is the first version compatible with the second?
- */
- compatible: boolean;
- /**
- * Is the first version older (-1), newser (+1) or
- * identical (0)?
- */
- currentCmp: number;
-}
-
-interface Version {
- current: number;
- revision: number;
- age: number;
-}
-
-/**
- * Compare two libtool-style version strings.
- */
-export function compare(
- me: string,
- other: string,
-): VersionMatchResult | undefined {
- const meVer = parseVersion(me);
- const otherVer = parseVersion(other);
-
- if (!(meVer && otherVer)) {
- return undefined;
- }
-
- const compatible =
- meVer.current - meVer.age <= otherVer.current &&
- meVer.current >= otherVer.current - otherVer.age;
-
- const currentCmp = Math.sign(meVer.current - otherVer.current);
-
- return { compatible, currentCmp };
-}
-
-function parseVersion(v: string): Version | undefined {
- const [currentStr, revisionStr, ageStr, ...rest] = v.split(":");
- if (rest.length !== 0) {
- return undefined;
- }
- const current = Number.parseInt(currentStr);
- const revision = Number.parseInt(revisionStr);
- const age = Number.parseInt(ageStr);
-
- if (Number.isNaN(current)) {
- return undefined;
- }
-
- if (Number.isNaN(revision)) {
- return undefined;
- }
-
- if (Number.isNaN(age)) {
- return undefined;
- }
-
- return { current, revision, age };
-}
diff --git a/src/util/logging.ts b/src/util/logging.ts
deleted file mode 100644
index 83e8d2192..000000000
--- a/src/util/logging.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- This file is part of TALER
- (C) 2019 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/>
- */
-
-/**
- * Imports.
- */
-import { isNode } from "../webex/compat";
-
-function writeNodeLog(
- message: string,
- tag: string,
- level: string,
- args: any[],
-): void {
- process.stderr.write(`${new Date().toISOString()} ${tag} ${level} `);
- process.stderr.write(message);
- if (args.length != 0) {
- process.stderr.write(" ");
- process.stderr.write(JSON.stringify(args, undefined, 2));
- }
- process.stderr.write("\n");
-}
-
-/**
- * Logger that writes to stderr when running under node,
- * and uses the corresponding console.* method to log in the browser.
- */
-export class Logger {
- constructor(private tag: string) {}
-
- info(message: string, ...args: any[]): void {
- if (isNode()) {
- writeNodeLog(message, this.tag, "INFO", args);
- } else {
- console.info(
- `${new Date().toISOString()} ${this.tag} INFO ` + message,
- ...args,
- );
- }
- }
-
- warn(message: string, ...args: any[]): void {
- if (isNode()) {
- writeNodeLog(message, this.tag, "WARN", args);
- } else {
- console.warn(
- `${new Date().toISOString()} ${this.tag} INFO ` + message,
- ...args,
- );
- }
- }
-
- error(message: string, ...args: any[]): void {
- if (isNode()) {
- writeNodeLog(message, this.tag, "ERROR", args);
- } else {
- console.info(
- `${new Date().toISOString()} ${this.tag} ERROR ` + message,
- ...args,
- );
- }
- }
-
- trace(message: any, ...args: any[]): void {
- if (isNode()) {
- writeNodeLog(message, this.tag, "TRACE", args);
- } else {
- console.info(
- `${new Date().toISOString()} ${this.tag} TRACE ` + message,
- ...args,
- );
- }
- }
-}
diff --git a/src/util/payto-test.ts b/src/util/payto-test.ts
deleted file mode 100644
index 01280b650..000000000
--- a/src/util/payto-test.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import test from "ava";
-
-import { parsePaytoUri } from "./payto";
-
-test("basic payto parsing", (t) => {
- const r1 = parsePaytoUri("https://example.com/");
- t.is(r1, undefined);
-
- const r2 = parsePaytoUri("payto:blabla");
- t.is(r2, undefined);
-
- const r3 = parsePaytoUri("payto://x-taler-bank/123");
- t.is(r3?.targetType, "x-taler-bank");
- t.is(r3?.targetPath, "123");
-});
diff --git a/src/util/payto.ts b/src/util/payto.ts
deleted file mode 100644
index 835214b86..000000000
--- a/src/util/payto.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-interface PaytoUri {
- targetType: string;
- targetPath: string;
- params: { [name: string]: string };
-}
-
-const paytoPfx = "payto://";
-
-/**
- * Add query parameters to a payto URI
- */
-export function addPaytoQueryParams(
- s: string,
- params: { [name: string]: string },
-): string {
- const [acct, search] = s.slice(paytoPfx.length).split("?");
- const searchParams = new URLSearchParams(search || "");
- for (const k of Object.keys(params)) {
- searchParams.set(k, params[k]);
- }
- return paytoPfx + acct + "?" + searchParams.toString();
-}
-
-export function parsePaytoUri(s: string): PaytoUri | undefined {
- if (!s.startsWith(paytoPfx)) {
- return undefined;
- }
-
- const [acct, search] = s.slice(paytoPfx.length).split("?");
-
- const firstSlashPos = acct.indexOf("/");
-
- if (firstSlashPos === -1) {
- return undefined;
- }
-
- const targetType = acct.slice(0, firstSlashPos);
- const targetPath = acct.slice(firstSlashPos + 1);
-
- const params: { [k: string]: string } = {};
-
- const searchParams = new URLSearchParams(search || "");
-
- searchParams.forEach((v, k) => {
- params[v] = k;
- });
-
- return {
- targetPath,
- targetType,
- params,
- };
-}
diff --git a/src/util/promiseUtils.ts b/src/util/promiseUtils.ts
deleted file mode 100644
index d409686d9..000000000
--- a/src/util/promiseUtils.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 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/>
- */
-
-export interface OpenedPromise<T> {
- promise: Promise<T>;
- resolve: (val: T) => void;
- reject: (err: any) => void;
-}
-
-/**
- * Get an unresolved promise together with its extracted resolve / reject
- * function.
- */
-export function openPromise<T>(): OpenedPromise<T> {
- let resolve: ((x?: any) => void) | null = null;
- let reject: ((reason?: any) => void) | null = null;
- const promise = new Promise<T>((res, rej) => {
- resolve = res;
- reject = rej;
- });
- if (!(resolve && reject)) {
- // Never happens, unless JS implementation is broken
- throw Error();
- }
- return { resolve, reject, promise };
-}
-
-export class AsyncCondition {
- private _waitPromise: Promise<void>;
- private _resolveWaitPromise: (val: void) => void;
- constructor() {
- const op = openPromise<void>();
- this._waitPromise = op.promise;
- this._resolveWaitPromise = op.resolve;
- }
-
- wait(): Promise<void> {
- return this._waitPromise;
- }
-
- trigger(): void {
- this._resolveWaitPromise();
- const op = openPromise<void>();
- this._waitPromise = op.promise;
- this._resolveWaitPromise = op.resolve;
- }
-}
diff --git a/src/util/query.ts b/src/util/query.ts
deleted file mode 100644
index be319049b..000000000
--- a/src/util/query.ts
+++ /dev/null
@@ -1,575 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 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/>
- */
-
-/**
- * Database query abstractions.
- * @module Query
- * @author Florian Dold
- */
-
-/**
- * Imports.
- */
-import { openPromise } from "./promiseUtils";
-
-/**
- * Exception that should be thrown by client code to abort a transaction.
- */
-export const TransactionAbort = Symbol("transaction_abort");
-
-/**
- * Definition of an object store.
- */
-export class Store<T> {
- constructor(
- public name: string,
- public storeParams?: IDBObjectStoreParameters,
- public validator?: (v: T) => T,
- ) {}
-}
-
-/**
- * Options for an index.
- */
-export interface IndexOptions {
- /**
- * If true and the path resolves to an array, create an index entry for
- * each member of the array (instead of one index entry containing the full array).
- *
- * Defaults to false.
- */
- multiEntry?: boolean;
-}
-
-function requestToPromise(req: IDBRequest): Promise<any> {
- const stack = Error("Failed request was started here.");
- return new Promise((resolve, reject) => {
- req.onsuccess = () => {
- resolve(req.result);
- };
- req.onerror = () => {
- console.log("error in DB request", req.error);
- reject(req.error);
- console.log("Request failed:", stack);
- };
- });
-}
-
-function transactionToPromise(tx: IDBTransaction): Promise<void> {
- const stack = Error("Failed transaction was started here.");
- return new Promise((resolve, reject) => {
- tx.onabort = () => {
- reject(TransactionAbort);
- };
- tx.oncomplete = () => {
- resolve();
- };
- tx.onerror = () => {
- console.error("Transaction failed:", stack);
- reject(tx.error);
- };
- });
-}
-
-function applyMutation<T>(
- req: IDBRequest,
- f: (x: T) => T | undefined,
-): Promise<void> {
- return new Promise((resolve, reject) => {
- req.onsuccess = () => {
- const cursor = req.result;
- if (cursor) {
- const val = cursor.value;
- const modVal = f(val);
- if (modVal !== undefined && modVal !== null) {
- const req2: IDBRequest = cursor.update(modVal);
- req2.onerror = () => {
- reject(req2.error);
- };
- req2.onsuccess = () => {
- cursor.continue();
- };
- } else {
- cursor.continue();
- }
- } else {
- resolve();
- }
- };
- req.onerror = () => {
- reject(req.error);
- };
- });
-}
-
-type CursorResult<T> = CursorEmptyResult<T> | CursorValueResult<T>;
-
-interface CursorEmptyResult<T> {
- hasValue: false;
-}
-
-interface CursorValueResult<T> {
- hasValue: true;
- value: T;
-}
-
-class ResultStream<T> {
- private currentPromise: Promise<void>;
- private gotCursorEnd = false;
- private awaitingResult = false;
-
- constructor(private req: IDBRequest) {
- this.awaitingResult = true;
- let p = openPromise<void>();
- this.currentPromise = p.promise;
- req.onsuccess = () => {
- if (!this.awaitingResult) {
- throw Error("BUG: invariant violated");
- }
- const cursor = req.result;
- if (cursor) {
- this.awaitingResult = false;
- p.resolve();
- p = openPromise<void>();
- this.currentPromise = p.promise;
- } else {
- this.gotCursorEnd = true;
- p.resolve();
- }
- };
- req.onerror = () => {
- p.reject(req.error);
- };
- }
-
- async toArray(): Promise<T[]> {
- const arr: T[] = [];
- while (true) {
- const x = await this.next();
- if (x.hasValue) {
- arr.push(x.value);
- } else {
- break;
- }
- }
- return arr;
- }
-
- async map<R>(f: (x: T) => R): Promise<R[]> {
- const arr: R[] = [];
- while (true) {
- const x = await this.next();
- if (x.hasValue) {
- arr.push(f(x.value));
- } else {
- break;
- }
- }
- return arr;
- }
-
- async forEachAsync(f: (x: T) => Promise<void>): Promise<void> {
- while (true) {
- const x = await this.next();
- if (x.hasValue) {
- await f(x.value);
- } else {
- break;
- }
- }
- }
-
- async forEach(f: (x: T) => void): Promise<void> {
- while (true) {
- const x = await this.next();
- if (x.hasValue) {
- f(x.value);
- } else {
- break;
- }
- }
- }
-
- async filter(f: (x: T) => boolean): Promise<T[]> {
- const arr: T[] = [];
- while (true) {
- const x = await this.next();
- if (x.hasValue) {
- if (f(x.value)) {
- arr.push(x.value);
- }
- } else {
- break;
- }
- }
- return arr;
- }
-
- async next(): Promise<CursorResult<T>> {
- if (this.gotCursorEnd) {
- return { hasValue: false };
- }
- if (!this.awaitingResult) {
- const cursor: IDBCursor | undefined = this.req.result;
- if (!cursor) {
- throw Error("assertion failed");
- }
- this.awaitingResult = true;
- cursor.continue();
- }
- await this.currentPromise;
- if (this.gotCursorEnd) {
- return { hasValue: false };
- }
- const cursor = this.req.result;
- if (!cursor) {
- throw Error("assertion failed");
- }
- return { hasValue: true, value: cursor.value };
- }
-}
-
-export class TransactionHandle {
- constructor(private tx: IDBTransaction) {}
-
- put<T>(store: Store<T>, value: T, key?: any): Promise<any> {
- const req = this.tx.objectStore(store.name).put(value, key);
- return requestToPromise(req);
- }
-
- add<T>(store: Store<T>, value: T, key?: any): Promise<any> {
- const req = this.tx.objectStore(store.name).add(value, key);
- return requestToPromise(req);
- }
-
- get<T>(store: Store<T>, key: any): Promise<T | undefined> {
- const req = this.tx.objectStore(store.name).get(key);
- return requestToPromise(req);
- }
-
- getIndexed<S extends IDBValidKey, T>(
- index: Index<S, T>,
- key: any,
- ): Promise<T | undefined> {
- const req = this.tx
- .objectStore(index.storeName)
- .index(index.indexName)
- .get(key);
- return requestToPromise(req);
- }
-
- iter<T>(store: Store<T>, key?: any): ResultStream<T> {
- const req = this.tx.objectStore(store.name).openCursor(key);
- return new ResultStream<T>(req);
- }
-
- iterIndexed<S extends IDBValidKey, T>(
- index: Index<S, T>,
- key?: any,
- ): ResultStream<T> {
- const req = this.tx
- .objectStore(index.storeName)
- .index(index.indexName)
- .openCursor(key);
- return new ResultStream<T>(req);
- }
-
- delete<T>(store: Store<T>, key: any): Promise<void> {
- const req = this.tx.objectStore(store.name).delete(key);
- return requestToPromise(req);
- }
-
- mutate<T>(
- store: Store<T>,
- key: any,
- f: (x: T) => T | undefined,
- ): Promise<void> {
- const req = this.tx.objectStore(store.name).openCursor(key);
- return applyMutation(req, f);
- }
-}
-
-function runWithTransaction<T>(
- db: IDBDatabase,
- stores: Store<any>[],
- f: (t: TransactionHandle) => Promise<T>,
- mode: "readonly" | "readwrite",
-): Promise<T> {
- const stack = Error("Failed transaction was started here.");
- return new Promise((resolve, reject) => {
- const storeName = stores.map((x) => x.name);
- const tx = db.transaction(storeName, mode);
- let funResult: any = undefined;
- let gotFunResult = false;
- tx.oncomplete = () => {
- // This is a fatal error: The transaction completed *before*
- // the transaction function returned. Likely, the transaction
- // function waited on a promise that is *not* resolved in the
- // microtask queue, thus triggering the auto-commit behavior.
- // Unfortunately, the auto-commit behavior of IDB can't be switched
- // of. There are some proposals to add this functionality in the future.
- if (!gotFunResult) {
- const msg =
- "BUG: transaction closed before transaction function returned";
- console.error(msg);
- reject(Error(msg));
- }
- resolve(funResult);
- };
- tx.onerror = () => {
- console.error("error in transaction");
- console.error(stack);
- };
- tx.onabort = () => {
- if (tx.error) {
- console.error("Transaction aborted with error:", tx.error);
- } else {
- console.log("Trasaction aborted (no error)");
- }
- reject(TransactionAbort);
- };
- const th = new TransactionHandle(tx);
- const resP = Promise.resolve().then(() => f(th));
- resP
- .then((result) => {
- gotFunResult = true;
- funResult = result;
- })
- .catch((e) => {
- if (e == TransactionAbort) {
- console.info("aborting transaction");
- } else {
- console.error("Transaction failed:", e);
- console.error(stack);
- tx.abort();
- }
- })
- .catch((e) => {
- console.error("fatal: aborting transaction failed", e);
- });
- });
-}
-
-/**
- * Definition of an index.
- */
-export class Index<S extends IDBValidKey, T> {
- /**
- * Name of the store that this index is associated with.
- */
- storeName: string;
-
- /**
- * Options to use for the index.
- */
- options: IndexOptions;
-
- constructor(
- s: Store<T>,
- public indexName: string,
- public keyPath: string | string[],
- options?: IndexOptions,
- ) {
- const defaultOptions = {
- multiEntry: false,
- };
- this.options = { ...defaultOptions, ...(options || {}) };
- this.storeName = s.name;
- }
-
- /**
- * We want to have the key type parameter in use somewhere,
- * because otherwise the compiler complains. In iterIndex the
- * key type is pretty useful.
- */
- protected _dummyKey: S | undefined;
-}
-
-/**
- * Return a promise that resolves
- * to the taler wallet db.
- */
-export function openDatabase(
- idbFactory: IDBFactory,
- databaseName: string,
- databaseVersion: number,
- onVersionChange: () => void,
- onUpgradeNeeded: (
- db: IDBDatabase,
- oldVersion: number,
- newVersion: number,
- ) => void,
-): Promise<IDBDatabase> {
- return new Promise<IDBDatabase>((resolve, reject) => {
- const req = idbFactory.open(databaseName, databaseVersion);
- req.onerror = (e) => {
- console.log("taler database error", e);
- reject(new Error("database error"));
- };
- req.onsuccess = (e) => {
- req.result.onversionchange = (evt: IDBVersionChangeEvent) => {
- console.log(
- `handling live db version change from ${evt.oldVersion} to ${evt.newVersion}`,
- );
- req.result.close();
- onVersionChange();
- };
- resolve(req.result);
- };
- req.onupgradeneeded = (e) => {
- const db = req.result;
- const newVersion = e.newVersion;
- if (!newVersion) {
- throw Error("upgrade needed, but new version unknown");
- }
- onUpgradeNeeded(db, e.oldVersion, newVersion);
- };
- });
-}
-
-export class Database {
- constructor(private db: IDBDatabase) {}
-
- static deleteDatabase(idbFactory: IDBFactory, dbName: string): void {
- idbFactory.deleteDatabase(dbName);
- }
-
- async exportDatabase(): Promise<any> {
- const db = this.db;
- const dump = {
- name: db.name,
- stores: {} as { [s: string]: any },
- version: db.version,
- };
-
- return new Promise((resolve, reject) => {
- 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++) {
- const name = db.objectStoreNames[i];
- const storeDump = {} as { [s: string]: any };
- dump.stores[name] = storeDump;
- tx.objectStore(name)
- .openCursor()
- .addEventListener("success", (e: Event) => {
- const cursor = (e.target as any).result;
- if (cursor) {
- storeDump[cursor.key] = cursor.value;
- cursor.continue();
- }
- });
- }
- });
- }
-
- importDatabase(dump: any): Promise<void> {
- const db = this.db;
- console.log("importing db", dump);
- return new Promise<void>((resolve, reject) => {
- 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);
- for (const obj of objects) {
- store.put(obj);
- }
- }
- }
- tx.addEventListener("complete", () => {
- resolve();
- });
- });
- }
-
- async get<T>(store: Store<T>, key: any): Promise<T | undefined> {
- const tx = this.db.transaction([store.name], "readonly");
- const req = tx.objectStore(store.name).get(key);
- const v = await requestToPromise(req);
- await transactionToPromise(tx);
- return v;
- }
-
- async getIndexed<S extends IDBValidKey, T>(
- index: Index<S, T>,
- key: any,
- ): Promise<T | undefined> {
- const tx = this.db.transaction([index.storeName], "readonly");
- const req = tx.objectStore(index.storeName).index(index.indexName).get(key);
- const v = await requestToPromise(req);
- await transactionToPromise(tx);
- return v;
- }
-
- async put<T>(store: Store<T>, value: T, key?: any): Promise<any> {
- const tx = this.db.transaction([store.name], "readwrite");
- const req = tx.objectStore(store.name).put(value, key);
- const v = await requestToPromise(req);
- await transactionToPromise(tx);
- return v;
- }
-
- async mutate<T>(
- store: Store<T>,
- key: any,
- f: (x: T) => T | undefined,
- ): Promise<void> {
- const tx = this.db.transaction([store.name], "readwrite");
- const req = tx.objectStore(store.name).openCursor(key);
- await applyMutation(req, f);
- await transactionToPromise(tx);
- }
-
- iter<T>(store: Store<T>): ResultStream<T> {
- const tx = this.db.transaction([store.name], "readonly");
- const req = tx.objectStore(store.name).openCursor();
- return new ResultStream<T>(req);
- }
-
- iterIndex<S extends IDBValidKey, T>(
- index: Index<S, T>,
- query?: any,
- ): ResultStream<T> {
- const tx = this.db.transaction([index.storeName], "readonly");
- const req = tx
- .objectStore(index.storeName)
- .index(index.indexName)
- .openCursor(query);
- return new ResultStream<T>(req);
- }
-
- async runWithReadTransaction<T>(
- stores: Store<any>[],
- f: (t: TransactionHandle) => Promise<T>,
- ): Promise<T> {
- return runWithTransaction<T>(this.db, stores, f, "readonly");
- }
-
- async runWithWriteTransaction<T>(
- stores: Store<any>[],
- f: (t: TransactionHandle) => Promise<T>,
- ): Promise<T> {
- return runWithTransaction<T>(this.db, stores, f, "readwrite");
- }
-}
diff --git a/src/util/reserveHistoryUtil-test.ts b/src/util/reserveHistoryUtil-test.ts
deleted file mode 100644
index 79022de77..000000000
--- a/src/util/reserveHistoryUtil-test.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems S.A.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Imports.
- */
-import test from "ava";
-import {
- reconcileReserveHistory,
- summarizeReserveHistory,
-} from "./reserveHistoryUtil";
-import {
- WalletReserveHistoryItem,
- WalletReserveHistoryItemType,
-} from "../types/dbTypes";
-import {
- ReserveTransaction,
- ReserveTransactionType,
-} from "../types/ReserveTransaction";
-import { Amounts } from "./amounts";
-
-test("basics", (t) => {
- const r = reconcileReserveHistory([], []);
- t.deepEqual(r.updatedLocalHistory, []);
-});
-
-test("unmatched credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 1);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:100");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:100");
-});
-
-test("unmatched credit #2", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:50",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC02",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:150");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:150");
-});
-
-test("matched credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- matchedExchangeTransaction: {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:50",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC02",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:150");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:150");
-});
-
-test("fulfilling credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:50",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC02",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:150");
-});
-
-test("unfulfilled credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:50",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC02",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:150");
-});
-
-test("awaited credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:50"),
- },
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:100");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:50");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:100");
-});
-
-test("withdrawal new match", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- matchedExchangeTransaction: {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- },
- {
- type: WalletReserveHistoryItemType.Withdraw,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:5"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Withdraw,
- amount: "TESTKUDOS:5",
- h_coin_envelope: "foobar",
- h_denom_pub: "foobar",
- reserve_sig: "foobar",
- withdraw_fee: "TESTKUDOS:0.1",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:95");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:95");
-});
-
-test("claimed but now arrived", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- matchedExchangeTransaction: {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- },
- {
- type: WalletReserveHistoryItemType.Withdraw,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:5"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:100");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:95");
-});
diff --git a/src/util/reserveHistoryUtil.ts b/src/util/reserveHistoryUtil.ts
deleted file mode 100644
index 855b71a3d..000000000
--- a/src/util/reserveHistoryUtil.ts
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems S.A.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Imports.
- */
-import {
- WalletReserveHistoryItem,
- WalletReserveHistoryItemType,
-} from "../types/dbTypes";
-import {
- ReserveTransaction,
- ReserveTransactionType,
-} from "../types/ReserveTransaction";
-import * as Amounts from "../util/amounts";
-import { timestampCmp } from "./time";
-import { deepCopy } from "./helpers";
-import { AmountJson } from "../util/amounts";
-
-/**
- * Helpers for dealing with reserve histories.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-export interface ReserveReconciliationResult {
- /**
- * The wallet's local history reconciled with the exchange's reserve history.
- */
- updatedLocalHistory: WalletReserveHistoryItem[];
-
- /**
- * History items that were newly created, subset of the
- * updatedLocalHistory items.
- */
- newAddedItems: WalletReserveHistoryItem[];
-
- /**
- * History items that were newly matched, subset of the
- * updatedLocalHistory items.
- */
- newMatchedItems: WalletReserveHistoryItem[];
-}
-
-/**
- * Various totals computed from the wallet's view
- * on the reserve history.
- */
-export interface ReserveHistorySummary {
- /**
- * Balance computed by the wallet, should match the balance
- * computed by the reserve.
- */
- computedReserveBalance: Amounts.AmountJson;
-
- /**
- * Reserve balance that is still available for withdrawal.
- */
- unclaimedReserveAmount: Amounts.AmountJson;
-
- /**
- * Amount that we're still expecting to come into the reserve.
- */
- awaitedReserveAmount: Amounts.AmountJson;
-
- /**
- * Amount withdrawn from the reserve so far. Only counts
- * finished withdrawals, not withdrawals in progress.
- */
- withdrawnAmount: Amounts.AmountJson;
-}
-
-/**
- * Check if two reserve history items (exchange's version) match.
- */
-function isRemoteHistoryMatch(
- t1: ReserveTransaction,
- t2: ReserveTransaction,
-): boolean {
- switch (t1.type) {
- case ReserveTransactionType.Closing: {
- return t1.type === t2.type && t1.wtid == t2.wtid;
- }
- case ReserveTransactionType.Credit: {
- return t1.type === t2.type && t1.wire_reference === t2.wire_reference;
- }
- case ReserveTransactionType.Recoup: {
- return (
- t1.type === t2.type &&
- t1.coin_pub === t2.coin_pub &&
- timestampCmp(t1.timestamp, t2.timestamp) === 0
- );
- }
- case ReserveTransactionType.Withdraw: {
- return t1.type === t2.type && t1.h_coin_envelope === t2.h_coin_envelope;
- }
- }
-}
-
-/**
- * Check a local reserve history item and a remote history item are a match.
- */
-export function isLocalRemoteHistoryMatch(
- t1: WalletReserveHistoryItem,
- t2: ReserveTransaction,
-): boolean {
- switch (t1.type) {
- case WalletReserveHistoryItemType.Credit: {
- return (
- t2.type === ReserveTransactionType.Credit &&
- !!t1.expectedAmount &&
- Amounts.cmp(t1.expectedAmount, Amounts.parseOrThrow(t2.amount)) === 0
- );
- }
- case WalletReserveHistoryItemType.Withdraw:
- return (
- t2.type === ReserveTransactionType.Withdraw &&
- !!t1.expectedAmount &&
- Amounts.cmp(t1.expectedAmount, Amounts.parseOrThrow(t2.amount)) === 0
- );
- case WalletReserveHistoryItemType.Recoup: {
- return (
- t2.type === ReserveTransactionType.Recoup &&
- !!t1.expectedAmount &&
- Amounts.cmp(t1.expectedAmount, Amounts.parseOrThrow(t2.amount)) === 0
- );
- }
- }
- return false;
-}
-
-/**
- * Compute totals for the wallet's view of the reserve history.
- */
-export function summarizeReserveHistory(
- localHistory: WalletReserveHistoryItem[],
- currency: string,
-): ReserveHistorySummary {
- const posAmounts: AmountJson[] = [];
- const negAmounts: AmountJson[] = [];
- const expectedPosAmounts: AmountJson[] = [];
- const expectedNegAmounts: AmountJson[] = [];
- const withdrawnAmounts: AmountJson[] = [];
-
- for (const item of localHistory) {
- switch (item.type) {
- case WalletReserveHistoryItemType.Credit:
- if (item.matchedExchangeTransaction) {
- posAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- } else if (item.expectedAmount) {
- expectedPosAmounts.push(item.expectedAmount);
- }
- break;
- case WalletReserveHistoryItemType.Recoup:
- if (item.matchedExchangeTransaction) {
- if (item.matchedExchangeTransaction) {
- posAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- } else if (item.expectedAmount) {
- expectedPosAmounts.push(item.expectedAmount);
- } else {
- throw Error("invariant failed");
- }
- }
- break;
- case WalletReserveHistoryItemType.Closing:
- if (item.matchedExchangeTransaction) {
- negAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- } else {
- throw Error("invariant failed");
- }
- break;
- case WalletReserveHistoryItemType.Withdraw:
- if (item.matchedExchangeTransaction) {
- negAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- withdrawnAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- } else if (item.expectedAmount) {
- expectedNegAmounts.push(item.expectedAmount);
- } else {
- throw Error("invariant failed");
- }
- break;
- }
- }
-
- const z = Amounts.getZero(currency);
-
- const computedBalance = Amounts.sub(
- Amounts.add(z, ...posAmounts).amount,
- ...negAmounts,
- ).amount;
-
- const unclaimedReserveAmount = Amounts.sub(
- Amounts.add(z, ...posAmounts).amount,
- ...negAmounts,
- ...expectedNegAmounts,
- ).amount;
-
- const awaitedReserveAmount = Amounts.sub(
- Amounts.add(z, ...expectedPosAmounts).amount,
- ...expectedNegAmounts,
- ).amount;
-
- const withdrawnAmount = Amounts.add(z, ...withdrawnAmounts).amount;
-
- return {
- computedReserveBalance: computedBalance,
- unclaimedReserveAmount: unclaimedReserveAmount,
- awaitedReserveAmount: awaitedReserveAmount,
- withdrawnAmount,
- };
-}
-
-/**
- * Reconcile the wallet's local model of the reserve history
- * with the reserve history of the exchange.
- */
-export function reconcileReserveHistory(
- localHistory: WalletReserveHistoryItem[],
- remoteHistory: ReserveTransaction[],
-): ReserveReconciliationResult {
- const updatedLocalHistory: WalletReserveHistoryItem[] = deepCopy(
- localHistory,
- );
- const newMatchedItems: WalletReserveHistoryItem[] = [];
- const newAddedItems: WalletReserveHistoryItem[] = [];
-
- const remoteMatched = remoteHistory.map(() => false);
- const localMatched = localHistory.map(() => false);
-
- // Take care of deposits
-
- // First, see which pairs are already a definite match.
- for (let remoteIndex = 0; remoteIndex < remoteHistory.length; remoteIndex++) {
- const rhi = remoteHistory[remoteIndex];
- for (let localIndex = 0; localIndex < localHistory.length; localIndex++) {
- if (localMatched[localIndex]) {
- continue;
- }
- const lhi = localHistory[localIndex];
- if (!lhi.matchedExchangeTransaction) {
- continue;
- }
- if (isRemoteHistoryMatch(rhi, lhi.matchedExchangeTransaction)) {
- localMatched[localIndex] = true;
- remoteMatched[remoteIndex] = true;
- break;
- }
- }
- }
-
- // Check that all previously matched items are still matched
- for (let localIndex = 0; localIndex < localHistory.length; localIndex++) {
- if (localMatched[localIndex]) {
- continue;
- }
- const lhi = localHistory[localIndex];
- if (lhi.matchedExchangeTransaction) {
- // Don't use for further matching
- localMatched[localIndex] = true;
- // FIXME: emit some error here!
- throw Error("previously matched reserve history item now unmatched");
- }
- }
-
- // Next, find out if there are any exact new matches between local and remote
- // history items
- for (let localIndex = 0; localIndex < localHistory.length; localIndex++) {
- if (localMatched[localIndex]) {
- continue;
- }
- const lhi = localHistory[localIndex];
- for (
- let remoteIndex = 0;
- remoteIndex < remoteHistory.length;
- remoteIndex++
- ) {
- const rhi = remoteHistory[remoteIndex];
- if (remoteMatched[remoteIndex]) {
- continue;
- }
- if (isLocalRemoteHistoryMatch(lhi, rhi)) {
- localMatched[localIndex] = true;
- remoteMatched[remoteIndex] = true;
- updatedLocalHistory[localIndex].matchedExchangeTransaction = rhi as any;
- newMatchedItems.push(lhi);
- break;
- }
- }
- }
-
- // Finally we add new history items
- for (let remoteIndex = 0; remoteIndex < remoteHistory.length; remoteIndex++) {
- if (remoteMatched[remoteIndex]) {
- continue;
- }
- const rhi = remoteHistory[remoteIndex];
- let newItem: WalletReserveHistoryItem;
- switch (rhi.type) {
- case ReserveTransactionType.Closing: {
- newItem = {
- type: WalletReserveHistoryItemType.Closing,
- matchedExchangeTransaction: rhi,
- };
- break;
- }
- case ReserveTransactionType.Credit: {
- newItem = {
- type: WalletReserveHistoryItemType.Credit,
- matchedExchangeTransaction: rhi,
- };
- break;
- }
- case ReserveTransactionType.Recoup: {
- newItem = {
- type: WalletReserveHistoryItemType.Recoup,
- matchedExchangeTransaction: rhi,
- };
- break;
- }
- case ReserveTransactionType.Withdraw: {
- newItem = {
- type: WalletReserveHistoryItemType.Withdraw,
- matchedExchangeTransaction: rhi,
- };
- break;
- }
- }
- updatedLocalHistory.push(newItem);
- newAddedItems.push(newItem);
- }
-
- return {
- updatedLocalHistory,
- newAddedItems,
- newMatchedItems,
- };
-}
diff --git a/src/util/talerconfig.ts b/src/util/talerconfig.ts
deleted file mode 100644
index ec08c352f..000000000
--- a/src/util/talerconfig.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems S.A.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Utilities to handle Taler-style configuration files.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-/**
- * Imports
- */
-import { AmountJson } from "./amounts";
-import * as Amounts from "./amounts";
-
-export class ConfigError extends Error {
- constructor(message: string) {
- super();
- Object.setPrototypeOf(this, ConfigError.prototype);
- this.name = "ConfigError";
- this.message = message;
- }
-}
-
-type OptionMap = { [optionName: string]: string };
-type SectionMap = { [sectionName: string]: OptionMap };
-
-export class ConfigValue<T> {
- constructor(
- private sectionName: string,
- private optionName: string,
- private val: string | undefined,
- private converter: (x: string) => T,
- ) {}
-
- required(): T {
- if (!this.val) {
- throw new ConfigError(
- `required option [${this.sectionName}]/${this.optionName} not found`,
- );
- }
- return this.converter(this.val);
- }
-}
-
-export class Configuration {
- private sectionMap: SectionMap = {};
-
- loadFromString(s: string): void {
- const reComment = /^\s*#.*$/;
- const reSection = /^\s*\[\s*([^\]]*)\s*\]\s*$/;
- const reParam = /^\s*([^=]+?)\s*=\s*(.*?)\s*$/;
- const reEmptyLine = /^\s*$/;
-
- let currentSection: string | undefined = undefined;
-
- const lines = s.split("\n");
- for (const line of lines) {
- console.log("parsing line", JSON.stringify(line));
- if (reEmptyLine.test(line)) {
- continue;
- }
- if (reComment.test(line)) {
- continue;
- }
- const secMatch = line.match(reSection);
- if (secMatch) {
- currentSection = secMatch[1];
- console.log("setting section to", currentSection);
- continue;
- }
- if (currentSection === undefined) {
- throw Error("invalid configuration, expected section header");
- }
- const paramMatch = line.match(reParam);
- if (paramMatch) {
- const optName = paramMatch[1];
- let val = paramMatch[2];
- if (val.startsWith('"') && val.endsWith('"')) {
- val = val.slice(1, val.length - 1);
- }
- const sec = this.sectionMap[currentSection] ?? {};
- this.sectionMap[currentSection] = Object.assign(sec, {
- [optName]: val,
- });
- continue;
- }
- throw Error(
- "invalid configuration, expected section header or option assignment",
- );
- }
-
- console.log("parsed config", JSON.stringify(this.sectionMap, undefined, 2));
- }
-
- getString(section: string, option: string): ConfigValue<string> {
- const val = (this.sectionMap[section] ?? {})[option];
- return new ConfigValue(section, option, val, (x) => x);
- }
-
- getAmount(section: string, option: string): ConfigValue<AmountJson> {
- const val = (this.sectionMap[section] ?? {})[option];
- return new ConfigValue(section, option, val, (x) =>
- Amounts.parseOrThrow(x),
- );
- }
-}
diff --git a/src/util/taleruri-test.ts b/src/util/taleruri-test.ts
deleted file mode 100644
index 44edbe1c1..000000000
--- a/src/util/taleruri-test.ts
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import test from "ava";
-import {
- parsePayUri,
- parseWithdrawUri,
- parseRefundUri,
- parseTipUri,
-} from "./taleruri";
-
-test("taler pay url parsing: wrong scheme", (t) => {
- const url1 = "talerfoo://";
- const r1 = parsePayUri(url1);
- t.is(r1, undefined);
-
- const url2 = "taler://refund/a/b/c/d/e/f";
- const r2 = parsePayUri(url2);
- t.is(r2, undefined);
-});
-
-test("taler pay url parsing: defaults", (t) => {
- const url1 = "taler://pay/example.com/myorder/";
- const r1 = parsePayUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "https://example.com/");
- t.is(r1.sessionId, "");
-
- const url2 = "taler://pay/example.com/myorder/mysession";
- const r2 = parsePayUri(url2);
- if (!r2) {
- t.fail();
- return;
- }
- t.is(r2.merchantBaseUrl, "https://example.com/");
- t.is(r2.sessionId, "mysession");
-});
-
-test("taler pay url parsing: instance", (t) => {
- const url1 = "taler://pay/example.com/instances/myinst/myorder/";
- const r1 = parsePayUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "https://example.com/instances/myinst/");
- t.is(r1.orderId, "myorder");
-});
-
-test("taler pay url parsing (claim token)", (t) => {
- const url1 = "taler://pay/example.com/instances/myinst/myorder/?c=ASDF";
- const r1 = parsePayUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "https://example.com/instances/myinst/");
- t.is(r1.orderId, "myorder");
- t.is(r1.claimToken, "ASDF");
-});
-
-test("taler refund uri parsing: non-https #1", (t) => {
- const url1 = "taler+http://refund/example.com/myorder";
- const r1 = parseRefundUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "http://example.com/");
- t.is(r1.orderId, "myorder");
-});
-
-test("taler pay uri parsing: non-https", (t) => {
- const url1 = "taler+http://pay/example.com/myorder/";
- const r1 = parsePayUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "http://example.com/");
- t.is(r1.orderId, "myorder");
-});
-
-test("taler pay uri parsing: missing session component", (t) => {
- const url1 = "taler+http://pay/example.com/myorder";
- const r1 = parsePayUri(url1);
- if (r1) {
- t.fail();
- return;
- }
- t.pass();
-});
-
-test("taler withdraw uri parsing", (t) => {
- const url1 = "taler://withdraw/bank.example.com/12345";
- const r1 = parseWithdrawUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.withdrawalOperationId, "12345");
- t.is(r1.bankIntegrationApiBaseUrl, "https://bank.example.com/");
-});
-
-test("taler withdraw uri parsing (http)", (t) => {
- const url1 = "taler+http://withdraw/bank.example.com/12345";
- const r1 = parseWithdrawUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.withdrawalOperationId, "12345");
- t.is(r1.bankIntegrationApiBaseUrl, "http://bank.example.com/");
-});
-
-test("taler refund uri parsing", (t) => {
- const url1 = "taler://refund/merchant.example.com/1234";
- const r1 = parseRefundUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "https://merchant.example.com/");
- t.is(r1.orderId, "1234");
-});
-
-test("taler refund uri parsing with instance", (t) => {
- const url1 = "taler://refund/merchant.example.com/instances/myinst/1234";
- const r1 = parseRefundUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.orderId, "1234");
- t.is(
- r1.merchantBaseUrl,
- "https://merchant.example.com/instances/myinst/",
- );
-});
-
-test("taler tip pickup uri", (t) => {
- const url1 = "taler://tip/merchant.example.com/tipid";
- const r1 = parseTipUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.merchantBaseUrl, "https://merchant.example.com/");
-});
-
-test("taler tip pickup uri with instance", (t) => {
- const url1 = "taler://tip/merchant.example.com/instances/tipm/tipid";
- const r1 = parseTipUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(
- r1.merchantBaseUrl,
- "https://merchant.example.com/instances/tipm/",
- );
- t.is(r1.merchantTipId, "tipid");
-});
-
-test("taler tip pickup uri with instance and prefix", (t) => {
- const url1 = "taler://tip/merchant.example.com/my/pfx/tipm/tipid";
- const r1 = parseTipUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(
- r1.merchantBaseUrl,
- "https://merchant.example.com/my/pfx/tipm/",
- );
- t.is(r1.merchantTipId, "tipid");
-});
diff --git a/src/util/taleruri.ts b/src/util/taleruri.ts
deleted file mode 100644
index c26c4a5db..000000000
--- a/src/util/taleruri.ts
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019-2020 Taler Systems S.A.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-export interface PayUriResult {
- merchantBaseUrl: string;
- orderId: string;
- sessionId: string;
- claimToken: string | undefined;
-}
-
-export interface WithdrawUriResult {
- bankIntegrationApiBaseUrl: string;
- withdrawalOperationId: string;
-}
-
-export interface RefundUriResult {
- merchantBaseUrl: string;
- orderId: string;
-}
-
-export interface TipUriResult {
- merchantTipId: string;
- merchantBaseUrl: string;
-}
-
-/**
- * Parse a taler[+http]://withdraw URI.
- * Return undefined if not passed a valid URI.
- */
-export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
- const pi = parseProtoInfo(s, "withdraw");
- if (!pi) {
- return undefined;
- }
- const parts = pi.rest.split("/");
-
- if (parts.length < 2) {
- return undefined;
- }
-
- const host = parts[0].toLowerCase();
- const pathSegments = parts.slice(1, parts.length - 1);
- const withdrawId = parts[parts.length - 1];
- const p = [host, ...pathSegments].join("/");
-
- return {
- bankIntegrationApiBaseUrl: `${pi.innerProto}://${p}/`,
- withdrawalOperationId: withdrawId,
- };
-}
-
-export const enum TalerUriType {
- TalerPay = "taler-pay",
- TalerWithdraw = "taler-withdraw",
- TalerTip = "taler-tip",
- TalerRefund = "taler-refund",
- TalerNotifyReserve = "taler-notify-reserve",
- Unknown = "unknown",
-}
-
-/**
- * Classify a taler:// URI.
- */
-export function classifyTalerUri(s: string): TalerUriType {
- const sl = s.toLowerCase();
- if (sl.startsWith("taler://pay/")) {
- return TalerUriType.TalerPay;
- }
- if (sl.startsWith("taler+http://pay/")) {
- return TalerUriType.TalerPay;
- }
- if (sl.startsWith("taler://tip/")) {
- return TalerUriType.TalerTip;
- }
- if (sl.startsWith("taler+http://tip/")) {
- return TalerUriType.TalerTip;
- }
- if (sl.startsWith("taler://refund/")) {
- return TalerUriType.TalerRefund;
- }
- if (sl.startsWith("taler+http://refund/")) {
- return TalerUriType.TalerRefund;
- }
- if (sl.startsWith("taler://withdraw/")) {
- return TalerUriType.TalerWithdraw;
- }
- if (sl.startsWith("taler://notify-reserve/")) {
- return TalerUriType.TalerNotifyReserve;
- }
- return TalerUriType.Unknown;
-}
-
-interface TalerUriProtoInfo {
- innerProto: "http" | "https";
- rest: string;
-}
-
-
-function parseProtoInfo(s: string, action: string): TalerUriProtoInfo | undefined {
- const pfxPlain = `taler://${action}/`;
- const pfxHttp = `taler+http://${action}/`;
- if (s.toLowerCase().startsWith(pfxPlain)) {
- return {
- innerProto: "https",
- rest: s.substring(pfxPlain.length),
- }
- } else if (s.toLowerCase().startsWith(pfxHttp)) {
- return {
- innerProto: "http",
- rest: s.substring(pfxHttp.length),
- }
- } else {
- return undefined;
- }
-}
-
-/**
- * Parse a taler[+http]://pay URI.
- * Return undefined if not passed a valid URI.
- */
-export function parsePayUri(s: string): PayUriResult | undefined {
- const pi = parseProtoInfo(s, "pay");
- if (!pi) {
- return undefined;
- }
- const c = pi?.rest.split("?");
- const q = new URLSearchParams(c[1] ?? "");
- const claimToken = q.get("c") ?? undefined;
- const parts = c[0].split("/");
- if (parts.length < 3) {
- return undefined;
- }
- const host = parts[0].toLowerCase();
- const sessionId = parts[parts.length - 1];
- const orderId = parts[parts.length - 2];
- const pathSegments = parts.slice(1, parts.length - 2);
- const p = [host, ...pathSegments].join("/");
- const merchantBaseUrl = `${pi.innerProto}://${p}/`;
-
- return {
- merchantBaseUrl,
- orderId,
- sessionId: sessionId,
- claimToken,
- };
-}
-
-/**
- * Parse a taler[+http]://tip URI.
- * Return undefined if not passed a valid URI.
- */
-export function parseTipUri(s: string): TipUriResult | undefined {
- const pi = parseProtoInfo(s, "tip");
- if (!pi) {
- return undefined;
- }
- const c = pi?.rest.split("?");
- const parts = c[0].split("/");
- if (parts.length < 2) {
- return undefined;
- }
- const host = parts[0].toLowerCase();
- const tipId = parts[parts.length - 1];
- const pathSegments = parts.slice(1, parts.length - 1);
- const p = [host, ...pathSegments].join("/");
- const merchantBaseUrl = `${pi.innerProto}://${p}/`;
-
- return {
- merchantBaseUrl,
- merchantTipId: tipId,
- };
-}
-
-/**
- * Parse a taler[+http]://refund URI.
- * Return undefined if not passed a valid URI.
- */
-export function parseRefundUri(s: string): RefundUriResult | undefined {
- const pi = parseProtoInfo(s, "refund");
- if (!pi) {
- return undefined;
- }
- const c = pi?.rest.split("?");
- const parts = c[0].split("/");
- if (parts.length < 2) {
- return undefined;
- }
- const host = parts[0].toLowerCase();
- const orderId = parts[parts.length - 1];
- const pathSegments = parts.slice(1, parts.length - 1);
- const p = [host, ...pathSegments].join("/");
- const merchantBaseUrl = `${pi.innerProto}://${p}/`;
-
- return {
- merchantBaseUrl,
- orderId,
- };
-}
diff --git a/src/util/time.ts b/src/util/time.ts
deleted file mode 100644
index 5c2f49d12..000000000
--- a/src/util/time.ts
+++ /dev/null
@@ -1,198 +0,0 @@
-import { Codec, renderContext, Context } from "./codec";
-
-/*
- This file is part of GNU Taler
- (C) 2017-2019 Taler Systems S.A.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Helpers for relative and absolute time.
- */
-
-export class Timestamp {
- /**
- * Timestamp in milliseconds.
- */
- readonly t_ms: number | "never";
-}
-
-export interface Duration {
- /**
- * Duration in milliseconds.
- */
- readonly d_ms: number | "forever";
-}
-
-let timeshift = 0;
-
-export function setDangerousTimetravel(dt: number): void {
- timeshift = dt;
-}
-
-export function getTimestampNow(): Timestamp {
- return {
- t_ms: new Date().getTime() + timeshift,
- };
-}
-
-export function getDurationRemaining(
- deadline: Timestamp,
- now = getTimestampNow(),
-): Duration {
- if (deadline.t_ms === "never") {
- return { d_ms: "forever" };
- }
- if (now.t_ms === "never") {
- throw Error("invalid argument for 'now'");
- }
- if (deadline.t_ms < now.t_ms) {
- return { d_ms: 0 };
- }
- return { d_ms: deadline.t_ms - now.t_ms };
-}
-
-export function timestampMin(t1: Timestamp, t2: Timestamp): Timestamp {
- if (t1.t_ms === "never") {
- return { t_ms: t2.t_ms };
- }
- if (t2.t_ms === "never") {
- return { t_ms: t2.t_ms };
- }
- return { t_ms: Math.min(t1.t_ms, t2.t_ms) };
-}
-
-/**
- * Truncate a timestamp so that that it represents a multiple
- * of seconds. The timestamp is always rounded down.
- */
-export function timestampTruncateToSecond(t1: Timestamp): Timestamp {
- if (t1.t_ms === "never") {
- return { t_ms: "never" };
- }
- return {
- t_ms: Math.floor(t1.t_ms / 1000) * 1000,
- };
-}
-
-export function durationMin(d1: Duration, d2: Duration): Duration {
- if (d1.d_ms === "forever") {
- return { d_ms: d2.d_ms };
- }
- if (d2.d_ms === "forever") {
- return { d_ms: d2.d_ms };
- }
- return { d_ms: Math.min(d1.d_ms, d2.d_ms) };
-}
-
-export function timestampCmp(t1: Timestamp, t2: Timestamp): number {
- if (t1.t_ms === "never") {
- if (t2.t_ms === "never") {
- return 0;
- }
- return 1;
- }
- if (t2.t_ms === "never") {
- return -1;
- }
- if (t1.t_ms == t2.t_ms) {
- return 0;
- }
- if (t1.t_ms > t2.t_ms) {
- return 1;
- }
- return -1;
-}
-
-export function timestampAddDuration(t1: Timestamp, d: Duration): Timestamp {
- if (t1.t_ms === "never" || d.d_ms === "forever") {
- return { t_ms: "never" };
- }
- return { t_ms: t1.t_ms + d.d_ms };
-}
-
-export function timestampSubtractDuraction(
- t1: Timestamp,
- d: Duration,
-): Timestamp {
- if (t1.t_ms === "never") {
- return { t_ms: "never" };
- }
- if (d.d_ms === "forever") {
- return { t_ms: 0 };
- }
- return { t_ms: Math.max(0, t1.t_ms - d.d_ms) };
-}
-
-export function stringifyTimestamp(t: Timestamp): string {
- if (t.t_ms === "never") {
- return "never";
- }
- return new Date(t.t_ms).toISOString();
-}
-
-export function timestampDifference(t1: Timestamp, t2: Timestamp): Duration {
- if (t1.t_ms === "never") {
- return { d_ms: "forever" };
- }
- if (t2.t_ms === "never") {
- return { d_ms: "forever" };
- }
- return { d_ms: Math.abs(t1.t_ms - t2.t_ms) };
-}
-
-export function timestampIsBetween(
- t: Timestamp,
- start: Timestamp,
- end: Timestamp,
-): boolean {
- if (timestampCmp(t, start) < 0) {
- return false;
- }
- if (timestampCmp(t, end) > 0) {
- return false;
- }
- return true;
-}
-
-export const codecForTimestamp: Codec<Timestamp> = {
- decode(x: any, c?: Context): Timestamp {
- const t_ms = x.t_ms;
- if (typeof t_ms === "string") {
- if (t_ms === "never") {
- return { t_ms: "never" };
- }
- throw Error(`expected timestamp at ${renderContext(c)}`);
- }
- if (typeof t_ms === "number") {
- return { t_ms };
- }
- throw Error(`expected timestamp at ${renderContext(c)}`);
- },
-};
-
-export const codecForDuration: Codec<Duration> = {
- decode(x: any, c?: Context): Duration {
- const d_ms = x.d_ms;
- if (typeof d_ms === "string") {
- if (d_ms === "forever") {
- return { d_ms: "forever" };
- }
- throw Error(`expected duration at ${renderContext(c)}`);
- }
- if (typeof d_ms === "number") {
- return { d_ms };
- }
- throw Error(`expected duration at ${renderContext(c)}`);
- },
-};
diff --git a/src/util/timer.ts b/src/util/timer.ts
deleted file mode 100644
index 5f37a6f4d..000000000
--- a/src/util/timer.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2017-2019 Taler Systems S.A.
-
- GNU 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.
-
- GNU 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
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Cross-platform timers.
- *
- * NodeJS and the browser use slightly different timer API,
- * this abstracts over these differences.
- */
-
-/**
- * Imports.
- */
-import { Duration } from "./time";
-import { Logger } from "./logging";
-
-const logger = new Logger("timer.ts");
-
-/**
- * Cancelable timer.
- */
-export interface TimerHandle {
- clear(): void;
-}
-
-class IntervalHandle {
- constructor(public h: any) {}
-
- clear(): void {
- clearInterval(this.h);
- }
-}
-
-class TimeoutHandle {
- constructor(public h: any) {}
-
- clear(): void {
- clearTimeout(this.h);
- }
-}
-
-/**
- * Get a performance counter in milliseconds.
- */
-export const performanceNow: () => number = (() => {
- if (typeof process !== "undefined" && process.hrtime) {
- return () => {
- const t = process.hrtime();
- return t[0] * 1e9 + t[1];
- };
- } else if (typeof performance !== "undefined") {
- return () => performance.now();
- } else {
- return () => 0;
- }
-})();
-
-/**
- * Call a function every time the delay given in milliseconds passes.
- */
-export function every(delayMs: number, callback: () => void): TimerHandle {
- return new IntervalHandle(setInterval(callback, delayMs));
-}
-
-/**
- * Call a function after the delay given in milliseconds passes.
- */
-export function after(delayMs: number, callback: () => void): TimerHandle {
- return new TimeoutHandle(setTimeout(callback, delayMs));
-}
-
-const nullTimerHandle = {
- clear() {
- // do nothing
- return;
- },
-};
-
-/**
- * Group of timers that can be destroyed at once.
- */
-export class TimerGroup {
- private stopped = false;
-
- private timerMap: { [index: number]: TimerHandle } = {};
-
- private idGen = 1;
-
- stopCurrentAndFutureTimers(): void {
- this.stopped = true;
- for (const x in this.timerMap) {
- if (!this.timerMap.hasOwnProperty(x)) {
- continue;
- }
- this.timerMap[x].clear();
- delete this.timerMap[x];
- }
- }
-
- resolveAfter(delayMs: Duration): Promise<void> {
- return new Promise<void>((resolve, reject) => {
- if (delayMs.d_ms !== "forever") {
- this.after(delayMs.d_ms, () => {
- resolve();
- });
- }
- });
- }
-
- after(delayMs: number, callback: () => void): TimerHandle {
- if (this.stopped) {
- logger.warn("dropping timer since timer group is stopped");
- return nullTimerHandle;
- }
- const h = after(delayMs, callback);
- const myId = this.idGen++;
- this.timerMap[myId] = h;
-
- const tm = this.timerMap;
-
- return {
- clear() {
- h.clear();
- delete tm[myId];
- },
- };
- }
-
- every(delayMs: number, callback: () => void): TimerHandle {
- if (this.stopped) {
- logger.warn("dropping timer since timer group is stopped");
- return nullTimerHandle;
- }
- const h = every(delayMs, callback);
- const myId = this.idGen++;
- this.timerMap[myId] = h;
-
- const tm = this.timerMap;
-
- return {
- clear() {
- h.clear();
- delete tm[myId];
- },
- };
- }
-}
diff --git a/src/util/wire.ts b/src/util/wire.ts
deleted file mode 100644
index 21ad600fc..000000000
--- a/src/util/wire.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 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/>
- */
-
-/**
- * Display and manipulate wire information.
- *
- * Right now, all types are hard-coded. In the future, there might be plugins / configurable
- * methods or support for the "payto://" URI scheme.
- */
-
-/**
- * Imports.
- */
-import * as i18n from "../webex/i18n";
-
-/**
- * Short summary of the wire information.
- *
- * Might abbreviate and return the same summary for different
- * wire details.
- */
-export function summarizeWire(w: any): string {
- if (!w.type) {
- return i18n.str`Invalid Wire`;
- }
- switch (w.type.toLowerCase()) {
- case "test":
- if (!w.account_number && w.account_number !== 0) {
- return i18n.str`Invalid Test Wire Detail`;
- }
- if (!w.bank_uri) {
- return i18n.str`Invalid Test Wire Detail`;
- }
- return i18n.str`Test Wire Acct #${w.account_number} on ${w.bank_uri}`;
- default:
- return i18n.str`Unknown Wire Detail`;
- }
-}