wallet-core: implement enabling/disabling dev mode
This commit is contained in:
parent
3da1e82a24
commit
ded00b680a
@ -605,6 +605,7 @@ export interface WalletCoreVersion {
|
||||
exchange: string;
|
||||
merchant: string;
|
||||
bank: string;
|
||||
devMode?: boolean;
|
||||
}
|
||||
|
||||
export interface KnownBankAccountsInfo {
|
||||
|
@ -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;
|
||||
|
@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<HttpResponse> {
|
||||
return this.fetch(url, {
|
||||
method: "GET",
|
||||
...opt,
|
||||
});
|
||||
}
|
||||
|
||||
postJson(
|
||||
url: string,
|
||||
body: any,
|
||||
opt?: HttpRequestOptions | undefined,
|
||||
): Promise<HttpResponse> {
|
||||
return this.fetch(url, {
|
||||
method: "POST",
|
||||
body,
|
||||
...opt,
|
||||
});
|
||||
}
|
||||
|
||||
fetch(
|
||||
url: string,
|
||||
opt?: HttpRequestOptions | undefined,
|
||||
): Promise<HttpResponse> {
|
||||
logger.info(`devexperiment httplib ${url}`);
|
||||
return this.underlyingLib.fetch(url, opt);
|
||||
}
|
||||
}
|
||||
|
@ -191,6 +191,8 @@ export interface InternalWalletState {
|
||||
merchantOps: MerchantOperations;
|
||||
refreshOps: RefreshOperations;
|
||||
|
||||
devModeActive: boolean;
|
||||
|
||||
getDenomInfo(
|
||||
ws: InternalWalletState,
|
||||
tx: GetReadOnlyAccess<{
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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<WalletBackupConfState> {
|
||||
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;
|
||||
}
|
||||
|
@ -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<HttpResponse>;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
|
@ -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<void> {
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user