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:
55
desktop/src/ErrorBoundary.tsx
Normal file
55
desktop/src/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
// Top-level error boundary. React eats thrown errors silently by default,
|
||||
// which in an Electron app with no URL bar means "blank window, nothing
|
||||
// to click" from the user's perspective. This component at least shows
|
||||
// the error text + stack so we can copy-paste it into a bug report.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface State {
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends React.Component<
|
||||
{ children: React.ReactNode }, State
|
||||
> {
|
||||
state: State = { error: null };
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: React.ErrorInfo): void {
|
||||
// Surface the exception in the devtools console too, for quick
|
||||
// copy-paste when the boundary is blocking the UI.
|
||||
console.error('[ErrorBoundary]', error, info);
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
if (!this.state.error) return this.props.children;
|
||||
return (
|
||||
<div style={{
|
||||
padding: 24, height: '100%', overflow: 'auto',
|
||||
background: '#000', color: '#fff', fontFamily: 'monospace',
|
||||
}}>
|
||||
<h2 style={{ color: '#ff6b6b', marginTop: 0 }}>Something broke.</h2>
|
||||
<p style={{ color: '#fff' }}>{this.state.error.message}</p>
|
||||
<pre style={{
|
||||
color: '#8b8b8b', fontSize: 12, lineHeight: 1.4,
|
||||
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
||||
}}>
|
||||
{this.state.error.stack}
|
||||
</pre>
|
||||
<button
|
||||
onClick={() => this.setState({ error: null })}
|
||||
style={{
|
||||
marginTop: 12, padding: '8px 14px', borderRadius: 999,
|
||||
border: '1px solid #1f1f1f', background: '#111',
|
||||
color: '#fff', cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user