import React, { useContext, useEffect, createContext, useMemo, useState, useCallback } from "react"; import { LiveAppRegistry } from "./types"; import { AppPlatform, LiveAppManifest, Loadable } from "../../types"; import api from "./api"; import { FilterParams } from "../../filters"; import useIsMounted from "../../../hooks/useIsMounted"; import { AppManifest, Visibility } from "../../../wallet-api/types"; import useEnv from "../../../hooks/useEnv"; const initialState: Loadable = { isLoading: false, value: null, error: null, }; const initialProvider = "production"; const initialParams: FilterParams = { branches: ["stable", "soon"], }; type LiveAppContextType = { state: Loadable; provider: string; setProvider: React.Dispatch>; updateManifests: () => Promise; }; export const liveAppContext = createContext({ state: initialState, provider: initialProvider, setProvider: () => {}, updateManifests: () => Promise.resolve(), }); type FetchLiveAppCatalogPrams = { apiVersions?: string[]; platform: AppPlatform; allowDebugApps: boolean; allowExperimentalApps: boolean; llVersion: string; lang?: string; }; type LiveAppProviderProps = { children: React.ReactNode; parameters: FetchLiveAppCatalogPrams; updateFrequency: number; }; export function useRemoteLiveAppManifest(appId?: string): LiveAppManifest | undefined { const liveAppRegistry = useContext(liveAppContext).state; if (!liveAppRegistry.value || !appId) { return undefined; } return ( liveAppRegistry.value.liveAppFilteredById[appId] || liveAppRegistry.value.liveAppById[appId] ); } export function useRemoteLiveAppContext(): LiveAppContextType { return useContext(liveAppContext); } export function useManifests( options: Partial & { visibility: Visibility[] }> = {}, ): AppManifest[] { const ctx = useRemoteLiveAppContext(); return useMemo(() => { const liveAppFiltered = ctx.state?.value?.liveAppFiltered ?? []; if (Object.keys(options).length === 0) { return liveAppFiltered; } return liveAppFiltered.filter(manifest => Object.entries(options).some(([key, val]) => { switch (key) { case "visibility": return (val as Visibility[]).includes(manifest[key]); default: return manifest[key] === val; } }), ); }, [options, ctx]); } export function RemoteLiveAppProvider({ children, parameters, updateFrequency, }: LiveAppProviderProps): React.JSX.Element { const isMounted = useIsMounted(); const [state, setState] = useState>(initialState); const [provider, setProvider] = useState(initialProvider); const { allowExperimentalApps, allowDebugApps, apiVersions, platform, llVersion, lang } = parameters; // apiVersion renamed without (s) because param const apiVersion = apiVersions ? apiVersions : ["1.0.0", "2.0.0"]; const envProviderURL = useEnv("PLATFORM_MANIFEST_API_URL"); const providerURL = provider === "production" ? envProviderURL : provider; const updateManifests = useCallback(async () => { setState(currentState => ({ ...currentState, isLoading: true, error: null, })); const branches = [...(initialParams.branches || [])]; allowExperimentalApps && branches.push("experimental"); allowDebugApps && branches.push("debug"); const result = await api .fetchLiveAppManifests(providerURL) .then(allManifests => api .fetchLiveAppManifests(providerURL, { apiVersion, branches, platform, private: false, llVersion, lang: lang ? lang : "en", }) .then(catalogManifests => ({ allManifests, catalogManifests })), ) .then( (manifests): { manifests: typeof manifests; fetchError: null } => ({ manifests, fetchError: null, }), (e): { manifests: null; fetchError: unknown } => ({ manifests: null, fetchError: e }), ); if (!isMounted()) return; if (result.manifests === null) { setState(currentState => ({ ...currentState, isLoading: false, error: result.fetchError, })); } else { const { allManifests, catalogManifests } = result.manifests; setState(() => ({ isLoading: false, value: { liveAppByIndex: allManifests, liveAppFiltered: catalogManifests, liveAppFilteredById: catalogManifests.reduce((acc, liveAppManifest) => { acc[liveAppManifest.id] = liveAppManifest; return acc; }, {}), liveAppById: allManifests.reduce((acc, liveAppManifest) => { acc[liveAppManifest.id] = liveAppManifest; return acc; }, {}), }, error: null, })); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [allowDebugApps, allowExperimentalApps, providerURL, lang, isMounted]); const value: LiveAppContextType = useMemo( () => ({ state, provider, setProvider, updateManifests, }), [state, provider, setProvider, updateManifests], ); useEffect(() => { const interval = setInterval(() => { updateManifests(); }, updateFrequency); updateManifests(); return () => { clearInterval(interval); }; }, [updateFrequency, updateManifests]); return {children}; }