// helpers for deploying a cross-origin sandbox environment // Node.js-only helper. Uses opaque dynamic require so bundlers can't resolve it. function readServiceWorkerSource(): string | null { try { // invisible to bundlers' static analysis const _req = globalThis['require' as keyof typeof globalThis] as typeof require | undefined; if (typeof _req !== 'function') return null; const fs = _req('fs' + ''); const path = _req('path' + ''); let dir: string; try { const url = _req('url' + ''); dir = path.dirname(url.fileURLToPath(import.meta.url)); } catch { dir = typeof __dirname !== 'undefined' ? __dirname : '.'; } let swFile = path.join(dir, '__sw__.js'); if (fs.existsSync(swFile)) return fs.readFileSync(swFile, 'utf-8'); swFile = path.join(dir, '../dist/__sw__.js'); if (fs.existsSync(swFile)) return fs.readFileSync(swFile, 'utf-8'); return null; } catch { return null; } } import { DEFAULT_NODEPOD_CDN } from "./constants/config"; export interface SandboxPageConfig { nodepodUrl?: string; enableServiceWorker?: boolean; /** parent page origin, e.g. 'https://myapp.com'. locks down who can * talk to the sandbox. defaults to '*' for backwards compat */ parentOrigin?: string; } export function getSandboxPageHtml(config: SandboxPageConfig | string = {}): string { const opts: SandboxPageConfig = typeof config === 'string' ? { nodepodUrl: config } : config; const nodepodUrl = opts.nodepodUrl ?? DEFAULT_NODEPOD_CDN; const withSW = opts.enableServiceWorker ?? true; const parentOriginJs = opts.parentOrigin ? JSON.stringify(opts.parentOrigin) : "'*'"; const swBlock = withSW ? ` if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/__sw__.js', { scope: '/' }) .then(() => console.log('[Sandbox] SW registered')) .catch(e => console.warn('[Sandbox] SW failed:', e)); } ` : ''; return ` Nodepod Sandbox `; } // Vercel config with CORS headers for cross-origin iframe embedding export function getSandboxHostingConfig(): object { return { headers: [{ source: '/(.*)', headers: [ { key: 'Access-Control-Allow-Origin', value: '*' }, { key: 'Cross-Origin-Resource-Policy', value: 'cross-origin' }, ], }], }; } export interface GeneratedSandboxFiles { 'index.html': string; 'vercel.json': string; '__sw__.js'?: string; } export function generateSandboxDeployment(config: SandboxPageConfig | string = {}): GeneratedSandboxFiles { const opts: SandboxPageConfig = typeof config === 'string' ? { nodepodUrl: config } : config; const withSW = opts.enableServiceWorker ?? true; const swSource = withSW ? readServiceWorkerSource() : null; const files: GeneratedSandboxFiles = { 'index.html': getSandboxPageHtml(opts), 'vercel.json': JSON.stringify(getSandboxHostingConfig(), null, 2), }; if (swSource) files['__sw__.js'] = swSource; return files; } export const SANDBOX_DEPLOYMENT_GUIDE = ` # Deploying a Nodepod Sandbox ## 1. Generate sandbox files Use generateSandboxDeployment() to create the required files. ## 2. Deploy to a hosting provider cd sandbox && vercel --prod ## 3. Use in your app const engine = await spawnEngine(volume, { sandboxUrl: 'https://your-sandbox.example.com' }); `.trim();