summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/host-impl.qtart.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/host-impl.qtart.ts')
-rw-r--r--packages/taler-wallet-core/src/host-impl.qtart.ts219
1 files changed, 219 insertions, 0 deletions
diff --git a/packages/taler-wallet-core/src/host-impl.qtart.ts b/packages/taler-wallet-core/src/host-impl.qtart.ts
new file mode 100644
index 000000000..9c985d0c1
--- /dev/null
+++ b/packages/taler-wallet-core/src/host-impl.qtart.ts
@@ -0,0 +1,219 @@
+/*
+ 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,
+ ResultRow,
+ Sqlite3Interface,
+ Sqlite3Statement,
+} from "@gnu-taler/idb-bridge";
+// eslint-disable-next-line no-duplicate-imports
+import {
+ AccessStats,
+ BridgeIDBFactory,
+ MemoryBackend,
+ createSqliteBackend,
+ shimIndexedDB,
+} from "@gnu-taler/idb-bridge";
+import {
+ Logger,
+ SetTimeoutTimerAPI,
+ WalletRunConfig,
+} from "@gnu-taler/taler-util";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
+import { qjsOs, qjsStd } from "@gnu-taler/taler-util/qtart";
+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.qtart.ts");
+
+interface MakeDbResult {
+ idbFactory: BridgeIDBFactory;
+ getStats: () => AccessStats;
+}
+
+let numStmt = 0;
+
+export async function createQtartSqlite3Impl(): Promise<Sqlite3Interface> {
+ const tart: any = (globalThis as any)._tart;
+ if (!tart) {
+ throw Error("globalThis._qtart not defined");
+ }
+ return {
+ open(filename: string) {
+ const internalDbHandle = tart.sqlite3Open(filename);
+ return {
+ internalDbHandle,
+ close() {
+ tart.sqlite3Close(internalDbHandle);
+ },
+ prepare(stmtStr): Sqlite3Statement {
+ const stmtHandle = tart.sqlite3Prepare(internalDbHandle, stmtStr);
+ return {
+ internalStatement: stmtHandle,
+ getAll(params): ResultRow[] {
+ numStmt++;
+ return tart.sqlite3StmtGetAll(stmtHandle, params);
+ },
+ getFirst(params): ResultRow | undefined {
+ numStmt++;
+ return tart.sqlite3StmtGetFirst(stmtHandle, params);
+ },
+ run(params) {
+ numStmt++;
+ return tart.sqlite3StmtRun(stmtHandle, params);
+ },
+ };
+ },
+ exec(sqlStr): void {
+ numStmt++;
+ tart.sqlite3Exec(internalDbHandle, sqlStr);
+ },
+ };
+ },
+ };
+}
+
+async function makeSqliteDb(
+ args: DefaultNodeWalletArgs,
+): Promise<MakeDbResult> {
+ BridgeIDBFactory.enableTracing = false;
+ const imp = await createQtartSqlite3Impl();
+ const myBackend = await createSqliteBackend(imp, {
+ filename: args.persistentStoragePath ?? ":memory:",
+ });
+ myBackend.trackStats = true;
+ myBackend.enableTracing = false;
+ const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
+ return {
+ getStats() {
+ return {
+ ...myBackend.accessStats,
+ primitiveStatements: numStmt,
+ };
+ },
+ idbFactory: myBridgeIdbFactory,
+ };
+}
+
+async function makeFileDb(
+ args: DefaultNodeWalletArgs = {},
+): Promise<MakeDbResult> {
+ BridgeIDBFactory.enableTracing = false;
+ const myBackend = new MemoryBackend();
+ myBackend.enableTracing = false;
+
+ const storagePath = args.persistentStoragePath;
+ if (storagePath) {
+ const dbContentStr = qjsStd.loadFile(storagePath);
+ if (dbContentStr != null) {
+ const dbContent = JSON.parse(dbContentStr);
+ myBackend.importDump(dbContent);
+ }
+
+ 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`;
+ const dbContent = myBackend.exportDump();
+ logger.trace("exported DB dump");
+ qjsStd.writeFile(tmpPath, JSON.stringify(dbContent, undefined, 2));
+ // Atomically move the temporary file onto the DB path.
+ const res = qjsOs.rename(tmpPath, args.persistentStoragePath);
+ if (res != 0) {
+ throw Error("db commit failed at rename");
+ }
+ logger.trace("committing database done");
+ };
+ }
+
+ const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
+ return {
+ idbFactory: myBridgeIdbFactory,
+ getStats: () => myBackend.accessStats,
+ };
+}
+
+export async function createNativeWalletHost2(
+ args: DefaultNodeWalletArgs = {},
+): Promise<{
+ wallet: Wallet;
+ getDbStats: () => AccessStats;
+}> {
+ BridgeIDBFactory.enableTracing = false;
+
+ 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);
+
+ const myHttpFactory = (config: WalletRunConfig) => {
+ let myHttpLib;
+ if (args.httpLib) {
+ myHttpLib = args.httpLib;
+ } else {
+ myHttpLib = createPlatformHttpLib({
+ enableThrottling: true,
+ requireTls: !config.features.allowHttp,
+ });
+ }
+ return myHttpLib;
+ };
+
+ let workerFactory;
+ workerFactory = new SynchronousCryptoWorkerFactoryPlain();
+
+ 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,
+ };
+}