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