taler-typescript-core

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

commit 05ffc0dbc7649c977f1b11f0d4373841e4501bcc
parent ae1f12bc2d59cef9a8c2da0e4d2000e90b93650b
Author: Florian Dold <dold@taler.net>
Date:   Tue,  3 Nov 2026 10:08:52 +0100

bank web UI: isolate withdrawal polling results by operation and backend

Diffstat:
Mpackages/libeufin-bank-webui/src/hooks/account.ts | 53++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 38 insertions(+), 15 deletions(-)

diff --git a/packages/libeufin-bank-webui/src/hooks/account.ts b/packages/libeufin-bank-webui/src/hooks/account.ts @@ -24,7 +24,7 @@ import { TalerError, TalerHttpError, } from "@gnu-taler/taler-util"; -import { useEffect } from "preact/hooks"; +import { useEffect, useMemo } from "preact/hooks"; import { useSessionState } from "./session.js"; import { @@ -78,36 +78,59 @@ export function revalidateWithdrawalDetails() { export function useWithdrawalDetails(wid: string | undefined) { const { + url: backendUrl, lib: { bank: api }, } = useBankCoreApiContext(); - - type Res = undefined | Awaited<ReturnType<typeof api.getWithdrawalById>>; - const result = useLongPolling( - (ct, res): Promise<Res> | undefined => { + // A new identity also distinguishes leaving and returning to the same ID. + const identity = useMemo( + () => ({ backend: backendUrl.href, wid }), + [backendUrl.href, wid], + ); + type Res = TalerError | Awaited<ReturnType<typeof api.getWithdrawalById>>; + type Polled = { identity: object; value: Res }; + const result = useLongPolling<Polled>( + (ct, previous): Promise<Polled> | undefined => { if (!wid) return undefined; + const res = previous?.identity === identity ? previous.value : undefined; const old_state = !res || res instanceof TalerError || res.type === "fail" ? undefined : res.body.status; - return api.getWithdrawalById(wid, { - old_state, - timeoutMs: !old_state ? undefined : LONG_POLL_DELAY, - ct, - }); + return api + .getWithdrawalById(wid, { + old_state, + timeoutMs: !old_state ? undefined : LONG_POLL_DELAY, + ct, + }) + .then((value) => { + // Some transports can finish after cancellation. Do not publish those + // responses into the new polling loop, even when the ID is unchanged. + ct.throwIfCancelled(); + return { identity, value }; + }) + .catch((error: unknown) => { + ct.throwIfCancelled(); + return { identity, value: TalerError.fromException(error) }; + }); }, { - deps: [wid], - shouldStop(res) { + deps: [identity], + shouldStop(previous) { + const res = + previous?.identity === identity ? previous.value : undefined; return ( - res?.type === "ok" && - (res.body.status === "confirmed" || res.body.status === "aborted") + res !== undefined && + !(res instanceof TalerError) && + (res.type === "fail" || + res.body.status === "confirmed" || + res.body.status === "aborted") ); }, }, ); - return result; + return result?.identity === identity ? result.value : undefined; } export async function revalidatePublicAccounts() {