aboutsummaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/RequestThrottler.ts14
-rw-r--r--src/util/amounts.ts34
-rw-r--r--src/util/checkable.ts417
-rw-r--r--src/util/codec.ts54
-rw-r--r--src/util/helpers.ts71
-rw-r--r--src/util/time.ts165
-rw-r--r--src/util/timer.ts22
7 files changed, 257 insertions, 520 deletions
diff --git a/src/util/RequestThrottler.ts b/src/util/RequestThrottler.ts
index 01695ec37..0566306de 100644
--- a/src/util/RequestThrottler.ts
+++ b/src/util/RequestThrottler.ts
@@ -21,7 +21,7 @@
/**
* Imports.
*/
-import { getTimestampNow, Timestamp } from "../types/walletTypes";
+import { getTimestampNow, Timestamp, timestampSubtractDuraction, timestampDifference } from "../util/time";
/**
* Maximum request per second, per origin.
@@ -50,10 +50,14 @@ class OriginState {
private refill(): void {
const now = getTimestampNow();
- const d = now.t_ms - this.lastUpdate.t_ms;
- this.tokensSecond = Math.min(MAX_PER_SECOND, this.tokensSecond + (d / 1000));
- this.tokensMinute = Math.min(MAX_PER_MINUTE, this.tokensMinute + (d / 1000 * 60));
- this.tokensHour = Math.min(MAX_PER_HOUR, this.tokensHour + (d / 1000 * 60 * 60));
+ 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;
}
diff --git a/src/util/amounts.ts b/src/util/amounts.ts
index c8fb76793..c85c4839a 100644
--- a/src/util/amounts.ts
+++ b/src/util/amounts.ts
@@ -1,17 +1,17 @@
/*
- This file is part of TALER
- (C) 2018 GNUnet e.V. and INRIA
+ This file is part of GNU Taler
+ (C) 2019 Taler Systems S.A.
- TALER is free software; you can redistribute it and/or modify it under the
+ 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
+ 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
@@ -21,7 +21,12 @@
/**
* Imports.
*/
-import { Checkable } from "./checkable";
+import {
+ typecheckedCodec,
+ makeCodecForObject,
+ codecForString,
+ codecForNumber,
+} from "./codec";
/**
* Number of fractional units that one value unit represents.
@@ -44,29 +49,32 @@ export const maxAmountValue = 2 ** 52;
* Non-negative financial amount. Fractional values are expressed as multiples
* of 1e-8.
*/
-@Checkable.Class()
-export class AmountJson {
+export interface AmountJson {
/**
* Value, must be an integer.
*/
- @Checkable.Number()
readonly value: number;
/**
* Fraction, must be an integer. Represent 1/1e8 of a unit.
*/
- @Checkable.Number()
readonly fraction: number;
/**
* Currency of the amount.
*/
- @Checkable.String()
readonly currency: string;
-
- static checked: (obj: any) => AmountJson;
}
+export const codecForAmountJson = () =>
+ typecheckedCodec<AmountJson>(
+ makeCodecForObject<AmountJson>()
+ .property("currency", codecForString)
+ .property("value", codecForNumber)
+ .property("fraction", codecForNumber)
+ .build("AmountJson"),
+ );
+
/**
* Result of a possibly overflowing operation.
*/
diff --git a/src/util/checkable.ts b/src/util/checkable.ts
deleted file mode 100644
index 3c9fe5bc1..000000000
--- a/src/util/checkable.ts
+++ /dev/null
@@ -1,417 +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/>
- */
-
-
-/**
- * Decorators for validating JSON objects and converting them to a typed
- * object.
- *
- * The decorators are put onto classes, and the validation is done
- * via a static method that is filled in by the annotation.
- *
- * Example:
- * ```
- * @Checkable.Class
- * class Person {
- * @Checkable.String
- * name: string;
- * @Checkable.Number
- * age: number;
- *
- * // Method will be implemented automatically
- * static checked(obj: any): Person;
- * }
- * ```
- */
-export namespace Checkable {
-
- type Path = Array<number | string>;
-
- interface SchemaErrorConstructor {
- new (err: string): SchemaError;
- }
-
- interface SchemaError {
- name: string;
- message: string;
- }
-
- interface Prop {
- propertyKey: any;
- checker: any;
- type?: any;
- typeThunk?: () => any;
- elementChecker?: any;
- elementProp?: any;
- keyProp?: any;
- stringChecker?: (s: string) => boolean;
- valueProp?: any;
- optional?: boolean;
- }
-
- interface CheckableInfo {
- extraAllowed: boolean;
- props: Prop[];
- }
-
- // tslint:disable-next-line:no-shadowed-variable
- export const SchemaError = (function SchemaError(this: any, message: string) {
- const that: any = this as any;
- that.name = "SchemaError";
- that.message = message;
- that.stack = (new Error() as any).stack;
- }) as any as SchemaErrorConstructor;
-
-
- SchemaError.prototype = new Error();
-
- /**
- * Classes that are checkable are annotated with this
- * checkable info symbol, which contains the information necessary
- * to check if they're valid.
- */
- const checkableInfoSym = Symbol("checkableInfo");
-
- /**
- * Get the current property list for a checkable type.
- */
- function getCheckableInfo(target: any): CheckableInfo {
- let chk = target[checkableInfoSym] as CheckableInfo|undefined;
- if (!chk) {
- chk = { props: [], extraAllowed: false };
- target[checkableInfoSym] = chk;
- }
- return chk;
- }
-
-
- function checkNumber(target: any, prop: Prop, path: Path): any {
- if ((typeof target) !== "number") {
- throw new SchemaError(`expected number for ${path}`);
- }
- return target;
- }
-
-
- function checkString(target: any, prop: Prop, path: Path): any {
- if (typeof target !== "string") {
- throw new SchemaError(`expected string for ${path}, got ${typeof target} instead`);
- }
- if (prop.stringChecker && !prop.stringChecker(target)) {
- throw new SchemaError(`string property ${path} malformed`);
- }
- return target;
- }
-
- function checkBoolean(target: any, prop: Prop, path: Path): any {
- if (typeof target !== "boolean") {
- throw new SchemaError(`expected boolean for ${path}, got ${typeof target} instead`);
- }
- return target;
- }
-
-
- function checkAnyObject(target: any, prop: Prop, path: Path): any {
- if (typeof target !== "object") {
- throw new SchemaError(`expected (any) object for ${path}, got ${typeof target} instead`);
- }
- return target;
- }
-
-
- function checkAny(target: any, prop: Prop, path: Path): any {
- return target;
- }
-
-
- function checkList(target: any, prop: Prop, path: Path): any {
- if (!Array.isArray(target)) {
- throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`);
- }
- for (let i = 0; i < target.length; i++) {
- const v = target[i];
- prop.elementChecker(v, prop.elementProp, path.concat([i]));
- }
- return target;
- }
-
- function checkMap(target: any, prop: Prop, path: Path): any {
- if (typeof target !== "object") {
- throw new SchemaError(`expected object for ${path}, got ${typeof target} instead`);
- }
- for (const key in target) {
- prop.keyProp.checker(key, prop.keyProp, path.concat([key]));
- const value = target[key];
- prop.valueProp.checker(value, prop.valueProp, path.concat([key]));
- }
- return target;
- }
-
-
- function checkOptional(target: any, prop: Prop, path: Path): any {
- console.assert(prop.propertyKey);
- prop.elementChecker(target,
- prop.elementProp,
- path.concat([prop.propertyKey]));
- return target;
- }
-
-
- function checkValue(target: any, prop: Prop, path: Path): any {
- let type;
- if (prop.type) {
- type = prop.type;
- } else if (prop.typeThunk) {
- type = prop.typeThunk();
- if (!type) {
- throw Error(`assertion failed: typeThunk returned null (prop is ${JSON.stringify(prop)})`);
- }
- } else {
- throw Error(`assertion failed: type/typeThunk missing (prop is ${JSON.stringify(prop)})`);
- }
- const typeName = type.name || "??";
- const v = target;
- if (!v || typeof v !== "object") {
- throw new SchemaError(
- `expected object for ${path.join(".")}, got ${typeof v} instead`);
- }
- const chk = type.prototype[checkableInfoSym];
- const props = chk.props;
- const remainingPropNames = new Set(Object.getOwnPropertyNames(v));
- const obj = new type();
- for (const innerProp of props) {
- if (!remainingPropNames.has(innerProp.propertyKey)) {
- if (innerProp.optional) {
- continue;
- }
- throw new SchemaError(`Property '${innerProp.propertyKey}' missing on '${path}' of '${typeName}'`);
- }
- if (!remainingPropNames.delete(innerProp.propertyKey)) {
- throw new SchemaError("assertion failed");
- }
- const propVal = v[innerProp.propertyKey];
- obj[innerProp.propertyKey] = innerProp.checker(propVal,
- innerProp,
- path.concat([innerProp.propertyKey]));
- }
-
- if (!chk.extraAllowed && remainingPropNames.size !== 0) {
- const err = `superfluous properties ${JSON.stringify(Array.from(remainingPropNames.values()))} of ${typeName}`;
- throw new SchemaError(err);
- }
- return obj;
- }
-
-
- /**
- * Class with checkable annotations on fields.
- * This annotation adds the implementation of the `checked`
- * static method.
- */
- export function Class(opts: {extra?: boolean, validate?: boolean} = {}) {
- return (target: any) => {
- const chk = getCheckableInfo(target.prototype);
- chk.extraAllowed = !!opts.extra;
- target.checked = (v: any) => {
- const cv = checkValue(v, {
- checker: checkValue,
- propertyKey: "(root)",
- type: target,
- }, ["(root)"]);
- if (opts.validate) {
- if (typeof target.validate !== "function") {
- throw Error("invalid Checkable annotion: validate method required");
- }
- // May throw exception
- target.validate(cv);
- }
- return cv;
- };
- return target;
- };
- }
-
-
- /**
- * Target property must be a Checkable object of the given type.
- */
- export function Value(typeThunk: () => any) {
- function deco(target: object, propertyKey: string | symbol): void {
- const chk = getCheckableInfo(target);
- chk.props.push({
- checker: checkValue,
- propertyKey,
- typeThunk,
- });
- }
-
- return deco;
- }
-
-
- /**
- * List of values that match the given annotation. For example, `@Checkable.List(Checkable.String)` is
- * an annotation for a list of strings.
- */
- export function List(type: any) {
- const stub = {};
- type(stub, "(list-element)");
- const elementProp = getCheckableInfo(stub).props[0];
- const elementChecker = elementProp.checker;
- if (!elementChecker) {
- throw Error("assertion failed");
- }
- function deco(target: object, propertyKey: string | symbol): void {
- const chk = getCheckableInfo(target);
- chk.props.push({
- checker: checkList,
- elementChecker,
- elementProp,
- propertyKey,
- });
- }
-
- return deco;
- }
-
-
- /**
- * Map from the key type to value type. Takes two annotations,
- * one for the key type and one for the value type.
- */
- export function Map(keyType: any, valueType: any) {
- const keyStub = {};
- keyType(keyStub, "(map-key)");
- const keyProp = getCheckableInfo(keyStub).props[0];
- if (!keyProp) {
- throw Error("assertion failed");
- }
- const valueStub = {};
- valueType(valueStub, "(map-value)");
- const valueProp = getCheckableInfo(valueStub).props[0];
- if (!valueProp) {
- throw Error("assertion failed");
- }
- function deco(target: object, propertyKey: string | symbol): void {
- const chk = getCheckableInfo(target);
- chk.props.push({
- checker: checkMap,
- keyProp,
- propertyKey,
- valueProp,
- });
- }
-
- return deco;
- }
-
-
- /**
- * Makes another annotation optional, for example `@Checkable.Optional(Checkable.Number)`.
- */
- export function Optional(type: (target: object, propertyKey: string | symbol) => void | any) {
- const stub = {};
- type(stub, "(optional-element)");
- const elementProp = getCheckableInfo(stub).props[0];
- const elementChecker = elementProp.checker;
- if (!elementChecker) {
- throw Error("assertion failed");
- }
- function deco(target: object, propertyKey: string | symbol): void {
- const chk = getCheckableInfo(target);
- chk.props.push({
- checker: checkOptional,
- elementChecker,
- elementProp,
- optional: true,
- propertyKey,
- });
- }
-
- return deco;
- }
-
-
- /**
- * Target property must be a number.
- */
- export function Number(): (target: object, propertyKey: string | symbol) => void {
- const deco = (target: object, propertyKey: string | symbol) => {
- const chk = getCheckableInfo(target);
- chk.props.push({checker: checkNumber, propertyKey});
- };
- return deco;
- }
-
-
- /**
- * Target property must be an arbitary object.
- */
- export function AnyObject(): (target: object, propertyKey: string | symbol) => void {
- const deco = (target: object, propertyKey: string | symbol) => {
- const chk = getCheckableInfo(target);
- chk.props.push({
- checker: checkAnyObject,
- propertyKey,
- });
- };
- return deco;
- }
-
-
- /**
- * Target property can be anything.
- *
- * Not useful by itself, but in combination with higher-order annotations
- * such as List or Map.
- */
- export function Any(): (target: object, propertyKey: string | symbol) => void {
- const deco = (target: object, propertyKey: string | symbol) => {
- const chk = getCheckableInfo(target);
- chk.props.push({
- checker: checkAny,
- optional: true,
- propertyKey,
- });
- };
- return deco;
- }
-
-
- /**
- * Target property must be a string.
- */
- export function String(
- stringChecker?: (s: string) => boolean): (target: object, propertyKey: string | symbol,
- ) => void {
- const deco = (target: object, propertyKey: string | symbol) => {
- const chk = getCheckableInfo(target);
- chk.props.push({ checker: checkString, propertyKey, stringChecker });
- };
- return deco;
- }
-
- /**
- * Target property must be a boolean value.
- */
- export function Boolean(): (target: object, propertyKey: string | symbol) => void {
- const deco = (target: object, propertyKey: string | symbol) => {
- const chk = getCheckableInfo(target);
- chk.props.push({ checker: checkBoolean, propertyKey });
- };
- return deco;
- }
-}
diff --git a/src/util/codec.ts b/src/util/codec.ts
index a13816c59..e18a5e74c 100644
--- a/src/util/codec.ts
+++ b/src/util/codec.ts
@@ -32,11 +32,11 @@ export class DecodingError extends Error {
/**
* Context information to show nicer error messages when decoding fails.
*/
-interface Context {
+export interface Context {
readonly path?: string[];
}
-function renderContext(c?: Context): string {
+export function renderContext(c?: Context): string {
const p = c?.path;
if (p) {
return p.join(".");
@@ -84,6 +84,9 @@ class ObjectCodecBuilder<OutputType, PartialOutputType> {
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;
}
@@ -143,6 +146,9 @@ class UnionCodecBuilder<
CommonBaseType,
PartialTargetType | V
> {
+ if (!codec) {
+ throw Error("inner codec must be defined");
+ }
this.alternatives.set(tagValue, { codec, tagValue });
return this as any;
}
@@ -215,6 +221,9 @@ export function makeCodecForUnion<T>(): UnionCodecPreBuilder<T> {
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 } = {};
@@ -233,6 +242,9 @@ export function makeCodecForMap<T>(
* 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[] = [];
@@ -255,7 +267,19 @@ export const codecForNumber: Codec<number> = {
if (typeof x === "number") {
return x;
}
- throw new DecodingError(`expected number at ${renderContext(c)}`);
+ 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}`);
},
};
@@ -267,7 +291,16 @@ export const codecForString: Codec<string> = {
if (typeof x === "string") {
return x;
}
- throw new DecodingError(`expected string at ${renderContext(c)}`);
+ 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;
},
};
@@ -281,12 +314,23 @@ export function makeCodecForConstString<V extends string>(s: V): Codec<V> {
return x;
}
throw new DecodingError(
- `expected string constant "${s}" at ${renderContext(c)}`,
+ `expected string constant "${s}" 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);
+ }
+ }
+}
+
export function typecheckedCodec<T = undefined>(c: Codec<T>): Codec<T> {
return c;
}
diff --git a/src/util/helpers.ts b/src/util/helpers.ts
index 8136f44fa..722688d35 100644
--- a/src/util/helpers.ts
+++ b/src/util/helpers.ts
@@ -24,8 +24,6 @@
import { AmountJson } from "./amounts";
import * as Amounts from "./amounts";
-import { Timestamp, Duration } from "../types/walletTypes";
-
/**
* Show an amount in a form suitable for the user.
* FIXME: In the future, this should consider currency-specific
@@ -114,75 +112,6 @@ export function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] {
return xs.reduce((acc: U[], next: T) => [...f(next), ...acc], []);
}
-
-/**
- * Extract a numeric timstamp (in seconds) from the Taler date format
- * ("/Date([n])/"). Returns null if input is not in the right format.
- */
-export function getTalerStampSec(stamp: string): number | null {
- const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/);
- if (!m || !m[1]) {
- return null;
- }
- return parseInt(m[1], 10);
-}
-
-/**
- * Extract a timestamp from a Taler timestamp string.
- */
-export function extractTalerStamp(stamp: string): Timestamp | undefined {
- const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/);
- if (!m || !m[1]) {
- return undefined;
- }
- return {
- t_ms: parseInt(m[1], 10) * 1000,
- };
-}
-
-/**
- * Extract a timestamp from a Taler timestamp string.
- */
-export function extractTalerStampOrThrow(stamp: string): Timestamp {
- const r = extractTalerStamp(stamp);
- if (!r) {
- throw Error("invalid time stamp");
- }
- return r;
-}
-
-/**
- * Extract a duration from a Taler duration string.
- */
-export function extractTalerDuration(duration: string): Duration | undefined {
- const m = duration.match(/\/?Delay\(([0-9]*)\)\/?/);
- if (!m || !m[1]) {
- return undefined;
- }
- return {
- d_ms: parseInt(m[1], 10) * 1000,
- };
-}
-
-/**
- * Extract a duration from a Taler duration string.
- */
-export function extractTalerDurationOrThrow(duration: string): Duration {
- const r = extractTalerDuration(duration);
- if (!r) {
- throw Error("invalid duration");
- }
- return r;
-}
-
-/**
- * Check if a timestamp is in the right format.
- */
-export function timestampCheck(stamp: string): boolean {
- return getTalerStampSec(stamp) !== null;
-}
-
-
/**
* Compute the hash function of a JSON object.
*/
diff --git a/src/util/time.ts b/src/util/time.ts
new file mode 100644
index 000000000..54d22bf81
--- /dev/null
+++ b/src/util/time.ts
@@ -0,0 +1,165 @@
+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";
+}
+
+export function getTimestampNow(): Timestamp {
+ return {
+ t_ms: new Date().getTime(),
+ };
+}
+
+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) };
+}
+
+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) {
+ 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 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
index 865c17faf..000f36608 100644
--- a/src/util/timer.ts
+++ b/src/util/timer.ts
@@ -1,17 +1,19 @@
+import { Duration } from "./time";
+
/*
- This file is part of TALER
- (C) 2017 GNUnet e.V.
+ This file is part of GNU Taler
+ (C) 2017-2019 Taler Systems S.A.
- TALER is free software; you can redistribute it and/or modify it under the
+ 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
+ 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
@@ -105,11 +107,13 @@ export class TimerGroup {
}
}
- resolveAfter(delayMs: number): Promise<void> {
+ resolveAfter(delayMs: Duration): Promise<void> {
return new Promise<void>((resolve, reject) => {
- this.after(delayMs, () => {
- resolve();
- });
+ if (delayMs.d_ms !== "forever") {
+ this.after(delayMs.d_ms, () => {
+ resolve();
+ });
+ }
});
}