commit 12d0515bb8b21e500a3a5a292c532c8b440b73a5
parent 62d5609ae9c34019f1b0f12cc47cd177ebcfb3ce
Author: Florian Dold <florian@dold.me>
Date: Tue, 17 Mar 2026 22:26:52 +0100
wallet-core: DCE, use new entry point for DB transactions
The new entry point supports retries and is a step towards the eventual
support for native sqlite3.
This commit also removes legacy features of the wallet that were never
fully implemented (user attention API, old backups) but still had
DB-related code.
Diffstat:
67 files changed, 3535 insertions(+), 9697 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-backup-basic.ts b/packages/taler-harness/src/integrationtests/test-wallet-backup-basic.ts
@@ -1,189 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Imports.
- */
-import { j2s } from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { GlobalTestState } from "../harness/harness.js";
-import {
- createSimpleTestkudosEnvironmentV3,
- createWalletDaemonWithClient,
- withdrawViaBankV3,
-} from "../harness/environments.js";
-import { SyncService } from "../harness/sync.js";
-
-/**
- * Run test for basic, bank-integrated withdrawal.
- */
-export async function runWalletBackupBasicTest(t: GlobalTestState) {
- // Set up test environment
-
- const { commonDb, merchant, walletClient, bankClient, exchange } =
- await createSimpleTestkudosEnvironmentV3(t);
-
- const sync = await SyncService.create(t, {
- currency: "TESTKUDOS",
- annualFee: "TESTKUDOS:0.5",
- database: commonDb.connStr,
- fulfillmentUrl: "taler://fulfillment-success",
- httpPort: 8089,
- name: "sync1",
- paymentBackendUrl: merchant.makeInstanceBaseUrl(),
- uploadLimitMb: 10,
- });
-
- await sync.start();
- await sync.pingUntilAvailable();
-
- await walletClient.call(WalletApiOperation.AddBackupProvider, {
- backupProviderBaseUrl: sync.baseUrl,
- activate: false,
- name: sync.baseUrl,
- });
-
- {
- const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});
- t.assertDeepEqual(bi.providers[0].active, false);
- }
-
- await walletClient.call(WalletApiOperation.AddBackupProvider, {
- backupProviderBaseUrl: sync.baseUrl,
- activate: true,
- name: sync.baseUrl,
- });
-
- {
- const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});
- t.assertDeepEqual(bi.providers[0].active, true);
- }
-
- await walletClient.call(WalletApiOperation.RunBackupCycle, {});
-
- {
- const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});
- console.log(bi);
- t.assertDeepEqual(
- bi.providers[0].paymentStatus.type,
- "insufficient-balance",
- );
- }
-
- await withdrawViaBankV3(t, {
- walletClient,
- bankClient,
- exchange,
- amount: "TESTKUDOS:10",
- });
-
- await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
-
- await walletClient.call(WalletApiOperation.RunBackupCycle, {});
-
- {
- const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});
- console.log(bi);
- }
-
- await withdrawViaBankV3(t, {
- walletClient,
- bankClient,
- exchange,
- amount: "TESTKUDOS:5",
- });
-
- await walletClient.call(WalletApiOperation.RunBackupCycle, {});
-
- {
- const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});
- console.log(bi);
- }
-
- const backupRecovery = await walletClient.call(
- WalletApiOperation.ExportBackupRecovery,
- {},
- );
-
- const txs = await walletClient.call(WalletApiOperation.GetTransactions, {});
- console.log(`backed up transactions ${j2s(txs)}`);
-
- const { walletClient: walletClient2 } = await createWalletDaemonWithClient(
- t,
- {
- name: "w2",
- },
- );
-
- // Check that the second wallet is a fresh wallet.
- {
- const bal = await walletClient2.call(WalletApiOperation.GetBalances, {});
- t.assertTrue(bal.balances.length === 0);
- }
-
- await walletClient2.call(WalletApiOperation.ImportBackupRecovery, {
- recovery: backupRecovery,
- });
-
- await walletClient2.call(WalletApiOperation.RunBackupCycle, {});
-
- // Check that now the old balance is available!
- {
- const bal = await walletClient2.call(WalletApiOperation.GetBalances, {});
- t.assertTrue(bal.balances.length === 1);
- console.log(bal);
- }
-
- // Now do some basic checks that the restored wallet is still functional
- {
- const txs = await walletClient2.call(
- WalletApiOperation.GetTransactions,
- {},
- );
- console.log(`restored transactions ${j2s(txs)}`);
- const bal1 = await walletClient2.call(WalletApiOperation.GetBalances, {});
-
- t.assertAmountEquals(bal1.balances[0].available, "TESTKUDOS:14.1");
-
- await withdrawViaBankV3(t, {
- walletClient: walletClient2,
- bankClient,
- exchange,
- amount: "TESTKUDOS:10",
- });
-
- await exchange.runWirewatchOnce();
-
- await walletClient2.call(
- WalletApiOperation.TestingWaitTransactionsFinal,
- {},
- );
-
- const txs2 = await walletClient2.call(
- WalletApiOperation.GetTransactions,
- {},
- );
- console.log(`tx after withdraw after restore ${j2s(txs2)}`);
-
- const bal2 = await walletClient2.call(WalletApiOperation.GetBalances, {});
-
- t.assertAmountEquals(bal2.balances[0].available, "TESTKUDOS:23.82");
- }
-}
-
-runWalletBackupBasicTest.suites = ["wallet", "wallet-backup"];
-// See https://bugs.taler.net/n/7598
-runWalletBackupBasicTest.experimental = true;
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-backup-doublespend.ts b/packages/taler-harness/src/integrationtests/test-wallet-backup-doublespend.ts
@@ -1,190 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Imports.
- */
-import {
- PreparePayResultType,
- succeedOrThrow,
- TalerMerchantInstanceHttpClient,
-} from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import {
- createSimpleTestkudosEnvironmentV3,
- createWalletDaemonWithClient,
- makeTestPaymentV2,
- withdrawViaBankV3,
-} from "../harness/environments.js";
-import { GlobalTestState } from "../harness/harness.js";
-import { SyncService } from "../harness/sync.js";
-
-export async function runWalletBackupDoublespendTest(t: GlobalTestState) {
- // Set up test environment
-
- const { commonDb, merchant, walletClient, bankClient, exchange, merchantAdminAccessToken } =
- await createSimpleTestkudosEnvironmentV3(t);
-
- const merchantClient = new TalerMerchantInstanceHttpClient(
- merchant.makeInstanceBaseUrl(),
- );
-
- const sync = await SyncService.create(t, {
- currency: "TESTKUDOS",
- annualFee: "TESTKUDOS:0.5",
- database: commonDb.connStr,
- fulfillmentUrl: "taler://fulfillment-success",
- httpPort: 8089,
- name: "sync1",
- paymentBackendUrl: merchant.makeInstanceBaseUrl(),
- uploadLimitMb: 10,
- });
-
- await sync.start();
- await sync.pingUntilAvailable();
-
- await walletClient.call(WalletApiOperation.AddBackupProvider, {
- backupProviderBaseUrl: sync.baseUrl,
- activate: true,
- name: sync.baseUrl,
- });
-
- await withdrawViaBankV3(t, {
- walletClient,
- bankClient,
- exchange,
- amount: "TESTKUDOS:10",
- });
-
- await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
-
- await walletClient.call(WalletApiOperation.RunBackupCycle, {});
- await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
- await walletClient.call(WalletApiOperation.RunBackupCycle, {});
-
- const backupRecovery = await walletClient.call(
- WalletApiOperation.ExportBackupRecovery,
- {},
- );
-
- const { walletClient: walletClientTwo } = await createWalletDaemonWithClient(
- t,
- { name: "default" },
- );
-
- await walletClientTwo.call(WalletApiOperation.ImportBackupRecovery, {
- recovery: backupRecovery,
- });
-
- await walletClientTwo.call(WalletApiOperation.RunBackupCycle, {});
-
- console.log(
- "wallet1 balance before spend:",
- await walletClient.call(WalletApiOperation.GetBalances, {}),
- );
-
- await makeTestPaymentV2(t, {
- merchant,
- merchantAdminAccessToken,
- walletClient,
- order: {
- summary: "foo",
- amount: "TESTKUDOS:7",
- },
- });
-
- await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
-
- console.log(
- "wallet1 balance after spend:",
- await walletClient.call(WalletApiOperation.GetBalances, {}),
- );
-
- {
- console.log(
- "wallet2 balance:",
- await walletClientTwo.call(WalletApiOperation.GetBalances, {}),
- );
- }
-
- // Now we double-spend with the second wallet
-
- {
- const orderResp = succeedOrThrow(
- await merchantClient.createOrder(merchantAdminAccessToken, {
- order: {
- amount: "TESTKUDOS:8",
- summary: "bla",
- fulfillment_url: "taler://fulfillment-success",
- },
- }),
- );
-
- let orderStatus = succeedOrThrow(
- await merchantClient.getOrderDetails(merchantAdminAccessToken, orderResp.order_id),
- );
-
- t.assertTrue(orderStatus.order_status === "unpaid");
-
- // Make wallet pay for the order
-
- {
- console.log(
- "wallet2 balance before preparePay:",
- await walletClientTwo.call(WalletApiOperation.GetBalances, {}),
- );
- }
-
- const preparePayResult = await walletClientTwo.call(
- WalletApiOperation.PreparePayForUri,
- {
- talerPayUri: orderStatus.taler_pay_uri,
- },
- );
-
- t.assertDeepEqual(
- preparePayResult.status,
- PreparePayResultType.PaymentPossible,
- );
-
- const res = await walletClientTwo.call(WalletApiOperation.ConfirmPay, {
- transactionId: preparePayResult.transactionId,
- });
-
- console.log(res);
-
- // FIXME: wait for a notification that indicates insufficient funds!
-
- await withdrawViaBankV3(t, {
- walletClient: walletClientTwo,
- bankClient,
- exchange,
- amount: "TESTKUDOS:50",
- });
-
- const bal = await walletClientTwo.call(WalletApiOperation.GetBalances, {});
- console.log("bal", bal);
-
- await walletClientTwo.call(
- WalletApiOperation.TestingWaitTransactionsFinal,
- {},
- );
- }
-}
-
-runWalletBackupDoublespendTest.suites = ["wallet", "wallet-backup"];
-// See https://bugs.taler.net/n/7598
-runWalletBackupDoublespendTest.experimental = true;
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -169,8 +169,6 @@ import { runTopsMerchantTosTest } from "./test-tops-merchant-tos.js";
import { runTopsPeerTest } from "./test-tops-peer.js";
import { runTermOfServiceFormatTest } from "./test-tos-format.js";
import { runUtilMerchantClientTest } from "./test-util-merchant-client.js";
-import { runWalletBackupBasicTest } from "./test-wallet-backup-basic.js";
-import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend.js";
import { runWalletBalanceNotificationsTest } from "./test-wallet-balance-notifications.js";
import { runWalletBalanceZeroTest } from "./test-wallet-balance-zero.js";
import { runWalletBalanceTest } from "./test-wallet-balance.js";
@@ -290,8 +288,6 @@ const allTests: TestMainFunction[] = [
runWithdrawalManualTest,
runTimetravelAutorefreshTest,
runTimetravelWithdrawTest,
- runWalletBackupBasicTest,
- runWalletBackupDoublespendTest,
runWalletNotificationsTest,
runWalletCryptoWorkerTest,
runWalletContactsBasicTest,
@@ -299,7 +295,6 @@ const allTests: TestMainFunction[] = [
runWalletDblessTest,
runWallettestingTest,
runWithdrawalAbortBankTest,
- // runWithdrawalNotifyBeforeTxTest,
runWithdrawalBankIntegratedTest,
runWithdrawalFakebankTest,
runWithdrawalFeesTest,
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -3308,189 +3308,12 @@ export const codecForRetryTransactionRequest =
.property("transactionId", codecForTransactionIdStr())
.build("RetryTransactionRequest");
-export interface SetWalletDeviceIdRequest {
- /**
- * New wallet device ID to set.
- */
- walletDeviceId: string;
-}
-
-export const codecForSetWalletDeviceIdRequest =
- (): Codec<SetWalletDeviceIdRequest> =>
- buildCodecForObject<SetWalletDeviceIdRequest>()
- .property("walletDeviceId", codecForString())
- .build("SetWalletDeviceIdRequest");
-
export interface WithdrawFakebankRequest {
amount: AmountString;
exchange: string;
bank: string;
}
-export enum AttentionPriority {
- High = "high",
- Medium = "medium",
- Low = "low",
-}
-
-export interface UserAttentionByIdRequest {
- entityId: string;
- type: AttentionType;
-}
-
-export const codecForUserAttentionByIdRequest =
- (): Codec<UserAttentionByIdRequest> =>
- buildCodecForObject<UserAttentionByIdRequest>()
- .property("type", codecForAny())
- .property("entityId", codecForString())
- .build("UserAttentionByIdRequest");
-
-export const codecForUserAttentionsRequest = (): Codec<UserAttentionsRequest> =>
- buildCodecForObject<UserAttentionsRequest>()
- .property(
- "priority",
- codecOptional(
- codecForEither(
- codecForConstString(AttentionPriority.Low),
- codecForConstString(AttentionPriority.Medium),
- codecForConstString(AttentionPriority.High),
- ),
- ),
- )
- .build("UserAttentionsRequest");
-
-export interface UserAttentionsRequest {
- priority?: AttentionPriority;
-}
-
-export type AttentionInfo =
- | AttentionKycWithdrawal
- | AttentionBackupUnpaid
- | AttentionBackupExpiresSoon
- | AttentionMerchantRefund
- | AttentionExchangeTosChanged
- | AttentionExchangeKeyExpired
- | AttentionExchangeDenominationExpired
- | AttentionAuditorTosChanged
- | AttentionAuditorKeyExpires
- | AttentionAuditorDenominationExpires
- | AttentionPullPaymentPaid
- | AttentionPushPaymentReceived;
-
-export enum AttentionType {
- KycWithdrawal = "kyc-withdrawal",
-
- BackupUnpaid = "backup-unpaid",
- BackupExpiresSoon = "backup-expires-soon",
- MerchantRefund = "merchant-refund",
-
- ExchangeTosChanged = "exchange-tos-changed",
- ExchangeKeyExpired = "exchange-key-expired",
- ExchangeKeyExpiresSoon = "exchange-key-expires-soon",
- ExchangeDenominationsExpired = "exchange-denominations-expired",
- ExchangeDenominationsExpiresSoon = "exchange-denominations-expires-soon",
-
- AuditorTosChanged = "auditor-tos-changed",
- AuditorKeyExpires = "auditor-key-expires",
- AuditorDenominationsExpires = "auditor-denominations-expires",
-
- PullPaymentPaid = "pull-payment-paid",
- PushPaymentReceived = "push-payment-withdrawn",
-}
-
-export const UserAttentionPriority: {
- [type in AttentionType]: AttentionPriority;
-} = {
- "kyc-withdrawal": AttentionPriority.Medium,
-
- "backup-unpaid": AttentionPriority.High,
- "backup-expires-soon": AttentionPriority.Medium,
- "merchant-refund": AttentionPriority.Medium,
-
- "exchange-tos-changed": AttentionPriority.Medium,
-
- "exchange-key-expired": AttentionPriority.High,
- "exchange-key-expires-soon": AttentionPriority.Medium,
- "exchange-denominations-expired": AttentionPriority.High,
- "exchange-denominations-expires-soon": AttentionPriority.Medium,
-
- "auditor-tos-changed": AttentionPriority.Medium,
- "auditor-key-expires": AttentionPriority.Medium,
- "auditor-denominations-expires": AttentionPriority.Medium,
-
- "pull-payment-paid": AttentionPriority.High,
- "push-payment-withdrawn": AttentionPriority.High,
-};
-
-interface AttentionBackupExpiresSoon {
- type: AttentionType.BackupExpiresSoon;
- provider_base_url: string;
-}
-interface AttentionBackupUnpaid {
- type: AttentionType.BackupUnpaid;
- provider_base_url: string;
- talerUri: string;
-}
-
-interface AttentionMerchantRefund {
- type: AttentionType.MerchantRefund;
- transactionId: TransactionIdStr;
-}
-
-interface AttentionKycWithdrawal {
- type: AttentionType.KycWithdrawal;
- transactionId: TransactionIdStr;
-}
-
-interface AttentionExchangeTosChanged {
- type: AttentionType.ExchangeTosChanged;
- exchange_base_url: string;
-}
-interface AttentionExchangeKeyExpired {
- type: AttentionType.ExchangeKeyExpired;
- exchange_base_url: string;
-}
-interface AttentionExchangeDenominationExpired {
- type: AttentionType.ExchangeDenominationsExpired;
- exchange_base_url: string;
-}
-interface AttentionAuditorTosChanged {
- type: AttentionType.AuditorTosChanged;
- auditor_base_url: string;
-}
-
-interface AttentionAuditorKeyExpires {
- type: AttentionType.AuditorKeyExpires;
- auditor_base_url: string;
-}
-interface AttentionAuditorDenominationExpires {
- type: AttentionType.AuditorDenominationsExpires;
- auditor_base_url: string;
-}
-interface AttentionPullPaymentPaid {
- type: AttentionType.PullPaymentPaid;
- transactionId: TransactionIdStr;
-}
-
-interface AttentionPushPaymentReceived {
- type: AttentionType.PushPaymentReceived;
- transactionId: TransactionIdStr;
-}
-
-export type UserAttentionUnreadList = Array<{
- info: AttentionInfo;
- when: TalerPreciseTimestamp;
- read: boolean;
-}>;
-
-export interface UserAttentionsResponse {
- pending: UserAttentionUnreadList;
-}
-
-export interface UserAttentionsCountResponse {
- total: number;
-}
-
export const codecForWithdrawFakebankRequest =
(): Codec<WithdrawFakebankRequest> =>
buildCodecForObject<WithdrawFakebankRequest>()
@@ -4277,112 +4100,6 @@ export const codecForRemoveGlobalCurrencyAuditorRequest =
.property("auditorPub", codecForString())
.build("RemoveGlobalCurrencyAuditorRequest");
-/**
- * Information about one provider.
- *
- * We don't store the account key here,
- * as that's derived from the wallet root key.
- */
-export interface ProviderInfo {
- active: boolean;
- syncProviderBaseUrl: string;
- name: string;
- terms?: BackupProviderTerms;
- /**
- * Last communication issue with the provider.
- */
- lastError?: TalerErrorDetail;
- lastSuccessfulBackupTimestamp?: TalerPreciseTimestamp;
- lastAttemptedBackupTimestamp?: TalerPreciseTimestamp;
- paymentProposalIds: string[];
- backupProblem?: BackupProblem;
- paymentStatus: ProviderPaymentStatus;
-}
-
-export interface BackupProviderTerms {
- supportedProtocolVersion: string;
- annualFee: AmountString;
- storageLimitInMegabytes: number;
-}
-
-export type BackupProblem =
- | BackupUnreadableProblem
- | BackupConflictingDeviceProblem;
-
-export interface BackupUnreadableProblem {
- type: "backup-unreadable";
-}
-
-export interface BackupConflictingDeviceProblem {
- type: "backup-conflicting-device";
- otherDeviceId: string;
- myDeviceId: string;
- backupTimestamp: AbsoluteTime;
-}
-
-export type ProviderPaymentStatus =
- | ProviderPaymentTermsChanged
- | ProviderPaymentPaid
- | ProviderPaymentInsufficientBalance
- | ProviderPaymentUnpaid
- | ProviderPaymentPending;
-
-export enum ProviderPaymentType {
- Unpaid = "unpaid",
- Pending = "pending",
- InsufficientBalance = "insufficient-balance",
- Paid = "paid",
- TermsChanged = "terms-changed",
-}
-
-export interface ProviderPaymentUnpaid {
- type: ProviderPaymentType.Unpaid;
-}
-
-export interface ProviderPaymentInsufficientBalance {
- type: ProviderPaymentType.InsufficientBalance;
- amount: AmountString;
-}
-
-export interface ProviderPaymentPending {
- type: ProviderPaymentType.Pending;
- talerUri?: string;
-}
-
-export interface ProviderPaymentPaid {
- type: ProviderPaymentType.Paid;
- paidUntil: AbsoluteTime;
-}
-
-export interface ProviderPaymentTermsChanged {
- type: ProviderPaymentType.TermsChanged;
- paidUntil: AbsoluteTime;
- oldTerms: BackupProviderTerms;
- newTerms: BackupProviderTerms;
-}
-
-// FIXME: Does not really belong here, move to sync API
-export interface SyncTermsOfServiceResponse {
- // maximum backup size supported
- storage_limit_in_megabytes: number;
-
- // Fee for an account, per year.
- annual_fee: AmountString;
-
- // protocol version supported by the server,
- // for now always "0.0".
- version: string;
-}
-
-// FIXME: Does not really belong here, move to sync API
-export const codecForSyncTermsOfServiceResponse =
- (): Codec<SyncTermsOfServiceResponse> =>
- buildCodecForObject<SyncTermsOfServiceResponse>()
- .property("storage_limit_in_megabytes", codecForNumber())
- .property("annual_fee", codecForAmountString())
- .property("version", codecForString())
- .build("SyncTermsOfServiceResponse");
-
export interface HintNetworkAvailabilityRequest {
isNetworkAvailable: boolean;
}
diff --git a/packages/taler-wallet-core/src/attention.ts b/packages/taler-wallet-core/src/attention.ts
@@ -1,139 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Imports.
- */
-import {
- AttentionInfo,
- Logger,
- TalerPreciseTimestamp,
- UserAttentionByIdRequest,
- UserAttentionPriority,
- UserAttentionUnreadList,
- UserAttentionsCountResponse,
- UserAttentionsRequest,
- UserAttentionsResponse,
-} from "@gnu-taler/taler-util";
-import { timestampPreciseFromDb, timestampPreciseToDb } from "./db.js";
-import { WalletExecutionContext } from "./wallet.js";
-
-const logger = new Logger("operations/attention.ts");
-
-export async function getUserAttentionsUnreadCount(
- wex: WalletExecutionContext,
- req: UserAttentionsRequest,
-): Promise<UserAttentionsCountResponse> {
- const total = await wex.db.runReadOnlyTx(
- { storeNames: ["userAttention"] },
- async (tx) => {
- let count = 0;
- await tx.userAttention.iter().forEach((x) => {
- if (
- req.priority !== undefined &&
- UserAttentionPriority[x.info.type] !== req.priority
- )
- return;
- if (x.read !== undefined) return;
- count++;
- });
-
- return count;
- },
- );
-
- return { total };
-}
-
-export async function getUserAttentions(
- wex: WalletExecutionContext,
- req: UserAttentionsRequest,
-): Promise<UserAttentionsResponse> {
- return await wex.db.runReadOnlyTx(
- { storeNames: ["userAttention"] },
- async (tx) => {
- const pending: UserAttentionUnreadList = [];
- await tx.userAttention.iter().forEach((x) => {
- if (
- req.priority !== undefined &&
- UserAttentionPriority[x.info.type] !== req.priority
- )
- return;
- pending.push({
- info: x.info,
- when: timestampPreciseFromDb(x.created),
- read: x.read !== undefined,
- });
- });
-
- return { pending };
- },
- );
-}
-
-export async function markAttentionRequestAsRead(
- wex: WalletExecutionContext,
- req: UserAttentionByIdRequest,
-): Promise<void> {
- await wex.db.runReadWriteTx({ storeNames: ["userAttention"] }, async (tx) => {
- const ua = await tx.userAttention.get([req.entityId, req.type]);
- if (!ua) throw Error("attention request not found");
- tx.userAttention.put({
- ...ua,
- read: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- });
- });
-}
-
-/**
- * the wallet need the user attention to complete a task
- * internal API
- *
- * @param wex
- * @param info
- */
-export async function addAttentionRequest(
- wex: WalletExecutionContext,
- info: AttentionInfo,
- entityId: string,
-): Promise<void> {
- await wex.db.runReadWriteTx({ storeNames: ["userAttention"] }, async (tx) => {
- await tx.userAttention.put({
- info,
- entityId,
- created: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- read: undefined,
- });
- });
-}
-
-/**
- * user completed the task, attention request is not needed
- * internal API
- *
- * @param wex
- * @param created
- */
-export async function removeAttentionRequest(
- wex: WalletExecutionContext,
- req: UserAttentionByIdRequest,
-): Promise<void> {
- await wex.db.runReadWriteTx({ storeNames: ["userAttention"] }, async (tx) => {
- const ua = await tx.userAttention.get([req.entityId, req.type]);
- if (!ua) throw Error("attention request not found");
- await tx.userAttention.delete([req.entityId, req.type]);
- });
-}
diff --git a/packages/taler-wallet-core/src/backup/index.ts b/packages/taler-wallet-core/src/backup/index.ts
@@ -1,997 +0,0 @@
-/*
- This file is part of GNU Taler
- (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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Implementation of wallet backups (export/import/upload) and sync
- * server management.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-/**
- * Imports.
- */
-import {
- AbsoluteTime,
- AttentionType,
- BackupRecovery,
- Codec,
- Duration,
- EddsaKeyPair,
- HttpStatusCode,
- Logger,
- PreparePayResult,
- ProviderInfo,
- ProviderPaymentStatus,
- RecoveryLoadRequest,
- RecoveryMergeStrategy,
- TalerError,
- TalerErrorCode,
- TalerPreciseTimestamp,
- URL,
- buildCodecForObject,
- buildCodecForUnion,
- bytesToString,
- canonicalJson,
- checkDbInvariant,
- checkLogicInvariant,
- codecForBoolean,
- codecForConstString,
- codecForList,
- codecForString,
- codecForSyncTermsOfServiceResponse,
- codecOptional,
- decodeCrock,
- eddsaGetPublic,
- encodeCrock,
- getRandomBytes,
- hash,
- j2s,
- kdf,
- notEmpty,
- secretbox,
- secretbox_open,
- stringToBytes,
-} from "@gnu-taler/taler-util";
-import {
- readSuccessResponseJsonOrThrow,
- readTalerErrorResponse,
-} from "@gnu-taler/taler-util/http";
-import { gunzipSync, gzipSync } from "fflate";
-import { addAttentionRequest, removeAttentionRequest } from "../attention.js";
-import {
- cancelableFetch,
- TaskIdentifiers,
- TaskRunResult,
- TaskRunResultType,
-} from "../common.js";
-import {
- BackupProviderRecord,
- BackupProviderState,
- BackupProviderStateTag,
- ConfigRecord,
- ConfigRecordKey,
- WalletBackupConfState,
- WalletDbReadOnlyTransaction,
- timestampOptionalPreciseFromDb,
- timestampPreciseToDb,
-} from "../db.js";
-import { preparePayForUri } from "../pay-merchant.js";
-import { InternalWalletState, WalletExecutionContext } from "../wallet.js";
-
-const logger = new Logger("operations/backup.ts");
-
-function concatArrays(xs: Uint8Array[]): Uint8Array {
- let len = 0;
- for (const x of xs) {
- len += x.byteLength;
- }
- const out = new Uint8Array(len);
- let offset = 0;
- for (const x of xs) {
- out.set(x, offset);
- offset += x.length;
- }
- return out;
-}
-
-const magic = "TLRWBK01";
-
-/**
- * Encrypt the backup.
- *
- * Blob format:
- * Magic "TLRWBK01" (8 bytes)
- * Nonce (24 bytes)
- * Compressed JSON blob (rest)
- */
-export async function encryptBackup(
- config: WalletBackupConfState,
- blob: any,
-): Promise<Uint8Array> {
- const chunks: Uint8Array[] = [];
- chunks.push(stringToBytes(magic));
- const nonceStr = config.lastBackupNonce;
- checkLogicInvariant(!!nonceStr);
- const nonce = decodeCrock(nonceStr).slice(0, 24);
- chunks.push(nonce);
- const backupJsonContent = canonicalJson(blob);
- logger.trace("backup JSON size", backupJsonContent.length);
- const compressedContent = gzipSync(stringToBytes(backupJsonContent), {
- mtime: 0,
- });
- const secret = deriveBlobSecret(config);
- const encrypted = secretbox(compressedContent, nonce.slice(0, 24), secret);
- chunks.push(encrypted);
- return concatArrays(chunks);
-}
-
-function deriveAccountKeyPair(
- bc: WalletBackupConfState,
- providerUrl: string,
-): EddsaKeyPair {
- const privateKey = kdf(
- 32,
- decodeCrock(bc.walletRootPriv),
- stringToBytes("taler-sync-account-key-salt"),
- stringToBytes(providerUrl),
- );
- return {
- eddsaPriv: privateKey,
- eddsaPub: eddsaGetPublic(privateKey),
- };
-}
-
-function deriveBlobSecret(bc: WalletBackupConfState): Uint8Array {
- return kdf(
- 32,
- decodeCrock(bc.walletRootPriv),
- stringToBytes("taler-sync-blob-secret-salt"),
- stringToBytes("taler-sync-blob-secret-info"),
- );
-}
-
-interface BackupForProviderArgs {
- backupProviderBaseUrl: string;
-}
-
-function getNextBackupTimestamp(): TalerPreciseTimestamp {
- // FIXME: Randomize!
- return AbsoluteTime.toPreciseTimestamp(
- AbsoluteTime.addDuration(
- AbsoluteTime.now(),
- Duration.fromSpec({ minutes: 5 }),
- ),
- );
-}
-
-async function runBackupCycleForProvider(
- wex: WalletExecutionContext,
- args: BackupForProviderArgs,
-): Promise<TaskRunResult> {
- const provider = await wex.db.runReadOnlyTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- return tx.backupProviders.get(args.backupProviderBaseUrl);
- },
- );
-
- if (!provider) {
- logger.warn("provider disappeared");
- return TaskRunResult.finished();
- }
-
- //const backupJson = await exportBackup(ws);
- // FIXME: re-implement backup
- const backupJson = {};
- const backupConfig = await provideBackupState(wex);
- const encBackup = await encryptBackup(backupConfig, backupJson);
- const currentBackupHash = hash(encBackup);
-
- const accountKeyPair = deriveAccountKeyPair(backupConfig, provider.baseUrl);
-
- const newHash = encodeCrock(currentBackupHash);
- const oldHash = provider.lastBackupHash;
-
- logger.trace(`trying to upload backup to ${provider.baseUrl}`);
- logger.trace(`old hash ${oldHash}, new hash ${newHash}`);
-
- const syncSigResp = await wex.cryptoApi.makeSyncSignature({
- newHash: encodeCrock(currentBackupHash),
- oldHash: provider.lastBackupHash,
- accountPriv: encodeCrock(accountKeyPair.eddsaPriv),
- });
-
- logger.trace(`sync signature is ${syncSigResp}`);
-
- const accountBackupUrl = new URL(
- `/backups/${encodeCrock(accountKeyPair.eddsaPub)}`,
- provider.baseUrl,
- );
-
- if (provider.shouldRetryFreshProposal) {
- accountBackupUrl.searchParams.set("fresh", "yes");
- }
-
- const resp = await cancelableFetch(wex, accountBackupUrl, {
- method: "POST",
- body: encBackup,
- headers: {
- "content-type": "application/octet-stream",
- "sync-signature": syncSigResp.sig,
- "if-none-match": JSON.stringify(newHash),
- ...(provider.lastBackupHash
- ? {
- "if-match": JSON.stringify(provider.lastBackupHash),
- }
- : {}),
- },
- });
-
- logger.trace(`sync response status: ${resp.status}`);
-
- if (resp.status === HttpStatusCode.NotModified) {
- await wex.db.runReadWriteTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- const prov = await tx.backupProviders.get(provider.baseUrl);
- if (!prov) {
- return;
- }
- prov.lastBackupCycleTimestamp = timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- );
- prov.state = {
- tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()),
- };
- await tx.backupProviders.put(prov);
- },
- );
-
- removeAttentionRequest(wex, {
- entityId: provider.baseUrl,
- type: AttentionType.BackupUnpaid,
- });
-
- return TaskRunResult.finished();
- }
-
- if (resp.status === HttpStatusCode.PaymentRequired) {
- logger.trace("payment required for backup");
- logger.trace(`headers: ${j2s(resp.headers)}`);
- const talerUri = resp.headers.get("taler");
- if (!talerUri) {
- throw Error("no taler URI available to pay provider");
- }
-
- //We can't delay downloading the proposal since we need the id
- //FIXME: check download errors
- let res: PreparePayResult | undefined = undefined;
- try {
- res = await preparePayForUri(wex, talerUri);
- } catch (e) {
- const error = TalerError.fromException(e);
- if (!error.hasErrorCode(TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED)) {
- throw error;
- }
- }
-
- if (res === undefined) {
- //claimed
-
- await wex.db.runReadWriteTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- const prov = await tx.backupProviders.get(provider.baseUrl);
- if (!prov) {
- logger.warn("backup provider not found anymore");
- return;
- }
- prov.shouldRetryFreshProposal = true;
- prov.state = {
- tag: BackupProviderStateTag.Retrying,
- };
- await tx.backupProviders.put(prov);
- },
- );
-
- throw Error("not implemented");
- // return {
- // type: TaskRunResultType.Pending,
- // };
- }
- const result = res;
-
- await wex.db.runReadWriteTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- const prov = await tx.backupProviders.get(provider.baseUrl);
- if (!prov) {
- logger.warn("backup provider not found anymore");
- return;
- }
- // const opId = TaskIdentifiers.forBackup(prov);
- // await scheduleRetryInTx(ws, tx, opId);
- prov.currentPaymentTransactionId = result.transactionId;
- prov.shouldRetryFreshProposal = false;
- prov.state = {
- tag: BackupProviderStateTag.Retrying,
- };
- await tx.backupProviders.put(prov);
- },
- );
-
- addAttentionRequest(
- wex,
- {
- type: AttentionType.BackupUnpaid,
- provider_base_url: provider.baseUrl,
- talerUri,
- },
- provider.baseUrl,
- );
-
- throw Error("not implemented");
- // return {
- // type: TaskRunResultType.Pending,
- // };
- }
-
- if (resp.status === HttpStatusCode.NoContent) {
- await wex.db.runReadWriteTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- const prov = await tx.backupProviders.get(provider.baseUrl);
- if (!prov) {
- return;
- }
- prov.lastBackupHash = encodeCrock(currentBackupHash);
- prov.lastBackupCycleTimestamp = timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- );
- prov.state = {
- tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()),
- };
- await tx.backupProviders.put(prov);
- },
- );
-
- removeAttentionRequest(wex, {
- entityId: provider.baseUrl,
- type: AttentionType.BackupUnpaid,
- });
-
- return {
- type: TaskRunResultType.Finished,
- };
- }
-
- if (resp.status === HttpStatusCode.Conflict) {
- logger.info("conflicting backup found");
- const backupEnc = new Uint8Array(await resp.bytes());
- const backupConfig = await provideBackupState(wex);
- // const blob = await decryptBackup(backupConfig, backupEnc);
- // FIXME: Re-implement backup import with merging
- // await importBackup(ws, blob, cryptoData);
- await wex.db.runReadWriteTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- const prov = await tx.backupProviders.get(provider.baseUrl);
- if (!prov) {
- logger.warn("backup provider not found anymore");
- return;
- }
- prov.lastBackupHash = encodeCrock(hash(backupEnc));
- // FIXME: Allocate error code for this situation?
- // FIXME: Add operation retry record!
- const opId = TaskIdentifiers.forBackup(prov);
- //await scheduleRetryInTx(ws, tx, opId);
- prov.state = {
- tag: BackupProviderStateTag.Retrying,
- };
- await tx.backupProviders.put(prov);
- },
- );
- logger.info("processed existing backup");
- // Now upload our own, merged backup.
- return await runBackupCycleForProvider(wex, args);
- }
-
- // Some other response that we did not expect!
-
- logger.error("parsing error response");
-
- const err = await readTalerErrorResponse(resp);
- logger.error(`got error response from backup provider: ${j2s(err)}`);
- return {
- type: TaskRunResultType.Error,
- errorDetail: err,
- };
-}
-
-export async function processBackupForProvider(
- wex: WalletExecutionContext,
- backupProviderBaseUrl: string,
-): Promise<TaskRunResult> {
- if (!wex.ws.networkAvailable) {
- return TaskRunResult.networkRequired();
- }
-
- const provider = await wex.db.runReadOnlyTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- return await tx.backupProviders.get(backupProviderBaseUrl);
- },
- );
- if (!provider) {
- throw Error("unknown backup provider");
- }
-
- logger.info(`running backup for provider ${backupProviderBaseUrl}`);
-
- return await runBackupCycleForProvider(wex, {
- backupProviderBaseUrl: provider.baseUrl,
- });
-}
-
-export interface RemoveBackupProviderRequest {
- provider: string;
-}
-
-export const codecForRemoveBackupProvider =
- (): Codec<RemoveBackupProviderRequest> =>
- buildCodecForObject<RemoveBackupProviderRequest>()
- .property("provider", codecForString())
- .build("RemoveBackupProviderRequest");
-
-export async function removeBackupProvider(
- wex: WalletExecutionContext,
- req: RemoveBackupProviderRequest,
-): Promise<void> {
- await wex.db.runReadWriteTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- await tx.backupProviders.delete(req.provider);
- },
- );
-}
-
-export interface RunBackupCycleRequest {
- /**
- * List of providers to backup or empty for all known providers.
- */
- providers?: Array<string>;
-}
-
-export const codecForRunBackupCycle = (): Codec<RunBackupCycleRequest> =>
- buildCodecForObject<RunBackupCycleRequest>()
- .property("providers", codecOptional(codecForList(codecForString())))
- .build("RunBackupCycleRequest");
-
-/**
- * Do one backup cycle that consists of:
- * 1. Exporting a backup and try to upload it.
- * Stop if this step succeeds.
- * 2. Download, verify and import backups from connected sync accounts.
- * 3. Upload the updated backup blob.
- */
-export async function runBackupCycle(
- wex: WalletExecutionContext,
- req: RunBackupCycleRequest,
-): Promise<void> {
- const providers = await wex.db.runReadOnlyTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- if (req.providers) {
- const rs = await Promise.all(
- req.providers.map((id) => tx.backupProviders.get(id)),
- );
- return rs.filter(notEmpty);
- }
- return await tx.backupProviders.iter().toArray();
- },
- );
-
- for (const provider of providers) {
- await runBackupCycleForProvider(wex, {
- backupProviderBaseUrl: provider.baseUrl,
- });
- }
-}
-
-export interface AddBackupProviderRequest {
- backupProviderBaseUrl: string;
-
- name: string;
- /**
- * Activate the provider. Should only be done after
- * the user has reviewed the provider.
- */
- activate?: boolean;
-}
-
-export const codecForAddBackupProviderRequest =
- (): Codec<AddBackupProviderRequest> =>
- buildCodecForObject<AddBackupProviderRequest>()
- .property("backupProviderBaseUrl", codecForString())
- .property("name", codecForString())
- .property("activate", codecOptional(codecForBoolean()))
- .build("AddBackupProviderRequest");
-
-export type AddBackupProviderResponse =
- | AddBackupProviderOk
- | AddBackupProviderPaymentRequired;
-
-interface AddBackupProviderOk {
- status: "ok";
-}
-interface AddBackupProviderPaymentRequired {
- status: "payment-required";
- talerUri?: string;
-}
-
-export const codecForAddBackupProviderOk = (): Codec<AddBackupProviderOk> =>
- buildCodecForObject<AddBackupProviderOk>()
- .property("status", codecForConstString("ok"))
- .build("AddBackupProviderOk");
-
-export const codecForAddBackupProviderPaymenrRequired =
- (): Codec<AddBackupProviderPaymentRequired> =>
- buildCodecForObject<AddBackupProviderPaymentRequired>()
- .property("status", codecForConstString("payment-required"))
- .property("talerUri", codecOptional(codecForString()))
- .build("AddBackupProviderPaymentRequired");
-
-export const codecForAddBackupProviderResponse =
- (): Codec<AddBackupProviderResponse> =>
- buildCodecForUnion<AddBackupProviderResponse>()
- .discriminateOn("status")
- .alternative("ok", codecForAddBackupProviderOk())
- .alternative(
- "payment-required",
- codecForAddBackupProviderPaymenrRequired(),
- )
- .build("AddBackupProviderResponse");
-
-export async function addBackupProvider(
- wex: WalletExecutionContext,
- req: AddBackupProviderRequest,
-): Promise<AddBackupProviderResponse> {
- logger.info(`adding backup provider ${j2s(req)}`);
- await provideBackupState(wex);
- const canonUrl = req.backupProviderBaseUrl;
- await wex.db.runReadWriteTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- const oldProv = await tx.backupProviders.get(canonUrl);
- if (oldProv) {
- logger.info("old backup provider found");
- if (req.activate) {
- oldProv.state = {
- tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- ),
- };
- logger.info("setting existing backup provider to active");
- await tx.backupProviders.put(oldProv);
- }
- return;
- }
- },
- );
- const termsUrl = new URL("config", canonUrl);
- const resp = await cancelableFetch(wex, termsUrl);
- const terms = await readSuccessResponseJsonOrThrow(
- resp,
- codecForSyncTermsOfServiceResponse(),
- );
- await wex.db.runReadWriteTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- let state: BackupProviderState;
- //FIXME: what is the difference provisional and ready?
- if (req.activate) {
- state = {
- tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- ),
- };
- } else {
- state = {
- tag: BackupProviderStateTag.Provisional,
- };
- }
- await tx.backupProviders.put({
- state,
- name: req.name,
- terms: {
- annualFee: terms.annual_fee,
- storageLimitInMegabytes: terms.storage_limit_in_megabytes,
- supportedProtocolVersion: terms.version,
- },
- shouldRetryFreshProposal: false,
- paymentProposalIds: [],
- baseUrl: canonUrl,
- uids: [encodeCrock(getRandomBytes(32))],
- });
- },
- );
-
- return await runFirstBackupCycleForProvider(wex, {
- backupProviderBaseUrl: canonUrl,
- });
-}
-
-async function runFirstBackupCycleForProvider(
- wex: WalletExecutionContext,
- args: BackupForProviderArgs,
-): Promise<AddBackupProviderResponse> {
- throw Error("not implemented");
- // const resp = await runBackupCycleForProvider(ws, args);
- // switch (resp.type) {
- // case TaskRunResultType.Error:
- // throw TalerError.fromDetail(
- // TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
- // resp.errorDetail as any, //FIXME create an error for backup problems
- // );
- // case TaskRunResultType.Finished:
- // return {
- // status: "ok",
- // };
- // case TaskRunResultType.Pending:
- // return {
- // status: "payment-required",
- // talerUri: "FIXME",
- // //talerUri: resp.result.talerUri,
- // };
- // default:
- // assertUnreachable(resp);
- // }
-}
-
-export async function restoreFromRecoverySecret(): Promise<void> {
- return;
-}
-
-export interface BackupInfo {
- walletRootPub: string;
- deviceId: string;
- providers: ProviderInfo[];
-}
-
-async function getProviderPaymentInfo(
- wex: WalletExecutionContext,
- provider: BackupProviderRecord,
-): Promise<ProviderPaymentStatus> {
- throw Error("not implemented");
- // if (!provider.currentPaymentProposalId) {
- // return {
- // type: ProviderPaymentType.Unpaid,
- // };
- // }
- // const status = await checkPaymentByProposalId(
- // ws,
- // provider.currentPaymentProposalId,
- // ).catch(() => undefined);
-
- // if (!status) {
- // return {
- // type: ProviderPaymentType.Unpaid,
- // };
- // }
-
- // switch (status.status) {
- // case PreparePayResultType.InsufficientBalance:
- // return {
- // type: ProviderPaymentType.InsufficientBalance,
- // amount: status.amountRaw,
- // };
- // case PreparePayResultType.PaymentPossible:
- // return {
- // type: ProviderPaymentType.Pending,
- // talerUri: status.talerUri,
- // };
- // case PreparePayResultType.AlreadyConfirmed:
- // if (status.paid) {
- // return {
- // type: ProviderPaymentType.Paid,
- // paidUntil: AbsoluteTime.addDuration(
- // AbsoluteTime.fromProtocolTimestamp(status.contractTerms.timestamp),
- // durationFromSpec({ years: 1 }), //FIXME: take this from the contract term
- // ),
- // };
- // } else {
- // return {
- // type: ProviderPaymentType.Pending,
- // talerUri: status.talerUri,
- // };
- // }
- // default:
- // assertUnreachable(status);
- // }
-}
-
-/**
- * Get information about the current state of wallet backups.
- */
-export async function getBackupInfo(
- wex: WalletExecutionContext,
-): Promise<BackupInfo> {
- const backupConfig = await provideBackupState(wex);
- const providerRecords = await wex.db.runReadOnlyTx(
- { storeNames: ["backupProviders", "operationRetries"] },
- async (tx) => {
- return await tx.backupProviders.iter().mapAsync(async (bp) => {
- const opId = TaskIdentifiers.forBackup(bp);
- const retryRecord = await tx.operationRetries.get(opId);
- return {
- provider: bp,
- retryRecord,
- };
- });
- },
- );
- const providers: ProviderInfo[] = [];
- for (const x of providerRecords) {
- providers.push({
- active: x.provider.state.tag !== BackupProviderStateTag.Provisional,
- syncProviderBaseUrl: x.provider.baseUrl,
- lastSuccessfulBackupTimestamp: timestampOptionalPreciseFromDb(
- x.provider.lastBackupCycleTimestamp,
- ),
- paymentProposalIds: x.provider.paymentProposalIds,
- lastError:
- x.provider.state.tag === BackupProviderStateTag.Retrying
- ? x.retryRecord?.lastError
- : undefined,
- paymentStatus: await getProviderPaymentInfo(wex, x.provider),
- terms: x.provider.terms,
- name: x.provider.name,
- });
- }
- return {
- deviceId: backupConfig.deviceId,
- walletRootPub: backupConfig.walletRootPub,
- providers,
- };
-}
-
-/**
- * Get backup recovery information, including the wallet's
- * private key.
- */
-export async function getBackupRecovery(
- wex: WalletExecutionContext,
-): Promise<BackupRecovery> {
- const bs = await provideBackupState(wex);
- const providers = await wex.db.runReadOnlyTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- return await tx.backupProviders.iter().toArray();
- },
- );
- return {
- providers: providers
- .filter((x) => x.state.tag !== BackupProviderStateTag.Provisional)
- .map((x) => {
- return {
- name: x.name,
- url: x.baseUrl,
- };
- }),
- walletRootPriv: bs.walletRootPriv,
- };
-}
-
-async function backupRecoveryTheirs(
- wex: WalletExecutionContext,
- br: BackupRecovery,
-) {
- await wex.db.runReadWriteTx(
- { storeNames: ["backupProviders", "config"] },
- async (tx) => {
- let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
- ConfigRecordKey.WalletBackupState,
- );
- checkDbInvariant(!!backupStateEntry, `no backup entry`);
- checkDbInvariant(
- backupStateEntry.key === ConfigRecordKey.WalletBackupState,
- `backup entry inconsistent`,
- );
- backupStateEntry.value.lastBackupNonce = undefined;
- backupStateEntry.value.lastBackupTimestamp = undefined;
- backupStateEntry.value.lastBackupCheckTimestamp = undefined;
- backupStateEntry.value.lastBackupPlainHash = undefined;
- backupStateEntry.value.walletRootPriv = br.walletRootPriv;
- backupStateEntry.value.walletRootPub = encodeCrock(
- eddsaGetPublic(decodeCrock(br.walletRootPriv)),
- );
- await tx.config.put(backupStateEntry);
- for (const prov of br.providers) {
- const existingProv = await tx.backupProviders.get(prov.url);
- if (!existingProv) {
- await tx.backupProviders.put({
- baseUrl: prov.url,
- name: prov.name,
- paymentProposalIds: [],
- shouldRetryFreshProposal: false,
- state: {
- tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- ),
- },
- uids: [encodeCrock(getRandomBytes(32))],
- });
- }
- }
- const providers = await tx.backupProviders.iter().toArray();
- for (const prov of providers) {
- prov.lastBackupCycleTimestamp = undefined;
- prov.lastBackupHash = undefined;
- await tx.backupProviders.put(prov);
- }
- },
- );
-}
-
-async function backupRecoveryOurs(
- wex: WalletExecutionContext,
- br: BackupRecovery,
-) {
- throw Error("not implemented");
-}
-
-export async function loadBackupRecovery(
- wex: WalletExecutionContext,
- br: RecoveryLoadRequest,
-): Promise<void> {
- const bs = await provideBackupState(wex);
- const providers = await wex.db.runReadOnlyTx(
- { storeNames: ["backupProviders"] },
- async (tx) => {
- return await tx.backupProviders.iter().toArray();
- },
- );
- let strategy = br.strategy;
- if (
- br.recovery.walletRootPriv != bs.walletRootPriv &&
- providers.length > 0 &&
- !strategy
- ) {
- throw Error(
- "recovery load strategy must be specified for wallet with existing providers",
- );
- } else if (!strategy) {
- // Default to using the new key if we don't have providers yet.
- strategy = RecoveryMergeStrategy.Theirs;
- }
- if (strategy === RecoveryMergeStrategy.Theirs) {
- return backupRecoveryTheirs(wex, br.recovery);
- } else {
- return backupRecoveryOurs(wex, br.recovery);
- }
-}
-
-export async function decryptBackup(
- backupConfig: WalletBackupConfState,
- data: Uint8Array,
-): Promise<any> {
- const rMagic = bytesToString(data.slice(0, 8));
- if (rMagic != magic) {
- throw Error("invalid backup file (magic tag mismatch)");
- }
-
- const nonce = data.slice(8, 8 + 24);
- const box = data.slice(8 + 24);
- const secret = deriveBlobSecret(backupConfig);
- const dataCompressed = secretbox_open(box, nonce, secret);
- if (!dataCompressed) {
- throw Error("decryption failed");
- }
- return JSON.parse(bytesToString(gunzipSync(dataCompressed)));
-}
-
-export async function provideBackupState(
- wex: WalletExecutionContext,
-): Promise<WalletBackupConfState> {
- const bs: ConfigRecord | undefined = await wex.db.runReadOnlyTx(
- { storeNames: ["config"] },
- async (tx) => {
- return await tx.config.get(ConfigRecordKey.WalletBackupState);
- },
- );
- if (bs) {
- checkDbInvariant(
- bs.key === ConfigRecordKey.WalletBackupState,
- `backup entry inconsistent`,
- );
- return bs.value;
- }
- // We need to generate the key outside of the transaction
- // due to how IndexedDB works.
- const k = await wex.cryptoApi.createEddsaKeypair({});
- const d = getRandomBytes(5);
- // FIXME: device ID should be configured when wallet is initialized
- // and be based on hostname
- const deviceId = `wallet-core-${encodeCrock(d)}`;
- return await wex.db.runReadWriteTx({ storeNames: ["config"] }, async (tx) => {
- let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
- ConfigRecordKey.WalletBackupState,
- );
- if (!backupStateEntry) {
- backupStateEntry = {
- key: ConfigRecordKey.WalletBackupState,
- value: {
- deviceId,
- walletRootPub: k.pub,
- walletRootPriv: k.priv,
- lastBackupPlainHash: undefined,
- },
- };
- await tx.config.put(backupStateEntry);
- }
- checkDbInvariant(
- backupStateEntry.key === ConfigRecordKey.WalletBackupState,
- `backup entry inconsistent`,
- );
- return backupStateEntry.value;
- });
-}
-
-export async function getWalletBackupState(
- ws: InternalWalletState,
- tx: WalletDbReadOnlyTransaction<["config"]>,
-): Promise<WalletBackupConfState> {
- const bs = await tx.config.get(ConfigRecordKey.WalletBackupState);
- checkDbInvariant(!!bs, "wallet backup state should be in DB");
- checkDbInvariant(
- bs.key === ConfigRecordKey.WalletBackupState,
- `backup entry inconsistent`,
- );
- return bs.value;
-}
-
-export async function setWalletDeviceId(
- wex: WalletExecutionContext,
- deviceId: string,
-): Promise<void> {
- await provideBackupState(wex);
- await wex.db.runReadWriteTx({ storeNames: ["config"] }, async (tx) => {
- const backupStateEntry: ConfigRecord | undefined = await tx.config.get(
- ConfigRecordKey.WalletBackupState,
- );
- if (
- !backupStateEntry ||
- backupStateEntry.key !== ConfigRecordKey.WalletBackupState
- ) {
- return;
- }
- backupStateEntry.value.deviceId = deviceId;
- await tx.config.put(backupStateEntry);
- });
-}
-
-export async function getWalletDeviceId(
- wex: WalletExecutionContext,
-): Promise<string> {
- const bs = await provideBackupState(wex);
- return bs.deviceId;
-}
diff --git a/packages/taler-wallet-core/src/backup/state.ts b/packages/taler-wallet-core/src/backup/state.ts
@@ -1,15 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts
@@ -763,7 +763,7 @@ export async function getBalances(
): Promise<BalancesResponse> {
logger.trace("starting to compute balance");
- const wbal = await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
+ const wbal = await wex.runLegacyWalletDbTx(async (tx) => {
return getBalancesInsideTransaction(wex, tx);
});
@@ -781,7 +781,7 @@ export async function getBalanceOfScope(
): Promise<BalancesResponse> {
logger.trace("starting to compute balance");
- const wbal = await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
+ const wbal = await wex.runLegacyWalletDbTx(async (tx) => {
return getBalancesInsideTransaction(wex, tx);
});
@@ -890,22 +890,9 @@ export async function getPaymentBalanceDetails(
wex: WalletExecutionContext,
req: PaymentRestrictionsForBalance,
): Promise<PaymentBalanceDetails> {
- return await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "coinAvailability",
- "refreshGroups",
- "exchanges",
- "exchangeDetails",
- "globalCurrencyAuditors",
- "globalCurrencyExchanges",
- "denominations",
- ],
- },
- async (tx) => {
- return getPaymentBalanceDetailsInTx(wex, tx, req);
- },
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ return getPaymentBalanceDetailsInTx(wex, tx, req);
+ });
}
export async function getPaymentBalanceDetailsInTx(
@@ -1114,28 +1101,25 @@ export async function getBalanceDetail(
): Promise<PaymentBalanceDetails> {
const exchanges: { exchangeBaseUrl: string; exchangePub: string }[] = [];
const wires = new Array<string>();
- await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges", "exchangeDetails"] },
- async (tx) => {
- const allExchanges = await tx.exchanges.iter().toArray();
- for (const e of allExchanges) {
- const details = await getExchangeDetailsInTx(tx, e.baseUrl);
- if (!details || req.currency !== details.currency) {
- continue;
- }
- details.wireInfo.accounts.forEach((a) => {
- const payto = parsePaytoUri(a.payto_uri);
- if (payto && !wires.includes(payto.targetType)) {
- wires.push(payto.targetType);
- }
- });
- exchanges.push({
- exchangePub: details.masterPublicKey,
- exchangeBaseUrl: e.baseUrl,
- });
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const allExchanges = await tx.exchanges.iter().toArray();
+ for (const e of allExchanges) {
+ const details = await getExchangeDetailsInTx(tx, e.baseUrl);
+ if (!details || req.currency !== details.currency) {
+ continue;
}
- },
- );
+ details.wireInfo.accounts.forEach((a) => {
+ const payto = parsePaytoUri(a.payto_uri);
+ if (payto && !wires.includes(payto.targetType)) {
+ wires.push(payto.targetType);
+ }
+ });
+ exchanges.push({
+ exchangePub: details.masterPublicKey,
+ exchangeBaseUrl: e.baseUrl,
+ });
+ }
+ });
return await getPaymentBalanceDetails(wex, {
currency: req.currency,
diff --git a/packages/taler-wallet-core/src/coinSelection.ts b/packages/taler-wallet-core/src/coinSelection.ts
@@ -406,23 +406,9 @@ export async function selectPayCoins(
wex: WalletExecutionContext,
req: SelectPayCoinRequestNg,
): Promise<SelectPayCoinsResult> {
- return await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "coinAvailability",
- "denominations",
- "refreshGroups",
- "exchanges",
- "exchangeDetails",
- "coins",
- "globalCurrencyAuditors",
- "globalCurrencyExchanges",
- ],
- },
- async (tx) => {
- return selectPayCoinsInTx(wex, tx, req);
- },
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ return selectPayCoinsInTx(wex, tx, req);
+ });
}
async function maybeRepairCoinSelection(
@@ -1527,20 +1513,7 @@ export async function selectPeerCoins(
wex: WalletExecutionContext,
req: PeerCoinSelectionRequest,
): Promise<SelectPeerCoinsResult> {
- return await wex.db.runReadWriteTx(
- {
- storeNames: [
- "exchanges",
- "contractTerms",
- "coins",
- "coinAvailability",
- "denominations",
- "refreshGroups",
- "exchangeDetails",
- "globalCurrencyExchanges",
- "globalCurrencyAuditors",
- ],
- },
+ return await wex.runLegacyWalletDbTx(
async (tx): Promise<SelectPeerCoinsResult> => {
return selectPeerCoinsInTx(wex, tx, req);
},
@@ -1653,17 +1626,7 @@ export async function getMaxDepositAmount(
req: GetMaxDepositAmountRequest,
): Promise<GetMaxDepositAmountResponse> {
logger.trace(`getting max deposit amount for: ${j2s(req)}`);
- return await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "exchanges",
- "coinAvailability",
- "denominations",
- "exchangeDetails",
- "globalCurrencyAuditors",
- "globalCurrencyExchanges",
- ],
- },
+ return await wex.runLegacyWalletDbTx(
async (tx): Promise<GetMaxDepositAmountResponse> => {
let restrictWireMethod: string | undefined = undefined;
const exchangeInfos: Exchange[] = await getExchangesForDepositInTx(
diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts
@@ -49,7 +49,6 @@ import {
} from "@gnu-taler/taler-util";
import { HttpRequestOptions, HttpResponse } from "@gnu-taler/taler-util/http";
import {
- BackupProviderRecord,
CoinHistoryRecord,
CoinRecord,
DbPreciseTimestamp,
@@ -59,8 +58,8 @@ import {
ExchangeEntryRecord,
PeerPullCreditRecord,
PeerPullPaymentIncomingRecord,
- PeerPushDebitRecord,
PeerPushCreditRecord,
+ PeerPushDebitRecord,
PurchaseRecord,
RecoupGroupRecord,
RefreshGroupRecord,
@@ -668,7 +667,6 @@ export enum PendingTaskType {
Recoup = "recoup",
Withdraw = "withdraw",
Deposit = "deposit",
- Backup = "backup",
PeerPushDebit = "peer-push-debit",
PeerPullCredit = "peer-pull-credit",
PeerPushCredit = "peer-push-credit",
@@ -687,7 +685,6 @@ export type ParsedTaskIdentifier =
| { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string }
| { tag: PendingTaskType.ExchangeAutoRefresh; exchangeBaseUrl: string }
| { tag: PendingTaskType.ExchangeWalletKyc; exchangeBaseUrl: string }
- | { tag: PendingTaskType.Backup; backupProviderBaseUrl: string }
| { tag: PendingTaskType.Deposit; depositGroupId: string }
| { tag: PendingTaskType.PeerPullDebit; peerPullDebitId: string }
| { tag: PendingTaskType.PeerPullCredit; pursePub: string }
@@ -707,8 +704,6 @@ export function parseTaskIdentifier(x: string): ParsedTaskIdentifier {
const [type, ...rest] = task;
switch (type) {
- case PendingTaskType.Backup:
- return { tag: type, backupProviderBaseUrl: decodeURIComponent(rest[0]) };
case PendingTaskType.Deposit:
return { tag: type, depositGroupId: rest[0] };
case PendingTaskType.ExchangeUpdate:
@@ -742,8 +737,6 @@ export function parseTaskIdentifier(x: string): ParsedTaskIdentifier {
export function constructTaskIdentifier(p: ParsedTaskIdentifier): TaskIdStr {
switch (p.tag) {
- case PendingTaskType.Backup:
- return `${p.tag}:${p.backupProviderBaseUrl}` as TaskIdStr;
case PendingTaskType.Deposit:
return `${p.tag}:${p.depositGroupId}` as TaskIdStr;
case PendingTaskType.ExchangeUpdate:
@@ -810,11 +803,6 @@ export namespace TaskIdentifiers {
export function forDeposit(depositRecord: DepositGroupRecord): TaskIdStr {
return `${PendingTaskType.Deposit}:${depositRecord.depositGroupId}` as TaskIdStr;
}
- export function forBackup(backupRecord: BackupProviderRecord): TaskIdStr {
- return `${PendingTaskType.Backup}:${encodeURIComponent(
- backupRecord.baseUrl,
- )}` as TaskIdStr;
- }
export function forPeerPushPaymentInitiation(
ppi: PeerPushDebitRecord,
): TaskIdStr {
@@ -830,9 +818,7 @@ export namespace TaskIdentifiers {
): TaskIdStr {
return `${PendingTaskType.PeerPullDebit}:${ppi.peerPullDebitId}` as TaskIdStr;
}
- export function forPeerPushCredit(
- ppi: PeerPushCreditRecord,
- ): TaskIdStr {
+ export function forPeerPushCredit(ppi: PeerPushCreditRecord): TaskIdStr {
return `${PendingTaskType.PeerPushCredit}:${ppi.peerPushCreditId}` as TaskIdStr;
}
}
diff --git a/packages/taler-wallet-core/src/contacts.ts b/packages/taler-wallet-core/src/contacts.ts
@@ -22,30 +22,22 @@
*/
import {
- EmptyObject,
AddContactRequest,
- DeleteContactRequest,
- ContactListResponse,
ContactEntry,
+ ContactListResponse,
+ DeleteContactRequest,
+ EmptyObject,
Logger,
NotificationType,
} from "@gnu-taler/taler-util";
-import {
- ContactRecord,
- WalletDbReadOnlyTransaction
-} from "./db.js"
-import {
- WalletExecutionContext,
-} from "./wallet.js";
-
+import { ContactRecord, WalletDbReadOnlyTransaction } from "./db.js";
+import { WalletExecutionContext } from "./wallet.js";
const logger = new Logger("contacts.ts");
async function makeContactListItem(
wex: WalletExecutionContext,
- tx: WalletDbReadOnlyTransaction<
- ["contacts"]
- >,
+ tx: WalletDbReadOnlyTransaction<["contacts"]>,
r: ContactRecord,
//lastError: TalerErrorDetail | undefined,
): Promise<ContactEntry> {
@@ -60,7 +52,6 @@ async function makeContactListItem(
return listItem;
}
-
/**
* Add contact to the database.
*/
@@ -68,21 +59,14 @@ export async function addContact(
wex: WalletExecutionContext,
req: AddContactRequest,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx(
- {
- storeNames: [
- "contacts",
- ],
- },
- async (tx) => {
- tx.contacts.put (req.contact);
- tx.notify({
- type: NotificationType.ContactAdded,
- contact: req.contact,
- });
- },
- );
- return { };
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ tx.contacts.put(req.contact);
+ tx.notify({
+ type: NotificationType.ContactAdded,
+ contact: req.contact,
+ });
+ });
+ return {};
}
/**
@@ -92,21 +76,14 @@ export async function deleteContact(
wex: WalletExecutionContext,
req: DeleteContactRequest,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx(
- {
- storeNames: [
- "contacts",
- ],
- },
- async (tx) => {
- tx.contacts.delete ([req.contact.alias, req.contact.aliasType]);
- tx.notify({
- type: NotificationType.ContactDeleted,
- contact: req.contact,
- });
- },
- );
- return { };
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ await tx.contacts.delete([req.contact.alias, req.contact.aliasType]);
+ tx.notify({
+ type: NotificationType.ContactDeleted,
+ contact: req.contact,
+ });
+ });
+ return {};
}
/**
@@ -117,24 +94,12 @@ export async function listContacts(
req: EmptyObject,
): Promise<ContactListResponse> {
const contacts: ContactEntry[] = [];
- await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "contacts",
- ],
- },
- async (tx) => {
- const contactsRecords = await tx.contacts.iter().toArray();
- for (const contactRec of contactsRecords) {
- const li = await makeContactListItem(
- wex,
- tx,
- contactRec,
- );
- contacts.push(li);
- }
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const contactsRecords = await tx.contacts.iter().toArray();
+ for (const contactRec of contactsRecords) {
+ const li = await makeContactListItem(wex, tx, contactRec);
+ contacts.push(li);
+ }
+ });
return { contacts: contacts };
}
-
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
@@ -35,8 +35,6 @@ import {
AgeCommitmentProof,
AmountString,
Amounts,
- AttentionInfo,
- BackupProviderTerms,
BlindedUniqueDonationIdentifier,
CancellationToken,
Codec,
@@ -1980,86 +1978,6 @@ export interface RecoupGroupRecord {
scheduleRefreshCoins: CoinRefreshRequest[];
}
-export enum BackupProviderStateTag {
- Provisional = "provisional",
- Ready = "ready",
- Retrying = "retrying",
-}
-
-export type BackupProviderState =
- | {
- tag: BackupProviderStateTag.Provisional;
- }
- | {
- tag: BackupProviderStateTag.Ready;
- nextBackupTimestamp: DbPreciseTimestamp;
- }
- | {
- tag: BackupProviderStateTag.Retrying;
- };
-
-export interface BackupProviderRecord {
- /**
- * Base URL of the provider.
- *
- * Primary key for the record.
- */
- baseUrl: string;
-
- /**
- * Name of the provider
- */
- name: string;
-
- /**
- * Terms of service of the provider.
- * Might be unavailable in the DB in certain situations
- * (such as loading a recovery document).
- */
- terms?: BackupProviderTerms;
-
- /**
- * Hash of the last encrypted backup that we already merged
- * or successfully uploaded ourselves.
- */
- lastBackupHash?: string;
-
- /**
- * Last time that we successfully uploaded a backup (or
- * the uploaded backup was already current).
- *
- * Does NOT correspond to the timestamp of the backup,
- * which only changes when the backup content changes.
- */
- lastBackupCycleTimestamp?: DbPreciseTimestamp;
-
- /**
- * Proposal that we're currently trying to pay for.
- *
- * (Also included in paymentProposalIds.)
- *
- * FIXME: Make this part of a proper BackupProviderState?
- */
- currentPaymentTransactionId?: string;
-
- shouldRetryFreshProposal: boolean;
-
- /**
- * Proposals that were used to pay (or attempt to pay) the provider.
- *
- * Stored to display a history of payments to the provider, and
- * to make sure that the wallet isn't overpaying.
- */
- paymentProposalIds: string[];
-
- state: BackupProviderState;
-
- /**
- * UIDs for the operation that added the backup provider.
- */
- uids: string[];
-}
-
export enum DepositOperationStatus {
PendingDeposit = 0x0100_0000,
SuspendedDeposit = 0x0110_0000,
@@ -2682,22 +2600,6 @@ export interface ContractTermsRecord {
contractTermsRaw: any;
}
-export interface UserAttentionRecord {
- info: AttentionInfo;
-
- entityId: string;
-
- /**
- * When the notification was created.
- */
- created: DbPreciseTimestamp;
-
- /**
- * When the user mark this notification as read.
- */
- read: DbPreciseTimestamp | undefined;
-}
-
export interface DbExchangeHandle {
url: string;
exchangeMasterPub: string;
@@ -3458,21 +3360,6 @@ export const WalletStoresV1 = {
byGroup: describeIndex("byGroup", "withdrawalGroupId"),
},
),
- backupProviders: describeStore(
- "backupProviders",
- describeContents<BackupProviderRecord>({
- keyPath: "baseUrl",
- }),
- {
- byPaymentProposalId: describeIndex(
- "byPaymentProposalId",
- "paymentProposalIds",
- {
- multiEntry: true,
- },
- ),
- },
- ),
depositGroups: describeStore(
"depositGroups",
describeContents<DepositGroupRecord>({
@@ -3616,6 +3503,21 @@ export const WalletStoresV1 = {
//
// Obsolete stores, not used anymore
//
+ obsolete_backupProviders: describeStore(
+ "backupProviders",
+ describeContents<unknown>({
+ keyPath: "baseUrl",
+ }),
+ {
+ byPaymentProposalId: describeIndex(
+ "byPaymentProposalId",
+ "paymentProposalIds",
+ {
+ multiEntry: true,
+ },
+ ),
+ },
+ ),
_obsolete_transactions: describeStoreV2({
recordCodec: passthroughCodec<unknown>(),
storeName: "transactions",
@@ -3651,9 +3553,9 @@ export const WalletStoresV1 = {
}),
},
),
- userAttention: describeStore(
+ obsolete_userAttention: describeStore(
"userAttention",
- describeContents<UserAttentionRecord>({
+ describeContents<unknown>({
keyPath: ["entityId", "info.type"],
}),
{},
diff --git a/packages/taler-wallet-core/src/denominations.ts b/packages/taler-wallet-core/src/denominations.ts
@@ -577,9 +577,7 @@ export async function validateDenoms(
return;
}
- logger.info(
- `need to validate ${denoms.length} denominations`,
- );
+ logger.info(`need to validate ${denoms.length} denominations`);
let current = 0;
while (current < denoms.length) {
@@ -590,12 +588,12 @@ export async function validateDenoms(
batchIdx++, current++
) {
const denom = denoms[current];
- if (denom.verificationStatus ===
- DenominationVerificationStatus.Unverified
+ if (
+ denom.verificationStatus === DenominationVerificationStatus.Unverified
) {
logger.trace(
- `Validating denomination ${current + 1}/${denoms.length}`
- + ` signature of ${denom.denomPubHash}`,
+ `Validating denomination ${current + 1}/${denoms.length}` +
+ ` signature of ${denom.denomPubHash}`,
);
let valid = false;
@@ -609,10 +607,13 @@ export async function validateDenoms(
logger.trace(`Done validating ${denom.denomPubHash}`);
if (!valid) {
- logger.warn(`Signature check for denomination h=${denom.denomPubHash} failed`);
+ logger.warn(
+ `Signature check for denomination h=${denom.denomPubHash} failed`,
+ );
denom.verificationStatus = DenominationVerificationStatus.VerifiedBad;
} else {
- denom.verificationStatus = DenominationVerificationStatus.VerifiedGood;
+ denom.verificationStatus =
+ DenominationVerificationStatus.VerifiedGood;
}
updatedDenoms.push(denom);
@@ -621,15 +622,12 @@ export async function validateDenoms(
if (updatedDenoms.length > 0) {
logger.trace("writing denomination batch to db");
- await wex.db.runReadWriteTx(
- { storeNames: ["denominations"] },
- async (tx) => {
- for (let i = 0; i < updatedDenoms.length; i++) {
- const denom = updatedDenoms[i];
- await tx.denominations.put(denom);
- }
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (let i = 0; i < updatedDenoms.length; i++) {
+ const denom = updatedDenoms[i];
+ await tx.denominations.put(denom);
}
- );
+ });
wex.ws.denomInfoCache.clear();
logger.trace("done with DB write");
diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts
@@ -63,7 +63,6 @@ import {
TransactionState,
TransactionType,
URL,
- WalletNotification,
assertUnreachable,
canonicalJson,
checkDbInvariant,
@@ -333,33 +332,24 @@ export class DepositTransactionContext implements TransactionContext {
}
async deleteTransaction(): Promise<void> {
- const res = await this.wex.db.runReadWriteTx(
- {
- storeNames: ["depositGroups", "tombstones", "transactionsMeta"],
- },
- async (tx) => {
- return this.deleteTransactionInTx(tx);
- },
- );
- for (const notif of res.notifs) {
- this.wex.ws.notify(notif);
- }
+ await this.wex.runLegacyWalletDbTx(async (tx) => {
+ await this.deleteTransactionInTx(tx);
+ });
}
async deleteTransactionInTx(
tx: WalletDbReadWriteTransaction<
["depositGroups", "tombstones", "transactionsMeta"]
>,
- ): Promise<{ notifs: WalletNotification[] }> {
- const notifs: WalletNotification[] = [];
+ ): Promise<void> {
const rec = await tx.depositGroups.get(this.depositGroupId);
if (!rec) {
- return { notifs };
+ return;
}
const oldTxState = computeDepositTransactionStatus(rec);
await tx.depositGroups.delete(rec.depositGroupId);
await this.updateTransactionMeta(tx);
- notifs.push({
+ tx.notify({
type: NotificationType.TransactionStateTransition,
transactionId: this.transactionId,
oldTxState,
@@ -368,263 +358,250 @@ export class DepositTransactionContext implements TransactionContext {
},
newStId: -1,
});
- return { notifs };
}
async suspendTransaction(): Promise<void> {
const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
- await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- logger.warn(
- `can't suspend deposit group, depositGroupId=${depositGroupId} not found`,
- );
- return undefined;
- }
- const oldState = computeDepositTransactionStatus(dg);
- const oldStId = dg.operationStatus;
- let newOpStatus: DepositOperationStatus | undefined;
- switch (dg.operationStatus) {
- case DepositOperationStatus.AbortedDeposit:
- case DepositOperationStatus.FailedDeposit:
- case DepositOperationStatus.FailedTrack:
- case DepositOperationStatus.Finished:
- case DepositOperationStatus.SuspendedAborting:
- case DepositOperationStatus.SuspendedAggregateKyc:
- case DepositOperationStatus.SuspendedDeposit:
- case DepositOperationStatus.SuspendedDepositKyc:
- case DepositOperationStatus.LegacySuspendedTrack:
- case DepositOperationStatus.SuspendedDepositKycAuth:
- case DepositOperationStatus.SuspendedFinalizingTrack:
- break;
- case DepositOperationStatus.FinalizingTrack:
- newOpStatus = DepositOperationStatus.SuspendedFinalizingTrack;
- break;
- case DepositOperationStatus.PendingDepositKyc:
- newOpStatus = DepositOperationStatus.SuspendedDepositKyc;
- break;
- case DepositOperationStatus.PendingDeposit:
- newOpStatus = DepositOperationStatus.SuspendedDeposit;
- break;
- case DepositOperationStatus.PendingAggregateKyc:
- newOpStatus = DepositOperationStatus.SuspendedAggregateKyc;
- break;
- case DepositOperationStatus.LegacyPendingTrack:
- newOpStatus = DepositOperationStatus.LegacySuspendedTrack;
- break;
- case DepositOperationStatus.Aborting:
- newOpStatus = DepositOperationStatus.SuspendedAborting;
- break;
- case DepositOperationStatus.PendingDepositKycAuth:
- newOpStatus = DepositOperationStatus.SuspendedDepositKycAuth;
- break;
- default:
- assertUnreachable(dg.operationStatus);
- }
- if (!newOpStatus) {
- return undefined;
- }
- dg.operationStatus = newOpStatus;
- await tx.depositGroups.put(dg);
- await this.updateTransactionMeta(tx);
- applyNotifyTransition(tx.notify, transactionId, {
- oldTxState: oldState,
- newTxState: computeDepositTransactionStatus(dg),
- balanceEffect: BalanceEffect.None,
- newStId: dg.operationStatus,
- oldStId,
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ logger.warn(
+ `can't suspend deposit group, depositGroupId=${depositGroupId} not found`,
+ );
+ return undefined;
+ }
+ const oldState = computeDepositTransactionStatus(dg);
+ const oldStId = dg.operationStatus;
+ let newOpStatus: DepositOperationStatus | undefined;
+ switch (dg.operationStatus) {
+ case DepositOperationStatus.AbortedDeposit:
+ case DepositOperationStatus.FailedDeposit:
+ case DepositOperationStatus.FailedTrack:
+ case DepositOperationStatus.Finished:
+ case DepositOperationStatus.SuspendedAborting:
+ case DepositOperationStatus.SuspendedAggregateKyc:
+ case DepositOperationStatus.SuspendedDeposit:
+ case DepositOperationStatus.SuspendedDepositKyc:
+ case DepositOperationStatus.LegacySuspendedTrack:
+ case DepositOperationStatus.SuspendedDepositKycAuth:
+ case DepositOperationStatus.SuspendedFinalizingTrack:
+ break;
+ case DepositOperationStatus.FinalizingTrack:
+ newOpStatus = DepositOperationStatus.SuspendedFinalizingTrack;
+ break;
+ case DepositOperationStatus.PendingDepositKyc:
+ newOpStatus = DepositOperationStatus.SuspendedDepositKyc;
+ break;
+ case DepositOperationStatus.PendingDeposit:
+ newOpStatus = DepositOperationStatus.SuspendedDeposit;
+ break;
+ case DepositOperationStatus.PendingAggregateKyc:
+ newOpStatus = DepositOperationStatus.SuspendedAggregateKyc;
+ break;
+ case DepositOperationStatus.LegacyPendingTrack:
+ newOpStatus = DepositOperationStatus.LegacySuspendedTrack;
+ break;
+ case DepositOperationStatus.Aborting:
+ newOpStatus = DepositOperationStatus.SuspendedAborting;
+ break;
+ case DepositOperationStatus.PendingDepositKycAuth:
+ newOpStatus = DepositOperationStatus.SuspendedDepositKycAuth;
+ break;
+ default:
+ assertUnreachable(dg.operationStatus);
+ }
+ if (!newOpStatus) {
+ return undefined;
+ }
+ dg.operationStatus = newOpStatus;
+ await tx.depositGroups.put(dg);
+ await this.updateTransactionMeta(tx);
+ applyNotifyTransition(tx.notify, transactionId, {
+ oldTxState: oldState,
+ newTxState: computeDepositTransactionStatus(dg),
+ balanceEffect: BalanceEffect.None,
+ newStId: dg.operationStatus,
+ oldStId,
+ });
+ });
wex.taskScheduler.stopShepherdTask(retryTag);
}
async abortTransaction(reason?: TalerErrorDetail): Promise<void> {
const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
- await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- logger.warn(
- `can't suspend deposit group, depositGroupId=${depositGroupId} not found`,
- );
- return undefined;
- }
- const oldState = computeDepositTransactionStatus(dg);
- const oldStId = dg.operationStatus;
- switch (dg.operationStatus) {
- case DepositOperationStatus.PendingDepositKyc:
- case DepositOperationStatus.SuspendedDepositKyc:
- case DepositOperationStatus.PendingDepositKycAuth:
- case DepositOperationStatus.SuspendedDepositKycAuth:
- case DepositOperationStatus.PendingDeposit:
- case DepositOperationStatus.SuspendedDeposit: {
- dg.operationStatus = DepositOperationStatus.Aborting;
- dg.abortReason = reason;
- await tx.depositGroups.put(dg);
- await this.updateTransactionMeta(tx);
- applyNotifyTransition(tx.notify, transactionId, {
- oldTxState: oldState,
- newTxState: computeDepositTransactionStatus(dg),
- balanceEffect: BalanceEffect.Any,
- oldStId,
- newStId: dg.operationStatus,
- });
- return;
- }
- case DepositOperationStatus.FinalizingTrack:
- case DepositOperationStatus.SuspendedFinalizingTrack:
- case DepositOperationStatus.LegacyPendingTrack:
- case DepositOperationStatus.LegacySuspendedTrack:
- case DepositOperationStatus.AbortedDeposit:
- case DepositOperationStatus.Aborting:
- case DepositOperationStatus.FailedDeposit:
- case DepositOperationStatus.FailedTrack:
- case DepositOperationStatus.Finished:
- case DepositOperationStatus.SuspendedAborting:
- case DepositOperationStatus.PendingAggregateKyc:
- case DepositOperationStatus.SuspendedAggregateKyc:
- break;
- default:
- assertUnreachable(dg.operationStatus);
- }
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ logger.warn(
+ `can't suspend deposit group, depositGroupId=${depositGroupId} not found`,
+ );
return undefined;
- },
- );
+ }
+ const oldState = computeDepositTransactionStatus(dg);
+ const oldStId = dg.operationStatus;
+ switch (dg.operationStatus) {
+ case DepositOperationStatus.PendingDepositKyc:
+ case DepositOperationStatus.SuspendedDepositKyc:
+ case DepositOperationStatus.PendingDepositKycAuth:
+ case DepositOperationStatus.SuspendedDepositKycAuth:
+ case DepositOperationStatus.PendingDeposit:
+ case DepositOperationStatus.SuspendedDeposit: {
+ dg.operationStatus = DepositOperationStatus.Aborting;
+ dg.abortReason = reason;
+ await tx.depositGroups.put(dg);
+ await this.updateTransactionMeta(tx);
+ applyNotifyTransition(tx.notify, transactionId, {
+ oldTxState: oldState,
+ newTxState: computeDepositTransactionStatus(dg),
+ balanceEffect: BalanceEffect.Any,
+ oldStId,
+ newStId: dg.operationStatus,
+ });
+ return;
+ }
+ case DepositOperationStatus.FinalizingTrack:
+ case DepositOperationStatus.SuspendedFinalizingTrack:
+ case DepositOperationStatus.LegacyPendingTrack:
+ case DepositOperationStatus.LegacySuspendedTrack:
+ case DepositOperationStatus.AbortedDeposit:
+ case DepositOperationStatus.Aborting:
+ case DepositOperationStatus.FailedDeposit:
+ case DepositOperationStatus.FailedTrack:
+ case DepositOperationStatus.Finished:
+ case DepositOperationStatus.SuspendedAborting:
+ case DepositOperationStatus.PendingAggregateKyc:
+ case DepositOperationStatus.SuspendedAggregateKyc:
+ break;
+ default:
+ assertUnreachable(dg.operationStatus);
+ }
+ return undefined;
+ });
wex.taskScheduler.stopShepherdTask(retryTag);
wex.taskScheduler.startShepherdTask(retryTag);
}
async resumeTransaction(): Promise<void> {
const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
- await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- logger.warn(
- `can't resume deposit group, depositGroupId=${depositGroupId} not found`,
- );
- return;
- }
- const oldState = computeDepositTransactionStatus(dg);
- const oldStId = dg.operationStatus;
- let newOpStatus: DepositOperationStatus | undefined;
- switch (dg.operationStatus) {
- case DepositOperationStatus.AbortedDeposit:
- case DepositOperationStatus.Aborting:
- case DepositOperationStatus.FailedDeposit:
- case DepositOperationStatus.FailedTrack:
- case DepositOperationStatus.Finished:
- case DepositOperationStatus.PendingAggregateKyc:
- case DepositOperationStatus.PendingDeposit:
- case DepositOperationStatus.PendingDepositKyc:
- case DepositOperationStatus.LegacyPendingTrack:
- case DepositOperationStatus.PendingDepositKycAuth:
- case DepositOperationStatus.FinalizingTrack:
- break;
- case DepositOperationStatus.SuspendedDepositKyc:
- newOpStatus = DepositOperationStatus.PendingDepositKyc;
- break;
- case DepositOperationStatus.SuspendedDeposit:
- newOpStatus = DepositOperationStatus.PendingDeposit;
- break;
- case DepositOperationStatus.SuspendedAborting:
- newOpStatus = DepositOperationStatus.Aborting;
- break;
- case DepositOperationStatus.SuspendedAggregateKyc:
- newOpStatus = DepositOperationStatus.PendingAggregateKyc;
- break;
- case DepositOperationStatus.LegacySuspendedTrack:
- newOpStatus = DepositOperationStatus.LegacyPendingTrack;
- break;
- case DepositOperationStatus.SuspendedDepositKycAuth:
- newOpStatus = DepositOperationStatus.PendingDepositKycAuth;
- break;
- case DepositOperationStatus.SuspendedFinalizingTrack:
- newOpStatus = DepositOperationStatus.FinalizingTrack;
- break;
- default:
- assertUnreachable(dg.operationStatus);
- }
- if (!newOpStatus) {
- return undefined;
- }
- dg.operationStatus = newOpStatus;
- await tx.depositGroups.put(dg);
- await this.updateTransactionMeta(tx);
- applyNotifyTransition(tx.notify, transactionId, {
- oldTxState: oldState,
- newTxState: computeDepositTransactionStatus(dg),
- balanceEffect: BalanceEffect.None,
- newStId: dg.operationStatus,
- oldStId,
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ logger.warn(
+ `can't resume deposit group, depositGroupId=${depositGroupId} not found`,
+ );
+ return;
+ }
+ const oldState = computeDepositTransactionStatus(dg);
+ const oldStId = dg.operationStatus;
+ let newOpStatus: DepositOperationStatus | undefined;
+ switch (dg.operationStatus) {
+ case DepositOperationStatus.AbortedDeposit:
+ case DepositOperationStatus.Aborting:
+ case DepositOperationStatus.FailedDeposit:
+ case DepositOperationStatus.FailedTrack:
+ case DepositOperationStatus.Finished:
+ case DepositOperationStatus.PendingAggregateKyc:
+ case DepositOperationStatus.PendingDeposit:
+ case DepositOperationStatus.PendingDepositKyc:
+ case DepositOperationStatus.LegacyPendingTrack:
+ case DepositOperationStatus.PendingDepositKycAuth:
+ case DepositOperationStatus.FinalizingTrack:
+ break;
+ case DepositOperationStatus.SuspendedDepositKyc:
+ newOpStatus = DepositOperationStatus.PendingDepositKyc;
+ break;
+ case DepositOperationStatus.SuspendedDeposit:
+ newOpStatus = DepositOperationStatus.PendingDeposit;
+ break;
+ case DepositOperationStatus.SuspendedAborting:
+ newOpStatus = DepositOperationStatus.Aborting;
+ break;
+ case DepositOperationStatus.SuspendedAggregateKyc:
+ newOpStatus = DepositOperationStatus.PendingAggregateKyc;
+ break;
+ case DepositOperationStatus.LegacySuspendedTrack:
+ newOpStatus = DepositOperationStatus.LegacyPendingTrack;
+ break;
+ case DepositOperationStatus.SuspendedDepositKycAuth:
+ newOpStatus = DepositOperationStatus.PendingDepositKycAuth;
+ break;
+ case DepositOperationStatus.SuspendedFinalizingTrack:
+ newOpStatus = DepositOperationStatus.FinalizingTrack;
+ break;
+ default:
+ assertUnreachable(dg.operationStatus);
+ }
+ if (!newOpStatus) {
+ return undefined;
+ }
+ dg.operationStatus = newOpStatus;
+ await tx.depositGroups.put(dg);
+ await this.updateTransactionMeta(tx);
+ applyNotifyTransition(tx.notify, transactionId, {
+ oldTxState: oldState,
+ newTxState: computeDepositTransactionStatus(dg),
+ balanceEffect: BalanceEffect.None,
+ newStId: dg.operationStatus,
+ oldStId,
+ });
+ });
wex.taskScheduler.startShepherdTask(retryTag);
}
async failTransaction(reason?: TalerErrorDetail): Promise<void> {
const { wex, depositGroupId, transactionId, taskId } = this;
- const transitionInfo = await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- logger.warn(
- `can't cancel aborting deposit group, depositGroupId=${depositGroupId} not found`,
- );
- return undefined;
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ logger.warn(
+ `can't cancel aborting deposit group, depositGroupId=${depositGroupId} not found`,
+ );
+ return undefined;
+ }
+ const oldState = computeDepositTransactionStatus(dg);
+ const oldStId = dg.operationStatus;
+ let newState: DepositOperationStatus;
+ switch (dg.operationStatus) {
+ case DepositOperationStatus.PendingAggregateKyc:
+ case DepositOperationStatus.SuspendedAggregateKyc:
+ case DepositOperationStatus.SuspendedAborting:
+ case DepositOperationStatus.Aborting: {
+ newState = DepositOperationStatus.FailedDeposit;
+ break;
}
- const oldState = computeDepositTransactionStatus(dg);
- const oldStId = dg.operationStatus;
- let newState: DepositOperationStatus;
- switch (dg.operationStatus) {
- case DepositOperationStatus.PendingAggregateKyc:
- case DepositOperationStatus.SuspendedAggregateKyc:
- case DepositOperationStatus.SuspendedAborting:
- case DepositOperationStatus.Aborting: {
- newState = DepositOperationStatus.FailedDeposit;
- break;
- }
- case DepositOperationStatus.LegacyPendingTrack:
- case DepositOperationStatus.LegacySuspendedTrack: {
- newState = DepositOperationStatus.FailedTrack;
- break;
- }
- case DepositOperationStatus.AbortedDeposit:
- case DepositOperationStatus.FailedDeposit:
- case DepositOperationStatus.FailedTrack:
- case DepositOperationStatus.Finished:
- case DepositOperationStatus.PendingDeposit:
- case DepositOperationStatus.PendingDepositKyc:
- case DepositOperationStatus.PendingDepositKycAuth:
- case DepositOperationStatus.SuspendedDeposit:
- case DepositOperationStatus.SuspendedDepositKyc:
- case DepositOperationStatus.SuspendedDepositKycAuth:
- case DepositOperationStatus.FinalizingTrack:
- case DepositOperationStatus.SuspendedFinalizingTrack:
- throw Error("failing not supported in current state");
- default:
- assertUnreachable(dg.operationStatus);
+ case DepositOperationStatus.LegacyPendingTrack:
+ case DepositOperationStatus.LegacySuspendedTrack: {
+ newState = DepositOperationStatus.FailedTrack;
+ break;
}
- dg.operationStatus = newState;
- dg.failReason = reason;
- await tx.depositGroups.put(dg);
- await this.updateTransactionMeta(tx);
- applyNotifyTransition(tx.notify, transactionId, {
- oldTxState: oldState,
- newTxState: computeDepositTransactionStatus(dg),
- balanceEffect: BalanceEffect.Any,
- newStId: dg.operationStatus,
- oldStId,
- });
- },
- );
+ case DepositOperationStatus.AbortedDeposit:
+ case DepositOperationStatus.FailedDeposit:
+ case DepositOperationStatus.FailedTrack:
+ case DepositOperationStatus.Finished:
+ case DepositOperationStatus.PendingDeposit:
+ case DepositOperationStatus.PendingDepositKyc:
+ case DepositOperationStatus.PendingDepositKycAuth:
+ case DepositOperationStatus.SuspendedDeposit:
+ case DepositOperationStatus.SuspendedDepositKyc:
+ case DepositOperationStatus.SuspendedDepositKycAuth:
+ case DepositOperationStatus.FinalizingTrack:
+ case DepositOperationStatus.SuspendedFinalizingTrack:
+ throw Error("failing not supported in current state");
+ default:
+ assertUnreachable(dg.operationStatus);
+ }
+ dg.operationStatus = newState;
+ dg.failReason = reason;
+ await tx.depositGroups.put(dg);
+ await this.updateTransactionMeta(tx);
+ applyNotifyTransition(tx.notify, transactionId, {
+ oldTxState: oldState,
+ newTxState: computeDepositTransactionStatus(dg),
+ balanceEffect: BalanceEffect.Any,
+ newStId: dg.operationStatus,
+ oldStId,
+ });
+ });
wex.taskScheduler.stopShepherdTask(taskId);
}
}
@@ -859,14 +836,11 @@ async function refundDepositGroup(
break;
default: {
const coinPub = payCoinSelection.coinPubs[i];
- const coinExchange = await wex.db.runReadOnlyTx(
- { storeNames: ["coins"] },
- async (tx) => {
- const coinRecord = await tx.coins.get(coinPub);
- checkDbInvariant(!!coinRecord, `coin ${coinPub} not found in DB`);
- return coinRecord.exchangeBaseUrl;
- },
- );
+ const coinExchange = await wex.runLegacyWalletDbTx(async (tx) => {
+ const coinRecord = await tx.coins.get(coinPub);
+ checkDbInvariant(!!coinRecord, `coin ${coinPub} not found in DB`);
+ return coinRecord.exchangeBaseUrl;
+ });
const refundAmount = payCoinSelection.coinContributions[i];
// We use a constant refund transaction ID, since there can
// only be one refund for this contract.
@@ -911,22 +885,15 @@ async function refundDepositGroup(
}
// FIXME: Handle case where refund request needs to be tried again
newTxPerCoin[i] = newStatus;
- await wex.db.runReadWriteTx(
- {
- storeNames: ["depositGroups"],
- },
- async (tx) => {
- const newDg = await tx.depositGroups.get(
- depositGroup.depositGroupId,
- );
- if (!newDg || !newDg.statusPerCoin) {
- return;
- }
- newDg.statusPerCoin[i] = newStatus;
- await tx.depositGroups.put(newDg);
- newTxPerCoin = [...newDg.statusPerCoin];
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const newDg = await tx.depositGroups.get(depositGroup.depositGroupId);
+ if (!newDg || !newDg.statusPerCoin) {
+ return;
+ }
+ newDg.statusPerCoin[i] = newStatus;
+ await tx.depositGroups.put(newDg);
+ newTxPerCoin = [...newDg.statusPerCoin];
+ });
break;
}
}
@@ -934,65 +901,50 @@ async function refundDepositGroup(
// Check if we are done trying to refund.
- const res = await wex.db.runReadWriteTx(
- {
- storeNames: [
- "coinAvailability",
- "coinHistory",
- "coins",
- "denominations",
- "denominationFamilies",
- "depositGroups",
- "refreshGroups",
- "refreshSessions",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- const newDg = await tx.depositGroups.get(depositGroup.depositGroupId);
- if (!newDg || !newDg.statusPerCoin) {
- return;
- }
- let refundsAllDone = true;
- for (let i = 0; i < newTxPerCoin.length; i++) {
- switch (newTxPerCoin[i]) {
- case DepositElementStatus.RefundFailed:
- case DepositElementStatus.RefundNotFound:
- case DepositElementStatus.RefundSuccess:
- break;
- default:
- refundsAllDone = false;
- }
- }
- if (!refundsAllDone) {
- return;
- }
- newTxPerCoin = [...newDg.statusPerCoin];
- const refreshCoins: CoinRefreshRequest[] = [];
- for (let i = 0; i < newTxPerCoin.length; i++) {
- refreshCoins.push({
- amount: payCoinSelection.coinContributions[i],
- coinPub: payCoinSelection.coinPubs[i],
- refundRequest: refundReqPerCoin[i],
- });
+ const res = await wex.runLegacyWalletDbTx(async (tx) => {
+ const newDg = await tx.depositGroups.get(depositGroup.depositGroupId);
+ if (!newDg || !newDg.statusPerCoin) {
+ return;
+ }
+ let refundsAllDone = true;
+ for (let i = 0; i < newTxPerCoin.length; i++) {
+ switch (newTxPerCoin[i]) {
+ case DepositElementStatus.RefundFailed:
+ case DepositElementStatus.RefundNotFound:
+ case DepositElementStatus.RefundSuccess:
+ break;
+ default:
+ refundsAllDone = false;
}
- const refreshRes = await createRefreshGroup(
- wex,
- tx,
- currency,
- refreshCoins,
- RefreshReason.AbortDeposit,
- constructTransactionIdentifier({
- tag: TransactionType.Deposit,
- depositGroupId: newDg.depositGroupId,
- }),
- );
- newDg.abortRefreshGroupId = refreshRes.refreshGroupId;
- await tx.depositGroups.put(newDg);
- await ctx.updateTransactionMeta(tx);
- return { refreshRes };
- },
- );
+ }
+ if (!refundsAllDone) {
+ return;
+ }
+ newTxPerCoin = [...newDg.statusPerCoin];
+ const refreshCoins: CoinRefreshRequest[] = [];
+ for (let i = 0; i < newTxPerCoin.length; i++) {
+ refreshCoins.push({
+ amount: payCoinSelection.coinContributions[i],
+ coinPub: payCoinSelection.coinPubs[i],
+ refundRequest: refundReqPerCoin[i],
+ });
+ }
+ const refreshRes = await createRefreshGroup(
+ wex,
+ tx,
+ currency,
+ refreshCoins,
+ RefreshReason.AbortDeposit,
+ constructTransactionIdentifier({
+ tag: TransactionType.Deposit,
+ depositGroupId: newDg.depositGroupId,
+ }),
+ );
+ newDg.abortRefreshGroupId = refreshRes.refreshGroupId;
+ await tx.depositGroups.put(newDg);
+ await ctx.updateTransactionMeta(tx);
+ return { refreshRes };
+ });
if (res?.refreshRes) {
return TaskRunResult.progress();
@@ -1027,19 +979,16 @@ async function waitForRefreshOnDepositGroup(
// Wait for the refresh transaction to be in a final state.
await genericWaitForState(wex, {
async checkState() {
- return await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "refreshGroups", "transactionsMeta"] },
- async (tx) => {
- const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
- switch (refreshGroup?.operationStatus) {
- case undefined:
- case RefreshOperationStatus.Failed:
- case RefreshOperationStatus.Finished:
- return true;
- }
- return false;
- },
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
+ switch (refreshGroup?.operationStatus) {
+ case undefined:
+ case RefreshOperationStatus.Failed:
+ case RefreshOperationStatus.Finished:
+ return true;
+ }
+ return false;
+ });
},
filterNotification(notif) {
return (
@@ -1049,48 +998,45 @@ async function waitForRefreshOnDepositGroup(
},
});
- const didTransition = await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "refreshGroups", "transactionsMeta"] },
- async (tx) => {
- const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
- let newOpState: DepositOperationStatus | undefined;
- switch (refreshGroup?.operationStatus) {
- case undefined: {
- // Maybe it got manually deleted? Means that we should
- // just go into aborted.
- logger.warn("no aborting refresh group found for deposit group");
- newOpState = DepositOperationStatus.AbortedDeposit;
- break;
- }
- case RefreshOperationStatus.Failed:
- case RefreshOperationStatus.Finished: {
- newOpState = DepositOperationStatus.AbortedDeposit;
- break;
- }
- default:
- return false;
+ const didTransition = await wex.runLegacyWalletDbTx(async (tx) => {
+ const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
+ let newOpState: DepositOperationStatus | undefined;
+ switch (refreshGroup?.operationStatus) {
+ case undefined: {
+ // Maybe it got manually deleted? Means that we should
+ // just go into aborted.
+ logger.warn("no aborting refresh group found for deposit group");
+ newOpState = DepositOperationStatus.AbortedDeposit;
+ break;
}
- const newDg = await tx.depositGroups.get(depositGroup.depositGroupId);
- if (!newDg) {
- return false;
+ case RefreshOperationStatus.Failed:
+ case RefreshOperationStatus.Finished: {
+ newOpState = DepositOperationStatus.AbortedDeposit;
+ break;
}
- const oldTxState = computeDepositTransactionStatus(newDg);
- const oldStId = newDg.operationStatus;
- newDg.operationStatus = newOpState;
- const newTxState = computeDepositTransactionStatus(newDg);
- const newStId = newDg.operationStatus;
- await tx.depositGroups.put(newDg);
- await ctx.updateTransactionMeta(tx);
- applyNotifyTransition(tx.notify, ctx.transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.Any,
- newStId,
- oldStId,
- });
- return true;
- },
- );
+ default:
+ return false;
+ }
+ const newDg = await tx.depositGroups.get(depositGroup.depositGroupId);
+ if (!newDg) {
+ return false;
+ }
+ const oldTxState = computeDepositTransactionStatus(newDg);
+ const oldStId = newDg.operationStatus;
+ newDg.operationStatus = newOpState;
+ const newTxState = computeDepositTransactionStatus(newDg);
+ const newStId = newDg.operationStatus;
+ await tx.depositGroups.put(newDg);
+ await ctx.updateTransactionMeta(tx);
+ applyNotifyTransition(tx.notify, ctx.transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.Any,
+ newStId,
+ oldStId,
+ });
+ return true;
+ });
if (didTransition) {
return TaskRunResult.progress();
}
@@ -1194,50 +1140,46 @@ async function processDepositGroupPendingKyc(
// Now store the result.
- return await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const newDg = await tx.depositGroups.get(depositGroupId);
- if (!newDg) {
- return TaskRunResult.finished();
- }
- const oldTxState = computeDepositTransactionStatus(newDg);
- const oldStId = newDg.operationStatus;
- switch (newDg.operationStatus) {
- case DepositOperationStatus.PendingAggregateKyc:
- if (requiresAuth) {
- throw Error("kyc auth during aggregation not yet supported");
- }
- break;
- case DepositOperationStatus.PendingDepositKyc:
- if (requiresAuth) {
- newDg.operationStatus =
- DepositOperationStatus.PendingDepositKycAuth;
- }
- break;
- case DepositOperationStatus.PendingDepositKycAuth:
- if (!requiresAuth) {
- newDg.operationStatus = DepositOperationStatus.PendingDepositKyc;
- }
- break;
- default:
- return TaskRunResult.backoff();
- }
- newDg.kycInfo = kycInfo;
- await tx.depositGroups.put(newDg);
- await ctx.updateTransactionMeta(tx);
- const newTxState = computeDepositTransactionStatus(newDg);
- const newStId = newDg.operationStatus;
- applyNotifyTransition(tx.notify, ctx.transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.Any,
- oldStId,
- newStId,
- });
- return algoRes.taskResult;
- },
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ const newDg = await tx.depositGroups.get(depositGroupId);
+ if (!newDg) {
+ return TaskRunResult.finished();
+ }
+ const oldTxState = computeDepositTransactionStatus(newDg);
+ const oldStId = newDg.operationStatus;
+ switch (newDg.operationStatus) {
+ case DepositOperationStatus.PendingAggregateKyc:
+ if (requiresAuth) {
+ throw Error("kyc auth during aggregation not yet supported");
+ }
+ break;
+ case DepositOperationStatus.PendingDepositKyc:
+ if (requiresAuth) {
+ newDg.operationStatus = DepositOperationStatus.PendingDepositKycAuth;
+ }
+ break;
+ case DepositOperationStatus.PendingDepositKycAuth:
+ if (!requiresAuth) {
+ newDg.operationStatus = DepositOperationStatus.PendingDepositKyc;
+ }
+ break;
+ default:
+ return TaskRunResult.backoff();
+ }
+ newDg.kycInfo = kycInfo;
+ await tx.depositGroups.put(newDg);
+ await ctx.updateTransactionMeta(tx);
+ const newTxState = computeDepositTransactionStatus(newDg);
+ const newStId = newDg.operationStatus;
+ applyNotifyTransition(tx.notify, ctx.transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.Any,
+ oldStId,
+ newStId,
+ });
+ return algoRes.taskResult;
+ });
}
/**
@@ -1331,74 +1273,71 @@ async function transitionToKycRequired(
const ctx = new DepositTransactionContext(wex, depositGroupId);
- await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- return undefined;
- }
- const oldTxState = computeDepositTransactionStatus(dg);
- const oldStId = dg.operationStatus;
- switch (dg.operationStatus) {
- case DepositOperationStatus.LegacyPendingTrack:
- case DepositOperationStatus.FinalizingTrack:
- if (args.badKycAuth) {
- throw Error("not yet supported");
- } else {
- dg.operationStatus = DepositOperationStatus.PendingAggregateKyc;
- }
- break;
- case DepositOperationStatus.PendingDeposit:
- if (args.badKycAuth) {
- dg.operationStatus = DepositOperationStatus.PendingDepositKycAuth;
- } else {
- dg.operationStatus = DepositOperationStatus.PendingDepositKyc;
- }
- break;
- case DepositOperationStatus.PendingDepositKycAuth:
- if (!args.badKycAuth) {
- dg.operationStatus = DepositOperationStatus.PendingDepositKyc;
- }
- break;
- case DepositOperationStatus.PendingDepositKyc:
- if (args.badKycAuth) {
- dg.operationStatus = DepositOperationStatus.PendingDepositKycAuth;
- }
- break;
- default:
- logger.warn(
- `transitionToKycRequired: state ${dg.operationStatus} / ${
- DepositOperationStatus[dg.operationStatus]
- } not handled`,
- );
- return;
- }
- if (dg.kycInfo && dg.kycInfo.exchangeBaseUrl === args.exchangeUrl) {
- dg.kycInfo.lastDeny = timestampPreciseToDb(TalerPreciseTimestamp.now());
- dg.kycInfo.lastBadKycAuth = args.badKycAuth;
- } else {
- // Reset other info when new exchange is involved.
- dg.kycInfo = {
- exchangeBaseUrl: args.exchangeUrl,
- paytoHash: args.kycPaytoHash,
- lastDeny: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- lastBadKycAuth: args.badKycAuth,
- };
- }
- await tx.depositGroups.put(dg);
- await ctx.updateTransactionMeta(tx);
- const newTxState = computeDepositTransactionStatus(dg);
- const newStId = dg.operationStatus;
- applyNotifyTransition(tx.notify, ctx.transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.Any,
- newStId,
- oldStId,
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return undefined;
+ }
+ const oldTxState = computeDepositTransactionStatus(dg);
+ const oldStId = dg.operationStatus;
+ switch (dg.operationStatus) {
+ case DepositOperationStatus.LegacyPendingTrack:
+ case DepositOperationStatus.FinalizingTrack:
+ if (args.badKycAuth) {
+ throw Error("not yet supported");
+ } else {
+ dg.operationStatus = DepositOperationStatus.PendingAggregateKyc;
+ }
+ break;
+ case DepositOperationStatus.PendingDeposit:
+ if (args.badKycAuth) {
+ dg.operationStatus = DepositOperationStatus.PendingDepositKycAuth;
+ } else {
+ dg.operationStatus = DepositOperationStatus.PendingDepositKyc;
+ }
+ break;
+ case DepositOperationStatus.PendingDepositKycAuth:
+ if (!args.badKycAuth) {
+ dg.operationStatus = DepositOperationStatus.PendingDepositKyc;
+ }
+ break;
+ case DepositOperationStatus.PendingDepositKyc:
+ if (args.badKycAuth) {
+ dg.operationStatus = DepositOperationStatus.PendingDepositKycAuth;
+ }
+ break;
+ default:
+ logger.warn(
+ `transitionToKycRequired: state ${dg.operationStatus} / ${
+ DepositOperationStatus[dg.operationStatus]
+ } not handled`,
+ );
+ return;
+ }
+ if (dg.kycInfo && dg.kycInfo.exchangeBaseUrl === args.exchangeUrl) {
+ dg.kycInfo.lastDeny = timestampPreciseToDb(TalerPreciseTimestamp.now());
+ dg.kycInfo.lastBadKycAuth = args.badKycAuth;
+ } else {
+ // Reset other info when new exchange is involved.
+ dg.kycInfo = {
+ exchangeBaseUrl: args.exchangeUrl,
+ paytoHash: args.kycPaytoHash,
+ lastDeny: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+ lastBadKycAuth: args.badKycAuth,
+ };
+ }
+ await tx.depositGroups.put(dg);
+ await ctx.updateTransactionMeta(tx);
+ const newTxState = computeDepositTransactionStatus(dg);
+ const newStId = dg.operationStatus;
+ applyNotifyTransition(tx.notify, ctx.transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.Any,
+ newStId,
+ oldStId,
+ });
+ });
return TaskRunResult.progress();
}
@@ -1424,14 +1363,11 @@ async function processDepositGroupTrack(
for (let i = 0; i < statusPerCoin.length; i++) {
const coinPub = payCoinSelection.coinPubs[i];
// FIXME: Make the URL part of the coin selection?
- const exchangeBaseUrl = await wex.db.runReadWriteTx(
- { storeNames: ["coins"] },
- async (tx) => {
- const coinRecord = await tx.coins.get(coinPub);
- checkDbInvariant(!!coinRecord, `coin ${coinPub} not found in DB`);
- return coinRecord.exchangeBaseUrl;
- },
- );
+ const exchangeBaseUrl = await wex.runLegacyWalletDbTx(async (tx) => {
+ const coinRecord = await tx.coins.get(coinPub);
+ checkDbInvariant(!!coinRecord, `coin ${coinPub} not found in DB`);
+ return coinRecord.exchangeBaseUrl;
+ });
let updatedTxStatus: DepositElementStatus | undefined = undefined;
let newWiredCoin:
@@ -1497,80 +1433,72 @@ async function processDepositGroupTrack(
}
if (updatedTxStatus !== undefined) {
- await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- return;
- }
- if (!dg.statusPerCoin) {
- return;
- }
- if (updatedTxStatus !== undefined) {
- dg.statusPerCoin[i] = updatedTxStatus;
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return;
+ }
+ if (!dg.statusPerCoin) {
+ return;
+ }
+ if (updatedTxStatus !== undefined) {
+ dg.statusPerCoin[i] = updatedTxStatus;
+ }
+ if (newWiredCoin) {
+ /**
+ * FIXME: if there is a new wire information from the exchange
+ * it should add up to the previous tracking states.
+ *
+ * This may loose information by overriding prev state.
+ *
+ * And: add checks to integration tests
+ */
+ if (!dg.trackingState) {
+ dg.trackingState = {};
}
- if (newWiredCoin) {
- /**
- * FIXME: if there is a new wire information from the exchange
- * it should add up to the previous tracking states.
- *
- * This may loose information by overriding prev state.
- *
- * And: add checks to integration tests
- */
- if (!dg.trackingState) {
- dg.trackingState = {};
- }
- dg.trackingState[newWiredCoin.id] = newWiredCoin.value;
- }
- await tx.depositGroups.put(dg);
- await ctx.updateTransactionMeta(tx);
- },
- );
+ dg.trackingState[newWiredCoin.id] = newWiredCoin.value;
+ }
+ await tx.depositGroups.put(dg);
+ await ctx.updateTransactionMeta(tx);
+ });
}
}
let allWired = true;
- await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- return undefined;
- }
- if (!dg.statusPerCoin) {
- return undefined;
- }
- const oldTxState = computeDepositTransactionStatus(dg);
- const oldStId = dg.operationStatus;
- for (let i = 0; i < dg.statusPerCoin.length; i++) {
- if (dg.statusPerCoin[i] !== DepositElementStatus.Wired) {
- allWired = false;
- break;
- }
- }
- if (allWired) {
- dg.timestampFinished = timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- );
- dg.operationStatus = DepositOperationStatus.Finished;
- await tx.depositGroups.put(dg);
- await ctx.updateTransactionMeta(tx);
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return undefined;
+ }
+ if (!dg.statusPerCoin) {
+ return undefined;
+ }
+ const oldTxState = computeDepositTransactionStatus(dg);
+ const oldStId = dg.operationStatus;
+ for (let i = 0; i < dg.statusPerCoin.length; i++) {
+ if (dg.statusPerCoin[i] !== DepositElementStatus.Wired) {
+ allWired = false;
+ break;
}
- const newTxState = computeDepositTransactionStatus(dg);
- const newStId = dg.operationStatus;
- applyNotifyTransition(tx.notify, ctx.transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.Any,
- oldStId,
- newStId,
- });
- },
- );
+ }
+ if (allWired) {
+ dg.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now());
+ dg.operationStatus = DepositOperationStatus.Finished;
+ await tx.depositGroups.put(dg);
+ await ctx.updateTransactionMeta(tx);
+ }
+ const newTxState = computeDepositTransactionStatus(dg);
+ const newStId = dg.operationStatus;
+ applyNotifyTransition(tx.notify, ctx.transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.Any,
+ oldStId,
+ newStId,
+ });
+ });
if (allWired) {
return TaskRunResult.finished();
} else {
@@ -1764,27 +1692,24 @@ async function submitDepositBatch(
await readSuccessResponseJsonOrThrow(httpResp, codecForBatchDepositSuccess());
- await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- return;
- }
- if (!dg.statusPerCoin) {
- return;
- }
- for (const batchIndex of coinIndexes) {
- const coinStatus = dg.statusPerCoin[batchIndex];
- switch (coinStatus) {
- case DepositElementStatus.DepositPending:
- dg.statusPerCoin[batchIndex] = DepositElementStatus.Tracking;
- await tx.depositGroups.put(dg);
- }
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return;
+ }
+ if (!dg.statusPerCoin) {
+ return;
+ }
+ for (const batchIndex of coinIndexes) {
+ const coinStatus = dg.statusPerCoin[batchIndex];
+ switch (coinStatus) {
+ case DepositElementStatus.DepositPending:
+ dg.statusPerCoin[batchIndex] = DepositElementStatus.Tracking;
+ await tx.depositGroups.put(dg);
}
- await ctx.updateTransactionMeta(tx);
- },
- );
+ }
+ await ctx.updateTransactionMeta(tx);
+ });
return null;
}
@@ -1803,12 +1728,9 @@ async function processDepositGroupPendingDeposit(
): Promise<TaskRunResult> {
logger.info("processing deposit group in pending(deposit)");
const depositGroupId = depositGroup.depositGroupId;
- const contractTermsRec = await wex.db.runReadOnlyTx(
- { storeNames: ["contractTerms"] },
- async (tx) => {
- return tx.contractTerms.get(depositGroup.contractTermsHash);
- },
- );
+ const contractTermsRec = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.contractTerms.get(depositGroup.contractTermsHash);
+ });
if (!contractTermsRec) {
throw Error("contract terms for deposit not found in database");
}
@@ -1826,7 +1748,7 @@ async function processDepositGroupPendingDeposit(
return await doCoinSelection(ctx, depositGroup, contractTerms);
}
- await wex.db.runReadWriteTx({ storeNames: ["depositGroups"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const dg = await tx.depositGroups.get(depositGroup.depositGroupId);
if (!dg) {
logger.warn(`deposit group ${depositGroup.depositGroupId} not found`);
@@ -1892,29 +1814,26 @@ async function processDepositGroupPendingDeposit(
}
}
- await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "transactionsMeta"] },
- async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- return undefined;
- }
- const oldTxState = computeDepositTransactionStatus(dg);
- const oldStId = dg.operationStatus;
- dg.operationStatus = DepositOperationStatus.FinalizingTrack;
- await tx.depositGroups.put(dg);
- await ctx.updateTransactionMeta(tx);
- const newTxState = computeDepositTransactionStatus(dg);
- const newStId = dg.operationStatus;
- applyNotifyTransition(tx.notify, ctx.transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.None,
- oldStId,
- newStId,
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return undefined;
+ }
+ const oldTxState = computeDepositTransactionStatus(dg);
+ const oldStId = dg.operationStatus;
+ dg.operationStatus = DepositOperationStatus.FinalizingTrack;
+ await tx.depositGroups.put(dg);
+ await ctx.updateTransactionMeta(tx);
+ const newTxState = computeDepositTransactionStatus(dg);
+ const newStId = dg.operationStatus;
+ applyNotifyTransition(tx.notify, ctx.transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.None,
+ oldStId,
+ newStId,
+ });
+ });
return TaskRunResult.progress();
}
@@ -1929,12 +1848,9 @@ export async function processDepositGroup(
return TaskRunResult.networkRequired();
}
- const depositGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["depositGroups"] },
- async (tx) => {
- return tx.depositGroups.get(depositGroupId);
- },
- );
+ const depositGroup = await wex.runLegacyWalletDbTx(async (tx) => {
+ return await tx.depositGroups.get(depositGroupId);
+ });
if (!depositGroup) {
logger.warn(`deposit group ${depositGroupId} not found`);
return TaskRunResult.finished();
@@ -2373,53 +2289,36 @@ export async function createDepositGroup(
const ctx = new DepositTransactionContext(wex, depositGroupId);
const transactionId = ctx.transactionId;
- const newTxState = await wex.db.runReadWriteTx(
- {
- storeNames: [
- "coinAvailability",
- "coinHistory",
- "coins",
- "contractTerms",
- "denominations",
- "denominationFamilies",
- "depositGroups",
- "recoupGroups",
- "refreshGroups",
- "refreshSessions",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- if (depositGroup.payCoinSelection) {
- await spendCoins(wex, tx, {
- transactionId,
- coinPubs: depositGroup.payCoinSelection.coinPubs,
- contributions: depositGroup.payCoinSelection.coinContributions.map(
- (x) => Amounts.parseOrThrow(x),
- ),
- refreshReason: RefreshReason.PayDeposit,
- });
- }
- await tx.depositGroups.put(depositGroup);
- await tx.contractTerms.put({
- contractTermsRaw: contractTerms,
- h: contractTermsHash,
- });
- await ctx.updateTransactionMeta(tx);
- const oldTxState = { major: TransactionMajorState.None };
- const oldStId = 0;
- const newTxState = computeDepositTransactionStatus(depositGroup);
- const newStId = depositGroup.operationStatus;
- applyNotifyTransition(tx.notify, transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.Any,
- oldStId,
- newStId,
+ const newTxState = await wex.runLegacyWalletDbTx(async (tx) => {
+ if (depositGroup.payCoinSelection) {
+ await spendCoins(wex, tx, {
+ transactionId,
+ coinPubs: depositGroup.payCoinSelection.coinPubs,
+ contributions: depositGroup.payCoinSelection.coinContributions.map(
+ (x) => Amounts.parseOrThrow(x),
+ ),
+ refreshReason: RefreshReason.PayDeposit,
});
- return newTxState;
- },
- );
+ }
+ await tx.depositGroups.put(depositGroup);
+ await tx.contractTerms.put({
+ contractTermsRaw: contractTerms,
+ h: contractTermsHash,
+ });
+ await ctx.updateTransactionMeta(tx);
+ const oldTxState = { major: TransactionMajorState.None };
+ const oldStId = 0;
+ const newTxState = computeDepositTransactionStatus(depositGroup);
+ const newStId = depositGroup.operationStatus;
+ applyNotifyTransition(tx.notify, transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.Any,
+ oldStId,
+ newStId,
+ });
+ return newTxState;
+ });
wex.taskScheduler.startShepherdTask(ctx.taskId);
@@ -2443,46 +2342,43 @@ async function getCounterpartyEffectiveDepositAmount(
const fees: AmountJson[] = [];
const exchangeSet: Set<string> = new Set();
- await wex.db.runReadOnlyTx(
- { storeNames: ["coins", "denominations", "exchangeDetails", "exchanges"] },
- async (tx) => {
- for (let i = 0; i < pcs.length; i++) {
- const denom = await getDenomInfo(
- wex,
- tx,
- pcs[i].exchangeBaseUrl,
- pcs[i].denomPubHash,
- );
- if (!denom) {
- throw Error("can't find denomination to calculate deposit amount");
- }
- amt.push(Amounts.parseOrThrow(pcs[i].contribution));
- fees.push(Amounts.parseOrThrow(denom.feeDeposit));
- exchangeSet.add(pcs[i].exchangeBaseUrl);
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (let i = 0; i < pcs.length; i++) {
+ const denom = await getDenomInfo(
+ wex,
+ tx,
+ pcs[i].exchangeBaseUrl,
+ pcs[i].denomPubHash,
+ );
+ if (!denom) {
+ throw Error("can't find denomination to calculate deposit amount");
}
+ amt.push(Amounts.parseOrThrow(pcs[i].contribution));
+ fees.push(Amounts.parseOrThrow(denom.feeDeposit));
+ exchangeSet.add(pcs[i].exchangeBaseUrl);
+ }
- for (const exchangeUrl of exchangeSet.values()) {
- const exchangeDetails = await getExchangeDetailsInTx(tx, exchangeUrl);
- if (!exchangeDetails) {
- continue;
- }
+ for (const exchangeUrl of exchangeSet.values()) {
+ const exchangeDetails = await getExchangeDetailsInTx(tx, exchangeUrl);
+ if (!exchangeDetails) {
+ continue;
+ }
- // FIXME/NOTE: the line below _likely_ throws exception
- // about "find method not found on undefined" when the wireType
- // is not supported by the Exchange.
- const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => {
- return AbsoluteTime.isBetween(
- AbsoluteTime.now(),
- AbsoluteTime.fromProtocolTimestamp(x.startStamp),
- AbsoluteTime.fromProtocolTimestamp(x.endStamp),
- );
- })?.wireFee;
- if (fee) {
- fees.push(Amounts.parseOrThrow(fee));
- }
+ // FIXME/NOTE: the line below _likely_ throws exception
+ // about "find method not found on undefined" when the wireType
+ // is not supported by the Exchange.
+ const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => {
+ return AbsoluteTime.isBetween(
+ AbsoluteTime.now(),
+ AbsoluteTime.fromProtocolTimestamp(x.startStamp),
+ AbsoluteTime.fromProtocolTimestamp(x.endStamp),
+ );
+ })?.wireFee;
+ if (fee) {
+ fees.push(Amounts.parseOrThrow(fee));
}
- },
- );
+ }
+ });
return Amounts.sub(Amounts.sum(amt).amount, Amounts.sum(fees).amount).amount;
}
@@ -2501,59 +2397,41 @@ async function getTotalFeesForDepositAmount(
const refreshFee: AmountJson[] = [];
const exchangeSet: Set<string> = new Set();
- await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "coins",
- "denominations",
- "exchanges",
- "exchangeDetails",
- "denominationFamilies",
- ],
- },
- async (tx) => {
- for (let i = 0; i < pcs.length; i++) {
- const denom = await getDenomInfo(
- wex,
- tx,
- pcs[i].exchangeBaseUrl,
- pcs[i].denomPubHash,
- );
- if (!denom) {
- throw Error("can't find denomination to calculate deposit amount");
- }
- coinFee.push(Amounts.parseOrThrow(denom.feeDeposit));
- exchangeSet.add(pcs[i].exchangeBaseUrl);
- const amountLeft = Amounts.sub(denom.value, pcs[i].contribution).amount;
- const refreshCost = await getTotalRefreshCost(
- wex,
- tx,
- denom,
- amountLeft,
- );
- refreshFee.push(refreshCost);
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (let i = 0; i < pcs.length; i++) {
+ const denom = await getDenomInfo(
+ wex,
+ tx,
+ pcs[i].exchangeBaseUrl,
+ pcs[i].denomPubHash,
+ );
+ if (!denom) {
+ throw Error("can't find denomination to calculate deposit amount");
}
+ coinFee.push(Amounts.parseOrThrow(denom.feeDeposit));
+ exchangeSet.add(pcs[i].exchangeBaseUrl);
+ const amountLeft = Amounts.sub(denom.value, pcs[i].contribution).amount;
+ const refreshCost = await getTotalRefreshCost(wex, tx, denom, amountLeft);
+ refreshFee.push(refreshCost);
+ }
- for (const exchangeUrl of exchangeSet.values()) {
- const exchangeDetails = await getExchangeDetailsInTx(tx, exchangeUrl);
- if (!exchangeDetails) {
- continue;
- }
- const fee = exchangeDetails.wireInfo.feesForType[wireType]?.find(
- (x) => {
- return AbsoluteTime.isBetween(
- AbsoluteTime.now(),
- AbsoluteTime.fromProtocolTimestamp(x.startStamp),
- AbsoluteTime.fromProtocolTimestamp(x.endStamp),
- );
- },
- )?.wireFee;
- if (fee) {
- wireFee.push(Amounts.parseOrThrow(fee));
- }
+ for (const exchangeUrl of exchangeSet.values()) {
+ const exchangeDetails = await getExchangeDetailsInTx(tx, exchangeUrl);
+ if (!exchangeDetails) {
+ continue;
}
- },
- );
+ const fee = exchangeDetails.wireInfo.feesForType[wireType]?.find((x) => {
+ return AbsoluteTime.isBetween(
+ AbsoluteTime.now(),
+ AbsoluteTime.fromProtocolTimestamp(x.startStamp),
+ AbsoluteTime.fromProtocolTimestamp(x.endStamp),
+ );
+ })?.wireFee;
+ if (fee) {
+ wireFee.push(Amounts.parseOrThrow(fee));
+ }
+ }
+ });
return {
coin: Amounts.stringify(Amounts.sumOrZero(total.currency, coinFee).amount),
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts
@@ -112,29 +112,26 @@ export async function applyDevExperiment(
}
case "insert-pending-refresh": {
const refreshGroupId = encodeCrock(getRandomBytes(32));
- await wex.db.runReadWriteTx(
- { storeNames: ["refreshGroups", "transactionsMeta"] },
- async (tx) => {
- const newRg: RefreshGroupRecord = {
- currency: "TESTKUDOS",
- expectedOutputPerCoin: [],
- inputPerCoin: [],
- oldCoinPubs: [],
- operationStatus: RefreshOperationStatus.Pending,
- reason: RefreshReason.Manual,
- refreshGroupId,
- statusPerCoin: [],
- timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- timestampFinished: undefined,
- originatingTransactionId: undefined,
- infoPerExchange: {},
- refundRequests: {},
- };
- await tx.refreshGroups.put(newRg);
- const ctx = new RefreshTransactionContext(wex, refreshGroupId);
- await ctx.updateTransactionMeta(tx);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const newRg: RefreshGroupRecord = {
+ currency: "TESTKUDOS",
+ expectedOutputPerCoin: [],
+ inputPerCoin: [],
+ oldCoinPubs: [],
+ operationStatus: RefreshOperationStatus.Pending,
+ reason: RefreshReason.Manual,
+ refreshGroupId,
+ statusPerCoin: [],
+ timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+ timestampFinished: undefined,
+ originatingTransactionId: undefined,
+ infoPerExchange: {},
+ refundRequests: {},
+ };
+ await tx.refreshGroups.put(newRg);
+ const ctx = new RefreshTransactionContext(wex, refreshGroupId);
+ await ctx.updateTransactionMeta(tx);
+ });
wex.taskScheduler.startShepherdTask(
constructTaskIdentifier({
tag: PendingTaskType.Refresh,
@@ -144,31 +141,28 @@ export async function applyDevExperiment(
return;
}
case "insert-denom-loss": {
- await wex.db.runReadWriteTx(
- { storeNames: ["denomLossEvents", "transactionsMeta"] },
- async (tx) => {
- const eventId = encodeCrock(getRandomBytes(32));
- const newRg: DenomLossEventRecord = {
- amount: "TESTKUDOS:42",
- currency: "TESTKUDOS",
- exchangeBaseUrl: "https://exchange.test.taler.net/",
- denomLossEventId: eventId,
- denomPubHashes: [
- encodeCrock(getRandomBytes(64)),
- encodeCrock(getRandomBytes(64)),
- ],
- eventType: DenomLossEventType.DenomExpired,
- status: DenomLossStatus.Done,
- timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- };
- await tx.denomLossEvents.put(newRg);
- const ctx = new DenomLossTransactionContext(
- wex,
- newRg.denomLossEventId,
- );
- await ctx.updateTransactionMeta(tx);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const eventId = encodeCrock(getRandomBytes(32));
+ const newRg: DenomLossEventRecord = {
+ amount: "TESTKUDOS:42",
+ currency: "TESTKUDOS",
+ exchangeBaseUrl: "https://exchange.test.taler.net/",
+ denomLossEventId: eventId,
+ denomPubHashes: [
+ encodeCrock(getRandomBytes(64)),
+ encodeCrock(getRandomBytes(64)),
+ ],
+ eventType: DenomLossEventType.DenomExpired,
+ status: DenomLossStatus.Done,
+ timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+ };
+ await tx.denomLossEvents.put(newRg);
+ const ctx = new DenomLossTransactionContext(
+ wex,
+ newRg.denomLossEventId,
+ );
+ await ctx.updateTransactionMeta(tx);
+ });
return;
}
case "merchant-deposit-insufficient": {
diff --git a/packages/taler-wallet-core/src/donau.ts b/packages/taler-wallet-core/src/donau.ts
@@ -95,26 +95,21 @@ async function submitDonationReceipts(
wex: WalletExecutionContext,
donauBaseUrl?: string,
): Promise<void> {
- const receipts = await wex.db.runReadOnlyTx(
- {
- storeNames: ["donationReceipts"],
- },
- async (tx) => {
- let receipts;
- if (donauBaseUrl) {
- receipts =
- await tx.donationReceipts.indexes.byStatusAndDonauBaseUrl.getAll([
- DonationReceiptStatus.Pending,
- donauBaseUrl,
- ]);
- } else {
- receipts = await tx.donationReceipts.indexes.byStatus.getAll(
+ const receipts = await wex.runLegacyWalletDbTx(async (tx) => {
+ let receipts;
+ if (donauBaseUrl) {
+ receipts =
+ await tx.donationReceipts.indexes.byStatusAndDonauBaseUrl.getAll([
DonationReceiptStatus.Pending,
- );
- }
- return receipts;
- },
- );
+ donauBaseUrl,
+ ]);
+ } else {
+ receipts = await tx.donationReceipts.indexes.byStatus.getAll(
+ DonationReceiptStatus.Pending,
+ );
+ }
+ return receipts;
+ });
const groups = groupDonauReceipts(receipts);
for (const group of groups) {
@@ -182,26 +177,21 @@ async function fetchDonauStatements(
wex: WalletExecutionContext,
donauBaseUrl?: string,
): Promise<DonauStatementItem[]> {
- const receipts = await wex.db.runReadOnlyTx(
- {
- storeNames: ["donationReceipts"],
- },
- async (tx) => {
- let receipts;
- if (donauBaseUrl) {
- receipts =
- await tx.donationReceipts.indexes.byStatusAndDonauBaseUrl.getAll([
- DonationReceiptStatus.DoneSubmitted,
- donauBaseUrl,
- ]);
- } else {
- receipts = await tx.donationReceipts.indexes.byStatus.getAll(
+ const receipts = await wex.runLegacyWalletDbTx(async (tx) => {
+ let receipts;
+ if (donauBaseUrl) {
+ receipts =
+ await tx.donationReceipts.indexes.byStatusAndDonauBaseUrl.getAll([
DonationReceiptStatus.DoneSubmitted,
- );
- }
- return receipts;
- },
- );
+ donauBaseUrl,
+ ]);
+ } else {
+ receipts = await tx.donationReceipts.indexes.byStatus.getAll(
+ DonationReceiptStatus.DoneSubmitted,
+ );
+ }
+ return receipts;
+ });
const statements: DonauStatementItem[] = [];
const groups = groupDonauReceipts(receipts);
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
@@ -621,29 +621,26 @@ export async function acceptExchangeTermsOfService(
wex: WalletExecutionContext,
exchangeBaseUrl: string,
): Promise<void> {
- const notif = await wex.db.runReadWriteTx(
- { storeNames: ["exchangeDetails", "exchanges"] },
- async (tx) => {
- const exch = await tx.exchanges.get(exchangeBaseUrl);
- if (exch && exch.tosCurrentEtag) {
- const oldExchangeState = getExchangeState(exch);
- exch.tosAcceptedEtag = exch.tosCurrentEtag;
- exch.tosAcceptedTimestamp = timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- );
- await tx.exchanges.put(exch);
- const newExchangeState = getExchangeState(exch);
- wex.ws.exchangeCache.clear();
- return {
- type: NotificationType.ExchangeStateTransition,
- exchangeBaseUrl,
- newExchangeState: newExchangeState,
- oldExchangeState: oldExchangeState,
- } satisfies WalletNotification;
- }
- return undefined;
- },
- );
+ const notif = await wex.runLegacyWalletDbTx(async (tx) => {
+ const exch = await tx.exchanges.get(exchangeBaseUrl);
+ if (exch && exch.tosCurrentEtag) {
+ const oldExchangeState = getExchangeState(exch);
+ exch.tosAcceptedEtag = exch.tosCurrentEtag;
+ exch.tosAcceptedTimestamp = timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ );
+ await tx.exchanges.put(exch);
+ const newExchangeState = getExchangeState(exch);
+ wex.ws.exchangeCache.clear();
+ return {
+ type: NotificationType.ExchangeStateTransition,
+ exchangeBaseUrl,
+ newExchangeState: newExchangeState,
+ oldExchangeState: oldExchangeState,
+ } satisfies WalletNotification;
+ }
+ return undefined;
+ });
if (notif) {
wex.ws.notify(notif);
}
@@ -656,30 +653,24 @@ export async function forgetExchangeTermsOfService(
wex: WalletExecutionContext,
exchangeBaseUrl: string,
): Promise<void> {
- const notif = await wex.db.runReadWriteTx(
- { storeNames: ["exchangeDetails", "exchanges"] },
- async (tx) => {
- const exch = await tx.exchanges.get(exchangeBaseUrl);
- if (exch) {
- const oldExchangeState = getExchangeState(exch);
- exch.tosAcceptedEtag = undefined;
- exch.tosAcceptedTimestamp = undefined;
- await tx.exchanges.put(exch);
- const newExchangeState = getExchangeState(exch);
- wex.ws.exchangeCache.clear();
- return {
- type: NotificationType.ExchangeStateTransition,
- exchangeBaseUrl,
- newExchangeState: newExchangeState,
- oldExchangeState: oldExchangeState,
- } satisfies WalletNotification;
- }
- return undefined;
- },
- );
- if (notif) {
- wex.ws.notify(notif);
- }
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const exch = await tx.exchanges.get(exchangeBaseUrl);
+ if (!exch) {
+ return;
+ }
+ const oldExchangeState = getExchangeState(exch);
+ exch.tosAcceptedEtag = undefined;
+ exch.tosAcceptedTimestamp = undefined;
+ await tx.exchanges.put(exch);
+ const newExchangeState = getExchangeState(exch);
+ wex.ws.exchangeCache.clear();
+ tx.notify({
+ type: NotificationType.ExchangeStateTransition,
+ exchangeBaseUrl,
+ newExchangeState: newExchangeState,
+ oldExchangeState: oldExchangeState,
+ });
+ });
}
/**
@@ -1117,102 +1108,93 @@ export async function startUpdateExchangeEntry(
}`,
);
- await wex.db.runReadWriteTx(
- { storeNames: ["exchanges", "exchangeDetails"] },
- async (tx) => {
- wex.ws.exchangeCache.clear();
- return provideExchangeRecordInTx(wex.ws, tx, exchangeBaseUrl);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ wex.ws.exchangeCache.clear();
+ return provideExchangeRecordInTx(wex.ws, tx, exchangeBaseUrl);
+ });
- const res = await wex.db.runReadWriteTx(
- { storeNames: ["exchanges", "operationRetries", "denominations"] },
- async (tx) => {
- const r = await tx.exchanges.get(exchangeBaseUrl);
- if (!r) {
- throw Error("exchange not found");
- }
+ const res = await wex.runLegacyWalletDbTx(async (tx) => {
+ const r = await tx.exchanges.get(exchangeBaseUrl);
+ if (!r) {
+ throw Error("exchange not found");
+ }
- const oldExchangeState = getExchangeState(r);
- if (options.forceUnavailable) {
- switch (r.updateStatus) {
- case ExchangeEntryDbUpdateStatus.UnavailableUpdate:
- return undefined;
- default:
- r.lastUpdate = undefined;
- r.nextUpdateStamp = timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- );
- r.cachebreakNextUpdate = options.forceUpdate;
- r.updateStatus = ExchangeEntryDbUpdateStatus.UnavailableUpdate;
+ const oldExchangeState = getExchangeState(r);
+ if (options.forceUnavailable) {
+ switch (r.updateStatus) {
+ case ExchangeEntryDbUpdateStatus.UnavailableUpdate:
+ return undefined;
+ default:
+ r.lastUpdate = undefined;
+ r.nextUpdateStamp = timestampPreciseToDb(TalerPreciseTimestamp.now());
+ r.cachebreakNextUpdate = options.forceUpdate;
+ r.updateStatus = ExchangeEntryDbUpdateStatus.UnavailableUpdate;
+ }
+ } else {
+ switch (r.updateStatus) {
+ case ExchangeEntryDbUpdateStatus.UnavailableUpdate:
+ r.cachebreakNextUpdate = options.forceUpdate;
+ break;
+ case ExchangeEntryDbUpdateStatus.Suspended:
+ r.cachebreakNextUpdate = options.forceUpdate;
+ break;
+ case ExchangeEntryDbUpdateStatus.ReadyUpdate: {
+ const outdated = await checkExchangeEntryOutdated(
+ wex,
+ tx,
+ exchangeBaseUrl,
+ );
+ if (outdated) {
+ r.updateStatus = ExchangeEntryDbUpdateStatus.OutdatedUpdate;
+ } else {
+ r.updateStatus = ExchangeEntryDbUpdateStatus.ReadyUpdate;
+ }
+ r.cachebreakNextUpdate = options.forceUpdate;
+ break;
}
- } else {
- switch (r.updateStatus) {
- case ExchangeEntryDbUpdateStatus.UnavailableUpdate:
- r.cachebreakNextUpdate = options.forceUpdate;
- break;
- case ExchangeEntryDbUpdateStatus.Suspended:
- r.cachebreakNextUpdate = options.forceUpdate;
- break;
- case ExchangeEntryDbUpdateStatus.ReadyUpdate: {
- const outdated = await checkExchangeEntryOutdated(
- wex,
- tx,
- exchangeBaseUrl,
- );
- if (outdated) {
- r.updateStatus = ExchangeEntryDbUpdateStatus.OutdatedUpdate;
- } else {
- r.updateStatus = ExchangeEntryDbUpdateStatus.ReadyUpdate;
- }
- r.cachebreakNextUpdate = options.forceUpdate;
- break;
+ case ExchangeEntryDbUpdateStatus.OutdatedUpdate:
+ r.cachebreakNextUpdate = options.forceUpdate;
+ break;
+ case ExchangeEntryDbUpdateStatus.Ready: {
+ const nextUpdateTimestamp = AbsoluteTime.fromPreciseTimestamp(
+ timestampPreciseFromDb(r.nextUpdateStamp),
+ );
+ // Only update if entry is outdated or update is forced.
+ if (
+ !(
+ options.forceUpdate || AbsoluteTime.isExpired(nextUpdateTimestamp)
+ )
+ ) {
+ return undefined;
}
- case ExchangeEntryDbUpdateStatus.OutdatedUpdate:
- r.cachebreakNextUpdate = options.forceUpdate;
- break;
- case ExchangeEntryDbUpdateStatus.Ready: {
- const nextUpdateTimestamp = AbsoluteTime.fromPreciseTimestamp(
- timestampPreciseFromDb(r.nextUpdateStamp),
- );
- // Only update if entry is outdated or update is forced.
- if (
- !(
- options.forceUpdate ||
- AbsoluteTime.isExpired(nextUpdateTimestamp)
- )
- ) {
- return undefined;
- }
- const outdated = await checkExchangeEntryOutdated(
- wex,
- tx,
- exchangeBaseUrl,
- );
- if (outdated) {
- r.updateStatus = ExchangeEntryDbUpdateStatus.OutdatedUpdate;
- } else {
- r.updateStatus = ExchangeEntryDbUpdateStatus.ReadyUpdate;
- }
- r.cachebreakNextUpdate = options.forceUpdate;
- break;
+ const outdated = await checkExchangeEntryOutdated(
+ wex,
+ tx,
+ exchangeBaseUrl,
+ );
+ if (outdated) {
+ r.updateStatus = ExchangeEntryDbUpdateStatus.OutdatedUpdate;
+ } else {
+ r.updateStatus = ExchangeEntryDbUpdateStatus.ReadyUpdate;
}
- case ExchangeEntryDbUpdateStatus.Initial:
- r.cachebreakNextUpdate = options.forceUpdate;
- r.updateStatus = ExchangeEntryDbUpdateStatus.InitialUpdate;
- break;
- case ExchangeEntryDbUpdateStatus.InitialUpdate:
- r.cachebreakNextUpdate = options.forceUpdate;
- break;
+ r.cachebreakNextUpdate = options.forceUpdate;
+ break;
}
+ case ExchangeEntryDbUpdateStatus.Initial:
+ r.cachebreakNextUpdate = options.forceUpdate;
+ r.updateStatus = ExchangeEntryDbUpdateStatus.InitialUpdate;
+ break;
+ case ExchangeEntryDbUpdateStatus.InitialUpdate:
+ r.cachebreakNextUpdate = options.forceUpdate;
+ break;
}
- wex.ws.exchangeCache.clear();
- await tx.exchanges.put(r);
- const newExchangeState = getExchangeState(r);
- const taskId = TaskIdentifiers.forExchangeUpdate(r);
- return { oldExchangeState, newExchangeState, taskId };
- },
- );
+ }
+ wex.ws.exchangeCache.clear();
+ await tx.exchanges.put(r);
+ const newExchangeState = getExchangeState(r);
+ const taskId = TaskIdentifiers.forExchangeUpdate(r);
+ return { oldExchangeState, newExchangeState, taskId };
+ });
if (!res) {
// Exchange entry is already good.
return;
@@ -1285,16 +1267,13 @@ export async function fetchFreshExchange(
wex.ws.exchangeCache.clear();
}
- await wex.db.runReadOnlyTx(
- { storeNames: ["exchangeBaseUrlFixups"] },
- async (tx) => {
- const rec = await tx.exchangeBaseUrlFixups.get(baseUrl);
- if (rec) {
- logger.warn(`using replacement ${rec.replacement} for ${baseUrl}`);
- baseUrl = rec.replacement;
- }
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const rec = await tx.exchangeBaseUrlFixups.get(baseUrl);
+ if (rec) {
+ logger.warn(`using replacement ${rec.replacement} for ${baseUrl}`);
+ baseUrl = rec.replacement;
+ }
+ });
// FIXME: We should only transition here when
// the update is forced or necessary!
@@ -1346,33 +1325,19 @@ async function waitReadyExchange(
},
async checkState(): Promise<boolean> {
const { exchange, exchangeDetails, retryInfo, scopeInfo } =
- await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "exchanges",
- "exchangeDetails",
- "operationRetries",
- "globalCurrencyAuditors",
- "globalCurrencyExchanges",
- ],
- },
- async (tx) => {
- const exchange = await tx.exchanges.get(exchangeBaseUrl);
- const exchangeDetails = await getExchangeRecordsInternal(
- tx,
- exchangeBaseUrl,
- );
- const retryInfo = await tx.operationRetries.get(operationId);
- let scopeInfo: ScopeInfo | undefined = undefined;
- if (exchange && exchangeDetails) {
- scopeInfo = await internalGetExchangeScopeInfo(
- tx,
- exchangeDetails,
- );
- }
- return { exchange, exchangeDetails, retryInfo, scopeInfo };
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchange = await tx.exchanges.get(exchangeBaseUrl);
+ const exchangeDetails = await getExchangeRecordsInternal(
+ tx,
+ exchangeBaseUrl,
+ );
+ const retryInfo = await tx.operationRetries.get(operationId);
+ let scopeInfo: ScopeInfo | undefined = undefined;
+ if (exchange && exchangeDetails) {
+ scopeInfo = await internalGetExchangeScopeInfo(tx, exchangeDetails);
+ }
+ return { exchange, exchangeDetails, retryInfo, scopeInfo };
+ });
if (!exchange) {
throw Error("exchange entry does not exist anymore");
@@ -1535,44 +1500,39 @@ async function handleExchageUpdateIncompatible(
exchangeBaseUrl: string,
exchangeProtocolVersion: string,
): Promise<TaskRunResult> {
- const updated = await wex.db.runReadWriteTx(
- {
- storeNames: ["exchanges"],
- },
- async (tx) => {
- const r = await tx.exchanges.get(exchangeBaseUrl);
- if (!r) {
- logger.warn(`exchange ${exchangeBaseUrl} no longer present`);
+ const updated = await wex.runLegacyWalletDbTx(async (tx) => {
+ const r = await tx.exchanges.get(exchangeBaseUrl);
+ if (!r) {
+ logger.warn(`exchange ${exchangeBaseUrl} no longer present`);
+ return undefined;
+ }
+ switch (r.updateStatus) {
+ case ExchangeEntryDbUpdateStatus.InitialUpdate:
+ case ExchangeEntryDbUpdateStatus.OutdatedUpdate:
+ case ExchangeEntryDbUpdateStatus.ReadyUpdate:
+ case ExchangeEntryDbUpdateStatus.UnavailableUpdate:
+ break;
+ default:
return undefined;
- }
- switch (r.updateStatus) {
- case ExchangeEntryDbUpdateStatus.InitialUpdate:
- case ExchangeEntryDbUpdateStatus.OutdatedUpdate:
- case ExchangeEntryDbUpdateStatus.ReadyUpdate:
- case ExchangeEntryDbUpdateStatus.UnavailableUpdate:
- break;
- default:
- return undefined;
- }
- const oldExchangeState = getExchangeState(r);
- r.updateRetryCounter = (r.updateRetryCounter ?? 0) + 1;
- r.nextUpdateStamp = computeDbBackoff(r.updateRetryCounter);
- r.updateStatus = ExchangeEntryDbUpdateStatus.UnavailableUpdate;
- r.unavailableReason = makeTalerErrorDetail(
- TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
- {
- exchangeProtocolVersion: exchangeProtocolVersion,
- walletProtocolVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
- },
- );
- const newExchangeState = getExchangeState(r);
- await tx.exchanges.put(r);
- return {
- oldExchangeState,
- newExchangeState,
- };
- },
- );
+ }
+ const oldExchangeState = getExchangeState(r);
+ r.updateRetryCounter = (r.updateRetryCounter ?? 0) + 1;
+ r.nextUpdateStamp = computeDbBackoff(r.updateRetryCounter);
+ r.updateStatus = ExchangeEntryDbUpdateStatus.UnavailableUpdate;
+ r.unavailableReason = makeTalerErrorDetail(
+ TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
+ {
+ exchangeProtocolVersion: exchangeProtocolVersion,
+ walletProtocolVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
+ },
+ );
+ const newExchangeState = getExchangeState(r);
+ await tx.exchanges.put(r);
+ return {
+ oldExchangeState,
+ newExchangeState,
+ };
+ });
if (updated) {
wex.ws.notify({
type: NotificationType.ExchangeStateTransition,
@@ -1602,12 +1562,9 @@ export async function updateExchangeFromUrlHandler(
logger.trace(`updating exchange info for ${exchangeBaseUrl}`);
- const oldExchangeRec = await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges"] },
- async (tx) => {
- return tx.exchanges.get(exchangeBaseUrl);
- },
- );
+ const oldExchangeRec = await wex.runLegacyWalletDbTx(async (tx) => {
+ return await tx.exchanges.get(exchangeBaseUrl);
+ });
if (!oldExchangeRec) {
logger.info(`not updating exchange ${exchangeBaseUrl}, no record in DB`);
@@ -2206,78 +2163,60 @@ async function doExchangeAutoRefresh(
Duration.fromSpec({ days: 1 }),
);
- await wex.db.runReadWriteTx(
- {
- storeNames: [
- "coinAvailability",
- "coinHistory",
- "coins",
- "denominations",
- "denominationFamilies",
- "exchanges",
- "refreshGroups",
- "refreshSessions",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- const exchange = await tx.exchanges.get(exchangeBaseUrl);
- if (!exchange || !exchange.detailsPointer) {
- return;
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchange = await tx.exchanges.get(exchangeBaseUrl);
+ if (!exchange || !exchange.detailsPointer) {
+ return;
+ }
+ const coins = await tx.coins.indexes.byBaseUrl
+ .iter(exchangeBaseUrl)
+ .toArray();
+ const refreshCoins: CoinRefreshRequest[] = [];
+ for (const coin of coins) {
+ if (coin.status !== CoinStatus.Fresh) {
+ continue;
}
- const coins = await tx.coins.indexes.byBaseUrl
- .iter(exchangeBaseUrl)
- .toArray();
- const refreshCoins: CoinRefreshRequest[] = [];
- for (const coin of coins) {
- if (coin.status !== CoinStatus.Fresh) {
- continue;
- }
- const denom = await tx.denominations.get([
- exchangeBaseUrl,
- coin.denomPubHash,
- ]);
- if (!denom) {
- logger.warn("denomination not in database");
- continue;
- }
- const executeThreshold = getAutoRefreshExecuteThresholdForDenom(denom);
- if (AbsoluteTime.isExpired(executeThreshold)) {
- refreshCoins.push({
- coinPub: coin.coinPub,
- amount: denom.value,
- });
- } else {
- const checkThreshold = getAutoRefreshCheckThreshold(denom);
- minCheckThreshold = AbsoluteTime.min(
- minCheckThreshold,
- checkThreshold,
- );
- }
+ const denom = await tx.denominations.get([
+ exchangeBaseUrl,
+ coin.denomPubHash,
+ ]);
+ if (!denom) {
+ logger.warn("denomination not in database");
+ continue;
}
- if (refreshCoins.length > 0) {
- const res = await createRefreshGroup(
- wex,
- tx,
- exchange.detailsPointer?.currency,
- refreshCoins,
- RefreshReason.Scheduled,
- undefined,
- );
- logger.trace(
- `created refresh group for auto-refresh (${res.refreshGroupId})`,
- );
+ const executeThreshold = getAutoRefreshExecuteThresholdForDenom(denom);
+ if (AbsoluteTime.isExpired(executeThreshold)) {
+ refreshCoins.push({
+ coinPub: coin.coinPub,
+ amount: denom.value,
+ });
+ } else {
+ const checkThreshold = getAutoRefreshCheckThreshold(denom);
+ minCheckThreshold = AbsoluteTime.min(minCheckThreshold, checkThreshold);
}
- logger.trace(
- `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`,
+ }
+ if (refreshCoins.length > 0) {
+ const res = await createRefreshGroup(
+ wex,
+ tx,
+ exchange.detailsPointer?.currency,
+ refreshCoins,
+ RefreshReason.Scheduled,
+ undefined,
);
- exchange.nextRefreshCheckStamp = timestampPreciseToDb(
- AbsoluteTime.toPreciseTimestamp(minCheckThreshold),
+ logger.trace(
+ `created refresh group for auto-refresh (${res.refreshGroupId})`,
);
- wex.ws.exchangeCache.clear();
- await tx.exchanges.put(exchange);
- },
- );
+ }
+ logger.trace(
+ `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`,
+ );
+ exchange.nextRefreshCheckStamp = timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(minCheckThreshold),
+ );
+ wex.ws.exchangeCache.clear();
+ await tx.exchanges.put(exchange);
+ });
}
export async function processTaskExchangeAutoRefresh(
@@ -2286,12 +2225,9 @@ export async function processTaskExchangeAutoRefresh(
): Promise<TaskRunResult> {
logger.trace(`doing auto-refresh check for '${exchangeBaseUrl}'`);
- const oldExchangeRec = await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges"] },
- async (tx) => {
- return tx.exchanges.get(exchangeBaseUrl);
- },
- );
+ const oldExchangeRec = await wex.runLegacyWalletDbTx(async (tx) => {
+ return await tx.exchanges.get(exchangeBaseUrl);
+ });
if (!oldExchangeRec) {
return TaskRunResult.finished();
@@ -2591,26 +2527,23 @@ export class DenomLossTransactionContext implements TransactionContext {
}
async deleteTransaction(): Promise<void> {
- await this.wex.db.runReadWriteTx(
- { storeNames: ["denomLossEvents"] },
- async (tx) => {
- const rec = await tx.denomLossEvents.get(this.denomLossEventId);
- if (!rec) {
- return;
- }
- const oldTxState = computeDenomLossTransactionStatus(rec);
- await tx.denomLossEvents.delete(this.denomLossEventId);
- applyNotifyTransition(tx.notify, this.transactionId, {
- oldTxState,
- newTxState: {
- major: TransactionMajorState.Deleted,
- },
- balanceEffect: BalanceEffect.Any,
- newStId: -1,
- oldStId: rec.status,
- });
- },
- );
+ await this.wex.runLegacyWalletDbTx(async (tx) => {
+ const rec = await tx.denomLossEvents.get(this.denomLossEventId);
+ if (!rec) {
+ return;
+ }
+ const oldTxState = computeDenomLossTransactionStatus(rec);
+ await tx.denomLossEvents.delete(this.denomLossEventId);
+ applyNotifyTransition(tx.notify, this.transactionId, {
+ oldTxState,
+ newTxState: {
+ major: TransactionMajorState.Deleted,
+ },
+ balanceEffect: BalanceEffect.Any,
+ newStId: -1,
+ oldStId: rec.status,
+ });
+ });
}
async lookupFullTransaction(
@@ -2727,12 +2660,9 @@ export async function getExchangePaytoUri(
): Promise<string> {
// We do the update here, since the exchange might not even exist
// yet in our database.
- const details = await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges", "exchangeDetails"] },
- async (tx) => {
- return getExchangeRecordsInternal(tx, exchangeBaseUrl);
- },
- );
+ const details = await wex.runLegacyWalletDbTx(async (tx) => {
+ return getExchangeRecordsInternal(tx, exchangeBaseUrl);
+ });
const accounts = details?.wireInfo.accounts ?? [];
for (const account of accounts) {
const res = parsePaytoUri(account.payto_uri);
@@ -2783,7 +2713,7 @@ export async function getExchangeTos(
acceptLanguage,
);
- await wex.db.runReadWriteTx({ storeNames: ["exchanges"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const updateExchangeEntry = await tx.exchanges.get(exchangeBaseUrl);
if (updateExchangeEntry) {
updateExchangeEntry.tosCurrentEtag = tosDownload.tosEtag;
@@ -2843,63 +2773,51 @@ export async function listExchanges(
req: ListExchangesRequest,
): Promise<ExchangesListResponse> {
const exchanges: ExchangeListItem[] = [];
- await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "exchanges",
- "reserves",
- "operationRetries",
- "exchangeDetails",
- "globalCurrencyAuditors",
- "globalCurrencyExchanges",
- ],
- },
- async (tx) => {
- const exchangeRecords = await tx.exchanges.iter().toArray();
- for (const exchangeRec of exchangeRecords) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.ExchangeUpdate,
- exchangeBaseUrl: exchangeRec.baseUrl,
- });
- const exchangeDetails = await getExchangeRecordsInternal(
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchangeRecords = await tx.exchanges.iter().toArray();
+ for (const exchangeRec of exchangeRecords) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.ExchangeUpdate,
+ exchangeBaseUrl: exchangeRec.baseUrl,
+ });
+ const exchangeDetails = await getExchangeRecordsInternal(
+ tx,
+ exchangeRec.baseUrl,
+ );
+ const opRetryRecord = await tx.operationRetries.get(taskId);
+ let reserveRec: ReserveRecord | undefined = undefined;
+ if (exchangeRec.currentMergeReserveRowId != null) {
+ reserveRec = await tx.reserves.get(
+ exchangeRec.currentMergeReserveRowId,
+ );
+ checkDbInvariant(!!reserveRec, "reserve record not found");
+ }
+ if (req.filterByScope) {
+ const inScope = await checkExchangeInScopeTx(
tx,
exchangeRec.baseUrl,
+ req.filterByScope,
);
- const opRetryRecord = await tx.operationRetries.get(taskId);
- let reserveRec: ReserveRecord | undefined = undefined;
- if (exchangeRec.currentMergeReserveRowId != null) {
- reserveRec = await tx.reserves.get(
- exchangeRec.currentMergeReserveRowId,
- );
- checkDbInvariant(!!reserveRec, "reserve record not found");
- }
- if (req.filterByScope) {
- const inScope = await checkExchangeInScopeTx(
- tx,
- exchangeRec.baseUrl,
- req.filterByScope,
- );
- if (!inScope) {
- continue;
- }
+ if (!inScope) {
+ continue;
}
- const li = await makeExchangeListItem(
- wex,
- tx,
- exchangeRec,
- exchangeDetails,
- reserveRec,
- opRetryRecord?.lastError,
- );
- if (req.filterByExchangeEntryStatus) {
- if (req.filterByExchangeEntryStatus !== li.exchangeEntryStatus) {
- continue;
- }
+ }
+ const li = await makeExchangeListItem(
+ wex,
+ tx,
+ exchangeRec,
+ exchangeDetails,
+ reserveRec,
+ opRetryRecord?.lastError,
+ );
+ if (req.filterByExchangeEntryStatus) {
+ if (req.filterByExchangeEntryStatus !== li.exchangeEntryStatus) {
+ continue;
}
- exchanges.push(li);
}
- },
- );
+ exchanges.push(li);
+ }
+ });
return { exchanges };
}
@@ -2950,43 +2868,40 @@ export async function getExchangeDetailedInfo(
wex: WalletExecutionContext,
exchangeBaseurl: string,
): Promise<ExchangeDetailedResponse> {
- const exchange = await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges", "exchangeDetails", "denominations"] },
- async (tx) => {
- const ex = await tx.exchanges.get(exchangeBaseurl);
- const dp = ex?.detailsPointer;
- if (!dp) {
- return;
- }
- const { currency } = dp;
- const exchangeDetails = await getExchangeRecordsInternal(tx, ex.baseUrl);
- if (!exchangeDetails) {
- return;
- }
- const denominationRecords =
- await tx.denominations.indexes.byExchangeBaseUrl.getAll(ex.baseUrl);
+ const exchange = await wex.runLegacyWalletDbTx(async (tx) => {
+ const ex = await tx.exchanges.get(exchangeBaseurl);
+ const dp = ex?.detailsPointer;
+ if (!dp) {
+ return;
+ }
+ const { currency } = dp;
+ const exchangeDetails = await getExchangeRecordsInternal(tx, ex.baseUrl);
+ if (!exchangeDetails) {
+ return;
+ }
+ const denominationRecords =
+ await tx.denominations.indexes.byExchangeBaseUrl.getAll(ex.baseUrl);
- if (!denominationRecords) {
- return;
- }
+ if (!denominationRecords) {
+ return;
+ }
- const denominations: DenominationInfo[] = denominationRecords.map((x) =>
- DenominationRecord.toDenomInfo(x),
- );
+ const denominations: DenominationInfo[] = denominationRecords.map((x) =>
+ DenominationRecord.toDenomInfo(x),
+ );
- return {
- info: {
- exchangeBaseUrl: ex.baseUrl,
- currency,
- paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
- auditors: exchangeDetails.auditors,
- wireInfo: exchangeDetails.wireInfo,
- globalFees: exchangeDetails.globalFees,
- },
- denominations,
- };
- },
- );
+ return {
+ info: {
+ exchangeBaseUrl: ex.baseUrl,
+ currency,
+ paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
+ auditors: exchangeDetails.auditors,
+ wireInfo: exchangeDetails.wireInfo,
+ globalFees: exchangeDetails.globalFees,
+ },
+ denominations,
+ };
+ });
if (!exchange) {
throw Error(`exchange with base url "${exchangeBaseurl}" not found`);
@@ -3343,16 +3258,13 @@ export async function getExchangeResources(
exchangeBaseUrl: string,
): Promise<GetExchangeResourcesResponse> {
// Withdrawals include internal withdrawals from peer transactions
- const res = await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges", "withdrawalGroups", "coins"] },
- async (tx) => {
- const exchangeRecord = await tx.exchanges.get(exchangeBaseUrl);
- if (!exchangeRecord) {
- return undefined;
- }
- return internalGetExchangeResources(tx, exchangeBaseUrl);
- },
- );
+ const res = await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchangeRecord = await tx.exchanges.get(exchangeBaseUrl);
+ if (!exchangeRecord) {
+ return undefined;
+ }
+ return internalGetExchangeResources(tx, exchangeBaseUrl);
+ });
if (!res) {
throw Error("exchange not found");
}
@@ -3368,18 +3280,15 @@ export async function getExchangeWireFee(
baseUrl: string,
time: TalerProtocolTimestamp,
): Promise<WireFee> {
- const exchangeDetails = await wex.db.runReadOnlyTx(
- { storeNames: ["exchangeDetails", "exchanges"] },
- async (tx) => {
- const ex = await tx.exchanges.get(baseUrl);
- if (!ex || !ex.detailsPointer) return undefined;
- return await tx.exchangeDetails.indexes.byPointer.get([
- baseUrl,
- ex.detailsPointer.currency,
- ex.detailsPointer.masterPublicKey,
- ]);
- },
- );
+ const exchangeDetails = await wex.runLegacyWalletDbTx(async (tx) => {
+ const ex = await tx.exchanges.get(baseUrl);
+ if (!ex || !ex.detailsPointer) return undefined;
+ return await tx.exchangeDetails.indexes.byPointer.get([
+ baseUrl,
+ ex.detailsPointer.currency,
+ ex.detailsPointer.masterPublicKey,
+ ]);
+ });
if (!exchangeDetails) {
throw Error(`exchange missing: ${baseUrl}`);
@@ -3425,15 +3334,7 @@ export async function checkIncomingAmountLegalUnderKycBalanceThreshold(
amountIncoming: AmountLike,
): Promise<BalanceThresholdCheckResult> {
logger.trace(`checking ${exchangeBaseUrl} +${amountIncoming} for KYC`);
- return await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "exchanges",
- "exchangeDetails",
- "reserves",
- "coinAvailability",
- ],
- },
+ return await wex.runLegacyWalletDbTx(
async (tx): Promise<BalanceThresholdCheckResult> => {
const exchangeRec = await tx.exchanges.get(exchangeBaseUrl);
if (!exchangeRec) {
@@ -3550,46 +3451,41 @@ export async function waitExchangeWalletKyc(
): Promise<void> {
await genericWaitForState(wex, {
async checkState(): Promise<boolean> {
- return await wex.db.runReadOnlyTx(
- {
- storeNames: ["exchanges", "reserves"],
- },
- async (tx) => {
- const exchange = await tx.exchanges.get(exchangeBaseUrl);
- if (!exchange) {
- throw new Error("exchange not found");
- }
- const reserveId = exchange.currentMergeReserveRowId;
- if (reserveId == null) {
- logger.warn("KYC does not exist yet");
- return false;
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchange = await tx.exchanges.get(exchangeBaseUrl);
+ if (!exchange) {
+ throw new Error("exchange not found");
+ }
+ const reserveId = exchange.currentMergeReserveRowId;
+ if (reserveId == null) {
+ logger.warn("KYC does not exist yet");
+ return false;
+ }
+ const reserve = await tx.reserves.get(reserveId);
+ if (!reserve) {
+ throw Error("reserve not found");
+ }
+ if (passed) {
+ if (
+ reserve.thresholdGranted &&
+ Amounts.cmp(reserve.thresholdGranted, amount) >= 0
+ ) {
+ return true;
}
- const reserve = await tx.reserves.get(reserveId);
- if (!reserve) {
- throw Error("reserve not found");
+ return false;
+ } else {
+ if (
+ reserve.thresholdGranted &&
+ Amounts.cmp(reserve.thresholdGranted, amount) >= 0
+ ) {
+ return true;
}
- if (passed) {
- if (
- reserve.thresholdGranted &&
- Amounts.cmp(reserve.thresholdGranted, amount) >= 0
- ) {
- return true;
- }
- return false;
- } else {
- if (
- reserve.thresholdGranted &&
- Amounts.cmp(reserve.thresholdGranted, amount) >= 0
- ) {
- return true;
- }
- if (reserve.status === ReserveRecordStatus.PendingLegi) {
- return true;
- }
- return false;
+ if (reserve.status === ReserveRecordStatus.PendingLegi) {
+ return true;
}
- },
- );
+ return false;
+ }
+ });
},
filterNotification(notif) {
return (
@@ -3654,63 +3550,58 @@ export async function handleStartExchangeWalletKyc(
req: StartExchangeWalletKycRequest,
): Promise<EmptyObject> {
const newReservePair = await wex.cryptoApi.createEddsaKeypair({});
- const dbRes = await wex.db.runReadWriteTx(
- {
- storeNames: ["exchanges", "reserves"],
- },
- async (tx) => {
- const exchange = await tx.exchanges.get(req.exchangeBaseUrl);
- if (!exchange) {
- throw Error("exchange not found");
- }
- const oldExchangeState = getExchangeState(exchange);
- let mergeReserveRowId = exchange.currentMergeReserveRowId;
- if (mergeReserveRowId == null) {
- const putRes = await tx.reserves.put({
- reservePriv: newReservePair.priv,
- reservePub: newReservePair.pub,
- });
- checkDbInvariant(typeof putRes.key === "number", "primary key type");
- mergeReserveRowId = putRes.key;
- exchange.currentMergeReserveRowId = mergeReserveRowId;
- await tx.exchanges.put(exchange);
- }
- const reserveRec = await tx.reserves.get(mergeReserveRowId);
- checkDbInvariant(reserveRec != null, "reserve record exists");
+ const dbRes = await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchange = await tx.exchanges.get(req.exchangeBaseUrl);
+ if (!exchange) {
+ throw Error("exchange not found");
+ }
+ const oldExchangeState = getExchangeState(exchange);
+ let mergeReserveRowId = exchange.currentMergeReserveRowId;
+ if (mergeReserveRowId == null) {
+ const putRes = await tx.reserves.put({
+ reservePriv: newReservePair.priv,
+ reservePub: newReservePair.pub,
+ });
+ checkDbInvariant(typeof putRes.key === "number", "primary key type");
+ mergeReserveRowId = putRes.key;
+ exchange.currentMergeReserveRowId = mergeReserveRowId;
+ await tx.exchanges.put(exchange);
+ }
+ const reserveRec = await tx.reserves.get(mergeReserveRowId);
+ checkDbInvariant(reserveRec != null, "reserve record exists");
+ if (
+ reserveRec.thresholdGranted == null ||
+ Amounts.cmp(reserveRec.thresholdGranted, req.amount) < 0
+ ) {
if (
- reserveRec.thresholdGranted == null ||
- Amounts.cmp(reserveRec.thresholdGranted, req.amount) < 0
+ reserveRec.thresholdRequested == null ||
+ Amounts.cmp(reserveRec.thresholdRequested, req.amount) < 0
) {
- if (
- reserveRec.thresholdRequested == null ||
- Amounts.cmp(reserveRec.thresholdRequested, req.amount) < 0
- ) {
- reserveRec.thresholdRequested = req.amount;
- reserveRec.status = ReserveRecordStatus.PendingLegiInit;
- await tx.reserves.put(reserveRec);
- return {
- notification: {
- type: NotificationType.ExchangeStateTransition,
- exchangeBaseUrl: exchange.baseUrl,
- oldExchangeState,
- newExchangeState: getExchangeState(exchange),
- } satisfies WalletNotification,
- };
- } else {
- logger.info(
- `another KYC process is already active for ${req.exchangeBaseUrl} over ${reserveRec.thresholdRequested}`,
- );
- return undefined;
- }
+ reserveRec.thresholdRequested = req.amount;
+ reserveRec.status = ReserveRecordStatus.PendingLegiInit;
+ await tx.reserves.put(reserveRec);
+ return {
+ notification: {
+ type: NotificationType.ExchangeStateTransition,
+ exchangeBaseUrl: exchange.baseUrl,
+ oldExchangeState,
+ newExchangeState: getExchangeState(exchange),
+ } satisfies WalletNotification,
+ };
} else {
- // FIXME: Check expiration once exchange tells us!
logger.info(
- `KYC already granted for ${req.exchangeBaseUrl} over ${req.amount}, granted ${reserveRec.thresholdGranted}`,
+ `another KYC process is already active for ${req.exchangeBaseUrl} over ${reserveRec.thresholdRequested}`,
);
return undefined;
}
- },
- );
+ } else {
+ // FIXME: Check expiration once exchange tells us!
+ logger.info(
+ `KYC already granted for ${req.exchangeBaseUrl} over ${req.amount}, granted ${reserveRec.thresholdGranted}`,
+ );
+ return undefined;
+ }
+ });
if (dbRes && dbRes.notification) {
wex.ws.notify(dbRes.notification);
}
@@ -3790,48 +3681,45 @@ async function handleExchangeKycSuccess(
nextThreshold: AmountLike | undefined,
): Promise<TaskRunResult> {
logger.info(`kyc check for ${exchangeBaseUrl} satisfied`);
- const dbRes = await wex.db.runReadWriteTx(
- { storeNames: ["exchanges", "reserves"] },
- async (tx) => {
- const exchange = await tx.exchanges.get(exchangeBaseUrl);
- if (!exchange) {
- throw Error("exchange not found");
- }
- const oldExchangeState = getExchangeState(exchange);
- const reserveId = exchange.currentMergeReserveRowId;
- if (reserveId == null) {
- throw Error("expected exchange to have reserve ID");
- }
- const reserve = await tx.reserves.get(reserveId);
- checkDbInvariant(!!reserve, "merge reserve should exist");
- switch (reserve.status) {
- case ReserveRecordStatus.PendingLegiInit:
- case ReserveRecordStatus.PendingLegi:
- break;
- default:
- throw Error("unexpected state (concurrent modification?)");
- }
- reserve.status = ReserveRecordStatus.Done;
- reserve.thresholdGranted = reserve.thresholdRequested;
- delete reserve.thresholdRequested;
- delete reserve.requirementRow;
+ const dbRes = await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchange = await tx.exchanges.get(exchangeBaseUrl);
+ if (!exchange) {
+ throw Error("exchange not found");
+ }
+ const oldExchangeState = getExchangeState(exchange);
+ const reserveId = exchange.currentMergeReserveRowId;
+ if (reserveId == null) {
+ throw Error("expected exchange to have reserve ID");
+ }
+ const reserve = await tx.reserves.get(reserveId);
+ checkDbInvariant(!!reserve, "merge reserve should exist");
+ switch (reserve.status) {
+ case ReserveRecordStatus.PendingLegiInit:
+ case ReserveRecordStatus.PendingLegi:
+ break;
+ default:
+ throw Error("unexpected state (concurrent modification?)");
+ }
+ reserve.status = ReserveRecordStatus.Done;
+ reserve.thresholdGranted = reserve.thresholdRequested;
+ delete reserve.thresholdRequested;
+ delete reserve.requirementRow;
- if (nextThreshold) {
- reserve.thresholdNext = Amounts.stringify(nextThreshold);
- }
+ if (nextThreshold) {
+ reserve.thresholdNext = Amounts.stringify(nextThreshold);
+ }
- await tx.reserves.put(reserve);
- logger.info(`newly granted threshold: ${reserve.thresholdGranted}`);
- return {
- notification: {
- type: NotificationType.ExchangeStateTransition,
- exchangeBaseUrl: exchange.baseUrl,
- oldExchangeState,
- newExchangeState: getExchangeState(exchange),
- } satisfies WalletNotification,
- };
- },
- );
+ await tx.reserves.put(reserve);
+ logger.info(`newly granted threshold: ${reserve.thresholdGranted}`);
+ return {
+ notification: {
+ type: NotificationType.ExchangeStateTransition,
+ exchangeBaseUrl: exchange.baseUrl,
+ oldExchangeState,
+ newExchangeState: getExchangeState(exchange),
+ } satisfies WalletNotification,
+ };
+ });
if (dbRes && dbRes.notification) {
wex.ws.notify(dbRes.notification);
}
@@ -3911,42 +3799,39 @@ async function handleExchangeKycRespLegi(
codecForAccountKycStatus(),
);
- const dbRes = await wex.db.runReadWriteTx(
- { storeNames: ["exchanges", "reserves"] },
- async (tx) => {
- const exchange = await tx.exchanges.get(exchangeBaseUrl);
- if (!exchange) {
- throw Error("exchange not found");
- }
- const oldExchangeState = getExchangeState(exchange);
- const reserveId = exchange.currentMergeReserveRowId;
- if (reserveId == null) {
- throw Error("expected exchange to have reserve ID");
- }
- const reserve = await tx.reserves.get(reserveId);
- checkDbInvariant(!!reserve, "merge reserve should exist");
- switch (reserve.status) {
- case ReserveRecordStatus.PendingLegiInit:
- break;
- default:
- throw Error("unexpected state (concurrent modification?)");
- }
- reserve.status = ReserveRecordStatus.PendingLegi;
- reserve.requirementRow = kycBody.requirement_row;
- reserve.amlReview = accountKycStatusResp.aml_review;
- reserve.kycAccessToken = accountKycStatusResp.access_token;
+ const dbRes = await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchange = await tx.exchanges.get(exchangeBaseUrl);
+ if (!exchange) {
+ throw Error("exchange not found");
+ }
+ const oldExchangeState = getExchangeState(exchange);
+ const reserveId = exchange.currentMergeReserveRowId;
+ if (reserveId == null) {
+ throw Error("expected exchange to have reserve ID");
+ }
+ const reserve = await tx.reserves.get(reserveId);
+ checkDbInvariant(!!reserve, "merge reserve should exist");
+ switch (reserve.status) {
+ case ReserveRecordStatus.PendingLegiInit:
+ break;
+ default:
+ throw Error("unexpected state (concurrent modification?)");
+ }
+ reserve.status = ReserveRecordStatus.PendingLegi;
+ reserve.requirementRow = kycBody.requirement_row;
+ reserve.amlReview = accountKycStatusResp.aml_review;
+ reserve.kycAccessToken = accountKycStatusResp.access_token;
- await tx.reserves.put(reserve);
- return {
- notification: {
- type: NotificationType.ExchangeStateTransition,
- exchangeBaseUrl: exchange.baseUrl,
- oldExchangeState,
- newExchangeState: getExchangeState(exchange),
- } satisfies WalletNotification,
- };
- },
- );
+ await tx.reserves.put(reserve);
+ return {
+ notification: {
+ type: NotificationType.ExchangeStateTransition,
+ exchangeBaseUrl: exchange.baseUrl,
+ oldExchangeState,
+ newExchangeState: getExchangeState(exchange),
+ } satisfies WalletNotification,
+ };
+ });
if (dbRes && dbRes.notification) {
wex.ws.notify(dbRes.notification);
}
@@ -4027,21 +3912,18 @@ export async function processExchangeKyc(
wex: WalletExecutionContext,
exchangeBaseUrl: string,
): Promise<TaskRunResult> {
- const res = await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges", "reserves"] },
- async (tx) => {
- const exchange = await tx.exchanges.get(exchangeBaseUrl);
- if (!exchange) {
- return undefined;
- }
- const reserveId = exchange.currentMergeReserveRowId;
- let reserve: ReserveRecord | undefined = undefined;
- if (reserveId != null) {
- reserve = await tx.reserves.get(reserveId);
- }
- return { exchange, reserve };
- },
- );
+ const res = await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchange = await tx.exchanges.get(exchangeBaseUrl);
+ if (!exchange) {
+ return undefined;
+ }
+ const reserveId = exchange.currentMergeReserveRowId;
+ let reserve: ReserveRecord | undefined = undefined;
+ if (reserveId != null) {
+ reserve = await tx.reserves.get(reserveId);
+ }
+ return { exchange, reserve };
+ });
if (!res) {
logger.warn(`exchange ${exchangeBaseUrl} not found, not processing KYC`);
return TaskRunResult.finished();
@@ -4131,61 +4013,51 @@ export async function getPreferredExchangeForCurrency(
}
// Find an exchange with the matching currency.
// Prefer exchanges with the most recent withdrawal.
- const url = await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "exchanges",
- "globalCurrencyAuditors",
- "globalCurrencyExchanges",
- "exchangeDetails",
- ],
- },
- async (tx) => {
- const exchanges = await tx.exchanges.iter().toArray();
- logger.trace(`have ${exchanges.length} exchanges`);
- let candidate = undefined;
- for (const e of exchanges) {
- if (e.detailsPointer?.currency !== currency) {
- logger.trace("no details pointer");
- continue;
- }
- const inScope =
- !restrictScope ||
- (await checkExchangeInScopeTx(tx, e.baseUrl, restrictScope));
- if (!inScope) {
- logger.trace("not in scope");
- continue;
- }
- if (!candidate) {
+ const url = await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchanges = await tx.exchanges.iter().toArray();
+ logger.trace(`have ${exchanges.length} exchanges`);
+ let candidate = undefined;
+ for (const e of exchanges) {
+ if (e.detailsPointer?.currency !== currency) {
+ logger.trace("no details pointer");
+ continue;
+ }
+ const inScope =
+ !restrictScope ||
+ (await checkExchangeInScopeTx(tx, e.baseUrl, restrictScope));
+ if (!inScope) {
+ logger.trace("not in scope");
+ continue;
+ }
+ if (!candidate) {
+ candidate = e;
+ continue;
+ }
+ if (candidate.lastWithdrawal && !e.lastWithdrawal) {
+ continue;
+ }
+ const exchangeLastWithdrawal = timestampOptionalPreciseFromDb(
+ e.lastWithdrawal,
+ );
+ const candidateLastWithdrawal = timestampOptionalPreciseFromDb(
+ candidate.lastWithdrawal,
+ );
+ if (exchangeLastWithdrawal && candidateLastWithdrawal) {
+ if (
+ AbsoluteTime.cmp(
+ AbsoluteTime.fromPreciseTimestamp(exchangeLastWithdrawal),
+ AbsoluteTime.fromPreciseTimestamp(candidateLastWithdrawal),
+ ) > 0
+ ) {
candidate = e;
- continue;
}
- if (candidate.lastWithdrawal && !e.lastWithdrawal) {
- continue;
- }
- const exchangeLastWithdrawal = timestampOptionalPreciseFromDb(
- e.lastWithdrawal,
- );
- const candidateLastWithdrawal = timestampOptionalPreciseFromDb(
- candidate.lastWithdrawal,
- );
- if (exchangeLastWithdrawal && candidateLastWithdrawal) {
- if (
- AbsoluteTime.cmp(
- AbsoluteTime.fromPreciseTimestamp(exchangeLastWithdrawal),
- AbsoluteTime.fromPreciseTimestamp(candidateLastWithdrawal),
- ) > 0
- ) {
- candidate = e;
- }
- }
- }
- if (candidate) {
- return candidate.baseUrl;
}
- return undefined;
- },
- );
+ }
+ if (candidate) {
+ return candidate.baseUrl;
+ }
+ return undefined;
+ });
return url;
}
diff --git a/packages/taler-wallet-core/src/instructedAmountConversion.ts b/packages/taler-wallet-core/src/instructedAmountConversion.ts
@@ -126,178 +126,165 @@ async function getAvailableCoins(
): Promise<AvailableCoins> {
const operationType = getOperationType(op);
- return await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "exchanges",
- "exchangeDetails",
- "denominations",
- "coinAvailability",
- ],
- },
- async (tx) => {
- const list: CoinInfo[] = [];
- const exchanges: Record<string, ExchangeInfo> = {};
-
- const databaseExchanges = await tx.exchanges.iter().toArray();
- const filteredExchanges =
- filters.exchanges ?? databaseExchanges.map((e) => e.baseUrl);
-
- for (const exchangeBaseUrl of filteredExchanges) {
- const exchangeDetails = await getExchangeDetailsInTx(
- tx,
- exchangeBaseUrl,
- );
- // 1.- exchange has same currency
- if (exchangeDetails?.currency !== currency) {
- continue;
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ const list: CoinInfo[] = [];
+ const exchanges: Record<string, ExchangeInfo> = {};
+
+ const databaseExchanges = await tx.exchanges.iter().toArray();
+ const filteredExchanges =
+ filters.exchanges ?? databaseExchanges.map((e) => e.baseUrl);
+
+ for (const exchangeBaseUrl of filteredExchanges) {
+ const exchangeDetails = await getExchangeDetailsInTx(tx, exchangeBaseUrl);
+ // 1.- exchange has same currency
+ if (exchangeDetails?.currency !== currency) {
+ continue;
+ }
+
+ let deadline = AbsoluteTime.never();
+ // 2.- exchange supports wire method
+ let wireFee: AmountJson | undefined;
+ if (filters.wireMethod) {
+ const wireMethodWithDates =
+ exchangeDetails.wireInfo.feesForType[filters.wireMethod];
+
+ if (!wireMethodWithDates) {
+ throw Error(
+ `exchange ${exchangeBaseUrl} doesn't have wire method ${filters.wireMethod}`,
+ );
}
+ const wireMethodFee = wireMethodWithDates.find((x) => {
+ return AbsoluteTime.isBetween(
+ AbsoluteTime.now(),
+ AbsoluteTime.fromProtocolTimestamp(x.startStamp),
+ AbsoluteTime.fromProtocolTimestamp(x.endStamp),
+ );
+ });
- let deadline = AbsoluteTime.never();
- // 2.- exchange supports wire method
- let wireFee: AmountJson | undefined;
- if (filters.wireMethod) {
- const wireMethodWithDates =
- exchangeDetails.wireInfo.feesForType[filters.wireMethod];
-
- if (!wireMethodWithDates) {
- throw Error(
- `exchange ${exchangeBaseUrl} doesn't have wire method ${filters.wireMethod}`,
- );
- }
- const wireMethodFee = wireMethodWithDates.find((x) => {
- return AbsoluteTime.isBetween(
- AbsoluteTime.now(),
- AbsoluteTime.fromProtocolTimestamp(x.startStamp),
- AbsoluteTime.fromProtocolTimestamp(x.endStamp),
- );
- });
-
- if (!wireMethodFee) {
- throw Error(
- `exchange ${exchangeBaseUrl} doesn't have wire fee defined for this period`,
- );
- }
- wireFee = Amounts.parseOrThrow(wireMethodFee.wireFee);
- deadline = AbsoluteTime.min(
- deadline,
- AbsoluteTime.fromProtocolTimestamp(wireMethodFee.endStamp),
+ if (!wireMethodFee) {
+ throw Error(
+ `exchange ${exchangeBaseUrl} doesn't have wire fee defined for this period`,
);
}
- // exchanges[exchangeBaseUrl].wireFee = wireMethodFee;
-
- // 3.- exchange supports wire method
- let purseFee: AmountJson | undefined;
- if (filters.shouldCalculatePurseFee) {
- const purseFeeFound = exchangeDetails.globalFees.find((x) => {
- return AbsoluteTime.isBetween(
- AbsoluteTime.now(),
- AbsoluteTime.fromProtocolTimestamp(x.startDate),
- AbsoluteTime.fromProtocolTimestamp(x.endDate),
- );
- });
- if (!purseFeeFound) {
- throw Error(
- `exchange ${exchangeBaseUrl} doesn't have purse fee defined for this period`,
- );
- }
- purseFee = Amounts.parseOrThrow(purseFeeFound.purseFee);
- deadline = AbsoluteTime.min(
- deadline,
- AbsoluteTime.fromProtocolTimestamp(purseFeeFound.endDate),
+ wireFee = Amounts.parseOrThrow(wireMethodFee.wireFee);
+ deadline = AbsoluteTime.min(
+ deadline,
+ AbsoluteTime.fromProtocolTimestamp(wireMethodFee.endStamp),
+ );
+ }
+ // exchanges[exchangeBaseUrl].wireFee = wireMethodFee;
+
+ // 3.- exchange supports wire method
+ let purseFee: AmountJson | undefined;
+ if (filters.shouldCalculatePurseFee) {
+ const purseFeeFound = exchangeDetails.globalFees.find((x) => {
+ return AbsoluteTime.isBetween(
+ AbsoluteTime.now(),
+ AbsoluteTime.fromProtocolTimestamp(x.startDate),
+ AbsoluteTime.fromProtocolTimestamp(x.endDate),
+ );
+ });
+ if (!purseFeeFound) {
+ throw Error(
+ `exchange ${exchangeBaseUrl} doesn't have purse fee defined for this period`,
);
}
+ purseFee = Amounts.parseOrThrow(purseFeeFound.purseFee);
+ deadline = AbsoluteTime.min(
+ deadline,
+ AbsoluteTime.fromProtocolTimestamp(purseFeeFound.endDate),
+ );
+ }
- let creditDeadline = AbsoluteTime.never();
- let debitDeadline = AbsoluteTime.never();
- //4.- filter coins restricted by age
- if (operationType === OperationType.Credit) {
- // FIXME: Use denom groups instead of querying all denominations!
- const ds =
- await tx.denominations.indexes.byExchangeBaseUrl.getAll(
- exchangeBaseUrl,
- );
- for (const denom of ds) {
- const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
- timestampProtocolFromDb(denom.stampExpireWithdraw),
- );
- const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
- timestampProtocolFromDb(denom.stampExpireDeposit),
- );
- creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
- debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
- list.push(
- buildCoinInfoFromDenom(
- denom,
- purseFee,
- wireFee,
- AgeRestriction.AGE_UNRESTRICTED,
- Number.MAX_SAFE_INTEGER, // Max withdrawable from single denom
- ),
- );
- }
- } else {
- const ageLower = filters.ageRestricted ?? 0;
- const ageUpper = AgeRestriction.AGE_UNRESTRICTED;
-
- const myExchangeCoins =
- await tx.coinAvailability.indexes.byExchangeAgeAvailability.getAll(
- GlobalIDB.KeyRange.bound(
- [exchangeDetails.exchangeBaseUrl, ageLower, 1],
- [
- exchangeDetails.exchangeBaseUrl,
- ageUpper,
- Number.MAX_SAFE_INTEGER,
- ],
- ),
- );
- //5.- save denoms with how many coins are available
- // FIXME: Check that the individual denomination is audited!
- // FIXME: Should we exclude denominations that are
- // not spendable anymore?
- for (const coinAvail of myExchangeCoins) {
- const denom = await tx.denominations.get([
- coinAvail.exchangeBaseUrl,
- coinAvail.denomPubHash,
- ]);
- checkDbInvariant(
- !!denom,
- `denomination of a coin is missing hash: ${coinAvail.denomPubHash}`,
- );
- if (denom.isRevoked || !denom.isOffered) {
- continue;
- }
- const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
- timestampProtocolFromDb(denom.stampExpireWithdraw),
- );
- const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
- timestampProtocolFromDb(denom.stampExpireDeposit),
- );
- creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
- debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
- list.push(
- buildCoinInfoFromDenom(
- denom,
- purseFee,
- wireFee,
- coinAvail.maxAge,
- coinAvail.freshCoinCount,
- ),
- );
+ let creditDeadline = AbsoluteTime.never();
+ let debitDeadline = AbsoluteTime.never();
+ //4.- filter coins restricted by age
+ if (operationType === OperationType.Credit) {
+ // FIXME: Use denom groups instead of querying all denominations!
+ const ds =
+ await tx.denominations.indexes.byExchangeBaseUrl.getAll(
+ exchangeBaseUrl,
+ );
+ for (const denom of ds) {
+ const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(denom.stampExpireWithdraw),
+ );
+ const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(denom.stampExpireDeposit),
+ );
+ creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
+ debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
+ list.push(
+ buildCoinInfoFromDenom(
+ denom,
+ purseFee,
+ wireFee,
+ AgeRestriction.AGE_UNRESTRICTED,
+ Number.MAX_SAFE_INTEGER, // Max withdrawable from single denom
+ ),
+ );
+ }
+ } else {
+ const ageLower = filters.ageRestricted ?? 0;
+ const ageUpper = AgeRestriction.AGE_UNRESTRICTED;
+
+ const myExchangeCoins =
+ await tx.coinAvailability.indexes.byExchangeAgeAvailability.getAll(
+ GlobalIDB.KeyRange.bound(
+ [exchangeDetails.exchangeBaseUrl, ageLower, 1],
+ [
+ exchangeDetails.exchangeBaseUrl,
+ ageUpper,
+ Number.MAX_SAFE_INTEGER,
+ ],
+ ),
+ );
+ //5.- save denoms with how many coins are available
+ // FIXME: Check that the individual denomination is audited!
+ // FIXME: Should we exclude denominations that are
+ // not spendable anymore?
+ for (const coinAvail of myExchangeCoins) {
+ const denom = await tx.denominations.get([
+ coinAvail.exchangeBaseUrl,
+ coinAvail.denomPubHash,
+ ]);
+ checkDbInvariant(
+ !!denom,
+ `denomination of a coin is missing hash: ${coinAvail.denomPubHash}`,
+ );
+ if (denom.isRevoked || !denom.isOffered) {
+ continue;
}
+ const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(denom.stampExpireWithdraw),
+ );
+ const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(denom.stampExpireDeposit),
+ );
+ creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
+ debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
+ list.push(
+ buildCoinInfoFromDenom(
+ denom,
+ purseFee,
+ wireFee,
+ coinAvail.maxAge,
+ coinAvail.freshCoinCount,
+ ),
+ );
}
-
- exchanges[exchangeBaseUrl] = {
- purseFee,
- wireFee,
- debitDeadline,
- creditDeadline,
- };
}
- return { list, exchanges };
- },
- );
+ exchanges[exchangeBaseUrl] = {
+ purseFee,
+ wireFee,
+ debitDeadline,
+ creditDeadline,
+ };
+ }
+
+ return { list, exchanges };
+ });
}
function buildCoinInfoFromDenom(
diff --git a/packages/taler-wallet-core/src/mailbox.ts b/packages/taler-wallet-core/src/mailbox.ts
@@ -65,18 +65,13 @@ export async function addMailboxMessage(
wex: WalletExecutionContext,
req: AddMailboxMessageRequest,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx(
- {
- storeNames: ["mailboxMessages"],
- },
- async (tx) => {
- tx.mailboxMessages.put(req.message);
- tx.notify({
- type: NotificationType.MailboxMessageAdded,
- message: req.message,
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ tx.mailboxMessages.put(req.message);
+ tx.notify({
+ type: NotificationType.MailboxMessageAdded,
+ message: req.message,
+ });
+ });
return {};
}
@@ -87,21 +82,16 @@ export async function deleteMailboxMessage(
wex: WalletExecutionContext,
req: DeleteMailboxMessageRequest,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx(
- {
- storeNames: ["mailboxMessages"],
- },
- async (tx) => {
- tx.mailboxMessages.delete([
- req.message.originMailboxBaseUrl,
- req.message.talerUri,
- ]);
- tx.notify({
- type: NotificationType.MailboxMessageDeleted,
- message: req.message,
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ tx.mailboxMessages.delete([
+ req.message.originMailboxBaseUrl,
+ req.message.talerUri,
+ ]);
+ tx.notify({
+ type: NotificationType.MailboxMessageDeleted,
+ message: req.message,
+ });
+ });
return {};
}
@@ -112,14 +102,9 @@ export async function listMailboxMessages(
wex: WalletExecutionContext,
req: EmptyObject,
): Promise<MailboxMessagesResponse> {
- const messages = await wex.db.runReadOnlyTx(
- {
- storeNames: ["mailboxMessages"],
- },
- async (tx) => {
- return await tx.mailboxMessages.getAll();
- },
- );
+ const messages = await wex.runLegacyWalletDbTx(async (tx) => {
+ return await tx.mailboxMessages.getAll();
+ });
return { messages: messages };
}
@@ -192,14 +177,9 @@ export async function getMailbox(
wex: WalletExecutionContext,
mailboxBaseUrl: string,
): Promise<MailboxConfiguration | undefined> {
- return await wex.db.runReadOnlyTx(
- {
- storeNames: ["mailboxConfigurations"],
- },
- async (tx) => {
- return await tx.mailboxConfigurations.get(mailboxBaseUrl);
- },
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ return await tx.mailboxConfigurations.get(mailboxBaseUrl);
+ });
}
/**
@@ -238,14 +218,9 @@ export async function createNewMailbox(
undefined,
);
}
- await wex.db.runReadWriteTx(
- {
- storeNames: ["mailboxConfigurations"],
- },
- async (tx) => {
- return await tx.mailboxConfigurations.put(mailboxConf);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ return await tx.mailboxConfigurations.put(mailboxConf);
+ });
return mailboxConf;
}
diff --git a/packages/taler-wallet-core/src/observable-wrappers.ts b/packages/taler-wallet-core/src/observable-wrappers.ts
@@ -47,7 +47,7 @@ export class ObservableTaskScheduler implements TaskScheduler {
constructor(
private impl: TaskScheduler,
private oc: ObservabilityContext,
- ) { }
+ ) {}
private taskDepCache = new Set<string>();
@@ -129,11 +129,13 @@ export function getCallerInfo(up: number = 2): string {
return identifies.slice(up, up + 2).join("/");
}
-export class ObservableDbAccess<Stores extends StoreMap> implements DbAccess<Stores> {
+export class ObservableDbAccess<Stores extends StoreMap>
+ implements DbAccess<Stores>
+{
constructor(
private impl: DbAccess<Stores>,
private oc: ObservabilityContext,
- ) { }
+ ) {}
idbHandle(): IDBDatabase {
return this.impl.idbHandle();
}
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -380,14 +380,9 @@ export class PayMerchantTransactionContext implements TransactionContext {
}
async deleteTransaction(): Promise<void> {
- await this.wex.db.runReadWriteTx(
- {
- storeNames: ["purchases", "tombstones", "transactionsMeta"],
- },
- async (tx) => {
- return this.deleteTransactionInTx(tx);
- },
- );
+ await this.wex.runLegacyWalletDbTx(async (tx) => {
+ return this.deleteTransactionInTx(tx);
+ });
}
async deleteTransactionInTx(
@@ -434,32 +429,29 @@ export class PayMerchantTransactionContext implements TransactionContext {
async suspendTransaction(): Promise<void> {
const { wex, proposalId, transactionId } = this;
wex.taskScheduler.stopShepherdTask(this.taskId);
- await wex.db.runReadWriteTx(
- { storeNames: ["purchases", "transactionsMeta"] },
- async (tx) => {
- const purchase = await tx.purchases.get(proposalId);
- if (!purchase) {
- throw Error("purchase not found");
- }
- const oldTxState = computePayMerchantTransactionState(purchase);
- const oldStId = purchase.purchaseStatus;
- let newStatus = transitionSuspend[purchase.purchaseStatus];
- if (!newStatus) {
- return;
- }
- await tx.purchases.put(purchase);
- await this.updateTransactionMeta(tx);
- const newTxState = computePayMerchantTransactionState(purchase);
- const newStId = purchase.purchaseStatus;
- applyNotifyTransition(tx.notify, transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.None,
- oldStId,
- newStId,
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const purchase = await tx.purchases.get(proposalId);
+ if (!purchase) {
+ throw Error("purchase not found");
+ }
+ const oldTxState = computePayMerchantTransactionState(purchase);
+ const oldStId = purchase.purchaseStatus;
+ let newStatus = transitionSuspend[purchase.purchaseStatus];
+ if (!newStatus) {
+ return;
+ }
+ await tx.purchases.put(purchase);
+ await this.updateTransactionMeta(tx);
+ const newTxState = computePayMerchantTransactionState(purchase);
+ const newStId = purchase.purchaseStatus;
+ applyNotifyTransition(tx.notify, transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.None,
+ oldStId,
+ newStId,
+ });
+ });
}
async abortTransaction(reason?: TalerErrorDetail): Promise<void> {
@@ -503,21 +495,18 @@ export class PayMerchantTransactionContext implements TransactionContext {
async resumeTransaction(): Promise<void> {
const { wex } = this;
- await wex.db.runReadWriteTx(
- { storeNames: ["purchases", "transactionsMeta"] },
- async (tx) => {
- const [purchase, h] = await this.getRecordHandle(tx);
- if (!purchase) {
- throw Error("purchase not found");
- }
- let newStatus = transitionResume[purchase.purchaseStatus];
- if (!newStatus?.next) {
- return;
- }
- purchase.purchaseStatus = newStatus.next;
- await h.update(purchase, BalanceEffect.Any);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const [purchase, h] = await this.getRecordHandle(tx);
+ if (!purchase) {
+ throw Error("purchase not found");
+ }
+ let newStatus = transitionResume[purchase.purchaseStatus];
+ if (!newStatus?.next) {
+ return;
+ }
+ purchase.purchaseStatus = newStatus.next;
+ await h.update(purchase, BalanceEffect.Any);
+ });
wex.taskScheduler.startShepherdTask(this.taskId);
}
@@ -728,21 +717,9 @@ export class RefundTransactionContext implements TransactionContext {
async deleteTransaction(): Promise<void> {
const { wex } = this;
- const res = await wex.db.runReadWriteTx(
- {
- storeNames: [
- "purchases",
- "refundGroups",
- "refundItems",
- "tombstones",
- "transactionsMeta",
- "contractTerms",
- ],
- },
- async (tx) => {
- return await this.deleteTransactionInTx(tx);
- },
- );
+ const res = await wex.runLegacyWalletDbTx(async (tx) => {
+ return await this.deleteTransactionInTx(tx);
+ });
for (const notif of res.notifs) {
this.wex.ws.notify(notif);
}
@@ -878,18 +855,15 @@ async function failProposalClaimPermanently(
err: TalerErrorDetail,
): Promise<void> {
const ctx = new PayMerchantTransactionContext(wex, proposalId);
- await wex.db.runReadWriteTx(
- { storeNames: ["purchases", "transactionsMeta"] },
- async (tx) => {
- const [p, h] = await ctx.getRecordHandle(tx);
- if (!p) {
- return;
- }
- p.purchaseStatus = PurchaseStatus.FailedClaim;
- p.failReason = err;
- await h.update(p);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const [p, h] = await ctx.getRecordHandle(tx);
+ if (!p) {
+ return;
+ }
+ p.purchaseStatus = PurchaseStatus.FailedClaim;
+ p.failReason = err;
+ await h.update(p);
+ });
}
function getPayRequestTimeout(purchase: PurchaseRecord): Duration {
@@ -944,24 +918,18 @@ async function expectProposalDownload(
wex: WalletExecutionContext,
p: PurchaseRecord,
): Promise<DownloadedContractData> {
- return await wex.db.runReadOnlyTx(
- { storeNames: ["contractTerms"] },
- async (tx) => {
- return expectProposalDownloadInTx(wex, tx, p);
- },
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ return expectProposalDownloadInTx(wex, tx, p);
+ });
}
async function processDownloadProposal(
wex: WalletExecutionContext,
proposalId: string,
): Promise<TaskRunResult> {
- const proposal = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- return await tx.purchases.get(proposalId);
- },
- );
+ const proposal = await wex.runLegacyWalletDbTx(async (tx) => {
+ return await tx.purchases.get(proposalId);
+ });
if (!proposal) {
return TaskRunResult.finished();
@@ -1166,105 +1134,102 @@ async function processDownloadProposal(
logger.trace(`extracted contract data: ${j2s(contractData)}`);
- await wex.db.runReadWriteTx(
- { storeNames: ["purchases", "contractTerms", "transactionsMeta"] },
- async (tx) => {
- const p = await tx.purchases.get(proposalId);
- if (!p) {
- return;
- }
- if (p.purchaseStatus !== PurchaseStatus.PendingDownloadingProposal) {
- return;
- }
- const oldTxState = computePayMerchantTransactionState(p);
- const oldStId = p.purchaseStatus;
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const p = await tx.purchases.get(proposalId);
+ if (!p) {
+ return;
+ }
+ if (p.purchaseStatus !== PurchaseStatus.PendingDownloadingProposal) {
+ return;
+ }
+ const oldTxState = computePayMerchantTransactionState(p);
+ const oldStId = p.purchaseStatus;
- const secretSeed = encodeCrock(getRandomBytes(32));
- p.secretSeed = secretSeed;
+ const secretSeed = encodeCrock(getRandomBytes(32));
+ p.secretSeed = secretSeed;
- // v1: currency is resolved after choice selection
- let currency: string = "UNKNOWN";
- if (
- contractData.contractTerms.version === undefined ||
- contractData.contractTerms.version === MerchantContractVersion.V0
- ) {
- currency = Amounts.currencyOf(contractData.contractTerms.amount);
- } else if (
- contractData.contractTerms.version === MerchantContractVersion.V1
- ) {
- // if there is only one choice, or all choices have the same currency
- if (contractData.contractTerms.choices.length === 1) {
- currency = Amounts.currencyOf(
- contractData.contractTerms.choices[0].amount,
- );
- } else if (contractData.contractTerms.choices.length > 1) {
- const firstCurrency = Amounts.currencyOf(
- contractData.contractTerms.choices[0].amount,
- );
- const allSame = contractData.contractTerms.choices.every(
- (c) => Amounts.currencyOf(c.amount) === firstCurrency,
- );
- if (allSame) {
- currency = firstCurrency;
- }
+ // v1: currency is resolved after choice selection
+ let currency: string = "UNKNOWN";
+ if (
+ contractData.contractTerms.version === undefined ||
+ contractData.contractTerms.version === MerchantContractVersion.V0
+ ) {
+ currency = Amounts.currencyOf(contractData.contractTerms.amount);
+ } else if (
+ contractData.contractTerms.version === MerchantContractVersion.V1
+ ) {
+ // if there is only one choice, or all choices have the same currency
+ if (contractData.contractTerms.choices.length === 1) {
+ currency = Amounts.currencyOf(
+ contractData.contractTerms.choices[0].amount,
+ );
+ } else if (contractData.contractTerms.choices.length > 1) {
+ const firstCurrency = Amounts.currencyOf(
+ contractData.contractTerms.choices[0].amount,
+ );
+ const allSame = contractData.contractTerms.choices.every(
+ (c) => Amounts.currencyOf(c.amount) === firstCurrency,
+ );
+ if (allSame) {
+ currency = firstCurrency;
}
}
+ }
- p.download = {
- contractTermsHash,
- contractTermsMerchantSig: proposalResp.sig,
- currency,
- fulfillmentUrl: contractData.contractTerms.fulfillment_url,
- };
- await tx.contractTerms.put({
- h: contractTermsHash,
- contractTermsRaw: proposalResp.contract_terms,
- });
- const isResourceFulfillmentUrl =
- fulfillmentUrl &&
- (fulfillmentUrl.startsWith("http://") ||
- fulfillmentUrl.startsWith("https://"));
- let repurchase: PurchaseRecord | undefined = undefined;
- const otherPurchases =
- await tx.purchases.indexes.byFulfillmentUrl.getAll(fulfillmentUrl);
- if (isResourceFulfillmentUrl) {
- for (const otherPurchase of otherPurchases) {
- if (
- otherPurchase.purchaseStatus == PurchaseStatus.Done ||
- otherPurchase.purchaseStatus == PurchaseStatus.PendingPaying ||
- otherPurchase.purchaseStatus == PurchaseStatus.PendingPayingReplay
- ) {
- repurchase = otherPurchase;
- break;
- }
+ p.download = {
+ contractTermsHash,
+ contractTermsMerchantSig: proposalResp.sig,
+ currency,
+ fulfillmentUrl: contractData.contractTerms.fulfillment_url,
+ };
+ await tx.contractTerms.put({
+ h: contractTermsHash,
+ contractTermsRaw: proposalResp.contract_terms,
+ });
+ const isResourceFulfillmentUrl =
+ fulfillmentUrl &&
+ (fulfillmentUrl.startsWith("http://") ||
+ fulfillmentUrl.startsWith("https://"));
+ let repurchase: PurchaseRecord | undefined = undefined;
+ const otherPurchases =
+ await tx.purchases.indexes.byFulfillmentUrl.getAll(fulfillmentUrl);
+ if (isResourceFulfillmentUrl) {
+ for (const otherPurchase of otherPurchases) {
+ if (
+ otherPurchase.purchaseStatus == PurchaseStatus.Done ||
+ otherPurchase.purchaseStatus == PurchaseStatus.PendingPaying ||
+ otherPurchase.purchaseStatus == PurchaseStatus.PendingPayingReplay
+ ) {
+ repurchase = otherPurchase;
+ break;
}
}
+ }
- // FIXME: Adjust this to account for refunds, don't count as repurchase
- // if original order is refunded.
- if (repurchase) {
- logger.warn("repurchase detected");
- p.purchaseStatus = PurchaseStatus.DoneRepurchaseDetected;
- p.repurchaseProposalId = repurchase.proposalId;
- await tx.purchases.put(p);
- } else {
- p.purchaseStatus = p.shared
- ? PurchaseStatus.DialogShared
- : PurchaseStatus.DialogProposed;
- await tx.purchases.put(p);
- }
- await ctx.updateTransactionMeta(tx);
- const newTxState = computePayMerchantTransactionState(p);
- const newStId = p.purchaseStatus;
- applyNotifyTransition(tx.notify, transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.None,
- oldStId,
- newStId,
- });
- },
- );
+ // FIXME: Adjust this to account for refunds, don't count as repurchase
+ // if original order is refunded.
+ if (repurchase) {
+ logger.warn("repurchase detected");
+ p.purchaseStatus = PurchaseStatus.DoneRepurchaseDetected;
+ p.repurchaseProposalId = repurchase.proposalId;
+ await tx.purchases.put(p);
+ } else {
+ p.purchaseStatus = p.shared
+ ? PurchaseStatus.DialogShared
+ : PurchaseStatus.DialogProposed;
+ await tx.purchases.put(p);
+ }
+ await ctx.updateTransactionMeta(tx);
+ const newTxState = computePayMerchantTransactionState(p);
+ const newStId = p.purchaseStatus;
+ applyNotifyTransition(tx.notify, transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.None,
+ oldStId,
+ newStId,
+ });
+ });
return TaskRunResult.progress();
}
@@ -1286,14 +1251,11 @@ async function generateSlate(
`generating slate (${choiceIndex}, ${outputIndex}) for purchase ${purchase.proposalId}`,
);
- let slate = await wex.db.runReadOnlyTx(
- { storeNames: ["slates"] },
- async (tx) => {
- return await tx.slates.indexes.byPurchaseIdAndChoiceIndexAndOutputIndex.get(
- [purchase.proposalId, choiceIndex, outputIndex],
- );
- },
- );
+ let slate = await wex.runLegacyWalletDbTx(async (tx) => {
+ return await tx.slates.indexes.byPurchaseIdAndChoiceIndexAndOutputIndex.get(
+ [purchase.proposalId, choiceIndex, outputIndex],
+ );
+ });
if (slate) {
return;
@@ -1341,7 +1303,7 @@ async function generateSlate(
newSlate.tokenFamilyHash = TokenRecord.hashInfo(newSlate);
- await wex.db.runReadWriteTx({ storeNames: ["slates"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const s =
await tx.slates.indexes.byPurchaseIdAndChoiceIndexAndOutputIndex.get([
purchase.proposalId,
@@ -1371,15 +1333,12 @@ async function createOrReusePurchase(
}> {
// Find existing proposals from the same merchant
// with the same order ID.
- const oldProposals = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- return tx.purchases.indexes.byUrlAndOrderId.getAll([
- merchantBaseUrl,
- orderId,
- ]);
- },
- );
+ const oldProposals = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.purchases.indexes.byUrlAndOrderId.getAll([
+ merchantBaseUrl,
+ orderId,
+ ]);
+ });
if (oldProposals.length > 1) {
logger.error(
@@ -1488,24 +1447,21 @@ async function createOrReusePurchase(
const ctx = new PayMerchantTransactionContext(wex, proposalId);
- await wex.db.runReadWriteTx(
- { storeNames: ["purchases", "transactionsMeta"] },
- async (tx) => {
- await tx.purchases.put(proposalRecord);
- await ctx.updateTransactionMeta(tx);
- const oldTxState: TransactionState = {
- major: TransactionMajorState.None,
- };
- const newTxState = computePayMerchantTransactionState(proposalRecord);
- applyNotifyTransition(tx.notify, ctx.transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.None,
- oldStId: 0,
- newStId: proposalRecord.purchaseStatus,
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ await tx.purchases.put(proposalRecord);
+ await ctx.updateTransactionMeta(tx);
+ const oldTxState: TransactionState = {
+ major: TransactionMajorState.None,
+ };
+ const newTxState = computePayMerchantTransactionState(proposalRecord);
+ applyNotifyTransition(tx.notify, ctx.transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.None,
+ oldStId: 0,
+ newStId: proposalRecord.purchaseStatus,
+ });
+ });
return {
proposalId,
@@ -1745,12 +1701,9 @@ async function handleInsufficientFunds(
const ctx = new PayMerchantTransactionContext(wex, proposalId);
- const proposal = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- return tx.purchases.get(proposalId);
- },
- );
+ const proposal = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.purchases.get(proposalId);
+ });
if (!proposal) {
return TaskRunResult.finished();
}
@@ -1837,41 +1790,36 @@ async function lookupProposalOrRepurchase(
}
| undefined
> {
- return await wex.db.runReadOnlyTx(
- { storeNames: ["purchases", "contractTerms"] },
- async (tx) => {
- let purchaseRec = await tx.purchases.get(proposalId);
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ let purchaseRec = await tx.purchases.get(proposalId);
- if (!purchaseRec) {
- return undefined;
- }
+ if (!purchaseRec) {
+ return undefined;
+ }
- if (
- purchaseRec.purchaseStatus === PurchaseStatus.DoneRepurchaseDetected
- ) {
- const existingProposalId = purchaseRec.repurchaseProposalId;
- if (existingProposalId) {
- logger.trace("using existing purchase for same product");
- const oldProposal = await tx.purchases.get(existingProposalId);
- if (oldProposal) {
- purchaseRec = oldProposal;
- }
+ if (purchaseRec.purchaseStatus === PurchaseStatus.DoneRepurchaseDetected) {
+ const existingProposalId = purchaseRec.repurchaseProposalId;
+ if (existingProposalId) {
+ logger.trace("using existing purchase for same product");
+ const oldProposal = await tx.purchases.get(existingProposalId);
+ if (oldProposal) {
+ purchaseRec = oldProposal;
}
}
+ }
- let { contractTerms, contractTermsHash } =
- await expectProposalDownloadByIdInTx(wex, tx, proposalId);
+ let { contractTerms, contractTermsHash } =
+ await expectProposalDownloadByIdInTx(wex, tx, proposalId);
- proposalId = purchaseRec.proposalId;
+ proposalId = purchaseRec.proposalId;
- return {
- proposalId,
- purchaseRec: purchaseRec,
- contractTerms,
- contractTermsHash,
- };
- },
- );
+ return {
+ proposalId,
+ purchaseRec: purchaseRec,
+ contractTerms,
+ contractTermsHash,
+ };
+ });
}
// FIXME: Should take a transaction ID instead of a proposal ID
@@ -2212,8 +2160,7 @@ async function waitProposalDownloaded(
);
},
async checkState() {
- const { purchase, retryInfo } = await ctx.wex.db.runReadOnlyTx(
- { storeNames: ["purchases", "operationRetries"] },
+ const { purchase, retryInfo } = await ctx.wex.runLegacyWalletDbTx(
async (tx) => {
return {
purchase: await tx.purchases.get(ctx.proposalId),
@@ -2376,27 +2323,24 @@ export async function generateDepositPermissions(
coin: CoinRecord;
denom: DenominationRecord;
}> = [];
- await wex.db.runReadOnlyTx(
- { storeNames: ["coins", "denominations"] },
- async (tx) => {
- for (let i = 0; i < payCoinSel.coinContributions.length; i++) {
- const coin = await tx.coins.get(payCoinSel.coinPubs[i]);
- if (!coin) {
- throw Error("can't pay, allocated coin not found anymore");
- }
- const denom = await tx.denominations.get([
- coin.exchangeBaseUrl,
- coin.denomPubHash,
- ]);
- if (!denom) {
- throw Error(
- "can't pay, denomination of allocated coin not found anymore",
- );
- }
- coinWithDenom.push({ coin, denom });
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (let i = 0; i < payCoinSel.coinContributions.length; i++) {
+ const coin = await tx.coins.get(payCoinSel.coinPubs[i]);
+ if (!coin) {
+ throw Error("can't pay, allocated coin not found anymore");
}
- },
- );
+ const denom = await tx.denominations.get([
+ coin.exchangeBaseUrl,
+ coin.denomPubHash,
+ ]);
+ if (!denom) {
+ throw Error(
+ "can't pay, denomination of allocated coin not found anymore",
+ );
+ }
+ coinWithDenom.push({ coin, denom });
+ }
+ });
for (let i = 0; i < payCoinSel.coinContributions.length; i++) {
const { coin, denom } = coinWithDenom[i];
@@ -2448,14 +2392,11 @@ async function waitPaymentResult(
);
},
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 txRes = await ctx.wex.runLegacyWalletDbTx(async (tx) => {
+ const purchase = await tx.purchases.get(ctx.proposalId);
+ const retryRecord = await tx.operationRetries.get(ctx.taskId);
+ return { purchase, retryRecord };
+ });
if (!txRes.purchase) {
throw Error("purchase gone");
@@ -2513,12 +2454,9 @@ export async function getChoicesForPayment(
throw Error("expected payment transaction ID");
}
const proposalId = parsedTx.proposalId;
- const proposal = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- return tx.purchases.get(proposalId);
- },
- );
+ const proposal = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.purchases.get(proposalId);
+ });
if (!proposal) {
throw Error(`proposal with id ${proposalId} not found`);
@@ -2773,12 +2711,9 @@ export async function confirmPay(
logger.trace(
`executing confirmPay with proposalId ${proposalId} and sessionIdOverride ${sessionIdOverride}`,
);
- const proposal = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- return tx.purchases.get(proposalId);
- },
- );
+ const proposal = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.purchases.get(proposalId);
+ });
if (!proposal) {
throw Error(`proposal with id ${proposalId} not found`);
@@ -2789,26 +2724,23 @@ export async function confirmPay(
throw Error("proposal is in invalid state");
}
- const existingPurchase = await wex.db.runReadWriteTx(
- { storeNames: ["purchases", "transactionsMeta"] },
- async (tx) => {
- const purchase = await tx.purchases.get(proposalId);
- if (
- purchase &&
- sessionIdOverride !== undefined &&
- sessionIdOverride != purchase.lastSessionId
- ) {
- logger.trace(`changing session ID to ${sessionIdOverride}`);
- purchase.lastSessionId = sessionIdOverride;
- if (purchase.purchaseStatus === PurchaseStatus.Done) {
- purchase.purchaseStatus = PurchaseStatus.PendingPayingReplay;
- }
- await tx.purchases.put(purchase);
- await ctx.updateTransactionMeta(tx);
+ const existingPurchase = await wex.runLegacyWalletDbTx(async (tx) => {
+ const purchase = await tx.purchases.get(proposalId);
+ if (
+ purchase &&
+ sessionIdOverride !== undefined &&
+ sessionIdOverride != purchase.lastSessionId
+ ) {
+ logger.trace(`changing session ID to ${sessionIdOverride}`);
+ purchase.lastSessionId = sessionIdOverride;
+ if (purchase.purchaseStatus === PurchaseStatus.Done) {
+ purchase.purchaseStatus = PurchaseStatus.PendingPayingReplay;
}
- return purchase;
- },
- );
+ await tx.purchases.put(purchase);
+ await ctx.updateTransactionMeta(tx);
+ }
+ return purchase;
+ });
if (existingPurchase && existingPurchase.payInfo) {
if (
@@ -3093,12 +3025,9 @@ export async function processPurchase(
wex: WalletExecutionContext,
proposalId: string,
): Promise<TaskRunResult> {
- const purchase = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- return tx.purchases.get(proposalId);
- },
- );
+ const purchase = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.purchases.get(proposalId);
+ });
if (!purchase) {
return {
type: TaskRunResultType.Error,
@@ -3161,12 +3090,9 @@ async function processPurchasePay(
wex: WalletExecutionContext,
proposalId: string,
): Promise<TaskRunResult> {
- const purchase = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- return tx.purchases.get(proposalId);
- },
- );
+ const purchase = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.purchases.get(proposalId);
+ });
if (!purchase) {
return {
type: TaskRunResultType.Error,
@@ -3341,22 +3267,17 @@ async function processPurchasePay(
const index = purchase.choiceIndex;
slates = [];
wallet_data = { choice_index: index, tokens_evs: [] };
- await wex.db.runReadOnlyTx(
- {
- storeNames: ["slates"],
- },
- async (tx) => {
- (
- await tx.slates.indexes.byPurchaseIdAndChoiceIndex.getAll([
- purchase.proposalId,
- index,
- ])
- ).forEach((s) => {
- slates?.push(s);
- wallet_data?.tokens_evs.push(s.tokenEv);
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ (
+ await tx.slates.indexes.byPurchaseIdAndChoiceIndex.getAll([
+ purchase.proposalId,
+ index,
+ ])
+ ).forEach((s) => {
+ slates?.push(s);
+ wallet_data?.tokens_evs.push(s.tokenEv);
+ });
+ });
if (purchase.donauOutputIndex != null) {
if (purchase.donauBaseUrl == null || purchase.donauYear == null) {
throw Error("incomplete donau info in DB");
@@ -3557,18 +3478,13 @@ async function processPurchasePay(
}
if (tokenSigs) {
- await wex.db.runReadWriteTx(
- {
- storeNames: ["purchases"],
- },
- async (tx) => {
- if (!purchase.payInfo) {
- return;
- }
- purchase.payInfo.slateTokenSigs = tokenSigs;
- tx.purchases.put(purchase);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ if (!purchase.payInfo) {
+ return;
+ }
+ purchase.payInfo.slateTokenSigs = tokenSigs;
+ tx.purchases.put(purchase);
+ });
}
// store token outputs
@@ -3688,15 +3604,10 @@ export async function validateAndStoreToken(
};
// insert token and delete slate
- await wex.db.runReadWriteTx(
- {
- storeNames: ["slates", "tokens"],
- },
- async (tx) => {
- await tx.tokens.add(token);
- await tx.slates.delete(slate.tokenUsePub);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ await tx.tokens.add(token);
+ await tx.slates.delete(slate.tokenUsePub);
+ });
}
export async function generateTokenSigs(
@@ -3708,18 +3619,13 @@ export async function generateTokenSigs(
): Promise<TokenUseSig[]> {
const tokens: TokenRecord[] = [];
const sigs: TokenUseSig[] = [];
- await wex.db.runReadOnlyTx(
- {
- storeNames: ["tokens", "purchases"],
- },
- async (tx) => {
- for (const pub of tokenPubs) {
- const token = await tx.tokens.get(pub);
- checkDbInvariant(!!token, `token not found for ${pub}`);
- tokens.push(token);
- }
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (const pub of tokenPubs) {
+ const token = await tx.tokens.get(pub);
+ checkDbInvariant(!!token, `token not found for ${pub}`);
+ tokens.push(token);
+ }
+ });
for (const token of tokens) {
if (token.tokenUseSig && token.purchaseId === proposalId) {
@@ -3741,19 +3647,14 @@ export async function generateTokenSigs(
});
}
- await wex.db.runReadWriteTx(
- {
- storeNames: ["tokens"],
- },
- async (tx) => {
- for (let i = 0; i < sigs.length; i++) {
- const token = tokens[i];
- const sig = sigs[i];
- token.tokenUseSig = sig;
- tx.tokens.put(token);
- }
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (let i = 0; i < sigs.length; i++) {
+ const token = tokens[i];
+ const sig = sigs[i];
+ token.tokenUseSig = sig;
+ tx.tokens.put(token);
+ }
+ });
return sigs;
}
@@ -3762,17 +3663,12 @@ export async function cleanupUsedTokens(
wex: WalletExecutionContext,
tokenPubs: string[],
): Promise<void> {
- await wex.db.runReadWriteTx(
- {
- storeNames: ["tokens"],
- },
- async (tx) => {
- for (const pub of tokenPubs) {
- logger.trace(`cleaning up used token ${pub}`);
- tx.tokens.delete(pub);
- }
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (const pub of tokenPubs) {
+ logger.trace(`cleaning up used token ${pub}`);
+ tx.tokens.delete(pub);
+ }
+ });
}
export async function refuseProposal(
@@ -4098,18 +3994,13 @@ export async function sharePayment(
orderId: string,
): Promise<SharePaymentResult> {
// First, translate the order ID into a proposal ID
- const proposalId = await wex.db.runReadOnlyTx(
- {
- storeNames: ["purchases"],
- },
- async (tx) => {
- const p = await tx.purchases.indexes.byUrlAndOrderId.get([
- merchantBaseUrl,
- orderId,
- ]);
- return p?.proposalId;
- },
- );
+ const proposalId = await wex.runLegacyWalletDbTx(async (tx) => {
+ const p = await tx.purchases.indexes.byUrlAndOrderId.get([
+ merchantBaseUrl,
+ orderId,
+ ]);
+ return p?.proposalId;
+ });
if (!proposalId) {
throw Error(`no proposal found for order id ${orderId}`);
@@ -4117,49 +4008,46 @@ export async function sharePayment(
const ctx = new PayMerchantTransactionContext(wex, proposalId);
- const result = await wex.db.runReadWriteTx(
- { storeNames: ["purchases", "transactionsMeta"] },
- async (tx) => {
- const p = await tx.purchases.get(proposalId);
- if (!p) {
- logger.warn("purchase does not exist anymore");
- return undefined;
- }
- if (
- p.purchaseStatus !== PurchaseStatus.DialogProposed &&
- p.purchaseStatus !== PurchaseStatus.DialogShared
- ) {
- // FIXME: purchase can be shared before being paid
- return undefined;
- }
- const oldTxState = computePayMerchantTransactionState(p);
- const oldStId = p.purchaseStatus;
- if (p.purchaseStatus === PurchaseStatus.DialogProposed) {
- p.purchaseStatus = PurchaseStatus.DialogShared;
- p.shared = true;
- await tx.purchases.put(p);
- }
+ const result = await wex.runLegacyWalletDbTx(async (tx) => {
+ const p = await tx.purchases.get(proposalId);
+ if (!p) {
+ logger.warn("purchase does not exist anymore");
+ return undefined;
+ }
+ if (
+ p.purchaseStatus !== PurchaseStatus.DialogProposed &&
+ p.purchaseStatus !== PurchaseStatus.DialogShared
+ ) {
+ // FIXME: purchase can be shared before being paid
+ return undefined;
+ }
+ const oldTxState = computePayMerchantTransactionState(p);
+ const oldStId = p.purchaseStatus;
+ if (p.purchaseStatus === PurchaseStatus.DialogProposed) {
+ p.purchaseStatus = PurchaseStatus.DialogShared;
+ p.shared = true;
+ await tx.purchases.put(p);
+ }
- await ctx.updateTransactionMeta(tx);
+ await ctx.updateTransactionMeta(tx);
- const newTxState = computePayMerchantTransactionState(p);
+ const newTxState = computePayMerchantTransactionState(p);
- applyNotifyTransition(tx.notify, ctx.transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.Any,
- oldStId,
- newStId: p.purchaseStatus,
- });
+ applyNotifyTransition(tx.notify, ctx.transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.Any,
+ oldStId,
+ newStId: p.purchaseStatus,
+ });
- return {
- proposalId: p.proposalId,
- nonce: p.noncePriv,
- session: p.lastSessionId ?? p.downloadSessionId,
- token: p.claimToken,
- };
- },
- );
+ return {
+ proposalId: p.proposalId,
+ nonce: p.noncePriv,
+ session: p.lastSessionId ?? p.downloadSessionId,
+ token: p.claimToken,
+ };
+ });
if (result === undefined) {
throw Error("This purchase can't be shared");
@@ -4318,24 +4206,21 @@ async function processPurchaseAutoRefund(
),
);
- const totalKnownRefund = await wex.db.runReadOnlyTx(
- { storeNames: ["refundGroups"] },
- async (tx) => {
- const refunds = await tx.refundGroups.indexes.byProposalId.getAll(
- purchase.proposalId,
- );
- const am = Amounts.parseOrThrow(amountRaw);
- return refunds.reduce((prev, cur) => {
- if (
- cur.status === RefundGroupStatus.Done ||
- cur.status === RefundGroupStatus.Pending
- ) {
- return Amounts.add(prev, cur.amountRaw).amount;
- }
- return prev;
- }, Amounts.zeroOfAmount(am));
- },
- );
+ const totalKnownRefund = await wex.runLegacyWalletDbTx(async (tx) => {
+ const refunds = await tx.refundGroups.indexes.byProposalId.getAll(
+ purchase.proposalId,
+ );
+ const am = Amounts.parseOrThrow(amountRaw);
+ return refunds.reduce((prev, cur) => {
+ if (
+ cur.status === RefundGroupStatus.Done ||
+ cur.status === RefundGroupStatus.Pending
+ ) {
+ return Amounts.add(prev, cur.amountRaw).amount;
+ }
+ return prev;
+ }, Amounts.zeroOfAmount(am));
+ });
const fullyRefunded = Amounts.cmp(amountRaw, totalKnownRefund) <= 0;
@@ -4627,15 +4512,12 @@ export async function startRefundQueryForUri(
if (parsedUri.type !== TalerUriAction.Refund) {
throw Error("expected taler://refund URI");
}
- const purchaseRecord = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- return tx.purchases.indexes.byUrlAndOrderId.get([
- parsedUri.merchantBaseUrl,
- parsedUri.orderId,
- ]);
- },
- );
+ const purchaseRecord = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.purchases.indexes.byUrlAndOrderId.get([
+ parsedUri.merchantBaseUrl,
+ parsedUri.orderId,
+ ]);
+ });
if (!purchaseRecord) {
logger.error(
`no purchase for order ID "${parsedUri.orderId}" from merchant "${parsedUri.merchantBaseUrl}" when processing "${talerUri}"`,
@@ -4658,33 +4540,30 @@ export async function startQueryRefund(
proposalId: string,
): Promise<void> {
const ctx = new PayMerchantTransactionContext(wex, proposalId);
- await wex.db.runReadWriteTx(
- { storeNames: ["purchases", "transactionsMeta"] },
- async (tx) => {
- const p = await tx.purchases.get(proposalId);
- if (!p) {
- logger.warn(`purchase ${proposalId} does not exist anymore`);
- return;
- }
- if (p.purchaseStatus !== PurchaseStatus.Done) {
- return;
- }
- const oldTxState = computePayMerchantTransactionState(p);
- const oldStId = p.purchaseStatus;
- p.purchaseStatus = PurchaseStatus.PendingQueryingRefund;
- const newTxState = computePayMerchantTransactionState(p);
- const newStId = p.purchaseStatus;
- await tx.purchases.put(p);
- await ctx.updateTransactionMeta(tx);
- applyNotifyTransition(tx.notify, ctx.transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.Any,
- newStId,
- oldStId,
- });
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const p = await tx.purchases.get(proposalId);
+ if (!p) {
+ logger.warn(`purchase ${proposalId} does not exist anymore`);
+ return;
+ }
+ if (p.purchaseStatus !== PurchaseStatus.Done) {
+ return;
+ }
+ const oldTxState = computePayMerchantTransactionState(p);
+ const oldStId = p.purchaseStatus;
+ p.purchaseStatus = PurchaseStatus.PendingQueryingRefund;
+ const newTxState = computePayMerchantTransactionState(p);
+ const newStId = p.purchaseStatus;
+ await tx.purchases.put(p);
+ await ctx.updateTransactionMeta(tx);
+ applyNotifyTransition(tx.notify, ctx.transactionId, {
+ oldTxState,
+ newTxState,
+ balanceEffect: BalanceEffect.Any,
+ newStId,
+ oldStId,
+ });
+ });
wex.taskScheduler.startShepherdTask(ctx.taskId);
}
diff --git a/packages/taler-wallet-core/src/pay-peer-common.ts b/packages/taler-wallet-core/src/pay-peer-common.ts
@@ -41,35 +41,32 @@ export async function queryCoinInfosForSelection(
csel: DbPeerPushPaymentCoinSelection,
): Promise<SpendCoinDetails[]> {
let infos: SpendCoinDetails[] = [];
- await wex.db.runReadOnlyTx(
- { storeNames: ["coins", "denominations"] },
- async (tx) => {
- for (let i = 0; i < csel.coinPubs.length; i++) {
- const coin = await tx.coins.get(csel.coinPubs[i]);
- if (!coin) {
- throw Error("coin not found anymore");
- }
- const denom = await getDenomInfo(
- wex,
- tx,
- coin.exchangeBaseUrl,
- coin.denomPubHash,
- );
- if (!denom) {
- throw Error("denom for coin not found anymore");
- }
- infos.push({
- coinPriv: coin.coinPriv,
- coinPub: coin.coinPub,
- denomPubHash: coin.denomPubHash,
- denomSig: coin.denomSig,
- ageCommitmentProof: coin.ageCommitmentProof,
- contribution: csel.contributions[i],
- feeDeposit: denom.feeDeposit,
- });
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (let i = 0; i < csel.coinPubs.length; i++) {
+ const coin = await tx.coins.get(csel.coinPubs[i]);
+ if (!coin) {
+ throw Error("coin not found anymore");
}
- },
- );
+ const denom = await getDenomInfo(
+ wex,
+ tx,
+ coin.exchangeBaseUrl,
+ coin.denomPubHash,
+ );
+ if (!denom) {
+ throw Error("denom for coin not found anymore");
+ }
+ infos.push({
+ coinPriv: coin.coinPriv,
+ coinPub: coin.coinPub,
+ denomPubHash: coin.denomPubHash,
+ denomSig: coin.denomSig,
+ ageCommitmentProof: coin.ageCommitmentProof,
+ contribution: csel.contributions[i],
+ feeDeposit: denom.feeDeposit,
+ });
+ }
+ });
return infos;
}
@@ -119,12 +116,9 @@ export async function getTotalPeerPaymentCost(
const exchangeBaseUrl = pcs[0].exchangeBaseUrl;
await updateWithdrawalDenomsForExchange(wex, exchangeBaseUrl);
}
- return wex.db.runReadOnlyTx(
- { storeNames: ["coins", "denominations", "denominationFamilies"] },
- async (tx) => {
- return getTotalPeerPaymentCostInTx(wex, tx, pcs);
- },
- );
+ return wex.runLegacyWalletDbTx(async (tx) => {
+ return getTotalPeerPaymentCostInTx(wex, tx, pcs);
+ });
}
export async function getMergeReserveInfo(
@@ -137,8 +131,7 @@ export async function getMergeReserveInfo(
// due to the async crypto API.
const newReservePair = await wex.cryptoApi.createEddsaKeypair({});
- const mergeReserveRecord: ReserveRecord = await wex.db.runReadWriteTx(
- { storeNames: ["exchanges", "reserves"] },
+ const mergeReserveRecord: ReserveRecord = await wex.runLegacyWalletDbTx(
async (tx) => {
const ex = await tx.exchanges.get(req.exchangeBaseUrl);
checkDbInvariant(!!ex, `no exchange record for ${req.exchangeBaseUrl}`);
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -317,20 +317,9 @@ export class PeerPullCreditTransactionContext implements TransactionContext {
}
async deleteTransaction(): Promise<void> {
- const res = await this.wex.db.runReadWriteTx(
- {
- storeNames: [
- "withdrawalGroups",
- "peerPullCredit",
- "planchets",
- "tombstones",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- await this.deleteTransactionInTx(tx);
- },
- );
+ const res = await this.wex.runLegacyWalletDbTx(async (tx) => {
+ await this.deleteTransactionInTx(tx);
+ });
}
async suspendTransaction(): Promise<void> {
@@ -552,9 +541,8 @@ async function queryPurseForPeerPullCredit(
return TaskRunResult.longpollReturnedPending();
}
- const reserve = await wex.db.runReadOnlyTx(
- { storeNames: ["reserves"] },
- (tx) => tx.reserves.get(pullIni.mergeReserveRowId),
+ const reserve = await wex.runLegacyWalletDbTx((tx) =>
+ tx.reserves.get(pullIni.mergeReserveRowId),
);
if (!reserve) {
@@ -793,17 +781,15 @@ async function processPeerPullCreditCreatePurse(
const purseFee = Amounts.stringify(Amounts.zeroOfAmount(pullIni.amount));
- const mergeReserve = await wex.db.runReadOnlyTx(
- { storeNames: ["reserves"] },
- async (tx) => tx.reserves.get(pullIni.mergeReserveRowId),
+ const mergeReserve = await wex.runLegacyWalletDbTx(async (tx) =>
+ tx.reserves.get(pullIni.mergeReserveRowId),
);
if (!mergeReserve) {
throw Error("merge reserve for peer pull payment not found in database");
}
- const contractTermsRecord = await wex.db.runReadOnlyTx(
- { storeNames: ["contractTerms"] },
- async (tx) => tx.contractTerms.get(pullIni.contractTermsHash),
+ const contractTermsRecord = await wex.runLegacyWalletDbTx(async (tx) =>
+ tx.contractTerms.get(pullIni.contractTermsHash),
);
if (!contractTermsRecord) {
throw Error("contract terms for peer pull payment not found in database");
@@ -905,9 +891,8 @@ export async function processPeerPullCredit(
return TaskRunResult.networkRequired();
}
- const pullIni = await wex.db.runReadOnlyTx(
- { storeNames: ["peerPullCredit"] },
- async (tx) => tx.peerPullCredit.get(pursePub),
+ const pullIni = await wex.runLegacyWalletDbTx(async (tx) =>
+ tx.peerPullCredit.get(pursePub),
);
if (!pullIni) {
throw Error("peer pull payment initiation not found in database");
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
@@ -81,7 +81,11 @@ import {
timestampPreciseFromDb,
timestampPreciseToDb,
} from "./db.js";
-import { fetchFreshExchange, getExchangeScopeInfo, getScopeForAllExchanges } from "./exchanges.js";
+import {
+ fetchFreshExchange,
+ getExchangeScopeInfo,
+ getScopeForAllExchanges,
+} from "./exchanges.js";
import {
getTotalPeerPaymentCost,
isPurseDeposited,
@@ -203,12 +207,9 @@ export class PeerPullDebitTransactionContext implements TransactionContext {
}
async deleteTransaction(): Promise<void> {
- await this.wex.db.runReadWriteTx(
- { storeNames: ["peerPullDebit", "transactionsMeta"] },
- async (tx) => {
- await this.deleteTransactionInTx(tx);
- },
- );
+ await this.wex.runLegacyWalletDbTx(async (tx) => {
+ await this.deleteTransactionInTx(tx);
+ });
}
async deleteTransactionInTx(
@@ -695,9 +696,8 @@ export async function processPeerPullDebit(
return TaskRunResult.networkRequired();
}
- const peerPullInc = await wex.db.runReadOnlyTx(
- { storeNames: ["peerPullDebit"] },
- async (tx) => tx.peerPullDebit.get(peerPullDebitId),
+ const peerPullInc = await wex.runLegacyWalletDbTx(async (tx) =>
+ tx.peerPullDebit.get(peerPullDebitId),
);
if (!peerPullInc) {
throw Error("peer pull debit not found");
@@ -730,9 +730,8 @@ export async function confirmPeerPullDebit(
throw Error("invalid peer-pull-debit transaction identifier");
}
- const peerPullInc = await wex.db.runReadOnlyTx(
- { storeNames: ["peerPullDebit"] },
- async (tx) => tx.peerPullDebit.get(parsed.peerPullDebitId),
+ const peerPullInc = await wex.runLegacyWalletDbTx(async (tx) =>
+ tx.peerPullDebit.get(parsed.peerPullDebitId),
);
if (peerPullInc == null) {
@@ -846,51 +845,40 @@ export async function preparePeerPullDebit(
parsedTxId = parsedRes.peerPullDebitId;
}
- const existing = await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "peerPullDebit",
- "contractTerms",
- "exchangeDetails",
- "exchanges",
- "globalCurrencyAuditors",
- "globalCurrencyExchanges",
- ],
- },
- async (tx) => {
- let peerPullDebitRecord: PeerPullPaymentIncomingRecord | undefined;
- if (uri) {
- peerPullDebitRecord = await tx.peerPullDebit.indexes.byExchangeAndContractPriv.get([
+ const existing = await wex.runLegacyWalletDbTx(async (tx) => {
+ let peerPullDebitRecord: PeerPullPaymentIncomingRecord | undefined;
+ if (uri) {
+ peerPullDebitRecord =
+ await tx.peerPullDebit.indexes.byExchangeAndContractPriv.get([
uri.exchangeBaseUrl,
uri.contractPriv,
]);
- } else if (parsedTxId) {
- peerPullDebitRecord = await tx.peerPullDebit.get(parsedTxId);
- }
- if (!peerPullDebitRecord) {
- return;
- }
- const contractTerms = await tx.contractTerms.get(
- peerPullDebitRecord.contractTermsHash,
- );
- if (!contractTerms) {
- return;
- }
- const currency = Amounts.currencyOf(peerPullDebitRecord.amount);
- const scopeInfo = await getExchangeScopeInfo(
- tx,
- peerPullDebitRecord.exchangeBaseUrl,
- currency,
- );
- return {
- peerPullDebitRecord,
- contractTerms,
- scopeInfo,
- exchangeBaseUrl: peerPullDebitRecord.exchangeBaseUrl,
- rec: peerPullDebitRecord,
- };
- },
- );
+ } else if (parsedTxId) {
+ peerPullDebitRecord = await tx.peerPullDebit.get(parsedTxId);
+ }
+ if (!peerPullDebitRecord) {
+ return;
+ }
+ const contractTerms = await tx.contractTerms.get(
+ peerPullDebitRecord.contractTermsHash,
+ );
+ if (!contractTerms) {
+ return;
+ }
+ const currency = Amounts.currencyOf(peerPullDebitRecord.amount);
+ const scopeInfo = await getExchangeScopeInfo(
+ tx,
+ peerPullDebitRecord.exchangeBaseUrl,
+ currency,
+ );
+ return {
+ peerPullDebitRecord,
+ contractTerms,
+ scopeInfo,
+ exchangeBaseUrl: peerPullDebitRecord.exchangeBaseUrl,
+ rec: peerPullDebitRecord,
+ };
+ });
if (existing) {
return {
diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
@@ -279,17 +279,8 @@ export class PeerPushCreditTransactionContext implements TransactionContext {
}
async deleteTransaction(): Promise<void> {
- await this.wex.db.runReadWriteTx(
- {
- storeNames: [
- "withdrawalGroups",
- "planchets",
- "peerPushCredit",
- "tombstones",
- "transactionsMeta",
- ],
- },
- async (tx) => this.deleteTransactionInTx(tx),
+ await this.wex.runLegacyWalletDbTx(async (tx) =>
+ this.deleteTransactionInTx(tx),
);
}
@@ -497,36 +488,33 @@ export async function preparePeerPushCredit(
parsedTxId = parsedRes.peerPushCreditId;
}
- const existing = await wex.db.runReadOnlyTx(
- { storeNames: ["contractTerms", "peerPushCredit"] },
- async (tx) => {
- let existingPushInc: PeerPushCreditRecord | undefined;
- if (uri) {
- existingPushInc =
- await tx.peerPushCredit.indexes.byExchangeAndContractPriv.get([
- uri.exchangeBaseUrl,
- uri.contractPriv,
- ]);
- } else if (parsedTxId) {
- existingPushInc = await tx.peerPushCredit.get(parsedTxId);
- }
- if (!existingPushInc) {
- return;
- }
- const existingContractTermsRec = await tx.contractTerms.get(
- existingPushInc.contractTermsHash,
- );
- if (!existingContractTermsRec) {
- throw Error(
- "contract terms for peer push payment credit not found in database",
- );
- }
- const existingContractTerms = codecForPeerContractTerms().decode(
- existingContractTermsRec.contractTermsRaw,
+ const existing = await wex.runLegacyWalletDbTx(async (tx) => {
+ let existingPushInc: PeerPushCreditRecord | undefined;
+ if (uri) {
+ existingPushInc =
+ await tx.peerPushCredit.indexes.byExchangeAndContractPriv.get([
+ uri.exchangeBaseUrl,
+ uri.contractPriv,
+ ]);
+ } else if (parsedTxId) {
+ existingPushInc = await tx.peerPushCredit.get(parsedTxId);
+ }
+ if (!existingPushInc) {
+ return;
+ }
+ const existingContractTermsRec = await tx.contractTerms.get(
+ existingPushInc.contractTermsHash,
+ );
+ if (!existingContractTermsRec) {
+ throw Error(
+ "contract terms for peer push payment credit not found in database",
);
- return { existingPushInc, existingContractTerms };
- },
- );
+ }
+ const existingContractTerms = codecForPeerContractTerms().decode(
+ existingContractTermsRec.contractTermsRaw,
+ );
+ return { existingPushInc, existingContractTerms };
+ });
if (existing) {
const exchange = await fetchFreshExchange(
@@ -757,20 +745,17 @@ async function transitionPeerPushCreditKycRequired(
peerInc.peerPushCreditId,
);
- return await wex.db.runReadWriteTx(
- { storeNames: ["peerPushCredit", "transactionsMeta"] },
- async (tx) => {
- const [peerInc, h] = await ctx.getRecordHandle(tx);
- if (!peerInc) {
- return TaskRunResult.finished();
- }
- peerInc.kycPaytoHash = kycPending.h_payto;
- peerInc.status = PeerPushCreditStatus.PendingMergeKycRequired;
- peerInc.kycLastDeny = timestampPreciseToDb(TalerPreciseTimestamp.now());
- await h.update(peerInc);
- return TaskRunResult.progress();
- },
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ const [peerInc, h] = await ctx.getRecordHandle(tx);
+ if (!peerInc) {
+ return TaskRunResult.finished();
+ }
+ peerInc.kycPaytoHash = kycPending.h_payto;
+ peerInc.status = PeerPushCreditStatus.PendingMergeKycRequired;
+ peerInc.kycLastDeny = timestampPreciseToDb(TalerPreciseTimestamp.now());
+ await h.update(peerInc);
+ return TaskRunResult.progress();
+ });
}
async function processPendingMerge(
@@ -1254,23 +1239,20 @@ export async function confirmPeerPushCredit(
logger.trace(`confirming peer-push-credit ${ctx.peerPushCreditId}`);
- const res = await wex.db.runReadOnlyTx(
- { storeNames: ["contractTerms", "peerPushCredit", "transactionsMeta"] },
- async (tx) => {
- const rec = await tx.peerPushCredit.get(ctx.peerPushCreditId);
- if (!rec) {
- return;
- }
- const ct = await tx.contractTerms.get(rec.contractTermsHash);
- if (!ct) {
- return undefined;
- }
- return {
- peerInc: rec,
- contractTerms: ct.contractTermsRaw as PeerContractTerms,
- };
- },
- );
+ const res = await wex.runLegacyWalletDbTx(async (tx) => {
+ const rec = await tx.peerPushCredit.get(ctx.peerPushCreditId);
+ if (!rec) {
+ return;
+ }
+ const ct = await tx.contractTerms.get(rec.contractTermsHash);
+ if (!ct) {
+ return undefined;
+ }
+ return {
+ peerInc: rec,
+ contractTerms: ct.contractTermsRaw as PeerContractTerms,
+ };
+ });
if (!res) {
throw Error(
diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
@@ -215,12 +215,9 @@ export class PeerPushDebitTransactionContext implements TransactionContext {
}
async deleteTransaction(): Promise<void> {
- await this.wex.db.runReadWriteTx(
- { storeNames: ["peerPushDebit", "transactionsMeta"] },
- async (tx) => {
- await this.deleteTransactionInTx(tx);
- },
- );
+ await this.wex.runLegacyWalletDbTx(async (tx) => {
+ await this.deleteTransactionInTx(tx);
+ });
}
async deleteTransactionInTx(
@@ -566,12 +563,9 @@ async function processPeerPushDebitCreateReserve(
logger.trace(`processing ${ctx.transactionId} pending(create-reserve)`);
- const contractTermsRecord = await wex.db.runReadOnlyTx(
- { storeNames: ["contractTerms"] },
- async (tx) => {
- return tx.contractTerms.get(contractTermsHash);
- },
- );
+ const contractTermsRecord = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.contractTerms.get(contractTermsHash);
+ });
if (!contractTermsRecord) {
throw Error(
@@ -971,12 +965,9 @@ export async function processPeerPushDebit(
return TaskRunResult.networkRequired();
}
- const peerPushInitiation = await wex.db.runReadOnlyTx(
- { storeNames: ["peerPushDebit"] },
- async (tx) => {
- return tx.peerPushDebit.get(pursePub);
- },
- );
+ const peerPushInitiation = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.peerPushDebit.get(pursePub);
+ });
if (!peerPushInitiation) {
throw Error("peer push payment not found");
}
diff --git a/packages/taler-wallet-core/src/query.ts b/packages/taler-wallet-core/src/query.ts
@@ -440,7 +440,7 @@ type GetIndexReadOnlyAccess<RecordType, IndexMap> = {
};
interface IndexReadWriteAccessor<RecordType> {
- iter(query: IDBKeyRange | IDBValidKey): ResultStream<RecordType>;
+ iter(query?: IDBKeyRange | IDBValidKey): ResultStream<RecordType>;
get(query: IDBValidKey): Promise<RecordType | undefined>;
getAll(
query?: IDBKeyRange | IDBValidKey,
diff --git a/packages/taler-wallet-core/src/recoup.ts b/packages/taler-wallet-core/src/recoup.ts
@@ -111,28 +111,16 @@ async function recoupRewardCoin(
// We can't really recoup a coin we got via tipping.
// Thus we just put the coin to sleep.
// FIXME: somehow report this to the user
- await wex.db.runReadWriteTx(
- {
- storeNames: [
- "recoupGroups",
- "denominations",
- "refreshGroups",
- "coins",
- "exchanges",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
- if (!recoupGroup) {
- return;
- }
- if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
- return;
- }
- await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
+ if (!recoupGroup) {
+ return;
+ }
+ if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+ return;
+ }
+ await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
+ });
}
async function recoupRefreshCoin(
@@ -142,21 +130,18 @@ async function recoupRefreshCoin(
coin: CoinRecord,
cs: RefreshCoinSource,
): Promise<void> {
- const d = await wex.db.runReadOnlyTx(
- { storeNames: ["coins", "denominations"] },
- async (tx) => {
- const denomInfo = await getDenomInfo(
- wex,
- tx,
- coin.exchangeBaseUrl,
- coin.denomPubHash,
- );
- if (!denomInfo) {
- return;
- }
- return { denomInfo };
- },
- );
+ const d = await wex.runLegacyWalletDbTx(async (tx) => {
+ const denomInfo = await getDenomInfo(
+ wex,
+ tx,
+ coin.exchangeBaseUrl,
+ coin.denomPubHash,
+ );
+ if (!denomInfo) {
+ return;
+ }
+ return { denomInfo };
+ });
if (!d) {
// FIXME: We should at least emit some pending operation / warning for this?
return;
@@ -189,66 +174,54 @@ async function recoupRefreshCoin(
throw Error(`Coin's oldCoinPub doesn't match reserve on recoup`);
}
- await wex.db.runReadWriteTx(
- {
- storeNames: [
- "coins",
- "denominations",
- "recoupGroups",
- "refreshGroups",
- "transactionsMeta",
- "exchanges",
- ],
- },
- async (tx) => {
- const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
- if (!recoupGroup) {
- return;
- }
- if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
- return;
- }
- const oldCoin = await tx.coins.get(cs.oldCoinPub);
- const revokedCoin = await tx.coins.get(coin.coinPub);
- if (!revokedCoin) {
- logger.warn("revoked coin for recoup not found");
- return;
- }
- if (!oldCoin) {
- logger.warn("refresh old coin for recoup not found");
- return;
- }
- const oldCoinDenom = await getDenomInfo(
- wex,
- tx,
- oldCoin.exchangeBaseUrl,
- oldCoin.denomPubHash,
- );
- const revokedCoinDenom = await getDenomInfo(
- wex,
- tx,
- revokedCoin.exchangeBaseUrl,
- revokedCoin.denomPubHash,
- );
- checkDbInvariant(
- !!oldCoinDenom,
- `no denom for coin, hash ${oldCoin.denomPubHash}`,
- );
- checkDbInvariant(
- !!revokedCoinDenom,
- `no revoked denom for coin, hash ${revokedCoin.denomPubHash}`,
- );
- revokedCoin.status = CoinStatus.Dormant;
- // FIXME: Schedule recoup for the sum of refreshes, based on the coin event history.
- // recoupGroup.scheduleRefreshCoins.push({
- // coinPub: oldCoin.coinPub,
- // amount: Amounts.stringify(refreshAmount),
- // });
- await tx.coins.put(revokedCoin);
- await tx.coins.put(oldCoin);
- await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
+ if (!recoupGroup) {
+ return;
+ }
+ if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+ return;
+ }
+ const oldCoin = await tx.coins.get(cs.oldCoinPub);
+ const revokedCoin = await tx.coins.get(coin.coinPub);
+ if (!revokedCoin) {
+ logger.warn("revoked coin for recoup not found");
+ return;
+ }
+ if (!oldCoin) {
+ logger.warn("refresh old coin for recoup not found");
+ return;
+ }
+ const oldCoinDenom = await getDenomInfo(
+ wex,
+ tx,
+ oldCoin.exchangeBaseUrl,
+ oldCoin.denomPubHash,
+ );
+ const revokedCoinDenom = await getDenomInfo(
+ wex,
+ tx,
+ revokedCoin.exchangeBaseUrl,
+ revokedCoin.denomPubHash,
+ );
+ checkDbInvariant(
+ !!oldCoinDenom,
+ `no denom for coin, hash ${oldCoin.denomPubHash}`,
+ );
+ checkDbInvariant(
+ !!revokedCoinDenom,
+ `no revoked denom for coin, hash ${revokedCoin.denomPubHash}`,
+ );
+ revokedCoin.status = CoinStatus.Dormant;
+ // FIXME: Schedule recoup for the sum of refreshes, based on the coin event history.
+ // recoupGroup.scheduleRefreshCoins.push({
+ // coinPub: oldCoin.coinPub,
+ // amount: Amounts.stringify(refreshAmount),
+ // });
+ await tx.coins.put(revokedCoin);
+ await tx.coins.put(oldCoin);
+ await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
+ });
}
export async function recoupWithdrawCoin(
@@ -259,18 +232,15 @@ export async function recoupWithdrawCoin(
cs: WithdrawCoinSource,
): Promise<void> {
const reservePub = cs.reservePub;
- const denomInfo = await wex.db.runReadOnlyTx(
- { storeNames: ["denominations"] },
- async (tx) => {
- const denomInfo = await getDenomInfo(
- wex,
- tx,
- coin.exchangeBaseUrl,
- coin.denomPubHash,
- );
- return denomInfo;
- },
- );
+ const denomInfo = await wex.runLegacyWalletDbTx(async (tx) => {
+ const denomInfo = await getDenomInfo(
+ wex,
+ tx,
+ coin.exchangeBaseUrl,
+ coin.denomPubHash,
+ );
+ return denomInfo;
+ });
if (!denomInfo) {
// FIXME: We should at least emit some pending operation / warning for this?
return;
@@ -302,34 +272,22 @@ export async function recoupWithdrawCoin(
}
// FIXME: verify that our expectations about the amount match
- await wex.db.runReadWriteTx(
- {
- storeNames: [
- "coins",
- "denominations",
- "recoupGroups",
- "refreshGroups",
- "transactionsMeta",
- "exchanges",
- ],
- },
- async (tx) => {
- const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
- if (!recoupGroup) {
- return;
- }
- if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
- return;
- }
- const updatedCoin = await tx.coins.get(coin.coinPub);
- if (!updatedCoin) {
- return;
- }
- updatedCoin.status = CoinStatus.Dormant;
- await tx.coins.put(updatedCoin);
- await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
+ if (!recoupGroup) {
+ return;
+ }
+ if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+ return;
+ }
+ const updatedCoin = await tx.coins.get(coin.coinPub);
+ if (!updatedCoin) {
+ return;
+ }
+ updatedCoin.status = CoinStatus.Dormant;
+ await tx.coins.put(updatedCoin);
+ await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
+ });
}
export async function processRecoupGroup(
@@ -340,12 +298,9 @@ export async function processRecoupGroup(
return TaskRunResult.networkRequired();
}
- let recoupGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["recoupGroups"] },
- async (tx) => {
- return tx.recoupGroups.get(recoupGroupId);
- },
- );
+ let recoupGroup = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.recoupGroups.get(recoupGroupId);
+ });
if (!recoupGroup) {
return TaskRunResult.finished();
}
@@ -363,12 +318,9 @@ export async function processRecoupGroup(
});
await Promise.all(ps);
- recoupGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["recoupGroups"] },
- async (tx) => {
- return tx.recoupGroups.get(recoupGroupId);
- },
- );
+ recoupGroup = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.recoupGroups.get(recoupGroupId);
+ });
if (!recoupGroup) {
return TaskRunResult.finished();
}
@@ -385,25 +337,22 @@ export async function processRecoupGroup(
const reservePrivMap: Record<string, string> = {};
for (let i = 0; i < recoupGroup.coinPubs.length; i++) {
const coinPub = recoupGroup.coinPubs[i];
- await wex.db.runReadOnlyTx(
- { storeNames: ["coins", "reserves"] },
- async (tx) => {
- const coin = await tx.coins.get(coinPub);
- if (!coin) {
- throw Error(`Coin ${coinPub} not found, can't request recoup`);
- }
- if (coin.coinSource.type === CoinSourceType.Withdraw) {
- const reserve = await tx.reserves.indexes.byReservePub.get(
- coin.coinSource.reservePub,
- );
- if (!reserve) {
- return;
- }
- reserveSet.add(coin.coinSource.reservePub);
- reservePrivMap[coin.coinSource.reservePub] = reserve.reservePriv;
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const coin = await tx.coins.get(coinPub);
+ if (!coin) {
+ throw Error(`Coin ${coinPub} not found, can't request recoup`);
+ }
+ if (coin.coinSource.type === CoinSourceType.Withdraw) {
+ const reserve = await tx.reserves.indexes.byReservePub.get(
+ coin.coinSource.reservePub,
+ );
+ if (!reserve) {
+ return;
}
- },
- );
+ reserveSet.add(coin.coinSource.reservePub);
+ reservePrivMap[coin.coinSource.reservePub] = reserve.reservePriv;
+ }
+ });
}
for (const reservePub of reserveSet) {
@@ -435,45 +384,29 @@ export async function processRecoupGroup(
const ctx = new RecoupTransactionContext(wex, recoupGroupId);
- await wex.db.runReadWriteTx(
- {
- storeNames: [
- "coinAvailability",
- "coinHistory",
- "coins",
- "denominations",
- "denominationFamilies",
- "recoupGroups",
- "refreshGroups",
- "refreshSessions",
- "transactionsMeta",
- "exchanges",
- ],
- },
- async (tx) => {
- const rg2 = await tx.recoupGroups.get(recoupGroupId);
- if (!rg2) {
- return;
- }
- rg2.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now());
- rg2.operationStatus = RecoupOperationStatus.Finished;
- if (rg2.scheduleRefreshCoins.length > 0) {
- await createRefreshGroup(
- wex,
- tx,
- Amounts.currencyOf(rg2.scheduleRefreshCoins[0].amount),
- rg2.scheduleRefreshCoins,
- RefreshReason.Recoup,
- constructTransactionIdentifier({
- tag: TransactionType.Recoup,
- recoupGroupId: rg2.recoupGroupId,
- }),
- );
- }
- await tx.recoupGroups.put(rg2);
- await ctx.updateTransactionMeta(tx);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const rg2 = await tx.recoupGroups.get(recoupGroupId);
+ if (!rg2) {
+ return;
+ }
+ rg2.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now());
+ rg2.operationStatus = RecoupOperationStatus.Finished;
+ if (rg2.scheduleRefreshCoins.length > 0) {
+ await createRefreshGroup(
+ wex,
+ tx,
+ Amounts.currencyOf(rg2.scheduleRefreshCoins[0].amount),
+ rg2.scheduleRefreshCoins,
+ RefreshReason.Recoup,
+ constructTransactionIdentifier({
+ tag: TransactionType.Recoup,
+ recoupGroupId: rg2.recoupGroupId,
+ }),
+ );
+ }
+ await tx.recoupGroups.put(rg2);
+ await ctx.updateTransactionMeta(tx);
+ });
return TaskRunResult.finished();
}
@@ -536,19 +469,9 @@ export class RecoupTransactionContext implements TransactionContext {
}
async deleteTransaction(): Promise<void> {
- const res = await this.wex.db.runReadWriteTx(
- {
- storeNames: [
- "recoupGroups",
- "tombstones",
- "exchanges",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- return this.deleteTransactionInTx(tx);
- },
- );
+ const res = await this.wex.runLegacyWalletDbTx(async (tx) => {
+ return this.deleteTransactionInTx(tx);
+ });
for (const notif of res.notifs) {
this.wex.ws.notify(notif);
}
@@ -631,29 +554,26 @@ async function processRecoupForCoin(
recoupGroupId: string,
coinIdx: number,
): Promise<void> {
- const coin = await wex.db.runReadOnlyTx(
- { storeNames: ["coins", "recoupGroups"] },
- async (tx) => {
- const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
- if (!recoupGroup) {
- return;
- }
- if (recoupGroup.timestampFinished) {
- return;
- }
- if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
- return;
- }
+ const coin = await wex.runLegacyWalletDbTx(async (tx) => {
+ const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
+ if (!recoupGroup) {
+ return;
+ }
+ if (recoupGroup.timestampFinished) {
+ return;
+ }
+ if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+ return;
+ }
- const coinPub = recoupGroup.coinPubs[coinIdx];
+ const coinPub = recoupGroup.coinPubs[coinIdx];
- const coin = await tx.coins.get(coinPub);
- if (!coin) {
- throw Error(`Coin ${coinPub} not found, can't request recoup`);
- }
- return coin;
- },
- );
+ const coin = await tx.coins.get(coinPub);
+ if (!coin) {
+ throw Error(`Coin ${coinPub} not found, can't request recoup`);
+ }
+ return coin;
+ });
if (!coin) {
return;
diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts
@@ -611,68 +611,55 @@ async function refreshMelt(
coinIndex: number,
): Promise<void> {
const ctx = new RefreshTransactionContext(wex, refreshGroupId);
- const d = await wex.db.runReadWriteTx(
- {
- storeNames: [
- "refreshGroups",
- "refreshSessions",
- "coins",
- "denominations",
- ],
- },
- async (tx) => {
- const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
- if (!refreshGroup) {
- return;
- }
- const refreshSession = await tx.refreshSessions.get([
- refreshGroupId,
- coinIndex,
- ]);
- if (!refreshSession) {
- return;
- }
- if (refreshSession.norevealIndex !== undefined) {
- return;
- }
+ const d = await wex.runLegacyWalletDbTx(async (tx) => {
+ const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
+ if (!refreshGroup) {
+ return;
+ }
+ const refreshSession = await tx.refreshSessions.get([
+ refreshGroupId,
+ coinIndex,
+ ]);
+ if (!refreshSession) {
+ return;
+ }
+ if (refreshSession.norevealIndex !== undefined) {
+ return;
+ }
- const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
- checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
- const oldDenom = await getDenomInfo(
+ const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
+ checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
+ const oldDenom = await getDenomInfo(
+ wex,
+ tx,
+ oldCoin.exchangeBaseUrl,
+ oldCoin.denomPubHash,
+ );
+ checkDbInvariant(!!oldDenom, "denomination for melted coin doesn't exist");
+
+ const newCoinDenoms: RefreshNewDenomInfo[] = [];
+
+ for (const dh of refreshSession.newDenoms) {
+ const newDenom = await getDenomInfo(
wex,
tx,
oldCoin.exchangeBaseUrl,
- oldCoin.denomPubHash,
+ dh.denomPubHash,
);
checkDbInvariant(
- !!oldDenom,
- "denomination for melted coin doesn't exist",
+ !!newDenom,
+ "new denomination for refresh not in database",
);
-
- const newCoinDenoms: RefreshNewDenomInfo[] = [];
-
- for (const dh of refreshSession.newDenoms) {
- const newDenom = await getDenomInfo(
- wex,
- tx,
- oldCoin.exchangeBaseUrl,
- dh.denomPubHash,
- );
- checkDbInvariant(
- !!newDenom,
- "new denomination for refresh not in database",
- );
- newCoinDenoms.push({
- count: dh.count,
- denomPub: newDenom.denomPub,
- denomPubHash: newDenom.denomPubHash,
- feeWithdraw: newDenom.feeWithdraw,
- value: Amounts.stringify(newDenom.value),
- });
- }
- return { newCoinDenoms, oldCoin, oldDenom, refreshGroup, refreshSession };
- },
- );
+ newCoinDenoms.push({
+ count: dh.count,
+ denomPub: newDenom.denomPub,
+ denomPubHash: newDenom.denomPubHash,
+ feeWithdraw: newDenom.feeWithdraw,
+ value: Amounts.stringify(newDenom.value),
+ });
+ }
+ return { newCoinDenoms, oldCoin, oldDenom, refreshGroup, refreshSession };
+ });
if (!d) {
return;
@@ -689,26 +676,21 @@ async function refreshMelt(
throw Error("unsupported exchange version");
}
const seed = encodeCrock(getRandomBytes(64));
- const updatedSession = await wex.db.runReadWriteTx(
- {
- storeNames: ["refreshSessions"],
- },
- async (tx) => {
- const refreshSession = await tx.refreshSessions.get([
- refreshGroupId,
- coinIndex,
- ]);
- if (!refreshSession) {
- return undefined;
- }
- if (refreshSession.sessionPublicSeed != null) {
- return refreshSession;
- }
- refreshSession.sessionPublicSeed = seed;
- await tx.refreshSessions.put(refreshSession);
+ const updatedSession = await wex.runLegacyWalletDbTx(async (tx) => {
+ const refreshSession = await tx.refreshSessions.get([
+ refreshGroupId,
+ coinIndex,
+ ]);
+ if (!refreshSession) {
+ return undefined;
+ }
+ if (refreshSession.sessionPublicSeed != null) {
return refreshSession;
- },
- );
+ }
+ refreshSession.sessionPublicSeed = seed;
+ await tx.refreshSessions.put(refreshSession);
+ return refreshSession;
+ });
if (!updatedSession) {
throw Error("Could not update refresh session (concurrent deletion?).");
}
@@ -814,27 +796,24 @@ async function refreshMelt(
refreshSession.norevealIndex = norevealIndex;
- await wex.db.runReadWriteTx(
- { storeNames: ["refreshGroups", "refreshSessions"] },
- async (tx) => {
- const rg = await tx.refreshGroups.get(refreshGroupId);
- if (!rg) {
- return;
- }
- if (rg.timestampFinished) {
- return;
- }
- const rs = await tx.refreshSessions.get([refreshGroupId, coinIndex]);
- if (!rs) {
- return;
- }
- if (rs.norevealIndex !== undefined) {
- return;
- }
- rs.norevealIndex = norevealIndex;
- await tx.refreshSessions.put(rs);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const rg = await tx.refreshGroups.get(refreshGroupId);
+ if (!rg) {
+ return;
+ }
+ if (rg.timestampFinished) {
+ return;
+ }
+ const rs = await tx.refreshSessions.get([refreshGroupId, coinIndex]);
+ if (!rs) {
+ return;
+ }
+ if (rs.norevealIndex !== undefined) {
+ return;
+ }
+ rs.norevealIndex = norevealIndex;
+ await tx.refreshSessions.put(rs);
+ });
}
/**
@@ -856,41 +835,29 @@ async function handleRefreshMeltGone(
target: ctx.transactionId,
});
- await ctx.wex.db.runReadWriteTx(
- {
- storeNames: [
- "refreshGroups",
- "refreshSessions",
- "coins",
- "denominations",
- "coinAvailability",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- const [rg, h] = await ctx.getRecordHandle(tx);
- if (!rg) {
- return;
- }
- if (rg.timestampFinished) {
- return;
- }
- if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
- return;
- }
- rg.statusPerCoin[coinIndex] = RefreshCoinStatus.PendingRedenominate;
- const refreshSession = await tx.refreshSessions.get([
- ctx.refreshGroupId,
- coinIndex,
- ]);
- if (!refreshSession) {
- throw Error("db invariant failed: missing refresh session in database");
- }
- refreshSession.lastError = errDetails;
- await tx.refreshSessions.put(refreshSession);
- await h.update(rg);
- },
- );
+ await ctx.wex.runLegacyWalletDbTx(async (tx) => {
+ const [rg, h] = await ctx.getRecordHandle(tx);
+ if (!rg) {
+ return;
+ }
+ if (rg.timestampFinished) {
+ return;
+ }
+ if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
+ return;
+ }
+ rg.statusPerCoin[coinIndex] = RefreshCoinStatus.PendingRedenominate;
+ const refreshSession = await tx.refreshSessions.get([
+ ctx.refreshGroupId,
+ coinIndex,
+ ]);
+ if (!refreshSession) {
+ throw Error("db invariant failed: missing refresh session in database");
+ }
+ refreshSession.lastError = errDetails;
+ await tx.refreshSessions.put(refreshSession);
+ await h.update(rg);
+ });
}
async function handleRefreshMeltConflict(
@@ -922,19 +889,14 @@ async function handleRefreshMeltConflict(
});
switch (httpResp.status) {
case HttpStatusCode.Ok:
- await ctx.wex.db.runReadWriteTx(
- {
- storeNames: ["refreshGroups"],
- },
- async (tx) => {
- const rg = await tx.refreshGroups.get(refreshGroup.refreshGroupId);
- if (!rg || rg.operationStatus != RefreshOperationStatus.Pending) {
- return;
- }
- delete rg.refundRequests[coinIndex];
- await tx.refreshGroups.put(rg);
- },
- );
+ await ctx.wex.runLegacyWalletDbTx(async (tx) => {
+ const rg = await tx.refreshGroups.get(refreshGroup.refreshGroupId);
+ if (!rg || rg.operationStatus != RefreshOperationStatus.Pending) {
+ return;
+ }
+ delete rg.refundRequests[coinIndex];
+ await tx.refreshGroups.put(rg);
+ });
break;
default:
// FIXME: Store the error somewhere in the DB?
@@ -973,61 +935,44 @@ async function handleRefreshMeltConflict(
// FIXME: If response seems wrong, report to auditor (in the future!);
- await ctx.wex.db.runReadWriteTx(
- {
- storeNames: [
- "refreshGroups",
- "refreshSessions",
- "denominations",
- "denominationFamilies",
- "coins",
- "coinAvailability",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- const [rg, h] = await ctx.getRecordHandle(tx);
- if (!rg) {
- return;
- }
- if (rg.timestampFinished) {
- return;
- }
- if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
- return;
+ await ctx.wex.runLegacyWalletDbTx(async (tx) => {
+ const [rg, h] = await ctx.getRecordHandle(tx);
+ if (!rg) {
+ return;
+ }
+ if (rg.timestampFinished) {
+ return;
+ }
+ if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
+ return;
+ }
+ if (Amounts.isZero(historyJson.balance)) {
+ rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Failed;
+ const refreshSession = await tx.refreshSessions.get([
+ ctx.refreshGroupId,
+ coinIndex,
+ ]);
+ if (!refreshSession) {
+ throw Error("db invariant failed: missing refresh session in database");
}
- if (Amounts.isZero(historyJson.balance)) {
- rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Failed;
- const refreshSession = await tx.refreshSessions.get([
- ctx.refreshGroupId,
- coinIndex,
- ]);
- if (!refreshSession) {
- throw Error(
- "db invariant failed: missing refresh session in database",
- );
- }
- refreshSession.lastError = errDetails;
- await tx.refreshSessions.put(refreshSession);
- await h.update(rg);
- } else {
- // Try again with new denoms!
- rg.inputPerCoin[coinIndex] = historyJson.balance;
- const refreshSession = await tx.refreshSessions.get([
- ctx.refreshGroupId,
- coinIndex,
- ]);
- if (!refreshSession) {
- throw Error(
- "db invariant failed: missing refresh session in database",
- );
- }
- await destroyRefreshSession(ctx.wex, tx, rg, refreshSession);
- await tx.refreshSessions.delete([ctx.refreshGroupId, coinIndex]);
- await initRefreshSession(ctx.wex, tx, rg, coinIndex);
+ refreshSession.lastError = errDetails;
+ await tx.refreshSessions.put(refreshSession);
+ await h.update(rg);
+ } else {
+ // Try again with new denoms!
+ rg.inputPerCoin[coinIndex] = historyJson.balance;
+ const refreshSession = await tx.refreshSessions.get([
+ ctx.refreshGroupId,
+ coinIndex,
+ ]);
+ if (!refreshSession) {
+ throw Error("db invariant failed: missing refresh session in database");
}
- },
- );
+ await destroyRefreshSession(ctx.wex, tx, rg, refreshSession);
+ await tx.refreshSessions.delete([ctx.refreshGroupId, coinIndex]);
+ await initRefreshSession(ctx.wex, tx, rg, coinIndex);
+ }
+ });
}
async function handleRefreshMeltNotFound(
@@ -1045,42 +990,30 @@ async function handleRefreshMeltNotFound(
default:
throwUnexpectedRequestError(resp, errDetails);
}
- await ctx.wex.db.runReadWriteTx(
- {
- storeNames: [
- "refreshGroups",
- "refreshSessions",
- "coins",
- "denominations",
- "coinAvailability",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- const [rg, h] = await ctx.getRecordHandle(tx);
- if (!rg) {
- return;
- }
- if (rg.timestampFinished) {
- return;
- }
- if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
- return;
- }
- rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Failed;
- const refreshSession = await tx.refreshSessions.get([
- ctx.refreshGroupId,
- coinIndex,
- ]);
- if (!refreshSession) {
- throw Error("db invariant failed: missing refresh session in database");
- }
- refreshSession.lastError = errDetails;
- await tx.refreshSessions.put(refreshSession);
- await destroyRefreshSession(ctx.wex, tx, rg, refreshSession);
- await h.update(rg);
- },
- );
+ await ctx.wex.runLegacyWalletDbTx(async (tx) => {
+ const [rg, h] = await ctx.getRecordHandle(tx);
+ if (!rg) {
+ return;
+ }
+ if (rg.timestampFinished) {
+ return;
+ }
+ if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
+ return;
+ }
+ rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Failed;
+ const refreshSession = await tx.refreshSessions.get([
+ ctx.refreshGroupId,
+ coinIndex,
+ ]);
+ if (!refreshSession) {
+ throw Error("db invariant failed: missing refresh session in database");
+ }
+ refreshSession.lastError = errDetails;
+ await tx.refreshSessions.put(refreshSession);
+ await destroyRefreshSession(ctx.wex, tx, rg, refreshSession);
+ await h.update(rg);
+ });
}
async function refreshReveal(
@@ -1092,76 +1025,63 @@ async function refreshReveal(
`doing refresh reveal for ${refreshGroupId} (old coin ${coinIndex})`,
);
const ctx = new RefreshTransactionContext(wex, refreshGroupId);
- const d = await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "refreshGroups",
- "refreshSessions",
- "coins",
- "denominations",
- ],
- },
- async (tx) => {
- const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
- if (!refreshGroup) {
- return;
- }
- const refreshSession = await tx.refreshSessions.get([
- refreshGroupId,
- coinIndex,
- ]);
- if (!refreshSession) {
- return;
- }
- const norevealIndex = refreshSession.norevealIndex;
- if (norevealIndex === undefined) {
- throw Error("can't reveal without melting first");
- }
+ const d = await wex.runLegacyWalletDbTx(async (tx) => {
+ const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
+ if (!refreshGroup) {
+ return;
+ }
+ const refreshSession = await tx.refreshSessions.get([
+ refreshGroupId,
+ coinIndex,
+ ]);
+ if (!refreshSession) {
+ return;
+ }
+ const norevealIndex = refreshSession.norevealIndex;
+ if (norevealIndex === undefined) {
+ throw Error("can't reveal without melting first");
+ }
+
+ const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
+ checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
+ const oldDenom = await getDenomInfo(
+ wex,
+ tx,
+ oldCoin.exchangeBaseUrl,
+ oldCoin.denomPubHash,
+ );
+ checkDbInvariant(!!oldDenom, "denomination for melted coin doesn't exist");
+
+ const newCoinDenoms: RefreshNewDenomInfo[] = [];
- const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
- checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
- const oldDenom = await getDenomInfo(
+ for (const dh of refreshSession.newDenoms) {
+ const newDenom = await getDenomInfo(
wex,
tx,
oldCoin.exchangeBaseUrl,
- oldCoin.denomPubHash,
+ dh.denomPubHash,
);
checkDbInvariant(
- !!oldDenom,
- "denomination for melted coin doesn't exist",
+ !!newDenom,
+ "new denomination for refresh not in database",
);
-
- const newCoinDenoms: RefreshNewDenomInfo[] = [];
-
- for (const dh of refreshSession.newDenoms) {
- const newDenom = await getDenomInfo(
- wex,
- tx,
- oldCoin.exchangeBaseUrl,
- dh.denomPubHash,
- );
- checkDbInvariant(
- !!newDenom,
- "new denomination for refresh not in database",
- );
- newCoinDenoms.push({
- count: dh.count,
- denomPub: newDenom.denomPub,
- denomPubHash: newDenom.denomPubHash,
- feeWithdraw: newDenom.feeWithdraw,
- value: Amounts.stringify(newDenom.value),
- });
- }
- return {
- oldCoin,
- oldDenom,
- newCoinDenoms,
- refreshSession,
- refreshGroup,
- norevealIndex,
- };
- },
- );
+ newCoinDenoms.push({
+ count: dh.count,
+ denomPub: newDenom.denomPub,
+ denomPubHash: newDenom.denomPubHash,
+ feeWithdraw: newDenom.feeWithdraw,
+ value: Amounts.stringify(newDenom.value),
+ });
+ }
+ return {
+ oldCoin,
+ oldDenom,
+ newCoinDenoms,
+ refreshSession,
+ refreshGroup,
+ norevealIndex,
+ };
+ });
if (!d) {
return;
@@ -1270,65 +1190,50 @@ async function refreshReveal(
}
}
- await wex.db.runReadWriteTx(
- {
- storeNames: [
- "coins",
- "denominations",
- "coinAvailability",
- "refreshGroups",
- "refreshSessions",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- const [rg, h] = await ctx.getRecordHandle(tx);
- if (!rg) {
- logger.warn("no refresh session found");
- return;
- }
- if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
- return;
- }
- const rs = await tx.refreshSessions.get([refreshGroupId, coinIndex]);
- if (!rs) {
- return;
- }
- rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished;
- for (const coin of coins) {
- const existingCoin = await tx.coins.get(coin.coinPub);
- if (existingCoin) {
- continue;
- }
- await tx.coins.add(coin);
- const denomInfo = await getDenomInfo(
- wex,
- tx,
- coin.exchangeBaseUrl,
- coin.denomPubHash,
- );
- checkDbInvariant(
- !!denomInfo,
- `no denom with hash ${coin.denomPubHash}`,
- );
- const car = await getCoinAvailabilityForDenom(
- wex,
- tx,
- denomInfo,
- coin.maxAge,
- );
- checkDbInvariant(
- car.pendingRefreshOutputCount != null &&
- car.pendingRefreshOutputCount > 0,
- `no pendingRefreshOutputCount for denom ${coin.denomPubHash} age ${coin.maxAge}`,
- );
- car.pendingRefreshOutputCount--;
- car.freshCoinCount++;
- await tx.coinAvailability.put(car);
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const [rg, h] = await ctx.getRecordHandle(tx);
+ if (!rg) {
+ logger.warn("no refresh session found");
+ return;
+ }
+ if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
+ return;
+ }
+ const rs = await tx.refreshSessions.get([refreshGroupId, coinIndex]);
+ if (!rs) {
+ return;
+ }
+ rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished;
+ for (const coin of coins) {
+ const existingCoin = await tx.coins.get(coin.coinPub);
+ if (existingCoin) {
+ continue;
}
- await h.update(rg);
- },
- );
+ await tx.coins.add(coin);
+ const denomInfo = await getDenomInfo(
+ wex,
+ tx,
+ coin.exchangeBaseUrl,
+ coin.denomPubHash,
+ );
+ checkDbInvariant(!!denomInfo, `no denom with hash ${coin.denomPubHash}`);
+ const car = await getCoinAvailabilityForDenom(
+ wex,
+ tx,
+ denomInfo,
+ coin.maxAge,
+ );
+ checkDbInvariant(
+ car.pendingRefreshOutputCount != null &&
+ car.pendingRefreshOutputCount > 0,
+ `no pendingRefreshOutputCount for denom ${coin.denomPubHash} age ${coin.maxAge}`,
+ );
+ car.pendingRefreshOutputCount--;
+ car.freshCoinCount++;
+ await tx.coinAvailability.put(car);
+ }
+ await h.update(rg);
+ });
logger.trace("refresh finished (end of reveal)");
}
@@ -1337,42 +1242,30 @@ async function handleRefreshRevealError(
coinIndex: number,
errDetails: TalerErrorDetail,
): Promise<void> {
- await ctx.wex.db.runReadWriteTx(
- {
- storeNames: [
- "refreshGroups",
- "refreshSessions",
- "coins",
- "denominations",
- "coinAvailability",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- const [rg, h] = await ctx.getRecordHandle(tx);
- if (!rg) {
- return;
- }
- if (rg.timestampFinished) {
- return;
- }
- if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
- return;
- }
- rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Failed;
- const refreshSession = await tx.refreshSessions.get([
- ctx.refreshGroupId,
- coinIndex,
- ]);
- if (!refreshSession) {
- throw Error("db invariant failed: missing refresh session in database");
- }
- refreshSession.lastError = errDetails;
- await destroyRefreshSession(ctx.wex, tx, rg, refreshSession);
- await tx.refreshSessions.put(refreshSession);
- await h.update(rg);
- },
- );
+ await ctx.wex.runLegacyWalletDbTx(async (tx) => {
+ const [rg, h] = await ctx.getRecordHandle(tx);
+ if (!rg) {
+ return;
+ }
+ if (rg.timestampFinished) {
+ return;
+ }
+ if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
+ return;
+ }
+ rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Failed;
+ const refreshSession = await tx.refreshSessions.get([
+ ctx.refreshGroupId,
+ coinIndex,
+ ]);
+ if (!refreshSession) {
+ throw Error("db invariant failed: missing refresh session in database");
+ }
+ refreshSession.lastError = errDetails;
+ await destroyRefreshSession(ctx.wex, tx, rg, refreshSession);
+ await tx.refreshSessions.put(refreshSession);
+ await h.update(rg);
+ });
}
export async function processRefreshGroup(
@@ -1385,9 +1278,8 @@ export async function processRefreshGroup(
logger.trace(`processing refresh group ${refreshGroupId}`);
- const refreshGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["refreshGroups"] },
- async (tx) => tx.refreshGroups.get(refreshGroupId),
+ const refreshGroup = await wex.runLegacyWalletDbTx(async (tx) =>
+ tx.refreshGroups.get(refreshGroupId),
);
if (!refreshGroup) {
return TaskRunResult.finished();
@@ -1476,54 +1368,43 @@ export async function processRefreshGroup(
// We've processed all refresh session and can now update the
// status of the whole refresh group.
- const didTransition: boolean = await wex.db.runReadWriteTx(
- {
- storeNames: [
- "coins",
- "coinAvailability",
- "refreshGroups",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- const [rg, h] = await ctx.getRecordHandle(tx);
- if (!rg) {
+ const didTransition: boolean = await wex.runLegacyWalletDbTx(async (tx) => {
+ const [rg, h] = await ctx.getRecordHandle(tx);
+ if (!rg) {
+ return false;
+ }
+ switch (rg.operationStatus) {
+ case RefreshOperationStatus.Pending:
+ break;
+ default:
return false;
+ }
+ const allFinal = fnutil.all(
+ rg.statusPerCoin,
+ (x) => x === RefreshCoinStatus.Finished || x === RefreshCoinStatus.Failed,
+ );
+ const anyFailed = fnutil.any(
+ rg.statusPerCoin,
+ (x) => x === RefreshCoinStatus.Failed,
+ );
+ if (allFinal) {
+ if (anyFailed) {
+ rg.timestampFinished = timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ );
+ rg.operationStatus = RefreshOperationStatus.Failed;
+ } else {
+ rg.timestampFinished = timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ );
+ rg.operationStatus = RefreshOperationStatus.Finished;
}
- switch (rg.operationStatus) {
- case RefreshOperationStatus.Pending:
- break;
- default:
- return false;
- }
- const allFinal = fnutil.all(
- rg.statusPerCoin,
- (x) =>
- x === RefreshCoinStatus.Finished || x === RefreshCoinStatus.Failed,
- );
- const anyFailed = fnutil.any(
- rg.statusPerCoin,
- (x) => x === RefreshCoinStatus.Failed,
- );
- if (allFinal) {
- if (anyFailed) {
- rg.timestampFinished = timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- );
- rg.operationStatus = RefreshOperationStatus.Failed;
- } else {
- rg.timestampFinished = timestampPreciseToDb(
- TalerPreciseTimestamp.now(),
- );
- rg.operationStatus = RefreshOperationStatus.Finished;
- }
- await makeCoinsVisible(wex, tx, ctx.transactionId);
- await h.update(rg);
- return true;
- }
- return false;
- },
- );
+ await makeCoinsVisible(wex, tx, ctx.transactionId);
+ await h.update(rg);
+ return true;
+ }
+ return false;
+ });
if (didTransition) {
return TaskRunResult.progress();
@@ -1987,7 +1868,7 @@ export function getRefreshesForTransaction(
wex: WalletExecutionContext,
transactionId: string,
): Promise<string[]> {
- return wex.db.runReadOnlyTx({ storeNames: ["refreshGroups"] }, async (tx) => {
+ return wex.runLegacyWalletDbTx(async (tx) => {
const groups =
await tx.refreshGroups.indexes.byOriginatingTransactionId.getAll(
transactionId,
@@ -2055,14 +1936,11 @@ export async function waitRefreshFinal(
await genericWaitForState(wex, {
async checkState(): Promise<boolean> {
// Check if refresh is final
- const res = await ctx.wex.db.runReadOnlyTx(
- { storeNames: ["refreshGroups"] },
- async (tx) => {
- return {
- rg: await tx.refreshGroups.get(ctx.refreshGroupId),
- };
- },
- );
+ const res = await ctx.wex.runLegacyWalletDbTx(async (tx) => {
+ return {
+ rg: await tx.refreshGroups.get(ctx.refreshGroupId),
+ };
+ });
const { rg } = res;
if (!rg) {
// Must've been deleted, we consider that final.
diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts
@@ -39,7 +39,6 @@ import {
j2s,
safeStringifyException,
} from "@gnu-taler/taler-util";
-import { processBackupForProvider } from "./backup/index.js";
import {
DbRetryInfo,
PendingTaskType,
@@ -130,7 +129,6 @@ interface ShepherdInfo {
function taskGivesLiveness(taskId: string): boolean {
const parsedTaskId = parseTaskIdentifier(taskId);
switch (parsedTaskId.tag) {
- case PendingTaskType.Backup:
case PendingTaskType.ExchangeUpdate:
case PendingTaskType.ExchangeAutoRefresh:
case PendingTaskType.ExchangeWalletKyc:
@@ -534,12 +532,9 @@ async function storeTaskProgress(
pendingTaskId: string,
): Promise<void> {
logger.trace(`storing task [progress] for ${pendingTaskId}`);
- await ws.db.runReadWriteTx(
- { storeNames: ["operationRetries"] },
- async (tx) => {
- await tx.operationRetries.delete(pendingTaskId);
- },
- );
+ await ws.db.runAllStoresReadWriteTx({}, async (tx) => {
+ await tx.operationRetries.delete(pendingTaskId);
+ });
}
async function storePendingTaskPending(
@@ -594,12 +589,9 @@ async function storePendingTaskFinished(
pendingTaskId: string,
): Promise<void> {
logger.trace(`storing task [finished] for ${pendingTaskId}`);
- await ws.db.runReadWriteTx(
- { storeNames: ["operationRetries"] },
- async (tx) => {
- await tx.operationRetries.delete(pendingTaskId);
- },
- );
+ await ws.db.runAllStoresReadWriteTx({}, async (tx) => {
+ await tx.operationRetries.delete(pendingTaskId);
+ });
}
function getWalletExecutionContextForTask(
@@ -668,8 +660,6 @@ async function callOperationHandlerForTaskId(
return await processRecoupGroup(wex, pending.recoupGroupId);
case PendingTaskType.Deposit:
return await processDepositGroup(wex, pending.depositGroupId);
- case PendingTaskType.Backup:
- return await processBackupForProvider(wex, pending.backupProviderBaseUrl);
case PendingTaskType.PeerPushDebit:
return await processPeerPushDebit(wex, pending.pursePub);
case PendingTaskType.PeerPullCredit:
@@ -718,7 +708,6 @@ async function taskToRetryNotification(
case PendingTaskType.PeerPushDebit:
case PendingTaskType.Purchase:
return makeTransactionRetryNotification(ws, tx, pendingTaskId, e);
- case PendingTaskType.Backup:
case PendingTaskType.Recoup:
case PendingTaskType.ValidateDenoms:
return undefined;
@@ -990,197 +979,175 @@ export async function getActiveTaskIds(
const res: ActiveTaskIdsResult = {
taskIds: [],
};
- await ws.db.runReadWriteTx(
- {
- storeNames: [
- "exchanges",
- "refreshGroups",
- "withdrawalGroups",
- "purchases",
- "depositGroups",
- "recoupGroups",
- "peerPullCredit",
- "peerPushDebit",
- "peerPullDebit",
- "peerPushCredit",
- "reserves",
- ],
- },
- async (tx) => {
- const active = GlobalIDB.KeyRange.bound(
- OPERATION_STATUS_NONFINAL_FIRST,
- OPERATION_STATUS_NONFINAL_LAST,
- );
+ await ws.db.runAllStoresReadWriteTx({}, async (tx) => {
+ const active = GlobalIDB.KeyRange.bound(
+ OPERATION_STATUS_NONFINAL_FIRST,
+ OPERATION_STATUS_NONFINAL_LAST,
+ );
- // Withdrawals
+ // Withdrawals
- {
- const activeRecs =
- await tx.withdrawalGroups.indexes.byStatus.getAll(active);
- for (const rec of activeRecs) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.Withdraw,
- withdrawalGroupId: rec.withdrawalGroupId,
- });
- res.taskIds.push(taskId);
- }
+ {
+ const activeRecs =
+ await tx.withdrawalGroups.indexes.byStatus.getAll(active);
+ for (const rec of activeRecs) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.Withdraw,
+ withdrawalGroupId: rec.withdrawalGroupId,
+ });
+ res.taskIds.push(taskId);
}
+ }
- // Deposits
+ // Deposits
- {
- const activeRecs =
- await tx.depositGroups.indexes.byStatus.getAll(active);
- for (const rec of activeRecs) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.Deposit,
- depositGroupId: rec.depositGroupId,
- });
- res.taskIds.push(taskId);
- }
+ {
+ const activeRecs = await tx.depositGroups.indexes.byStatus.getAll(active);
+ for (const rec of activeRecs) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.Deposit,
+ depositGroupId: rec.depositGroupId,
+ });
+ res.taskIds.push(taskId);
}
+ }
- // Refreshes
+ // Refreshes
- {
- const activeRecs =
- await tx.refreshGroups.indexes.byStatus.getAll(active);
- for (const rec of activeRecs) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.Refresh,
- refreshGroupId: rec.refreshGroupId,
- });
- res.taskIds.push(taskId);
- }
+ {
+ const activeRecs = await tx.refreshGroups.indexes.byStatus.getAll(active);
+ for (const rec of activeRecs) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.Refresh,
+ refreshGroupId: rec.refreshGroupId,
+ });
+ res.taskIds.push(taskId);
}
+ }
- // Purchases
+ // Purchases
- {
- const activeRecs = await tx.purchases.indexes.byStatus.getAll(active);
- for (const rec of activeRecs) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.Purchase,
- proposalId: rec.proposalId,
- });
- res.taskIds.push(taskId);
- }
+ {
+ const activeRecs = await tx.purchases.indexes.byStatus.getAll(active);
+ for (const rec of activeRecs) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.Purchase,
+ proposalId: rec.proposalId,
+ });
+ res.taskIds.push(taskId);
}
+ }
- // peer-push-debit
+ // peer-push-debit
- {
- const activeRecs =
- await tx.peerPushDebit.indexes.byStatus.getAll(active);
- for (const rec of activeRecs) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.PeerPushDebit,
- pursePub: rec.pursePub,
- });
- res.taskIds.push(taskId);
- }
+ {
+ const activeRecs = await tx.peerPushDebit.indexes.byStatus.getAll(active);
+ for (const rec of activeRecs) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPushDebit,
+ pursePub: rec.pursePub,
+ });
+ res.taskIds.push(taskId);
}
+ }
- // peer-push-credit
+ // peer-push-credit
- {
- const activeRecs =
- await tx.peerPushCredit.indexes.byStatus.getAll(active);
- for (const rec of activeRecs) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.PeerPushCredit,
- peerPushCreditId: rec.peerPushCreditId,
- });
- res.taskIds.push(taskId);
- }
+ {
+ const activeRecs =
+ await tx.peerPushCredit.indexes.byStatus.getAll(active);
+ for (const rec of activeRecs) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPushCredit,
+ peerPushCreditId: rec.peerPushCreditId,
+ });
+ res.taskIds.push(taskId);
}
+ }
- // peer-pull-debit
+ // peer-pull-debit
- {
- const activeRecs =
- await tx.peerPullDebit.indexes.byStatus.getAll(active);
- for (const rec of activeRecs) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.PeerPullDebit,
- peerPullDebitId: rec.peerPullDebitId,
- });
- res.taskIds.push(taskId);
- }
+ {
+ const activeRecs = await tx.peerPullDebit.indexes.byStatus.getAll(active);
+ for (const rec of activeRecs) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPullDebit,
+ peerPullDebitId: rec.peerPullDebitId,
+ });
+ res.taskIds.push(taskId);
}
+ }
- // peer-pull-credit
+ // peer-pull-credit
- {
- const activeRecs =
- await tx.peerPullCredit.indexes.byStatus.getAll(active);
- for (const rec of activeRecs) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.PeerPullCredit,
- pursePub: rec.pursePub,
- });
- res.taskIds.push(taskId);
- }
+ {
+ const activeRecs =
+ await tx.peerPullCredit.indexes.byStatus.getAll(active);
+ for (const rec of activeRecs) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.PeerPullCredit,
+ pursePub: rec.pursePub,
+ });
+ res.taskIds.push(taskId);
}
+ }
- // recoup
+ // recoup
- {
- const activeRecs =
- await tx.recoupGroups.indexes.byStatus.getAll(active);
- for (const rec of activeRecs) {
- const taskId = constructTaskIdentifier({
- tag: PendingTaskType.Recoup,
- recoupGroupId: rec.recoupGroupId,
- });
- res.taskIds.push(taskId);
- }
+ {
+ const activeRecs = await tx.recoupGroups.indexes.byStatus.getAll(active);
+ for (const rec of activeRecs) {
+ const taskId = constructTaskIdentifier({
+ tag: PendingTaskType.Recoup,
+ recoupGroupId: rec.recoupGroupId,
+ });
+ res.taskIds.push(taskId);
}
+ }
- // exchange update and KYC
+ // exchange update and KYC
- {
- const exchanges = await tx.exchanges.getAll();
- for (const rec of exchanges) {
- const taskIdUpdate = constructTaskIdentifier({
- tag: PendingTaskType.ExchangeUpdate,
- exchangeBaseUrl: rec.baseUrl,
- });
- res.taskIds.push(taskIdUpdate);
+ {
+ const exchanges = await tx.exchanges.getAll();
+ for (const rec of exchanges) {
+ const taskIdUpdate = constructTaskIdentifier({
+ tag: PendingTaskType.ExchangeUpdate,
+ exchangeBaseUrl: rec.baseUrl,
+ });
+ res.taskIds.push(taskIdUpdate);
- const taskIdAutoRefresh = constructTaskIdentifier({
- tag: PendingTaskType.ExchangeAutoRefresh,
+ const taskIdAutoRefresh = constructTaskIdentifier({
+ tag: PendingTaskType.ExchangeAutoRefresh,
+ exchangeBaseUrl: rec.baseUrl,
+ });
+ res.taskIds.push(taskIdAutoRefresh);
+
+ const reserveId = rec.currentMergeReserveRowId;
+ if (reserveId == null) {
+ continue;
+ }
+ const reserveRec = await tx.reserves.get(reserveId);
+ if (
+ reserveRec?.status != null &&
+ reserveRec.status != ReserveRecordStatus.Done
+ ) {
+ const taskIdKyc = constructTaskIdentifier({
+ tag: PendingTaskType.ExchangeWalletKyc,
exchangeBaseUrl: rec.baseUrl,
});
- res.taskIds.push(taskIdAutoRefresh);
-
- const reserveId = rec.currentMergeReserveRowId;
- if (reserveId == null) {
- continue;
- }
- const reserveRec = await tx.reserves.get(reserveId);
- if (
- reserveRec?.status != null &&
- reserveRec.status != ReserveRecordStatus.Done
- ) {
- const taskIdKyc = constructTaskIdentifier({
- tag: PendingTaskType.ExchangeWalletKyc,
- exchangeBaseUrl: rec.baseUrl,
- });
- res.taskIds.push(taskIdKyc);
- }
+ res.taskIds.push(taskIdKyc);
}
}
+ }
- // Always try to validate unvalidated denoms.
+ // Always try to validate unvalidated denoms.
- res.taskIds.push(
- constructTaskIdentifier({ tag: PendingTaskType.ValidateDenoms }),
- );
+ res.taskIds.push(
+ constructTaskIdentifier({ tag: PendingTaskType.ValidateDenoms }),
+ );
- // FIXME: Recoup!
- },
- );
+ // FIXME: Recoup!
+ });
return res;
}
diff --git a/packages/taler-wallet-core/src/testing.ts b/packages/taler-wallet-core/src/testing.ts
@@ -964,14 +964,11 @@ export async function testPay(
if (r.type != ConfirmPayResultType.Done) {
throw Error("payment not done");
}
- const purchase = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- const parsedTx = parseTransactionIdentifier(r.transactionId);
- checkLogicInvariant(parsedTx?.tag === TransactionType.Payment);
- return tx.purchases.get(parsedTx.proposalId);
- },
- );
+ const purchase = await wex.runLegacyWalletDbTx(async (tx) => {
+ const parsedTx = parseTransactionIdentifier(r.transactionId);
+ checkLogicInvariant(parsedTx?.tag === TransactionType.Payment);
+ return tx.purchases.get(parsedTx.proposalId);
+ });
checkLogicInvariant(!!purchase);
return {
numCoins: purchase.payInfo?.payCoinSelection?.coinContributions.length ?? 0,
diff --git a/packages/taler-wallet-core/src/tokenFamilies.ts b/packages/taler-wallet-core/src/tokenFamilies.ts
@@ -30,10 +30,7 @@ import {
} from "@gnu-taler/taler-util";
import { WalletExecutionContext } from "./index.js";
import { TokenRecord } from "./db.js";
-import {
- isTokenInUse,
- isTokenValid,
-} from "./tokenSelection.js";
+import { isTokenInUse, isTokenValid } from "./tokenSelection.js";
const logger = new Logger("tokenFamilies.ts");
@@ -42,7 +39,7 @@ function groupDiscounts(tokens: TokenRecord[]): DiscountListDetail[] {
const groupedIdx: number[] = [];
const items: DiscountListDetail[] = [];
tokens = tokens
- .filter(t => t.tokenFamilyHash)
+ .filter((t) => t.tokenFamilyHash)
.sort((a, b) => a.validBefore - b.validBefore);
// compare tokens against each other,
@@ -84,7 +81,7 @@ function groupSubscriptions(tokens: TokenRecord[]): SubscriptionListDetail[] {
const groupedIdx: number[] = [];
const items: SubscriptionListDetail[] = [];
tokens = tokens
- .filter(t => t.tokenFamilyHash)
+ .filter((t) => t.tokenFamilyHash)
.sort((a, b) => a.validBefore - b.validBefore);
// compare tokens against each other,
@@ -124,14 +121,14 @@ export async function listDiscounts(
tokenIssuePubHash?: string,
merchantBaseUrl?: string,
): Promise<ListDiscountsResponse> {
- const tokens: TokenRecord[] = await wex.db.runReadOnlyTx({
- storeNames: ["tokens"],
- }, async (tx) => {
+ const tokens: TokenRecord[] = await wex.runLegacyWalletDbTx(async (tx) => {
return (await tx.tokens.getAll())
- .filter(t => isTokenValid(t))
- .filter(t => t.kind === MerchantContractTokenKind.Discount)
- .filter(t => !tokenIssuePubHash || t.tokenIssuePubHash === tokenIssuePubHash)
- .filter(t => !merchantBaseUrl || t.merchantBaseUrl === merchantBaseUrl)
+ .filter((t) => isTokenValid(t))
+ .filter((t) => t.kind === MerchantContractTokenKind.Discount)
+ .filter(
+ (t) => !tokenIssuePubHash || t.tokenIssuePubHash === tokenIssuePubHash,
+ )
+ .filter((t) => !merchantBaseUrl || t.merchantBaseUrl === merchantBaseUrl);
});
if (tokens.length === 0) {
@@ -148,14 +145,14 @@ export async function listSubscriptions(
tokenIssuePubHash?: string,
merchantBaseUrl?: string,
): Promise<ListSubscriptionsResponse> {
- const tokens: TokenRecord[] = await wex.db.runReadOnlyTx({
- storeNames: ["tokens"],
- }, async (tx) => {
+ const tokens: TokenRecord[] = await wex.runLegacyWalletDbTx(async (tx) => {
return (await tx.tokens.getAll())
- .filter(t => isTokenValid(t))
- .filter(t => t.kind === MerchantContractTokenKind.Subscription)
- .filter(t => !tokenIssuePubHash || t.tokenIssuePubHash === tokenIssuePubHash)
- .filter(t => !merchantBaseUrl || t.merchantBaseUrl === merchantBaseUrl);
+ .filter((t) => isTokenValid(t))
+ .filter((t) => t.kind === MerchantContractTokenKind.Subscription)
+ .filter(
+ (t) => !tokenIssuePubHash || t.tokenIssuePubHash === tokenIssuePubHash,
+ )
+ .filter((t) => !merchantBaseUrl || t.merchantBaseUrl === merchantBaseUrl);
});
if (tokens.length === 0) {
@@ -171,12 +168,10 @@ export async function deleteDiscount(
wex: WalletExecutionContext,
tokenFamilyHash: string,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx({
- storeNames: ["tokens"],
- }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const tokens = (await tx.tokens.getAll())
- .filter(t => t.kind === MerchantContractTokenKind.Discount)
- .filter(t => t.tokenFamilyHash === tokenFamilyHash);
+ .filter((t) => t.kind === MerchantContractTokenKind.Discount)
+ .filter((t) => t.tokenFamilyHash === tokenFamilyHash);
let inUse: boolean = false;
for (const token of tokens) {
@@ -194,7 +189,7 @@ export async function deleteDiscount(
for (const token of tokens) {
if (logger.shouldLogTrace()) {
logger.trace(
- `deleting token in ${token.tokenIssuePubHash} token family`
+ `deleting token in ${token.tokenIssuePubHash} token family`,
);
}
await tx.tokens.delete(token.tokenUsePub);
@@ -208,12 +203,10 @@ export async function deleteSubscription(
wex: WalletExecutionContext,
tokenFamilyHash: string,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx({
- storeNames: ["tokens"],
- }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const tokens = (await tx.tokens.getAll())
- .filter(t => t.kind === MerchantContractTokenKind.Subscription)
- .filter(t => t.tokenFamilyHash === tokenFamilyHash)
+ .filter((t) => t.kind === MerchantContractTokenKind.Subscription)
+ .filter((t) => t.tokenFamilyHash === tokenFamilyHash)
.sort((a, b) => a.validBefore - b.validBefore);
let inUse: boolean = false;
diff --git a/packages/taler-wallet-core/src/tokenSelection.test.ts b/packages/taler-wallet-core/src/tokenSelection.test.ts
@@ -13,9 +13,7 @@
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import {
- MerchantContractTokenKind,
-} from "@gnu-taler/taler-util";
+import { MerchantContractTokenKind } from "@gnu-taler/taler-util";
import test from "ava";
import {
TokenMerchantVerificationResult,
diff --git a/packages/taler-wallet-core/src/tokenSelection.ts b/packages/taler-wallet-core/src/tokenSelection.ts
@@ -50,14 +50,14 @@ export interface SelectPayTokensAllChoicesRequest {
export type SelectPayTokensResult =
| {
- type: "failure",
- details: PaymentTokenAvailabilityDetails,
- }
+ type: "failure";
+ details: PaymentTokenAvailabilityDetails;
+ }
| {
- type: "success",
- tokens: TokenRecord[],
- details: PaymentTokenAvailabilityDetails,
- };
+ type: "success";
+ tokens: TokenRecord[];
+ details: PaymentTokenAvailabilityDetails;
+ };
export enum TokenMerchantVerificationResult {
/**
@@ -110,7 +110,7 @@ export function verifyTokenMerchant(
break;
}
- if (domains.find(t => t === "*")) {
+ if (domains.find((t) => t === "*")) {
// If catch-all (*) is present, token can be spent anywhere
return TokenMerchantVerificationResult.Automatic;
} else if (merchantDomain === tokenDomain) {
@@ -126,22 +126,21 @@ export function verifyTokenMerchant(
return TokenMerchantVerificationResult.Untrusted;
default:
assertUnreachable(tokenDetails);
- }
+ }
}
var warning = true;
const regex = new RegExp("^(\\*\\.)?([\\w\\d]+\\.)+[\\w\\d]+$");
for (let domain of domains) {
domain = domain.toLowerCase();
- if (!regex.test(domain))
- throw new Error("assertion failed");
+ if (!regex.test(domain)) throw new Error("assertion failed");
if (warning) {
// If the two domains match exactly, no warning.
// If the domain has a wildcard, do multi-level matching.
- warning = domain !== merchantDomain && (
- domain.startsWith("*.")
- && !merchantDomain.endsWith(domain.slice(2))
- );
+ warning =
+ domain !== merchantDomain &&
+ domain.startsWith("*.") &&
+ !merchantDomain.endsWith(domain.slice(2));
}
}
@@ -160,14 +159,8 @@ export function verifyTokenMerchant(
}
export async function selectPayTokensInTx(
- tx: WalletDbReadOnlyTransaction<
- [
- "tokens",
- "purchases",
- ]
- >,
+ tx: WalletDbReadOnlyTransaction<["tokens", "purchases"]>,
req: SelectPayTokensRequest,
-
): Promise<SelectPayTokensResult> {
if (logger.shouldLogTrace()) {
logger.trace(`selecting tokens for ${j2s(req)}`);
@@ -179,23 +172,25 @@ export async function selectPayTokensInTx(
}
var tokensRequested = 0;
- const inputTokens: {[slug: string]: {
- records: TokenRecord[],
- requested: number,
- }} = {};
+ const inputTokens: {
+ [slug: string]: {
+ records: TokenRecord[];
+ requested: number;
+ };
+ } = {};
const inputs = req.contractTerms.choices[req.choiceIndex].inputs;
const tokenIssuePubs: string[] = [];
for (const slug in req.contractTerms.token_families) {
const requested = inputs
- .filter(i => i.type === MerchantContractInputType.Token)
- .filter(i => i.token_family_slug === slug)
+ .filter((i) => i.type === MerchantContractInputType.Token)
+ .filter((i) => i.token_family_slug === slug)
.reduce((a, b) => a + (b.count ?? 1), 0);
if (requested > 0) {
tokensRequested += requested;
- inputTokens[slug] = {records: [], requested};
+ inputTokens[slug] = { records: [], requested };
const keys = req.contractTerms.token_families[slug].keys;
for (const key of keys) {
const keyHash = encodeCrock(hashTokenIssuePub(key));
@@ -207,8 +202,8 @@ export async function selectPayTokensInTx(
}
logger.trace(
- `found total of ${inputTokens[slug].records.length} records for token family ${slug}, `
- + `out of ${requested} requested`
+ `found total of ${inputTokens[slug].records.length} records for token family ${slug}, ` +
+ `out of ${requested} requested`,
);
}
}
@@ -224,24 +219,18 @@ export async function selectPayTokens(
wex: WalletExecutionContext,
req: SelectPayTokensRequest,
): Promise<SelectPayTokensResult> {
- return await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "tokens",
- "purchases",
- ]
- },
- async (tx) => {
- return selectPayTokensInTx(tx, req);
- }
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ return selectPayTokensInTx(tx, req);
+ });
}
export function selectTokenCandidates(
- inputTokens: {[slug: string]: {
- records: TokenRecord[],
- requested: number,
- }},
+ inputTokens: {
+ [slug: string]: {
+ records: TokenRecord[];
+ requested: number;
+ };
+ },
tokensRequested: number,
merchantBaseUrl: string,
): SelectPayTokensResult {
@@ -257,7 +246,7 @@ export function selectTokenCandidates(
const tokens: TokenRecord[] = [];
for (const slug in inputTokens) {
- const {records, requested} = inputTokens[slug];
+ const { records, requested } = inputTokens[slug];
details.perTokenFamily[slug] = {
requested,
@@ -273,9 +262,9 @@ export function selectTokenCandidates(
// - sort ascending by expiration date
// - choose the first n tokens in the list
const usable = records
- .filter(tok => !isTokenInUse(tok))
- .filter(tok => isTokenValid(tok))
- .filter(tok => {
+ .filter((tok) => !isTokenInUse(tok))
+ .filter((tok) => isTokenValid(tok))
+ .filter((tok) => {
const res = verifyTokenMerchant(
merchantBaseUrl,
tok.merchantBaseUrl,
@@ -293,7 +282,8 @@ export function selectTokenCandidates(
default:
assertUnreachable(res);
}
- }).sort((a, b) => a.validBefore - b.validBefore);
+ })
+ .sort((a, b) => a.validBefore - b.validBefore);
details.perTokenFamily[slug].available = usable.length;
details.tokensAvailable += details.perTokenFamily[slug].available;
@@ -345,7 +335,9 @@ export function isTokenValid(tok: TokenRecord): boolean {
return AbsoluteTime.isBetween(
AbsoluteTime.now(),
AbsoluteTime.fromProtocolTimestamp(timestampProtocolFromDb(tok.validAfter)),
- AbsoluteTime.fromProtocolTimestamp(timestampProtocolFromDb(tok.validBefore)),
+ AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(tok.validBefore),
+ ),
);
}
@@ -354,11 +346,18 @@ export function isTokenValidBetween(
start: TalerProtocolTimestamp,
end: TalerProtocolTimestamp,
): boolean {
- return AbsoluteTime.cmp(
- AbsoluteTime.fromProtocolTimestamp(start ?? TalerProtocolTimestamp.now()),
- AbsoluteTime.fromProtocolTimestamp(tok.tokenIssuePub.signature_validity_start),
- ) <= 0 && AbsoluteTime.cmp(
- AbsoluteTime.fromProtocolTimestamp(end),
- AbsoluteTime.fromProtocolTimestamp(tok.tokenIssuePub.signature_validity_end),
- ) >= 0
+ return (
+ AbsoluteTime.cmp(
+ AbsoluteTime.fromProtocolTimestamp(start ?? TalerProtocolTimestamp.now()),
+ AbsoluteTime.fromProtocolTimestamp(
+ tok.tokenIssuePub.signature_validity_start,
+ ),
+ ) <= 0 &&
+ AbsoluteTime.cmp(
+ AbsoluteTime.fromProtocolTimestamp(end),
+ AbsoluteTime.fromProtocolTimestamp(
+ tok.tokenIssuePub.signature_validity_end,
+ ),
+ ) >= 0
+ );
}
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -42,7 +42,6 @@ import {
AddMailboxMessageRequest,
AmountResponse,
ApplyDevExperimentRequest,
- BackupRecovery,
BalancesResponse,
CanonicalizeBaseUrlRequest,
CanonicalizeBaseUrlResponse,
@@ -161,7 +160,6 @@ import {
PrepareWithdrawExchangeRequest,
PrepareWithdrawExchangeResponse,
RecoverStoredBackupRequest,
- RecoveryLoadRequest,
RemoveGlobalCurrencyAuditorRequest,
RemoveGlobalCurrencyExchangeRequest,
RetryTransactionRequest,
@@ -169,7 +167,6 @@ import {
SendTalerUriMailboxMessageRequest,
SetCoinSuspendedRequest,
SetDonauRequest,
- SetWalletDeviceIdRequest,
SharePaymentRequest,
SharePaymentResult,
StartExchangeWalletKycRequest,
@@ -195,10 +192,6 @@ import {
TransactionsResponse,
TxIdResponse,
UpdateExchangeEntryRequest,
- UserAttentionByIdRequest,
- UserAttentionsCountResponse,
- UserAttentionsRequest,
- UserAttentionsResponse,
ValidateIbanRequest,
ValidateIbanResponse,
WalletCoreVersion,
@@ -206,13 +199,6 @@ import {
WithdrawUriInfoResponse,
WithdrawalDetailsForAmount,
} from "@gnu-taler/taler-util";
-import {
- AddBackupProviderRequest,
- AddBackupProviderResponse,
- BackupInfo,
- RemoveBackupProviderRequest,
- RunBackupCycleRequest,
-} from "./backup/index.js";
import { PaymentBalanceDetails } from "./balance.js";
import { WithdrawTestBalanceResult } from "./testing.js";
@@ -247,9 +233,6 @@ export enum WalletApiOperation {
ConvertDepositAmount = "convertDepositAmount",
GetMaxDepositAmount = "getMaxDepositAmount",
GetMaxPeerPushDebitAmount = "getMaxPeerPushDebitAmount",
- GetUserAttentionRequests = "getUserAttentionRequests",
- GetUserAttentionUnreadCount = "getUserAttentionUnreadCount",
- MarkAttentionRequestAsRead = "markAttentionRequestAsRead",
GetActiveTasks = "getActiveTasks",
SetExchangeTosAccepted = "setExchangeTosAccepted",
SetExchangeTosForgotten = "setExchangeTosForgotten",
@@ -345,17 +328,6 @@ export enum WalletApiOperation {
DeleteStoredBackup = "deleteStoredBackup",
RecoverStoredBackup = "recoverStoredBackup",
- // legacy backup functionality
-
- SetWalletDeviceId = "setWalletDeviceId",
- ExportBackup = "exportBackup",
- AddBackupProvider = "addBackupProvider",
- RemoveBackupProvider = "removeBackupProvider",
- RunBackupCycle = "runBackupCycle",
- ExportBackupRecovery = "exportBackupRecovery",
- ImportBackupRecovery = "importBackupRecovery",
- GetBackupInfo = "getBackupInfo",
-
// Testing
TestingWaitTransactionsFinal = "testingWaitTransactionsFinal",
@@ -1148,39 +1120,6 @@ export type CheckDepositOp = {
// group: Backups
/**
- * Export the recovery information for the wallet.
- */
-export type ExportBackupRecoveryOp = {
- op: WalletApiOperation.ExportBackupRecovery;
- request: EmptyObject;
- response: BackupRecovery;
-};
-
-/**
- * Import recovery information into the wallet.
- */
-export type ImportBackupRecoveryOp = {
- op: WalletApiOperation.ImportBackupRecovery;
- request: RecoveryLoadRequest;
- response: EmptyObject;
-};
-
-/**
- * Manually make and upload a backup.
- */
-export type RunBackupCycleOp = {
- op: WalletApiOperation.RunBackupCycle;
- request: RunBackupCycleRequest;
- response: EmptyObject;
-};
-
-export type ExportBackupOp = {
- op: WalletApiOperation.ExportBackup;
- request: EmptyObject;
- response: EmptyObject;
-};
-
-/**
* Export the database to a file.
*
* The target directory must already exist.
@@ -1202,41 +1141,6 @@ export type ImportDbFromFileOp = {
response: EmptyObject;
};
-/**
- * Add a new backup provider.
- */
-export type AddBackupProviderOp = {
- op: WalletApiOperation.AddBackupProvider;
- request: AddBackupProviderRequest;
- response: AddBackupProviderResponse;
-};
-
-export type RemoveBackupProviderOp = {
- op: WalletApiOperation.RemoveBackupProvider;
- request: RemoveBackupProviderRequest;
- response: EmptyObject;
-};
-
-/**
- * Get some useful stats about the backup state.
- */
-export type GetBackupInfoOp = {
- op: WalletApiOperation.GetBackupInfo;
- request: EmptyObject;
- response: BackupInfo;
-};
-
-/**
- * Set the internal device ID of the wallet, used to
- * identify whether a different/new wallet is accessing
- * the backup of another wallet.
- */
-export type SetWalletDeviceIdOp = {
- op: WalletApiOperation.SetWalletDeviceId;
- request: SetWalletDeviceIdRequest;
- response: EmptyObject;
-};
-
export type ListStoredBackupsOp = {
op: WalletApiOperation.ListStoredBackups;
request: EmptyObject;
@@ -1491,33 +1395,6 @@ export type TestPayOp = {
response: TestPayResult;
};
-/**
- * Get wallet-internal pending tasks.
- */
-export type GetUserAttentionRequests = {
- op: WalletApiOperation.GetUserAttentionRequests;
- request: UserAttentionsRequest;
- response: UserAttentionsResponse;
-};
-
-/**
- * Get wallet-internal pending tasks.
- */
-export type MarkAttentionRequestAsRead = {
- op: WalletApiOperation.MarkAttentionRequestAsRead;
- request: UserAttentionByIdRequest;
- response: EmptyObject;
-};
-
-/**
- * Get wallet-internal pending tasks.
- */
-export type GetUserAttentionsUnreadCount = {
- op: WalletApiOperation.GetUserAttentionUnreadCount;
- request: UserAttentionsRequest;
- response: UserAttentionsCountResponse;
-};
-
export type GetActiveTasksOp = {
op: WalletApiOperation.GetActiveTasks;
request: EmptyObject;
@@ -1713,9 +1590,6 @@ export type WalletOperations = {
[WalletApiOperation.GetTransactionById]: GetTransactionByIdOp;
[WalletApiOperation.RetryPendingNow]: RetryPendingNowOp;
[WalletApiOperation.GetActiveTasks]: GetActiveTasksOp;
- [WalletApiOperation.GetUserAttentionRequests]: GetUserAttentionRequests;
- [WalletApiOperation.GetUserAttentionUnreadCount]: GetUserAttentionsUnreadCount;
- [WalletApiOperation.MarkAttentionRequestAsRead]: MarkAttentionRequestAsRead;
[WalletApiOperation.DumpCoins]: DumpCoinsOp;
[WalletApiOperation.SetCoinSuspended]: SetCoinSuspendedOp;
[WalletApiOperation.ForceRefresh]: ForceRefreshOp;
@@ -1741,16 +1615,8 @@ export type WalletOperations = {
[WalletApiOperation.CheckDeposit]: CheckDepositOp;
[WalletApiOperation.GenerateDepositGroupTxId]: GenerateDepositGroupTxIdOp;
[WalletApiOperation.CreateDepositGroup]: CreateDepositGroupOp;
- [WalletApiOperation.SetWalletDeviceId]: SetWalletDeviceIdOp;
- [WalletApiOperation.ExportBackupRecovery]: ExportBackupRecoveryOp;
- [WalletApiOperation.ImportBackupRecovery]: ImportBackupRecoveryOp;
- [WalletApiOperation.RunBackupCycle]: RunBackupCycleOp;
- [WalletApiOperation.ExportBackup]: ExportBackupOp;
[WalletApiOperation.ExportDbToFile]: ExportDbToFileOp;
[WalletApiOperation.ImportDbFromFile]: ImportDbFromFileOp;
- [WalletApiOperation.AddBackupProvider]: AddBackupProviderOp;
- [WalletApiOperation.RemoveBackupProvider]: RemoveBackupProviderOp;
- [WalletApiOperation.GetBackupInfo]: GetBackupInfoOp;
[WalletApiOperation.RunIntegrationTest]: RunIntegrationTestOp;
[WalletApiOperation.RunIntegrationTestV2]: RunIntegrationTestV2Op;
[WalletApiOperation.TestCrypto]: TestCryptoOp;
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -249,7 +249,6 @@ import {
codecForSendTalerUriMailboxMessageRequest,
codecForSetCoinSuspendedRequest,
codecForSetDonauRequest,
- codecForSetWalletDeviceIdRequest,
codecForSharePaymentRequest,
codecForStartExchangeWalletKycRequest,
codecForStartRefundQueryRequest,
@@ -265,8 +264,6 @@ import {
codecForTransactionByIdRequest,
codecForTransactionsRequest,
codecForUpdateExchangeEntryRequest,
- codecForUserAttentionByIdRequest,
- codecForUserAttentionsRequest,
codecForValidateIbanRequest,
codecForWithdrawTestBalance,
convertCHF_BBANtoIBAN,
@@ -292,23 +289,6 @@ import {
readSuccessResponseJsonOrThrow,
type HttpRequestLibrary,
} from "@gnu-taler/taler-util/http";
-import {
- getUserAttentions,
- getUserAttentionsUnreadCount,
- markAttentionRequestAsRead,
-} from "./attention.js";
-import {
- addBackupProvider,
- codecForAddBackupProviderRequest,
- codecForRemoveBackupProvider,
- codecForRunBackupCycle,
- getBackupInfo,
- getBackupRecovery,
- loadBackupRecovery,
- removeBackupProvider,
- runBackupCycle,
- setWalletDeviceId,
-} from "./backup/index.js";
import { getBalanceDetail, getBalances } from "./balance.js";
import {
getMaxDepositAmount,
@@ -553,31 +533,28 @@ type CancelFn = () => void;
*/
async function fillDefaults(wex: WalletExecutionContext): Promise<void> {
const notifications: WalletNotification[] = [];
- await wex.db.runReadWriteTx(
- { storeNames: ["config", "exchanges"] },
- async (tx) => {
- const appliedRec = await tx.config.get("currencyDefaultsApplied");
- let alreadyApplied = appliedRec ? !!appliedRec.value : false;
- if (alreadyApplied) {
- logger.trace("defaults already applied");
- return;
- }
- for (const exch of wex.ws.config.builtin.exchanges) {
- const resp = await addPresetExchangeEntry(
- tx,
- exch.exchangeBaseUrl,
- exch.currencyHint,
- );
- if (resp.notification) {
- notifications.push(resp.notification);
- }
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const appliedRec = await tx.config.get("currencyDefaultsApplied");
+ let alreadyApplied = appliedRec ? !!appliedRec.value : false;
+ if (alreadyApplied) {
+ logger.trace("defaults already applied");
+ return;
+ }
+ for (const exch of wex.ws.config.builtin.exchanges) {
+ const resp = await addPresetExchangeEntry(
+ tx,
+ exch.exchangeBaseUrl,
+ exch.currencyHint,
+ );
+ if (resp.notification) {
+ notifications.push(resp.notification);
}
- await tx.config.put({
- key: ConfigRecordKey.CurrencyDefaultsApplied,
- value: true,
- });
- },
- );
+ }
+ await tx.config.put({
+ key: ConfigRecordKey.CurrencyDefaultsApplied,
+ value: true,
+ });
+ });
for (const notif of notifications) {
wex.ws.notify(notif);
}
@@ -645,7 +622,7 @@ async function handleListBankAccounts(
): Promise<ListBankAccountsResponse> {
const accounts: WalletBankAccountInfo[] = [];
const currency = req.currency;
- await wex.db.runReadOnlyTx({ storeNames: ["bankAccountsV2"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const knownAccounts = await tx.bankAccountsV2.iter().toArray();
for (const r of knownAccounts) {
if (currency && r.currencies && !r.currencies.includes(currency)) {
@@ -670,12 +647,9 @@ async function handleGetBankAccountById(
wex: WalletExecutionContext,
req: GetBankAccountByIdRequest,
): Promise<GetBankAccountByIdResponse> {
- const acct = await wex.db.runReadOnlyTx(
- { storeNames: ["bankAccountsV2"] },
- async (tx) => {
- return tx.bankAccountsV2.get(req.bankAccountId);
- },
- );
+ const acct = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.bankAccountsV2.get(req.bankAccountId);
+ });
if (!acct) {
throw Error(`bank account ${req.bankAccountId} not found`);
}
@@ -689,16 +663,13 @@ async function forgetBankAccount(
wex: WalletExecutionContext,
bankAccountId: string,
): Promise<void> {
- await wex.db.runReadWriteTx(
- { storeNames: ["bankAccountsV2"] },
- async (tx) => {
- const account = await tx.bankAccountsV2.get(bankAccountId);
- if (!account) {
- throw Error(`account not found: ${bankAccountId}`);
- }
- tx.bankAccountsV2.delete(account.bankAccountId);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const account = await tx.bankAccountsV2.get(bankAccountId);
+ if (!account) {
+ throw Error(`account not found: ${bankAccountId}`);
+ }
+ tx.bankAccountsV2.delete(account.bankAccountId);
+ });
wex.ws.notify({
type: NotificationType.BankAccountChange,
bankAccountId,
@@ -711,45 +682,42 @@ async function setCoinSuspended(
coinPub: string,
suspended: boolean,
): Promise<void> {
- await wex.db.runReadWriteTx(
- { storeNames: ["coins", "coinAvailability"] },
- async (tx) => {
- const c = await tx.coins.get(coinPub);
- if (!c) {
- logger.warn(`coin ${coinPub} not found, won't suspend`);
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const c = await tx.coins.get(coinPub);
+ if (!c) {
+ logger.warn(`coin ${coinPub} not found, won't suspend`);
+ return;
+ }
+ const coinAvailability = await tx.coinAvailability.get([
+ c.exchangeBaseUrl,
+ c.denomPubHash,
+ c.maxAge,
+ ]);
+ checkDbInvariant(
+ !!coinAvailability,
+ `no denom info for ${c.denomPubHash} age ${c.maxAge}`,
+ );
+ if (suspended) {
+ if (c.status !== CoinStatus.Fresh) {
return;
}
- const coinAvailability = await tx.coinAvailability.get([
- c.exchangeBaseUrl,
- c.denomPubHash,
- c.maxAge,
- ]);
- checkDbInvariant(
- !!coinAvailability,
- `no denom info for ${c.denomPubHash} age ${c.maxAge}`,
- );
- if (suspended) {
- if (c.status !== CoinStatus.Fresh) {
- return;
- }
- if (coinAvailability.freshCoinCount === 0) {
- throw Error(
- `invalid coin count ${coinAvailability.freshCoinCount} in DB`,
- );
- }
- coinAvailability.freshCoinCount--;
- c.status = CoinStatus.FreshSuspended;
- } else {
- if (c.status == CoinStatus.Dormant) {
- return;
- }
- coinAvailability.freshCoinCount++;
- c.status = CoinStatus.Fresh;
+ if (coinAvailability.freshCoinCount === 0) {
+ throw Error(
+ `invalid coin count ${coinAvailability.freshCoinCount} in DB`,
+ );
}
- await tx.coins.put(c);
- await tx.coinAvailability.put(coinAvailability);
- },
- );
+ coinAvailability.freshCoinCount--;
+ c.status = CoinStatus.FreshSuspended;
+ } else {
+ if (c.status == CoinStatus.Dormant) {
+ return;
+ }
+ coinAvailability.freshCoinCount++;
+ c.status = CoinStatus.Fresh;
+ }
+ await tx.coins.put(c);
+ await tx.coinAvailability.put(coinAvailability);
+ });
}
/**
@@ -758,54 +726,51 @@ async function setCoinSuspended(
async function dumpCoins(wex: WalletExecutionContext): Promise<CoinDumpJson> {
const coinsJson: CoinDumpJson = { coins: [] };
logger.info("dumping coins");
- await wex.db.runReadOnlyTx(
- { storeNames: ["coins", "coinHistory", "denominations"] },
- async (tx) => {
- const coins = await tx.coins.iter().toArray();
- for (const c of coins) {
- const denom = await tx.denominations.get([
- c.exchangeBaseUrl,
- c.denomPubHash,
- ]);
- if (!denom) {
- logger.warn("no denom found for coin");
- continue;
- }
- const cs = c.coinSource;
- let refreshParentCoinPub: string | undefined;
- if (cs.type == CoinSourceType.Refresh) {
- refreshParentCoinPub = cs.oldCoinPub;
- }
- let withdrawalReservePub: string | undefined;
- if (cs.type == CoinSourceType.Withdraw) {
- withdrawalReservePub = cs.reservePub;
- }
- const denomInfo = await getDenomInfo(
- wex,
- tx,
- c.exchangeBaseUrl,
- c.denomPubHash,
- );
- if (!denomInfo) {
- logger.warn("no denomination found for coin");
- continue;
- }
- const historyRec = await tx.coinHistory.get(c.coinPub);
- coinsJson.coins.push({
- coinPub: c.coinPub,
- denomPub: denom.denomPub,
- denomPubHash: c.denomPubHash,
- denomValue: denom.value,
- exchangeBaseUrl: c.exchangeBaseUrl,
- refreshParentCoinPub: refreshParentCoinPub,
- withdrawalReservePub: withdrawalReservePub,
- coinStatus: c.status,
- ageCommitmentProof: c.ageCommitmentProof,
- history: historyRec ? historyRec.history : [],
- });
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const coins = await tx.coins.iter().toArray();
+ for (const c of coins) {
+ const denom = await tx.denominations.get([
+ c.exchangeBaseUrl,
+ c.denomPubHash,
+ ]);
+ if (!denom) {
+ logger.warn("no denom found for coin");
+ continue;
}
- },
- );
+ const cs = c.coinSource;
+ let refreshParentCoinPub: string | undefined;
+ if (cs.type == CoinSourceType.Refresh) {
+ refreshParentCoinPub = cs.oldCoinPub;
+ }
+ let withdrawalReservePub: string | undefined;
+ if (cs.type == CoinSourceType.Withdraw) {
+ withdrawalReservePub = cs.reservePub;
+ }
+ const denomInfo = await getDenomInfo(
+ wex,
+ tx,
+ c.exchangeBaseUrl,
+ c.denomPubHash,
+ );
+ if (!denomInfo) {
+ logger.warn("no denomination found for coin");
+ continue;
+ }
+ const historyRec = await tx.coinHistory.get(c.coinPub);
+ coinsJson.coins.push({
+ coinPub: c.coinPub,
+ denomPub: denom.denomPub,
+ denomPubHash: c.denomPubHash,
+ denomValue: denom.value,
+ exchangeBaseUrl: c.exchangeBaseUrl,
+ refreshParentCoinPub: refreshParentCoinPub,
+ withdrawalReservePub: withdrawalReservePub,
+ coinStatus: c.status,
+ ageCommitmentProof: c.ageCommitmentProof,
+ history: historyRec ? historyRec.history : [],
+ });
+ }
+ });
return coinsJson;
}
@@ -1002,7 +967,7 @@ async function handleSetWalletRunConfig(
// Write to the DB to make sure that we're failing early in
// case the DB is not writeable.
try {
- await wex.db.runReadWriteTx({ storeNames: ["config"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
tx.config.put({
key: ConfigRecordKey.LastInitInfo,
value: timestampProtocolToDb(TalerProtocolTimestamp.now()),
@@ -1161,7 +1126,7 @@ async function handleTestingGetDenomStats(
numLost: 0,
numOffered: 0,
};
- await wex.db.runReadOnlyTx({ storeNames: ["denominations"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const denoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll(
req.exchangeBaseUrl,
);
@@ -1182,38 +1147,32 @@ async function handleAddBankAccount(
wex: WalletExecutionContext,
req: AddBankAccountRequest,
): Promise<AddBankAccountResponse> {
- const acctId = await wex.db.runReadWriteTx(
- { storeNames: ["bankAccountsV2"] },
- async (tx) => {
- let currencies = req.currencies;
- let myId: string;
- const oldAcct = await tx.bankAccountsV2.indexes.byPaytoUri.get(
- req.paytoUri,
- );
- if (req.replaceBankAccountId) {
- myId = req.replaceBankAccountId;
- } else if (oldAcct) {
- myId = oldAcct.bankAccountId;
- currencies = [
- ...new Set([
- ...(req.currencies ?? []),
- ...(oldAcct.currencies ?? []),
- ]),
- ];
- } else {
- // New Account!
- myId = `acct:${encodeCrock(getRandomBytes(32))}`;
- }
- await tx.bankAccountsV2.put({
- bankAccountId: myId,
- paytoUri: req.paytoUri,
- label: req.label,
- currencies,
- kycCompleted: false,
- });
- return myId;
- },
- );
+ const acctId = await wex.runLegacyWalletDbTx(async (tx) => {
+ let currencies = req.currencies;
+ let myId: string;
+ const oldAcct = await tx.bankAccountsV2.indexes.byPaytoUri.get(
+ req.paytoUri,
+ );
+ if (req.replaceBankAccountId) {
+ myId = req.replaceBankAccountId;
+ } else if (oldAcct) {
+ myId = oldAcct.bankAccountId;
+ currencies = [
+ ...new Set([...(req.currencies ?? []), ...(oldAcct.currencies ?? [])]),
+ ];
+ } else {
+ // New Account!
+ myId = `acct:${encodeCrock(getRandomBytes(32))}`;
+ }
+ await tx.bankAccountsV2.put({
+ bankAccountId: myId,
+ paytoUri: req.paytoUri,
+ label: req.label,
+ currencies,
+ kycCompleted: false,
+ });
+ return myId;
+ });
wex.ws.notify({
type: NotificationType.BankAccountChange,
bankAccountId: acctId,
@@ -1236,12 +1195,9 @@ async function handleTestingGetReserveHistory(
wex: WalletExecutionContext,
req: TestingGetReserveHistoryRequest,
): Promise<any> {
- const reserve = await wex.db.runReadOnlyTx(
- { storeNames: ["reserves"] },
- async (tx) => {
- return tx.reserves.indexes.byReservePub.get(req.reservePub);
- },
- );
+ const reserve = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.reserves.indexes.byReservePub.get(req.reservePub);
+ });
if (!reserve) {
throw Error("no reserve pub found");
}
@@ -1412,12 +1368,9 @@ async function handleGetActiveTasks(
const tasksInfo = await Promise.all(
allTasksId.map(async (id) => {
- return await wex.db.runReadOnlyTx(
- { storeNames: ["operationRetries"] },
- async (tx) => {
- return tx.operationRetries.get(id);
- },
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.operationRetries.get(id);
+ });
}),
);
@@ -1503,63 +1456,57 @@ async function handleGetDepositWireTypes(
const wtSet: Set<string> = new Set();
const wireTypeDetails: WireTypeDetails[] = [];
const talerBankHostnames: string[] = [];
- await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges", "exchangeDetails"] },
- async (tx) => {
- const exchanges = await tx.exchanges.getAll();
- for (const exchange of exchanges) {
- const det = await getExchangeDetailsInTx(tx, exchange.baseUrl);
- if (!det) {
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchanges = await tx.exchanges.getAll();
+ for (const exchange of exchanges) {
+ const det = await getExchangeDetailsInTx(tx, exchange.baseUrl);
+ if (!det) {
+ continue;
+ }
+ if (req.currency !== undefined && det.currency !== req.currency) {
+ continue;
+ }
+ for (const acc of det.wireInfo.accounts) {
+ let usable = true;
+ for (const dr of acc.debit_restrictions) {
+ if (dr.type === "deny") {
+ usable = false;
+ break;
+ }
+ }
+ if (!usable) {
continue;
}
- if (req.currency !== undefined && det.currency !== req.currency) {
+ const parsedPayto = parsePaytoUri(acc.payto_uri);
+ if (!parsedPayto) {
continue;
}
- for (const acc of det.wireInfo.accounts) {
- let usable = true;
- for (const dr of acc.debit_restrictions) {
- if (dr.type === "deny") {
- usable = false;
- break;
- }
- }
- if (!usable) {
- continue;
- }
- const parsedPayto = parsePaytoUri(acc.payto_uri);
- if (!parsedPayto) {
- continue;
+ let preferredEntryType: "iban" | "bban" | undefined = undefined;
+ if (parsedPayto.targetType === "iban") {
+ if (det.currency === "HUF") {
+ preferredEntryType = "bban";
+ } else {
+ preferredEntryType = "iban";
}
- let preferredEntryType: "iban" | "bban" | undefined = undefined;
- if (parsedPayto.targetType === "iban") {
- if (det.currency === "HUF") {
- preferredEntryType = "bban";
- } else {
- preferredEntryType = "iban";
- }
- }
- if (
- parsedPayto.isKnown &&
- parsedPayto.targetType === "x-taler-bank"
- ) {
- if (!talerBankHostnames.includes(parsedPayto.host)) {
- talerBankHostnames.push(parsedPayto.host);
- }
- }
- if (!wtSet.has(parsedPayto.targetType)) {
- wtSet.add(parsedPayto.targetType);
- wireTypeDetails.push({
- paymentTargetType: parsedPayto.targetType,
- // Will possibly extended later by other exchanges
- // with the same wire type.
- talerBankHostnames,
- preferredEntryType,
- });
+ }
+ if (parsedPayto.isKnown && parsedPayto.targetType === "x-taler-bank") {
+ if (!talerBankHostnames.includes(parsedPayto.host)) {
+ talerBankHostnames.push(parsedPayto.host);
}
}
+ if (!wtSet.has(parsedPayto.targetType)) {
+ wtSet.add(parsedPayto.targetType);
+ wireTypeDetails.push({
+ paymentTargetType: parsedPayto.targetType,
+ // Will possibly extended later by other exchanges
+ // with the same wire type.
+ talerBankHostnames,
+ preferredEntryType,
+ });
+ }
}
- },
- );
+ }
+ });
return {
wireTypeDetails,
};
@@ -1572,54 +1519,48 @@ async function handleGetDepositWireTypesForCurrency(
const wtSet: Set<string> = new Set();
const wireTypeDetails: WireTypeDetails[] = [];
const talerBankHostnames: string[] = [];
- await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges", "exchangeDetails"] },
- async (tx) => {
- const exchanges = await tx.exchanges.getAll();
- for (const exchange of exchanges) {
- const det = await getExchangeDetailsInTx(tx, exchange.baseUrl);
- if (!det) {
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const exchanges = await tx.exchanges.getAll();
+ for (const exchange of exchanges) {
+ const det = await getExchangeDetailsInTx(tx, exchange.baseUrl);
+ if (!det) {
+ continue;
+ }
+ if (det.currency !== req.currency) {
+ continue;
+ }
+ for (const acc of det.wireInfo.accounts) {
+ let usable = true;
+ for (const dr of acc.debit_restrictions) {
+ if (dr.type === "deny") {
+ usable = false;
+ break;
+ }
+ }
+ if (!usable) {
continue;
}
- if (det.currency !== req.currency) {
+ const parsedPayto = parsePaytoUri(acc.payto_uri);
+ if (!parsedPayto) {
continue;
}
- for (const acc of det.wireInfo.accounts) {
- let usable = true;
- for (const dr of acc.debit_restrictions) {
- if (dr.type === "deny") {
- usable = false;
- break;
- }
- }
- if (!usable) {
- continue;
- }
- const parsedPayto = parsePaytoUri(acc.payto_uri);
- if (!parsedPayto) {
- continue;
- }
- if (
- parsedPayto.isKnown &&
- parsedPayto.targetType === "x-taler-bank"
- ) {
- if (!talerBankHostnames.includes(parsedPayto.host)) {
- talerBankHostnames.push(parsedPayto.host);
- }
- }
- if (!wtSet.has(parsedPayto.targetType)) {
- wtSet.add(parsedPayto.targetType);
- wireTypeDetails.push({
- paymentTargetType: parsedPayto.targetType,
- // Will possibly extended later by other exchanges
- // with the same wire type.
- talerBankHostnames,
- });
+ if (parsedPayto.isKnown && parsedPayto.targetType === "x-taler-bank") {
+ if (!talerBankHostnames.includes(parsedPayto.host)) {
+ talerBankHostnames.push(parsedPayto.host);
}
}
+ if (!wtSet.has(parsedPayto.targetType)) {
+ wtSet.add(parsedPayto.targetType);
+ wireTypeDetails.push({
+ paymentTargetType: parsedPayto.targetType,
+ // Will possibly extended later by other exchanges
+ // with the same wire type.
+ talerBankHostnames,
+ });
+ }
}
- },
- );
+ }
+ });
return {
wireTypes: [...wtSet],
wireTypeDetails,
@@ -1633,19 +1574,16 @@ async function handleListGlobalCurrencyExchanges(
const resp: ListGlobalCurrencyExchangesResponse = {
exchanges: [],
};
- await wex.db.runReadOnlyTx(
- { storeNames: ["globalCurrencyExchanges"] },
- async (tx) => {
- const gceList = await tx.globalCurrencyExchanges.iter().toArray();
- for (const gce of gceList) {
- resp.exchanges.push({
- currency: gce.currency,
- exchangeBaseUrl: gce.exchangeBaseUrl,
- exchangeMasterPub: gce.exchangeMasterPub,
- });
- }
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const gceList = await tx.globalCurrencyExchanges.iter().toArray();
+ for (const gce of gceList) {
+ resp.exchanges.push({
+ currency: gce.currency,
+ exchangeBaseUrl: gce.exchangeBaseUrl,
+ exchangeMasterPub: gce.exchangeMasterPub,
+ });
+ }
+ });
return resp;
}
@@ -1656,19 +1594,16 @@ async function handleListGlobalCurrencyAuditors(
const resp: ListGlobalCurrencyAuditorsResponse = {
auditors: [],
};
- await wex.db.runReadOnlyTx(
- { storeNames: ["globalCurrencyAuditors"] },
- async (tx) => {
- const gcaList = await tx.globalCurrencyAuditors.iter().toArray();
- for (const gca of gcaList) {
- resp.auditors.push({
- currency: gca.currency,
- auditorBaseUrl: gca.auditorBaseUrl,
- auditorPub: gca.auditorPub,
- });
- }
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const gcaList = await tx.globalCurrencyAuditors.iter().toArray();
+ for (const gca of gcaList) {
+ resp.auditors.push({
+ currency: gca.currency,
+ auditorBaseUrl: gca.auditorBaseUrl,
+ auditorPub: gca.auditorPub,
+ });
+ }
+ });
return resp;
}
@@ -1676,39 +1611,34 @@ export async function handleAddGlobalCurrencyExchange(
wex: WalletExecutionContext,
req: AddGlobalCurrencyExchangeRequest,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx(
- { storeNames: ["globalCurrencyExchanges", "currencyInfo"] },
- async (tx) => {
- const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub];
- const existingRec =
- await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get(
- key,
- );
- if (existingRec) {
- return;
- }
- wex.ws.exchangeCache.clear();
- const info = await tx.currencyInfo.get(
- stringifyScopeInfo({
- type: ScopeType.Exchange,
- currency: req.currency,
- url: req.exchangeBaseUrl,
- }),
- );
- if (info) {
- info.scopeInfoStr = stringifyScopeInfo({
- type: ScopeType.Global,
- currency: req.currency,
- });
- await tx.currencyInfo.put(info);
- }
- await tx.globalCurrencyExchanges.add({
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub];
+ const existingRec =
+ await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get(key);
+ if (existingRec) {
+ return;
+ }
+ wex.ws.exchangeCache.clear();
+ const info = await tx.currencyInfo.get(
+ stringifyScopeInfo({
+ type: ScopeType.Exchange,
+ currency: req.currency,
+ url: req.exchangeBaseUrl,
+ }),
+ );
+ if (info) {
+ info.scopeInfoStr = stringifyScopeInfo({
+ type: ScopeType.Global,
currency: req.currency,
- exchangeBaseUrl: req.exchangeBaseUrl,
- exchangeMasterPub: req.exchangeMasterPub,
});
- },
- );
+ await tx.currencyInfo.put(info);
+ }
+ await tx.globalCurrencyExchanges.add({
+ currency: req.currency,
+ exchangeBaseUrl: req.exchangeBaseUrl,
+ exchangeMasterPub: req.exchangeMasterPub,
+ });
+ });
return {};
}
@@ -1716,20 +1646,17 @@ async function handleRemoveGlobalCurrencyAuditor(
wex: WalletExecutionContext,
req: RemoveGlobalCurrencyAuditorRequest,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx(
- { storeNames: ["globalCurrencyAuditors"] },
- async (tx) => {
- const key = [req.currency, req.auditorBaseUrl, req.auditorPub];
- const existingRec =
- await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get(key);
- if (!existingRec) {
- return;
- }
- checkDbInvariant(!!existingRec.id, `no global currency for ${j2s(key)}`);
- await tx.globalCurrencyAuditors.delete(existingRec.id);
- wex.ws.exchangeCache.clear();
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const key = [req.currency, req.auditorBaseUrl, req.auditorPub];
+ const existingRec =
+ await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get(key);
+ if (!existingRec) {
+ return;
+ }
+ checkDbInvariant(!!existingRec.id, `no global currency for ${j2s(key)}`);
+ await tx.globalCurrencyAuditors.delete(existingRec.id);
+ wex.ws.exchangeCache.clear();
+ });
return {};
}
@@ -1737,28 +1664,23 @@ export async function handleRemoveGlobalCurrencyExchange(
wex: WalletExecutionContext,
req: RemoveGlobalCurrencyExchangeRequest,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx(
- { storeNames: ["globalCurrencyExchanges", "currencyInfo"] },
- async (tx) => {
- const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub];
- const existingRec =
- await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get(
- key,
- );
- if (!existingRec) {
- return;
- }
- wex.ws.exchangeCache.clear();
- checkDbInvariant(!!existingRec.id, `no global exchange for ${j2s(key)}`);
- await tx.currencyInfo.delete(
- stringifyScopeInfo({
- type: ScopeType.Global,
- currency: req.currency,
- }),
- );
- await tx.globalCurrencyExchanges.delete(existingRec.id);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub];
+ const existingRec =
+ await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get(key);
+ if (!existingRec) {
+ return;
+ }
+ wex.ws.exchangeCache.clear();
+ checkDbInvariant(!!existingRec.id, `no global exchange for ${j2s(key)}`);
+ await tx.currencyInfo.delete(
+ stringifyScopeInfo({
+ type: ScopeType.Global,
+ currency: req.currency,
+ }),
+ );
+ await tx.globalCurrencyExchanges.delete(existingRec.id);
+ });
return {};
}
@@ -1766,23 +1688,20 @@ async function handleAddGlobalCurrencyAuditor(
wex: WalletExecutionContext,
req: AddGlobalCurrencyAuditorRequest,
): Promise<EmptyObject> {
- await wex.db.runReadWriteTx(
- { storeNames: ["globalCurrencyAuditors"] },
- async (tx) => {
- const key = [req.currency, req.auditorBaseUrl, req.auditorPub];
- const existingRec =
- await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get(key);
- if (existingRec) {
- return;
- }
- await tx.globalCurrencyAuditors.add({
- currency: req.currency,
- auditorBaseUrl: req.auditorBaseUrl,
- auditorPub: req.auditorPub,
- });
- wex.ws.exchangeCache.clear();
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const key = [req.currency, req.auditorBaseUrl, req.auditorPub];
+ const existingRec =
+ await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get(key);
+ if (existingRec) {
+ return;
+ }
+ await tx.globalCurrencyAuditors.add({
+ currency: req.currency,
+ auditorBaseUrl: req.auditorBaseUrl,
+ auditorPub: req.auditorPub,
+ });
+ wex.ws.exchangeCache.clear();
+ });
return {};
}
@@ -2439,21 +2358,6 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
codec: codecForGetBalanceDetailRequest(),
handler: getBalanceDetail,
},
- [WalletApiOperation.GetUserAttentionRequests]: {
- codec: codecForUserAttentionsRequest(),
- handler: getUserAttentions,
- },
- [WalletApiOperation.MarkAttentionRequestAsRead]: {
- codec: codecForUserAttentionByIdRequest(),
- handler: async (wex, req) => {
- await markAttentionRequestAsRead(wex, req);
- return {};
- },
- },
- [WalletApiOperation.GetUserAttentionUnreadCount]: {
- codec: codecForUserAttentionsRequest(),
- handler: getUserAttentionsUnreadCount,
- },
[WalletApiOperation.SetExchangeTosAccepted]: {
codec: codecForAcceptExchangeTosRequest(),
handler: async (wex, req) => {
@@ -2578,28 +2482,6 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
codec: codecForStartRefundQueryRequest(),
handler: handleStartRefundQuery,
},
- [WalletApiOperation.AddBackupProvider]: {
- codec: codecForAddBackupProviderRequest(),
- handler: addBackupProvider,
- },
- [WalletApiOperation.RunBackupCycle]: {
- codec: codecForRunBackupCycle(),
- handler: async (wex, req) => {
- await runBackupCycle(wex, req);
- return {};
- },
- },
- [WalletApiOperation.RemoveBackupProvider]: {
- codec: codecForRemoveBackupProvider(),
- handler: async (wex, req) => {
- await removeBackupProvider(wex, req);
- return {};
- },
- },
- [WalletApiOperation.ExportBackupRecovery]: {
- codec: codecForEmptyObject(),
- handler: getBackupRecovery,
- },
[WalletApiOperation.TestingWaitTransactionState]: {
codec: codecForAny(),
handler: async (wex, req) => {
@@ -2611,13 +2493,6 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
codec: codecForGetCurrencyInfoRequest(),
handler: handleGetCurrencySpecification,
},
- [WalletApiOperation.ImportBackupRecovery]: {
- codec: codecForAny(),
- handler: async (wex, req) => {
- await loadBackupRecovery(wex, req);
- return {};
- },
- },
[WalletApiOperation.HintNetworkAvailability]: {
codec: codecForHintNetworkAvailabilityRequest(),
handler: handleHintNetworkAvailability,
@@ -2634,10 +2509,6 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
codec: codecForGetMaxPeerPushDebitAmountRequest(),
handler: getMaxPeerPushDebitAmount,
},
- [WalletApiOperation.GetBackupInfo]: {
- codec: codecForEmptyObject(),
- handler: getBackupInfo,
- },
[WalletApiOperation.CheckDeposit]: {
codec: codecForCheckDepositRequest(),
handler: checkDepositGroup,
@@ -2668,13 +2539,6 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
return {};
},
},
- [WalletApiOperation.SetWalletDeviceId]: {
- codec: codecForSetWalletDeviceIdRequest(),
- handler: async (wex, req) => {
- await setWalletDeviceId(wex, req.walletDeviceId);
- return {};
- },
- },
[WalletApiOperation.TestCrypto]: {
codec: codecForEmptyObject(),
handler: async (wex, req) => {
@@ -2851,12 +2715,6 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
return {};
},
},
- [WalletApiOperation.ExportBackup]: {
- codec: codecForAny(),
- handler: async (wex, req) => {
- throw Error("not implemented");
- },
- },
[WalletApiOperation.ListAssociatedRefreshes]: {
codec: codecForAny(),
handler: async (wex, req) => {
@@ -2937,50 +2795,89 @@ export function getObservedWalletExecutionContext(
return wex;
}
-async function runWalletDbTx<T>(
- wex: WalletExecutionContext,
- f: (tx: WalletDbTransaction) => Promise<T>,
-): Promise<T> {
- // FIXME: Here's where we should add some retry logic.
- return wex.db.runAllStoresReadWriteTx({}, async (mytx) => {
- const tx = new IdbWalletTransaction(mytx);
- return await f(tx);
- });
+function maybeExtractUnverifiedDenomError(
+ e: unknown,
+): UnverifiedDenomError | undefined {
+ if (e instanceof UnverifiedDenomError) {
+ return e;
+ }
+ if (
+ e instanceof TransactionAbortedError &&
+ e.exn instanceof UnverifiedDenomError
+ ) {
+ return e.exn;
+ }
+ return undefined;
}
-async function runLegacyWalletDbTx<T>(
+async function handleTxRetries<T>(
wex: WalletExecutionContext,
- f: (tx: WalletDbAllStoresReadWriteTransaction) => Promise<T>,
+ f: () => Promise<T>,
): Promise<T> {
- // FIXME: Make sure this doesn't recurse
const coveredExchanges = new Set<string>();
while (1) {
try {
- return await wex.db.runAllStoresReadWriteTx({}, async (mytx) => {
- return await f(mytx);
- });
+ return await f();
} catch (e) {
- if (
- e instanceof TransactionAbortedError &&
- e.exn instanceof UnverifiedDenomError
- ) {
- const url = e.exn.denomInfo.exchangeBaseUrl;
- logger.info(`got unverified denominations, updating ${url}`);
- if (coveredExchanges.has(url)) {
- logger.error(`exchange was already covered, giving up`);
- throw e;
- }
+ let exn = maybeExtractUnverifiedDenomError(e);
+ if (exn == null) {
+ throw e;
+ }
+ const url = exn.denomInfo.exchangeBaseUrl;
+ logger.info(`got unverified denominations, updating ${url}`);
+ if (coveredExchanges.has(url)) {
+ logger.error(`exchange was already covered, giving up`);
+ throw e;
+ }
+ // Make sure this doesn't recurse
+ if (wex.ws.disableTransactionRetries) {
+ throw e;
+ }
+ try {
+ wex.ws.disableTransactionRetries = true;
await fetchFreshExchange(wex, url);
await updateWithdrawalDenomsForExchange(wex, url);
coveredExchanges.add(url);
- } else {
+ } catch (e) {
+ logger.error(
+ `Exception thrown while trying to heal UnverifiedDenomError`,
+ );
throw e;
+ } finally {
+ wex.ws.disableTransactionRetries = true;
}
}
}
throw Error("not reached");
}
+async function runWalletDbTx<T>(
+ wex: WalletExecutionContext,
+ f: (tx: WalletDbTransaction) => Promise<T>,
+): Promise<T> {
+ return await handleTxRetries(wex, async () => {
+ return await wex.db.runAllStoresReadWriteTx({}, async (mytx) => {
+ const tx = new IdbWalletTransaction(mytx);
+ return await f(tx);
+ });
+ });
+}
+
+async function runLegacyWalletDbTx<T>(
+ wex: WalletExecutionContext,
+ f: (
+ tx: WalletDbAllStoresReadWriteTransaction,
+ wtx: WalletDbTransaction,
+ ) => Promise<T>,
+): Promise<T> {
+ return await handleTxRetries(wex, async () => {
+ return await wex.db.runAllStoresReadWriteTx({}, async (mytx) => {
+ const tx = new IdbWalletTransaction(mytx);
+ return await f(mytx, tx);
+ });
+ });
+}
+
export function getNormalWalletExecutionContext(
ws: InternalWalletState,
cancellationToken: CancellationToken,
@@ -3441,6 +3338,8 @@ export class InternalWalletState {
*/
refcntIgnoreTos: number = 0;
+ disableTransactionRetries: boolean = false;
+
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
@@ -519,8 +519,7 @@ export class WithdrawTransactionContext implements TransactionContext {
: baseStores;
let errorThrown: Error | undefined;
- const didTransition: boolean = await this.wex.db.runReadWriteTx(
- { storeNames: stores },
+ const didTransition: boolean = await this.wex.runLegacyWalletDbTx(
async (tx) => {
const wgRec = await tx.withdrawalGroups.get(this.withdrawalGroupId);
let oldTxState: TransactionState;
@@ -583,19 +582,9 @@ export class WithdrawTransactionContext implements TransactionContext {
}
async deleteTransaction(): Promise<void> {
- const res = await this.wex.db.runReadWriteTx(
- {
- storeNames: [
- "withdrawalGroups",
- "planchets",
- "tombstones",
- "transactionsMeta",
- ],
- },
- async (tx) => {
- return this.deleteTransactionInTx(tx);
- },
- );
+ const res = await this.wex.runLegacyWalletDbTx(async (tx) => {
+ return this.deleteTransactionInTx(tx);
+ });
}
async deleteTransactionInTx(
@@ -1371,12 +1360,9 @@ async function getWithdrawableDenoms(
exchangeBaseUrl: string,
currency: string,
): Promise<DenominationRecord[]> {
- return await wex.db.runReadOnlyTx(
- { storeNames: ["denominations", "denominationFamilies"] },
- async (tx) => {
- return getWithdrawableDenomsTx(wex, tx, exchangeBaseUrl, currency);
- },
- );
+ return await wex.runLegacyWalletDbTx(async (tx) => {
+ return getWithdrawableDenomsTx(wex, tx, exchangeBaseUrl, currency);
+ });
}
/**
@@ -1506,15 +1492,12 @@ async function processPlanchetGenerate(
"can't get funding uri from uninitialized wg",
);
const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
- let planchet = await wex.db.runReadOnlyTx(
- { storeNames: ["planchets"] },
- async (tx) => {
- return tx.planchets.indexes.byGroupAndIndex.get([
- withdrawalGroup.withdrawalGroupId,
- coinIdx,
- ]);
- },
- );
+ let planchet = await wex.runLegacyWalletDbTx(async (tx) => {
+ return tx.planchets.indexes.byGroupAndIndex.get([
+ withdrawalGroup.withdrawalGroupId,
+ coinIdx,
+ ]);
+ });
if (planchet) {
return {};
}
@@ -1540,12 +1523,9 @@ async function processPlanchetGenerate(
}
const denomPubHash = maybeDenomPubHash;
- const denom = await wex.db.runReadOnlyTx(
- { storeNames: ["denominations"] },
- async (tx) => {
- return getDenomInfo(wex, tx, exchangeBaseUrl, denomPubHash);
- },
- );
+ const denom = await wex.runLegacyWalletDbTx(async (tx) => {
+ return getDenomInfo(wex, tx, exchangeBaseUrl, denomPubHash);
+ });
if (!denom) {
// We handle this gracefully, to fix previous bugs that made it into production.
return { badDenom: true };
@@ -1574,7 +1554,7 @@ async function processPlanchetGenerate(
ageCommitmentProof: r.ageCommitmentProof,
lastError: undefined,
};
- await wex.db.runReadWriteTx({ storeNames: ["planchets"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const p = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroup.withdrawalGroupId,
coinIdx,
@@ -1676,51 +1656,48 @@ async function processPlanchetExchangeLegacyBatchRequest(
// Indices of coins that are included in the batch request
const requestCoinIdxs: number[] = [];
- await wex.db.runReadOnlyTx(
- { storeNames: ["planchets", "denominations"] },
- async (tx) => {
- for (
- let coinIdx = args.coinStartIndex;
- coinIdx < args.coinStartIndex + args.batchSize &&
- coinIdx < wgContext.numPlanchets;
- coinIdx++
- ) {
- const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
- withdrawalGroup.withdrawalGroupId,
- coinIdx,
- ]);
- if (!planchet) {
- continue;
- }
- if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
- logger.warn("processPlanchet: planchet already withdrawn");
- continue;
- }
- if (planchet.planchetStatus === PlanchetStatus.AbortedReplaced) {
- continue;
- }
- const denom = await getDenomInfo(
- wex,
- tx,
- exchangeBaseUrl,
- planchet.denomPubHash,
- );
-
- if (!denom) {
- logger.error("db inconsistent: denom for planchet not found");
- continue;
- }
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (
+ let coinIdx = args.coinStartIndex;
+ coinIdx < args.coinStartIndex + args.batchSize &&
+ coinIdx < wgContext.numPlanchets;
+ coinIdx++
+ ) {
+ const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+ withdrawalGroup.withdrawalGroupId,
+ coinIdx,
+ ]);
+ if (!planchet) {
+ continue;
+ }
+ if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
+ logger.warn("processPlanchet: planchet already withdrawn");
+ continue;
+ }
+ if (planchet.planchetStatus === PlanchetStatus.AbortedReplaced) {
+ continue;
+ }
+ const denom = await getDenomInfo(
+ wex,
+ tx,
+ exchangeBaseUrl,
+ planchet.denomPubHash,
+ );
- const planchetReq: ExchangeLegacyWithdrawRequest = {
- denom_pub_hash: planchet.denomPubHash,
- reserve_sig: planchet.withdrawSig,
- coin_ev: planchet.coinEv,
- };
- batchReq.planchets.push(planchetReq);
- requestCoinIdxs.push(coinIdx);
+ if (!denom) {
+ logger.error("db inconsistent: denom for planchet not found");
+ continue;
}
- },
- );
+
+ const planchetReq: ExchangeLegacyWithdrawRequest = {
+ denom_pub_hash: planchet.denomPubHash,
+ reserve_sig: planchet.withdrawSig,
+ coin_ev: planchet.coinEv,
+ };
+ batchReq.planchets.push(planchetReq);
+ requestCoinIdxs.push(coinIdx);
+ }
+ });
if (batchReq.planchets.length == 0) {
logger.warn("empty withdrawal batch");
@@ -1735,7 +1712,7 @@ async function processPlanchetExchangeLegacyBatchRequest(
coinIdx: number,
): Promise<void> {
logger.trace(`withdrawal request failed: ${j2s(errDetail)}`);
- await wex.db.runReadWriteTx({ storeNames: ["planchets"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroup.withdrawalGroupId,
coinIdx,
@@ -1836,48 +1813,45 @@ async function processPlanchetExchangeBatchRequest(
);
let accAmount = Amounts.zeroOfAmount(withdrawalGroup.instructedAmount);
let accFee = Amounts.zeroOfAmount(withdrawalGroup.instructedAmount);
- await wex.db.runReadOnlyTx(
- { storeNames: ["planchets", "denominations"] },
- async (tx) => {
- for (
- let coinIdx = args.coinStartIndex;
- coinIdx < args.coinStartIndex + args.batchSize &&
- coinIdx < wgContext.numPlanchets;
- coinIdx++
- ) {
- const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
- withdrawalGroup.withdrawalGroupId,
- coinIdx,
- ]);
- if (!planchet) {
- continue;
- }
- if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
- logger.warn("processPlanchet: planchet already withdrawn");
- continue;
- }
- if (planchet.planchetStatus === PlanchetStatus.AbortedReplaced) {
- continue;
- }
- const denom = await getDenomInfo(
- wex,
- tx,
- exchangeBaseUrl,
- planchet.denomPubHash,
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ for (
+ let coinIdx = args.coinStartIndex;
+ coinIdx < args.coinStartIndex + args.batchSize &&
+ coinIdx < wgContext.numPlanchets;
+ coinIdx++
+ ) {
+ const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+ withdrawalGroup.withdrawalGroupId,
+ coinIdx,
+ ]);
+ if (!planchet) {
+ continue;
+ }
+ if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
+ logger.warn("processPlanchet: planchet already withdrawn");
+ continue;
+ }
+ if (planchet.planchetStatus === PlanchetStatus.AbortedReplaced) {
+ continue;
+ }
+ const denom = await getDenomInfo(
+ wex,
+ tx,
+ exchangeBaseUrl,
+ planchet.denomPubHash,
+ );
- if (!denom) {
- logger.error("db inconsistent: denom for planchet not found");
- continue;
- }
- accAmount = Amounts.add(accAmount, denom.value).amount;
- accFee = Amounts.add(accFee, denom.feeWithdraw).amount;
- requestCoinIdxs.push(coinIdx);
- coinEvs.push(planchet.coinEv);
- denomHashes.push(planchet.denomPubHash);
+ if (!denom) {
+ logger.error("db inconsistent: denom for planchet not found");
+ continue;
}
- },
- );
+ accAmount = Amounts.add(accAmount, denom.value).amount;
+ accFee = Amounts.add(accFee, denom.feeWithdraw).amount;
+ requestCoinIdxs.push(coinIdx);
+ coinEvs.push(planchet.coinEv);
+ denomHashes.push(planchet.denomPubHash);
+ }
+ });
if (coinEvs.length == 0) {
logger.warn("empty withdrawal batch");
@@ -1892,7 +1866,7 @@ async function processPlanchetExchangeBatchRequest(
coinIdx: number,
): Promise<void> {
logger.trace(`withdrawal request failed: ${j2s(errDetail)}`);
- await wex.db.runReadWriteTx({ storeNames: ["planchets"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroup.withdrawalGroupId,
coinIdx,
@@ -1996,36 +1970,33 @@ async function processPlanchetVerifyAndStoreCoin(
const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
logger.trace(`checking and storing planchet idx=${coinIdx}`);
- const d = await wex.db.runReadOnlyTx(
- { storeNames: ["planchets", "denominations"] },
- async (tx) => {
- const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
- withdrawalGroup.withdrawalGroupId,
- coinIdx,
- ]);
- if (!planchet) {
- return;
- }
- if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
- logger.warn("processPlanchet: planchet already withdrawn");
- return;
- }
- const denomInfo = await getDenomInfo(
- wex,
- tx,
- exchangeBaseUrl,
- planchet.denomPubHash,
- );
- if (!denomInfo) {
- return;
- }
- return {
- planchet,
- denomInfo,
- exchangeBaseUrl: exchangeBaseUrl,
- };
- },
- );
+ const d = await wex.runLegacyWalletDbTx(async (tx) => {
+ const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+ withdrawalGroup.withdrawalGroupId,
+ coinIdx,
+ ]);
+ if (!planchet) {
+ return;
+ }
+ if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
+ logger.warn("processPlanchet: planchet already withdrawn");
+ return;
+ }
+ const denomInfo = await getDenomInfo(
+ wex,
+ tx,
+ exchangeBaseUrl,
+ planchet.denomPubHash,
+ );
+ if (!denomInfo) {
+ return;
+ }
+ return {
+ planchet,
+ denomInfo,
+ exchangeBaseUrl: exchangeBaseUrl,
+ };
+ });
if (!d) {
return;
@@ -2061,7 +2032,7 @@ async function processPlanchetVerifyAndStoreCoin(
});
if (!rsaVerifyResp.valid) {
- await wex.db.runReadWriteTx({ storeNames: ["planchets"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroup.withdrawalGroupId,
coinIdx,
@@ -2113,19 +2084,16 @@ async function processPlanchetVerifyAndStoreCoin(
wgContext.planchetsFinished.add(planchet.coinPub);
- await wex.db.runReadWriteTx(
- { storeNames: ["planchets", "coins", "coinAvailability", "denominations"] },
- async (tx) => {
- const p = await tx.planchets.get(planchetCoinPub);
- if (!p || p.planchetStatus === PlanchetStatus.WithdrawalDone) {
- return;
- }
- p.planchetStatus = PlanchetStatus.WithdrawalDone;
- p.lastError = undefined;
- await tx.planchets.put(p);
- await makeCoinAvailable(wex, tx, coin);
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const p = await tx.planchets.get(planchetCoinPub);
+ if (!p || p.planchetStatus === PlanchetStatus.WithdrawalDone) {
+ return;
+ }
+ p.planchetStatus = PlanchetStatus.WithdrawalDone;
+ p.lastError = undefined;
+ await tx.planchets.put(p);
+ await makeCoinAvailable(wex, tx, coin);
+ });
}
/**
@@ -2136,12 +2104,9 @@ export async function updateWithdrawalDenomsForCurrency(
wex: WalletExecutionContext,
currency: string,
): Promise<void> {
- const res = await wex.db.runReadOnlyTx(
- { storeNames: ["exchanges", "exchangeDetails", "denominations"] },
- async (tx) => {
- return await tx.exchanges.getAll();
- },
- );
+ const res = await wex.runLegacyWalletDbTx(async (tx) => {
+ return await tx.exchanges.getAll();
+ });
for (const exch of res) {
if (exch.detailsPointer?.currency === currency) {
await updateWithdrawalDenomsForExchange(wex, exch.baseUrl);
@@ -2163,66 +2128,61 @@ export async function updateWithdrawalDenomsForExchange(
const dbNow = timestampProtocolToDb(TalerProtocolTimestamp.now());
- const denoms = await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "denominations",
- "denominationFamilies",
- ],
- },
- async (tx) => {
- const allFamilies = await tx.denominationFamilies.indexes.byExchangeBaseUrl.getAll();
- const denominations: DenominationRecord[] | undefined = [];
- for (const fam of allFamilies) {
- const fpSerial = fam.denominationFamilySerial;
- checkDbInvariant(
- typeof fpSerial === "number",
- "denominationFamilySerial",
- );
- const denomCursor = tx.denominations.indexes
- .byDenominationFamilySerialAndStampExpireWithdraw.iter();
- // Need to wait for cursor to be positioned before we can move it.
- const dr0 = await denomCursor.current();
+ const denoms = await wex.runLegacyWalletDbTx(async (tx) => {
+ const allFamilies =
+ await tx.denominationFamilies.indexes.byExchangeBaseUrl.getAll();
+ const denominations: DenominationRecord[] | undefined = [];
+ for (const fam of allFamilies) {
+ const fpSerial = fam.denominationFamilySerial;
+ checkDbInvariant(
+ typeof fpSerial === "number",
+ "denominationFamilySerial",
+ );
+ const denomCursor =
+ tx.denominations.indexes.byDenominationFamilySerialAndStampExpireWithdraw.iter();
+ // Need to wait for cursor to be positioned before we can move it.
+ const dr0 = await denomCursor.current();
- if (!dr0.hasValue) {
- logger.warn(`no current denom for family ${fpSerial}`);
- continue;
- }
+ if (!dr0.hasValue) {
+ logger.warn(`no current denom for family ${fpSerial}`);
+ continue;
+ }
- if (dr0.value.denominationFamilySerial < fpSerial ||
- (dr0.value.denominationFamilySerial === fpSerial &&
- dr0.value.stampExpireWithdraw < dbNow)
- ) {
- denomCursor.continue([fpSerial, dbNow]);
- }
+ if (
+ dr0.value.denominationFamilySerial < fpSerial ||
+ (dr0.value.denominationFamilySerial === fpSerial &&
+ dr0.value.stampExpireWithdraw < dbNow)
+ ) {
+ denomCursor.continue([fpSerial, dbNow]);
+ }
- while (1) {
- const dr = await denomCursor.current();
- if (!dr.hasValue) {
- break;
- }
+ while (1) {
+ const dr = await denomCursor.current();
+ if (!dr.hasValue) {
+ break;
+ }
- if (dr.value.denominationFamilySerial != fpSerial) {
- // Cursor went to next serial, we need to stop.
- break;
- }
+ if (dr.value.denominationFamilySerial != fpSerial) {
+ // Cursor went to next serial, we need to stop.
+ break;
+ }
- if (isCandidateWithdrawableDenomRec(dr.value)) {
- if (dr.value.verificationStatus ===
- DenominationVerificationStatus.Unverified
- ) {
- denominations.push(dr.value);
- }
- break;
+ if (isCandidateWithdrawableDenomRec(dr.value)) {
+ if (
+ dr.value.verificationStatus ===
+ DenominationVerificationStatus.Unverified
+ ) {
+ denominations.push(dr.value);
}
-
- denomCursor.continue();
+ break;
}
+
+ denomCursor.continue();
}
+ }
- return denominations;
- },
- );
+ return denominations;
+ });
await validateDenoms(wex, denoms);
}
@@ -2261,7 +2221,7 @@ async function processQueryReserve(
withdrawalGroupId: string,
): Promise<TaskRunResult> {
const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
- const withdrawalGroup = await getWithdrawalGroupRecordTx(wex.db, {
+ const withdrawalGroup = await getWithdrawalGroupRecordTx(wex, {
withdrawalGroupId,
});
if (!withdrawalGroup) {
@@ -2544,157 +2504,147 @@ async function redenominateWithdrawal(
await updateWithdrawalDenomsForExchange(wex, exchangeBaseUrl);
logger.trace(`redenominating withdrawal group ${withdrawalGroupId}`);
- await wex.db.runReadWriteTx(
- {
- storeNames: [
- "withdrawalGroups",
- "planchets",
- "denominations",
- "denominationFamilies",
- ],
- },
- async (tx) => {
- const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
- if (!wg) {
- return;
- }
- checkDbInvariant(
- wg.exchangeBaseUrl !== undefined,
- "can't get funding uri from uninitialized wg",
- );
- checkDbInvariant(
- wg.denomsSel !== undefined,
- "can't process uninitialized wg",
- );
- if (!wg.reserveBalanceAmount) {
- // FIXME: Should we transition to another state here to query it?
- throw Error("reserve amount not known yet");
- }
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
+ if (!wg) {
+ return;
+ }
+ checkDbInvariant(
+ wg.exchangeBaseUrl !== undefined,
+ "can't get funding uri from uninitialized wg",
+ );
+ checkDbInvariant(
+ wg.denomsSel !== undefined,
+ "can't process uninitialized wg",
+ );
+ if (!wg.reserveBalanceAmount) {
+ // FIXME: Should we transition to another state here to query it?
+ throw Error("reserve amount not known yet");
+ }
- const currency = Amounts.currencyOf(wg.denomsSel.totalWithdrawCost);
- let remaining = Amount.from(wg.reserveBalanceAmount);
- const zero = Amount.zeroOfCurrency(currency);
+ const currency = Amounts.currencyOf(wg.denomsSel.totalWithdrawCost);
+ let remaining = Amount.from(wg.reserveBalanceAmount);
+ const zero = Amount.zeroOfCurrency(currency);
- const exchangeBaseUrl = wg.exchangeBaseUrl;
+ const exchangeBaseUrl = wg.exchangeBaseUrl;
- const candidates = await getWithdrawableDenomsTx(
- wex,
- tx,
- exchangeBaseUrl,
- currency,
- );
-
- const oldSel = wg.denomsSel;
+ const candidates = await getWithdrawableDenomsTx(
+ wex,
+ tx,
+ exchangeBaseUrl,
+ currency,
+ );
- if (logger.shouldLogTrace()) {
- logger.trace(`old denom sel: ${j2s(oldSel)}`);
- }
+ const oldSel = wg.denomsSel;
- let prevTotalCoinValue = zero;
- let prevTotalWithdrawalCost = zero;
- let prevHasDenomWithAgeRestriction = false;
- let prevEarliestDepositExpiration = AbsoluteTime.never();
- const prevDenoms: DenomSelItem[] = [];
- let coinIndex = 0;
- for (let i = 0; i < oldSel.selectedDenoms.length; i++) {
- const sel = wg.denomsSel.selectedDenoms[i];
- const denom = await tx.denominations.get([
- exchangeBaseUrl,
- sel.denomPubHash,
- ]);
+ if (logger.shouldLogTrace()) {
+ logger.trace(`old denom sel: ${j2s(oldSel)}`);
+ }
- let denomOkay: boolean = false;
+ let prevTotalCoinValue = zero;
+ let prevTotalWithdrawalCost = zero;
+ let prevHasDenomWithAgeRestriction = false;
+ let prevEarliestDepositExpiration = AbsoluteTime.never();
+ const prevDenoms: DenomSelItem[] = [];
+ let coinIndex = 0;
+ for (let i = 0; i < oldSel.selectedDenoms.length; i++) {
+ const sel = wg.denomsSel.selectedDenoms[i];
+ const denom = await tx.denominations.get([
+ exchangeBaseUrl,
+ sel.denomPubHash,
+ ]);
- if (denom != null) {
- const denomOkay = isWithdrawableDenom(denom);
- const numCoins = sel.count - (sel.skip ?? 0);
+ let denomOkay: boolean = false;
- const denomValue = Amount.from(denom.value).mult(numCoins);
- const denomFeeWithdraw = Amount.from(denom.fees.feeWithdraw).mult(
- numCoins,
- );
+ if (denom != null) {
+ const denomOkay = isWithdrawableDenom(denom);
+ const numCoins = sel.count - (sel.skip ?? 0);
- if (denomOkay) {
- remaining = remaining.sub(denomValue).sub(denomFeeWithdraw);
- prevTotalCoinValue = prevTotalCoinValue.add(denomValue);
- prevTotalWithdrawalCost = prevTotalWithdrawalCost.add(
- denomValue,
- denomFeeWithdraw,
- );
- prevDenoms.push({
- count: sel.count,
- denomPubHash: sel.denomPubHash,
- skip: sel.skip,
- });
- prevHasDenomWithAgeRestriction =
- prevHasDenomWithAgeRestriction || denom.denomPub.age_mask > 0;
- prevEarliestDepositExpiration = AbsoluteTime.min(
- prevEarliestDepositExpiration,
- timestampAbsoluteFromDb(denom.stampExpireDeposit),
- );
- }
- }
+ const denomValue = Amount.from(denom.value).mult(numCoins);
+ const denomFeeWithdraw = Amount.from(denom.fees.feeWithdraw).mult(
+ numCoins,
+ );
- if (!denomOkay) {
+ if (denomOkay) {
+ remaining = remaining.sub(denomValue).sub(denomFeeWithdraw);
+ prevTotalCoinValue = prevTotalCoinValue.add(denomValue);
+ prevTotalWithdrawalCost = prevTotalWithdrawalCost.add(
+ denomValue,
+ denomFeeWithdraw,
+ );
prevDenoms.push({
count: sel.count,
denomPubHash: sel.denomPubHash,
- skip: sel.count,
+ skip: sel.skip,
});
+ prevHasDenomWithAgeRestriction =
+ prevHasDenomWithAgeRestriction || denom.denomPub.age_mask > 0;
+ prevEarliestDepositExpiration = AbsoluteTime.min(
+ prevEarliestDepositExpiration,
+ timestampAbsoluteFromDb(denom.stampExpireDeposit),
+ );
+ }
+ }
- for (let j = 0; j < sel.count; j++) {
- const ci = coinIndex + j;
- const p = await tx.planchets.indexes.byGroupAndIndex.get([
- withdrawalGroupId,
- ci,
- ]);
- if (!p) {
- // Maybe planchet wasn't yet generated.
- // No problem!
- logger.info(
- `not aborting planchet #${coinIndex}, planchet not found`,
- );
- continue;
- }
- // Technically the planchet could already
- // have been withdrawn, then we're in for another
- // re-denomination later.
- logger.info(`aborting planchet #${coinIndex}`);
- p.planchetStatus = PlanchetStatus.AbortedReplaced;
- await tx.planchets.put(p);
+ if (!denomOkay) {
+ prevDenoms.push({
+ count: sel.count,
+ denomPubHash: sel.denomPubHash,
+ skip: sel.count,
+ });
+
+ for (let j = 0; j < sel.count; j++) {
+ const ci = coinIndex + j;
+ const p = await tx.planchets.indexes.byGroupAndIndex.get([
+ withdrawalGroupId,
+ ci,
+ ]);
+ if (!p) {
+ // Maybe planchet wasn't yet generated.
+ // No problem!
+ logger.info(
+ `not aborting planchet #${coinIndex}, planchet not found`,
+ );
+ continue;
}
+ // Technically the planchet could already
+ // have been withdrawn, then we're in for another
+ // re-denomination later.
+ logger.info(`aborting planchet #${coinIndex}`);
+ p.planchetStatus = PlanchetStatus.AbortedReplaced;
+ await tx.planchets.put(p);
}
-
- coinIndex += sel.count;
}
- const newSel = selectWithdrawalDenominations(
- remaining.toJson(),
- candidates,
- );
+ coinIndex += sel.count;
+ }
- if (logger.shouldLogTrace()) {
- logger.trace(`new denom sel: ${j2s(newSel)}`);
- }
+ const newSel = selectWithdrawalDenominations(
+ remaining.toJson(),
+ candidates,
+ );
- const mergedSel: DenomSelectionState = {
- selectedDenoms: [...prevDenoms, ...newSel.selectedDenoms],
- totalCoinValue: zero
- .add(prevTotalCoinValue, newSel.totalCoinValue)
- .toString(),
- totalWithdrawCost: zero
- .add(prevTotalWithdrawalCost, newSel.totalWithdrawCost)
- .toString(),
- hasDenomWithAgeRestriction:
- prevHasDenomWithAgeRestriction || newSel.hasDenomWithAgeRestriction,
- };
- wg.denomsSel = mergedSel;
- if (logger.shouldLogTrace()) {
- logger.trace(`merged denom sel: ${j2s(mergedSel)}`);
- }
- await tx.withdrawalGroups.put(wg);
- },
- );
+ if (logger.shouldLogTrace()) {
+ logger.trace(`new denom sel: ${j2s(newSel)}`);
+ }
+
+ const mergedSel: DenomSelectionState = {
+ selectedDenoms: [...prevDenoms, ...newSel.selectedDenoms],
+ totalCoinValue: zero
+ .add(prevTotalCoinValue, newSel.totalCoinValue)
+ .toString(),
+ totalWithdrawCost: zero
+ .add(prevTotalWithdrawalCost, newSel.totalWithdrawCost)
+ .toString(),
+ hasDenomWithAgeRestriction:
+ prevHasDenomWithAgeRestriction || newSel.hasDenomWithAgeRestriction,
+ };
+ wg.denomsSel = mergedSel;
+ if (logger.shouldLogTrace()) {
+ logger.trace(`merged denom sel: ${j2s(mergedSel)}`);
+ }
+ await tx.withdrawalGroups.put(wg);
+ });
}
async function processWithdrawalGroupPendingReady(
@@ -2778,7 +2728,7 @@ async function processWithdrawalGroupPendingReady(
wgRecord: withdrawalGroup,
};
- await wex.db.runReadOnlyTx({ storeNames: ["planchets"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const planchets =
await tx.planchets.indexes.byGroup.getAll(withdrawalGroupId);
for (const p of planchets) {
@@ -2854,7 +2804,7 @@ async function processWithdrawalGroupPendingReady(
let redenomRequired = false;
- await wex.db.runReadOnlyTx({ storeNames: ["planchets"] }, async (tx) => {
+ await wex.runLegacyWalletDbTx(async (tx) => {
const planchets =
await tx.planchets.indexes.byGroup.getAll(withdrawalGroupId);
for (const p of planchets) {
@@ -2984,9 +2934,8 @@ export async function processWithdrawalGroup(
}
logger.trace("processing withdrawal group", withdrawalGroupId);
- const withdrawalGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups"] },
- (tx) => tx.withdrawalGroups.get(withdrawalGroupId),
+ const withdrawalGroup = await wex.runLegacyWalletDbTx((tx) =>
+ tx.withdrawalGroups.get(withdrawalGroupId),
);
if (!withdrawalGroup) {
@@ -3308,12 +3257,12 @@ export async function getFundingPaytoUris(
}
async function getWithdrawalGroupRecordTx(
- db: DbAccess<typeof WalletStoresV1>,
+ wex: WalletExecutionContext,
req: {
withdrawalGroupId: string;
},
): Promise<WithdrawalGroupRecord | undefined> {
- return await db.runReadOnlyTx({ storeNames: ["withdrawalGroups"] }, (tx) =>
+ return await wex.runLegacyWalletDbTx((tx) =>
tx.withdrawalGroups.get(req.withdrawalGroupId),
);
}
@@ -3351,9 +3300,8 @@ async function registerReserveWithBank(
withdrawalGroupId: string,
isFlexibleAmount: boolean,
): Promise<TaskRunResult> {
- const withdrawalGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups"] },
- (tx) => tx.withdrawalGroups.get(withdrawalGroupId),
+ const withdrawalGroup = await wex.runLegacyWalletDbTx((tx) =>
+ tx.withdrawalGroups.get(withdrawalGroupId),
);
const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
switch (withdrawalGroup?.status) {
@@ -3485,7 +3433,7 @@ async function processBankRegisterReserve(
withdrawalGroupId: string,
): Promise<TaskRunResult> {
const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
- const withdrawalGroup = await getWithdrawalGroupRecordTx(wex.db, {
+ const withdrawalGroup = await getWithdrawalGroupRecordTx(wex, {
withdrawalGroupId,
});
if (!withdrawalGroup) {
@@ -3560,7 +3508,7 @@ async function processReserveBankStatus(
wex: WalletExecutionContext,
withdrawalGroupId: string,
): Promise<TaskRunResult> {
- const withdrawalGroup = await getWithdrawalGroupRecordTx(wex.db, {
+ const withdrawalGroup = await getWithdrawalGroupRecordTx(wex, {
withdrawalGroupId,
});
@@ -3729,9 +3677,8 @@ export async function internalPrepareCreateWithdrawalGroup(
if (args.forcedWithdrawalGroupId) {
withdrawalGroupId = args.forcedWithdrawalGroupId;
const wgId = withdrawalGroupId;
- const existingWg = await wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups"] },
- (tx) => tx.withdrawalGroups.get(wgId),
+ const existingWg = await wex.runLegacyWalletDbTx((tx) =>
+ tx.withdrawalGroups.get(wgId),
);
if (existingWg) {
@@ -3907,23 +3854,11 @@ export async function internalCreateWithdrawalGroup(
wex,
prep.withdrawalGroup.withdrawalGroupId,
);
- const res = await wex.db.runReadWriteTx(
- {
- storeNames: [
- "withdrawalGroups",
- "reserves",
- "exchanges",
- "exchangeDetails",
- "transactionsMeta",
- "operationRetries",
- ],
- },
- async (tx) => {
- const res = await internalPerformCreateWithdrawalGroup(wex, tx, prep);
- await ctx.updateTransactionMeta(tx);
- return res;
- },
- );
+ const res = await wex.runLegacyWalletDbTx(async (tx) => {
+ const res = await internalPerformCreateWithdrawalGroup(wex, tx, prep);
+ await ctx.updateTransactionMeta(tx);
+ return res;
+ });
return res.withdrawalGroup;
}
@@ -3934,10 +3869,8 @@ export async function prepareBankIntegratedWithdrawal(
isForeignAccount?: boolean;
},
): Promise<PrepareBankIntegratedWithdrawalResponse> {
- const existingWithdrawalGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups"] },
- (tx) =>
- tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(req.talerWithdrawUri),
+ const existingWithdrawalGroup = await wex.runLegacyWalletDbTx((tx) =>
+ tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(req.talerWithdrawUri),
);
const parsedUri = parseTalerUri(req.talerWithdrawUri);
@@ -4067,9 +4000,8 @@ export async function confirmWithdrawal(
if (parsedTx?.tag !== TransactionType.Withdrawal) {
throw Error("invalid withdrawal transaction ID");
}
- const withdrawalGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups"] },
- (tx) => tx.withdrawalGroups.get(parsedTx.withdrawalGroupId),
+ const withdrawalGroup = await wex.runLegacyWalletDbTx((tx) =>
+ tx.withdrawalGroups.get(parsedTx.withdrawalGroupId),
);
if (!withdrawalGroup) {
@@ -4083,15 +4015,12 @@ export async function confirmWithdrawal(
throw Error("not a bank integrated withdrawal");
}
- await wex.db.runReadOnlyTx(
- { storeNames: ["exchangeBaseUrlFixups"] },
- async (tx) => {
- const rec = await tx.exchangeBaseUrlFixups.get(selectedExchange);
- if (rec) {
- selectedExchange = rec.replacement;
- }
- },
- );
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ const rec = await tx.exchangeBaseUrlFixups.get(selectedExchange);
+ if (rec) {
+ selectedExchange = rec.replacement;
+ }
+ });
let instructedCurrency: string;
if (instructedAmount) {
@@ -4187,9 +4116,9 @@ export async function confirmWithdrawal(
logger.info(`adding account ${senderWire} to know bank accounts`);
- const bankAccountId = await wex.db.runReadWriteTx(
- { storeNames: ["bankAccountsV2"] },
- (tx) => storeKnownBankAccount(tx, instructedCurrency, senderWire),
+ const bankAccountId = await wex.runLegacyWalletDbTx(
+ async (tx) =>
+ await storeKnownBankAccount(tx, instructedCurrency, senderWire),
);
if (bankAccountId) {
@@ -4394,10 +4323,11 @@ export async function acceptBankIntegratedWithdrawal(
contents: "confirmed acceptBankIntegratedWithdrawal",
});
- const newWithdrawralGroup = await wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups"] },
- (tx) =>
- tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(req.talerWithdrawUri),
+ const newWithdrawralGroup = await wex.runLegacyWalletDbTx(
+ async (tx) =>
+ await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
+ req.talerWithdrawUri,
+ ),
);
checkDbInvariant(
@@ -4603,9 +4533,9 @@ export async function createManualWithdrawal(
withdrawalGroup.withdrawalGroupId,
);
- const exchangePaytoUris = await wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups", "exchanges", "exchangeDetails"] },
- (tx) => getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId),
+ const exchangePaytoUris = await wex.runLegacyWalletDbTx(
+ async (tx) =>
+ await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId),
);
wex.ws.notify({
@@ -4642,9 +4572,8 @@ export async function waitWithdrawalFinal(
},
async checkState() {
// Check if withdrawal is final
- const wg = await ctx.wex.db.runReadOnlyTx(
- { storeNames: ["withdrawalGroups"] },
- (tx) => tx.withdrawalGroups.get(ctx.withdrawalGroupId),
+ const wg = await ctx.wex.runLegacyWalletDbTx(
+ async (tx) => await tx.withdrawalGroups.get(ctx.withdrawalGroupId),
);
if (!wg) {
// Must've been deleted, we consider that final.
diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx
@@ -24,7 +24,11 @@
/**
* Imports.
*/
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { parseTalerUri, TalerUriAction } from "@gnu-taler/taler-util";
+import {
+ encodeCrockForURI,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { EnabledBySettings } from "./components/EnabledBySettings.js";
import {
@@ -32,17 +36,9 @@ import {
NavigationHeaderHolder,
SvgIcon,
} from "./components/styled/index.js";
-import { useBackendContext } from "./context/backend.js";
-import { useAsyncAsHook } from "./hooks/useAsyncAsHook.js";
-import searchIcon from "./svg/search_24px.inline.svg";
import qrIcon from "./svg/qr_code_24px.inline.svg";
+import searchIcon from "./svg/search_24px.inline.svg";
import settingsIcon from "./svg/settings_black_24dp.inline.svg";
-import warningIcon from "./svg/warning_24px.inline.svg";
-import { parseTalerUri, TalerUriAction } from "@gnu-taler/taler-util";
-import {
- encodeCrockForURI,
- useTranslationContext,
-} from "@gnu-taler/web-util/browser";
/**
* List of pages used by the wallet
@@ -118,9 +114,7 @@ export const Pages = {
balanceDeposit: pageDefinition<{
scope: CrockEncodedString;
}>("/balance/deposit/:scope"),
- contacts: pageDefinition<{ tid?: string }>(
- "/contacts/:tid?",
- ),
+ contacts: pageDefinition<{ tid?: string }>("/contacts/:tid?"),
contactsAdd: "/contacts/add/",
mailbox: "/mailbox",
sendCash: pageDefinition<{ scope: CrockEncodedString; amount?: string }>(
@@ -184,7 +178,9 @@ export const Pages = {
amount?: string;
}>("/cta/scope-withdraw/:scope/:amount?"),
ctaWithdrawTransferResult: "/cta/transfer-result",
- ctaWithdrawManual: pageDefinition<{amount?: string;}>("/cta/manual-withdraw/:amount?"),
+ ctaWithdrawManual: pageDefinition<{ amount?: string }>(
+ "/cta/manual-withdraw/:amount?",
+ ),
paytoQrs: pageDefinition<{ payto: CrockEncodedString }>("/payto/qrs/:payto?"),
paytoBanks: pageDefinition<{ payto: CrockEncodedString }>(
"/payto/banks/:payto?",
@@ -226,15 +222,6 @@ export function getPathnameForTalerURI(talerUri: string): string | undefined {
export type PopupNavBarOptions = "balance" | "backup" | "dev";
export function PopupNavBar({ path }: { path?: PopupNavBarOptions }): VNode {
- const api = useBackendContext();
- const hook = useAsyncAsHook(async () => {
- return await api.wallet.call(
- WalletApiOperation.GetUserAttentionUnreadCount,
- {},
- );
- });
- const attentionCount = !hook || hook.hasError ? 0 : hook.response.total;
-
const { i18n } = useTranslationContext();
return (
<NavigationHeader>
@@ -247,17 +234,6 @@ export function PopupNavBar({ path }: { path?: PopupNavBarOptions }): VNode {
</a>
</EnabledBySettings>
<div style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}>
- {attentionCount > 0 ? (
- <a href={`#${Pages.notifications}`}>
- <SvgIcon
- title={i18n.str`Notifications`}
- dangerouslySetInnerHTML={{ __html: warningIcon }}
- color="yellow"
- />
- </a>
- ) : (
- <Fragment />
- )}
<a href={`#${Pages.qr}`}>
<SvgIcon
title={i18n.str`QR Reader and Taler URI`}
@@ -276,20 +252,14 @@ export function PopupNavBar({ path }: { path?: PopupNavBarOptions }): VNode {
</NavigationHeader>
);
}
-export type WalletNavBarOptions = "balance" | "backup" | "dev" | "contacts" | "mailbox";
+export type WalletNavBarOptions =
+ | "balance"
+ | "backup"
+ | "dev"
+ | "contacts"
+ | "mailbox";
export function WalletNavBar({ path }: { path?: WalletNavBarOptions }): VNode {
const { i18n } = useTranslationContext();
-
- const api = useBackendContext();
- const hook = useAsyncAsHook(async () => {
- return await api.wallet.call(
- WalletApiOperation.GetUserAttentionUnreadCount,
- {},
- );
- });
- const attentionCount =
- (!hook || hook.hasError ? 0 : hook.response?.total) ?? 0;
-
return (
<NavigationHeaderHolder>
<NavigationHeader>
@@ -307,28 +277,25 @@ export function WalletNavBar({ path }: { path?: WalletNavBarOptions }): VNode {
<i18n.Translate>Backup</i18n.Translate>
</a>
</EnabledBySettings>
-
- {attentionCount > 0 ? (
- <a href={`#${Pages.notifications}`}>
- <i18n.Translate>Notifications</i18n.Translate>
- </a>
- ) : (
- <Fragment />
- )}
-
<EnabledBySettings name="advancedMode">
<a href={`#${Pages.dev}`} class={path === "dev" ? "active" : ""}>
<i18n.Translate>Dev tools</i18n.Translate>
</a>
</EnabledBySettings>
<EnabledBySettings name="p2p_aliases">
- <a href={`#${Pages.contacts({})}`} class={path === "contacts" ? "active" : ""}>
- <i18n.Translate>Contacts</i18n.Translate>
- </a>
- <a href={`#${Pages.mailbox}`} class={path === "mailbox" ? "active" : ""}>
- <i18n.Translate>Mailbox</i18n.Translate>
- </a>
- </EnabledBySettings>
+ <a
+ href={`#${Pages.contacts({})}`}
+ class={path === "contacts" ? "active" : ""}
+ >
+ <i18n.Translate>Contacts</i18n.Translate>
+ </a>
+ <a
+ href={`#${Pages.mailbox}`}
+ class={path === "mailbox" ? "active" : ""}
+ >
+ <i18n.Translate>Mailbox</i18n.Translate>
+ </a>
+ </EnabledBySettings>
<div
style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}
diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/index.ts b/packages/taler-wallet-webextension/src/cta/Recovery/index.ts
@@ -1,65 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { ErrorAlertView } from "../../components/CurrentAlerts.js";
-import { Loading } from "../../components/Loading.js";
-import { ErrorAlert } from "../../context/alert.js";
-import { ButtonHandler } from "../../mui/handlers.js";
-import { compose, StateViewMap } from "../../utils/index.js";
-import { useComponentState } from "./state.js";
-import { ReadyView } from "./views.js";
-
-export interface Props {
- talerRecoveryUri?: string;
- onCancel: () => Promise<void>;
- onSuccess: () => Promise<void>;
-}
-
-export type State = State.Loading | State.LoadingUriError | State.Ready;
-
-export namespace State {
- export interface Loading {
- status: "loading";
- error: undefined;
- }
-
- export interface LoadingUriError {
- status: "error";
- error: ErrorAlert;
- }
-
- export interface BaseInfo {
- error: undefined;
- cancel: ButtonHandler;
- }
-
- export interface Ready extends BaseInfo {
- status: "ready";
- accept: ButtonHandler;
- }
-}
-
-const viewMapping: StateViewMap<State> = {
- loading: Loading,
- error: ErrorAlertView,
- ready: ReadyView,
-};
-
-export const RecoveryPage = compose(
- "Recovery",
- (p: Props) => useComponentState(p),
- viewMapping,
-);
diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts
@@ -1,84 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { parseRestoreUri } from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { useAlertContext } from "../../context/alert.js";
-import { useBackendContext } from "../../context/backend.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Props, State } from "./index.js";
-
-export function useComponentState({
- talerRecoveryUri,
- onCancel,
- onSuccess,
-}: Props): State {
- const api = useBackendContext();
- const { pushAlertOnError } = useAlertContext();
- const { i18n } = useTranslationContext();
- if (!talerRecoveryUri) {
- return {
- status: "error",
- error: {
- type: "error",
- message: i18n.str`Missing URI`,
- description: i18n.str`Check the link.`,
- cause: new Error("something"),
- context: {},
- },
- };
- }
- const info = parseRestoreUri(talerRecoveryUri);
-
- if (!info) {
- return {
- status: "error",
- error: {
- type: "error",
- message: i18n.str`Could not parse the recovery URI`,
- description: i18n.str`Try another URI`,
- cause: new Error("something"),
- context: {},
- },
- };
- }
- const recovery = info;
-
- async function recoverBackup(): Promise<void> {
- await api.wallet.call(WalletApiOperation.ImportBackupRecovery, {
- recovery: {
- walletRootPriv: recovery.walletRootPriv,
- providers: recovery.providers.map((url) => ({
- name: new URL(url).hostname,
- url,
- })),
- },
- });
- onSuccess();
- }
-
- return {
- status: "ready",
-
- accept: {
- onClick: pushAlertOnError(recoverBackup),
- },
- cancel: {
- onClick: pushAlertOnError(onCancel),
- },
- error: undefined,
- };
-}
diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/stories.tsx b/packages/taler-wallet-webextension/src/cta/Recovery/stories.tsx
@@ -1,28 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { Amounts } from "@gnu-taler/taler-util";
-import * as tests from "@gnu-taler/web-util/testing";
-import { ReadyView } from "./views.js";
-
-export default {
- title: "recovery",
-};
diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/test.ts b/packages/taler-wallet-webextension/src/cta/Recovery/test.ts
@@ -1,21 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-describe("Backup import CTA states", () => {
- it.skip("should test something", async () => {
- return;
- });
-});
diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/views.tsx b/packages/taler-wallet-webextension/src/cta/Recovery/views.tsx
@@ -1,41 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { Fragment, h, VNode } from "preact";
-import { LogoHeader } from "../../components/LogoHeader.js";
-import { SubTitle, WalletAction } from "../../components/styled/index.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Button } from "../../mui/Button.js";
-import { State } from "./index.js";
-
-export function ReadyView({ accept, cancel }: State.Ready): VNode {
- const { i18n } = useTranslationContext();
- return (
- <Fragment>
- <section>
- <p>
- <i18n.Translate>Import backup, show info</i18n.Translate>
- </p>
- <Button variant="contained" onClick={accept.onClick}>
- Import
- </Button>
- <Button variant="contained" onClick={cancel.onClick}>
- Cancel
- </Button>
- </section>
- </Fragment>
- );
-}
diff --git a/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts b/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts
@@ -1,54 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { useEffect, useState } from "preact/hooks";
-import { useBackendContext } from "../context/backend.js";
-
-export interface BackupDeviceName {
- name: string;
- update: (s: string) => Promise<void>;
-}
-
-export function useBackupDeviceName(): BackupDeviceName {
- const [status, setStatus] = useState<BackupDeviceName>({
- name: "",
- update: () => Promise.resolve(),
- });
- const api = useBackendContext();
-
- useEffect(() => {
- async function run(): Promise<void> {
- //create a first list of backup info by currency
- const status = await api.wallet.call(
- WalletApiOperation.GetBackupInfo,
- {},
- );
-
- async function update(newName: string): Promise<void> {
- await api.wallet.call(WalletApiOperation.SetWalletDeviceId, {
- walletDeviceId: newName,
- });
- setStatus((old) => ({ ...old, name: newName }));
- }
-
- setStatus({ name: status.deviceId, update });
- }
- run();
- }, []);
-
- return status;
-}
diff --git a/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts b/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts
@@ -1,66 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { ProviderInfo } from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { useEffect, useState } from "preact/hooks";
-import { useBackendContext } from "../context/backend.js";
-
-export interface ProviderStatus {
- info?: ProviderInfo;
- sync: () => Promise<void>;
- remove: () => Promise<void>;
-}
-
-export function useProviderStatus(url: string): ProviderStatus | undefined {
- const [status, setStatus] = useState<ProviderStatus | undefined>(undefined);
- const api = useBackendContext();
- useEffect(() => {
- async function run(): Promise<void> {
- //create a first list of backup info by currency
- const status = await api.wallet.call(
- WalletApiOperation.GetBackupInfo,
- {},
- );
-
- const providers = status.providers.filter(
- (p) => p.syncProviderBaseUrl === url,
- );
- const info = providers.length ? providers[0] : undefined;
-
- async function sync(): Promise<void> {
- if (info) {
- await api.wallet.call(WalletApiOperation.RunBackupCycle, {
- providers: [info.syncProviderBaseUrl],
- });
- }
- }
-
- async function remove(): Promise<void> {
- if (info) {
- await api.wallet.call(WalletApiOperation.RemoveBackupProvider, {
- provider: info.syncProviderBaseUrl,
- });
- }
- }
-
- setStatus({ info, sync, remove });
- }
- run();
- });
-
- return status;
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Application.tsx b/packages/taler-wallet-webextension/src/popup/Application.tsx
@@ -23,9 +23,9 @@
import { ScopeInfo, stringifyScopeInfoShort } from "@gnu-taler/taler-util";
import {
TranslationProvider,
- useTranslationContext,
- encodeCrockForURI,
decodeCrockFromURI,
+ encodeCrockForURI,
+ useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { createHashHistory } from "history";
import { ComponentChildren, Fragment, VNode, h } from "preact";
@@ -39,8 +39,6 @@ import { IoCProviderForRuntime } from "../context/iocContext.js";
import { useTalerActionURL } from "../hooks/useTalerActionURL.js";
import { strings } from "../i18n/strings.js";
import { platform } from "../platform/foreground.js";
-import { BackupPage } from "../wallet/BackupPage.js";
-import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js";
import { BalancePage } from "./BalancePage.js";
import { TalerActionFound } from "./TalerActionFound.js";
@@ -133,38 +131,6 @@ function ApplicationView(): VNode {
/>
<Route
- path={Pages.backup}
- component={() => (
- <PopupTemplate
- path="backup"
- goToTransaction={redirectToTxInfo}
- goToURL={redirectToURL}
- >
- <BackupPage
- onAddProvider={() => redirectTo(Pages.backupProviderAdd)}
- />
- </PopupTemplate>
- )}
- />
- <Route
- path={Pages.backupProviderDetail.pattern}
- component={({ pid }: { pid: string }) => (
- <PopupTemplate path="backup" goToURL={redirectToURL}>
- <ProviderDetailPage
- onPayProvider={(uri: string) =>
- redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
- }
- onWithdraw={async (_amount: string) => {
- // redirectTo(Pages.receiveCash({ amount }))
- }}
- pid={pid}
- onBack={() => redirectTo(Pages.backup)}
- />
- </PopupTemplate>
- )}
- />
-
- <Route
path={Pages.balanceTransaction.pattern}
component={RedirectToWalletPage}
/>
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts
@@ -1,88 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
- SyncTermsOfServiceResponse,
- TalerErrorDetail,
-} from "@gnu-taler/taler-util";
-import { ErrorAlertView } from "../../components/CurrentAlerts.js";
-import { Loading } from "../../components/Loading.js";
-import { ErrorAlert } from "../../context/alert.js";
-import {
- ButtonHandler,
- TextFieldHandler,
- ToggleHandler,
-} from "../../mui/handlers.js";
-import { StateViewMap, compose } from "../../utils/index.js";
-import { useComponentState } from "./state.js";
-import { ConfirmProviderView, SelectProviderView } from "./views.js";
-
-export interface Props {
- onBack: () => Promise<void>;
- onComplete: (pid: string) => Promise<void>;
- onPaymentRequired: (uri: string) => Promise<void>;
-}
-
-export type State =
- | State.Loading
- | State.LoadingUriError
- | State.ConfirmProvider
- | State.SelectProvider;
-
-export namespace State {
- export interface Loading {
- status: "loading";
- error: undefined;
- }
-
- export interface LoadingUriError {
- status: "error";
- error: ErrorAlert;
- }
-
- export interface ConfirmProvider {
- status: "confirm-provider";
- error: undefined;
- url: string;
- provider: SyncTermsOfServiceResponse;
- tos: ToggleHandler;
- onCancel: ButtonHandler;
- onAccept: ButtonHandler;
- }
-
- export interface SelectProvider {
- status: "select-provider";
- url: TextFieldHandler;
- urlOk: boolean;
- name: TextFieldHandler;
- onConfirm: ButtonHandler;
- onCancel: ButtonHandler;
- error: undefined | TalerErrorDetail;
- }
-}
-
-const viewMapping: StateViewMap<State> = {
- loading: Loading,
- error: ErrorAlertView,
- "select-provider": SelectProviderView,
- "confirm-provider": ConfirmProviderView,
-};
-
-export const AddBackupProviderPage = compose(
- "AddBackupProvider",
- (p: Props) => useComponentState(p),
- viewMapping,
-);
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts
@@ -1,263 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022-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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
- canonicalizeBaseUrl,
- Codec,
- codecForSyncTermsOfServiceResponse,
- assertUnreachable
-} from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { useEffect, useState } from "preact/hooks";
-import { useAlertContext } from "../../context/alert.js";
-import { useBackendContext } from "../../context/backend.js";
-import { Props, State } from "./index.js";
-
-type UrlState<T> = UrlOk<T> | UrlError;
-
-interface UrlOk<T> {
- status: "ok";
- result: T;
-}
-type UrlError =
- | UrlNetworkError
- | UrlClientError
- | UrlServerError
- | UrlParsingError
- | UrlReadError;
-
-interface UrlNetworkError {
- status: "network-error";
- href: string;
-}
-interface UrlClientError {
- status: "client-error";
- code: number;
-}
-interface UrlServerError {
- status: "server-error";
- code: number;
-}
-interface UrlParsingError {
- status: "parsing-error";
- json: any;
-}
-interface UrlReadError {
- status: "url-error";
-}
-
-function useDebounceEffect(
- time: number,
- cb: undefined | (() => Promise<void>),
- deps: Array<any>,
-): void {
- const [currentTimer, setCurrentTimer] = useState<any>();
- useEffect(() => {
- if (currentTimer !== undefined) clearTimeout(currentTimer);
- if (cb !== undefined) {
- const tid = setTimeout(cb, time);
- setCurrentTimer(tid);
- }
- }, deps);
-}
-
-function useUrlState<T>(
- host: string | undefined,
- path: string,
- codec: Codec<T>,
-): UrlState<T> | undefined {
- const [state, setState] = useState<UrlState<T> | undefined>();
-
- let href: string | undefined;
- try {
- if (host) {
- const isHttps =
- host.startsWith("https://") && host.length > "https://".length;
- const isHttp =
- host.startsWith("http://") && host.length > "http://".length;
- const withProto = isHttp || isHttps ? host : `https://${host}`;
- const baseUrl = canonicalizeBaseUrl(withProto);
- href = new URL(path, baseUrl).href;
- }
- } catch (e) {
- setState({
- status: "url-error",
- });
- }
- const constHref = href;
-
- async function checkURL() {
- if (!constHref) {
- return;
- }
- const req = await fetch(constHref).catch((e) => {
- return setState({
- status: "network-error",
- href: constHref,
- });
- });
- if (!req) return;
-
- if (req.status >= 400 && req.status < 500) {
- setState({
- status: "client-error",
- code: req.status,
- });
- return;
- }
- if (req.status > 500) {
- setState({
- status: "server-error",
- code: req.status,
- });
- return;
- }
-
- const json = await req.json();
- try {
- const result = codec.decode(json);
- setState({ status: "ok", result });
- } catch (e: any) {
- setState({ status: "parsing-error", json });
- }
- }
-
- useDebounceEffect(
- 500,
- constHref == undefined ? undefined : checkURL,
- [host, path],
- );
-
- return state;
-}
-
-export function useComponentState({
- onBack,
- onComplete,
- onPaymentRequired,
-}: Props): State {
- const api = useBackendContext();
- const [url, setHost] = useState<string | undefined>();
- const [name, setName] = useState<string | undefined>();
- const [tos, setTos] = useState(false);
- const { pushAlertOnError } = useAlertContext();
- const urlState = useUrlState(
- url,
- "config",
- codecForSyncTermsOfServiceResponse(),
- );
- const [showConfirm, setShowConfirm] = useState(false);
-
- async function addBackupProvider(): Promise<void> {
- if (!url || !name) return;
-
- const resp = await api.wallet.call(WalletApiOperation.AddBackupProvider, {
- backupProviderBaseUrl: url,
- name: name,
- activate: true,
- });
-
- switch (resp.status) {
- case "payment-required":
- if (resp.talerUri) {
- return onPaymentRequired(resp.talerUri);
- } else {
- return onComplete(url);
- }
- case "ok":
- return onComplete(url);
- default:
- assertUnreachable(resp);
- }
- }
-
- if (showConfirm && urlState && urlState.status === "ok") {
- return {
- status: "confirm-provider",
- error: undefined,
- onAccept: {
- onClick: !tos ? undefined : pushAlertOnError(addBackupProvider),
- },
- onCancel: {
- onClick: pushAlertOnError(onBack),
- },
- provider: urlState.result,
- tos: {
- value: tos,
- button: {
- onClick: pushAlertOnError(async () => setTos(!tos)),
- },
- },
- url: url ?? "",
- };
- }
-
- return {
- status: "select-provider",
- error: undefined,
- name: {
- value: name || "",
- onInput: pushAlertOnError(async (e) => setName(e)),
- error:
- name === undefined ? undefined : !name ? "Can't be empty" : undefined,
- },
- onCancel: {
- onClick: pushAlertOnError(onBack),
- },
- onConfirm: {
- onClick:
- !urlState || urlState.status !== "ok" || !name
- ? undefined
- : pushAlertOnError(async () => {
- setShowConfirm(true);
- }),
- },
- urlOk: urlState?.status === "ok",
- url: {
- value: url || "",
- onInput: pushAlertOnError(async (e) => setHost(e)),
- error: errorString(urlState),
- },
- };
-}
-
-function errorString(state: undefined | UrlState<any>): string | undefined {
- if (!state) return state;
- switch (state.status) {
- case "ok":
- return undefined;
- case "client-error": {
- switch (state.code) {
- case 404:
- return "Not found";
- case 401:
- return "Unauthorized";
- case 403:
- return "Forbidden";
- default:
- return `Server says it a client error: ${state.code}.`;
- }
- }
- case "server-error":
- return `Server had a problem ${state.code}.`;
- case "parsing-error":
- return `Server response doesn't have the right format.`;
- case "network-error":
- return `Unable to connect to ${state.href}.`;
- case "url-error":
- return "URL is not complete";
- }
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/stories.tsx b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/stories.tsx
@@ -1,110 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import * as tests from "@gnu-taler/web-util/testing";
-import { ConfirmProviderView, SelectProviderView } from "./views.js";
-import { AmountString } from "@gnu-taler/taler-util";
-
-export default {
- title: "add backup provider",
-};
-
-export const DemoService = tests.createExample(ConfirmProviderView, {
- url: "https://sync.demo.taler.net/",
- provider: {
- annual_fee: "KUDOS:0.1" as AmountString,
- storage_limit_in_megabytes: 20,
- version: "1",
- },
- tos: {
- button: {},
- },
- onAccept: {},
- onCancel: {},
-});
-
-export const FreeService = tests.createExample(ConfirmProviderView, {
- url: "https://sync.taler:9667/",
- provider: {
- annual_fee: "ARS:0" as AmountString,
- storage_limit_in_megabytes: 20,
- version: "1",
- },
- tos: {
- button: {},
- },
- onAccept: {},
- onCancel: {},
-});
-
-export const Initial = tests.createExample(SelectProviderView, {
- url: { value: "" },
- name: { value: "" },
- onCancel: {},
- onConfirm: {},
-});
-
-export const WithValue = tests.createExample(SelectProviderView, {
- url: {
- value: "sync.demo.taler.net",
- },
- name: {
- value: "Demo backup service",
- },
- onCancel: {},
- onConfirm: {},
-});
-
-export const WithConnectionError = tests.createExample(SelectProviderView, {
- url: {
- value: "sync.demo.taler.net",
- error: "Network error",
- },
- name: {
- value: "Demo backup service",
- },
- onCancel: {},
- onConfirm: {},
-});
-
-export const WithClientError = tests.createExample(SelectProviderView, {
- url: {
- value: "sync.demo.taler.net",
- error: "URL may not be right: (404) Not Found",
- },
- name: {
- value: "Demo backup service",
- },
- onCancel: {},
- onConfirm: {},
-});
-
-export const WithServerError = tests.createExample(SelectProviderView, {
- url: {
- value: "sync.demo.taler.net",
- error: "Try another server: (500) Internal Server Error",
- },
- name: {
- value: "Demo backup service",
- },
- onCancel: {},
- onConfirm: {},
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts
@@ -1,68 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { expect } from "chai";
-import * as tests from "@gnu-taler/web-util/testing";
-import { nullFunction } from "../../mui/handlers.js";
-import { createWalletApiMock } from "../../test-utils.js";
-import { Props } from "./index.js";
-import { useComponentState } from "./state.js";
-
-const props: Props = {
- onBack: nullFunction,
- onComplete: nullFunction,
- onPaymentRequired: nullFunction,
-};
-describe("AddBackupProvider states", () => {
- /**
- * FIXME: this test has inconsistent behavior.
- * it should always expect one state but for some reason
- * (maybe race condition) it sometime expect 1 update when
- * it should no update
- */
- it.skip("should start in 'select-provider' state", async () => {
- const { handler, TestingContext } = createWalletApiMock();
-
- const hookBehavior = await tests.hookBehaveLikeThis(
- useComponentState,
- props,
- [
- (state) => {
- expect(state.status).equal("select-provider");
- if (state.status !== "select-provider") return;
- expect(state.name.value).eq("");
- expect(state.url.value).eq("");
- },
- //FIXME: this shouldn't take 2 updates, just
- // (state) => {
- // expect(state.status).equal("select-provider");
- // if (state.status !== "select-provider") return;
- // expect(state.name.value).eq("");
- // expect(state.url.value).eq("");
- // },
- ],
- TestingContext,
- );
-
- expect(hookBehavior).deep.equal({ result: "ok" });
- expect(handler.getCallingQueueState()).eq("empty");
- });
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx
@@ -1,158 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { Amounts } from "@gnu-taler/taler-util";
-import { Fragment, h, VNode } from "preact";
-import { Checkbox } from "../../components/Checkbox.js";
-import {
- LightText,
- SmallLightText,
- SubTitle,
- Title,
-} from "../../components/styled/index.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Button } from "../../mui/Button.js";
-import { TextField } from "../../mui/TextField.js";
-import { State } from "./index.js";
-
-export function ConfirmProviderView({
- url,
- provider,
- tos,
- onCancel,
- onAccept,
-}: State.ConfirmProvider): VNode {
- const { i18n } = useTranslationContext();
- const noFee = Amounts.isZero(provider.annual_fee);
- return (
- <Fragment>
- <section>
- <Title>
- <i18n.Translate>Review terms of service</i18n.Translate>
- </Title>
- <div>
- <i18n.Translate>Provider URL</i18n.Translate>:{" "}
- <a href={url} target="_blank" rel="noreferrer">
- {url}
- </a>
- </div>
- <SmallLightText>
- <i18n.Translate>
- Please review and accept this provider's terms of service
- </i18n.Translate>
- </SmallLightText>
- <SubTitle>
- 1. <i18n.Translate>Pricing</i18n.Translate>
- </SubTitle>
- <p>
- {noFee ? (
- <i18n.Translate>free of charge</i18n.Translate>
- ) : (
- <i18n.Translate>
- {provider.annual_fee} per year of service
- </i18n.Translate>
- )}
- </p>
- <SubTitle>
- 2. <i18n.Translate>Storage</i18n.Translate>
- </SubTitle>
- <p>
- <i18n.Translate>
- {provider.storage_limit_in_megabytes} megabytes of storage per year
- of service
- </i18n.Translate>
- </p>
- <Checkbox
- label={i18n.str`Accept terms of service`}
- name="terms"
- onToggle={tos.button.onClick}
- enabled={tos.value}
- />
- </section>
- <footer>
- <Button
- variant="contained"
- color="secondary"
- onClick={onCancel.onClick}
- >
- <i18n.Translate>Cancel</i18n.Translate>
- </Button>
- <Button variant="contained" color="primary" onClick={onAccept.onClick}>
- {noFee ? (
- <i18n.Translate>Add provider</i18n.Translate>
- ) : (
- <i18n.Translate>Pay</i18n.Translate>
- )}
- </Button>
- </footer>
- </Fragment>
- );
-}
-
-export function SelectProviderView({
- url,
- name,
- urlOk,
- onCancel,
- onConfirm,
-}: State.SelectProvider): VNode {
- const { i18n } = useTranslationContext();
- return (
- <Fragment>
- <section>
- <Title>
- <i18n.Translate>Add backup provider</i18n.Translate>
- </Title>
- <LightText>
- <i18n.Translate>
- Backup providers may charge for their service
- </i18n.Translate>
- </LightText>
- <p>
- <TextField
- label={<i18n.Translate>URL</i18n.Translate>}
- placeholder="https://"
- color={urlOk ? "success" : undefined}
- value={url.value}
- error={url.error}
- onChange={url.onInput}
- />
- </p>
- <p>
- <TextField
- label={<i18n.Translate>Name</i18n.Translate>}
- placeholder="provider name"
- value={name.value}
- error={name.error}
- onChange={name.onInput}
- />
- </p>
- </section>
- <footer>
- <Button
- variant="contained"
- color="secondary"
- onClick={onCancel.onClick}
- >
- <i18n.Translate>Cancel</i18n.Translate>
- </Button>
- <Button variant="contained" color="primary" onClick={onConfirm.onClick}>
- <i18n.Translate>Next</i18n.Translate>
- </Button>
- </footer>
- </Fragment>
- );
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx
@@ -31,7 +31,7 @@ import {
parseScopeInfoShort,
parseTalerUri,
stringifyScopeInfoShort,
- stringifyTalerUri
+ stringifyTalerUri,
} from "@gnu-taler/taler-util";
import {
TranslationProvider,
@@ -69,7 +69,6 @@ import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js";
import { InvoicePayPage } from "../cta/InvoicePay/index.js";
import { PaymentPage } from "../cta/Payment/index.js";
import { PaymentTemplatePage } from "../cta/PaymentTemplate/index.js";
-import { RecoveryPage } from "../cta/Recovery/index.js";
import { RefundPage } from "../cta/Refund/index.js";
import { TransferCreatePage } from "../cta/TransferCreate/index.js";
import { TransferPickupPage } from "../cta/TransferPickup/index.js";
@@ -80,26 +79,22 @@ import {
import { useIsOnline } from "../hooks/useIsOnline.js";
import { strings } from "../i18n/strings.js";
import CloseIcon from "../svg/close_24px.inline.svg";
-import { AddBackupProviderPage } from "./AddBackupProvider/index.js";
-import { AddExchange } from "./AddExchange/index.js";
-import { ConfirmAddExchangeView } from "./AddExchange/views.js";
import { AddContact } from "./AddContact/index.js";
import { ConfirmAddContactView } from "./AddContact/views.js";
-import { BackupPage } from "./BackupPage.js";
+import { AddExchange } from "./AddExchange/index.js";
+import { ConfirmAddExchangeView } from "./AddExchange/views.js";
+import { ContactsPage } from "./Contacts.js";
import { DepositPage } from "./DepositPage/index.js";
import { DestinationSelectionPage } from "./DestinationSelection/index.js";
import { DeveloperPage } from "./DeveloperPage.js";
import { HistoryPage } from "./History.js";
+import { MailboxPage } from "./Mailbox.js";
import { ManageAccountPage } from "./ManageAccount/index.js";
-import { NotificationsPage } from "./Notifications/index.js";
-import { ProviderDetailPage } from "./ProviderDetailPage.js";
import { QrReaderPage } from "./QrReader.js";
import { SettingsPage } from "./Settings.js";
-import { ContactsPage } from "./Contacts.js";
import { SupportedBanksForAccount } from "./SupportedBanksForAccount.js";
import { TransactionPage } from "./Transaction.js";
import { WelcomePage } from "./Welcome.js";
-import { MailboxPage } from "./Mailbox.js";
export function Application(): VNode {
const { i18n } = useTranslationContext();
@@ -159,7 +154,7 @@ export function Application(): VNode {
)}
/>
<Route
- path={Pages.contacts.pattern}
+ path={Pages.contacts.pattern}
component={({ tid }: { tid?: string }) => (
<WalletTemplate
goToTransaction={redirectToTxInfo}
@@ -167,16 +162,15 @@ export function Application(): VNode {
>
<ContactsPage
onMessageSent={() => {
- return redirectTo(Pages.contacts({}));
- }
- }
+ return redirectTo(Pages.contacts({}));
+ }}
tid={tid}
- />
+ />
</WalletTemplate>
)}
/>
<Route
- path={Pages.mailbox}
+ path={Pages.mailbox}
component={() => (
<WalletTemplate
goToTransaction={redirectToTxInfo}
@@ -186,9 +180,7 @@ export function Application(): VNode {
onTalerUri={(talerActionUrl: string) => {
redirectTo(
Pages.defaultCta({
- uri: encodeCrockForURI(
- talerActionUrl
- ),
+ uri: encodeCrockForURI(talerActionUrl),
}),
);
}}
@@ -196,22 +188,14 @@ export function Application(): VNode {
</WalletTemplate>
)}
/>
- <Route
- path={Pages.notifications}
- component={() => (
- <WalletTemplate goToURL={redirectToURL}>
- <NotificationsPage />
- </WalletTemplate>
- )}
- />
{/**
* SETTINGS
*/}
<Route
- path={Pages.settingsExchangeAdd.pattern}
+ path={Pages.settingsExchangeAdd.pattern}
component={() => (
<WalletTemplate goToURL={redirectToURL}>
- <AddExchange onBack={() => redirectTo(Pages.balance)} />
+ <AddExchange onBack={() => redirectTo(Pages.balance)} />
</WalletTemplate>
)}
/>
@@ -224,7 +208,6 @@ export function Application(): VNode {
)}
/>
-
<Route
path={Pages.balanceHistory.pattern}
component={({ scope }: { scope?: string }) => (
@@ -439,7 +422,7 @@ export function Application(): VNode {
)
}
onForwardToContact={(tid: TransactionIdStr) =>
- redirectTo(Pages.contacts({tid}))
+ redirectTo(Pages.contacts({ tid }))
}
/>
</WalletTemplate>
@@ -481,55 +464,6 @@ export function Application(): VNode {
}}
/>
- <Route
- path={Pages.backup}
- component={() => (
- <WalletTemplate
- path="backup"
- goToTransaction={redirectToTxInfo}
- goToURL={redirectToURL}
- >
- <BackupPage
- onAddProvider={() => redirectTo(Pages.backupProviderAdd)}
- />
- </WalletTemplate>
- )}
- />
- <Route
- path={Pages.backupProviderDetail.pattern}
- component={({ pid }: { pid: string }) => (
- <WalletTemplate goToURL={redirectToURL}>
- <ProviderDetailPage
- pid={pid}
- onPayProvider={(uri: string) =>
- redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
- }
- onWithdraw={(_amount: string) =>
- // FIXME: use receiveCashForPurchase
- redirectTo(Pages.receiveCash({ scope: "FIXME missing" }))
- }
- onBack={() => redirectTo(Pages.backup)}
- />
- </WalletTemplate>
- )}
- />
- <Route
- path={Pages.backupProviderAdd}
- component={() => (
- <WalletTemplate goToURL={redirectToURL}>
- <AddBackupProviderPage
- onPaymentRequired={(uri: string) =>
- redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
- }
- onComplete={(pid: string) =>
- redirectTo(Pages.backupProviderDetail({ pid }))
- }
- onBack={() => redirectTo(Pages.backup)}
- />
- </WalletTemplate>
- )}
- />
-
{/**
* DEV
*/}
@@ -820,22 +754,6 @@ export function Application(): VNode {
}}
/>
<Route
- path={Pages.ctaRecovery}
- component={({ talerRecoveryUri }: { talerRecoveryUri: string }) => (
- <CallToActionTemplate title={i18n.str`Digital cash recovery`}>
- <RecoveryPage
- talerRecoveryUri={
- !talerRecoveryUri
- ? undefined
- : decodeCrockFromURI(talerRecoveryUri)
- }
- onCancel={() => redirectTo(Pages.balance)}
- onSuccess={() => redirectTo(Pages.backup)}
- />
- </CallToActionTemplate>
- )}
- />
- <Route
path={Pages.ctaExperiment}
component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Development experiment`}>
@@ -885,13 +803,13 @@ export function Application(): VNode {
const contact =
tUri?.type === TalerUriAction.AddContact
? {
- alias: tUri.alias,
- aliasType: tUri.aliasType,
- mailboxBaseUri: tUri.mailboxBaseUri,
- mailboxAddress: tUri.mailboxIdentity,
- source: tUri.sourceBaseUrl,
- petname: tUri.alias,
- }
+ alias: tUri.alias,
+ aliasType: tUri.aliasType,
+ mailboxBaseUri: tUri.mailboxBaseUri,
+ mailboxAddress: tUri.mailboxIdentity,
+ source: tUri.sourceBaseUrl,
+ petname: tUri.alias,
+ }
: undefined;
if (!contact) {
redirectTo(Pages.balanceHistory({}));
diff --git a/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx
@@ -1,197 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import {
- AbsoluteTime,
- AmountString,
- ProviderPaymentType,
- TalerPreciseTimestamp,
-} from "@gnu-taler/taler-util";
-import * as tests from "@gnu-taler/web-util/testing";
-import { addDays } from "date-fns";
-import {
- ShowRecoveryInfo,
- BackupView as TestedComponent,
-} from "./BackupPage.js";
-
-export default {
- title: "backup",
-};
-
-export const LotOfProviders = tests.createExample(TestedComponent, {
- providers: [
- {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp:
- TalerPreciseTimestamp.fromSeconds(1625063925),
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: AbsoluteTime.fromMilliseconds(1656599921000),
- },
- terms: {
- annualFee: "ARS:1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp:
- TalerPreciseTimestamp.fromSeconds(1625063925),
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: AbsoluteTime.fromMilliseconds(
- addDays(new Date(), 13).getTime(),
- ),
- },
- terms: {
- annualFee: "ARS:1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Pending,
- talerUri: "taler://",
- },
- terms: {
- annualFee: "KUDOS:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.InsufficientBalance,
- amount: "KUDOS:10" as AmountString,
- },
- terms: {
- annualFee: "KUDOS:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.TermsChanged,
- newTerms: {
- annualFee: "USD:2" as AmountString,
- storageLimitInMegabytes: 8,
- supportedProtocolVersion: "2",
- },
- oldTerms: {
- annualFee: "USD:1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "1",
- },
- paidUntil: AbsoluteTime.never(),
- },
- terms: {
- annualFee: "KUDOS:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Unpaid,
- },
- terms: {
- annualFee: "KUDOS:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Unpaid,
- },
- terms: {
- annualFee: "KUDOS:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- ],
-});
-
-export const OneProvider = tests.createExample(TestedComponent, {
- providers: [
- {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp:
- TalerPreciseTimestamp.fromSeconds(1625063925),
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: AbsoluteTime.fromMilliseconds(1656599921000),
- },
- terms: {
- annualFee: "ARS:1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- ],
-});
-
-export const Empty = tests.createExample(TestedComponent, {
- providers: [],
-});
-
-export const Recovery = tests.createExample(ShowRecoveryInfo, {
- info: "taler://recovery/ASLDKJASLKDJASD",
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
@@ -1,357 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
- AbsoluteTime,
- HostPortPath,
- ProviderInfo,
- ProviderPaymentPaid,
- ProviderPaymentStatus,
- ProviderPaymentType,
- stringifyRestoreUri,
-} from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import {
- differenceInMonths,
- formatDuration,
- intervalToDuration,
-} from "date-fns";
-import { Fragment, VNode, h } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { Pages } from "../NavigationBar.js";
-import { ErrorAlertView } from "../components/CurrentAlerts.js";
-import { Loading } from "../components/Loading.js";
-import { QR } from "../components/QR.js";
-import {
- BoldLight,
- Centered,
- CenteredBoldText,
- CenteredText,
- RowBorderGray,
- SmallLightText,
- SmallText,
- WarningBox,
-} from "../components/styled/index.js";
-import { alertFromError } from "../context/alert.js";
-import { useBackendContext } from "../context/backend.js";
-import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
-import { Button } from "../mui/Button.js";
-
-interface Props {
- onAddProvider: () => Promise<void>;
-}
-
-export function ShowRecoveryInfo({
- info,
- onClose,
-}: {
- info: string;
- onClose: () => Promise<void>;
-}): VNode {
- const [display, setDisplay] = useState(false);
- const [copied, setCopied] = useState(false);
- async function copyText(): Promise<void> {
- navigator.clipboard.writeText(info);
- setCopied(true);
- }
- useEffect(() => {
- if (copied) {
- setTimeout(() => {
- setCopied(false);
- }, 1000);
- }
- }, [copied]);
- return (
- <Fragment>
- <h2>Wallet Recovery</h2>
- <WarningBox>Do not share this QR or URI with anyone</WarningBox>
- <section>
- <p>
- The qr code can be scanned by another wallet to keep synchronized with
- this wallet.
- </p>
- <Button variant="contained" onClick={async () => setDisplay((d) => !d)}>
- {display ? "Hide" : "Show"} QR code
- </Button>
- {display && <QR text={JSON.stringify(info)} />}
- </section>
-
- <section>
- <p>You can also use the string version</p>
- <Button variant="contained" disabled={copied} onClick={copyText}>
- Copy recovery URI
- </Button>
- </section>
- <footer>
- <div></div>
- <div>
- <Button variant="contained" onClick={onClose}>
- Close
- </Button>
- </div>
- </footer>
- </Fragment>
- );
-}
-
-export function BackupPage({ onAddProvider }: Props): VNode {
- const { i18n } = useTranslationContext();
- const api = useBackendContext();
- const status = useAsyncAsHook(() =>
- api.wallet.call(WalletApiOperation.GetBackupInfo, {}),
- );
- const [recoveryInfo, setRecoveryInfo] = useState<string>("");
- if (!status) {
- return <Loading />;
- }
- if (status.hasError) {
- return (
- <ErrorAlertView
- error={alertFromError(
- i18n,
- i18n.str`Could not load backup providers`,
- status,
- )}
- />
- );
- }
-
- async function getRecoveryInfo(): Promise<void> {
- const r = await api.wallet.call(
- WalletApiOperation.ExportBackupRecovery,
- {},
- );
- const str = stringifyRestoreUri({
- walletRootPriv: r.walletRootPriv,
- providers: r.providers.map((p) => p.url as HostPortPath),
- });
- setRecoveryInfo(str);
- }
-
- const providers = status.response.providers.sort((a, b) => {
- if (
- a.paymentStatus.type === ProviderPaymentType.Paid &&
- b.paymentStatus.type === ProviderPaymentType.Paid
- ) {
- return getStatusPaidOrder(a.paymentStatus, b.paymentStatus);
- }
- return (
- getStatusTypeOrder(a.paymentStatus) - getStatusTypeOrder(b.paymentStatus)
- );
- });
-
- if (recoveryInfo) {
- return (
- <ShowRecoveryInfo
- info={recoveryInfo}
- onClose={async () => setRecoveryInfo("")}
- />
- );
- }
-
- return (
- <BackupView
- providers={providers}
- onAddProvider={onAddProvider}
- onSyncAll={async () =>
- api.wallet.call(WalletApiOperation.RunBackupCycle, {}).then()
- }
- onShowInfo={getRecoveryInfo}
- />
- );
-}
-
-export interface ViewProps {
- providers: ProviderInfo[];
- onAddProvider: () => Promise<void>;
- onSyncAll: () => Promise<void>;
- onShowInfo: () => Promise<void>;
-}
-
-export function BackupView({
- providers,
- onAddProvider,
- onSyncAll,
- onShowInfo,
-}: ViewProps): VNode {
- const { i18n } = useTranslationContext();
- return (
- <Fragment>
- <section>
- {providers.map((provider, idx) => (
- <BackupLayout
- key={idx}
- status={provider.paymentStatus}
- timestamp={
- provider.lastSuccessfulBackupTimestamp
- ? AbsoluteTime.fromPreciseTimestamp(
- provider.lastSuccessfulBackupTimestamp,
- )
- : undefined
- }
- id={provider.syncProviderBaseUrl}
- active={provider.active}
- title={provider.name}
- />
- ))}
- {!providers.length && (
- <Centered style={{ marginTop: 100 }}>
- <BoldLight>
- <i18n.Translate>No backup providers configured</i18n.Translate>
- </BoldLight>
- <Button variant="contained" color="success" onClick={onAddProvider}>
- <i18n.Translate>Add provider</i18n.Translate>
- </Button>
- </Centered>
- )}
- </section>
- {!!providers.length && (
- <footer>
- <div>
- <Button variant="contained" onClick={onShowInfo}>
- Show recovery
- </Button>
- </div>
- <div>
- <Button variant="contained" onClick={onSyncAll}>
- {providers.length > 1
- ? i18n.str`Sync all backups`
- : i18n.str`Sync now`}
- </Button>
- <Button variant="contained" color="success" onClick={onAddProvider}>
- <i18n.Translate>Add provider</i18n.Translate>
- </Button>
- </div>
- </footer>
- )}
- </Fragment>
- );
-}
-
-interface TransactionLayoutProps {
- status: ProviderPaymentStatus;
- timestamp?: AbsoluteTime;
- title: string;
- id: string;
- active: boolean;
-}
-
-function BackupLayout(props: TransactionLayoutProps): VNode {
- const { i18n } = useTranslationContext();
- const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
- const dateStr = date?.toLocaleString([], {
- dateStyle: "medium",
- timeStyle: "short",
- } as any);
-
- return (
- <RowBorderGray>
- <div style={{ color: !props.active ? "grey" : undefined }}>
- <a
- href={`#${Pages.backupProviderDetail({
- pid: props.id,
- })}`}
- >
- <span>{props.title}</span>
- </a>
-
- {dateStr && (
- <SmallText style={{ marginTop: 5 }}>
- <i18n.Translate>Last synced</i18n.Translate>: {dateStr}
- </SmallText>
- )}
- {!dateStr && (
- <SmallLightText style={{ marginTop: 5 }}>
- <i18n.Translate>Not synced</i18n.Translate>
- </SmallLightText>
- )}
- </div>
- <div>
- {props.status?.type === "paid" ? (
- <ExpirationText until={props.status.paidUntil} />
- ) : (
- <div>{props.status.type}</div>
- )}
- </div>
- </RowBorderGray>
- );
-}
-
-function ExpirationText({ until }: { until: AbsoluteTime }): VNode {
- const { i18n } = useTranslationContext();
- return (
- <Fragment>
- <CenteredText>
- <i18n.Translate>Expires in</i18n.Translate>
- </CenteredText>
- <CenteredBoldText {...{ color: colorByTimeToExpire(until) }}>
- {" "}
- {daysUntil(until)}{" "}
- </CenteredBoldText>
- </Fragment>
- );
-}
-
-function colorByTimeToExpire(d: AbsoluteTime): string {
- if (d.t_ms === "never") return "rgb(28, 184, 65)";
- const months = differenceInMonths(d.t_ms, new Date());
- return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)";
-}
-
-function daysUntil(d: AbsoluteTime): string {
- if (d.t_ms === "never") return "";
- const duration = intervalToDuration({
- start: d.t_ms,
- end: new Date(),
- });
- const str = formatDuration(duration, {
- delimiter: ", ",
- format: [
- duration?.years
- ? "years"
- : duration?.months
- ? "months"
- : duration?.days
- ? "days"
- : duration.hours
- ? "hours"
- : "minutes",
- ],
- });
- return `${str}`;
-}
-
-function getStatusTypeOrder(t: ProviderPaymentStatus): number {
- return [
- ProviderPaymentType.InsufficientBalance,
- ProviderPaymentType.TermsChanged,
- ProviderPaymentType.Unpaid,
- ProviderPaymentType.Paid,
- ProviderPaymentType.Pending,
- ].indexOf(t.type);
-}
-
-function getStatusPaidOrder(
- a: ProviderPaymentPaid,
- b: ProviderPaymentPaid,
-): number {
- return a.paidUntil.t_ms === "never"
- ? -1
- : b.paidUntil.t_ms === "never"
- ? 1
- : a.paidUntil.t_ms - b.paidUntil.t_ms;
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts
@@ -1,61 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { UserAttentionUnreadList } from "@gnu-taler/taler-util";
-import { ErrorAlertView } from "../../components/CurrentAlerts.js";
-import { Loading } from "../../components/Loading.js";
-import { ErrorAlert } from "../../context/alert.js";
-import { compose, StateViewMap } from "../../utils/index.js";
-import { useComponentState } from "./state.js";
-import { ReadyView } from "./views.js";
-
-export type Props = object;
-
-export type State = State.Loading | State.LoadingUriError | State.Ready;
-
-export namespace State {
- export interface Loading {
- status: "loading";
- error: undefined;
- }
-
- export interface LoadingUriError {
- status: "error";
- error: ErrorAlert;
- }
-
- export interface BaseInfo {
- error: undefined;
- }
-
- export interface Ready extends BaseInfo {
- status: "ready";
- error: undefined;
- list: UserAttentionUnreadList;
- }
-}
-
-const viewMapping: StateViewMap<State> = {
- loading: Loading,
- error: ErrorAlertView,
- ready: ReadyView,
-};
-
-export const NotificationsPage = compose(
- "NotificationsPage",
- (p: Props) => useComponentState(p),
- viewMapping,
-);
diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts
@@ -1,57 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { alertFromError } from "../../context/alert.js";
-import { useBackendContext } from "../../context/backend.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
-import { Props, State } from "./index.js";
-
-export function useComponentState(p: Props): State {
- const api = useBackendContext();
- const { i18n } = useTranslationContext();
- const hook = useAsyncAsHook(async () => {
- return await api.wallet.call(
- WalletApiOperation.GetUserAttentionRequests,
- {},
- );
- });
-
- if (!hook) {
- return {
- status: "loading",
- error: undefined,
- };
- }
-
- if (hook.hasError) {
- return {
- status: "error",
- error: alertFromError(
- i18n,
- i18n.str`Could not load user attention request`,
- hook,
- ),
- };
- }
-
- return {
- status: "ready",
- error: undefined,
- list: hook.response.pending,
- };
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/stories.tsx b/packages/taler-wallet-webextension/src/wallet/Notifications/stories.tsx
@@ -1,63 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import {
- AbsoluteTime,
- AttentionType,
- TalerPreciseTimestamp,
- TransactionIdStr,
-} from "@gnu-taler/taler-util";
-import * as tests from "@gnu-taler/web-util/testing";
-import { ReadyView } from "./views.js";
-
-export default {
- title: "notifications",
-};
-
-export const Ready = tests.createExample(ReadyView, {
- list: [
- {
- when: TalerPreciseTimestamp.now(),
- read: false,
- info: {
- type: AttentionType.KycWithdrawal,
- transactionId: "123" as TransactionIdStr,
- },
- },
- {
- when: TalerPreciseTimestamp.now(),
- read: false,
- info: {
- type: AttentionType.MerchantRefund,
- transactionId: "123" as TransactionIdStr,
- },
- },
- {
- when: TalerPreciseTimestamp.now(),
- read: false,
- info: {
- type: AttentionType.BackupUnpaid,
- provider_base_url: "http://sync.taler.net",
- talerUri: "taler://payment/asdasdasd",
- },
- },
- ],
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts
@@ -1,28 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { expect } from "chai";
-
-describe("Notifications states", () => {
- it.skip("should create some tests", () => {
- expect([]).deep.equals([]);
- });
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/views.tsx b/packages/taler-wallet-webextension/src/wallet/Notifications/views.tsx
@@ -1,211 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022-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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
- AbsoluteTime,
- AttentionInfo,
- AttentionType,
- assertUnreachable
-} from "@gnu-taler/taler-util";
-import { Fragment, h, VNode } from "preact";
-import {
- Column,
- DateSeparator,
- HistoryRow,
- LargeText,
- SmallLightText,
-} from "../../components/styled/index.js";
-import { Time } from "../../components/Time.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Avatar } from "../../mui/Avatar.js";
-import { Button } from "../../mui/Button.js";
-import { Grid } from "../../mui/Grid.js";
-import { Pages } from "../../NavigationBar.js";
-import { State } from "./index.js";
-
-const term = 1000 * 60 * 60 * 24;
-function normalizeToDay(x: number): number {
- return Math.round(x / term) * term;
-}
-
-export function ReadyView({ list }: State.Ready): VNode {
- const { i18n } = useTranslationContext();
- if (list.length < 1) {
- return (
- <section>
- <i18n.Translate>No notification left</i18n.Translate>
- </section>
- );
- }
-
- const byDate = list.reduce((rv, x) => {
- const theDate =
- x.when.t_s === "never" ? 0 : normalizeToDay(x.when.t_s * 1000);
- if (theDate) {
- (rv[theDate] = rv[theDate] || []).push(x);
- }
-
- return rv;
- }, {} as { [x: string]: typeof list });
- const datesWithNotifications = Object.keys(byDate);
-
- return (
- <section>
- {datesWithNotifications.map((d, i) => {
- return (
- <Fragment key={i}>
- <DateSeparator>
- <Time
- timestamp={AbsoluteTime.fromMilliseconds(
- Number.parseInt(d, 10),
- )}
- format="dd MMMM yyyy"
- />
- </DateSeparator>
- {byDate[d].map((n, i) => (
- <NotificationItem
- key={i}
- info={n.info}
- isRead={n.read}
- timestamp={AbsoluteTime.fromPreciseTimestamp(n.when)}
- />
- ))}
- </Fragment>
- );
- })}
- </section>
- );
-}
-
-function NotificationItem({
- info,
- isRead,
- timestamp,
-}: {
- info: AttentionInfo;
- timestamp: AbsoluteTime;
- isRead: boolean;
-}): VNode {
- switch (info.type) {
- case AttentionType.KycWithdrawal:
- return (
- <NotificationLayout
- timestamp={timestamp}
- href={`#${Pages.balanceTransaction({ tid: info.transactionId })}`}
- title="Withdrawal on hold"
- subtitle="Know-your-customer validation is required"
- iconPath={"K"}
- isRead={isRead}
- />
- );
- case AttentionType.MerchantRefund:
- return (
- <NotificationLayout
- timestamp={timestamp}
- href={`#${Pages.balanceTransaction({ tid: info.transactionId })}`}
- title="Merchant has refund your payment"
- subtitle="Accept or deny refund"
- iconPath={"K"}
- isRead={isRead}
- />
- );
- case AttentionType.BackupUnpaid:
- return (
- <NotificationLayout
- timestamp={timestamp}
- href={`#${Pages.ctaPay}?talerPayUri=${info.talerUri}`}
- title="Backup provider is unpaid"
- subtitle="Complete the payment or remove the service provider"
- iconPath={"K"}
- isRead={isRead}
- />
- );
- case AttentionType.AuditorDenominationsExpires:
- return <div>not implemented</div>;
- case AttentionType.AuditorKeyExpires:
- return <div>not implemented</div>;
- case AttentionType.AuditorTosChanged:
- return <div>not implemented</div>;
- case AttentionType.ExchangeDenominationsExpired:
- return <div>not implemented</div>;
- // case AttentionType.ExchangeDenominationsExpiresSoon:
- // return <div>not implemented</div>;
- case AttentionType.ExchangeKeyExpired:
- return <div>not implemented</div>;
- // case AttentionType.ExchangeKeyExpiresSoon:
- // return <div>not implemented</div>;
- case AttentionType.ExchangeTosChanged:
- return <div>not implemented</div>;
- case AttentionType.BackupExpiresSoon:
- return <div>not implemented</div>;
- case AttentionType.PushPaymentReceived:
- return <div>not implemented</div>;
- case AttentionType.PullPaymentPaid:
- return <div>not implemented</div>;
- default:
- assertUnreachable(info);
- }
-}
-
-function NotificationLayout(props: {
- title: string;
- href: string;
- subtitle?: string;
- timestamp: AbsoluteTime;
- iconPath: string;
- isRead: boolean;
-}): VNode {
- const { i18n } = useTranslationContext();
- return (
- <HistoryRow
- href={props.href}
- style={{
- backgroundColor: props.isRead ? "lightcyan" : "inherit",
- alignItems: "center",
- }}
- >
- <Avatar
- style={{
- border: "solid gray 1px",
- color: "gray",
- boxSizing: "border-box",
- }}
- >
- {props.iconPath}
- </Avatar>
- <Column>
- <LargeText>
- <div>{props.title}</div>
- {props.subtitle && (
- <div style={{ color: "gray", fontSize: "medium", marginTop: 5 }}>
- {props.subtitle}
- </div>
- )}
- </LargeText>
- <SmallLightText style={{ marginTop: 5 }}>
- <Time timestamp={props.timestamp} format="HH:mm" />
- </SmallLightText>
- </Column>
- <Column>
- <Grid>
- <Button variant="outlined">
- <i18n.Translate>Ignore</i18n.Translate>
- </Button>
- </Grid>
- </Column>
- </HistoryRow>
- );
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddConfirmProvider.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddConfirmProvider.stories.tsx
@@ -1,51 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import * as tests from "@gnu-taler/web-util/testing";
-import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage.js";
-
-export default {
- title: "confirm",
- component: TestedComponent,
- argTypes: {
- onRetry: { action: "onRetry" },
- onDelete: { action: "onDelete" },
- onBack: { action: "onBack" },
- },
-};
-
-export const DemoService = tests.createExample(TestedComponent, {
- url: "https://sync.demo.taler.net/",
- provider: {
- annual_fee: "KUDOS:0.1",
- storage_limit_in_megabytes: 20,
- supported_protocol_version: "1",
- },
-});
-
-export const FreeService = tests.createExample(TestedComponent, {
- url: "https://sync.taler:9667/",
- provider: {
- annual_fee: "ARS:0",
- storage_limit_in_megabytes: 20,
- supported_protocol_version: "1",
- },
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
@@ -1,260 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
- Amounts,
- canonicalizeBaseUrl,
-} from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { Fragment, h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { Checkbox } from "../components/Checkbox.js";
-import { ErrorMessage } from "../components/ErrorMessage.js";
-import {
- Input,
- LightText,
- SmallLightText,
- SubTitle,
- Title,
-} from "../components/styled/index.js";
-import { useBackendContext } from "../context/backend.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Button } from "../mui/Button.js";
-import { queryToSlashConfig } from "../utils/index.js";
-
-interface Props {
- currency: string;
- onBack: () => Promise<void>;
-}
-
-interface BackupBackupProviderTerms {
- annual_fee: string;
- storage_limit_in_megabytes: number;
- supported_protocol_version: string;
-}
-
-export function ProviderAddPage({ onBack }: Props): VNode {
- const [verifying, setVerifying] = useState<
- | { url: string; name: string; provider: BackupBackupProviderTerms }
- | undefined
- >(undefined);
- const api = useBackendContext();
- if (!verifying) {
- return (
- <SetUrlView
- onCancel={onBack}
- onVerify={(url) => queryToSlashConfig(url)}
- onConfirm={(url, name) =>
- queryToSlashConfig<BackupBackupProviderTerms>(url)
- .then((provider) => {
- setVerifying({ url, name, provider });
- })
- .catch((e) => e.message)
- }
- />
- );
- }
- return (
- <ConfirmProviderView
- provider={verifying.provider}
- url={verifying.url}
- onCancel={async () => {
- setVerifying(undefined);
- }}
- onConfirm={() => {
- return api.wallet
- .call(WalletApiOperation.AddBackupProvider, {
- backupProviderBaseUrl: verifying.url,
- name: verifying.name,
- activate: true,
- })
- .then(onBack);
- }}
- />
- );
-}
-
-export interface SetUrlViewProps {
- initialValue?: string;
- onCancel: () => Promise<void>;
- onVerify: (s: string) => Promise<BackupBackupProviderTerms | undefined>;
- onConfirm: (url: string, name: string) => Promise<string | undefined>;
- withError?: string;
-}
-
-export function SetUrlView({
- initialValue,
- onCancel,
- onVerify,
- onConfirm,
- withError,
-}: SetUrlViewProps): VNode {
- const { i18n } = useTranslationContext();
- const [value, setValue] = useState<string>(initialValue || "");
- const [urlError, setUrlError] = useState(false);
- const [name, setName] = useState<string | undefined>(undefined);
- const [error, setError] = useState<string | undefined>(withError);
- useEffect(() => {
- try {
- const url = canonicalizeBaseUrl(value);
- onVerify(url)
- .then((r) => {
- setUrlError(false);
- setName(new URL(url).hostname);
- })
- .catch(() => {
- setUrlError(true);
- setName(undefined);
- });
- } catch {
- setUrlError(true);
- setName(undefined);
- }
- }, [onVerify, value]);
- return (
- <Fragment>
- <section>
- <Title>
- <i18n.Translate>Add backup provider</i18n.Translate>
- </Title>
- {error && (
- <ErrorMessage
- title={i18n.str`Could not get provider information`}
- description={error}
- />
- )}
- <LightText>
- <i18n.Translate>
- Backup providers may charge for their service
- </i18n.Translate>
- </LightText>
- <p>
- <Input invalid={urlError}>
- <label>
- <i18n.Translate>URL</i18n.Translate>
- </label>
- <input
- type="text"
- placeholder="https://"
- value={value}
- onChange={(e) => setValue(e.currentTarget.value)}
- />
- </Input>
- <Input>
- <label>
- <i18n.Translate>Name</i18n.Translate>
- </label>
- <input
- type="text"
- disabled={name === undefined}
- value={name}
- onChange={(e) => setName(e.currentTarget.value)}
- />
- </Input>
- </p>
- </section>
- <footer>
- <Button variant="contained" color="secondary" onClick={onCancel}>
- <i18n.Translate>Cancel</i18n.Translate>
- </Button>
- <Button
- variant="contained"
- disabled={!value && !urlError}
- onClick={() => {
- const url = canonicalizeBaseUrl(value);
- return onConfirm(url, name!).then((r) =>
- r ? setError(r) : undefined,
- );
- }}
- >
- <i18n.Translate>Next</i18n.Translate>
- </Button>
- </footer>
- </Fragment>
- );
-}
-
-export interface ConfirmProviderViewProps {
- provider: BackupBackupProviderTerms;
- url: string;
- onCancel: () => Promise<void>;
- onConfirm: () => Promise<void>;
-}
-export function ConfirmProviderView({
- url,
- provider,
- onCancel,
- onConfirm,
-}: ConfirmProviderViewProps): VNode {
- const [accepted, setAccepted] = useState(false);
- const { i18n } = useTranslationContext();
-
- return (
- <Fragment>
- <section>
- <Title>
- <i18n.Translate>Review terms of service</i18n.Translate>
- </Title>
- <div>
- <i18n.Translate>Provider URL</i18n.Translate>:{" "}
- <a href={url} target="_blank" rel="noreferrer">
- {url}
- </a>
- </div>
- <SmallLightText>
- <i18n.Translate>
- Please review and accept this provider's terms of service
- </i18n.Translate>
- </SmallLightText>
- <SubTitle>
- 1. <i18n.Translate>Pricing</i18n.Translate>
- </SubTitle>
- <p>
- {Amounts.isZero(provider.annual_fee) ? (
- i18n.str`free of charge`
- ) : (
- <i18n.Translate>
- {provider.annual_fee} per year of service
- </i18n.Translate>
- )}
- </p>
- <SubTitle>
- 2. <i18n.Translate>Storage</i18n.Translate>
- </SubTitle>
- <p>
- <i18n.Translate>
- {provider.storage_limit_in_megabytes} megabytes of storage per year
- of service
- </i18n.Translate>
- </p>
- <Checkbox
- label={i18n.str`Accept terms of service`}
- name="terms"
- onToggle={async () => setAccepted((old) => !old)}
- enabled={accepted}
- />
- </section>
- <footer>
- <Button variant="contained" color="secondary" onClick={onCancel}>
- <i18n.Translate>Cancel</i18n.Translate>
- </Button>
- <Button variant="contained" disabled={!accepted} onClick={onConfirm}>
- <i18n.Translate>Add provider</i18n.Translate>
- </Button>
- </footer>
- </Fragment>
- );
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddSetUrl.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddSetUrl.stories.tsx
@@ -1,51 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import * as tests from "@gnu-taler/web-util/testing";
-import { SetUrlView as TestedComponent } from "./ProviderAddPage.js";
-
-export default {
- title: "add",
- component: TestedComponent,
- argTypes: {
- onRetry: { action: "onRetry" },
- onDelete: { action: "onDelete" },
- onBack: { action: "onBack" },
- },
-};
-
-export const Initial = tests.createExample(TestedComponent, {});
-
-export const WithValue = tests.createExample(TestedComponent, {
- initialValue: "sync.demo.taler.net",
-});
-
-export const WithConnectionError = tests.createExample(TestedComponent, {
- withError: "Network error",
-});
-
-export const WithClientError = tests.createExample(TestedComponent, {
- withError: "URL may not be right: (404) Not Found",
-});
-
-export const WithServerError = tests.createExample(TestedComponent, {
- withError: "Try another server: (500) Internal Server Error",
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx
@@ -1,232 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import {
- AbsoluteTime,
- AmountString,
- ProviderPaymentType,
- TalerPreciseTimestamp,
-} from "@gnu-taler/taler-util";
-import * as tests from "@gnu-taler/web-util/testing";
-import { ProviderView as TestedComponent } from "./ProviderDetailPage.js";
-
-export default {
- title: "provider details",
- component: TestedComponent,
- argTypes: {
- onRetry: { action: "onRetry" },
- onDelete: { action: "onDelete" },
- onBack: { action: "onBack" },
- },
-};
-
-export const Active = tests.createExample(TestedComponent, {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp:
- TalerPreciseTimestamp.fromSeconds(1625063925),
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: AbsoluteTime.fromMilliseconds(1656599921000),
- },
- terms: {
- annualFee: "EUR:1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const ActiveErrorSync = tests.createExample(TestedComponent, {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp:
- TalerPreciseTimestamp.fromSeconds(1625063925),
- lastAttemptedBackupTimestamp:
- TalerPreciseTimestamp.fromSeconds(1625063925078),
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: AbsoluteTime.fromMilliseconds(1656599921000),
- },
- lastError: {
- code: 2002,
- details: "details",
- when: AbsoluteTime.now(),
- hint: "error hint from the server",
- message: "message",
- },
- terms: {
- annualFee: "EUR:1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const ActiveBackupProblemUnreadable = tests.createExample(
- TestedComponent,
- {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp:
- TalerPreciseTimestamp.fromSeconds(1625063925),
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: AbsoluteTime.fromMilliseconds(1656599921000),
- },
- backupProblem: {
- type: "backup-unreadable",
- },
- terms: {
- annualFee: "EUR:1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- },
-);
-
-export const ActiveBackupProblemDevice = tests.createExample(TestedComponent, {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.taler:9967/",
- lastSuccessfulBackupTimestamp:
- TalerPreciseTimestamp.fromSeconds(1625063925078),
- paymentProposalIds: [
- "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
- ],
- paymentStatus: {
- type: ProviderPaymentType.Paid,
- paidUntil: AbsoluteTime.fromMilliseconds(1656599921000),
- },
- backupProblem: {
- type: "backup-conflicting-device",
- myDeviceId: "my-device-id",
- otherDeviceId: "other-device-id",
- backupTimestamp: AbsoluteTime.fromMilliseconds(1656599921000),
- },
- terms: {
- annualFee: "EUR:1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const InactiveUnpaid = tests.createExample(TestedComponent, {
- info: {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Unpaid,
- },
- terms: {
- annualFee: "EUR:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const InactiveInsufficientBalance = tests.createExample(
- TestedComponent,
- {
- info: {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.InsufficientBalance,
- amount: "EUR:123" as AmountString,
- },
- terms: {
- annualFee: "EUR:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- },
-);
-
-export const InactivePending = tests.createExample(TestedComponent, {
- info: {
- active: false,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.Pending,
- talerUri: "taler://pay/sad",
- },
- terms: {
- annualFee: "EUR:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
-
-export const ActiveTermsChanged = tests.createExample(TestedComponent, {
- info: {
- active: true,
- name: "sync.demo",
- syncProviderBaseUrl: "http://sync.demo.taler.net/",
- paymentProposalIds: [],
- paymentStatus: {
- type: ProviderPaymentType.TermsChanged,
- paidUntil: AbsoluteTime.fromMilliseconds(1656599921000),
- newTerms: {
- annualFee: "EUR:10" as AmountString,
- storageLimitInMegabytes: 8,
- supportedProtocolVersion: "0.0",
- },
- oldTerms: {
- annualFee: "EUR:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
- terms: {
- annualFee: "EUR:0.1" as AmountString,
- storageLimitInMegabytes: 16,
- supportedProtocolVersion: "0.0",
- },
- },
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
@@ -1,328 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import * as utils from "@gnu-taler/taler-util";
-import {
- AbsoluteTime,
- ProviderInfo,
- ProviderPaymentStatus,
- ProviderPaymentType,
-} from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
-import { ErrorAlertView } from "../components/CurrentAlerts.js";
-import { ErrorMessage } from "../components/ErrorMessage.js";
-import { Loading } from "../components/Loading.js";
-import { Time } from "../components/Time.js";
-import { PaymentStatus, SmallLightText } from "../components/styled/index.js";
-import { alertFromError } from "../context/alert.js";
-import { useBackendContext } from "../context/backend.js";
-import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
-import { Button } from "../mui/Button.js";
-
-interface Props {
- pid: string;
- onBack: () => Promise<void>;
- onPayProvider: (uri: string) => Promise<void>;
- onWithdraw: (amount: string) => Promise<void>;
-}
-
-export function ProviderDetailPage({
- pid: providerURL,
- onBack,
- onPayProvider,
- onWithdraw,
-}: Props): VNode {
- const { i18n } = useTranslationContext();
- const api = useBackendContext();
- async function getProviderInfo(): Promise<ProviderInfo | null> {
- //create a first list of backup info by currency
- const status = await api.wallet.call(WalletApiOperation.GetBackupInfo, {});
-
- const providers = status.providers.filter(
- (p) => p.syncProviderBaseUrl === providerURL,
- );
- return providers.length ? providers[0] : null;
- }
-
- const state = useAsyncAsHook(getProviderInfo);
-
- if (!state) {
- return <Loading />;
- }
- if (state.hasError) {
- return (
- <ErrorAlertView
- error={alertFromError(
- i18n,
- i18n.str`There was an error loading the provider detail for "${providerURL}"`,
- state,
- )}
- />
- );
- }
- const info = state.response;
- if (info === null) {
- return (
- <Fragment>
- <section>
- <p>
- <i18n.Translate>
- No provider known to us with the URL "{providerURL}" was found.
- </i18n.Translate>
- </p>
- </section>
- <footer>
- <Button variant="contained" color="secondary" onClick={onBack}>
- <i18n.Translate>See providers</i18n.Translate>
- </Button>
- <div />
- </footer>
- </Fragment>
- );
- }
-
- return (
- <ProviderView
- info={info}
- onSync={async () =>
- api.wallet
- .call(WalletApiOperation.RunBackupCycle, {
- providers: [providerURL],
- })
- .then()
- }
- onPayProvider={async () => {
- if (info.paymentStatus.type !== ProviderPaymentType.Pending) return;
- if (!info.paymentStatus.talerUri) return;
- onPayProvider(info.paymentStatus.talerUri);
- }}
- onWithdraw={async () => {
- if (info.paymentStatus.type !== ProviderPaymentType.InsufficientBalance)
- return;
- onWithdraw(info.paymentStatus.amount);
- }}
- onDelete={() =>
- api.wallet
- .call(WalletApiOperation.RemoveBackupProvider, {
- provider: providerURL,
- })
- .then(onBack)
- }
- onBack={onBack}
- onExtend={async () => {
- null;
- }}
- />
- );
-}
-
-export interface ViewProps {
- info: ProviderInfo;
- onDelete: () => Promise<void>;
- onSync: () => Promise<void>;
- onBack: () => Promise<void>;
- onExtend: () => Promise<void>;
- onPayProvider: () => Promise<void>;
- onWithdraw: () => Promise<void>;
-}
-
-export function ProviderView({
- info,
- onDelete,
- onPayProvider,
- onWithdraw,
- onSync,
- onBack,
- onExtend,
-}: ViewProps): VNode {
- const { i18n } = useTranslationContext();
- const lb = info.lastSuccessfulBackupTimestamp
- ? AbsoluteTime.fromPreciseTimestamp(info.lastSuccessfulBackupTimestamp)
- : undefined;
- const isPaid =
- info.paymentStatus.type === ProviderPaymentType.Paid ||
- info.paymentStatus.type === ProviderPaymentType.TermsChanged;
- return (
- <Fragment>
- <Error info={info} />
- <header>
- <h3>
- {info.name}{" "}
- <SmallLightText>{info.syncProviderBaseUrl}</SmallLightText>
- </h3>
- <PaymentStatus color={isPaid ? "rgb(28, 184, 65)" : "rgb(202, 60, 60)"}>
- {isPaid ? "Paid" : "Unpaid"}
- </PaymentStatus>
- </header>
- <section>
- <p>
- <b>
- <i18n.Translate>Last backup</i18n.Translate>:
- </b>{" "}
- <Time timestamp={lb} format="dd MMMM yyyy" />
- </p>
- <Button variant="contained" onClick={onSync}>
- <i18n.Translate>Back up</i18n.Translate>
- </Button>
- {info.terms && (
- <Fragment>
- <p>
- <b>
- <i18n.Translate>Provider fee</i18n.Translate>:
- </b>{" "}
- {info.terms && info.terms.annualFee}{" "}
- <i18n.Translate>per year</i18n.Translate>
- </p>
- </Fragment>
- )}
- <p>{descriptionByStatus(info.paymentStatus, i18n)}</p>
- <Button variant="contained" disabled onClick={onExtend}>
- <i18n.Translate>Extend</i18n.Translate>
- </Button>
-
- {info.paymentStatus.type === ProviderPaymentType.TermsChanged && (
- <div>
- <p>
- <i18n.Translate>
- terms has changed, extending the service will imply accepting
- the new terms of service
- </i18n.Translate>
- </p>
- <table>
- <thead>
- <tr>
- <td> </td>
- <td>
- <i18n.Translate>old</i18n.Translate>
- </td>
- <td> -></td>
- <td>
- <i18n.Translate>new</i18n.Translate>
- </td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>
- <i18n.Translate>fee</i18n.Translate>
- </td>
- <td>{info.paymentStatus.oldTerms.annualFee}</td>
- <td>-></td>
- <td>{info.paymentStatus.newTerms.annualFee}</td>
- </tr>
- <tr>
- <td>
- <i18n.Translate>storage</i18n.Translate>
- </td>
- <td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td>
- <td>-></td>
- <td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td>
- </tr>
- </tbody>
- </table>
- </div>
- )}
- </section>
- <footer>
- <Button variant="contained" color="secondary" onClick={onBack}>
- <i18n.Translate>See providers</i18n.Translate>
- </Button>
- <div>
- <Button variant="contained" color="error" onClick={onDelete}>
- <i18n.Translate>Remove provider</i18n.Translate>
- </Button>
- {info.paymentStatus.type === ProviderPaymentType.Pending &&
- info.paymentStatus.talerUri ? (
- <Button variant="contained" color="primary" onClick={onPayProvider}>
- <i18n.Translate>Pay</i18n.Translate>
- </Button>
- ) : undefined}
- {info.paymentStatus.type ===
- ProviderPaymentType.InsufficientBalance ? (
- <Button variant="contained" color="primary" onClick={onWithdraw}>
- <i18n.Translate>Withdraw</i18n.Translate>
- </Button>
- ) : undefined}
- </div>
- </footer>
- </Fragment>
- );
-}
-
-function Error({ info }: { info: ProviderInfo }): VNode {
- const { i18n } = useTranslationContext();
- if (info.lastError) {
- return (
- <ErrorMessage
- title={i18n.str`This provider has reported an error`}
- description={info.lastError.hint}
- />
- );
- }
- if (info.backupProblem) {
- switch (info.backupProblem.type) {
- case "backup-conflicting-device":
- return (
- <ErrorMessage
- title={i18n.str`There is conflict with another backup from "${info.backupProblem.otherDeviceId}"`}
- />
- );
- case "backup-unreadable":
- return <ErrorMessage title={i18n.str`Backup is not readable`} />;
- default:
- return (
- <ErrorMessage
- title={i18n.str`Unknown backup problem: ${JSON.stringify(
- info.backupProblem,
- )}`}
- />
- );
- }
- }
- return <Fragment />;
-}
-
-function descriptionByStatus(
- status: ProviderPaymentStatus,
- i18n: typeof utils.i18n,
-): VNode {
- switch (status.type) {
- case ProviderPaymentType.Paid:
- case ProviderPaymentType.TermsChanged:
- if (status.paidUntil.t_ms === "never") {
- return (
- <span>
- <i18n.Translate>service paid</i18n.Translate>
- </span>
- );
- }
- return (
- <Fragment>
- <b>
- <i18n.Translate>Backup valid until</i18n.Translate>:
- </b>{" "}
- <Time timestamp={status.paidUntil} format="dd MMM yyyy" />
- </Fragment>
- );
-
- case ProviderPaymentType.Unpaid:
- case ProviderPaymentType.InsufficientBalance:
- case ProviderPaymentType.Pending:
- return <span />;
- }
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
@@ -19,9 +19,9 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { WalletCoreVersion } from "@gnu-taler/taler-util";
import * as tests from "@gnu-taler/web-util/testing";
import { SettingsView as TestedComponent } from "./Settings.js";
-import { WalletCoreVersion } from "@gnu-taler/taler-util";
export default {
title: "settings",
@@ -52,40 +52,32 @@ const version = {
};
export const AllOff = tests.createExample(TestedComponent, {
- deviceName: "this-is-the-device-name",
advanceToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} },
langToggle: { value: false, button: {} },
- setDeviceName: () => Promise.resolve(),
...version,
});
export const OneChecked = tests.createExample(TestedComponent, {
- deviceName: "this-is-the-device-name",
advanceToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} },
langToggle: { value: false, button: {} },
- setDeviceName: () => Promise.resolve(),
...version,
});
export const WithOneExchange = tests.createExample(TestedComponent, {
- deviceName: "this-is-the-device-name",
advanceToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} },
langToggle: { value: false, button: {} },
- setDeviceName: () => Promise.resolve(),
...version,
});
export const WithExchangeInDifferentState = tests.createExample(
TestedComponent,
{
- deviceName: "this-is-the-device-name",
advanceToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} },
langToggle: { value: false, button: {} },
- setDeviceName: () => Promise.resolve(),
...version,
},
);
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -17,7 +17,7 @@
import {
LibtoolVersion,
TranslatedString,
- WalletCoreVersion
+ WalletCoreVersion,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
@@ -26,15 +26,10 @@ import { Checkbox } from "../components/Checkbox.js";
import { EnabledBySettings } from "../components/EnabledBySettings.js";
import { Part } from "../components/Part.js";
import { SelectList } from "../components/SelectList.js";
-import {
- Input,
- SubTitle,
- WarningBox
-} from "../components/styled/index.js";
+import { Input, SubTitle, WarningBox } from "../components/styled/index.js";
import { useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
-import { useBackupDeviceName } from "../hooks/useBackupDeviceName.js";
import { useSettings } from "../hooks/useSettings.js";
import { ToggleHandler } from "../mui/handlers.js";
import { Settings } from "../platform/api.js";
@@ -46,7 +41,6 @@ const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
export function SettingsPage(): VNode {
const [settings, updateSettings] = useSettings();
const { safely } = useAlertContext();
- const { name, update } = useBackupDeviceName();
const webex = platform.getWalletWebExVersion();
const api = useBackendContext();
@@ -55,12 +49,10 @@ export function SettingsPage(): VNode {
return { version };
});
- const version = hook && !hook.hasError ? hook.response.version : undefined
+ const version = hook && !hook.hasError ? hook.response.version : undefined;
return (
<SettingsView
- deviceName={name}
- setDeviceName={update}
autoOpenToggle={{
value: settings.autoOpen,
button: {
@@ -95,8 +87,6 @@ export function SettingsPage(): VNode {
}
export interface ViewProps {
- deviceName: string;
- setDeviceName: (s: string) => Promise<void>;
autoOpenToggle: ToggleHandler;
advanceToggle: ToggleHandler;
langToggle: ToggleHandler;
@@ -116,8 +106,6 @@ export function SettingsView({
}: ViewProps): VNode {
const { i18n, lang, supportedLang, changeLanguage } = useTranslationContext();
- const api = useBackendContext();
-
return (
<Fragment>
<section>
@@ -159,7 +147,8 @@ export function SettingsView({
<WarningBox>
<i18n.Translate>
The version of wallet core is not supported. (supported
- version: {WALLET_CORE_SUPPORTED_VERSION}, wallet version: {coreVersion.version})
+ version: {WALLET_CORE_SUPPORTED_VERSION}, wallet version:{" "}
+ {coreVersion.version})
</i18n.Translate>
</WarningBox>
)}
@@ -271,7 +260,7 @@ function AdvanceSettings(): VNode {
walletEnableV1Contracts: {
label: i18n.str`Enable v1 contract`,
description: i18n.str`v1 is under development and wallet-core converts v1 contract to v0. Enable this options for testing`,
- }
+ },
};
return (
<Fragment>
diff --git a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
@@ -19,18 +19,24 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-export * as a1 from "./Backup.stories.js";
export * as a4 from "./DepositPage/stories.js";
+
export * as a7 from "./History.stories.js";
-export * as a8 from "./AddBackupProvider/stories.js";
-export * as a10 from "./ProviderDetail.stories.js";
+
export * as a12 from "./Settings.stories.js";
+
export * as a13 from "./Transaction.stories.js";
+
export * as a14 from "./Welcome.stories.js";
+
export * as a15 from "./AddNewActionView.stories.js";
+
export * as a16 from "./DeveloperPage.stories.js";
+
export * as a17 from "./QrReader.stories.js";
+
export * as a18 from "./DestinationSelection/stories.js";
+
export * as a19 from "./ExchangeSelection/stories.js";
+
export * as a20 from "./ManageAccount/stories.js";
-export * as a21 from "./Notifications/stories.js";