aboutsummaryrefslogtreecommitdiff
path: root/packages/bank-ui/src/context
diff options
context:
space:
mode:
Diffstat (limited to 'packages/bank-ui/src/context')
-rw-r--r--packages/bank-ui/src/context/config.ts263
-rw-r--r--packages/bank-ui/src/context/navigation.ts92
-rw-r--r--packages/bank-ui/src/context/settings.ts44
-rw-r--r--packages/bank-ui/src/context/wallet-integration.ts83
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,
+ });
+};