mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
# Description of Changes Adds the code for the SaaS frontend as proprietary code to the OSS repo. This version of the code is adapted from 22/1/2026, which was the last SaaS version based on the 'V2' design. This will move us closer to being able to have the OSS products understand whether the user has a SaaS account, and provide the correct UI in those cases.
208 lines
7.5 KiB
TypeScript
208 lines
7.5 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
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<string>();
|
|
const globalEndpointCache: Record<string, EndpointAvailabilityDetails> = {};
|
|
/**
|
|
* 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<void>;
|
|
} {
|
|
const [enabled, setEnabled] = useState<boolean | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchEndpointStatus = async () => {
|
|
if (!endpoint) {
|
|
setEnabled(null);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const response = await apiClient.get<boolean>(`/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<string, boolean>;
|
|
endpointDetails: Record<string, EndpointAvailabilityDetails>;
|
|
loading: boolean;
|
|
error: string | null;
|
|
refetch: () => Promise<void>;
|
|
} {
|
|
const [endpointStatus, setEndpointStatus] = useState<Record<string, boolean>>({});
|
|
const [endpointDetails, setEndpointDetails] = useState<Record<string, EndpointAvailabilityDetails>>({});
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(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<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> }
|
|
);
|
|
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<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> }
|
|
);
|
|
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<string, EndpointAvailabilityDetails>>(`/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<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> }
|
|
);
|
|
setEndpointStatus(fullStatus.status);
|
|
setEndpointDetails(prev => ({ ...prev, ...fullStatus.details }));
|
|
globalFetchedSets.add(endpointsKey);
|
|
} catch (err: any) {
|
|
// On 401 (auth error), use optimistic fallback instead of disabling
|
|
if (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<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> }
|
|
);
|
|
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<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> }
|
|
);
|
|
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),
|
|
};
|
|
}
|