From 74b9ee559fc57f48a591140eb342cc8e2bbd3dd3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 28 Feb 2024 22:41:41 -0300 Subject: get activity --- packages/taler-util/src/wallet-types.ts | 38 +++++++++-- .../taler-wallet-core/src/observable-wrappers.ts | 4 ++ packages/taler-wallet-core/src/shepherd.ts | 7 +- packages/taler-wallet-core/src/wallet-api-types.ts | 11 ++++ packages/taler-wallet-core/src/wallet.ts | 41 ++++++++++-- .../src/components/WalletActivity.tsx | 74 ++++++++++++++++++++++ .../src/cta/Payment/test.ts | 10 ++- .../src/hooks/useSettings.ts | 1 + .../taler-wallet-webextension/src/platform/api.ts | 2 + .../taler-wallet-webextension/src/test-utils.ts | 14 ++-- .../src/wallet/Application.tsx | 2 + .../src/wallet/DeveloperPage.tsx | 44 +++---------- .../src/wallet/Settings.tsx | 4 ++ packages/taler-wallet-webextension/src/wxApi.ts | 6 +- 14 files changed, 201 insertions(+), 57 deletions(-) create mode 100644 packages/taler-wallet-webextension/src/components/WalletActivity.tsx (limited to 'packages') diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index c336752a4..19bebfb19 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -580,11 +580,11 @@ export interface CoinDumpJson { withdrawal_reserve_pub: string | undefined; coin_status: CoinStatus; spend_allocation: - | { - id: string; - amount: AmountString; - } - | undefined; + | { + id: string; + amount: AmountString; + } + | undefined; /** * Information about the age restriction */ @@ -2518,6 +2518,34 @@ export const codecForWithdrawFakebankRequest = .property("exchange", codecForString()) .build("WithdrawFakebankRequest"); +export interface ActiveTask { + id: string; + transaction: TransactionIdStr | undefined; + firstTry: AbsoluteTime | undefined; + nextTry: AbsoluteTime | undefined; + counter: number | undefined; + lastError: TalerErrorDetail | undefined; +} + +export interface GetActiveTasks { + tasks: ActiveTask[]; +} + +export const codecForActiveTask = (): Codec => + buildCodecForObject() + .property("id", codecForString()) + .property("transaction", codecOptional(codecForTransactionIdStr())) + .property("counter", codecForNumber()) + .property("firstTry", (codecForAbsoluteTime)) + .property("nextTry", (codecForAbsoluteTime)) + .property("lastError", codecForTalerErrorDetail()) + .build("ActiveTask") + +export const codecForGetActiveTasks = (): Codec => + buildCodecForObject() + .property("tasks", codecForList(codecForActiveTask())) + .build("GetActiveTasks") + export interface ImportDbRequest { dump: any; } diff --git a/packages/taler-wallet-core/src/observable-wrappers.ts b/packages/taler-wallet-core/src/observable-wrappers.ts index f2c76ae5b..5653f185e 100644 --- a/packages/taler-wallet-core/src/observable-wrappers.ts +++ b/packages/taler-wallet-core/src/observable-wrappers.ts @@ -67,6 +67,10 @@ export class ObservableTaskScheduler implements TaskScheduler { } } + getActiveTasks(): TaskIdStr[] { + return this.impl.getActiveTasks(); + } + ensureRunning(): void { return this.impl.ensureRunning(); } diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index 8fdf2b66b..6f2b2fa44 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -148,6 +148,7 @@ export interface TaskScheduler { stopShepherdTask(taskId: TaskIdStr): void; resetTaskRetries(taskId: TaskIdStr): Promise; reload(): void; + getActiveTasks(): TaskIdStr[]; } export class TaskSchedulerImpl implements TaskScheduler { @@ -171,6 +172,10 @@ export class TaskSchedulerImpl implements TaskScheduler { } } + getActiveTasks(): TaskIdStr[] { + return [...this.sheps.keys()] + } + ensureRunning(): void { if (this.isRunning) { return; @@ -932,7 +937,7 @@ export function listTaskForTransactionId(transactionId: string): TaskIdStr[] { * Convert the task ID for a task that processes a transaction int * the ID for the transaction. */ -function convertTaskToTransactionId( +export function convertTaskToTransactionId( taskId: string, ): TransactionIdStr | undefined { const parsedTaskId = parseTaskIdentifier(taskId); diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 5e2d83a33..e5ff6cc26 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -60,6 +60,7 @@ import { FailTransactionRequest, ForceRefreshRequest, ForgetKnownBankAccountsRequest, + GetActiveTasks, GetAmountRequest, GetBalanceDetailRequest, GetContractTermsDetailsRequest, @@ -186,6 +187,7 @@ export enum WalletApiOperation { GetUserAttentionUnreadCount = "getUserAttentionUnreadCount", MarkAttentionRequestAsRead = "markAttentionRequestAsRead", GetPendingOperations = "getPendingOperations", + GetActiveTasks = "getActiveTasks", SetExchangeTosAccepted = "setExchangeTosAccepted", SetExchangeTosForgotten = "SetExchangeTosForgotten", StartRefundQueryForUri = "startRefundQueryForUri", @@ -1098,6 +1100,14 @@ export type GetPendingTasksOp = { response: any; }; + +export type GetActiveTasksOp = { + op: WalletApiOperation.GetActiveTasks; + request: EmptyObject; + response: GetActiveTasks; +}; + + /** * Dump all coins of the wallet in a simple JSON format. */ @@ -1199,6 +1209,7 @@ export type WalletOperations = { [WalletApiOperation.GetWithdrawalTransactionByUri]: GetWithdrawalTransactionByUriOp; [WalletApiOperation.RetryPendingNow]: RetryPendingNowOp; [WalletApiOperation.GetPendingOperations]: GetPendingTasksOp; + [WalletApiOperation.GetActiveTasks]: GetActiveTasksOp; [WalletApiOperation.GetUserAttentionRequests]: GetUserAttentionRequests; [WalletApiOperation.GetUserAttentionUnreadCount]: GetUserAttentionsUnreadCount; [WalletApiOperation.MarkAttentionRequestAsRead]: MarkAttentionRequestAsRead; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 14f4c85c3..2757f7fec 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -24,6 +24,7 @@ */ import { IDBFactory } from "@gnu-taler/idb-bridge"; import { + ActiveTask, AmountString, Amounts, AsyncCondition, @@ -168,6 +169,7 @@ import { CoinSourceType, ConfigRecordKey, DenominationRecord, + OperationRetryRecord, WalletDbReadOnlyTransaction, WalletStoresV1, clearDatabase, @@ -175,6 +177,8 @@ import { importDb, openStoredBackupsDatabase, openTalerDatabase, + timestampAbsoluteFromDb, + timestampPreciseFromDb, timestampProtocolToDb, } from "./db.js"; import { @@ -238,6 +242,7 @@ import { forceRefresh } from "./refresh.js"; import { TaskScheduler, TaskSchedulerImpl, + convertTaskToTransactionId, listTaskForTransactionId, } from "./shepherd.js"; import { @@ -513,9 +518,9 @@ async function dumpCoins(wex: WalletExecutionContext): Promise { ageCommitmentProof: c.ageCommitmentProof, spend_allocation: c.spendAllocation ? { - amount: c.spendAllocation.amount, - id: c.spendAllocation.id, - } + amount: c.spendAllocation.amount, + id: c.spendAllocation.id, + } : undefined, }); } @@ -968,6 +973,34 @@ async function dispatchRequestInternal( await suspendTransaction(wex, req.transactionId); return {}; } + case WalletApiOperation.GetActiveTasks: { + const allTasksId = wex.taskScheduler.getActiveTasks() + + const tasksInfo = await Promise.all(allTasksId.map(async (id) => { + return await wex.ws.db.runReadOnlyTx( + ["operationRetries"], + async (tx) => { + return tx.operationRetries.get(id); + }, + ) + })) + + const tasks = allTasksId + .map((taskId, i): ActiveTask => { + const transaction = convertTaskToTransactionId(taskId); + const d = tasksInfo[i] + + const firstTry = !d?undefined:timestampAbsoluteFromDb(d.retryInfo.firstTry) + const nextTry = !d?undefined:timestampAbsoluteFromDb(d.retryInfo.nextRetry) + const counter = d?.retryInfo.retryCounter + const lastError = d?.lastError + + return { + id: taskId, counter, firstTry, nextTry, lastError, transaction + } + }) + return { tasks }; + } case WalletApiOperation.FailTransaction: { const req = codecForFailTransactionRequest().decode(payload); await failTransaction(wex, req.transactionId); @@ -1438,7 +1471,7 @@ async function handleCoreApiRequest( wex = getObservedWalletExecutionContext(ws, CancellationToken.CONTINUE, oc); } else { oc = { - observe(evt) {}, + observe(evt) { }, }; wex = getNormalWalletExecutionContext(ws, CancellationToken.CONTINUE, oc); } diff --git a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx new file mode 100644 index 000000000..a63ee97cb --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx @@ -0,0 +1,74 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + 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 + */ +import { + AbsoluteTime, + Amounts, + NotificationType, + Transaction, + TransactionMajorState, +} from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { Fragment, h, JSX, VNode } from "preact"; +import { useEffect } from "preact/hooks"; +import { useBackendContext } from "../context/backend.js"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; +import { Avatar } from "../mui/Avatar.js"; +import { Grid } from "../mui/Grid.js"; +import { Typography } from "../mui/Typography.js"; +import Banner from "./Banner.js"; +import { Time } from "./Time.js"; + +interface Props extends JSX.HTMLAttributes { +} + +/** + * this cache will save the tx from the previous render + */ +const cache = { tx: [] as Transaction[] }; + +export function WalletActivity({ }: Props): VNode { + const api = useBackendContext(); + const state = useAsyncAsHook(() => + api.wallet.call(WalletApiOperation.GetTransactions, {}), + ); + const listenAllEvents = Array.from({ length: 1 }); + + useEffect(() => { + return api.listener.onUpdateNotification( listenAllEvents, (notif) => { + console.log(notif) + }); + }); + + const transactions = + !state || state.hasError + ? cache.tx + : state.response.transactions.filter( + (t) => t.txState.major === TransactionMajorState.Pending, + ); + + if (state && !state.hasError) { + cache.tx = transactions; + } + if (!transactions.length) { + return ; + } + return ( +
+ this is shown below +
+ ); +} diff --git a/packages/taler-wallet-webextension/src/cta/Payment/test.ts b/packages/taler-wallet-webextension/src/cta/Payment/test.ts index 5e009b3de..5847cc833 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/test.ts @@ -29,6 +29,7 @@ import { PreparePayResultPaymentPossible, PreparePayResultType, ScopeType, + TransactionMajorState, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; @@ -549,8 +550,13 @@ describe("Payment CTA states", () => { // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); expect(state.payHandler.onClick).not.undefined; - handler.notifyEventFromWallet( - NotificationType.TransactionStateTransition, + handler.notifyEventFromWallet({ + type: NotificationType.TransactionStateTransition, + newTxState: {} as any, + oldTxState: {} as any, + transactionId: "123", + } + ); }, (state) => { diff --git a/packages/taler-wallet-webextension/src/hooks/useSettings.ts b/packages/taler-wallet-webextension/src/hooks/useSettings.ts index c6b8c6b0e..8c9d09caf 100644 --- a/packages/taler-wallet-webextension/src/hooks/useSettings.ts +++ b/packages/taler-wallet-webextension/src/hooks/useSettings.ts @@ -45,6 +45,7 @@ export const codecForSettings = (): Codec => .property("showRefeshTransactions", codecForBoolean()) .property("showExchangeManagement", codecForBoolean()) .property("selectTosFormat", codecForBoolean()) + .property("showWalletActivity", codecForBoolean()) .build("Settings"); const SETTINGS_KEY = buildStorageKey("wallet-settings", codecForSettings()); diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts index 059b234cc..e3afe35bd 100644 --- a/packages/taler-wallet-webextension/src/platform/api.ts +++ b/packages/taler-wallet-webextension/src/platform/api.ts @@ -122,6 +122,7 @@ export interface Settings extends WebexWalletConfig { suspendIndividualTransaction: boolean; showExchangeManagement: boolean; selectTosFormat: boolean; + showWalletActivity: boolean; } export const defaultSettings: Settings = { @@ -137,6 +138,7 @@ export const defaultSettings: Settings = { showExchangeManagement: false, walletAllowHttp: false, selectTosFormat: false, + showWalletActivity: false, }; /** diff --git a/packages/taler-wallet-webextension/src/test-utils.ts b/packages/taler-wallet-webextension/src/test-utils.ts index e66693f53..d25326942 100644 --- a/packages/taler-wallet-webextension/src/test-utils.ts +++ b/packages/taler-wallet-webextension/src/test-utils.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ -import { NotificationType, TalerBankIntegrationHttpClient, TalerCoreBankHttpClient, TalerRevenueHttpClient, TalerWireGatewayHttpClient } from "@gnu-taler/taler-util"; +import { NotificationType, TalerBankIntegrationHttpClient, TalerCoreBankHttpClient, TalerRevenueHttpClient, TalerWireGatewayHttpClient, WalletNotification } from "@gnu-taler/taler-util"; import { WalletCoreApiClient, WalletCoreOpKeys, @@ -46,7 +46,7 @@ interface MockHandler { getCallingQueueState(): "empty" | string; - notifyEventFromWallet(event: NotificationType): void; + notifyEventFromWallet(notif: WalletNotification): void; } type CallRecord = WalletCallRecord | BackgroundCallRecord; @@ -65,7 +65,7 @@ interface BackgroundCallRecord { } type Subscriptions = { - [key in NotificationType]?: VoidFunction; + [key in NotificationType]?: (d: WalletNotification) => void; }; export function createWalletApiMock(): { @@ -117,7 +117,7 @@ export function createWalletApiMock(): { listener: { onUpdateNotification( mTypes: NotificationType[], - callback: (() => void) | undefined, + callback: ((d: WalletNotification) => void) | undefined, ): () => void { mTypes.forEach((m) => { subscriptions[m] = callback; @@ -164,11 +164,11 @@ export function createWalletApiMock(): { }); return handler; }, - notifyEventFromWallet(event: NotificationType): void { - const callback = subscriptions[event]; + notifyEventFromWallet(event: WalletNotification): void { + const callback = subscriptions[event.type]; if (!callback) throw Error(`Expected to have a subscription for ${event}`); - return callback(); + return callback(event); }, getCallingQueueState() { return calls.length === 0 ? "empty" : `${calls.length} left`; diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index 140bb5683..4fafc73ad 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -81,6 +81,7 @@ import { QrReaderPage } from "./QrReader.js"; import { SettingsPage } from "./Settings.js"; import { TransactionPage } from "./Transaction.js"; import { WelcomePage } from "./Welcome.js"; +import { WalletActivity } from "../components/WalletActivity.js"; export function Application(): VNode { const { i18n } = useTranslationContext(); @@ -609,6 +610,7 @@ function WalletTemplate({ {children} +
); } diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx index d19fef155..adb114862 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -103,16 +103,16 @@ export function DeveloperPage({ }: Props): VNode { const hook = useAsyncAsHook(async () => { const list = await api.wallet.call(WalletApiOperation.ListExchanges, {}); const version = await api.wallet.call(WalletApiOperation.GetVersion, {}); - const operations: any[] = await api.wallet.call( - WalletApiOperation.GetPendingOperations, + const tasks = await api.wallet.call( + WalletApiOperation.GetActiveTasks, {}, ); const coins = await api.wallet.call(WalletApiOperation.DumpCoins, {}); - return { exchanges: list.exchanges, version, coins, operations }; + return { exchanges: list.exchanges, version, coins, tasks:tasks.tasks }; }); const exchangeList = hook && !hook.hasError ? hook.response.exchanges : []; const coins = hook && !hook.hasError ? hook.response.coins.coins : []; - const operations = hook && !hook.hasError ? hook.response.operations : []; + const tasks = hook && !hook.hasError ? hook.response.tasks : []; useEffect(() => { return api.listener.onUpdateNotification(listenAllEvents, hook?.retry); @@ -485,34 +485,6 @@ export function DeveloperPage({ }: Props): VNode { Set log level - -

Exchange

-
- -
- -
{downloadedDatabase && (
@@ -573,19 +545,19 @@ export function DeveloperPage({ }: Props): VNode { ); })}
- {operations && operations.length > 0 && ( + {tasks && tasks.length > 0 && (

Pending operations

- {operations.reverse().map((o) => { + {tasks.map((o) => { return (
- {o.type}{" "} + {o.id}{" "}
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index fd2cf0b26..64a96d643 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -269,6 +269,10 @@ function AdvanceSettings(): VNode { label: i18n.str`Select terms of service format`, description: i18n.str`Allows to render the terms of service on different format selected by the user.`, }, + showWalletActivity: { + label: i18n.str`Show wallet activity`, + description: i18n.str`Show the wallet notification and observavility event in the UI.`, + }, }; return ( diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index b73b12263..df99d3f17 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -32,6 +32,7 @@ import { TalerErrorCode, TalerErrorDetail, WalletDiagnostics, + WalletNotification, } from "@gnu-taler/taler-util"; import { WalletCoreApiClient, @@ -170,7 +171,7 @@ class WalletApiClientImpl implements WalletCoreApiClient { function onUpdateNotification( messageTypes: Array, - doCallback: undefined | (() => void), + doCallback: undefined | ((n:WalletNotification) => void), ): () => void { //if no callback, then ignore if (!doCallback) @@ -180,7 +181,8 @@ function onUpdateNotification( const onNewMessage = (message: MessageFromBackend): void => { const shouldNotify = message.type === "wallet" && messageTypes.includes(message.notification.type); if (shouldNotify) { - doCallback(); + message.notification + doCallback(message.notification); } }; return platform.listenToWalletBackground(onNewMessage); -- cgit v1.2.3