import { applicationHtmlPlugin } from './application-html.js'; import cesium from 'vite-plugin-cesium'; import { embedFramingPlugin } from './embed-framing.js'; import { panelBuildPlugin } from './panel.js'; /** * Content-Security-Policy for every document the dev/preview server serves. * No inline script and no foreign script origin is permitted. 'unsafe-eval' is * required: Knockout (bundled inside @cesium/widgets) resolves the global * object with `(0, eval)("this")` at module load, and without it the Cesium * widget never initializes (verified in headless Chrome). It also covers * Cesium's WASM decoders. `blob:` is required in the built app: Cesium's * bundled workers bootstrap through `importScripts(blob:...)`, which a worker * checks against script-src (the dev server loads them by URL instead). * Widen any other directive only for a real violation. */ export const BROWSER_CSP = [ "default-src 'self'", "script-src 'self' 'unsafe-eval' blob:", "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", "font-src 'self' data: https://fonts.gstatic.com", "img-src 'self' data: blob: https:", "media-src 'self' blob: https:", "connect-src 'self' blob: data: https: wss: ws:", "worker-src 'self' blob:", "child-src 'self' blob:", "manifest-src 'self'", "object-src 'none'", "base-uri 'self'", "form-action 'self'", "frame-ancestors 'none'", ].join('; '); /** Response headers shared by the dev and preview servers. */ export const BROWSER_HEADERS = Object.freeze({ 'X-Frame-Options': 'DENY', 'Content-Security-Policy': BROWSER_CSP, }); /** Build browser assets with explicit inputs; never load environment or providers. */ export function createBrowserViteConfig({ plugins = [], publicDir, googleApiKey, cesiumToken, host = 'localhost', port = 4173, command, } = {}) { return { plugins: [ cesium(), applicationHtmlPlugin(), ...plugins, embedFramingPlugin(), panelBuildPlugin(), ], ...(publicDir === undefined ? {} : { publicDir }), // A production build must not clean the dependency cache a running dev // server is still serving optimized module URLs from. ...(command === 'build' ? { cacheDir: 'node_modules/.vite-build' } : {}), optimizeDeps: { // First reached through the SDR worker or a dynamic import. Pre-bundle // them at startup so first use cannot invalidate already-transformed // URLs with Vite's "Outdated Optimize Dep" 504 response. include: [ '@jtarrio/signals/demod/demodulator.js', '@jtarrio/signals/demod/modes.js', '@jtarrio/webrtlsdr/rtlsdr.js', 'egm96-universal', ], }, server: { host: host || 'localhost', port: parseInt(port, 10) || 4173, allowedHosts: host === '0.0.0.0' || host === '::' ? true : ['localhost', '127.0.0.1', '.local'], fs: { deny: ['.env', '.env.*', '*.{crt,pem}', '**/.git/**', '**/ENVIRONMENT'], }, // These headers protect the document containing Provider Settings and // give the whole page a real Content-Security-Policy (BROWSER_CSP). // Embed-mode documents are framable instead: embed-framing.js rewrites // only the frame-ancestors directive and keeps the rest of the policy. headers: BROWSER_HEADERS, }, // The preview server serves the same documents, so it carries the same // framing + CSP hardening (one constant, no drift). preview: { headers: BROWSER_HEADERS }, define: { 'import.meta.env.GOOGLE_MAPS_API_KEY': JSON.stringify(googleApiKey), 'import.meta.env.CESIUM_ION_TOKEN': JSON.stringify(cesiumToken), }, build: { chunkSizeWarningLimit: 1500 }, }; }