@vielzeug/sandbox 2.1.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.
Files changed (50) hide show
  1. package/README.md +34 -125
  2. package/dist/_bridge.cjs +6 -6
  3. package/dist/_bridge.cjs.map +1 -1
  4. package/dist/_bridge.js +6 -6
  5. package/dist/_bridge.js.map +1 -1
  6. package/dist/_dev.cjs.map +1 -1
  7. package/dist/_dev.js.map +1 -1
  8. package/dist/_document.cjs +6 -7
  9. package/dist/_document.cjs.map +1 -1
  10. package/dist/_document.d.ts.map +1 -1
  11. package/dist/_document.js +13 -20
  12. package/dist/_document.js.map +1 -1
  13. package/dist/_policy.cjs +1 -1
  14. package/dist/_policy.cjs.map +1 -1
  15. package/dist/_policy.d.ts +3 -1
  16. package/dist/_policy.d.ts.map +1 -1
  17. package/dist/_policy.js +33 -28
  18. package/dist/_policy.js.map +1 -1
  19. package/dist/_protocol.cjs +1 -1
  20. package/dist/_protocol.cjs.map +1 -1
  21. package/dist/_protocol.d.ts +0 -1
  22. package/dist/_protocol.d.ts.map +1 -1
  23. package/dist/_protocol.js +5 -5
  24. package/dist/_protocol.js.map +1 -1
  25. package/dist/_runtime.cjs +1 -1
  26. package/dist/_runtime.cjs.map +1 -1
  27. package/dist/_runtime.d.ts +1 -1
  28. package/dist/_runtime.d.ts.map +1 -1
  29. package/dist/_runtime.js +64 -74
  30. package/dist/_runtime.js.map +1 -1
  31. package/dist/errors.cjs.map +1 -1
  32. package/dist/errors.d.ts +1 -2
  33. package/dist/errors.d.ts.map +1 -1
  34. package/dist/errors.js.map +1 -1
  35. package/dist/index.cjs +1 -1
  36. package/dist/index.d.ts +1 -3
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -4
  39. package/dist/sandbox.cjs +15 -16
  40. package/dist/sandbox.cjs.map +1 -1
  41. package/dist/sandbox.iife.js +15 -16
  42. package/dist/sandbox.iife.js.map +1 -1
  43. package/dist/sandbox.js +15 -16
  44. package/dist/sandbox.js.map +1 -1
  45. package/dist/testing.cjs.map +1 -1
  46. package/dist/testing.d.ts +2 -2
  47. package/dist/testing.js.map +1 -1
  48. package/dist/types.d.ts +28 -147
  49. package/dist/types.d.ts.map +1 -1
  50. package/package.json +8 -7
package/README.md CHANGED
@@ -1,148 +1,57 @@
1
1
  # @vielzeug/sandbox
2
2
 
3
- Sandboxed iframe runtime with a typed postMessage state bridge for safe execution of AI-generated UI.
3
+ > Sandboxed iframe runtime with typed postMessage state bridge
4
4
 
5
- ## Install
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
- ## Usage
13
+ ## Quick Start
12
14
 
13
15
  ```ts
14
16
  import { createSandbox } from '@vielzeug/sandbox';
15
17
 
16
- const container = document.getElementById('preview')!;
17
- const sandbox = createSandbox(container);
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
- Sandbox code emits custom events to the host, and can subscribe to host-pushed state:
116
-
117
- ```js
118
- window.__sandbox__.emit('button:click', { label: 'Save' });
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
- const unsubscribe = window.__sandbox__.onState('theme', (value) => {
121
- document.body.dataset.theme = String(value);
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
- For TypeScript support in sandbox-side code, add an ambient declaration:
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
- ```ts
128
- // sandbox-env.d.ts
129
- declare interface Window {
130
- __sandbox__: import('@vielzeug/sandbox').SandboxBridge;
131
- }
44
+ sandbox.dispose();
132
45
  ```
133
46
 
134
- ## Errors
47
+ ## Documentation
135
48
 
136
- `SandboxError` is the base class for package errors. `SandboxConfigurationError` rejects malformed CSP/document options during construction. `SandboxTimeoutError extends SandboxError` and is thrown by `render()` when no `'ready'` signal arrives within 5 seconds (usually a document missing the bridge script). This rejection happens in every build, not just dev — always handle it:
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
- ```ts
139
- import { SandboxTimeoutError } from '@vielzeug/sandbox';
55
+ ## License
140
56
 
141
- try {
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
- post({ type: 'ready' });
56
- if (typeof ResizeObserver !== 'undefined') {
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
@@ -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') {\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"],"mappings":"AAEA,SAAgB,EAAkB,EAAoC,CAIpE,MAAO;4BAHS,KAAK,UAAU,EAAU,OAIf,EAAQ;+BAHf,OAAO,EAAU,UAIP,EAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+DxC,KAAK,CACP"}
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
- post({ type: 'ready' });
58
- if (typeof ResizeObserver !== 'undefined') {
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
@@ -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') {\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"],"mappings":";AAEA,SAAgB,EAAkB,GAAoC;CAIpE,OAAO;4BAHS,KAAK,UAAU,EAAU,OAIf,EAAQ;+BAHf,OAAO,EAAU,UAIP,EAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+DxC,KAAK;AACP"}
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. 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"],"mappings":"AAGA,SAAgB,EAAK,EAAmB,CAExC,CAGA,SAAgB,EAAQ,EAAsB,CAE9C"}
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. 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"],"mappings":";AAGA,SAAgB,EAAK,GAAmB,CAExC;AAGA,SAAgB,EAAQ,GAAsB,CAE9C"}
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"}
@@ -1,21 +1,20 @@
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,`&amp;`).replace(/"/g,`&quot;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}function i(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}function a(a,o,s){let c=r(t.buildCspFromOptions(o)),l=Object.entries(o.namedStyles).map(([e,t])=>`<style id="${r(e)}">${n(t)}</style>`).join(`
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,`&amp;`).replace(/"/g,`&quot;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}function i(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}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)}"`:``,f=`<script${d}>window.__sandboxChannel=${JSON.stringify(s.channel)};window.__sandboxGeneration=${s.generation};<\/script>`;return`<!doctype html>
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
- ${e.buildBridgeScript(s)}
18
- <\/script>
19
18
  </body>
20
- </html>`}function o(e,n={}){return a(e,t.normalizeSandboxOptions(n),{channel:``,generation:0})}exports.buildDocument=o,exports.buildDocumentFromOptions=a;
19
+ </html>`}exports.buildDocumentFromOptions=a;
21
20
  //# sourceMappingURL=_document.cjs.map
@@ -1 +1 @@
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, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\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"],"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,WAAW,CAAC,CAC/C,KAAK,CAAC,EAAI,KAAS,cAAc,EAAW,CAAE,EAAE,IAAI,EAAU,CAAG,EAAE,SAAS,CAAC,CAC7E,KAAK;CAAI,EACN,EAAU,EAAQ,QACrB,IAAK,GAAQ,wCAAwC,EAAW,CAAG,EAAE,aAAY,CAAC,CAClF,KAAK;CAAI,EACN,EAAQ,EAAQ,MAAQ,WAAW,EAAW,EAAQ,KAAK,EAAE,GAAK,GAClE,EAAkB,UAAU,EAAM,2BAA2B,KAAK,UAAU,EAAU,OAAO,EAAE,8BAA8B,EAAU,WAAW,aAExJ,MAAO;cACK,EAAW,EAAQ,IAAI,EAAE;;sDAEe,EAAI;EACxD,EAAgB;;;SAGT,EAAW,EAAQ,KAAK,EAAE;EACjC,EAAO;;;EAGP,EAAQ;EACR,EAAK;SACE,EAAM;EACb,EAAA,kBAAkB,CAAS,EAAE;;;QAI/B,CAEA,SAAgB,EAAc,EAAc,EAA0B,CAAC,EAAW,CAChF,OAAO,EAAyB,EAAM,EAAA,wBAAwB,CAAO,EAAG,CAAE,QAAS,GAAI,WAAY,CAAE,CAAC,CACxG"}
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, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\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"}
@@ -1 +1 @@
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,CA6BR;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CAEhF"}
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,43 +1,36 @@
1
1
  import { buildBridgeScript as e } from "./_bridge.js";
2
- import { buildCspFromOptions as t, normalizeSandboxOptions as n } from "./_policy.js";
2
+ import { buildCspFromOptions as t } from "./_policy.js";
3
3
  //#region src/_document.ts
4
- function r(e) {
4
+ function n(e) {
5
5
  return e.replace(/<\/style/gi, "\\3C /style");
6
6
  }
7
- function i(e) {
7
+ function r(e) {
8
8
  return e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9
9
  }
10
- function a(e) {
10
+ function i(e) {
11
11
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
12
12
  }
13
- function o(n, o, s) {
14
- let c = i(t(o)), l = Object.entries(o.namedStyles).map(([e, t]) => `<style id="${i(e)}">${r(t)}</style>`).join("\n"), u = o.scripts.map((e) => `<script crossorigin="anonymous" src="${i(e)}"><\/script>`).join("\n"), d = o.nonce ? ` nonce="${i(o.nonce)}"` : "", f = `<script${d}>window.__sandboxChannel=${JSON.stringify(s.channel)};window.__sandboxGeneration=${s.generation};<\/script>`;
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="${i(o.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>${a(o.title)}</title>
21
+ <title>${i(o.title)}</title>
23
22
  ${l}
24
- </head>
25
- <body>
26
- ${u}
27
- ${n}
28
23
  <script${d}>
29
24
  ${e(s)}
30
25
  <\/script>
26
+ </head>
27
+ <body>
28
+ ${u}
29
+ ${a}
31
30
  </body>
32
31
  </html>`;
33
32
  }
34
- function s(e, t = {}) {
35
- return o(e, n(t), {
36
- channel: "",
37
- generation: 0
38
- });
39
- }
40
33
  //#endregion
41
- export { s as buildDocument, o as buildDocumentFromOptions };
34
+ export { a as buildDocumentFromOptions };
42
35
 
43
36
  //# sourceMappingURL=_document.js.map
@@ -1 +1 @@
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, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\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"],"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,WAAW,CAAC,CAC/C,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,IAClE,IAAkB,UAAU,EAAM,2BAA2B,KAAK,UAAU,EAAU,OAAO,EAAE,8BAA8B,EAAU,WAAW;CAExJ,OAAO;cACK,EAAW,EAAQ,IAAI,EAAE;;sDAEe,EAAI;EACxD,EAAgB;;;SAGT,EAAW,EAAQ,KAAK,EAAE;EACjC,EAAO;;;EAGP,EAAQ;EACR,EAAK;SACE,EAAM;EACb,EAAkB,CAAS,EAAE;;;;AAI/B;AAEA,SAAgB,EAAc,GAAc,IAA0B,CAAC,GAAW;CAChF,OAAO,EAAyB,GAAM,EAAwB,CAAO,GAAG;EAAE,SAAS;EAAI,YAAY;CAAE,CAAC;AACxG"}
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, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nfunction escapeText(value: string): string {\n return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\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_-]*$/,n=/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/,r=/^[A-Za-z0-9+/_-]+={0,2}$/;function i(t,n){throw new e.SandboxConfigurationError(`${t} is invalid: ${String(n)}`)}function a(e,t){let n;try{n=new URL(e)}catch{return i(t,e)}return n.protocol!==`http:`&&n.protocol!==`https:`||n.username||n.password||n.pathname!==`/`||n.search||n.hash?i(t,e):n.origin}function o(e,t){return(e??[]).map(e=>a(e,t))}function s(e){let t;try{t=new URL(e)}catch{return i(`scripts`,e)}return t.protocol!==`http:`&&t.protocol!==`https:`?i(`scripts`,e):t.href}function c(e){let n={};for(let[r,a]of Object.entries(e??{}))(!t.test(r)||typeof a!=`string`)&&i(`namedStyles`,r),n[r]=a;return n}function l(e={}){let t=e.lang??`en`;n.test(t)||i(`lang`,t),e.nonce!==void 0&&(!r.test(e.nonce)||e.nonce.length===0)&&i(`nonce`,e.nonce),e.title!==void 0&&typeof e.title!=`string`&&i(`title`,e.title);let a=(e.scripts??[]).map(s);return{allowedFontOrigins:o(e.allowedFontOrigins,`allowedFontOrigins`),allowedImageOrigins:o(e.allowedImageOrigins,`allowedImageOrigins`),allowedScriptOrigins:o(e.allowedScriptOrigins,`allowedScriptOrigins`),allowedStyleOrigins:o(e.allowedStyleOrigins,`allowedStyleOrigins`),lang:t,namedStyles:c(e.namedStyles),nonce:e.nonce,scripts:a,title:e.title??``}}function u(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 d(e={}){return u(l(e))}exports.buildCsp=d,exports.buildCspFromOptions=u,exports.normalizeSandboxOptions=l;
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
@@ -1 +1 @@
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 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"],"mappings":"gCAeA,IAAM,EAAK,2BACL,EAAO,wCACP,EAAQ,2BAEd,SAAS,EAAK,EAAe,EAAuB,CAClD,MAAM,IAAI,EAAA,0BAA0B,GAAG,EAAM,eAAe,OAAO,CAAK,GAAG,CAC7E,CAEA,SAAS,EAAgB,EAAe,EAAuB,CAC7D,IAAI,EAEJ,GAAI,CACF,EAAM,IAAI,IAAI,CAAK,CACrB,MAAQ,CACN,OAAO,EAAK,EAAO,CAAK,CAC1B,CAaA,OAVG,EAAI,WAAa,SAAW,EAAI,WAAa,UAC9C,EAAI,UACJ,EAAI,UACJ,EAAI,WAAa,KACjB,EAAI,QACJ,EAAI,KAEG,EAAK,EAAO,CAAK,EAGnB,EAAI,MACb,CAEA,SAAS,EAAiB,EAA8B,EAAyB,CAC/E,OAAQ,GAAU,CAAC,EAAA,CAAG,IAAK,GAAU,EAAgB,EAAO,CAAK,CAAC,CACpE,CAEA,SAAS,EAAgB,EAAuB,CAC9C,IAAI,EAEJ,GAAI,CACF,EAAM,IAAI,IAAI,CAAK,CACrB,MAAQ,CACN,OAAO,EAAK,UAAW,CAAK,CAC9B,CAIA,OAFI,EAAI,WAAa,SAAW,EAAI,WAAa,SAAiB,EAAK,UAAW,CAAK,EAEhF,EAAI,IACb,CAEA,SAAS,EAAgB,EAAoE,CAC3F,IAAM,EAAqC,CAAC,EAE5C,IAAK,GAAM,CAAC,EAAI,KAAQ,OAAO,QAAQ,GAAU,CAAC,CAAC,GAC7C,CAAC,EAAG,KAAK,CAAE,GAAK,OAAO,GAAQ,WAAU,EAAK,cAAe,CAAE,EAEnE,EAAW,GAAM,EAGnB,OAAO,CACT,CAEA,SAAgB,EAAwB,EAA0B,CAAC,EAA6B,CAC9F,IAAM,EAAO,EAAQ,MAAQ,KAExB,EAAK,KAAK,CAAI,GAAG,EAAK,OAAQ,CAAI,EAEnC,EAAQ,QAAU,IAAA,KAAc,CAAC,EAAM,KAAK,EAAQ,KAAK,GAAK,EAAQ,MAAM,SAAW,IACzF,EAAK,QAAS,EAAQ,KAAK,EAGzB,EAAQ,QAAU,IAAA,IAAa,OAAO,EAAQ,OAAU,UAAU,EAAK,QAAS,EAAQ,KAAK,EAEjG,IAAM,GAAW,EAAQ,SAAW,CAAC,EAAA,CAAG,IAAI,CAAe,EAE3D,MAAO,CACL,mBAAoB,EAAiB,EAAQ,mBAAoB,oBAAoB,EACrF,oBAAqB,EAAiB,EAAQ,oBAAqB,qBAAqB,EACxF,qBAAsB,EAAiB,EAAQ,qBAAsB,sBAAsB,EAC3F,oBAAqB,EAAiB,EAAQ,oBAAqB,qBAAqB,EACxF,OACA,YAAa,EAAgB,EAAQ,WAAW,EAChD,MAAO,EAAQ,MACf,UACA,MAAO,EAAQ,OAAS,EAC1B,CACF,CAEA,SAAgB,EAAoB,EAA2C,CAC7E,IAAM,EAAgB,CACpB,kBACA,GAAI,EAAQ,MAAQ,CAAC,UAAU,EAAQ,MAAM,EAAE,EAAI,CAAC,EACpD,GAAG,EAAQ,qBACX,GAAG,EAAQ,QAAQ,IAAK,GAAW,IAAI,IAAI,CAAM,CAAC,CAAC,MAAM,CAC3D,CAAC,CAAC,KAAK,GAAG,EACJ,EAAe,CAAC,kBAAmB,GAAG,EAAQ,mBAAmB,CAAC,CAAC,KAAK,GAAG,EAC3E,EAAe,CAAC,QAAS,GAAG,EAAQ,mBAAmB,CAAC,CAAC,KAAK,GAAG,EACjE,EAAc,EAAQ,mBAAmB,KAAK,GAAG,GAAK,SAE5D,MAAO,CACL,qBACA,cAAc,IACd,aAAa,IACb,WAAW,IACX,YAAY,IACZ,qBACA,qBACA,iBACF,CAAC,CAAC,KAAK,IAAI,CACb,CAEA,SAAgB,EAAS,EAA0B,CAAC,EAAW,CAC7D,OAAO,EAAoB,EAAwB,CAAO,CAAC,CAC7D"}
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":"gCAgBA,IAAa,EAA2B,IAElC,EAAK,2BACL,EAAO,wCACP,EAAQ,2BAEd,SAAS,EAAK,EAAe,EAAuB,CAClD,MAAM,IAAI,EAAA,0BAA0B,GAAG,EAAM,eAAe,OAAO,CAAK,GAAG,CAC7E,CAEA,SAAS,EAAgB,EAAgB,EAAuB,CAC9D,GAAI,OAAO,GAAU,SAAU,OAAO,EAAK,EAAO,CAAK,EAEvD,IAAI,EAEJ,GAAI,CACF,EAAM,IAAI,IAAI,CAAK,CACrB,MAAQ,CACN,OAAO,EAAK,EAAO,CAAK,CAC1B,CAaA,OAVG,EAAI,WAAa,SAAW,EAAI,WAAa,UAC9C,EAAI,UACJ,EAAI,UACJ,EAAI,WAAa,KACjB,EAAI,QACJ,EAAI,KAEG,EAAK,EAAO,CAAK,EAGnB,EAAI,MACb,CAEA,SAAS,EAAiB,EAA8B,EAAyB,CAI/E,OAHI,IAAW,IAAA,GAAkB,CAAC,EAC7B,MAAM,QAAQ,CAAM,EAElB,EAAO,IAAK,GAAU,EAAgB,EAAO,CAAK,CAAC,EAFvB,EAAK,EAAO,CAAM,CAGvD,CAEA,SAAS,EAAgB,EAAwB,CAC/C,GAAI,OAAO,GAAU,SAAU,OAAO,EAAK,UAAW,CAAK,EAE3D,IAAI,EAEJ,GAAI,CACF,EAAM,IAAI,IAAI,CAAK,CACrB,MAAQ,CACN,OAAO,EAAK,UAAW,CAAK,CAC9B,CAIA,OAFI,EAAI,WAAa,SAAW,EAAI,WAAa,SAAiB,EAAK,UAAW,CAAK,EAEhF,EAAI,IACb,CAEA,SAAS,EAAgB,EAAoE,CAC3F,GAAI,IAAW,IAAA,GAAW,MAAO,CAAC,EAClC,GAAI,OAAO,GAAW,WAAY,GAAmB,MAAM,QAAQ,CAAM,EAAG,OAAO,EAAK,SAAU,CAAM,EAExG,IAAM,EAAqC,CAAC,EAE5C,IAAK,GAAM,CAAC,EAAI,KAAQ,OAAO,QAAQ,CAAM,GACvC,CAAC,EAAG,KAAK,CAAE,GAAK,OAAO,GAAQ,WAAU,EAAK,SAAU,CAAE,EAE9D,EAAW,GAAM,EAGnB,OAAO,CACT,CAEA,SAAgB,EAAwB,EAA0B,CAAC,EAA6B,EAC1F,OAAO,GAAY,WAAY,GAAoB,MAAM,QAAQ,CAAO,IAAG,EAAK,UAAW,CAAO,EAEtG,IAAM,EAAO,EAAQ,MAAQ,KAExB,EAAK,KAAK,CAAI,GAAG,EAAK,OAAQ,CAAI,EAEnC,EAAQ,QAAU,IAAA,KAAc,CAAC,EAAM,KAAK,EAAQ,KAAK,GAAK,EAAQ,MAAM,SAAW,IACzF,EAAK,QAAS,EAAQ,KAAK,EAGzB,EAAQ,QAAU,IAAA,IAAa,OAAO,EAAQ,OAAU,UAAU,EAAK,QAAS,EAAQ,KAAK,EAEjG,IAAM,EAAkB,EAAQ,aAC1B,EAAe,GAAA,IAGnB,IAAoB,IAAA,KACnB,IAAoB,MACnB,OAAO,GAAoB,UAC3B,CAAC,OAAO,SAAS,CAAe,GAChC,GAAmB,GACnB,EAAkB,GAAK,GAAK,IAE9B,EAAK,eAAgB,CAAe,EAGlC,EAAQ,UAAY,IAAA,IAAa,CAAC,MAAM,QAAQ,EAAQ,OAAO,GAAG,EAAK,UAAW,EAAQ,OAAO,EAErG,IAAM,GAAW,EAAQ,SAAW,CAAC,EAAA,CAAG,IAAI,CAAe,EAE3D,MAAO,CACL,mBAAoB,EAAiB,EAAQ,mBAAoB,oBAAoB,EACrF,oBAAqB,EAAiB,EAAQ,oBAAqB,qBAAqB,EACxF,qBAAsB,EAAiB,EAAQ,qBAAsB,sBAAsB,EAC3F,oBAAqB,EAAiB,EAAQ,oBAAqB,qBAAqB,EACxF,OACA,MAAO,EAAQ,MACf,eACA,UACA,OAAQ,EAAgB,EAAQ,MAAM,EACtC,MAAO,EAAQ,OAAS,EAC1B,CACF,CAEA,SAAgB,EAAoB,EAA2C,CAC7E,IAAM,EAAgB,CACpB,kBACA,GAAI,EAAQ,MAAQ,CAAC,UAAU,EAAQ,MAAM,EAAE,EAAI,CAAC,EACpD,GAAG,EAAQ,qBACX,GAAG,EAAQ,QAAQ,IAAK,GAAW,IAAI,IAAI,CAAM,CAAC,CAAC,MAAM,CAC3D,CAAC,CAAC,KAAK,GAAG,EACJ,EAAe,CAAC,kBAAmB,GAAG,EAAQ,mBAAmB,CAAC,CAAC,KAAK,GAAG,EAC3E,EAAe,CAAC,QAAS,GAAG,EAAQ,mBAAmB,CAAC,CAAC,KAAK,GAAG,EACjE,EAAc,EAAQ,mBAAmB,KAAK,GAAG,GAAK,SAE5D,MAAO,CACL,qBACA,cAAc,IACd,aAAa,IACb,WAAW,IACX,YAAY,IACZ,qBACA,qBACA,iBACF,CAAC,CAAC,KAAK,IAAI,CACb"}
package/dist/_policy.d.ts CHANGED
@@ -5,11 +5,13 @@ 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;
15
17
  export declare function buildCsp(options?: SandboxOptions): string;
@@ -1 +1 @@
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,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AA+DD,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,cAAmB,GAAG,wBAAwB,CAwB9F;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAqB7E;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAE7D"}
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"}
package/dist/_policy.js CHANGED
@@ -1,52 +1,60 @@
1
1
  import { SandboxConfigurationError as e } from "./errors.js";
2
2
  //#region src/_policy.ts
3
- var t = /^[A-Za-z][A-Za-z0-9_-]*$/, n = /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/, r = /^[A-Za-z0-9+/_-]+={0,2}$/;
4
- function i(t, n) {
3
+ 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}$/;
4
+ function a(t, n) {
5
5
  throw new e(`${t} is invalid: ${String(n)}`);
6
6
  }
7
- function a(e, t) {
7
+ function o(e, t) {
8
+ if (typeof e != "string") return a(t, e);
8
9
  let n;
9
10
  try {
10
11
  n = new URL(e);
11
12
  } catch {
12
- return i(t, e);
13
+ return a(t, e);
13
14
  }
14
- return n.protocol !== "http:" && n.protocol !== "https:" || n.username || n.password || n.pathname !== "/" || n.search || n.hash ? i(t, e) : n.origin;
15
+ return n.protocol !== "http:" && n.protocol !== "https:" || n.username || n.password || n.pathname !== "/" || n.search || n.hash ? a(t, e) : n.origin;
15
16
  }
16
- function o(e, t) {
17
- return (e ?? []).map((e) => a(e, t));
17
+ function s(e, t) {
18
+ return e === void 0 ? [] : Array.isArray(e) ? e.map((e) => o(e, t)) : a(t, e);
18
19
  }
19
- function s(e) {
20
+ function c(e) {
21
+ if (typeof e != "string") return a("scripts", e);
20
22
  let t;
21
23
  try {
22
24
  t = new URL(e);
23
25
  } catch {
24
- return i("scripts", e);
26
+ return a("scripts", e);
25
27
  }
26
- return t.protocol !== "http:" && t.protocol !== "https:" ? i("scripts", e) : t.href;
28
+ return t.protocol !== "http:" && t.protocol !== "https:" ? a("scripts", e) : t.href;
27
29
  }
28
- function c(e) {
29
- let n = {};
30
- for (let [r, a] of Object.entries(e ?? {})) (!t.test(r) || typeof a != "string") && i("namedStyles", r), n[r] = a;
31
- return n;
30
+ function l(e) {
31
+ if (e === void 0) return {};
32
+ if (typeof e != "object" || !e || Array.isArray(e)) return a("styles", e);
33
+ let t = {};
34
+ for (let [r, i] of Object.entries(e)) (!n.test(r) || typeof i != "string") && a("styles", r), t[r] = i;
35
+ return t;
32
36
  }
33
- function l(e = {}) {
37
+ function u(e = {}) {
38
+ (typeof e != "object" || !e || Array.isArray(e)) && a("options", e);
34
39
  let t = e.lang ?? "en";
35
- n.test(t) || i("lang", t), e.nonce !== void 0 && (!r.test(e.nonce) || e.nonce.length === 0) && i("nonce", e.nonce), e.title !== void 0 && typeof e.title != "string" && i("title", e.title);
36
- let a = (e.scripts ?? []).map(s);
40
+ 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);
41
+ let n = e.readyTimeout, o = n ?? 5e3;
42
+ 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);
43
+ let u = (e.scripts ?? []).map(c);
37
44
  return {
38
- allowedFontOrigins: o(e.allowedFontOrigins, "allowedFontOrigins"),
39
- allowedImageOrigins: o(e.allowedImageOrigins, "allowedImageOrigins"),
40
- allowedScriptOrigins: o(e.allowedScriptOrigins, "allowedScriptOrigins"),
41
- allowedStyleOrigins: o(e.allowedStyleOrigins, "allowedStyleOrigins"),
45
+ allowedFontOrigins: s(e.allowedFontOrigins, "allowedFontOrigins"),
46
+ allowedImageOrigins: s(e.allowedImageOrigins, "allowedImageOrigins"),
47
+ allowedScriptOrigins: s(e.allowedScriptOrigins, "allowedScriptOrigins"),
48
+ allowedStyleOrigins: s(e.allowedStyleOrigins, "allowedStyleOrigins"),
42
49
  lang: t,
43
- namedStyles: c(e.namedStyles),
44
50
  nonce: e.nonce,
45
- scripts: a,
51
+ readyTimeout: o,
52
+ scripts: u,
53
+ styles: l(e.styles),
46
54
  title: e.title ?? ""
47
55
  };
48
56
  }
49
- function u(e) {
57
+ function d(e) {
50
58
  let t = [
51
59
  "'unsafe-inline'",
52
60
  ...e.nonce ? [`'nonce-${e.nonce}'`] : [],
@@ -64,10 +72,7 @@ function u(e) {
64
72
  "base-uri 'none'"
65
73
  ].join("; ");
66
74
  }
67
- function d(e = {}) {
68
- return u(l(e));
69
- }
70
75
  //#endregion
71
- export { d as buildCsp, u as buildCspFromOptions, l as normalizeSandboxOptions };
76
+ export { t as DEFAULT_READY_TIMEOUT_MS, d as buildCspFromOptions, u as normalizeSandboxOptions };
72
77
 
73
78
  //# sourceMappingURL=_policy.js.map