import { useState, useEffect } from "react"; import { isAxiosError } from "axios"; import apiClient from "@app/services/apiClient"; import type { EndpointAvailabilityDetails } from "@app/types/endpointAvailability"; // Track globally fetched endpoint sets to prevent duplicate fetches across components const globalFetchedSets = new Set(); const globalEndpointCache: Record = {}; /** * Hook to check if a specific endpoint is enabled * This wraps the context for single endpoint checks */ export function useEndpointEnabled(endpoint: string): { enabled: boolean | null; loading: boolean; error: string | null; refetch: () => Promise; } { const [enabled, setEnabled] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchEndpointStatus = async () => { if (!endpoint) { setEnabled(null); setLoading(false); return; } try { setLoading(true); setError(null); const response = await apiClient.get( `/api/v1/config/endpoint-enabled?endpoint=${encodeURIComponent(endpoint)}`, ); const isEnabled = response.data; setEnabled(isEnabled); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Unknown error occurred"; setError(errorMessage); } finally { setLoading(false); } }; useEffect(() => { fetchEndpointStatus(); }, [endpoint]); return { enabled, loading, error, refetch: fetchEndpointStatus, }; } /** * Hook to check multiple endpoints at once using batch API * Returns a map of endpoint -> enabled status */ export function useMultipleEndpointsEnabled(endpoints: string[]): { endpointStatus: Record; endpointDetails: Record; loading: boolean; error: string | null; refetch: () => Promise; } { const [endpointStatus, setEndpointStatus] = useState>( {}, ); const [endpointDetails, setEndpointDetails] = useState< Record >({}); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchAllEndpointStatuses = async (force = false) => { const endpointsKey = [...endpoints].sort().join(","); // Skip if we already fetched these exact endpoints globally if (!force && globalFetchedSets.has(endpointsKey)) { console.debug( "[useEndpointConfig] Already fetched these endpoints globally, using cache", ); const cached = endpoints.reduce( (acc, endpoint) => { const cachedDetails = globalEndpointCache[endpoint]; if (cachedDetails) { acc.status[endpoint] = cachedDetails.enabled; acc.details[endpoint] = cachedDetails; } return acc; }, { status: {} as Record, details: {} as Record, }, ); setEndpointStatus(cached.status); setEndpointDetails((prev) => ({ ...prev, ...cached.details })); setLoading(false); return; } if (!endpoints || endpoints.length === 0) { setEndpointStatus({}); setEndpointDetails({}); setLoading(false); return; } try { setLoading(true); setError(null); // Check which endpoints we haven't fetched yet const newEndpoints = endpoints.filter( (ep) => !(ep in globalEndpointCache), ); if (newEndpoints.length === 0) { console.debug( "[useEndpointConfig] All endpoints already in global cache", ); const cached = endpoints.reduce( (acc, endpoint) => { const cachedDetails = globalEndpointCache[endpoint]; if (cachedDetails) { acc.status[endpoint] = cachedDetails.enabled; acc.details[endpoint] = cachedDetails; } return acc; }, { status: {} as Record, details: {} as Record, }, ); setEndpointStatus(cached.status); setEndpointDetails((prev) => ({ ...prev, ...cached.details })); globalFetchedSets.add(endpointsKey); setLoading(false); return; } // Use batch API for efficiency - only fetch new endpoints const endpointsParam = newEndpoints.join(","); const response = await apiClient.get< Record >( `/api/v1/config/endpoints-availability?endpoints=${encodeURIComponent(endpointsParam)}`, ); const statusMap = response.data; // Update global cache with new results Object.entries(statusMap).forEach(([endpoint, details]) => { globalEndpointCache[endpoint] = { enabled: details?.enabled ?? true, reason: details?.reason ?? null, }; }); // Get all requested endpoints from cache (including previously cached ones) const fullStatus = endpoints.reduce( (acc, endpoint) => { const cachedDetails = globalEndpointCache[endpoint]; if (cachedDetails) { acc.status[endpoint] = cachedDetails.enabled; acc.details[endpoint] = cachedDetails; } else { acc.status[endpoint] = true; } return acc; }, { status: {} as Record, details: {} as Record, }, ); setEndpointStatus(fullStatus.status); setEndpointDetails((prev) => ({ ...prev, ...fullStatus.details })); globalFetchedSets.add(endpointsKey); } catch (err: unknown) { // On 401 (auth error), use optimistic fallback instead of disabling if (isAxiosError(err) && err.response?.status === 401) { console.warn( "[useEndpointConfig] 401 error - using optimistic fallback", ); const optimisticStatus = endpoints.reduce( (acc, endpoint) => { const optimisticDetails: EndpointAvailabilityDetails = { enabled: true, reason: null, }; acc.status[endpoint] = true; acc.details[endpoint] = optimisticDetails; globalEndpointCache[endpoint] = optimisticDetails; return acc; }, { status: {} as Record, details: {} as Record, }, ); setEndpointStatus(optimisticStatus.status); setEndpointDetails((prev) => ({ ...prev, ...optimisticStatus.details, })); setLoading(false); return; } const errorMessage = err instanceof Error ? err.message : "Unknown error occurred"; setError(errorMessage); console.error( "[EndpointConfig] Failed to check multiple endpoints:", err, ); // Fallback: assume all endpoints are enabled on error (optimistic) const optimisticStatus = endpoints.reduce( (acc, endpoint) => { const optimisticDetails: EndpointAvailabilityDetails = { enabled: true, reason: null, }; acc.status[endpoint] = true; acc.details[endpoint] = optimisticDetails; return acc; }, { status: {} as Record, details: {} as Record, }, ); setEndpointStatus(optimisticStatus.status); setEndpointDetails((prev) => ({ ...prev, ...optimisticStatus.details })); } finally { setLoading(false); } }; useEffect(() => { fetchAllEndpointStatuses(); }, [endpoints.join(",")]); // Re-run when endpoints array changes return { endpointStatus, endpointDetails, loading, error, refetch: () => fetchAllEndpointStatuses(true), }; }