@vielzeug/sandbox 1.2.3 → 2.1.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 +14 -14
- package/dist/_bridge.cjs +67 -0
- package/dist/_bridge.cjs.map +1 -0
- package/dist/_bridge.d.ts +3 -0
- package/dist/_bridge.d.ts.map +1 -0
- package/dist/_bridge.js +73 -0
- package/dist/_bridge.js.map +1 -0
- package/dist/_document.cjs +21 -0
- package/dist/_document.cjs.map +1 -0
- package/dist/_document.d.ts +6 -0
- package/dist/_document.d.ts.map +1 -0
- package/dist/_document.js +43 -0
- package/dist/_document.js.map +1 -0
- package/dist/_policy.cjs +2 -0
- package/dist/_policy.cjs.map +1 -0
- package/dist/_policy.d.ts +16 -0
- package/dist/_policy.d.ts.map +1 -0
- package/dist/_policy.js +73 -0
- package/dist/_policy.js.map +1 -0
- package/dist/_protocol.cjs +2 -0
- package/dist/_protocol.cjs.map +1 -0
- package/dist/_protocol.d.ts +21 -0
- package/dist/_protocol.d.ts.map +1 -0
- package/dist/_protocol.js +21 -0
- package/dist/_protocol.js.map +1 -0
- package/dist/_runtime.cjs +2 -0
- package/dist/_runtime.cjs.map +1 -0
- package/dist/_runtime.d.ts +3 -0
- package/dist/_runtime.d.ts.map +1 -0
- package/dist/_runtime.js +133 -0
- package/dist/_runtime.js.map +1 -0
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -6
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/sandbox.cjs +28 -23
- package/dist/sandbox.cjs.map +1 -1
- package/dist/sandbox.iife.js +27 -22
- package/dist/sandbox.iife.js.map +1 -1
- package/dist/sandbox.js +28 -23
- package/dist/sandbox.js.map +1 -1
- package/dist/testing.cjs +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.ts +4 -4
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +9 -3
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +25 -27
- package/dist/types.d.ts.map +1 -1
- package/package.json +28 -23
- package/dist/_sandbox.cjs +0 -81
- package/dist/_sandbox.cjs.map +0 -1
- package/dist/_sandbox.d.ts +0 -20
- package/dist/_sandbox.d.ts.map +0 -1
- package/dist/_sandbox.js +0 -193
- package/dist/_sandbox.js.map +0 -1
package/dist/_runtime.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { SandboxTimeoutError as e } from "./errors.js";
|
|
2
|
+
import { normalizeSandboxOptions as t } from "./_policy.js";
|
|
3
|
+
import { buildDocumentFromOptions as n } from "./_document.js";
|
|
4
|
+
import "./_dev.js";
|
|
5
|
+
import { MSG_CUSTOM as r, MSG_ERROR as i, MSG_HTML_REPLACE as a, MSG_READY as o, MSG_RESIZE as s, MSG_STATE_UPDATE as c, MSG_STATE_UPDATE_ALL as l, MSG_STYLE_PATCH as u, createChannel as d, envelope as f, isProtocolMessage as p } from "./_protocol.js";
|
|
6
|
+
//#region src/_runtime.ts
|
|
7
|
+
var m = 5e3;
|
|
8
|
+
function h(h, g = {}) {
|
|
9
|
+
let _ = t(g), v = d(), y = { ..._.namedStyles }, b = /* @__PURE__ */ new Set(), x = new AbortController(), S = null, C = !1, w = !1, T = 0, E = null, D = null, O, k = new Promise((e) => {
|
|
10
|
+
O = e;
|
|
11
|
+
});
|
|
12
|
+
function A() {
|
|
13
|
+
return {
|
|
14
|
+
channel: v,
|
|
15
|
+
generation: T
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function j() {
|
|
19
|
+
E !== null && (clearTimeout(E), E = null);
|
|
20
|
+
}
|
|
21
|
+
function M() {
|
|
22
|
+
j(), D?.resolve(), D = null, w = !1;
|
|
23
|
+
}
|
|
24
|
+
function N(e) {
|
|
25
|
+
for (let t of b) t(e);
|
|
26
|
+
}
|
|
27
|
+
function P(e) {
|
|
28
|
+
if (!S || e.source !== S.contentWindow) return;
|
|
29
|
+
let t = A();
|
|
30
|
+
if (p(e.data, t)) switch (e.data.type) {
|
|
31
|
+
case r:
|
|
32
|
+
typeof e.data.event == "string" && N({
|
|
33
|
+
detail: e.data.detail,
|
|
34
|
+
event: e.data.event,
|
|
35
|
+
type: r
|
|
36
|
+
});
|
|
37
|
+
break;
|
|
38
|
+
case i:
|
|
39
|
+
typeof e.data.message == "string" && N({
|
|
40
|
+
message: e.data.message,
|
|
41
|
+
...typeof e.data.stack == "string" ? { stack: e.data.stack } : {},
|
|
42
|
+
type: i
|
|
43
|
+
});
|
|
44
|
+
break;
|
|
45
|
+
case o:
|
|
46
|
+
M(), O(), w = !0;
|
|
47
|
+
break;
|
|
48
|
+
case s: typeof e.data.height == "number" && Number.isFinite(e.data.height) && e.data.height >= 0 && N({
|
|
49
|
+
height: e.data.height,
|
|
50
|
+
type: s
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function F() {
|
|
55
|
+
return S || (S = document.createElement("iframe"), S.setAttribute("sandbox", "allow-scripts"), S.setAttribute("referrerpolicy", "no-referrer"), S.dataset.sandboxChannel = v, h.appendChild(S), window.addEventListener("message", P), S);
|
|
56
|
+
}
|
|
57
|
+
function I() {
|
|
58
|
+
C || (C = !0, M(), x.abort(), O(), S &&= (window.removeEventListener("message", P), S.remove(), null), b.clear());
|
|
59
|
+
}
|
|
60
|
+
function L(e) {
|
|
61
|
+
return C ? () => {} : (b.add(e), () => b.delete(e));
|
|
62
|
+
}
|
|
63
|
+
function R(t, r) {
|
|
64
|
+
if (C || r?.signal?.aborted) return Promise.resolve();
|
|
65
|
+
t.trim(), M(), T += 1;
|
|
66
|
+
let i = F(), a = A(), o = {
|
|
67
|
+
..._,
|
|
68
|
+
namedStyles: { ...y }
|
|
69
|
+
};
|
|
70
|
+
i.dataset.sandboxGeneration = String(T), i.srcdoc = n(t, o, a);
|
|
71
|
+
let s = new Promise((e, t) => {
|
|
72
|
+
D = {
|
|
73
|
+
reject: t,
|
|
74
|
+
resolve: e
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
return E = setTimeout(() => {
|
|
78
|
+
D && (D.reject(new e(`render() did not receive a 'ready' signal from the sandbox document within ${m}ms. The document is likely missing the bridge script — use buildDocument() to generate documents that include it.`)), D = null, E = null);
|
|
79
|
+
}, m), s;
|
|
80
|
+
}
|
|
81
|
+
function z(e) {
|
|
82
|
+
S?.contentWindow?.postMessage(f(A(), e), "*");
|
|
83
|
+
}
|
|
84
|
+
function B(e) {
|
|
85
|
+
C || !w || !S?.contentWindow || z({
|
|
86
|
+
html: e,
|
|
87
|
+
type: a
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function V(e, t) {
|
|
91
|
+
C || S?.contentWindow && z({
|
|
92
|
+
key: e,
|
|
93
|
+
type: c,
|
|
94
|
+
value: t
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function H(e) {
|
|
98
|
+
C || S?.contentWindow && z({
|
|
99
|
+
record: e,
|
|
100
|
+
type: l
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function U(e, t) {
|
|
104
|
+
C || (y[e] = t, w && S?.contentWindow && z({
|
|
105
|
+
css: t,
|
|
106
|
+
id: e,
|
|
107
|
+
type: u
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
get disposalSignal() {
|
|
112
|
+
return x.signal;
|
|
113
|
+
},
|
|
114
|
+
dispose: I,
|
|
115
|
+
get disposed() {
|
|
116
|
+
return C;
|
|
117
|
+
},
|
|
118
|
+
onMessage: L,
|
|
119
|
+
ready: k,
|
|
120
|
+
render: R,
|
|
121
|
+
replaceBody: B,
|
|
122
|
+
setState: V,
|
|
123
|
+
setStateAll: H,
|
|
124
|
+
[Symbol.dispose]() {
|
|
125
|
+
I();
|
|
126
|
+
},
|
|
127
|
+
updateStyle: U
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { h as createSandbox };
|
|
132
|
+
|
|
133
|
+
//# sourceMappingURL=_runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_runtime.js","names":[],"sources":["../src/_runtime.ts"],"sourcesContent":["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,\n MSG_STATE_UPDATE_ALL,\n MSG_STYLE_PATCH,\n} from './_protocol.js';\nimport { SandboxTimeoutError } from './errors.js';\nimport type { SandboxHandle, SandboxMessage, SandboxOptions, Unsubscribe } from './types.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 = 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 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"],"mappings":"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"}
|
package/dist/errors.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=class
|
|
1
|
+
var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{},n=class extends e{};exports.SandboxConfigurationError=n,exports.SandboxError=e,exports.SandboxTimeoutError=t;
|
|
2
2
|
//# sourceMappingURL=errors.cjs.map
|
package/dist/errors.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.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
|
|
1
|
+
{"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.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\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"],"mappings":"AACA,IAAa,EAAb,cAAkC,KAAM,CACtC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAOa,EAAb,cAAyC,CAAa,CAAC,EAG1C,EAAb,cAA+C,CAAa,CAAC"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/** Base class for all sandbox errors. */
|
|
2
2
|
export declare class SandboxError extends Error {
|
|
3
3
|
constructor(message: string, opts?: ErrorOptions);
|
|
4
|
-
static is(err: unknown): err is SandboxError;
|
|
5
4
|
}
|
|
6
5
|
/**
|
|
7
6
|
* Thrown when render() does not receive a 'ready' signal from the sandbox document
|
|
@@ -10,4 +9,7 @@ export declare class SandboxError extends Error {
|
|
|
10
9
|
*/
|
|
11
10
|
export declare class SandboxTimeoutError extends SandboxError {
|
|
12
11
|
}
|
|
12
|
+
/** Thrown when Sandbox configuration cannot produce a valid document or CSP policy. */
|
|
13
|
+
export declare class SandboxConfigurationError extends SandboxError {
|
|
14
|
+
}
|
|
13
15
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAKjD;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,YAAY;CAAG;AAExD,uFAAuF;AACvF,qBAAa,yBAA0B,SAAQ,YAAY;CAAG"}
|
package/dist/errors.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
//#region src/errors.ts
|
|
2
|
-
var e = class
|
|
2
|
+
var e = class extends Error {
|
|
3
3
|
constructor(e, t) {
|
|
4
4
|
super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
return t instanceof e;
|
|
8
|
-
}
|
|
9
|
-
}, t = class extends e {};
|
|
6
|
+
}, t = class extends e {}, n = class extends e {};
|
|
10
7
|
//#endregion
|
|
11
|
-
export { e as SandboxError, t as SandboxTimeoutError };
|
|
8
|
+
export { n as SandboxConfigurationError, e as SandboxError, t as SandboxTimeoutError };
|
|
12
9
|
|
|
13
10
|
//# sourceMappingURL=errors.js.map
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.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
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.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\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"],"mappings":";AACA,IAAa,IAAb,cAAkC,MAAM;CACtC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAOa,IAAb,cAAyC,EAAa,CAAC,GAG1C,IAAb,cAA+C,EAAa,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./_policy.cjs"),n=require("./_document.cjs"),r=require("./_runtime.cjs");exports.SandboxConfigurationError=e.SandboxConfigurationError,exports.SandboxError=e.SandboxError,exports.SandboxTimeoutError=e.SandboxTimeoutError,exports.buildCsp=t.buildCsp,exports.buildDocument=n.buildDocument,exports.createSandbox=r.createSandbox;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { buildDocument } from './_document.js';
|
|
2
|
+
export { buildCsp } from './_policy.js';
|
|
3
|
+
export { createSandbox } from './_runtime.js';
|
|
4
|
+
export { SandboxConfigurationError, SandboxError, SandboxTimeoutError } from './errors.js';
|
|
3
5
|
export type { SandboxBridge, SandboxHandle, SandboxMessage, SandboxOptions, SandboxStateUpdateDetail, Unsubscribe, } from './types.js';
|
|
4
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC3F,YAAY,EACV,aAAa,EACb,aAAa,EACb,cAAc,EACd,cAAc,EACd,wBAAwB,EACxB,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildCsp as
|
|
3
|
-
|
|
1
|
+
import { SandboxConfigurationError as e, SandboxError as t, SandboxTimeoutError as n } from "./errors.js";
|
|
2
|
+
import { buildCsp as r } from "./_policy.js";
|
|
3
|
+
import { buildDocument as i } from "./_document.js";
|
|
4
|
+
import { createSandbox as a } from "./_runtime.js";
|
|
5
|
+
export { e as SandboxConfigurationError, t as SandboxError, n as SandboxTimeoutError, r as buildCsp, i as buildDocument, a as createSandbox };
|
package/dist/sandbox.cjs
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){return`
|
|
2
|
+
window.__sandboxChannel = ${JSON.stringify(e.channel)};
|
|
3
|
+
window.__sandboxGeneration = ${String(e.generation)};
|
|
4
|
+
const __sandboxChannel = window.__sandboxChannel;
|
|
5
|
+
const __sandboxGeneration = window.__sandboxGeneration;
|
|
6
|
+
if ('ontouchstart' in window) document.addEventListener('touchstart', function() {}, { passive: true });
|
|
2
7
|
window.addEventListener('message', function(e) {
|
|
3
8
|
var msg = e.data;
|
|
4
|
-
if (msg
|
|
9
|
+
if (e.source !== parent || !msg || msg.channel !== __sandboxChannel || msg.generation !== __sandboxGeneration) return;
|
|
10
|
+
if (msg.type === 'state-update') {
|
|
5
11
|
document.dispatchEvent(new CustomEvent('sandbox:state-update', { detail: { key: msg.key, value: msg.value } }));
|
|
6
12
|
}
|
|
7
|
-
if (msg
|
|
13
|
+
if (msg.type === 'state-update-all' && msg.record && typeof msg.record === 'object') {
|
|
8
14
|
var keys = Object.keys(msg.record);
|
|
9
15
|
for (var i = 0; i < keys.length; i++) {
|
|
10
16
|
var k = keys[i];
|
|
11
17
|
document.dispatchEvent(new CustomEvent('sandbox:state-update', { detail: { key: k, value: msg.record[k] } }));
|
|
12
18
|
}
|
|
13
19
|
}
|
|
14
|
-
if (msg
|
|
20
|
+
if (msg.type === 'style-patch' && msg.id && typeof msg.css === 'string') {
|
|
15
21
|
var el = document.getElementById(msg.id);
|
|
16
22
|
if (el && el.tagName === 'STYLE') el.textContent = msg.css;
|
|
17
23
|
}
|
|
18
|
-
if (msg
|
|
24
|
+
if (msg.type === 'html-replace' && typeof msg.html === 'string') {
|
|
19
25
|
document.body.innerHTML = msg.html;
|
|
20
26
|
}
|
|
21
27
|
});
|
|
22
|
-
// post() tags every outgoing message with window.__sandboxGeneration (set by render(),
|
|
23
|
-
// see below) and always targets '*' — srcdoc iframes have origin 'null' so no specific
|
|
24
|
-
// origin can be addressed. The host uses the generation tag to ignore messages from a
|
|
25
|
-
// document a newer render() has already superseded.
|
|
26
28
|
function post(msg) {
|
|
27
|
-
msg.
|
|
29
|
+
msg.channel = __sandboxChannel;
|
|
30
|
+
msg.generation = __sandboxGeneration;
|
|
28
31
|
parent.postMessage(msg, '*');
|
|
29
32
|
}
|
|
30
33
|
window.onerror = function(message, _src, _line, _col, err) {
|
|
@@ -37,7 +40,7 @@ window.addEventListener('unhandledrejection', function(e) {
|
|
|
37
40
|
});
|
|
38
41
|
window.__sandbox__ = {
|
|
39
42
|
emit: function(event, detail) {
|
|
40
|
-
post({ type: 'custom', event: event, detail: detail });
|
|
43
|
+
post({ type: 'custom', event: String(event), detail: detail });
|
|
41
44
|
},
|
|
42
45
|
onState: function(key, handler) {
|
|
43
46
|
function listener(e) {
|
|
@@ -59,23 +62,25 @@ if (typeof ResizeObserver !== 'undefined') {
|
|
|
59
62
|
: entry.contentRect.height;
|
|
60
63
|
post({ type: 'resize', height: h });
|
|
61
64
|
}).observe(document.body);
|
|
62
|
-
}
|
|
63
|
-
|
|
65
|
+
}
|
|
66
|
+
`.trim()}var t=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},n=class extends t{},r=class extends t{},i=/^[A-Za-z][A-Za-z0-9_-]*$/,a=/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/,o=/^[A-Za-z0-9+/_-]+={0,2}$/;function s(e,t){throw new r(`${e} is invalid: ${String(t)}`)}function c(e,t){let n;try{n=new URL(e)}catch{return s(t,e)}return n.protocol!==`http:`&&n.protocol!==`https:`||n.username||n.password||n.pathname!==`/`||n.search||n.hash?s(t,e):n.origin}function l(e,t){return(e??[]).map(e=>c(e,t))}function u(e){let t;try{t=new URL(e)}catch{return s(`scripts`,e)}return t.protocol!==`http:`&&t.protocol!==`https:`?s(`scripts`,e):t.href}function d(e){let t={};for(let[n,r]of Object.entries(e??{}))(!i.test(n)||typeof r!=`string`)&&s(`namedStyles`,n),t[n]=r;return t}function f(e={}){let t=e.lang??`en`;a.test(t)||s(`lang`,t),e.nonce!==void 0&&(!o.test(e.nonce)||e.nonce.length===0)&&s(`nonce`,e.nonce),e.title!==void 0&&typeof e.title!=`string`&&s(`title`,e.title);let n=(e.scripts??[]).map(u);return{allowedFontOrigins:l(e.allowedFontOrigins,`allowedFontOrigins`),allowedImageOrigins:l(e.allowedImageOrigins,`allowedImageOrigins`),allowedScriptOrigins:l(e.allowedScriptOrigins,`allowedScriptOrigins`),allowedStyleOrigins:l(e.allowedStyleOrigins,`allowedStyleOrigins`),lang:t,namedStyles:d(e.namedStyles),nonce:e.nonce,scripts:n,title:e.title??``}}function p(e){let t=[`'unsafe-inline'`,...e.nonce?[`'nonce-${e.nonce}'`]:[],...e.allowedScriptOrigins,...e.scripts.map(e=>new URL(e).origin)].join(` `),n=[`'unsafe-inline'`,...e.allowedStyleOrigins].join(` `),r=[`data:`,...e.allowedImageOrigins].join(` `),i=e.allowedFontOrigins.join(` `)||`'none'`;return[`default-src 'none'`,`script-src ${t}`,`style-src ${n}`,`img-src ${r}`,`font-src ${i}`,`connect-src 'none'`,`form-action 'none'`,`base-uri 'none'`].join(`; `)}function m(e={}){return p(f(e))}function h(e){return e.replace(/<\/style/gi,`\\3C /style`)}function g(e){return e.replace(/&/g,`&`).replace(/"/g,`"`).replace(/</g,`<`).replace(/>/g,`>`)}function _(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`)}function v(t,n,r){let i=g(p(n)),a=Object.entries(n.namedStyles).map(([e,t])=>`<style id="${g(e)}">${h(t)}</style>`).join(`
|
|
67
|
+
`),o=n.scripts.map(e=>`<script crossorigin="anonymous" src="${g(e)}"><\/script>`).join(`
|
|
68
|
+
`),s=n.nonce?` nonce="${g(n.nonce)}"`:``,c=`<script${s}>window.__sandboxChannel=${JSON.stringify(r.channel)};window.__sandboxGeneration=${r.generation};<\/script>`;return`<!doctype html>
|
|
69
|
+
<html lang="${g(n.lang)}">
|
|
64
70
|
<head>
|
|
65
|
-
<meta http-equiv="Content-Security-Policy" content="${
|
|
71
|
+
<meta http-equiv="Content-Security-Policy" content="${i}">
|
|
72
|
+
${c}
|
|
66
73
|
<meta charset="utf-8">
|
|
67
74
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
68
|
-
<title>${
|
|
69
|
-
${
|
|
70
|
-
`)}
|
|
75
|
+
<title>${_(n.title)}</title>
|
|
76
|
+
${a}
|
|
71
77
|
</head>
|
|
72
78
|
<body>
|
|
73
|
-
${
|
|
74
|
-
|
|
75
|
-
${
|
|
76
|
-
|
|
77
|
-
${y}
|
|
79
|
+
${o}
|
|
80
|
+
${t}
|
|
81
|
+
<script${s}>
|
|
82
|
+
${e(r)}
|
|
78
83
|
<\/script>
|
|
79
84
|
</body>
|
|
80
|
-
</html>`}var x=5e3;function
|
|
85
|
+
</html>`}function y(e,t={}){return v(e,f(t),{channel:``,generation:0})}var b=`custom`,x=`error`,S=`html-replace`,C=`ready`,w=`resize`,T=`state-update`,E=`state-update-all`,D=`style-patch`;function O(e,t){return typeof e==`object`&&!!e&&typeof e.channel==`string`&&e.channel===t.channel&&typeof e.generation==`number`&&e.generation===t.generation&&typeof e.type==`string`}function k(){if(typeof crypto.randomUUID==`function`)return crypto.randomUUID();let e=crypto.getRandomValues(new Uint32Array(4));return Array.from(e,e=>e.toString(16).padStart(8,`0`)).join(``)}function A(e,t){return{...t,channel:e.channel,generation:e.generation}}var j=5e3;function M(e,t={}){let r=f(t),i=k(),a={...r.namedStyles},o=new Set,s=new AbortController,c=null,l=!1,u=!1,d=0,p=null,m=null,h,g=new Promise(e=>{h=e});function _(){return{channel:i,generation:d}}function y(){p!==null&&(clearTimeout(p),p=null)}function M(){y(),m?.resolve(),m=null,u=!1}function N(e){for(let t of o)t(e)}function P(e){if(!c||e.source!==c.contentWindow)return;let t=_();if(O(e.data,t))switch(e.data.type){case b:typeof e.data.event==`string`&&N({detail:e.data.detail,event:e.data.event,type:b});break;case x:typeof e.data.message==`string`&&N({message:e.data.message,...typeof e.data.stack==`string`?{stack:e.data.stack}:{},type:x});break;case C:M(),h(),u=!0;break;case w:typeof e.data.height==`number`&&Number.isFinite(e.data.height)&&e.data.height>=0&&N({height:e.data.height,type:w})}}function F(){return c||(c=document.createElement(`iframe`),c.setAttribute(`sandbox`,`allow-scripts`),c.setAttribute(`referrerpolicy`,`no-referrer`),c.dataset.sandboxChannel=i,e.appendChild(c),window.addEventListener(`message`,P),c)}function I(){l||(l=!0,M(),s.abort(),h(),c&&=(window.removeEventListener(`message`,P),c.remove(),null),o.clear())}function L(e){return l?()=>{}:(o.add(e),()=>o.delete(e))}function R(e,t){if(l||t?.signal?.aborted)return Promise.resolve();e.trim(),M(),d+=1;let i=F(),o=_(),s={...r,namedStyles:{...a}};i.dataset.sandboxGeneration=String(d),i.srcdoc=v(e,s,o);let c=new Promise((e,t)=>{m={reject:t,resolve:e}});return p=setTimeout(()=>{m&&(m.reject(new n(`render() did not receive a 'ready' signal from the sandbox document within ${j}ms. The document is likely missing the bridge script — use buildDocument() to generate documents that include it.`)),m=null,p=null)},j),c}function z(e){c?.contentWindow?.postMessage(A(_(),e),`*`)}function B(e){l||!u||!c?.contentWindow||z({html:e,type:S})}function V(e,t){l||c?.contentWindow&&z({key:e,type:T,value:t})}function H(e){l||c?.contentWindow&&z({record:e,type:E})}function U(e,t){l||(a[e]=t,u&&c?.contentWindow&&z({css:t,id:e,type:D}))}return{get disposalSignal(){return s.signal},dispose:I,get disposed(){return l},onMessage:L,ready:g,render:R,replaceBody:B,setState:V,setStateAll:H,[Symbol.dispose](){I()},updateStyle:U}}exports.SandboxConfigurationError=r,exports.SandboxError=t,exports.SandboxTimeoutError=n,exports.buildCsp=m,exports.buildDocument=y,exports.createSandbox=M;
|
|
81
86
|
//# sourceMappingURL=sandbox.cjs.map
|
package/dist/sandbox.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.cjs","names":[],"sources":["../src/_dev.ts","../src/errors.ts","../src/_sandbox.ts"],"sourcesContent":["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","/** 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","import type { SandboxHandle, SandboxMessage, SandboxOptions, Unsubscribe } from './types.js';\n\nimport { devOnly, warn } from './_dev';\nimport { SandboxTimeoutError } from './errors.js';\n\n// Re-export types so that _sandbox.ts still exposes them via its original named exports.\n// index.ts now imports types from ./types.js directly — these re-exports are kept only\n// to avoid breaking any direct imports of _sandbox.js in tests.\nexport type {\n SandboxBridge,\n SandboxHandle,\n SandboxMessage,\n SandboxOptions,\n SandboxStateUpdateDetail,\n Unsubscribe,\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Protocol constants\n//\n// String values for the postMessage type field crossing the host↔sandbox\n// boundary. Inlined here (not a shared module) because the bridge script is\n// a plain-JS string that cannot import TypeScript modules.\n// ---------------------------------------------------------------------------\n\nconst MSG_READY = 'ready';\nconst MSG_STATE_UPDATE = 'state-update';\nconst MSG_STATE_UPDATE_ALL = 'state-update-all';\nconst MSG_STYLE_PATCH = 'style-patch';\nconst MSG_HTML_PATCH = 'html-patch';\nconst MSG_ERROR = 'error';\nconst MSG_CUSTOM = 'custom';\nconst MSG_RESIZE = 'resize';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isMsgObject(data: unknown): data is Record<string, unknown> & { type: string } {\n return typeof data === 'object' && data !== null && typeof (data as { type?: unknown }).type === 'string';\n}\n\nfunction extractOrigin(url: string): string | null {\n try {\n const o = new URL(url).origin;\n\n return o !== 'null' ? o : null;\n } catch {\n return null;\n }\n}\n\n// Prevent CSS breakout: the HTML spec treats <style> as a raw-text element —\n// it ends at the first `</style` token and the sequence cannot be escaped in\n// HTML. The correct fix is to escape `<` in CSS using its CSS unicode escape\n// `\\3C ` (trailing space terminates the escape sequence). This produces valid\n// CSS that the browser parses correctly, but breaks the HTML tokenizer's\n// `</style` match. The backslash itself is safe because CSS ignores unknown\n// escape sequences.\nfunction escapeCss(css: string): string {\n return css.replace(/<\\/style/gi, '\\\\3C /style');\n}\n\n// Escape a string for safe interpolation into an HTML double-quoted attribute\n// value (e.g. lang=\"…\", id=\"…\", nonce=\"…\"). Encodes the four characters that\n// can break out of an attribute or inject new attributes.\nfunction escapeAttr(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n}\n\n// Escape a string for safe interpolation into HTML text content (e.g. <title>).\n// Encodes & and < to prevent tag/entity injection; > is included for symmetry.\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\n// ---------------------------------------------------------------------------\n// CSP builder\n// ---------------------------------------------------------------------------\n\n// Characters illegal inside CSP source-list tokens (origins and nonces alike) that\n// would allow directive injection or HTML attribute breakout. Specifically:\n// ';' — CSP directive separator (would inject a new directive)\n// '\"' / \"'\" — breaks out of the HTML content=\"...\" / nonce=\"...\" attribute\n// '\\n' / '\\r' — CSP header line folding / response-splitting\nconst CSP_UNSAFE_CHARS = /['\";\\n\\r]/g;\n\n// Origins must be scheme+host(+port) form; none of CSP_UNSAFE_CHARS belong there.\nfunction sanitizeCspOrigin(origin: string): string {\n return origin.replace(CSP_UNSAFE_CHARS, '');\n}\n\n// Sanitize a nonce for CSP-token embedding — CSP nonces must be base64\n// characters only; stripping quotes/semicolons/newlines prevents early token\n// termination inside 'nonce-{value}'. buildCsp() and buildDocument() MUST both\n// derive their nonce from this exact function: the CSP header's nonce token and\n// the bridge <script nonce=\"...\"> attribute have to match byte-for-byte, or the\n// browser silently rejects the script — breaking the bridge (and CSP nonce\n// matching is stricter than HTML-attribute escaping, so applying escapeAttr()\n// to the raw, unsanitized nonce for the attribute while sanitizing separately\n// for the CSP token can produce two different strings).\nfunction sanitizeNonce(nonce: string): string {\n return nonce.replace(CSP_UNSAFE_CHARS, '');\n}\n\n/**\n * Builds a strict Content-Security-Policy string for sandboxed iframe documents.\n *\n * Accepts SandboxOptions directly — origins from `scripts` URLs are extracted and\n * merged with `allowedScriptOrigins` automatically.\n */\nexport function buildCsp(options: SandboxOptions = {}): string {\n const safeNonce = options.nonce ? sanitizeNonce(options.nonce) : null;\n\n const scriptOrigins = [\n \"'unsafe-inline'\",\n ...(safeNonce ? [`'nonce-${safeNonce}'`] : []),\n ...(options.allowedScriptOrigins ?? []).map(sanitizeCspOrigin),\n ...(options.scripts ?? []).map(extractOrigin).filter((o): o is string => o !== null),\n ].join(' ');\n\n const styleOrigins = [\"'unsafe-inline'\", ...(options.allowedStyleOrigins ?? []).map(sanitizeCspOrigin)].join(' ');\n const imgOrigins = ['data:', ...(options.allowedImageOrigins ?? []).map(sanitizeCspOrigin)].join(' ');\n const fontOrigins = (options.allowedFontOrigins ?? []).map(sanitizeCspOrigin).join(' ') || \"'none'\";\n\n return [\n \"default-src 'none'\",\n `script-src ${scriptOrigins}`,\n `style-src ${styleOrigins}`,\n `img-src ${imgOrigins}`,\n `font-src ${fontOrigins}`,\n \"connect-src 'none'\",\n \"form-action 'none'\",\n \"base-uri 'none'\",\n ].join('; ');\n}\n\n// ---------------------------------------------------------------------------\n// Document builder\n// ---------------------------------------------------------------------------\n\n// Bridge script embedded in every sandbox document.\n//\n// Plain JS — cannot import TypeScript modules. Message type strings must\n// match the MSG_* constants above.\n//\n// Auto-resize: ResizeObserver on document.body emits 'resize' messages\n// automatically — consumers don't need to wire ResizeObserver in their\n// sandbox content. The observer is set up AFTER the 'ready' postMessage so\n// that the first resize callback (which fires synchronously on observe()) does\n// not arrive at the host before bridgeReady is set.\n//\n// style-patch is scoped to <style> elements (tagName guard) to prevent\n// accidental content injection into non-style elements with a matching id.\nconst BRIDGE_SCRIPT = `\nif ('ontouchstart' in window) document.addEventListener('touchstart', function() {}, { passive: true });\nwindow.addEventListener('message', function(e) {\n var msg = e.data;\n if (msg && msg.type === 'state-update') {\n document.dispatchEvent(new CustomEvent('sandbox:state-update', { detail: { key: msg.key, value: msg.value } }));\n }\n if (msg && 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 && 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 && msg.type === 'html-patch' && typeof msg.html === 'string') {\n document.body.innerHTML = msg.html;\n }\n});\n// post() tags every outgoing message with window.__sandboxGeneration (set by render(),\n// see below) and always targets '*' — srcdoc iframes have origin 'null' so no specific\n// origin can be addressed. The host uses the generation tag to ignore messages from a\n// document a newer render() has already superseded.\nfunction post(msg) {\n msg.generation = window.__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: 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/**\n * Builds a complete, standalone sandbox HTML document.\n *\n * `namedStyles` entries are rendered as `<style id=\"key\">` elements in the\n * document `<head>`. External scripts carry `crossorigin=\"anonymous\"` so the\n * bridge's error handler receives full error details for cross-origin scripts.\n * The bridge script auto-emits `resize` messages via `ResizeObserver`.\n */\nexport function buildDocument(html: string, options: SandboxOptions = {}): string {\n // escapeAttr is applied to the whole CSP string when embedding it in content=\"...\"\n // because buildCsp() does not know the embedding context. Any user-supplied origin\n // values that survived sanitizeCspOrigin() but still contain & or < (very unusual)\n // would otherwise break the HTML attribute.\n const csp = escapeAttr(buildCsp(options));\n const named = options.namedStyles ?? {};\n const lang = escapeAttr(options.lang ?? 'en');\n const title = escapeText(options.title ?? '');\n\n const styleTags = Object.entries(named)\n .map(([id, css]) => `<style id=\"${escapeAttr(id)}\">${escapeCss(css)}</style>`)\n .join('\\n');\n\n const scriptTags = (options.scripts ?? [])\n .map((src) => `<script crossorigin=\"anonymous\" src=\"${escapeAttr(src)}\"></script>`)\n .join('\\n');\n\n // Must sanitize with the exact same sanitizeNonce() used by buildCsp() — see its\n // comment. escapeAttr() alone is not enough: it HTML-encodes but does not strip,\n // so a raw nonce containing e.g. ';' would attribute-escape differently than the\n // CSP token strips it, producing a mismatched nonce and a silently broken bridge.\n const nonceAttr = options.nonce ? ` nonce=\"${escapeAttr(sanitizeNonce(options.nonce))}\"` : '';\n\n return `<!doctype html>\n<html lang=\"${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>${title}</title>\n${styleTags}\n</head>\n<body>\n${scriptTags}\n${html}\n<script${nonceAttr}>\n${BRIDGE_SCRIPT}\n</script>\n</body>\n</html>`;\n}\n\n// ---------------------------------------------------------------------------\n// Sandbox factory\n// ---------------------------------------------------------------------------\n\n// Dev-only timeout: if the bridge never fires 'ready' after a render(), warn\n// so the developer knows the bridge script is missing or broken.\nconst READY_TIMEOUT_MS = 5000;\n\nexport function createSandbox(container: HTMLElement, options: SandboxOptions = {}): SandboxHandle {\n // Iframe is created lazily on the first render() call — no DOM work until\n // there is content to show.\n let iframe: HTMLIFrameElement | null = null;\n const listeners = new Set<(msg: SandboxMessage) => void>();\n let disposed = false;\n const ac = new AbortController();\n\n // Mutable namedStyles baseline — updateStyle() patches this so re-renders\n // automatically use the latest CSS without the caller re-passing options.\n const namedStyles: Record<string, string> = { ...(options.namedStyles ?? {}) };\n\n // bridgeReady: true once the bridge inside the current document has fired MSG_READY.\n // Reset to false on each render() so setState/updateStyle guards work correctly.\n let bridgeReady = false;\n\n // Monotonic counter embedded into each srcdoc as window.__sandboxGeneration (see\n // render()). iframe.contentWindow's identity is stable across srcdoc navigations in\n // real browsers, so event.source alone cannot distinguish a superseded document's\n // messages from the current one — the generation tag closes that gap.\n let renderGeneration = 0;\n\n // First-render gate — resolves on first ready signal or on dispose().\n // Always resolves (never rejects) so callers can safely fire-and-forget it.\n // Check sandbox.disposed afterward to distinguish a dispose-resolution.\n let resolveReady!: () => void;\n const ready = new Promise<void>((resolve) => {\n resolveReady = resolve;\n });\n\n // Per-render settlers — replaced on each render(), resolved on the next 'ready'\n // message or when superseded/disposed (not an error — the document navigated away\n // or the sandbox was torn down intentionally), rejected only by the ready-timeout.\n let currentRenderSettlers: { reject: (err: unknown) => void; resolve: () => void } | null = null;\n let readyTimeoutId: ReturnType<typeof setTimeout> | null = null;\n\n function clearReadyTimeout(): void {\n if (readyTimeoutId !== null) {\n clearTimeout(readyTimeoutId);\n readyTimeoutId = null;\n }\n }\n\n // Supersede the in-flight render: resolve (not reject) its promise, clear the\n // timeout, and reset bridgeReady. All three must change together — keep them here.\n function supersedePendingRender(): void {\n clearReadyTimeout();\n currentRenderSettlers?.resolve();\n currentRenderSettlers = null;\n bridgeReady = false;\n }\n\n // Dispatch table for incoming sandbox→host messages.\n // Adding a new message type means adding one entry here — no separate allowlist.\n // Each handler constructs a typed SandboxMessage rather than casting, so the\n // compiler verifies the shape and malformed payloads are silently dropped.\n const msgHandlers: Record<string, (msg: Record<string, unknown>) => void> = {\n [MSG_CUSTOM]: (msg) => {\n if (typeof msg.event === 'string') {\n broadcast({ detail: msg.detail, event: msg.event, type: MSG_CUSTOM });\n }\n },\n [MSG_ERROR]: (msg) => {\n if (typeof msg.message === 'string') {\n broadcast({\n message: msg.message,\n ...(typeof msg.stack === 'string' ? { stack: msg.stack } : {}),\n type: MSG_ERROR,\n });\n }\n },\n [MSG_READY]: () => {\n // supersedePendingRender clears the timeout and resolves the render promise.\n // bridgeReady is re-set to true immediately after because the bridge IS ready.\n supersedePendingRender();\n resolveReady();\n bridgeReady = true;\n },\n [MSG_RESIZE]: (msg) => {\n if (typeof msg.height === 'number') broadcast({ height: msg.height, type: MSG_RESIZE });\n },\n };\n\n function broadcast(msg: SandboxMessage): void {\n for (const listener of listeners) listener(msg);\n }\n\n function handleMessage(event: MessageEvent): void {\n if (!iframe || event.source !== iframe.contentWindow) return;\n\n if (!isMsgObject(event.data)) return;\n\n // Reject messages tagged with a superseded generation — a document torn down by a\n // newer render() may still have a message in flight. Messages with no generation\n // (or a non-number one, e.g. from testing helpers) are trusted and pass through.\n const generation = event.data.generation;\n\n if (typeof generation === 'number' && generation !== renderGeneration) return;\n\n // Guard against prototype-inherited properties on msgHandlers (e.g. '__proto__',\n // '__defineGetter__') — these are not functions and calling them via ?.() throws.\n // Object.hasOwn ensures only keys explicitly defined in the dispatch table are invoked.\n if (Object.hasOwn(msgHandlers, event.data.type)) {\n msgHandlers[event.data.type]!(event.data);\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 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 ac.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: (msg: 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 () => {\n listeners.delete(handler);\n };\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()) {\n warn('render() called with empty HTML.');\n }\n\n // Supersede any in-flight render — its document navigated away.\n supersedePendingRender();\n\n const generation = ++renderGeneration;\n const docOptions = Object.keys(namedStyles).length > 0 ? { ...options, namedStyles: { ...namedStyles } } : options;\n\n // Inject the generation marker into <head> — it always appears before any user\n // content in the template, so this replace can never hit HTML inside `html`.\n const doc = buildDocument(html, docOptions).replace(\n '<head>',\n `<head>\\n<script>window.__sandboxGeneration=${generation};</script>`,\n );\n\n ensureIframe().srcdoc = doc;\n\n const p = new Promise<void>((resolve, reject) => {\n currentRenderSettlers = { reject, resolve };\n });\n\n // If the bridge never fires 'ready', reject with SandboxTimeoutError so the\n // document-missing-bridge-script failure is catchable rather than hanging forever.\n readyTimeoutId = setTimeout(() => {\n if (currentRenderSettlers) {\n currentRenderSettlers.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 currentRenderSettlers = null;\n readyTimeoutId = null;\n }\n }, READY_TIMEOUT_MS);\n\n return p;\n }\n\n function patch(html: string): void {\n if (disposed) {\n warn('patch() called on a disposed sandbox.');\n\n return;\n }\n\n if (!bridgeReady || !iframe?.contentWindow) {\n warn(\n 'patch() called before render() has resolved — bridge is not yet initialized. Await the Promise returned by render() first.',\n );\n\n return;\n }\n\n // srcdoc iframes have origin 'null' — '*' is required because no specific origin can be targeted.\n iframe.contentWindow.postMessage({ html, type: MSG_HTML_PATCH }, '*');\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(\n 'setState() called before ready — state update may be lost if bridge is not yet initialized. Await the Promise returned by render() first.',\n );\n }\n\n // srcdoc iframes have origin 'null' — '*' is required because no specific origin can be targeted.\n iframe.contentWindow.postMessage({ 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(\n 'setStateAll() called before ready — state updates may be lost. Await the Promise returned by render() first.',\n );\n }\n\n // srcdoc iframes have origin 'null' — '*' is required because no specific origin can be targeted.\n iframe.contentWindow.postMessage({ 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(\n `updateStyle('${id}', …) — '${id}' is not a known namedStyles key. ` +\n 'The live patch will have no effect. Pass the id as a key in namedStyles when calling createSandbox().',\n );\n }\n });\n\n namedStyles[id] = css;\n\n if (bridgeReady && iframe?.contentWindow) {\n // srcdoc iframes have origin 'null' — '*' is required because no specific origin can be targeted.\n iframe.contentWindow.postMessage({ css, id, type: MSG_STYLE_PATCH }, '*');\n }\n }\n\n return {\n get disposalSignal() {\n return ac.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n onMessage,\n patch,\n ready,\n render,\n setState,\n setStateAll,\n [Symbol.dispose]() {\n dispose();\n },\n updateStyle,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sandbox.cjs","names":[],"sources":["../src/_bridge.ts","../src/errors.ts","../src/_policy.ts","../src/_document.ts","../src/_dev.ts","../src/_protocol.ts","../src/_runtime.ts"],"sourcesContent":["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","/** 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. 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 { 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 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\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.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","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 { 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,\n MSG_STATE_UPDATE_ALL,\n MSG_STYLE_PATCH,\n} from './_protocol.js';\nimport { SandboxTimeoutError } from './errors.js';\nimport type { SandboxHandle, SandboxMessage, SandboxOptions, Unsubscribe } from './types.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 = 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 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"],"mappings":"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"}
|