From 96d9ea3840626f71efe38018b75748d8338565fa Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 26 May 2023 13:52:00 +0200 Subject: taler-util,wallet-core: make AbsoluteTime opaque --- .../src/components/Transactions/state.ts | 4 +- .../src/components/Transactions/stories.tsx | 5 +- .../src/handlers/InputDate.tsx | 4 +- .../src/pages/AntiMoneyLaunderingForm.tsx | 5 +- packages/taler-util/src/http-common.ts | 4 +- packages/taler-util/src/time.ts | 66 +++++++++++++++------- packages/taler-util/src/wallet-types.ts | 3 +- packages/taler-wallet-core/src/db.ts | 2 + .../taler-wallet-core/src/operations/attention.ts | 4 +- .../taler-wallet-core/src/util/denominations.ts | 4 +- packages/taler-wallet-core/src/util/retries.ts | 4 +- .../src/cta/InvoicePay/stories.tsx | 12 ++-- .../src/cta/TransferPickup/stories.tsx | 7 ++- .../src/wallet/Backup.stories.tsx | 24 ++++---- .../src/wallet/DeveloperPage.stories.tsx | 6 +- .../src/wallet/DeveloperPage.tsx | 3 +- .../src/wallet/History.tsx | 3 +- .../src/wallet/Notifications/stories.tsx | 7 ++- .../src/wallet/Notifications/views.tsx | 9 ++- .../src/wallet/ProviderDetail.stories.tsx | 24 ++------ 20 files changed, 109 insertions(+), 91 deletions(-) (limited to 'packages') diff --git a/packages/demobank-ui/src/components/Transactions/state.ts b/packages/demobank-ui/src/components/Transactions/state.ts index 4f99ed678..09c039055 100644 --- a/packages/demobank-ui/src/components/Transactions/state.ts +++ b/packages/demobank-ui/src/components/Transactions/state.ts @@ -70,9 +70,7 @@ export function useComponentState({ account }: Props): State { } const when: AbsoluteTime = !date ? AbsoluteTime.never() - : { - t_ms: date, - }; + : AbsoluteTime.fromMilliseconds(date); const amount = Amounts.parse(`${anyItem.currency}:${anyItem.amount}`); const subject = anyItem.subject; return { diff --git a/packages/demobank-ui/src/components/Transactions/stories.tsx b/packages/demobank-ui/src/components/Transactions/stories.tsx index 2dedeb01c..17e234cc7 100644 --- a/packages/demobank-ui/src/components/Transactions/stories.tsx +++ b/packages/demobank-ui/src/components/Transactions/stories.tsx @@ -21,6 +21,7 @@ import * as tests from "@gnu-taler/web-util/testing"; import { ReadyView } from "./views.js"; +import { AbsoluteTime } from "@gnu-taler/taler-util"; export default { title: "transaction list", @@ -37,9 +38,7 @@ export const Ready = tests.createExample(ReadyView, { counterpart: "ASD", negative: false, subject: "Some", - when: { - t_ms: new Date().getTime(), - }, + when: AbsoluteTime.now(), }, ], }); diff --git a/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx b/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx index e834b6cdb..1fd81aad9 100644 --- a/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx +++ b/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx @@ -18,9 +18,9 @@ export function InputDate( converter={{ //@ts-ignore fromStringUI: (v): AbsoluteTime => { - if (!v) return { t_ms: "never" }; + if (!v) return AbsoluteTime.never(); const t_ms = parse(v, pattern, Date.now()).getTime(); - return { t_ms }; + return AbsoluteTime.fromMilliseconds(t_ms); }, //@ts-ignore toStringUI: (v: AbsoluteTime) => { diff --git a/packages/exchange-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx b/packages/exchange-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx index ee9d1ce30..fc5838dd9 100644 --- a/packages/exchange-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx +++ b/packages/exchange-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx @@ -9,6 +9,7 @@ import { v1 as form_902_4e_v1 } from "../forms/902_4e.js"; import { v1 as form_902_5e_v1 } from "../forms/902_5e.js"; import { v1 as form_902_9e_v1 } from "../forms/902_9e.js"; import { DocumentDuplicateIcon } from "@heroicons/react/24/solid"; +import { AbsoluteTime } from "@gnu-taler/taler-util"; export function AntiMoneyLaunderingForm({ number }: { number?: string }) { const selectedForm = Number.parseInt(number ?? "0", 10); @@ -18,9 +19,7 @@ export function AntiMoneyLaunderingForm({ number }: { number?: string }) { const showingFrom = allForms[selectedForm].impl; const storedValue = { fullName: "loggedIn_user_fullname", - when: { - t_ms: new Date().getTime(), - }, + when: AbsoluteTime.now(), }; return ( {}} /> diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts index 8da4003b5..4f8f12789 100644 --- a/packages/taler-util/src/http-common.ts +++ b/packages/taler-util/src/http-common.ts @@ -410,9 +410,7 @@ export function getExpiry( if (Number.isNaN(expiryDateMs)) { t = AbsoluteTime.now(); } else { - t = { - t_ms: expiryDateMs, - }; + t = AbsoluteTime.fromMilliseconds(expiryDateMs); } if (opt.minDuration) { const t2 = AbsoluteTime.addDuration(AbsoluteTime.now(), opt.minDuration); diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts index 697bde5c0..717da7ecd 100644 --- a/packages/taler-util/src/time.ts +++ b/packages/taler-util/src/time.ts @@ -27,6 +27,8 @@ declare const flavor_AbsoluteTime: unique symbol; declare const flavor_TalerProtocolTimestamp: unique symbol; declare const flavor_TalerPreciseTimestamp: unique symbol; +const opaque_AbsoluteTime: unique symbol = Symbol("opaque_AbsoluteTime"); + // FIXME: Make this opaque! export interface AbsoluteTime { /** @@ -35,6 +37,10 @@ export interface AbsoluteTime { readonly t_ms: number | "never"; readonly _flavor?: typeof flavor_AbsoluteTime; + + // Make the type opaque, we only want our constructors + // to able to create an AbsoluteTime value. + [opaque_AbsoluteTime]: true; } export interface TalerProtocolTimestamp { @@ -69,7 +75,7 @@ export namespace TalerPreciseTimestamp { export function round(t: TalerPreciseTimestamp): TalerProtocolTimestamp { return { t_s: t.t_s, - } + }; } export function fromSeconds(s: number): TalerPreciseTimestamp { @@ -78,6 +84,13 @@ export namespace TalerPreciseTimestamp { off_us: (s - Math.floor(s)) / 1000 / 1000, }; } + + export function fromMilliseconds(ms: number): TalerPreciseTimestamp { + return { + t_s: Math.floor(ms / 1000), + off_us: Math.floor((ms - Math.floor(ms / 100) * 1000) * 1000), + } + } } export namespace TalerProtocolTimestamp { @@ -269,12 +282,21 @@ export namespace AbsoluteTime { export function now(): AbsoluteTime { return { t_ms: new Date().getTime() + timeshift, + [opaque_AbsoluteTime]: true, }; } export function never(): AbsoluteTime { return { t_ms: "never", + [opaque_AbsoluteTime]: true, + }; + } + + export function fromMilliseconds(ms: number): AbsoluteTime { + return { + t_ms: ms, + [opaque_AbsoluteTime]: true, }; } @@ -299,22 +321,22 @@ export namespace AbsoluteTime { export function min(t1: AbsoluteTime, t2: AbsoluteTime): AbsoluteTime { if (t1.t_ms === "never") { - return { t_ms: t2.t_ms }; + return { t_ms: t2.t_ms, [opaque_AbsoluteTime]: true }; } if (t2.t_ms === "never") { - return { t_ms: t2.t_ms }; + return { t_ms: t2.t_ms, [opaque_AbsoluteTime]: true }; } - return { t_ms: Math.min(t1.t_ms, t2.t_ms) }; + return { t_ms: Math.min(t1.t_ms, t2.t_ms), [opaque_AbsoluteTime]: true }; } export function max(t1: AbsoluteTime, t2: AbsoluteTime): AbsoluteTime { if (t1.t_ms === "never") { - return { t_ms: "never" }; + return { t_ms: "never", [opaque_AbsoluteTime]: true }; } if (t2.t_ms === "never") { - return { t_ms: "never" }; + return { t_ms: "never", [opaque_AbsoluteTime]: true }; } - return { t_ms: Math.max(t1.t_ms, t2.t_ms) }; + return { t_ms: Math.max(t1.t_ms, t2.t_ms), [opaque_AbsoluteTime]: true }; } export function difference(t1: AbsoluteTime, t2: AbsoluteTime): Duration { @@ -331,22 +353,26 @@ export namespace AbsoluteTime { return cmp(t, now()) <= 0; } - export function fromProtocolTimestamp(t: TalerProtocolTimestamp): AbsoluteTime { + export function fromProtocolTimestamp( + t: TalerProtocolTimestamp, + ): AbsoluteTime { if (t.t_s === "never") { - return { t_ms: "never" }; + return { t_ms: "never", [opaque_AbsoluteTime]: true }; } return { t_ms: t.t_s * 1000, + [opaque_AbsoluteTime]: true, }; } export function fromPreciseTimestamp(t: TalerPreciseTimestamp): AbsoluteTime { if (t.t_s === "never") { - return { t_ms: "never" }; + return { t_ms: "never", [opaque_AbsoluteTime]: true }; } const offsetUs = t.off_us ?? 0; return { t_ms: t.t_s * 1000 + offsetUs / 1000, + [opaque_AbsoluteTime]: true, }; } @@ -361,10 +387,12 @@ export namespace AbsoluteTime { return { t_s, off_us, - } + }; } - export function toProtocolTimestamp(at: AbsoluteTime): TalerProtocolTimestamp { + export function toProtocolTimestamp( + at: AbsoluteTime, + ): TalerProtocolTimestamp { if (at.t_ms === "never") { return { t_s: "never" }; } @@ -397,9 +425,9 @@ export namespace AbsoluteTime { export function addDuration(t1: AbsoluteTime, d: Duration): AbsoluteTime { if (t1.t_ms === "never" || d.d_ms === "forever") { - return { t_ms: "never" }; + return { t_ms: "never", [opaque_AbsoluteTime]: true }; } - return { t_ms: t1.t_ms + d.d_ms }; + return { t_ms: t1.t_ms + d.d_ms, [opaque_AbsoluteTime]: true }; } export function subtractDuraction( @@ -407,12 +435,12 @@ export namespace AbsoluteTime { d: Duration, ): AbsoluteTime { if (t1.t_ms === "never") { - return { t_ms: "never" }; + return { t_ms: "never", [opaque_AbsoluteTime]: true }; } if (d.d_ms === "forever") { - return { t_ms: 0 }; + return { t_ms: 0, [opaque_AbsoluteTime]: true }; } - return { t_ms: Math.max(0, t1.t_ms - d.d_ms) }; + return { t_ms: Math.max(0, t1.t_ms - d.d_ms), [opaque_AbsoluteTime]: true }; } export function stringify(t: AbsoluteTime): string { @@ -487,10 +515,10 @@ export const codecForAbsoluteTime: Codec = { const t_ms = x.t_ms; if (typeof t_ms === "string") { if (t_ms === "never") { - return { t_ms: "never" }; + return { t_ms: "never", [opaque_AbsoluteTime]: true }; } } else if (typeof t_ms === "number") { - return { t_ms }; + return { t_ms, [opaque_AbsoluteTime]: true }; } throw Error(`expected timestamp at ${renderContext(c)}`); }, diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 38358e734..390df9638 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -65,6 +65,7 @@ import { } from "./taler-types.js"; import { AbsoluteTime, + TalerPreciseTimestamp, TalerProtocolDuration, TalerProtocolTimestamp, codecForAbsoluteTime, @@ -2024,7 +2025,7 @@ interface AttentionPushPaymentReceived { export type UserAttentionUnreadList = Array<{ info: AttentionInfo; - when: AbsoluteTime; + when: TalerPreciseTimestamp; read: boolean; }>; diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 12e799c71..82ad54a20 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -2074,8 +2074,10 @@ export interface UserAttentionRecord { info: AttentionInfo; entityId: string; + /** * When the notification was created. + * FIXME: This should be a TalerPreciseTimestamp */ createdMs: number; diff --git a/packages/taler-wallet-core/src/operations/attention.ts b/packages/taler-wallet-core/src/operations/attention.ts index b802b15cd..7d84b43ef 100644 --- a/packages/taler-wallet-core/src/operations/attention.ts +++ b/packages/taler-wallet-core/src/operations/attention.ts @@ -74,9 +74,7 @@ export async function getUserAttentions( return; pending.push({ info: x.info, - when: { - t_ms: x.createdMs, - }, + when: TalerPreciseTimestamp.fromMilliseconds(x.createdMs), read: x.read !== undefined, }); }); diff --git a/packages/taler-wallet-core/src/util/denominations.ts b/packages/taler-wallet-core/src/util/denominations.ts index ecf8c5671..2b2faa458 100644 --- a/packages/taler-wallet-core/src/util/denominations.ts +++ b/packages/taler-wallet-core/src/util/denominations.ts @@ -132,10 +132,10 @@ export function createPairTimeline( //check which start after, add gap so both list starts at the same time // one list may be empty const leftStartTime: AbsoluteTime = leftGroupIsEmpty - ? { t_ms: "never" } + ? AbsoluteTime.never() : left[li].from; const rightStartTime: AbsoluteTime = rightGroupIsEmpty - ? { t_ms: "never" } + ? AbsoluteTime.never() : right[ri].from; //first time cut is the smallest time diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts index d4bd1c05f..e85eb0a6b 100644 --- a/packages/taler-wallet-core/src/util/retries.ts +++ b/packages/taler-wallet-core/src/util/retries.ts @@ -129,7 +129,7 @@ function updateTimeout( throw Error("assertion failed"); } if (p.backoffDelta.d_ms === "forever") { - r.nextRetry = { t_ms: "never" }; + r.nextRetry = AbsoluteTime.never(); return; } @@ -141,7 +141,7 @@ function updateTimeout( (p.maxTimeout.d_ms === "forever" ? nextIncrement : Math.min(p.maxTimeout.d_ms, nextIncrement)); - r.nextRetry = { t_ms: t }; + r.nextRetry = AbsoluteTime.fromMilliseconds(t); } export namespace RetryInfo { diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/stories.tsx b/packages/taler-wallet-webextension/src/cta/InvoicePay/stories.tsx index 00460bc8f..a8f55c512 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/stories.tsx @@ -19,7 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util"; +import { + AbsoluteTime, + PreparePayResult, + PreparePayResultType, +} from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { ReadyView } from "./views.js"; @@ -43,9 +47,9 @@ export const Ready = tests.createExample(ReadyView, { status: PreparePayResultType.PaymentPossible, amountEffective: "ARS:1", } as PreparePayResult, - expiration: { - t_ms: new Date().getTime() + 1000 * 60 * 60, - }, + expiration: AbsoluteTime.fromMilliseconds( + new Date().getTime() + 1000 * 60 * 60, + ), accept: {}, cancel: {}, }); diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/stories.tsx b/packages/taler-wallet-webextension/src/cta/TransferPickup/stories.tsx index 000d450b5..4fb230cd9 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/stories.tsx @@ -21,6 +21,7 @@ import * as tests from "@gnu-taler/web-util/testing"; import { ReadyView } from "./views.js"; +import { AbsoluteTime } from "@gnu-taler/taler-util"; export default { title: "transfer pickup", @@ -38,9 +39,9 @@ export const Ready = tests.createExample(ReadyView, { fraction: 0, }, summary: "some subject", - expiration: { - t_ms: new Date().getTime() + 1000 * 60 * 60, - }, + expiration: AbsoluteTime.fromMilliseconds( + new Date().getTime() + 1000 * 60 * 60, + ), accept: {}, cancel: {}, }); diff --git a/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx index ddcba6fae..59bcd5ce9 100644 --- a/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx @@ -26,7 +26,11 @@ import { ShowRecoveryInfo, } from "./BackupPage.js"; import * as tests from "@gnu-taler/web-util/testing"; -import { TalerPreciseTimestamp, TalerProtocolTimestamp } from "@gnu-taler/taler-util"; +import { + AbsoluteTime, + TalerPreciseTimestamp, + TalerProtocolTimestamp, +} from "@gnu-taler/taler-util"; export default { title: "backup", @@ -45,9 +49,7 @@ export const LotOfProviders = tests.createExample(TestedComponent, { ], paymentStatus: { type: ProviderPaymentType.Paid, - paidUntil: { - t_ms: 1656599921000, - }, + paidUntil: AbsoluteTime.fromMilliseconds(1656599921000), }, terms: { annualFee: "ARS:1", @@ -66,9 +68,9 @@ export const LotOfProviders = tests.createExample(TestedComponent, { ], paymentStatus: { type: ProviderPaymentType.Paid, - paidUntil: { - t_ms: addDays(new Date(), 13).getTime(), - }, + paidUntil: AbsoluteTime.fromMilliseconds( + addDays(new Date(), 13).getTime(), + ), }, terms: { annualFee: "ARS:1", @@ -123,9 +125,7 @@ export const LotOfProviders = tests.createExample(TestedComponent, { storageLimitInMegabytes: 16, supportedProtocolVersion: "1", }, - paidUntil: { - t_ms: "never", - }, + paidUntil: AbsoluteTime.never(), }, terms: { annualFee: "KUDOS:0.1", @@ -177,9 +177,7 @@ export const OneProvider = tests.createExample(TestedComponent, { ], paymentStatus: { type: ProviderPaymentType.Paid, - paidUntil: { - t_ms: 1656599921000, - }, + paidUntil: AbsoluteTime.fromMilliseconds(1656599921000), }, terms: { annualFee: "ARS:1", diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.stories.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.stories.tsx index 28caf9c22..2ca5305f5 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.stories.tsx @@ -22,7 +22,7 @@ import { PendingTaskType, TaskId } from "@gnu-taler/taler-wallet-core"; import * as tests from "@gnu-taler/web-util/testing"; import { View as TestedComponent } from "./DeveloperPage.js"; -import { PendingIdStr } from "@gnu-taler/taler-util"; +import { AbsoluteTime, PendingIdStr } from "@gnu-taler/taler-util"; export default { title: "developer", @@ -41,9 +41,7 @@ export const AllOff = tests.createExample(TestedComponent, { exchangeBaseUrl: "http://exchange.url.", givesLifeness: false, lastError: undefined, - timestampDue: { - t_ms: 123123213, - }, + timestampDue: AbsoluteTime.fromMilliseconds(123123213), retryInfo: undefined, isDue: false, isLongpolling: false, diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx index ddbb5bbba..388a331e6 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -15,6 +15,7 @@ */ import { + AbsoluteTime, Amounts, CoinDumpJson, CoinStatus, @@ -404,7 +405,7 @@ export function View({ Database exported at