summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-03-22 21:16:38 +0100
committerFlorian Dold <florian@dold.me>2022-03-22 21:16:38 +0100
commit5d23eb36354d07508a015531f298b3e261bbafce (patch)
treefae0d2599c94d88c9264bb63a301adb1706824c1 /packages
parentf8d12f7b0d4af1b1769b89e80c87f9c169678564 (diff)
downloadwallet-core-5d23eb36354d07508a015531f298b3e261bbafce.tar.gz
wallet-core-5d23eb36354d07508a015531f298b3e261bbafce.tar.bz2
wallet-core-5d23eb36354d07508a015531f298b3e261bbafce.zip
wallet: improve error handling and error codes
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/notifications.ts26
-rw-r--r--packages/taler-util/src/taler-error-codes.ts70
-rw-r--r--packages/taler-util/src/transactionsTypes.ts4
-rw-r--r--packages/taler-util/src/walletTypes.ts14
-rw-r--r--packages/taler-wallet-cli/src/harness/harness.ts14
-rw-r--r--packages/taler-wallet-cli/src/index.ts21
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts5
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts32
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts18
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts2
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts30
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts8
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts6
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts4
-rw-r--r--packages/taler-wallet-core/src/bank-api-client.ts19
-rw-r--r--packages/taler-wallet-core/src/db.ts28
-rw-r--r--packages/taler-wallet-core/src/errors.ts216
-rw-r--r--packages/taler-wallet-core/src/headless/NodeHttpLib.ts46
-rw-r--r--packages/taler-wallet-core/src/operations/backup/index.ts10
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts22
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts106
-rw-r--r--packages/taler-wallet-core/src/operations/recoup.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/refund.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/reserves.ts13
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts16
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts35
-rw-r--r--packages/taler-wallet-core/src/pending-types.ts20
-rw-r--r--packages/taler-wallet-core/src/util/http.ts92
-rw-r--r--packages/taler-wallet-core/src/wallet.ts62
-rw-r--r--packages/taler-wallet-embedded/src/index.ts8
-rw-r--r--packages/taler-wallet-webextension/src/browserHttpLib.ts22
-rw-r--r--packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx6
-rw-r--r--packages/taler-wallet-webextension/src/cta/Deposit.tsx14
-rw-r--r--packages/taler-wallet-webextension/src/cta/Pay.tsx4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw.tsx13
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts30
-rw-r--r--packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts18
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts73
-rw-r--r--packages/taler-wallet-webextension/src/wxBackend.ts11
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).
@@ -79,6 +81,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).
* (A value of 0 indicates that the error is generated client-side).
@@ -373,6 +382,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).
* (A value of 0 indicates that the error is generated client-side).
@@ -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.
@@ -1395,6 +1432,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).
* (A value of 0 indicates that the error is generated client-side).
@@ -2151,6 +2209,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).
* (A value of 0 indicates that the error is generated client-side).
@@ -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<void>,
- ): Promise<OperationFailedError> {
+ ): Promise<TalerError> {
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<T>(
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<string, unknown>,
- ): 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> = Y extends keyof DetailsMap ? DetailsMap[Y] : never;
- // Set the prototype explicitly.
- Object.setPrototypeOf(this, OperationFailedAndReportedError.prototype);
- }
+export function makeErrorDetail<C extends TalerErrorCode>(
+ code: C,
+ detail: ErrBody<C>,
+ 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<string, unknown>,
- ): 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<T = any> 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<C extends TalerErrorCode>(
+ code: C,
+ detail: ErrBody<C>,
+ hint?: string,
+ ): TalerError {
+ // FIXME: include default hint?
+ return new TalerError<unknown>({ code, hint, ...detail });
+ }
- // Set the prototype explicitly.
- Object.setPrototypeOf(this, OperationFailedError.prototype);
+ static fromUncheckedDetail(d: TalerErrorDetail): TalerError {
+ return new TalerError<unknown>({ ...d });
+ }
+
+ static fromException(e: any): TalerError {
+ const errDetail = getErrorDetailFromException(e);
+ return new TalerError(errDetail);
+ }
+
+ hasErrorCode<C extends keyof DetailsMap>(
+ code: C,
+ ): this is TalerError<DetailsMap[C]> {
+ return this.errorDetail.code === code;
}
}
-export function makeErrorDetails(
- ec: TalerErrorCode,
- message: string,
- details: Record<string, unknown>,
-): 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<T>(
op: () => Promise<T>,
- onOpError: (e: TalerErrorDetails) => Promise<void>,
+ onOpError: (e: TalerErrorDetail) => Promise<void>,
): Promise<T> {
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<void> {
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<void> {
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<void> =>
+ const onOpErr = (err: TalerErrorDetail): Promise<void> =>
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<void> {
await ws.db
.mktx((x) => ({ depositGroups: x.depositGroups }))
@@ -111,7 +111,7 @@ export async function processDepositGroup(
forceNow = false,
): Promise<void> {
await ws.memoProcessDeposit.memo(depositGroupId, async () => {
- const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+ const onOpErr = (e: TalerErrorDetail): Promise<void> =>
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<void> {
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<void> =>
+ const onOpErr = (e: TalerErrorDetail): Promise<void> =>
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<void> {
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<void> {
await ws.db
.mktx((x) => ({ purchases: x.purchases }))
@@ -575,7 +573,7 @@ export async function processDownloadProposal(
proposalId: string,
forceNow = false,
): Promise<void> {
- const onOpErr = (err: TalerErrorDetails): Promise<void> =>
+ const onOpErr = (err: TalerErrorDetail): Promise<void> =>
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<void> {
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<void> {
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<ConfirmPayResult> {
- const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+ const onOpErr = (e: TalerErrorDetail): Promise<void> =>
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<void> {
await ws.db
.mktx((x) => ({
@@ -384,7 +384,7 @@ export async function processRecoupGroup(
forceNow = false,
): Promise<void> {
await ws.memoProcessRecoup.memo(recoupGroupId, async () => {
- const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+ const onOpErr = (e: TalerErrorDetail): Promise<void> =>
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<void> {
await ws.db
.mktx((x) => ({
@@ -747,7 +747,7 @@ export async function processRefreshGroup(
forceNow = false,
): Promise<void> {
await ws.memoProcessRefresh.memo(refreshGroupId, async () => {
- const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+ const onOpErr = (e: TalerErrorDetail): Promise<void> =>
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<void> {
await ws.db
.mktx((x) => ({
@@ -592,7 +592,7 @@ export async function processPurchaseQueryRefund(
proposalId: string,
forceNow = false,
): Promise<void> {
- const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+ const onOpErr = (e: TalerErrorDetail): Promise<void> =>
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<void> {
await ws.db
.mktx((x) => ({
@@ -338,7 +338,7 @@ export async function processReserve(
forceNow = false,
): Promise<void> {
return ws.memoProcessReserve.memo(reservePub, async () => {
- const onOpError = (err: TalerErrorDetails): Promise<void> =>
+ const onOpError = (err: TalerErrorDetail): Promise<void> =>
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<void> {
await ws.db
.mktx((x) => ({
@@ -192,7 +192,7 @@ export async function processTip(
tipId: string,
forceNow = false,
): Promise<void> {
- const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+ const onOpErr = (e: TalerErrorDetail): Promise<void> =>
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<void> {
await ws.db
.mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
@@ -821,7 +820,7 @@ export async function processWithdrawGroup(
withdrawalGroupId: string,
forceNow = false,
): Promise<void> {
- const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+ const onOpErr = (e: TalerErrorDetail): Promise<void> =>
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<number, TalerErrorDetails> = {};
+ let errorsPerCoin: Record<number, TalerErrorDetail> = {};
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<T> =
export async function readTalerErrorResponse(
httpResponse: HttpResponse,
-): Promise<TalerErrorDetails> {
+): Promise<TalerErrorDetail> {
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<TalerErrorDetails> {
+): Promise<TalerErrorDetail> {
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<T>(
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<T>(
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 <http://www.gnu.org/licenses/>
*/
-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({
<Fragment>
<div style={{ padding: 5, textAlign: "left" }}>
<div>
- <b>{error.message}</b> {!errorHint ? "" : `: ${errorHint}`}{" "}
+ <b>{error.hint}</b> {!errorHint ? "" : `: ${errorHint}`}{" "}
</div>
</div>
{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<ConfirmPayResult | undefined>(
undefined,
);
- const [payErrMsg, setPayErrMsg] = useState<
- OperationFailedError | string | undefined
- >(undefined);
+ const [payErrMsg, setPayErrMsg] = useState<TalerError | string | undefined>(
+ 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 (
<WalletAction>
<LogoHeader />
@@ -131,7 +131,7 @@ export function DepositPage({ talerPayUri, goBack }: Props): VNode {
Could not get the payment information for this order
</i18n.Translate>
}
- error={payErrMsg?.operationError}
+ error={payErrMsg?.errorDetail}
/>
</section>
</WalletAction>
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<TalerError | undefined>(
+ undefined,
+ );
const [confirmDisabled, setConfirmDisabled] = useState<boolean>(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
</i18n.Translate>
}
- 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 <http://www.gnu.org/licenses/>
*/
-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<T> {
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<T> = HookOk<T> | HookError | undefined;
@@ -51,10 +55,18 @@ export function useAsyncAsHook<T>(
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<any> {
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<void> {
return callBackend("reset-db", {});
}
-export function getFeeForDeposit(depositPaytoUri: string, amount: AmountString): Promise<DepositFee> {
+export function getFeeForDeposit(
+ depositPaytoUri: string,
+ amount: AmountString,
+): Promise<DepositFee> {
return callBackend("getFeeForDeposit", {
- depositPaytoUri, amount
+ depositPaytoUri,
+ amount,
} as GetFeeForDepositRequest);
}
-export function createDepositGroup(depositPaytoUri: string, amount: AmountString): Promise<CreateDepositGroupResponse> {
+export function createDepositGroup(
+ depositPaytoUri: string,
+ amount: AmountString,
+): Promise<CreateDepositGroupResponse> {
return callBackend("createDepositGroup", {
- depositPaytoUri, amount
+ depositPaytoUri,
+ amount,
} as CreateDepositGroupRequest);
}
@@ -190,7 +220,9 @@ export function listKnownCurrencies(): Promise<ListOfKnownCurrencies> {
export function listExchanges(): Promise<ExchangesListRespose> {
return callBackend("listExchanges", {});
}
-export function listKnownBankAccounts(currency?: string): Promise<KnownBankAccounts> {
+export function listKnownBankAccounts(
+ currency?: string,
+): Promise<KnownBankAccounts> {
return callBackend("listKnownBankAccounts", { currency });
}
@@ -387,14 +419,17 @@ export function exportDB(): Promise<any> {
}
export function importDB(dump: any): Promise<void> {
- return callBackend("importDb", { dump })
+ return callBackend("importDb", { dump });
}
-export function onUpdateNotification(messageTypes: Array<NotificationType>, doCallback: () => void): () => void {
+export function onUpdateNotification(
+ messageTypes: Array<NotificationType>,
+ 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();