summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-03-29 13:47:32 +0200
committerFlorian Dold <florian@dold.me>2022-03-29 13:48:51 +0200
commitc265e7d019d445add2d2cfb7cfcbdeee059684d3 (patch)
tree6904e9cdb723992728ed511103be65094ca50034 /packages/taler-wallet-core
parentbe489b6b3ea4214f546dfc33d6bb0f39ce82b5ab (diff)
downloadwallet-core-c265e7d019d445add2d2cfb7cfcbdeee059684d3.tar.gz
wallet-core-c265e7d019d445add2d2cfb7cfcbdeee059684d3.tar.bz2
wallet-core-c265e7d019d445add2d2cfb7cfcbdeee059684d3.zip
wallet: make retries more robust and consistent
Diffstat (limited to 'packages/taler-wallet-core')
-rw-r--r--packages/taler-wallet-core/src/headless/NodeHttpLib.ts20
-rw-r--r--packages/taler-wallet-core/src/internal-wallet-state.ts21
-rw-r--r--packages/taler-wallet-core/src/operations/README.md2
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts18
-rw-r--r--packages/taler-wallet-core/src/operations/backup/index.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/common.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts85
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts58
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts117
-rw-r--r--packages/taler-wallet-core/src/operations/pending.ts11
-rw-r--r--packages/taler-wallet-core/src/operations/recoup.ts88
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts85
-rw-r--r--packages/taler-wallet-core/src/operations/refund.ts84
-rw-r--r--packages/taler-wallet-core/src/operations/reserves.ts83
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts62
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts81
-rw-r--r--packages/taler-wallet-core/src/util/http.ts2
-rw-r--r--packages/taler-wallet-core/src/util/retries.ts4
-rw-r--r--packages/taler-wallet-core/src/wallet.ts64
19 files changed, 459 insertions, 434 deletions
diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
index df25a1092..5290bd441 100644
--- a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
+++ b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
@@ -20,6 +20,7 @@
* Imports.
*/
import {
+ DEFAULT_REQUEST_TIMEOUT_MS,
Headers,
HttpRequestLibrary,
HttpRequestOptions,
@@ -65,13 +66,16 @@ export class NodeHttpLib implements HttpRequestLibrary {
`request to origin ${parsedUrl.origin} was throttled`,
);
}
- let timeout: number | undefined;
+ let timeoutMs: number | undefined;
if (typeof opt?.timeout?.d_ms === "number") {
- timeout = opt.timeout.d_ms;
+ timeoutMs = opt.timeout.d_ms;
+ } else {
+ timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
}
+ // FIXME: Use AbortController / etc. to handle cancellation
let resp: AxiosResponse;
try {
- resp = await Axios({
+ let respPromise = Axios({
method,
url: url,
responseType: "arraybuffer",
@@ -79,9 +83,13 @@ export class NodeHttpLib implements HttpRequestLibrary {
validateStatus: () => true,
transformResponse: (x) => x,
data: body,
- timeout,
+ timeout: timeoutMs,
maxRedirects: 0,
});
+ if (opt?.cancellationToken) {
+ respPromise = opt.cancellationToken.racePromise(respPromise);
+ }
+ resp = await respPromise;
} catch (e: any) {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_NETWORK_ERROR,
@@ -94,11 +102,13 @@ export class NodeHttpLib implements HttpRequestLibrary {
}
const makeText = async (): Promise<string> => {
+ opt?.cancellationToken?.throwIfCancelled();
const respText = new Uint8Array(resp.data);
return bytesToString(respText);
};
const makeJson = async (): Promise<any> => {
+ opt?.cancellationToken?.throwIfCancelled();
let responseJson;
const respText = await makeText();
try {
@@ -130,6 +140,7 @@ export class NodeHttpLib implements HttpRequestLibrary {
return responseJson;
};
const makeBytes = async () => {
+ opt?.cancellationToken?.throwIfCancelled();
if (typeof resp.data.byteLength !== "number") {
throw Error("expected array buffer");
}
@@ -150,6 +161,7 @@ export class NodeHttpLib implements HttpRequestLibrary {
bytes: makeBytes,
};
}
+
async get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
return this.fetch(url, {
method: "GET",
diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts
index 6b964cdf7..bfd006d3d 100644
--- a/packages/taler-wallet-core/src/internal-wallet-state.ts
+++ b/packages/taler-wallet-core/src/internal-wallet-state.ts
@@ -77,7 +77,9 @@ export interface ReserveOperations {
processReserve(
ws: InternalWalletState,
reservePub: string,
- forceNow?: boolean,
+ options?: {
+ forceNow?: boolean;
+ },
): Promise<void>;
}
@@ -101,8 +103,10 @@ export interface ExchangeOperations {
updateExchangeFromUrl(
ws: InternalWalletState,
baseUrl: string,
- acceptedFormat?: string[],
- forceNow?: boolean,
+ options?: {
+ forceNow?: boolean;
+ cancellationToken?: CancellationToken;
+ },
): Promise<{
exchange: ExchangeRecord;
exchangeDetails: ExchangeDetailsRecord;
@@ -123,7 +127,9 @@ export interface RecoupOperations {
processRecoupGroup(
ws: InternalWalletState,
recoupGroupId: string,
- forceNow?: boolean,
+ options?: {
+ forceNow?: boolean;
+ },
): Promise<void>;
}
@@ -201,13 +207,8 @@ export interface InternalWalletState {
memoGetBalance: AsyncOpMemoSingle<BalancesResponse>;
memoProcessRefresh: AsyncOpMemoMap<void>;
memoProcessRecoup: AsyncOpMemoMap<void>;
- cryptoApi: TalerCryptoInterface;
- /**
- * Cancellation token for the currently running
- * deposit operation, if any.
- */
- taskCancellationSourceForDeposit?: CancellationToken.Source;
+ cryptoApi: TalerCryptoInterface;
timerGroup: TimerGroup;
stopped: boolean;
diff --git a/packages/taler-wallet-core/src/operations/README.md b/packages/taler-wallet-core/src/operations/README.md
index 426f2c553..9a2937c0c 100644
--- a/packages/taler-wallet-core/src/operations/README.md
+++ b/packages/taler-wallet-core/src/operations/README.md
@@ -17,7 +17,7 @@ Generally, the code to process a pending operation should first increment the
retryInfo (and reset the lastError) and then process the operation. This way,
it is impossble to forget incrementing the retryInfo.
-For each retriable operation, there are usually `reset<Op>Retry`, `increment<Op>Retry` and
+For each retriable operation, there are usually `setup<Op>Retry`, `increment<Op>Retry` and
`report<Op>Error` operations.
Note that this means that _during_ some operation, lastError will be cleared. The UI
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts
index ece0781a8..37e97fbc8 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -57,7 +57,7 @@ import {
checkLogicInvariant,
} from "../../util/invariants.js";
import { Logger } from "@gnu-taler/taler-util";
-import { initRetryInfo } from "../../util/retries.js";
+import { resetRetryInfo } from "../../util/retries.js";
import { InternalWalletState } from "../../internal-wallet-state.js";
import { provideBackupState } from "./state.js";
import { makeEventId, TombstoneTag } from "../transactions.js";
@@ -276,7 +276,7 @@ export async function importBackup(
protocolVersionRange: backupExchange.protocol_version_range,
},
permanent: true,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
lastUpdate: undefined,
nextUpdate: TalerProtocolTimestamp.now(),
nextRefreshCheck: TalerProtocolTimestamp.now(),
@@ -464,7 +464,7 @@ export async function importBackup(
timestampReserveInfoPosted:
backupReserve.bank_info?.timestamp_reserve_info_posted,
senderWire: backupReserve.sender_wire,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
lastError: undefined,
initialWithdrawalGroupId:
backupReserve.initial_withdrawal_group_id,
@@ -505,7 +505,7 @@ export async function importBackup(
backupWg.raw_withdrawal_amount,
),
reservePub,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
secretSeed: backupWg.secret_seed,
timestampStart: backupWg.timestamp_created,
timestampFinish: backupWg.timestamp_finish,
@@ -618,7 +618,7 @@ export async function importBackup(
cryptoComp.proposalNoncePrivToPub[backupProposal.nonce_priv],
proposalId: backupProposal.proposal_id,
repurchaseProposalId: backupProposal.repurchase_proposal_id,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
download,
proposalStatus,
});
@@ -753,7 +753,7 @@ export async function importBackup(
cryptoComp.proposalNoncePrivToPub[backupPurchase.nonce_priv],
lastPayError: undefined,
autoRefundDeadline: TalerProtocolTimestamp.never(),
- refundStatusRetryInfo: initRetryInfo(),
+ refundStatusRetryInfo: resetRetryInfo(),
lastRefundStatusError: undefined,
timestampAccept: backupPurchase.timestamp_accept,
timestampFirstSuccessfulPay:
@@ -763,7 +763,7 @@ export async function importBackup(
lastSessionId: undefined,
abortStatus,
// FIXME!
- payRetryInfo: initRetryInfo(),
+ payRetryInfo: resetRetryInfo(),
download,
paymentSubmitPending:
!backupPurchase.timestamp_first_successful_pay,
@@ -864,7 +864,7 @@ export async function importBackup(
Amounts.parseOrThrow(x.estimated_output_amount),
),
refreshSessionPerCoin,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
});
}
}
@@ -890,7 +890,7 @@ export async function importBackup(
merchantBaseUrl: backupTip.exchange_base_url,
merchantTipId: backupTip.merchant_tip_id,
pickedUpTimestamp: backupTip.timestamp_finished,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
secretSeed: backupTip.secret_seed,
tipAmountEffective: denomsSel.totalCoinValue,
tipAmountRaw: Amounts.parseOrThrow(backupTip.tip_amount_raw),
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts
index 5013b9032..0b2bd7b80 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -89,7 +89,7 @@ import {
checkLogicInvariant,
} from "../../util/invariants.js";
import { GetReadWriteAccess } from "../../util/query.js";
-import { initRetryInfo, updateRetryInfoTimeout } from "../../util/retries.js";
+import { resetRetryInfo, updateRetryInfoTimeout } from "../../util/retries.js";
import {
checkPaymentByProposalId,
confirmPay,
@@ -434,7 +434,7 @@ async function runBackupCycleForProvider(
// FIXME: Allocate error code for this situation?
prov.state = {
tag: BackupProviderStateTag.Retrying,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
};
await tx.backupProvider.put(prov);
});
@@ -478,7 +478,7 @@ async function incrementBackupRetryInTx(
} else if (pr.state.tag === BackupProviderStateTag.Ready) {
pr.state = {
tag: BackupProviderStateTag.Retrying,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
lastError: err,
};
}
diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts
index 0fdde9dca..6d54503a1 100644
--- a/packages/taler-wallet-core/src/operations/common.ts
+++ b/packages/taler-wallet-core/src/operations/common.ts
@@ -47,7 +47,7 @@ export async function guardOperationException<T>(
throw TalerError.fromDetail(
TalerErrorCode.WALLET_PENDING_OPERATION_FAILED,
{
- innerError: e.errorDetail,
+ innerError: opErr,
},
);
}
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts
index c11c45dc6..075d9d738 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -41,11 +41,11 @@ import {
TrackDepositGroupResponse,
URL,
} from "@gnu-taler/taler-util";
-import { DepositGroupRecord, OperationStatus } from "../db.js";
+import { DepositGroupRecord, OperationStatus, WireFee } from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { PayCoinSelection, selectPayCoins } from "../util/coinSelection.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
-import { initRetryInfo, RetryInfo } from "../util/retries.js";
+import { resetRetryInfo, RetryInfo } from "../util/retries.js";
import { guardOperationException } from "./common.js";
import { getExchangeDetails } from "./exchanges.js";
import {
@@ -63,9 +63,15 @@ import { getTotalRefreshCost } from "./refresh.js";
*/
const logger = new Logger("deposits.ts");
-async function resetDepositGroupRetry(
+/**
+ * Set up the retry timeout for a deposit group.
+ */
+async function setupDepositGroupRetry(
ws: InternalWalletState,
depositGroupId: string,
+ options: {
+ resetRetry: boolean;
+ },
): Promise<void> {
await ws.db
.mktx((x) => ({
@@ -76,29 +82,19 @@ async function resetDepositGroupRetry(
if (!x) {
return;
}
- x.retryInfo = initRetryInfo();
+ if (options.resetRetry) {
+ x.retryInfo = resetRetryInfo();
+ } else {
+ x.retryInfo = RetryInfo.increment(x.retryInfo);
+ }
delete x.lastError;
await tx.depositGroups.put(x);
});
}
-async function incrementDepositGroupRetry(
- ws: InternalWalletState,
- depositGroupId: string,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({ depositGroups: x.depositGroups }))
- .runReadWrite(async (tx) => {
- const r = await tx.depositGroups.get(depositGroupId);
- if (!r) {
- return;
- }
- r.retryInfo = RetryInfo.increment(r.retryInfo);
- delete r.lastError;
- await tx.depositGroups.put(r);
- });
-}
-
+/**
+ * Report an error that occurred while processing the deposit group.
+ */
async function reportDepositGroupError(
ws: InternalWalletState,
depositGroupId: string,
@@ -131,9 +127,6 @@ export async function processDepositGroup(
cancellationToken?: CancellationToken;
} = {},
): Promise<void> {
- if (ws.taskCancellationSourceForDeposit) {
- ws.taskCancellationSourceForDeposit.cancel();
- }
const onOpErr = (err: TalerErrorDetail): Promise<void> =>
reportDepositGroupError(ws, depositGroupId, err);
return await guardOperationException(
@@ -170,11 +163,7 @@ async function processDepositGroupImpl(
return;
}
- if (forceNow) {
- await resetDepositGroupRetry(ws, depositGroupId);
- } else {
- await incrementDepositGroupRetry(ws, depositGroupId);
- }
+ await setupDepositGroupRetry(ws, depositGroupId, { resetRetry: forceNow });
const contractData = extractContractData(
depositGroup.contractTermsRaw,
@@ -315,7 +304,7 @@ export async function trackDepositGroup(
export async function getFeeForDeposit(
ws: InternalWalletState,
req: GetFeeForDepositRequest,
-): Promise<DepositFee> {
+): Promise<DepositGroupFees> {
const p = parsePaytoUri(req.depositPaytoUri);
if (!p) {
throw Error("invalid payto URI");
@@ -370,7 +359,7 @@ export async function getFeeForDeposit(
throw Error("insufficient funds");
}
- return await getTotalFeeForDepositAmount(
+ return await getTotalFeesForDepositAmount(
ws,
p.targetType,
amount,
@@ -429,14 +418,12 @@ export async function createDepositGroup(
nonce: noncePair.pub,
wire_transfer_deadline: nowRounded,
order_id: "",
- // This is always the v2 wire hash, as we're the "merchant" and support v2.
h_wire: wireHash,
- // Required for older exchanges.
pay_deadline: AbsoluteTime.toTimestamp(
AbsoluteTime.addDuration(now, durationFromSpec({ hours: 1 })),
),
merchant: {
- name: "",
+ name: "(wallet)",
},
merchant_pub: merchantPair.pub,
refund_deadline: TalerProtocolTimestamp.zero(),
@@ -505,7 +492,7 @@ export async function createDepositGroup(
payto_uri: req.depositPaytoUri,
salt: wireSalt,
},
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
operationStatus: OperationStatus.Pending,
lastError: undefined,
};
@@ -594,8 +581,7 @@ export async function getEffectiveDepositAmount(
return Amounts.sub(Amounts.sum(amt).amount, Amounts.sum(fees).amount).amount;
}
-// FIXME: rename to DepositGroupFee
-export interface DepositFee {
+export interface DepositGroupFees {
coin: AmountJson;
wire: AmountJson;
refresh: AmountJson;
@@ -605,12 +591,12 @@ export interface DepositFee {
* Get the fee amount that will be charged when trying to deposit the
* specified amount using the selected coins and the wire method.
*/
-export async function getTotalFeeForDepositAmount(
+export async function getTotalFeesForDepositAmount(
ws: InternalWalletState,
wireType: string,
total: AmountJson,
pcs: PayCoinSelection,
-): Promise<DepositFee> {
+): Promise<DepositGroupFees> {
const wireFee: AmountJson[] = [];
const coinFee: AmountJson[] = [];
const refreshFee: AmountJson[] = [];
@@ -638,8 +624,6 @@ export async function getTotalFeeForDepositAmount(
if (!denom) {
throw Error("can't find denomination to calculate deposit amount");
}
- // const cc = pcs.coinContributions[i]
- // acc = Amounts.add(acc, cc).amount
coinFee.push(denom.feeDeposit);
exchangeSet.add(coin.exchangeBaseUrl);
@@ -661,16 +645,15 @@ export async function getTotalFeeForDepositAmount(
if (!exchangeDetails) {
continue;
}
- // FIXME/NOTE: the line below _likely_ throws exception
- // about "find method not found on undefined" when the wireType
- // is not supported by the Exchange.
- const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => {
- return AbsoluteTime.isBetween(
- AbsoluteTime.now(),
- AbsoluteTime.fromTimestamp(x.startStamp),
- AbsoluteTime.fromTimestamp(x.endStamp),
- );
- })?.wireFee;
+ const fee = exchangeDetails.wireInfo.feesForType[wireType]?.find(
+ (x) => {
+ return AbsoluteTime.isBetween(
+ AbsoluteTime.now(),
+ AbsoluteTime.fromTimestamp(x.startStamp),
+ AbsoluteTime.fromTimestamp(x.endStamp),
+ );
+ },
+ )?.wireFee;
if (fee) {
wireFee.push(fee);
}
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index fe1c9ef35..26bca8c14 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -20,6 +20,7 @@
import {
AbsoluteTime,
Amounts,
+ CancellationToken,
canonicalizeBaseUrl,
codecForExchangeKeysJson,
codecForExchangeWireJson,
@@ -61,11 +62,7 @@ import {
readSuccessResponseTextOrThrow,
} from "../util/http.js";
import { DbAccess, GetReadOnlyAccess } from "../util/query.js";
-import {
- initRetryInfo,
- RetryInfo,
- updateRetryInfoTimeout,
-} from "../util/retries.js";
+import { resetRetryInfo, RetryInfo } from "../util/retries.js";
import {
WALLET_CACHE_BREAKER_CLIENT_VERSION,
WALLET_EXCHANGE_PROTOCOL_VERSION,
@@ -124,9 +121,12 @@ async function reportExchangeUpdateError(
ws.notify({ type: NotificationType.ExchangeOperationError, error: err });
}
-async function resetExchangeUpdateRetry(
+async function setupExchangeUpdateRetry(
ws: InternalWalletState,
baseUrl: string,
+ options: {
+ reset: boolean;
+ },
): Promise<void> {
await ws.db
.mktx((x) => ({ exchanges: x.exchanges }))
@@ -135,25 +135,12 @@ async function resetExchangeUpdateRetry(
if (!exchange) {
return;
}
- delete exchange.lastError;
- exchange.retryInfo = initRetryInfo();
- await tx.exchanges.put(exchange);
- });
-}
-
-async function incrementExchangeUpdateRetry(
- ws: InternalWalletState,
- baseUrl: string,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({ exchanges: x.exchanges }))
- .runReadWrite(async (tx) => {
- const exchange = await tx.exchanges.get(baseUrl);
- if (!exchange) {
- return;
+ if (options.reset) {
+ exchange.retryInfo = resetRetryInfo();
+ } else {
+ exchange.retryInfo = RetryInfo.increment(exchange.retryInfo);
}
delete exchange.lastError;
- exchange.retryInfo = RetryInfo.increment(exchange.retryInfo);
await tx.exchanges.put(exchange);
});
}
@@ -376,8 +363,10 @@ async function downloadExchangeWireInfo(
export async function updateExchangeFromUrl(
ws: InternalWalletState,
baseUrl: string,
- acceptedFormat?: string[],
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ cancellationToken?: CancellationToken;
+ } = {},
): Promise<{
exchange: ExchangeRecord;
exchangeDetails: ExchangeDetailsRecord;
@@ -385,7 +374,7 @@ export async function updateExchangeFromUrl(
const onOpErr = (e: TalerErrorDetail): Promise<void> =>
reportExchangeUpdateError(ws, baseUrl, e);
return await guardOperationException(
- () => updateExchangeFromUrlImpl(ws, baseUrl, acceptedFormat, forceNow),
+ () => updateExchangeFromUrlImpl(ws, baseUrl, options),
onOpErr,
);
}
@@ -409,7 +398,7 @@ async function provideExchangeRecord(
const r: ExchangeRecord = {
permanent: true,
baseUrl: baseUrl,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
detailsPointer: undefined,
lastUpdate: undefined,
nextUpdate: AbsoluteTime.toTimestamp(now),
@@ -552,12 +541,15 @@ export async function downloadTosFromAcceptedFormat(
async function updateExchangeFromUrlImpl(
ws: InternalWalletState,
baseUrl: string,
- acceptedFormat?: string[],
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ cancellationToken?: CancellationToken;
+ } = {},
): Promise<{
exchange: ExchangeRecord;
exchangeDetails: ExchangeDetailsRecord;
}> {
+ const forceNow = options.forceNow ?? false;
logger.info(`updating exchange info for ${baseUrl}, forced: ${forceNow}`);
const now = AbsoluteTime.now();
baseUrl = canonicalizeBaseUrl(baseUrl);
@@ -577,11 +569,7 @@ async function updateExchangeFromUrlImpl(
return { exchange, exchangeDetails };
}
- if (forceNow) {
- await resetExchangeUpdateRetry(ws, baseUrl);
- } else {
- await incrementExchangeUpdateRetry(ws, baseUrl);
- }
+ await setupExchangeUpdateRetry(ws, baseUrl, { reset: forceNow });
logger.info("updating exchange /keys info");
@@ -617,7 +605,7 @@ async function updateExchangeFromUrlImpl(
ws,
baseUrl,
timeout,
- acceptedFormat,
+ ["text/plain"],
);
const tosHasBeenAccepted =
exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag;
diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts
index b761367fb..fa36c724f 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -97,7 +97,7 @@ import {
import { GetReadWriteAccess } from "../util/query.js";
import {
getRetryDuration,
- initRetryInfo,
+ resetRetryInfo,
RetryInfo,
updateRetryInfoTimeout,
} from "../util/retries.js";
@@ -428,8 +428,8 @@ async function recordConfirmPay(
proposalId: proposal.proposalId,
lastPayError: undefined,
lastRefundStatusError: undefined,
- payRetryInfo: initRetryInfo(),
- refundStatusRetryInfo: initRetryInfo(),
+ payRetryInfo: resetRetryInfo(),
+ refundStatusRetryInfo: resetRetryInfo(),
refundQueryRequested: false,
timestampFirstSuccessfulPay: undefined,
autoRefundDeadline: undefined,
@@ -453,7 +453,7 @@ async function recordConfirmPay(
if (p) {
p.proposalStatus = ProposalStatus.Accepted;
delete p.lastError;
- p.retryInfo = initRetryInfo();
+ delete p.retryInfo;
await tx.proposals.put(p);
}
await tx.purchases.put(t);
@@ -491,9 +491,12 @@ async function reportProposalError(
ws.notify({ type: NotificationType.ProposalOperationError, error: err });
}
-async function incrementProposalRetry(
+async function setupProposalRetry(
ws: InternalWalletState,
proposalId: string,
+ options: {
+ reset: boolean;
+ },
): Promise<void> {
await ws.db
.mktx((x) => ({ proposals: x.proposals }))
@@ -502,47 +505,37 @@ async function incrementProposalRetry(
if (!pr) {
return;
}
- if (!pr.retryInfo) {
- return;
+ if (options.reset) {
+ pr.retryInfo = resetRetryInfo();
} else {
- pr.retryInfo.retryCounter++;
- updateRetryInfoTimeout(pr.retryInfo);
+ pr.retryInfo = RetryInfo.increment(pr.retryInfo);
}
delete pr.lastError;
await tx.proposals.put(pr);
});
}
-async function resetPurchasePayRetry(
+async function setupPurchasePayRetry(
ws: InternalWalletState,
proposalId: string,
+ options: {
+ reset: boolean;
+ },
): Promise<void> {
await ws.db
.mktx((x) => ({ purchases: x.purchases }))
.runReadWrite(async (tx) => {
const p = await tx.purchases.get(proposalId);
- if (p) {
- p.payRetryInfo = initRetryInfo();
- delete p.lastPayError;
- await tx.purchases.put(p);
- }
- });
-}
-
-async function incrementPurchasePayRetry(
- ws: InternalWalletState,
- proposalId: string,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({ purchases: x.purchases }))
- .runReadWrite(async (tx) => {
- const pr = await tx.purchases.get(proposalId);
- if (!pr) {
+ if (!p) {
return;
}
- pr.payRetryInfo = RetryInfo.increment(pr.payRetryInfo);
- delete pr.lastPayError;
- await tx.purchases.put(pr);
+ if (options.reset) {
+ p.payRetryInfo = resetRetryInfo();
+ } else {
+ p.payRetryInfo = RetryInfo.increment(p.payRetryInfo);
+ }
+ delete p.lastPayError;
+ await tx.purchases.put(p);
});
}
@@ -572,32 +565,18 @@ async function reportPurchasePayError(
export async function processDownloadProposal(
ws: InternalWalletState,
proposalId: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
const onOpErr = (err: TalerErrorDetail): Promise<void> =>
reportProposalError(ws, proposalId, err);
await guardOperationException(
- () => processDownloadProposalImpl(ws, proposalId, forceNow),
+ () => processDownloadProposalImpl(ws, proposalId, options),
onOpErr,
);
}
-async function resetDownloadProposalRetry(
- ws: InternalWalletState,
- proposalId: string,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({ proposals: x.proposals }))
- .runReadWrite(async (tx) => {
- const p = await tx.proposals.get(proposalId);
- if (p) {
- p.retryInfo = initRetryInfo();
- delete p.lastError;
- await tx.proposals.put(p);
- }
- });
-}
-
async function failProposalPermanently(
ws: InternalWalletState,
proposalId: string,
@@ -678,8 +657,11 @@ export function extractContractData(
async function processDownloadProposalImpl(
ws: InternalWalletState,
proposalId: string,
- forceNow: boolean,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
+ const forceNow = options.forceNow ?? false;
const proposal = await ws.db
.mktx((x) => ({ proposals: x.proposals }))
.runReadOnly(async (tx) => {
@@ -694,11 +676,7 @@ async function processDownloadProposalImpl(
return;
}
- if (forceNow) {
- await resetDownloadProposalRetry(ws, proposalId);
- } else {
- await incrementProposalRetry(ws, proposalId);
- }
+ await setupProposalRetry(ws, proposalId, { reset: forceNow });
const orderClaimUrl = new URL(
`orders/${proposal.orderId}/claim`,
@@ -946,7 +924,7 @@ async function startDownloadProposal(
proposalId: proposalId,
proposalStatus: ProposalStatus.Downloading,
repurchaseProposalId: undefined,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
lastError: undefined,
downloadSessionId: sessionId,
};
@@ -994,7 +972,7 @@ async function storeFirstPaySuccess(
purchase.paymentSubmitPending = false;
purchase.lastPayError = undefined;
purchase.lastSessionId = sessionId;
- purchase.payRetryInfo = initRetryInfo();
+ purchase.payRetryInfo = resetRetryInfo();
purchase.merchantPaySig = paySig;
if (isFirst) {
const protoAr = purchase.download.contractData.autoRefund;
@@ -1002,7 +980,7 @@ async function storeFirstPaySuccess(
const ar = Duration.fromTalerProtocolDuration(protoAr);
logger.info("auto_refund present");
purchase.refundQueryRequested = true;
- purchase.refundStatusRetryInfo = initRetryInfo();
+ purchase.refundStatusRetryInfo = resetRetryInfo();
purchase.lastRefundStatusError = undefined;
purchase.autoRefundDeadline = AbsoluteTime.toTimestamp(
AbsoluteTime.addDuration(AbsoluteTime.now(), ar),
@@ -1033,7 +1011,7 @@ async function storePayReplaySuccess(
}
purchase.paymentSubmitPending = false;
purchase.lastPayError = undefined;
- purchase.payRetryInfo = initRetryInfo();
+ purchase.payRetryInfo = resetRetryInfo();
purchase.lastSessionId = sessionId;
await tx.purchases.put(purchase);
});
@@ -1289,7 +1267,7 @@ export async function checkPaymentByProposalId(
p.paymentSubmitPending = true;
await tx.purchases.put(p);
});
- const r = await processPurchasePay(ws, proposalId, true);
+ const r = await processPurchasePay(ws, proposalId, { forceNow: true });
if (r.type !== ConfirmPayResultType.Done) {
throw Error("submitting pay failed");
}
@@ -1466,7 +1444,7 @@ export async function confirmPay(
if (existingPurchase) {
logger.trace("confirmPay: submitting payment for existing purchase");
- return await processPurchasePay(ws, proposalId, true);
+ return await processPurchasePay(ws, proposalId, { forceNow: true });
}
logger.trace("confirmPay: purchase record does not exist yet");
@@ -1516,18 +1494,20 @@ export async function confirmPay(
sessionIdOverride,
);
- return await processPurchasePay(ws, proposalId, true);
+ return await processPurchasePay(ws, proposalId, { forceNow: true });
}
export async function processPurchasePay(
ws: InternalWalletState,
proposalId: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<ConfirmPayResult> {
const onOpErr = (e: TalerErrorDetail): Promise<void> =>
reportPurchasePayError(ws, proposalId, e);
return await guardOperationException(
- () => processPurchasePayImpl(ws, proposalId, forceNow),
+ () => processPurchasePayImpl(ws, proposalId, options),
onOpErr,
);
}
@@ -1535,8 +1515,11 @@ export async function processPurchasePay(
async function processPurchasePayImpl(
ws: InternalWalletState,
proposalId: string,
- forceNow: boolean,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<ConfirmPayResult> {
+ const forceNow = options.forceNow ?? false;
const purchase = await ws.db
.mktx((x) => ({ purchases: x.purchases }))
.runReadOnly(async (tx) => {
@@ -1559,11 +1542,7 @@ async function processPurchasePayImpl(
lastError: purchase.lastPayError,
};
}
- if (forceNow) {
- await resetPurchasePayRetry(ws, proposalId);
- } else {
- await incrementPurchasePayRetry(ws, proposalId);
- }
+ await setupPurchasePayRetry(ws, proposalId, { reset: forceNow });
logger.trace(`processing purchase pay ${proposalId}`);
const sessionId = purchase.lastSessionId;
diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts
index 71b8b34f4..b89c7c640 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -51,18 +51,17 @@ async function gatherExchangePending(
resp.pendingOperations.push({
type: PendingTaskType.ExchangeUpdate,
givesLifeness: false,
- timestampDue: e.lastError
- ? e.retryInfo.nextRetry
- : AbsoluteTime.fromTimestamp(e.nextUpdate),
+ timestampDue:
+ e.retryInfo?.nextRetry ?? AbsoluteTime.fromTimestamp(e.nextUpdate),
exchangeBaseUrl: e.baseUrl,
lastError: e.lastError,
});
resp.pendingOperations.push({
type: PendingTaskType.ExchangeCheckRefresh,
- timestampDue: e.lastError
- ? e.retryInfo.nextRetry
- : AbsoluteTime.fromTimestamp(e.nextRefreshCheck),
+ timestampDue:
+ e.retryInfo?.nextRetry ??
+ AbsoluteTime.fromTimestamp(e.nextRefreshCheck),
givesLifeness: false,
exchangeBaseUrl: e.baseUrl,
});
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts
index e30f09808..4fac2ec16 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -48,7 +48,11 @@ import {
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
import { Logger, URL } from "@gnu-taler/taler-util";
-import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
+import {
+ resetRetryInfo,
+ RetryInfo,
+ updateRetryInfoTimeout,
+} from "../util/retries.js";
import { createRefreshGroup, processRefreshGroup } from "./refresh.js";
import { getReserveRequestTimeout, processReserve } from "./reserves.js";
import { InternalWalletState } from "../internal-wallet-state.js";
@@ -57,10 +61,12 @@ import { guardOperationException } from "./common.js";
const logger = new Logger("operations/recoup.ts");
-async function incrementRecoupRetry(
+async function setupRecoupRetry(
ws: InternalWalletState,
recoupGroupId: string,
- err: TalerErrorDetail | undefined,
+ options: {
+ reset: boolean;
+ },
): Promise<void> {
await ws.db
.mktx((x) => ({
@@ -71,17 +77,39 @@ async function incrementRecoupRetry(
if (!r) {
return;
}
- if (!r.retryInfo) {
+ if (options.reset) {
+ r.retryInfo = resetRetryInfo();
+ } else {
+ r.retryInfo = RetryInfo.increment(r.retryInfo);
+ }
+ delete r.lastError;
+ await tx.recoupGroups.put(r);
+ });
+}
+
+async function reportRecoupError(
+ ws: InternalWalletState,
+ recoupGroupId: string,
+ err: TalerErrorDetail,
+): Promise<void> {
+ await ws.db
+ .mktx((x) => ({
+ recoupGroups: x.recoupGroups,
+ }))
+ .runReadWrite(async (tx) => {
+ const r = await tx.recoupGroups.get(recoupGroupId);
+ if (!r) {
return;
}
- r.retryInfo.retryCounter++;
- updateRetryInfoTimeout(r.retryInfo);
+ if (!r.retryInfo) {
+ logger.error(
+ "reporting error for inactive recoup group (no retry info)",
+ );
+ }
r.lastError = err;
await tx.recoupGroups.put(r);
});
- if (err) {
- ws.notify({ type: NotificationType.RecoupOperationError, error: err });
- }
+ ws.notify({ type: NotificationType.RecoupOperationError, error: err });
}
async function putGroupAsFinished(
@@ -111,7 +139,7 @@ async function putGroupAsFinished(
if (allFinished) {
logger.info("all recoups of recoup group are finished");
recoupGroup.timestampFinished = TalerProtocolTimestamp.now();
- recoupGroup.retryInfo = initRetryInfo();
+ recoupGroup.retryInfo = resetRetryInfo();
recoupGroup.lastError = undefined;
if (recoupGroup.scheduleRefreshCoins.length > 0) {
const refreshGroupId = await createRefreshGroup(
@@ -250,7 +278,7 @@ async function recoupWithdrawCoin(
const currency = updatedCoin.currentAmount.currency;
updatedCoin.currentAmount = Amounts.getZero(currency);
updatedReserve.reserveStatus = ReserveRecordStatus.QueryingStatus;
- updatedReserve.retryInfo = initRetryInfo();
+ updatedReserve.retryInfo = resetRetryInfo();
updatedReserve.operationStatus = OperationStatus.Pending;
await tx.coins.put(updatedCoin);
await tx.reserves.put(updatedReserve);
@@ -361,33 +389,18 @@ async function recoupRefreshCoin(
});
}
-async function resetRecoupGroupRetry(
- ws: InternalWalletState,
- recoupGroupId: string,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({
- recoupGroups: x.recoupGroups,
- }))
- .runReadWrite(async (tx) => {
- const x = await tx.recoupGroups.get(recoupGroupId);
- if (x) {
- x.retryInfo = initRetryInfo();
- await tx.recoupGroups.put(x);
- }
- });
-}
-
export async function processRecoupGroup(
ws: InternalWalletState,
recoupGroupId: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
await ws.memoProcessRecoup.memo(recoupGroupId, async () => {
const onOpErr = (e: TalerErrorDetail): Promise<void> =>
- incrementRecoupRetry(ws, recoupGroupId, e);
+ reportRecoupError(ws, recoupGroupId, e);
return await guardOperationException(
- async () => await processRecoupGroupImpl(ws, recoupGroupId, forceNow),
+ async () => await processRecoupGroupImpl(ws, recoupGroupId, options),
onOpErr,
);
});
@@ -396,11 +409,12 @@ export async function processRecoupGroup(
async function processRecoupGroupImpl(
ws: InternalWalletState,
recoupGroupId: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
- if (forceNow) {
- await resetRecoupGroupRetry(ws, recoupGroupId);
- }
+ const forceNow = options.forceNow ?? false;
+ await setupRecoupRetry(ws, recoupGroupId, { reset: forceNow });
const recoupGroup = await ws.db
.mktx((x) => ({
recoupGroups: x.recoupGroups,
@@ -444,7 +458,7 @@ async function processRecoupGroupImpl(
}
for (const r of reserveSet.values()) {
- processReserve(ws, r, true).catch((e) => {
+ processReserve(ws, r, { forceNow: true }).catch((e) => {
logger.error(`processing reserve ${r} after recoup failed`);
});
}
@@ -468,7 +482,7 @@ export async function createRecoupGroup(
lastError: undefined,
timestampFinished: undefined,
timestampStarted: TalerProtocolTimestamp.now(),
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
recoupFinishedPerCoin: coinPubs.map(() => false),
// Will be populated later
oldAmountPerCoin: [],
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index 2ab06abae..762023d2e 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -53,7 +53,11 @@ import {
} from "../util/http.js";
import { checkDbInvariant } from "../util/invariants.js";
import { Logger } from "@gnu-taler/taler-util";
-import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
+import {
+ resetRetryInfo,
+ RetryInfo,
+ updateRetryInfoTimeout,
+} from "../util/retries.js";
import {
Duration,
durationFromSpec,
@@ -130,11 +134,11 @@ function updateGroupStatus(rg: RefreshGroupRecord): void {
if (allDone) {
if (anyFrozen) {
rg.frozen = true;
- rg.retryInfo = initRetryInfo();
+ rg.retryInfo = resetRetryInfo();
} else {
rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now());
rg.operationStatus = OperationStatus.Finished;
- rg.retryInfo = initRetryInfo();
+ rg.retryInfo = resetRetryInfo();
}
}
}
@@ -712,7 +716,33 @@ async function refreshReveal(
});
}
-async function incrementRefreshRetry(
+async function setupRefreshRetry(
+ ws: InternalWalletState,
+ refreshGroupId: string,
+ options: {
+ reset: boolean;
+ },
+): Promise<void> {
+ await ws.db
+ .mktx((x) => ({
+ refreshGroups: x.refreshGroups,
+ }))
+ .runReadWrite(async (tx) => {
+ const r = await tx.refreshGroups.get(refreshGroupId);
+ if (!r) {
+ return;
+ }
+ if (options.reset) {
+ r.retryInfo = resetRetryInfo();
+ } else {
+ r.retryInfo = RetryInfo.increment(r.retryInfo);
+ }
+ delete r.lastError;
+ await tx.refreshGroups.put(r);
+ });
+}
+
+async function reportRefreshError(
ws: InternalWalletState,
refreshGroupId: string,
err: TalerErrorDetail | undefined,
@@ -727,10 +757,10 @@ async function incrementRefreshRetry(
return;
}
if (!r.retryInfo) {
- return;
+ logger.error(
+ "reported error for inactive refresh group (no retry info)",
+ );
}
- r.retryInfo.retryCounter++;
- updateRetryInfoTimeout(r.retryInfo);
r.lastError = err;
await tx.refreshGroups.put(r);
});
@@ -745,44 +775,31 @@ async function incrementRefreshRetry(
export async function processRefreshGroup(
ws: InternalWalletState,
refreshGroupId: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
await ws.memoProcessRefresh.memo(refreshGroupId, async () => {
const onOpErr = (e: TalerErrorDetail): Promise<void> =>
- incrementRefreshRetry(ws, refreshGroupId, e);
+ reportRefreshError(ws, refreshGroupId, e);
return await guardOperationException(
- async () => await processRefreshGroupImpl(ws, refreshGroupId, forceNow),
+ async () => await processRefreshGroupImpl(ws, refreshGroupId, options),
onOpErr,
);
});
}
-async function resetRefreshGroupRetry(
- ws: InternalWalletState,
- refreshGroupId: string,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({
- refreshGroups: x.refreshGroups,
- }))
- .runReadWrite(async (tx) => {
- const x = await tx.refreshGroups.get(refreshGroupId);
- if (x) {
- x.retryInfo = initRetryInfo();
- await tx.refreshGroups.put(x);
- }
- });
-}
-
async function processRefreshGroupImpl(
ws: InternalWalletState,
refreshGroupId: string,
- forceNow: boolean,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
+ const forceNow = options.forceNow ?? false;
logger.info(`processing refresh group ${refreshGroupId}`);
- if (forceNow) {
- await resetRefreshGroupRetry(ws, refreshGroupId);
- }
+ await setupRefreshRetry(ws, refreshGroupId, { reset: forceNow });
+
const refreshGroup = await ws.db
.mktx((x) => ({
refreshGroups: x.refreshGroups,
@@ -939,7 +956,7 @@ export async function createRefreshGroup(
reason,
refreshGroupId,
refreshSessionPerCoin: oldCoinPubs.map(() => undefined),
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
inputPerCoin,
estimatedOutputPerCoin,
timestampCreated: TalerProtocolTimestamp.now(),
@@ -994,7 +1011,9 @@ export async function autoRefresh(
exchangeBaseUrl: string,
): Promise<void> {
logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`);
- await updateExchangeFromUrl(ws, exchangeBaseUrl, undefined, true);
+ await updateExchangeFromUrl(ws, exchangeBaseUrl, {
+ forceNow: true,
+ });
let minCheckThreshold = AbsoluteTime.addDuration(
AbsoluteTime.now(),
durationFromSpec({ days: 1 }),
diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts
index 4190bee7f..7ef8076f0 100644
--- a/packages/taler-wallet-core/src/operations/refund.ts
+++ b/packages/taler-wallet-core/src/operations/refund.ts
@@ -58,37 +58,54 @@ import {
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
import { checkDbInvariant } from "../util/invariants.js";
import { GetReadWriteAccess } from "../util/query.js";
-import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
+import {
+ resetRetryInfo,
+ RetryInfo,
+ updateRetryInfoTimeout,
+} from "../util/retries.js";
import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { guardOperationException } from "./common.js";
const logger = new Logger("refund.ts");
-async function resetPurchaseQueryRefundRetry(
+/**
+ * Retry querying and applying refunds for an order later.
+ */
+async function setupPurchaseQueryRefundRetry(
ws: InternalWalletState,
proposalId: string,
+ options: {
+ reset: boolean;
+ },
): Promise<void> {
await ws.db
.mktx((x) => ({
purchases: x.purchases,
}))
.runReadWrite(async (tx) => {
- const x = await tx.purchases.get(proposalId);
- if (x) {
- x.refundStatusRetryInfo = initRetryInfo();
- await tx.purchases.put(x);
+ const pr = await tx.purchases.get(proposalId);
+ if (!pr) {
+ return;
+ }
+ if (options.reset) {
+ pr.refundStatusRetryInfo = resetRetryInfo();
+ } else {
+ pr.refundStatusRetryInfo = RetryInfo.increment(
+ pr.refundStatusRetryInfo,
+ );
}
+ await tx.purchases.put(pr);
});
}
/**
- * Retry querying and applying refunds for an order later.
+ * Report an error that happending when querying for a purchase's refund.
*/
-async function incrementPurchaseQueryRefundRetry(
+async function reportPurchaseQueryRefundError(
ws: InternalWalletState,
proposalId: string,
- err: TalerErrorDetail | undefined,
+ err: TalerErrorDetail,
): Promise<void> {
await ws.db
.mktx((x) => ({
@@ -100,10 +117,10 @@ async function incrementPurchaseQueryRefundRetry(
return;
}
if (!pr.refundStatusRetryInfo) {
- return;
+ logger.error(
+ "reported error on an inactive purchase (no refund status retry info)",
+ );
}
- pr.refundStatusRetryInfo.retryCounter++;
- updateRetryInfoTimeout(pr.refundStatusRetryInfo);
pr.lastRefundStatusError = err;
await tx.purchases.put(pr);
});
@@ -425,7 +442,7 @@ async function acceptRefunds(
if (queryDone) {
p.timestampLastRefundStatus = now;
p.lastRefundStatusError = undefined;
- p.refundStatusRetryInfo = initRetryInfo();
+ p.refundStatusRetryInfo = resetRetryInfo();
p.refundQueryRequested = false;
if (p.abortStatus === AbortStatus.AbortRefund) {
p.abortStatus = AbortStatus.AbortFinished;
@@ -506,7 +523,7 @@ export async function applyRefund(
}
p.refundQueryRequested = true;
p.lastRefundStatusError = undefined;
- p.refundStatusRetryInfo = initRetryInfo();
+ p.refundStatusRetryInfo = resetRetryInfo();
await tx.purchases.put(p);
return true;
});
@@ -515,7 +532,10 @@ export async function applyRefund(
ws.notify({
type: NotificationType.RefundStarted,
});
- await processPurchaseQueryRefundImpl(ws, proposalId, true, false);
+ await processPurchaseQueryRefundImpl(ws, proposalId, {
+ forceNow: true,
+ waitForAutoRefund: false,
+ });
}
purchase = await ws.db
@@ -590,12 +610,15 @@ export async function applyRefund(
export async function processPurchaseQueryRefund(
ws: InternalWalletState,
proposalId: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ waitForAutoRefund?: boolean;
+ } = {},
): Promise<void> {
const onOpErr = (e: TalerErrorDetail): Promise<void> =>
- incrementPurchaseQueryRefundRetry(ws, proposalId, e);
+ reportPurchaseQueryRefundError(ws, proposalId, e);
await guardOperationException(
- () => processPurchaseQueryRefundImpl(ws, proposalId, forceNow, true),
+ () => processPurchaseQueryRefundImpl(ws, proposalId, options),
onOpErr,
);
}
@@ -603,12 +626,14 @@ export async function processPurchaseQueryRefund(
async function processPurchaseQueryRefundImpl(
ws: InternalWalletState,
proposalId: string,
- forceNow: boolean,
- waitForAutoRefund: boolean,
+ options: {
+ forceNow?: boolean;
+ waitForAutoRefund?: boolean;
+ } = {},
): Promise<void> {
- if (forceNow) {
- await resetPurchaseQueryRefundRetry(ws, proposalId);
- }
+ const forceNow = options.forceNow ?? false;
+ const waitForAutoRefund = options.waitForAutoRefund ?? false;
+ await setupPurchaseQueryRefundRetry(ws, proposalId, { reset: forceNow });
const purchase = await ws.db
.mktx((x) => ({
purchases: x.purchases,
@@ -650,7 +675,7 @@ async function processPurchaseQueryRefundImpl(
codecForMerchantOrderStatusPaid(),
);
if (!orderStatus.refunded) {
- incrementPurchaseQueryRefundRetry(ws, proposalId, undefined);
+ // Wait for retry ...
return;
}
}
@@ -666,11 +691,6 @@ async function processPurchaseQueryRefundImpl(
h_contract: purchase.download.contractData.contractTermsHash,
});
- logger.trace(
- "got json",
- JSON.stringify(await request.json(), undefined, 2),
- );
-
const refundResponse = await readSuccessResponseJsonOrThrow(
request,
codecForMerchantOrderRefundPickupResponse(),
@@ -777,10 +797,12 @@ export async function abortFailedPayWithRefund(
purchase.paymentSubmitPending = false;
purchase.abortStatus = AbortStatus.AbortRefund;
purchase.lastPayError = undefined;
- purchase.payRetryInfo = initRetryInfo();
+ purchase.payRetryInfo = resetRetryInfo();
await tx.purchases.put(purchase);
});
- processPurchaseQueryRefund(ws, proposalId, true).catch((e) => {
+ processPurchaseQueryRefund(ws, proposalId, {
+ forceNow: true,
+ }).catch((e) => {
logger.trace(`error during refund processing after abort pay: ${e}`);
});
}
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts
index 9cbd63c45..38a7386b2 100644
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ b/packages/taler-wallet-core/src/operations/reserves.ts
@@ -57,7 +57,8 @@ import {
import { GetReadOnlyAccess } from "../util/query.js";
import {
getRetryDuration,
- initRetryInfo,
+ resetRetryInfo,
+ RetryInfo,
updateRetryInfoTimeout,
} from "../util/retries.js";
import {
@@ -79,34 +80,15 @@ import { guardOperationException } from "./common.js";
const logger = new Logger("taler-wallet-core:reserves.ts");
/**
- * Reset the retry counter for the reserve
- * and reset the last error.
+ * Set up the reserve's retry timeout in preparation for
+ * processing the reserve.
*/
-async function resetReserveRetry(
- ws: InternalWalletState,
- reservePub: string,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadWrite(async (tx) => {
- const x = await tx.reserves.get(reservePub);
- if (x) {
- x.retryInfo = initRetryInfo();
- delete x.lastError;
- await tx.reserves.put(x);
- }
- });
-}
-
-/**
- * Increment the retry counter for the reserve and
- * reset the last eror.
- */
-async function incrementReserveRetry(
+async function setupReserveRetry(
ws: InternalWalletState,
reservePub: string,
+ options: {
+ reset: boolean;
+ },
): Promise<void> {
await ws.db
.mktx((x) => ({
@@ -117,11 +99,10 @@ async function incrementReserveRetry(
if (!r) {
return;
}
- if (!r.retryInfo) {
- r.retryInfo = initRetryInfo();
+ if (options.reset) {
+ r.retryInfo = resetRetryInfo();
} else {
- r.retryInfo.retryCounter++;
- updateRetryInfoTimeout(r.retryInfo);
+ r.retryInfo = RetryInfo.increment(r.retryInfo);
}
delete r.lastError;
await tx.reserves.put(r);
@@ -216,7 +197,7 @@ export async function createReserve(
timestampReserveInfoPosted: undefined,
bankInfo,
reserveStatus,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
lastError: undefined,
currency: req.amount.currency,
operationStatus: OperationStatus.Pending,
@@ -288,7 +269,7 @@ export async function createReserve(
// Asynchronously process the reserve, but return
// to the caller already.
- processReserve(ws, resp.reservePub, true).catch((e) => {
+ processReserve(ws, resp.reservePub, { forceNow: true }).catch((e) => {
logger.error("Processing reserve (after createReserve) failed:", e);
});
@@ -316,14 +297,14 @@ export async function forceQueryReserve(
case ReserveRecordStatus.Dormant:
reserve.reserveStatus = ReserveRecordStatus.QueryingStatus;
reserve.operationStatus = OperationStatus.Pending;
- reserve.retryInfo = initRetryInfo();
+ reserve.retryInfo = resetRetryInfo();
break;
default:
break;
}
await tx.reserves.put(reserve);
});
- await processReserve(ws, reservePub, true);
+ await processReserve(ws, reservePub, { forceNow: true });
}
/**
@@ -336,13 +317,15 @@ export async function forceQueryReserve(
export async function processReserve(
ws: InternalWalletState,
reservePub: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
return ws.memoProcessReserve.memo(reservePub, async () => {
const onOpError = (err: TalerErrorDetail): Promise<void> =>
reportReserveError(ws, reservePub, err);
await guardOperationException(
- () => processReserveImpl(ws, reservePub, forceNow),
+ () => processReserveImpl(ws, reservePub, options),
onOpError,
);
});
@@ -409,7 +392,7 @@ async function registerReserveWithBank(
if (!r.bankInfo) {
throw Error("invariant failed");
}
- r.retryInfo = initRetryInfo();
+ r.retryInfo = resetRetryInfo();
await tx.reserves.put(r);
});
ws.notify({ type: NotificationType.ReserveRegisteredWithBank });
@@ -476,7 +459,7 @@ async function processReserveBankStatus(
r.timestampBankConfirmed = now;
r.reserveStatus = ReserveRecordStatus.BankAborted;
r.operationStatus = OperationStatus.Finished;
- r.retryInfo = initRetryInfo();
+ r.retryInfo = resetRetryInfo();
await tx.reserves.put(r);
});
return;
@@ -513,7 +496,7 @@ async function processReserveBankStatus(
r.timestampBankConfirmed = now;
r.reserveStatus = ReserveRecordStatus.QueryingStatus;
r.operationStatus = OperationStatus.Pending;
- r.retryInfo = initRetryInfo();
+ r.retryInfo = resetRetryInfo();
} else {
switch (r.reserveStatus) {
case ReserveRecordStatus.WaitConfirmBank:
@@ -684,7 +667,7 @@ async function updateReserve(
reservePub: reserve.reservePub,
rawWithdrawalAmount: remainingAmount,
timestampStart: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
lastError: undefined,
denomsSel: denomSelectionInfoToState(denomSelInfo),
secretSeed: encodeCrock(getRandomBytes(64)),
@@ -717,8 +700,12 @@ async function updateReserve(
async function processReserveImpl(
ws: InternalWalletState,
reservePub: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
+ const forceNow = options.forceNow ?? false;
+ await setupReserveRetry(ws, reservePub, { reset: forceNow });
const reserve = await ws.db
.mktx((x) => ({
reserves: x.reserves,
@@ -732,27 +719,17 @@ async function processReserveImpl(
);
return;
}
- if (forceNow) {
- await resetReserveRetry(ws, reservePub);
- } else if (
- reserve.retryInfo &&
- !AbsoluteTime.isExpired(reserve.retryInfo.nextRetry)
- ) {
- logger.trace("processReserve retry not due yet");
- return;
- }
- await incrementReserveRetry(ws, reservePub);
logger.trace(
`Processing reserve ${reservePub} with status ${reserve.reserveStatus}`,
);
switch (reserve.reserveStatus) {
case ReserveRecordStatus.RegisteringBank:
await processReserveBankStatus(ws, reservePub);
- return await processReserveImpl(ws, reservePub, true);
+ return await processReserveImpl(ws, reservePub, { forceNow: true });
case ReserveRecordStatus.QueryingStatus:
const res = await updateReserve(ws, reservePub);
if (res.ready) {
- return await processReserveImpl(ws, reservePub, true);
+ return await processReserveImpl(ws, reservePub, { forceNow: true });
}
break;
case ReserveRecordStatus.Dormant:
diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts
index 9582f18e8..8bf85fe99 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -43,7 +43,11 @@ import {
} from "../db.js";
import { j2s } from "@gnu-taler/taler-util";
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
-import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
+import {
+ resetRetryInfo,
+ RetryInfo,
+ updateRetryInfoTimeout,
+} from "../util/retries.js";
import { makeErrorDetail } from "../errors.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import { InternalWalletState } from "../internal-wallet-state.js";
@@ -127,7 +131,7 @@ export async function prepareTip(
createdTimestamp: TalerProtocolTimestamp.now(),
merchantTipId: res.merchantTipId,
tipAmountEffective: selectedDenoms.totalCoinValue,
- retryInfo: initRetryInfo(),
+ retryInfo: resetRetryInfo(),
lastError: undefined,
denomsSel: denomSelectionInfoToState(selectedDenoms),
pickedUpTimestamp: undefined,
@@ -157,10 +161,10 @@ export async function prepareTip(
return tipStatus;
}
-async function incrementTipRetry(
+async function reportTipError(
ws: InternalWalletState,
walletTipId: string,
- err: TalerErrorDetail | undefined,
+ err: TalerErrorDetail,
): Promise<void> {
await ws.db
.mktx((x) => ({
@@ -172,10 +176,8 @@ async function incrementTipRetry(
return;
}
if (!t.retryInfo) {
- return;
+ logger.reportBreak();
}
- t.retryInfo.retryCounter++;
- updateRetryInfoTimeout(t.retryInfo);
t.lastError = err;
await tx.tips.put(t);
});
@@ -184,15 +186,43 @@ async function incrementTipRetry(
}
}
+async function setupTipRetry(
+ ws: InternalWalletState,
+ walletTipId: string,
+ options: {
+ reset: boolean;
+ },
+): Promise<void> {
+ await ws.db
+ .mktx((x) => ({
+ tips: x.tips,
+ }))
+ .runReadWrite(async (tx) => {
+ const t = await tx.tips.get(walletTipId);
+ if (!t) {
+ return;
+ }
+ if (options.reset) {
+ t.retryInfo = resetRetryInfo();
+ } else {
+ t.retryInfo = RetryInfo.increment(t.retryInfo);
+ }
+ delete t.lastError;
+ await tx.tips.put(t);
+ });
+}
+
export async function processTip(
ws: InternalWalletState,
tipId: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
const onOpErr = (e: TalerErrorDetail): Promise<void> =>
- incrementTipRetry(ws, tipId, e);
+ reportTipError(ws, tipId, e);
await guardOperationException(
- () => processTipImpl(ws, tipId, forceNow),
+ () => processTipImpl(ws, tipId, options),
onOpErr,
);
}
@@ -208,7 +238,7 @@ async function resetTipRetry(
.runReadWrite(async (tx) => {
const x = await tx.tips.get(tipId);
if (x) {
- x.retryInfo = initRetryInfo();
+ x.retryInfo = resetRetryInfo();
await tx.tips.put(x);
}
});
@@ -217,8 +247,11 @@ async function resetTipRetry(
async function processTipImpl(
ws: InternalWalletState,
walletTipId: string,
- forceNow: boolean,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
+ const forceNow = options.forceNow ?? false;
if (forceNow) {
await resetTipRetry(ws, walletTipId);
}
@@ -293,12 +326,13 @@ async function processTipImpl(
merchantResp.status === 424)
) {
logger.trace(`got transient tip error`);
+ // FIXME: wrap in another error code that indicates a transient error
const err = makeErrorDetail(
TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
getHttpResponseErrorDetails(merchantResp),
"tip pickup failed (transient)",
);
- await incrementTipRetry(ws, tipRecord.walletTipId, err);
+ await reportTipError(ws, tipRecord.walletTipId, err);
// FIXME: Maybe we want to signal to the caller that the transient error happened?
return;
}
@@ -397,7 +431,7 @@ async function processTipImpl(
}
tr.pickedUpTimestamp = TalerProtocolTimestamp.now();
tr.lastError = undefined;
- tr.retryInfo = initRetryInfo();
+ tr.retryInfo = resetRetryInfo();
await tx.tips.put(tr);
for (const cr of newCoinRecords) {
await tx.coins.put(cr);
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index 4a7adbb9c..e7dcd0784 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -68,7 +68,11 @@ import {
HttpRequestLibrary,
readSuccessResponseJsonOrThrow,
} from "../util/http.js";
-import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
+import {
+ resetRetryInfo,
+ RetryInfo,
+ updateRetryInfoTimeout,
+} from "../util/retries.js";
import {
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
WALLET_EXCHANGE_PROTOCOL_VERSION,
@@ -792,10 +796,33 @@ export async function updateWithdrawalDenoms(
}
}
-async function incrementWithdrawalRetry(
+async function setupWithdrawalRetry(
+ ws: InternalWalletState,
+ withdrawalGroupId: string,
+ options: {
+ reset: boolean;
+ },
+): Promise<void> {
+ await ws.db
+ .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
+ .runReadWrite(async (tx) => {
+ const wsr = await tx.withdrawalGroups.get(withdrawalGroupId);
+ if (!wsr) {
+ return;
+ }
+ if (options.reset) {
+ wsr.retryInfo = resetRetryInfo();
+ } else {
+ wsr.retryInfo = RetryInfo.increment(wsr.retryInfo);
+ }
+ await tx.withdrawalGroups.put(wsr);
+ });
+}
+
+async function reportWithdrawalError(
ws: InternalWalletState,
withdrawalGroupId: string,
- err: TalerErrorDetail | undefined,
+ err: TalerErrorDetail,
): Promise<void> {
await ws.db
.mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
@@ -804,56 +831,40 @@ async function incrementWithdrawalRetry(
if (!wsr) {
return;
}
- wsr.retryInfo.retryCounter++;
- updateRetryInfoTimeout(wsr.retryInfo);
+ if (!wsr.retryInfo) {
+ logger.reportBreak();
+ }
wsr.lastError = err;
await tx.withdrawalGroups.put(wsr);
});
- if (err) {
- ws.notify({ type: NotificationType.WithdrawOperationError, error: err });
- }
+ ws.notify({ type: NotificationType.WithdrawOperationError, error: err });
}
export async function processWithdrawGroup(
ws: InternalWalletState,
withdrawalGroupId: string,
- forceNow = false,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
const onOpErr = (e: TalerErrorDetail): Promise<void> =>
- incrementWithdrawalRetry(ws, withdrawalGroupId, e);
+ reportWithdrawalError(ws, withdrawalGroupId, e);
await guardOperationException(
- () => processWithdrawGroupImpl(ws, withdrawalGroupId, forceNow),
+ () => processWithdrawGroupImpl(ws, withdrawalGroupId, options),
onOpErr,
);
}
-async function resetWithdrawalGroupRetry(
- ws: InternalWalletState,
- withdrawalGroupId: string,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({
- withdrawalGroups: x.withdrawalGroups,
- reserves: x.reserves,
- }))
- .runReadWrite(async (tx) => {
- const x = await tx.withdrawalGroups.get(withdrawalGroupId);
- if (x) {
- x.retryInfo = initRetryInfo();
- await tx.withdrawalGroups.put(x);
- }
- });
-}
-
async function processWithdrawGroupImpl(
ws: InternalWalletState,
withdrawalGroupId: string,
- forceNow: boolean,
+ options: {
+ forceNow?: boolean;
+ } = {},
): Promise<void> {
+ const forceNow = options.forceNow ?? false;
logger.trace("processing withdraw group", withdrawalGroupId);
- if (forceNow) {
- await resetWithdrawalGroupRetry(ws, withdrawalGroupId);
- }
+ await setupWithdrawalRetry(ws, withdrawalGroupId, { reset: forceNow });
const withdrawalGroup = await ws.db
.mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
.runReadOnly(async (tx) => {
@@ -876,7 +887,7 @@ async function processWithdrawGroupImpl(
);
return;
}
- return await ws.reserveOps.processReserve(ws, reservePub, forceNow);
+ return await ws.reserveOps.processReserve(ws, reservePub, { forceNow });
}
await ws.exchangeOps.updateExchangeFromUrl(
@@ -948,7 +959,7 @@ async function processWithdrawGroupImpl(
wg.timestampFinish = TalerProtocolTimestamp.now();
wg.operationStatus = OperationStatus.Finished;
delete wg.lastError;
- wg.retryInfo = initRetryInfo();
+ wg.retryInfo = resetRetryInfo();
}
await tx.withdrawalGroups.put(wg);
diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts
index 9ccd560d9..58edd289b 100644
--- a/packages/taler-wallet-core/src/util/http.ts
+++ b/packages/taler-wallet-core/src/util/http.ts
@@ -51,6 +51,8 @@ export interface HttpResponse {
bytes(): Promise<ArrayBuffer>;
}
+export const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
+
export interface HttpRequestOptions {
method?: "POST" | "PUT" | "GET";
headers?: { [name: string]: string };
diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts
index 25b4c5055..8b7d64406 100644
--- a/packages/taler-wallet-core/src/util/retries.ts
+++ b/packages/taler-wallet-core/src/util/retries.ts
@@ -82,7 +82,7 @@ export function getRetryDuration(
};
}
-export function initRetryInfo(p: RetryPolicy = defaultRetryPolicy): RetryInfo {
+export function resetRetryInfo(p: RetryPolicy = defaultRetryPolicy): RetryInfo {
const now = AbsoluteTime.now();
const info = {
firstTry: now,
@@ -99,7 +99,7 @@ export namespace RetryInfo {
p: RetryPolicy = defaultRetryPolicy,
) {
if (!r) {
- return initRetryInfo(p);
+ return resetRetryInfo(p);
}
const r2 = { ...r };
r2.retryCounter++;
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index abd11faab..b0bd2a2cb 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -238,51 +238,41 @@ async function processOnePendingOperation(
logger.trace(`running pending ${JSON.stringify(pending, undefined, 2)}`);
switch (pending.type) {
case PendingTaskType.ExchangeUpdate:
- await updateExchangeFromUrl(
- ws,
- pending.exchangeBaseUrl,
- undefined,
+ await updateExchangeFromUrl(ws, pending.exchangeBaseUrl, {
forceNow,
- );
+ });
break;
case PendingTaskType.Refresh:
- await processRefreshGroup(ws, pending.refreshGroupId, forceNow);
+ await processRefreshGroup(ws, pending.refreshGroupId, { forceNow });
break;
case PendingTaskType.Reserve:
- await processReserve(ws, pending.reservePub, forceNow);
+ await processReserve(ws, pending.reservePub, { forceNow });
break;
case PendingTaskType.Withdraw:
- await processWithdrawGroup(ws, pending.withdrawalGroupId, forceNow);
+ await processWithdrawGroup(ws, pending.withdrawalGroupId, { forceNow });
break;
case PendingTaskType.ProposalDownload:
- await processDownloadProposal(ws, pending.proposalId, forceNow);
+ await processDownloadProposal(ws, pending.proposalId, { forceNow });
break;
case PendingTaskType.TipPickup:
- await processTip(ws, pending.tipId, forceNow);
+ await processTip(ws, pending.tipId, { forceNow });
break;
case PendingTaskType.Pay:
- await processPurchasePay(ws, pending.proposalId, forceNow);
+ await processPurchasePay(ws, pending.proposalId, { forceNow });
break;
case PendingTaskType.RefundQuery:
- await processPurchaseQueryRefund(ws, pending.proposalId, forceNow);
+ await processPurchaseQueryRefund(ws, pending.proposalId, { forceNow });
break;
case PendingTaskType.Recoup:
- await processRecoupGroup(ws, pending.recoupGroupId, forceNow);
+ await processRecoupGroup(ws, pending.recoupGroupId, { forceNow });
break;
case PendingTaskType.ExchangeCheckRefresh:
await autoRefresh(ws, pending.exchangeBaseUrl);
break;
case PendingTaskType.Deposit: {
- const cts = CancellationToken.create();
- ws.taskCancellationSourceForDeposit = cts;
- try {
- await processDepositGroup(ws, pending.depositGroupId, {
- cancellationToken: cts.token,
- });
- } finally {
- cts.dispose();
- delete ws.taskCancellationSourceForDeposit;
- }
+ await processDepositGroup(ws, pending.depositGroupId, {
+ forceNow,
+ });
break;
}
case PendingTaskType.Backup:
@@ -497,11 +487,8 @@ async function getExchangeTos(
exchangeBaseUrl: string,
acceptedFormat?: string[],
): Promise<GetExchangeTosResult> {
- const { exchangeDetails } = await updateExchangeFromUrl(
- ws,
- exchangeBaseUrl,
- acceptedFormat,
- );
+ // FIXME: download ToS in acceptable format if passed!
+ const { exchangeDetails } = await updateExchangeFromUrl(ws, exchangeBaseUrl);
const content = exchangeDetails.termsOfServiceText;
const currentEtag = exchangeDetails.termsOfServiceLastEtag;
const contentType = exchangeDetails.termsOfServiceContentType;
@@ -802,12 +789,9 @@ async function dispatchRequestInternal(
}
case "addExchange": {
const req = codecForAddExchangeRequest().decode(payload);
- await updateExchangeFromUrl(
- ws,
- req.exchangeBaseUrl,
- undefined,
- req.forceUpdate,
- );
+ await updateExchangeFromUrl(ws, req.exchangeBaseUrl, {
+ forceNow: req.forceUpdate,
+ });
return {};
}
case "listExchanges": {
@@ -919,11 +903,11 @@ async function dispatchRequestInternal(
RefreshReason.Manual,
);
});
- processRefreshGroup(ws, refreshGroupId.refreshGroupId, true).catch(
- (x) => {
- logger.error(x);
- },
- );
+ processRefreshGroup(ws, refreshGroupId.refreshGroupId, {
+ forceNow: true,
+ }).catch((x) => {
+ logger.error(x);
+ });
return {
refreshGroupId,
};
@@ -1170,7 +1154,7 @@ class InternalWalletStateImpl implements InternalWalletState {
memoGetBalance: AsyncOpMemoSingle<BalancesResponse> = new AsyncOpMemoSingle();
memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
- memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
+
cryptoApi: TalerCryptoInterface;
cryptoDispatcher: CryptoDispatcher;