commit c73a73e3471c37e59d63f4fd429e9f4589685003
parent 5fc116155854d334d131db06e2e8db6f6add855b
Author: Florian Dold <dold@taler.net>
Date: Sun, 9 Aug 2026 14:49:47 +0200
wallet-cli: add offline mode
Diffstat:
2 files changed, 52 insertions(+), 3 deletions(-)
diff --git a/packages/taler-wallet-cli/README.md b/packages/taler-wallet-cli/README.md
@@ -7,3 +7,12 @@ GNU Taler wallet.
To be able to use the sqlite3 backend, make sure that better-sqlite3
is installed as an optional dependency in the ../idb-bridge package.
+
+## Offline mode
+
+Set `TALER_WALLET_OFFLINE=1` to make every network request from the local
+wallet-core fail before it is sent. This is useful when inspecting or
+debugging a wallet database without changing server-side state. Offline mode
+does not support `--wallet-connection` (or `TALER_WALLET_CONNECTION`), because
+that connects to a separately running wallet-core process that the CLI cannot
+constrain.
diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts
@@ -43,6 +43,7 @@ import {
setDangerousTimetravel,
setGlobalLogLevelFromString,
summarizeTalerErrorDetail,
+ TalerError,
TalerErrorCode,
TalerUriAction,
TalerUris,
@@ -81,7 +82,11 @@ import {
readlinePrompt,
setUnhandledRejectionHandler,
} from "@gnu-taler/taler-util/compat";
-import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
+import {
+ createPlatformHttpLib,
+ type HttpRequestOptions,
+ type HttpRequestLibrary,
+} from "@gnu-taler/taler-util/http";
import { JsonMessage, runRpcServer } from "@gnu-taler/taler-util/twrpc";
import {
createNativeWalletHost2,
@@ -493,6 +498,35 @@ function checkEnvFlag(name: string): boolean {
return false;
}
+/**
+ * HTTP implementation used when TALER_WALLET_OFFLINE=1.
+ *
+ * Rejecting at this boundary means the request cannot reach the platform HTTP
+ * implementation, and thus cannot result in a connection to a server.
+ */
+class OfflineHttpLib implements HttpRequestLibrary {
+ async fetch(requestUrl: string, opt?: HttpRequestOptions): Promise<never> {
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_NETWORK_ERROR,
+ {
+ requestUrl,
+ requestMethod: opt?.method ?? "GET",
+ },
+ "network requests are disabled by TALER_WALLET_OFFLINE=1",
+ );
+ }
+}
+
+function createWalletHttpLib(args?: {
+ enableThrottling?: boolean;
+ requireTls?: boolean;
+}): HttpRequestLibrary {
+ if (checkEnvFlag("TALER_WALLET_OFFLINE")) {
+ return new OfflineHttpLib();
+ }
+ return createPlatformHttpLib(args);
+}
+
export interface WalletContext {
/**
* High-level client for making API requests to wallet-core.
@@ -535,7 +569,7 @@ async function createLocalWallet(
notificationHandler?: (n: WalletNotification) => void,
): Promise<CreateWalletResult> {
const dbPath = walletCliArgs.wallet.walletDbFile ?? defaultWalletDbPath;
- const myHttpLib = createPlatformHttpLib({
+ const myHttpLib = createWalletHttpLib({
enableThrottling: walletCliArgs.wallet.noThrottle ? false : true,
requireTls: walletCliArgs.wallet.noHttp,
});
@@ -649,6 +683,12 @@ async function withWallet<T>(
}
if (walletSocketPath) {
+ if (checkEnvFlag("TALER_WALLET_OFFLINE")) {
+ throw new CliUsageError(
+ "TALER_WALLET_OFFLINE=1 cannot be used with a remote wallet connection",
+ "offline mode only supports the wallet-core process started by taler-wallet-cli",
+ );
+ }
logger.info("creating remote wallet");
const w = await createRemoteWallet({
name: "wallet",
@@ -3181,7 +3221,7 @@ advancedCli
mark: "experimental",
})
.action(async (args) => {
- const myHttpLib = createPlatformHttpLib();
+ const myHttpLib = createWalletHttpLib();
const res = await createNativeWalletHost2({
// No persistent DB storage.
persistentStoragePath: undefined,