From 46835d5155a561ddf9f3e21bbb81c823c3eab943 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 7 Dec 2022 16:07:42 -0300 Subject: no-fix: user logger instead of console.log --- packages/demobank-ui/src/components/app.tsx | 8 + packages/demobank-ui/src/hooks/async.ts | 2 - .../demobank-ui/src/pages/home/AccountPage.tsx | 37 +-- packages/demobank-ui/src/pages/home/BankFrame.tsx | 5 +- .../src/pages/home/PaytoWireTransferForm.tsx | 20 +- .../src/pages/home/PublicHistoriesPage.tsx | 17 +- .../src/pages/home/RegistrationPage.tsx | 5 +- .../home/TalerWithdrawalConfirmationQuestion.tsx | 301 -------------------- .../src/pages/home/TalerWithdrawalQRCode.tsx | 103 ------- .../demobank-ui/src/pages/home/Transactions.tsx | 8 +- .../src/pages/home/WalletWithdrawForm.tsx | 11 +- .../pages/home/WithdrawalConfirmationQuestion.tsx | 304 +++++++++++++++++++++ .../src/pages/home/WithdrawalQRCode.tsx | 104 +++++++ packages/demobank-ui/src/utils.ts | 21 +- 14 files changed, 478 insertions(+), 468 deletions(-) delete mode 100644 packages/demobank-ui/src/pages/home/TalerWithdrawalConfirmationQuestion.tsx delete mode 100644 packages/demobank-ui/src/pages/home/TalerWithdrawalQRCode.tsx create mode 100644 packages/demobank-ui/src/pages/home/WithdrawalConfirmationQuestion.tsx create mode 100644 packages/demobank-ui/src/pages/home/WithdrawalQRCode.tsx (limited to 'packages/demobank-ui') diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index b6b88f910..07ac9b8f3 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -1,3 +1,7 @@ +import { + globalLogLevel, + setGlobalLogLevelFromString, +} from "@gnu-taler/taler-util"; import { h, FunctionalComponent } from "preact"; import { BackendStateProvider } from "../context/backend.js"; import { PageStateProvider } from "../context/pageState.js"; @@ -32,5 +36,9 @@ const App: FunctionalComponent = () => { ); }; +(window as any).setGlobalLogLevelFromString = setGlobalLogLevelFromString; +(window as any).getGlobaLevel = () => { + return globalLogLevel; +}; export default App; diff --git a/packages/demobank-ui/src/hooks/async.ts b/packages/demobank-ui/src/hooks/async.ts index 0fc197554..090522d30 100644 --- a/packages/demobank-ui/src/hooks/async.ts +++ b/packages/demobank-ui/src/hooks/async.ts @@ -51,9 +51,7 @@ export function useAsync( }, tooLong); try { - console.log("calling async", args); const result = await fn(...args); - console.log("async back", result); setData(result); } catch (error) { setError(error); diff --git a/packages/demobank-ui/src/pages/home/AccountPage.tsx b/packages/demobank-ui/src/pages/home/AccountPage.tsx index 16ff601ec..ddb1e663b 100644 --- a/packages/demobank-ui/src/pages/home/AccountPage.tsx +++ b/packages/demobank-ui/src/pages/home/AccountPage.tsx @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ -import { Amounts, HttpStatusCode } from "@gnu-taler/taler-util"; +import { Amounts, HttpStatusCode, Logger } from "@gnu-taler/taler-util"; import { hooks } from "@gnu-taler/web-util/lib/index.browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { StateUpdater, useEffect } from "preact/hooks"; @@ -28,7 +28,7 @@ import { getIbanFromPayto, prepareHeaders } from "../../utils.js"; import { BankFrame } from "./BankFrame.js"; import { LoginForm } from "./LoginForm.js"; import { PaymentOptions } from "./PaymentOptions.js"; -import { TalerWithdrawalQRCode } from "./TalerWithdrawalQRCode.js"; +import { WithdrawalQRCode } from "./TalerWithdrawalQRCode.js"; import { Transactions } from "./Transactions.js"; export function AccountPage(): VNode { @@ -80,6 +80,8 @@ function SWRWithCredentials({ ); } +const logger = new Logger("AccountPage"); + /** * Show only the account's balance. NOTE: the backend state * is mostly needed to provide the user's credentials to POST @@ -116,7 +118,7 @@ function Account({ accountLabel }: { accountLabel: string }): VNode { // } if (typeof error !== "undefined") { - console.log("account error", error, endpoint); + logger.trace("account error", error, endpoint); /** * FIXME: to minimize the code, try only one invocation * of pageStateSetter, after having decided the error @@ -189,12 +191,11 @@ function Account({ accountLabel }: { accountLabel: string }): VNode { * brought to this ("Account") page where they get informed about * the outcome. */ - console.log(`maybe new withdrawal ${talerWithdrawUri}`); if (talerWithdrawUri && withdrawalId) { - console.log("Bank created a new Taler withdrawal"); + logger.trace("Bank created a new Taler withdrawal"); return ( - @@ -252,15 +253,15 @@ function Account({ accountLabel }: { accountLabel: string }): VNode { ); } -function useTransactionPageNumber(): [number, StateUpdater] { - const ret = hooks.useNotNullLocalStorage("transaction-page", "0"); - const retObj = JSON.parse(ret[0]); - const retSetter: StateUpdater = function (val) { - const newVal = - val instanceof Function - ? JSON.stringify(val(retObj)) - : JSON.stringify(val); - ret[1](newVal); - }; - return [retObj, retSetter]; -} +// function useTransactionPageNumber(): [number, StateUpdater] { +// const ret = hooks.useNotNullLocalStorage("transaction-page", "0"); +// const retObj = JSON.parse(ret[0]); +// const retSetter: StateUpdater = function (val) { +// const newVal = +// val instanceof Function +// ? JSON.stringify(val(retObj)) +// : JSON.stringify(val); +// ret[1](newVal); +// }; +// return [retObj, retSetter]; +// } diff --git a/packages/demobank-ui/src/pages/home/BankFrame.tsx b/packages/demobank-ui/src/pages/home/BankFrame.tsx index f6b8fbd96..2a0c797f2 100644 --- a/packages/demobank-ui/src/pages/home/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/home/BankFrame.tsx @@ -14,6 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ +import { Logger } from "@gnu-taler/taler-util"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import talerLogo from "../../assets/logo-white.svg"; import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js"; @@ -22,6 +23,8 @@ import { PageStateType, usePageContext } from "../../context/pageState.js"; import { useTranslationContext } from "../../context/translation.js"; import { bankUiSettings } from "../../settings.js"; +const logger = new Logger("BankFrame"); + export function BankFrame({ children, }: { @@ -30,7 +33,7 @@ export function BankFrame({ const { i18n } = useTranslationContext(); const backend = useBackendContext(); const { pageState, pageStateSetter } = usePageContext(); - console.log("BankFrame state", pageState); + logger.trace("state", pageState); const logOut = (
*/ -import { Amounts, parsePaytoUri } from "@gnu-taler/taler-util"; +import { Amounts, Logger, parsePaytoUri } from "@gnu-taler/taler-util"; import { hooks } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { StateUpdater, useEffect, useRef, useState } from "preact/hooks"; @@ -25,6 +25,8 @@ import { BackendState } from "../../hooks/backend.js"; import { prepareHeaders, undefinedIfEmpty } from "../../utils.js"; import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; +const logger = new Logger("PaytoWireTransferForm"); + export function PaytoWireTransferForm({ focus, currency, @@ -162,7 +164,7 @@ export function PaytoWireTransferForm({ typeof submitData.amount === "undefined" || submitData.amount === "" ) { - console.log("Not all the fields were given."); + logger.error("Not all the fields were given."); pageStateSetter((prevState: PageStateType) => ({ ...prevState, @@ -209,7 +211,7 @@ export function PaytoWireTransferForm({ { - console.log("switch to raw payto form"); + logger.trace("switch to raw payto form"); pageStateSetter((prevState) => ({ ...prevState, isRawPayto: true, @@ -272,7 +274,7 @@ export function PaytoWireTransferForm({ onClick={async () => { // empty string evaluates to false. if (!rawPaytoInput) { - console.log("Didn't get any raw Payto string!"); + logger.error("Didn't get any raw Payto string!"); return; } transactionData = { paytoUri: rawPaytoInput }; @@ -295,7 +297,7 @@ export function PaytoWireTransferForm({ { - console.log("switch to wire-transfer-form"); + logger.trace("switch to wire-transfer-form"); pageStateSetter((prevState) => ({ ...prevState, isRawPayto: false, @@ -355,7 +357,7 @@ async function createTransactionCall( cleanUpForm: () => void, ): Promise { if (backendState.status === "loggedOut") { - console.log("No credentials found."); + logger.error("No credentials found."); pageStateSetter((prevState) => ({ ...prevState, @@ -379,7 +381,7 @@ async function createTransactionCall( body: JSON.stringify(req), }); } catch (error) { - console.log("Could not POST transaction request to the bank", error); + logger.error("Could not POST transaction request to the bank", error); pageStateSetter((prevState) => ({ ...prevState, @@ -394,7 +396,7 @@ async function createTransactionCall( // POST happened, status not sure yet. if (!res.ok) { const response = await res.json(); - console.log( + logger.error( `Transfer creation gave response error: ${response} (${res.status})`, ); pageStateSetter((prevState) => ({ @@ -409,7 +411,7 @@ async function createTransactionCall( return; } // status is 200 OK here, tell the user. - console.log("Wire transfer created!"); + logger.trace("Wire transfer created!"); pageStateSetter((prevState) => ({ ...prevState, diff --git a/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx b/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx index a8028f3bf..a0fb8493b 100644 --- a/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx +++ b/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx @@ -14,6 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ +import { Logger } from "@gnu-taler/taler-util"; import { hooks } from "@gnu-taler/web-util/lib/index.browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { route } from "preact-router"; @@ -25,6 +26,8 @@ import { getBankBackendBaseUrl } from "../../utils.js"; import { BankFrame } from "./BankFrame.js"; import { Transactions } from "./Transactions.js"; +const logger = new Logger("PublicHistoriesPage"); + export function PublicHistoriesPage(): VNode { return ( @@ -42,7 +45,7 @@ function SWRWithoutCredentials({ children: ComponentChildren; baseUrl: string; }): VNode { - console.log("Base URL", baseUrl); + logger.trace("Base URL", baseUrl); return ( ({ ...prevState, @@ -84,7 +86,7 @@ function PublicHistories(): VNode { })); break; default: - console.log("public accounts: non-404 error", error); + logger.error("public accounts: non-404 error", error); route("/account"); pageStateSetter((prevState: PageStateType) => ({ ...prevState, @@ -105,13 +107,14 @@ function PublicHistories(): VNode { * Show the account specified in the props, or just one * from the list if that's not given. */ - if (typeof showAccount === "undefined" && data.publicAccounts.length > 0) + if (typeof showAccount === "undefined" && data.publicAccounts.length > 0) { setShowAccount(data.publicAccounts[1].accountLabel); - console.log(`Public history tab: ${showAccount}`); + } + logger.trace(`Public history tab: ${showAccount}`); // Ask story of all the public accounts. for (const account of data.publicAccounts) { - console.log("Asking transactions for", account.accountLabel); + logger.trace("Asking transactions for", account.accountLabel); const isSelected = account.accountLabel == showAccount; accountsBar.push(
  • */ +import { Logger } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { route } from "preact-router"; import { StateUpdater, useState } from "preact/hooks"; @@ -25,6 +26,8 @@ import { getBankBackendBaseUrl, undefinedIfEmpty } from "../../utils.js"; import { BankFrame } from "./BankFrame.js"; import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; +const logger = new Logger("RegistrationPage"); + export function RegistrationPage(): VNode { const { i18n } = useTranslationContext(); if (!bankUiSettings.allowRegistrations) { @@ -197,7 +200,7 @@ async function registrationCall( headers, }); } catch (error) { - console.log( + logger.trace( `Could not POST new registration to the bank (${registerEndpoint.href})`, error, ); diff --git a/packages/demobank-ui/src/pages/home/TalerWithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/home/TalerWithdrawalConfirmationQuestion.tsx deleted file mode 100644 index 4fd46878b..000000000 --- a/packages/demobank-ui/src/pages/home/TalerWithdrawalConfirmationQuestion.tsx +++ /dev/null @@ -1,301 +0,0 @@ -import { Fragment, h, VNode } from "preact"; -import { StateUpdater } from "preact/hooks"; -import { useBackendContext } from "../../context/backend.js"; -import { PageStateType, usePageContext } from "../../context/pageState.js"; -import { useTranslationContext } from "../../context/translation.js"; -import { BackendState } from "../../hooks/backend.js"; -import { prepareHeaders } from "../../utils.js"; - -/** - * Additional authentication required to complete the operation. - * Not providing a back button, only abort. - */ -export function TalerWithdrawalConfirmationQuestion(): VNode { - const { pageState, pageStateSetter } = usePageContext(); - const backend = useBackendContext(); - const { i18n } = useTranslationContext(); - const captchaNumbers = { - a: Math.floor(Math.random() * 10), - b: Math.floor(Math.random() * 10), - }; - let captchaAnswer = ""; - - return ( - -

    {i18n.str`Confirm Withdrawal`}

    -
    -
    -
    -
    -

    {i18n.str`Authorize withdrawal by solving challenge`}

    -

    - -   - { - captchaAnswer = e.currentTarget.value; - }} - /> -

    -

    - -   - -

    -
    -
    -
    -

    - - A this point, a real bank would ask for an additional - authentication proof (PIN/TAN, one time password, ..), instead - of a simple calculation. - -

    -
    -
    -
    -
    - ); -} - -/** - * This function confirms a withdrawal operation AFTER - * the wallet has given the exchange's payment details - * to the bank (via the Integration API). Such details - * can be given by scanning a QR code or by passing the - * raw taler://withdraw-URI to the CLI wallet. - * - * This function will set the confirmation status in the - * 'page state' and let the related components refresh. - */ -async function confirmWithdrawalCall( - backendState: BackendState, - withdrawalId: string | undefined, - pageStateSetter: StateUpdater, -): Promise { - if (backendState.status === "loggedOut") { - console.log("No credentials found."); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: "No credentials found.", - }, - })); - return; - } - if (typeof withdrawalId === "undefined") { - console.log("No withdrawal ID found."); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: "No withdrawal ID found.", - }, - })); - return; - } - let res: Response; - try { - const { username, password } = backendState; - const headers = prepareHeaders(username, password); - /** - * NOTE: tests show that when a same object is being - * POSTed, caching might prevent same requests from being - * made. Hence, trying to POST twice the same amount might - * get silently ignored. - * - * headers.append("cache-control", "no-store"); - * headers.append("cache-control", "no-cache"); - * headers.append("pragma", "no-cache"); - * */ - - // Backend URL must have been stored _with_ a final slash. - const url = new URL( - `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/confirm`, - backendState.url, - ); - res = await fetch(url.href, { - method: "POST", - headers, - }); - } catch (error) { - console.log("Could not POST withdrawal confirmation to the bank", error); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: `Could not confirm the withdrawal`, - description: (error as any).error.description, - debug: JSON.stringify(error), - }, - })); - return; - } - if (!res || !res.ok) { - const response = await res.json(); - // assume not ok if res is null - console.log( - `Withdrawal confirmation gave response error (${res.status})`, - res.statusText, - ); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: `Withdrawal confirmation gave response error`, - debug: JSON.stringify(response), - }, - })); - return; - } - console.log("Withdrawal operation confirmed!"); - pageStateSetter((prevState) => { - const { talerWithdrawUri, ...rest } = prevState; - return { - ...rest, - - info: "Withdrawal confirmed!", - }; - }); -} - -/** - * Abort a withdrawal operation via the Access API's /abort. - */ -async function abortWithdrawalCall( - backendState: BackendState, - withdrawalId: string | undefined, - pageStateSetter: StateUpdater, -): Promise { - if (backendState.status === "loggedOut") { - console.log("No credentials found."); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: `No credentials found.`, - }, - })); - return; - } - if (typeof withdrawalId === "undefined") { - console.log("No withdrawal ID found."); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: `No withdrawal ID found.`, - }, - })); - return; - } - let res: Response; - try { - const { username, password } = backendState; - const headers = prepareHeaders(username, password); - /** - * NOTE: tests show that when a same object is being - * POSTed, caching might prevent same requests from being - * made. Hence, trying to POST twice the same amount might - * get silently ignored. Needs more observation! - * - * headers.append("cache-control", "no-store"); - * headers.append("cache-control", "no-cache"); - * headers.append("pragma", "no-cache"); - * */ - - // Backend URL must have been stored _with_ a final slash. - const url = new URL( - `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/abort`, - backendState.url, - ); - res = await fetch(url.href, { method: "POST", headers }); - } catch (error) { - console.log("Could not abort the withdrawal", error); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: `Could not abort the withdrawal.`, - description: (error as any).error.description, - debug: JSON.stringify(error), - }, - })); - return; - } - if (!res.ok) { - const response = await res.json(); - console.log( - `Withdrawal abort gave response error (${res.status})`, - res.statusText, - ); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: `Withdrawal abortion failed.`, - description: response.error.description, - debug: JSON.stringify(response), - }, - })); - return; - } - console.log("Withdrawal operation aborted!"); - pageStateSetter((prevState) => { - const { ...rest } = prevState; - return { - ...rest, - - info: "Withdrawal aborted!", - }; - }); -} diff --git a/packages/demobank-ui/src/pages/home/TalerWithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/home/TalerWithdrawalQRCode.tsx deleted file mode 100644 index 848a9c45c..000000000 --- a/packages/demobank-ui/src/pages/home/TalerWithdrawalQRCode.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { Fragment, h, VNode } from "preact"; -import useSWR from "swr"; -import { useBackendContext } from "../../context/backend.js"; -import { PageStateType, usePageContext } from "../../context/pageState.js"; -import { useTranslationContext } from "../../context/translation.js"; -import { QrCodeSection } from "./QrCodeSection.js"; -import { TalerWithdrawalConfirmationQuestion } from "./TalerWithdrawalConfirmationQuestion.js"; - -/** - * Offer the QR code (and a clickable taler://-link) to - * permit the passing of exchange and reserve details to - * the bank. Poll the backend until such operation is done. - */ -export function TalerWithdrawalQRCode({ - withdrawalId, - talerWithdrawUri, -}: { - withdrawalId: string; - talerWithdrawUri: string; -}): VNode { - // turns true when the wallet POSTed the reserve details: - const { pageState, pageStateSetter } = usePageContext(); - const { i18n } = useTranslationContext(); - const abortButton = ( -
    { - pageStateSetter((prevState: PageStateType) => { - return { - ...prevState, - withdrawalId: undefined, - talerWithdrawUri: undefined, - withdrawalInProgress: false, - }; - }); - }} - >{i18n.str`Abort`} - ); - - console.log(`Showing withdraw URI: ${talerWithdrawUri}`); - // waiting for the wallet: - - const { data, error } = useSWR( - `integration-api/withdrawal-operation/${withdrawalId}`, - { refreshInterval: 1000 }, - ); - - if (typeof error !== "undefined") { - console.log( - `withdrawal (${withdrawalId}) was never (correctly) created at the bank...`, - error, - ); - pageStateSetter((prevState: PageStateType) => ({ - ...prevState, - - error: { - title: i18n.str`withdrawal (${withdrawalId}) was never (correctly) created at the bank...`, - }, - })); - return ( - -
    -
    - {abortButton} -
    - ); - } - - // data didn't arrive yet and wallet didn't communicate: - if (typeof data === "undefined") - return

    {i18n.str`Waiting the bank to create the operation...`}

    ; - - /** - * Wallet didn't communicate withdrawal details yet: - */ - console.log("withdrawal status", data); - if (data.aborted) - pageStateSetter((prevState: PageStateType) => { - const { withdrawalId, talerWithdrawUri, ...rest } = prevState; - return { - ...rest, - withdrawalInProgress: false, - - error: { - title: i18n.str`This withdrawal was aborted!`, - }, - }; - }); - - if (!data.selection_done) { - return ( - - ); - } - /** - * Wallet POSTed the withdrawal details! Ask the - * user to authorize the operation (here CAPTCHA). - */ - return ; -} diff --git a/packages/demobank-ui/src/pages/home/Transactions.tsx b/packages/demobank-ui/src/pages/home/Transactions.tsx index c0bb86024..295bfe0e6 100644 --- a/packages/demobank-ui/src/pages/home/Transactions.tsx +++ b/packages/demobank-ui/src/pages/home/Transactions.tsx @@ -1,8 +1,10 @@ +import { Logger } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useEffect } from "preact/hooks"; import useSWR from "swr"; import { useTranslationContext } from "../../context/translation.js"; +const logger = new Logger("Transactions"); /** * Show one page of transactions. */ @@ -25,7 +27,7 @@ export function Transactions({ } }, [balanceValue ?? ""]); if (typeof error !== "undefined") { - console.log("transactions not found error", error); + logger.error("transactions not found error", error); switch (error.status) { case 404: { return

    Transactions page {pageNumber} was not found.

    ; @@ -39,10 +41,10 @@ export function Transactions({ } } if (!data) { - console.log(`History data of ${accountLabel} not arrived`); + logger.trace(`History data of ${accountLabel} not arrived`); return

    Transactions page loading...

    ; } - console.log(`History data of ${accountLabel}`, data); + logger.trace(`History data of ${accountLabel}`, data); return (
    diff --git a/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx index ee43d2006..29fc1eb6a 100644 --- a/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx @@ -14,6 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ +import { Logger } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { StateUpdater, useEffect, useRef } from "preact/hooks"; import { useBackendContext } from "../../context/backend.js"; @@ -22,6 +23,8 @@ import { useTranslationContext } from "../../context/translation.js"; import { BackendState } from "../../hooks/backend.js"; import { prepareHeaders, validateAmount } from "../../utils.js"; +const logger = new Logger("WalletWithdrawForm"); + export function WalletWithdrawForm({ focus, currency, @@ -110,7 +113,7 @@ async function createWithdrawalCall( pageStateSetter: StateUpdater, ): Promise { if (backendState?.status === "loggedOut") { - console.log("Page has a problem: no credentials found in the state."); + logger.error("Page has a problem: no credentials found in the state."); pageStateSetter((prevState) => ({ ...prevState, @@ -137,7 +140,7 @@ async function createWithdrawalCall( body: JSON.stringify({ amount }), }); } catch (error) { - console.log("Could not POST withdrawal request to the bank", error); + logger.trace("Could not POST withdrawal request to the bank", error); pageStateSetter((prevState) => ({ ...prevState, @@ -151,7 +154,7 @@ async function createWithdrawalCall( } if (!res.ok) { const response = await res.json(); - console.log( + logger.error( `Withdrawal creation gave response error: ${response} (${res.status})`, ); pageStateSetter((prevState) => ({ @@ -166,7 +169,7 @@ async function createWithdrawalCall( return; } - console.log("Withdrawal operation created!"); + logger.trace("Withdrawal operation created!"); const resp = await res.json(); pageStateSetter((prevState: PageStateType) => ({ ...prevState, diff --git a/packages/demobank-ui/src/pages/home/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/home/WithdrawalConfirmationQuestion.tsx new file mode 100644 index 000000000..3dbe8708e --- /dev/null +++ b/packages/demobank-ui/src/pages/home/WithdrawalConfirmationQuestion.tsx @@ -0,0 +1,304 @@ +import { Logger } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { StateUpdater } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; +import { PageStateType, usePageContext } from "../../context/pageState.js"; +import { useTranslationContext } from "../../context/translation.js"; +import { BackendState } from "../../hooks/backend.js"; +import { prepareHeaders } from "../../utils.js"; + +const logger = new Logger("WithdrawalConfirmationQuestion"); + +/** + * Additional authentication required to complete the operation. + * Not providing a back button, only abort. + */ +export function WithdrawalConfirmationQuestion(): VNode { + const { pageState, pageStateSetter } = usePageContext(); + const backend = useBackendContext(); + const { i18n } = useTranslationContext(); + const captchaNumbers = { + a: Math.floor(Math.random() * 10), + b: Math.floor(Math.random() * 10), + }; + let captchaAnswer = ""; + + return ( + +

    {i18n.str`Confirm Withdrawal`}

    +
    +
    +
    +
    +

    {i18n.str`Authorize withdrawal by solving challenge`}

    +

    + +   + { + captchaAnswer = e.currentTarget.value; + }} + /> +

    +

    + +   + +

    +
    + +
    +

    + + A this point, a real bank would ask for an additional + authentication proof (PIN/TAN, one time password, ..), instead + of a simple calculation. + +

    +
    +
    +
    +
    + ); +} + +/** + * This function confirms a withdrawal operation AFTER + * the wallet has given the exchange's payment details + * to the bank (via the Integration API). Such details + * can be given by scanning a QR code or by passing the + * raw taler://withdraw-URI to the CLI wallet. + * + * This function will set the confirmation status in the + * 'page state' and let the related components refresh. + */ +async function confirmWithdrawalCall( + backendState: BackendState, + withdrawalId: string | undefined, + pageStateSetter: StateUpdater, +): Promise { + if (backendState.status === "loggedOut") { + logger.error("No credentials found."); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: "No credentials found.", + }, + })); + return; + } + if (typeof withdrawalId === "undefined") { + logger.error("No withdrawal ID found."); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: "No withdrawal ID found.", + }, + })); + return; + } + let res: Response; + try { + const { username, password } = backendState; + const headers = prepareHeaders(username, password); + /** + * NOTE: tests show that when a same object is being + * POSTed, caching might prevent same requests from being + * made. Hence, trying to POST twice the same amount might + * get silently ignored. + * + * headers.append("cache-control", "no-store"); + * headers.append("cache-control", "no-cache"); + * headers.append("pragma", "no-cache"); + * */ + + // Backend URL must have been stored _with_ a final slash. + const url = new URL( + `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/confirm`, + backendState.url, + ); + res = await fetch(url.href, { + method: "POST", + headers, + }); + } catch (error) { + logger.error("Could not POST withdrawal confirmation to the bank", error); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: `Could not confirm the withdrawal`, + description: (error as any).error.description, + debug: JSON.stringify(error), + }, + })); + return; + } + if (!res || !res.ok) { + const response = await res.json(); + // assume not ok if res is null + logger.error( + `Withdrawal confirmation gave response error (${res.status})`, + res.statusText, + ); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: `Withdrawal confirmation gave response error`, + debug: JSON.stringify(response), + }, + })); + return; + } + logger.trace("Withdrawal operation confirmed!"); + pageStateSetter((prevState) => { + const { talerWithdrawUri, ...rest } = prevState; + return { + ...rest, + + info: "Withdrawal confirmed!", + }; + }); +} + +/** + * Abort a withdrawal operation via the Access API's /abort. + */ +async function abortWithdrawalCall( + backendState: BackendState, + withdrawalId: string | undefined, + pageStateSetter: StateUpdater, +): Promise { + if (backendState.status === "loggedOut") { + logger.error("No credentials found."); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: `No credentials found.`, + }, + })); + return; + } + if (typeof withdrawalId === "undefined") { + logger.error("No withdrawal ID found."); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: `No withdrawal ID found.`, + }, + })); + return; + } + let res: Response; + try { + const { username, password } = backendState; + const headers = prepareHeaders(username, password); + /** + * NOTE: tests show that when a same object is being + * POSTed, caching might prevent same requests from being + * made. Hence, trying to POST twice the same amount might + * get silently ignored. Needs more observation! + * + * headers.append("cache-control", "no-store"); + * headers.append("cache-control", "no-cache"); + * headers.append("pragma", "no-cache"); + * */ + + // Backend URL must have been stored _with_ a final slash. + const url = new URL( + `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/abort`, + backendState.url, + ); + res = await fetch(url.href, { method: "POST", headers }); + } catch (error) { + logger.error("Could not abort the withdrawal", error); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: `Could not abort the withdrawal.`, + description: (error as any).error.description, + debug: JSON.stringify(error), + }, + })); + return; + } + if (!res.ok) { + const response = await res.json(); + logger.error( + `Withdrawal abort gave response error (${res.status})`, + res.statusText, + ); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: `Withdrawal abortion failed.`, + description: response.error.description, + debug: JSON.stringify(response), + }, + })); + return; + } + logger.trace("Withdrawal operation aborted!"); + pageStateSetter((prevState) => { + const { ...rest } = prevState; + return { + ...rest, + + info: "Withdrawal aborted!", + }; + }); +} diff --git a/packages/demobank-ui/src/pages/home/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/home/WithdrawalQRCode.tsx new file mode 100644 index 000000000..d5b8794d3 --- /dev/null +++ b/packages/demobank-ui/src/pages/home/WithdrawalQRCode.tsx @@ -0,0 +1,104 @@ +import { Logger } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import useSWR from "swr"; +import { PageStateType, usePageContext } from "../../context/pageState.js"; +import { useTranslationContext } from "../../context/translation.js"; +import { QrCodeSection } from "./QrCodeSection.js"; +import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js"; + +const logger = new Logger("WithdrawalQRCode"); +/** + * Offer the QR code (and a clickable taler://-link) to + * permit the passing of exchange and reserve details to + * the bank. Poll the backend until such operation is done. + */ +export function WithdrawalQRCode({ + withdrawalId, + talerWithdrawUri, +}: { + withdrawalId: string; + talerWithdrawUri: string; +}): VNode { + // turns true when the wallet POSTed the reserve details: + const { pageState, pageStateSetter } = usePageContext(); + const { i18n } = useTranslationContext(); + const abortButton = ( + { + pageStateSetter((prevState: PageStateType) => { + return { + ...prevState, + withdrawalId: undefined, + talerWithdrawUri: undefined, + withdrawalInProgress: false, + }; + }); + }} + >{i18n.str`Abort`} + ); + + logger.trace(`Showing withdraw URI: ${talerWithdrawUri}`); + // waiting for the wallet: + + const { data, error } = useSWR( + `integration-api/withdrawal-operation/${withdrawalId}`, + { refreshInterval: 1000 }, + ); + + if (typeof error !== "undefined") { + logger.error( + `withdrawal (${withdrawalId}) was never (correctly) created at the bank...`, + error, + ); + pageStateSetter((prevState: PageStateType) => ({ + ...prevState, + + error: { + title: i18n.str`withdrawal (${withdrawalId}) was never (correctly) created at the bank...`, + }, + })); + return ( + +
    +
    + {abortButton} +
    + ); + } + + // data didn't arrive yet and wallet didn't communicate: + if (typeof data === "undefined") + return

    {i18n.str`Waiting the bank to create the operation...`}

    ; + + /** + * Wallet didn't communicate withdrawal details yet: + */ + logger.trace("withdrawal status", data); + if (data.aborted) + pageStateSetter((prevState: PageStateType) => { + const { withdrawalId, talerWithdrawUri, ...rest } = prevState; + return { + ...rest, + withdrawalInProgress: false, + + error: { + title: i18n.str`This withdrawal was aborted!`, + }, + }; + }); + + if (!data.selection_done) { + return ( + + ); + } + /** + * Wallet POSTed the withdrawal details! Ask the + * user to authorize the operation (here CAPTCHA). + */ + return ; +} diff --git a/packages/demobank-ui/src/utils.ts b/packages/demobank-ui/src/utils.ts index d74f4d129..223dbe707 100644 --- a/packages/demobank-ui/src/utils.ts +++ b/packages/demobank-ui/src/utils.ts @@ -5,21 +5,11 @@ import { canonicalizeBaseUrl } from "@gnu-taler/taler-util"; * replace comma with a dot. Returns 'false' whenever * the input is invalid, the valid amount otherwise. */ +const amountRegex = /^[0-9]+(.[0-9]+)?$/; export function validateAmount(maybeAmount: string | undefined): string | undefined { - const amountRegex = "^[0-9]+(.[0-9]+)?$"; - if (!maybeAmount) { - console.log(`Entered amount (${maybeAmount}) mismatched pattern.`); + if (!maybeAmount || !amountRegex.test(maybeAmount)) { return; } - if (typeof maybeAmount !== "undefined" || maybeAmount !== "") { - console.log(`Maybe valid amount: ${maybeAmount}`); - // tolerating comma instead of point. - const re = RegExp(amountRegex); - if (!re.test(maybeAmount)) { - console.log(`Not using invalid amount '${maybeAmount}'.`); - return; - } - } return maybeAmount; } @@ -39,13 +29,6 @@ const maybeRootPath = "https://bank.demo.taler.net/demobanks/default/"; export function getBankBackendBaseUrl(): string { const overrideUrl = localStorage.getItem("bank-base-url"); - if (overrideUrl) { - console.log( - `using bank base URL ${overrideUrl} (override via bank-base-url localStorage)`, - ); - } else { - console.log(`using bank base URL (${maybeRootPath})`); - } return canonicalizeBaseUrl(overrideUrl ? overrideUrl : maybeRootPath) } -- cgit v1.2.3