taler-typescript-core

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

commit e10b2907606a01ebb71bfd44a517a84af5cd06c1
parent eba882f7a8ba65d6159e1bd4355f388535268612
Author: Antoine A <>
Date:   Thu, 17 Apr 2025 17:41:55 +0200

wallet-core: use generic fetch & longpool function for cancellation

Diffstat:
Mpackages/taler-wallet-core/src/backup/index.ts | 7++++---
Mpackages/taler-wallet-core/src/common.ts | 63++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mpackages/taler-wallet-core/src/dbless.ts | 4++--
Mpackages/taler-wallet-core/src/deposits.ts | 76++++++++++++++++++++++------------------------------------------------------
Mpackages/taler-wallet-core/src/exchanges.ts | 31+++++++++++--------------------
Mpackages/taler-wallet-core/src/pay-merchant.ts | 66+++++++++++++++++++-----------------------------------------------
Mpackages/taler-wallet-core/src/pay-peer-pull-credit.ts | 51++++++++++++++++-----------------------------------
Mpackages/taler-wallet-core/src/pay-peer-pull-debit.ts | 22+++++++---------------
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 42++++++++++++------------------------------
Mpackages/taler-wallet-core/src/pay-peer-push-debit.ts | 32+++++++++++---------------------
Mpackages/taler-wallet-core/src/recoup.ts | 9+++++----
Mpackages/taler-wallet-core/src/refresh.ts | 28+++++++++++-----------------
Mpackages/taler-wallet-core/src/wallet.ts | 24+++++++-----------------
Mpackages/taler-wallet-core/src/withdraw.ts | 111++++++++++++++++++++++++-------------------------------------------------------
14 files changed, 207 insertions(+), 359 deletions(-)

diff --git a/packages/taler-wallet-core/src/backup/index.ts b/packages/taler-wallet-core/src/backup/index.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2020 Taler Systems SA + (C) 2020-2025 Taler Systems SA GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -73,6 +73,7 @@ import { import { gunzipSync, gzipSync } from "fflate"; import { addAttentionRequest, removeAttentionRequest } from "../attention.js"; import { + cancelableFetch, TaskIdentifiers, TaskRunResult, TaskRunResultType, @@ -225,7 +226,7 @@ async function runBackupCycleForProvider( accountBackupUrl.searchParams.set("fresh", "yes"); } - const resp = await wex.http.fetch(accountBackupUrl.href, { + const resp = await cancelableFetch(wex, accountBackupUrl, { method: "POST", body: encBackup, headers: { @@ -595,7 +596,7 @@ export async function addBackupProvider( }, ); const termsUrl = new URL("config", canonUrl); - const resp = await wex.http.fetch(termsUrl.href); + const resp = await cancelableFetch(wex, termsUrl); const terms = await readSuccessResponseJsonOrThrow( resp, codecForSyncTermsOfServiceResponse(), diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts @@ -1,6 +1,7 @@ /* This file is part of GNU Taler (C) 2022 GNUnet e.V. + (C) 2025 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -14,9 +15,6 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -/** - * Imports. - */ import { AbsoluteTime, AmountJson, @@ -71,6 +69,7 @@ import { ReadyExchangeSummary } from "./exchanges.js"; import { createRefreshGroup } from "./refresh.js"; import { BalanceEffect } from "./transactions.js"; import { WalletExecutionContext, getDenomInfo } from "./wallet.js"; +import { HttpRequestOptions, HttpResponse } from "@gnu-taler/taler-util/http"; const logger = new Logger("operations/common.ts"); @@ -263,10 +262,10 @@ export async function spendCoins( export async function spendTokens( tx: WalletDbReadWriteTransaction< - [ - "tokens", - "purchases", - ] + [ + "tokens", + "purchases", + ] >, tsi: TokensSpendInfo, ): Promise<void> { @@ -375,9 +374,9 @@ export function getExchangeState(r: ExchangeEntryRecord): ExchangeEntryState { export type ParsedTombstone = | { - tag: TombstoneTag.DeleteWithdrawalGroup; - withdrawalGroupId: string; - } + tag: TombstoneTag.DeleteWithdrawalGroup; + withdrawalGroupId: string; + } | { tag: TombstoneTag.DeleteRefund; refundGroupId: string } | { tag: TombstoneTag.DeleteReserve; reservePub: string } | { tag: TombstoneTag.DeleteRefreshGroup; refreshGroupId: string } @@ -658,9 +657,9 @@ export enum PendingTaskType { */ export type ParsedTaskIdentifier = | { - tag: PendingTaskType.Withdraw; - withdrawalGroupId: string; - } + tag: PendingTaskType.Withdraw; + withdrawalGroupId: string; + } | { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string } | { tag: PendingTaskType.ExchangeWalletKyc; exchangeBaseUrl: string } | { tag: PendingTaskType.Backup; backupProviderBaseUrl: string } @@ -814,10 +813,10 @@ export enum TransitionResultType { export type TransitionResult<R> = | { type: TransitionResultType.Stay } | { - type: TransitionResultType.Transition; - rec: R; - balanceEffect: BalanceEffect; - } + type: TransitionResultType.Transition; + rec: R; + balanceEffect: BalanceEffect; + } | { type: TransitionResultType.Delete }; export const TransitionResult = { @@ -1034,3 +1033,32 @@ export async function runWithClientCancellation<R, T>( } } } + +/** + * Run a queued longpool fetch with cancellation token and timeout_ms + */ +export async function cancelableLongPool( + wex: WalletExecutionContext, + url: URL, + opt?: HttpRequestOptions +): Promise<HttpResponse> { + const longPool = async (timeoutMs: number) => { + url.searchParams.set("timeout_ms", `${timeoutMs}`); + return cancelableFetch(wex, url, opt) + }; + return wex.ws.longpollQueue.queue(wex.cancellationToken, url, longPool) +} + +/** + * Fetch with cancellation token + */ +export async function cancelableFetch( + wex: WalletExecutionContext, + url: URL, + opt?: HttpRequestOptions +): Promise<HttpResponse> { + return wex.http.fetch(url.href, { + ...opt, + cancellationToken: wex.cancellationToken, + }); +} +\ No newline at end of file diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021 Taler Systems S.A. + (C) 2021-2025 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -116,7 +116,7 @@ export async function topupReserveWithBank(args: TopupReserveWithBankArgs) { if (plainPaytoUris.length <= 0) { throw new Error(); } - const httpResp = await http.fetch(bankStatusUrl, { + const httpResp = await http.fetch(bankStatusUrl.href, { method: "POST", body: { reserve_pub: reservePub, diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -90,6 +90,8 @@ import { TaskIdStr, TaskRunResult, TransactionContext, + cancelableFetch, + cancelableLongPool, constructTaskIdentifier, runWithClientCancellation, spendCoins, @@ -824,10 +826,9 @@ async function refundDepositGroup( rtransaction_id: rtid, }; const refundUrl = new URL(`coins/${coinPub}/refund`, coinExchange); - const httpResp = await wex.http.fetch(refundUrl.href, { + const httpResp = await cancelableFetch(wex, refundUrl, { method: "POST", body: refundReq, - cancellationToken: wex.cancellationToken, }); logger.info( `coin ${i} refund HTTP status for coin: ${httpResp.status}`, @@ -1020,22 +1021,12 @@ async function processDepositGroupPendingKyc( `kyc-check/${kycInfo.paytoHash}`, kycInfo.exchangeBaseUrl, ); - - const kycStatusRes = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - url.hostname, - async (timeoutMs) => { - url.searchParams.set("timeout_ms", `${timeoutMs}`); - logger.info(`kyc url ${url.href}`); - return await wex.http.fetch(url.href, { - method: "GET", - cancellationToken: wex.cancellationToken, - headers: { - ["Account-Owner-Signature"]: sigResp.sig, - }, - }); + logger.info(`kyc url ${url.href}`); + const kycStatusRes = await cancelableLongPool(wex, url, { + headers: { + ["Account-Owner-Signature"]: sigResp.sig, }, - ); + }); logger.trace( `request to ${kycStatusRes.requestUrl} returned status ${kycStatusRes.status}`, @@ -1112,22 +1103,12 @@ async function processDepositGroupPendingKycAuth( // lpt=1 => wait for the KYC auth transfer (access token available) url.searchParams.set("lpt", "1"); - - const kycStatusRes = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - url.hostname, - async (timeoutMs) => { - url.searchParams.set("timeout_ms", `${timeoutMs}`); - logger.info(`kyc url ${url.href}`); - return await wex.http.fetch(url.href, { - method: "GET", - cancellationToken: wex.cancellationToken, - headers: { - ["Account-Owner-Signature"]: sigResp.sig, - }, - }); + logger.info(`kyc url ${url.href}`); + const kycStatusRes = await cancelableLongPool(wex, url,{ + headers: { + ["Account-Owner-Signature"]: sigResp.sig, }, - ); + }); logger.info(`merchant pub: ${depositGroup.merchantPub}`); @@ -1244,8 +1225,7 @@ async function transitionToKycRequired( const url = new URL(`kyc-check/${kycPaytoHash}`, exchangeUrl); logger.info(`kyc url ${url.href}`); - const kycStatusResp = await wex.http.fetch(url.href, { - method: "GET", + const kycStatusResp = await cancelableFetch(wex, url, { headers: { ["Account-Owner-Signature"]: sigResp.sig, }, @@ -1514,8 +1494,7 @@ async function processDepositGroupTrack( async function processDepositGroupPendingDeposit( wex: WalletExecutionContext, - depositGroup: DepositGroupRecord, - cancellationToken?: CancellationToken, + depositGroup: DepositGroupRecord ): Promise<TaskRunResult> { logger.info("processing deposit group in pending(deposit)"); const depositGroupId = depositGroup.depositGroupId; @@ -1545,7 +1524,7 @@ async function processDepositGroupPendingDeposit( const ctx = new DepositTransactionContext(wex, depositGroupId); // Check for cancellation before expensive operations. - cancellationToken?.throwIfCancelled(); + wex.cancellationToken?.throwIfCancelled(); if (!depositGroup.payCoinSelection) { logger.info("missing coin selection for deposit group, selecting now"); @@ -1687,14 +1666,13 @@ async function processDepositGroupPendingDeposit( } // Check for cancellation before making network request. - cancellationToken?.throwIfCancelled(); + wex.cancellationToken?.throwIfCancelled(); const url = new URL(`batch-deposit`, exchangeBaseUrl); logger.info(`depositing to ${url.href}`); logger.trace(`deposit request: ${j2s(batchReq)}`); - const httpResp = await wex.http.fetch(url.href, { + const httpResp = await cancelableFetch(wex, url, { method: "POST", - body: batchReq, - cancellationToken: cancellationToken, + body: batchReq }); logger.info(`deposit result status ${httpResp.status}`); @@ -1840,19 +1818,9 @@ async function trackDeposit( wireHash, }); url.searchParams.set("merchant_sig", sigResp.sig); - const httpResp = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - url.hostname, - async (timeoutMs) => { - url.searchParams.set("timeout_ms", `${timeoutMs}`); - // wait for the a 202 state where kyc_ok is false or a 200 OK response - url.searchParams.set("lpt", `1`); - return await wex.http.fetch(url.href, { - method: "GET", - cancellationToken: wex.cancellationToken, - }); - }, - ); + // wait for the a 202 state where kyc_ok is false or a 200 OK response + url.searchParams.set("lpt", `1`); + const httpResp = await cancelableLongPool(wex, url); logger.trace(`deposits response status: ${httpResp.status}`); switch (httpResp.status) { case HttpStatusCode.Accepted: { diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts @@ -129,6 +129,8 @@ import { TaskRunResult, TaskRunResultType, TransactionContext, + cancelableFetch, + cancelableLongPool, computeDbBackoff, constructTaskIdentifier, genericWaitForState, @@ -1088,9 +1090,7 @@ async function downloadTosMeta( // FIXME: We can/should make a HEAD request here. // Not sure if qtart supports it at the moment. - const resp = await wex.http.fetch(reqUrl.href, { - cancellationToken: wex.cancellationToken, - }); + const resp = await cancelableFetch(wex, reqUrl); switch (resp.status) { case HttpStatusCode.NotFound: @@ -3451,7 +3451,7 @@ async function handleExchangeKycPendingWallet( reserve_sig: sigResp.sig, }; logger.info(`kyc-wallet request body: ${j2s(body)}`); - const res = await wex.http.fetch(requestUrl.href, { + const res = await cancelableLongPool(wex, requestUrl, { method: "POST", body, }); @@ -3567,7 +3567,7 @@ async function handleExchangeKycRespLegi( accountPub: reserve.reservePub, }); const reqUrl = new URL(`kyc-check/${kycBody.h_payto}`, exchangeBaseUrl); - const resp = await wex.http.fetch(reqUrl.href, { + const resp = await cancelableFetch(wex, reqUrl, { method: "GET", headers: { ["Account-Owner-Signature"]: sigResp.sig, @@ -3669,22 +3669,13 @@ async function handleExchangeKycPendingLegitimization( const paytoHash = encodeCrock(hashFullPaytoUri(reservePayto)); - const resp = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - exchange.baseUrl, - async (timeoutMs) => { - const reqUrl = new URL(`kyc-check/${paytoHash}`, exchange.baseUrl); - reqUrl.searchParams.set("timeout_ms", `${timeoutMs}`); - logger.info(`long-polling wallet KYC status at ${reqUrl.href}`); - return await wex.http.fetch(reqUrl.href, { - method: "GET", - cancellationToken: wex.cancellationToken, - headers: { - ["Account-Owner-Signature"]: sigResp.sig, - }, - }); + const reqUrl = new URL(`kyc-check/${paytoHash}`, exchange.baseUrl); + logger.info(`long-polling wallet KYC status at ${reqUrl.href}`); + const resp = await cancelableLongPool(wex, reqUrl, { + headers: { + ["Account-Owner-Signature"]: sigResp.sig, }, - ); + }); logger.info(`kyc-check (long-poll) response status ${resp.status}`); diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -139,6 +139,8 @@ import { TaskRunResultType, TransactionContext, TransitionResultType, + cancelableLongPool, + cancelableFetch, } from "./common.js"; import { EddsaKeyPairStrings } from "./crypto/cryptoImplementation.js"; import { @@ -1014,7 +1016,7 @@ async function processDownloadProposal( const orderClaimUrl = new URL( `orders/${proposal.orderId}/claim`, proposal.merchantBaseUrl, - ).href; + ); logger.trace("downloading contract from '" + orderClaimUrl + "'"); const requestBody: { @@ -1027,10 +1029,9 @@ async function processDownloadProposal( requestBody.token = proposal.claimToken; } - const httpResponse = await wex.http.fetch(orderClaimUrl, { + const httpResponse = await cancelableFetch(wex, orderClaimUrl, { method: "POST", - body: requestBody, - cancellationToken: wex.cancellationToken, + body: requestBody }); const r = await readSuccessResponseJsonOrErrorCode( httpResponse, @@ -1043,7 +1044,7 @@ async function processDownloadProposal( TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED, { orderId: proposal.orderId, - claimUrl: orderClaimUrl, + claimUrl: orderClaimUrl.href, }, "order already claimed (likely by other wallet)", ); @@ -2232,10 +2233,7 @@ async function downloadTemplate( templateId: string, ): Promise<TalerMerchantApi.WalletTemplateDetails> { const reqUrl = new URL(`templates/${templateId}`, merchantBaseUrl); - const httpReq = await wex.http.fetch(reqUrl.href, { - method: "GET", - cancellationToken: wex.cancellationToken, - }); + const httpReq = await cancelableFetch(wex, reqUrl); const resp = await readSuccessResponseJsonOrThrow( httpReq, codecForWalletTemplateDetails(), @@ -2325,7 +2323,7 @@ export async function preparePayForTemplate( `templates/${parsedUri.templateId}`, parsedUri.merchantBaseUrl, ); - const httpReq = await wex.http.fetch(reqUrl.href, { + const httpReq = await cancelableFetch(wex, reqUrl, { method: "POST", body: templateDetails, }); @@ -3223,7 +3221,7 @@ async function processPurchasePay( const payUrl = new URL( `orders/${download.contractData.order_id}/pay`, download.contractData.merchant_base_url, - ).href; + ); let slates: SlateRecord[] | undefined = undefined; let wallet_data: PayWalletData | undefined = undefined; @@ -3280,11 +3278,10 @@ async function processPurchasePay( } const resp = await wex.ws.runSequentialized([EXCHANGE_COINS_LOCK], () => - wex.http.fetch(payUrl, { + cancelableFetch(wex, payUrl, { method: "POST", body: reqBody, timeout: getPayRequestTimeout(purchase), - cancellationToken: wex.cancellationToken, }), ); @@ -3388,7 +3385,7 @@ async function processPurchasePay( const payAgainUrl = new URL( `orders/${download.contractData.order_id}/paid`, download.contractData.merchant_base_url, - ).href; + ); const reqBody = { sig: purchase.merchantPaySig, h_contract: download.contractData.contractTermsHash, @@ -3396,10 +3393,9 @@ async function processPurchasePay( }; logger.trace(`/paid request body: ${j2s(reqBody)}`); const resp = await wex.ws.runSequentialized([EXCHANGE_COINS_LOCK], () => - wex.http.fetch(payAgainUrl, { + cancelableFetch(wex, payAgainUrl, { method: "POST", body: reqBody, - cancellationToken: wex.cancellationToken, }), ); logger.trace(`/paid response status: ${resp.status}`); @@ -3957,20 +3953,9 @@ async function checkIfOrderIsAlreadyPaid( let resp: HttpResponse; if (doLongPolling) { - resp = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - requestUrl.hostname, - async (timeoutMs) => { - requestUrl.searchParams.set("timeout_ms", `${timeoutMs}`); - return await wex.http.fetch(requestUrl.href, { - cancellationToken: wex.cancellationToken, - }); - }, - ); + resp = await cancelableLongPool(wex, requestUrl); } else { - resp = await wex.http.fetch(requestUrl.href, { - cancellationToken: wex.cancellationToken, - }); + resp = await cancelableFetch(wex, requestUrl); } if ( @@ -4135,16 +4120,7 @@ async function processPurchaseAutoRefund( requestUrl.searchParams.set("refund", Amounts.stringify(totalKnownRefund)); - const resp = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - requestUrl.hostname, - async (timeoutMs) => { - requestUrl.searchParams.set("timeout_ms", `${timeoutMs}`); - return await wex.http.fetch(requestUrl.href, { - cancellationToken: wex.cancellationToken, - }); - }, - ); + const resp = await cancelableLongPool(wex, requestUrl); // FIXME: Check other status codes! @@ -4224,10 +4200,9 @@ async function processPurchaseAbortingRefund( logger.trace(`making order abort request to ${requestUrl.href}`); - const abortHttpResp = await wex.http.fetch(requestUrl.href, { + const abortHttpResp = await cancelableFetch(wex, requestUrl, { method: "POST", body: abortReq, - cancellationToken: wex.cancellationToken, }); logger.trace(`abort response status: ${j2s(abortHttpResp.status)}`); @@ -4304,9 +4279,7 @@ async function processPurchaseQueryRefund( download.contractData.contractTermsHash, ); - const resp = await wex.http.fetch(requestUrl.href, { - cancellationToken: wex.cancellationToken, - }); + const resp = await cancelableFetch(wex, requestUrl); const orderStatus = await readSuccessResponseJsonOrThrow( resp, codecForMerchantOrderStatusPaid(), @@ -4381,12 +4354,11 @@ async function processPurchaseAcceptRefund( logger.trace(`making refund request to ${requestUrl.href}`); - const request = await wex.http.fetch(requestUrl.href, { + const request = await cancelableFetch(wex, requestUrl, { method: "POST", body: { h_contract: download.contractData.contractTermsHash, - }, - cancellationToken: wex.cancellationToken, + } }); const refundResponse = await readSuccessResponseJsonOrThrow( diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -66,6 +66,8 @@ import { TaskRunResult, TransactionContext, TransitionResultType, + cancelableFetch, + cancelableLongPool, constructTaskIdentifier, genericWaitForStateVal, requireExchangeTosAcceptedOrThrow, @@ -597,17 +599,9 @@ async function queryPurseForPeerPullCredit( pullIni.exchangeBaseUrl, ); logger.info(`querying purse status via ${purseDepositUrl.href}`); - const resp = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - purseDepositUrl.hostname, - async (timeoutMs) => { - purseDepositUrl.searchParams.set("timeout_ms", `${timeoutMs}`); - return await wex.http.fetch(purseDepositUrl.href, { - timeout: { d_ms: 60000 }, - cancellationToken: wex.cancellationToken, - }); - }, - ); + const resp = await cancelableLongPool(wex, purseDepositUrl, { + timeout: { d_ms: 60000 }, + }); const ctx = new PeerPullCreditTransactionContext(wex, pullIni.pursePub); logger.info(`purse status code: HTTP ${resp.status}`); @@ -684,21 +678,12 @@ async function longpollKycStatus( const ctx = new PeerPullCreditTransactionContext(wex, pursePub); const url = new URL(`kyc-check/${kycPaytoHash}`, exchangeUrl); - const kycStatusRes = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - url.hostname, - async (timeoutMs) => { - url.searchParams.set("timeout_ms", `${timeoutMs}`); - logger.info(`kyc url ${url.href}`); - return await wex.http.fetch(url.href, { - method: "GET", - headers: { - ["Account-Owner-Signature"]: sigResp.sig, - }, - cancellationToken: wex.cancellationToken, - }); - }, - ); + logger.info(`kyc url ${url.href}`); + const kycStatusRes = await cancelableLongPool(wex, url, { + headers: { + ["Account-Owner-Signature"]: sigResp.sig, + } + }); if ( kycStatusRes.status === HttpStatusCode.Ok || // FIXME: NoContent is not expected https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge @@ -725,12 +710,11 @@ async function processPeerPullCreditAbortingDeletePurse( pursePriv, }); const purseUrl = new URL(`purses/${pursePub}`, peerPullIni.exchangeBaseUrl); - const resp = await wex.http.fetch(purseUrl.href, { + const resp = await cancelableFetch(wex, purseUrl, { method: "DELETE", headers: { "taler-purse-signature": sigResp.sig, - }, - cancellationToken: wex.cancellationToken, + } }); logger.info(`deleted purse with response status ${resp.status}`); @@ -871,10 +855,9 @@ async function handlePeerPullCreditCreatePurse( pullIni.exchangeBaseUrl, ); - const httpResp = await wex.http.fetch(reservePurseMergeUrl.href, { + const httpResp = await cancelableFetch(wex, reservePurseMergeUrl, { method: "POST", body: reservePurseReqBody, - cancellationToken: wex.cancellationToken, }); if (httpResp.status === HttpStatusCode.UnavailableForLegalReasons) { @@ -1046,12 +1029,10 @@ async function processPeerPullCreditKycRequired( const url = new URL(`kyc-check/${kycPayoHash}`, peerIni.exchangeBaseUrl); logger.info(`kyc url ${url.href}`); - const kycStatusRes = await wex.http.fetch(url.href, { - method: "GET", + const kycStatusRes = await cancelableFetch(wex, url, { headers: { ["Account-Owner-Signature"]: sigResp.sig, - }, - cancellationToken: wex.cancellationToken, + } }); if ( diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -77,6 +77,8 @@ import { TaskRunResultType, TransactionContext, TransitionResultType, + cancelableFetch, + cancelableLongPool, constructTaskIdentifier, spendCoins, } from "./common.js"; @@ -525,16 +527,7 @@ async function processPeerPullDebitDialogProposed( pullIni.exchangeBaseUrl, ); logger.info(`querying purse status via ${purseDepositUrl.href}`); - const resp = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - purseDepositUrl.hostname, - async (timeoutMs) => { - purseDepositUrl.searchParams.set("timeout_ms", `${timeoutMs}`); - return await wex.http.fetch(purseDepositUrl.href, { - cancellationToken: wex.cancellationToken, - }); - }, - ); + const resp = await cancelableLongPool(wex, purseDepositUrl); const ctx = new PeerPullDebitTransactionContext(wex, pullIni.peerPullDebitId); logger.info(`purse status code: HTTP ${resp.status}`); @@ -683,10 +676,9 @@ async function processPeerPullDebitPendingDeposit( logger.trace(`purse deposit payload: ${j2s(depositPayload)}`); } - const httpResp = await wex.http.fetch(purseDepositUrl.href, { + const httpResp = await cancelableFetch(wex, purseDepositUrl, { method: "POST", - body: depositPayload, - cancellationToken: wex.cancellationToken, + body: depositPayload }); switch (httpResp.status) { @@ -962,7 +954,7 @@ export async function preparePeerPullDebit( const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl); - const contractHttpResp = await wex.http.fetch(getContractUrl.href); + const contractHttpResp = await cancelableFetch(wex, getContractUrl); const contractResp = await readSuccessResponseJsonOrThrow( contractHttpResp, @@ -979,7 +971,7 @@ export async function preparePeerPullDebit( const getPurseUrl = new URL(`purses/${pursePub}/merge`, exchangeBaseUrl); - const purseHttpResp = await wex.http.fetch(getPurseUrl.href); + const purseHttpResp = await cancelableFetch(wex, getPurseUrl); if (purseHttpResp.status == HttpStatusCode.Gone) { throw TalerError.fromDetail( diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -67,6 +67,8 @@ import { TaskRunResult, TransactionContext, TransitionResultType, + cancelableFetch, + cancelableLongPool, constructTaskIdentifier, genericWaitForStateVal, requireExchangeTosAcceptedOrThrow, @@ -604,7 +606,7 @@ export async function preparePeerPushCredit( const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl); - const contractHttpResp = await wex.http.fetch(getContractUrl.href); + const contractHttpResp = await cancelableFetch(wex, getContractUrl); const contractResp = await readSuccessResponseJsonOrThrow( contractHttpResp, @@ -623,7 +625,7 @@ export async function preparePeerPushCredit( const getPurseUrl = new URL(`purses/${pursePub}/deposit`, exchangeBaseUrl); - const purseHttpResp = await wex.http.fetch(getPurseUrl.href); + const purseHttpResp = await cancelableFetch(wex, getPurseUrl); const purseStatus = await readSuccessResponseJsonOrThrow( purseHttpResp, @@ -734,20 +736,11 @@ async function longpollKycStatus( const url = new URL(`kyc-check/${kycPaytoHash}`, exchangeUrl); logger.info(`kyc url ${url.href}`); - const kycStatusRes = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - url.hostname, - async (timeoutMs) => { - url.searchParams.set("timeout_ms", `${timeoutMs}`); - return await wex.http.fetch(url.href, { - method: "GET", - headers: { - ["Account-Owner-Signature"]: sigResp.sig, - }, - cancellationToken: wex.cancellationToken, - }); + const kycStatusRes = await cancelableLongPool(wex, url, { + headers: { + ["Account-Owner-Signature"]: sigResp.sig, }, - ); + }); if ( kycStatusRes.status === HttpStatusCode.Ok || @@ -794,12 +787,10 @@ async function processPeerPushCreditKycRequired( ); logger.info(`kyc url ${url.href}`); - const kycStatusRes = await wex.http.fetch(url.href, { - method: "GET", + const kycStatusRes = await cancelableFetch(wex, url, { headers: { ["Account-Owner-Signature"]: sigResp.sig, - }, - cancellationToken: wex.cancellationToken, + } }); logger.info(`kyc result status ${kycStatusRes.status}`); @@ -917,7 +908,7 @@ async function handlePendingMerge( reserve_sig: sigRes.accountSig, }; - const mergeHttpResp = await wex.http.fetch(mergePurseUrl.href, { + const mergeHttpResp = await cancelableFetch(wex, mergePurseUrl, { method: "POST", body: mergeReq, }); @@ -1082,16 +1073,7 @@ async function processPeerPushDebitDialogProposed( pullIni.exchangeBaseUrl, ); logger.info(`querying purse status via ${purseDepositUrl.href}`); - const resp = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - purseDepositUrl.hostname, - async (timeoutMs) => { - purseDepositUrl.searchParams.set("timeout_ms", `${timeoutMs}`); - return await wex.http.fetch(purseDepositUrl.href, { - cancellationToken: wex.cancellationToken, - }); - }, - ); + const resp = await cancelableLongPool(wex, purseDepositUrl); const ctx = new PeerPushCreditTransactionContext(wex, pullIni.peerPushCreditId); logger.info(`purse status code: HTTP ${resp.status}`); diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -69,6 +69,8 @@ import { TaskRunResultType, TransactionContext, TransitionResultType, + cancelableFetch, + cancelableLongPool, constructTaskIdentifier, runWithClientCancellation, spendCoins, @@ -784,10 +786,9 @@ async function processPeerPushDebitCreateReserve( logger.trace(`request body: ${j2s(reqBody)}`); } - const httpResp = await wex.http.fetch(createPurseUrl.href, { + const httpResp = await cancelableFetch(wex, createPurseUrl, { method: "POST", - body: reqBody, - cancellationToken: wex.cancellationToken, + body: reqBody }); switch (httpResp.status) { @@ -823,10 +824,9 @@ async function processPeerPushDebitCreateReserve( deposits: depositSigsResp.deposits, }; - const httpResp = await wex.http.fetch(purseDepositUrl.href, { + const httpResp = await cancelableFetch(wex, purseDepositUrl, { method: "POST", - body: depositPayload, - cancellationToken: wex.cancellationToken, + body: depositPayload }); switch (httpResp.status) { @@ -860,7 +860,7 @@ async function processPeerPushDebitCreateReserve( peerPushInitiation.exchangeBaseUrl, ); - const purseHttpResp = await wex.http.fetch(getPurseUrl.href); + const purseHttpResp = await cancelableFetch(wex, getPurseUrl); const purseStatus = await readSuccessResponseJsonOrThrow( purseHttpResp, @@ -890,12 +890,11 @@ async function processPeerPushDebitAbortingDeletePurse( `purses/${pursePub}`, peerPushInitiation.exchangeBaseUrl, ); - const resp = await wex.http.fetch(purseUrl.href, { + const resp = await cancelableFetch(wex, purseUrl, { method: "DELETE", headers: { "taler-purse-signature": sigResp.sig, - }, - cancellationToken: wex.cancellationToken, + } }); logger.info(`deleted purse with response status ${resp.status}`); @@ -956,17 +955,8 @@ async function processPeerPushDebitReady( `purses/${pursePub}/merge`, peerPushInitiation.exchangeBaseUrl, ); - const resp = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - mergeUrl.hostname, - async (timeoutMs) => { - mergeUrl.searchParams.set("timeout_ms", `${timeoutMs}`); - logger.info(`long-polling on purse status at ${mergeUrl.href}`); - return await wex.http.fetch(mergeUrl.href, { - cancellationToken: wex.cancellationToken, - }); - }, - ); + logger.info(`long-polling on purse status at ${mergeUrl.href}`); + const resp = await cancelableLongPool(wex, mergeUrl); if (resp.status === HttpStatusCode.Ok) { const purseStatus = await readSuccessResponseJsonOrThrow( resp, diff --git a/packages/taler-wallet-core/src/recoup.ts b/packages/taler-wallet-core/src/recoup.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2019-2020 Taler Systems SA + (C) 2019-2025 Taler Systems SA GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -48,6 +48,7 @@ import { TaskIdStr, TaskRunResult, TransactionContext, + cancelableFetch, constructTaskIdentifier, } from "./common.js"; import { @@ -175,7 +176,7 @@ async function recoupRefreshCoin( ); logger.trace(`making recoup request for ${coin.coinPub}`); - const resp = await wex.http.fetch(reqUrl.href, { + const resp = await cancelableFetch(wex, reqUrl, { method: "POST", body: recoupRequest, }); @@ -285,7 +286,7 @@ export async function recoupWithdrawCoin( }); const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl); logger.trace(`requesting recoup via ${reqUrl.href}`); - const resp = await wex.http.fetch(reqUrl.href, { + const resp = await cancelableFetch(wex, reqUrl, { method: "POST", body: recoupRequest, }); @@ -412,7 +413,7 @@ export async function processRecoupGroup( ); logger.info(`querying reserve status for recoup via ${reserveUrl}`); - const resp = await wex.http.fetch(reserveUrl.href); + const resp = await cancelableFetch(wex, reserveUrl); const result = await readSuccessResponseJsonOrThrow( resp, diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2019-2024 Taler Systems S.A. + (C) 2019-2025 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -74,6 +74,7 @@ import { throwUnexpectedRequestError, } from "@gnu-taler/taler-util/http"; import { + cancelableFetch, constructTaskIdentifier, genericWaitForState, makeCoinsVisible, @@ -776,11 +777,10 @@ async function refreshMelt( const resp = await wex.ws.runSequentialized( [EXCHANGE_COINS_LOCK], async () => { - return await wex.http.fetch(reqUrl.href, { + return await cancelableFetch(wex, reqUrl, { method: "POST", body: meltReqBody, - timeout: getRefreshRequestTimeout(refreshGroup), - cancellationToken: wex.cancellationToken, + timeout: getRefreshRequestTimeout(refreshGroup) }); }, ); @@ -919,10 +919,9 @@ async function handleRefreshMeltConflict( oldCoin.exchangeBaseUrl, ); logger.trace(`Doing deposit in refresh for coin ${coinIndex}`); - const httpResp = await ctx.wex.http.fetch(refundUrl.href, { + const httpResp = await cancelableFetch(ctx.wex, refundUrl, { method: "POST", body: refundReq, - cancellationToken: ctx.wex.cancellationToken, }); switch (httpResp.status) { case HttpStatusCode.Ok: @@ -963,12 +962,10 @@ async function handleRefreshMeltConflict( oldCoin.exchangeBaseUrl, ); - const historyResp = await ctx.wex.http.fetch(historyUrl.href, { - method: "GET", + const historyResp = await cancelableFetch(ctx.wex, historyUrl, { headers: { "Taler-Coin-History-Signature": historySig.sig, }, - cancellationToken: ctx.wex.cancellationToken, }); const historyJson = await readSuccessResponseJsonOrThrow( @@ -1281,14 +1278,11 @@ async function refreshReveal( const resp = await wex.ws.runSequentialized( [EXCHANGE_COINS_LOCK], - async () => { - return await wex.http.fetch(reqUrl.href, { - body: req, - method: "POST", - timeout: getRefreshRequestTimeout(refreshGroup), - cancellationToken: wex.cancellationToken, - }); - }, + async () => cancelableFetch(wex, reqUrl, { + body: req, + method: "POST", + timeout: getRefreshRequestTimeout(refreshGroup) + }), ); switch (resp.status) { diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -1,6 +1,7 @@ /* This file is part of GNU Taler (C) 2015-2019 GNUnet e.V. + (C) 2025 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -398,6 +399,7 @@ import { getWithdrawalDetailsForUri, prepareBankIntegratedWithdrawal, } from "./withdraw.js"; +import { cancelableFetch } from "./common.js"; const logger = new Logger("wallet.ts"); @@ -1087,7 +1089,7 @@ async function handleTestingGetReserveHistory( }); const exchangeBaseUrl = req.exchangeBaseUrl; const url = new URL(`reserves/${req.reservePub}/history`, exchangeBaseUrl); - const resp = await wex.http.fetch(url.href, { + const resp = await cancelableFetch(wex, url, { headers: { ["Taler-Reserve-History-Signature"]: sigResp.sig }, }); const historyJson = await readSuccessResponseJsonOrThrow(resp, codecForAny()); @@ -2666,9 +2668,10 @@ class LongpollQueue { async queue<T>( cancellationToken: CancellationToken, - hostname: string, + url: URL, f: LongpollRunFn<T> ): Promise<T> { + const hostname = url.hostname const rid = this.idCounter++; const triggerNextLongpoll = () => { @@ -2685,7 +2688,7 @@ class LongpollQueue { // Else release permit state.permit++; if (state.permit == PER_HOSTNAME_PERMITS) { - this.hostNameQueues.delete(hostname); + this.hostNameQueues.delete(url.hostname); } } }; @@ -2790,7 +2793,7 @@ export class InternalWalletState { clientCancellationMap: Map<string, CancellationToken.Source> = new Map(); - private longpollQueue = new LongpollQueue(); + longpollQueue = new LongpollQueue(); public get idbFactory(): BridgeIDBFactory { return this.dbImplementation.idbFactory; @@ -2822,19 +2825,6 @@ export class InternalWalletState { this._networkAvailable = status; } - /** - * Run a long-polling request, potentially queuing the request - * if too many other long-polling requests against the same hostname - * (or too many overall) are active. - */ - async runLongpollQueueing<T>( - cancellationToken: CancellationToken, - hostname: string, - f: LongpollRunFn<T>, - ): Promise<T> { - return this.longpollQueue.queue(cancellationToken, hostname, f) - } - clearAllCaches(): void { this.exchangeCache.clear(); this.denomInfoCache.clear(); diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -120,6 +120,8 @@ import { TransactionContext, TransitionResult, TransitionResultType, + cancelableFetch, + cancelableLongPool, constructTaskIdentifier, genericWaitForState, genericWaitForStateVal, @@ -1147,17 +1149,7 @@ async function processWithdrawalGroupDialogProposed( url.searchParams.set("old_state", "pending"); - const resp = await ctx.wex.ws.runLongpollQueueing( - ctx.wex.cancellationToken, - url.hostname, - async (timeoutMs) => { - url.searchParams.set("long_poll_ms", `${timeoutMs}`); - return await ctx.wex.http.fetch(url.href, { - method: "GET", - cancellationToken: ctx.wex.cancellationToken, - }); - }, - ); + const resp = await cancelableLongPool(ctx.wex, url); switch (resp.status) { case HttpStatusCode.NotFound: { @@ -1453,9 +1445,7 @@ async function handleKycRequired( const url = new URL(`kyc-check/${legiRequiredResp.h_payto}`, exchangeUrl); logger.info(`kyc url ${url.href}`); // We do not longpoll here, as this is the initial request to get information about the KYC. - const kycStatusRes = await wex.http.fetch(url.href, { - method: "GET", - cancellationToken: wex.cancellationToken, + const kycStatusRes = await cancelableFetch(wex, url, { headers: { ["Account-Owner-Signature"]: sigResp.sig, }, @@ -1613,17 +1603,16 @@ async function processPlanchetExchangeBatchRequest( const reqUrl = new URL( `reserves/${withdrawalGroup.reservePub}/batch-withdraw`, withdrawalGroup.exchangeBaseUrl, - ).href; + ); // if (logger.shouldLogTrace()) { // logger.trace(`batch-withdraw request: ${j2s(batchReq)}`); // } try { - const resp = await wex.http.fetch(reqUrl, { + const resp = await cancelableFetch(wex, reqUrl, { method: "POST", body: batchReq, - cancellationToken: wex.cancellationToken, timeout: Duration.fromSpec({ seconds: 40 }), }); if (resp.status === HttpStatusCode.UnavailableForLegalReasons) { @@ -1997,18 +1986,10 @@ async function processQueryReserve( withdrawalGroup.exchangeBaseUrl, ); - const resp = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - reserveUrl.hostname, - async (timeoutMs) => { - reserveUrl.searchParams.set("timeout_ms", `${timeoutMs}`); - logger.trace(`querying reserve status via ${reserveUrl.href}`); - return await wex.http.fetch(reserveUrl.href, { - timeout: getReserveRequestTimeout(withdrawalGroup), - cancellationToken: wex.cancellationToken, - }); - }, - ); + logger.trace(`querying reserve status via ${reserveUrl.href}`); + const resp = await cancelableLongPool(wex, reserveUrl, { + timeout: getReserveRequestTimeout(withdrawalGroup), + }); logger.trace(`reserve status code: HTTP ${resp.status}`); @@ -2123,10 +2104,9 @@ async function processWithdrawalGroupAbortingBank( } const abortUrl = getBankAbortUrl(wgInfo.bankInfo.talerWithdrawUri); logger.info(`aborting withdrawal at ${abortUrl}`); - const abortResp = await wex.http.fetch(abortUrl, { + const abortResp = await cancelableFetch(wex, abortUrl, { method: "POST", body: {}, - cancellationToken: wex.cancellationToken, }); logger.info(`abort response status: ${abortResp.status}`); @@ -2157,28 +2137,18 @@ async function processWithdrawalGroupPendingKyc( if (!kycPaytoHash) { throw Error("no kyc info available in pending(kyc)"); } - const exchangeUrl = withdrawalGroup.exchangeBaseUrl; - const url = new URL(`kyc-check/${kycPaytoHash}`, exchangeUrl); const sigResp = await wex.cryptoApi.signWalletKycAuth({ accountPriv: withdrawalGroup.reservePriv, accountPub: withdrawalGroup.reservePub, }); - const kycStatusRes = await wex.ws.runLongpollQueueing( - wex.cancellationToken, - url.hostname, - async (timeoutMs) => { - url.searchParams.set("timeout_ms", `${timeoutMs}`); - url.searchParams.set("lpt", "3"); // wait for the KYC status to be OK - logger.info(`long-polling for withdrawal KYC status via ${url.href}`); - return await wex.http.fetch(url.href, { - method: "GET", - cancellationToken: wex.cancellationToken, - headers: { - ["Account-Owner-Signature"]: sigResp.sig, - }, - }); + const url = new URL(`kyc-check/${kycPaytoHash}`, withdrawalGroup.exchangeBaseUrl); + url.searchParams.set("lpt", "3"); // wait for the KYC status to be OK + logger.info(`long-polling for withdrawal KYC status via ${url.href}`); + const kycStatusRes = await cancelableLongPool(wex, url, { + headers: { + ["Account-Owner-Signature"]: sigResp.sig, }, - ); + }); logger.info(`kyc long-polling response status: HTTP ${kycStatusRes.status}`); if ( @@ -2678,8 +2648,7 @@ export async function getExchangeWithdrawalInfo( { exchange, instructedAmount, - }, - wex.cancellationToken, + } ); logger.trace("updating withdrawal denoms"); @@ -2923,7 +2892,7 @@ export function getReserveRequestTimeout(r: WithdrawalGroupRecord): Duration { return { d_ms: 60000 }; } -export function getBankStatusUrl(talerWithdrawUri: string): string { +export function getBankStatusUrl(talerWithdrawUri: string): URL { const uriResult = parseWithdrawUri(talerWithdrawUri); if (!uriResult) { throw Error(`can't parse withdrawal URL ${talerWithdrawUri}`); @@ -2932,10 +2901,10 @@ export function getBankStatusUrl(talerWithdrawUri: string): string { `withdrawal-operation/${uriResult.withdrawalOperationId}`, uriResult.bankIntegrationApiBaseUrl, ); - return url.href; + return url; } -export function getBankAbortUrl(talerWithdrawUri: string): string { +export function getBankAbortUrl(talerWithdrawUri: string): URL { const uriResult = parseWithdrawUri(talerWithdrawUri); if (!uriResult) { throw Error(`can't parse withdrawal URL ${talerWithdrawUri}`); @@ -2944,7 +2913,7 @@ export function getBankAbortUrl(talerWithdrawUri: string): string { `withdrawal-operation/${uriResult.withdrawalOperationId}/abort`, uriResult.bankIntegrationApiBaseUrl, ); - return url.href; + return url; } async function registerReserveWithBank( @@ -2987,11 +2956,10 @@ async function registerReserveWithBank( } logger.trace(`isFlexibleAmount: ${isFlexibleAmount}`); logger.info(`registering reserve with bank: ${j2s(reqBody)}`); - const httpResp = await wex.http.fetch(bankStatusUrl, { + const httpResp = await cancelableFetch(wex, bankStatusUrl, { method: "POST", body: reqBody, - timeout: getReserveRequestTimeout(withdrawalGroup), - cancellationToken: wex.cancellationToken, + timeout: getReserveRequestTimeout(withdrawalGroup) }); switch (httpResp.status) { @@ -3100,9 +3068,8 @@ async function processBankRegisterReserve( uriResult.bankIntegrationApiBaseUrl, ); - const statusResp = await wex.http.fetch(url.href, { + const statusResp = await cancelableFetch(wex, url, { timeout: getReserveRequestTimeout(withdrawalGroup), - cancellationToken: wex.cancellationToken, }); // FIXME: Consider looking at the exact taler error code @@ -3182,9 +3149,8 @@ async function processReserveBankStatus( bankStatusUrl.searchParams.set("old_state", "selected"); logger.info(`long-polling for withdrawal operation at ${bankStatusUrl.href}`); - const statusResp = await wex.http.fetch(bankStatusUrl.href, { + const statusResp = await cancelableFetch(wex, bankStatusUrl, { timeout: getReserveRequestTimeout(withdrawalGroup), - cancellationToken: wex.cancellationToken, }); logger.info( `long-polling for withdrawal operation returned status ${statusResp.status}`, @@ -3747,8 +3713,7 @@ export async function confirmWithdrawal( { exchange, instructedAmount, - }, - wex.cancellationToken, + } ); } @@ -4011,8 +3976,7 @@ async function fetchAccount( instructedAmount: AmountJson, scopeInfo: ScopeInfo, acct: ExchangeWireAccount, - reservePub: string | undefined, - cancellationToken: CancellationToken, + reservePub: string | undefined ): Promise<WithdrawalExchangeAccountDetails> { let paytoUri: string; let transferAmount: AmountString | undefined; @@ -4023,9 +3987,7 @@ async function fetchAccount( "amount_credit", Amounts.stringify(instructedAmount), ); - const httpResp = await wex.http.fetch(reqUrl.href, { - cancellationToken, - }); + const httpResp = await cancelableFetch(wex, reqUrl); const respOrErr = await readSuccessResponseJsonOrErrorCode( httpResp, codecForCashinConversionResponse(), @@ -4041,9 +4003,7 @@ async function fetchAccount( paytoUri = acct.payto_uri; transferAmount = resp.amount_debit; const configUrl = new URL("config", acct.conversion_url); - const configResp = await wex.http.fetch(configUrl.href, { - cancellationToken, - }); + const configResp = await cancelableFetch(wex, configUrl); const configRespOrError = await readSuccessResponseJsonOrErrorCode( configResp, codecForConversionBankConfig(), @@ -4107,8 +4067,7 @@ async function fetchWithdrawalAccountInfo( exchange: ReadyExchangeSummary; instructedAmount: AmountJson; reservePub?: string; - }, - cancellationToken: CancellationToken, + } ): Promise<WithdrawalExchangeAccountDetails[]> { const { exchange } = req; const withdrawalAccounts: WithdrawalExchangeAccountDetails[] = []; @@ -4118,8 +4077,7 @@ async function fetchWithdrawalAccountInfo( req.instructedAmount, req.exchange.scopeInfo, acct, - req.reservePub, - cancellationToken, + req.reservePub ); withdrawalAccounts.push(acctInfo); } @@ -4184,8 +4142,7 @@ export async function createManualWithdrawal( exchange, instructedAmount: amount, reservePub: reserveKeyPair.pub, - }, - CancellationToken.CONTINUE, + } ); const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {