Mirror — see github.com/blacksky-algorithms/blacksky.community
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix splash screen hang from stale persisted state

Three issues could cause the app to hang on the splash screen forever
when the browser has stale localStorage/IndexedDB data from a previous
deployment:

1. Promise.all in App had no .catch() handler. If initPersistedState()
or setupDeviceId rejected, setReady(true) never fired.

2. features.init used the async executor anti-pattern: if GrowthBook
init threw (e.g. CORS failure, corrupted cache), the wrapping
Promise never settled because resolve() was never called.

3. InnerApp.onLaunch awaits resumeSession/features.init, but if either
promise never settles (e.g. IndexedDB upgrade blocked by stale
version), the finally block never runs and setIsReady(true) never
fires. Added a 15-second safety timeout to guarantee the app loads.

+16 -6
+13 -3
src/App.web.tsx
··· 100 100 101 101 // init 102 102 useEffect(() => { 103 + // Safety valve: if onLaunch hangs (e.g. stale IndexedDB blocking an 104 + // upgrade, or a never-settling promise), the app will still load after 105 + // this timeout fires. Without this, a hanging `await` prevents the 106 + // `finally` block from ever executing. 107 + const safetyTimeout = setTimeout(() => { 108 + logger.warn('session: onLaunch safety timeout fired, forcing ready state') 109 + setIsReady(true) 110 + }, 15_000) 111 + 103 112 async function onLaunch(account?: SessionAccount) { 104 113 try { 105 114 // Check for OAuth callback params first (loopback redirects to /) ··· 130 139 } catch (e) { 131 140 logger.error(`session: resumeSession failed`, {message: e}) 132 141 } finally { 142 + clearTimeout(safetyTimeout) 133 143 setIsReady(true) 134 144 } 135 145 } ··· 214 224 const [isReady, setReady] = useState(false) 215 225 216 226 React.useEffect(() => { 217 - Promise.all([initPersistedState(), setupDeviceId]).then(() => 218 - setReady(true), 219 - ) 227 + Promise.all([initPersistedState(), setupDeviceId]) 228 + .then(() => setReady(true)) 229 + .catch(() => setReady(true)) 220 230 }, []) 221 231 222 232 if (!isReady) {
+3 -3
src/analytics/features/index.ts
··· 43 43 * that case, we may see a flash of uncustomized content until the 44 44 * initialization completes. 45 45 */ 46 - export const init = new Promise<void>(async y => { 47 - await features.init({timeout: TIMEOUT_INIT}) 48 - y() 46 + export const init = features.init({timeout: TIMEOUT_INIT}).catch(() => { 47 + // Swallow errors from GrowthBook init (e.g. CORS failures, cache 48 + // corruption). The app should still load without feature gates. 49 49 }) 50 50 51 51 /**