From ded00b680a776d03cd8c928354c87c0be8690f56 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 12 Oct 2022 22:27:50 +0200 Subject: wallet-core: implement enabling/disabling dev mode --- packages/taler-util/src/walletTypes.ts | 1 + packages/taler-wallet-core/src/db.ts | 11 +- packages/taler-wallet-core/src/dev-experiments.ts | 118 ++++++++++++++++++++- .../taler-wallet-core/src/internal-wallet-state.ts | 2 + .../src/operations/backup/export.ts | 4 +- .../src/operations/backup/index.ts | 8 +- .../src/operations/backup/state.ts | 20 ++-- packages/taler-wallet-core/src/util/http.ts | 4 + packages/taler-wallet-core/src/wallet.ts | 9 +- 9 files changed, 156 insertions(+), 21 deletions(-) (limited to 'packages') diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 05b18fe6d..a1fa9b439 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -605,6 +605,7 @@ export interface WalletCoreVersion { exchange: string; merchant: string; bank: string; + devMode?: boolean; } export interface KnownBankAccountsInfo { diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 539a925c1..f4cdb68c1 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1240,7 +1240,11 @@ export interface PurchaseRecord { refundAmountAwaiting: AmountJson | undefined; } -export const WALLET_BACKUP_STATE_KEY = "walletBackupState"; +export enum ConfigRecordKey { + WalletBackupState = "walletBackupState", + CurrencyDefaultsApplied = "currencyDefaultsApplied", + DevMode = "devMode", +} /** * Configuration key/value entries to configure @@ -1248,10 +1252,11 @@ export const WALLET_BACKUP_STATE_KEY = "walletBackupState"; */ export type ConfigRecord = | { - key: typeof WALLET_BACKUP_STATE_KEY; + key: ConfigRecordKey.WalletBackupState; value: WalletBackupConfState; } - | { key: "currencyDefaultsApplied"; value: boolean }; + | { key: ConfigRecordKey.CurrencyDefaultsApplied; value: boolean } + | { key: ConfigRecordKey.DevMode; value: boolean }; export interface WalletBackupConfState { deviceId: string; diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts index 8e2ce5882..c3167b3e4 100644 --- a/packages/taler-wallet-core/src/dev-experiments.ts +++ b/packages/taler-wallet-core/src/dev-experiments.ts @@ -25,14 +25,130 @@ * Imports. */ -import { Logger } from "@gnu-taler/taler-util"; +import { Logger, parseDevExperimentUri } from "@gnu-taler/taler-util"; +import { ConfigRecordKey } from "./db.js"; import { InternalWalletState } from "./internal-wallet-state.js"; +import { + HttpRequestLibrary, + HttpRequestOptions, + HttpResponse, +} from "./util/http.js"; const logger = new Logger("dev-experiments.ts"); +/** + * Apply a dev experiment to the wallet database / state. + */ export async function applyDevExperiment( ws: InternalWalletState, uri: string, ): Promise { logger.info(`applying dev experiment ${uri}`); + const parsedUri = parseDevExperimentUri(uri); + if (!parsedUri) { + logger.info("unable to parse dev experiment URI"); + return; + } + if (parsedUri.devExperimentId == "enable-devmode") { + logger.info("enabling devmode"); + await ws.db + .mktx((x) => [x.config]) + .runReadWrite(async (tx) => { + tx.config.put({ + key: ConfigRecordKey.DevMode, + value: true, + }); + }); + await maybeInitDevMode(ws); + return; + } + if (parsedUri.devExperimentId === "disable-devmode") { + logger.info("disabling devmode"); + await ws.db + .mktx((x) => [x.config]) + .runReadWrite(async (tx) => { + tx.config.put({ + key: ConfigRecordKey.DevMode, + value: false, + }); + }); + await leaveDevMode(ws); + return; + } + if (!ws.devModeActive) { + throw Error( + "can't handle devmode URI (other than enable-devmode) unless devmode is active", + ); + } + throw Error(`dev-experiment id not understood ${parsedUri.devExperimentId}`); +} + +/** + * Enter dev mode, if the wallet's config entry in the DB demands it. + */ +export async function maybeInitDevMode(ws: InternalWalletState): Promise { + const devMode = await ws.db + .mktx((x) => [x.config]) + .runReadOnly(async (tx) => { + const rec = await tx.config.get(ConfigRecordKey.DevMode); + if (!rec || rec.key !== ConfigRecordKey.DevMode) { + return false; + } + return rec.value; + }); + if (!devMode) { + ws.devModeActive = false; + return; + } + ws.devModeActive = true; + if (ws.http instanceof DevExperimentHttpLib) { + return; + } + ws.http = new DevExperimentHttpLib(ws.http); +} + +export async function leaveDevMode(ws: InternalWalletState): Promise { + if (ws.http instanceof DevExperimentHttpLib) { + ws.http = ws.http.underlyingLib; + } + ws.devModeActive = false; +} + +export class DevExperimentHttpLib implements HttpRequestLibrary { + _isDevExperimentLib = true; + underlyingLib: HttpRequestLibrary; + + constructor(lib: HttpRequestLibrary) { + this.underlyingLib = lib; + } + + get( + url: string, + opt?: HttpRequestOptions | undefined, + ): Promise { + return this.fetch(url, { + method: "GET", + ...opt, + }); + } + + postJson( + url: string, + body: any, + opt?: HttpRequestOptions | undefined, + ): Promise { + return this.fetch(url, { + method: "POST", + body, + ...opt, + }); + } + + fetch( + url: string, + opt?: HttpRequestOptions | undefined, + ): Promise { + logger.info(`devexperiment httplib ${url}`); + return this.underlyingLib.fetch(url, opt); + } } diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts index 6c7d943cb..bc956bd17 100644 --- a/packages/taler-wallet-core/src/internal-wallet-state.ts +++ b/packages/taler-wallet-core/src/internal-wallet-state.ts @@ -191,6 +191,8 @@ export interface InternalWalletState { merchantOps: MerchantOperations; refreshOps: RefreshOperations; + devModeActive: boolean; + getDenomInfo( ws: InternalWalletState, tx: GetReadOnlyAccess<{ diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index a3c4c8d99..c7890b5d8 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -64,11 +64,11 @@ import { import { CoinSourceType, CoinStatus, + ConfigRecordKey, DenominationRecord, PurchaseStatus, RefreshCoinStatus, RefundState, - WALLET_BACKUP_STATE_KEY, WithdrawalGroupStatus, WithdrawalRecordType, } from "../../db.js"; @@ -547,7 +547,7 @@ export async function exportBackup( )} and nonce to ${bs.lastBackupNonce}`, ); await tx.config.put({ - key: WALLET_BACKUP_STATE_KEY, + key: ConfigRecordKey.WalletBackupState, value: bs, }); } else { diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 3d3ebf04a..8e5e69097 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -74,8 +74,8 @@ import { BackupProviderStateTag, BackupProviderTerms, ConfigRecord, + ConfigRecordKey, WalletBackupConfState, - WALLET_BACKUP_STATE_KEY, } from "../../db.js"; import { InternalWalletState } from "../../internal-wallet-state.js"; import { @@ -861,10 +861,12 @@ async function backupRecoveryTheirs( .mktx((x) => [x.config, x.backupProviders]) .runReadWrite(async (tx) => { let backupStateEntry: ConfigRecord | undefined = await tx.config.get( - WALLET_BACKUP_STATE_KEY, + ConfigRecordKey.WalletBackupState, ); checkDbInvariant(!!backupStateEntry); - checkDbInvariant(backupStateEntry.key === WALLET_BACKUP_STATE_KEY); + checkDbInvariant( + backupStateEntry.key === ConfigRecordKey.WalletBackupState, + ); backupStateEntry.value.lastBackupNonce = undefined; backupStateEntry.value.lastBackupTimestamp = undefined; backupStateEntry.value.lastBackupCheckTimestamp = undefined; diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts b/packages/taler-wallet-core/src/operations/backup/state.ts index 2efd9be8e..b8dbb15c1 100644 --- a/packages/taler-wallet-core/src/operations/backup/state.ts +++ b/packages/taler-wallet-core/src/operations/backup/state.ts @@ -17,9 +17,9 @@ import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; import { ConfigRecord, + ConfigRecordKey, WalletBackupConfState, WalletStoresV1, - WALLET_BACKUP_STATE_KEY, } from "../../db.js"; import { checkDbInvariant } from "../../util/invariants.js"; import { GetReadOnlyAccess } from "../../util/query.js"; @@ -31,10 +31,10 @@ export async function provideBackupState( const bs: ConfigRecord | undefined = await ws.db .mktx((stores) => [stores.config]) .runReadOnly(async (tx) => { - return await tx.config.get(WALLET_BACKUP_STATE_KEY); + return await tx.config.get(ConfigRecordKey.WalletBackupState); }); if (bs) { - checkDbInvariant(bs.key === WALLET_BACKUP_STATE_KEY); + checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState); return bs.value; } // We need to generate the key outside of the transaction @@ -48,11 +48,11 @@ export async function provideBackupState( .mktx((x) => [x.config]) .runReadWrite(async (tx) => { let backupStateEntry: ConfigRecord | undefined = await tx.config.get( - WALLET_BACKUP_STATE_KEY, + ConfigRecordKey.WalletBackupState, ); if (!backupStateEntry) { backupStateEntry = { - key: WALLET_BACKUP_STATE_KEY, + key: ConfigRecordKey.WalletBackupState, value: { deviceId, walletRootPub: k.pub, @@ -62,7 +62,7 @@ export async function provideBackupState( }; await tx.config.put(backupStateEntry); } - checkDbInvariant(backupStateEntry.key === WALLET_BACKUP_STATE_KEY); + checkDbInvariant(backupStateEntry.key === ConfigRecordKey.WalletBackupState); return backupStateEntry.value; }); } @@ -71,9 +71,9 @@ export async function getWalletBackupState( ws: InternalWalletState, tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>, ): Promise { - const bs = await tx.config.get(WALLET_BACKUP_STATE_KEY); + const bs = await tx.config.get(ConfigRecordKey.WalletBackupState); checkDbInvariant(!!bs, "wallet backup state should be in DB"); - checkDbInvariant(bs.key === WALLET_BACKUP_STATE_KEY); + checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState); return bs.value; } @@ -86,11 +86,11 @@ export async function setWalletDeviceId( .mktx((x) => [x.config]) .runReadWrite(async (tx) => { let backupStateEntry: ConfigRecord | undefined = await tx.config.get( - WALLET_BACKUP_STATE_KEY, + ConfigRecordKey.WalletBackupState, ); if ( !backupStateEntry || - backupStateEntry.key !== WALLET_BACKUP_STATE_KEY + backupStateEntry.key !== ConfigRecordKey.WalletBackupState ) { return; } diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts index 58edd289b..0489f920b 100644 --- a/packages/taler-wallet-core/src/util/http.ts +++ b/packages/taler-wallet-core/src/util/http.ts @@ -111,11 +111,15 @@ export class Headers { export interface HttpRequestLibrary { /** * Make an HTTP GET request. + * + * FIXME: Get rid of this, we want the API surface to be minimal. */ get(url: string, opt?: HttpRequestOptions): Promise; /** * Make an HTTP POST request with a JSON body. + * + * FIXME: Get rid of this, we want the API surface to be minimal. */ postJson( url: string, diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index e25b4bd95..48d379931 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -105,12 +105,13 @@ import { AuditorTrustRecord, CoinSourceType, CoinStatus, + ConfigRecordKey, DenominationRecord, exportDb, importDb, WalletStoresV1, } from "./db.js"; -import { applyDevExperiment } from "./dev-experiments.js"; +import { applyDevExperiment, maybeInitDevMode } from "./dev-experiments.js"; import { getErrorDetailFromException, TalerError } from "./errors.js"; import { ActiveLongpollInfo, @@ -476,7 +477,7 @@ async function fillDefaults(ws: InternalWalletState): Promise { provideExchangeRecordInTx(ws, tx, baseUrl, now); } await tx.config.put({ - key: "currencyDefaultsApplied", + key: ConfigRecordKey.CurrencyDefaultsApplied, value: true, }); }); @@ -970,6 +971,7 @@ async function dispatchRequestInternal( logger.trace("filling defaults"); await fillDefaults(ws); } + await maybeInitDevMode(ws); return {}; } case "withdrawTestkudos": { @@ -1339,6 +1341,7 @@ async function dispatchRequestInternal( exchange: WALLET_EXCHANGE_PROTOCOL_VERSION, merchant: WALLET_MERCHANT_PROTOCOL_VERSION, bank: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, + devMode: ws.devModeActive, }; return version; } @@ -1480,6 +1483,8 @@ class InternalWalletStateImpl implements InternalWalletState { initCalled = false; + devModeActive = false; + exchangeOps: ExchangeOperations = { getExchangeDetails, getExchangeTrust, -- cgit v1.2.3