commit 03b8a0058c7030bd23336112320d879e080bc753
parent 50f4866cc0a435c22be3c99832a1febce6951bc1
Author: Florian Dold <florian@dold.me>
Date: Wed, 30 Oct 2024 12:33:01 +0100
wallet-core: use generic helper to wait for events
Diffstat:
2 files changed, 95 insertions(+), 151 deletions(-)
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -32,7 +32,6 @@ import {
Amounts,
AmountString,
assertUnreachable,
- AsyncFlag,
checkDbInvariant,
CheckPayTemplateReponse,
CheckPayTemplateRequest,
@@ -115,6 +114,7 @@ import {
import {
constructTaskIdentifier,
genericWaitForState,
+ genericWaitForStateVal,
PendingTaskType,
spendCoins,
TaskIdentifiers,
@@ -2060,108 +2060,80 @@ export async function generateDepositPermissions(
return depositPermissions;
}
-async function internalWaitPaymentResult(
- ctx: PayMerchantTransactionContext,
- purchaseNotifFlag: AsyncFlag,
+/**
+ * Wait until either:
+ * a) the payment succeeded (if provided under the {@param waitSessionId}), or
+ * b) the attempt to pay failed (merchant unavailable, etc.)
+ */
+async function waitPaymentResult(
+ wex: WalletExecutionContext,
+ proposalId: string,
waitSessionId?: string,
): Promise<ConfirmPayResult> {
- while (true) {
- const txRes = await ctx.wex.db.runReadOnlyTx(
- { storeNames: ["purchases", "operationRetries"] },
- async (tx) => {
- const purchase = await tx.purchases.get(ctx.proposalId);
- const retryRecord = await tx.operationRetries.get(ctx.taskId);
- return { purchase, retryRecord };
- },
- );
+ const ctx = new PayMerchantTransactionContext(wex, proposalId);
+ wex.taskScheduler.startShepherdTask(ctx.taskId);
- if (!txRes.purchase) {
- throw Error("purchase gone");
- }
+ return await genericWaitForStateVal<ConfirmPayResult>(wex, {
+ filterNotification(notif) {
+ return (
+ notif.type === NotificationType.TransactionStateTransition &&
+ notif.transactionId === ctx.transactionId
+ );
+ },
+ async checkState() {
+ const txRes = await ctx.wex.db.runReadOnlyTx(
+ { storeNames: ["purchases", "operationRetries"] },
+ async (tx) => {
+ const purchase = await tx.purchases.get(ctx.proposalId);
+ const retryRecord = await tx.operationRetries.get(ctx.taskId);
+ return { purchase, retryRecord };
+ },
+ );
- const purchase = txRes.purchase;
+ if (!txRes.purchase) {
+ throw Error("purchase gone");
+ }
- logger.info(
- `purchase is in state ${PurchaseStatus[purchase.purchaseStatus]}`,
- );
+ const purchase = txRes.purchase;
- const d = await expectProposalDownload(ctx.wex, purchase);
+ logger.info(
+ `purchase is in state ${PurchaseStatus[purchase.purchaseStatus]}`,
+ );
- if (txRes.purchase.timestampFirstSuccessfulPay) {
- if (
- waitSessionId == null ||
- txRes.purchase.lastSessionId === waitSessionId
- ) {
+ const d = await expectProposalDownload(ctx.wex, purchase);
+
+ if (txRes.purchase.timestampFirstSuccessfulPay) {
+ if (
+ waitSessionId == null ||
+ txRes.purchase.lastSessionId === waitSessionId
+ ) {
+ return {
+ type: ConfirmPayResultType.Done,
+ contractTerms: d.contractTermsRaw,
+ transactionId: ctx.transactionId,
+ };
+ }
+ }
+
+ if (txRes.retryRecord) {
+ return {
+ type: ConfirmPayResultType.Pending,
+ lastError: txRes.retryRecord.lastError,
+ transactionId: ctx.transactionId,
+ };
+ }
+
+ if (txRes.purchase.purchaseStatus >= PurchaseStatus.Done) {
return {
type: ConfirmPayResultType.Done,
contractTerms: d.contractTermsRaw,
transactionId: ctx.transactionId,
};
}
- }
- if (txRes.retryRecord) {
- return {
- type: ConfirmPayResultType.Pending,
- lastError: txRes.retryRecord.lastError,
- transactionId: ctx.transactionId,
- };
- }
-
- if (txRes.purchase.purchaseStatus >= PurchaseStatus.Done) {
- return {
- type: ConfirmPayResultType.Done,
- contractTerms: d.contractTermsRaw,
- transactionId: ctx.transactionId,
- };
- }
-
- await purchaseNotifFlag.wait();
- purchaseNotifFlag.reset();
- }
-}
-
-/**
- * Wait until either:
- * a) the payment succeeded (if provided under the {@param waitSessionId}), or
- * b) the attempt to pay failed (merchant unavailable, etc.)
- */
-async function waitPaymentResult(
- wex: WalletExecutionContext,
- proposalId: string,
- waitSessionId?: string,
-): Promise<ConfirmPayResult> {
- // FIXME: We don't support cancelletion yet!
- const ctx = new PayMerchantTransactionContext(wex, proposalId);
- wex.taskScheduler.startShepherdTask(ctx.taskId);
-
- // FIXME: Clean up using the new JS "using" / Symbol.dispose syntax.
- const purchaseNotifFlag = new AsyncFlag();
- // Raise purchaseNotifFlag whenever we get a notification
- // about our purchase.
- const cancelNotif = wex.ws.addNotificationListener((notif) => {
- if (
- notif.type === NotificationType.TransactionStateTransition &&
- notif.transactionId === ctx.transactionId
- ) {
- purchaseNotifFlag.raise();
- }
+ return undefined;
+ },
});
-
- try {
- logger.info(`waiting for first payment success on ${ctx.transactionId}`);
- const res = await internalWaitPaymentResult(
- ctx,
- purchaseNotifFlag,
- waitSessionId,
- );
- logger.info(
- `done waiting for first payment success on ${ctx.transactionId}, result ${res.type}`,
- );
- return res;
- } finally {
- cancelNotif();
- }
}
/**
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -33,7 +33,6 @@ import {
AmountLike,
AmountString,
Amounts,
- AsyncFlag,
BankWithdrawDetails,
CancellationToken,
CoinStatus,
@@ -3956,7 +3955,7 @@ export async function createManualWithdrawal(
}
/**
- * Wait until a refresh operation is final.
+ * Wait until a withdrawal operation is final.
*/
export async function waitWithdrawalFinal(
wex: WalletExecutionContext,
@@ -3965,68 +3964,41 @@ export async function waitWithdrawalFinal(
const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
wex.taskScheduler.startShepherdTask(ctx.taskId);
- // FIXME: Clean up using the new JS "using" / Symbol.dispose syntax.
- const withdrawalNotifFlag = new AsyncFlag();
- // Raise purchaseNotifFlag whenever we get a notification
- // about our refresh.
- const cancelNotif = wex.ws.addNotificationListener((notif) => {
- if (
- notif.type === NotificationType.TransactionStateTransition &&
- notif.transactionId === ctx.transactionId
- ) {
- withdrawalNotifFlag.raise();
- }
- });
- const unregisterOnCancelled = wex.cancellationToken.onCancelled(() => {
- cancelNotif();
- withdrawalNotifFlag.raise();
- });
-
- try {
- await internalWaitWithdrawalFinal(ctx, withdrawalNotifFlag);
- } catch (e) {
- unregisterOnCancelled();
- cancelNotif();
- }
-}
-
-async function internalWaitWithdrawalFinal(
- ctx: WithdrawTransactionContext,
- flag: AsyncFlag,
-): Promise<void> {
- while (true) {
- if (ctx.wex.cancellationToken.isCancelled) {
- throw Error("cancelled");
- }
-
- // Check if refresh is final
- const res = await ctx.wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups"] },
- async (tx) => {
- return {
- wg: await tx.withdrawalGroups.get(ctx.withdrawalGroupId),
- };
- },
- );
- const { wg } = res;
- if (!wg) {
- // Must've been deleted, we consider that final.
- return;
- }
- switch (wg.status) {
- case WithdrawalGroupStatus.AbortedBank:
- case WithdrawalGroupStatus.AbortedExchange:
- case WithdrawalGroupStatus.Done:
- case WithdrawalGroupStatus.FailedAbortingBank:
- case WithdrawalGroupStatus.FailedBankAborted:
- // Transaction is final
- return;
- }
+ await genericWaitForState(wex, {
+ filterNotification(notif) {
+ return (
+ notif.type === NotificationType.TransactionStateTransition &&
+ notif.transactionId === ctx.transactionId
+ );
+ },
+ async checkState() {
+ // Check if withdrawal is final
+ const res = await ctx.wex.db.runReadOnlyTx(
+ { storeNames: ["withdrawalGroups"] },
+ async (tx) => {
+ return {
+ wg: await tx.withdrawalGroups.get(ctx.withdrawalGroupId),
+ };
+ },
+ );
+ const { wg } = res;
+ if (!wg) {
+ // Must've been deleted, we consider that final.
+ return true;
+ }
+ switch (wg.status) {
+ case WithdrawalGroupStatus.AbortedBank:
+ case WithdrawalGroupStatus.AbortedExchange:
+ case WithdrawalGroupStatus.Done:
+ case WithdrawalGroupStatus.FailedAbortingBank:
+ case WithdrawalGroupStatus.FailedBankAborted:
+ // Transaction is final
+ return true;
+ }
- // Wait for the next transition
- await flag.wait();
- flag.reset();
- }
+ return false;
+ },
+ });
}
export async function getWithdrawalDetailsForAmount(