diff options
Diffstat (limited to 'packages/taler-wallet-core/src/headless')
-rw-r--r-- | packages/taler-wallet-core/src/headless/NodeHttpLib.ts | 183 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/headless/helpers.ts | 199 |
2 files changed, 0 insertions, 382 deletions
diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts deleted file mode 100644 index c1d42796d..000000000 --- a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* - 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/> - - SPDX-License-Identifier: AGPL3.0-or-later -*/ - -/** - * Imports. - */ -import { - DEFAULT_REQUEST_TIMEOUT_MS, - Headers, - HttpRequestLibrary, - HttpRequestOptions, - HttpResponse, -} from "../util/http.js"; -import { RequestThrottler } from "@gnu-taler/taler-util"; -import axios, { AxiosResponse } from "axios"; -import { TalerError } from "../errors.js"; -import { Logger, bytesToString } from "@gnu-taler/taler-util"; -import { TalerErrorCode, URL } from "@gnu-taler/taler-util"; - -const logger = new Logger("NodeHttpLib.ts"); - -/** - * Implementation of the HTTP request library interface for node. - */ -export class NodeHttpLib implements HttpRequestLibrary { - private throttle = new RequestThrottler(); - private throttlingEnabled = true; - - /** - * Set whether requests should be throttled. - */ - setThrottling(enabled: boolean): void { - this.throttlingEnabled = enabled; - } - - async fetch(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> { - const method = opt?.method ?? "GET"; - let body = opt?.body; - - logger.trace(`Requesting ${method} ${url}`); - - const parsedUrl = new URL(url); - if (this.throttlingEnabled && this.throttle.applyThrottle(url)) { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, - { - requestMethod: method, - requestUrl: url, - throttleStats: this.throttle.getThrottleStats(url), - }, - `request to origin ${parsedUrl.origin} was throttled`, - ); - } - let timeoutMs: number | undefined; - if (typeof opt?.timeout?.d_ms === "number") { - timeoutMs = opt.timeout.d_ms; - } else { - timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS; - } - // FIXME: Use AbortController / etc. to handle cancellation - let resp: AxiosResponse; - try { - let respPromise = axios.default({ - method, - url: url, - responseType: "arraybuffer", - headers: opt?.headers, - validateStatus: () => true, - transformResponse: (x) => x, - data: body, - timeout: timeoutMs, - maxRedirects: 0, - }); - if (opt?.cancellationToken) { - respPromise = opt.cancellationToken.racePromise(respPromise); - } - resp = await respPromise; - } catch (e: any) { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_NETWORK_ERROR, - { - requestUrl: url, - requestMethod: method, - }, - `${e.message}`, - ); - } - - const makeText = async (): Promise<string> => { - opt?.cancellationToken?.throwIfCancelled(); - const respText = new Uint8Array(resp.data); - return bytesToString(respText); - }; - - const makeJson = async (): Promise<any> => { - opt?.cancellationToken?.throwIfCancelled(); - let responseJson; - const respText = await makeText(); - try { - responseJson = JSON.parse(respText); - } catch (e) { - logger.trace(`invalid json: '${resp.data}'`); - throw TalerError.fromDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - httpStatusCode: resp.status, - requestUrl: url, - requestMethod: method, - }, - "Could not parse response body as JSON", - ); - } - if (responseJson === null || typeof responseJson !== "object") { - logger.trace(`invalid json (not an object): '${respText}'`); - throw TalerError.fromDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - httpStatusCode: resp.status, - requestUrl: url, - requestMethod: method, - }, - `invalid JSON`, - ); - } - return responseJson; - }; - const makeBytes = async () => { - opt?.cancellationToken?.throwIfCancelled(); - if (typeof resp.data.byteLength !== "number") { - throw Error("expected array buffer"); - } - const buf = resp.data; - return buf; - }; - const headers = new Headers(); - for (const hn of Object.keys(resp.headers)) { - headers.set(hn, resp.headers[hn]); - } - return { - requestUrl: url, - requestMethod: method, - headers, - status: resp.status, - text: makeText, - json: makeJson, - bytes: makeBytes, - }; - } - - async get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> { - return this.fetch(url, { - method: "GET", - ...opt, - }); - } - - async postJson( - url: string, - body: any, - opt?: HttpRequestOptions, - ): Promise<HttpResponse> { - return this.fetch(url, { - method: "POST", - body, - ...opt, - }); - } -} diff --git a/packages/taler-wallet-core/src/headless/helpers.ts b/packages/taler-wallet-core/src/headless/helpers.ts deleted file mode 100644 index fbeb84c67..000000000 --- a/packages/taler-wallet-core/src/headless/helpers.ts +++ /dev/null @@ -1,199 +0,0 @@ -/* - 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 { - BridgeIDBFactory, - MemoryBackend, - shimIndexedDB, -} from "@gnu-taler/idb-bridge"; -import { AccessStats } from "@gnu-taler/idb-bridge"; -import { Logger, WalletNotification } from "@gnu-taler/taler-util"; -import * as fs from "fs"; -import { NodeThreadCryptoWorkerFactory } from "../crypto/workers/nodeThreadWorker.js"; -import { SynchronousCryptoWorkerFactoryNode } from "../crypto/workers/synchronousWorkerFactoryNode.js"; -import { openTalerDatabase } from "../index.js"; -import { HttpRequestLibrary } from "../util/http.js"; -import { SetTimeoutTimerAPI } from "../util/timer.js"; -import { Wallet } from "../wallet.js"; -import { NodeHttpLib } from "./NodeHttpLib.js"; - -const logger = new Logger("headless/helpers.ts"); - -export interface DefaultNodeWalletArgs { - /** - * Location of the wallet database. - * - * If not specified, the wallet starts out with an empty database and - * the wallet database is stored only in memory. - */ - persistentStoragePath?: string; - - /** - * Handler for asynchronous notifications from the wallet. - */ - notifyHandler?: (n: WalletNotification) => void; - - /** - * If specified, use this as HTTP request library instead - * of the default one. - */ - httpLib?: HttpRequestLibrary; - - cryptoWorkerType?: "sync" | "node-worker-thread"; -} - -/** - * Generate a random alphanumeric ID. Does *not* use cryptographically - * secure randomness. - */ -function makeId(length: number): string { - let result = ""; - const characters = "abcdefghijklmnopqrstuvwxyz0123456789"; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * characters.length)); - } - return result; -} - -/** - * Get a wallet instance with default settings for node. - */ -export async function getDefaultNodeWallet( - args: DefaultNodeWalletArgs = {}, -): Promise<Wallet> { - const res = await getDefaultNodeWallet2(args); - return res.wallet; -} - -/** - * Get a wallet instance with default settings for node. - * - * Extended version that allows getting DB stats. - */ -export async function getDefaultNodeWallet2( - args: DefaultNodeWalletArgs = {}, -): Promise<{ - wallet: Wallet; - getDbStats: () => AccessStats; -}> { - BridgeIDBFactory.enableTracing = false; - 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 e; - } - } - - myBackend.afterCommitCallback = async () => { - logger.trace("committing database"); - // Allow caller to stop persisting the wallet. - if (args.persistentStoragePath === undefined) { - return; - } - const tmpPath = `${args.persistentStoragePath}-${makeId(5)}.tmp`; - 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); - const myIdbFactory: IDBFactory = myBridgeIdbFactory as any as IDBFactory; - - let myHttpLib; - if (args.httpLib) { - myHttpLib = args.httpLib; - } else { - myHttpLib = new NodeHttpLib(); - } - - const myVersionChange = (): Promise<void> => { - logger.error("version change requested, should not happen"); - throw Error( - "BUG: wallet DB version change event can't happen with memory IDB", - ); - }; - - shimIndexedDB(myBridgeIdbFactory); - - const myDb = await openTalerDatabase(myIdbFactory, myVersionChange); - - let workerFactory; - const cryptoWorkerType = args.cryptoWorkerType ?? "node-worker-thread"; - if (cryptoWorkerType === "sync") { - logger.info("using synchronous crypto worker"); - workerFactory = new SynchronousCryptoWorkerFactoryNode(); - } 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 SynchronousCryptoWorkerFactoryNode(); - } - } else { - throw Error(`unsupported crypto worker type '${cryptoWorkerType}'`); - } - - const timer = new SetTimeoutTimerAPI(); - - const w = await Wallet.create(myDb, myHttpLib, timer, workerFactory); - - if (args.notifyHandler) { - w.addNotificationListener(args.notifyHandler); - } - return { - wallet: w, - getDbStats: () => myBackend.accessStats, - }; -} |