diff options
author | Florian Dold <florian@dold.me> | 2022-09-20 21:44:21 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-09-20 23:17:42 +0200 |
commit | 16a5bb40834c01e50e84144bb644517e67a66187 (patch) | |
tree | f6e7167aca608316ad03cf1e0debdb65eabe02cc /packages/taler-wallet-core/src | |
parent | 52ec740c825d4e94fd59ef0a5cd8e8b73f4dfc06 (diff) | |
download | wallet-core-16a5bb40834c01e50e84144bb644517e67a66187.tar.xz |
wallet-core: make basic backup work again
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r-- | packages/taler-wallet-core/src/db-utils.ts | 27 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 50 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/backup/export.ts | 93 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/backup/import.ts | 89 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/backup/index.ts | 10 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/withdraw.ts | 7 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/util/query.ts | 65 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet-api-types.ts | 10 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 16 |
9 files changed, 320 insertions, 47 deletions
diff --git a/packages/taler-wallet-core/src/db-utils.ts b/packages/taler-wallet-core/src/db-utils.ts index de54719c9..b32b3d585 100644 --- a/packages/taler-wallet-core/src/db-utils.ts +++ b/packages/taler-wallet-core/src/db-utils.ts @@ -72,6 +72,33 @@ function upgradeFromStoreMap( throw Error("upgrade not supported"); } +function promiseFromTransaction(transaction: IDBTransaction): Promise<void> { + return new Promise<void>((resolve, reject) => { + transaction.oncomplete = () => { + resolve(); + }; + transaction.onerror = () => { + reject(); + }; + }); +} + +/** + * Purge all data in the given database. + */ +export function clearDatabase(db: IDBDatabase): Promise<void> { + // db.objectStoreNames is a DOMStringList, so we need to convert + let stores: string[] = []; + for (let i = 0; i < db.objectStoreNames.length; i++) { + stores.push(db.objectStoreNames[i]); + } + const tx = db.transaction(stores, "readwrite"); + for (const store of stores) { + tx.objectStore(store).clear(); + } + return promiseFromTransaction(tx); +} + function onTalerDbUpgradeNeeded( db: IDBDatabase, oldVersion: number, diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 1275b0cf2..078060297 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021 Taler Systems S.A. + (C) 2021-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 @@ -50,6 +50,24 @@ import { RetryInfo, RetryTags } from "./util/retries.js"; import { Event, IDBDatabase } from "@gnu-taler/idb-bridge"; /** + * This file contains the database schema of the Taler wallet together + * with some helper functions. + * + * Some design considerations: + * - By convention, each object store must have a corresponding "<Name>Record" + * interface defined for it. + * - For records that represent operations, there should be exactly + * one top-level enum field that indicates the status of the operation. + * This field should be present even if redundant, because the field + * will have an index. + * - Amounts are stored as strings, except when they are needed for + * indexing. + * - Optional fields should be avoided, use "T | undefined" instead. + * + * @author Florian Dold <dold@taler.net> + */ + +/** * Name of the Taler database. This is effectively the major * version of the DB schema. Whenever it changes, custom import logic * for all previous versions must be written, which should be @@ -76,6 +94,9 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName"; */ export const WALLET_DB_MINOR_VERSION = 1; +/** + * Status of a withdrawal. + */ export enum ReserveRecordStatus { /** * Reserve must be registered with the bank. @@ -293,7 +314,7 @@ export interface DenominationRecord { * Was this denomination still offered by the exchange the last time * we checked? * Only false when the exchange redacts a previously published denomination. - * + * * FIXME: Consider rolling this and isRevoked into some bitfield? */ isOffered: boolean; @@ -520,6 +541,9 @@ export interface PlanchetRecord { */ coinIdx: number; + /** + * FIXME: make this an enum! + */ withdrawalDone: boolean; lastError: TalerErrorDetail | undefined; @@ -639,6 +663,9 @@ export interface CoinRecord { /** * Amount that's left on the coin. + * + * FIXME: This is pretty redundant with "allocation" and "status". + * Do we really need this? */ currentAmount: AmountJson; @@ -716,6 +743,9 @@ export interface ProposalDownload { */ contractTermsRaw: any; + /** + * Extracted / parsed data from the contract terms. + */ contractData: WalletContractData; } @@ -780,6 +810,9 @@ export interface TipRecord { */ tipAmountRaw: AmountJson; + /** + * Effect on the balance (including fees etc). + */ tipAmountEffective: AmountJson; /** @@ -800,6 +833,9 @@ export interface TipRecord { /** * Denomination selection made by the wallet for picking up * this tip. + * + * FIXME: Put this into some DenomSelectionCacheRecord instead of + * storing it here! */ denomsSel: DenomSelectionState; @@ -889,6 +925,8 @@ export interface RefreshGroupRecord { /** * No coins are pending, but at least one is frozen. + * + * FIXME: What does this mean? */ frozen?: boolean; } @@ -1319,11 +1357,15 @@ export interface WithdrawalGroupRecord { /** * Operation status of the withdrawal group. * Used for indexing in the database. + * + * FIXME: Redundant with reserveStatus */ operationStatus: OperationStatus; /** * Current status of the reserve. + * + * FIXME: Wrong name! */ reserveStatus: ReserveRecordStatus; @@ -1756,6 +1798,10 @@ export interface CoinAvailabilityRecord { freshCoinCount: number; } +/** + * Schema definition for the IndexedDB + * wallet database. + */ export const WalletStoresV1 = { coinAvailability: describeStore( "coinAvailability", diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index 35d5e6ef7..b39e6dc27 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -25,6 +25,7 @@ * Imports. */ import { + AbsoluteTime, Amounts, BackupBackupProvider, BackupBackupProviderTerms, @@ -35,6 +36,7 @@ import { BackupExchange, BackupExchangeDetails, BackupExchangeWireFee, + BackupOperationStatus, BackupProposal, BackupProposalStatus, BackupPurchase, @@ -44,30 +46,35 @@ import { BackupRefreshSession, BackupRefundItem, BackupRefundState, - BackupReserve, BackupTip, + BackupWgInfo, + BackupWgType, BackupWithdrawalGroup, + BACKUP_VERSION_MAJOR, + BACKUP_VERSION_MINOR, canonicalizeBaseUrl, canonicalJson, - Logger, - WalletBackupContentV1, - hash, encodeCrock, getRandomBytes, + hash, + Logger, stringToBytes, - AbsoluteTime, + WalletBackupContentV1, } from "@gnu-taler/taler-util"; -import { InternalWalletState } from "../../internal-wallet-state.js"; import { AbortStatus, CoinSourceType, CoinStatus, DenominationRecord, + OperationStatus, ProposalStatus, RefreshCoinStatus, RefundState, WALLET_BACKUP_STATE_KEY, + WithdrawalRecordType, } from "../../db.js"; +import { InternalWalletState } from "../../internal-wallet-state.js"; +import { assertUnreachable } from "../../util/assertUnreachable.js"; import { getWalletBackupState, provideBackupState } from "./state.js"; const logger = new Logger("backup/export.ts"); @@ -100,31 +107,75 @@ export async function exportBackup( const backupDenominationsByExchange: { [url: string]: BackupDenomination[]; } = {}; - const backupReservesByExchange: { [url: string]: BackupReserve[] } = {}; const backupPurchases: BackupPurchase[] = []; const backupProposals: BackupProposal[] = []; const backupRefreshGroups: BackupRefreshGroup[] = []; const backupBackupProviders: BackupBackupProvider[] = []; const backupTips: BackupTip[] = []; const backupRecoupGroups: BackupRecoupGroup[] = []; - const withdrawalGroupsByReserve: { - [reservePub: string]: BackupWithdrawalGroup[]; - } = {}; + const backupWithdrawalGroups: BackupWithdrawalGroup[] = []; await tx.withdrawalGroups.iter().forEachAsync(async (wg) => { - const withdrawalGroups = (withdrawalGroupsByReserve[wg.reservePub] ??= - []); - withdrawalGroups.push({ + let info: BackupWgInfo; + switch (wg.wgInfo.withdrawalType) { + case WithdrawalRecordType.BankIntegrated: + info = { + type: BackupWgType.BankIntegrated, + exchange_payto_uri: wg.wgInfo.bankInfo.exchangePaytoUri, + taler_withdraw_uri: wg.wgInfo.bankInfo.talerWithdrawUri, + confirm_url: wg.wgInfo.bankInfo.confirmUrl, + timestamp_bank_confirmed: + wg.wgInfo.bankInfo.timestampBankConfirmed, + timestamp_reserve_info_posted: + wg.wgInfo.bankInfo.timestampReserveInfoPosted, + }; + break; + case WithdrawalRecordType.BankManual: + info = { + type: BackupWgType.BankManual, + }; + break; + case WithdrawalRecordType.PeerPullCredit: + info = { + type: BackupWgType.PeerPullCredit, + contract_priv: wg.wgInfo.contractPriv, + contract_terms: wg.wgInfo.contractTerms, + }; + break; + case WithdrawalRecordType.PeerPushCredit: + info = { + type: BackupWgType.PeerPushCredit, + contract_terms: wg.wgInfo.contractTerms, + }; + break; + case WithdrawalRecordType.Recoup: + info = { + type: BackupWgType.Recoup, + }; + break; + default: + assertUnreachable(wg.wgInfo); + } + backupWithdrawalGroups.push({ raw_withdrawal_amount: Amounts.stringify(wg.rawWithdrawalAmount), - selected_denoms: wg.denomsSel.selectedDenoms.map((x) => ({ - count: x.count, - denom_pub_hash: x.denomPubHash, - })), + info, timestamp_created: wg.timestampStart, timestamp_finish: wg.timestampFinish, withdrawal_group_id: wg.withdrawalGroupId, secret_seed: wg.secretSeed, - selected_denoms_id: wg.denomSelUid, + exchange_base_url: wg.exchangeBaseUrl, + instructed_amount: Amounts.stringify(wg.instructedAmount), + reserve_priv: wg.reservePriv, + restrict_age: wg.restrictAge, + operation_status: + wg.operationStatus == OperationStatus.Finished + ? BackupOperationStatus.Finished + : BackupOperationStatus.Pending, + selected_denoms_uid: wg.denomSelUid, + selected_denoms: wg.denomsSel.selectedDenoms.map((x) => ({ + count: x.count, + denom_pub_hash: x.denomPubHash, + })), }); }); @@ -299,7 +350,6 @@ export async function exportBackup( tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp, denominations: backupDenominationsByExchange[ex.exchangeBaseUrl] ?? [], - reserves: backupReservesByExchange[ex.exchangeBaseUrl] ?? [], }); }); @@ -439,7 +489,8 @@ export async function exportBackup( const backupBlob: WalletBackupContentV1 = { schema_id: "gnu-taler-wallet-backup-content", - schema_version: 1, + schema_version: BACKUP_VERSION_MAJOR, + minor_version: BACKUP_VERSION_MINOR, exchanges: backupExchanges, exchange_details: backupExchangeDetails, wallet_root_pub: bs.walletRootPub, @@ -456,6 +507,8 @@ export async function exportBackup( intern_table: {}, error_reports: [], tombstones: [], + // FIXME! + withdrawal_groups: backupWithdrawalGroups, }; // If the backup changed, we change our nonce and timestamp. diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index be09952cd..507a6cf10 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -24,6 +24,7 @@ import { BackupPurchase, BackupRefreshReason, BackupRefundState, + BackupWgType, codecForContractTerms, DenomKeyType, j2s, @@ -53,8 +54,11 @@ import { WalletContractData, WalletRefundItem, WalletStoresV1, + WgInfo, + WithdrawalRecordType, } from "../../db.js"; import { InternalWalletState } from "../../internal-wallet-state.js"; +import { assertUnreachable } from "../../util/assertUnreachable.js"; import { checkDbInvariant, checkLogicInvariant, @@ -444,6 +448,91 @@ export async function importBackup( } } + for (const backupWg of backupBlob.withdrawal_groups) { + const reservePub = cryptoComp.reservePrivToPub[backupWg.reserve_priv]; + checkLogicInvariant(!!reservePub); + const ts = makeEventId(TombstoneTag.DeleteReserve, reservePub); + if (tombstoneSet.has(ts)) { + continue; + } + const existingWg = await tx.withdrawalGroups.get( + backupWg.withdrawal_group_id, + ); + if (existingWg) { + continue; + } + let wgInfo: WgInfo; + switch (backupWg.info.type) { + case BackupWgType.BankIntegrated: + wgInfo = { + withdrawalType: WithdrawalRecordType.BankIntegrated, + bankInfo: { + exchangePaytoUri: backupWg.info.exchange_payto_uri, + talerWithdrawUri: backupWg.info.taler_withdraw_uri, + confirmUrl: backupWg.info.confirm_url, + timestampBankConfirmed: + backupWg.info.timestamp_bank_confirmed, + timestampReserveInfoPosted: + backupWg.info.timestamp_reserve_info_posted, + }, + }; + break; + case BackupWgType.BankManual: + wgInfo = { + withdrawalType: WithdrawalRecordType.BankManual, + }; + break; + case BackupWgType.PeerPullCredit: + wgInfo = { + withdrawalType: WithdrawalRecordType.PeerPullCredit, + contractTerms: backupWg.info.contract_terms, + contractPriv: backupWg.info.contract_priv, + }; + break; + case BackupWgType.PeerPushCredit: + wgInfo = { + withdrawalType: WithdrawalRecordType.PeerPushCredit, + contractTerms: backupWg.info.contract_terms, + }; + break; + case BackupWgType.Recoup: + wgInfo = { + withdrawalType: WithdrawalRecordType.Recoup, + }; + break; + default: + assertUnreachable(backupWg.info); + } + await tx.withdrawalGroups.put({ + withdrawalGroupId: backupWg.withdrawal_group_id, + exchangeBaseUrl: backupWg.exchange_base_url, + instructedAmount: Amounts.parseOrThrow(backupWg.instructed_amount), + secretSeed: backupWg.secret_seed, + operationStatus: backupWg.timestamp_finish + ? OperationStatus.Finished + : OperationStatus.Pending, + denomsSel: await getDenomSelStateFromBackup( + tx, + backupWg.exchange_base_url, + backupWg.selected_denoms, + ), + denomSelUid: backupWg.selected_denoms_uid, + rawWithdrawalAmount: Amounts.parseOrThrow( + backupWg.raw_withdrawal_amount, + ), + reservePriv: backupWg.reserve_priv, + reservePub, + reserveStatus: backupWg.timestamp_finish + ? ReserveRecordStatus.Dormant + : ReserveRecordStatus.QueryingStatus, // FIXME! + timestampStart: backupWg.timestamp_created, + wgInfo, + restrictAge: backupWg.restrict_age, + senderWire: undefined, // FIXME! + timestampFinish: backupWg.timestamp_finish, + }); + } + // FIXME: import reserves with new schema // for (const backupReserve of backupExchangeDetails.reserves) { diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index c7c93e909..b69c0b7b7 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -187,11 +187,11 @@ async function computeBackupCryptoData( cryptoData.rsaDenomPubToHash[backupDenom.denom_pub.rsa_public_key] = encodeCrock(hashDenomPub(backupDenom.denom_pub)); } - for (const backupReserve of backupExchangeDetails.reserves) { - cryptoData.reservePrivToPub[backupReserve.reserve_priv] = encodeCrock( - eddsaGetPublic(decodeCrock(backupReserve.reserve_priv)), - ); - } + } + for (const backupWg of backupContent.withdrawal_groups) { + cryptoData.reservePrivToPub[backupWg.reserve_priv] = encodeCrock( + eddsaGetPublic(decodeCrock(backupWg.reserve_priv)), + ); } for (const prop of backupContent.proposals) { const { h: contractTermsHash } = await cryptoApi.hashString({ diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index de9721f3d..7dd874f49 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -96,12 +96,13 @@ import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; import { OperationAttemptResult, OperationAttemptResultType, + RetryTags, } from "../util/retries.js"; import { WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, } from "../versions.js"; -import { makeCoinAvailable } from "../wallet.js"; +import { makeCoinAvailable, storeOperationPending } from "../wallet.js"; import { getExchangeDetails, getExchangePaytoUri, @@ -1099,6 +1100,7 @@ export async function processWithdrawalGroup( ); if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) { + logger.warn("Finishing empty withdrawal group (no denoms)"); await ws.db .mktx((x) => [x.withdrawalGroups]) .runReadWrite(async (tx) => { @@ -1107,6 +1109,7 @@ export async function processWithdrawalGroup( return; } wg.operationStatus = OperationStatus.Finished; + wg.timestampFinish = TalerProtocolTimestamp.now(); await tx.withdrawalGroups.put(wg); }); return { @@ -1185,7 +1188,7 @@ export async function processWithdrawalGroup( errorsPerCoin[x.coinIdx] = x.lastError; } }); - logger.trace(`now withdrawn ${numFinished} of ${numTotalCoins} coins`); + logger.info(`now withdrawn ${numFinished} of ${numTotalCoins} coins`); if (wg.timestampFinish === undefined && numFinished === numTotalCoins) { finishedForFirstTime = true; wg.timestampFinish = TalerProtocolTimestamp.now(); diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index 8b8c30f35..d1aae6fd6 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -409,10 +409,12 @@ export type GetReadWriteAccess<BoundStores> = { type ReadOnlyTransactionFunction<BoundStores, T> = ( t: GetReadOnlyAccess<BoundStores>, + rawTx: IDBTransaction, ) => Promise<T>; type ReadWriteTransactionFunction<BoundStores, T> = ( t: GetReadWriteAccess<BoundStores>, + rawTx: IDBTransaction, ) => Promise<T>; export interface TransactionContext<BoundStores> { @@ -420,22 +422,10 @@ export interface TransactionContext<BoundStores> { runReadOnly<T>(f: ReadOnlyTransactionFunction<BoundStores, T>): Promise<T>; } -type CheckDescriptor<T> = T extends StoreWithIndexes< - infer SN, - infer SD, - infer IM -> - ? StoreWithIndexes<SN, SD, IM> - : unknown; - -type GetPickerType<F, SM> = F extends (x: SM) => infer Out - ? { [P in keyof Out]: CheckDescriptor<Out[P]> } - : unknown; - function runTx<Arg, Res>( tx: IDBTransaction, arg: Arg, - f: (t: Arg) => Promise<Res>, + f: (t: Arg, t2: IDBTransaction) => Promise<Res>, ): Promise<Res> { const stack = Error("Failed transaction was started here."); return new Promise((resolve, reject) => { @@ -474,7 +464,7 @@ function runTx<Arg, Res>( logger.error(msg); reject(new TransactionAbortedError(msg)); }; - const resP = Promise.resolve().then(() => f(arg)); + const resP = Promise.resolve().then(() => f(arg, tx)); resP .then((result) => { gotFunResult = true; @@ -625,6 +615,46 @@ export class DbAccess<StoreMap> { } /** + * Run a transaction with all object stores. + */ + mktxAll(): TransactionContext<StoreMap> { + const storeNames: string[] = []; + const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = + {}; + + for (let i = 0; i < this.db.objectStoreNames.length; i++) { + const sn = this.db.objectStoreNames[i]; + const swi = (this.stores as any)[sn] as StoreWithIndexes<any, any, any>; + if (!swi) { + throw Error(`store metadata not available (${sn})`); + } + storeNames.push(sn); + accessibleStores[sn] = swi; + } + + const runReadOnly = <T>( + txf: ReadOnlyTransactionFunction<StoreMap, T>, + ): Promise<T> => { + const tx = this.db.transaction(storeNames, "readonly"); + const readContext = makeReadContext(tx, accessibleStores); + return runTx(tx, readContext, txf); + }; + + const runReadWrite = <T>( + txf: ReadWriteTransactionFunction<StoreMap, T>, + ): Promise<T> => { + const tx = this.db.transaction(storeNames, "readwrite"); + const writeContext = makeWriteContext(tx, accessibleStores); + return runTx(tx, writeContext, txf); + }; + + return { + runReadOnly, + runReadWrite, + }; + } + + /** * Run a transaction with selected object stores. * * The {@link namePicker} must be a function that selects a list of object @@ -638,13 +668,14 @@ export class DbAccess<StoreMap> { [X in StoreNamesOf<StoreList>]: StoreList[number] & { storeName: X }; }, >(namePicker: (x: StoreMap) => StoreList): TransactionContext<BoundStores> { + const storeNames: string[] = []; + const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = + {}; + const storePick = namePicker(this.stores) as any; if (typeof storePick !== "object" || storePick === null) { throw Error(); } - const storeNames: string[] = []; - const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = - {}; for (const swiPicked of storePick) { const swi = swiPicked as StoreWithIndexes<any, any, any>; if (swi.mark !== storeWithIndexesSymbol) { diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 665be80fb..f2c76731b 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -134,6 +134,8 @@ export enum WalletApiOperation { InitiatePeerPullPayment = "initiatePeerPullPayment", CheckPeerPullPayment = "checkPeerPullPayment", AcceptPeerPullPayment = "acceptPeerPullPayment", + ClearDb = "clearDb", + Recycle = "recycle", } export type WalletOperations = { @@ -317,6 +319,14 @@ export type WalletOperations = { request: AcceptPeerPullPaymentRequest; response: {}; }; + [WalletApiOperation.ClearDb]: { + request: {}; + response: {}; + }; + [WalletApiOperation.Recycle]: { + request: {}; + response: {}; + }; }; export type RequestType< diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 1b74f2025..2e362da6e 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -99,6 +99,7 @@ import { CryptoDispatcher, CryptoWorkerFactory, } from "./crypto/workers/cryptoDispatcher.js"; +import { clearDatabase } from "./db-utils.js"; import { AuditorTrustRecord, CoinRecord, @@ -114,7 +115,6 @@ import { makeErrorDetail, TalerError, } from "./errors.js"; -import { createDenominationTimeline } from "./index.browser.js"; import { ExchangeOperations, InternalWalletState, @@ -131,6 +131,7 @@ import { codecForRunBackupCycle, getBackupInfo, getBackupRecovery, + importBackupPlain, loadBackupRecovery, processBackupForProvider, removeBackupProvider, @@ -215,6 +216,7 @@ import { } from "./pending-types.js"; import { assertUnreachable } from "./util/assertUnreachable.js"; import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js"; +import { createDenominationTimeline } from "./util/denominations.js"; import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, @@ -1060,8 +1062,11 @@ async function dispatchRequestInternal( `wallet must be initialized before running operation ${operation}`, ); } + // FIXME: Can we make this more type-safe by using the request/response type + // definitions we already have? switch (operation) { case "initWallet": { + logger.info("initializing wallet"); ws.initCalled = true; if (typeof payload === "object" && (payload as any).skipDefaults) { logger.info("skipping defaults"); @@ -1371,6 +1376,15 @@ async function dispatchRequestInternal( logger.info(`started fakebank withdrawal: ${j2s(fbResp)}`); return {}; } + case "clearDb": + await clearDatabase(ws.db.idbHandle()); + return {}; + case "recycle": { + const backup = await exportBackup(ws); + await clearDatabase(ws.db.idbHandle()); + await importBackupPlain(ws, backup); + return {}; + } case "exportDb": { const dbDump = await exportDb(ws.db.idbHandle()); return dbDump; |