gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-typescript-core] branch master updated: wallet-core: refactor tra


From: Admin
Subject: [taler-typescript-core] branch master updated: wallet-core: refactor transaction/balance notifications
Date: Wed, 04 Jun 2025 22:33:43 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository taler-typescript-core.

The following commit(s) were added to refs/heads/master by this push:
     new 7efd43522 wallet-core: refactor transaction/balance notifications
7efd43522 is described below

commit 7efd435222d0e5a35d3319ddbbf5314c402830f8
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Jun 4 22:32:18 2025 +0200

    wallet-core: refactor transaction/balance notifications
    
    Instead of threading the notifications explicitly through the code, the
    database handle now supports caching notifications and emitting them iff
    the transaction has commited successfully.
---
 packages/taler-wallet-core/src/db.ts               |   8 --
 packages/taler-wallet-core/src/deposits.ts         |  17 ++--
 packages/taler-wallet-core/src/exchanges.ts        |  52 +++++------
 packages/taler-wallet-core/src/pay-peer-common.ts  |  28 +++---
 .../taler-wallet-core/src/pay-peer-pull-credit.ts  |  15 +++-
 .../taler-wallet-core/src/pay-peer-push-credit.ts  |  61 +++++--------
 packages/taler-wallet-core/src/query.ts            | 100 ++++++++++++++++-----
 packages/taler-wallet-core/src/refresh.ts          |  37 ++++----
 packages/taler-wallet-core/src/transactions.ts     |  59 +++++++++---
 packages/taler-wallet-core/src/wallet.ts           |   9 ++
 packages/taler-wallet-core/src/withdraw.ts         |  65 +++++---------
 11 files changed, 251 insertions(+), 200 deletions(-)

diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index e61795357..0322cbeb0 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -3823,14 +3823,6 @@ export async function openTalerDatabase(
     onTalerDbUpgradeNeeded,
   );
 
-  const mainDbAccess = new DbAccessImpl(
-    mainDbHandle,
-    WalletStoresV1,
-    {},
-    CancellationToken.CONTINUE,
-  );
-  await applyFixups(mainDbAccess);
-
   return mainDbHandle;
 }
 
diff --git a/packages/taler-wallet-core/src/deposits.ts 
b/packages/taler-wallet-core/src/deposits.ts
index 65260ec9e..a3b14100e 100644
--- a/packages/taler-wallet-core/src/deposits.ts
+++ b/packages/taler-wallet-core/src/deposits.ts
@@ -135,7 +135,7 @@ import {
 } from "./refresh.js";
 import {
   BalanceEffect,
-  TransitionInfo,
+  applyNotifyTransition,
   constructTransactionIdentifier,
   isUnsuccessfulTransaction,
   notifyTransition,
@@ -362,7 +362,7 @@ export class DepositTransactionContext implements 
TransactionContext {
 
   async suspendTransaction(): Promise<void> {
     const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await wex.db.runReadWriteTx(
+    await wex.db.runReadWriteTx(
       { storeNames: ["depositGroups", "transactionsMeta"] },
       async (tx) => {
         const dg = await tx.depositGroups.get(depositGroupId);
@@ -417,20 +417,19 @@ export class DepositTransactionContext implements 
TransactionContext {
         dg.operationStatus = newOpStatus;
         await tx.depositGroups.put(dg);
         await this.updateTransactionMeta(tx);
-        return {
+        applyNotifyTransition(tx.notify, transactionId, {
           oldTxState: oldState,
           newTxState: computeDepositTransactionStatus(dg),
           balanceEffect: BalanceEffect.None,
-        } satisfies TransitionInfo;
+        });
       },
     );
     wex.taskScheduler.stopShepherdTask(retryTag);
-    notifyTransition(wex, transactionId, transitionInfo);
   }
 
   async abortTransaction(reason?: TalerErrorDetail): Promise<void> {
     const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await wex.db.runReadWriteTx(
+    await wex.db.runReadWriteTx(
       { storeNames: ["depositGroups", "transactionsMeta"] },
       async (tx) => {
         const dg = await tx.depositGroups.get(depositGroupId);
@@ -452,11 +451,12 @@ export class DepositTransactionContext implements 
TransactionContext {
             dg.abortReason = reason;
             await tx.depositGroups.put(dg);
             await this.updateTransactionMeta(tx);
-            return {
+            applyNotifyTransition(tx.notify, transactionId, {
               oldTxState: oldState,
               newTxState: computeDepositTransactionStatus(dg),
               balanceEffect: BalanceEffect.Any,
-            };
+            });
+            return;
           }
           case DepositOperationStatus.FinalizingTrack:
           case DepositOperationStatus.SuspendedFinalizingTrack:
@@ -478,7 +478,6 @@ export class DepositTransactionContext implements 
TransactionContext {
       },
     );
     wex.taskScheduler.stopShepherdTask(retryTag);
-    notifyTransition(wex, transactionId, transitionInfo);
     wex.taskScheduler.startShepherdTask(retryTag);
   }
 
diff --git a/packages/taler-wallet-core/src/exchanges.ts 
b/packages/taler-wallet-core/src/exchanges.ts
index 11bd6543f..ebcef58b3 100644
--- a/packages/taler-wallet-core/src/exchanges.ts
+++ b/packages/taler-wallet-core/src/exchanges.ts
@@ -181,8 +181,8 @@ import { RecoupTransactionContext, createRecoupGroup } from 
"./recoup.js";
 import { RefreshTransactionContext, createRefreshGroup } from "./refresh.js";
 import {
   BalanceEffect,
+  applyNotifyTransition,
   constructTransactionIdentifier,
-  notifyTransition,
   rematerializeTransactions,
 } from "./transactions.js";
 import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "./versions.js";
@@ -2408,25 +2408,24 @@ export class DenomLossTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const transitionInfo = await this.wex.db.runReadWriteTx(
+    await this.wex.db.runReadWriteTx(
       { storeNames: ["denomLossEvents"] },
       async (tx) => {
         const rec = await tx.denomLossEvents.get(this.denomLossEventId);
-        if (rec) {
-          const oldTxState = computeDenomLossTransactionStatus(rec);
-          await tx.denomLossEvents.delete(this.denomLossEventId);
-          return {
-            oldTxState,
-            newTxState: {
-              major: TransactionMajorState.Deleted,
-            },
-            balanceEffect: BalanceEffect.Any,
-          };
+        if (!rec) {
+          return;
         }
-        return undefined;
+        const oldTxState = computeDenomLossTransactionStatus(rec);
+        await tx.denomLossEvents.delete(this.denomLossEventId);
+        applyNotifyTransition(tx.notify, this.transactionId, {
+          oldTxState,
+          newTxState: {
+            major: TransactionMajorState.Deleted,
+          },
+          balanceEffect: BalanceEffect.Any,
+        });
       },
     );
-    notifyTransition(this.wex, this.transactionId, transitionInfo);
   }
 
   async lookupFullTransaction(
@@ -2728,14 +2727,12 @@ export async function listExchanges(
 export async function markExchangeUsed(
   tx: WalletDbReadWriteTransaction<["exchanges"]>,
   exchangeBaseUrl: string,
-): Promise<{ notif: WalletNotification | undefined }> {
+): Promise<void> {
   logger.trace(`marking exchange ${exchangeBaseUrl} as used`);
   const exch = await tx.exchanges.get(exchangeBaseUrl);
   if (!exch) {
     logger.info(`exchange ${exchangeBaseUrl} NOT found`);
-    return {
-      notif: undefined,
-    };
+    return;
   }
 
   const oldExchangeState = getExchangeState(exch);
@@ -2745,19 +2742,16 @@ export async function markExchangeUsed(
       exch.entryStatus = ExchangeEntryDbRecordStatus.Used;
       await tx.exchanges.put(exch);
       const newExchangeState = getExchangeState(exch);
-      return {
-        notif: {
-          type: NotificationType.ExchangeStateTransition,
-          exchangeBaseUrl,
-          newExchangeState: newExchangeState,
-          oldExchangeState: oldExchangeState,
-        } satisfies WalletNotification,
-      };
+      tx.notify({
+        type: NotificationType.ExchangeStateTransition,
+        exchangeBaseUrl,
+        newExchangeState: newExchangeState,
+        oldExchangeState: oldExchangeState,
+      });
+      return;
     }
     default:
-      return {
-        notif: undefined,
-      };
+      return;
   }
 }
 
diff --git a/packages/taler-wallet-core/src/pay-peer-common.ts 
b/packages/taler-wallet-core/src/pay-peer-common.ts
index 991a1123f..17bed155d 100644
--- a/packages/taler-wallet-core/src/pay-peer-common.ts
+++ b/packages/taler-wallet-core/src/pay-peer-common.ts
@@ -44,11 +44,7 @@ import {
   WalletStoresV1,
 } from "./db.js";
 import { getTotalRefreshCost } from "./refresh.js";
-import {
-  BalanceEffect,
-  TransitionInfo,
-  notifyTransition,
-} from "./transactions.js";
+import { BalanceEffect, applyNotifyTransition } from "./transactions.js";
 import {
   WalletExecutionContext,
   getDenomInfo,
@@ -281,10 +277,13 @@ export async function recordCreate<
       await (tx[ctx.store] as any).add(rec);
       await tx.transactionsMeta.put(ctx.recordMeta(rec));
       const newTxState = ctx.recordState(rec);
-      return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any };
+      applyNotifyTransition(tx.notify, ctx.transactionId, {
+        oldTxState,
+        newTxState,
+        balanceEffect: BalanceEffect.Any,
+      });
     },
   );
-  notifyTransition(ctx.wex, ctx.transactionId, transitionInfo);
 }
 
 /**
@@ -303,7 +302,7 @@ export async function recordTransition<
       [Store, "transactionsMeta", ...ExtraStores]
     >,
   ) => Promise<TransitionResultType.Stay | TransitionResultType.Transition>,
-): Promise<TransitionInfo | undefined> {
+): Promise<void> {
   const baseStore = [ctx.store, "transactionsMeta" as const];
   const storeNames = opts.extraStores
     ? [...baseStore, ...opts.extraStores]
@@ -323,19 +322,18 @@ export async function recordTransition<
           await tx[ctx.store].put(rec);
           await tx.transactionsMeta.put(ctx.recordMeta(rec));
           const newTxState = ctx.recordState(rec);
-          return {
+          applyNotifyTransition(tx.notify, ctx.transactionId, {
             oldTxState,
             newTxState,
             balanceEffect: BalanceEffect.Any,
-          };
+          });
+          return;
         }
         case TransitionResultType.Stay:
           return;
       }
     },
   );
-  notifyTransition(ctx.wex, ctx.transactionId, transitionInfo);
-  return transitionInfo;
 }
 
 /** Extract the stored type status if any */
@@ -345,13 +343,13 @@ type StoreTypeStatus<Store extends WalletDbStoresName> =
 /**
  * Optionally update an existing record status from a state to another, ignore 
if missing.
  * If a transition occurs, update its metadata and notify.
- **/
+ */
 export async function recordTransitionStatus<Store extends WalletDbStoresName>(
   ctx: RecordCtx<Store>,
   from: StoreTypeStatus<Store>,
   to: StoreTypeStatus<Store>,
-): Promise<TransitionInfo | undefined> {
-  return recordTransition(ctx, {}, async (rec, _) => {
+): Promise<void> {
+  await recordTransition(ctx, {}, async (rec, _) => {
     const it = rec as { status: StoreTypeStatus<Store> };
     if (it.status !== from) {
       return TransitionResultType.Stay;
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts 
b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
index 3e26ee1c1..183c86a2f 100644
--- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -95,6 +95,8 @@ import {
   waitForKycCompletion,
 } from "./pay-peer-common.js";
 import {
+  BalanceEffect,
+  applyNotifyBalanceEffect,
   constructTransactionIdentifier,
   isUnsuccessfulTransaction,
 } from "./transactions.js";
@@ -590,7 +592,8 @@ async function handlePeerPullCreditWithdrawing(
   await waitWithdrawalFinal(wex, pullIni.withdrawalGroupId);
   const ctx = new PeerPullCreditTransactionContext(wex, pullIni.pursePub);
   const wgId = pullIni.withdrawalGroupId;
-  const info = await recordTransition(
+  let newTxState: TransactionState | undefined;
+  await recordTransition(
     ctx,
     {
       extraStores: ["withdrawalGroups"],
@@ -610,10 +613,11 @@ async function handlePeerPullCreditWithdrawing(
           break;
         // FIXME: Also handle other final states!
       }
+      newTxState = computePeerPullCreditTransactionState(rec);
       return TransitionResultType.Transition;
     },
   );
-  if (info?.newTxState.major != TransactionMajorState.Pending) {
+  if (newTxState && newTxState.major != TransactionMajorState.Pending) {
     return TaskRunResult.finished();
   } else {
     // FIXME: Return indicator that we depend on the other operation!
@@ -904,13 +908,18 @@ async function processPeerPullCreditKycRequired(
       return TaskRunResult.finished();
     case HttpStatusCode.Accepted: {
       logger.info(`kyc status: ${j2s(res.body)}`);
-      await recordTransition(ctx, {}, async (rec) => {
+      await recordTransition(ctx, {}, async (rec, tx) => {
         rec.kycPaytoHash = kycPaytoHash;
         logger.info(
           `setting peer-pull-credit kyc payto hash to ${kycPaytoHash}`,
         );
         rec.kycAccessToken = res.body.access_token;
         rec.status = PeerPullPaymentCreditStatus.PendingMergeKycRequired;
+        applyNotifyBalanceEffect(
+          tx.notify,
+          ctx.transactionId,
+          BalanceEffect.Flags,
+        );
         return TransitionResultType.Transition;
       });
       return TaskRunResult.progress();
diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts 
b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
index bf69c9420..d96cd5353 100644
--- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
@@ -98,9 +98,9 @@ import {
 } from "./pay-peer-common.js";
 import {
   BalanceEffect,
+  applyNotifyTransition,
   constructTransactionIdentifier,
   isUnsuccessfulTransaction,
-  notifyTransition,
   parseTransactionIdentifier,
 } from "./transactions.js";
 import { WalletExecutionContext, walletExchangeClient } from "./wallet.js";
@@ -669,15 +669,12 @@ async function processPeerPushCreditKycRequired(
       return TaskRunResult.finished();
     case HttpStatusCode.Accepted:
       logger.info(`kyc-check response body: ${j2s(resp.body)}`);
-      const { transitionInfo, result } = await wex.db.runReadWriteTx(
+      return await wex.db.runReadWriteTx(
         { storeNames: ["peerPushCredit", "transactionsMeta"] },
         async (tx) => {
           const peerInc = await tx.peerPushCredit.get(ctx.peerPushCreditId);
           if (!peerInc) {
-            return {
-              transitionInfo: undefined,
-              result: TaskRunResult.finished(),
-            };
+            return TaskRunResult.finished();
           }
           const oldTxState = computePeerPushCreditTransactionState(peerInc);
           peerInc.kycPaytoHash = kycPending.h_payto;
@@ -686,18 +683,14 @@ async function processPeerPushCreditKycRequired(
           const newTxState = computePeerPushCreditTransactionState(peerInc);
           await tx.peerPushCredit.put(peerInc);
           await ctx.updateTransactionMeta(tx);
-          return {
-            transitionInfo: {
-              oldTxState,
-              newTxState,
-              balanceEffect: BalanceEffect.Any,
-            },
-            result: TaskRunResult.progress(),
-          };
+          applyNotifyTransition(tx.notify, ctx.transactionId, {
+            oldTxState,
+            newTxState,
+            balanceEffect: BalanceEffect.Flags,
+          });
+          return TaskRunResult.progress();
         },
       );
-      notifyTransition(wex, ctx.transactionId, transitionInfo);
-      return result;
     case HttpStatusCode.Conflict:
     case HttpStatusCode.Forbidden:
     case HttpStatusCode.NotFound:
@@ -825,7 +818,7 @@ async function handlePendingMerge(
     },
   });
 
-  const txRes = await wex.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     {
       storeNames: [
         "contractTerms",
@@ -862,27 +855,13 @@ async function handlePendingMerge(
       await tx.peerPushCredit.put(peerInc);
       await ctx.updateTransactionMeta(tx);
       const newTxState = computePeerPushCreditTransactionState(peerInc);
-      return {
-        peerPushCreditTransition: {
-          oldTxState,
-          newTxState,
-          balanceEffect: BalanceEffect.Any,
-        },
-        wgCreateRes,
-      };
+      applyNotifyTransition(tx.notify, ctx.transactionId, {
+        oldTxState,
+        newTxState,
+        balanceEffect: BalanceEffect.Any,
+      });
     },
   );
-  // Transaction was committed, now we can emit notifications.
-  if (txRes?.wgCreateRes?.exchangeNotif) {
-    wex.ws.notify(txRes.wgCreateRes.exchangeNotif);
-  }
-  notifyTransition(
-    wex,
-    withdrawalGroupPrep.transactionId,
-    txRes?.wgCreateRes?.transitionInfo,
-  );
-  notifyTransition(wex, ctx.transactionId, txRes?.peerPushCreditTransition);
-
   return TaskRunResult.backoff();
 }
 
@@ -900,7 +879,7 @@ async function handlePendingWithdrawing(
   );
   const wgId = peerInc.withdrawalGroupId;
   let finished: boolean = false;
-  const transitionInfo = await wex.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     { storeNames: ["peerPushCredit", "withdrawalGroups", "transactionsMeta"] },
     async (tx) => {
       const ppi = await tx.peerPushCredit.get(peerInc.peerPushCreditId);
@@ -916,7 +895,7 @@ async function handlePendingWithdrawing(
       const wg = await tx.withdrawalGroups.get(wgId);
       if (!wg) {
         // FIXME: Fail the operation instead?
-        return undefined;
+        return;
       }
       switch (wg.status) {
         case WithdrawalGroupStatus.Done:
@@ -928,14 +907,14 @@ async function handlePendingWithdrawing(
       await tx.peerPushCredit.put(ppi);
       await ctx.updateTransactionMeta(tx);
       const newTxState = computePeerPushCreditTransactionState(ppi);
-      return {
+      applyNotifyTransition(tx.notify, ctx.transactionId, {
         oldTxState,
         newTxState,
         balanceEffect: BalanceEffect.Any,
-      };
+      });
+      return;
     },
   );
-  notifyTransition(wex, ctx.transactionId, transitionInfo);
   if (finished) {
     return TaskRunResult.finished();
   } else {
diff --git a/packages/taler-wallet-core/src/query.ts 
b/packages/taler-wallet-core/src/query.ts
index 53e347aa9..2dc524e8a 100644
--- a/packages/taler-wallet-core/src/query.ts
+++ b/packages/taler-wallet-core/src/query.ts
@@ -43,6 +43,7 @@ import {
   Logger,
   openPromise,
   safeStringifyException,
+  WalletNotification,
 } from "@gnu-taler/taler-util";
 
 const logger = new Logger("query.ts");
@@ -526,8 +527,8 @@ type DerefKeyPath<T, P> = P extends `${infer PX extends 
keyof T &
   KeyPathComponents}`
   ? T[PX]
   : P extends `${infer P0 extends keyof T & KeyPathComponents}.${infer Rest}`
-  ? DerefKeyPath<T[P0], Rest>
-  : unknown;
+    ? DerefKeyPath<T[P0], Rest>
+    : unknown;
 
 /**
  * Return a path if it is a valid dot-separate path to an object.
@@ -537,8 +538,8 @@ type ValidateKeyPath<T, P> = P extends `${infer PX extends 
keyof T &
   KeyPathComponents}`
   ? PX
   : P extends `${infer P0 extends keyof T & KeyPathComponents}.${infer Rest}`
-  ? `${P0}.${ValidateKeyPath<T[P0], Rest>}`
-  : never;
+    ? `${P0}.${ValidateKeyPath<T[P0], Rest>}`
+    : never;
 
 // function foo<T, P>(
 //   x: T,
@@ -547,20 +548,31 @@ type ValidateKeyPath<T, P> = P extends `${infer PX 
extends keyof T &
 
 // foo({x: [0,1,2]}, "x.0");
 
-export type StoreMap = { [Store: string]: StoreWithIndexes<any, any, any> }
-export type StoreNames<Stores extends StoreMap> = keyof Stores
+export type StoreMap = { [Store: string]: StoreWithIndexes<any, any, any> };
+export type StoreNames<Stores extends StoreMap> = keyof Stores;
 export type DbReadWriteTransaction<
   Stores extends StoreMap,
   StoresArr extends Array<StoreNames<Stores>>,
 > = {
-    [X in StoresArr[number]]: 
StoreReadWriteAccessor<Stores[X]['store']['_dummy'], Stores[X]['indexMap']>
-  }
+  [X in StoresArr[number]]: StoreReadWriteAccessor<
+    Stores[X]["store"]["_dummy"],
+    Stores[X]["indexMap"]
+  >;
+} & {
+  notify: (w: WalletNotification) => void;
+};
+
 export type DbReadOnlyTransaction<
   Stores extends StoreMap,
   StoresArr extends Array<StoreNames<Stores>>,
 > = {
-    [X in StoresArr[number]]: 
StoreReadOnlyAccessor<Stores[X]['store']['_dummy'], Stores[X]['indexMap']>
-  }
+  [X in StoresArr[number]]: StoreReadOnlyAccessor<
+    Stores[X]["store"]["_dummy"],
+    Stores[X]["indexMap"]
+  >;
+} & {
+  notify: (w: WalletNotification) => void;
+};
 
 /**
  * Convert the type of an array to a union of the contents.
@@ -623,6 +635,13 @@ function runTx<Arg, Res>(
         resolve(funResult);
       }
       internalContext.handleAfterCommit();
+      // Notify here.
+      if (
+        internalContext.notifications.length > 0 &&
+        internalContext.applyNotifications
+      ) {
+        internalContext.applyNotifications(internalContext.notifications);
+      }
       unregisterOnCancelled();
     };
     tx.onerror = () => {
@@ -701,12 +720,20 @@ function runTx<Arg, Res>(
  * Create a transaction handle that will be passed
  * to the main handler for the transaction.
  */
-function makeTxContext(
+function makeTxClientContext(
   tx: IDBTransaction,
   storePick: { [n: string]: StoreWithIndexes<any, any, any> },
   internalContext: InternalTransactionContext,
 ): any {
-  const ctx: { [s: string]: StoreReadWriteAccessor<any, any> } = {};
+  const ctx: {
+    [s: string]:
+      | StoreReadWriteAccessor<any, any>
+      | ((notif: WalletNotification) => void);
+  } = {
+    notify(notif: WalletNotification): void {
+      internalContext.scheduleNotification(notif);
+    },
+  };
   for (const storeAlias in storePick) {
     const indexes: { [s: string]: IndexReadWriteAccessor<any> } = {};
     const swi = storePick[storeAlias];
@@ -926,17 +953,23 @@ class InternalTransactionContext {
   storesModified: Set<string> = new Set();
   allowWrite: boolean;
   abortExn: TransactionAbortedError | undefined;
+  notifications: WalletNotification[] = [];
 
   constructor(
-    private triggerSpec: TriggerSpec,
-    private mode: IDBTransactionMode,
-    scope: string[],
-    public cancellationToken: CancellationToken,
+    private readonly triggerSpec: TriggerSpec,
+    private readonly mode: IDBTransactionMode,
+    readonly scope: string[],
+    public readonly cancellationToken: CancellationToken,
+    public readonly applyNotifications?: (notifs: WalletNotification[]) => 
void,
   ) {
     this.storesScope = new Set(scope);
     this.allowWrite = mode === "readwrite" || mode === "versionchange";
   }
 
+  scheduleNotification(notif: WalletNotification): void {
+    this.notifications.push(notif);
+  }
+
   handleAfterCommit() {
     if (this.triggerSpec.afterCommit) {
       this.triggerSpec.afterCommit({
@@ -967,7 +1000,8 @@ export class DbAccessImpl<Stores extends StoreMap> 
implements DbAccess<Stores> {
     private stores: Stores,
     private triggers: TriggerSpec = {},
     private cancellationToken: CancellationToken,
-  ) { }
+    private applyNotifications?: (notifs: WalletNotification[]) => void,
+  ) {}
 
   idbHandle(): IDBDatabase {
     return this.db;
@@ -996,9 +1030,14 @@ export class DbAccessImpl<Stores extends StoreMap> 
implements DbAccess<Stores> {
       mode,
       strStoreNames,
       this.cancellationToken,
+      this.applyNotifications,
     );
     const tx = this.db.transaction(strStoreNames, mode);
-    const writeContext = makeTxContext(tx, accessibleStores, triggerContext);
+    const writeContext = makeTxClientContext(
+      tx,
+      accessibleStores,
+      triggerContext,
+    );
     return await runTx(tx, writeContext, txf, triggerContext);
   }
 
@@ -1020,15 +1059,20 @@ export class DbAccessImpl<Stores extends StoreMap> 
implements DbAccess<Stores> {
       accessibleStores[swi.storeName] = swi;
     }
     const mode = "readonly";
-    const triggerContext = new InternalTransactionContext(
+    const internalContext = new InternalTransactionContext(
       this.triggers,
       mode,
       strStoreNames,
       this.cancellationToken,
+      this.applyNotifications,
     );
     const tx = this.db.transaction(strStoreNames, mode);
-    const writeContext = makeTxContext(tx, accessibleStores, triggerContext);
-    const res = await runTx(tx, writeContext, txf, triggerContext);
+    const writeContext = makeTxClientContext(
+      tx,
+      accessibleStores,
+      internalContext,
+    );
+    const res = await runTx(tx, writeContext, txf, internalContext);
     return res;
   }
 
@@ -1053,9 +1097,14 @@ export class DbAccessImpl<Stores extends StoreMap> 
implements DbAccess<Stores> {
       mode,
       strStoreNames,
       this.cancellationToken,
+      this.applyNotifications,
     );
     const tx = this.db.transaction(strStoreNames, mode);
-    const writeContext = makeTxContext(tx, accessibleStores, triggerContext);
+    const writeContext = makeTxClientContext(
+      tx,
+      accessibleStores,
+      triggerContext,
+    );
     const res = await runTx(tx, writeContext, txf, triggerContext);
     return res;
   }
@@ -1081,9 +1130,14 @@ export class DbAccessImpl<Stores extends StoreMap> 
implements DbAccess<Stores> {
       mode,
       strStoreNames,
       this.cancellationToken,
+      this.applyNotifications,
     );
     const tx = this.db.transaction(strStoreNames, mode);
-    const readContext = makeTxContext(tx, accessibleStores, triggerContext);
+    const readContext = makeTxClientContext(
+      tx,
+      accessibleStores,
+      triggerContext,
+    );
     const res = await runTx(tx, readContext, txf, triggerContext);
     return res;
   }
diff --git a/packages/taler-wallet-core/src/refresh.ts 
b/packages/taler-wallet-core/src/refresh.ts
index a5ed16aac..2f2a196ce 100644
--- a/packages/taler-wallet-core/src/refresh.ts
+++ b/packages/taler-wallet-core/src/refresh.ts
@@ -120,11 +120,10 @@ import {
 import { selectWithdrawalDenominations } from "./denomSelection.js";
 import { fetchFreshExchange, getScopeForAllExchanges } from "./exchanges.js";
 import {
+  applyNotifyTransition,
   BalanceEffect,
   constructTransactionIdentifier,
   isUnsuccessfulTransaction,
-  notifyTransition,
-  TransitionInfo,
 } from "./transactions.js";
 import {
   EXCHANGE_COINS_LOCK,
@@ -245,7 +244,7 @@ export class RefreshTransactionContext implements 
TransactionContext {
         ]
       >,
     ) => Promise<TransitionResult<RefreshGroupRecord>>,
-  ): Promise<TransitionInfo | undefined> {
+  ): Promise<boolean> {
     const baseStores = [
       "refreshGroups" as const,
       "transactionsMeta" as const,
@@ -256,7 +255,7 @@ export class RefreshTransactionContext implements 
TransactionContext {
     let stores = opts.extraStores
       ? [...baseStores, ...opts.extraStores]
       : baseStores;
-    const transitionInfo = await this.wex.db.runReadWriteTx(
+    return await this.wex.db.runReadWriteTx(
       { storeNames: stores },
       async (tx) => {
         const wgRec = await tx.refreshGroups.get(this.refreshGroupId);
@@ -274,30 +273,30 @@ export class RefreshTransactionContext implements 
TransactionContext {
             await tx.refreshGroups.put(res.rec);
             await this.updateTransactionMeta(tx);
             const newTxState = computeRefreshTransactionState(res.rec);
-            return {
+            applyNotifyTransition(tx.notify, this.transactionId, {
               oldTxState,
               newTxState,
               balanceEffect: BalanceEffect.PreserveUserVisible,
-            } satisfies TransitionInfo;
+            });
+            return true;
           }
           case TransitionResultType.Delete:
             await tx.refreshGroups.delete(this.refreshGroupId);
             await this.updateTransactionMeta(tx);
-            return {
+            applyNotifyTransition(tx.notify, this.transactionId, {
               oldTxState,
               newTxState: {
                 major: TransactionMajorState.None,
               },
               // Deletion will affect balance
               balanceEffect: BalanceEffect.Any,
-            } satisfies TransitionInfo;
+            });
+            return true;
           default:
-            return undefined;
+            return false;
         }
       },
     );
-    notifyTransition(this.wex, this.transactionId, transitionInfo);
-    return transitionInfo;
   }
 
   async deleteTransaction(): Promise<void> {
@@ -1747,7 +1746,7 @@ export async function processRefreshGroup(
   // We've processed all refresh session and can now update the
   // status of the whole refresh group.
 
-  const transitionInfo = await wex.db.runReadWriteTx(
+  const didTransition: boolean = await wex.db.runReadWriteTx(
     {
       storeNames: [
         "coins",
@@ -1759,13 +1758,13 @@ export async function processRefreshGroup(
     async (tx) => {
       const rg = await tx.refreshGroups.get(refreshGroupId);
       if (!rg) {
-        return;
+        return false;
       }
       switch (rg.operationStatus) {
         case RefreshOperationStatus.Pending:
           break;
         default:
-          return undefined;
+          return false;
       }
       const oldTxState = computeRefreshTransactionState(rg);
       const allFinal = fnutil.all(
@@ -1793,21 +1792,21 @@ export async function processRefreshGroup(
         await tx.refreshGroups.put(rg);
         await ctx.updateTransactionMeta(tx);
         const newTxState = computeRefreshTransactionState(rg);
-        return {
+        applyNotifyTransition(tx.notify, ctx.transactionId, {
           oldTxState,
           newTxState,
           balanceEffect:
             rg.operationStatus === RefreshOperationStatus.Failed
               ? BalanceEffect.Any
               : BalanceEffect.PreserveUserVisible,
-        } satisfies TransitionInfo;
+        });
+        return true;
       }
-      return undefined;
+      return false;
     },
   );
 
-  if (transitionInfo) {
-    notifyTransition(wex, ctx.transactionId, transitionInfo);
+  if (didTransition) {
     return TaskRunResult.progress();
   }
 
diff --git a/packages/taler-wallet-core/src/transactions.ts 
b/packages/taler-wallet-core/src/transactions.ts
index 11369be4a..7ba8c3e15 100644
--- a/packages/taler-wallet-core/src/transactions.ts
+++ b/packages/taler-wallet-core/src/transactions.ts
@@ -41,6 +41,7 @@ import {
   TransactionsResponse,
   TransactionState,
   TransactionType,
+  WalletNotification,
 } from "@gnu-taler/taler-util";
 import {
   constructTaskIdentifier,
@@ -942,10 +943,28 @@ export enum BalanceEffect {
 }
 
 /**
- * Notify of a state transition if necessary.
+ * Call the given notification function with a
+ * balance effect notification, if necessary.
  */
-export function notifyTransition(
-  wex: WalletExecutionContext,
+export function applyNotifyBalanceEffect(
+  notify: (n: WalletNotification) => void,
+  transactionId: string,
+  balanceEffect: BalanceEffect,
+): void {
+  if (balanceEffect > BalanceEffect.None) {
+    notify({
+      type: NotificationType.BalanceChange,
+      hintTransactionId: transactionId,
+      isInternal: balanceEffect <= BalanceEffect.PreserveUserVisible,
+    });
+  }
+}
+
+/**
+ * Notify of a state transition and balance change if necessary.
+ */
+export function applyNotifyTransition(
+  notify: (n: WalletNotification) => void,
   transactionId: string,
   transitionInfo: TransitionInfo | undefined,
   experimentalUserData: any = undefined,
@@ -957,7 +976,7 @@ export function notifyTransition(
       transitionInfo.oldTxState.minor === transitionInfo.newTxState.minor
     )
   ) {
-    wex.ws.notify({
+    notify({
       type: NotificationType.TransactionStateTransition,
       oldTxState: transitionInfo.oldTxState,
       newTxState: transitionInfo.newTxState,
@@ -965,13 +984,29 @@ export function notifyTransition(
       experimentalUserData,
     });
 
-    if (transitionInfo.balanceEffect > BalanceEffect.None) {
-      wex.ws.notify({
-        type: NotificationType.BalanceChange,
-        hintTransactionId: transactionId,
-        isInternal:
-          transitionInfo.balanceEffect <= BalanceEffect.PreserveUserVisible,
-      });
-    }
+    applyNotifyBalanceEffect(
+      notify,
+      transactionId,
+      transitionInfo.balanceEffect,
+    );
   }
 }
+
+/**
+ * Notify of a state transition if necessary.
+ *
+ * @deprecated use applyNotifyTransition inside transaction
+ */
+export function notifyTransition(
+  wex: WalletExecutionContext,
+  transactionId: string,
+  transitionInfo: TransitionInfo | undefined,
+  experimentalUserData: any = undefined,
+): void {
+  applyNotifyTransition(
+    (wn: WalletNotification) => wex.ws.notify(wn),
+    transactionId,
+    transitionInfo,
+    experimentalUserData,
+  );
+}
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 60a153a05..4b2872f9c 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -277,6 +277,7 @@ import {
   WalletDbHelpers,
   WalletDbReadOnlyTransaction,
   WalletStoresV1,
+  applyFixups,
   clearDatabase,
   exportDb,
   importDb,
@@ -2825,11 +2826,17 @@ export class InternalWalletState {
     if (!this._indexedDbHandle) {
       throw Error("db not initialized");
     }
+    const iws = this;
     return new DbAccessImpl(
       this._indexedDbHandle,
       WalletStoresV1,
       new WalletDbTriggerSpec(this),
       cancellationToken,
+      (notifs: WalletNotification[]): void => {
+        for (const notif of notifs) {
+          iws.notify(notif);
+        }
+      },
     );
   }
 
@@ -2868,6 +2875,8 @@ export class InternalWalletState {
     try {
       const myDb = await openTalerDatabase(this.idbFactory, myVersionChange);
       this._indexedDbHandle = myDb;
+      const dbAccess = this.createDbAccessHandle(CancellationToken.CONTINUE);
+      await applyFixups(dbAccess);
     } catch (e) {
       logger.error("error writing to database during initialization");
       throw TalerError.fromDetail(TalerErrorCode.WALLET_DB_UNAVAILABLE, {
diff --git a/packages/taler-wallet-core/src/withdraw.ts 
b/packages/taler-wallet-core/src/withdraw.ts
index cda575485..3d6bae389 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -184,10 +184,9 @@ import {
 import { DbAccess } from "./query.js";
 import {
   BalanceEffect,
-  TransitionInfo,
+  applyNotifyTransition,
   constructTransactionIdentifier,
   isUnsuccessfulTransaction,
-  notifyTransition,
   parseTransactionIdentifier,
 } from "./transactions.js";
 import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "./versions.js";
@@ -498,7 +497,7 @@ export class WithdrawTransactionContext implements 
TransactionContext {
         ]
       >,
     ) => Promise<TransitionResult<WithdrawalGroupRecord>>,
-  ): Promise<TransitionInfo | undefined> {
+  ): Promise<boolean> {
     const baseStores = [
       "withdrawalGroups" as const,
       "transactionsMeta" as const,
@@ -511,7 +510,7 @@ export class WithdrawTransactionContext implements 
TransactionContext {
       : baseStores;
 
     let errorThrown: Error | undefined;
-    const transitionInfo = await this.wex.db.runReadWriteTx(
+    const didTransition: boolean = await this.wex.db.runReadWriteTx(
       { storeNames: stores },
       async (tx) => {
         const wgRec = await tx.withdrawalGroups.get(this.withdrawalGroupId);
@@ -530,7 +529,7 @@ export class WithdrawTransactionContext implements 
TransactionContext {
           if (error instanceof Error) {
             errorThrown = error;
           }
-          return undefined;
+          return false;
         }
 
         switch (res.type) {
@@ -538,32 +537,33 @@ export class WithdrawTransactionContext implements 
TransactionContext {
             await tx.withdrawalGroups.put(res.rec);
             await this.updateTransactionMeta(tx);
             const newTxState = computeWithdrawalTransactionStatus(res.rec);
-            return {
+            applyNotifyTransition(tx.notify, this.transactionId, {
               oldTxState,
               newTxState,
               balanceEffect: res.balanceEffect,
-            };
+            });
+            return true;
           }
           case TransitionResultType.Delete:
             await tx.withdrawalGroups.delete(this.withdrawalGroupId);
             await this.updateTransactionMeta(tx);
-            return {
+            applyNotifyTransition(tx.notify, this.transactionId, {
               oldTxState,
               newTxState: {
                 major: TransactionMajorState.None,
               },
               balanceEffect: BalanceEffect.Any,
-            };
+            });
+            return true;
           default:
-            return undefined;
+            return false;
         }
       },
     );
     if (errorThrown) {
       throw errorThrown;
     }
-    notifyTransition(this.wex, this.transactionId, transitionInfo);
-    return transitionInfo;
+    return didTransition;
   }
 
   async deleteTransaction(): Promise<void> {
@@ -3552,14 +3552,6 @@ export async function 
internalPrepareCreateWithdrawalGroup(
 
 export interface PerformCreateWithdrawalGroupResult {
   withdrawalGroup: WithdrawalGroupRecord;
-  transitionInfo: TransitionInfo | undefined;
-
-  /**
-   * Notification for the exchange state transition.
-   *
-   * Should be emitted after the transaction has succeeded.
-   */
-  exchangeNotif: WalletNotification | undefined;
 }
 
 export async function internalPerformCreateWithdrawalGroup(
@@ -3576,8 +3568,6 @@ export async function 
internalPerformCreateWithdrawalGroup(
   if (existingWg) {
     return {
       withdrawalGroup: existingWg,
-      transitionInfo: undefined,
-      exchangeNotif: undefined,
     };
   }
   await tx.withdrawalGroups.add(withdrawalGroup);
@@ -3589,8 +3579,6 @@ export async function 
internalPerformCreateWithdrawalGroup(
   if (!prep.creationInfo) {
     return {
       withdrawalGroup,
-      transitionInfo: undefined,
-      exchangeNotif: undefined,
     };
   }
   return internalPerformExchangeWasUsed(
@@ -3624,7 +3612,7 @@ async function internalPerformExchangeWasUsed(
     balanceEffect: BalanceEffect.Any,
   };
 
-  const exchangeUsedRes = await markExchangeUsed(tx, canonExchange);
+  await markExchangeUsed(tx, canonExchange);
 
   const ctx = new WithdrawTransactionContext(
     wex,
@@ -3633,10 +3621,10 @@ async function internalPerformExchangeWasUsed(
 
   wex.taskScheduler.startShepherdTask(ctx.taskId);
 
+  applyNotifyTransition(tx.notify, ctx.transactionId, transitionInfo);
+
   return {
     withdrawalGroup,
-    transitionInfo,
-    exchangeNotif: exchangeUsedRes.notif,
   };
 }
 
@@ -3688,10 +3676,6 @@ export async function internalCreateWithdrawalGroup(
       return res;
     },
   );
-  if (res.exchangeNotif) {
-    wex.ws.notify(res.exchangeNotif);
-  }
-  notifyTransition(wex, transactionId, res.transitionInfo);
   return res.withdrawalGroup;
 }
 
@@ -4019,17 +4003,16 @@ export async function confirmWithdrawal(
   await wex.taskScheduler.resetTaskRetries(ctx.taskId);
 
   // FIXME: Merge with transaction above!
-  const res = await wex.db.runReadWriteTx({ storeNames: ["exchanges"] }, (tx) 
=>
-    internalPerformExchangeWasUsed(
-      wex,
-      tx,
-      exchange.exchangeBaseUrl,
-      withdrawalGroup,
-    ),
+  await wex.db.runReadWriteTx(
+    { storeNames: ["exchanges"] },
+    async (tx) =>
+      await internalPerformExchangeWasUsed(
+        wex,
+        tx,
+        exchange.exchangeBaseUrl,
+        withdrawalGroup,
+      ),
   );
-  if (res.exchangeNotif) {
-    wex.ws.notify(res.exchangeNotif);
-  }
 
   return {
     transactionId: req.transactionId as TransactionIdStr,

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]