diff options
Diffstat (limited to 'packages/taler-wallet-core/src/host-impl.node.ts')
-rw-r--r-- | packages/taler-wallet-core/src/host-impl.node.ts | 212 |
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, + }; +} |