mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
# 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
123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
import i18n from '@app/i18n';
|
|
import { tauriBackendService } from '@app/services/tauriBackendService';
|
|
import type { BackendHealthState } from '@app/types/backendHealth';
|
|
|
|
type Listener = (state: BackendHealthState) => void;
|
|
|
|
class BackendHealthMonitor {
|
|
private listeners = new Set<Listener>();
|
|
private intervalId: ReturnType<typeof setInterval> | null = null;
|
|
private readonly intervalMs: number;
|
|
private state: BackendHealthState = {
|
|
status: tauriBackendService.getBackendStatus(),
|
|
error: null,
|
|
isHealthy: tauriBackendService.getBackendStatus() === 'healthy',
|
|
};
|
|
|
|
constructor(pollingInterval = 5000) {
|
|
this.intervalMs = pollingInterval;
|
|
|
|
// Reflect status updates from the backend service immediately
|
|
tauriBackendService.subscribeToStatus((status) => {
|
|
this.updateState({
|
|
status,
|
|
error: status === 'healthy' ? null : this.state.error,
|
|
message: status === 'healthy'
|
|
? i18n.t('backendHealth.online', 'Backend Online')
|
|
: this.state.message ?? i18n.t('backendHealth.offline', 'Backend Offline'),
|
|
});
|
|
});
|
|
}
|
|
|
|
private updateState(partial: Partial<BackendHealthState>) {
|
|
const nextStatus = partial.status ?? this.state.status;
|
|
const nextState = {
|
|
...this.state,
|
|
...partial,
|
|
status: nextStatus,
|
|
isHealthy: nextStatus === 'healthy',
|
|
};
|
|
|
|
// Only notify listeners if meaningful state changed
|
|
const meaningfulChange =
|
|
this.state.status !== nextState.status ||
|
|
this.state.error !== nextState.error ||
|
|
this.state.message !== nextState.message;
|
|
|
|
this.state = nextState;
|
|
|
|
if (meaningfulChange) {
|
|
this.listeners.forEach((listener) => listener(this.state));
|
|
}
|
|
}
|
|
|
|
private ensurePolling() {
|
|
if (this.intervalId !== null) {
|
|
return;
|
|
}
|
|
void this.pollOnce();
|
|
this.intervalId = setInterval(() => {
|
|
void this.pollOnce();
|
|
}, this.intervalMs);
|
|
}
|
|
|
|
private stopPolling() {
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = null;
|
|
}
|
|
}
|
|
|
|
private async pollOnce(): Promise<boolean> {
|
|
try {
|
|
const healthy = await tauriBackendService.checkBackendHealth();
|
|
if (healthy) {
|
|
this.updateState({
|
|
status: 'healthy',
|
|
message: i18n.t('backendHealth.online', 'Backend Online'),
|
|
error: null,
|
|
});
|
|
} else {
|
|
this.updateState({
|
|
status: 'unhealthy',
|
|
message: i18n.t('backendHealth.offline', 'Backend Offline'),
|
|
error: i18n.t('backendHealth.offline', 'Backend Offline'),
|
|
});
|
|
}
|
|
return healthy;
|
|
} catch (error) {
|
|
console.error('[BackendHealthMonitor] Health check failed:', error);
|
|
this.updateState({
|
|
status: 'unhealthy',
|
|
message: 'Backend is unavailable',
|
|
error: 'Backend offline',
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
|
|
subscribe(listener: Listener): () => void {
|
|
this.listeners.add(listener);
|
|
listener(this.state);
|
|
if (this.listeners.size === 1) {
|
|
this.ensurePolling();
|
|
}
|
|
return () => {
|
|
this.listeners.delete(listener);
|
|
if (this.listeners.size === 0) {
|
|
this.stopPolling();
|
|
}
|
|
};
|
|
}
|
|
|
|
getSnapshot(): BackendHealthState {
|
|
return this.state;
|
|
}
|
|
|
|
async checkNow(): Promise<boolean> {
|
|
return this.pollOnce();
|
|
}
|
|
}
|
|
|
|
export const backendHealthMonitor = new BackendHealthMonitor();
|