/* This file is part of GNU Taler (C) 2021-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 */ /** * * @author Sebastian Javier Marchano (sebasjm) */ import { TalerErrorDetail, TalerMerchantApi } from "@gnu-taler/taler-util"; import { EmptyObject, HttpError, HttpResponse, HttpResponseOk, RequestError, RequestOptions, useApiContext } from "@gnu-taler/web-util/browser"; import { useCallback, useEffect, useState } from "preact/hooks"; import { useSWRConfig } from "swr"; import { useSessionContext } from "../context/session.js"; export function useMatchMutate(): ( re?: RegExp, value?: unknown, ) => Promise { const { cache, mutate } = useSWRConfig(); if (!(cache instanceof Map)) { throw new Error( "matchMutate requires the cache provider to be a Map instance", ); } return function matchRegexMutate(re?: RegExp) { return mutate( (key) => { // evict if no key or regex === all if (!key || !re) return true; // match string if (typeof key === "string" && re.test(key)) return true; // record or object have the path at [0] if (typeof key === "object" && re.test(key[0])) return true; //key didn't match regex return false; }, undefined, { revalidate: true, }, ); }; } export function useBackendInstancesTestForAdmin(): HttpResponse< TalerMerchantApi.InstancesResponse, TalerErrorDetail > { const { request } = useBackendBaseRequest(); type Type = TalerMerchantApi.InstancesResponse; const [result, setResult] = useState< HttpResponse >({ loading: true }); useEffect(() => { request(`/management/instances`) .then((data) => setResult(data)) .catch((error: RequestError) => setResult(error.cause), ); }, [request]); return result; } const CHECK_CONFIG_INTERVAL_OK = 5 * 60 * 1000; const CHECK_CONFIG_INTERVAL_FAIL = 2 * 1000; export function useBackendConfig(): HttpResponse< TalerMerchantApi.VersionResponse | undefined, RequestError > { const { request } = useBackendBaseRequest(); type Type = TalerMerchantApi.VersionResponse; type State = { data: HttpResponse>; timer: number; }; const [result, setResult] = useState({ data: { loading: true }, timer: 0, }); useEffect(() => { if (result.timer) { clearTimeout(result.timer); } function tryConfig(): void { request(`/config`) .then((data) => { const timer: any = setTimeout(() => { tryConfig(); }, CHECK_CONFIG_INTERVAL_OK); setResult({ data, timer }); }) .catch((error) => { const timer: any = setTimeout(() => { tryConfig(); }, CHECK_CONFIG_INTERVAL_FAIL); const data = error.cause; setResult({ data, timer }); }); } tryConfig(); }, [request]); return result.data; } interface useBackendInstanceRequestType { request: ( endpoint: string, options?: RequestOptions, ) => Promise>; fetcher: (endpoint: string) => Promise>; multiFetcher: (params: [url: string[]]) => Promise[]>; orderFetcher: ( params: [ endpoint: string, paid?: YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, searchDate?: Date, delta?: number, ], ) => Promise>; transferFetcher: ( params: [ endpoint: string, payto_uri?: string, verified?: string, position?: string, delta?: number, ], ) => Promise>; templateFetcher: ( params: [endpoint: string, position?: string, delta?: number], ) => Promise>; webhookFetcher: ( params: [endpoint: string, position?: string, delta?: number], ) => Promise>; } interface useBackendBaseRequestType { request: ( endpoint: string, options?: RequestOptions, ) => Promise>; } type YesOrNo = "yes" | "no"; type LoginResult = | { valid: true; token: string; expiration: Timestamp; } | { valid: false; cause: HttpError; }; /** * * @param root the request is intended to the base URL and no the instance URL * @returns request handler to */ export function useBackendBaseRequest(): useBackendBaseRequestType { const { request: requestHandler } = useApiContext(); const { state } = useSessionContext(); const token = state.status === "loggedIn" ? state.token : undefined; const baseUrl = state.backendUrl; const request = useCallback( function requestImpl( endpoint: string, options: RequestOptions = {}, ): Promise> { return requestHandler(baseUrl, endpoint, { ...options, token }) .then((res) => { return res; }) .catch((err) => { throw err; }); }, [baseUrl, token], ); return { request }; } export function useBackendInstanceRequest(): useBackendInstanceRequestType { const { request: requestHandler } = useApiContext(); const { state } = useSessionContext(); const token = state.status === "loggedIn" ? state.token : undefined; const baseUrl = state.backendUrl; const request = useCallback( function requestImpl( endpoint: string, options: RequestOptions = {}, ): Promise> { return requestHandler(baseUrl, endpoint, { token, ...options }); }, [baseUrl, token], ); const multiFetcher = useCallback( function multiFetcherImpl( args: [endpoints: string[]], ): Promise[]> { const [endpoints] = args; return Promise.all( endpoints.map((endpoint) => requestHandler(baseUrl, endpoint, { token }), ), ); }, [baseUrl, token], ); const fetcher = useCallback( function fetcherImpl(endpoint: string): Promise> { return requestHandler(baseUrl, endpoint, { token }); }, [baseUrl, token], ); const orderFetcher = useCallback( function orderFetcherImpl( args: [ endpoint: string, paid?: YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, searchDate?: Date, delta?: number, ], ): Promise> { const [endpoint, paid, refunded, wired, searchDate, delta] = args; const date_s = delta && delta < 0 && searchDate ? Math.floor(searchDate.getTime() / 1000) + 1 : searchDate !== undefined ? Math.floor(searchDate.getTime() / 1000) : undefined; const params: any = {}; if (paid !== undefined) params.paid = paid; if (delta !== undefined) params.delta = delta; if (refunded !== undefined) params.refunded = refunded; if (wired !== undefined) params.wired = wired; if (date_s !== undefined) params.date_s = date_s; if (delta === 0) { //in this case we can already assume the response //and avoid network return Promise.resolve({ ok: true, data: { orders: [] } as T, }); } return requestHandler(baseUrl, endpoint, { params, token }); }, [baseUrl, token], ); const transferFetcher = useCallback( function transferFetcherImpl( args: [ endpoint: string, payto_uri?: string, verified?: string, position?: string, delta?: number, ], ): Promise> { const [endpoint, payto_uri, verified, position, delta] = args; const params: any = {}; if (payto_uri !== undefined) params.payto_uri = payto_uri; if (verified !== undefined) params.verified = verified; if (delta === 0) { //in this case we can already assume the response //and avoid network return Promise.resolve({ ok: true, data: { transfers: [] } as T, }); } if (delta !== undefined) { params.limit = delta; } if (position !== undefined) params.offset = position; return requestHandler(baseUrl, endpoint, { params, token }); }, [baseUrl, token], ); const templateFetcher = useCallback( function templateFetcherImpl( args: [endpoint: string, position?: string, delta?: number], ): Promise> { const [endpoint, position, delta] = args; const params: any = {}; if (delta === 0) { //in this case we can already assume the response //and avoid network return Promise.resolve({ ok: true, data: { templates: [] } as T, }); } if (delta !== undefined) { params.limit = delta; } if (position !== undefined) params.offset = position; return requestHandler(baseUrl, endpoint, { params, token }); }, [baseUrl, token], ); const webhookFetcher = useCallback( function webhookFetcherImpl( args: [endpoint: string, position?: string, delta?: number], ): Promise> { const [endpoint, position, delta] = args; const params: any = {}; if (delta === 0) { //in this case we can already assume the response //and avoid network return Promise.resolve({ ok: true, data: { webhooks: [] } as T, }); } if (delta !== undefined) { params.limit = delta; } if (position !== undefined) params.offset = position; return requestHandler(baseUrl, endpoint, { params, token }); }, [baseUrl, token], ); return { request, fetcher, multiFetcher, orderFetcher, transferFetcher, templateFetcher, webhookFetcher, }; }