From 825d2c4352022e7397854b2bd9ba7d3589873c07 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 15 Feb 2023 23:32:42 +0100 Subject: make wallet-cli runnable under qtart --- packages/taler-wallet-core/package.json | 8 +- packages/taler-wallet-core/src/bank-api-client.ts | 7 +- .../src/crypto/workers/crypto-dispatcher.test.ts | 2 - .../src/crypto/workers/crypto-dispatcher.ts | 2 +- .../src/crypto/workers/rpcClient.ts | 92 ------ .../crypto/workers/synchronousWorkerFactoryNode.ts | 36 --- .../src/crypto/workers/synchronousWorkerNode.ts | 174 ---------- .../src/crypto/workers/worker-common.ts | 2 +- packages/taler-wallet-core/src/db.ts | 3 + packages/taler-wallet-core/src/dbless.ts | 2 +- packages/taler-wallet-core/src/dev-experiments.ts | 2 +- packages/taler-wallet-core/src/errors.ts | 248 --------------- .../taler-wallet-core/src/headless/NodeHttpLib.ts | 183 ----------- packages/taler-wallet-core/src/headless/helpers.ts | 199 ------------ packages/taler-wallet-core/src/host-common.ts | 60 ++++ packages/taler-wallet-core/src/host-impl.node.ts | 159 +++++++++ packages/taler-wallet-core/src/host-impl.qtart.ts | 120 +++++++ packages/taler-wallet-core/src/host.ts | 47 +++ packages/taler-wallet-core/src/index.node.ts | 11 +- packages/taler-wallet-core/src/index.ts | 6 +- .../taler-wallet-core/src/internal-wallet-state.ts | 13 +- .../src/operations/backup/index.ts | 4 +- .../taler-wallet-core/src/operations/common.ts | 2 +- .../taler-wallet-core/src/operations/deposits.ts | 6 +- .../taler-wallet-core/src/operations/exchanges.ts | 9 +- .../taler-wallet-core/src/operations/merchants.ts | 2 +- .../src/operations/pay-merchant.ts | 7 +- .../taler-wallet-core/src/operations/pay-peer.ts | 5 +- .../taler-wallet-core/src/operations/recoup.ts | 2 +- .../taler-wallet-core/src/operations/refresh.ts | 4 +- .../taler-wallet-core/src/operations/testing.ts | 2 +- packages/taler-wallet-core/src/operations/tip.ts | 4 +- .../taler-wallet-core/src/operations/withdraw.ts | 4 +- packages/taler-wallet-core/src/remote.ts | 2 +- packages/taler-wallet-core/src/util/http.ts | 354 --------------------- packages/taler-wallet-core/src/util/retries.ts | 2 +- packages/taler-wallet-core/src/wallet.ts | 4 +- 37 files changed, 435 insertions(+), 1354 deletions(-) delete mode 100644 packages/taler-wallet-core/src/crypto/workers/rpcClient.ts delete mode 100644 packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactoryNode.ts delete mode 100644 packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts delete mode 100644 packages/taler-wallet-core/src/errors.ts delete mode 100644 packages/taler-wallet-core/src/headless/NodeHttpLib.ts delete mode 100644 packages/taler-wallet-core/src/headless/helpers.ts create mode 100644 packages/taler-wallet-core/src/host-common.ts create mode 100644 packages/taler-wallet-core/src/host-impl.node.ts create mode 100644 packages/taler-wallet-core/src/host-impl.qtart.ts create mode 100644 packages/taler-wallet-core/src/host.ts delete mode 100644 packages/taler-wallet-core/src/util/http.ts (limited to 'packages/taler-wallet-core') diff --git a/packages/taler-wallet-core/package.json b/packages/taler-wallet-core/package.json index 4f1692872..72b4eb410 100644 --- a/packages/taler-wallet-core/package.json +++ b/packages/taler-wallet-core/package.json @@ -38,7 +38,13 @@ "default": "./lib/index.js" }, "./remote": { - "node": "./lib/remote.js" + "default": "./lib/remote.js" + } + }, + "imports": { + "#host-impl": { + "node": "./lib/host-impl.node.js", + "qtart": "./lib/host-impl.qtart.js" } }, "devDependencies": { diff --git a/packages/taler-wallet-core/src/bank-api-client.ts b/packages/taler-wallet-core/src/bank-api-client.ts index dc7845150..addec709f 100644 --- a/packages/taler-wallet-core/src/bank-api-client.ts +++ b/packages/taler-wallet-core/src/bank-api-client.ts @@ -33,13 +33,10 @@ import { j2s, Logger, stringToBytes, + TalerError, TalerErrorCode, } from "@gnu-taler/taler-util"; -import { TalerError } from "./errors.js"; -import { - HttpRequestLibrary, - readSuccessResponseJsonOrThrow, -} from "./util/http.js"; +import { HttpRequestLibrary, readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; const logger = new Logger("bank-api-client.ts"); diff --git a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.test.ts b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.test.ts index d8d53a839..1e9d82f66 100644 --- a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.test.ts +++ b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.test.ts @@ -21,8 +21,6 @@ import { CryptoWorker, CryptoWorkerResponseMessage, } from "./cryptoWorkerInterface.js"; -import { SynchronousCryptoWorkerFactoryNode } from "./synchronousWorkerFactoryNode.js"; -import { processRequestWithImpl } from "./worker-common.js"; export class MyCryptoWorker implements CryptoWorker { /** diff --git a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts index f086691e5..192e9cda1 100644 --- a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts +++ b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts @@ -24,7 +24,7 @@ * Imports. */ import { j2s, Logger, TalerErrorCode } from "@gnu-taler/taler-util"; -import { TalerError } from "../../errors.js"; +import { TalerError } from "@gnu-taler/taler-util"; import { openPromise } from "../../util/promiseUtils.js"; import { timer, performanceNow, TimerHandle } from "../../util/timer.js"; import { nullCrypto, TalerCryptoInterface } from "../cryptoImplementation.js"; diff --git a/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts b/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts deleted file mode 100644 index 21d88fffa..000000000 --- a/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 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 - */ - -/** - * Imports. - */ -import { Logger } from "@gnu-taler/taler-util"; -import child_process from "child_process"; -import type internal from "stream"; -import { OpenedPromise, openPromise } from "../../util/promiseUtils.js"; - -const logger = new Logger("synchronousWorkerFactory.ts"); - -/** - * Client for the crypto helper process (taler-crypto-worker from exchange.git). - */ -export class CryptoRpcClient { - proc: child_process.ChildProcessByStdio< - internal.Writable, - internal.Readable, - null - >; - requests: Array<{ - p: OpenedPromise; - req: any; - }> = []; - - constructor() { - const stdoutChunks: Buffer[] = []; - this.proc = child_process.spawn("taler-crypto-worker", { - //stdio: ["pipe", "pipe", "inherit"], - stdio: ["pipe", "pipe", "inherit"], - detached: true, - }); - this.proc.on("close", (): void => { - logger.error("child process exited"); - }); - (this.proc.stdout as any).unref(); - (this.proc.stdin as any).unref(); - this.proc.unref(); - - this.proc.stdout.on("data", (x) => { - if (x instanceof Buffer) { - const nlIndex = x.indexOf("\n"); - if (nlIndex >= 0) { - const before = x.slice(0, nlIndex); - const after = x.slice(nlIndex + 1); - stdoutChunks.push(after); - const str = Buffer.concat([...stdoutChunks, before]).toString( - "utf-8", - ); - const req = this.requests.shift(); - if (!req) { - throw Error("request was undefined"); - } - if (this.requests.length === 0) { - this.proc.unref(); - } - //logger.info(`got response: ${str}`); - req.p.resolve(JSON.parse(str)); - } else { - stdoutChunks.push(x); - } - } else { - throw Error(`unexpected data chunk type (${typeof x})`); - } - }); - } - - async queueRequest(req: any): Promise { - const p = openPromise(); - if (this.requests.length === 0) { - this.proc.ref(); - } - this.requests.push({ req, p }); - this.proc.stdin.write(`${JSON.stringify(req)}\n`); - return p.promise; - } -} diff --git a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactoryNode.ts b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactoryNode.ts deleted file mode 100644 index 90f9a43fa..000000000 --- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactoryNode.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - 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 - */ - -/** - * Imports. - */ -import { CryptoWorkerFactory } from "./crypto-dispatcher.js"; -import { CryptoWorker } from "./cryptoWorkerInterface.js"; -import { SynchronousCryptoWorkerNode } from "./synchronousWorkerNode.js"; - -/** - * The synchronous crypto worker produced by this factory doesn't run in the - * background, but actually blocks the caller until the operation is done. - */ -export class SynchronousCryptoWorkerFactoryNode implements CryptoWorkerFactory { - startWorker(): CryptoWorker { - return new SynchronousCryptoWorkerNode(); - } - - getConcurrency(): number { - return 1; - } -} diff --git a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts deleted file mode 100644 index b2653158c..000000000 --- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts +++ /dev/null @@ -1,174 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - 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 - */ - -import { j2s, Logger } from "@gnu-taler/taler-util"; -import { - nativeCryptoR, - TalerCryptoInterfaceR, -} from "../cryptoImplementation.js"; -import { CryptoWorker } from "./cryptoWorkerInterface.js"; -import { CryptoRpcClient } from "./rpcClient.js"; -import { processRequestWithImpl } from "./worker-common.js"; - -const logger = new Logger("synchronousWorker.ts"); - -/** - * Worker implementation that uses node subprocesses. - * - * The node crypto worker can also use IPC to offload cryptographic - * operations to a helper process (usually written in C / part of taler-exchange). - */ -export class SynchronousCryptoWorkerNode implements CryptoWorker { - /** - * Function to be called when we receive a message from the worker thread. - */ - onmessage: undefined | ((m: any) => void); - - /** - * Function to be called when we receive an error from the worker thread. - */ - onerror: undefined | ((m: any) => void); - - cryptoImplR: TalerCryptoInterfaceR; - - rpcClient: CryptoRpcClient | undefined; - - constructor() { - this.onerror = undefined; - this.onmessage = undefined; - - this.cryptoImplR = { ...nativeCryptoR }; - - if (process.env["TALER_WALLET_PRIMITIVE_WORKER"]) { - logger.info("using RPC for some crypto operations"); - const rpc = (this.rpcClient = new CryptoRpcClient()); - this.cryptoImplR.eddsaSign = async (_, req) => { - return await rpc.queueRequest({ - op: "eddsa_sign", - args: { - msg: req.msg, - priv: req.priv, - }, - }); - }; - this.cryptoImplR.setupRefreshPlanchet = async (_, req) => { - const res = await rpc.queueRequest({ - op: "setup_refresh_planchet", - args: { - coin_index: req.coinNumber, - transfer_secret: req.transferSecret, - }, - }); - return { - bks: res.blinding_key, - coinPriv: res.coin_priv, - coinPub: res.coin_pub, - }; - }; - this.cryptoImplR.rsaBlind = async (_, req) => { - const res = await rpc.queueRequest({ - op: "rsa_blind", - args: { - bks: req.bks, - hm: req.hm, - pub: req.pub, - }, - }); - return { - blinded: res.blinded, - }; - }; - this.cryptoImplR.keyExchangeEcdheEddsa = async (_, req) => { - const res = await rpc.queueRequest({ - op: "kx_ecdhe_eddsa", - args: { - ecdhe_priv: req.ecdhePriv, - eddsa_pub: req.eddsaPub, - }, - }); - return { - h: res.h, - }; - }; - this.cryptoImplR.eddsaGetPublic = async (_, req) => { - const res = await rpc.queueRequest({ - op: "eddsa_get_public", - args: { - eddsa_priv: req.priv, - }, - }); - return { - pub: res.eddsa_pub, - }; - }; - this.cryptoImplR.ecdheGetPublic = async (_, req) => { - const res = await rpc.queueRequest({ - op: "ecdhe_get_public", - args: { - ecdhe_priv: req.priv, - }, - }); - return { - pub: res.ecdhe_pub, - }; - }; - } - } - - /** - * Add an event listener for either an "error" or "message" event. - */ - addEventListener(event: "message" | "error", fn: (x: any) => void): void { - switch (event) { - case "message": - this.onmessage = fn; - break; - case "error": - this.onerror = fn; - break; - } - } - - private dispatchMessage(msg: any): void { - if (this.onmessage) { - this.onmessage(msg); - } - } - - /** - * Send a message to the worker thread. - */ - postMessage(msg: any): void { - const handleRequest = async () => { - const responseMsg = await processRequestWithImpl(msg, this.cryptoImplR); - try { - setTimeout(() => this.dispatchMessage(responseMsg), 0); - } catch (e) { - logger.error("got error during dispatch", e); - } - }; - handleRequest().catch((e) => { - logger.error("Error while handling crypto request:", e); - }); - } - - /** - * Forcibly terminate the worker thread. - */ - terminate(): void { - // This is a no-op. - } -} diff --git a/packages/taler-wallet-core/src/crypto/workers/worker-common.ts b/packages/taler-wallet-core/src/crypto/workers/worker-common.ts index 8a74a5231..9f23cf685 100644 --- a/packages/taler-wallet-core/src/crypto/workers/worker-common.ts +++ b/packages/taler-wallet-core/src/crypto/workers/worker-common.ts @@ -23,7 +23,7 @@ import { stringifyError as safeStringifyError, TalerErrorCode, } from "@gnu-taler/taler-util"; -import { getErrorDetailFromException, makeErrorDetail } from "../../errors.js"; +import { getErrorDetailFromException, makeErrorDetail } from "@gnu-taler/taler-util"; import { TalerCryptoInterfaceR } from "../cryptoImplementation.js"; import { CryptoWorkerRequestMessage, diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 8bb8d519f..75e6408f7 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -2659,6 +2659,9 @@ function onMetaDbUpgradeNeeded( /** * Return a promise that resolves * to the taler wallet db. + * + * @param onVersionChange Called when another client concurrenctly connects to the database + * with a higher version. */ export async function openTalerDatabase( idbFactory: IDBFactory, diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 544e2d458..99596edd8 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -58,7 +58,7 @@ import { import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, -} from "./util/http.js"; +} from "@gnu-taler/taler-util/http"; import { getBankStatusUrl, getBankWithdrawalInfo, diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts index 6c36d6f6c..3e6194ccd 100644 --- a/packages/taler-wallet-core/src/dev-experiments.ts +++ b/packages/taler-wallet-core/src/dev-experiments.ts @@ -32,7 +32,7 @@ import { HttpRequestLibrary, HttpRequestOptions, HttpResponse, -} from "./util/http.js"; +} from "@gnu-taler/taler-util/http"; const logger = new Logger("dev-experiments.ts"); diff --git a/packages/taler-wallet-core/src/errors.ts b/packages/taler-wallet-core/src/errors.ts deleted file mode 100644 index 038bdbc7c..000000000 --- a/packages/taler-wallet-core/src/errors.ts +++ /dev/null @@ -1,248 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019-2020 Taler Systems SA - - 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 - */ - -/** - * Classes and helpers for error handling specific to wallet operations. - * - * @author Florian Dold - */ - -/** - * Imports. - */ -import { - AbsoluteTime, - PayMerchantInsufficientBalanceDetails, - PayPeerInsufficientBalanceDetails, - TalerErrorCode, - TalerErrorDetail, - TransactionType, -} from "@gnu-taler/taler-util"; - -type empty = Record; - -export interface DetailsMap { - [TalerErrorCode.WALLET_PENDING_OPERATION_FAILED]: { - innerError: TalerErrorDetail; - transactionId?: string; - }; - [TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT]: { - exchangeBaseUrl: string; - }; - [TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE]: { - exchangeProtocolVersion: string; - walletProtocolVersion: string; - }; - [TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK]: empty; - [TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID]: empty; - [TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED]: { - orderId: string; - claimUrl: string; - }; - [TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED]: empty; - [TalerErrorCode.WALLET_CONTRACT_TERMS_SIGNATURE_INVALID]: { - merchantPub: string; - orderId: string; - }; - [TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH]: { - baseUrlForDownload: string; - baseUrlFromContractTerms: string; - }; - [TalerErrorCode.WALLET_INVALID_TALER_PAY_URI]: { - talerPayUri: string; - }; - [TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR]: { - requestUrl: string; - requestMethod: string; - httpStatusCode: number; - errorResponse?: any; - }; - [TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION]: { - stack?: string; - }; - [TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE]: { - exchangeProtocolVersion: string; - walletProtocolVersion: string; - }; - [TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN]: { - operation: string; - }; - [TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED]: { - requestUrl: string; - requestMethod: string; - throttleStats: Record; - }; - [TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT]: empty; - [TalerErrorCode.WALLET_NETWORK_ERROR]: { - requestUrl: string; - requestMethod: string; - }; - [TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE]: { - requestUrl: string; - requestMethod: string; - httpStatusCode: number; - validationError?: string; - }; - [TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID]: empty; - [TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE]: { - errorsPerCoin: Record; - }; - [TalerErrorCode.WALLET_CORE_NOT_AVAILABLE]: empty; - [TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR]: { - httpStatusCode: number; - }; - [TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR]: { - requestError: TalerErrorDetail; - }; - [TalerErrorCode.WALLET_CRYPTO_WORKER_ERROR]: { - innerError: TalerErrorDetail; - }; - [TalerErrorCode.WALLET_CRYPTO_WORKER_BAD_REQUEST]: { - detail: string; - }; - [TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED]: { - kycUrl: string; - }; - [TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE]: { - insufficientBalanceDetails: PayMerchantInsufficientBalanceDetails; - }; - [TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE]: { - insufficientBalanceDetails: PayPeerInsufficientBalanceDetails; - }; -} - -type ErrBody = Y extends keyof DetailsMap ? DetailsMap[Y] : empty; - -export function makeErrorDetail( - code: C, - detail: ErrBody, - hint?: string, -): TalerErrorDetail { - if (!hint && !(detail as any).hint) { - hint = getDefaultHint(code); - } - const when = AbsoluteTime.now(); - return { code, when, hint, ...detail }; -} - -export function makePendingOperationFailedError( - innerError: TalerErrorDetail, - tag: TransactionType, - uid: string, -): TalerError { - return TalerError.fromDetail(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED, { - innerError, - transactionId: `${tag}:${uid}`, - }); -} - -export function summarizeTalerErrorDetail(ed: TalerErrorDetail): string { - const errName = TalerErrorCode[ed.code] ?? ""; - return `Error (${ed.code}/${errName})`; -} - -function getDefaultHint(code: number): string { - const errName = TalerErrorCode[code]; - if (errName) { - return `Error (${errName})`; - } else { - return `Error ()`; - } -} - -export class TalerProtocolViolationError extends Error { - constructor(hint?: string) { - let msg: string; - if (hint) { - msg = `Taler protocol violation error (${hint})`; - } else { - msg = `Taler protocol violation error`; - } - super(msg); - Object.setPrototypeOf(this, TalerProtocolViolationError.prototype); - } -} - -export class TalerError extends Error { - errorDetail: TalerErrorDetail & T; - private constructor(d: TalerErrorDetail & T) { - super(d.hint ?? `Error (code ${d.code})`); - this.errorDetail = d; - Object.setPrototypeOf(this, TalerError.prototype); - } - - static fromDetail( - code: C, - detail: ErrBody, - hint?: string, - ): TalerError { - if (!hint) { - hint = getDefaultHint(code); - } - const when = AbsoluteTime.now(); - return new TalerError({ code, when, hint, ...detail }); - } - - static fromUncheckedDetail(d: TalerErrorDetail): TalerError { - return new TalerError({ ...d }); - } - - static fromException(e: any): TalerError { - const errDetail = getErrorDetailFromException(e); - return new TalerError(errDetail); - } - - hasErrorCode( - code: C, - ): this is TalerError { - return this.errorDetail.code === code; - } -} - -/** - * Convert an exception (or anything that was thrown) into - * a TalerErrorDetail object. - */ -export function getErrorDetailFromException(e: any): TalerErrorDetail { - if (e instanceof TalerError) { - return e.errorDetail; - } - if (e instanceof Error) { - const err = makeErrorDetail( - TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - { - stack: e.stack, - }, - `unexpected exception (message: ${e.message})`, - ); - return err; - } - // Something was thrown that is not even an exception! - // Try to stringify it. - let excString: string; - try { - excString = e.toString(); - } catch (e) { - // Something went horribly wrong. - excString = "can't stringify exception"; - } - const err = makeErrorDetail( - TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - {}, - `unexpected exception (not an exception, ${excString})`, - ); - return err; -} 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 - - 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 { - 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 => { - opt?.cancellationToken?.throwIfCancelled(); - const respText = new Uint8Array(resp.data); - return bytesToString(respText); - }; - - const makeJson = async (): Promise => { - 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 { - return this.fetch(url, { - method: "GET", - ...opt, - }); - } - - async postJson( - url: string, - body: any, - opt?: HttpRequestOptions, - ): Promise { - 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 - */ - -/** - * Helpers to create headless wallets. - * @author Florian Dold - */ - -/** - * 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 { - 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 => { - 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, - }; -} diff --git a/packages/taler-wallet-core/src/host-common.ts b/packages/taler-wallet-core/src/host-common.ts new file mode 100644 index 000000000..7651e5a12 --- /dev/null +++ b/packages/taler-wallet-core/src/host-common.ts @@ -0,0 +1,60 @@ +/* + 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 + */ + +import { WalletNotification } from "@gnu-taler/taler-util"; +import { HttpRequestLibrary } from "@gnu-taler/taler-util/http"; + +/** + * Helpers to initiate a wallet in a host environment. + */ + +/** + */ +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. + */ +export function makeTempfileId(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; +} 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..ec57e0ebe --- /dev/null +++ b/packages/taler-wallet-core/src/host-impl.node.ts @@ -0,0 +1,159 @@ +/* + 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 + */ + +/** + * Helpers to create headless wallets. + * @author Florian Dold + */ + +/** + * 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 } from "@gnu-taler/taler-util"; +import * as fs from "fs"; +import { NodeThreadCryptoWorkerFactory } from "./crypto/workers/nodeThreadWorker.js"; +import { SynchronousCryptoWorkerFactoryPlain } from "./crypto/workers/synchronousWorkerFactoryPlain.js"; +import { openTalerDatabase } from "./index.js"; +import { + createPlatformHttpLib, +} from "@gnu-taler/taler-util/http"; +import { SetTimeoutTimerAPI } from "./util/timer.js"; +import { Wallet } from "./wallet.js"; +import { DefaultNodeWalletArgs, makeTempfileId } from "./host-common.js"; + +const logger = new Logger("host-impl.node.ts"); + + +/** + * 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; +}> { + 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}-${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); + const myIdbFactory: IDBFactory = myBridgeIdbFactory as any as IDBFactory; + + let myHttpLib; + if (args.httpLib) { + myHttpLib = args.httpLib; + } else { + myHttpLib = createPlatformHttpLib({ + enableThrottling: true, + }); + } + + const myVersionChange = (): Promise => { + 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 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(myDb, myHttpLib, timer, workerFactory); + + if (args.notifyHandler) { + w.addNotificationListener(args.notifyHandler); + } + return { + wallet: w, + getDbStats: () => myBackend.accessStats, + }; +} 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..337914292 --- /dev/null +++ b/packages/taler-wallet-core/src/host-impl.qtart.ts @@ -0,0 +1,120 @@ +/* + 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 + */ + +/** + * Helpers to create headless wallets. + * @author Florian Dold + */ + +/** + * 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 { SynchronousCryptoWorkerFactoryPlain } from "./crypto/workers/synchronousWorkerFactoryPlain.js"; +import { openTalerDatabase } from "./index.js"; +import { Logger } from "@gnu-taler/taler-util"; +import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; +import { SetTimeoutTimerAPI } from "./util/timer.js"; +import { Wallet } from "./wallet.js"; +import { qjsOs, qjsStd } from "@gnu-taler/taler-util/qtart"; +import { DefaultNodeWalletArgs, makeTempfileId } from "./host-common.js"; + +const logger = new Logger("host-impl.qtart.ts"); + +export async function createNativeWalletHost2( + args: DefaultNodeWalletArgs = {}, +): Promise<{ + wallet: Wallet; + getDbStats: () => AccessStats; +}> { + 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"); + }; + } + + logger.info("done processing storage path"); + + 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 = createPlatformHttpLib(); + } + + const myVersionChange = (): Promise => { + 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; + workerFactory = new SynchronousCryptoWorkerFactoryPlain(); + + 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, + }; +} diff --git a/packages/taler-wallet-core/src/host.ts b/packages/taler-wallet-core/src/host.ts new file mode 100644 index 000000000..4b319f081 --- /dev/null +++ b/packages/taler-wallet-core/src/host.ts @@ -0,0 +1,47 @@ +/* + 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 + */ + +import { DefaultNodeWalletArgs } from "./host-common.js"; +import { Wallet } from "./index.js"; + +import * as hostImpl from "#host-impl"; +import { AccessStats } from "@gnu-taler/idb-bridge"; + +/** + * Helpers to initiate a wallet in a host environment. + */ + +/** + * Get a wallet instance. + */ +export async function createNativeWalletHost2( + args: DefaultNodeWalletArgs = {}, +): Promise<{ + wallet: Wallet; + getDbStats: () => AccessStats; +}> { + return hostImpl.createNativeWalletHost2(args); +} + +/** + * Get a wallet instance. + */ +export async function createNativeWalletHost( + args: DefaultNodeWalletArgs = {}, +): Promise { + const res = await hostImpl.createNativeWalletHost2(args); + return res.wallet; +} diff --git a/packages/taler-wallet-core/src/index.node.ts b/packages/taler-wallet-core/src/index.node.ts index 8567d13ac..13392d39c 100644 --- a/packages/taler-wallet-core/src/index.node.ts +++ b/packages/taler-wallet-core/src/index.node.ts @@ -16,15 +16,8 @@ export * from "./index.js"; -// Utils for using the wallet under node -export { NodeHttpLib } from "./headless/NodeHttpLib.js"; -export { - getDefaultNodeWallet, - getDefaultNodeWallet2, - DefaultNodeWalletArgs, -} from "./headless/helpers.js"; export * from "./crypto/workers/nodeThreadWorker.js"; -export { SynchronousCryptoWorkerNode as SynchronousCryptoWorker } from "./crypto/workers/synchronousWorkerNode.js"; +export { SynchronousCryptoWorkerPlain } from "./crypto/workers/synchronousWorkerPlain.js"; export type { AccessStats } from "@gnu-taler/idb-bridge"; -export * from "./crypto/workers/synchronousWorkerFactoryNode.js"; +export * from "./crypto/workers/synchronousWorkerFactoryPlain.js"; diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index 031656a6c..7b21d8f91 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -18,13 +18,9 @@ * Module entry point for the wallet when used as a node module. */ -// Errors -export * from "./errors.js"; - // Util functionality export * from "./util/promiseUtils.js"; export * from "./util/query.js"; -export * from "./util/http.js"; export * from "./versions.js"; @@ -67,3 +63,5 @@ export * from "./util/timer.js"; export * from "./util/denominations.js"; export { SynchronousCryptoWorkerFactoryPlain } from "./crypto/workers/synchronousWorkerFactoryPlain.js"; +export * from "./host-common.js"; +export * from "./host.js"; diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts index d180861f8..8434c3b8f 100644 --- a/packages/taler-wallet-core/src/internal-wallet-state.ts +++ b/packages/taler-wallet-core/src/internal-wallet-state.ts @@ -30,18 +30,14 @@ * Imports. */ import { - WalletNotification, - BalancesResponse, - AmountJson, - DenominationPubKey, - TalerProtocolTimestamp, CancellationToken, + CoinRefreshRequest, DenominationInfo, RefreshGroupId, - CoinRefreshRequest, RefreshReason, + WalletNotification, } from "@gnu-taler/taler-util"; -import { CryptoDispatcher } from "./crypto/workers/crypto-dispatcher.js"; +import { HttpRequestLibrary } from "@gnu-taler/taler-util/http"; import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { ExchangeDetailsRecord, @@ -49,9 +45,6 @@ import { RefreshReasonDetails, WalletStoresV1, } from "./db.js"; -import { PendingOperationsResponse } from "./pending-types.js"; -import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js"; -import { HttpRequestLibrary } from "./util/http.js"; import { AsyncCondition } from "./util/promiseUtils.js"; import { DbAccess, diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 7d3953ebb..3dae26087 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -85,13 +85,13 @@ import { ConfigRecordKey, WalletBackupConfState, } from "../../db.js"; -import { TalerError } from "../../errors.js"; +import { TalerError } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../../internal-wallet-state.js"; import { assertUnreachable } from "../../util/assertUnreachable.js"; import { readSuccessResponseJsonOrThrow, readTalerErrorResponse, -} from "../../util/http.js"; +} from "@gnu-taler/taler-util/http"; import { checkDbInvariant, checkLogicInvariant, diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index 3ea02012b..e61a6fe95 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -42,7 +42,7 @@ import { ExchangeDetailsRecord, ExchangeRecord, } from "../db.js"; -import { makeErrorDetail, TalerError } from "../errors.js"; +import { makeErrorDetail, TalerError } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; import { GetReadWriteAccess } from "../util/query.js"; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 4ff6a65cd..9d71f020f 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -62,10 +62,10 @@ import { OperationStatus, TransactionStatus, } from "../db.js"; -import { TalerError } from "../errors.js"; -import { checkWithdrawalKycStatus, KycPendingInfo, KycUserType } from "../index.js"; +import { TalerError } from "@gnu-taler/taler-util"; +import { KycPendingInfo, KycUserType } from "../index.js"; import { InternalWalletState } from "../internal-wallet-state.js"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { OperationAttemptResult } from "../util/retries.js"; import { makeTransactionId, spendCoins } from "./common.js"; import { getExchangeDetails } from "./exchanges.js"; diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 67f77de77..2b6a881dd 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -41,6 +41,7 @@ import { NotificationType, parsePaytoUri, Recoup, + TalerError, TalerErrorCode, TalerProtocolDuration, TalerProtocolTimestamp, @@ -49,6 +50,7 @@ import { WireFeeMap, WireInfo, } from "@gnu-taler/taler-util"; +import { HttpRequestLibrary, readSuccessResponseTextOrThrow, readSuccessResponseJsonOrThrow, getExpiry } from "@gnu-taler/taler-util/http"; import { DenominationRecord, DenominationVerificationStatus, @@ -56,14 +58,7 @@ import { ExchangeRecord, WalletStoresV1, } from "../db.js"; -import { TalerError } from "../errors.js"; import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js"; -import { - getExpiry, - HttpRequestLibrary, - readSuccessResponseJsonOrThrow, - readSuccessResponseTextOrThrow, -} from "../util/http.js"; import { checkDbInvariant } from "../util/invariants.js"; import { DbAccess, diff --git a/packages/taler-wallet-core/src/operations/merchants.ts b/packages/taler-wallet-core/src/operations/merchants.ts index eeefc0f79..c47ec4a0a 100644 --- a/packages/taler-wallet-core/src/operations/merchants.ts +++ b/packages/taler-wallet-core/src/operations/merchants.ts @@ -25,7 +25,7 @@ import { LibtoolVersion, } from "@gnu-taler/taler-util"; import { InternalWalletState, MerchantInfo } from "../internal-wallet-state.js"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; const logger = new Logger("taler-wallet-core:merchants.ts"); diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index 2a89c59ed..f84ac2567 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -94,13 +94,12 @@ import { makePendingOperationFailedError, TalerError, TalerProtocolViolationError, -} from "../errors.js"; -import { GetReadWriteAccess } from "../index.browser.js"; +} from "@gnu-taler/taler-util"; +import { GetReadWriteAccess } from "../index.js"; import { EXCHANGE_COINS_LOCK, InternalWalletState, } from "../internal-wallet-state.js"; -import { PendingTaskType } from "../pending-types.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; import { CoinSelectionTally, @@ -114,7 +113,7 @@ import { readTalerErrorResponse, readUnexpectedResponseDetails, throwUnexpectedRequestError, -} from "../util/http.js"; +} from "@gnu-taler/taler-util/http"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; import { OperationAttemptResult, diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts index 7dc7b67fe..022a824de 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer.ts @@ -81,16 +81,15 @@ import { WithdrawalGroupStatus, WithdrawalRecordType, } from "../db.js"; -import { TalerError } from "../errors.js"; +import { TalerError } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; import { makeTransactionId, runOperationWithErrorReporting, spendCoins, } from "../operations/common.js"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { checkDbInvariant } from "../util/invariants.js"; -import { GetReadOnlyAccess } from "../util/query.js"; import { OperationAttemptResult, OperationAttemptResultType, diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 00dd0e1c6..3b423474b 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -49,7 +49,7 @@ import { WithdrawCoinSource, } from "../db.js"; import { InternalWalletState } from "../internal-wallet-state.js"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { checkDbInvariant } from "../util/invariants.js"; import { GetReadWriteAccess } from "../util/query.js"; import { diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 5b7bf8d83..773689635 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -63,7 +63,7 @@ import { RefreshReasonDetails, WalletStoresV1, } from "../db.js"; -import { TalerError } from "../errors.js"; +import { TalerError } from "@gnu-taler/taler-util"; import { EXCHANGE_COINS_LOCK, InternalWalletState, @@ -72,7 +72,7 @@ import { assertUnreachable } from "../util/assertUnreachable.js"; import { readSuccessResponseJsonOrThrow, readUnexpectedResponseDetails, -} from "../util/http.js"; +} from "@gnu-taler/taler-util/http"; import { checkDbInvariant } from "../util/invariants.js"; import { GetReadWriteAccess } from "../util/query.js"; import { diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index 50454a920..873fac021 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -29,7 +29,7 @@ import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, checkSuccessResponseOrThrow, -} from "../util/http.js"; +} from "@gnu-taler/taler-util/http"; import { AmountString, codecForAny, diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 2bf216102..ec7546992 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -45,12 +45,12 @@ import { DenominationRecord, TipRecord, } from "../db.js"; -import { makeErrorDetail } from "../errors.js"; +import { makeErrorDetail } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; import { getHttpResponseErrorDetails, readSuccessResponseJsonOrThrow, -} from "../util/http.js"; +} from "@gnu-taler/taler-util/http"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; import { OperationAttemptResult, diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index bcc8600c7..f6d79b229 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -85,7 +85,7 @@ import { getErrorDetailFromException, makeErrorDetail, TalerError, -} from "../errors.js"; +} from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; import { makeCoinAvailable, @@ -99,7 +99,7 @@ import { readSuccessResponseJsonOrErrorCode, readSuccessResponseJsonOrThrow, throwUnexpectedRequestError, -} from "../util/http.js"; +} from "@gnu-taler/taler-util/http"; import { checkDbInvariant, checkLogicInvariant, diff --git a/packages/taler-wallet-core/src/remote.ts b/packages/taler-wallet-core/src/remote.ts index bc0be9d30..89348698e 100644 --- a/packages/taler-wallet-core/src/remote.ts +++ b/packages/taler-wallet-core/src/remote.ts @@ -19,10 +19,10 @@ import { CoreApiResponse, j2s, Logger, + TalerError, WalletNotification, } from "@gnu-taler/taler-util"; import { connectRpc, JsonMessage } from "@gnu-taler/taler-util/twrpc"; -import { TalerError } from "./errors.js"; import { OpenedPromise, openPromise } from "./index.js"; import { WalletCoreApiClient } from "./wallet-api-types.js"; diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts deleted file mode 100644 index 1da31a315..000000000 --- a/packages/taler-wallet-core/src/util/http.ts +++ /dev/null @@ -1,354 +0,0 @@ -/* - This file is part of TALER - (C) 2016 GNUnet e.V. - - 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. - - 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 - TALER; see the file COPYING. If not, see - */ - -/** - * Helpers for doing XMLHttpRequest-s that are based on ES6 promises. - * Allows for easy mocking for test cases. - * - * The API is inspired by the HTML5 fetch API. - */ - -/** - * Imports - */ -import { - Logger, - Duration, - AbsoluteTime, - TalerErrorDetail, - Codec, - j2s, - CancellationToken, -} from "@gnu-taler/taler-util"; -import { TalerErrorCode } from "@gnu-taler/taler-util"; -import { makeErrorDetail, TalerError } from "../errors.js"; - -const logger = new Logger("http.ts"); - -/** - * An HTTP response that is returned by all request methods of this library. - */ -export interface HttpResponse { - requestUrl: string; - requestMethod: string; - status: number; - headers: Headers; - json(): Promise; - text(): Promise; - bytes(): Promise; -} - -export const DEFAULT_REQUEST_TIMEOUT_MS = 60000; - -export interface HttpRequestOptions { - method?: "POST" | "PUT" | "GET"; - headers?: { [name: string]: string }; - - /** - * Timeout after which the request should be aborted. - */ - timeout?: Duration; - - /** - * Cancellation token that should abort the request when - * cancelled. - */ - cancellationToken?: CancellationToken; - - body?: string | ArrayBuffer | Record; -} - -/** - * Headers, roughly modeled after the fetch API's headers object. - */ -export class Headers { - private headerMap = new Map(); - - get(name: string): string | null { - const r = this.headerMap.get(name.toLowerCase()); - if (r) { - return r; - } - return null; - } - - set(name: string, value: string): void { - const normalizedName = name.toLowerCase(); - const existing = this.headerMap.get(normalizedName); - if (existing !== undefined) { - this.headerMap.set(normalizedName, existing + "," + value); - } else { - this.headerMap.set(normalizedName, value); - } - } - - toJSON(): any { - const m: Record = {}; - this.headerMap.forEach((v, k) => (m[k] = v)); - return m; - } -} - -/** - * Interface for the HTTP request library used by the wallet. - * - * The request library is bundled into an interface to make mocking and - * request tunneling easy. - */ -export interface HttpRequestLibrary { - /** - * Make an HTTP GET request. - * - * FIXME: Get rid of this, we want the API surface to be minimal. - */ - get(url: string, opt?: HttpRequestOptions): Promise; - - /** - * Make an HTTP POST request with a JSON body. - * - * FIXME: Get rid of this, we want the API surface to be minimal. - */ - postJson( - url: string, - body: any, - opt?: HttpRequestOptions, - ): Promise; - - /** - * Make an HTTP POST request with a JSON body. - */ - fetch(url: string, opt?: HttpRequestOptions): Promise; -} - -type TalerErrorResponse = { - code: number; -} & unknown; - -type ResponseOrError = - | { isError: false; response: T } - | { isError: true; talerErrorResponse: TalerErrorResponse }; - -export async function readTalerErrorResponse( - httpResponse: HttpResponse, -): Promise { - const errJson = await httpResponse.json(); - const talerErrorCode = errJson.code; - if (typeof talerErrorCode !== "number") { - logger.warn( - `malformed error response (status ${httpResponse.status}): ${j2s( - errJson, - )}`, - ); - throw TalerError.fromDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - httpStatusCode: httpResponse.status, - }, - "Error response did not contain error code", - ); - } - return errJson; -} - -export async function readUnexpectedResponseDetails( - httpResponse: HttpResponse, -): Promise { - const errJson = await httpResponse.json(); - const talerErrorCode = errJson.code; - if (typeof talerErrorCode !== "number") { - return makeErrorDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - httpStatusCode: httpResponse.status, - }, - "Error response did not contain error code", - ); - } - return makeErrorDetail( - TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - { - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - httpStatusCode: httpResponse.status, - errorResponse: errJson, - }, - `Unexpected HTTP status (${httpResponse.status}) in response`, - ); -} - -export async function readSuccessResponseJsonOrErrorCode( - httpResponse: HttpResponse, - codec: Codec, -): Promise> { - if (!(httpResponse.status >= 200 && httpResponse.status < 300)) { - return { - isError: true, - talerErrorResponse: await readTalerErrorResponse(httpResponse), - }; - } - const respJson = await httpResponse.json(); - let parsedResponse: T; - try { - parsedResponse = codec.decode(respJson); - } catch (e: any) { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - httpStatusCode: httpResponse.status, - validationError: e.toString(), - }, - "Response invalid", - ); - } - return { - isError: false, - response: parsedResponse, - }; -} - -type HttpErrorDetails = { - requestUrl: string; - requestMethod: string; - httpStatusCode: number; -}; - -export function getHttpResponseErrorDetails( - httpResponse: HttpResponse, -): HttpErrorDetails { - return { - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - httpStatusCode: httpResponse.status, - }; -} - -export function throwUnexpectedRequestError( - httpResponse: HttpResponse, - talerErrorResponse: TalerErrorResponse, -): never { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - { - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - httpStatusCode: httpResponse.status, - errorResponse: talerErrorResponse, - }, - `Unexpected HTTP status ${httpResponse.status} in response`, - ); -} - -export async function readSuccessResponseJsonOrThrow( - httpResponse: HttpResponse, - codec: Codec, -): Promise { - const r = await readSuccessResponseJsonOrErrorCode(httpResponse, codec); - if (!r.isError) { - return r.response; - } - throwUnexpectedRequestError(httpResponse, r.talerErrorResponse); -} - -export async function readSuccessResponseTextOrErrorCode( - httpResponse: HttpResponse, -): Promise> { - if (!(httpResponse.status >= 200 && httpResponse.status < 300)) { - const errJson = await httpResponse.json(); - const talerErrorCode = errJson.code; - if (typeof talerErrorCode !== "number") { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - httpStatusCode: httpResponse.status, - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - }, - "Error response did not contain error code", - ); - } - return { - isError: true, - talerErrorResponse: errJson, - }; - } - const respJson = await httpResponse.text(); - return { - isError: false, - response: respJson, - }; -} - -export async function checkSuccessResponseOrThrow( - httpResponse: HttpResponse, -): Promise { - if (!(httpResponse.status >= 200 && httpResponse.status < 300)) { - const errJson = await httpResponse.json(); - const talerErrorCode = errJson.code; - if (typeof talerErrorCode !== "number") { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - httpStatusCode: httpResponse.status, - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - }, - "Error response did not contain error code", - ); - } - throwUnexpectedRequestError(httpResponse, errJson); - } -} - -export async function readSuccessResponseTextOrThrow( - httpResponse: HttpResponse, -): Promise { - const r = await readSuccessResponseTextOrErrorCode(httpResponse); - if (!r.isError) { - return r.response; - } - throwUnexpectedRequestError(httpResponse, r.talerErrorResponse); -} - -/** - * Get the timestamp at which the response's content is considered expired. - */ -export function getExpiry( - httpResponse: HttpResponse, - opt: { minDuration?: Duration }, -): AbsoluteTime { - const expiryDateMs = new Date( - httpResponse.headers.get("expiry") ?? "", - ).getTime(); - let t: AbsoluteTime; - if (Number.isNaN(expiryDateMs)) { - t = AbsoluteTime.now(); - } else { - t = { - t_ms: expiryDateMs, - }; - } - if (opt.minDuration) { - const t2 = AbsoluteTime.addDuration(AbsoluteTime.now(), opt.minDuration); - return AbsoluteTime.max(t, t2); - } - return t; -} diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts index fcb63ecd1..742381f7b 100644 --- a/packages/taler-wallet-core/src/util/retries.ts +++ b/packages/taler-wallet-core/src/util/retries.ts @@ -40,7 +40,7 @@ import { WalletStoresV1, WithdrawalGroupRecord, } from "../db.js"; -import { TalerError } from "../errors.js"; +import { TalerError } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; import { PendingTaskType } from "../pending-types.js"; import { GetReadWriteAccess } from "./query.js"; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index a57c71bcf..0d02b667b 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -129,7 +129,7 @@ import { maybeInitDevMode, setDevMode, } from "./dev-experiments.js"; -import { getErrorDetailFromException, TalerError } from "./errors.js"; +import { getErrorDetailFromException, TalerError } from "@gnu-taler/taler-util"; import { ActiveLongpollInfo, ExchangeOperations, @@ -247,7 +247,7 @@ import { import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, -} from "./util/http.js"; +} from "@gnu-taler/taler-util/http"; import { checkDbInvariant } from "./util/invariants.js"; import { AsyncCondition, -- cgit v1.2.3