diff --git a/CHANGELOG.md b/CHANGELOG.md
index d440b7e..cc54e84 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,17 @@
+## next
+
+- Reworked sandbox init:
+ - UI is loading into a [sandboxed](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox) frame with `allow-scripts allow-forms allow-popups allow-modals` features enabled. That prevents access to [data storage/cookies](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#cross-origin_data_storage_access) and some JavaScript APIs from UI scripts (because `allow-same-origin` is not enabled) but puts UI scripts in the same conditions across environments (e.g. a regular page, a page in "incognito mode", a devtools page etc).
+ - Added `sandboxSrc` option for `createSandbox()` to specify a sandbox page URL, needed to define a specific origin e.g. in devtools
+ - Added `rempl/sanbox-init` endpoint which exposes a code to inject into a sandbox page to init UI scripts, e.g.
+ ```html
+
+
+ ```
+
## 1.0.0-alpha.22 (June 29, 2022)
- Fixed crash on UI init in a sandbox when a bundle declares variables with a name as a readonly globals referring to a window object like `top`, `parent` etc.
diff --git a/package.json b/package.json
index b8cbd34..2edee20 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,11 @@
"import": "./lib/browser.js"
}
},
+ "./sandbox-init": {
+ "types": "./lib/sandbox/browser/sandbox-init.d.ts",
+ "require": "./lib/sandbox/browser/sandbox-init.cjs",
+ "import": "./lib/sandbox/browser/sandbox-init.js"
+ },
"./dist/*": "./dist/*.js",
"./package.json": "./package.json"
},
diff --git a/scripts/transpile.cjs b/scripts/transpile.cjs
index 338c835..0a11c0c 100644
--- a/scripts/transpile.cjs
+++ b/scripts/transpile.cjs
@@ -193,7 +193,7 @@ async function transpileAll(options) {
const { watch = false, types = false, bundle = false } = options || {};
await transpile({
- entryPoints: ['src/node.ts', 'src/browser.ts'],
+ entryPoints: ['src/node.ts', 'src/browser.ts', 'src/sandbox/browser/sandbox-init.ts'],
outputDir: './lib',
format: 'esm',
watch,
diff --git a/src/sandbox/browser/index.ts b/src/sandbox/browser/index.ts
index c0c60ef..762e591 100644
--- a/src/sandbox/browser/index.ts
+++ b/src/sandbox/browser/index.ts
@@ -2,6 +2,7 @@
import { Sandbox } from '../../types.js';
import { OnInitCallback, EventTransport } from '../../transport/event.js';
import { globalThis, parent, genUID } from '../../utils/index.js';
+import { initSandboxScript } from './sandbox-init.js';
type Global = typeof globalThis;
type SandboxWindow = Window | Global;
@@ -9,6 +10,7 @@ type Settings =
| {
type: 'script';
content: Record;
+ sandboxSrc?: string;
window?: Window;
container?: HTMLElement;
}
@@ -23,23 +25,29 @@ const initEnvSubscriberMessage = new WeakMap();
// TODO: make tree-shaking friendly
if (parent !== globalThis) {
- addEventListener('message', function (event: MessageEvent) {
- const data = event.data || {};
+ addEventListener(
+ 'message',
+ function (event: MessageEvent) {
+ const data = event.data || {};
- if (event.source && data.to === 'rempl-env-publisher:connect') {
- initEnvSubscriberMessage.set(event.source, data);
- }
- });
+ if (event.source && data.to === 'rempl-env-publisher:connect') {
+ initEnvSubscriberMessage.set(event.source, data);
+ }
+ },
+ true
+ );
}
export function createSandbox(settings: Settings, callback: OnInitCallback) {
function initSandbox(sandboxWindow: SandboxWindow) {
if (settings.type === 'script') {
- for (const [sourceURL, source] of Object.entries(settings.content)) {
- (sandboxWindow as Global).eval(
- `(function(){${source}})()\n//# sourceURL=${sourceURL}`
- );
- }
+ sandboxWindow.postMessage(
+ {
+ action: 'rempl-sandbox-init-scripts',
+ scripts: settings.content,
+ },
+ '*'
+ );
}
if (parent !== globalThis && sandboxWindow !== globalThis) {
@@ -60,7 +68,7 @@ export function createSandbox(settings: Settings, callback: OnInitCallback) {
case 'rempl-env-subscriber:connect':
case toSandbox:
toEnv = data.from;
- sandboxWindow.postMessage(data);
+ sandboxWindow.postMessage(data, '*');
break;
case 'rempl-env-publisher:connect':
@@ -108,11 +116,20 @@ export function createSandbox(settings: Settings, callback: OnInitCallback) {
iframe = document.createElement('iframe');
iframe.name = genUID(); // to avoid cache
iframe.onload = () => iframe?.contentWindow && initSandbox(iframe.contentWindow);
+ iframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-popups allow-modals');
if (settings.type === 'url') {
iframe.src = settings.content;
+ } else if (settings.sandboxSrc) {
+ iframe.src = settings.sandboxSrc;
} else {
- iframe.srcdoc = '';
+ iframe.srcdoc = '';
+ // iframe.src = URL.createObjectURL(
+ // new Blob(
+ // [''],
+ // { type: 'text/html' }
+ // )
+ // );
}
(settings.container || document.documentElement).appendChild(iframe);
diff --git a/src/sandbox/browser/sandbox-init.ts b/src/sandbox/browser/sandbox-init.ts
new file mode 100644
index 0000000..a88017d
--- /dev/null
+++ b/src/sandbox/browser/sandbox-init.ts
@@ -0,0 +1,21 @@
+type SandboxInitEvent = MessageEvent<{
+ action: 'rempl-sandbox-init-scripts';
+ scripts: Record;
+}>;
+
+export function initSandboxScript() {
+ addEventListener('message', function handleMessage(event: SandboxInitEvent) {
+ const { action, scripts } = event.data || {};
+
+ if (action === 'rempl-sandbox-init-scripts' && scripts) {
+ // handle message only once
+ removeEventListener('message', handleMessage);
+
+ // evaluate scripts
+ for (const [sourceURL, source] of Object.entries(scripts)) {
+ // indirect eval, see detail: https://esbuild.github.io/content-types/#direct-eval
+ Function(`${source}\n//# sourceURL=${sourceURL}`)();
+ }
+ }
+ });
+}
diff --git a/src/transport/event.ts b/src/transport/event.ts
index 3cfae40..0af39ba 100644
--- a/src/transport/event.ts
+++ b/src/transport/event.ts
@@ -308,7 +308,7 @@ export class EventTransport {
payload,
};
- this.realm.postMessage(message);
+ this.realm.postMessage(message, '*');
}
}