commit a8d66b9b095467f414a156dc12ec4f1d2ec3e1c7
parent 2609c7359356f1e991e41eceb7221c424d53bd1b
Author: Florian Dold <florian@dold.me>
Date: Mon, 15 Jul 2024 15:45:12 +0200
wallet-core: support for clientCancellationId in more requests
Added for:
- checkPeerPushDebit
- checkPeerPullCredit
Diffstat:
6 files changed, 119 insertions(+), 38 deletions(-)
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -2805,6 +2805,19 @@ export interface CheckPeerPushDebitRequest {
* FIXME: Allow specifying the instructed amount type.
*/
amount: AmountString;
+
+ /**
+ * ID provided by the client to cancel the request.
+ *
+ * If the same request is made again with the same clientCancellationId,
+ * all previous requests are cancelled.
+ *
+ * The cancelled request will receive an error response with
+ * an error code that indicates the cancellation.
+ *
+ * The cancellation is best-effort, responses might still arrive.
+ */
+ clientCancellationId?: string;
}
export const codecForCheckPeerPushDebitRequest =
@@ -2812,6 +2825,7 @@ export const codecForCheckPeerPushDebitRequest =
buildCodecForObject<CheckPeerPushDebitRequest>()
.property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl()))
.property("amount", codecForAmountString())
+ .property("clientCancellationId", codecOptional(codecForString()))
.build("CheckPeerPushDebitRequest");
export interface CheckPeerPushDebitResponse {
@@ -2944,12 +2958,27 @@ export const codecForAcceptPeerPullPaymentRequest =
export interface CheckPeerPullCreditRequest {
exchangeBaseUrl?: string;
amount: AmountString;
+
+ /**
+ * ID provided by the client to cancel the request.
+ *
+ * If the same request is made again with the same clientCancellationId,
+ * all previous requests are cancelled.
+ *
+ * The cancelled request will receive an error response with
+ * an error code that indicates the cancellation.
+ *
+ * The cancellation is best-effort, responses might still arrive.
+ */
+ clientCancellationId?: string;
}
+
export const codecForPreparePeerPullPaymentRequest =
(): Codec<CheckPeerPullCreditRequest> =>
buildCodecForObject<CheckPeerPullCreditRequest>()
.property("amount", codecForAmountString())
.property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl()))
+ .property("clientCancellationId", codecOptional(codecForString()))
.build("CheckPeerPullCreditRequest");
export interface CheckPeerPullCreditResponse {
@@ -2963,6 +2992,7 @@ export interface CheckPeerPullCreditResponse {
*/
numCoins: number;
}
+
export interface InitiatePeerPullCreditRequest {
exchangeBaseUrl?: string;
partialContractTerms: PeerContractTerms;
diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts
@@ -30,6 +30,7 @@ import {
ExchangeTosStatus,
ExchangeUpdateStatus,
Logger,
+ ObservabilityEventType,
RefreshReason,
TalerError,
TalerErrorCode,
@@ -866,3 +867,52 @@ export function requireExchangeTosAcceptedOrThrow(
);
}
}
+
+/**
+ * Run a request, but cancel the wallet execution context as soon as the client
+ * submits another request of the same type.
+ */
+export async function runWithClientCancellation<R, T>(
+ wex: WalletExecutionContext,
+ operation: string,
+ clientCancellationId: string | undefined,
+ handler: () => Promise<T>,
+): Promise<T> {
+ const clientCancelKey = clientCancellationId
+ ? `ccid:${operation}:${clientCancellationId}`
+ : undefined;
+ const cts = wex.cts;
+ if (clientCancelKey && cts) {
+ const prevCts = wex.ws.clientCancellationMap.get(clientCancelKey);
+ if (prevCts) {
+ wex.oc.observe({
+ type: ObservabilityEventType.Message,
+ contents: `Cancelling previous key ${clientCancelKey}`,
+ });
+ prevCts.cancel(
+ `cancelled by subsequent request with same cancellation ID`,
+ );
+ } else {
+ wex.oc.observe({
+ type: ObservabilityEventType.Message,
+ contents: `No previous key ${clientCancelKey}`,
+ });
+ }
+ wex.oc.observe({
+ type: ObservabilityEventType.Message,
+ contents: `Setting clientCancelKey ${clientCancelKey} to ${cts}`,
+ });
+ wex.ws.clientCancellationMap.set(clientCancelKey, cts);
+ }
+ try {
+ return await handler();
+ } finally {
+ wex.oc.observe({
+ type: ObservabilityEventType.Message,
+ contents: `Deleting clientCancelKey ${clientCancelKey} to ${cts}`,
+ });
+ if (clientCancelKey && wex.cts && !wex.cts.token.isCancelled) {
+ wex.ws.clientCancellationMap.delete(clientCancelKey);
+ }
+ }
+}
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -60,6 +60,7 @@ import {
TransactionContext,
constructTaskIdentifier,
requireExchangeTosAcceptedOrThrow,
+ runWithClientCancellation,
} from "./common.js";
import {
KycPendingInfo,
@@ -920,7 +921,22 @@ async function processPeerPullCreditKycRequired(
/**
* Check fees and available exchanges for a peer push payment initiation.
*/
-export async function checkPeerPullPaymentInitiation(
+export async function checkPeerPullCredit(
+ wex: WalletExecutionContext,
+ req: CheckPeerPullCreditRequest,
+): Promise<CheckPeerPullCreditResponse> {
+ return runWithClientCancellation(
+ wex,
+ "checkPeerPullCredit",
+ req.clientCancellationId,
+ async () => internalCheckPeerPullCredit(wex, req),
+ );
+}
+
+/**
+ * Check fees and available exchanges for a peer push payment initiation.
+ */
+export async function internalCheckPeerPullCredit(
wex: WalletExecutionContext,
req: CheckPeerPullCreditRequest,
): Promise<CheckPeerPullCreditResponse> {
diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
@@ -63,6 +63,7 @@ import {
TaskRunResultType,
TransactionContext,
constructTaskIdentifier,
+ runWithClientCancellation,
spendCoins,
} from "./common.js";
import { EncryptContractRequest } from "./crypto/cryptoTypes.js";
@@ -350,6 +351,18 @@ export async function checkPeerPushDebit(
wex: WalletExecutionContext,
req: CheckPeerPushDebitRequest,
): Promise<CheckPeerPushDebitResponse> {
+ return runWithClientCancellation(
+ wex,
+ "checkPeerPushDebit",
+ req.clientCancellationId,
+ () => internalCheckPeerPushDebit(wex, req),
+ );
+}
+
+async function internalCheckPeerPushDebit(
+ wex: WalletExecutionContext,
+ req: CheckPeerPushDebitRequest,
+): Promise<CheckPeerPushDebitResponse> {
const instructedAmount = Amounts.parseOrThrow(req.amount);
logger.trace(
`checking peer push debit for ${Amounts.stringify(instructedAmount)}`,
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -290,7 +290,7 @@ import {
startRefundQueryForUri,
} from "./pay-merchant.js";
import {
- checkPeerPullPaymentInitiation,
+ checkPeerPullCredit,
initiatePeerPullPayment,
} from "./pay-peer-pull-credit.js";
import {
@@ -1920,7 +1920,7 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
},
[WalletApiOperation.CheckPeerPullCredit]: {
codec: codecForPreparePeerPullPaymentRequest(),
- handler: checkPeerPullPaymentInitiation,
+ handler: checkPeerPullCredit,
},
[WalletApiOperation.InitiatePeerPullCredit]: {
codec: codecForInitiatePeerPullPaymentRequest(),
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -122,6 +122,7 @@ import {
makeCoinAvailable,
makeCoinsVisible,
requireExchangeTosAcceptedOrThrow,
+ runWithClientCancellation,
} from "./common.js";
import { EddsaKeypair } from "./crypto/cryptoImplementation.js";
import {
@@ -3808,41 +3809,12 @@ export async function getWithdrawalDetailsForAmount(
wex: WalletExecutionContext,
req: GetWithdrawalDetailsForAmountRequest,
): Promise<WithdrawalDetailsForAmount> {
- const clientCancelKey = req.clientCancellationId
- ? `ccid:getWithdrawalDetailsForAmount:${req.clientCancellationId}`
- : undefined;
- const cts = wex.cts;
- if (clientCancelKey && cts) {
- const prevCts = wex.ws.clientCancellationMap.get(clientCancelKey);
- if (prevCts) {
- wex.oc.observe({
- type: ObservabilityEventType.Message,
- contents: `Cancelling previous key ${clientCancelKey}`,
- });
- prevCts.cancel(`getting details amount`);
- } else {
- wex.oc.observe({
- type: ObservabilityEventType.Message,
- contents: `No previous key ${clientCancelKey}`,
- });
- }
- wex.oc.observe({
- type: ObservabilityEventType.Message,
- contents: `Setting clientCancelKey ${clientCancelKey} to ${cts}`,
- });
- wex.ws.clientCancellationMap.set(clientCancelKey, cts);
- }
- try {
- return await internalGetWithdrawalDetailsForAmount(wex, req);
- } finally {
- wex.oc.observe({
- type: ObservabilityEventType.Message,
- contents: `Deleting clientCancelKey ${clientCancelKey} to ${cts}`,
- });
- if (clientCancelKey && wex.cts && !wex.cts.token.isCancelled) {
- wex.ws.clientCancellationMap.delete(clientCancelKey);
- }
- }
+ return runWithClientCancellation(
+ wex,
+ "getWithdrawalDetailsForAmount",
+ req.clientCancellationId,
+ async () => internalGetWithdrawalDetailsForAmount(wex, req),
+ );
}
async function internalGetWithdrawalDetailsForAmount(