@vielzeug/sandbox 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -125
- package/dist/_bridge.cjs +6 -6
- package/dist/_bridge.cjs.map +1 -1
- package/dist/_bridge.js +6 -6
- package/dist/_bridge.js.map +1 -1
- package/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js.map +1 -1
- package/dist/_document.cjs +6 -7
- package/dist/_document.cjs.map +1 -1
- package/dist/_document.d.ts +2 -2
- package/dist/_document.d.ts.map +1 -1
- package/dist/_document.js +14 -24
- package/dist/_document.js.map +1 -1
- package/dist/_policy.cjs +1 -1
- package/dist/_policy.cjs.map +1 -1
- package/dist/_policy.d.ts +4 -1
- package/dist/_policy.d.ts.map +1 -1
- package/dist/_policy.js +33 -25
- package/dist/_policy.js.map +1 -1
- package/dist/_protocol.cjs +1 -1
- package/dist/_protocol.cjs.map +1 -1
- package/dist/_protocol.d.ts +0 -1
- package/dist/_protocol.d.ts.map +1 -1
- package/dist/_protocol.js +5 -5
- package/dist/_protocol.js.map +1 -1
- package/dist/_runtime.cjs +1 -1
- package/dist/_runtime.cjs.map +1 -1
- package/dist/_runtime.d.ts +1 -1
- package/dist/_runtime.d.ts.map +1 -1
- package/dist/_runtime.js +65 -75
- package/dist/_runtime.js.map +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +1 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -4
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/sandbox.cjs +20 -21
- package/dist/sandbox.cjs.map +1 -1
- package/dist/sandbox.iife.js +19 -20
- package/dist/sandbox.iife.js.map +1 -1
- package/dist/sandbox.js +20 -21
- package/dist/sandbox.js.map +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.ts +6 -6
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +34 -153
- package/dist/types.d.ts.map +1 -1
- package/package.json +31 -25
- package/dist/_sandbox.cjs +0 -2
- package/dist/_sandbox.cjs.map +0 -1
- package/dist/_sandbox.d.ts +0 -5
- package/dist/_sandbox.d.ts.map +0 -1
- package/dist/_sandbox.js +0 -11
- package/dist/_sandbox.js.map +0 -1
package/dist/sandbox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.js","names":[],"sources":["../src/errors.ts","../src/_policy.ts","../src/_bridge.ts","../src/_document.ts","../src/_dev.ts","../src/_protocol.ts","../src/_runtime.ts","../src/_sandbox.ts"],"sourcesContent":["/** Base class for all sandbox errors. */\nexport class SandboxError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is SandboxError {\n return err instanceof SandboxError;\n }\n}\n\n/**\n * Thrown when render() does not receive a 'ready' signal from the sandbox document\n * within the timeout window. The document is most likely missing the bridge script —\n * use buildDocument() to generate documents that include it.\n */\nexport class SandboxTimeoutError extends SandboxError {}\n\n/** Thrown when Sandbox configuration cannot produce a valid document or CSP policy. */\nexport class SandboxConfigurationError extends SandboxError {}\n","import type { SandboxOptions } from './types.js';\n\nimport { SandboxConfigurationError } from './errors.js';\n\nexport interface NormalizedSandboxOptions {\n allowedFontOrigins: string[];\n allowedImageOrigins: string[];\n allowedScriptOrigins: string[];\n allowedStyleOrigins: string[];\n lang: string;\n namedStyles: Record<string, string>;\n nonce: string | undefined;\n scripts: string[];\n title: string;\n}\n\nconst ID = /^[A-Za-z][A-Za-z0-9_-]*$/;\nconst LANG = /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;\nconst NONCE = /^[A-Za-z0-9+/_-]+={0,2}$/;\n\nfunction fail(field: string, value: unknown): never {\n throw new SandboxConfigurationError(`${field} is invalid: ${String(value)}`);\n}\n\nfunction normalizeOrigin(value: string, field: string): string {\n let url: URL;\n\n try {\n url = new URL(value);\n } catch {\n return fail(field, value);\n }\n\n if (\n (url.protocol !== 'http:' && url.protocol !== 'https:') ||\n url.username ||\n url.password ||\n url.pathname !== '/' ||\n url.search ||\n url.hash\n ) {\n return fail(field, value);\n }\n\n return url.origin;\n}\n\nfunction normalizeOrigins(values: string[] | undefined, field: string): string[] {\n return (values ?? []).map((value) => normalizeOrigin(value, field));\n}\n\nfunction normalizeScript(value: string): string {\n let url: URL;\n\n try {\n url = new URL(value);\n } catch {\n return fail('scripts', value);\n }\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') return fail('scripts', value);\n\n return url.href;\n}\n\nfunction normalizeStyles(styles: Record<string, string> | undefined): Record<string, string> {\n const normalized: Record<string, string> = {};\n\n for (const [id, css] of Object.entries(styles ?? {})) {\n if (!ID.test(id) || typeof css !== 'string') fail('namedStyles', id);\n\n normalized[id] = css;\n }\n\n return normalized;\n}\n\nexport function normalizeSandboxOptions(options: SandboxOptions = {}): NormalizedSandboxOptions {\n const lang = options.lang ?? 'en';\n\n if (!LANG.test(lang)) fail('lang', lang);\n\n if (options.nonce !== undefined && (!NONCE.test(options.nonce) || options.nonce.length === 0)) {\n fail('nonce', options.nonce);\n }\n\n if (options.title !== undefined && typeof options.title !== 'string') fail('title', options.title);\n\n const scripts = (options.scripts ?? []).map(normalizeScript);\n\n return {\n allowedFontOrigins: normalizeOrigins(options.allowedFontOrigins, 'allowedFontOrigins'),\n allowedImageOrigins: normalizeOrigins(options.allowedImageOrigins, 'allowedImageOrigins'),\n allowedScriptOrigins: normalizeOrigins(options.allowedScriptOrigins, 'allowedScriptOrigins'),\n allowedStyleOrigins: normalizeOrigins(options.allowedStyleOrigins, 'allowedStyleOrigins'),\n lang,\n namedStyles: normalizeStyles(options.namedStyles),\n nonce: options.nonce,\n scripts,\n title: options.title ?? '',\n };\n}\n\nexport function buildCspFromOptions(options: NormalizedSandboxOptions): string {\n const scriptOrigins = [\n \"'unsafe-inline'\",\n ...(options.nonce ? [`'nonce-${options.nonce}'`] : []),\n ...options.allowedScriptOrigins,\n ...options.scripts.map((script) => new URL(script).origin),\n ].join(' ');\n const styleOrigins = [\"'unsafe-inline'\", ...options.allowedStyleOrigins].join(' ');\n const imageOrigins = ['data:', ...options.allowedImageOrigins].join(' ');\n const fontOrigins = options.allowedFontOrigins.join(' ') || \"'none'\";\n\n return [\n \"default-src 'none'\",\n `script-src ${scriptOrigins}`,\n `style-src ${styleOrigins}`,\n `img-src ${imageOrigins}`,\n `font-src ${fontOrigins}`,\n \"connect-src 'none'\",\n \"form-action 'none'\",\n \"base-uri 'none'\",\n ].join('; ');\n}\n","import type { BridgeBootstrap } from './_protocol.js';\n\nexport function buildBridgeScript(bootstrap: BridgeBootstrap): string {\n const channel = JSON.stringify(bootstrap.channel);\n const generation = String(bootstrap.generation);\n\n return `\nwindow.__sandboxChannel = ${channel};\nwindow.__sandboxGeneration = ${generation};\nconst __sandboxChannel = window.__sandboxChannel;\nconst __sandboxGeneration = window.__sandboxGeneration;\nif ('ontouchstart' in window) document.addEventListener('touchstart', function() {}, { passive: true });\nwindow.addEventListener('message', function(e) {\n var msg = e.data;\n if (e.source !== parent || !msg || msg.channel !== __sandboxChannel || msg.generation !== __sandboxGeneration) return;\n if (msg.type === 'state-update') {\n document.dispatchEvent(new CustomEvent('sandbox:state-update', { detail: { key: msg.key, value: msg.value } }));\n }\n if (msg.type === 'state-update-all' && msg.record && typeof msg.record === 'object') {\n var keys = Object.keys(msg.record);\n for (var i = 0; i < keys.length; i++) {\n var k = keys[i];\n document.dispatchEvent(new CustomEvent('sandbox:state-update', { detail: { key: k, value: msg.record[k] } }));\n }\n }\n if (msg.type === 'style-patch' && msg.id && typeof msg.css === 'string') {\n var el = document.getElementById(msg.id);\n if (el && el.tagName === 'STYLE') el.textContent = msg.css;\n }\n if (msg.type === 'html-replace' && typeof msg.html === 'string') {\n document.body.innerHTML = msg.html;\n }\n});\nfunction post(msg) {\n msg.channel = __sandboxChannel;\n msg.generation = __sandboxGeneration;\n parent.postMessage(msg, '*');\n}\nwindow.onerror = function(message, _src, _line, _col, err) {\n post({ type: 'error', message: String(message), stack: err && err.stack });\n return true;\n};\nwindow.addEventListener('unhandledrejection', function(e) {\n var reason = e.reason;\n post({ type: 'error', message: String(reason), stack: reason && reason.stack });\n});\nwindow.__sandbox__ = {\n emit: function(event, detail) {\n post({ type: 'custom', event: String(event), detail: detail });\n },\n onState: function(key, handler) {\n function listener(e) {\n if (e.detail && e.detail.key === key) handler(e.detail.value);\n }\n document.addEventListener('sandbox:state-update', listener);\n return function() {\n document.removeEventListener('sandbox:state-update', listener);\n };\n }\n};\npost({ type: 'ready' });\nif (typeof ResizeObserver !== 'undefined') {\n new ResizeObserver(function(entries) {\n var entry = entries[0];\n if (!entry) return;\n var h = entry.borderBoxSize && entry.borderBoxSize[0]\n ? entry.borderBoxSize[0].blockSize\n : entry.contentRect.height;\n post({ type: 'resize', height: h });\n }).observe(document.body);\n}\n`.trim();\n}\n","import type { BridgeBootstrap } from './_protocol.js';\nimport type { SandboxOptions } from './types.js';\n\nimport { buildBridgeScript } from './_bridge.js';\nimport { buildCspFromOptions, normalizeSandboxOptions, type NormalizedSandboxOptions } from './_policy.js';\n\nfunction escapeCss(css: string): string {\n return css.replace(/<\\/style/gi, '\\\\3C /style');\n}\n\nfunction escapeAttr(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\nfunction buildDocumentFromOptions(html: string, options: NormalizedSandboxOptions, bootstrap: BridgeBootstrap): string {\n const csp = escapeAttr(buildCspFromOptions(options));\n const styles = Object.entries(options.namedStyles)\n .map(([id, css]) => `<style id=\"${escapeAttr(id)}\">${escapeCss(css)}</style>`)\n .join('\\n');\n const scripts = options.scripts\n .map((src) => `<script crossorigin=\"anonymous\" src=\"${escapeAttr(src)}\"></script>`)\n .join('\\n');\n const nonce = options.nonce ? ` nonce=\"${escapeAttr(options.nonce)}\"` : '';\n const bootstrapScript = `<script${nonce}>window.__sandboxChannel=${JSON.stringify(bootstrap.channel)};window.__sandboxGeneration=${bootstrap.generation};</script>`;\n\n return `<!doctype html>\n<html lang=\"${escapeAttr(options.lang)}\">\n<head>\n<meta http-equiv=\"Content-Security-Policy\" content=\"${csp}\">\n${bootstrapScript}\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeText(options.title)}</title>\n${styles}\n</head>\n<body>\n${scripts}\n${html}\n<script${nonce}>\n${buildBridgeScript(bootstrap)}\n</script>\n</body>\n</html>`;\n}\n\nexport function buildDocument(html: string, options: SandboxOptions = {}): string {\n return buildDocumentFromOptions(html, normalizeSandboxOptions(options), { channel: '', generation: 0 });\n}\n\nexport function buildRuntimeDocument(\n html: string,\n options: NormalizedSandboxOptions,\n bootstrap: BridgeBootstrap,\n): string {\n return buildDocumentFromOptions(html, options, bootstrap);\n}\n","const isDev = !(globalThis as { __SANDBOX_PROD__?: boolean }).__SANDBOX_PROD__;\n\n/** @internal @security — some callers interpolate user-supplied values (e.g. namedStyles key) into msg; do not log PII. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/sandbox] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","export const MSG_CUSTOM = 'custom';\nexport const MSG_ERROR = 'error';\nexport const MSG_HTML_REPLACE = 'html-replace';\nexport const MSG_READY = 'ready';\nexport const MSG_RESIZE = 'resize';\nexport const MSG_STATE_UPDATE = 'state-update';\nexport const MSG_STATE_UPDATE_ALL = 'state-update-all';\nexport const MSG_STYLE_PATCH = 'style-patch';\n\nexport interface BridgeBootstrap {\n channel: string;\n generation: number;\n}\n\nexport type ProtocolMessage = Record<string, unknown> & { channel: string; generation: number; type: string };\n\nexport function isProtocolMessage(value: unknown, bootstrap: BridgeBootstrap): value is ProtocolMessage {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { channel?: unknown }).channel === 'string' &&\n (value as { channel: string }).channel === bootstrap.channel &&\n typeof (value as { generation?: unknown }).generation === 'number' &&\n (value as { generation: number }).generation === bootstrap.generation &&\n typeof (value as { type?: unknown }).type === 'string'\n );\n}\n\nexport function createChannel(): string {\n if (typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n\n const bytes = crypto.getRandomValues(new Uint32Array(4));\n\n return Array.from(bytes, (byte) => byte.toString(16).padStart(8, '0')).join('');\n}\n\nexport function envelope(bootstrap: BridgeBootstrap, message: Record<string, unknown>): ProtocolMessage {\n return { ...message, channel: bootstrap.channel, generation: bootstrap.generation } as ProtocolMessage;\n}\n","import type { SandboxHandle, SandboxMessage, SandboxOptions, Unsubscribe } from './types.js';\n\nimport { devOnly, warn } from './_dev.js';\nimport { buildRuntimeDocument } from './_document.js';\nimport { normalizeSandboxOptions } from './_policy.js';\nimport {\n createChannel,\n envelope,\n isProtocolMessage,\n MSG_CUSTOM,\n MSG_ERROR,\n MSG_HTML_REPLACE,\n MSG_READY,\n MSG_RESIZE,\n MSG_STATE_UPDATE,\n MSG_STATE_UPDATE_ALL,\n MSG_STYLE_PATCH,\n type BridgeBootstrap,\n} from './_protocol.js';\nimport { SandboxTimeoutError } from './errors.js';\n\nconst READY_TIMEOUT_MS = 5000;\n\nexport function createSandbox(container: HTMLElement, options: SandboxOptions = {}): SandboxHandle {\n const normalized = normalizeSandboxOptions(options);\n const channel = createChannel();\n const namedStyles: Record<string, string> = { ...normalized.namedStyles };\n const listeners = new Set<(message: SandboxMessage) => void>();\n const controller = new AbortController();\n let iframe: HTMLIFrameElement | null = null;\n let disposed = false;\n let bridgeReady = false;\n let generation = 0;\n let readyTimeout: ReturnType<typeof setTimeout> | null = null;\n let currentRender: { reject: (error: unknown) => void; resolve: () => void } | null = null;\n\n let resolveReady!: () => void;\n const ready = new Promise<void>((resolve) => {\n resolveReady = resolve;\n });\n\n function currentBootstrap(): BridgeBootstrap {\n return { channel, generation };\n }\n\n function clearReadyTimeout(): void {\n if (readyTimeout === null) return;\n\n clearTimeout(readyTimeout);\n readyTimeout = null;\n }\n\n function supersedePendingRender(): void {\n clearReadyTimeout();\n currentRender?.resolve();\n currentRender = null;\n bridgeReady = false;\n }\n\n function broadcast(message: SandboxMessage): void {\n for (const listener of listeners) listener(message);\n }\n\n function handleMessage(event: MessageEvent): void {\n if (!iframe || event.source !== iframe.contentWindow) return;\n\n const bootstrap = currentBootstrap();\n\n if (!isProtocolMessage(event.data, bootstrap)) return;\n\n switch (event.data.type) {\n case MSG_CUSTOM:\n if (typeof event.data.event === 'string') {\n broadcast({ detail: event.data.detail, event: event.data.event, type: MSG_CUSTOM });\n }\n\n break;\n case MSG_ERROR:\n if (typeof event.data.message === 'string') {\n broadcast({\n message: event.data.message,\n ...(typeof event.data.stack === 'string' ? { stack: event.data.stack } : {}),\n type: MSG_ERROR,\n });\n }\n\n break;\n case MSG_READY:\n supersedePendingRender();\n resolveReady();\n bridgeReady = true;\n break;\n case MSG_RESIZE:\n if (typeof event.data.height === 'number' && Number.isFinite(event.data.height) && event.data.height >= 0) {\n broadcast({ height: event.data.height, type: MSG_RESIZE });\n }\n\n break;\n }\n }\n\n function ensureIframe(): HTMLIFrameElement {\n if (iframe) return iframe;\n\n iframe = document.createElement('iframe');\n iframe.setAttribute('sandbox', 'allow-scripts');\n iframe.setAttribute('referrerpolicy', 'no-referrer');\n iframe.dataset.sandboxChannel = channel;\n container.appendChild(iframe);\n window.addEventListener('message', handleMessage);\n\n return iframe;\n }\n\n function dispose(): void {\n if (disposed) return;\n\n disposed = true;\n supersedePendingRender();\n controller.abort();\n resolveReady();\n\n if (iframe) {\n window.removeEventListener('message', handleMessage);\n iframe.remove();\n iframe = null;\n }\n\n listeners.clear();\n }\n\n function onMessage(handler: (message: SandboxMessage) => void): Unsubscribe {\n if (disposed) {\n warn('onMessage() called on a disposed sandbox — handler will never fire.');\n\n return () => {};\n }\n\n listeners.add(handler);\n\n return () => listeners.delete(handler);\n }\n\n function render(html: string, renderOptions?: { signal?: AbortSignal }): Promise<void> {\n if (disposed) {\n warn('render() called on a disposed sandbox.');\n\n return Promise.resolve();\n }\n\n if (renderOptions?.signal?.aborted) return Promise.resolve();\n\n if (!html.trim()) warn('render() called with empty HTML.');\n\n supersedePendingRender();\n generation += 1;\n\n const current = ensureIframe();\n const bootstrap = currentBootstrap();\n const documentOptions = { ...normalized, namedStyles: { ...namedStyles } };\n\n current.dataset.sandboxGeneration = String(generation);\n current.srcdoc = buildRuntimeDocument(html, documentOptions, bootstrap);\n\n const promise = new Promise<void>((resolve, reject) => {\n currentRender = { reject, resolve };\n });\n\n readyTimeout = setTimeout(() => {\n if (!currentRender) return;\n\n currentRender.reject(\n new SandboxTimeoutError(\n `render() did not receive a 'ready' signal from the sandbox document within ${READY_TIMEOUT_MS}ms. ` +\n 'The document is likely missing the bridge script — use buildDocument() to generate documents that include it.',\n ),\n );\n currentRender = null;\n readyTimeout = null;\n }, READY_TIMEOUT_MS);\n\n return promise;\n }\n\n function send(message: Record<string, unknown>): void {\n iframe?.contentWindow?.postMessage(envelope(currentBootstrap(), message), '*');\n }\n\n function replaceBody(html: string): void {\n if (disposed) {\n warn('replaceBody() called on a disposed sandbox.');\n\n return;\n }\n\n if (!bridgeReady || !iframe?.contentWindow) {\n warn('replaceBody() called before render() has resolved — bridge is not ready. Await render() first.');\n\n return;\n }\n\n send({ html, type: MSG_HTML_REPLACE });\n }\n\n function setState(key: string, value: unknown): void {\n if (disposed) {\n warn('setState() called on a disposed sandbox.');\n\n return;\n }\n\n if (!iframe?.contentWindow) {\n warn('setState() called before render() — sandbox has no document yet.');\n\n return;\n }\n\n if (!bridgeReady) {\n warn('setState() called before ready — state update may be lost. Await render() first.');\n }\n\n send({ key, type: MSG_STATE_UPDATE, value });\n }\n\n function setStateAll(record: Record<string, unknown>): void {\n if (disposed) {\n warn('setStateAll() called on a disposed sandbox.');\n\n return;\n }\n\n if (!iframe?.contentWindow) {\n warn('setStateAll() called before render() — sandbox has no document yet.');\n\n return;\n }\n\n if (!bridgeReady) {\n warn('setStateAll() called before ready — state updates may be lost. Await render() first.');\n }\n\n send({ record, type: MSG_STATE_UPDATE_ALL });\n }\n\n function updateStyle(id: string, css: string): void {\n if (disposed) return;\n\n devOnly(() => {\n if (!(id in namedStyles)) {\n warn(`updateStyle('${id}', …) — '${id}' is not a known namedStyles key.`);\n }\n });\n\n namedStyles[id] = css;\n\n if (bridgeReady && iframe?.contentWindow) send({ css, id, type: MSG_STYLE_PATCH });\n }\n\n return {\n get disposalSignal() {\n return controller.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n onMessage,\n ready,\n render,\n replaceBody,\n setState,\n setStateAll,\n [Symbol.dispose]() {\n dispose();\n },\n updateStyle,\n };\n}\n","import type { SandboxOptions } from './types.js';\n\nimport { buildCspFromOptions, normalizeSandboxOptions } from './_policy.js';\n\nexport { buildDocument } from './_document.js';\nexport { createSandbox } from './_runtime.js';\n\nexport function buildCsp(options: SandboxOptions = {}): string {\n return buildCspFromOptions(normalizeSandboxOptions(options));\n}\n"],"mappings":"AACA,IAAa,EAAb,MAAa,UAAqB,KAAM,CACtC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CAEA,OAAO,GAAG,EAAmC,CAC3C,OAAO,aAAe,CACxB,CACF,EAOa,EAAb,cAAyC,CAAa,CAAC,EAG1C,EAAb,cAA+C,CAAa,CAAC,ECLvD,EAAK,2BACL,EAAO,wCACP,EAAQ,2BAEd,SAAS,EAAK,EAAe,EAAuB,CAClD,MAAM,IAAI,EAA0B,GAAG,EAAM,eAAe,OAAO,CAAK,GAAG,CAC7E,CAEA,SAAS,EAAgB,EAAe,EAAuB,CAC7D,IAAI,EAEJ,GAAI,CACF,EAAM,IAAI,IAAI,CAAK,CACrB,MAAQ,CACN,OAAO,EAAK,EAAO,CAAK,CAC1B,CAaA,OAVG,EAAI,WAAa,SAAW,EAAI,WAAa,UAC9C,EAAI,UACJ,EAAI,UACJ,EAAI,WAAa,KACjB,EAAI,QACJ,EAAI,KAEG,EAAK,EAAO,CAAK,EAGnB,EAAI,MACb,CAEA,SAAS,EAAiB,EAA8B,EAAyB,CAC/E,OAAQ,GAAU,CAAC,EAAA,CAAG,IAAK,GAAU,EAAgB,EAAO,CAAK,CAAC,CACpE,CAEA,SAAS,EAAgB,EAAuB,CAC9C,IAAI,EAEJ,GAAI,CACF,EAAM,IAAI,IAAI,CAAK,CACrB,MAAQ,CACN,OAAO,EAAK,UAAW,CAAK,CAC9B,CAIA,OAFI,EAAI,WAAa,SAAW,EAAI,WAAa,SAAiB,EAAK,UAAW,CAAK,EAEhF,EAAI,IACb,CAEA,SAAS,EAAgB,EAAoE,CAC3F,IAAM,EAAqC,CAAC,EAE5C,IAAK,GAAM,CAAC,EAAI,KAAQ,OAAO,QAAQ,GAAU,CAAC,CAAC,GAC7C,CAAC,EAAG,KAAK,CAAE,GAAK,OAAO,GAAQ,WAAU,EAAK,cAAe,CAAE,EAEnE,EAAW,GAAM,EAGnB,OAAO,CACT,CAEA,SAAgB,EAAwB,EAA0B,CAAC,EAA6B,CAC9F,IAAM,EAAO,EAAQ,MAAQ,KAExB,EAAK,KAAK,CAAI,GAAG,EAAK,OAAQ,CAAI,EAEnC,EAAQ,QAAU,IAAA,KAAc,CAAC,EAAM,KAAK,EAAQ,KAAK,GAAK,EAAQ,MAAM,SAAW,IACzF,EAAK,QAAS,EAAQ,KAAK,EAGzB,EAAQ,QAAU,IAAA,IAAa,OAAO,EAAQ,OAAU,UAAU,EAAK,QAAS,EAAQ,KAAK,EAEjG,IAAM,GAAW,EAAQ,SAAW,CAAC,EAAA,CAAG,IAAI,CAAe,EAE3D,MAAO,CACL,mBAAoB,EAAiB,EAAQ,mBAAoB,oBAAoB,EACrF,oBAAqB,EAAiB,EAAQ,oBAAqB,qBAAqB,EACxF,qBAAsB,EAAiB,EAAQ,qBAAsB,sBAAsB,EAC3F,oBAAqB,EAAiB,EAAQ,oBAAqB,qBAAqB,EACxF,OACA,YAAa,EAAgB,EAAQ,WAAW,EAChD,MAAO,EAAQ,MACf,UACA,MAAO,EAAQ,OAAS,EAC1B,CACF,CAEA,SAAgB,EAAoB,EAA2C,CAC7E,IAAM,EAAgB,CACpB,kBACA,GAAI,EAAQ,MAAQ,CAAC,UAAU,EAAQ,MAAM,EAAE,EAAI,CAAC,EACpD,GAAG,EAAQ,qBACX,GAAG,EAAQ,QAAQ,IAAK,GAAW,IAAI,IAAI,CAAM,CAAC,CAAC,MAAM,CAC3D,CAAC,CAAC,KAAK,GAAG,EACJ,EAAe,CAAC,kBAAmB,GAAG,EAAQ,mBAAmB,CAAC,CAAC,KAAK,GAAG,EAC3E,EAAe,CAAC,QAAS,GAAG,EAAQ,mBAAmB,CAAC,CAAC,KAAK,GAAG,EACjE,EAAc,EAAQ,mBAAmB,KAAK,GAAG,GAAK,SAE5D,MAAO,CACL,qBACA,cAAc,IACd,aAAa,IACb,WAAW,IACX,YAAY,IACZ,qBACA,qBACA,iBACF,CAAC,CAAC,KAAK,IAAI,CACb,CC1HA,SAAgB,EAAkB,EAAoC,CAIpE,MAAO;4BAHS,KAAK,UAAU,EAAU,OAIf,EAAQ;+BAHf,OAAO,EAAU,UAIP,EAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+DxC,KAAK,CACP,CClEA,SAAS,EAAU,EAAqB,CACtC,OAAO,EAAI,QAAQ,aAAc,aAAa,CAChD,CAEA,SAAS,EAAW,EAAuB,CACzC,OAAO,EAAM,QAAQ,KAAM,OAAO,CAAC,CAAC,QAAQ,KAAM,QAAQ,CAAC,CAAC,QAAQ,KAAM,MAAM,CAAC,CAAC,QAAQ,KAAM,MAAM,CACxG,CAEA,SAAS,EAAW,EAAuB,CACzC,OAAO,EAAM,QAAQ,KAAM,OAAO,CAAC,CAAC,QAAQ,KAAM,MAAM,CAAC,CAAC,QAAQ,KAAM,MAAM,CAChF,CAEA,SAAS,EAAyB,EAAc,EAAmC,EAAoC,CACrH,IAAM,EAAM,EAAW,EAAoB,CAAO,CAAC,EAC7C,EAAS,OAAO,QAAQ,EAAQ,WAAW,CAAC,CAC/C,KAAK,CAAC,EAAI,KAAS,cAAc,EAAW,CAAE,EAAE,IAAI,EAAU,CAAG,EAAE,SAAS,CAAC,CAC7E,KAAK;CAAI,EACN,EAAU,EAAQ,QACrB,IAAK,GAAQ,wCAAwC,EAAW,CAAG,EAAE,aAAY,CAAC,CAClF,KAAK;CAAI,EACN,EAAQ,EAAQ,MAAQ,WAAW,EAAW,EAAQ,KAAK,EAAE,GAAK,GAClE,EAAkB,UAAU,EAAM,2BAA2B,KAAK,UAAU,EAAU,OAAO,EAAE,8BAA8B,EAAU,WAAW,aAExJ,MAAO;cACK,EAAW,EAAQ,IAAI,EAAE;;sDAEe,EAAI;EACxD,EAAgB;;;SAGT,EAAW,EAAQ,KAAK,EAAE;EACjC,EAAO;;;EAGP,EAAQ;EACR,EAAK;SACE,EAAM;EACb,EAAkB,CAAS,EAAE;;;QAI/B,CAEA,SAAgB,EAAc,EAAc,EAA0B,CAAC,EAAW,CAChF,OAAO,EAAyB,EAAM,EAAwB,CAAO,EAAG,CAAE,QAAS,GAAI,WAAY,CAAE,CAAC,CACxG,CAEA,SAAgB,EACd,EACA,EACA,EACQ,CACR,OAAO,EAAyB,EAAM,EAAS,CAAS,CAC1D,CE3DA,IAAa,EAAa,SACb,EAAY,QACZ,EAAmB,eACnB,EAAY,QACZ,EAAa,SACb,EAAmB,eACnB,EAAuB,mBACvB,EAAkB,cAS/B,SAAgB,EAAkB,EAAgB,EAAsD,CACtG,OACE,OAAO,GAAU,YACjB,GACA,OAAQ,EAAgC,SAAY,UACnD,EAA8B,UAAY,EAAU,SACrD,OAAQ,EAAmC,YAAe,UACzD,EAAiC,aAAe,EAAU,YAC3D,OAAQ,EAA6B,MAAS,QAElD,CAEA,SAAgB,GAAwB,CACtC,GAAI,OAAO,OAAO,YAAe,WAAY,OAAO,OAAO,WAAW,EAEtE,IAAM,EAAQ,OAAO,gBAAgB,IAAI,YAAY,CAAC,CAAC,EAEvD,OAAO,MAAM,KAAK,EAAQ,GAAS,EAAK,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAChF,CAEA,SAAgB,EAAS,EAA4B,EAAmD,CACtG,MAAO,CAAE,GAAG,EAAS,QAAS,EAAU,QAAS,WAAY,EAAU,UAAW,CACpF,CCjBA,IAAM,EAAmB,IAEzB,SAAgB,EAAc,EAAwB,EAA0B,CAAC,EAAkB,CACjG,IAAM,EAAa,EAAwB,CAAO,EAC5C,EAAU,EAAc,EACxB,EAAsC,CAAE,GAAG,EAAW,WAAY,EAClE,EAAY,IAAI,IAChB,EAAa,IAAI,gBACnB,EAAmC,KACnC,EAAW,GACX,EAAc,GACd,EAAa,EACb,EAAqD,KACrD,EAAkF,KAElF,EACE,EAAQ,IAAI,QAAe,GAAY,CAC3C,EAAe,CACjB,CAAC,EAED,SAAS,GAAoC,CAC3C,MAAO,CAAE,UAAS,YAAW,CAC/B,CAEA,SAAS,GAA0B,CAC7B,IAAiB,OAErB,aAAa,CAAY,EACzB,EAAe,KACjB,CAEA,SAAS,GAA+B,CACtC,EAAkB,EAClB,GAAe,QAAQ,EACvB,EAAgB,KAChB,EAAc,EAChB,CAEA,SAAS,EAAU,EAA+B,CAChD,IAAK,IAAM,KAAY,EAAW,EAAS,CAAO,CACpD,CAEA,SAAS,EAAc,EAA2B,CAChD,GAAI,CAAC,GAAU,EAAM,SAAW,EAAO,cAAe,OAEtD,IAAM,EAAY,EAAiB,EAE9B,KAAkB,EAAM,KAAM,CAAS,EAE5C,OAAQ,EAAM,KAAK,KAAnB,CACE,KAAK,EACC,OAAO,EAAM,KAAK,OAAU,UAC9B,EAAU,CAAE,OAAQ,EAAM,KAAK,OAAQ,MAAO,EAAM,KAAK,MAAO,KAAM,CAAW,CAAC,EAGpF,MACF,KAAK,EACC,OAAO,EAAM,KAAK,SAAY,UAChC,EAAU,CACR,QAAS,EAAM,KAAK,QACpB,GAAI,OAAO,EAAM,KAAK,OAAU,SAAW,CAAE,MAAO,EAAM,KAAK,KAAM,EAAI,CAAC,EAC1E,KAAM,CACR,CAAC,EAGH,MACF,KAAK,EACH,EAAuB,EACvB,EAAa,EACb,EAAc,GACd,MACF,KAAK,EACC,OAAO,EAAM,KAAK,QAAW,UAAY,OAAO,SAAS,EAAM,KAAK,MAAM,GAAK,EAAM,KAAK,QAAU,GACtG,EAAU,CAAE,OAAQ,EAAM,KAAK,OAAQ,KAAM,CAAW,CAAC,CAI/D,CACF,CAEA,SAAS,GAAkC,CAUzC,OATI,IAEJ,EAAS,SAAS,cAAc,QAAQ,EACxC,EAAO,aAAa,UAAW,eAAe,EAC9C,EAAO,aAAa,iBAAkB,aAAa,EACnD,EAAO,QAAQ,eAAiB,EAChC,EAAU,YAAY,CAAM,EAC5B,OAAO,iBAAiB,UAAW,CAAa,EAEzC,EACT,CAEA,SAAS,GAAgB,CACnB,IAEJ,EAAW,GACX,EAAuB,EACvB,EAAW,MAAM,EACjB,EAAa,EAEb,AAGE,KAFA,OAAO,oBAAoB,UAAW,CAAa,EACnD,EAAO,OAAO,EACL,MAGX,EAAU,MAAM,EAClB,CAEA,SAAS,EAAU,EAAyD,CAS1E,OARI,MAGW,CAAC,GAGhB,EAAU,IAAI,CAAO,MAER,EAAU,OAAO,CAAO,EACvC,CAEA,SAAS,EAAO,EAAc,EAAyD,CAOrF,GANI,GAMA,GAAe,QAAQ,QAAS,OAAO,QAAQ,QAAQ,EAEtD,EAAK,KAAK,EAEf,EAAuB,EACvB,GAAc,EAEd,IAAM,EAAU,EAAa,EACvB,EAAY,EAAiB,EAC7B,EAAkB,CAAE,GAAG,EAAY,YAAa,CAAE,GAAG,CAAY,CAAE,EAEzE,EAAQ,QAAQ,kBAAoB,OAAO,CAAU,EACrD,EAAQ,OAAS,EAAqB,EAAM,EAAiB,CAAS,EAEtE,IAAM,EAAU,IAAI,SAAe,EAAS,IAAW,CACrD,EAAgB,CAAE,SAAQ,SAAQ,CACpC,CAAC,EAeD,MAbA,GAAe,eAAiB,CACzB,IAEL,EAAc,OACZ,IAAI,EACF,8EAA8E,EAAiB,kHAEjG,CACF,EACA,EAAgB,KAChB,EAAe,KACjB,EAAG,CAAgB,EAEZ,CACT,CAEA,SAAS,EAAK,EAAwC,CACpD,GAAQ,eAAe,YAAY,EAAS,EAAiB,EAAG,CAAO,EAAG,GAAG,CAC/E,CAEA,SAAS,EAAY,EAAoB,CACnC,GAMA,CAAC,GAAe,CAAC,GAAQ,eAM7B,EAAK,CAAE,OAAM,KAAM,CAAiB,CAAC,CACvC,CAEA,SAAS,EAAS,EAAa,EAAsB,CAC/C,GAMC,GAAQ,eAUb,EAAK,CAAE,MAAK,KAAM,EAAkB,OAAM,CAAC,CAC7C,CAEA,SAAS,EAAY,EAAuC,CACtD,GAMC,GAAQ,eAUb,EAAK,CAAE,SAAQ,KAAM,CAAqB,CAAC,CAC7C,CAEA,SAAS,EAAY,EAAY,EAAmB,CAC9C,IAQJ,EAAY,GAAM,EAEd,GAAe,GAAQ,eAAe,EAAK,CAAE,MAAK,KAAI,KAAM,CAAgB,CAAC,EACnF,CAEA,MAAO,CACL,IAAI,gBAAiB,CACnB,OAAO,EAAW,MACpB,EACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,YACA,QACA,SACA,cACA,WACA,cACA,CAAC,OAAO,UAAW,CACjB,EAAQ,CACV,EACA,aACF,CACF,CC9QA,SAAgB,EAAS,EAA0B,CAAC,EAAW,CAC7D,OAAO,EAAoB,EAAwB,CAAO,CAAC,CAC7D"}
|
|
1
|
+
{"version":3,"file":"sandbox.js","names":[],"sources":["../src/_dev.ts","../src/_bridge.ts","../src/errors.ts","../src/_policy.ts","../src/_document.ts","../src/_protocol.ts","../src/_runtime.ts"],"sourcesContent":["const isDev = !(globalThis as { __SANDBOX_PROD__?: boolean }).__SANDBOX_PROD__;\n\n/** @internal @security — some callers interpolate user-supplied values (e.g. styles key) into msg; do not log PII. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/sandbox] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import type { BridgeBootstrap } from './_protocol.js';\n\nexport function buildBridgeScript(bootstrap: BridgeBootstrap): string {\n const channel = JSON.stringify(bootstrap.channel);\n const generation = String(bootstrap.generation);\n\n return `\n(function() {\nwindow.__sandboxChannel = ${channel};\nwindow.__sandboxGeneration = ${generation};\nconst __sandboxChannel = window.__sandboxChannel;\nconst __sandboxGeneration = window.__sandboxGeneration;\nif ('ontouchstart' in window) document.addEventListener('touchstart', function() {}, { passive: true });\nwindow.addEventListener('message', function(e) {\n var msg = e.data;\n if (e.source !== parent || !msg || msg.channel !== __sandboxChannel || msg.generation !== __sandboxGeneration) return;\n if (msg.type === 'state-update-all' && msg.record && typeof msg.record === 'object') {\n var keys = Object.keys(msg.record);\n for (var i = 0; i < keys.length; i++) {\n var k = keys[i];\n document.dispatchEvent(new CustomEvent('sandbox:state-update', { detail: { key: k, value: msg.record[k] } }));\n }\n }\n if (msg.type === 'style-patch' && msg.id && typeof msg.css === 'string') {\n var el = document.getElementById(msg.id);\n if (el && el.tagName === 'STYLE') el.textContent = msg.css;\n }\n if (msg.type === 'html-replace' && typeof msg.html === 'string') {\n document.body.innerHTML = msg.html;\n }\n});\nfunction post(msg) {\n msg.channel = __sandboxChannel;\n msg.generation = __sandboxGeneration;\n parent.postMessage(msg, '*');\n}\nwindow.onerror = function(message, _src, _line, _col, err) {\n post({ type: 'error', message: String(message), stack: err && err.stack });\n return true;\n};\nwindow.addEventListener('unhandledrejection', function(e) {\n var reason = e.reason;\n post({ type: 'error', message: String(reason), stack: reason && reason.stack });\n});\nwindow.__sandbox__ = {\n emit: function(event, detail) {\n post({ type: 'custom', event: String(event), detail: detail });\n },\n onState: function(key, handler) {\n function listener(e) {\n if (e.detail && e.detail.key === key) handler(e.detail.value);\n }\n document.addEventListener('sandbox:state-update', listener);\n return function() {\n document.removeEventListener('sandbox:state-update', listener);\n };\n }\n};\nwindow.addEventListener('DOMContentLoaded', function() {\n post({ type: 'ready' });\n if (typeof ResizeObserver === 'undefined') return;\n new ResizeObserver(function(entries) {\n var entry = entries[0];\n if (!entry) return;\n var h = entry.borderBoxSize && entry.borderBoxSize[0]\n ? entry.borderBoxSize[0].blockSize\n : entry.contentRect.height;\n post({ type: 'resize', height: h });\n }).observe(document.body);\n});\n})();\n`.trim();\n}\n","/** Base class for all sandbox errors. */\nexport class SandboxError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Thrown when render() does not receive a 'ready' signal from the sandbox document\n * within the timeout window. An injected script may have blocked document initialization.\n */\nexport class SandboxTimeoutError extends SandboxError {}\n\n/** Thrown when Sandbox configuration cannot produce a valid document or CSP policy. */\nexport class SandboxConfigurationError extends SandboxError {}\n","import { SandboxConfigurationError } from './errors.js';\nimport type { SandboxOptions } from './types.js';\n\nexport interface NormalizedSandboxOptions {\n allowedFontOrigins: string[];\n allowedImageOrigins: string[];\n allowedScriptOrigins: string[];\n allowedStyleOrigins: string[];\n lang: string;\n nonce: string | undefined;\n readyTimeout: number;\n scripts: string[];\n styles: Record<string, string>;\n title: string;\n}\n\nexport const DEFAULT_READY_TIMEOUT_MS = 5000;\n\nconst ID = /^[A-Za-z][A-Za-z0-9_-]*$/;\nconst LANG = /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;\nconst NONCE = /^[A-Za-z0-9+/_-]+={0,2}$/;\n\nfunction fail(field: string, value: unknown): never {\n throw new SandboxConfigurationError(`${field} is invalid: ${String(value)}`);\n}\n\nfunction normalizeOrigin(value: unknown, field: string): string {\n if (typeof value !== 'string') return fail(field, value);\n\n let url: URL;\n\n try {\n url = new URL(value);\n } catch {\n return fail(field, value);\n }\n\n if (\n (url.protocol !== 'http:' && url.protocol !== 'https:') ||\n url.username ||\n url.password ||\n url.pathname !== '/' ||\n url.search ||\n url.hash\n ) {\n return fail(field, value);\n }\n\n return url.origin;\n}\n\nfunction normalizeOrigins(values: string[] | undefined, field: string): string[] {\n if (values === undefined) return [];\n if (!Array.isArray(values)) return fail(field, values);\n\n return values.map((value) => normalizeOrigin(value, field));\n}\n\nfunction normalizeScript(value: unknown): string {\n if (typeof value !== 'string') return fail('scripts', value);\n\n let url: URL;\n\n try {\n url = new URL(value);\n } catch {\n return fail('scripts', value);\n }\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') return fail('scripts', value);\n\n return url.href;\n}\n\nfunction normalizeStyles(styles: Record<string, string> | undefined): Record<string, string> {\n if (styles === undefined) return {};\n if (typeof styles !== 'object' || styles === null || Array.isArray(styles)) return fail('styles', styles);\n\n const normalized: Record<string, string> = {};\n\n for (const [id, css] of Object.entries(styles)) {\n if (!ID.test(id) || typeof css !== 'string') fail('styles', id);\n\n normalized[id] = css;\n }\n\n return normalized;\n}\n\nexport function normalizeSandboxOptions(options: SandboxOptions = {}): NormalizedSandboxOptions {\n if (typeof options !== 'object' || options === null || Array.isArray(options)) fail('options', options);\n\n const lang = options.lang ?? 'en';\n\n if (!LANG.test(lang)) fail('lang', lang);\n\n if (options.nonce !== undefined && (!NONCE.test(options.nonce) || options.nonce.length === 0)) {\n fail('nonce', options.nonce);\n }\n\n if (options.title !== undefined && typeof options.title !== 'string') fail('title', options.title);\n\n const rawReadyTimeout = options.readyTimeout;\n const readyTimeout = rawReadyTimeout ?? DEFAULT_READY_TIMEOUT_MS;\n\n if (\n rawReadyTimeout !== undefined &&\n (rawReadyTimeout === null ||\n typeof rawReadyTimeout !== 'number' ||\n !Number.isFinite(rawReadyTimeout) ||\n rawReadyTimeout <= 0 ||\n rawReadyTimeout > 2 ** 31 - 1)\n ) {\n fail('readyTimeout', rawReadyTimeout);\n }\n\n if (options.scripts !== undefined && !Array.isArray(options.scripts)) fail('scripts', options.scripts);\n\n const scripts = (options.scripts ?? []).map(normalizeScript);\n\n return {\n allowedFontOrigins: normalizeOrigins(options.allowedFontOrigins, 'allowedFontOrigins'),\n allowedImageOrigins: normalizeOrigins(options.allowedImageOrigins, 'allowedImageOrigins'),\n allowedScriptOrigins: normalizeOrigins(options.allowedScriptOrigins, 'allowedScriptOrigins'),\n allowedStyleOrigins: normalizeOrigins(options.allowedStyleOrigins, 'allowedStyleOrigins'),\n lang,\n nonce: options.nonce,\n readyTimeout,\n scripts,\n styles: normalizeStyles(options.styles),\n title: options.title ?? '',\n };\n}\n\nexport function buildCspFromOptions(options: NormalizedSandboxOptions): string {\n const scriptOrigins = [\n \"'unsafe-inline'\",\n ...(options.nonce ? [`'nonce-${options.nonce}'`] : []),\n ...options.allowedScriptOrigins,\n ...options.scripts.map((script) => new URL(script).origin),\n ].join(' ');\n const styleOrigins = [\"'unsafe-inline'\", ...options.allowedStyleOrigins].join(' ');\n const imageOrigins = ['data:', ...options.allowedImageOrigins].join(' ');\n const fontOrigins = options.allowedFontOrigins.join(' ') || \"'none'\";\n\n return [\n \"default-src 'none'\",\n `script-src ${scriptOrigins}`,\n `style-src ${styleOrigins}`,\n `img-src ${imageOrigins}`,\n `font-src ${fontOrigins}`,\n \"connect-src 'none'\",\n \"form-action 'none'\",\n \"base-uri 'none'\",\n ].join('; ');\n}\n\nexport function buildCsp(options: SandboxOptions = {}): string {\n return buildCspFromOptions(normalizeSandboxOptions(options));\n}\n","import { buildBridgeScript } from './_bridge.js';\nimport { buildCspFromOptions, type NormalizedSandboxOptions, normalizeSandboxOptions } from './_policy.js';\nimport type { BridgeBootstrap } from './_protocol.js';\nimport type { SandboxOptions } from './types.js';\n\nfunction escapeCss(css: string): string {\n return css.replace(/<\\/style/gi, '\\\\3C /style');\n}\n\nfunction escapeAttr(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\nexport function buildDocumentFromOptions(\n html: string,\n options: NormalizedSandboxOptions,\n bootstrap: BridgeBootstrap,\n): string {\n const csp = escapeAttr(buildCspFromOptions(options));\n const styles = Object.entries(options.styles)\n .map(([id, css]) => `<style id=\"${escapeAttr(id)}\">${escapeCss(css)}</style>`)\n .join('\\n');\n const scripts = options.scripts\n .map((src) => `<script crossorigin=\"anonymous\" src=\"${escapeAttr(src)}\"></script>`)\n .join('\\n');\n const nonce = options.nonce ? ` nonce=\"${escapeAttr(options.nonce)}\"` : '';\n\n return `<!doctype html>\n<html lang=\"${escapeAttr(options.lang)}\">\n<head>\n<meta http-equiv=\"Content-Security-Policy\" content=\"${csp}\">\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeText(options.title)}</title>\n${styles}\n<script${nonce}>\n${buildBridgeScript(bootstrap)}\n</script>\n</head>\n<body>\n${scripts}\n${html}\n</body>\n</html>`;\n}\n\nexport function buildDocument(html: string, options: SandboxOptions = {}): string {\n return buildDocumentFromOptions(html, normalizeSandboxOptions(options), { channel: '', generation: 0 });\n}\n","export const MSG_CUSTOM = 'custom';\nexport const MSG_ERROR = 'error';\nexport const MSG_HTML_REPLACE = 'html-replace';\nexport const MSG_READY = 'ready';\nexport const MSG_RESIZE = 'resize';\nexport const MSG_STATE_UPDATE_ALL = 'state-update-all';\nexport const MSG_STYLE_PATCH = 'style-patch';\n\nexport interface BridgeBootstrap {\n channel: string;\n generation: number;\n}\n\nexport type ProtocolMessage = Record<string, unknown> & { channel: string; generation: number; type: string };\n\nexport function isProtocolMessage(value: unknown, bootstrap: BridgeBootstrap): value is ProtocolMessage {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { channel?: unknown }).channel === 'string' &&\n (value as { channel: string }).channel === bootstrap.channel &&\n typeof (value as { generation?: unknown }).generation === 'number' &&\n (value as { generation: number }).generation === bootstrap.generation &&\n typeof (value as { type?: unknown }).type === 'string'\n );\n}\n\nexport function createChannel(): string {\n if (typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n\n const bytes = crypto.getRandomValues(new Uint32Array(4));\n\n return Array.from(bytes, (byte) => byte.toString(16).padStart(8, '0')).join('');\n}\n\nexport function envelope(bootstrap: BridgeBootstrap, message: Record<string, unknown>): ProtocolMessage {\n return { ...message, channel: bootstrap.channel, generation: bootstrap.generation } as ProtocolMessage;\n}\n","import { devOnly, warn } from './_dev.js';\nimport { buildDocumentFromOptions } from './_document.js';\nimport { normalizeSandboxOptions } from './_policy.js';\nimport {\n type BridgeBootstrap,\n createChannel,\n envelope,\n isProtocolMessage,\n MSG_CUSTOM,\n MSG_ERROR,\n MSG_HTML_REPLACE,\n MSG_READY,\n MSG_RESIZE,\n MSG_STATE_UPDATE_ALL,\n MSG_STYLE_PATCH,\n} from './_protocol.js';\nimport { SandboxTimeoutError } from './errors.js';\nimport type { SandboxHandle, SandboxMessage, SandboxOptions } from './types.js';\n\nexport function createSandbox<State extends object = Record<string, unknown>>(\n container: HTMLElement,\n options: SandboxOptions = {},\n): SandboxHandle<State> {\n const normalized = normalizeSandboxOptions(options);\n const channel = createChannel();\n const styles: Record<string, string> = { ...normalized.styles };\n const listeners = new Set<(message: SandboxMessage) => void>();\n const controller = new AbortController();\n let iframe: HTMLIFrameElement | null = null;\n let disposed = false;\n let bridgeReady = false;\n let generation = 0;\n let readyTimeout: ReturnType<typeof setTimeout> | null = null;\n let currentRender: { reject: (error: unknown) => void; resolve: () => void } | null = null;\n\n function currentBootstrap(): BridgeBootstrap {\n return { channel, generation };\n }\n\n function clearReadyTimeout(): void {\n if (readyTimeout === null) return;\n\n clearTimeout(readyTimeout);\n readyTimeout = null;\n }\n\n function supersedePendingRender(): void {\n clearReadyTimeout();\n currentRender?.resolve();\n currentRender = null;\n bridgeReady = false;\n }\n\n function broadcast(message: SandboxMessage): void {\n for (const listener of listeners) {\n try {\n listener(message);\n } catch {\n warn('onMessage() handler threw — remaining handlers will still run.');\n }\n }\n }\n\n function handleMessage(event: MessageEvent): void {\n if (!iframe || event.source !== iframe.contentWindow) return;\n\n const bootstrap = currentBootstrap();\n\n if (!isProtocolMessage(event.data, bootstrap)) return;\n\n switch (event.data.type) {\n case MSG_CUSTOM:\n if (typeof event.data.event === 'string') {\n broadcast({ detail: event.data.detail, event: event.data.event, type: MSG_CUSTOM });\n }\n\n break;\n case MSG_ERROR:\n if (typeof event.data.message === 'string') {\n broadcast({\n message: event.data.message,\n ...(typeof event.data.stack === 'string' ? { stack: event.data.stack } : {}),\n type: MSG_ERROR,\n });\n }\n\n break;\n case MSG_READY:\n supersedePendingRender();\n bridgeReady = true;\n break;\n case MSG_RESIZE:\n if (typeof event.data.height === 'number' && Number.isFinite(event.data.height) && event.data.height >= 0) {\n broadcast({ height: event.data.height, type: MSG_RESIZE });\n }\n\n break;\n }\n }\n\n function ensureIframe(): HTMLIFrameElement {\n if (iframe) return iframe;\n\n iframe = document.createElement('iframe');\n iframe.setAttribute('sandbox', 'allow-scripts');\n iframe.setAttribute('referrerpolicy', 'no-referrer');\n iframe.dataset.sandboxChannel = channel;\n container.appendChild(iframe);\n window.addEventListener('message', handleMessage);\n\n return iframe;\n }\n\n function dispose(): void {\n if (disposed) return;\n\n disposed = true;\n supersedePendingRender();\n controller.abort();\n\n if (iframe) {\n window.removeEventListener('message', handleMessage);\n iframe.remove();\n iframe = null;\n }\n\n listeners.clear();\n }\n\n function onMessage(handler: (message: SandboxMessage) => void): () => void {\n if (disposed) {\n warn('onMessage() called on a disposed sandbox — handler will never fire.');\n\n return () => {};\n }\n\n listeners.add(handler);\n\n return () => listeners.delete(handler);\n }\n\n function render(html: string): Promise<void> {\n if (disposed) {\n warn('render() called on a disposed sandbox.');\n\n return Promise.resolve();\n }\n\n if (!html.trim()) warn('render() called with empty HTML.');\n\n supersedePendingRender();\n generation += 1;\n\n const current = ensureIframe();\n const bootstrap = currentBootstrap();\n const documentOptions = { ...normalized, styles: { ...styles } };\n\n current.dataset.sandboxGeneration = String(generation);\n current.srcdoc = buildDocumentFromOptions(html, documentOptions, bootstrap);\n\n const promise = new Promise<void>((resolve, reject) => {\n currentRender = { reject, resolve };\n });\n\n readyTimeout = setTimeout(() => {\n if (!currentRender) return;\n\n currentRender.reject(\n new SandboxTimeoutError(\n `render() did not receive a 'ready' signal within ${normalized.readyTimeout}ms. ` +\n 'An injected script may have blocked document initialization.',\n ),\n );\n currentRender = null;\n readyTimeout = null;\n }, normalized.readyTimeout);\n\n return promise;\n }\n\n function send(message: Record<string, unknown>): void {\n iframe?.contentWindow?.postMessage(envelope(currentBootstrap(), message), '*');\n }\n\n function setState(update: Partial<State>): void {\n if (disposed) {\n warn('setState() called on a disposed sandbox.');\n\n return;\n }\n\n if (!iframe?.contentWindow) {\n warn('setState() called before render() — sandbox has no document yet.');\n\n return;\n }\n\n if (!bridgeReady) warn('setState() called before ready — state updates may be lost. Await render() first.');\n\n if (Object.keys(update).length > 0) send({ record: update, type: MSG_STATE_UPDATE_ALL });\n }\n\n function replaceBody(html: string): void {\n if (disposed) {\n warn('replaceBody() called on a disposed sandbox.');\n\n return;\n }\n\n if (!bridgeReady || !iframe?.contentWindow) {\n warn('replaceBody() called before render() has resolved — bridge is not ready. Await render() first.');\n\n return;\n }\n\n send({ html, type: MSG_HTML_REPLACE });\n }\n\n function updateStyle(id: string, css: string): void {\n if (disposed) {\n warn('updateStyle() called on a disposed sandbox.');\n\n return;\n }\n\n devOnly(() => {\n if (!(id in styles)) {\n warn(`updateStyle('${id}', …) — '${id}' is not a known styles key.`);\n }\n });\n\n styles[id] = css;\n\n if (bridgeReady && iframe?.contentWindow) send({ css, id, type: MSG_STYLE_PATCH });\n }\n\n return {\n get disposalSignal() {\n return controller.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n onMessage,\n render,\n replaceBody,\n setState,\n [Symbol.dispose]() {\n dispose();\n },\n updateStyle,\n };\n}\n"],"mappings":"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"}
|
package/dist/testing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.cjs","names":[],"sources":["../src/testing.ts"],"sourcesContent":["/**\n * Test utilities for code that integrates with @vielzeug/sandbox.\n *\n * Import from '@vielzeug/sandbox/testing' — not the main entry point.\n * These helpers encapsulate the postMessage protocol so tests don't\n * need to know internal message shapes.\n */\n\n/** Helpers for simulating sandbox→host messages in tests. */\nexport interface SandboxTestHelpers {\n /** Fire a '
|
|
1
|
+
{"version":3,"file":"testing.cjs","names":[],"sources":["../src/testing.ts"],"sourcesContent":["/**\n * Test utilities for code that integrates with @vielzeug/sandbox.\n *\n * Import from '@vielzeug/sandbox/testing' — not the main entry point.\n * These helpers encapsulate the postMessage protocol so tests don't\n * need to know internal message shapes.\n */\n\n/** Helpers for simulating sandbox→host messages in tests. */\nexport interface SandboxTestHelpers {\n /** Fire a 'custom' event as if sandbox code called window.__sandbox__.emit(). */\n fireCustom(event: string, detail?: unknown): void;\n /** Fire an 'error' message as if the sandbox caught an uncaught error. */\n fireError(message: string, stack?: string): void;\n /** Fire a 'ready' signal as if the bridge script executed. */\n fireReady(): void;\n /** Fire a 'resize' message as if the bridge ResizeObserver fired. */\n fireResize(height: number): void;\n}\n\n/**\n * Create test helpers bound to a specific sandbox container's iframe.\n *\n * @example\n * ```ts\n * const container = document.createElement('div');\n * document.body.appendChild(container);\n * const sandbox = createSandbox(container);\n * const helpers = createSandboxTestHelpers(container);\n *\n * const render = sandbox.render('<p>test</p>');\n * helpers.fireReady();\n * await render;\n * ```\n */\nexport function createSandboxTestHelpers(container: HTMLElement): SandboxTestHelpers {\n function getIframe(): HTMLIFrameElement | null {\n return container.querySelector('iframe');\n }\n\n function fire(data: Record<string, unknown>): void {\n const iframe = getIframe();\n const channel = iframe?.dataset.sandboxChannel;\n const generation = Number(iframe?.dataset.sandboxGeneration);\n\n if (!iframe || !channel || !Number.isInteger(generation)) {\n throw new Error('createSandboxTestHelpers requires a rendered sandbox.');\n }\n\n window.dispatchEvent(\n new MessageEvent('message', { data: { ...data, channel, generation }, source: iframe.contentWindow }),\n );\n }\n\n return {\n fireCustom: (event, detail = undefined) => fire({ detail, event, type: 'custom' }),\n fireError: (message, stack) => fire({ message, type: 'error', ...(stack !== undefined ? { stack } : {}) }),\n fireReady: () => fire({ type: 'ready' }),\n fireResize: (height) => fire({ height, type: 'resize' }),\n };\n}\n"],"mappings":"mEAmCA,SAAgB,EAAyB,EAA4C,CACnF,SAAS,GAAsC,CAC7C,OAAO,EAAU,cAAc,QAAQ,CACzC,CAEA,SAAS,EAAK,EAAqC,CACjD,IAAM,EAAS,EAAU,EACnB,EAAU,GAAQ,QAAQ,eAC1B,EAAa,OAAO,GAAQ,QAAQ,iBAAiB,EAE3D,GAAI,CAAC,GAAU,CAAC,GAAW,CAAC,OAAO,UAAU,CAAU,EACrD,MAAU,MAAM,uDAAuD,EAGzE,OAAO,cACL,IAAI,aAAa,UAAW,CAAE,KAAM,CAAE,GAAG,EAAM,UAAS,YAAW,EAAG,OAAQ,EAAO,aAAc,CAAC,CACtG,CACF,CAEA,MAAO,CACL,YAAa,EAAO,EAAS,IAAA,KAAc,EAAK,CAAE,SAAQ,QAAO,KAAM,QAAS,CAAC,EACjF,WAAY,EAAS,IAAU,EAAK,CAAE,UAAS,KAAM,QAAS,GAAI,IAAU,IAAA,GAAwB,CAAC,EAAb,CAAE,OAAM,CAAQ,CAAC,EACzG,cAAiB,EAAK,CAAE,KAAM,OAAQ,CAAC,EACvC,WAAa,GAAW,EAAK,CAAE,SAAQ,KAAM,QAAS,CAAC,CACzD,CACF"}
|
package/dist/testing.d.ts
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** Helpers for simulating sandbox→host messages in tests. */
|
|
9
9
|
export interface SandboxTestHelpers {
|
|
10
|
-
/** Fire a 'ready' signal as if the bridge script executed. */
|
|
11
|
-
fireReady(): void;
|
|
12
10
|
/** Fire a 'custom' event as if sandbox code called window.__sandbox__.emit(). */
|
|
13
11
|
fireCustom(event: string, detail?: unknown): void;
|
|
14
|
-
/** Fire a 'resize' message as if the bridge ResizeObserver fired. */
|
|
15
|
-
fireResize(height: number): void;
|
|
16
12
|
/** Fire an 'error' message as if the sandbox caught an uncaught error. */
|
|
17
13
|
fireError(message: string, stack?: string): void;
|
|
14
|
+
/** Fire a 'ready' signal as if the bridge script executed. */
|
|
15
|
+
fireReady(): void;
|
|
16
|
+
/** Fire a 'resize' message as if the bridge ResizeObserver fired. */
|
|
17
|
+
fireResize(height: number): void;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Create test helpers bound to a specific sandbox container's iframe.
|
|
@@ -26,9 +26,9 @@ export interface SandboxTestHelpers {
|
|
|
26
26
|
* const sandbox = createSandbox(container);
|
|
27
27
|
* const helpers = createSandboxTestHelpers(container);
|
|
28
28
|
*
|
|
29
|
-
* sandbox.render('<p>test</p>');
|
|
29
|
+
* const render = sandbox.render('<p>test</p>');
|
|
30
30
|
* helpers.fireReady();
|
|
31
|
-
* await
|
|
31
|
+
* await render;
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export declare function createSandboxTestHelpers(container: HTMLElement): SandboxTestHelpers;
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,6DAA6D;AAC7D,MAAM,WAAW,kBAAkB;IACjC,
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,6DAA6D;AAC7D,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAClD,0EAA0E;IAC1E,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjD,8DAA8D;IAC9D,SAAS,IAAI,IAAI,CAAC;IAClB,qEAAqE;IACrE,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,WAAW,GAAG,kBAAkB,CAyBnF"}
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["/**\n * Test utilities for code that integrates with @vielzeug/sandbox.\n *\n * Import from '@vielzeug/sandbox/testing' — not the main entry point.\n * These helpers encapsulate the postMessage protocol so tests don't\n * need to know internal message shapes.\n */\n\n/** Helpers for simulating sandbox→host messages in tests. */\nexport interface SandboxTestHelpers {\n /** Fire a '
|
|
1
|
+
{"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["/**\n * Test utilities for code that integrates with @vielzeug/sandbox.\n *\n * Import from '@vielzeug/sandbox/testing' — not the main entry point.\n * These helpers encapsulate the postMessage protocol so tests don't\n * need to know internal message shapes.\n */\n\n/** Helpers for simulating sandbox→host messages in tests. */\nexport interface SandboxTestHelpers {\n /** Fire a 'custom' event as if sandbox code called window.__sandbox__.emit(). */\n fireCustom(event: string, detail?: unknown): void;\n /** Fire an 'error' message as if the sandbox caught an uncaught error. */\n fireError(message: string, stack?: string): void;\n /** Fire a 'ready' signal as if the bridge script executed. */\n fireReady(): void;\n /** Fire a 'resize' message as if the bridge ResizeObserver fired. */\n fireResize(height: number): void;\n}\n\n/**\n * Create test helpers bound to a specific sandbox container's iframe.\n *\n * @example\n * ```ts\n * const container = document.createElement('div');\n * document.body.appendChild(container);\n * const sandbox = createSandbox(container);\n * const helpers = createSandboxTestHelpers(container);\n *\n * const render = sandbox.render('<p>test</p>');\n * helpers.fireReady();\n * await render;\n * ```\n */\nexport function createSandboxTestHelpers(container: HTMLElement): SandboxTestHelpers {\n function getIframe(): HTMLIFrameElement | null {\n return container.querySelector('iframe');\n }\n\n function fire(data: Record<string, unknown>): void {\n const iframe = getIframe();\n const channel = iframe?.dataset.sandboxChannel;\n const generation = Number(iframe?.dataset.sandboxGeneration);\n\n if (!iframe || !channel || !Number.isInteger(generation)) {\n throw new Error('createSandboxTestHelpers requires a rendered sandbox.');\n }\n\n window.dispatchEvent(\n new MessageEvent('message', { data: { ...data, channel, generation }, source: iframe.contentWindow }),\n );\n }\n\n return {\n fireCustom: (event, detail = undefined) => fire({ detail, event, type: 'custom' }),\n fireError: (message, stack) => fire({ message, type: 'error', ...(stack !== undefined ? { stack } : {}) }),\n fireReady: () => fire({ type: 'ready' }),\n fireResize: (height) => fire({ height, type: 'resize' }),\n };\n}\n"],"mappings":";AAmCA,SAAgB,EAAyB,GAA4C;CACnF,SAAS,IAAsC;EAC7C,OAAO,EAAU,cAAc,QAAQ;CACzC;CAEA,SAAS,EAAK,GAAqC;EACjD,IAAM,IAAS,EAAU,GACnB,IAAU,GAAQ,QAAQ,gBAC1B,IAAa,OAAO,GAAQ,QAAQ,iBAAiB;EAE3D,IAAI,CAAC,KAAU,CAAC,KAAW,CAAC,OAAO,UAAU,CAAU,GACrD,MAAU,MAAM,uDAAuD;EAGzE,OAAO,cACL,IAAI,aAAa,WAAW;GAAE,MAAM;IAAE,GAAG;IAAM;IAAS;GAAW;GAAG,QAAQ,EAAO;EAAc,CAAC,CACtG;CACF;CAEA,OAAO;EACL,aAAa,GAAO,IAAS,KAAA,MAAc,EAAK;GAAE;GAAQ;GAAO,MAAM;EAAS,CAAC;EACjF,YAAY,GAAS,MAAU,EAAK;GAAE;GAAS,MAAM;GAAS,GAAI,MAAU,KAAA,IAAwB,CAAC,IAAb,EAAE,SAAM;EAAQ,CAAC;EACzG,iBAAiB,EAAK,EAAE,MAAM,QAAQ,CAAC;EACvC,aAAa,MAAW,EAAK;GAAE;GAAQ,MAAM;EAAS,CAAC;CACzD;AACF"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Options for createSandbox, buildCsp, and buildDocument.
|
|
3
|
-
*
|
|
4
|
-
* All `allowed*Origins` fields whitelist CDN/external origins for their
|
|
5
|
-
* respective CSP directives. Origins from `scripts` URLs are extracted
|
|
6
|
-
* automatically and merged with `allowedScriptOrigins`.
|
|
7
|
-
*
|
|
8
|
-
* Use `namedStyles` to inject named `<style id="key">` blocks. Each block is
|
|
9
|
-
* individually hot-patchable via `sandbox.updateStyle(id, css)` without
|
|
10
|
-
* a full re-render.
|
|
11
|
-
*/
|
|
1
|
+
/** Options for generated sandbox documents and the managed iframe runtime. */
|
|
12
2
|
export interface SandboxOptions {
|
|
13
3
|
/** Origins added to font-src. Default: 'none'. */
|
|
14
4
|
allowedFontOrigins?: string[];
|
|
@@ -18,166 +8,57 @@ export interface SandboxOptions {
|
|
|
18
8
|
allowedScriptOrigins?: string[];
|
|
19
9
|
/** Origins appended to style-src alongside 'unsafe-inline'. */
|
|
20
10
|
allowedStyleOrigins?: string[];
|
|
21
|
-
/**
|
|
22
|
-
* BCP 47 language tag for the `<html lang="…">` attribute of the generated document.
|
|
23
|
-
* Defaults to `'en'`. Pass the primary language of the sandbox content for correct
|
|
24
|
-
* screen-reader behaviour.
|
|
25
|
-
* @example { lang: 'de' }
|
|
26
|
-
*/
|
|
11
|
+
/** BCP 47 language tag for the generated `<html>` element. Default: `'en'`. */
|
|
27
12
|
lang?: string;
|
|
28
|
-
/**
|
|
29
|
-
* Named CSS blocks injected as `<style id="key">` elements in the document `<head>`.
|
|
30
|
-
* Each entry is individually hot-patchable via `sandbox.updateStyle(id, css)`.
|
|
31
|
-
* Keys must be valid HTML id values.
|
|
32
|
-
*/
|
|
33
|
-
namedStyles?: Record<string, string>;
|
|
34
|
-
/**
|
|
35
|
-
* A cryptographic nonce added to the bridge <script> tag and to `script-src` in the CSP.
|
|
36
|
-
* In CSP Level 3 browsers the nonce suppresses `'unsafe-inline'` — only the bridge script
|
|
37
|
-
* (which carries the nonce attribute) executes. `'unsafe-inline'` is retained for CSP Level 2
|
|
38
|
-
* fallback only.
|
|
39
|
-
* @example { nonce: crypto.randomUUID() }
|
|
40
|
-
*/
|
|
13
|
+
/** A cryptographic nonce added to bridge scripts and script-src. */
|
|
41
14
|
nonce?: string;
|
|
42
|
-
/**
|
|
15
|
+
/** Milliseconds to wait for the generated document to become ready. Default: `5000`. */
|
|
16
|
+
readyTimeout?: number;
|
|
17
|
+
/** External script URLs injected before user content. Origins are added to script-src. */
|
|
43
18
|
scripts?: string[];
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*/
|
|
19
|
+
/** Named CSS blocks injected into the document head and addressable through `updateStyle()`. */
|
|
20
|
+
styles?: Record<string, string>;
|
|
21
|
+
/** Title for the generated document. Default: `''`. */
|
|
48
22
|
title?: string;
|
|
49
23
|
}
|
|
50
|
-
/** Named unsubscribe function returned by `onMessage`. */
|
|
51
|
-
export type Unsubscribe = () => void;
|
|
52
|
-
/**
|
|
53
|
-
* The bridge API available as `window.__sandbox__` inside sandbox documents.
|
|
54
|
-
*
|
|
55
|
-
* @example Ambient declaration for sandbox-side TypeScript:
|
|
56
|
-
* ```ts
|
|
57
|
-
* declare interface Window {
|
|
58
|
-
* __sandbox__: import('@vielzeug/sandbox').SandboxBridge;
|
|
59
|
-
* }
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
export interface SandboxBridge {
|
|
63
|
-
/** Emit a custom event to the host. Received via sandbox.onMessage() as { type: 'custom', event, detail }. */
|
|
64
|
-
emit(event: string, detail?: unknown): void;
|
|
65
|
-
/**
|
|
66
|
-
* Subscribe to state pushed via sandbox.setState()/setStateAll() for a specific key.
|
|
67
|
-
* The handler receives only the value for the matching key — filtering the raw
|
|
68
|
-
* `sandbox:state-update` CustomEvent is handled for you. Returns an unsubscribe function.
|
|
69
|
-
* @example
|
|
70
|
-
* ```ts
|
|
71
|
-
* const off = window.__sandbox__.onState('theme', (value) => {
|
|
72
|
-
* document.body.dataset.theme = String(value);
|
|
73
|
-
* });
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
onState(key: string, handler: (value: unknown) => void): Unsubscribe;
|
|
77
|
-
}
|
|
78
24
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* listen to this event directly — `window.__sandbox__.onState(key, handler)` wraps it
|
|
82
|
-
* for the common case of listening to a single key.
|
|
83
|
-
*
|
|
84
|
-
* @example Sandbox-side listener:
|
|
85
|
-
* ```ts
|
|
86
|
-
* document.addEventListener('sandbox:state-update', (e: CustomEvent<SandboxStateUpdateDetail>) => {
|
|
87
|
-
* console.log(e.detail.key, e.detail.value);
|
|
88
|
-
* });
|
|
89
|
-
* ```
|
|
25
|
+
* Typed bridge available as `window.__sandbox__` inside sandbox documents.
|
|
26
|
+
* Host-side messages remain untrusted and expose custom event details as `unknown`.
|
|
90
27
|
*/
|
|
91
|
-
export interface
|
|
92
|
-
|
|
93
|
-
value:
|
|
28
|
+
export interface SandboxBridge<State extends object = Record<string, unknown>, Events extends object = Record<string, unknown>> {
|
|
29
|
+
emit<K extends keyof Events & string>(event: K, ...detail: undefined extends Events[K] ? [detail?: Events[K]] : [detail: Events[K]]): void;
|
|
30
|
+
onState<K extends keyof State & string>(key: K, handler: (value: State[K]) => void): () => void;
|
|
94
31
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Application-level messages the sandbox sends to the host via onMessage().
|
|
97
|
-
*
|
|
98
|
-
* 'ready' is an internal lifecycle signal — it resolves render() Promises and
|
|
99
|
-
* is never forwarded to onMessage() subscribers.
|
|
100
|
-
*
|
|
101
|
-
* @security All SandboxMessage data is untrusted — sandboxed code controls the payload.
|
|
102
|
-
* Never evaluate or execute message content on the host.
|
|
103
|
-
*/
|
|
32
|
+
/** Untrusted application-level message received from the sandbox document. */
|
|
104
33
|
export type SandboxMessage = {
|
|
105
|
-
|
|
34
|
+
detail: unknown;
|
|
35
|
+
event: string;
|
|
36
|
+
type: 'custom';
|
|
37
|
+
} | {
|
|
106
38
|
message: string;
|
|
107
39
|
stack?: string;
|
|
108
|
-
|
|
109
|
-
type: 'custom';
|
|
110
|
-
event: string;
|
|
111
|
-
detail: unknown;
|
|
40
|
+
type: 'error';
|
|
112
41
|
} | {
|
|
113
42
|
height: number;
|
|
114
43
|
type: 'resize';
|
|
115
44
|
};
|
|
116
|
-
/** Handle for a
|
|
117
|
-
export interface SandboxHandle {
|
|
118
|
-
/**
|
|
45
|
+
/** Handle for a managed sandbox iframe. */
|
|
46
|
+
export interface SandboxHandle<State extends object = Record<string, unknown>> {
|
|
47
|
+
/** Signal aborted by `dispose()`. */
|
|
119
48
|
readonly disposalSignal: AbortSignal;
|
|
120
|
-
/**
|
|
121
|
-
readonly disposed: boolean;
|
|
122
|
-
/**
|
|
123
|
-
* Resolves when the first sandbox document signals it is ready.
|
|
124
|
-
* Also resolves if the sandbox is disposed before the first render — check
|
|
125
|
-
* `sandbox.disposed` afterward if you need to distinguish the two cases.
|
|
126
|
-
* Does not reset on re-renders; use the Promise returned by render() for
|
|
127
|
-
* subsequent renders.
|
|
128
|
-
*/
|
|
129
|
-
readonly ready: Promise<void>;
|
|
130
|
-
/** Tear down the sandbox — removes the iframe from the DOM and clears all listeners. */
|
|
49
|
+
/** Tear down the iframe and listeners. */
|
|
131
50
|
dispose(): void;
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Replace the live document body without navigating the iframe. Document/window listeners,
|
|
141
|
-
* head scripts, and named styles remain; body descendants, their listeners, references, and
|
|
142
|
-
* form state are replaced. Scripts included in `html` do not execute through innerHTML.
|
|
143
|
-
*
|
|
144
|
-
* Call after `render()` resolves — warns in dev if the bridge is not ready. No-ops if disposed.
|
|
145
|
-
*/
|
|
51
|
+
/** True after disposal. */
|
|
52
|
+
readonly disposed: boolean;
|
|
53
|
+
/** Subscribe to untrusted custom, error, and resize messages. */
|
|
54
|
+
onMessage(handler: (message: SandboxMessage) => void): () => void;
|
|
55
|
+
/** Replace the full document and resolve when its bridge reports ready. */
|
|
56
|
+
render(html: string): Promise<void>;
|
|
57
|
+
/** Replace body descendants without navigating the iframe. */
|
|
146
58
|
replaceBody(html: string): void;
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* Returns a Promise that resolves when the new document signals it is ready, or
|
|
151
|
-
* rejects with a SandboxTimeoutError if no 'ready' signal arrives within 5s — the
|
|
152
|
-
* document is likely missing the bridge script; use buildDocument() to generate
|
|
153
|
-
* documents that include it.
|
|
154
|
-
* If a second render() starts before the first resolves, the first Promise resolves
|
|
155
|
-
* (not rejects) immediately — the document simply navigated away.
|
|
156
|
-
* Warns in dev if html is empty. Pass a signal to skip the render if already aborted.
|
|
157
|
-
*/
|
|
158
|
-
render(html: string, options?: {
|
|
159
|
-
signal?: AbortSignal;
|
|
160
|
-
}): Promise<void>;
|
|
161
|
-
/**
|
|
162
|
-
* Push multiple state values into the sandbox in a single postMessage.
|
|
163
|
-
* More efficient than calling setState() repeatedly for initial state setup.
|
|
164
|
-
* Warns in dev if called before render() resolves.
|
|
165
|
-
*/
|
|
166
|
-
setStateAll(record: Record<string, unknown>): void;
|
|
167
|
-
/**
|
|
168
|
-
* Push a state value into the sandbox without re-rendering.
|
|
169
|
-
* Dispatches a `sandbox:state-update` CustomEvent on `document` inside the sandbox.
|
|
170
|
-
* Warns in dev if called before render() resolves.
|
|
171
|
-
*/
|
|
172
|
-
setState(key: string, value: unknown): void;
|
|
173
|
-
/**
|
|
174
|
-
* Replace the CSS for a named style block without a full re-render.
|
|
175
|
-
* `id` must match a key in the `namedStyles` option passed to `createSandbox`.
|
|
176
|
-
* Updates both the live iframe and the baseline so the next render() uses the new CSS.
|
|
177
|
-
* No-ops if the sandbox is disposed. Safe to call before the first render —
|
|
178
|
-
* only the baseline is updated; the iframe patch is skipped until ready.
|
|
179
|
-
* Warns in dev if `id` is not a known namedStyles key.
|
|
180
|
-
*/
|
|
59
|
+
/** Push one or more typed state values into the ready document. */
|
|
60
|
+
setState(update: Partial<State>): void;
|
|
61
|
+
/** Update a named style in the live document and future renders. */
|
|
181
62
|
updateStyle(id: string, css: string): void;
|
|
182
63
|
[Symbol.dispose](): void;
|
|
183
64
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,6DAA6D;IAC7D,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,oGAAoG;IACpG,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa,CAC5B,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9C,MAAM,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAE/C,IAAI,CAAC,CAAC,SAAS,MAAM,MAAM,GAAG,MAAM,EAClC,KAAK,EAAE,CAAC,EACR,GAAG,MAAM,EAAE,SAAS,SAAS,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAClF,IAAI,CAAC;IACR,OAAO,CAAC,CAAC,SAAS,MAAM,KAAK,GAAG,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACjG;AAED,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GACtB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClD;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEvC,2CAA2C;AAC3C,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3E,qCAAqC;IACrC,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IACrC,0CAA0C;IAC1C,OAAO,IAAI,IAAI,CAAC;IAChB,2BAA2B;IAC3B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,iEAAiE;IACjE,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAClE,2EAA2E;IAC3E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,8DAA8D;IAC9D,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IACvC,oEAAoE;IACpE,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vielzeug/sandbox",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Sandboxed iframe runtime with a typed postMessage state bridge for safe execution of AI-generated UI",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/helmuthdu/vielzeug",
|
|
8
|
+
"directory": "packages/sandbox"
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=22"
|
|
13
|
+
},
|
|
6
14
|
"files": [
|
|
7
15
|
"dist"
|
|
8
16
|
],
|
|
@@ -11,44 +19,42 @@
|
|
|
11
19
|
"types": "dist/index.d.ts",
|
|
12
20
|
"exports": {
|
|
13
21
|
".": {
|
|
14
|
-
"types": "./dist/index.d.ts",
|
|
15
22
|
"import": "./dist/index.js",
|
|
16
|
-
"require": "./dist/index.cjs"
|
|
23
|
+
"require": "./dist/index.cjs",
|
|
24
|
+
"types": "./dist/index.d.ts"
|
|
17
25
|
},
|
|
18
26
|
"./testing": {
|
|
19
|
-
"types": "./dist/testing.d.ts",
|
|
20
27
|
"import": "./dist/testing.js",
|
|
21
|
-
"require": "./dist/testing.cjs"
|
|
28
|
+
"require": "./dist/testing.cjs",
|
|
29
|
+
"types": "./dist/testing.d.ts"
|
|
22
30
|
}
|
|
23
31
|
},
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"prepublishOnly": "pnpm run build",
|
|
31
|
-
"test": "vitest"
|
|
32
|
+
"typesVersions": {
|
|
33
|
+
"*": {
|
|
34
|
+
"testing": [
|
|
35
|
+
"dist/testing.d.ts"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
32
38
|
},
|
|
33
39
|
"publishConfig": {
|
|
34
40
|
"access": "public",
|
|
35
41
|
"registry": "https://registry.npmjs.org/"
|
|
36
42
|
},
|
|
37
|
-
"
|
|
38
|
-
"
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "vite build && pnpm --silent run build:bundle && pnpm --silent run build:types && pnpm --silent run test:types",
|
|
45
|
+
"build:bundle": "vite build --config vite.bundle.config.ts",
|
|
46
|
+
"build:types": "tsc -p tsconfig.declarations.json",
|
|
47
|
+
"fix": "biome check --write src",
|
|
48
|
+
"lint": "biome ci src",
|
|
49
|
+
"prepublishOnly": "pnpm --silent run build",
|
|
50
|
+
"test": "vitest",
|
|
51
|
+
"test:types": "tsc -p tsconfig.type-tests.json"
|
|
39
52
|
},
|
|
40
53
|
"devDependencies": {
|
|
41
|
-
"@types/node": "^26.1
|
|
54
|
+
"@types/node": "^26.4.1",
|
|
42
55
|
"jsdom": "^30.0.1",
|
|
43
56
|
"typescript": "^6.0.3",
|
|
44
|
-
"vite": "^8.2.
|
|
45
|
-
"vitest": "^
|
|
46
|
-
},
|
|
47
|
-
"typesVersions": {
|
|
48
|
-
"*": {
|
|
49
|
-
"testing": [
|
|
50
|
-
"dist/testing.d.ts"
|
|
51
|
-
]
|
|
52
|
-
}
|
|
57
|
+
"vite": "^8.2.2",
|
|
58
|
+
"vitest": "^5.0.0"
|
|
53
59
|
}
|
|
54
60
|
}
|
package/dist/_sandbox.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./_policy.cjs"),t=require("./_document.cjs"),n=require("./_runtime.cjs");function r(t={}){return e.buildCspFromOptions(e.normalizeSandboxOptions(t))}exports.buildCsp=r,exports.buildDocument=t.buildDocument,exports.createSandbox=n.createSandbox;
|
|
2
|
-
//# sourceMappingURL=_sandbox.cjs.map
|
package/dist/_sandbox.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_sandbox.cjs","names":[],"sources":["../src/_sandbox.ts"],"sourcesContent":["import type { SandboxOptions } from './types.js';\n\nimport { buildCspFromOptions, normalizeSandboxOptions } from './_policy.js';\n\nexport { buildDocument } from './_document.js';\nexport { createSandbox } from './_runtime.js';\n\nexport function buildCsp(options: SandboxOptions = {}): string {\n return buildCspFromOptions(normalizeSandboxOptions(options));\n}\n"],"mappings":"0FAOA,SAAgB,EAAS,EAA0B,CAAC,EAAW,CAC7D,OAAO,EAAA,oBAAoB,EAAA,wBAAwB,CAAO,CAAC,CAC7D"}
|
package/dist/_sandbox.d.ts
DELETED
package/dist/_sandbox.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_sandbox.d.ts","sourceRoot":"","sources":["../src/_sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIjD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,wBAAgB,QAAQ,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAE7D"}
|
package/dist/_sandbox.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { buildCspFromOptions as e, normalizeSandboxOptions as t } from "./_policy.js";
|
|
2
|
-
import { buildDocument as n } from "./_document.js";
|
|
3
|
-
import { createSandbox as r } from "./_runtime.js";
|
|
4
|
-
//#region src/_sandbox.ts
|
|
5
|
-
function i(n = {}) {
|
|
6
|
-
return e(t(n));
|
|
7
|
-
}
|
|
8
|
-
//#endregion
|
|
9
|
-
export { i as buildCsp, n as buildDocument, r as createSandbox };
|
|
10
|
-
|
|
11
|
-
//# sourceMappingURL=_sandbox.js.map
|
package/dist/_sandbox.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_sandbox.js","names":[],"sources":["../src/_sandbox.ts"],"sourcesContent":["import type { SandboxOptions } from './types.js';\n\nimport { buildCspFromOptions, normalizeSandboxOptions } from './_policy.js';\n\nexport { buildDocument } from './_document.js';\nexport { createSandbox } from './_runtime.js';\n\nexport function buildCsp(options: SandboxOptions = {}): string {\n return buildCspFromOptions(normalizeSandboxOptions(options));\n}\n"],"mappings":";;;;AAOA,SAAgB,EAAS,IAA0B,CAAC,GAAW;CAC7D,OAAO,EAAoB,EAAwB,CAAO,CAAC;AAC7D"}
|