taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit e156a42b70758b2dca5fe7dfe4e3f74a446ef47f
parent df3dcd2f7b47d94ed2c2fd2b9e81024f26e8c5ec
Author: Florian Dold <florian@dold.me>
Date:   Tue, 14 Jul 2026 21:40:55 +0200

wallet-core: emit progress phase notifications

Diffstat:
Mpackages/taler-util/src/notifications.ts | 11++++++++++-
Mpackages/taler-wallet-core/src/wallet.ts | 46+++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts @@ -46,6 +46,7 @@ export enum NotificationType { TaskObservabilityEvent = "task-observability-event", RequestObservabilityEvent = "request-observability-event", RequestProgress = "request-progress", + RequestProgressPhase = "request-progress-phase", } export interface ErrorInfoSummary { @@ -225,6 +226,13 @@ export interface RequestProgressNotification { retryCounter: number; } +export interface RequestProgressPhaseNotification { + type: NotificationType.RequestProgressPhase; + progressToken: string; + operation: string; + phase: "delayed" | "stalled" | "retryable"; +} + export enum ObservabilityEventType { HttpFetchStart = "http-fetch-start", HttpFetchFinishError = "http-fetch-finish-error", @@ -379,4 +387,5 @@ export type WalletNotification = | TaskProgressNotification | RequestObservabilityEventNotification | IdleNotification - | RequestProgressNotification; + | RequestProgressNotification + | RequestProgressPhaseNotification; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -539,6 +539,7 @@ export function walletMerchantClient( export interface ProgressContext { progressToken: string; operation: string; + finished: boolean; } export const EXCHANGE_COINS_LOCK = "exchange-coins-lock"; @@ -899,6 +900,43 @@ async function recoverStoredBackup( logger.info(`import done`); } +/** + * Emit progress notifications until the request is finished/cancelled. + */ +async function emitProgressPhaseNotifications( + wex: WalletExecutionContext, + pc: ProgressContext, +): Promise<void> { + await wex.ws.timerGroup.resolveAfter( + Duration.fromSpec({ + seconds: 5, + }), + ); + if (wex.cancellationToken.isCancelled || pc.finished) { + return; + } + wex.ws.notify({ + type: NotificationType.RequestProgressPhase, + operation: pc.operation, + progressToken: pc.progressToken, + phase: "delayed", + }); + await wex.ws.timerGroup.resolveAfter( + Duration.fromSpec({ + seconds: 5, + }), + ); + if (wex.cancellationToken.isCancelled || pc.finished) { + return; + } + wex.ws.notify({ + type: NotificationType.RequestProgressPhase, + operation: pc.operation, + progressToken: pc.progressToken, + phase: "stalled", + }); +} + async function withMaybeProgressContext<T>( wex: WalletExecutionContext, op: string, @@ -911,13 +949,19 @@ async function withMaybeProgressContext<T>( const pc: ProgressContext = { operation: op, progressToken: tok, + finished: false, }; if (wex.cts) { wex.ws.progressCancellationMap.set(key, wex.cts); } else { logger.warn(`no cancellation source for ${op}:${tok}`); } - return await f(pc); + emitProgressPhaseNotifications(wex, pc).catch((e) => { + logger.warn(safeStringifyException(e)); + }); + const res = await f(pc); + pc.finished = true; + return res; } finally { wex.ws.progressCancellationMap.delete(key); // Clean up progress token registration