Add startup retry logic and persist logs across refresh
All checks were successful
Release / build-and-release (push) Successful in 25m5s
All checks were successful
Release / build-and-release (push) Successful in 25m5s
Retry connectActiveProfile up to 3 times with 1s delay before showing the error page. Persist log buffer to sessionStorage so logs survive page refresh and are visible in the settings log viewer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6f84964689
commit
fb92cfc12c
2 changed files with 48 additions and 8 deletions
27
src/App.tsx
27
src/App.tsx
|
|
@ -16,6 +16,8 @@ import ProfileSelectionPage from "./pages/ProfileSelectionPage";
|
|||
import ErrorPage from "./components/shared/ErrorPage";
|
||||
|
||||
const STARTUP_TIMEOUT_MS = 10_000;
|
||||
const MAX_RETRIES = 3;
|
||||
const RETRY_DELAY_MS = 1_000;
|
||||
|
||||
export default function App() {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -23,29 +25,42 @@ export default function App() {
|
|||
const [dbReady, setDbReady] = useState(false);
|
||||
const [startupError, setStartupError] = useState<string | null>(null);
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const cancelledRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeProfile && !isLoading) {
|
||||
setDbReady(false);
|
||||
setStartupError(null);
|
||||
cancelledRef.current = false;
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setStartupError(t("error.startupTimeout"));
|
||||
}, STARTUP_TIMEOUT_MS);
|
||||
|
||||
connectActiveProfile()
|
||||
.then(() => {
|
||||
const attemptConnect = async (attempt: number): Promise<void> => {
|
||||
try {
|
||||
await connectActiveProfile();
|
||||
if (cancelledRef.current) return;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
setDbReady(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
} catch (err) {
|
||||
if (cancelledRef.current) return;
|
||||
console.error(`Failed to connect profile (attempt ${attempt}/${MAX_RETRIES}):`, err);
|
||||
if (attempt < MAX_RETRIES) {
|
||||
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
||||
if (!cancelledRef.current) return attemptConnect(attempt + 1);
|
||||
} else {
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
console.error("Failed to connect profile:", err);
|
||||
setStartupError(err instanceof Error ? err.message : String(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
attemptConnect(1);
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelledRef.current = true;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
};
|
||||
}, [activeProfile, isLoading, connectActiveProfile, t]);
|
||||
|
|
|
|||
|
|
@ -9,11 +9,32 @@ export interface LogEntry {
|
|||
type LogListener = () => void;
|
||||
|
||||
const MAX_ENTRIES = 500;
|
||||
const STORAGE_KEY = "simpl-resultat-logs";
|
||||
const logs: LogEntry[] = [];
|
||||
const listeners = new Set<LogListener>();
|
||||
|
||||
let initialized = false;
|
||||
|
||||
function loadFromStorage() {
|
||||
try {
|
||||
const stored = sessionStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
const parsed: LogEntry[] = JSON.parse(stored);
|
||||
logs.push(...parsed);
|
||||
}
|
||||
} catch {
|
||||
// ignore corrupted storage
|
||||
}
|
||||
}
|
||||
|
||||
function saveToStorage() {
|
||||
try {
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(logs));
|
||||
} catch {
|
||||
// ignore quota errors
|
||||
}
|
||||
}
|
||||
|
||||
function addEntry(level: LogLevel, args: unknown[]) {
|
||||
const message = args
|
||||
.map((a) => {
|
||||
|
|
@ -31,6 +52,7 @@ function addEntry(level: LogLevel, args: unknown[]) {
|
|||
logs.splice(0, logs.length - MAX_ENTRIES);
|
||||
}
|
||||
|
||||
saveToStorage();
|
||||
listeners.forEach((fn) => fn());
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +60,8 @@ export function initLogCapture() {
|
|||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
loadFromStorage();
|
||||
|
||||
const origLog = console.log.bind(console);
|
||||
const origWarn = console.warn.bind(console);
|
||||
const origError = console.error.bind(console);
|
||||
|
|
@ -64,6 +88,7 @@ export function getLogs(): readonly LogEntry[] {
|
|||
|
||||
export function clearLogs() {
|
||||
logs.length = 0;
|
||||
saveToStorage();
|
||||
listeners.forEach((fn) => fn());
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue