diff options
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));  | 
