diff options
Diffstat (limited to 'packages/taler-wallet-core/src/common.ts')
-rw-r--r-- | packages/taler-wallet-core/src/common.ts | 102 |
1 files changed, 84 insertions, 18 deletions
diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index eb06b8eb0..755c46188 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -21,6 +21,7 @@ import { AbsoluteTime, AmountJson, Amounts, + AsyncFlag, CoinRefreshRequest, CoinStatus, Duration, @@ -35,6 +36,7 @@ import { TalerProtocolTimestamp, TombstoneIdStr, TransactionIdStr, + WalletNotification, assertUnreachable, checkDbInvariant, checkLogicInvariant, @@ -145,7 +147,13 @@ export async function makeCoinAvailable( export async function spendCoins( wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction< - ["coins", "coinAvailability", "refreshGroups", "denominations"] + [ + "coins", + "coinAvailability", + "refreshGroups", + "refreshSessions", + "denominations", + ] >, csi: CoinsSpendInfo, ): Promise<void> { @@ -275,6 +283,8 @@ export function getExchangeUpdateStatusFromRecord( return ExchangeUpdateStatus.ReadyUpdate; case ExchangeEntryDbUpdateStatus.Suspended: return ExchangeUpdateStatus.Suspended; + default: + assertUnreachable(r.updateStatus); } } @@ -288,6 +298,8 @@ export function getExchangeEntryStatusFromRecord( return ExchangeEntryStatus.Preset; case ExchangeEntryDbRecordStatus.Used: return ExchangeEntryStatus.Used; + default: + assertUnreachable(r.entryStatus); } } @@ -480,25 +492,28 @@ function updateTimeout( r.nextRetry = timestampPreciseToDb(TalerPreciseTimestamp.fromMilliseconds(t)); } -export namespace DbRetryInfo { - export function getDuration( - r: DbRetryInfo | undefined, - p: RetryPolicy = defaultRetryPolicy, - ): Duration { - if (!r) { - // If we don't have any retry info, run immediately. - return { d_ms: 0 }; - } - if (p.backoffDelta.d_ms === "forever") { - return { d_ms: "forever" }; - } - const t = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter); - return { - d_ms: - p.maxTimeout.d_ms === "forever" ? t : Math.min(p.maxTimeout.d_ms, t), - }; +export function computeDbBackoff(retryCounter: number): DbPreciseTimestamp { + const now = AbsoluteTime.now(); + if (now.t_ms === "never") { + throw Error("assertion failed"); + } + const p = defaultRetryPolicy; + if (p.backoffDelta.d_ms === "forever") { + throw Error("assertion failed"); } + const nextIncrement = + p.backoffDelta.d_ms * Math.pow(p.backoffBase, retryCounter); + + const t = + now.t_ms + + (p.maxTimeout.d_ms === "forever" + ? nextIncrement + : Math.min(p.maxTimeout.d_ms, nextIncrement)); + return timestampPreciseToDb(TalerPreciseTimestamp.fromMilliseconds(t)); +} + +export namespace DbRetryInfo { export function reset(p: RetryPolicy = defaultRetryPolicy): DbRetryInfo { const now = TalerPreciseTimestamp.now(); const info: DbRetryInfo = { @@ -756,3 +771,54 @@ export enum PendingTaskType { declare const __taskIdStr: unique symbol; export type TaskIdStr = string & { [__taskIdStr]: true }; + +/** + * Wait until the wallet is in a particular state. + * + * Two functions must be provided: + * 1. checkState, which checks if the wallet is in the + * desired state. + * 2. filterNotification, which checks whether a notification + * might have lead to a state change. + */ +export async function genericWaitForState( + wex: WalletExecutionContext, + args: { + checkState: () => Promise<boolean>; + filterNotification: (notif: WalletNotification) => boolean; + }, +): Promise<void> { + await wex.taskScheduler.ensureRunning(); + + // FIXME: Clean up using the new JS "using" / Symbol.dispose syntax. + const flag = new AsyncFlag(); + // Raise purchaseNotifFlag whenever we get a notification + // about our refresh. + const cancelNotif = wex.ws.addNotificationListener((notif) => { + if (args.filterNotification(notif)) { + flag.raise(); + } + }); + const unregisterOnCancelled = wex.cancellationToken.onCancelled(() => { + cancelNotif(); + flag.raise(); + }); + + try { + while (true) { + if (wex.cancellationToken.isCancelled) { + throw Error("cancelled"); + } + if (await args.checkState()) { + return; + } + // Wait for the next transition + await flag.wait(); + flag.reset(); + } + } catch (e) { + unregisterOnCancelled(); + cancelNotif(); + throw e; + } +} |