diff options
author | Sebastian <sebasjm@gmail.com> | 2022-10-25 12:23:08 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-10-25 12:23:52 -0300 |
commit | 3f2db7707fdf4eb4c1dfdb527d5726dd1694e126 (patch) | |
tree | 4366d443db56eb200ba29760bf1f4a0b9def6c97 /packages/taler-wallet-webextension/src | |
parent | 587674dd10bd714b68ff5a6e836eb21113c0337a (diff) | |
download | wallet-core-3f2db7707fdf4eb4c1dfdb527d5726dd1694e126.tar.xz |
using new wallet api (typed interface)
Diffstat (limited to 'packages/taler-wallet-webextension/src')
60 files changed, 1514 insertions, 1884 deletions
diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx index e9688da21..5d5dae092 100644 --- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx +++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx @@ -19,13 +19,14 @@ import { NotificationType, Transaction, } 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 { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Avatar } from "../mui/Avatar.js"; import { Typography } from "../mui/Typography.js"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; import Banner from "./Banner.js"; import { Time } from "./Time.js"; @@ -34,14 +35,14 @@ interface Props extends JSX.HTMLAttributes { } export function PendingTransactions({ goToTransaction }: Props): VNode { - const state = useAsyncAsHook(wxApi.getTransactions); + const state = useAsyncAsHook(() => + wxApi.wallet.call(WalletApiOperation.GetTransactions, {}), + ); useEffect(() => { - return wxApi.onUpdateNotification( + return wxApi.listener.onUpdateNotification( [NotificationType.WithdrawGroupFinished], - () => { - state?.retry(); - }, + state?.retry, ); }); diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx index 7c1d2c6fc..6461f76e3 100644 --- a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx +++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx @@ -14,7 +14,10 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ import { AbsoluteTime, Duration, Location } from "@gnu-taler/taler-util"; -import { WalletContractData } from "@gnu-taler/taler-wallet-core"; +import { + WalletApiOperation, + WalletContractData, +} from "@gnu-taler/taler-wallet-core"; import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -26,9 +29,9 @@ import { useTranslationContext } from "../context/translation.js"; import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../mui/handlers.js"; import { compose, StateViewMap } from "../utils/index.js"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; import { Amount } from "./Amount.js"; -import { Link, LinkPrimary } from "./styled/index.js"; +import { Link } from "./styled/index.js"; const ContractTermsTable = styled.table` width: 100%; @@ -99,7 +102,9 @@ function useComponentState({ proposalId }: Props, api: typeof wxApi): State { const [show, setShow] = useState(false); const hook = useAsyncAsHook(async () => { if (!show) return undefined; - return await api.getContractTermsDetails(proposalId); + return await api.wallet.call(WalletApiOperation.GetContractTermsDetails, { + proposalId, + }); }, [show]); const hideHandler = { diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts index 2c77e5d3c..79778a595 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts @@ -18,7 +18,7 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ToggleHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { TermsState } from "./utils.js"; import { @@ -26,7 +26,7 @@ import { LoadingUriView, ShowButtonsAcceptedTosView, ShowButtonsNonAcceptedTosView, - ShowTosContentView, + ShowTosContentView } from "./views.js"; export interface Props { diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts index 30322e139..3cad967ae 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts @@ -14,9 +14,10 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; import { buildTermsOfServiceState } from "./utils.js"; @@ -34,7 +35,10 @@ export function useComponentState( * For the exchange selected, bring the status of the terms of service */ const terms = useAsyncAsHook(async () => { - const exchangeTos = await api.getExchangeTos(exchangeUrl, ["text/xml"]); + const exchangeTos = await api.wallet.call(WalletApiOperation.GetExchangeTos, { + exchangeBaseUrl: exchangeUrl, + acceptedFormat: ["text/xml"] + }) const state = buildTermsOfServiceState(exchangeTos); @@ -72,10 +76,16 @@ export function useComponentState( try { if (accepted) { - await api.setExchangeTosAccepted(exchangeUrl, state.version); + api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { + exchangeBaseUrl: exchangeUrl, + etag: state.version + }) } else { // mark as not accepted - await api.setExchangeTosAccepted(exchangeUrl, undefined); + api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { + exchangeBaseUrl: exchangeUrl, + etag: undefined + }) } // setAccepted(accepted); if (!readOnly) onChange(accepted); //external update diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts index 0246b6f7c..539709821 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts @@ -19,7 +19,7 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts index 5662a24c8..ba7bd147b 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts @@ -14,10 +14,10 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Amounts, CreateDepositGroupResponse } from "@gnu-taler/taler-util"; -import { useState } from "preact/hooks"; +import { Amounts } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -29,10 +29,10 @@ export function useComponentState( if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT"); const amount = Amounts.parse(amountStr); if (!amount) throw Error("ERROR_INVALID-AMOUNT-FOR-DEPOSIT"); - const deposit = await api.prepareDeposit( - talerDepositUri, - Amounts.stringify(amount), - ); + const deposit = await api.wallet.call(WalletApiOperation.PrepareDeposit, { + amount: Amounts.stringify(amount), + depositPaytoUri: talerDepositUri, + }); return { deposit, uri: talerDepositUri, amount }; }); @@ -46,7 +46,10 @@ export function useComponentState( const { deposit, uri, amount } = info.response; async function doDeposit(): Promise<void> { - const resp = await api.createDepositGroup(uri, Amounts.stringify(amount)); + const resp = await api.wallet.call(WalletApiOperation.CreateDepositGroup, { + amount: Amounts.stringify(amount), + depositPaytoUri: uri, + }); onSuccess(resp.transactionId); } diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts index b6e63a796..f628b3287 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts @@ -19,43 +19,40 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { Amounts, PrepareDepositResponse } from "@gnu-taler/taler-util"; +import { Amounts } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../../test-utils.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; describe("Deposit CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerDepositUri: undefined, + amountStr: undefined, + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerDepositUri: undefined, - amountStr: undefined, - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareRefund: async () => ({}), - applyRefund: async () => ({}), - onUpdateNotification: async () => ({}), - } as any, - ), + useComponentState(props, mock), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equals("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading-uri"); @@ -64,44 +61,41 @@ describe("Deposit CTA states", () => { if (error.operational) expect.fail(); expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); } - await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ready after loading", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + handler.addWalletCallResponse(WalletApiOperation.PrepareDeposit, undefined, { + effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"), + totalDepositCost: Amounts.parseOrThrow("EUR:1.2"), + }); + const props = { + talerDepositUri: "payto://refund/asdasdas", + amountStr: "EUR:1", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerDepositUri: "payto://refund/asdasdas", - amountStr: "EUR:1", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareDeposit: async () => - ({ - effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"), - totalDepositCost: Amounts.parseOrThrow("EUR:1.2"), - } as PrepareDepositResponse as any), - createDepositGroup: async () => ({}), - } as any, - ), + useComponentState(props, mock), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equals("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); if (state.status !== "ready") expect.fail(); if (state.error) expect.fail(); @@ -112,5 +106,6 @@ describe("Deposit CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty"); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts index ff04a8247..0389a17fb 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts @@ -22,7 +22,7 @@ import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts index 205a664e0..d845e121a 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts @@ -16,11 +16,11 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; type RecursiveState<S extends object> = S | (() => RecursiveState<S>); @@ -31,7 +31,7 @@ export function useComponentState( ): RecursiveState<State> { const amount = Amounts.parseOrThrow(amountStr); - const hook = useAsyncAsHook(api.listExchanges); + const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {})); if (!hook) { return { @@ -69,7 +69,7 @@ export function useComponentState( async function accept(): Promise<void> { try { - const resp = await api.initiatePeerPullPayment({ + const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPullPayment, { amount: Amounts.stringify(amount), exchangeBaseUrl: exchange.exchangeBaseUrl, partialContractTerms: { diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts index 7c6f13286..693803587 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts @@ -18,13 +18,13 @@ import { AbsoluteTime, AmountJson, PreparePayResult, - TalerErrorDetail, + TalerErrorDetail } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts index c88e80602..457827127 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts @@ -21,12 +21,12 @@ import { PreparePayResult, PreparePayResultType, TalerErrorDetail, - TalerProtocolTimestamp, + TalerProtocolTimestamp } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -34,18 +34,17 @@ export function useComponentState( api: typeof wxApi, ): State { const hook = useAsyncAsHook(async () => { - const p2p = await api.checkPeerPullPayment({ + const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, { talerUri: talerPayPullUri, }); - const balance = await api.getBalance(); + const balance = await api.wallet.call(WalletApiOperation.GetBalances, {}); return { p2p, balance }; }); - useEffect(() => { - api.onUpdateNotification([NotificationType.CoinWithdrawn], () => { - hook?.retry(); - }); - }); + useEffect(() => api.listener.onUpdateNotification( + [NotificationType.CoinWithdrawn], + hook?.retry + )); const [operationError, setOperationError] = useState< TalerErrorDetail | undefined @@ -64,10 +63,7 @@ export function useComponentState( }; } - // const { payStatus } = hook.response.p2p; - const { - amount: purseAmount, contractTerms, peerPullPaymentIncomingId, } = hook.response.p2p; @@ -136,17 +132,9 @@ export function useComponentState( }; } - // if (payStatus.status === PreparePayResultType.AlreadyConfirmed) { - // return { - // status: "confirmed", - // balance: foundAmount, - // ...baseResult, - // }; - // } - async function accept(): Promise<void> { try { - const resp = await api.acceptPeerPullPayment({ + const resp = await api.wallet.call(WalletApiOperation.AcceptPeerPullPayment, { peerPullPaymentIncomingId, }); onSuccess(resp.transactionId); diff --git a/packages/taler-wallet-webextension/src/cta/Payment/index.ts b/packages/taler-wallet-webextension/src/cta/Payment/index.ts index 8e446722e..b4e59e666 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/index.ts @@ -15,21 +15,16 @@ */ import { - AmountJson, - ConfirmPayResult, - PreparePayResult, - PreparePayResultAlreadyConfirmed, - PreparePayResultInsufficientBalance, - PreparePayResultPaymentPossible, + AmountJson, PreparePayResult, + PreparePayResultAlreadyConfirmed, PreparePayResultPaymentPossible } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, BaseView } from "./views.js"; +import { BaseView, LoadingUriView } from "./views.js"; export interface Props { talerPayUri?: string; diff --git a/packages/taler-wallet-webextension/src/cta/Payment/state.ts b/packages/taler-wallet-webextension/src/cta/Payment/state.ts index 8d388aa60..414bc2000 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/state.ts @@ -15,19 +15,16 @@ */ import { - AmountJson, - Amounts, - ConfirmPayResult, - ConfirmPayResultType, + Amounts, ConfirmPayResultType, NotificationType, PreparePayResultType, - TalerErrorCode, + TalerErrorCode } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -38,16 +35,17 @@ export function useComponentState( const hook = useAsyncAsHook(async () => { if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT"); - const payStatus = await api.preparePay(talerPayUri); - const balance = await api.getBalance(); + const payStatus = await api.wallet.call(WalletApiOperation.PreparePayForUri, { + talerPayUri: talerPayUri + }); + const balance = await api.wallet.call(WalletApiOperation.GetBalances, {}); return { payStatus, balance, uri: talerPayUri }; - }); + }, []); - useEffect(() => { - api.onUpdateNotification([NotificationType.CoinWithdrawn], () => { - hook?.retry(); - }); - }); + useEffect(() => api.listener.onUpdateNotification( + [NotificationType.CoinWithdrawn], + hook?.retry + ), [hook]); const hookResponse = !hook || hook.hasError ? undefined : hook.response; @@ -127,7 +125,9 @@ export function useComponentState( hint: `payment is not possible: ${payStatus.status}`, }); } - const res = await api.confirmPay(payStatus.proposalId, undefined); + const res = await api.wallet.call(WalletApiOperation.ConfirmPay, { + proposalId: payStatus.proposalId, + }); // handle confirm pay if (res.type !== ConfirmPayResultType.Done) { throw TalerError.fromUncheckedDetail({ diff --git a/packages/taler-wallet-webextension/src/cta/Payment/test.ts b/packages/taler-wallet-webextension/src/cta/Payment/test.ts index f4ce5afb3..8aa099fdc 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/test.ts @@ -20,81 +20,44 @@ */ import { - AmountJson, - Amounts, - BalancesResponse, - ConfirmPayResult, + Amounts, ConfirmPayResult, ConfirmPayResultType, - NotificationType, - PreparePayResult, - PreparePayResultType, + NotificationType, PreparePayResultInsufficientBalance, + PreparePayResultPaymentPossible, + PreparePayResultType } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; -import { mountHook } from "../../test-utils.js"; +import { mountHook, nullFunction } from "../../test-utils.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; -import * as wxApi from "../../wxApi.js"; - -const nullFunction: any = () => null; -type VoidFunction = () => void; - -type Subs = { - [key in NotificationType]?: VoidFunction; -}; - -export class SubsHandler { - private subs: Subs = {}; - - constructor() { - this.saveSubscription = this.saveSubscription.bind(this); - } - - saveSubscription( - messageTypes: NotificationType[], - callback: VoidFunction, - ): VoidFunction { - messageTypes.forEach((m) => { - this.subs[m] = callback; - }); - return nullFunction; - } - - notifyEvent(event: NotificationType): void { - const cb = this.subs[event]; - if (cb === undefined) - expect.fail(`Expected to have a subscription for ${event}`); - cb(); - } -} describe("Payment CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: undefined, + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerPayUri: undefined, - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - } as Partial<typeof wxApi> as any, - ), + useComponentState(props, mock), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading-uri"); if (error === undefined) expect.fail(); @@ -103,324 +66,312 @@ describe("Payment CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should response with no balance", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.InsufficientBalance, + amountRaw: "USD:10", + } as PreparePayResultInsufficientBalance) + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { balances: [] }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:10", - status: PreparePayResultType.InsufficientBalance, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, - ), + useComponentState(props, mock), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "no-balance-for-currency") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "no-balance-for-currency") { + expect(r).eq({}) + return; + } expect(r.balance).undefined; expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should not be able to pay if there is no enough balance", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.InsufficientBalance, + amountRaw: "USD:10", + } as PreparePayResultInsufficientBalance) + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:5", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:10", - status: PreparePayResultType.InsufficientBalance, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:5", - }, - ], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, - ), + useComponentState(props, mock), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "no-enough-balance") expect.fail(); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:5")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be able to pay (without fee)", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:10", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:10", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:15", - }, - ], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, - ), + useComponentState(props, mock), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "ready") { + expect(r).eq({}) + return + } expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:0")); expect(r.payHandler.onClick).not.undefined; } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be able to pay (with fee)", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:9", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:15", - }, - ], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, + props, + mock + ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); expect(r.payHandler.onClick).not.undefined; } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should get confirmation done after pay successfully", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + handler.addWalletCallResponse(WalletApiOperation.ConfirmPay, undefined, { + type: ConfirmPayResultType.Done, + contractTerms: {}, + } as ConfirmPayResult) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:9", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:15", - }, - ], - } as Partial<BalancesResponse>), - confirmPay: async () => - ({ - type: ConfirmPayResultType.Done, - contractTerms: {}, - } as Partial<ConfirmPayResult>), - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "ready") { + expect(r).eq({}) + return; + } expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); if (r.payHandler.onClick === undefined) expect.fail(); r.payHandler.onClick(); } - // await waitNextUpdate(); - - // { - // const r = getLastResultOrThrow(); - // if (r.status !== "completed") expect.fail(); - // expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); - // expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); - // // if (r.payResult.type !== ConfirmPayResultType.Done) expect.fail(); - // // expect(r.payResult.contractTerms).not.undefined; - // // expect(r.payHandler.onClick).undefined; - // } - await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should not stay in ready state after pay with error", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: nullFunction, + }; + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + handler.addWalletCallResponse(WalletApiOperation.ConfirmPay, undefined, { + type: ConfirmPayResultType.Pending, + lastError: { code: 1 }, + } as ConfirmPayResult) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:9", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:15", - }, - ], - } as Partial<BalancesResponse>), - confirmPay: async () => - ({ - type: ConfirmPayResultType.Pending, - lastError: { code: 1 }, - } as Partial<ConfirmPayResult>), - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); @@ -429,10 +380,10 @@ describe("Payment CTA states", () => { r.payHandler.onClick(); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); @@ -450,72 +401,91 @@ describe("Payment CTA states", () => { } await assertNoPendingUpdate(); + + expect(handler.getCallingQueueState()).eq("empty") }); it("should update balance if a coins is withdraw", async () => { - const subscriptions = new SubsHandler(); - let availableBalance = Amounts.parseOrThrow("USD:10"); - - function notifyCoinWithdrawn(newAmount: AmountJson): void { - availableBalance = Amounts.add(availableBalance, newAmount).amount; - subscriptions.notifyEvent(NotificationType.CoinWithdrawn); + const { handler, mock } = createWalletApiMock(); + + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, } - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:10", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: subscriptions.saveSubscription, - preparePay: async () => - ({ - amountRaw: "USD:9", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: Amounts.stringify(availableBalance), - }, - ], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "ready") { + expect(r).eq({}) + return + } expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:10")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); expect(r.payHandler.onClick).not.undefined; - notifyCoinWithdrawn(Amounts.parseOrThrow("USD:5")); + handler.notifyEventFromWallet(NotificationType.CoinWithdrawn); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "ready") { + expect(r).eq({}) + return + } expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); @@ -523,5 +493,6 @@ describe("Payment CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); }); diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/index.ts b/packages/taler-wallet-webextension/src/cta/Recovery/index.ts index 013e9c041..4a65c571b 100644 --- a/packages/taler-wallet-webextension/src/cta/Recovery/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Recovery/index.ts @@ -14,12 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts index 965a64e69..750fd22f7 100644 --- a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts @@ -16,8 +16,7 @@ import { parseRecoveryUri } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import * as wxApi from "../../wxApi.js"; -import { wxClient } from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -49,7 +48,7 @@ export function useComponentState( const recovery = info; async function recoverBackup(): Promise<void> { - await wxClient.call(WalletApiOperation.ImportBackupRecovery, { recovery }); + await wxApi.wallet.call(WalletApiOperation.ImportBackupRecovery, { recovery }); onSuccess(); } diff --git a/packages/taler-wallet-webextension/src/cta/Refund/index.ts b/packages/taler-wallet-webextension/src/cta/Refund/index.ts index be9e3499b..6bd976aab 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/index.ts @@ -19,13 +19,13 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { IgnoredView, InProgressView, LoadingUriView, - ReadyView, + ReadyView } from "./views.js"; export interface Props { diff --git a/packages/taler-wallet-webextension/src/cta/Refund/state.ts b/packages/taler-wallet-webextension/src/cta/Refund/state.ts index 16dbbf70d..65a895fc3 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/state.ts @@ -15,9 +15,10 @@ */ import { Amounts, NotificationType } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -28,15 +29,14 @@ export function useComponentState( const info = useAsyncAsHook(async () => { if (!talerRefundUri) throw Error("ERROR_NO-URI-FOR-REFUND"); - const refund = await api.prepareRefund({ talerRefundUri }); + const refund = await api.wallet.call(WalletApiOperation.PrepareRefund, { talerRefundUri }); return { refund, uri: talerRefundUri }; }); - useEffect(() => { - api.onUpdateNotification([NotificationType.RefreshMelted], () => { - info?.retry(); - }); - }); + useEffect(() => api.listener.onUpdateNotification( + [NotificationType.RefreshMelted], + info?.retry) + ); if (!info) { return { status: "loading", error: undefined }; @@ -51,7 +51,9 @@ export function useComponentState( const { refund, uri } = info.response; const doAccept = async (): Promise<void> => { - const res = await api.applyRefund(uri); + const res = await api.wallet.call(WalletApiOperation.ApplyRefund, { + talerRefundUri: uri + }); onSuccess(res.transactionId); }; diff --git a/packages/taler-wallet-webextension/src/cta/Refund/test.ts b/packages/taler-wallet-webextension/src/cta/Refund/test.ts index 3111a85c6..41996c133 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/test.ts @@ -21,18 +21,19 @@ import { AmountJson, - Amounts, - NotificationType, - PrepareRefundResult, + Amounts, NotificationType, OrderShortInfo, PrepareRefundResult } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../../test-utils.js"; -import { SubsHandler } from "../Payment/test.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; describe("Refund CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -44,24 +45,25 @@ describe("Refund CTA states", () => { null; }, }, - { - prepareRefund: async () => ({}), - applyRefund: async () => ({}), - onUpdateNotification: async () => ({}), - } as any, + mock + // { + // prepareRefund: async () => ({}), + // applyRefund: async () => ({}), + // onUpdateNotification: async () => ({}), + // } as any, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading-uri"); if (!error) expect.fail(); @@ -71,55 +73,76 @@ describe("Refund CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ready after loading", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerRefundUri: "taler://refund/asdasdas", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:2", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:0", + pending: false, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerRefundUri: "taler://refund/asdasdas", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareRefund: async () => - ({ - effectivePaid: "EUR:2", - awaiting: "EUR:2", - gone: "EUR:0", - granted: "EUR:0", - pending: false, - proposalId: "1", - info: { - contractTermsHash: "123", - merchant: { - name: "the merchant name", - }, - orderId: "orderId1", - summary: "the summary", - }, - } as PrepareRefundResult as any), - applyRefund: async () => ({}), - onUpdateNotification: async () => ({}), - } as any, + props, mock + // { + // prepareRefund: async () => + // ({ + // effectivePaid: "EUR:2", + // awaiting: "EUR:2", + // gone: "EUR:0", + // granted: "EUR:0", + // pending: false, + // proposalId: "1", + // info: { + // contractTermsHash: "123", + // merchant: { + // name: "the merchant name", + // }, + // orderId: "orderId1", + // summary: "the summary", + // }, + // } as PrepareRefundResult as any), + // applyRefund: async () => ({}), + // onUpdateNotification: async () => ({}), + // } as any, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); if (state.status !== "ready") expect.fail(); if (state.error) expect.fail(); @@ -131,58 +154,101 @@ describe("Refund CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ignored after clicking the ignore button", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerRefundUri: "taler://refund/asdasdas", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:2", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:0", + pending: false, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + // handler.addWalletCall(WalletApiOperation.ApplyRefund) + // handler.addWalletCall(WalletApiOperation.PrepareRefund, undefined, { + // awaiting: "EUR:1", + // effectivePaid: "EUR:2", + // gone: "EUR:0", + // granted: "EUR:1", + // pending: true, + // proposalId: "1", + // info: { + // contractTermsHash: "123", + // merchant: { + // name: "the merchant name", + // }, + // orderId: "orderId1", + // summary: "the summary", + // } as OrderShortInfo, + // }) + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerRefundUri: "taler://refund/asdasdas", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareRefund: async () => - ({ - effectivePaid: "EUR:2", - awaiting: "EUR:2", - gone: "EUR:0", - granted: "EUR:0", - pending: false, - proposalId: "1", - info: { - contractTermsHash: "123", - merchant: { - name: "the merchant name", - }, - orderId: "orderId1", - summary: "the summary", - }, - } as PrepareRefundResult as any), - applyRefund: async () => ({}), - onUpdateNotification: async () => ({}), - } as any, + props, mock + // { + // prepareRefund: async () => + // ({ + // effectivePaid: "EUR:2", + // awaiting: "EUR:2", + // gone: "EUR:0", + // granted: "EUR:0", + // pending: false, + // proposalId: "1", + // info: { + // contractTermsHash: "123", + // merchant: { + // name: "the merchant name", + // }, + // orderId: "orderId1", + // summary: "the summary", + // }, + // } as PrepareRefundResult as any), + // applyRefund: async () => ({}), + // onUpdateNotification: async () => ({}), + // } as any, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); - - if (state.status !== "ready") expect.fail(); - if (state.error) expect.fail(); + const state = pullLastResultOrThrow(); + + if (state.status !== "ready") { + expect(state).eq({}) + return; + } + if (state.error) { + expect(state).eq({}) + return; + } expect(state.accept.onClick).not.undefined; expect(state.merchantName).eq("the merchant name"); expect(state.orderId).eq("orderId1"); @@ -192,113 +258,145 @@ describe("Refund CTA states", () => { state.ignore.onClick(); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); - - if (state.status !== "ignored") expect.fail(); - if (state.error) expect.fail(); + const state = pullLastResultOrThrow(); + + if (state.status !== "ignored") { + expect(state).eq({}) + return; + } + if (state.error) { + expect(state).eq({}) + return; + } expect(state.merchantName).eq("the merchant name"); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be in progress when doing refresh", async () => { - let granted = Amounts.getZero("EUR"); - const unit: AmountJson = { currency: "EUR", value: 1, fraction: 0 }; - const refunded: AmountJson = { currency: "EUR", value: 2, fraction: 0 }; - let awaiting: AmountJson = refunded; - let pending = true; - - const subscriptions = new SubsHandler(); - - function notifyMelt(): void { - granted = Amounts.add(granted, unit).amount; - pending = granted.value < refunded.value; - awaiting = Amounts.sub(refunded, granted).amount; - subscriptions.notifyEvent(NotificationType.RefreshMelted); + const { handler, mock } = createWalletApiMock(); + const props = { + talerRefundUri: "taler://refund/asdasdas", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, } - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:2", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:0", + pending: true, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:1", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:1", + pending: true, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:0", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:2", + pending: false, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerRefundUri: "taler://refund/asdasdas", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareRefund: async () => - ({ - awaiting: Amounts.stringify(awaiting), - effectivePaid: "EUR:2", - gone: "EUR:0", - granted: Amounts.stringify(granted), - pending, - proposalId: "1", - info: { - contractTermsHash: "123", - merchant: { - name: "the merchant name", - }, - orderId: "orderId1", - summary: "the summary", - }, - } as PrepareRefundResult as any), - applyRefund: async () => ({}), - onUpdateNotification: subscriptions.saveSubscription, - } as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "in-progress") expect.fail("1"); + if (state.status !== "in-progress") { + expect(state).eq({}) + return; + } if (state.error) expect.fail(); expect(state.merchantName).eq("the merchant name"); expect(state.products).undefined; expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); // expect(state.progress).closeTo(1 / 3, 0.01) - notifyMelt(); + handler.notifyEventFromWallet(NotificationType.RefreshMelted) } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "in-progress") expect.fail("2"); + if (state.status !== "in-progress") { + expect(state).eq({}) + return; + } if (state.error) expect.fail(); expect(state.merchantName).eq("the merchant name"); expect(state.products).undefined; expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); // expect(state.progress).closeTo(2 / 3, 0.01) - notifyMelt(); + handler.notifyEventFromWallet(NotificationType.RefreshMelted) } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "ready") expect.fail("3"); + if (state.status !== "ready") { + expect(state).eq({}) + return; + } if (state.error) expect.fail(); expect(state.merchantName).eq("the merchant name"); expect(state.products).undefined; @@ -306,5 +404,6 @@ describe("Refund CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); }); diff --git a/packages/taler-wallet-webextension/src/cta/Tip/index.ts b/packages/taler-wallet-webextension/src/cta/Tip/index.ts index 03cbd2196..520d854f2 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/index.ts @@ -19,13 +19,13 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { AcceptedView, IgnoredView, LoadingUriView, - ReadyView, + ReadyView } from "./views.js"; export interface Props { diff --git a/packages/taler-wallet-webextension/src/cta/Tip/state.ts b/packages/taler-wallet-webextension/src/cta/Tip/state.ts index f6721d504..00e1fddad 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/state.ts @@ -15,20 +15,18 @@ */ import { Amounts } from "@gnu-taler/taler-util"; -import { useState } from "preact/hooks"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { talerTipUri, onCancel, onSuccess }: Props, api: typeof wxApi, ): State { - const [tipIgnored, setTipIgnored] = useState(false); - const tipInfo = useAsyncAsHook(async () => { if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP"); - const tip = await api.prepareTip({ talerTipUri }); + const tip = await api.wallet.call(WalletApiOperation.PrepareTip, { talerTipUri }); return { tip }; }); @@ -48,7 +46,7 @@ export function useComponentState( const { tip } = tipInfo.response; const doAccept = async (): Promise<void> => { - const res = await api.acceptTip({ walletTipId: tip.walletTipId }); + const res = await api.wallet.call(WalletApiOperation.AcceptTip, { walletTipId: tip.walletTipId }); //FIX: this may not be seen since we are moving to the success also tipInfo.retry(); @@ -65,13 +63,6 @@ export function useComponentState( }, }; - if (tipIgnored) { - return { - status: "ignored", - ...baseInfo, - }; - } - if (tip.accepted) { return { status: "accepted", diff --git a/packages/taler-wallet-webextension/src/cta/Tip/test.ts b/packages/taler-wallet-webextension/src/cta/Tip/test.ts index 47d9aa8db..69badbede 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/test.ts @@ -19,14 +19,18 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { Amounts, PrepareTipResult } from "@gnu-taler/taler-util"; +import { Amounts } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../../test-utils.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; describe("Tip CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -38,23 +42,20 @@ describe("Tip CTA states", () => { null; }, }, - { - prepareTip: async () => ({}), - acceptTip: async () => ({}), - } as any, + mock, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading-uri"); if (!error) expect.fail(); @@ -64,12 +65,26 @@ describe("Tip CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ready for accepting the tip", async () => { - let tipAccepted = false; - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + + handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + accepted: false, + exchangeBaseUrl: "exchange url", + merchantBaseUrl: "merchant url", + tipAmountEffective: "EUR:1", + walletTipId: "tip_id", + expirationTimestamp: { + t_s: 1 + }, + tipAmountRaw: "" + }); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -81,58 +96,79 @@ describe("Tip CTA states", () => { null; }, }, - { - prepareTip: async () => - ({ - accepted: tipAccepted, - exchangeBaseUrl: "exchange url", - merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", - } as PrepareTipResult as any), - acceptTip: async () => { - tipAccepted = true; - }, - } as any, + mock, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "ready") expect.fail(); + if (state.status !== "ready") { + expect(state).eq({ status: "ready" }) + return; + } if (state.error) expect.fail(); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.merchantBaseUrl).eq("merchant url"); expect(state.exchangeBaseUrl).eq("exchange url"); if (state.accept.onClick === undefined) expect.fail(); + handler.addWalletCallResponse(WalletApiOperation.AcceptTip); state.accept.onClick(); } - await waitNextUpdate(); + handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + accepted: true, + exchangeBaseUrl: "exchange url", + merchantBaseUrl: "merchant url", + tipAmountEffective: "EUR:1", + walletTipId: "tip_id", + expirationTimestamp: { + t_s: 1 + }, + tipAmountRaw: "" + }); + expect(await waitForStateUpdate()).true; + { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "accepted") expect.fail(); + if (state.status !== "accepted") { + expect(state).eq({ status: "accepted" }) + return; + } if (state.error) expect.fail(); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.merchantBaseUrl).eq("merchant url"); expect(state.exchangeBaseUrl).eq("exchange url"); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ignored after clicking the ignore button", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + exchangeBaseUrl: "exchange url", + merchantBaseUrl: "merchant url", + tipAmountEffective: "EUR:1", + walletTipId: "tip_id", + accepted: false, + expirationTimestamp: { + t_s: 1, + }, + tipAmountRaw: "" + }); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -144,52 +180,48 @@ describe("Tip CTA states", () => { null; }, }, - { - prepareTip: async () => - ({ - exchangeBaseUrl: "exchange url", - merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", - } as PrepareTipResult as any), - acceptTip: async () => ({}), - } as any, + mock, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); if (state.status !== "ready") expect.fail(); if (state.error) expect.fail(); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.merchantBaseUrl).eq("merchant url"); expect(state.exchangeBaseUrl).eq("exchange url"); - // if (state.ignore.onClick === undefined) expect.fail(); - - // state.ignore.onClick(); } - // await waitNextUpdate(); - // { - // const state = getLastResultOrThrow(); - - // if (state.status !== "ignored") expect.fail(); - // if (state.error) expect.fail(); - // } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should render accepted if the tip has been used previously", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + + handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + accepted: true, + exchangeBaseUrl: "exchange url", + merchantBaseUrl: "merchant url", + tipAmountEffective: "EUR:1", + walletTipId: "tip_id", + expirationTimestamp: { + t_s: 1, + }, + tipAmountRaw: "", + }); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -201,30 +233,20 @@ describe("Tip CTA states", () => { null; }, }, - { - prepareTip: async () => - ({ - accepted: true, - exchangeBaseUrl: "exchange url", - merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", - } as PrepareTipResult as any), - acceptTip: async () => ({}), - } as any, + mock, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); if (state.status !== "accepted") expect.fail(); if (state.error) expect.fail(); @@ -233,5 +255,6 @@ describe("Tip CTA states", () => { expect(state.exchangeBaseUrl).eq("exchange url"); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); }); diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts index 820bffdea..83293438f 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts @@ -19,7 +19,7 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts index d4ba18c12..b229924b2 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts @@ -15,9 +15,9 @@ */ import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -33,7 +33,7 @@ export function useComponentState( async function accept(): Promise<void> { try { - const resp = await api.initiatePeerPushPayment({ + const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPushPayment, { amount: Amounts.stringify(amount), partialContractTerms: { summary: subject, diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts index 0ffdb1b95..954243fe8 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts @@ -17,13 +17,13 @@ import { AbsoluteTime, AmountJson, - TalerErrorDetail, + TalerErrorDetail } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts index 0095910b5..4b860559e 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts @@ -18,12 +18,12 @@ import { AbsoluteTime, Amounts, TalerErrorDetail, - TalerProtocolTimestamp, + TalerProtocolTimestamp } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -31,7 +31,7 @@ export function useComponentState( api: typeof wxApi, ): State { const hook = useAsyncAsHook(async () => { - return await api.checkPeerPushPayment({ + return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, { talerUri: talerPayPushUri, }); }, []); @@ -53,7 +53,6 @@ export function useComponentState( } const { - amount: purseAmount, contractTerms, peerPushPaymentIncomingId, } = hook.response; @@ -65,7 +64,7 @@ export function useComponentState( async function accept(): Promise<void> { try { - const resp = await api.acceptPeerPushPayment({ + const resp = await api.wallet.call(WalletApiOperation.AcceptPeerPushPayment, { peerPushPaymentIncomingId, }); onSuccess(resp.transactionId); diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index a3b3df8b3..9a7acf9f1 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -20,15 +20,15 @@ import { HookError } from "../../hooks/useAsyncAsHook.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentStateFromParams, - useComponentStateFromURI, + useComponentStateFromURI } from "./state.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; -import { LoadingInfoView, LoadingUriView, SuccessView } from "./views.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; +import { LoadingInfoView, LoadingUriView, SuccessView } from "./views.js"; export interface PropsFromURI { talerWithdrawUri: string | undefined; diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index c542d8aae..704ef1ac3 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -19,13 +19,13 @@ import { AmountJson, Amounts, ExchangeListItem, - ExchangeTosStatus, + ExchangeTosStatus } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { PropsFromParams, PropsFromURI, State } from "./index.js"; type RecursiveState<S extends object> = S | (() => RecursiveState<S>); @@ -35,7 +35,7 @@ export function useComponentStateFromParams( api: typeof wxApi, ): RecursiveState<State> { const uriInfoHook = useAsyncAsHook(async () => { - const exchanges = await api.listExchanges(); + const exchanges = await api.wallet.call(WalletApiOperation.ListExchanges, {}); return { amount: Amounts.parseOrThrow(amount), exchanges }; }); @@ -58,11 +58,11 @@ export function useComponentStateFromParams( transactionId: string; confirmTransferUrl: string | undefined; }> { - const res = await api.acceptManualWithdrawal( - exchange, - Amounts.stringify(chosenAmount), - ageRestricted, - ); + const res = await api.wallet.call(WalletApiOperation.AcceptManualWithdrawal, { + exchangeBaseUrl: exchange, + amount: Amounts.stringify(chosenAmount), + restrictAge: ageRestricted, + }); return { confirmTransferUrl: undefined, transactionId: res.transactionId, @@ -93,16 +93,15 @@ export function useComponentStateFromURI( const uriInfoHook = useAsyncAsHook(async () => { if (!talerWithdrawUri) throw Error("ERROR_NO-URI-FOR-WITHDRAWAL"); - const uriInfo = await api.getWithdrawalDetailsForUri({ + const uriInfo = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForUri, { talerWithdrawUri, }); - const exchanges = await api.listExchanges(); const { amount, defaultExchangeBaseUrl } = uriInfo; return { talerWithdrawUri, amount: Amounts.parseOrThrow(amount), thisExchange: defaultExchangeBaseUrl, - exchanges, + exchanges: uriInfo.possibleExchanges, }; }); @@ -118,7 +117,7 @@ export function useComponentStateFromURI( const uri = uriInfoHook.response.talerWithdrawUri; const chosenAmount = uriInfoHook.response.amount; const defaultExchange = uriInfoHook.response.thisExchange; - const exchangeList = uriInfoHook.response.exchanges.exchanges; + const exchangeList = uriInfoHook.response.exchanges; async function doManagedWithdraw( exchange: string, @@ -127,7 +126,11 @@ export function useComponentStateFromURI( transactionId: string; confirmTransferUrl: string | undefined; }> { - const res = await api.acceptWithdrawal(uri, exchange, ageRestricted); + const res = await api.wallet.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { + exchangeBaseUrl: exchange, + talerWithdrawUri: uri, + restrictAge: ageRestricted + }); return { confirmTransferUrl: res.confirmTransferUrl, transactionId: res.transactionId, @@ -186,7 +189,7 @@ function exchangeSelectionState( * about the withdrawal */ const amountHook = useAsyncAsHook(async () => { - const info = await api.getWithdrawalDetailsForAmount({ + const info = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForAmount, { exchangeBaseUrl: currentExchange.exchangeBaseUrl, amount: Amounts.stringify(chosenAmount), restrictAge: ageRestricted, @@ -261,10 +264,10 @@ function exchangeSelectionState( //TODO: calculate based on exchange info const ageRestriction = ageRestrictionEnabled ? { - list: ageRestrictionOptions, - value: String(ageRestricted), - onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)), - } + list: ageRestrictionOptions, + value: String(ageRestricted), + onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)), + } : undefined; return { diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts index 977faa03a..b4ba32f8a 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts @@ -21,16 +21,12 @@ import { Amounts, - ExchangeEntryStatus, - ExchangeFullDetails, - ExchangeListItem, - ExchangesListResponse, - ExchangeTosStatus, - GetExchangeTosResult, - ManualWithdrawalDetails, + ExchangeEntryStatus, ExchangeListItem, ExchangeTosStatus } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../../test-utils.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentStateFromURI } from "./state.js"; const exchanges: ExchangeListItem[] = [ @@ -65,39 +61,32 @@ const exchanges: ExchangeListItem[] = [ describe("Withdraw CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerWithdrawUri: undefined, + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentStateFromURI( - { - talerWithdrawUri: undefined, - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - listExchanges: async () => ({ exchanges }), - getWithdrawalDetailsForAmount: async ({ - talerWithdrawUri, - }: any) => ({ - amount: "ARS:2", - possibleExchanges: exchanges, - }), - } as any, + props, mock ), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equals("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); if (status != "uri-error") expect.fail(); if (!error) expect.fail(); @@ -107,40 +96,41 @@ describe("Withdraw CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should tell the user that there is not known exchange", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerWithdrawUri: "taler-withdraw://", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + amount: "EUR:2", + possibleExchanges: [], + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentStateFromURI( - { - talerWithdrawUri: "taler-withdraw://", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - listExchanges: async () => ({ exchanges }), - getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ - amount: "EUR:2", - possibleExchanges: [], - }), - } as any, + props, mock ), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equals("loading", "1"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("no-exchange", "3"); @@ -148,65 +138,60 @@ describe("Withdraw CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be able to withdraw if tos are ok", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerWithdrawUri: "taler-withdraw://", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + amount: "ARS:2", + possibleExchanges: exchanges, + defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl + }) + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForAmount, undefined, { + amountRaw: "ARS:2", + amountEffective: "ARS:2", + paytoUris: ["payto://"], + tosAccepted: true, + ageRestrictionOptions: [] + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentStateFromURI( - { - talerWithdrawUri: "taler-withdraw://", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - listExchanges: async () => ({ exchanges }), - getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ - amount: "ARS:2", - possibleExchanges: exchanges, - defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, - }), - getWithdrawalDetailsForAmount: - async (): Promise<ManualWithdrawalDetails> => - ({ - amountRaw: "ARS:2", - amountEffective: "ARS:2", - } as any), - getExchangeTos: async (): Promise<GetExchangeTosResult> => ({ - contentType: "text", - content: "just accept", - acceptedEtag: "v1", - currentEtag: "v1", - tosStatus: ExchangeTosStatus.Accepted, - }), - } as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); expect(state.status).equals("success"); if (state.status !== "success") return; @@ -218,82 +203,72 @@ describe("Withdraw CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); - it("should be accept the tos before withdraw", async () => { - const listExchangesResponse: ExchangesListResponse = { - exchanges: exchanges.map((e) => ({ - ...e, - tosStatus: ExchangeTosStatus.New, - })), - }; - - function updateAcceptedVersionToCurrentVersion(): void { - listExchangesResponse.exchanges = listExchangesResponse.exchanges.map( - (e) => ({ - ...e, - tosStatus: ExchangeTosStatus.Accepted, - }), - ); + it("should accept the tos before withdraw", async () => { + const { handler, mock } = createWalletApiMock(); + const props = { + talerWithdrawUri: "taler-withdraw://", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, } - - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const exchangeWithNewTos = exchanges.map((e) => ({ + ...e, + tosStatus: ExchangeTosStatus.New, + })); + + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + amount: "ARS:2", + possibleExchanges: exchangeWithNewTos, + defaultExchangeBaseUrl: exchangeWithNewTos[0].exchangeBaseUrl + }) + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForAmount, undefined, { + amountRaw: "ARS:2", + amountEffective: "ARS:2", + paytoUris: ["payto://"], + tosAccepted: false, + ageRestrictionOptions: [] + }) + + + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + amount: "ARS:2", + possibleExchanges: exchanges, + defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentStateFromURI( - { - talerWithdrawUri: "taler-withdraw://", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - listExchanges: async () => listExchangesResponse, - getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ - amount: "ARS:2", - possibleExchanges: exchanges, - defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, - }), - getWithdrawalDetailsForAmount: - async (): Promise<ManualWithdrawalDetails> => - ({ - amountRaw: "ARS:2", - amountEffective: "ARS:2", - } as any), - getExchangeTos: async (): Promise<GetExchangeTosResult> => ({ - contentType: "text", - content: "just accept", - acceptedEtag: "v1", - currentEtag: "v2", - tosStatus: ExchangeTosStatus.Changed, - }), - setExchangeTosAccepted: async () => ({}), - } as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); expect(state.status).equals("success"); if (state.status !== "success") return; @@ -303,14 +278,14 @@ describe("Withdraw CTA states", () => { expect(state.doWithdrawal.onClick).undefined; - updateAcceptedVersionToCurrentVersion(); + // updateAcceptedVersionToCurrentVersion(); state.onTosUpdate(); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); expect(state.status).equals("success"); if (state.status !== "success") return; @@ -322,5 +297,6 @@ describe("Withdraw CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); }); diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts index f24896bf3..1b2929317 100644 --- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts +++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts @@ -13,10 +13,9 @@ 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/> */ -import { NotificationType, TalerErrorDetail } from "@gnu-taler/taler-util"; +import { TalerErrorDetail } from "@gnu-taler/taler-util"; import { TalerError } from "@gnu-taler/taler-wallet-core"; import { useEffect, useMemo, useState } from "preact/hooks"; -import * as wxApi from "../wxApi.js"; export interface HookOk<T> { hasError: false; diff --git a/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts b/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts index 727d653af..5a0194db8 100644 --- a/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts +++ b/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts @@ -14,11 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { useState, useEffect } from "preact/hooks"; -import * as wxApi from "../wxApi.js"; -import { platform } from "../platform/api.js"; -import { ToggleHandler } from "../mui/handlers.js"; import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { useEffect, useState } from "preact/hooks"; +import { ToggleHandler } from "../mui/handlers.js"; +import { platform } from "../platform/api.js"; +import { wxApi } from "../wxApi.js"; export function useAutoOpenPermissions(): ToggleHandler { const [enabled, setEnabled] = useState(false); @@ -31,7 +31,7 @@ export function useAutoOpenPermissions(): ToggleHandler { useEffect(() => { async function getValue(): Promise<void> { - const res = await wxApi.containsHeaderListener(); + const res = await wxApi.background.containsHeaderListener(); setEnabled(res.newValue); } getValue(); @@ -59,11 +59,11 @@ async function handleAutoOpenPerm( onChange(false); throw lastError; } - const res = await wxApi.toggleHeaderListener(granted); + const res = await wxApi.background.toggleHeaderListener(granted); onChange(res.newValue); } else { try { - await wxApi.toggleHeaderListener(false).then((r) => onChange(r.newValue)); + await wxApi.background.toggleHeaderListener(false).then((r) => onChange(r.newValue)); } catch (e) { console.log(e); } diff --git a/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts b/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts index da3b05df4..7339a876a 100644 --- a/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts +++ b/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts @@ -14,8 +14,9 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; export interface BackupDeviceName { name: string; @@ -31,10 +32,10 @@ export function useBackupDeviceName(): BackupDeviceName { useEffect(() => { async function run(): Promise<void> { //create a first list of backup info by currency - const status = await wxApi.getBackupInfo(); + const status = await wxApi.wallet.call(WalletApiOperation.GetBackupInfo, {}); async function update(newName: string): Promise<void> { - await wxApi.setWalletDeviceId(newName); + await wxApi.wallet.call(WalletApiOperation.SetWalletDeviceId, { walletDeviceId: newName }); setStatus((old) => ({ ...old, name: newName })); } diff --git a/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts b/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts index 3e9629dcb..3d284fb5a 100644 --- a/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts +++ b/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts @@ -14,11 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { useState, useEffect } from "preact/hooks"; -import * as wxApi from "../wxApi.js"; -import { platform } from "../platform/api.js"; -import { ToggleHandler } from "../mui/handlers.js"; import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { useEffect, useState } from "preact/hooks"; +import { ToggleHandler } from "../mui/handlers.js"; +import { platform } from "../platform/api.js"; +import { wxApi } from "../wxApi.js"; export function useClipboardPermissions(): ToggleHandler { const [enabled, setEnabled] = useState(false); @@ -31,7 +31,7 @@ export function useClipboardPermissions(): ToggleHandler { useEffect(() => { async function getValue(): Promise<void> { - const res = await wxApi.containsHeaderListener(); + const res = await wxApi.background.containsHeaderListener(); setEnabled(res.newValue); } getValue(); @@ -66,7 +66,7 @@ async function handleClipboardPerm( onChange(granted); } else { try { - await wxApi.toggleHeaderListener(false).then((r) => onChange(r.newValue)); + await wxApi.background.toggleHeaderListener(false).then((r) => onChange(r.newValue)); } catch (e) { console.log(e); } diff --git a/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts index a61fe7965..a8564fddb 100644 --- a/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts +++ b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts @@ -16,7 +16,7 @@ import { WalletDiagnostics } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] { const [timedOut, setTimedOut] = useState(false); @@ -33,7 +33,7 @@ export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] { } }, 1000); const doFetch = async (): Promise<void> => { - const d = await wxApi.getDiagnostics(); + const d = await wxApi.background.getDiagnostics(); gotDiagnostics = true; setDiagnostics(d); }; diff --git a/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts b/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts index 187517b41..b73c9fc16 100644 --- a/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts +++ b/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts @@ -14,9 +14,9 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { ProviderInfo } from "@gnu-taler/taler-wallet-core"; +import { ProviderInfo, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; export interface ProviderStatus { info?: ProviderInfo; @@ -30,7 +30,7 @@ export function useProviderStatus(url: string): ProviderStatus | undefined { useEffect(() => { async function run(): Promise<void> { //create a first list of backup info by currency - const status = await wxApi.getBackupInfo(); + const status = await wxApi.wallet.call(WalletApiOperation.GetBackupInfo, {}); const providers = status.providers.filter( (p) => p.syncProviderBaseUrl === url, @@ -39,13 +39,17 @@ export function useProviderStatus(url: string): ProviderStatus | undefined { async function sync(): Promise<void> { if (info) { - await wxApi.syncOneProvider(info.syncProviderBaseUrl); + await wxApi.wallet.call(WalletApiOperation.RunBackupCycle, { + providers: [info.syncProviderBaseUrl] + }); } } async function remove(): Promise<void> { if (info) { - await wxApi.removeProvider(info.syncProviderBaseUrl); + await wxApi.wallet.call(WalletApiOperation.RemoveBackupProvider, { + provider: info.syncProviderBaseUrl + }); } } diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.test.ts b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.test.ts index a7ad3e81b..8aabb8adf 100644 --- a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.test.ts +++ b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.test.ts @@ -31,18 +31,18 @@ describe("useTalerActionURL hook", () => { }); }; - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(useTalerActionURL, ctx); { - const [url] = getLastResultOrThrow(); + const [url] = pullLastResultOrThrow(); expect(url).undefined; } - await waitNextUpdate("waiting for useEffect"); + expect(await waitForStateUpdate()).true; { - const [url, setDismissed] = getLastResultOrThrow(); + const [url, setDismissed] = pullLastResultOrThrow(); expect(url).deep.equals({ location: "clipboard", uri: "qwe", @@ -50,10 +50,10 @@ describe("useTalerActionURL hook", () => { setDismissed(true); } - await waitNextUpdate("after dismiss"); + expect(await waitForStateUpdate()).true; { - const [url] = getLastResultOrThrow(); + const [url] = pullLastResultOrThrow(); if (url !== undefined) throw Error("invalid"); expect(url).undefined; } diff --git a/packages/taler-wallet-webextension/src/hooks/useWalletDevMode.ts b/packages/taler-wallet-webextension/src/hooks/useWalletDevMode.ts index 8021db686..8d4921392 100644 --- a/packages/taler-wallet-webextension/src/hooks/useWalletDevMode.ts +++ b/packages/taler-wallet-webextension/src/hooks/useWalletDevMode.ts @@ -15,7 +15,7 @@ */ import { useState, useEffect } from "preact/hooks"; -import { wxClient } from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; import { ToggleHandler } from "../mui/handlers.js"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -30,7 +30,7 @@ export function useWalletDevMode(): ToggleHandler { useEffect(() => { async function getValue(): Promise<void> { - const res = await wxClient.call(WalletApiOperation.GetVersion, {}); + const res = await wxApi.wallet.call(WalletApiOperation.GetVersion, {}); setEnabled(res.devMode); } getValue(); @@ -49,7 +49,7 @@ async function handleOpen( onChange: (value: boolean) => void, ): Promise<void> { const nextValue = !currentValue - await wxClient.call(WalletApiOperation.SetDevMode, { devModeEnabled: nextValue }); + await wxApi.wallet.call(WalletApiOperation.SetDevMode, { devModeEnabled: nextValue }); onChange(nextValue); return; } diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx index 94d5d1e16..98c6d166c 100644 --- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx @@ -15,6 +15,7 @@ */ import { Amounts, Balance, NotificationType } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { BalanceTable } from "../components/BalanceTable.js"; @@ -27,7 +28,7 @@ import { Button } from "../mui/Button.js"; import { ButtonHandler } from "../mui/handlers.js"; import { compose, StateViewMap } from "../utils/index.js"; import { AddNewActionView } from "../wallet/AddNewActionView.js"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; import { NoBalanceHelp } from "./NoBalanceHelp.js"; export interface Props { @@ -71,16 +72,16 @@ function useComponentState( api: typeof wxApi, ): State { const [addingAction, setAddingAction] = useState(false); - const state = useAsyncAsHook(api.getBalance); + const state = useAsyncAsHook(() => + api.wallet.call(WalletApiOperation.GetBalances, {}), + ); - useEffect(() => { - return api.onUpdateNotification( + useEffect(() => + api.listener.onUpdateNotification( [NotificationType.WithdrawGroupFinished], - () => { - state?.retry(); - }, - ); - }); + state?.retry, + ), + ); if (!state) { return { diff --git a/packages/taler-wallet-webextension/src/test-utils.ts b/packages/taler-wallet-webextension/src/test-utils.ts index e2339bff3..695fec201 100644 --- a/packages/taler-wallet-webextension/src/test-utils.ts +++ b/packages/taler-wallet-webextension/src/test-utils.ts @@ -14,6 +14,8 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { NotificationType } from "@gnu-taler/taler-util"; +import { WalletCoreApiClient, WalletCoreOpKeys, WalletCoreRequestType, WalletCoreResponseType } from "@gnu-taler/taler-wallet-core"; import { ComponentChildren, Fragment, @@ -24,6 +26,7 @@ import { VNode, } from "preact"; import { render as renderToString } from "preact-render-to-string"; +import { BackgroundApiClient, wxApi } from "./wxApi.js"; // When doing tests we want the requestAnimationFrame to be as fast as possible. // without this option the RAF will timeout after 100ms making the tests slower @@ -86,9 +89,10 @@ type RecursiveState<S> = S | (() => RecursiveState<S>); interface Mounted<T> { unmount: () => void; - getLastResultOrThrow: () => Exclude<T, VoidFunction>; + pullLastResultOrThrow: () => Exclude<T, VoidFunction>; assertNoPendingUpdate: () => void; - waitNextUpdate: (s?: string) => Promise<void>; + // waitNextUpdate: (s?: string) => Promise<void>; + waitForStateUpdate: () => Promise<boolean>; } const isNode = typeof window === "undefined"; @@ -97,9 +101,6 @@ export function mountHook<T extends object>( callback: () => RecursiveState<T>, Context?: ({ children }: { children: any }) => VNode, ): Mounted<T> { - // const result: { current: T | null } = { - // current: null - // } let lastResult: Exclude<T, VoidFunction> | Error | null = null; const listener: Array<() => void> = []; @@ -132,23 +133,6 @@ export function mountHook<T extends object>( ? create(Component, {}) : create(Context, { children: [create(Component, {})] }); - // waiter callback - async function waitNextUpdate(_label = ""): Promise<void> { - if (_label) _label = `. label: "${_label}"`; - await new Promise((res, rej) => { - const tid = setTimeout(() => { - rej( - Error(`waiting for an update but the hook didn't make one${_label}`), - ); - }, 100); - - listener.push(() => { - clearTimeout(tid); - res(undefined); - }); - }); - } - const customElement = {} as Element; const parentElement = isNode ? customElement : document.createElement("div"); if (!isNode) { @@ -164,14 +148,14 @@ export function mountHook<T extends object>( } } - function getLastResult(): Exclude<T | Error | null, VoidFunction> { + function pullLastResult(): Exclude<T | Error | null, VoidFunction> { const copy: Exclude<T | Error | null, VoidFunction> = lastResult; lastResult = null; return copy; } - function getLastResultOrThrow(): Exclude<T, VoidFunction> { - const r = getLastResult(); + function pullLastResultOrThrow(): Exclude<T, VoidFunction> { + const r = pullLastResult(); if (r instanceof Error) throw r; if (!r) throw Error("there was no last result"); return r; @@ -194,15 +178,137 @@ export function mountHook<T extends object>( }); }); - const r = getLastResult(); + const r = pullLastResult(); if (r) throw Error(`There are still pending results. - This may happen because the hook did a new update but the test didn't consume the result using getLastResult`); + This may happen because the hook did a new update but the test didn't consume the result using pullLastResult`); } + async function waitForStateUpdate(): Promise<boolean> { + return await new Promise((res, rej) => { + const tid = setTimeout(() => { + res(false); + }, 10); + + listener.push(() => { + clearTimeout(tid); + res(true); + }); + }); + } + return { unmount, - getLastResultOrThrow, - waitNextUpdate, + pullLastResultOrThrow, + waitForStateUpdate, assertNoPendingUpdate, }; } + +export const nullFunction: any = () => null; + +interface MockHandler { + addWalletCallResponse<Op extends WalletCoreOpKeys>(operation: Op, + payload?: Partial<WalletCoreRequestType<Op>>, + response?: WalletCoreResponseType<Op>, + callback?: () => void, + ): MockHandler; + + getCallingQueueState(): "empty" | string; + + notifyEventFromWallet(event: NotificationType): void; +} + +type CallRecord = WalletCallRecord | BackgroundCallRecord; +interface WalletCallRecord { + source: "wallet" + callback: () => void; + operation: WalletCoreOpKeys, + payload?: WalletCoreRequestType<WalletCoreOpKeys>, + response?: WalletCoreResponseType<WalletCoreOpKeys>, +} +interface BackgroundCallRecord { + source: "background" + name: string, + args: any, + response: any; +} + +type Subscriptions = { + [key in NotificationType]?: VoidFunction; +}; + +export function createWalletApiMock(): { handler: MockHandler, mock: typeof wxApi } { + const calls = new Array<CallRecord>() + const subscriptions: Subscriptions = {}; + + + const mock: typeof wxApi = { + wallet: new Proxy<WalletCoreApiClient>({} as any, { + get(target, name, receiver) { + const functionName = String(name) + if (functionName !== "call") { + throw Error(`the only method in wallet api should be 'call': ${functionName}`) + } + return function (operation: WalletCoreOpKeys, payload: WalletCoreRequestType<WalletCoreOpKeys>) { + const next = calls.shift() + + if (!next) { + throw Error(`wallet operation was called but none was expected: ${operation} (${JSON.stringify(payload, undefined, 2)})`) + } + if (next.source !== "wallet") { + throw Error(`wallet operation expected`) + } + if (operation !== next.operation) { + //more checks, deep check payload + throw Error(`wallet operation doesn't match: expected ${next.operation} actual ${operation}`) + } + next.callback() + + return next.response ?? {} + } + } + }), + listener: { + onUpdateNotification(mTypes: NotificationType[], callback: (() => void) | undefined): (() => void) { + mTypes.forEach(m => { + subscriptions[m] = callback + }) + return nullFunction + } + }, + background: new Proxy<BackgroundApiClient>({} as any, { + get(target, name, receiver) { + const functionName = String(name); + return function (...args: any) { + const next = calls.shift() + if (!next) { + throw Error(`background operation was called but none was expected: ${functionName} (${JSON.stringify(args, undefined, 2)})`) + } + if (next.source !== "background" || functionName !== next.name) { + //more checks, deep check args + throw Error(`background operation doesn't match`) + } + return next.response + } + } + }), + } + + + const handler: MockHandler = { + addWalletCallResponse(operation, payload, response, cb) { + calls.push({ source: "wallet", operation, payload, response, callback: cb ? cb : () => { null } }) + return handler + }, + notifyEventFromWallet(event: NotificationType): void { + const callback = subscriptions[event] + if (!callback) throw Error(`Expected to have a subscription for ${event}`); + return callback(); + }, + getCallingQueueState() { + return calls.length === 0 ? "empty" : `${calls.length} left`; + }, + } + + return { handler, mock } +} diff --git a/packages/taler-wallet-webextension/src/wallet/AddAccount/index.ts b/packages/taler-wallet-webextension/src/wallet/AddAccount/index.ts index 0b50d9d85..09609a8a1 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddAccount/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddAccount/index.ts @@ -16,15 +16,15 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; -import { compose, StateViewMap } from "../../utils/index.js"; -import { LoadingUriView, ReadyView } from "./views.js"; -import * as wxApi from "../../wxApi.js"; -import { useComponentState } from "./state.js"; import { ButtonHandler, SelectFieldHandler, - TextFieldHandler, + TextFieldHandler } from "../../mui/handlers.js"; +import { compose, StateViewMap } from "../../utils/index.js"; +import { wxApi } from "../../wxApi.js"; +import { useComponentState } from "./state.js"; +import { LoadingUriView, ReadyView } from "./views.js"; export interface Props { currency: string; diff --git a/packages/taler-wallet-webextension/src/wallet/AddAccount/state.ts b/packages/taler-wallet-webextension/src/wallet/AddAccount/state.ts index f14c4c1bb..a9e8dfb30 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddAccount/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddAccount/state.ts @@ -15,19 +15,17 @@ */ import { parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { currency, onAccountAdded, onCancel }: Props, api: typeof wxApi, ): State { - const hook = useAsyncAsHook(async () => { - const { accounts } = await api.listKnownBankAccounts(currency); - return { accounts }; - }); + const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency })); const [payto, setPayto] = useState(""); const [alias, setAlias] = useState(""); @@ -61,7 +59,10 @@ export function useComponentState( async function addAccount(): Promise<void> { if (!uri || found) return; - await api.addKnownBankAccounts(uri, currency, alias); + const normalizedPayto = stringifyPaytoUri(uri); + await api.wallet.call(WalletApiOperation.AddKnownBankAccounts, { + alias, currency, payto: normalizedPayto + }); onAccountAdded(payto); } @@ -69,10 +70,10 @@ export function useComponentState( payto === "" ? undefined : !uri - ? "the uri is not ok" - : found - ? "that account is already present" - : undefined; + ? "the uri is not ok" + : found + ? "that account is already present" + : undefined; const unableToAdd = !type || !alias || paytoUriError; diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx index bba8b5964..c9dbfb64d 100644 --- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx @@ -14,11 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { - AbsoluteTime, - BackupRecovery, - constructRecoveryUri, -} from "@gnu-taler/taler-util"; +import { AbsoluteTime, constructRecoveryUri } from "@gnu-taler/taler-util"; import { ProviderInfo, ProviderPaymentPaid, @@ -32,8 +28,10 @@ import { intervalToDuration, } from "date-fns"; import { Fragment, h, VNode } from "preact"; +import { useEffect, useState } from "preact/hooks"; import { Loading } from "../components/Loading.js"; import { LoadingError } from "../components/LoadingError.js"; +import { QR } from "../components/QR.js"; import { BoldLight, Centered, @@ -48,10 +46,7 @@ import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Button } from "../mui/Button.js"; import { Pages } from "../NavigationBar.js"; -import * as wxApi from "../wxApi.js"; -import { wxClient } from "../wxApi.js"; -import { useEffect, useState } from "preact/hooks"; -import { QR } from "../components/QR.js"; +import { wxApi } from "../wxApi.js"; interface Props { onAddProvider: () => Promise<void>; @@ -112,7 +107,9 @@ export function ShowRecoveryInfo({ export function BackupPage({ onAddProvider }: Props): VNode { const { i18n } = useTranslationContext(); - const status = useAsyncAsHook(wxApi.getBackupInfo); + const status = useAsyncAsHook(() => + wxApi.wallet.call(WalletApiOperation.GetBackupInfo, {}), + ); const [recoveryInfo, setRecoveryInfo] = useState<string>(""); if (!status) { return <Loading />; @@ -127,7 +124,10 @@ export function BackupPage({ onAddProvider }: Props): VNode { } async function getRecoveryInfo(): Promise<void> { - const r = await wxClient.call(WalletApiOperation.ExportBackupRecovery, {}); + const r = await wxApi.wallet.call( + WalletApiOperation.ExportBackupRecovery, + {}, + ); const str = constructRecoveryUri(r); setRecoveryInfo(str); } @@ -157,7 +157,9 @@ export function BackupPage({ onAddProvider }: Props): VNode { <BackupView providers={providers} onAddProvider={onAddProvider} - onSyncAll={wxApi.syncAllProviders} + onSyncAll={async () => + wxApi.wallet.call(WalletApiOperation.RunBackupCycle, {}).then() + } onShowInfo={getRecoveryInfo} /> ); diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts index 374d6639f..c757610fc 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts @@ -34,73 +34,73 @@ const exchangeListEmpty = {}; describe("CreateManualWithdraw states", () => { it("should set noExchangeFound when exchange list is empty", () => { - const { getLastResultOrThrow } = mountHook(() => + const { pullLastResultOrThrow } = mountHook(() => useComponentState(exchangeListEmpty, undefined, undefined), ); - const { noExchangeFound } = getLastResultOrThrow(); + const { noExchangeFound } = pullLastResultOrThrow(); expect(noExchangeFound).equal(true); }); it("should set noExchangeFound when exchange list doesn't include selected currency", () => { - const { getLastResultOrThrow } = mountHook(() => + const { pullLastResultOrThrow } = mountHook(() => useComponentState(exchangeListWithARSandUSD, undefined, "COL"), ); - const { noExchangeFound } = getLastResultOrThrow(); + const { noExchangeFound } = pullLastResultOrThrow(); expect(noExchangeFound).equal(true); }); it("should select the first exchange from the list", () => { - const { getLastResultOrThrow } = mountHook(() => + const { pullLastResultOrThrow } = mountHook(() => useComponentState(exchangeListWithARSandUSD, undefined, undefined), ); - const { exchange } = getLastResultOrThrow(); + const { exchange } = pullLastResultOrThrow(); expect(exchange.value).equal("url1"); }); it("should select the first exchange with the selected currency", () => { - const { getLastResultOrThrow } = mountHook(() => + const { pullLastResultOrThrow } = mountHook(() => useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), ); - const { exchange } = getLastResultOrThrow(); + const { exchange } = pullLastResultOrThrow(); expect(exchange.value).equal("url2"); }); it("should change the exchange when currency change", async () => { - const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => + const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() => useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), ); { - const { exchange, currency } = getLastResultOrThrow(); + const { exchange, currency } = pullLastResultOrThrow(); expect(exchange.value).equal("url2"); if (currency.onChange === undefined) expect.fail(); currency.onChange("USD"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { exchange } = getLastResultOrThrow(); + const { exchange } = pullLastResultOrThrow(); expect(exchange.value).equal("url1"); } }); it("should change the currency when exchange change", async () => { - const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => + const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() => useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), ); { - const { exchange, currency } = getLastResultOrThrow(); + const { exchange, currency } = pullLastResultOrThrow(); expect(exchange.value).equal("url2"); expect(currency.value).equal("ARS"); @@ -109,10 +109,10 @@ describe("CreateManualWithdraw states", () => { exchange.onChange("url1"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { exchange, currency } = getLastResultOrThrow(); + const { exchange, currency } = pullLastResultOrThrow(); expect(exchange.value).equal("url1"); expect(currency.value).equal("USD"); @@ -120,22 +120,22 @@ describe("CreateManualWithdraw states", () => { }); it("should update parsed amount when amount change", async () => { - const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => + const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() => useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), ); { - const { amount, parsedAmount } = getLastResultOrThrow(); + const { amount, parsedAmount } = pullLastResultOrThrow(); expect(parsedAmount).equal(undefined); amount.onInput("12"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { parsedAmount } = getLastResultOrThrow(); + const { parsedAmount } = pullLastResultOrThrow(); expect(parsedAmount).deep.equals({ value: 12, @@ -146,41 +146,41 @@ describe("CreateManualWithdraw states", () => { }); it("should have an amount field", async () => { - const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => + const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() => useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), ); await defaultTestForInputText( - waitNextUpdate, - () => getLastResultOrThrow().amount, + waitForStateUpdate, + () => pullLastResultOrThrow().amount, ); }); it("should have an exchange selector ", async () => { - const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => + const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() => useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), ); await defaultTestForInputSelect( - waitNextUpdate, - () => getLastResultOrThrow().exchange, + waitForStateUpdate, + () => pullLastResultOrThrow().exchange, ); }); it("should have a currency selector ", async () => { - const { getLastResultOrThrow, waitNextUpdate } = mountHook(() => + const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() => useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), ); await defaultTestForInputSelect( - waitNextUpdate, - () => getLastResultOrThrow().currency, + waitForStateUpdate, + () => pullLastResultOrThrow().currency, ); }); }); async function defaultTestForInputText( - awaiter: () => Promise<void>, + awaiter: () => Promise<boolean>, getField: () => TextFieldHandler, ): Promise<void> { let nextValue = ""; @@ -191,7 +191,7 @@ async function defaultTestForInputText( field.onInput(nextValue); } - await awaiter(); + expect(await awaiter()).true; { const field = getField(); @@ -200,7 +200,7 @@ async function defaultTestForInputText( } async function defaultTestForInputSelect( - awaiter: () => Promise<void>, + awaiter: () => Promise<boolean>, getField: () => SelectFieldHandler, ): Promise<void> { let nextValue = ""; @@ -218,7 +218,7 @@ async function defaultTestForInputSelect( field.onChange(nextValue); } - await awaiter(); + expect(await awaiter()).true; { const field = getField(); diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts index 81d401a70..77661fe15 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts @@ -14,26 +14,25 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { AmountJson, PaytoUri } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { + ButtonHandler, + SelectFieldHandler, + TextFieldHandler +} from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; +import { wxApi } from "../../wxApi.js"; +import { AddAccountPage } from "../AddAccount/index.js"; +import { useComponentState } from "./state.js"; import { AmountOrCurrencyErrorView, LoadingErrorView, NoAccountToDepositView, NoEnoughBalanceView, - ReadyView, + ReadyView } from "./views.js"; -import * as wxApi from "../../wxApi.js"; -import { useComponentState } from "./state.js"; -import { AmountJson, PaytoUri } from "@gnu-taler/taler-util"; -import { - ButtonHandler, - SelectFieldHandler, - TextFieldHandler, - ToggleHandler, -} from "../../mui/handlers.js"; -import { AddAccountPage } from "../AddAccount/index.js"; export interface Props { amount?: string; diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts index 57380a632..686cfb4b4 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts @@ -21,11 +21,12 @@ import { KnownBankAccountsInfo, parsePaytoUri, PaytoUri, - stringifyPaytoUri, + stringifyPaytoUri } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -36,8 +37,10 @@ export function useComponentState( const currency = parsed !== undefined ? parsed.currency : currencyStr; const hook = useAsyncAsHook(async () => { - const { balances } = await api.getBalance(); - const { accounts } = await api.listKnownBankAccounts(currency); + const { balances } = await api.wallet.call(WalletApiOperation.GetBalances, {}); + const { accounts } = await api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { + currency + }); return { accounts, balances }; }); @@ -127,25 +130,29 @@ export function useComponentState( // const newSelected = !accountMap[accountStr] ? undefined : accountMap[accountStr]; // if (!newSelected) return; const uri = !accountStr ? undefined : parsePaytoUri(accountStr); - setSelectedAccount(uri); if (uri && parsedAmount) { try { const result = await getFeeForAmount(uri, parsedAmount, api); + setSelectedAccount(uri); setFee(result); } catch (e) { + console.error(e) + setSelectedAccount(uri); setFee(undefined); } } } async function updateAmount(numStr: string): Promise<void> { - setAmount(numStr); const parsed = Amounts.parse(`${currency}:${numStr}`); if (parsed && selectedAccount) { try { const result = await getFeeForAmount(selectedAccount, parsed, api); + setAmount(numStr); setFee(result); } catch (e) { + console.error(e) + setAmount(numStr); setFee(undefined); } } @@ -165,10 +172,10 @@ export function useComponentState( const amountError = !isDirty ? undefined : !parsedAmount - ? "Invalid amount" - : Amounts.cmp(balance, parsedAmount) === -1 - ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` - : undefined; + ? "Invalid amount" + : Amounts.cmp(balance, parsedAmount) === -1 + ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` + : undefined; const unableToDeposit = !parsedAmount || @@ -176,13 +183,16 @@ export function useComponentState( Amounts.isZero(totalToDeposit) || fee === undefined || amountError !== undefined; + // console.log(parsedAmount, selectedAccount, fee, totalToDeposit, amountError) async function doSend(): Promise<void> { if (!selectedAccount || !parsedAmount || !currency) return; - const account = `payto://${selectedAccount.targetType}/${selectedAccount.targetPath}`; + const depositPaytoUri = `payto://${selectedAccount.targetType}/${selectedAccount.targetPath}`; const amount = Amounts.stringify(parsedAmount); - await api.createDepositGroup(account, amount); + await api.wallet.call(WalletApiOperation.CreateDepositGroup, { + amount, depositPaytoUri + }) onSuccess(currency); } @@ -226,9 +236,11 @@ async function getFeeForAmount( a: AmountJson, api: typeof wxApi, ): Promise<DepositGroupFees> { - const account = `payto://${p.targetType}/${p.targetPath}`; + const depositPaytoUri = `payto://${p.targetType}/${p.targetPath}`; const amount = Amounts.stringify(a); - return await api.getFeeForDeposit(account, amount); + return await api.wallet.call(WalletApiOperation.GetFeeForDeposit, { + amount, depositPaytoUri + }) } export function labelForAccountType(id: string) { diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts index 68df5e402..4d36bc740 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts @@ -20,101 +20,108 @@ */ import { - Amounts, - Balance, - BalancesResponse, - DepositGroupFees, + Amounts, DepositGroupFees, parsePaytoUri, - stringifyPaytoUri, + stringifyPaytoUri } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; -import { mountHook } from "../../test-utils.js"; +import { createWalletApiMock, mountHook, nullFunction } from "../../test-utils.js"; -import * as wxApi from "../../wxApi.js"; import { useComponentState } from "./state.js"; const currency = "EUR"; -const withoutFee = async (): Promise<DepositGroupFees> => ({ +const withoutFee = (): DepositGroupFees => ({ coin: Amounts.parseOrThrow(`${currency}:0`), wire: Amounts.parseOrThrow(`${currency}:0`), refresh: Amounts.parseOrThrow(`${currency}:0`), }); -const withSomeFee = async (): Promise<DepositGroupFees> => ({ +const withSomeFee = (): DepositGroupFees => ({ coin: Amounts.parseOrThrow(`${currency}:1`), wire: Amounts.parseOrThrow(`${currency}:1`), refresh: Amounts.parseOrThrow(`${currency}:1`), }); -const freeJustForIBAN = async (account: string): Promise<DepositGroupFees> => - /IBAN/i.test(account) ? withSomeFee() : withoutFee(); - -const someBalance = [ - { - available: "EUR:10", - } as Balance, -]; - -const nullFunction: any = () => null; -type VoidFunction = () => void; - describe("DepositPage states", () => { it("should have status 'no-enough-balance' when balance is empty", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { currency, onCancel: nullFunction, onSuccess: nullFunction } + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { + balances: [{ + available: `${currency}:0`, + hasPendingTransactions: false, + pendingIncoming: `${currency}:0`, + pendingOutgoing: `${currency}:0`, + requiresUserInput: false, + }], + }) + handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, { + accounts: [] + }); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { currency, onCancel: nullFunction, onSuccess: nullFunction }, - { - getBalance: async () => - ({ - balances: [{ available: `${currency}:0` }], - } as Partial<BalancesResponse>), - listKnownBankAccounts: async () => ({ accounts: {} }), - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equal("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equal("no-enough-balance"); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); - // it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => { - // const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = - // mountHook(() => - // useComponentState({ currency, onCancel: nullFunction, onSuccess: nullFunction }, { - // getBalance: async () => - // ({ - // balances: [{ available: `${currency}:1` }], - // } as Partial<BalancesResponse>), - // listKnownBankAccounts: async () => ({ accounts: {} }), - // } as Partial<typeof wxApi> as any), - // ); - - // { - // const { status } = getLastResultOrThrow(); - // expect(status).equal("loading"); - // } - - // await waitNextUpdate(); - // { - // const r = getLastResultOrThrow(); - // if (r.status !== "no-accounts") expect.fail(); - // expect(r.cancelHandler.onClick).not.undefined; - // } - - // await assertNoPendingUpdate(); - // }); + it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => { + const { handler, mock } = createWalletApiMock(); + const props = { currency, onCancel: nullFunction, onSuccess: nullFunction } + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { + balances: [{ + available: `${currency}:1`, + hasPendingTransactions: false, + pendingIncoming: `${currency}:0`, + pendingOutgoing: `${currency}:0`, + requiresUserInput: false, + }], + }) + handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, { + accounts: [] + }); + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = + mountHook(() => + useComponentState( + props, mock + ) + ); + + { + const { status } = pullLastResultOrThrow(); + expect(status).equal("loading"); + } + + expect(await waitForStateUpdate()).true; + { + const r = pullLastResultOrThrow(); + if (r.status !== "no-accounts") expect.fail(); + // expect(r.cancelHandler.onClick).not.undefined; + } + + await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") + }); const ibanPayto = { uri: parsePaytoUri("payto://iban/ES8877998399652238")!, @@ -130,29 +137,38 @@ describe("DepositPage states", () => { }; it("should have status 'ready' but unable to deposit ", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { currency, onCancel: nullFunction, onSuccess: nullFunction } + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { + balances: [{ + available: `${currency}:1`, + hasPendingTransactions: false, + pendingIncoming: `${currency}:0`, + pendingOutgoing: `${currency}:0`, + requiresUserInput: false, + }], + }) + handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, { + accounts: [ibanPayto] + }); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { currency, onCancel: nullFunction, onSuccess: nullFunction }, - { - getBalance: async () => - ({ - balances: [{ available: `${currency}:1` }], - } as Partial<BalancesResponse>), - listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }), - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equal("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.cancelHandler.onClick).not.undefined; expect(r.currency).eq(currency); @@ -162,33 +178,46 @@ describe("DepositPage states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); - it.skip("should not be able to deposit more than the balance ", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + it("should not be able to deposit more than the balance ", async () => { + const { handler, mock } = createWalletApiMock(); + const props = { currency, onCancel: nullFunction, onSuccess: nullFunction } + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { + balances: [{ + available: `${currency}:5`, + hasPendingTransactions: false, + pendingIncoming: `${currency}:0`, + pendingOutgoing: `${currency}:0`, + requiresUserInput: false, + }], + }) + handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, { + accounts: [ibanPayto] + }); + handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee()) + handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee()) + handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee()) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { currency, onCancel: nullFunction, onSuccess: nullFunction }, - { - getBalance: async () => - ({ - balances: [{ available: `${currency}:1` }], - } as Partial<BalancesResponse>), - listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }), - getFeeForDeposit: withoutFee, - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equal("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; + const accountSelected = stringifyPaytoUri(ibanPayto.uri) { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.cancelHandler.onClick).not.undefined; expect(r.currency).eq(currency); @@ -196,68 +225,19 @@ describe("DepositPage states", () => { expect(r.amount.value).eq("0"); expect(r.depositHandler.onClick).undefined; expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); + expect(r.account.onChange).not.undefined; - r.amount.onInput("10"); - } - - await waitNextUpdate(); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(""); - expect(r.amount.value).eq("10"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.depositHandler.onClick).undefined; + r.account.onChange!(accountSelected) } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.cancelHandler.onClick).not.undefined; expect(r.currency).eq(currency); - expect(r.account.value).eq(""); - expect(r.amount.value).eq("10"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.depositHandler.onClick).undefined; - } - - await assertNoPendingUpdate(); - }); - - it.skip("should calculate the fee upon entering amount ", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = - mountHook(() => - useComponentState( - { currency, onCancel: nullFunction, onSuccess: nullFunction }, - { - getBalance: async () => - ({ - balances: [{ available: `${currency}:1` }], - } as Partial<BalancesResponse>), - listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }), - getFeeForDeposit: withSomeFee, - } as Partial<typeof wxApi> as any, - ), - ); - - { - const { status } = getLastResultOrThrow(); - expect(status).equal("loading"); - } - - await waitNextUpdate(); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(""); + expect(r.account.value).eq(accountSelected); expect(r.amount.value).eq("0"); expect(r.depositHandler.onClick).undefined; expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); @@ -265,64 +245,74 @@ describe("DepositPage states", () => { r.amount.onInput("10"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.cancelHandler.onClick).not.undefined; expect(r.currency).eq(currency); - expect(r.account.value).eq(""); + expect(r.account.value).eq(accountSelected); expect(r.amount.value).eq("10"); expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`)); expect(r.depositHandler.onClick).undefined; + + r.amount.onInput("3"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.cancelHandler.onClick).not.undefined; expect(r.currency).eq(currency); - expect(r.account.value).eq(""); - expect(r.amount.value).eq("10"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`)); - expect(r.depositHandler.onClick).undefined; + expect(r.account.value).eq(accountSelected); + expect(r.amount.value).eq("3"); + expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); + expect(r.depositHandler.onClick).not.undefined; } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); - it("should calculate the fee upon selecting account ", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + it("should calculate the fee upon entering amount ", async () => { + const { handler, mock } = createWalletApiMock(); + const props = { currency, onCancel: nullFunction, onSuccess: nullFunction } + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { + balances: [{ + available: `${currency}:10`, + hasPendingTransactions: false, + pendingIncoming: `${currency}:0`, + pendingOutgoing: `${currency}:0`, + requiresUserInput: false, + }], + }) + handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, { + accounts: [ibanPayto] + }); + handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withSomeFee()) + handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withSomeFee()) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { currency, onCancel: nullFunction, onSuccess: nullFunction }, - { - getBalance: async () => - ({ - balances: [{ available: `${currency}:1` }], - } as Partial<BalancesResponse>), - listKnownBankAccounts: async () => ({ - accounts: [ibanPayto, talerBankPayto], - }), - getFeeForDeposit: freeJustForIBAN, - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equal("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; + const accountSelected = stringifyPaytoUri(ibanPayto.uri) { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.cancelHandler.onClick).not.undefined; expect(r.currency).eq(currency); @@ -330,198 +320,42 @@ describe("DepositPage states", () => { expect(r.amount.value).eq("0"); expect(r.depositHandler.onClick).undefined; expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); + expect(r.account.onChange).not.undefined; - if (r.account.onChange === undefined) expect.fail(); - r.account.onChange(stringifyPaytoUri(ibanPayto.uri)); + r.account.onChange!(accountSelected) } - await waitNextUpdate(""); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.cancelHandler.onClick).not.undefined; expect(r.currency).eq(currency); - expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri)); + expect(r.account.value).eq(accountSelected); expect(r.amount.value).eq("0"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.depositHandler.onClick).undefined; - } - - await waitNextUpdate(""); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri)); - expect(r.amount.value).eq("0"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.depositHandler.onClick).undefined; - - r.amount.onInput("10"); - } - - await waitNextUpdate(""); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri)); - expect(r.amount.value).eq("10"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`)); - expect(r.depositHandler.onClick).undefined; - } - - await waitNextUpdate(""); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri)); - expect(r.amount.value).eq("10"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`)); expect(r.depositHandler.onClick).undefined; - - if (r.account.onChange === undefined) expect.fail(); - r.account.onChange(stringifyPaytoUri(talerBankPayto.uri)); - } - - await waitNextUpdate(""); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri)); - expect(r.amount.value).eq("10"); expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`)); - expect(r.depositHandler.onClick).undefined; - } - - await waitNextUpdate(""); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri)); - expect(r.amount.value).eq("10"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`)); - expect(r.depositHandler.onClick).undefined; - } - - await assertNoPendingUpdate(); - }); - - it.skip("should be able to deposit if has the enough balance ", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = - mountHook(() => - useComponentState( - { currency, onCancel: nullFunction, onSuccess: nullFunction }, - { - getBalance: async () => - ({ - balances: [{ available: `${currency}:15` }], - } as Partial<BalancesResponse>), - listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }), - getFeeForDeposit: withSomeFee, - } as Partial<typeof wxApi> as any, - ), - ); - - { - const { status } = getLastResultOrThrow(); - expect(status).equal("loading"); - } - - await waitNextUpdate(); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(""); - expect(r.amount.value).eq("0"); - expect(r.depositHandler.onClick).undefined; - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); r.amount.onInput("10"); } - await waitNextUpdate(); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(""); - expect(r.amount.value).eq("10"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`)); - expect(r.depositHandler.onClick).undefined; - } - - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.cancelHandler.onClick).not.undefined; expect(r.currency).eq(currency); - expect(r.account.value).eq(""); + expect(r.account.value).eq(accountSelected); expect(r.amount.value).eq("10"); expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`)); expect(r.depositHandler.onClick).not.undefined; - - r.amount.onInput("13"); - } - - await waitNextUpdate(); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(""); - expect(r.amount.value).eq("13"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`)); - expect(r.depositHandler.onClick).not.undefined; - } - - await waitNextUpdate(); - - { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(""); - expect(r.amount.value).eq("13"); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:10`)); - expect(r.depositHandler.onClick).not.undefined; } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); + }); diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx index 94e6ab442..1e52f11bc 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx @@ -15,6 +15,7 @@ */ import { Amounts } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -36,7 +37,7 @@ import { TextField } from "../mui/TextField.js"; import { Pages } from "../NavigationBar.js"; import arrowIcon from "../svg/chevron-down.svg"; import bankIcon from "../svg/ri-bank-line.svg"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; const Container = styled.div` display: flex; @@ -171,7 +172,9 @@ export function SelectCurrency({ }): VNode { const { i18n } = useTranslationContext(); - const hook = useAsyncAsHook(wxApi.listExchanges); + const hook = useAsyncAsHook(() => + wxApi.wallet.call(WalletApiOperation.ListExchanges, {}), + ); if (!hook) { return <Loading />; diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx index c0e35b17b..2333fd3c1 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -21,7 +21,10 @@ import { ExchangeListItem, NotificationType, } from "@gnu-taler/taler-util"; -import { PendingTaskInfo } from "@gnu-taler/taler-wallet-core"; +import { + PendingTaskInfo, + WalletApiOperation, +} from "@gnu-taler/taler-wallet-core"; import { format } from "date-fns"; import { Fragment, h, VNode } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; @@ -33,8 +36,7 @@ import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useDiagnostics } from "../hooks/useDiagnostics.js"; import { Button } from "../mui/Button.js"; import { Grid } from "../mui/Grid.js"; -import { Paper } from "../mui/Paper.js"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; export function DeveloperPage(): VNode { const [status, timedOut] = useDiagnostics(); @@ -44,9 +46,12 @@ export function DeveloperPage(): VNode { listenAllEvents.includes = (e) => e !== "waiting-for-retry"; // includes every event const response = useAsyncAsHook(async () => { - const op = await wxApi.getPendingOperations(); - const c = await wxApi.dumpCoins(); - const ex = await wxApi.listExchanges(); + const op = await wxApi.wallet.call( + WalletApiOperation.GetPendingOperations, + {}, + ); + const c = await wxApi.wallet.call(WalletApiOperation.DumpCoins, {}); + const ex = await wxApi.wallet.call(WalletApiOperation.ListExchanges, {}); return { operations: op.pendingOperations, coins: c.coins, @@ -55,9 +60,10 @@ export function DeveloperPage(): VNode { }); useEffect(() => { - return wxApi.onUpdateNotification(listenAllEvents, () => { - response?.retry(); - }); + return wxApi.listener.onUpdateNotification( + listenAllEvents, + response?.retry, + ); }); const nonResponse = { operations: [], coins: [], exchanges: [] }; @@ -76,7 +82,7 @@ export function DeveloperPage(): VNode { coins={coins} exchanges={exchanges} onDownloadDatabase={async () => { - const db = await wxApi.exportDB(); + const db = await wxApi.wallet.call(WalletApiOperation.ExportDb, {}); return JSON.stringify(db); }} /> @@ -131,7 +137,9 @@ export function View({ } const fileRef = useRef<HTMLInputElement>(null); async function onImportDatabase(str: string): Promise<void> { - return wxApi.importDB(JSON.parse(str)); + return wxApi.wallet.call(WalletApiOperation.ImportDb, { + dump: JSON.parse(str), + }); } const currencies: { [ex: string]: string } = {}; const money_by_exchange = coins.reduce( @@ -169,7 +177,7 @@ export function View({ onClick={() => confirmReset( i18n.str`Do you want to IRREVOCABLY DESTROY everything inside your wallet and LOSE ALL YOUR COINS?`, - wxApi.resetDb, + () => wxApi.background.resetDb(), ) } > @@ -182,7 +190,7 @@ export function View({ onClick={() => confirmReset( i18n.str`TESTING: This may delete all your coin, proceed with caution`, - wxApi.runGarbageCollector, + () => wxApi.background.runGarbageCollector(), ) } > diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts index 605c71e80..4b7725264 100644 --- a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts @@ -17,9 +17,9 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { LoadingUriView, ReadyView } from "./views.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; +import { LoadingUriView, ReadyView } from "./views.js"; export interface Props { p: string; diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts index 2c5ac95c0..d194b3f97 100644 --- a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState({ p }: Props, api: typeof wxApi): State { diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx index 859a7f86b..a0c62787a 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx @@ -18,11 +18,12 @@ import { canonicalizeBaseUrl, TalerConfigResponse, } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { queryToSlashKeys } from "../utils/index.js"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; import { ExchangeAddConfirmPage } from "./ExchangeAddConfirm.js"; import { ExchangeSetUrlPage } from "./ExchangeSetUrl.js"; @@ -36,7 +37,9 @@ export function ExchangeAddPage({ currency, onBack }: Props): VNode { { url: string; config: TalerConfigResponse } | undefined >(undefined); - const knownExchangesResponse = useAsyncAsHook(wxApi.listExchanges); + const knownExchangesResponse = useAsyncAsHook(() => + wxApi.wallet.call(WalletApiOperation.ListExchanges, {}), + ); const knownExchanges = !knownExchangesResponse ? [] : knownExchangesResponse.hasError @@ -72,7 +75,7 @@ export function ExchangeAddPage({ currency, onBack }: Props): VNode { url={verifying.url} onCancel={onBack} onConfirm={async () => { - await wxApi.addExchange({ + await wxApi.wallet.call(WalletApiOperation.AddExchange, { exchangeBaseUrl: canonicalizeBaseUrl(verifying.url), forceUpdate: true, }); diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts index 2ea73d310..ddfaa71f9 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts @@ -18,15 +18,14 @@ import { DenomOperationMap, ExchangeFullDetails, ExchangeListItem, - FeeDescriptionPair, + FeeDescriptionPair } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; -import { TermsState } from "../../components/TermsOfService/utils.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { ComparingView, @@ -34,7 +33,7 @@ import { NoExchangesView, PrivacyContentView, ReadyView, - TosContentView, + TosContentView } from "./views.js"; export interface Props { diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts index 2450a90ca..ee839cad7 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts @@ -15,10 +15,10 @@ */ import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util"; -import { createPairTimeline } from "@gnu-taler/taler-wallet-core"; +import { createPairTimeline, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -36,22 +36,20 @@ export function useComponentState( const [value, setValue] = useState(String(initialValue)); const hook = useAsyncAsHook(async () => { - // const { exchanges } = await api.listExchanges(); - const selectedIdx = parseInt(value, 10); const selectedExchange = exchanges.length == 0 ? undefined : exchanges[selectedIdx]; const selected = !selectedExchange ? undefined - : await api.getExchangeDetailedInfo(selectedExchange.exchangeBaseUrl); + : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: selectedExchange.exchangeBaseUrl }); const initialExchange = selectedIdx === initialValue ? undefined : exchanges[initialValue]; const original = !initialExchange ? undefined - : await api.getExchangeDetailedInfo(initialExchange.exchangeBaseUrl); + : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: initialExchange.exchangeBaseUrl }); - return { exchanges, selected, original }; + return { exchanges, selected: selected?.exchange, original: original?.exchange }; }, [value]); const [showingTos, setShowingTos] = useState<string | undefined>(undefined); diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index 53913501d..4b9c5c711 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -20,6 +20,7 @@ import { NotificationType, Transaction, } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { Loading } from "../components/Loading.js"; @@ -38,7 +39,7 @@ import { Button } from "../mui/Button.js"; import { NoBalanceHelp } from "../popup/NoBalanceHelp.js"; import DownloadIcon from "../svg/download_24px.svg"; import UploadIcon from "../svg/upload_24px.svg"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; interface Props { currency?: string; @@ -52,16 +53,14 @@ export function HistoryPage({ }: Props): VNode { const { i18n } = useTranslationContext(); const state = useAsyncAsHook(async () => ({ - b: await wxApi.getBalance(), - tx: await wxApi.getTransactions(), + b: await wxApi.wallet.call(WalletApiOperation.GetBalances, {}), + tx: await wxApi.wallet.call(WalletApiOperation.GetTransactions, {}), })); useEffect(() => { - return wxApi.onUpdateNotification( + return wxApi.listener.onUpdateNotification( [NotificationType.WithdrawGroupFinished], - () => { - state?.retry(); - }, + state?.retry, ); }); diff --git a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx index 3714ae538..e2284a466 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx @@ -22,13 +22,14 @@ import { parsePaytoUri, PaytoUri, } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { Loading } from "../components/Loading.js"; import { LoadingError } from "../components/LoadingError.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; import { CreateManualWithdraw } from "./CreateManualWithdraw.js"; import { ReserveCreated } from "./ReserveCreated.js"; @@ -50,11 +51,14 @@ export function ManualWithdrawPage({ amount, onCancel }: Props): VNode { >(undefined); const [error, setError] = useState<string | undefined>(undefined); - const state = useAsyncAsHook(wxApi.listExchanges); + const state = useAsyncAsHook(() => + wxApi.wallet.call(WalletApiOperation.ListExchanges, {}), + ); useEffect(() => { - return wxApi.onUpdateNotification([NotificationType.ExchangeAdded], () => { - state?.retry(); - }); + return wxApi.listener.onUpdateNotification( + [NotificationType.ExchangeAdded], + state?.retry, + ); }); const { i18n } = useTranslationContext(); @@ -63,9 +67,12 @@ export function ManualWithdrawPage({ amount, onCancel }: Props): VNode { amount: AmountJson, ): Promise<void> { try { - const response = await wxApi.acceptManualWithdrawal( - exchangeBaseUrl, - Amounts.stringify(amount), + const response = await wxApi.wallet.call( + WalletApiOperation.AcceptManualWithdrawal, + { + exchangeBaseUrl: exchangeBaseUrl, + amount: Amounts.stringify(amount), + }, ); const payto = response.exchangePaytoUris[0]; const paytoURI = parsePaytoUri(payto); diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx index e0bdeec5f..d5f072828 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx @@ -34,8 +34,7 @@ import { import { useTranslationContext } from "../context/translation.js"; import { Button } from "../mui/Button.js"; import { queryToSlashConfig } from "../utils/index.js"; -import * as wxApi from "../wxApi.js"; -import { wxClient } from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; interface Props { currency: string; @@ -71,7 +70,7 @@ export function ProviderAddPage({ onBack }: Props): VNode { setVerifying(undefined); }} onConfirm={() => { - return wxClient + return wxApi.wallet .call(WalletApiOperation.AddBackupProvider, { backupProviderBaseUrl: verifying.url, name: verifying.name, diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx index 5378f4b93..d9dd1d746 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx @@ -20,6 +20,7 @@ import { ProviderInfo, ProviderPaymentStatus, ProviderPaymentType, + WalletApiOperation, } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { ErrorMessage } from "../components/ErrorMessage.js"; @@ -30,7 +31,7 @@ import { Time } from "../components/Time.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Button } from "../mui/Button.js"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; interface Props { pid: string; @@ -41,7 +42,10 @@ export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode { const { i18n } = useTranslationContext(); async function getProviderInfo(): Promise<ProviderInfo | null> { //create a first list of backup info by currency - const status = await wxApi.getBackupInfo(); + const status = await wxApi.wallet.call( + WalletApiOperation.GetBackupInfo, + {}, + ); const providers = status.providers.filter( (p) => p.syncProviderBaseUrl === providerURL, @@ -72,8 +76,20 @@ export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode { <ProviderView url={providerURL} info={state.response} - onSync={() => wxApi.syncOneProvider(providerURL)} - onDelete={() => wxApi.removeProvider(providerURL).then(onBack)} + onSync={async () => + wxApi.wallet + .call(WalletApiOperation.RunBackupCycle, { + providers: [providerURL], + }) + .then() + } + onDelete={() => + wxApi.wallet + .call(WalletApiOperation.RemoveBackupProvider, { + provider: providerURL, + }) + .then(onBack) + } onBack={onBack} onExtend={async () => { null; diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index 8f6807d46..a4f51de29 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -43,7 +43,7 @@ import { useClipboardPermissions } from "../hooks/useClipboardPermissions.js"; import { ToggleHandler } from "../mui/handlers.js"; import { Pages } from "../NavigationBar.js"; import { platform } from "../platform/api.js"; -import { wxClient } from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; @@ -55,8 +55,8 @@ export function SettingsPage(): VNode { const webex = platform.getWalletWebExVersion(); const exchangesHook = useAsyncAsHook(async () => { - const list = await wxClient.call(WalletApiOperation.ListExchanges, {}); - const version = await wxClient.call(WalletApiOperation.GetVersion, {}); + const list = await wxApi.wallet.call(WalletApiOperation.ListExchanges, {}); + const version = await wxApi.wallet.call(WalletApiOperation.GetVersion, {}); return { exchanges: list.exchanges, version }; }); const { exchanges, version } = diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index c95b79fbc..d7b6e3b1c 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -34,6 +34,7 @@ import { TransactionType, WithdrawalType, } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { styled } from "@linaria/react"; import { differenceInSeconds } from "date-fns"; import { ComponentChildren, Fragment, h, VNode } from "preact"; @@ -62,31 +63,33 @@ import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Button } from "../mui/Button.js"; import { Pages } from "../NavigationBar.js"; -import * as wxApi from "../wxApi.js"; +import { wxApi } from "../wxApi.js"; interface Props { tid: string; goToWalletHistory: (currency?: string) => Promise<void>; } -async function getTransaction(tid: string): Promise<Transaction> { - const res = await wxApi.getTransactionById(tid); - return res; -} - -export function TransactionPage({ tid, goToWalletHistory }: Props): VNode { +export function TransactionPage({ + tid: transactionId, + goToWalletHistory, +}: Props): VNode { const { i18n } = useTranslationContext(); - const state = useAsyncAsHook(() => getTransaction(tid), [tid]); + const state = useAsyncAsHook( + () => + wxApi.wallet.call(WalletApiOperation.GetTransactionById, { + transactionId, + }), + [transactionId], + ); - useEffect(() => { - return wxApi.onUpdateNotification( + useEffect(() => + wxApi.listener.onUpdateNotification( [NotificationType.WithdrawGroupFinished], - () => { - state?.retry(); - }, - ); - }); + state?.retry, + ), + ); if (!state) { return <Loading />; @@ -113,15 +116,23 @@ export function TransactionPage({ tid, goToWalletHistory }: Props): VNode { onSend={async () => { null; }} - onDelete={() => - wxApi.deleteTransaction(tid).then(() => goToWalletHistory(currency)) - } - onRetry={async () => - await wxApi - .retryTransaction(tid) - .then(() => goToWalletHistory(currency)) - } - onRefund={(id) => wxApi.applyRefundFromPurchaseId(id).then()} + onDelete={async () => { + await wxApi.wallet.call(WalletApiOperation.DeleteTransaction, { + transactionId, + }); + goToWalletHistory(currency); + }} + onRetry={async () => { + await wxApi.wallet.call(WalletApiOperation.RetryTransaction, { + transactionId, + }); + goToWalletHistory(currency); + }} + onRefund={async (purchaseId) => { + await wxApi.wallet.call(WalletApiOperation.ApplyRefundFromPurchaseId, { + purchaseId, + }); + }} onBack={() => goToWalletHistory(currency)} /> ); diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index cb333f1dc..bd1184d46 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -22,77 +22,16 @@ * Imports. */ import { - AcceptExchangeTosRequest, - AcceptManualWithdrawalResult, - AcceptPeerPullPaymentRequest, - AcceptPeerPullPaymentResponse, - AcceptPeerPushPaymentRequest, - AcceptPeerPushPaymentResponse, - AcceptTipRequest, - AcceptTipResponse, - AcceptWithdrawalResponse, - AddExchangeRequest, - AddKnownBankAccountsRequest, - AmountString, - ApplyRefundResponse, - BalancesResponse, - CheckPeerPullPaymentRequest, - CheckPeerPullPaymentResponse, - CheckPeerPushPaymentRequest, - CheckPeerPushPaymentResponse, - CoinDumpJson, - ConfirmPayResult, - CoreApiResponse, - CreateDepositGroupRequest, - CreateDepositGroupResponse, - DeleteTransactionRequest, - DepositGroupFees, - ExchangeFullDetails, - ExchangesListResponse, - ForgetKnownBankAccountsRequest, - GetExchangeTosResult, - GetFeeForDepositRequest, - GetWithdrawalDetailsForAmountRequest, - GetWithdrawalDetailsForUriRequest, - InitiatePeerPullPaymentRequest, - InitiatePeerPullPaymentResponse, - InitiatePeerPushPaymentRequest, - InitiatePeerPushPaymentResponse, - KnownBankAccounts, - Logger, - ManualWithdrawalDetails, - NotificationType, - PaytoUri, - PrepareDepositRequest, - PrepareDepositResponse, - PreparePayResult, - PrepareRefundRequest, - PrepareRefundResult, - PrepareTipRequest, - PrepareTipResult, - RetryTransactionRequest, - SetWalletDeviceIdRequest, - stringifyPaytoUri, - Transaction, - TransactionsResponse, - WalletCoreVersion, - WalletDiagnostics, - WithdrawUriInfoResponse, + CoreApiResponse, Logger, NotificationType, WalletDiagnostics } from "@gnu-taler/taler-util"; import { - AddBackupProviderRequest, - BackupInfo, - PendingOperationsResponse, - RemoveBackupProviderRequest, - TalerError, - WalletApiOperation, - WalletContractData, - WalletCoreApiClient, + TalerError, WalletCoreApiClient, WalletCoreOpKeys, WalletCoreRequestType, - WalletCoreResponseType, + WalletCoreResponseType } from "@gnu-taler/taler-wallet-core"; import { MessageFromBackend, platform } from "./platform/api.js"; +import { nullFunction } from "./test-utils.js"; /** * @@ -167,381 +106,39 @@ export class WxWalletCoreApiClient implements WalletCoreApiClient { } } -export const wxClient = new WxWalletCoreApiClient(); +export class BackgroundApiClient { -/** - * Pay for a proposal. - */ -export function confirmPay( - proposalId: string, - sessionId: string | undefined, -): Promise<ConfirmPayResult> { - return wxClient.call(WalletApiOperation.ConfirmPay, { - proposalId, - sessionId, - }); -} - -/** - * Check upgrade information - */ -export function checkUpgrade(): Promise<UpgradeResponse> { - return callBackend("check-upgrade", {}); -} - -/** - * Reset database - */ -export function resetDb(): Promise<void> { - return callBackend("reset-db", {}); -} - -/** - * Reset database - */ -export function runGarbageCollector(): Promise<void> { - return callBackend("run-gc", {}); -} - -export function getFeeForDeposit( - depositPaytoUri: string, - amount: AmountString, -): Promise<DepositGroupFees> { - return callBackend("getFeeForDeposit", { - depositPaytoUri, - amount, - } as GetFeeForDepositRequest); -} - -export function prepareDeposit( - depositPaytoUri: string, - amount: AmountString, -): Promise<PrepareDepositResponse> { - return callBackend("prepareDeposit", { - depositPaytoUri, - amount, - } as PrepareDepositRequest); -} - -export function createDepositGroup( - depositPaytoUri: string, - amount: AmountString, -): Promise<CreateDepositGroupResponse> { - return callBackend("createDepositGroup", { - depositPaytoUri, - amount, - } as CreateDepositGroupRequest); -} - -/** - * Get balances for all currencies/exchanges. - */ -export function getBalance(): Promise<BalancesResponse> { - return callBackend("getBalances", {}); -} - -export function getContractTermsDetails( - proposalId: string, -): Promise<WalletContractData> { - return callBackend("getContractTermsDetails", { proposalId }); -} - -/** - * Retrieve the full event history for this wallet. - */ -export function getTransactions(): Promise<TransactionsResponse> { - return callBackend("getTransactions", {}); -} - -interface CurrencyInfo { - name: string; - baseUrl: string; - pub: string; -} -interface ListOfKnownCurrencies { - auditors: CurrencyInfo[]; - exchanges: CurrencyInfo[]; -} - -/** - * Get a list of currencies from known auditors and exchanges - */ -export function listKnownCurrencies(): Promise<ListOfKnownCurrencies> { - return callBackend("listCurrencies", {}).then((result) => { - const auditors = result.trustedAuditors.map( - (a: Record<string, string>) => ({ - name: a.currency, - baseUrl: a.auditorBaseUrl, - pub: a.auditorPub, - }), - ); - const exchanges = result.trustedExchanges.map( - (a: Record<string, string>) => ({ - name: a.currency, - baseUrl: a.exchangeBaseUrl, - pub: a.exchangeMasterPub, - }), - ); - return { auditors, exchanges }; - }); -} - -export function listExchanges(): Promise<ExchangesListResponse> { - return callBackend("listExchanges", {}); -} - -export function getExchangeDetailedInfo( - exchangeBaseUrl: string, -): Promise<ExchangeFullDetails> { - return callBackend("getExchangeDetailedInfo", { - exchangeBaseUrl, - }); -} - -export function getVersion(): Promise<WalletCoreVersion> { - return callBackend("getVersion", {}); -} - -export function listKnownBankAccounts( - currency?: string, -): Promise<KnownBankAccounts> { - return callBackend("listKnownBankAccounts", { currency }); -} - -export function addKnownBankAccounts( - payto: PaytoUri, - currency: string, - alias: string, -): Promise<void> { - return callBackend("addKnownBankAccounts", { - payto: stringifyPaytoUri(payto), - currency, - alias, - } as AddKnownBankAccountsRequest); -} -export function forgetKnownBankAccounts(payto: string): Promise<void> { - return callBackend("forgetKnownBankAccounts", { - payto, - } as ForgetKnownBankAccountsRequest); -} - -/** - * Get information about the current state of wallet backups. - */ -export function getBackupInfo(): Promise<BackupInfo> { - return callBackend("getBackupInfo", {}); -} - -/** - * Add a backup provider and activate it - */ -export function addBackupProvider( - backupProviderBaseUrl: string, - name: string, -): Promise<void> { - return callBackend("addBackupProvider", { - backupProviderBaseUrl, - activate: true, - name, - } as AddBackupProviderRequest); -} - -export function setWalletDeviceId(walletDeviceId: string): Promise<void> { - return callBackend("setWalletDeviceId", { - walletDeviceId, - } as SetWalletDeviceIdRequest); -} - -export function syncAllProviders(): Promise<void> { - return callBackend("runBackupCycle", {}); -} - -export function syncOneProvider(url: string): Promise<void> { - return callBackend("runBackupCycle", { providers: [url] }); -} -export function removeProvider(url: string): Promise<void> { - return callBackend("removeBackupProvider", { - provider: url, - } as RemoveBackupProviderRequest); -} -export function extendedProvider(url: string): Promise<void> { - return callBackend("extendBackupProvider", { provider: url }); -} - -/** - * Retry a transaction - * @param transactionId - * @returns - */ -export function retryTransaction(transactionId: string): Promise<void> { - return callBackend("retryTransaction", { - transactionId, - } as RetryTransactionRequest); -} - -/** - * Permanently delete a transaction from the transaction list - */ -export function deleteTransaction(transactionId: string): Promise<void> { - return callBackend("deleteTransaction", { - transactionId, - } as DeleteTransactionRequest); -} - -/** - * Download a refund and accept it. - */ -export function applyRefund( - talerRefundUri: string, -): Promise<ApplyRefundResponse> { - return callBackend("applyRefund", { talerRefundUri }); -} - -/** - * Do refund for purchase. - */ -export function applyRefundFromPurchaseId( - purchaseId: string, -): Promise<ApplyRefundResponse> { - return callBackend("applyRefundFromPurchaseId", { purchaseId }); -} - -/** - * Get details about a pay operation. - */ -export function preparePay(talerPayUri: string): Promise<PreparePayResult> { - return callBackend("preparePayForUri", { talerPayUri }); -} - -/** - * Get details about a withdraw operation. - */ -export function acceptWithdrawal( - talerWithdrawUri: string, - selectedExchange: string, - restrictAge?: number, -): Promise<AcceptWithdrawalResponse> { - return callBackend("acceptBankIntegratedWithdrawal", { - talerWithdrawUri, - exchangeBaseUrl: selectedExchange, - restrictAge, - }); -} - -/** - * Create a reserve into the exchange that expect the amount indicated - * @param exchangeBaseUrl - * @param amount - * @returns - */ -export function acceptManualWithdrawal( - exchangeBaseUrl: string, - amount: string, - restrictAge?: number, -): Promise<AcceptManualWithdrawalResult> { - return callBackend("acceptManualWithdrawal", { - amount, - exchangeBaseUrl, - restrictAge, - }); -} - -export function setExchangeTosAccepted( - exchangeBaseUrl: string, - etag: string | undefined, -): Promise<void> { - return callBackend("setExchangeTosAccepted", { - exchangeBaseUrl, - etag, - } as AcceptExchangeTosRequest); -} - -/** - * Get diagnostics information - */ -export function getDiagnostics(): Promise<WalletDiagnostics> { - return callBackend("wxGetDiagnostics", {}); -} - -/** - * Get diagnostics information - */ -export function toggleHeaderListener( - value: boolean, -): Promise<ExtendedPermissionsResponse> { - return callBackend("toggleHeaderListener", { value }); -} - -/** - * Get diagnostics information - */ -export function containsHeaderListener(): Promise<ExtendedPermissionsResponse> { - return callBackend("containsHeaderListener", {}); -} - -/** - * Get diagnostics information - */ -export function getWithdrawalDetailsForUri( - req: GetWithdrawalDetailsForUriRequest, -): Promise<WithdrawUriInfoResponse> { - return callBackend("getWithdrawalDetailsForUri", req); -} - -export function getWithdrawalDetailsForAmount( - req: GetWithdrawalDetailsForAmountRequest, -): Promise<ManualWithdrawalDetails> { - return callBackend("getWithdrawalDetailsForAmount", req); -} - -export function getExchangeTos( - exchangeBaseUrl: string, - acceptedFormat: string[], -): Promise<GetExchangeTosResult> { - return callBackend("getExchangeTos", { - exchangeBaseUrl, - acceptedFormat, - }); -} - -export function dumpCoins(): Promise<CoinDumpJson> { - return callBackend("dumpCoins", {}); -} - -export function getPendingOperations(): Promise<PendingOperationsResponse> { - return callBackend("getPendingOperations", {}); -} - -export function addExchange(req: AddExchangeRequest): Promise<void> { - return callBackend("addExchange", req); -} + public resetDb(): Promise<void> { + return callBackend("reset-db", {}); + } -export function prepareRefund( - req: PrepareRefundRequest, -): Promise<PrepareRefundResult> { - return callBackend("prepareRefund", req); -} + public containsHeaderListener(): Promise<ExtendedPermissionsResponse> { + return callBackend("containsHeaderListener", {}); + } -export function prepareTip(req: PrepareTipRequest): Promise<PrepareTipResult> { - return callBackend("prepareTip", req); -} + public getDiagnostics(): Promise<WalletDiagnostics> { + return callBackend("wxGetDiagnostics", {}); + } -export function acceptTip(req: AcceptTipRequest): Promise<AcceptTipResponse> { - return callBackend("acceptTip", req); -} + public toggleHeaderListener( + value: boolean, + ): Promise<ExtendedPermissionsResponse> { + return callBackend("toggleHeaderListener", { value }); + } -export function exportDB(): Promise<any> { - return callBackend("exportDb", {}); -} + public runGarbageCollector(): Promise<void> { + return callBackend("run-gc", {}); + } -export function importDB(dump: any): Promise<void> { - return callBackend("importDb", { dump }); } - -export function onUpdateNotification( +function onUpdateNotification( messageTypes: Array<NotificationType>, - doCallback: () => void, + doCallback: undefined | (() => void), ): () => void { + //if no callback, then ignore + if (!doCallback) return () => { + return + }; const onNewMessage = (message: MessageFromBackend): void => { const shouldNotify = messageTypes.includes(message.type); if (shouldNotify) { @@ -551,39 +148,11 @@ export function onUpdateNotification( return platform.listenToWalletBackground(onNewMessage); } -export function initiatePeerPushPayment( - req: InitiatePeerPushPaymentRequest, -): Promise<InitiatePeerPushPaymentResponse> { - return callBackend("initiatePeerPushPayment", req); -} -export function checkPeerPushPayment( - req: CheckPeerPushPaymentRequest, -): Promise<CheckPeerPushPaymentResponse> { - return callBackend("checkPeerPushPayment", req); -} -export function acceptPeerPushPayment( - req: AcceptPeerPushPaymentRequest, -): Promise<AcceptPeerPushPaymentResponse> { - return callBackend("acceptPeerPushPayment", req); -} -export function initiatePeerPullPayment( - req: InitiatePeerPullPaymentRequest, -): Promise<InitiatePeerPullPaymentResponse> { - return callBackend("initiatePeerPullPayment", req); -} -export function checkPeerPullPayment( - req: CheckPeerPullPaymentRequest, -): Promise<CheckPeerPullPaymentResponse> { - return callBackend("checkPeerPullPayment", req); -} -export function acceptPeerPullPayment( - req: AcceptPeerPullPaymentRequest, -): Promise<AcceptPeerPullPaymentResponse> { - return callBackend("acceptPeerPullPayment", req); +export const wxApi = { + wallet: new WxWalletCoreApiClient(), + background: new BackgroundApiClient(), + listener: { + onUpdateNotification + } } -export function getTransactionById(tid: string): Promise<Transaction> { - return callBackend("getTransactionById", { - transactionId: tid, - }); -} |