diff options
Diffstat (limited to 'packages/bank-ui/src/context')
-rw-r--r-- | packages/bank-ui/src/context/config.ts | 263 | ||||
-rw-r--r-- | packages/bank-ui/src/context/navigation.ts | 92 | ||||
-rw-r--r-- | packages/bank-ui/src/context/settings.ts | 44 | ||||
-rw-r--r-- | packages/bank-ui/src/context/wallet-integration.ts | 83 |
4 files changed, 482 insertions, 0 deletions
diff --git a/packages/bank-ui/src/context/config.ts b/packages/bank-ui/src/context/config.ts new file mode 100644 index 000000000..39d12be86 --- /dev/null +++ b/packages/bank-ui/src/context/config.ts @@ -0,0 +1,263 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { + assertUnreachable, + LibtoolVersion, + TalerBankConversionCacheEviction, + TalerBankConversionHttpClient, + TalerCorebankApi, + TalerAuthenticationHttpClient, + TalerCoreBankCacheEviction, + TalerCoreBankHttpClient, + TalerError +} from "@gnu-taler/taler-util"; +import { + BrowserHttpLib, + ErrorLoading, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { + ComponentChildren, + createContext, + FunctionComponent, + h, + VNode, +} from "preact"; +import { useContext, useEffect, useState } from "preact/hooks"; +import { + revalidateAccountDetails, + revalidatePublicAccounts, + revalidateTransactions, +} from "../hooks/account.js"; +import { + revalidateBusinessAccounts, + revalidateCashouts, + revalidateConversionInfo, +} from "../hooks/regional.js"; + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +export type Type = { + url: URL; + config: TalerCorebankApi.Config; + bank: TalerCoreBankHttpClient; + conversion: TalerBankConversionHttpClient; + authenticator: (user: string) => TalerAuthenticationHttpClient; + hints: VersionHint[]; +}; + +// FIXME: below +// @ts-expect-error default value to undefined, should it be another thing? +const Context = createContext<Type>(undefined); + +export const useBankCoreApiContext = (): Type => useContext(Context); + +export enum VersionHint { + /** + * when this flag is on, server is running an old version with cashout before implementing 2fa API + */ + CASHOUT_BEFORE_2FA, +} + +export type ConfigResult = + | undefined + | { type: "ok"; config: TalerCorebankApi.Config; hints: VersionHint[] } + | { type: "incompatible"; result: TalerCorebankApi.Config; supported: string } + | { type: "error"; error: TalerError }; + +export const BankCoreApiProvider = ({ + baseUrl, + children, + frameOnError, +}: { + baseUrl: string; + children: ComponentChildren; + frameOnError: FunctionComponent<{ children: ComponentChildren }>; +}): VNode => { + const [checked, setChecked] = useState<ConfigResult>(); + const { i18n } = useTranslationContext(); + + const { bankClient, conversionClient, authClient } = buildApiClient(new URL(baseUrl)) + + useEffect(() => { + bankClient + .getConfig() + .then((resp) => { + if (bankClient.isCompatible(resp.body.version)) { + setChecked({ type: "ok", config: resp.body, hints: [] }); + } else { + // this API supports version 3.0.3 + const compare = LibtoolVersion.compare("3:0:3", resp.body.version); + if (compare?.compatible ?? false) { + setChecked({ + type: "ok", + config: resp.body, + hints: [VersionHint.CASHOUT_BEFORE_2FA], + }); + } else { + setChecked({ + type: "incompatible", + result: resp.body, + supported: bankClient.PROTOCOL_VERSION, + }); + } + } + }) + .catch((error: unknown) => { + if (error instanceof TalerError) { + setChecked({ type: "error", error }); + } + }); + }, []); + + if (checked === undefined) { + return h(frameOnError, { children: h("div", {}, "loading...") }); + } + if (checked.type === "error") { + return h(frameOnError, { + children: h(ErrorLoading, { error: checked.error, showDetail: true }), + }); + } + if (checked.type === "incompatible") { + return h(frameOnError, { + children: h( + "div", + {}, + i18n.str`The bank backend is not supported. Supported version "${checked.supported}", server version "${checked.result.version}"`, + ), + }); + } + const value: Type = { + url: new URL(bankClient.baseUrl), + config: checked.config, + bank: bankClient, + conversion: conversionClient, + authenticator: authClient, + hints: checked.hints, + }; + return h(Context.Provider, { + value, + children, + }); +}; + +/** + * build http client with cache breaker due to SWR + * @param url + * @returns + */ +function buildApiClient(url: URL) { + const httpLib = new BrowserHttpLib(); + + const bankClient = new TalerCoreBankHttpClient(url.href, httpLib, { + async notifySuccess(op) { + switch (op) { + case TalerCoreBankCacheEviction.DELELE_ACCOUNT: { + await Promise.all([ + revalidatePublicAccounts(), + revalidateBusinessAccounts(), + ]); + return + } + case TalerCoreBankCacheEviction.CREATE_ACCOUNT: { + // admin balance change on new account + await Promise.all([ + revalidateAccountDetails(), + revalidateTransactions(), + revalidatePublicAccounts(), + revalidateBusinessAccounts(), + ]) + return; + } + case TalerCoreBankCacheEviction.UPDATE_ACCOUNT: { + await Promise.all([ + revalidateAccountDetails(), + ]) + return; + } + case TalerCoreBankCacheEviction.CREATE_TRANSACTION: { + await Promise.all([ + revalidateAccountDetails(), + revalidateTransactions(), + ]) + return; + } + case TalerCoreBankCacheEviction.CONFIRM_WITHDRAWAL: { + await Promise.all([ + revalidateAccountDetails(), + revalidateTransactions(), + ]) + return; + } + case TalerCoreBankCacheEviction.CREATE_CASHOUT: { + await Promise.all([ + revalidateAccountDetails(), + revalidateCashouts(), + revalidateTransactions(), + ]) + return; + } + case TalerCoreBankCacheEviction.UPDATE_PASSWORD: + case TalerCoreBankCacheEviction.ABORT_WITHDRAWAL: + case TalerCoreBankCacheEviction.CREATE_WITHDRAWAL: + return; + default: + assertUnreachable(op) + } + } + }); + const conversionClient = new TalerBankConversionHttpClient(bankClient.getConversionInfoAPI(), httpLib, { + async notifySuccess(op) { + switch (op) { + case TalerBankConversionCacheEviction.UPDATE_RATE: { + await revalidateConversionInfo(); + return + } + default: + assertUnreachable(op) + } + } + }); + const authClient = (user: string) => new TalerAuthenticationHttpClient(bankClient.getAuthenticationAPI(user), user, httpLib); + return { bankClient, conversionClient, authClient } +} + +export const BankCoreApiProviderTesting = ({ + children, + state, + url, +}: { + children: ComponentChildren; + state: TalerCorebankApi.Config; + url: string; +}): VNode => { + const value: Type = { + url: new URL(url), + config: state, + // @ts-expect-error this API is not being used, not really needed + bank: undefined, + hints: [], + }; + + return h(Context.Provider, { + value, + children, + }); +}; diff --git a/packages/bank-ui/src/context/navigation.ts b/packages/bank-ui/src/context/navigation.ts new file mode 100644 index 000000000..9552bf899 --- /dev/null +++ b/packages/bank-ui/src/context/navigation.ts @@ -0,0 +1,92 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { ComponentChildren, createContext, h, VNode } from "preact"; +import { useContext, useEffect, useState } from "preact/hooks"; +import { AppLocation } from "../route.js"; + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +export type Type = { + path: string; + params: Record<string, string>; + navigateTo: (path: AppLocation) => void; + // addNavigationListener: (listener: (path: string, params: Record<string, string>) => void) => (() => void); +}; + +// @ts-expect-error should not be used without provider +const Context = createContext<Type>(undefined); + +export const useNavigationContext = (): Type => useContext(Context); + +function getPathAndParamsFromWindow() { + const path = + typeof window !== "undefined" ? window.location.hash.substring(1) : "/"; + const params: Record<string, string> = {}; + if (typeof window !== "undefined") { + for (const [key, value] of new URLSearchParams(window.location.search)) { + params[key] = value; + } + } + return { path, params }; +} + +const { path: initialPath, params: initialParams } = + getPathAndParamsFromWindow(); + +// there is a possibility that if the browser does a redirection +// (which doesn't go through navigatTo function) and that executed +// too early (before addEventListener runs) it won't be taking +// into account +const PopStateEventType = "popstate"; + +export const BrowserHashNavigationProvider = ({ + children, +}: { + children: ComponentChildren; +}): VNode => { + const [{ path, params }, setState] = useState({ + path: initialPath, + params: initialParams, + }); + if (typeof window === "undefined") { + throw Error( + "Can't use BrowserHashNavigationProvider if there is no window object", + ); + } + function navigateTo(path: string) { + const { params } = getPathAndParamsFromWindow(); + setState({ path, params }); + window.location.href = path; + } + + useEffect(() => { + function eventListener() { + setState(getPathAndParamsFromWindow()); + } + window.addEventListener(PopStateEventType, eventListener); + return () => { + window.removeEventListener(PopStateEventType, eventListener); + }; + }, []); + return h(Context.Provider, { + value: { path, params, navigateTo }, + children, + }); +}; diff --git a/packages/bank-ui/src/context/settings.ts b/packages/bank-ui/src/context/settings.ts new file mode 100644 index 000000000..053fcbd12 --- /dev/null +++ b/packages/bank-ui/src/context/settings.ts @@ -0,0 +1,44 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { ComponentChildren, createContext, h, VNode } from "preact"; +import { useContext } from "preact/hooks"; +import { BankUiSettings } from "../settings.js"; + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +export type Type = BankUiSettings; + +const initial: BankUiSettings = {}; +const Context = createContext<Type>(initial); + +export const useSettingsContext = (): Type => useContext(Context); + +export const SettingsProvider = ({ + children, + value, +}: { + value: BankUiSettings; + children: ComponentChildren; +}): VNode => { + return h(Context.Provider, { + value, + children, + }); +}; diff --git a/packages/bank-ui/src/context/wallet-integration.ts b/packages/bank-ui/src/context/wallet-integration.ts new file mode 100644 index 000000000..e14988ed1 --- /dev/null +++ b/packages/bank-ui/src/context/wallet-integration.ts @@ -0,0 +1,83 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { stringifyTalerUri, TalerUri } from "@gnu-taler/taler-util"; +import { ComponentChildren, createContext, h, VNode } from "preact"; +import { useContext } from "preact/hooks"; + +/** + * https://docs.taler.net/design-documents/039-taler-browser-integration.html + * + * @param uri + */ +function createHeadMetaTag(uri: TalerUri, onNotFound?: () => void) { + const meta = document.createElement("meta"); + meta.setAttribute("name", "taler-uri"); + meta.setAttribute("content", stringifyTalerUri(uri)); + + document.head.appendChild(meta); + + let walletFound = false; + window.addEventListener("beforeunload", () => { + walletFound = true; + }); + setTimeout(() => { + if (!walletFound && onNotFound) { + onNotFound(); + } + }, 10); //very short timeout +} +interface Type { + /** + * Tell the active wallet that an action is found + * + * @param uri + * @returns + */ + publishTalerAction: (uri: TalerUri, onNotFound?: () => void) => void; +} + +// @ts-expect-error default value to undefined, should it be another thing? +const Context = createContext<Type>(undefined); + +export const useTalerWalletIntegrationAPI = (): Type => useContext(Context); + +export const TalerWalletIntegrationBrowserProvider = ({ + children, +}: { + children: ComponentChildren; +}): VNode => { + const value: Type = { + publishTalerAction: createHeadMetaTag, + }; + return h(Context.Provider, { + value, + children, + }); +}; + +export const TalerWalletIntegrationTestingProvider = ({ + children, + value, +}: { + children: ComponentChildren; + value: Type; +}): VNode => { + return h(Context.Provider, { + value, + children, + }); +}; |