summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/host-impl.node.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/host-impl.node.ts')
-rw-r--r--packages/taler-wallet-core/src/host-impl.node.ts212
1 files changed, 212 insertions, 0 deletions
diff --git a/packages/taler-wallet-core/src/host-impl.node.ts b/packages/taler-wallet-core/src/host-impl.node.ts
new file mode 100644
index 000000000..ec026b296
--- /dev/null
+++ b/packages/taler-wallet-core/src/host-impl.node.ts
@@ -0,0 +1,212 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Helpers to create headless wallets.
+ * @author Florian Dold <dold@taler.net>
+ */
+
+/**
+ * Imports.
+ */
+import type { IDBFactory } from "@gnu-taler/idb-bridge";
+// eslint-disable-next-line no-duplicate-imports
+import {
+ AccessStats,
+ BridgeIDBFactory,
+ MemoryBackend,
+ createSqliteBackend,
+ shimIndexedDB,
+} from "@gnu-taler/idb-bridge";
+import { createNodeSqlite3Impl } from "@gnu-taler/idb-bridge/node-sqlite3-bindings";
+import {
+ Logger,
+ SetTimeoutTimerAPI,
+ WalletRunConfig,
+} from "@gnu-taler/taler-util";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
+import * as fs from "fs";
+import { NodeThreadCryptoWorkerFactory } from "./crypto/workers/nodeThreadWorker.js";
+import { SynchronousCryptoWorkerFactoryPlain } from "./crypto/workers/synchronousWorkerFactoryPlain.js";
+import { DefaultNodeWalletArgs, makeTempfileId } from "./host-common.js";
+import { Wallet } from "./wallet.js";
+
+const logger = new Logger("host-impl.node.ts");
+
+interface MakeDbResult {
+ idbFactory: BridgeIDBFactory;
+ getStats: () => AccessStats;
+}
+
+async function makeFileDb(
+ args: DefaultNodeWalletArgs = {},
+): Promise<MakeDbResult> {
+ const myBackend = new MemoryBackend();
+ myBackend.enableTracing = false;
+ const storagePath = args.persistentStoragePath;
+ if (storagePath) {
+ try {
+ const dbContentStr: string = fs.readFileSync(storagePath, {
+ encoding: "utf-8",
+ });
+ const dbContent = JSON.parse(dbContentStr);
+ myBackend.importDump(dbContent);
+ } catch (e: any) {
+ const code: string = e.code;
+ if (code === "ENOENT") {
+ logger.trace("wallet file doesn't exist yet");
+ } else {
+ logger.error("could not open wallet database file");
+ throw Error(
+ "could not open wallet database file",
+ // @ts-expect-error no support for options.cause yet
+ { cause: e },
+ );
+ }
+ }
+
+ myBackend.afterCommitCallback = async () => {
+ logger.trace("committing database");
+ // Allow caller to stop persisting the wallet.
+ if (args.persistentStoragePath === undefined) {
+ return;
+ }
+ const tmpPath = `${args.persistentStoragePath}-${makeTempfileId(5)}.tmp`;
+ logger.trace("exported DB dump");
+ const dbContent = myBackend.exportDump();
+ fs.writeFileSync(tmpPath, JSON.stringify(dbContent, undefined, 2), {
+ encoding: "utf-8",
+ });
+ // Atomically move the temporary file onto the DB path.
+ fs.renameSync(tmpPath, args.persistentStoragePath);
+ logger.trace("committing database done");
+ };
+ }
+
+ BridgeIDBFactory.enableTracing = false;
+
+ const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
+ return {
+ idbFactory: myBridgeIdbFactory,
+ getStats: () => myBackend.accessStats,
+ };
+}
+
+async function makeSqliteDb(
+ args: DefaultNodeWalletArgs,
+): Promise<MakeDbResult> {
+ BridgeIDBFactory.enableTracing = false;
+ const imp = await createNodeSqlite3Impl();
+ const dbFilename = args.persistentStoragePath ?? ":memory:";
+ logger.info(`using database ${dbFilename}`);
+ const myBackend = await createSqliteBackend(imp, {
+ filename: dbFilename,
+ });
+ myBackend.enableTracing = false;
+ if (process.env.TALER_WALLET_DBSTATS) {
+ myBackend.trackStats = true;
+ }
+ const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
+ return {
+ getStats() {
+ return myBackend.accessStats;
+ },
+ idbFactory: myBridgeIdbFactory,
+ };
+}
+
+/**
+ * Get a wallet instance with default settings for node.
+ *
+ * Extended version that allows getting DB stats.
+ */
+export async function createNativeWalletHost2(
+ args: DefaultNodeWalletArgs = {},
+): Promise<{
+ wallet: Wallet;
+ getDbStats: () => AccessStats;
+}> {
+ const myHttpFactory = (config: WalletRunConfig) => {
+ let myHttpLib;
+ if (args.httpLib) {
+ myHttpLib = args.httpLib;
+ } else {
+ myHttpLib = createPlatformHttpLib({
+ enableThrottling: true,
+ requireTls: !config.features.allowHttp,
+ });
+ }
+ return myHttpLib;
+ };
+
+ let dbResp: MakeDbResult;
+
+ if (
+ args.persistentStoragePath &&
+ args.persistentStoragePath.endsWith(".json")
+ ) {
+ logger.info("using JSON file DB backend (slow, only use for testing)");
+ dbResp = await makeFileDb(args);
+ } else {
+ logger.info(`using sqlite3 DB backend`);
+ dbResp = await makeSqliteDb(args);
+ }
+
+ const myIdbFactory: IDBFactory = dbResp.idbFactory as any as IDBFactory;
+
+ shimIndexedDB(dbResp.idbFactory);
+
+ let workerFactory;
+ const cryptoWorkerType = args.cryptoWorkerType ?? "node-worker-thread";
+ if (cryptoWorkerType === "sync") {
+ logger.info("using synchronous crypto worker");
+ workerFactory = new SynchronousCryptoWorkerFactoryPlain();
+ } else if (cryptoWorkerType === "node-worker-thread") {
+ try {
+ // Try if we have worker threads available, fails in older node versions.
+ const _r = "require";
+ // eslint-disable-next-line no-unused-vars
+ const worker_threads = module[_r]("worker_threads");
+ // require("worker_threads");
+ workerFactory = new NodeThreadCryptoWorkerFactory();
+ logger.info("using node thread crypto worker");
+ } catch (e) {
+ logger.warn(
+ "worker threads not available, falling back to synchronous workers",
+ );
+ workerFactory = new SynchronousCryptoWorkerFactoryPlain();
+ }
+ } else {
+ throw Error(`unsupported crypto worker type '${cryptoWorkerType}'`);
+ }
+
+ const timer = new SetTimeoutTimerAPI();
+
+ const w = await Wallet.create(
+ myIdbFactory,
+ myHttpFactory,
+ timer,
+ workerFactory,
+ );
+
+ if (args.notifyHandler) {
+ w.addNotificationListener(args.notifyHandler);
+ }
+ return {
+ wallet: w,
+ getDbStats: dbResp.getStats,
+ };
+}