diff options
author | Florian Dold <florian@dold.me> | 2023-06-30 16:14:58 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2023-06-30 16:14:58 +0200 |
commit | d4ee96138774e8bc469f172bbb6276af89d6f240 (patch) | |
tree | eda5bc6833306727f711cc5aedd5f307fa8b1f67 /packages | |
parent | 7523ffa9105f71a6a4c201d3ee46dbfccc929cea (diff) | |
download | wallet-core-d4ee96138774e8bc469f172bbb6276af89d6f240.tar.xz |
wallet-core: rename OperationAttempt->TaskRun, do not allow task result values anymore
Diffstat (limited to 'packages')
17 files changed, 284 insertions, 364 deletions
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 237c1c39d..66b5e7262 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -1754,6 +1754,7 @@ export interface CoreApiRequestEnvelope { operation: string; args: unknown; } + export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError; export type CoreApiMessageEnvelope = CoreApiResponse | CoreApiNotification; diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 364e876ec..236ef1e0f 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -94,8 +94,8 @@ import { } from "../../util/invariants.js"; import { addAttentionRequest, removeAttentionRequest } from "../attention.js"; import { - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResult, + TaskRunResultType, TaskIdentifiers, } from "../common.js"; import { checkPaymentByProposalId, preparePayForUri } from "../pay-merchant.js"; @@ -250,7 +250,7 @@ function getNextBackupTimestamp(): TalerPreciseTimestamp { async function runBackupCycleForProvider( ws: InternalWalletState, args: BackupForProviderArgs, -): Promise<OperationAttemptResult<unknown, { talerUri?: string }>> { +): Promise<TaskRunResult> { const provider = await ws.db .mktx((x) => [x.backupProviders]) .runReadOnly(async (tx) => { @@ -259,10 +259,7 @@ async function runBackupCycleForProvider( if (!provider) { logger.warn("provider disappeared"); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } const backupJson = await exportBackup(ws); @@ -333,10 +330,7 @@ async function runBackupCycleForProvider( type: AttentionType.BackupUnpaid, }); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } if (resp.status === HttpStatusCode.PaymentRequired) { @@ -378,10 +372,7 @@ async function runBackupCycleForProvider( }); return { - type: OperationAttemptResultType.Pending, - result: { - talerUri, - }, + type: TaskRunResultType.Pending, }; } const result = res; @@ -415,10 +406,7 @@ async function runBackupCycleForProvider( ); return { - type: OperationAttemptResultType.Pending, - result: { - talerUri, - }, + type: TaskRunResultType.Pending, }; } @@ -445,8 +433,7 @@ async function runBackupCycleForProvider( }); return { - type: OperationAttemptResultType.Finished, - result: undefined, + type: TaskRunResultType.Finished, }; } @@ -487,7 +474,7 @@ async function runBackupCycleForProvider( const err = await readTalerErrorResponse(resp); logger.error(`got error response from backup provider: ${j2s(err)}`); return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: err, }; } @@ -495,7 +482,7 @@ async function runBackupCycleForProvider( export async function processBackupForProvider( ws: InternalWalletState, backupProviderBaseUrl: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const provider = await ws.db .mktx((x) => [x.backupProviders]) .runReadOnly(async (tx) => { @@ -720,23 +707,24 @@ async function runFirstBackupCycleForProvider( ): Promise<AddBackupProviderResponse> { const resp = await runBackupCycleForProvider(ws, args); switch (resp.type) { - case OperationAttemptResultType.Error: + case TaskRunResultType.Error: throw TalerError.fromDetail( TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, resp.errorDetail as any, //FIXME create an error for backup problems ); - case OperationAttemptResultType.Finished: + case TaskRunResultType.Finished: return { status: "ok", }; - case OperationAttemptResultType.Longpoll: + case TaskRunResultType.Longpoll: throw Error( "unexpected runFirstBackupCycleForProvider result (longpoll)", ); - case OperationAttemptResultType.Pending: + case TaskRunResultType.Pending: return { status: "payment-required", - talerUri: resp.result.talerUri, + talerUri: "FIXME", + //talerUri: resp.result.talerUri, }; default: assertUnreachable(resp); diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index 620054cae..cc16a4704 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -433,25 +433,25 @@ async function storePendingTaskFinished( }); } -export async function runTaskWithErrorReporting<T1, T2>( +export async function runTaskWithErrorReporting( ws: InternalWalletState, opId: TaskId, - f: () => Promise<OperationAttemptResult<T1, T2>>, -): Promise<OperationAttemptResult<T1, T2>> { + f: () => Promise<TaskRunResult>, +): Promise<TaskRunResult> { let maybeError: TalerErrorDetail | undefined; try { const resp = await f(); switch (resp.type) { - case OperationAttemptResultType.Error: + case TaskRunResultType.Error: await storePendingTaskError(ws, opId, resp.errorDetail); return resp; - case OperationAttemptResultType.Finished: + case TaskRunResultType.Finished: await storePendingTaskFinished(ws, opId); return resp; - case OperationAttemptResultType.Pending: + case TaskRunResultType.Pending: await storePendingTaskPending(ws, opId); return resp; - case OperationAttemptResultType.Longpoll: + case TaskRunResultType.Longpoll: return resp; } } catch (e) { @@ -459,7 +459,7 @@ export async function runTaskWithErrorReporting<T1, T2>( if (ws.stopped) { logger.warn("crypto API stopped during shutdown, ignoring error"); return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: makeErrorDetail( TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, {}, @@ -474,7 +474,7 @@ export async function runTaskWithErrorReporting<T1, T2>( maybeError = e.errorDetail; await storePendingTaskError(ws, opId, maybeError!); return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: e.errorDetail, }; } else if (e instanceof Error) { @@ -492,7 +492,7 @@ export async function runTaskWithErrorReporting<T1, T2>( ); await storePendingTaskError(ws, opId, maybeError); return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: maybeError, }; } else { @@ -504,7 +504,7 @@ export async function runTaskWithErrorReporting<T1, T2>( ); await storePendingTaskError(ws, opId, maybeError); return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: maybeError, }; } @@ -654,59 +654,55 @@ export interface TransactionManager { abort(): Promise<void>; suspend(): Promise<void>; resume(): Promise<void>; - process(): Promise<OperationAttemptResult>; + process(): Promise<TaskRunResult>; } -export enum OperationAttemptResultType { +export enum TaskRunResultType { Finished = "finished", Pending = "pending", Error = "error", Longpoll = "longpoll", } -export type OperationAttemptResult<TSuccess = unknown, TPending = unknown> = - | OperationAttemptFinishedResult<TSuccess> - | OperationAttemptErrorResult - | OperationAttemptLongpollResult - | OperationAttemptPendingResult<TPending>; +export type TaskRunResult = + | TaskRunFinishedResult + | TaskRunErrorResult + | TaskRunLongpollResult + | TaskRunPendingResult; -export namespace OperationAttemptResult { - export function finishedEmpty(): OperationAttemptResult<unknown, unknown> { +export namespace TaskRunResult { + export function finished(): TaskRunResult { return { - type: OperationAttemptResultType.Finished, - result: undefined, + type: TaskRunResultType.Finished, }; } - export function pendingEmpty(): OperationAttemptResult<unknown, unknown> { + export function pending(): TaskRunResult { return { - type: OperationAttemptResultType.Pending, - result: undefined, + type: TaskRunResultType.Pending, }; } - export function longpoll(): OperationAttemptResult<unknown, unknown> { + export function longpoll(): TaskRunResult { return { - type: OperationAttemptResultType.Longpoll, + type: TaskRunResultType.Longpoll, }; } } -export interface OperationAttemptFinishedResult<T> { - type: OperationAttemptResultType.Finished; - result: T; +export interface TaskRunFinishedResult { + type: TaskRunResultType.Finished; } -export interface OperationAttemptPendingResult<T> { - type: OperationAttemptResultType.Pending; - result: T; +export interface TaskRunPendingResult { + type: TaskRunResultType.Pending; } -export interface OperationAttemptErrorResult { - type: OperationAttemptResultType.Error; +export interface TaskRunErrorResult { + type: TaskRunResultType.Error; errorDetail: TalerErrorDetail; } -export interface OperationAttemptLongpollResult { - type: OperationAttemptResultType.Longpoll; +export interface TaskRunLongpollResult { + type: TaskRunResultType.Longpoll; } export interface RetryInfo { @@ -942,19 +938,3 @@ export namespace TaskIdentifiers { return `${PendingTaskType.PeerPushCredit}:${ppi.peerPushPaymentIncomingId}` as TaskId; } } - -/** - * Run an operation handler, expect a success result and extract the success value. - */ -export async function unwrapOperationHandlerResultOrThrow<T>( - res: OperationAttemptResult<T>, -): Promise<T> { - switch (res.type) { - case OperationAttemptResultType.Finished: - return res.result; - case OperationAttemptResultType.Error: - throw TalerError.fromUncheckedDetail(res.errorDetail); - default: - throw Error(`unexpected operation result (${res.type})`); - } -} diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 236fa6b59..a8ec859cf 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -81,7 +81,7 @@ import { InternalWalletState } from "../internal-wallet-state.js"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { constructTaskIdentifier, - OperationAttemptResult, + TaskRunResult, runLongpollAsync, spendCoins, TombstoneTag, @@ -462,7 +462,7 @@ async function checkDepositKycStatus( async function waitForRefreshOnDepositGroup( ws: InternalWalletState, depositGroup: DepositGroupRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const abortRefreshGroupId = depositGroup.abortRefreshGroupId; checkLogicInvariant(!!abortRefreshGroupId); const transactionId = constructTransactionIdentifier({ @@ -503,13 +503,13 @@ async function waitForRefreshOnDepositGroup( }); notifyTransition(ws, transactionId, transitionInfo); - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } async function refundDepositGroup( ws: InternalWalletState, depositGroup: DepositGroupRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const newTxPerCoin = [...depositGroup.transactionPerCoin]; logger.info(`status per coin: ${j2s(depositGroup.transactionPerCoin)}`); for (let i = 0; i < depositGroup.transactionPerCoin.length; i++) { @@ -614,13 +614,13 @@ async function refundDepositGroup( await tx.depositGroups.put(newDg); }); - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } async function processDepositGroupAborting( ws: InternalWalletState, depositGroup: DepositGroupRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { logger.info("processing deposit tx in 'aborting'"); const abortRefreshGroupId = depositGroup.abortRefreshGroupId; if (!abortRefreshGroupId) { @@ -634,7 +634,7 @@ async function processDepositGroupAborting( async function processDepositGroupPendingKyc( ws: InternalWalletState, depositGroup: DepositGroupRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const { depositGroupId } = depositGroup; const transactionId = constructTransactionIdentifier({ tag: TransactionType.Deposit, @@ -696,7 +696,7 @@ async function processDepositGroupPendingKyc( ); } }); - return OperationAttemptResult.longpoll(); + return TaskRunResult.longpoll(); } /** @@ -709,7 +709,7 @@ async function transitionToKycRequired( depositGroup: DepositGroupRecord, kycInfo: KycPendingInfo, exchangeUrl: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const { depositGroupId } = depositGroup; const userType = "individual"; @@ -728,7 +728,7 @@ async function transitionToKycRequired( }); if (kycStatusReq.status === HttpStatusCode.Ok) { logger.warn("kyc requested, but already fulfilled"); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } else if (kycStatusReq.status === HttpStatusCode.Accepted) { const kycStatus = await kycStatusReq.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); @@ -754,7 +754,7 @@ async function transitionToKycRequired( return { oldTxState, newTxState }; }); notifyTransition(ws, transactionId, transitionInfo); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } else { throw Error(`unexpected response from kyc-check (${kycStatusReq.status})`); } @@ -764,7 +764,7 @@ async function processDepositGroupPendingTrack( ws: InternalWalletState, depositGroup: DepositGroupRecord, cancellationToken?: CancellationToken, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const { depositGroupId } = depositGroup; for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) { const coinPub = depositGroup.payCoinSelection.coinPubs[i]; @@ -905,10 +905,10 @@ async function processDepositGroupPendingTrack( }); notifyTransition(ws, transactionId, transitionInfo); if (allWired) { - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } else { // FIXME: Use long-polling. - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } } @@ -916,7 +916,7 @@ async function processDepositGroupPendingDeposit( ws: InternalWalletState, depositGroup: DepositGroupRecord, cancellationToken?: CancellationToken, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { logger.info("processing deposit group in pending(deposit)"); const depositGroupId = depositGroup.depositGroupId; const contractData = extractContractData( @@ -1000,7 +1000,7 @@ async function processDepositGroupPendingDeposit( }); notifyTransition(ws, transactionId, transitionInfo); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } /** @@ -1012,7 +1012,7 @@ export async function processDepositGroup( options: { cancellationToken?: CancellationToken; } = {}, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const depositGroup = await ws.db .mktx((x) => [x.depositGroups]) .runReadOnly(async (tx) => { @@ -1020,7 +1020,7 @@ export async function processDepositGroup( }); if (!depositGroup) { logger.warn(`deposit group ${depositGroupId} not found`); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } switch (depositGroup.operationStatus) { @@ -1042,7 +1042,7 @@ export async function processDepositGroup( return processDepositGroupAborting(ws, depositGroup); } - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } async function getExchangeWireFee( diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 56ef672dc..c0373704a 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -76,11 +76,10 @@ import { } from "../util/query.js"; import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "../versions.js"; import { - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResultType, runTaskWithErrorReporting, TaskIdentifiers, - unwrapOperationHandlerResultOrThrow, + TaskRunResult, } from "./common.js"; const logger = new Logger("exchanges.ts"); @@ -559,13 +558,34 @@ export async function updateExchangeFromUrl( exchangeDetails: ExchangeDetailsRecord; }> { const canonUrl = canonicalizeBaseUrl(baseUrl); - return unwrapOperationHandlerResultOrThrow( - await runTaskWithErrorReporting( - ws, - TaskIdentifiers.forExchangeUpdateFromUrl(canonUrl), - () => updateExchangeFromUrlHandler(ws, canonUrl, options), - ), + const res = await runTaskWithErrorReporting( + ws, + TaskIdentifiers.forExchangeUpdateFromUrl(canonUrl), + () => updateExchangeFromUrlHandler(ws, canonUrl, options), ); + switch (res.type) { + case TaskRunResultType.Finished: { + const now = AbsoluteTime.now(); + const { exchange, exchangeDetails } = await ws.db + .mktx((x) => [x.exchanges, x.exchangeDetails]) + .runReadWrite(async (tx) => { + let exchange = await tx.exchanges.get(canonUrl); + const exchangeDetails = await getExchangeDetails(tx, baseUrl); + return { exchange, exchangeDetails }; + }); + if (!exchange) { + throw Error("exchange not found"); + } + if (!exchangeDetails) { + throw Error("exchange details not found"); + } + return { exchange, exchangeDetails }; + } + case TaskRunResultType.Error: + throw TalerError.fromUncheckedDetail(res.errorDetail); + default: + throw Error(`unexpected operation result (${res.type})`); + } } /** @@ -581,12 +601,7 @@ export async function updateExchangeFromUrlHandler( forceNow?: boolean; cancellationToken?: CancellationToken; } = {}, -): Promise< - OperationAttemptResult<{ - exchange: ExchangeRecord; - exchangeDetails: ExchangeDetailsRecord; - }> -> { +): Promise<TaskRunResult> { const forceNow = options.forceNow ?? false; logger.trace( `updating exchange info for ${exchangeBaseUrl}, forced: ${forceNow}`, @@ -620,10 +635,7 @@ export async function updateExchangeFromUrlHandler( } } - return { - type: OperationAttemptResultType.Finished, - result: { exchange, exchangeDetails }, - }; + return TaskRunResult.finished(); } logger.info("updating exchange /keys info"); @@ -679,7 +691,7 @@ export async function updateExchangeFromUrlHandler( }, ); return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail, }; } @@ -911,13 +923,7 @@ export async function updateExchangeFromUrlHandler( }); } - return { - type: OperationAttemptResultType.Finished, - result: { - exchange: updated.exchange, - exchangeDetails: updated.exchangeDetails, - }, - }; + return TaskRunResult.finished(); } /** diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index f2df08247..c74fcedcf 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -112,8 +112,8 @@ import { checkDbInvariant } from "../util/invariants.js"; import { GetReadOnlyAccess } from "../util/query.js"; import { constructTaskIdentifier, - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResult, + TaskRunResultType, RetryInfo, TaskIdentifiers, } from "./common.js"; @@ -325,7 +325,7 @@ export function extractContractData( async function processDownloadProposal( ws: InternalWalletState, proposalId: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const proposal = await ws.db .mktx((x) => [x.purchases]) .runReadOnly(async (tx) => { @@ -333,17 +333,11 @@ async function processDownloadProposal( }); if (!proposal) { - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } if (proposal.purchaseStatus != PurchaseStatus.PendingDownloadingProposal) { - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } const transactionId = constructTransactionIdentifier({ @@ -560,10 +554,7 @@ async function processDownloadProposal( notifyTransition(ws, transactionId, transitionInfo); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } /** @@ -1065,7 +1056,7 @@ export async function checkPaymentByProposalId( notifyTransition(ws, transactionId, transitionInfo); // FIXME: What about error handling?! This doesn't properly store errors in the DB. const r = await processPurchasePay(ws, proposalId, { forceNow: true }); - if (r.type !== OperationAttemptResultType.Finished) { + if (r.type !== TaskRunResultType.Finished) { // FIXME: This does not surface the original error throw Error("submitting pay failed"); } @@ -1253,7 +1244,7 @@ export async function runPayForConfirmPay( }); logger.trace(`processPurchasePay response type ${res.type}`); switch (res.type) { - case OperationAttemptResultType.Finished: { + case TaskRunResultType.Finished: { const purchase = await ws.db .mktx((x) => [x.purchases]) .runReadOnly(async (tx) => { @@ -1272,7 +1263,7 @@ export async function runPayForConfirmPay( }), }; } - case OperationAttemptResultType.Error: { + case TaskRunResultType.Error: { // We hide transient errors from the caller. const opRetry = await ws.db .mktx((x) => [x.operationRetries]) @@ -1286,7 +1277,7 @@ export async function runPayForConfirmPay( }), }; } - case OperationAttemptResultType.Pending: + case TaskRunResultType.Pending: logger.trace("reporting pending as confirmPay response"); return { type: ConfirmPayResultType.Pending, @@ -1296,7 +1287,7 @@ export async function runPayForConfirmPay( }), lastError: undefined, }; - case OperationAttemptResultType.Longpoll: + case TaskRunResultType.Longpoll: throw Error("unexpected processPurchasePay result (longpoll)"); default: assertUnreachable(res); @@ -1456,7 +1447,7 @@ export async function confirmPay( export async function processPurchase( ws: InternalWalletState, proposalId: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const purchase = await ws.db .mktx((x) => [x.purchases]) .runReadOnly(async (tx) => { @@ -1464,7 +1455,7 @@ export async function processPurchase( }); if (!purchase) { return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: { // FIXME: allocate more specific error code code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, @@ -1504,10 +1495,7 @@ export async function processPurchase( case PurchaseStatus.SuspendedQueryingAutoRefund: case PurchaseStatus.SuspendedQueryingRefund: case PurchaseStatus.FailedAbort: - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); default: assertUnreachable(purchase.purchaseStatus); // throw Error(`unexpected purchase status (${purchase.purchaseStatus})`); @@ -1518,7 +1506,7 @@ export async function processPurchasePay( ws: InternalWalletState, proposalId: string, options: unknown = {}, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const purchase = await ws.db .mktx((x) => [x.purchases]) .runReadOnly(async (tx) => { @@ -1526,7 +1514,7 @@ export async function processPurchasePay( }); if (!purchase) { return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: { // FIXME: allocate more specific error code code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, @@ -1541,7 +1529,7 @@ export async function processPurchasePay( case PurchaseStatus.PendingPayingReplay: break; default: - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } logger.trace(`processing purchase pay ${proposalId}`); @@ -1589,7 +1577,7 @@ export async function processPurchasePay( if (resp.status >= 500 && resp.status <= 599) { const errDetails = await readUnexpectedResponseDetails(resp); return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: makeErrorDetail( TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR, { @@ -1613,10 +1601,7 @@ export async function processPurchasePay( // FIXME: Should we really consider this to be pending? - return { - type: OperationAttemptResultType.Pending, - result: undefined, - }; + return TaskRunResult.pending(); } } @@ -1677,7 +1662,7 @@ export async function processPurchasePay( await unblockBackup(ws, proposalId); } - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } export async function refuseProposal( @@ -2114,7 +2099,7 @@ export function computePayMerchantTransactionActions( async function processPurchaseAutoRefund( ws: InternalWalletState, purchase: PurchaseRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const proposalId = purchase.proposalId; logger.trace(`processing auto-refund for proposal ${proposalId}`); @@ -2130,7 +2115,7 @@ async function processPurchaseAutoRefund( // FIXME: Put this logic into runLongpollAsync? if (ws.activeLongpoll[taskId]) { - return OperationAttemptResult.longpoll(); + return TaskRunResult.longpoll(); } const download = await expectProposalDownload(ws, purchase); @@ -2215,13 +2200,13 @@ async function processPurchaseAutoRefund( } }); - return OperationAttemptResult.longpoll(); + return TaskRunResult.longpoll(); } async function processPurchaseAbortingRefund( ws: InternalWalletState, purchase: PurchaseRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const proposalId = purchase.proposalId; const download = await expectProposalDownload(ws, purchase); logger.trace(`processing aborting-refund for proposal ${proposalId}`); @@ -2296,7 +2281,7 @@ async function processPurchaseAbortingRefund( async function processPurchaseQueryRefund( ws: InternalWalletState, purchase: PurchaseRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const proposalId = purchase.proposalId; logger.trace(`processing query-refund for proposal ${proposalId}`); @@ -2341,7 +2326,7 @@ async function processPurchaseQueryRefund( return { oldTxState, newTxState }; }); notifyTransition(ws, transactionId, transitionInfo); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } else { const refundAwaiting = Amounts.sub( Amounts.parseOrThrow(orderStatus.refund_amount), @@ -2367,14 +2352,14 @@ async function processPurchaseQueryRefund( return { oldTxState, newTxState }; }); notifyTransition(ws, transactionId, transitionInfo); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } } async function processPurchaseAcceptRefund( ws: InternalWalletState, purchase: PurchaseRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const proposalId = purchase.proposalId; const download = await expectProposalDownload(ws, purchase); @@ -2472,7 +2457,7 @@ async function storeRefunds( purchase: PurchaseRecord, refunds: MerchantCoinRefundStatus[], reason: RefundReason, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { logger.info(`storing refunds: ${j2s(refunds)}`); const transactionId = constructTransactionIdentifier({ @@ -2699,16 +2684,16 @@ async function storeRefunds( }); if (!result) { - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } notifyTransition(ws, transactionId, result.transitionInfo); if (result.numPendingItemsTotal > 0) { - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } export function computeRefundTransactionState( diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts index 88bdcb90e..4c00ed592 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts @@ -66,8 +66,8 @@ import { assertUnreachable } from "../util/assertUnreachable.js"; import { checkDbInvariant } from "../util/invariants.js"; import { LongpollResult, - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResult, + TaskRunResultType, constructTaskIdentifier, runLongpollAsync, } from "./common.js"; @@ -184,7 +184,7 @@ async function longpollKycStatus( exchangeUrl: string, kycInfo: KycPendingInfo, userType: KycUserType, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPullCredit, pursePub, @@ -242,14 +242,14 @@ async function longpollKycStatus( } }); return { - type: OperationAttemptResultType.Longpoll, + type: TaskRunResultType.Longpoll, }; } async function processPeerPullCreditAbortingDeletePurse( ws: InternalWalletState, peerPullIni: PeerPullPaymentInitiationRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const { pursePub, pursePriv } = peerPullIni; const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPushDebit, @@ -296,13 +296,13 @@ async function processPeerPullCreditAbortingDeletePurse( }); notifyTransition(ws, transactionId, transitionInfo); - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } async function handlePeerPullCreditWithdrawing( ws: InternalWalletState, pullIni: PeerPullPaymentInitiationRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { if (!pullIni.withdrawalGroupId) { throw Error("invalid db state (withdrawing, but no withdrawal group ID"); } @@ -346,17 +346,17 @@ async function handlePeerPullCreditWithdrawing( }); notifyTransition(ws, transactionId, transitionInfo); if (finished) { - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } else { // FIXME: Return indicator that we depend on the other operation! - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } } async function handlePeerPullCreditCreatePurse( ws: InternalWalletState, pullIni: PeerPullPaymentInitiationRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const purseFee = Amounts.stringify(Amounts.zeroOfAmount(pullIni.amount)); const pursePub = pullIni.pursePub; const mergeReserve = await ws.db @@ -447,16 +447,13 @@ async function handlePeerPullCreditCreatePurse( await tx.peerPullPaymentInitiations.put(pi2); }); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } export async function processPeerPullCredit( ws: InternalWalletState, pursePub: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const pullIni = await ws.db .mktx((x) => [x.peerPullPaymentInitiations]) .runReadOnly(async (tx) => { @@ -475,7 +472,7 @@ export async function processPeerPullCredit( if (ws.activeLongpoll[retryTag]) { logger.info("peer-pull-credit already in long-polling, returning!"); return { - type: OperationAttemptResultType.Longpoll, + type: TaskRunResultType.Longpoll, }; } @@ -483,10 +480,7 @@ export async function processPeerPullCredit( switch (pullIni.status) { case PeerPullPaymentInitiationStatus.Done: { - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } case PeerPullPaymentInitiationStatus.PendingReady: runLongpollAsync(ws, retryTag, async (cancellationToken) => @@ -496,7 +490,7 @@ export async function processPeerPullCredit( "returning early from processPeerPullCredit for long-polling in background", ); return { - type: OperationAttemptResultType.Longpoll, + type: TaskRunResultType.Longpoll, }; case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: { if (!pullIni.kycInfo) { @@ -528,14 +522,14 @@ export async function processPeerPullCredit( assertUnreachable(pullIni.status); } - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } async function processPeerPullCreditKycRequired( ws: InternalWalletState, peerIni: PeerPullPaymentInitiationRecord, kycPending: WalletKycUuid, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPullCredit, pursePub: peerIni.pursePub, @@ -560,10 +554,7 @@ async function processPeerPullCreditKycRequired( kycStatusRes.status === HttpStatusCode.NoContent ) { logger.warn("kyc requested, but already fulfilled"); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } else if (kycStatusRes.status === HttpStatusCode.Accepted) { const kycStatus = await kycStatusRes.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); @@ -574,7 +565,7 @@ async function processPeerPullCreditKycRequired( if (!peerInc) { return { transitionInfo: undefined, - result: OperationAttemptResult.finishedEmpty(), + result: TaskRunResult.finished(), }; } const oldTxState = computePeerPullCreditTransactionState(peerInc); @@ -589,8 +580,8 @@ async function processPeerPullCreditKycRequired( await tx.peerPullPaymentInitiations.put(peerInc); // We'll remove this eventually! New clients should rely on the // kycUrl field of the transaction, not the error code. - const res: OperationAttemptResult = { - type: OperationAttemptResultType.Error, + const res: TaskRunResult = { + type: TaskRunResultType.Error, errorDetail: makeErrorDetail( TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED, { @@ -604,10 +595,7 @@ async function processPeerPullCreditKycRequired( }; }); notifyTransition(ws, transactionId, transitionInfo); - return { - type: OperationAttemptResultType.Pending, - result: undefined, - }; + return TaskRunResult.pending(); } else { throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`); } diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts index 9d8fabfb2..1aa332439 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts @@ -62,8 +62,8 @@ import { import { assertUnreachable } from "../util/assertUnreachable.js"; import { checkLogicInvariant } from "../util/invariants.js"; import { - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResult, + TaskRunResultType, TaskIdentifiers, constructTaskIdentifier, runTaskWithErrorReporting, @@ -89,12 +89,12 @@ async function handlePurseCreationConflict( ws: InternalWalletState, peerPullInc: PeerPullPaymentIncomingRecord, resp: HttpResponse, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const pursePub = peerPullInc.pursePub; const errResp = await readTalerErrorResponse(resp); if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) { await failPeerPullDebitTransaction(ws, pursePub); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } // FIXME: Properly parse! @@ -167,13 +167,13 @@ async function handlePurseCreationConflict( } await tx.peerPullPaymentIncoming.put(myPpi); }); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } async function processPeerPullDebitPendingDeposit( ws: InternalWalletState, peerPullInc: PeerPullPaymentIncomingRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const peerPullPaymentIncomingId = peerPullInc.peerPullPaymentIncomingId; const pursePub = peerPullInc.pursePub; @@ -299,21 +299,18 @@ async function processPeerPullDebitPendingDeposit( default: { const errResp = await readTalerErrorResponse(httpResp); return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: errResp, }; } } - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } async function processPeerPullDebitAbortingRefresh( ws: InternalWalletState, peerPullInc: PeerPullPaymentIncomingRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const peerPullPaymentIncomingId = peerPullInc.peerPullPaymentIncomingId; const abortRefreshGroupId = peerPullInc.abortRefreshGroupId; checkLogicInvariant(!!abortRefreshGroupId); @@ -357,13 +354,13 @@ async function processPeerPullDebitAbortingRefresh( }); notifyTransition(ws, transactionId, transitionInfo); // FIXME: Shouldn't this be finished in some cases?! - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } export async function processPeerPullDebit( ws: InternalWalletState, peerPullPaymentIncomingId: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const peerPullInc = await ws.db .mktx((x) => [x.peerPullPaymentIncoming]) .runReadOnly(async (tx) => { @@ -379,10 +376,7 @@ export async function processPeerPullDebit( case PeerPullDebitRecordStatus.AbortingRefresh: return await processPeerPullDebitAbortingRefresh(ws, peerPullInc); } - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } export async function confirmPeerPullDebit( diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts index 3e5750af7..e76b934fa 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts @@ -62,8 +62,8 @@ import { import { assertUnreachable } from "../util/assertUnreachable.js"; import { checkDbInvariant } from "../util/invariants.js"; import { - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResult, + TaskRunResultType, constructTaskIdentifier, runLongpollAsync, } from "./common.js"; @@ -233,7 +233,7 @@ async function longpollKycStatus( exchangeUrl: string, kycInfo: KycPendingInfo, userType: KycUserType, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, peerPushPaymentIncomingId, @@ -293,7 +293,7 @@ async function longpollKycStatus( } }); return { - type: OperationAttemptResultType.Longpoll, + type: TaskRunResultType.Longpoll, }; } @@ -301,7 +301,7 @@ async function processPeerPushCreditKycRequired( ws: InternalWalletState, peerInc: PeerPushPaymentIncomingRecord, kycPending: WalletKycUuid, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId, @@ -326,10 +326,7 @@ async function processPeerPushCreditKycRequired( kycStatusRes.status === HttpStatusCode.NoContent ) { logger.warn("kyc requested, but already fulfilled"); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } else if (kycStatusRes.status === HttpStatusCode.Accepted) { const kycStatus = await kycStatusRes.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); @@ -342,7 +339,7 @@ async function processPeerPushCreditKycRequired( if (!peerInc) { return { transitionInfo: undefined, - result: OperationAttemptResult.finishedEmpty(), + result: TaskRunResult.finished(), }; } const oldTxState = computePeerPushCreditTransactionState(peerInc); @@ -356,8 +353,8 @@ async function processPeerPushCreditKycRequired( await tx.peerPushPaymentIncoming.put(peerInc); // We'll remove this eventually! New clients should rely on the // kycUrl field of the transaction, not the error code. - const res: OperationAttemptResult = { - type: OperationAttemptResultType.Error, + const res: TaskRunResult = { + type: TaskRunResultType.Error, errorDetail: makeErrorDetail( TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED, { @@ -381,7 +378,7 @@ async function handlePendingMerge( ws: InternalWalletState, peerInc: PeerPushPaymentIncomingRecord, contractTerms: PeerContractTerms, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const { peerPushPaymentIncomingId } = peerInc; const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, @@ -506,16 +503,13 @@ async function handlePendingMerge( ); notifyTransition(ws, transactionId, txRes?.peerPushCreditTransition); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } async function handlePendingWithdrawing( ws: InternalWalletState, peerInc: PeerPushPaymentIncomingRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { if (!peerInc.withdrawalGroupId) { throw Error("invalid db state (withdrawing, but no withdrawal group ID"); } @@ -561,17 +555,17 @@ async function handlePendingWithdrawing( }); notifyTransition(ws, transactionId, transitionInfo); if (finished) { - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } else { // FIXME: Return indicator that we depend on the other operation! - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } } export async function processPeerPushCredit( ws: InternalWalletState, peerPushPaymentIncomingId: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { let peerInc: PeerPushPaymentIncomingRecord | undefined; let contractTerms: PeerContractTerms | undefined; await ws.db @@ -617,7 +611,7 @@ export async function processPeerPushCredit( return handlePendingWithdrawing(ws, peerInc); default: - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } } diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts index 9ae94fff8..c853bc0ef 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts @@ -61,8 +61,8 @@ import { PendingTaskType } from "../pending-types.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; import { checkLogicInvariant } from "../util/invariants.js"; import { - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResult, + TaskRunResultType, constructTaskIdentifier, runLongpollAsync, spendCoins, @@ -110,12 +110,12 @@ async function handlePurseCreationConflict( ws: InternalWalletState, peerPushInitiation: PeerPushPaymentInitiationRecord, resp: HttpResponse, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const pursePub = peerPushInitiation.pursePub; const errResp = await readTalerErrorResponse(resp); if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) { await failPeerPushDebitTransaction(ws, pursePub); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } // FIXME: Properly parse! @@ -176,13 +176,13 @@ async function handlePurseCreationConflict( } await tx.peerPushPaymentInitiations.put(myPpi); }); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } async function processPeerPushDebitCreateReserve( ws: InternalWalletState, peerPushInitiation: PeerPushPaymentInitiationRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { logger.info("processing peer-push-debit pending(create-reserve)"); const pursePub = peerPushInitiation.pursePub; const purseExpiration = peerPushInitiation.purseExpiration; @@ -264,7 +264,7 @@ async function processPeerPushDebitCreateReserve( case HttpStatusCode.Forbidden: { // FIXME: Store this error! await failPeerPushDebitTransaction(ws, pursePub); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } case HttpStatusCode.Conflict: { // Handle double-spending @@ -273,7 +273,7 @@ async function processPeerPushDebitCreateReserve( default: { const errResp = await readTalerErrorResponse(httpResp); return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: errResp, }; } @@ -289,13 +289,13 @@ async function processPeerPushDebitCreateReserve( stTo: PeerPushPaymentInitiationStatus.PendingReady, }); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } async function processPeerPushDebitAbortingDeletePurse( ws: InternalWalletState, peerPushInitiation: PeerPushPaymentInitiationRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const { pursePub, pursePriv } = peerPushInitiation; const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPushDebit, @@ -364,7 +364,7 @@ async function processPeerPushDebitAbortingDeletePurse( }); notifyTransition(ws, transactionId, transitionInfo); - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } interface SimpleTransition { @@ -406,7 +406,7 @@ async function transitionPeerPushDebitTransaction( async function processPeerPushDebitAbortingRefresh( ws: InternalWalletState, peerPushInitiation: PeerPushPaymentInitiationRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const pursePub = peerPushInitiation.pursePub; const abortRefreshGroupId = peerPushInitiation.abortRefreshGroupId; checkLogicInvariant(!!abortRefreshGroupId); @@ -448,7 +448,7 @@ async function processPeerPushDebitAbortingRefresh( }); notifyTransition(ws, transactionId, transitionInfo); // FIXME: Shouldn't this be finished in some cases?! - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); } /** @@ -457,7 +457,7 @@ async function processPeerPushDebitAbortingRefresh( async function processPeerPushDebitReady( ws: InternalWalletState, peerPushInitiation: PeerPushPaymentInitiationRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { logger.info("processing peer-push-debit pending(ready)"); const pursePub = peerPushInitiation.pursePub; const retryTag = constructTaskIdentifier({ @@ -520,14 +520,14 @@ async function processPeerPushDebitReady( "returning early from peer-push-debit for long-polling in background", ); return { - type: OperationAttemptResultType.Longpoll, + type: TaskRunResultType.Longpoll, }; } export async function processPeerPushDebit( ws: InternalWalletState, pursePub: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const peerPushInitiation = await ws.db .mktx((x) => [x.peerPushPaymentInitiations]) .runReadOnly(async (tx) => { @@ -546,7 +546,7 @@ export async function processPeerPushDebit( if (ws.activeLongpoll[retryTag]) { logger.info("peer-push-debit task already in long-polling, returning!"); return { - type: OperationAttemptResultType.Longpoll, + type: TaskRunResultType.Longpoll, }; } @@ -567,10 +567,7 @@ export async function processPeerPushDebit( } } - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } /** diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 056aa83b8..c8c766d1b 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -55,7 +55,7 @@ import { checkDbInvariant } from "../util/invariants.js"; import { GetReadWriteAccess } from "../util/query.js"; import { createRefreshGroup, processRefreshGroup } from "./refresh.js"; import { internalCreateWithdrawalGroup } from "./withdraw.js"; -import { OperationAttemptResult } from "./common.js"; +import { TaskRunResult } from "./common.js"; const logger = new Logger("operations/recoup.ts"); @@ -289,18 +289,18 @@ async function recoupRefreshCoin( export async function processRecoupGroup( ws: InternalWalletState, recoupGroupId: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { let recoupGroup = await ws.db .mktx((x) => [x.recoupGroups]) .runReadOnly(async (tx) => { return tx.recoupGroups.get(recoupGroupId); }); if (!recoupGroup) { - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } if (recoupGroup.timestampFinished) { logger.trace("recoup group finished"); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } const ps = recoupGroup.coinPubs.map(async (x, i) => { try { @@ -318,12 +318,12 @@ export async function processRecoupGroup( return tx.recoupGroups.get(recoupGroupId); }); if (!recoupGroup) { - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } for (const b of recoupGroup.recoupFinishedPerCoin) { if (!b) { - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } } @@ -408,7 +408,7 @@ export async function processRecoupGroup( } await tx.recoupGroups.put(rg2); }); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } export async function createRecoupGroup( diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index c1a16badf..caa5f9c9f 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -89,8 +89,8 @@ import { constructTaskIdentifier, makeCoinAvailable, makeCoinsVisible, - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResult, + TaskRunResultType, } from "./common.js"; import { updateExchangeFromUrl } from "./exchanges.js"; import { @@ -770,23 +770,17 @@ export async function processRefreshGroup( ws: InternalWalletState, refreshGroupId: string, options: Record<string, never> = {}, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { logger.info(`processing refresh group ${refreshGroupId}`); const refreshGroup = await ws.db .mktx((x) => [x.refreshGroups]) .runReadOnly(async (tx) => tx.refreshGroups.get(refreshGroupId)); if (!refreshGroup) { - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished() } if (refreshGroup.timestampFinished) { - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } // Process refresh sessions of the group in parallel. logger.trace("processing refresh sessions for old coins"); @@ -823,14 +817,11 @@ export async function processRefreshGroup( logger.warn(`exception: ${e}`); } if (inShutdown) { - return { - type: OperationAttemptResultType.Pending, - result: undefined, - }; + return TaskRunResult.pending(); } if (errors.length > 0) { return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: makeErrorDetail( TalerErrorCode.WALLET_REFRESH_GROUP_INCOMPLETE, { @@ -841,10 +832,7 @@ export async function processRefreshGroup( }; } - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.pending(); } async function processRefreshSession( @@ -1122,7 +1110,7 @@ function getAutoRefreshExecuteThreshold(d: DenominationRecord): AbsoluteTime { export async function autoRefresh( ws: InternalWalletState, exchangeBaseUrl: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`); // We must make sure that the exchange is up-to-date so that @@ -1204,7 +1192,7 @@ export async function autoRefresh( AbsoluteTime.toPreciseTimestamp(minCheckThreshold); await tx.exchanges.put(exchange); }); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } export function computeRefreshTransactionState( diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index ece71439c..77e218cd7 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -24,6 +24,7 @@ import { Duration, IntegrationTestV2Args, Logger, + NotificationType, stringToBytes, TestPayResult, WithdrawTestBalanceRequest, @@ -64,6 +65,7 @@ import { confirmPeerPushCredit, } from "./pay-peer-push-credit.js"; import { initiatePeerPushDebit } from "./pay-peer-push-debit.js"; +import { OpenedPromise, openPromise } from "../index.js"; const logger = new Logger("operations/testing.ts"); @@ -445,6 +447,18 @@ export async function runIntegrationTest( logger.trace("integration test: all done!"); } +async function waitUntilDone(ws: InternalWalletState): Promise<void> { + let p: OpenedPromise<void> | undefined = undefined; + ws.addNotificationListener((notif) => { + if (!p) { + return; + } + if (notif.type === NotificationType.TransactionStateTransition) { + p.resolve(); + } + }); +} + export async function runIntegrationTest2( ws: InternalWalletState, args: IntegrationTestV2Args, diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 18ef03c51..e56fb1e8d 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -62,8 +62,8 @@ import { constructTaskIdentifier, makeCoinAvailable, makeCoinsVisible, - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResult, + TaskRunResultType, } from "./common.js"; import { updateExchangeFromUrl } from "./exchanges.js"; import { @@ -241,17 +241,14 @@ export async function prepareTip( export async function processTip( ws: InternalWalletState, walletTipId: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const tipRecord = await ws.db .mktx((x) => [x.tips]) .runReadOnly(async (tx) => { return tx.tips.get(walletTipId); }); if (!tipRecord) { - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } switch (tipRecord.status) { @@ -259,10 +256,7 @@ export async function processTip( case TipRecordStatus.DialogAccept: case TipRecordStatus.Done: case TipRecordStatus.SuspendidPickup: - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } const transactionId = constructTransactionIdentifier({ @@ -324,7 +318,7 @@ export async function processTip( logger.trace(`got transient tip error`); // FIXME: wrap in another error code that indicates a transient error return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: makeErrorDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, getHttpResponseErrorDetails(merchantResp), @@ -376,7 +370,7 @@ export async function processTip( if (!isValid) { return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: makeErrorDetail( TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID, {}, @@ -430,10 +424,7 @@ export async function processTip( notifyTransition(ws, transactionId, transitionInfo); ws.notify({ type: NotificationType.BalanceChange }); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } export async function acceptTip( diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 1362ca278..f972d3cb1 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -90,8 +90,8 @@ import { } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; import { - OperationAttemptResult, - OperationAttemptResultType, + TaskRunResult, + TaskRunResultType, TaskIdentifiers, constructTaskIdentifier, makeCoinAvailable, @@ -1326,7 +1326,7 @@ export interface WithdrawalGroupContext { async function processWithdrawalGroupAbortingBank( ws: InternalWalletState, withdrawalGroup: WithdrawalGroupRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const { withdrawalGroupId } = withdrawalGroup; const transactionId = constructTransactionIdentifier({ tag: TransactionType.Withdrawal, @@ -1363,10 +1363,7 @@ async function processWithdrawalGroupAbortingBank( }; }); notifyTransition(ws, transactionId, transitionInfo); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } /** @@ -1413,7 +1410,7 @@ async function transitionKycSatisfied( async function processWithdrawalGroupPendingKyc( ws: InternalWalletState, withdrawalGroup: WithdrawalGroupRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const userType = "individual"; const kycInfo = withdrawalGroup.kycPending; if (!kycInfo) { @@ -1456,13 +1453,13 @@ async function processWithdrawalGroupPendingKyc( ); } }); - return OperationAttemptResult.longpoll(); + return TaskRunResult.longpoll(); } async function processWithdrawalGroupPendingReady( ws: InternalWalletState, withdrawalGroup: WithdrawalGroupRecord, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { const { withdrawalGroupId } = withdrawalGroup; const transactionId = constructTransactionIdentifier({ tag: TransactionType.Withdrawal, @@ -1494,7 +1491,7 @@ async function processWithdrawalGroupPendingReady( }; }); notifyTransition(ws, transactionId, transitionInfo); - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); } const numTotalCoins = withdrawalGroup.denomsSel.selectedDenoms @@ -1608,7 +1605,7 @@ async function processWithdrawalGroupPendingReady( if (numPlanchetErrors > 0) { return { - type: OperationAttemptResultType.Error, + type: TaskRunResultType.Error, errorDetail: makeErrorDetail( TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE, { @@ -1619,16 +1616,13 @@ async function processWithdrawalGroupPendingReady( }; } - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); } export async function processWithdrawalGroup( ws: InternalWalletState, withdrawalGroupId: string, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { logger.trace("processing withdrawal group", withdrawalGroupId); const withdrawalGroup = await ws.db .mktx((x) => [x.withdrawalGroups]) @@ -1646,7 +1640,7 @@ export async function processWithdrawalGroup( if (ws.activeLongpoll[retryTag]) { logger.info("withdrawal group already in long-polling, returning!"); return { - type: OperationAttemptResultType.Longpoll, + type: TaskRunResultType.Longpoll, }; } @@ -1663,7 +1657,7 @@ export async function processWithdrawalGroup( "returning early from withdrawal for long-polling in background", ); return { - type: OperationAttemptResultType.Longpoll, + type: TaskRunResultType.Longpoll, }; } case WithdrawalGroupStatus.PendingWaitConfirmBank: { @@ -1671,15 +1665,9 @@ export async function processWithdrawalGroup( switch (res.status) { case BankStatusResultCode.Aborted: case BankStatusResultCode.Done: - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return TaskRunResult.finished(); case BankStatusResultCode.Waiting: { - return { - type: OperationAttemptResultType.Pending, - result: undefined, - }; + return TaskRunResult.pending(); } } break; @@ -1687,14 +1675,11 @@ export async function processWithdrawalGroup( case WithdrawalGroupStatus.Finished: case WithdrawalGroupStatus.FailedBankAborted: { // FIXME - return { - type: OperationAttemptResultType.Pending, - result: undefined, - }; + return TaskRunResult.pending(); } case WithdrawalGroupStatus.PendingAml: // FIXME: Handle this case, withdrawal doesn't support AML yet. - return OperationAttemptResult.pendingEmpty(); + return TaskRunResult.pending(); case WithdrawalGroupStatus.PendingKyc: return processWithdrawalGroupPendingKyc(ws, withdrawalGroup); case WithdrawalGroupStatus.PendingReady: @@ -1713,7 +1698,7 @@ export async function processWithdrawalGroup( case WithdrawalGroupStatus.SuspendedRegisteringBank: case WithdrawalGroupStatus.SuspendedWaitConfirmBank: // Nothing to do. - return OperationAttemptResult.finishedEmpty(); + return TaskRunResult.finished(); default: assertUnreachable(withdrawalGroup.status); } diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index c72c7236f..583dc33d4 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -171,6 +171,7 @@ import { import { setWalletDeviceId } from "./operations/backup/state.js"; import { getBalanceDetail, getBalances } from "./operations/balance.js"; import { + TaskRunResult, getExchangeTosStatus, makeExchangeListItem, runTaskWithErrorReporting, @@ -287,7 +288,6 @@ import { GetReadWriteAccess, } from "./util/query.js"; import { - OperationAttemptResult, TaskIdentifiers, } from "./operations/common.js"; import { TimerAPI, TimerGroup } from "./util/timer.js"; @@ -320,7 +320,7 @@ const logger = new Logger("wallet.ts"); async function callOperationHandler( ws: InternalWalletState, pending: PendingTaskInfo, -): Promise<OperationAttemptResult> { +): Promise<TaskRunResult> { switch (pending.type) { case PendingTaskType.ExchangeUpdate: return await updateExchangeFromUrlHandler(ws, pending.exchangeBaseUrl); diff --git a/packages/taler-wallet-embedded/src/wallet-qjs.ts b/packages/taler-wallet-embedded/src/wallet-qjs.ts index 57452301f..7e2ee1b2d 100644 --- a/packages/taler-wallet-embedded/src/wallet-qjs.ts +++ b/packages/taler-wallet-embedded/src/wallet-qjs.ts @@ -54,6 +54,11 @@ setPRNG(function (x: Uint8Array, n: number) { const logger = new Logger("taler-wallet-embedded/index.ts"); +/** + * Sends JSON to the host application, i.e. the process that + * runs the JavaScript interpreter (quickjs / qtart) to run + * the embedded wallet. + */ function sendNativeMessage(ev: CoreApiMessageEnvelope): void { const m = JSON.stringify(ev); qjsOs.postMessageToHost(m); @@ -183,21 +188,25 @@ export function installNativeWalletListener(): void { const id = msg.id; logger.info(`native listener: got request for ${operation} (${id})`); - let respMsg: CoreApiResponse; - try { - respMsg = await handler.handleMessage(operation, id, msg.args ?? {}); - } catch (e) { - respMsg = { - type: "error", - id, - operation, - error: getErrorDetailFromException(e), - }; + if (operation === "anastasisReduce") { + sendNativeMessage(respMsg); + } else { + let respMsg: CoreApiResponse; + try { + respMsg = await handler.handleMessage(operation, id, msg.args ?? {}); + } catch (e) { + respMsg = { + type: "error", + id, + operation, + error: getErrorDetailFromException(e), + }; + } + logger.info( + `native listener: sending back ${respMsg.type} message for operation ${operation} (${id})`, + ); + sendNativeMessage(respMsg); } - logger.info( - `native listener: sending back ${respMsg.type} message for operation ${operation} (${id})`, - ); - sendNativeMessage(respMsg); }; qjsOs.setMessageFromHostHandler((m) => onMessage(m)); |