summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/taler-util/src/walletTypes.ts1
-rw-r--r--packages/taler-wallet-core/src/db.ts11
-rw-r--r--packages/taler-wallet-core/src/dev-experiments.ts118
-rw-r--r--packages/taler-wallet-core/src/internal-wallet-state.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/backup/export.ts4
-rw-r--r--packages/taler-wallet-core/src/operations/backup/index.ts8
-rw-r--r--packages/taler-wallet-core/src/operations/backup/state.ts20
-rw-r--r--packages/taler-wallet-core/src/util/http.ts4
-rw-r--r--packages/taler-wallet-core/src/wallet.ts9
9 files changed, 156 insertions, 21 deletions
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<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);
+ }
}
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<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;
}
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<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,
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<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,