fix(app): scope header overrides to gateway URLs only

- The session.webRequest.onHeadersReceived was stripping X-Frame-Options
  and modifying CSP for ALL responses including the Vite dev server,
  which could break the main app rendering. Now only applies to
  gateway URLs (127.0.0.1:18789 / localhost:18789).
- Dashboard: only fetch channels/skills when gateway is running
- Dashboard: guard against non-array channels/skills data
- Gateway store: use dynamic import() instead of require() for chat
  store to avoid ESM/CJS issues in Vite
This commit is contained in:
Haze
2026-02-06 03:40:47 +08:00
Unverified
parent 71409042cb
commit f67370ce03
3 changed files with 34 additions and 18 deletions

View File

@@ -75,12 +75,19 @@ async function initialize(): Promise<void> {
// Create system tray // Create system tray
createTray(mainWindow); createTray(mainWindow);
// Override security headers for the OpenClaw Control UI webview // Override security headers ONLY for the OpenClaw Gateway Control UI
// The Control UI sets X-Frame-Options: DENY and CSP frame-ancestors 'none' // The Control UI sets X-Frame-Options: DENY and CSP frame-ancestors 'none'
// which prevents embedding in an Electron webview // which prevents embedding in an iframe. Only apply to gateway URLs.
session.defaultSession.webRequest.onHeadersReceived((details, callback) => { session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
const isGatewayUrl = details.url.includes('127.0.0.1:18789') || details.url.includes('localhost:18789');
if (!isGatewayUrl) {
callback({ responseHeaders: details.responseHeaders });
return;
}
const headers = { ...details.responseHeaders }; const headers = { ...details.responseHeaders };
// Remove X-Frame-Options to allow embedding in webview // Remove X-Frame-Options to allow embedding in iframe
delete headers['X-Frame-Options']; delete headers['X-Frame-Options'];
delete headers['x-frame-options']; delete headers['x-frame-options'];
// Remove restrictive CSP frame-ancestors // Remove restrictive CSP frame-ancestors

View File

@@ -26,15 +26,19 @@ export function Dashboard() {
const { channels, fetchChannels } = useChannelsStore(); const { channels, fetchChannels } = useChannelsStore();
const { skills, fetchSkills } = useSkillsStore(); const { skills, fetchSkills } = useSkillsStore();
// Fetch data on mount const isGatewayRunning = gatewayStatus.state === 'running';
// Fetch data only when gateway is running
useEffect(() => { useEffect(() => {
if (isGatewayRunning) {
fetchChannels(); fetchChannels();
fetchSkills(); fetchSkills();
}, [fetchChannels, fetchSkills]); }
}, [fetchChannels, fetchSkills, isGatewayRunning]);
// Calculate statistics // Calculate statistics safely
const connectedChannels = channels.filter((c) => c.status === 'connected').length; const connectedChannels = Array.isArray(channels) ? channels.filter((c) => c.status === 'connected').length : 0;
const enabledSkills = skills.filter((s) => s.enabled).length; const enabledSkills = Array.isArray(skills) ? skills.filter((s) => s.enabled).length : 0;
// Calculate uptime // Calculate uptime
const uptime = gatewayStatus.connectedAt const uptime = gatewayStatus.connectedAt

View File

@@ -62,14 +62,19 @@ export const useGatewayStore = create<GatewayState>((set, get) => ({
// Listen for chat events from the gateway and forward to chat store // Listen for chat events from the gateway and forward to chat store
window.electron.ipcRenderer.on('gateway:chat-message', (data) => { window.electron.ipcRenderer.on('gateway:chat-message', (data) => {
const { useChatStore } = require('./chat'); try {
// Dynamic import to avoid circular dependency
import('./chat').then(({ useChatStore }) => {
const chatData = data as { message?: Record<string, unknown> } | Record<string, unknown>; const chatData = data as { message?: Record<string, unknown> } | Record<string, unknown>;
// The event payload may be nested under 'message' or directly on data
const event = ('message' in chatData && typeof chatData.message === 'object') const event = ('message' in chatData && typeof chatData.message === 'object')
? chatData.message as Record<string, unknown> ? chatData.message as Record<string, unknown>
: chatData as Record<string, unknown>; : chatData as Record<string, unknown>;
useChatStore.getState().handleChatEvent(event); useChatStore.getState().handleChatEvent(event);
}); });
} catch (err) {
console.warn('Failed to forward chat event:', err);
}
});
} catch (error) { } catch (error) {
console.error('Failed to initialize Gateway:', error); console.error('Failed to initialize Gateway:', error);