wallet: improve error handling and error codes
This commit is contained in:
parent
f8d12f7b0d
commit
5d23eb3635
@ -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 {
|
||||
|
@ -22,6 +22,8 @@
|
||||
*/
|
||||
|
||||
export enum TalerErrorCode {
|
||||
|
||||
|
||||
/**
|
||||
* Special code to indicate success (no error).
|
||||
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
||||
@ -78,6 +80,13 @@ export enum TalerErrorCode {
|
||||
*/
|
||||
GENERIC_CONFIGURATION_INVALID = 14,
|
||||
|
||||
/**
|
||||
* The client made a request to a service, but received an error response it does not know how to handle.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
GENERIC_UNEXPECTED_REQUEST_ERROR = 15,
|
||||
|
||||
/**
|
||||
* The HTTP method used is invalid for this endpoint.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_METHOD_NOT_ALLOWED (405).
|
||||
@ -372,6 +381,20 @@ export enum TalerErrorCode {
|
||||
*/
|
||||
EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE = 1018,
|
||||
|
||||
/**
|
||||
* The reserve public key was malformed.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_GENERIC_RESERVE_PUB_MALFORMED = 1019,
|
||||
|
||||
/**
|
||||
* The time at the server is too far off from the time specified in the request. Most likely the client system time is wrong.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_GENERIC_CLOCK_SKEW = 1020,
|
||||
|
||||
/**
|
||||
* The exchange did not find information about the specified transaction in the database.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
|
||||
@ -541,11 +564,25 @@ export enum TalerErrorCode {
|
||||
EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT = 1222,
|
||||
|
||||
/**
|
||||
* The reserve status was requested using a unknown key.
|
||||
* The reserve balance, status or history was requested for a reserve which is not known to the exchange.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_RESERVES_GET_STATUS_UNKNOWN = 1250,
|
||||
EXCHANGE_RESERVES_STATUS_UNKNOWN = 1250,
|
||||
|
||||
/**
|
||||
* The reserve status was requested with a bad signature.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_RESERVES_STATUS_BAD_SIGNATURE = 1251,
|
||||
|
||||
/**
|
||||
* The reserve history was requested with a bad signature.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
EXCHANGE_RESERVES_HISTORY_BAD_SIGNATURE = 1252,
|
||||
|
||||
/**
|
||||
* The exchange encountered melt fees exceeding the melted coin's contribution.
|
||||
@ -1394,6 +1431,27 @@ export enum TalerErrorCode {
|
||||
*/
|
||||
MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_FAILED = 2170,
|
||||
|
||||
/**
|
||||
* The payment required a minimum age but one of the coins (of a denomination with support for age restriction) did not provide any age_commitment.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING = 2171,
|
||||
|
||||
/**
|
||||
* The payment required a minimum age but one of the coins provided an age_commitment that contained a wrong number of public keys compared to the number of age groups defined in the denomination of the coin.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH = 2172,
|
||||
|
||||
/**
|
||||
* The payment required a minimum age but one of the coins provided a minimum_age_sig that couldn't be verified with the given age_commitment for that particular minimum age.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED = 2173,
|
||||
|
||||
/**
|
||||
* The contract hash does not match the given order ID.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
|
||||
@ -2150,6 +2208,13 @@ export enum TalerErrorCode {
|
||||
*/
|
||||
WALLET_CONTRACT_TERMS_MALFORMED = 7020,
|
||||
|
||||
/**
|
||||
* A pending operation failed, and thus the request can't be completed.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
WALLET_PENDING_OPERATION_FAILED = 7021,
|
||||
|
||||
/**
|
||||
* We encountered a timeout with our payment backend.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
|
||||
@ -2646,4 +2711,5 @@ export enum TalerErrorCode {
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
END = 9999,
|
||||
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
constructor(public operationError: TalerErrorDetails) {
|
||||
super(operationError.message);
|
||||
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, OperationFailedAndReportedError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
constructor(public operationError: TalerErrorDetails) {
|
||||
super(operationError.message);
|
||||
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, OperationFailedError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export function makeErrorDetails(
|
||||
ec: TalerErrorCode,
|
||||
message: string,
|
||||
details: Record<string, unknown>,
|
||||
): TalerErrorDetails {
|
||||
return {
|
||||
code: ec,
|
||||
hint: `Error: ${TalerErrorCode[ec]}`,
|
||||
details: details,
|
||||
message,
|
||||
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]: {};
|
||||
}
|
||||
|
||||
type ErrBody<Y> = Y extends keyof DetailsMap ? DetailsMap[Y] : never;
|
||||
|
||||
export function makeErrorDetail<C extends TalerErrorCode>(
|
||||
code: C,
|
||||
detail: ErrBody<C>,
|
||||
hint?: string,
|
||||
): TalerErrorDetail {
|
||||
// FIXME: include default hint?
|
||||
return { code, hint, ...detail };
|
||||
}
|
||||
|
||||
export function makePendingOperationFailedError(
|
||||
innerError: TalerErrorDetail,
|
||||
tag: TransactionType,
|
||||
uid: string,
|
||||
): TalerError {
|
||||
return TalerError.fromDetail(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED, {
|
||||
innerError,
|
||||
transactionId: `${tag}:${uid}`,
|
||||
});
|
||||
}
|
||||
|
||||
export class TalerError<T = any> extends Error {
|
||||
errorDetail: TalerErrorDetail & T;
|
||||
private constructor(d: TalerErrorDetail & T) {
|
||||
super();
|
||||
this.errorDetail = d;
|
||||
Object.setPrototypeOf(this, TalerError.prototype);
|
||||
}
|
||||
|
||||
static fromDetail<C extends TalerErrorCode>(
|
||||
code: C,
|
||||
detail: ErrBody<C>,
|
||||
hint?: string,
|
||||
): TalerError {
|
||||
// FIXME: include default hint?
|
||||
return new TalerError<unknown>({ code, hint, ...detail });
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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[];
|
||||
|
@ -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),
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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",
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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)`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 && (
|
||||
|
@ -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>
|
||||
|
@ -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 () => {
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user