From cb46483f5d264e3d43d6006d1974f3e8504915e3 Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 16:24:34 +0200 Subject: [PATCH] =?UTF-8?q?feat(mobile):=20AppBootstrap=20boundary=20?= =?UTF-8?q?=E2=80=94=20defers=20crypto=20init,=20catches=20global=20throws?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/mobile/components/AppBootstrap.tsx | 51 +++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 apps/mobile/components/AppBootstrap.tsx diff --git a/apps/mobile/components/AppBootstrap.tsx b/apps/mobile/components/AppBootstrap.tsx new file mode 100644 index 0000000..81c70a4 --- /dev/null +++ b/apps/mobile/components/AppBootstrap.tsx @@ -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(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 ; + if (!ready) return ; + return <>{children}; +}