/* 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 */ import { CacheEvictor, LibtoolVersion, ObservabilityEvent, ObservableHttpClientLibrary, TalerAuthenticationHttpClient, TalerError, TalerMerchantApi, TalerMerchantInstanceCacheEviction, TalerMerchantInstanceHttpClient, TalerMerchantManagementCacheEviction, TalerMerchantManagementHttpClient, } from "@gnu-taler/taler-util"; import { ComponentChildren, FunctionComponent, VNode, createContext, h, } from "preact"; import { useContext, useEffect, useState } from "preact/hooks"; import { APIClient, ActiviyTracker, MerchantLib, Suscriber, } from "./activity.js"; import { useTranslationContext } from "./translation.js"; import { BrowserFetchHttpLib, ErrorLoading } from "../index.browser.js"; /** * * @author Sebastian Javier Marchano (sebasjm) */ export type MerchantContextType = { url: URL; config: TalerMerchantApi.VersionResponse; lib: MerchantLib; hints: VersionHint[]; onActivity: Suscriber; cancelRequest: (eventId: string) => void; }; // FIXME: below // @ts-expect-error default value to undefined, should it be another thing? const MerchantContext = createContext(undefined); export const useMerchantApiContext = (): MerchantContextType => useContext(MerchantContext); enum VersionHint { NONE, } type Evictors = { management?: CacheEvictor; instance?: ( instanceId: string, ) => CacheEvictor; }; type ConfigResult = | undefined | { type: "ok"; config: T; hints: VersionHint[] } | { type: "incompatible"; result: T; supported: string } | { type: "error"; error: TalerError }; export const MerchantApiProvider = ({ baseUrl, children, evictors = {}, frameOnError, }: { baseUrl: URL; evictors?: Evictors; children: ComponentChildren; frameOnError: FunctionComponent<{ children: ComponentChildren }>; }): VNode => { const [checked, setChecked] = useState>(); const { i18n } = useTranslationContext(); const { getRemoteConfig, VERSION, lib, cancelRequest, onActivity } = buildMerchantApiClient(baseUrl, evictors); useEffect(() => { getRemoteConfig() .then((config) => { if (LibtoolVersion.compare(VERSION, config.version)) { setChecked({ type: "ok", config, hints: [] }); } else { setChecked({ type: "incompatible", result: config, supported: VERSION, }); } }) .catch((error: unknown) => { if (error instanceof TalerError) { setChecked({ type: "error", error }); } }); }, []); if (checked === undefined) { return h(frameOnError, { children: h("div", {}, "checking compatibility with server..."), }); } 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 server version is not supported. Supported version "${checked.supported}", server version "${checked.result.version}"`, ), }); } const value: MerchantContextType = { url: baseUrl, config: checked.config, onActivity: onActivity, lib, cancelRequest, hints: checked.hints, }; return h(MerchantContext.Provider, { value, children, }); }; function buildMerchantApiClient( url: URL, evictors: Evictors, ): APIClient { const httpFetch = new BrowserFetchHttpLib({ enableThrottling: true, requireTls: false, }); const tracker = new ActiviyTracker(); const httpLib = new ObservableHttpClientLibrary(httpFetch, { observe(ev) { tracker.notify(ev); }, }); const management = new TalerMerchantManagementHttpClient( url.href, httpLib, evictors.management, ); const instance = (instanceId: string) => new TalerMerchantInstanceHttpClient( management.getSubInstanceAPI(instanceId).href, httpLib, evictors.instance ? evictors.instance(instanceId) : undefined, ); const authenticate = new TalerAuthenticationHttpClient( management.getAuthenticationAPI().href, "default", httpLib, ); const impersonate = (instanceId: string) => new TalerAuthenticationHttpClient( instance(instanceId).getAuthenticationAPI().href, instanceId, httpLib, ); async function getRemoteConfig(): Promise { const resp = await management.getConfig(); return resp.body; } return { getRemoteConfig, VERSION: management.PROTOCOL_VERSION, lib: { management, authenticate, impersonate, instance, }, onActivity: tracker.subscribe, cancelRequest: httpLib.cancelRequest, }; } export const MerchantApiProviderTesting = ({ children, value, }: { value: MerchantContextType; children: ComponentChildren; }): VNode => { return h(MerchantContext.Provider, { value, children, }); };