feat(mobile): AppBootstrap boundary — defers crypto init, catches global throws

This commit is contained in:
byGalax
2026-05-16 16:24:34 +02:00
parent 87fed820dc
commit cb46483f5d
+51
View File
@@ -0,0 +1,51 @@
import { crypto } from '@chat-app/shared';
import { type ReactNode, useEffect, useState } from 'react';
import { createLibsodiumBackend } from '../lib/cryptoBackend';
import { BootError } from './BootError';
import { BootSplash } from './BootSplash';
interface Props {
children: ReactNode;
}
// React Native exposes ErrorUtils on the global. The types ship with RN but
// we cast defensively because the renderer used by Vitest does not.
interface RNErrorUtils {
getGlobalHandler: () => (err: Error, isFatal?: boolean) => void;
setGlobalHandler: (handler: (err: Error, isFatal?: boolean) => void) => void;
}
// Initialises the crypto backend inside a useEffect (not at module-eval) so
// any failure surfaces in the React tree. Also installs a global JS error
// handler that routes unhandled throws to BootError; this catches errors
// thrown during render (e.g. the lazy env proxy reading a missing var) that
// would otherwise escape every per-screen ErrorBoundary.
export function AppBootstrap({ children }: Props) {
const [error, setError] = useState<Error | null>(null);
const [ready, setReady] = useState(false);
useEffect(() => {
try {
crypto.setCryptoBackend(createLibsodiumBackend());
setReady(true);
} catch (e: unknown) {
setError(e instanceof Error ? e : new Error(String(e)));
}
}, []);
useEffect(() => {
const eu = (globalThis as unknown as { ErrorUtils?: RNErrorUtils }).ErrorUtils;
if (!eu) return;
const prev = eu.getGlobalHandler();
eu.setGlobalHandler((err, isFatal) => {
prev?.(err, isFatal);
setError(err);
});
return () => eu.setGlobalHandler(prev);
}, []);
if (error) return <BootError error={error} />;
if (!ready) return <BootSplash />;
return <>{children}</>;
}