diff options
author | Sebastian <sebasjm@gmail.com> | 2024-04-26 13:54:34 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-04-26 13:54:34 -0300 |
commit | dbe5a5e5ee646b0d6824bc461c95f3c8c1572ced (patch) | |
tree | 1dd342858255a9cc62af08ceaba65e6a6f428df6 /packages/web-util | |
parent | f5cea56a6f38442f04eee9c78e4592698151a0b2 (diff) |
exchange api for web
Diffstat (limited to 'packages/web-util')
-rw-r--r-- | packages/web-util/src/context/activity.ts | 6 | ||||
-rw-r--r-- | packages/web-util/src/context/exchange-api.ts | 204 | ||||
-rw-r--r-- | packages/web-util/src/context/index.ts | 1 | ||||
-rw-r--r-- | packages/web-util/src/forms/DefaultForm.tsx | 2 |
4 files changed, 211 insertions, 2 deletions
diff --git a/packages/web-util/src/context/activity.ts b/packages/web-util/src/context/activity.ts index fd366cbe5..d12d1efb6 100644 --- a/packages/web-util/src/context/activity.ts +++ b/packages/web-util/src/context/activity.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { ChallengerHttpClient, ObservabilityEvent, TalerAuthenticationHttpClient, TalerBankConversionHttpClient, TalerCoreBankHttpClient, TalerMerchantInstanceHttpClient, TalerMerchantManagementHttpClient } from "@gnu-taler/taler-util"; +import { ChallengerHttpClient, ObservabilityEvent, TalerAuthenticationHttpClient, TalerBankConversionHttpClient, TalerCoreBankHttpClient, TalerExchangeHttpClient, TalerMerchantInstanceHttpClient, TalerMerchantManagementHttpClient } from "@gnu-taler/taler-util"; type Listener<Event> = (e: Event) => void; type Unsuscriber = () => void; @@ -60,6 +60,10 @@ export interface MerchantLib { subInstanceApi: (instanceId: string) => MerchantLib; } +export interface ExchangeLib { + exchange: TalerExchangeHttpClient; +} + export interface BankLib { bank: TalerCoreBankHttpClient; conversion: TalerBankConversionHttpClient; diff --git a/packages/web-util/src/context/exchange-api.ts b/packages/web-util/src/context/exchange-api.ts new file mode 100644 index 000000000..6f0b6b9f4 --- /dev/null +++ b/packages/web-util/src/context/exchange-api.ts @@ -0,0 +1,204 @@ +/* + 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 { + CacheEvictor, + LibtoolVersion, + ObservabilityEvent, + ObservableHttpClientLibrary, + TalerError, + TalerExchangeApi, + TalerExchangeCacheEviction, + TalerExchangeHttpClient +} from "@gnu-taler/taler-util"; +import { + ComponentChildren, + FunctionComponent, + VNode, + createContext, + h, +} from "preact"; +import { useContext, useEffect, useState } from "preact/hooks"; +import { BrowserFetchHttpLib } from "../index.browser.js"; +import { + APIClient, + ActiviyTracker, + ExchangeLib, + Subscriber, +} from "./activity.js"; + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +export type ExchangeContextType = { + url: URL; + config: TalerExchangeApi.ExchangeVersionResponse; + lib: ExchangeLib; + hints: VersionHint[]; + onActivity: Subscriber<ObservabilityEvent>; + cancelRequest: (eventId: string) => void; +}; + +// FIXME: below +// @ts-expect-error default value to undefined, should it be another thing? +const ExchangeContext = createContext<ExchangeContextType>(undefined); + +export const useExchangeApiContext = (): ExchangeContextType => + useContext(ExchangeContext); + +enum VersionHint { + NONE, +} + +type Evictors = { + exchange?: CacheEvictor<TalerExchangeCacheEviction>; +}; + +type ConfigResult<T> = + | undefined + | { type: "ok"; config: T; hints: VersionHint[] } + | ConfigResultFail<T>; + +type ConfigResultFail<T> = + | { type: "incompatible"; result: T; supported: string } + | { type: "error"; error: TalerError }; + +const CONFIG_FAIL_TRY_AGAIN_MS = 5000; + +export const ExchangeApiProvider = ({ + baseUrl, + children, + evictors = {}, + frameOnError, +}: { + baseUrl: URL; + evictors?: Evictors; + children: ComponentChildren; + frameOnError: FunctionComponent<{ + state: + | ConfigResultFail<TalerExchangeApi.ExchangeVersionResponse> + | undefined; + }>; +}): VNode => { + const [checked, setChecked] = + useState<ConfigResult<TalerExchangeApi.ExchangeVersionResponse>>(); + + const { getRemoteConfig, VERSION, lib, cancelRequest, onActivity } = + buildExchangeApiClient(baseUrl, evictors); + + useEffect(() => { + let keepRetrying = true; + async function testConfig(): Promise<void> { + try { + const config = await getRemoteConfig(); + if (LibtoolVersion.compare(VERSION, config.version)) { + setChecked({ type: "ok", config, hints: [] }); + } else { + setChecked({ + type: "incompatible", + result: config, + supported: VERSION, + }); + } + } catch (error) { + if (error instanceof TalerError) { + if (keepRetrying) { + setTimeout(() => { + testConfig(); + }, CONFIG_FAIL_TRY_AGAIN_MS); + } + setChecked({ type: "error", error }); + } else { + setChecked({ type: "error", error: TalerError.fromException(error) }); + } + } + } + testConfig(); + return () => { + // on unload, stop retry + keepRetrying = false; + }; + }, []); + + if (!checked || checked.type !== "ok") { + return h(frameOnError, { state: checked }, []); + } + + const value: ExchangeContextType = { + url: baseUrl, + config: checked.config, + onActivity: onActivity, + lib, + cancelRequest, + hints: checked.hints, + }; + return h(ExchangeContext.Provider, { + value, + children, + }); +}; + +function buildExchangeApiClient( + url: URL, + evictors: Evictors, +): APIClient<ExchangeLib, TalerExchangeApi.ExchangeVersionResponse> { + const httpFetch = new BrowserFetchHttpLib({ + enableThrottling: true, + requireTls: false, + }); + const tracker = new ActiviyTracker<ObservabilityEvent>(); + + const httpLib = new ObservableHttpClientLibrary(httpFetch, { + observe(ev) { + tracker.notify(ev); + }, + }); + + const ex = new TalerExchangeHttpClient(url.href, httpLib, evictors.exchange); + + async function getRemoteConfig(): Promise<TalerExchangeApi.ExchangeVersionResponse> { + const resp = await ex.getConfig(); + if (resp.type === "fail") { + throw TalerError.fromUncheckedDetail(resp.detail); + } + return resp.body; + } + + return { + getRemoteConfig, + VERSION: ex.PROTOCOL_VERSION, + lib: { + exchange: ex, + }, + onActivity: tracker.subscribe, + cancelRequest: httpLib.cancelRequest, + }; +} + +export const ExchangeApiProviderTesting = ({ + children, + value, +}: { + value: ExchangeContextType; + children: ComponentChildren; +}): VNode => { + return h(ExchangeContext.Provider, { + value, + children, + }); +}; diff --git a/packages/web-util/src/context/index.ts b/packages/web-util/src/context/index.ts index 8e7f096da..7e30ecd09 100644 --- a/packages/web-util/src/context/index.ts +++ b/packages/web-util/src/context/index.ts @@ -7,5 +7,6 @@ export { export * from "./bank-api.js"; export * from "./challenger-api.js"; export * from "./merchant-api.js"; +export * from "./exchange-api.js"; export * from "./navigation.js"; export * from "./wallet-integration.js"; diff --git a/packages/web-util/src/forms/DefaultForm.tsx b/packages/web-util/src/forms/DefaultForm.tsx index 1155401f5..118699487 100644 --- a/packages/web-util/src/forms/DefaultForm.tsx +++ b/packages/web-util/src/forms/DefaultForm.tsx @@ -52,7 +52,7 @@ export function DefaultForm<T extends object>({ {form.design.map((section, i) => { if (!section) return <Fragment />; return ( - <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3"> + <div key={i} class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3"> <div class="px-4 sm:px-0"> <h2 class="text-base font-semibold leading-7 text-gray-900"> {section.title} |