@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/README.md
CHANGED
|
@@ -1,148 +1,57 @@
|
|
|
1
1
|
# @vielzeug/sandbox
|
|
2
2
|
|
|
3
|
-
Sandboxed iframe runtime with
|
|
3
|
+
> Sandboxed iframe runtime with typed postMessage state bridge
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
pnpm add @vielzeug/sandbox
|
|
9
|
+
npm install @vielzeug/sandbox
|
|
10
|
+
yarn add @vielzeug/sandbox
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Quick Start
|
|
12
14
|
|
|
13
15
|
```ts
|
|
14
16
|
import { createSandbox } from '@vielzeug/sandbox';
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// render() returns a Promise per call — resolves on 'ready', or rejects with
|
|
20
|
-
// SandboxTimeoutError if the document never signals ready within 5s (in every build,
|
|
21
|
-
// not just dev — always handle it).
|
|
22
|
-
await sandbox.render('<ore-button variant="primary">Click me</ore-button>');
|
|
23
|
-
|
|
24
|
-
// Subsequent renders — same contract, a fresh Promise each time
|
|
25
|
-
await sandbox.render(newHtml);
|
|
26
|
-
|
|
27
|
-
// Replace body descendants without a full page reset
|
|
28
|
-
sandbox.replaceBody('<p>updated</p>');
|
|
29
|
-
|
|
30
|
-
// Push state into the sandbox
|
|
31
|
-
sandbox.setState('theme', 'dark');
|
|
32
|
-
sandbox.setStateAll({ locale: 'en', theme: 'dark' }); // multiple values, one message
|
|
33
|
-
|
|
34
|
-
// Hot-patch a named style block (see namedStyles below) without a full re-render
|
|
35
|
-
sandbox.updateStyle('theme', 'body { background: #111; }');
|
|
36
|
-
|
|
37
|
-
// Receive error and custom events (ready is not forwarded)
|
|
38
|
-
sandbox.onMessage((msg) => {
|
|
39
|
-
if (msg.type === 'custom') console.log(msg.event, msg.detail);
|
|
40
|
-
if (msg.type === 'error') console.error(msg.message);
|
|
41
|
-
if (msg.type === 'resize') console.log('height:', msg.height);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Skip render if cancelled
|
|
45
|
-
sandbox.render(html, { signal: controller.signal });
|
|
46
|
-
|
|
47
|
-
// Clean up
|
|
48
|
-
sandbox.dispose();
|
|
49
|
-
// or using explicit resource management:
|
|
50
|
-
using s = createSandbox(container);
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## API
|
|
54
|
-
|
|
55
|
-
### `createSandbox(container, options?): SandboxHandle`
|
|
56
|
-
|
|
57
|
-
Creates an isolated `<iframe sandbox="allow-scripts">` inside `container`.
|
|
58
|
-
|
|
59
|
-
**Options (`SandboxOptions`):**
|
|
60
|
-
|
|
61
|
-
| Option | Type | Description |
|
|
62
|
-
| ---------------------- | ------------------------ | ------------------------------------------------------------------------------- |
|
|
63
|
-
| `scripts` | `string[]` | Absolute `http:` or `https:` script URLs injected with `crossorigin="anonymous"`; origins auto-added |
|
|
64
|
-
| `namedStyles` | `Record<string, string>` | Named `<style>` blocks; keys start with a letter and use only letters, digits, `_`, or `-` |
|
|
65
|
-
| `nonce` | `string` | Non-empty base64/base64url-style token for both bridge scripts and `script-src` |
|
|
66
|
-
| `lang` | `string` | Basic language tag, such as `en`, `de`, or `zh-Hant`. Default: `'en'` |
|
|
67
|
-
| `title` | `string` | Document `<title>`. Default: `''` |
|
|
68
|
-
| `allowedScriptOrigins` | `string[]` | Absolute `http:` or `https:` origins added to `script-src` |
|
|
69
|
-
| `allowedStyleOrigins` | `string[]` | Absolute `http:` or `https:` origins added to `style-src` |
|
|
70
|
-
| `allowedImageOrigins` | `string[]` | Absolute `http:` or `https:` origins added to `img-src` |
|
|
71
|
-
| `allowedFontOrigins` | `string[]` | Absolute `http:` or `https:` origins added to `font-src` |
|
|
72
|
-
|
|
73
|
-
### `SandboxHandle`
|
|
74
|
-
|
|
75
|
-
| Member | Description |
|
|
76
|
-
| ---------------------------- | ---------------------------------------------------------------------------------- |
|
|
77
|
-
| `disposed` | `true` once `dispose()` has been called |
|
|
78
|
-
| `disposalSignal` | `AbortSignal` aborted on `dispose()` — tie async operations to the sandbox's lifetime |
|
|
79
|
-
| `ready` | Resolves on the **first** load or dispose; does not reset on re-renders |
|
|
80
|
-
| `render(html, options?)` | Full page reset; lazy iframe creation. Returns a Promise that resolves on ready or rejects with `SandboxTimeoutError` after 5s. Pass `signal` to skip if already aborted |
|
|
81
|
-
| `replaceBody(html)` | Replace body descendants without navigating. Head scripts/styles survive; descendant listeners and state do not. Call after `render()` resolves |
|
|
82
|
-
| `setState(key, value)` | Push a single state value into the sandbox; warns if called before ready |
|
|
83
|
-
| `setStateAll(record)` | Push multiple state values in one message; more efficient than repeated `setState()` |
|
|
84
|
-
| `updateStyle(id, css)` | Hot-patch a `namedStyles` block by id, live and in the baseline for the next render |
|
|
85
|
-
| `onMessage(handler)` | Subscribe to `error`, `custom`, and `resize` events; returns an unsubscribe function |
|
|
86
|
-
| `dispose()` | Tear down: removes iframe, clears listeners, resolves pending ready Promises |
|
|
87
|
-
| `[Symbol.dispose]()` | Explicit resource management alias for `dispose()` |
|
|
88
|
-
|
|
89
|
-
### `buildCsp(options?): string`
|
|
90
|
-
|
|
91
|
-
Builds a strict Content-Security-Policy string from `SandboxOptions`. Origins from `scripts` URLs are extracted and merged with `allowedScriptOrigins` automatically.
|
|
92
|
-
|
|
93
|
-
### `buildDocument(html, options?): string`
|
|
94
|
-
|
|
95
|
-
Builds isolated markup with CSP meta tag, bridge script, and injected scripts/styles. It does not return a host runtime handle; use `createSandbox()` for state updates, body replacement, style updates, readiness, or disposal.
|
|
96
|
-
|
|
97
|
-
## Bridge Protocol
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
// Sandbox → host (received via onMessage)
|
|
101
|
-
// 'ready' is handled internally and NOT forwarded to subscribers
|
|
102
|
-
type SandboxMessage =
|
|
103
|
-
| { type: 'error'; message: string; stack?: string }
|
|
104
|
-
| { type: 'custom'; event: string; detail: unknown }
|
|
105
|
-
| { type: 'resize'; height: number };
|
|
106
|
-
|
|
107
|
-
// Bridge API available as window.__sandbox__ inside sandbox documents
|
|
108
|
-
interface SandboxBridge {
|
|
109
|
-
emit(event: string, detail?: unknown): void;
|
|
110
|
-
// Subscribe to state pushed via sandbox.setState()/setStateAll() for one key
|
|
111
|
-
onState(key: string, handler: (value: unknown) => void): () => void;
|
|
18
|
+
interface AppState {
|
|
19
|
+
locale: string;
|
|
20
|
+
theme: 'dark' | 'light';
|
|
112
21
|
}
|
|
113
|
-
```
|
|
114
22
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
23
|
+
const container = document.getElementById('preview')!;
|
|
24
|
+
const sandbox = createSandbox<AppState>(container, {
|
|
25
|
+
styles: { theme: 'body { color-scheme: light; }' },
|
|
26
|
+
title: 'Preview',
|
|
27
|
+
});
|
|
119
28
|
|
|
120
|
-
|
|
121
|
-
|
|
29
|
+
sandbox.onMessage((message) => {
|
|
30
|
+
if (message.type === 'custom' && message.event === 'button:click') {
|
|
31
|
+
// Custom details are untrusted. Narrow before use.
|
|
32
|
+
if (typeof message.detail === 'object' && message.detail !== null && 'label' in message.detail) {
|
|
33
|
+
console.log(message.detail.label);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (message.type === 'error') console.error(message.message);
|
|
37
|
+
if (message.type === 'resize') console.log(message.height);
|
|
122
38
|
});
|
|
123
|
-
```
|
|
124
39
|
|
|
125
|
-
|
|
40
|
+
await sandbox.render('<button>Save</button>');
|
|
41
|
+
sandbox.setState({ locale: 'en', theme: 'dark' });
|
|
42
|
+
sandbox.updateStyle('theme', 'body { color-scheme: dark; }');
|
|
126
43
|
|
|
127
|
-
|
|
128
|
-
// sandbox-env.d.ts
|
|
129
|
-
declare interface Window {
|
|
130
|
-
__sandbox__: import('@vielzeug/sandbox').SandboxBridge;
|
|
131
|
-
}
|
|
44
|
+
sandbox.dispose();
|
|
132
45
|
```
|
|
133
46
|
|
|
134
|
-
##
|
|
47
|
+
## Documentation
|
|
135
48
|
|
|
136
|
-
|
|
49
|
+
- [Overview](https://vielzeug.dev/sandbox/)
|
|
50
|
+
- [Usage Guide](https://vielzeug.dev/sandbox/usage)
|
|
51
|
+
- [API Reference](https://vielzeug.dev/sandbox/api)
|
|
52
|
+
- [Examples](https://vielzeug.dev/sandbox/examples)
|
|
53
|
+
- [Migration Guide](https://vielzeug.dev/sandbox/migration)
|
|
137
54
|
|
|
138
|
-
|
|
139
|
-
import { SandboxTimeoutError } from '@vielzeug/sandbox';
|
|
55
|
+
## License
|
|
140
56
|
|
|
141
|
-
|
|
142
|
-
await sandbox.render(html);
|
|
143
|
-
} catch (err) {
|
|
144
|
-
if (err instanceof SandboxTimeoutError) {
|
|
145
|
-
// document likely missing the bridge script
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
```
|
|
57
|
+
MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
|
package/dist/_bridge.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
function e(e){return`
|
|
2
|
+
(function() {
|
|
2
3
|
window.__sandboxChannel = ${JSON.stringify(e.channel)};
|
|
3
4
|
window.__sandboxGeneration = ${String(e.generation)};
|
|
4
5
|
const __sandboxChannel = window.__sandboxChannel;
|
|
@@ -7,9 +8,6 @@ if ('ontouchstart' in window) document.addEventListener('touchstart', function()
|
|
|
7
8
|
window.addEventListener('message', function(e) {
|
|
8
9
|
var msg = e.data;
|
|
9
10
|
if (e.source !== parent || !msg || msg.channel !== __sandboxChannel || msg.generation !== __sandboxGeneration) return;
|
|
10
|
-
if (msg.type === 'state-update') {
|
|
11
|
-
document.dispatchEvent(new CustomEvent('sandbox:state-update', { detail: { key: msg.key, value: msg.value } }));
|
|
12
|
-
}
|
|
13
11
|
if (msg.type === 'state-update-all' && msg.record && typeof msg.record === 'object') {
|
|
14
12
|
var keys = Object.keys(msg.record);
|
|
15
13
|
for (var i = 0; i < keys.length; i++) {
|
|
@@ -52,8 +50,9 @@ window.__sandbox__ = {
|
|
|
52
50
|
};
|
|
53
51
|
}
|
|
54
52
|
};
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
window.addEventListener('DOMContentLoaded', function() {
|
|
54
|
+
post({ type: 'ready' });
|
|
55
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
57
56
|
new ResizeObserver(function(entries) {
|
|
58
57
|
var entry = entries[0];
|
|
59
58
|
if (!entry) return;
|
|
@@ -62,6 +61,7 @@ if (typeof ResizeObserver !== 'undefined') {
|
|
|
62
61
|
: entry.contentRect.height;
|
|
63
62
|
post({ type: 'resize', height: h });
|
|
64
63
|
}).observe(document.body);
|
|
65
|
-
}
|
|
64
|
+
});
|
|
65
|
+
})();
|
|
66
66
|
`.trim()}exports.buildBridgeScript=e;
|
|
67
67
|
//# sourceMappingURL=_bridge.cjs.map
|
package/dist/_bridge.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_bridge.cjs","names":[],"sources":["../src/_bridge.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
|
|
1
|
+
{"version":3,"file":"_bridge.cjs","names":[],"sources":["../src/_bridge.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 `\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"],"mappings":"AAEA,SAAgB,EAAkB,EAAoC,CAIpE,MAAO;;4BAHS,KAAK,UAAU,EAAU,OAKf,EAAQ;+BAJf,OAAO,EAAU,UAKP,EAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8DxC,KAAK,CACP"}
|
package/dist/_bridge.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//#region src/_bridge.ts
|
|
2
2
|
function e(e) {
|
|
3
3
|
return `
|
|
4
|
+
(function() {
|
|
4
5
|
window.__sandboxChannel = ${JSON.stringify(e.channel)};
|
|
5
6
|
window.__sandboxGeneration = ${String(e.generation)};
|
|
6
7
|
const __sandboxChannel = window.__sandboxChannel;
|
|
@@ -9,9 +10,6 @@ if ('ontouchstart' in window) document.addEventListener('touchstart', function()
|
|
|
9
10
|
window.addEventListener('message', function(e) {
|
|
10
11
|
var msg = e.data;
|
|
11
12
|
if (e.source !== parent || !msg || msg.channel !== __sandboxChannel || msg.generation !== __sandboxGeneration) return;
|
|
12
|
-
if (msg.type === 'state-update') {
|
|
13
|
-
document.dispatchEvent(new CustomEvent('sandbox:state-update', { detail: { key: msg.key, value: msg.value } }));
|
|
14
|
-
}
|
|
15
13
|
if (msg.type === 'state-update-all' && msg.record && typeof msg.record === 'object') {
|
|
16
14
|
var keys = Object.keys(msg.record);
|
|
17
15
|
for (var i = 0; i < keys.length; i++) {
|
|
@@ -54,8 +52,9 @@ window.__sandbox__ = {
|
|
|
54
52
|
};
|
|
55
53
|
}
|
|
56
54
|
};
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
window.addEventListener('DOMContentLoaded', function() {
|
|
56
|
+
post({ type: 'ready' });
|
|
57
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
59
58
|
new ResizeObserver(function(entries) {
|
|
60
59
|
var entry = entries[0];
|
|
61
60
|
if (!entry) return;
|
|
@@ -64,7 +63,8 @@ if (typeof ResizeObserver !== 'undefined') {
|
|
|
64
63
|
: entry.contentRect.height;
|
|
65
64
|
post({ type: 'resize', height: h });
|
|
66
65
|
}).observe(document.body);
|
|
67
|
-
}
|
|
66
|
+
});
|
|
67
|
+
})();
|
|
68
68
|
`.trim();
|
|
69
69
|
}
|
|
70
70
|
//#endregion
|
package/dist/_bridge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_bridge.js","names":[],"sources":["../src/_bridge.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
|
|
1
|
+
{"version":3,"file":"_bridge.js","names":[],"sources":["../src/_bridge.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 `\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"],"mappings":";AAEA,SAAgB,EAAkB,GAAoC;CAIpE,OAAO;;4BAHS,KAAK,UAAU,EAAU,OAKf,EAAQ;+BAJf,OAAO,EAAU,UAKP,EAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8DxC,KAAK;AACP"}
|
package/dist/_dev.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __SANDBOX_PROD__?: boolean }).__SANDBOX_PROD__;\n\n/** @internal @security — some callers interpolate user-supplied values (e.g.
|
|
1
|
+
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.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"],"mappings":"AAGA,SAAgB,EAAK,EAAmB,CAExC,CAGA,SAAgB,EAAQ,EAAsB,CAE9C"}
|
package/dist/_dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __SANDBOX_PROD__?: boolean }).__SANDBOX_PROD__;\n\n/** @internal @security — some callers interpolate user-supplied values (e.g.
|
|
1
|
+
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.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"],"mappings":";AAGA,SAAgB,EAAK,GAAmB,CAExC;AAGA,SAAgB,EAAQ,GAAsB,CAE9C"}
|
package/dist/_document.cjs
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
const e=require("./
|
|
1
|
+
const e=require("./_bridge.cjs"),t=require("./_policy.cjs");function n(e){return e.replace(/<\/style/gi,`\\3C /style`)}function r(e){return e.replace(/&/g,`&`).replace(/"/g,`"`).replace(/</g,`<`).replace(/>/g,`>`)}function i(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`)}function a(a,o,s){let c=r(t.buildCspFromOptions(o)),l=Object.entries(o.styles).map(([e,t])=>`<style id="${r(e)}">${n(t)}</style>`).join(`
|
|
2
2
|
`),u=o.scripts.map(e=>`<script crossorigin="anonymous" src="${r(e)}"><\/script>`).join(`
|
|
3
|
-
`),d=o.nonce?` nonce="${r(o.nonce)}"
|
|
3
|
+
`),d=o.nonce?` nonce="${r(o.nonce)}"`:``;return`<!doctype html>
|
|
4
4
|
<html lang="${r(o.lang)}">
|
|
5
5
|
<head>
|
|
6
6
|
<meta http-equiv="Content-Security-Policy" content="${c}">
|
|
7
|
-
${f}
|
|
8
7
|
<meta charset="utf-8">
|
|
9
8
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
10
9
|
<title>${i(o.title)}</title>
|
|
11
10
|
${l}
|
|
11
|
+
<script${d}>
|
|
12
|
+
${e.buildBridgeScript(s)}
|
|
13
|
+
<\/script>
|
|
12
14
|
</head>
|
|
13
15
|
<body>
|
|
14
16
|
${u}
|
|
15
17
|
${a}
|
|
16
|
-
<script${d}>
|
|
17
|
-
${t.buildBridgeScript(s)}
|
|
18
|
-
<\/script>
|
|
19
18
|
</body>
|
|
20
|
-
</html>`}
|
|
19
|
+
</html>`}exports.buildDocumentFromOptions=a;
|
|
21
20
|
//# sourceMappingURL=_document.cjs.map
|
package/dist/_document.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_document.cjs","names":[],"sources":["../src/_document.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"_document.cjs","names":[],"sources":["../src/_document.ts"],"sourcesContent":["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"],"mappings":"4DAKA,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,SAAgB,EACd,EACA,EACA,EACQ,CACR,IAAM,EAAM,EAAW,EAAA,oBAAoB,CAAO,CAAC,EAC7C,EAAS,OAAO,QAAQ,EAAQ,MAAM,CAAC,CAC1C,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,GAExE,MAAO;cACK,EAAW,EAAQ,IAAI,EAAE;;sDAEe,EAAI;;;SAGjD,EAAW,EAAQ,KAAK,EAAE;EACjC,EAAO;SACA,EAAM;EACb,EAAA,kBAAkB,CAAS,EAAE;;;;EAI7B,EAAQ;EACR,EAAK;;QAGP"}
|
package/dist/_document.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { type NormalizedSandboxOptions } from './_policy.js';
|
|
1
2
|
import type { BridgeBootstrap } from './_protocol.js';
|
|
2
3
|
import type { SandboxOptions } from './types.js';
|
|
3
|
-
|
|
4
|
+
export declare function buildDocumentFromOptions(html: string, options: NormalizedSandboxOptions, bootstrap: BridgeBootstrap): string;
|
|
4
5
|
export declare function buildDocument(html: string, options?: SandboxOptions): string;
|
|
5
|
-
export declare function buildRuntimeDocument(html: string, options: NormalizedSandboxOptions, bootstrap: BridgeBootstrap): string;
|
|
6
6
|
//# sourceMappingURL=_document.d.ts.map
|
package/dist/_document.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_document.d.ts","sourceRoot":"","sources":["../src/_document.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"_document.d.ts","sourceRoot":"","sources":["../src/_document.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,wBAAwB,EAA2B,MAAM,cAAc,CAAC;AAC3G,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAcjD,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,wBAAwB,EACjC,SAAS,EAAE,eAAe,GACzB,MAAM,CA2BR;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CAEhF"}
|
package/dist/_document.js
CHANGED
|
@@ -1,46 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { buildBridgeScript as e } from "./_bridge.js";
|
|
2
|
+
import { buildCspFromOptions as t } from "./_policy.js";
|
|
3
3
|
//#region src/_document.ts
|
|
4
|
-
function
|
|
4
|
+
function n(e) {
|
|
5
5
|
return e.replace(/<\/style/gi, "\\3C /style");
|
|
6
6
|
}
|
|
7
|
-
function
|
|
7
|
+
function r(e) {
|
|
8
8
|
return e.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
9
9
|
}
|
|
10
|
-
function
|
|
10
|
+
function i(e) {
|
|
11
11
|
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
12
12
|
}
|
|
13
|
-
function
|
|
14
|
-
let c =
|
|
13
|
+
function a(a, o, s) {
|
|
14
|
+
let c = r(t(o)), l = Object.entries(o.styles).map(([e, t]) => `<style id="${r(e)}">${n(t)}</style>`).join("\n"), u = o.scripts.map((e) => `<script crossorigin="anonymous" src="${r(e)}"><\/script>`).join("\n"), d = o.nonce ? ` nonce="${r(o.nonce)}"` : "";
|
|
15
15
|
return `<!doctype html>
|
|
16
|
-
<html lang="${
|
|
16
|
+
<html lang="${r(o.lang)}">
|
|
17
17
|
<head>
|
|
18
18
|
<meta http-equiv="Content-Security-Policy" content="${c}">
|
|
19
|
-
${f}
|
|
20
19
|
<meta charset="utf-8">
|
|
21
20
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
22
|
-
<title>${
|
|
21
|
+
<title>${i(o.title)}</title>
|
|
23
22
|
${l}
|
|
23
|
+
<script${d}>
|
|
24
|
+
${e(s)}
|
|
25
|
+
<\/script>
|
|
24
26
|
</head>
|
|
25
27
|
<body>
|
|
26
28
|
${u}
|
|
27
|
-
${
|
|
28
|
-
<script${d}>
|
|
29
|
-
${n(s)}
|
|
30
|
-
<\/script>
|
|
29
|
+
${a}
|
|
31
30
|
</body>
|
|
32
31
|
</html>`;
|
|
33
32
|
}
|
|
34
|
-
function s(e, n = {}) {
|
|
35
|
-
return o(e, t(n), {
|
|
36
|
-
channel: "",
|
|
37
|
-
generation: 0
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
function c(e, t, n) {
|
|
41
|
-
return o(e, t, n);
|
|
42
|
-
}
|
|
43
33
|
//#endregion
|
|
44
|
-
export {
|
|
34
|
+
export { a as buildDocumentFromOptions };
|
|
45
35
|
|
|
46
36
|
//# sourceMappingURL=_document.js.map
|
package/dist/_document.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_document.js","names":[],"sources":["../src/_document.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"_document.js","names":[],"sources":["../src/_document.ts"],"sourcesContent":["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"],"mappings":";;;AAKA,SAAS,EAAU,GAAqB;CACtC,OAAO,EAAI,QAAQ,cAAc,aAAa;AAChD;AAEA,SAAS,EAAW,GAAuB;CACzC,OAAO,EAAM,QAAQ,MAAM,OAAO,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,CAAC,QAAQ,MAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAM;AACxG;AAEA,SAAS,EAAW,GAAuB;CACzC,OAAO,EAAM,QAAQ,MAAM,OAAO,CAAC,CAAC,QAAQ,MAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAM;AAChF;AAEA,SAAgB,EACd,GACA,GACA,GACQ;CACR,IAAM,IAAM,EAAW,EAAoB,CAAO,CAAC,GAC7C,IAAS,OAAO,QAAQ,EAAQ,MAAM,CAAC,CAC1C,KAAK,CAAC,GAAI,OAAS,cAAc,EAAW,CAAE,EAAE,IAAI,EAAU,CAAG,EAAE,SAAS,CAAC,CAC7E,KAAK,IAAI,GACN,IAAU,EAAQ,QACrB,KAAK,MAAQ,wCAAwC,EAAW,CAAG,EAAE,aAAY,CAAC,CAClF,KAAK,IAAI,GACN,IAAQ,EAAQ,QAAQ,WAAW,EAAW,EAAQ,KAAK,EAAE,KAAK;CAExE,OAAO;cACK,EAAW,EAAQ,IAAI,EAAE;;sDAEe,EAAI;;;SAGjD,EAAW,EAAQ,KAAK,EAAE;EACjC,EAAO;SACA,EAAM;EACb,EAAkB,CAAS,EAAE;;;;EAI7B,EAAQ;EACR,EAAK;;;AAGP"}
|
package/dist/_policy.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs");var t=/^[A-Za-z][A-Za-z0-9_-]*$/,
|
|
1
|
+
const e=require("./errors.cjs");var t=5e3,n=/^[A-Za-z][A-Za-z0-9_-]*$/,r=/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/,i=/^[A-Za-z0-9+/_-]+={0,2}$/;function a(t,n){throw new e.SandboxConfigurationError(`${t} is invalid: ${String(n)}`)}function o(e,t){if(typeof e!=`string`)return a(t,e);let n;try{n=new URL(e)}catch{return a(t,e)}return n.protocol!==`http:`&&n.protocol!==`https:`||n.username||n.password||n.pathname!==`/`||n.search||n.hash?a(t,e):n.origin}function s(e,t){return e===void 0?[]:Array.isArray(e)?e.map(e=>o(e,t)):a(t,e)}function c(e){if(typeof e!=`string`)return a(`scripts`,e);let t;try{t=new URL(e)}catch{return a(`scripts`,e)}return t.protocol!==`http:`&&t.protocol!==`https:`?a(`scripts`,e):t.href}function l(e){if(e===void 0)return{};if(typeof e!=`object`||!e||Array.isArray(e))return a(`styles`,e);let t={};for(let[r,i]of Object.entries(e))(!n.test(r)||typeof i!=`string`)&&a(`styles`,r),t[r]=i;return t}function u(e={}){(typeof e!=`object`||!e||Array.isArray(e))&&a(`options`,e);let t=e.lang??`en`;r.test(t)||a(`lang`,t),e.nonce!==void 0&&(!i.test(e.nonce)||e.nonce.length===0)&&a(`nonce`,e.nonce),e.title!==void 0&&typeof e.title!=`string`&&a(`title`,e.title);let n=e.readyTimeout,o=n??5e3;n!==void 0&&(n===null||typeof n!=`number`||!Number.isFinite(n)||n<=0||n>2**31-1)&&a(`readyTimeout`,n),e.scripts!==void 0&&!Array.isArray(e.scripts)&&a(`scripts`,e.scripts);let u=(e.scripts??[]).map(c);return{allowedFontOrigins:s(e.allowedFontOrigins,`allowedFontOrigins`),allowedImageOrigins:s(e.allowedImageOrigins,`allowedImageOrigins`),allowedScriptOrigins:s(e.allowedScriptOrigins,`allowedScriptOrigins`),allowedStyleOrigins:s(e.allowedStyleOrigins,`allowedStyleOrigins`),lang:t,nonce:e.nonce,readyTimeout:o,scripts:u,styles:l(e.styles),title:e.title??``}}function d(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(`; `)}exports.DEFAULT_READY_TIMEOUT_MS=t,exports.buildCspFromOptions=d,exports.normalizeSandboxOptions=u;
|
|
2
2
|
//# sourceMappingURL=_policy.cjs.map
|
package/dist/_policy.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_policy.cjs","names":[],"sources":["../src/_policy.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"_policy.cjs","names":[],"sources":["../src/_policy.ts"],"sourcesContent":["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"],"mappings":"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"}
|
package/dist/_policy.d.ts
CHANGED
|
@@ -5,11 +5,14 @@ export interface NormalizedSandboxOptions {
|
|
|
5
5
|
allowedScriptOrigins: string[];
|
|
6
6
|
allowedStyleOrigins: string[];
|
|
7
7
|
lang: string;
|
|
8
|
-
namedStyles: Record<string, string>;
|
|
9
8
|
nonce: string | undefined;
|
|
9
|
+
readyTimeout: number;
|
|
10
10
|
scripts: string[];
|
|
11
|
+
styles: Record<string, string>;
|
|
11
12
|
title: string;
|
|
12
13
|
}
|
|
14
|
+
export declare const DEFAULT_READY_TIMEOUT_MS = 5000;
|
|
13
15
|
export declare function normalizeSandboxOptions(options?: SandboxOptions): NormalizedSandboxOptions;
|
|
14
16
|
export declare function buildCspFromOptions(options: NormalizedSandboxOptions): string;
|
|
17
|
+
export declare function buildCsp(options?: SandboxOptions): string;
|
|
15
18
|
//# sourceMappingURL=_policy.d.ts.map
|
package/dist/_policy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_policy.d.ts","sourceRoot":"","sources":["../src/_policy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"_policy.d.ts","sourceRoot":"","sources":["../src/_policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,wBAAwB;IACvC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAyE7C,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,cAAmB,GAAG,wBAAwB,CA2C9F;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAqB7E;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAE7D"}
|