summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core
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/taler-wallet-core
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/taler-wallet-core')
-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
17 files changed, 377 insertions, 332 deletions
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,
+ };
}
}