From 5d23eb36354d07508a015531f298b3e261bbafce Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 22 Mar 2022 21:16:38 +0100 Subject: wallet: improve error handling and error codes --- packages/taler-util/src/notifications.ts | 26 +-- packages/taler-util/src/taler-error-codes.ts | 70 ++++++- packages/taler-util/src/transactionsTypes.ts | 4 +- packages/taler-util/src/walletTypes.ts | 14 +- packages/taler-wallet-cli/src/harness/harness.ts | 14 +- packages/taler-wallet-cli/src/index.ts | 21 +- .../src/integrationtests/test-bank-api.ts | 5 +- .../src/integrationtests/test-denom-unoffered.ts | 32 +-- .../integrationtests/test-exchange-management.ts | 18 +- .../src/integrationtests/test-pay-abort.ts | 2 +- .../src/integrationtests/test-payment-claim.ts | 30 +-- .../src/integrationtests/test-payment-transient.ts | 8 +- .../src/integrationtests/test-wallet-dbless.ts | 6 +- .../integrationtests/test-withdrawal-abort-bank.ts | 4 +- packages/taler-wallet-core/src/bank-api-client.ts | 19 +- packages/taler-wallet-core/src/db.ts | 28 +-- packages/taler-wallet-core/src/errors.ts | 216 +++++++++++++-------- .../taler-wallet-core/src/headless/NodeHttpLib.ts | 46 ++--- .../src/operations/backup/index.ts | 10 +- .../taler-wallet-core/src/operations/deposits.ts | 6 +- .../taler-wallet-core/src/operations/exchanges.ts | 22 +-- packages/taler-wallet-core/src/operations/pay.ts | 106 +++++----- .../taler-wallet-core/src/operations/recoup.ts | 6 +- .../taler-wallet-core/src/operations/refresh.ts | 6 +- .../taler-wallet-core/src/operations/refund.ts | 6 +- .../taler-wallet-core/src/operations/reserves.ts | 13 +- packages/taler-wallet-core/src/operations/tip.ts | 16 +- .../taler-wallet-core/src/operations/withdraw.ts | 35 ++-- packages/taler-wallet-core/src/pending-types.ts | 20 +- packages/taler-wallet-core/src/util/http.ts | 92 ++++----- packages/taler-wallet-core/src/wallet.ts | 62 +++--- packages/taler-wallet-embedded/src/index.ts | 8 +- .../src/browserHttpLib.ts | 22 +-- .../src/components/ErrorTalerOperation.tsx | 6 +- .../taler-wallet-webextension/src/cta/Deposit.tsx | 14 +- packages/taler-wallet-webextension/src/cta/Pay.tsx | 4 +- .../taler-wallet-webextension/src/cta/Withdraw.tsx | 13 +- .../src/hooks/useAsyncAsHook.ts | 30 ++- .../src/serviceWorkerHttpLib.ts | 18 +- packages/taler-wallet-webextension/src/wxApi.ts | 73 +++++-- .../taler-wallet-webextension/src/wxBackend.ts | 11 +- 41 files changed, 663 insertions(+), 499 deletions(-) diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts index e8f27062c..b3d9ad1dc 100644 --- a/packages/taler-util/src/notifications.ts +++ b/packages/taler-util/src/notifications.ts @@ -22,7 +22,7 @@ /** * Imports. */ -import { TalerErrorDetails } from "./walletTypes.js"; +import { TalerErrorDetail } from "./walletTypes.js"; export enum NotificationType { CoinWithdrawn = "coin-withdrawn", @@ -157,62 +157,62 @@ export interface ExchangeAddedNotification { export interface ExchangeOperationErrorNotification { type: NotificationType.ExchangeOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface RefreshOperationErrorNotification { type: NotificationType.RefreshOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface BackupOperationErrorNotification { type: NotificationType.BackupOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface RefundStatusOperationErrorNotification { type: NotificationType.RefundStatusOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface RefundApplyOperationErrorNotification { type: NotificationType.RefundApplyOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface PayOperationErrorNotification { type: NotificationType.PayOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface ProposalOperationErrorNotification { type: NotificationType.ProposalOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface TipOperationErrorNotification { type: NotificationType.TipOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface WithdrawOperationErrorNotification { type: NotificationType.WithdrawOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface RecoupOperationErrorNotification { type: NotificationType.RecoupOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface DepositOperationErrorNotification { type: NotificationType.DepositOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface ReserveOperationErrorNotification { type: NotificationType.ReserveOperationError; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface ReserveCreatedNotification { diff --git a/packages/taler-util/src/taler-error-codes.ts b/packages/taler-util/src/taler-error-codes.ts index b22f29a19..8ea97f7e7 100644 --- a/packages/taler-util/src/taler-error-codes.ts +++ b/packages/taler-util/src/taler-error-codes.ts @@ -22,6 +22,8 @@ */ export enum TalerErrorCode { + + /** * Special code to indicate success (no error). * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). @@ -78,6 +80,13 @@ export enum TalerErrorCode { */ GENERIC_CONFIGURATION_INVALID = 14, + /** + * The client made a request to a service, but received an error response it does not know how to handle. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + GENERIC_UNEXPECTED_REQUEST_ERROR = 15, + /** * The HTTP method used is invalid for this endpoint. * Returned with an HTTP status code of #MHD_HTTP_METHOD_NOT_ALLOWED (405). @@ -372,6 +381,20 @@ export enum TalerErrorCode { */ EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE = 1018, + /** + * The reserve public key was malformed. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_GENERIC_RESERVE_PUB_MALFORMED = 1019, + + /** + * The time at the server is too far off from the time specified in the request. Most likely the client system time is wrong. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_GENERIC_CLOCK_SKEW = 1020, + /** * The exchange did not find information about the specified transaction in the database. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). @@ -541,11 +564,25 @@ export enum TalerErrorCode { EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT = 1222, /** - * The reserve status was requested using a unknown key. + * The reserve balance, status or history was requested for a reserve which is not known to the exchange. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). */ - EXCHANGE_RESERVES_GET_STATUS_UNKNOWN = 1250, + EXCHANGE_RESERVES_STATUS_UNKNOWN = 1250, + + /** + * The reserve status was requested with a bad signature. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_RESERVES_STATUS_BAD_SIGNATURE = 1251, + + /** + * The reserve history was requested with a bad signature. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_RESERVES_HISTORY_BAD_SIGNATURE = 1252, /** * The exchange encountered melt fees exceeding the melted coin's contribution. @@ -1394,6 +1431,27 @@ export enum TalerErrorCode { */ MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_FAILED = 2170, + /** + * The payment required a minimum age but one of the coins (of a denomination with support for age restriction) did not provide any age_commitment. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING = 2171, + + /** + * The payment required a minimum age but one of the coins provided an age_commitment that contained a wrong number of public keys compared to the number of age groups defined in the denomination of the coin. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH = 2172, + + /** + * The payment required a minimum age but one of the coins provided a minimum_age_sig that couldn't be verified with the given age_commitment for that particular minimum age. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED = 2173, + /** * The contract hash does not match the given order ID. * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). @@ -2150,6 +2208,13 @@ export enum TalerErrorCode { */ WALLET_CONTRACT_TERMS_MALFORMED = 7020, + /** + * A pending operation failed, and thus the request can't be completed. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_PENDING_OPERATION_FAILED = 7021, + /** * We encountered a timeout with our payment backend. * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504). @@ -2646,4 +2711,5 @@ export enum TalerErrorCode { * (A value of 0 indicates that the error is generated client-side). */ END = 9999, + } diff --git a/packages/taler-util/src/transactionsTypes.ts b/packages/taler-util/src/transactionsTypes.ts index bccbc7737..b9a227b68 100644 --- a/packages/taler-util/src/transactionsTypes.ts +++ b/packages/taler-util/src/transactionsTypes.ts @@ -42,7 +42,7 @@ import { codecForList, codecForAny, } from "./codec.js"; -import { TalerErrorDetails } from "./walletTypes.js"; +import { TalerErrorDetail } from "./walletTypes.js"; export interface TransactionsRequest { /** @@ -92,7 +92,7 @@ export interface TransactionCommon { // Amount added or removed from the wallet's balance (including all fees and other costs) amountEffective: AmountString; - error?: TalerErrorDetails; + error?: TalerErrorDetail; } export type Transaction = diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 3c4fa96c7..1f88c39ee 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -60,6 +60,7 @@ import { import { OrderShortInfo, codecForOrderShortInfo } from "./transactionsTypes.js"; import { BackupRecovery } from "./backupTypes.js"; import { PaytoUri } from "./payto.js"; +import { TalerErrorCode } from "./taler-error-codes.js"; /** * Response for the create reserve request to the wallet. @@ -136,7 +137,7 @@ export interface ConfirmPayResultDone { export interface ConfirmPayResultPending { type: ConfirmPayResultType.Pending; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending; @@ -455,11 +456,10 @@ export interface WalletDiagnostics { dbOutdated: boolean; } -export interface TalerErrorDetails { - code: number; - hint: string; - message: string; - details: unknown; +export interface TalerErrorDetail { + code: TalerErrorCode; + hint?: string; + [x: string]: unknown; } /** @@ -850,7 +850,7 @@ export interface CoreApiResponseError { type: "error"; operation: string; id: string; - error: TalerErrorDetails; + error: TalerErrorDetail; } export interface WithdrawTestBalanceRequest { diff --git a/packages/taler-wallet-cli/src/harness/harness.ts b/packages/taler-wallet-cli/src/harness/harness.ts index 63bb17fcc..46ddd0ed2 100644 --- a/packages/taler-wallet-cli/src/harness/harness.ts +++ b/packages/taler-wallet-cli/src/harness/harness.ts @@ -49,7 +49,7 @@ import { HarnessExchangeBankAccount, NodeHttpLib, openPromise, - OperationFailedError, + TalerError, WalletCoreApiClient, } from "@gnu-taler/taler-wallet-core"; import { @@ -227,19 +227,19 @@ export class GlobalTestState { this.servers = []; } - async assertThrowsOperationErrorAsync( + async assertThrowsTalerErrorAsync( block: () => Promise, - ): Promise { + ): Promise { try { await block(); } catch (e) { - if (e instanceof OperationFailedError) { + if (e instanceof TalerError) { return e; } - throw Error(`expected OperationFailedError to be thrown, but got ${e}`); + throw Error(`expected TalerError to be thrown, but got ${e}`); } throw Error( - `expected OperationFailedError to be thrown, but block finished without throwing`, + `expected TalerError to be thrown, but block finished without throwing`, ); } @@ -1904,7 +1904,7 @@ export class WalletCli { throw new Error("wallet CLI did not return a proper JSON response"); } if (ar.type === "error") { - throw new OperationFailedError(ar.error); + throw TalerError.fromUncheckedDetail(ar.error); } return ar.result; }, diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index f754ca915..e7b76fa9e 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -49,14 +49,13 @@ import { import { NodeHttpLib, getDefaultNodeWallet, - OperationFailedAndReportedError, - OperationFailedError, NodeThreadCryptoWorkerFactory, CryptoApi, walletCoreDebugFlags, WalletApiOperation, WalletCoreApiClient, Wallet, + getErrorDetailFromException, } from "@gnu-taler/taler-wallet-core"; import { lintExchangeDeployment } from "./lint.js"; import { runBench1 } from "./bench1.js"; @@ -206,18 +205,12 @@ async function withWallet( const ret = await f(w); return ret; } catch (e) { - if ( - e instanceof OperationFailedAndReportedError || - e instanceof OperationFailedError - ) { - console.error("Operation failed: " + e.message); - console.error( - "Error details:", - JSON.stringify(e.operationError, undefined, 2), - ); - } else { - console.error("caught unhandled exception (bug?):", e); - } + const ed = getErrorDetailFromException(e); + console.error("Operation failed: " + ed.message); + console.error( + "Error details:", + JSON.stringify(ed.operationError, undefined, 2), + ); process.exit(1); } finally { logger.info("operation with wallet finished, stopping"); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts index 8a11b79c6..97dbf369c 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts @@ -32,7 +32,6 @@ import { BankApi, BankAccessApi, CreditDebitIndicator, - OperationFailedError, } from "@gnu-taler/taler-wallet-core"; /** @@ -104,10 +103,10 @@ export async function runBankApiTest(t: GlobalTestState) { // Make sure that registering twice results in a 409 Conflict { - const e = await t.assertThrowsAsync(async () => { + const e = await t.assertThrowsTalerErrorAsync(async () => { await BankApi.registerAccount(bank, "user1", "pw1"); }); - t.assertTrue(e.details.httpStatusCode === 409); + t.assertTrue(e.errorDetail.httpStatusCode === 409); } let balResp = await BankAccessApi.getAccountBalance(bank, bankUser); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts b/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts index 28cca0758..ec1d9f64b 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts @@ -20,25 +20,21 @@ import { PreparePayResultType, TalerErrorCode, - TalerErrorDetails, - TransactionType, + TalerErrorDetail, } from "@gnu-taler/taler-util"; -import { - WalletApiOperation, -} from "@gnu-taler/taler-wallet-core"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { makeEventId } from "@gnu-taler/taler-wallet-core"; import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment, withdrawViaBank } from "../harness/helpers.js"; +import { + createSimpleTestkudosEnvironment, + withdrawViaBank, +} from "../harness/helpers.js"; export async function runDenomUnofferedTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - bank, - exchange, - merchant, - } = await createSimpleTestkudosEnvironment(t); + const { wallet, bank, exchange, merchant } = + await createSimpleTestkudosEnvironment(t); // Withdraw digital cash into the wallet. @@ -95,19 +91,23 @@ export async function runDenomUnofferedTest(t: GlobalTestState) { preparePayResult.status === PreparePayResultType.PaymentPossible, ); - const exc = await t.assertThrowsAsync(async () => { + const exc = await t.assertThrowsTalerErrorAsync(async () => { await wallet.client.call(WalletApiOperation.ConfirmPay, { proposalId: preparePayResult.proposalId, }); }); - const errorDetails: TalerErrorDetails = exc.operationError; + t.assertTrue( + exc.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED), + ); + // FIXME: We might want a more specific error code here! t.assertDeepEqual( - errorDetails.code, + exc.errorDetail.innerError.code, TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, ); - const merchantErrorCode = (errorDetails.details as any).errorResponse.code; + const merchantErrorCode = (exc.errorDetail.innerError.errorResponse as any) + .code; t.assertDeepEqual( merchantErrorCode, TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts index f9c7c4b99..dc650830d 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts @@ -181,16 +181,22 @@ export async function runExchangeManagementTest(t: GlobalTestState) { }, }); - const err1 = await t.assertThrowsOperationErrorAsync(async () => { + const err1 = await t.assertThrowsTalerErrorAsync(async () => { await wallet.client.call(WalletApiOperation.AddExchange, { exchangeBaseUrl: faultyExchange.baseUrl, }); }); + // Updating the exchange from the base URL is technically a pending operation + // and it will be retried later. + t.assertTrue( + err1.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED), + ); + // Response is malformed, since it didn't even contain a version code // in a format the wallet can understand. t.assertTrue( - err1.operationError.code === + err1.errorDetail.innerError.code === TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, ); @@ -223,14 +229,18 @@ export async function runExchangeManagementTest(t: GlobalTestState) { }, }); - const err2 = await t.assertThrowsOperationErrorAsync(async () => { + const err2 = await t.assertThrowsTalerErrorAsync(async () => { await wallet.client.call(WalletApiOperation.AddExchange, { exchangeBaseUrl: faultyExchange.baseUrl, }); }); t.assertTrue( - err2.operationError.code === + err2.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED), + ); + + t.assertTrue( + err2.errorDetail.innerError.code === TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE, ); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts b/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts index 0fa9ec81d..09b546f46 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts @@ -123,7 +123,7 @@ export async function runPayAbortTest(t: GlobalTestState) { }, }); - await t.assertThrowsOperationErrorAsync(async () => { + await t.assertThrowsTalerErrorAsync(async () => { await wallet.client.call(WalletApiOperation.ConfirmPay, { proposalId: preparePayResult.proposalId, }); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts index ba3bd8e0a..e878854f8 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts @@ -17,8 +17,15 @@ /** * Imports. */ -import { GlobalTestState, MerchantPrivateApi, WalletCli } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment, withdrawViaBank } from "../harness/helpers.js"; +import { + GlobalTestState, + MerchantPrivateApi, + WalletCli, +} from "../harness/harness.js"; +import { + createSimpleTestkudosEnvironment, + withdrawViaBank, +} from "../harness/helpers.js"; import { PreparePayResultType } from "@gnu-taler/taler-util"; import { TalerErrorCode } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -29,12 +36,8 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; export async function runPaymentClaimTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - bank, - exchange, - merchant, - } = await createSimpleTestkudosEnvironment(t); + const { wallet, bank, exchange, merchant } = + await createSimpleTestkudosEnvironment(t); const walletTwo = new WalletCli(t, "two"); @@ -73,7 +76,7 @@ export async function runPaymentClaimTest(t: GlobalTestState) { preparePayResult.status === PreparePayResultType.PaymentPossible, ); - t.assertThrowsOperationErrorAsync(async () => { + t.assertThrowsTalerErrorAsync(async () => { await walletTwo.client.call(WalletApiOperation.PreparePayForUri, { talerPayUri, }); @@ -93,14 +96,19 @@ export async function runPaymentClaimTest(t: GlobalTestState) { walletTwo.deleteDatabase(); - const err = await t.assertThrowsOperationErrorAsync(async () => { + const err = await t.assertThrowsTalerErrorAsync(async () => { await walletTwo.client.call(WalletApiOperation.PreparePayForUri, { talerPayUri, }); }); t.assertTrue( - err.operationError.code === TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED, + err.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED), + ); + + t.assertTrue( + err.errorDetail.innerError.code === + TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED, ); await t.shutdown(); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts index 75d44d495..7e178077e 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts @@ -32,7 +32,7 @@ import { ConfirmPayResultType, PreparePayResultType, TalerErrorCode, - TalerErrorDetails, + TalerErrorDetail, URL, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -135,11 +135,9 @@ export async function runPaymentTransientTest(t: GlobalTestState) { } faultInjected = true; console.log("injecting pay fault"); - const err: TalerErrorDetails = { + const err: TalerErrorDetail = { code: TalerErrorCode.GENERIC_DB_COMMIT_FAILED, - details: {}, - hint: "huh", - message: "something went wrong", + hint: "something went wrong", }; ctx.responseBody = Buffer.from(JSON.stringify(err)); ctx.statusCode = 500; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts index 93c22af70..146603f3a 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts @@ -26,9 +26,9 @@ import { findDenomOrThrow, generateReserveKeypair, NodeHttpLib, - OperationFailedError, refreshCoin, SynchronousCryptoWorkerFactory, + TalerError, topupReserveWithDemobank, withdrawCoin, } from "@gnu-taler/taler-wallet-core"; @@ -95,9 +95,9 @@ export async function runWalletDblessTest(t: GlobalTestState) { newDenoms: refreshDenoms, }); } catch (e) { - if (e instanceof OperationFailedError) { + if (e instanceof TalerError) { console.log(e); - console.log(j2s(e.operationError)); + console.log(j2s(e.errorDetail)); } else { console.log(e); } diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts index 19668d760..0125b3b41 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts @@ -63,7 +63,7 @@ export async function runWithdrawalAbortBankTest(t: GlobalTestState) { // // WHY ?! // - const e = await t.assertThrowsOperationErrorAsync(async () => { + const e = await t.assertThrowsTalerErrorAsync(async () => { await wallet.client.call( WalletApiOperation.AcceptBankIntegratedWithdrawal, { @@ -73,7 +73,7 @@ export async function runWithdrawalAbortBankTest(t: GlobalTestState) { ); }); t.assertDeepEqual( - e.operationError.code, + e.errorDetail.code, TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, ); diff --git a/packages/taler-wallet-core/src/bank-api-client.ts b/packages/taler-wallet-core/src/bank-api-client.ts index 128e9a7a7..14bf07174 100644 --- a/packages/taler-wallet-core/src/bank-api-client.ts +++ b/packages/taler-wallet-core/src/bank-api-client.ts @@ -31,7 +31,9 @@ import { getRandomBytes, j2s, Logger, + TalerErrorCode, } from "@gnu-taler/taler-util"; +import { TalerError } from "./errors.js"; import { HttpRequestLibrary, readSuccessResponseJsonOrErrorCode, @@ -104,15 +106,20 @@ export namespace BankApi { let paytoUri = `payto://x-taler-bank/localhost/${username}`; if (resp.status !== 200 && resp.status !== 202) { logger.error(`${j2s(await resp.json())}`); - throw new Error(); - } - const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny()); - // LibEuFin demobank returns payto URI in response - if (respJson.paytoUri) { - paytoUri = respJson.paytoUri; + throw TalerError.fromDetail( + TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + { + httpStatusCode: resp.status, + }, + ); } try { + // Pybank has no body, thus this might throw. const respJson = await resp.json(); + // LibEuFin demobank returns payto URI in response + if (respJson.paytoUri) { + paytoUri = respJson.paytoUri; + } } catch (e) {} return { password, diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index e9fe6a47b..69606b8ff 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -35,7 +35,7 @@ import { MerchantInfo, Product, RefreshReason, - TalerErrorDetails, + TalerErrorDetail, UnblindedSignature, CoinEnvelope, TalerProtocolTimestamp, @@ -229,7 +229,7 @@ export interface ReserveRecord { * Last error that happened in a reserve operation * (either talking to the bank or the exchange). */ - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } /** @@ -545,7 +545,7 @@ export interface ExchangeRecord { * Last error (if any) for fetching updated information about the * exchange. */ - lastError?: TalerErrorDetails; + lastError?: TalerErrorDetail; /** * Retry status for fetching updated information about the exchange. @@ -580,7 +580,7 @@ export interface PlanchetRecord { withdrawalDone: boolean; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; /** * Public key of the reserve that this planchet @@ -820,14 +820,14 @@ export interface ProposalRecord { */ retryInfo?: RetryInfo; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } /** * Status of a tip we got from a merchant. */ export interface TipRecord { - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; /** * Has the user accepted the tip? Only after the tip has been accepted coins @@ -922,9 +922,9 @@ export interface RefreshGroupRecord { */ retryInfo: RetryInfo; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; - lastErrorPerCoin: { [coinIndex: number]: TalerErrorDetails }; + lastErrorPerCoin: { [coinIndex: number]: TalerErrorDetail }; /** * Unique, randomly generated identifier for this group of @@ -1256,7 +1256,7 @@ export interface PurchaseRecord { payRetryInfo?: RetryInfo; - lastPayError: TalerErrorDetails | undefined; + lastPayError: TalerErrorDetail | undefined; /** * Retry information for querying the refund status with the merchant. @@ -1266,7 +1266,7 @@ export interface PurchaseRecord { /** * Last error (or undefined) for querying the refund status with the merchant. */ - lastRefundStatusError: TalerErrorDetails | undefined; + lastRefundStatusError: TalerErrorDetail | undefined; /** * Continue querying the refund status until this deadline has expired. @@ -1400,7 +1400,7 @@ export interface WithdrawalGroupRecord { */ retryInfo: RetryInfo; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } export interface BankWithdrawUriRecord { @@ -1465,7 +1465,7 @@ export interface RecoupGroupRecord { /** * Last error that occurred, if any. */ - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } export enum BackupProviderStateTag { @@ -1485,7 +1485,7 @@ export type BackupProviderState = | { tag: BackupProviderStateTag.Retrying; retryInfo: RetryInfo; - lastError?: TalerErrorDetails; + lastError?: TalerErrorDetail; }; export interface BackupProviderTerms { @@ -1598,7 +1598,7 @@ export interface DepositGroupRecord { operationStatus: OperationStatus; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; /** * Retry info. diff --git a/packages/taler-wallet-core/src/errors.ts b/packages/taler-wallet-core/src/errors.ts index 3109644ac..07a01a760 100644 --- a/packages/taler-wallet-core/src/errors.ts +++ b/packages/taler-wallet-core/src/errors.ts @@ -23,63 +23,143 @@ /** * Imports. */ -import { TalerErrorCode, TalerErrorDetails } from "@gnu-taler/taler-util"; +import { + TalerErrorCode, + TalerErrorDetail, + TransactionType, +} from "@gnu-taler/taler-util"; -/** - * This exception is there to let the caller know that an error happened, - * but the error has already been reported by writing it to the database. - */ -export class OperationFailedAndReportedError extends Error { - static fromCode( - ec: TalerErrorCode, - message: string, - details: Record, - ): OperationFailedAndReportedError { - return new OperationFailedAndReportedError( - makeErrorDetails(ec, message, details), - ); - } +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]: {}; + [TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID]: {}; + [TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED]: { + orderId: string; + claimUrl: string; + }; + [TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED]: {}; + [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]: {}; + [TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION]: {}; + [TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE]: {}; + [TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN]: {}; + [TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED]: {}; + [TalerErrorCode.WALLET_NETWORK_ERROR]: {}; + [TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE]: {}; + [TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID]: {}; + [TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE]: {}; + [TalerErrorCode.WALLET_CORE_NOT_AVAILABLE]: {}; + [TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR]: {}; +} - constructor(public operationError: TalerErrorDetails) { - super(operationError.message); +type ErrBody = Y extends keyof DetailsMap ? DetailsMap[Y] : never; - // Set the prototype explicitly. - Object.setPrototypeOf(this, OperationFailedAndReportedError.prototype); - } +export function makeErrorDetail( + code: C, + detail: ErrBody, + hint?: string, +): TalerErrorDetail { + // FIXME: include default hint? + return { code, hint, ...detail }; } -/** - * This exception is thrown when an error occurred and the caller is - * responsible for recording the failure in the database. - */ -export class OperationFailedError extends Error { - static fromCode( - ec: TalerErrorCode, - message: string, - details: Record, - ): OperationFailedError { - return new OperationFailedError(makeErrorDetails(ec, message, details)); +export function makePendingOperationFailedError( + innerError: TalerErrorDetail, + tag: TransactionType, + uid: string, +): TalerError { + return TalerError.fromDetail(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED, { + innerError, + transactionId: `${tag}:${uid}`, + }); +} + +export class TalerError extends Error { + errorDetail: TalerErrorDetail & T; + private constructor(d: TalerErrorDetail & T) { + super(); + this.errorDetail = d; + Object.setPrototypeOf(this, TalerError.prototype); } - constructor(public operationError: TalerErrorDetails) { - super(operationError.message); + static fromDetail( + code: C, + detail: ErrBody, + hint?: string, + ): TalerError { + // FIXME: include default hint? + return new TalerError({ code, hint, ...detail }); + } - // Set the prototype explicitly. - Object.setPrototypeOf(this, OperationFailedError.prototype); + 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; } } -export function makeErrorDetails( - ec: TalerErrorCode, - message: string, - details: Record, -): TalerErrorDetails { - return { - code: ec, - hint: `Error: ${TalerErrorCode[ec]}`, - details: details, - message, - }; +/** + * 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; } /** @@ -89,44 +169,24 @@ export function makeErrorDetails( */ export async function guardOperationException( op: () => Promise, - onOpError: (e: TalerErrorDetails) => Promise, + onOpError: (e: TalerErrorDetail) => Promise, ): Promise { try { return await op(); } catch (e: any) { - if (e instanceof OperationFailedAndReportedError) { + if ( + e instanceof TalerError && + e.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED) + ) { throw e; } - if (e instanceof OperationFailedError) { - await onOpError(e.operationError); - throw new OperationFailedAndReportedError(e.operationError); - } - if (e instanceof Error) { - const opErr = makeErrorDetails( - TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - `unexpected exception (message: ${e.message})`, - { - stack: e.stack, - }, - ); - await onOpError(opErr); - throw new OperationFailedAndReportedError(opErr); - } - // 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 opErr = makeErrorDetails( - TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - `unexpected exception (not an exception, ${excString})`, - {}, - ); + const opErr = getErrorDetailFromException(e); await onOpError(opErr); - throw new OperationFailedAndReportedError(opErr); + throw TalerError.fromDetail( + TalerErrorCode.WALLET_PENDING_OPERATION_FAILED, + { + innerError: e.errorDetail, + }, + ); } } diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts index 2a8c9e36c..df25a1092 100644 --- a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts +++ b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts @@ -27,7 +27,7 @@ import { } from "../util/http.js"; import { RequestThrottler } from "@gnu-taler/taler-util"; import Axios, { AxiosResponse } from "axios"; -import { OperationFailedError, makeErrorDetails } from "../errors.js"; +import { TalerError } from "../errors.js"; import { Logger, bytesToString } from "@gnu-taler/taler-util"; import { TalerErrorCode, URL } from "@gnu-taler/taler-util"; @@ -55,14 +55,14 @@ export class NodeHttpLib implements HttpRequestLibrary { const parsedUrl = new URL(url); if (this.throttlingEnabled && this.throttle.applyThrottle(url)) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, - `request to origin ${parsedUrl.origin} was throttled`, { requestMethod: method, requestUrl: url, throttleStats: this.throttle.getThrottleStats(url), }, + `request to origin ${parsedUrl.origin} was throttled`, ); } let timeout: number | undefined; @@ -83,13 +83,13 @@ export class NodeHttpLib implements HttpRequestLibrary { maxRedirects: 0, }); } catch (e: any) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_NETWORK_ERROR, - `${e.message}`, { requestUrl: url, requestMethod: method, }, + `${e.message}`, ); } @@ -105,30 +105,26 @@ export class NodeHttpLib implements HttpRequestLibrary { responseJson = JSON.parse(respText); } catch (e) { logger.trace(`invalid json: '${resp.data}'`); - throw new OperationFailedError( - makeErrorDetails( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "invalid JSON", - { - httpStatusCode: resp.status, - requestUrl: url, - requestMethod: method, - }, - ), + 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 new OperationFailedError( - makeErrorDetails( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "invalid JSON", - { - httpStatusCode: resp.status, - requestUrl: url, - requestMethod: method, - }, - ), + throw TalerError.fromDetail( + TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, + { + httpStatusCode: resp.status, + requestUrl: url, + requestMethod: method, + }, + `invalid JSON`, ); } return responseJson; diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 48eea56ad..400406ce3 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -48,7 +48,7 @@ import { PreparePayResultType, RecoveryLoadRequest, RecoveryMergeStrategy, - TalerErrorDetails, + TalerErrorDetail, AbsoluteTime, URL, WalletBackupContentV1, @@ -464,7 +464,7 @@ async function incrementBackupRetryInTx( backupProviders: typeof WalletStoresV1.backupProviders; }>, backupProviderBaseUrl: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { const pr = await tx.backupProviders.get(backupProviderBaseUrl); if (!pr) { @@ -487,7 +487,7 @@ async function incrementBackupRetryInTx( async function incrementBackupRetry( ws: InternalWalletState, backupProviderBaseUrl: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ backupProviders: x.backupProviders })) @@ -509,7 +509,7 @@ export async function processBackupForProvider( throw Error("unknown backup provider"); } - const onOpErr = (err: TalerErrorDetails): Promise => + const onOpErr = (err: TalerErrorDetail): Promise => incrementBackupRetry(ws, backupProviderBaseUrl, err); const run = async () => { @@ -700,7 +700,7 @@ export interface ProviderInfo { /** * Last communication issue with the provider. */ - lastError?: TalerErrorDetails; + lastError?: TalerErrorDetail; lastSuccessfulBackupTimestamp?: TalerProtocolTimestamp; lastAttemptedBackupTimestamp?: TalerProtocolTimestamp; paymentProposalIds: string[]; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 4b976011b..42ce5e7c9 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -36,7 +36,7 @@ import { Logger, NotificationType, parsePaytoUri, - TalerErrorDetails, + TalerErrorDetail, TalerProtocolTimestamp, TrackDepositGroupRequest, TrackDepositGroupResponse, @@ -83,7 +83,7 @@ async function resetDepositGroupRetry( async function incrementDepositRetry( ws: InternalWalletState, depositGroupId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ depositGroups: x.depositGroups })) @@ -111,7 +111,7 @@ export async function processDepositGroup( forceNow = false, ): Promise { await ws.memoProcessDeposit.memo(depositGroupId, async () => { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementDepositRetry(ws, depositGroupId, e); return await guardOperationException( async () => await processDepositGroupImpl(ws, depositGroupId, forceNow), diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index df7eee76d..bbed42288 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -34,7 +34,7 @@ import { Recoup, TalerErrorCode, URL, - TalerErrorDetails, + TalerErrorDetail, AbsoluteTime, hashDenomPub, LibtoolVersion, @@ -64,11 +64,7 @@ import { } from "../util/http.js"; import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; -import { - guardOperationException, - makeErrorDetails, - OperationFailedError, -} from "../errors.js"; +import { guardOperationException, TalerError } from "../errors.js"; import { InternalWalletState, TrustInfo } from "../common.js"; import { WALLET_CACHE_BREAKER_CLIENT_VERSION, @@ -112,7 +108,7 @@ function denominationRecordFromKeys( async function handleExchangeUpdateError( ws: InternalWalletState, baseUrl: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ exchanges: x.exchanges })) @@ -353,7 +349,7 @@ export async function updateExchangeFromUrl( exchange: ExchangeRecord; exchangeDetails: ExchangeDetailsRecord; }> { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => handleExchangeUpdateError(ws, baseUrl, e); return await guardOperationException( () => updateExchangeFromUrlImpl(ws, baseUrl, acceptedFormat, forceNow), @@ -429,14 +425,13 @@ async function downloadExchangeKeysInfo( logger.info("received /keys response"); if (exchangeKeysJsonUnchecked.denoms.length === 0) { - const opErr = makeErrorDetails( + throw TalerError.fromDetail( TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT, - "exchange doesn't offer any denominations", { exchangeBaseUrl: baseUrl, }, + "exchange doesn't offer any denominations", ); - throw new OperationFailedError(opErr); } const protocolVersion = exchangeKeysJsonUnchecked.version; @@ -446,15 +441,14 @@ async function downloadExchangeKeysInfo( protocolVersion, ); if (versionRes?.compatible != true) { - const opErr = makeErrorDetails( + throw TalerError.fromDetail( TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE, - "exchange protocol version not compatible with wallet", { exchangeProtocolVersion: protocolVersion, walletProtocolVersion: WALLET_EXCHANGE_PROTOCOL_VERSION, }, + "exchange protocol version not compatible with wallet", ); - throw new OperationFailedError(opErr); } const currency = Amounts.parseOrThrow( diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 9521d544f..ce3a26c34 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -25,6 +25,7 @@ * Imports. */ import { + AbsoluteTime, AmountJson, Amounts, codecForContractTerms, @@ -34,7 +35,6 @@ import { ConfirmPayResult, ConfirmPayResultType, ContractTerms, - decodeCrock, Duration, durationMax, durationMin, @@ -43,19 +43,17 @@ import { getRandomBytes, HttpStatusCode, j2s, - kdf, Logger, NotificationType, parsePayUri, PreparePayResult, PreparePayResultType, RefreshReason, - stringToBytes, TalerErrorCode, - TalerErrorDetails, - AbsoluteTime, - URL, + TalerErrorDetail, TalerProtocolTimestamp, + TransactionType, + URL, } from "@gnu-taler/taler-util"; import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../common.js"; import { @@ -74,9 +72,9 @@ import { } from "../db.js"; import { guardOperationException, - makeErrorDetails, - OperationFailedAndReportedError, - OperationFailedError, + makeErrorDetail, + makePendingOperationFailedError, + TalerError, } from "../errors.js"; import { AvailableCoinInfo, @@ -467,7 +465,7 @@ async function recordConfirmPay( async function reportProposalError( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ proposals: x.proposals })) @@ -550,7 +548,7 @@ async function incrementPurchasePayRetry( async function reportPurchasePayError( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ purchases: x.purchases })) @@ -575,7 +573,7 @@ export async function processDownloadProposal( proposalId: string, forceNow = false, ): Promise { - const onOpErr = (err: TalerErrorDetails): Promise => + const onOpErr = (err: TalerErrorDetail): Promise => reportProposalError(ws, proposalId, err); await guardOperationException( () => processDownloadProposalImpl(ws, proposalId, forceNow), @@ -602,7 +600,7 @@ async function resetDownloadProposalRetry( async function failProposalPermanently( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ proposals: x.proposals })) @@ -727,13 +725,13 @@ async function processDownloadProposalImpl( if (r.isError) { switch (r.talerErrorResponse.code) { case TalerErrorCode.MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED: - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED, - "order already claimed (likely by other wallet)", { orderId: proposal.orderId, claimUrl: orderClaimUrl, }, + "order already claimed (likely by other wallet)", ); default: throwUnexpectedRequestError(httpResponse, r.talerErrorResponse); @@ -758,13 +756,17 @@ async function processDownloadProposalImpl( logger.trace( `malformed contract terms: ${j2s(proposalResp.contract_terms)}`, ); - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED, - "validation for well-formedness failed", {}, + "validation for well-formedness failed", ); await failProposalPermanently(ws, proposalId, err); - throw new OperationFailedAndReportedError(err); + throw makePendingOperationFailedError( + err, + TransactionType.Payment, + proposalId, + ); } const contractTermsHash = ContractTermsUtil.hashContractTerms( @@ -780,13 +782,17 @@ async function processDownloadProposalImpl( proposalResp.contract_terms, ); } catch (e) { - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED, - `schema validation failed: ${e}`, {}, + `schema validation failed: ${e}`, ); await failProposalPermanently(ws, proposalId, err); - throw new OperationFailedAndReportedError(err); + throw makePendingOperationFailedError( + err, + TransactionType.Payment, + proposalId, + ); } const sigValid = await ws.cryptoApi.isValidContractTermsSignature( @@ -796,16 +802,20 @@ async function processDownloadProposalImpl( ); if (!sigValid) { - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_CONTRACT_TERMS_SIGNATURE_INVALID, - "merchant's signature on contract terms is invalid", { merchantPub: parsedContractTerms.merchant_pub, orderId: parsedContractTerms.order_id, }, + "merchant's signature on contract terms is invalid", ); await failProposalPermanently(ws, proposalId, err); - throw new OperationFailedAndReportedError(err); + throw makePendingOperationFailedError( + err, + TransactionType.Payment, + proposalId, + ); } const fulfillmentUrl = parsedContractTerms.fulfillment_url; @@ -814,16 +824,20 @@ async function processDownloadProposalImpl( const baseUrlFromContractTerms = parsedContractTerms.merchant_base_url; if (baseUrlForDownload !== baseUrlFromContractTerms) { - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH, - "merchant base URL mismatch", { baseUrlForDownload, baseUrlFromContractTerms, }, + "merchant base URL mismatch", ); await failProposalPermanently(ws, proposalId, err); - throw new OperationFailedAndReportedError(err); + throw makePendingOperationFailedError( + err, + TransactionType.Payment, + proposalId, + ); } const contractData = extractContractData( @@ -895,10 +909,8 @@ async function startDownloadProposal( ]); }); - /** - * If we have already claimed this proposal with the same sessionId - * nonce and claim token, reuse it. - */ + /* If we have already claimed this proposal with the same sessionId + * nonce and claim token, reuse it. */ if ( oldProposal && oldProposal.downloadSessionId === sessionId && @@ -1029,7 +1041,7 @@ async function storePayReplaySuccess( async function handleInsufficientFunds( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { logger.trace("handling insufficient funds, trying to re-select coins"); @@ -1319,12 +1331,12 @@ export async function preparePayForUri( const uriResult = parsePayUri(talerPayUri); if (!uriResult) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_INVALID_TALER_PAY_URI, - `invalid taler://pay URI (${talerPayUri})`, { talerPayUri, }, + `invalid taler://pay URI (${talerPayUri})`, ); } @@ -1503,7 +1515,7 @@ export async function processPurchasePay( proposalId: string, forceNow = false, ): Promise { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => reportPurchasePayError(ws, proposalId, e); return await guardOperationException( () => processPurchasePayImpl(ws, proposalId, forceNow), @@ -1527,9 +1539,8 @@ async function processPurchasePayImpl( lastError: { // FIXME: allocate more specific error code code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - message: `trying to pay for purchase that is not in the database`, - hint: `proposal ID is ${proposalId}`, - details: {}, + hint: `trying to pay for purchase that is not in the database`, + proposalId: proposalId, }, }; } @@ -1594,10 +1605,10 @@ async function processPurchasePayImpl( resp.status <= 599 ) { logger.trace("treating /pay error as transient"); - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - "/pay failed", getHttpResponseErrorDetails(resp), + "/pay failed", ); return { type: ConfirmPayResultType.Pending, @@ -1621,8 +1632,11 @@ async function processPurchasePayImpl( delete purch.payRetryInfo; await tx.purchases.put(purch); }); - // FIXME: Maybe introduce a new return type for this instead of throwing? - throw new OperationFailedAndReportedError(errDetails); + throw makePendingOperationFailedError( + errDetails, + TransactionType.Payment, + proposalId, + ); } if (resp.status === HttpStatusCode.Conflict) { @@ -1692,10 +1706,10 @@ async function processPurchasePayImpl( resp.status >= 500 && resp.status <= 599 ) { - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - "/paid failed", getHttpResponseErrorDetails(resp), + "/paid failed", ); return { type: ConfirmPayResultType.Pending, @@ -1703,10 +1717,10 @@ async function processPurchasePayImpl( }; } if (resp.status !== 204) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - "/paid failed", getHttpResponseErrorDetails(resp), + "/paid failed", ); } await storePayReplaySuccess(ws, proposalId, sessionId); diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 84a27966d..56c13f1b0 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -30,7 +30,7 @@ import { j2s, NotificationType, RefreshReason, - TalerErrorDetails, + TalerErrorDetail, TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; @@ -60,7 +60,7 @@ const logger = new Logger("operations/recoup.ts"); async function incrementRecoupRetry( ws: InternalWalletState, recoupGroupId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ @@ -384,7 +384,7 @@ export async function processRecoupGroup( forceNow = false, ): Promise { await ws.memoProcessRecoup.memo(recoupGroupId, async () => { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementRecoupRetry(ws, recoupGroupId, e); return await guardOperationException( async () => await processRecoupGroupImpl(ws, recoupGroupId, forceNow), diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 11f0f6c51..7753992f7 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -43,7 +43,7 @@ import { NotificationType, RefreshGroupId, RefreshReason, - TalerErrorDetails, + TalerErrorDetail, } from "@gnu-taler/taler-util"; import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { amountToPretty } from "@gnu-taler/taler-util"; @@ -714,7 +714,7 @@ async function refreshReveal( async function incrementRefreshRetry( ws: InternalWalletState, refreshGroupId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ @@ -747,7 +747,7 @@ export async function processRefreshGroup( forceNow = false, ): Promise { await ws.memoProcessRefresh.memo(refreshGroupId, async () => { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementRefreshRetry(ws, refreshGroupId, e); return await guardOperationException( async () => await processRefreshGroupImpl(ws, refreshGroupId, forceNow), diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts index 686d545df..d888ff015 100644 --- a/packages/taler-wallet-core/src/operations/refund.ts +++ b/packages/taler-wallet-core/src/operations/refund.ts @@ -40,7 +40,7 @@ import { parseRefundUri, RefreshReason, TalerErrorCode, - TalerErrorDetails, + TalerErrorDetail, URL, codecForMerchantOrderStatusPaid, AbsoluteTime, @@ -88,7 +88,7 @@ async function resetPurchaseQueryRefundRetry( async function incrementPurchaseQueryRefundRetry( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ @@ -592,7 +592,7 @@ export async function processPurchaseQueryRefund( proposalId: string, forceNow = false, ): Promise { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementPurchaseQueryRefundRetry(ws, proposalId, e); await guardOperationException( () => processPurchaseQueryRefundImpl(ws, proposalId, forceNow, true), diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index ac9483631..baa977033 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -34,7 +34,7 @@ import { NotificationType, randomBytes, TalerErrorCode, - TalerErrorDetails, + TalerErrorDetail, AbsoluteTime, URL, } from "@gnu-taler/taler-util"; @@ -47,7 +47,7 @@ import { WalletStoresV1, WithdrawalGroupRecord, } from "../db.js"; -import { guardOperationException, OperationFailedError } from "../errors.js"; +import { guardOperationException, TalerError } from "../errors.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; import { readSuccessResponseJsonOrErrorCode, @@ -135,7 +135,7 @@ async function incrementReserveRetry( async function reportReserveError( ws: InternalWalletState, reservePub: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ @@ -338,7 +338,7 @@ export async function processReserve( forceNow = false, ): Promise { return ws.memoProcessReserve.memo(reservePub, async () => { - const onOpError = (err: TalerErrorDetails): Promise => + const onOpError = (err: TalerErrorDetail): Promise => reportReserveError(ws, reservePub, err); await guardOperationException( () => processReserveImpl(ws, reservePub, forceNow), @@ -571,7 +571,7 @@ async function updateReserve( if ( resp.status === 404 && result.talerErrorResponse.code === - TalerErrorCode.EXCHANGE_RESERVES_GET_STATUS_UNKNOWN + TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN ) { ws.notify({ type: NotificationType.ReserveNotYetFound, @@ -803,9 +803,8 @@ export async function createTalerWithdrawReserve( return tx.reserves.get(reserve.reservePub); }); if (processedReserve?.reserveStatus === ReserveRecordStatus.BankAborted) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, - "withdrawal aborted by bank", {}, ); } diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 765120294..7b3d36a7c 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -22,7 +22,7 @@ import { parseTipUri, codecForTipPickupGetResponse, Amounts, - TalerErrorDetails, + TalerErrorDetail, NotificationType, TipPlanchetDetail, TalerErrorCode, @@ -44,7 +44,7 @@ import { import { j2s } from "@gnu-taler/taler-util"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; -import { guardOperationException, makeErrorDetails } from "../errors.js"; +import { guardOperationException, makeErrorDetail } from "../errors.js"; import { updateExchangeFromUrl } from "./exchanges.js"; import { InternalWalletState } from "../common.js"; import { @@ -163,7 +163,7 @@ export async function prepareTip( async function incrementTipRetry( ws: InternalWalletState, walletTipId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ @@ -192,7 +192,7 @@ export async function processTip( tipId: string, forceNow = false, ): Promise { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementTipRetry(ws, tipId, e); await guardOperationException( () => processTipImpl(ws, tipId, forceNow), @@ -296,10 +296,10 @@ async function processTipImpl( merchantResp.status === 424) ) { logger.trace(`got transient tip error`); - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - "tip pickup failed (transient)", getHttpResponseErrorDetails(merchantResp), + "tip pickup failed (transient)", ); await incrementTipRetry(ws, tipRecord.walletTipId, err); // FIXME: Maybe we want to signal to the caller that the transient error happened? @@ -355,10 +355,10 @@ async function processTipImpl( if (!tipRecord) { return; } - tipRecord.lastError = makeErrorDetails( + tipRecord.lastError = makeErrorDetail( TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID, - "invalid signature from the exchange (via merchant tip) after unblinding", {}, + "invalid signature from the exchange (via merchant tip) after unblinding", ); await tx.tips.put(tipRecord); }); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index e4c6f2a6a..1d7bf9303 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -30,7 +30,7 @@ import { NotificationType, parseWithdrawUri, TalerErrorCode, - TalerErrorDetails, + TalerErrorDetail, AbsoluteTime, WithdrawResponse, URL, @@ -42,6 +42,7 @@ import { ExchangeWithdrawRequest, Duration, TalerProtocolTimestamp, + TransactionType, } from "@gnu-taler/taler-util"; import { CoinRecord, @@ -63,9 +64,11 @@ import { } from "../util/http.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; import { + getErrorDetailFromException, guardOperationException, - makeErrorDetails, - OperationFailedError, + makeErrorDetail, + makePendingOperationFailedError, + TalerError, } from "../errors.js"; import { InternalWalletState } from "../common.js"; import { @@ -299,15 +302,14 @@ export async function getBankWithdrawalInfo( config.version, ); if (versionRes?.compatible != true) { - const opErr = makeErrorDetails( + throw TalerError.fromDetail( TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE, - "bank integration protocol version not compatible with wallet", { exchangeProtocolVersion: config.version, walletProtocolVersion: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, }, + "bank integration protocol version not compatible with wallet", ); - throw new OperationFailedError(opErr); } const reqUrl = new URL( @@ -526,12 +528,9 @@ async function processPlanchetExchangeRequest( ); return r; } catch (e) { + const errDetail = getErrorDetailFromException(e); logger.trace("withdrawal request failed", e); logger.trace(e); - if (!(e instanceof OperationFailedError)) { - throw e; - } - const errDetails = e.operationError; await ws.db .mktx((x) => ({ planchets: x.planchets })) .runReadWrite(async (tx) => { @@ -542,7 +541,7 @@ async function processPlanchetExchangeRequest( if (!planchet) { return; } - planchet.lastError = errDetails; + planchet.lastError = errDetail; await tx.planchets.put(planchet); }); return; @@ -628,10 +627,10 @@ async function processPlanchetVerifyAndStoreCoin( if (!planchet) { return; } - planchet.lastError = makeErrorDetails( + planchet.lastError = makeErrorDetail( TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID, - "invalid signature from the exchange after unblinding", {}, + "invalid signature from the exchange after unblinding", ); await tx.planchets.put(planchet); }); @@ -797,7 +796,7 @@ export async function updateWithdrawalDenoms( async function incrementWithdrawalRetry( ws: InternalWalletState, withdrawalGroupId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups })) @@ -821,7 +820,7 @@ export async function processWithdrawGroup( withdrawalGroupId: string, forceNow = false, ): Promise { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementWithdrawalRetry(ws, withdrawalGroupId, e); await guardOperationException( () => processWithdrawGroupImpl(ws, withdrawalGroupId, forceNow), @@ -919,7 +918,7 @@ async function processWithdrawGroupImpl( let numFinished = 0; let finishedForFirstTime = false; - let errorsPerCoin: Record = {}; + let errorsPerCoin: Record = {}; await ws.db .mktx((x) => ({ @@ -957,12 +956,12 @@ async function processWithdrawGroupImpl( }); if (numFinished != numTotalCoins) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE, - `withdrawal did not finish (${numFinished} / ${numTotalCoins} coins withdrawn)`, { errorsPerCoin, }, + `withdrawal did not finish (${numFinished} / ${numTotalCoins} coins withdrawn)`, ); } diff --git a/packages/taler-wallet-core/src/pending-types.ts b/packages/taler-wallet-core/src/pending-types.ts index 4b1434bb5..f4e5216bc 100644 --- a/packages/taler-wallet-core/src/pending-types.ts +++ b/packages/taler-wallet-core/src/pending-types.ts @@ -25,7 +25,7 @@ * Imports. */ import { - TalerErrorDetails, + TalerErrorDetail, BalancesResponse, AbsoluteTime, TalerProtocolTimestamp, @@ -71,7 +71,7 @@ export type PendingTaskInfo = PendingTaskInfoCommon & export interface PendingBackupTask { type: PendingTaskType.Backup; backupProviderBaseUrl: string; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } /** @@ -80,7 +80,7 @@ export interface PendingBackupTask { export interface PendingExchangeUpdateTask { type: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } /** @@ -124,7 +124,7 @@ export interface PendingReserveTask { */ export interface PendingRefreshTask { type: PendingTaskType.Refresh; - lastError?: TalerErrorDetails; + lastError?: TalerErrorDetail; refreshGroupId: string; finishedPerCoin: boolean[]; retryInfo: RetryInfo; @@ -139,7 +139,7 @@ export interface PendingProposalDownloadTask { proposalTimestamp: TalerProtocolTimestamp; proposalId: string; orderId: string; - lastError?: TalerErrorDetails; + lastError?: TalerErrorDetail; retryInfo?: RetryInfo; } @@ -173,7 +173,7 @@ export interface PendingPayTask { proposalId: string; isReplay: boolean; retryInfo?: RetryInfo; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } /** @@ -184,14 +184,14 @@ export interface PendingRefundQueryTask { type: PendingTaskType.RefundQuery; proposalId: string; retryInfo: RetryInfo; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } export interface PendingRecoupTask { type: PendingTaskType.Recoup; recoupGroupId: string; retryInfo: RetryInfo; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; } /** @@ -199,7 +199,7 @@ export interface PendingRecoupTask { */ export interface PendingWithdrawTask { type: PendingTaskType.Withdraw; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; retryInfo: RetryInfo; withdrawalGroupId: string; } @@ -209,7 +209,7 @@ export interface PendingWithdrawTask { */ export interface PendingDepositTask { type: PendingTaskType.Deposit; - lastError: TalerErrorDetails | undefined; + lastError: TalerErrorDetail | undefined; retryInfo: RetryInfo | undefined; depositGroupId: string; } diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts index 79afd5707..31e38b609 100644 --- a/packages/taler-wallet-core/src/util/http.ts +++ b/packages/taler-wallet-core/src/util/http.ts @@ -24,16 +24,16 @@ /** * Imports */ -import { OperationFailedError, makeErrorDetails } from "../errors.js"; import { Logger, Duration, AbsoluteTime, - TalerErrorDetails, + TalerErrorDetail, Codec, j2s, } from "@gnu-taler/taler-util"; import { TalerErrorCode } from "@gnu-taler/taler-util"; +import { makeErrorDetail, TalerError } from "../errors.js"; const logger = new Logger("http.ts"); @@ -125,7 +125,7 @@ type ResponseOrError = export async function readTalerErrorResponse( httpResponse: HttpResponse, -): Promise { +): Promise { const errJson = await httpResponse.json(); const talerErrorCode = errJson.code; if (typeof talerErrorCode !== "number") { @@ -134,16 +134,14 @@ export async function readTalerErrorResponse( errJson, )}`, ); - throw new OperationFailedError( - makeErrorDetails( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Error response did not contain error code", - { - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - httpStatusCode: httpResponse.status, - }, - ), + 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; @@ -151,28 +149,28 @@ export async function readTalerErrorResponse( export async function readUnexpectedResponseDetails( httpResponse: HttpResponse, -): Promise { +): Promise { const errJson = await httpResponse.json(); const talerErrorCode = errJson.code; if (typeof talerErrorCode !== "number") { - return makeErrorDetails( + return makeErrorDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Error response did not contain error code", { requestUrl: httpResponse.requestUrl, requestMethod: httpResponse.requestMethod, httpStatusCode: httpResponse.status, }, + "Error response did not contain error code", ); } - return makeErrorDetails( + return makeErrorDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - `Unexpected HTTP status (${httpResponse.status}) in response`, { requestUrl: httpResponse.requestUrl, httpStatusCode: httpResponse.status, errorResponse: errJson, }, + `Unexpected HTTP status (${httpResponse.status}) in response`, ); } @@ -191,14 +189,14 @@ export async function readSuccessResponseJsonOrErrorCode( try { parsedResponse = codec.decode(respJson); } catch (e: any) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Response invalid", { requestUrl: httpResponse.requestUrl, httpStatusCode: httpResponse.status, validationError: e.toString(), }, + "Response invalid", ); } return { @@ -220,16 +218,14 @@ export function throwUnexpectedRequestError( httpResponse: HttpResponse, talerErrorResponse: TalerErrorResponse, ): never { - throw new OperationFailedError( - makeErrorDetails( - TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - `Unexpected HTTP status ${httpResponse.status} in response`, - { - requestUrl: httpResponse.requestUrl, - httpStatusCode: httpResponse.status, - errorResponse: talerErrorResponse, - }, - ), + throw TalerError.fromDetail( + TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, + { + requestUrl: httpResponse.requestUrl, + httpStatusCode: httpResponse.status, + errorResponse: talerErrorResponse, + }, + `Unexpected HTTP status ${httpResponse.status} in response`, ); } @@ -251,16 +247,14 @@ export async function readSuccessResponseTextOrErrorCode( const errJson = await httpResponse.json(); const talerErrorCode = errJson.code; if (typeof talerErrorCode !== "number") { - throw new OperationFailedError( - makeErrorDetails( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Error response did not contain error code", - { - httpStatusCode: httpResponse.status, - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - }, - ), + 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 { @@ -282,16 +276,14 @@ export async function checkSuccessResponseOrThrow( const errJson = await httpResponse.json(); const talerErrorCode = errJson.code; if (typeof talerErrorCode !== "number") { - throw new OperationFailedError( - makeErrorDetails( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Error response did not contain error code", - { - httpStatusCode: httpResponse.status, - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - }, - ), + 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); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index bbff465a8..cb8b53adf 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -100,9 +100,8 @@ import { WalletStoresV1, } from "./db.js"; import { - makeErrorDetails, - OperationFailedAndReportedError, - OperationFailedError, + getErrorDetailFromException, + TalerError, } from "./errors.js"; import { exportBackup } from "./operations/backup/export.js"; import { @@ -297,10 +296,10 @@ export async function runPending( try { await processOnePendingOperation(ws, p, forceNow); } catch (e) { - if (e instanceof OperationFailedAndReportedError) { + if (e instanceof TalerError) { console.error( "Operation failed:", - JSON.stringify(e.operationError, undefined, 2), + JSON.stringify(e.errorDetail, undefined, 2), ); } else { console.error(e); @@ -399,10 +398,16 @@ async function runTaskLoop( try { await processOnePendingOperation(ws, p); } catch (e) { - if (e instanceof OperationFailedAndReportedError) { - logger.warn("operation processed resulted in reported error"); - logger.warn(`reported error was: ${j2s(e.operationError)}`); + if ( + e instanceof TalerError && + e.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED) + ) { + logger.warn("operation processed resulted in error"); + logger.warn(`error was: ${j2s(e.errorDetail)}`); } else { + // This is a bug, as we expect pending operations to always + // do their own error handling and only throw WALLET_PENDING_OPERATION_FAILED + // or return something. logger.error("Uncaught exception", e); ws.notify({ type: NotificationType.InternalError, @@ -722,7 +727,7 @@ export async function getClientFromWalletState( const res = await handleCoreApiRequest(ws, op, `${id++}`, payload); switch (res.type) { case "error": - throw new OperationFailedError(res.error); + throw TalerError.fromUncheckedDetail(res.error); case "response": return res.result; } @@ -1040,12 +1045,12 @@ async function dispatchRequestInternal( return []; } } - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN, - "unknown operation", { operation, }, + "unknown operation", ); } @@ -1067,34 +1072,13 @@ export async function handleCoreApiRequest( result, }; } catch (e: any) { - if ( - e instanceof OperationFailedError || - e instanceof OperationFailedAndReportedError - ) { - logger.error("Caught operation failed error"); - logger.trace((e as any).stack); - return { - type: "error", - operation, - id, - error: e.operationError, - }; - } else { - try { - logger.error("Caught unexpected exception:"); - logger.error(e.stack); - } catch (e) {} - return { - type: "error", - operation, - id, - error: makeErrorDetails( - TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - `unexpected exception: ${e}`, - {}, - ), - }; - } + const err = getErrorDetailFromException(e); + return { + type: "error", + operation, + id, + error: err, + }; } } diff --git a/packages/taler-wallet-embedded/src/index.ts b/packages/taler-wallet-embedded/src/index.ts index e01281bc3..64b12f63c 100644 --- a/packages/taler-wallet-embedded/src/index.ts +++ b/packages/taler-wallet-embedded/src/index.ts @@ -21,7 +21,6 @@ import { getDefaultNodeWallet, DefaultNodeWalletArgs, NodeHttpLib, - makeErrorDetails, handleWorkerError, handleWorkerMessage, HttpRequestLibrary, @@ -33,6 +32,7 @@ import { WALLET_EXCHANGE_PROTOCOL_VERSION, WALLET_MERCHANT_PROTOCOL_VERSION, Wallet, + getErrorDetailFromException, } from "@gnu-taler/taler-wallet-core"; import fs from "fs"; @@ -270,11 +270,7 @@ export function installNativeWalletListener(): void { type: "error", id, operation, - error: makeErrorDetails( - TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - "unexpected exception", - {}, - ), + error: getErrorDetailFromException(e), }; sendNativeMessage(respMsg); return; diff --git a/packages/taler-wallet-webextension/src/browserHttpLib.ts b/packages/taler-wallet-webextension/src/browserHttpLib.ts index 8877edfc3..53ab85598 100644 --- a/packages/taler-wallet-webextension/src/browserHttpLib.ts +++ b/packages/taler-wallet-webextension/src/browserHttpLib.ts @@ -18,11 +18,11 @@ * Imports. */ import { - OperationFailedError, HttpRequestLibrary, HttpRequestOptions, HttpResponse, Headers, + TalerError, } from "@gnu-taler/taler-wallet-core"; import { Logger, @@ -49,14 +49,14 @@ export class BrowserHttpLib implements HttpRequestLibrary { if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) { const parsedUrl = new URL(requestUrl); - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, - `request to origin ${parsedUrl.origin} was throttled`, { requestMethod, requestUrl, throttleStats: this.throttle.getThrottleStats(requestUrl), }, + `request to origin ${parsedUrl.origin} was throttled`, ); } @@ -78,12 +78,12 @@ export class BrowserHttpLib implements HttpRequestLibrary { myRequest.onerror = (e) => { logger.error("http request error"); reject( - OperationFailedError.fromCode( + TalerError.fromDetail( TalerErrorCode.WALLET_NETWORK_ERROR, - "Could not make request", { requestUrl: requestUrl, }, + "Could not make request", ), ); }; @@ -91,12 +91,12 @@ export class BrowserHttpLib implements HttpRequestLibrary { myRequest.addEventListener("readystatechange", (e) => { if (myRequest.readyState === XMLHttpRequest.DONE) { if (myRequest.status === 0) { - const exc = OperationFailedError.fromCode( + const exc = TalerError.fromDetail( TalerErrorCode.WALLET_NETWORK_ERROR, - "HTTP request failed (status 0, maybe URI scheme was wrong?)", { requestUrl: requestUrl, }, + "HTTP request failed (status 0, maybe URI scheme was wrong?)", ); reject(exc); return; @@ -112,23 +112,23 @@ export class BrowserHttpLib implements HttpRequestLibrary { const responseString = td.decode(myRequest.response); responseJson = JSON.parse(responseString); } catch (e) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Invalid JSON from HTTP response", { requestUrl: requestUrl, httpStatusCode: myRequest.status, }, + "Invalid JSON from HTTP response", ); } if (responseJson === null || typeof responseJson !== "object") { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Invalid JSON from HTTP response", { requestUrl: requestUrl, httpStatusCode: myRequest.status, }, + "Invalid JSON from HTTP response", ); } return responseJson; diff --git a/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx b/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx index 356709091..38d6ec561 100644 --- a/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx +++ b/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx @@ -13,7 +13,7 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ -import { TalerErrorDetails } from "@gnu-taler/taler-util"; +import { TalerErrorDetail } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import arrowDown from "../../static/img/chevron-down.svg"; @@ -25,7 +25,7 @@ export function ErrorTalerOperation({ error, }: { title?: VNode; - error?: TalerErrorDetails; + error?: TalerErrorDetail; }): VNode | null { const { devMode } = useDevContext(); const [showErrorDetail, setShowErrorDetail] = useState(false); @@ -59,7 +59,7 @@ export function ErrorTalerOperation({
- {error.message} {!errorHint ? "" : `: ${errorHint}`}{" "} + {error.hint} {!errorHint ? "" : `: ${errorHint}`}{" "}
{devMode && ( diff --git a/packages/taler-wallet-webextension/src/cta/Deposit.tsx b/packages/taler-wallet-webextension/src/cta/Deposit.tsx index ac6c1fd6d..933195a9e 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit.tsx +++ b/packages/taler-wallet-webextension/src/cta/Deposit.tsx @@ -35,7 +35,7 @@ import { PreparePayResultType, Translate, } from "@gnu-taler/taler-util"; -import { OperationFailedError } from "@gnu-taler/taler-wallet-core"; +import { TalerError } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { ErrorTalerOperation } from "../components/ErrorTalerOperation"; @@ -64,9 +64,9 @@ export function DepositPage({ talerPayUri, goBack }: Props): VNode { const [payResult, setPayResult] = useState( undefined, ); - const [payErrMsg, setPayErrMsg] = useState< - OperationFailedError | string | undefined - >(undefined); + const [payErrMsg, setPayErrMsg] = useState( + undefined, + ); const balance = useAsyncAsHook(wxApi.getBalance, [ NotificationType.CoinWithdrawn, @@ -97,7 +97,7 @@ export function DepositPage({ talerPayUri, goBack }: Props): VNode { setPayStatus(p); } catch (e) { console.log("Got error while trying to pay", e); - if (e instanceof OperationFailedError) { + if (e instanceof TalerError) { setPayErrMsg(e); } if (e instanceof Error) { @@ -117,7 +117,7 @@ export function DepositPage({ talerPayUri, goBack }: Props): VNode { } if (!payStatus) { - if (payErrMsg instanceof OperationFailedError) { + if (payErrMsg instanceof TalerError) { return ( @@ -131,7 +131,7 @@ export function DepositPage({ talerPayUri, goBack }: Props): VNode { Could not get the payment information for this order } - error={payErrMsg?.operationError} + error={payErrMsg?.errorDetail} /> diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx index 2a0d1b46b..f6ae02f72 100644 --- a/packages/taler-wallet-webextension/src/cta/Pay.tsx +++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx @@ -36,7 +36,7 @@ import { PreparePayResultType, Product, } from "@gnu-taler/taler-util"; -import { OperationFailedError } from "@gnu-taler/taler-wallet-core"; +import { TalerError } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { ErrorMessage } from "../components/ErrorMessage"; @@ -93,7 +93,7 @@ export function PayPage({ undefined, ); const [payErrMsg, setPayErrMsg] = useState< - OperationFailedError | string | undefined + TalerError | string | undefined >(undefined); const hook = useAsyncAsHook(async () => { diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx index 66c83cf15..d58e2ec80 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx @@ -25,10 +25,8 @@ import { AmountJson, Amounts, ExchangeListItem, - Translate, WithdrawUriInfoResponse, } from "@gnu-taler/taler-util"; -import { OperationFailedError } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../components/Loading"; @@ -52,6 +50,7 @@ import { import * as wxApi from "../wxApi"; import { TermsOfServiceSection } from "./TermsOfServiceSection"; import { useTranslationContext } from "../context/translation"; +import { TalerError } from "@gnu-taler/taler-wallet-core"; interface Props { talerWithdrawUri?: string; @@ -85,9 +84,9 @@ export function View({ reviewed, }: ViewProps): VNode { const { i18n } = useTranslationContext(); - const [withdrawError, setWithdrawError] = useState< - OperationFailedError | undefined - >(undefined); + const [withdrawError, setWithdrawError] = useState( + undefined, + ); const [confirmDisabled, setConfirmDisabled] = useState(false); const needsReview = terms.status === "changed" || terms.status === "new"; @@ -109,7 +108,7 @@ export function View({ setConfirmDisabled(true); await onWithdraw(); } catch (e) { - if (e instanceof OperationFailedError) { + if (e instanceof TalerError) { setWithdrawError(e); } setConfirmDisabled(false); @@ -130,7 +129,7 @@ export function View({ Could not finish the withdrawal operation } - error={withdrawError.operationError} + error={withdrawError.errorDetail} /> )} diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts index 6efa1d181..8d31de94f 100644 --- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts +++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts @@ -13,28 +13,32 @@ You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ -import { NotificationType, TalerErrorDetails } from "@gnu-taler/taler-util"; +import { + NotificationType, + TalerErrorCode, + TalerErrorDetail, +} from "@gnu-taler/taler-util"; +import { TalerError } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; import * as wxApi from "../wxApi"; -import { OperationFailedError } from "@gnu-taler/taler-wallet-core"; interface HookOk { hasError: false; response: T; } -export type HookError = HookGenericError | HookOperationalError +export type HookError = HookGenericError | HookOperationalError; export interface HookGenericError { hasError: true; - operational: false, + operational: false; message: string; } export interface HookOperationalError { hasError: true; - operational: true, - details: TalerErrorDetails; + operational: true; + details: TalerErrorDetail; } export type HookResponse = HookOk | HookError | undefined; @@ -51,10 +55,18 @@ export function useAsyncAsHook( const response = await fn(); setHookResponse({ hasError: false, response }); } catch (e) { - if (e instanceof OperationFailedError) { - setHookResponse({ hasError: true, operational: true, details: e.operationError }); + if (e instanceof TalerError) { + setHookResponse({ + hasError: true, + operational: true, + details: e.errorDetail, + }); } else if (e instanceof Error) { - setHookResponse({ hasError: true, operational: false, message: e.message }); + setHookResponse({ + hasError: true, + operational: false, + message: e.message, + }); } } } diff --git a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts index 6f2585c1e..4dee28a6c 100644 --- a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts +++ b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts @@ -23,7 +23,7 @@ import { HttpRequestLibrary, HttpRequestOptions, HttpResponse, - OperationFailedError, + TalerError, } from "@gnu-taler/taler-wallet-core"; /** @@ -44,14 +44,14 @@ export class ServiceWorkerHttpLib implements HttpRequestLibrary { if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) { const parsedUrl = new URL(requestUrl); - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, - `request to origin ${parsedUrl.origin} was throttled`, { requestMethod, requestUrl, throttleStats: this.throttle.getThrottleStats(requestUrl), }, + `request to origin ${parsedUrl.origin} was throttled`, ); } @@ -107,13 +107,13 @@ function makeTextHandler(response: Response, requestUrl: string) { try { respText = await response.text(); } catch (e) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Invalid JSON from HTTP response", { requestUrl, httpStatusCode: response.status, }, + "Invalid JSON from HTTP response", ); } return respText; @@ -126,23 +126,23 @@ function makeJsonHandler(response: Response, requestUrl: string) { try { responseJson = await response.json(); } catch (e) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Invalid JSON from HTTP response", { requestUrl, httpStatusCode: response.status, }, + "Invalid JSON from HTTP response", ); } if (responseJson === null || typeof responseJson !== "object") { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - "Invalid JSON from HTTP response", { requestUrl, httpStatusCode: response.status, }, + "Invalid JSON from HTTP response", ); } return responseJson; diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 31b46d88d..2071f85e5 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -23,26 +23,48 @@ */ import { AcceptExchangeTosRequest, - AcceptManualWithdrawalResult, AcceptTipRequest, AcceptWithdrawalResponse, - AddExchangeRequest, AmountString, ApplyRefundResponse, BalancesResponse, CoinDumpJson, ConfirmPayResult, - CoreApiResponse, CreateDepositGroupRequest, CreateDepositGroupResponse, DeleteTransactionRequest, ExchangesListRespose, - GetExchangeTosResult, GetExchangeWithdrawalInfo, + AcceptManualWithdrawalResult, + AcceptTipRequest, + AcceptWithdrawalResponse, + AddExchangeRequest, + AmountString, + ApplyRefundResponse, + BalancesResponse, + CoinDumpJson, + ConfirmPayResult, + CoreApiResponse, + CreateDepositGroupRequest, + CreateDepositGroupResponse, + DeleteTransactionRequest, + ExchangesListRespose, + GetExchangeTosResult, + GetExchangeWithdrawalInfo, GetFeeForDepositRequest, - GetWithdrawalDetailsForUriRequest, KnownBankAccounts, NotificationType, PreparePayResult, PrepareTipRequest, - PrepareTipResult, RetryTransactionRequest, - SetWalletDeviceIdRequest, TransactionsResponse, WalletDiagnostics, WithdrawUriInfoResponse + GetWithdrawalDetailsForUriRequest, + KnownBankAccounts, + NotificationType, + PreparePayResult, + PrepareTipRequest, + PrepareTipResult, + RetryTransactionRequest, + SetWalletDeviceIdRequest, + TransactionsResponse, + WalletDiagnostics, + WithdrawUriInfoResponse, } from "@gnu-taler/taler-util"; import { - AddBackupProviderRequest, BackupInfo, OperationFailedError, + AddBackupProviderRequest, + BackupInfo, PendingOperationsResponse, - RemoveBackupProviderRequest + RemoveBackupProviderRequest, + TalerError, } from "@gnu-taler/taler-wallet-core"; import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits"; import type { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw"; import { MessageFromBackend } from "./wxBackend"; /** - * + * * @autor Florian Dold * @autor sebasjm */ @@ -88,7 +110,7 @@ async function callBackend(operation: string, payload: any): Promise { console.log("got response", resp); const r = resp as CoreApiResponse; if (r.type === "error") { - reject(new OperationFailedError(r.error)); + reject(TalerError.fromUncheckedDetail(r.error)); return; } resolve(r.result); @@ -127,15 +149,23 @@ export function resetDb(): Promise { return callBackend("reset-db", {}); } -export function getFeeForDeposit(depositPaytoUri: string, amount: AmountString): Promise { +export function getFeeForDeposit( + depositPaytoUri: string, + amount: AmountString, +): Promise { return callBackend("getFeeForDeposit", { - depositPaytoUri, amount + depositPaytoUri, + amount, } as GetFeeForDepositRequest); } -export function createDepositGroup(depositPaytoUri: string, amount: AmountString): Promise { +export function createDepositGroup( + depositPaytoUri: string, + amount: AmountString, +): Promise { return callBackend("createDepositGroup", { - depositPaytoUri, amount + depositPaytoUri, + amount, } as CreateDepositGroupRequest); } @@ -190,7 +220,9 @@ export function listKnownCurrencies(): Promise { export function listExchanges(): Promise { return callBackend("listExchanges", {}); } -export function listKnownBankAccounts(currency?: string): Promise { +export function listKnownBankAccounts( + currency?: string, +): Promise { return callBackend("listKnownBankAccounts", { currency }); } @@ -387,14 +419,17 @@ export function exportDB(): Promise { } export function importDB(dump: any): Promise { - return callBackend("importDb", { dump }) + return callBackend("importDb", { dump }); } -export function onUpdateNotification(messageTypes: Array, doCallback: () => void): () => void { +export function onUpdateNotification( + messageTypes: Array, + doCallback: () => void, +): () => void { // eslint-disable-next-line no-undef const port = chrome.runtime.connect({ name: "notifications" }); const listener = (message: MessageFromBackend): void => { - const shouldNotify = messageTypes.includes(message.type) + const shouldNotify = messageTypes.includes(message.type); if (shouldNotify) { doCallback(); } diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index e158d2946..b7a0cdc54 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -35,7 +35,7 @@ import { import { DbAccess, deleteTalerDatabase, - makeErrorDetails, + makeErrorDetail, OpenedPromise, openPromise, openTalerDatabase, @@ -167,10 +167,10 @@ async function dispatch( type: "error", id: req.id, operation: req.operation, - error: makeErrorDetails( + error: makeErrorDetail( TalerErrorCode.WALLET_CORE_NOT_AVAILABLE, - "wallet core not available", {}, + "wallet core not available", ), }; break; @@ -233,7 +233,10 @@ function makeSyncWalletRedirect( const tab = await getTab(tabId); if (tab.url === oldUrl) { console.log("redirecting to", innerUrl.href); - chrome.tabs.update(tabId, { url: innerUrl.href, loadReplace: true } as any); + chrome.tabs.update(tabId, { + url: innerUrl.href, + loadReplace: true, + } as any); } }; doit(); -- cgit v1.2.3