diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pending.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/pending.ts | 814 |
1 files changed, 0 insertions, 814 deletions
diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts deleted file mode 100644 index 990d9a7b3..000000000 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ /dev/null @@ -1,814 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Derive pending tasks from the wallet database. - */ - -/** - * Imports. - */ -import { GlobalIDB } from "@gnu-taler/idb-bridge"; -import { - AbsoluteTime, - TalerPreciseTimestamp, - TransactionRecordFilter, -} from "@gnu-taler/taler-util"; -import { - BackupProviderStateTag, - DbPreciseTimestamp, - DepositElementStatus, - DepositGroupRecord, - ExchangeEntryDbUpdateStatus, - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - PeerPullCreditRecord, - PeerPullDebitRecordStatus, - PeerPullPaymentIncomingRecord, - PeerPushCreditStatus, - PeerPushDebitRecord, - PeerPushPaymentIncomingRecord, - PurchaseRecord, - PurchaseStatus, - RefreshCoinStatus, - RefreshGroupRecord, - RefreshOperationStatus, - RefundGroupRecord, - RewardRecord, - WalletStoresV1, - WithdrawalGroupRecord, - timestampAbsoluteFromDb, - timestampOptionalAbsoluteFromDb, - timestampPreciseFromDb, - timestampPreciseToDb, -} from "../db.js"; -import { InternalWalletState } from "../internal-wallet-state.js"; -import { - PendingOperationsResponse, - PendingTaskType, - TaskId, -} from "../pending-types.js"; -import { GetReadOnlyAccess } from "../util/query.js"; -import { TaskIdentifiers } from "./common.js"; - -function getPendingCommon( - ws: InternalWalletState, - opTag: TaskId, - timestampDue: AbsoluteTime, -): { - id: TaskId; - isDue: boolean; - timestampDue: AbsoluteTime; - isLongpolling: boolean; -} { - const isDue = - AbsoluteTime.isExpired(timestampDue) && !ws.activeLongpoll[opTag]; - return { - id: opTag, - isDue, - timestampDue, - isLongpolling: !!ws.activeLongpoll[opTag], - }; -} - -async function gatherExchangePending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - exchanges: typeof WalletStoresV1.exchanges; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - let timestampDue: DbPreciseTimestamp | undefined = undefined; - await tx.exchanges.iter().forEachAsync(async (exch) => { - switch (exch.updateStatus) { - case ExchangeEntryDbUpdateStatus.Initial: - case ExchangeEntryDbUpdateStatus.Suspended: - return; - } - const opUpdateExchangeTag = TaskIdentifiers.forExchangeUpdate(exch); - let opr = await tx.operationRetries.get(opUpdateExchangeTag); - - switch (exch.updateStatus) { - case ExchangeEntryDbUpdateStatus.Ready: - timestampDue = opr?.retryInfo.nextRetry ?? exch.nextRefreshCheckStamp; - break; - case ExchangeEntryDbUpdateStatus.ReadyUpdate: - case ExchangeEntryDbUpdateStatus.InitialUpdate: - case ExchangeEntryDbUpdateStatus.UnavailableUpdate: - timestampDue = - opr?.retryInfo.nextRetry ?? - timestampPreciseToDb(TalerPreciseTimestamp.now()); - break; - } - - resp.pendingOperations.push({ - type: PendingTaskType.ExchangeUpdate, - ...getPendingCommon( - ws, - opUpdateExchangeTag, - AbsoluteTime.fromPreciseTimestamp(timestampPreciseFromDb(timestampDue)), - ), - givesLifeness: false, - exchangeBaseUrl: exch.baseUrl, - lastError: opr?.lastError, - }); - - // We only schedule a check for auto-refresh if the exchange update - // was successful. - if (!opr?.lastError) { - const opCheckRefreshTag = TaskIdentifiers.forExchangeCheckRefresh(exch); - resp.pendingOperations.push({ - type: PendingTaskType.ExchangeCheckRefresh, - ...getPendingCommon( - ws, - opCheckRefreshTag, - AbsoluteTime.fromPreciseTimestamp( - timestampPreciseFromDb(timestampDue), - ), - ), - timestampDue: AbsoluteTime.fromPreciseTimestamp( - timestampPreciseFromDb(exch.nextRefreshCheckStamp), - ), - givesLifeness: false, - exchangeBaseUrl: exch.baseUrl, - }); - } - }); -} - -/** - * Iterate refresh records based on a filter. - */ -export async function iterRecordsForRefresh( - tx: GetReadOnlyAccess<{ - refreshGroups: typeof WalletStoresV1.refreshGroups; - }>, - filter: TransactionRecordFilter, - f: (r: RefreshGroupRecord) => Promise<void>, -): Promise<void> { - let refreshGroups: RefreshGroupRecord[]; - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - RefreshOperationStatus.Pending, - RefreshOperationStatus.Suspended, - ); - refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll(keyRange); - } else { - refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll(); - } - - for (const r of refreshGroups) { - await f(r); - } -} - -async function gatherRefreshPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - refreshGroups: typeof WalletStoresV1.refreshGroups; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await iterRecordsForRefresh(tx, { onlyState: "nonfinal" }, async (r) => { - if (r.timestampFinished) { - return; - } - const opId = TaskIdentifiers.forRefresh(r); - const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? - AbsoluteTime.now(); - resp.pendingOperations.push({ - type: PendingTaskType.Refresh, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: true, - refreshGroupId: r.refreshGroupId, - finishedPerCoin: r.statusPerCoin.map( - (x) => x === RefreshCoinStatus.Finished, - ), - retryInfo: retryRecord?.retryInfo, - }); - }); -} - -export async function iterRecordsForWithdrawal( - tx: GetReadOnlyAccess<{ - withdrawalGroups: typeof WalletStoresV1.withdrawalGroups; - }>, - filter: TransactionRecordFilter, - f: (r: WithdrawalGroupRecord) => Promise<void>, -): Promise<void> { - let withdrawalGroupRecords: WithdrawalGroupRecord[]; - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - ); - withdrawalGroupRecords = - await tx.withdrawalGroups.indexes.byStatus.getAll(keyRange); - } else { - withdrawalGroupRecords = - await tx.withdrawalGroups.indexes.byStatus.getAll(); - } - for (const wgr of withdrawalGroupRecords) { - await f(wgr); - } -} - -async function gatherWithdrawalPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - withdrawalGroups: typeof WalletStoresV1.withdrawalGroups; - planchets: typeof WalletStoresV1.planchets; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await iterRecordsForWithdrawal(tx, { onlyState: "nonfinal" }, async (wsr) => { - const opTag = TaskIdentifiers.forWithdrawal(wsr); - let opr = await tx.operationRetries.get(opTag); - /** - * kyc pending operation don't give lifeness - * since the user need to complete kyc procedure - */ - const userNeedToCompleteKYC = wsr.kycUrl !== undefined; - const now = AbsoluteTime.now(); - if (!opr) { - opr = { - id: opTag, - retryInfo: { - firstTry: timestampPreciseToDb(AbsoluteTime.toPreciseTimestamp(now)), - nextRetry: timestampPreciseToDb(AbsoluteTime.toPreciseTimestamp(now)), - retryCounter: 0, - }, - }; - } - resp.pendingOperations.push({ - type: PendingTaskType.Withdraw, - ...getPendingCommon( - ws, - opTag, - timestampOptionalAbsoluteFromDb(opr.retryInfo?.nextRetry) ?? - AbsoluteTime.now(), - ), - givesLifeness: !userNeedToCompleteKYC, - withdrawalGroupId: wsr.withdrawalGroupId, - lastError: opr.lastError, - retryInfo: opr.retryInfo, - }); - }); -} - -export async function iterRecordsForDeposit( - tx: GetReadOnlyAccess<{ - depositGroups: typeof WalletStoresV1.depositGroups; - }>, - filter: TransactionRecordFilter, - f: (r: DepositGroupRecord) => Promise<void>, -): Promise<void> { - let dgs: DepositGroupRecord[]; - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - ); - dgs = await tx.depositGroups.indexes.byStatus.getAll(keyRange); - } else { - dgs = await tx.depositGroups.indexes.byStatus.getAll(); - } - - for (const dg of dgs) { - await f(dg); - } -} - -async function gatherDepositPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - depositGroups: typeof WalletStoresV1.depositGroups; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await iterRecordsForDeposit(tx, { onlyState: "nonfinal" }, async (dg) => { - let deposited = true; - for (const d of dg.statusPerCoin) { - if (d === DepositElementStatus.DepositPending) { - deposited = false; - } - } - /** - * kyc pending operation don't give lifeness - * since the user need to complete kyc procedure - */ - const userNeedToCompleteKYC = dg.kycInfo !== undefined; - const opId = TaskIdentifiers.forDeposit(dg); - const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? - AbsoluteTime.now(); - resp.pendingOperations.push({ - type: PendingTaskType.Deposit, - ...getPendingCommon(ws, opId, timestampDue), - // Fully deposited operations don't give lifeness, - // because there is no reason to wait on the - // deposit tracking status. - givesLifeness: !deposited && !userNeedToCompleteKYC, - depositGroupId: dg.depositGroupId, - lastError: retryRecord?.lastError, - retryInfo: retryRecord?.retryInfo, - }); - }); -} - -export async function iterRecordsForReward( - tx: GetReadOnlyAccess<{ - rewards: typeof WalletStoresV1.rewards; - }>, - filter: TransactionRecordFilter, - f: (r: RewardRecord) => Promise<void>, -): Promise<void> { - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - ); - await tx.rewards.indexes.byStatus.iter(keyRange).forEachAsync(f); - } else { - await tx.rewards.indexes.byStatus.iter().forEachAsync(f); - } -} - -async function gatherRewardPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - rewards: typeof WalletStoresV1.rewards; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await iterRecordsForReward(tx, { onlyState: "nonfinal" }, async (tip) => { - const opId = TaskIdentifiers.forTipPickup(tip); - const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? - AbsoluteTime.now(); - - /** - * kyc pending operation don't give lifeness - * since the user need to complete kyc procedure - */ - // const userNeedToCompleteKYC = tip. - - if (tip.acceptedTimestamp) { - resp.pendingOperations.push({ - type: PendingTaskType.RewardPickup, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: true, - timestampDue, - merchantBaseUrl: tip.merchantBaseUrl, - tipId: tip.walletRewardId, - merchantTipId: tip.merchantRewardId, - }); - } - }); -} - -export async function iterRecordsForRefund( - tx: GetReadOnlyAccess<{ - refundGroups: typeof WalletStoresV1.refundGroups; - }>, - filter: TransactionRecordFilter, - f: (r: RefundGroupRecord) => Promise<void>, -): Promise<void> { - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - ); - await tx.refundGroups.indexes.byStatus.iter(keyRange).forEachAsync(f); - } else { - await tx.refundGroups.iter().forEachAsync(f); - } -} - -export async function iterRecordsForPurchase( - tx: GetReadOnlyAccess<{ - purchases: typeof WalletStoresV1.purchases; - }>, - filter: TransactionRecordFilter, - f: (r: PurchaseRecord) => Promise<void>, -): Promise<void> { - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - ); - await tx.purchases.indexes.byStatus.iter(keyRange).forEachAsync(f); - } else { - await tx.purchases.indexes.byStatus.iter().forEachAsync(f); - } -} - -async function gatherPurchasePending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - purchases: typeof WalletStoresV1.purchases; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await iterRecordsForPurchase(tx, { onlyState: "nonfinal" }, async (pr) => { - switch (pr.purchaseStatus) { - // These states are nonfinal but don't need any processing - case PurchaseStatus.DialogProposed: - case PurchaseStatus.DialogShared: - return; - } - const opId = TaskIdentifiers.forPay(pr); - const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? - AbsoluteTime.now(); - resp.pendingOperations.push({ - type: PendingTaskType.Purchase, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: true, - statusStr: PurchaseStatus[pr.purchaseStatus], - proposalId: pr.proposalId, - retryInfo: retryRecord?.retryInfo, - lastError: retryRecord?.lastError, - }); - }); -} - -async function gatherRecoupPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - recoupGroups: typeof WalletStoresV1.recoupGroups; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - // FIXME: Have a status field! - await tx.recoupGroups.iter().forEachAsync(async (rg) => { - if (rg.timestampFinished) { - return; - } - const opId = TaskIdentifiers.forRecoup(rg); - const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? - AbsoluteTime.now(); - resp.pendingOperations.push({ - type: PendingTaskType.Recoup, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: true, - recoupGroupId: rg.recoupGroupId, - retryInfo: retryRecord?.retryInfo, - lastError: retryRecord?.lastError, - }); - }); -} - -async function gatherBackupPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - backupProviders: typeof WalletStoresV1.backupProviders; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await tx.backupProviders.iter().forEachAsync(async (bp) => { - const opId = TaskIdentifiers.forBackup(bp); - const retryRecord = await tx.operationRetries.get(opId); - if (bp.state.tag === BackupProviderStateTag.Ready) { - const timestampDue = timestampAbsoluteFromDb( - bp.state.nextBackupTimestamp, - ); - resp.pendingOperations.push({ - type: PendingTaskType.Backup, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: false, - backupProviderBaseUrl: bp.baseUrl, - lastError: undefined, - }); - } else if (bp.state.tag === BackupProviderStateTag.Retrying) { - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo?.nextRetry) ?? - AbsoluteTime.now(); - resp.pendingOperations.push({ - type: PendingTaskType.Backup, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: false, - backupProviderBaseUrl: bp.baseUrl, - retryInfo: retryRecord?.retryInfo, - lastError: retryRecord?.lastError, - }); - } - }); -} - -export async function iterRecordsForPeerPullInitiation( - tx: GetReadOnlyAccess<{ - peerPullCredit: typeof WalletStoresV1.peerPullCredit; - }>, - filter: TransactionRecordFilter, - f: (r: PeerPullCreditRecord) => Promise<void>, -): Promise<void> { - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - ); - await tx.peerPullCredit.indexes.byStatus.iter(keyRange).forEachAsync(f); - } else { - await tx.peerPullCredit.indexes.byStatus.iter().forEachAsync(f); - } -} - -async function gatherPeerPullInitiationPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - peerPullCredit: typeof WalletStoresV1.peerPullCredit; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await iterRecordsForPeerPullInitiation( - tx, - { onlyState: "nonfinal" }, - async (pi) => { - const opId = TaskIdentifiers.forPeerPullPaymentInitiation(pi); - const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? - AbsoluteTime.now(); - - /** - * kyc pending operation don't give lifeness - * since the user need to complete kyc procedure - */ - const userNeedToCompleteKYC = pi.kycUrl !== undefined; - - resp.pendingOperations.push({ - type: PendingTaskType.PeerPullCredit, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: !userNeedToCompleteKYC, - retryInfo: retryRecord?.retryInfo, - pursePub: pi.pursePub, - internalOperationStatus: `0x${pi.status.toString(16)}`, - }); - }, - ); -} - -export async function iterRecordsForPeerPullDebit( - tx: GetReadOnlyAccess<{ - peerPullDebit: typeof WalletStoresV1.peerPullDebit; - }>, - filter: TransactionRecordFilter, - f: (r: PeerPullPaymentIncomingRecord) => Promise<void>, -): Promise<void> { - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - ); - await tx.peerPullDebit.indexes.byStatus.iter(keyRange).forEachAsync(f); - } else { - await tx.peerPullDebit.indexes.byStatus.iter().forEachAsync(f); - } -} - -async function gatherPeerPullDebitPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - peerPullDebit: typeof WalletStoresV1.peerPullDebit; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await iterRecordsForPeerPullDebit( - tx, - { onlyState: "nonfinal" }, - async (pi) => { - const opId = TaskIdentifiers.forPeerPullPaymentDebit(pi); - const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? - AbsoluteTime.now(); - switch (pi.status) { - case PeerPullDebitRecordStatus.DialogProposed: - return; - } - resp.pendingOperations.push({ - type: PendingTaskType.PeerPullDebit, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: true, - retryInfo: retryRecord?.retryInfo, - peerPullDebitId: pi.peerPullDebitId, - internalOperationStatus: `0x${pi.status.toString(16)}`, - }); - }, - ); -} - -export async function iterRecordsForPeerPushInitiation( - tx: GetReadOnlyAccess<{ - peerPushDebit: typeof WalletStoresV1.peerPushDebit; - }>, - filter: TransactionRecordFilter, - f: (r: PeerPushDebitRecord) => Promise<void>, -): Promise<void> { - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - ); - await tx.peerPushDebit.indexes.byStatus.iter(keyRange).forEachAsync(f); - } else { - await tx.peerPushDebit.indexes.byStatus.iter().forEachAsync(f); - } -} - -async function gatherPeerPushInitiationPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - peerPushDebit: typeof WalletStoresV1.peerPushDebit; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await iterRecordsForPeerPushInitiation( - tx, - { onlyState: "nonfinal" }, - async (pi) => { - const opId = TaskIdentifiers.forPeerPushPaymentInitiation(pi); - const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? - AbsoluteTime.now(); - resp.pendingOperations.push({ - type: PendingTaskType.PeerPushDebit, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: true, - retryInfo: retryRecord?.retryInfo, - pursePub: pi.pursePub, - }); - }, - ); -} - -export async function iterRecordsForPeerPushCredit( - tx: GetReadOnlyAccess<{ - peerPushCredit: typeof WalletStoresV1.peerPushCredit; - }>, - filter: TransactionRecordFilter, - f: (r: PeerPushPaymentIncomingRecord) => Promise<void>, -): Promise<void> { - if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, - ); - await tx.peerPushCredit.indexes.byStatus.iter(keyRange).forEachAsync(f); - } else { - await tx.peerPushCredit.indexes.byStatus.iter().forEachAsync(f); - } -} - -async function gatherPeerPushCreditPending( - ws: InternalWalletState, - tx: GetReadOnlyAccess<{ - peerPushCredit: typeof WalletStoresV1.peerPushCredit; - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - now: AbsoluteTime, - resp: PendingOperationsResponse, -): Promise<void> { - await iterRecordsForPeerPushCredit( - tx, - { onlyState: "nonfinal" }, - async (pi) => { - const opId = TaskIdentifiers.forPeerPushCredit(pi); - const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = - timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? - AbsoluteTime.now(); - - /** - * kyc pending operation don't give lifeness - * since the user need to complete kyc procedure - */ - const userNeedToCompleteKYC = pi.kycUrl !== undefined; - - switch (pi.status) { - // Status is nonfinal but no processing needs to be done - case PeerPushCreditStatus.DialogProposed: - return; - default: - resp.pendingOperations.push({ - type: PendingTaskType.PeerPushCredit, - ...getPendingCommon(ws, opId, timestampDue), - givesLifeness: !userNeedToCompleteKYC, - retryInfo: retryRecord?.retryInfo, - peerPushCreditId: pi.peerPushCreditId, - }); - } - }, - ); -} - -const taskPrio: { [X in PendingTaskType]: number } = { - [PendingTaskType.Deposit]: 2, - [PendingTaskType.ExchangeUpdate]: 1, - [PendingTaskType.PeerPullCredit]: 2, - [PendingTaskType.PeerPullDebit]: 2, - [PendingTaskType.PeerPushCredit]: 2, - [PendingTaskType.Purchase]: 2, - [PendingTaskType.Recoup]: 3, - [PendingTaskType.RewardPickup]: 2, - [PendingTaskType.Refresh]: 3, - [PendingTaskType.Withdraw]: 3, - [PendingTaskType.ExchangeCheckRefresh]: 3, - [PendingTaskType.PeerPushDebit]: 2, - [PendingTaskType.Backup]: 4, -}; - -export async function getPendingOperations( - ws: InternalWalletState, -): Promise<PendingOperationsResponse> { - const now = AbsoluteTime.now(); - const resp = await ws.db - .mktx((x) => [ - x.backupProviders, - x.exchanges, - x.exchangeDetails, - x.refreshGroups, - x.coins, - x.withdrawalGroups, - x.rewards, - x.purchases, - x.planchets, - x.depositGroups, - x.recoupGroups, - x.operationRetries, - x.peerPullCredit, - x.peerPushDebit, - x.peerPullDebit, - x.peerPushCredit, - ]) - .runReadWrite(async (tx) => { - const resp: PendingOperationsResponse = { - pendingOperations: [], - }; - await gatherExchangePending(ws, tx, now, resp); - await gatherRefreshPending(ws, tx, now, resp); - await gatherWithdrawalPending(ws, tx, now, resp); - await gatherDepositPending(ws, tx, now, resp); - await gatherRewardPending(ws, tx, now, resp); - await gatherPurchasePending(ws, tx, now, resp); - await gatherRecoupPending(ws, tx, now, resp); - await gatherBackupPending(ws, tx, now, resp); - await gatherPeerPushInitiationPending(ws, tx, now, resp); - await gatherPeerPullInitiationPending(ws, tx, now, resp); - await gatherPeerPullDebitPending(ws, tx, now, resp); - await gatherPeerPushCreditPending(ws, tx, now, resp); - return resp; - }); - - resp.pendingOperations.sort((a, b) => { - let prioA = taskPrio[a.type]; - let prioB = taskPrio[b.type]; - return Math.sign(prioA - prioB); - }); - - return resp; -} |