52 lines
1.7 KiB
TypeScript
52 lines
1.7 KiB
TypeScript
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}</>;
|
|
}
|