fix(desktop): CSP via webRequest + boot error visibility
Two problems from the first alpha4 run reported as "blank window + CSP
warning in devtools":
1. CSP was set via <meta> in index.html with a strict policy (script-src
'self'). Vite's dev server uses eval() for HMR, which the strict CSP
blocked at module-load time, so the renderer never ran. The meta CSP
also conflicted with Electron's own security heuristics (hence the
warning even though *we* had a policy — Electron was looking for it
on the HTTP response).
Moved the CSP to electron/main.ts via session.webRequest
.onHeadersReceived. Dev profile enables 'unsafe-eval' + ws:/wss: for
HMR; production profile stays strict (no eval, no remote scripts,
connect-src still wide because the user picks arbitrary node URLs).
2. When window.dchain isn't available (preload failed to load, dev
misconfig, etc.), loadKeyFile() throws inside a useEffect. React
swallows async-effect throws, so the app renders blank forever.
Added:
- requireDchain() guard in storage.ts with an explicit error.
- App.tsx catches boot-effect errors and renders them inline.
- ErrorBoundary.tsx for render-time throws.
- window.addEventListener('error') in main.tsx as a last-resort
paint for throws that escape React entirely.
Also: npm script electron:dev now rebuilds main.ts before spawning
Electron (was a silent concurrency bug — TypeScript errors in main.ts
would produce stale dist-electron/).
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
// 2. Render either the Welcome auth flow (no key yet) or the Shell
|
||||
// (3-panel layout + current section).
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useStore } from '@/lib/store';
|
||||
import { loadKeyFile, loadSettings, loadContacts } from '@/lib/storage';
|
||||
import { setNodeUrl } from '@/lib/api';
|
||||
@@ -14,23 +14,47 @@ import { Welcome } from '@/auth/Welcome';
|
||||
export function App(): React.ReactElement {
|
||||
const booted = useStore(s => s.booted);
|
||||
const keyFile = useStore(s => s.keyFile);
|
||||
const [bootError, setBootError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const set = loadSettings();
|
||||
setNodeUrl(set.nodeUrl);
|
||||
useStore.getState().setSettings(set);
|
||||
try {
|
||||
const set = loadSettings();
|
||||
setNodeUrl(set.nodeUrl);
|
||||
useStore.getState().setSettings(set);
|
||||
|
||||
const cs = loadContacts();
|
||||
useStore.getState().setContacts(cs);
|
||||
const cs = loadContacts();
|
||||
useStore.getState().setContacts(cs);
|
||||
|
||||
const kf = await loadKeyFile();
|
||||
useStore.getState().setKeyFile(kf);
|
||||
const kf = await loadKeyFile();
|
||||
useStore.getState().setKeyFile(kf);
|
||||
|
||||
useStore.getState().setBooted(true);
|
||||
useStore.getState().setBooted(true);
|
||||
} catch (err) {
|
||||
// Show the error inline — the boundary only catches render
|
||||
// throws, not async-effect throws like this one.
|
||||
setBootError(err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
if (bootError) {
|
||||
return (
|
||||
<div style={{
|
||||
padding: 24, color: '#ff6b6b', fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
||||
}}>
|
||||
<h2 style={{ color: '#ff6b6b', margin: 0 }}>Boot failed</h2>
|
||||
<p style={{ color: '#fff', marginTop: 8 }}>{bootError}</p>
|
||||
<p style={{ color: '#8b8b8b', fontSize: 12, marginTop: 12 }}>
|
||||
This usually means the Electron preload script didn't load.
|
||||
Check that `npm run build:main` has produced `dist-electron/preload.js`
|
||||
and restart `npm run dev`.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!booted) {
|
||||
// Matches the splash: whole window is already black from index.html,
|
||||
// so showing nothing is the right behaviour — no flash, no spinner.
|
||||
|
||||
Reference in New Issue
Block a user