mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
Fix backend issues in desktop app (#4995)
# Description of Changes Fixes two distinct but related issues in the backend of the desktop app: - Correctly shows tools as unavaialable when the backend doesn't have the dependencies or has disabled them etc. (same as web version - this primarily didn't work on desktop because the app spawns before the backend is running) - Fixes infinite re-rendering issues caused by the app polling whether the backend is healthy or not
This commit is contained in:
@@ -6,6 +6,7 @@ import { tauriBackendService } from '@app/services/tauriBackendService';
|
||||
import { isBackendNotReadyError } from '@app/constants/backendErrors';
|
||||
import type { EndpointAvailabilityDetails } from '@app/types/endpointAvailability';
|
||||
import { connectionModeService } from '@desktop/services/connectionModeService';
|
||||
import type { AppConfig } from '@app/contexts/AppConfigContext';
|
||||
|
||||
|
||||
interface EndpointConfig {
|
||||
@@ -28,6 +29,17 @@ function getErrorMessage(err: unknown): string {
|
||||
return 'Unknown error occurred';
|
||||
}
|
||||
|
||||
async function checkDependenciesReady(): Promise<boolean> {
|
||||
try {
|
||||
const response = await apiClient.get<AppConfig>('/api/v1/config/app-config', {
|
||||
suppressErrorToast: true,
|
||||
});
|
||||
return response.data?.dependenciesReady ?? false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Desktop-specific endpoint checker that hits the backend directly via axios.
|
||||
*/
|
||||
@@ -38,7 +50,7 @@ export function useEndpointEnabled(endpoint: string): {
|
||||
refetch: () => Promise<void>;
|
||||
} {
|
||||
const { t } = useTranslation();
|
||||
const [enabled, setEnabled] = useState<boolean | null>(() => (endpoint ? true : null));
|
||||
const [enabled, setEnabled] = useState<boolean | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const isMountedRef = useRef(true);
|
||||
@@ -68,6 +80,11 @@ export function useEndpointEnabled(endpoint: string): {
|
||||
return;
|
||||
}
|
||||
|
||||
const dependenciesReady = await checkDependenciesReady();
|
||||
if (!dependenciesReady) {
|
||||
return; // Health monitor will trigger retry when truly ready
|
||||
}
|
||||
|
||||
try {
|
||||
setError(null);
|
||||
|
||||
@@ -76,27 +93,27 @@ export function useEndpointEnabled(endpoint: string): {
|
||||
suppressErrorToast: true,
|
||||
});
|
||||
|
||||
if (!isMountedRef.current) return;
|
||||
setEnabled(response.data);
|
||||
} catch (err: unknown) {
|
||||
const isBackendStarting = isBackendNotReadyError(err);
|
||||
const message = getErrorMessage(err);
|
||||
if (!isMountedRef.current) return;
|
||||
setError(isBackendStarting ? t('backendHealth.starting', 'Backend starting up...') : message);
|
||||
setEnabled(true);
|
||||
|
||||
if (!retryTimeoutRef.current) {
|
||||
retryTimeoutRef.current = setTimeout(() => {
|
||||
retryTimeoutRef.current = null;
|
||||
fetchEndpointStatus();
|
||||
}, RETRY_DELAY_MS);
|
||||
if (isBackendStarting) {
|
||||
setError(t('backendHealth.starting', 'Backend starting up...'));
|
||||
if (!retryTimeoutRef.current) {
|
||||
retryTimeoutRef.current = setTimeout(() => {
|
||||
retryTimeoutRef.current = null;
|
||||
fetchEndpointStatus();
|
||||
}, RETRY_DELAY_MS);
|
||||
}
|
||||
} else {
|
||||
setError(message);
|
||||
setEnabled(false);
|
||||
}
|
||||
} finally {
|
||||
if (isMountedRef.current) {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
}, [endpoint, clearRetryTimeout]);
|
||||
}, [endpoint, clearRetryTimeout, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!endpoint) {
|
||||
@@ -136,15 +153,9 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
refetch: () => Promise<void>;
|
||||
} {
|
||||
const { t } = useTranslation();
|
||||
const [endpointStatus, setEndpointStatus] = useState<Record<string, boolean>>(() => {
|
||||
if (!endpoints || endpoints.length === 0) return {};
|
||||
return endpoints.reduce((acc, endpointName) => {
|
||||
acc[endpointName] = true;
|
||||
return acc;
|
||||
}, {} as Record<string, boolean>);
|
||||
});
|
||||
const [endpointStatus, setEndpointStatus] = useState<Record<string, boolean>>({});
|
||||
const [endpointDetails, setEndpointDetails] = useState<Record<string, EndpointAvailabilityDetails>>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const isMountedRef = useRef(true);
|
||||
const retryTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -167,26 +178,31 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
clearRetryTimeout();
|
||||
|
||||
if (!endpoints || endpoints.length === 0) {
|
||||
if (!isMountedRef.current) return;
|
||||
setEndpointStatus({});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const dependenciesReady = await checkDependenciesReady();
|
||||
if (!dependenciesReady) {
|
||||
return; // Health monitor will trigger retry when truly ready
|
||||
}
|
||||
|
||||
try {
|
||||
setError(null);
|
||||
|
||||
const endpointsParam = endpoints.join(',');
|
||||
|
||||
const response = await apiClient.get<Record<string, EndpointAvailabilityDetails>>('/api/v1/config/endpoints-availability', {
|
||||
params: { endpoints: endpointsParam },
|
||||
suppressErrorToast: true,
|
||||
});
|
||||
const response = await apiClient.get<Record<string, EndpointAvailabilityDetails>>(
|
||||
`/api/v1/config/endpoints-availability?endpoints=${encodeURIComponent(endpointsParam)}`,
|
||||
{
|
||||
suppressErrorToast: true,
|
||||
}
|
||||
);
|
||||
|
||||
if (!isMountedRef.current) return;
|
||||
const details = Object.entries(response.data).reduce((acc, [endpointName, detail]) => {
|
||||
acc[endpointName] = {
|
||||
enabled: detail?.enabled ?? true,
|
||||
enabled: detail?.enabled ?? false,
|
||||
reason: detail?.reason ?? null,
|
||||
};
|
||||
return acc;
|
||||
@@ -198,34 +214,34 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
}, {} as Record<string, boolean>);
|
||||
|
||||
setEndpointDetails(prev => ({ ...prev, ...details }));
|
||||
setEndpointStatus(statusMap);
|
||||
setEndpointStatus(prev => ({ ...prev, ...statusMap }));
|
||||
} catch (err: unknown) {
|
||||
const isBackendStarting = isBackendNotReadyError(err);
|
||||
const message = getErrorMessage(err);
|
||||
if (!isMountedRef.current) return;
|
||||
setError(isBackendStarting ? t('backendHealth.starting', 'Backend starting up...') : message);
|
||||
|
||||
const fallbackStatus = endpoints.reduce((acc, endpointName) => {
|
||||
const fallbackDetail: EndpointAvailabilityDetails = { enabled: true, reason: null };
|
||||
acc.status[endpointName] = true;
|
||||
acc.details[endpointName] = fallbackDetail;
|
||||
return acc;
|
||||
}, { status: {} as Record<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> });
|
||||
setEndpointStatus(fallbackStatus.status);
|
||||
setEndpointDetails(prev => ({ ...prev, ...fallbackStatus.details }));
|
||||
|
||||
if (!retryTimeoutRef.current) {
|
||||
retryTimeoutRef.current = setTimeout(() => {
|
||||
retryTimeoutRef.current = null;
|
||||
fetchAllEndpointStatuses();
|
||||
}, RETRY_DELAY_MS);
|
||||
if (isBackendStarting) {
|
||||
setError(t('backendHealth.starting', 'Backend starting up...'));
|
||||
if (!retryTimeoutRef.current) {
|
||||
retryTimeoutRef.current = setTimeout(() => {
|
||||
retryTimeoutRef.current = null;
|
||||
fetchAllEndpointStatuses();
|
||||
}, RETRY_DELAY_MS);
|
||||
}
|
||||
} else {
|
||||
setError(message);
|
||||
const fallbackStatus = endpoints.reduce((acc, endpointName) => {
|
||||
const fallbackDetail: EndpointAvailabilityDetails = { enabled: false, reason: 'UNKNOWN' };
|
||||
acc.status[endpointName] = false;
|
||||
acc.details[endpointName] = fallbackDetail;
|
||||
return acc;
|
||||
}, { status: {} as Record<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> });
|
||||
setEndpointStatus(fallbackStatus.status);
|
||||
setEndpointDetails(prev => ({ ...prev, ...fallbackStatus.details }));
|
||||
}
|
||||
} finally {
|
||||
if (isMountedRef.current) {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
}, [endpoints, clearRetryTimeout]);
|
||||
}, [endpoints, clearRetryTimeout, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!endpoints || endpoints.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user