import { injectIntoDocumentHead } from './html-document'; /** * In-memory localStorage/sessionStorage shim, injected as the FIRST thing in the * document so the page's own scripts see working storage. * * The interactive iframe is sandboxed `allow-scripts` WITHOUT `allow-same-origin` * (intentional — combining them negates the sandbox for LLM-authored HTML). In a * null-origin document, touching `window.localStorage` throws a SecurityError; * many generated pages read/write storage in their setup code, so that throw * crashes the script before anything renders → a blank/black widget. This shim * replaces both storages with an in-memory implementation when the real ones are * inaccessible, keeping the sandbox intact while letting storage-using pages run. */ const STORAGE_SHIM = ``; /** * Runtime-error capture, injected as the VERY FIRST script so it observes errors * from the storage shim and every page script that follows. Generated interactive * pages frequently die on a runtime error (a `JSON.parse` of malformed config, a * reference to a CDN lib that failed to load, …) → the script aborts and the * widget renders blank. The sandboxed (null-origin) iframe can't be read by the * editor, but it CAN `postMessage` out: this forwards `window.onerror`, unhandled * rejections and `console.error` to the parent, which stores them per scene and * feeds them to the editor agent — so it can diagnose a blank page instead of * guessing. Only touches `window.*` so it stays sandbox-safe and unit-testable. * * The most important errors (a `JSON.parse` that aborts setup) fire SYNCHRONOUSLY * while srcDoc parses — potentially before the parent has subscribed its `message` * listener (which it installs from a passive effect after inserting the iframe). * To avoid losing exactly the errors this feature exists to surface, every post is * also buffered, and the shim re-emits the whole buffer when the parent sends a * `{ __maicErrorReplayRequest: true }` message once its listener is ready. The * parent dedups, so the live + replayed copies collapse to one. */ const ERROR_CAPTURE_SHIM = ``; /** * Patch embedded HTML to display correctly inside an iframe. * * Injects a runtime-error capture shim + a storage shim (so sandboxed pages that * use localStorage don't crash) plus CSS that ensures proper sizing and scrolling * behavior when HTML content is rendered via srcDoc in an iframe. The shims are * placed first so they run before the page's own scripts (error capture first, so * it also observes the storage shim). */ export function patchHtmlForIframe(html: string, options: { offline?: boolean } = {}): string { const iframeCss = ``; // Desktop course packages are frozen before publication. Their interactive // pages must therefore be self-contained: the local player blocks every // network, parent, plugin and nested-frame capability while preserving inline // scripts/styles, data/blob media and the existing sandboxed interaction UI. const offlineCsp = options.offline ? `` : ''; const injection = `\n${[offlineCsp, ERROR_CAPTURE_SHIM, STORAGE_SHIM, iframeCss].filter(Boolean).join('\n')}`; return injectIntoDocumentHead(html, injection); }