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:
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() {