safe-content-frame 0.0.4 → 0.0.6

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/dist/index.d.ts CHANGED
@@ -1,18 +1,18 @@
1
- type SandboxOption = "allow-same-origin" | "allow-scripts" | "allow-forms" | "allow-popups" | "allow-modals" | "allow-downloads" | "allow-popups-to-escape-sandbox";
2
- interface SafeContentFrameOptions {
1
+ export type SandboxOption = "allow-same-origin" | "allow-scripts" | "allow-forms" | "allow-popups" | "allow-modals" | "allow-downloads" | "allow-popups-to-escape-sandbox";
2
+ export interface SafeContentFrameOptions {
3
3
  useShadowDom?: boolean;
4
4
  enableBrowserCaching?: boolean;
5
5
  sandbox?: SandboxOption[];
6
6
  salt?: string;
7
7
  }
8
- interface RenderedFrame {
8
+ export interface RenderedFrame {
9
9
  iframe: HTMLIFrameElement;
10
10
  origin: string;
11
11
  sendMessage(data: unknown, transfer?: Transferable[]): void;
12
12
  fullyLoadedPromiseWithTimeout(timeoutMs: number): Promise<void>;
13
13
  dispose(): void;
14
14
  }
15
- declare class SafeContentFrame {
15
+ export declare class SafeContentFrame {
16
16
  private product;
17
17
  private options;
18
18
  constructor(product: string, options?: SafeContentFrameOptions);
@@ -24,5 +24,4 @@ declare class SafeContentFrame {
24
24
  private render;
25
25
  private getSandbox;
26
26
  }
27
-
28
- export { type RenderedFrame, SafeContentFrame, type SafeContentFrameOptions, type SandboxOption };
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GACrB,mBAAmB,GACnB,eAAe,GACf,aAAa,GACb,cAAc,GACd,cAAc,GACd,iBAAiB,GACjB,gCAAgC,CAAC;AAErC,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAC5D,6BAA6B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,OAAO,IAAI,IAAI,CAAC;CACjB;AA2DD,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;gBADP,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,uBAA4B;IAGzC,UAAU,CACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,WAAW,EACtB,IAAI,CAAC,EAAE;QAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;KAAE,GACvC,OAAO,CAAC,aAAa,CAAC;IASnB,SAAS,CACb,OAAO,EAAE,UAAU,GAAG,MAAM,EAC5B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,WAAW,GACrB,OAAO,CAAC,aAAa,CAAC;IAMnB,SAAS,CACb,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,WAAW,GACrB,OAAO,CAAC,aAAa,CAAC;YAIX,MAAM;IA2EpB,OAAO,CAAC,UAAU;CAMnB"}
package/dist/index.js CHANGED
@@ -1,132 +1,119 @@
1
- // src/index.ts
2
- var SCF_HOST = "scf.auiusercontent.com";
3
- var PRODUCT_HASH = "h184756";
1
+ const SCF_HOST = "scf.auiusercontent.com";
2
+ const PRODUCT_HASH = "h184756";
4
3
  async function sha256(data) {
5
- return crypto.subtle.digest("SHA-256", data);
4
+ return crypto.subtle.digest("SHA-256", data);
6
5
  }
7
6
  async function computeOriginHash(product, salt, origin) {
8
- const enc = new TextEncoder();
9
- const sep = enc.encode("$@#|");
10
- const parts = [
11
- enc.encode(product),
12
- sep,
13
- new Uint8Array(salt),
14
- sep,
15
- enc.encode(origin)
16
- ];
17
- const combined = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
18
- let offset = 0;
19
- for (const p of parts) {
20
- combined.set(p, offset);
21
- offset += p.length;
22
- }
23
- const hash = new Uint8Array(await sha256(combined.buffer));
24
- const bigint = hash.reduce(
25
- (acc, b) => BigInt(256) * acc + BigInt(b),
26
- BigInt(0)
27
- );
28
- return bigint.toString(36).padStart(50, "0").slice(0, 50);
7
+ const enc = new TextEncoder();
8
+ const sep = enc.encode("$@#|");
9
+ const parts = [
10
+ enc.encode(product),
11
+ sep,
12
+ new Uint8Array(salt),
13
+ sep,
14
+ enc.encode(origin),
15
+ ];
16
+ const combined = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
17
+ let offset = 0;
18
+ for (const p of parts) {
19
+ combined.set(p, offset);
20
+ offset += p.length;
21
+ }
22
+ const hash = new Uint8Array(await sha256(combined.buffer));
23
+ const bigint = hash.reduce((acc, b) => BigInt(256) * acc + BigInt(b), BigInt(0));
24
+ return bigint.toString(36).padStart(50, "0").slice(0, 50);
29
25
  }
30
26
  function randomSalt() {
31
- const arr = new Uint8Array(10);
32
- crypto.getRandomValues(arr);
33
- return arr.buffer;
27
+ const arr = new Uint8Array(10);
28
+ crypto.getRandomValues(arr);
29
+ return arr.buffer;
34
30
  }
35
31
  async function contentSalt(content, pathname) {
36
- const enc = new TextEncoder();
37
- const sep = enc.encode("$@#|");
38
- const combined = new Uint8Array(
39
- content.length + sep.length + pathname.length
40
- );
41
- combined.set(content, 0);
42
- combined.set(sep, content.length);
43
- combined.set(enc.encode(pathname), content.length + sep.length);
44
- return sha256(combined.buffer);
32
+ const enc = new TextEncoder();
33
+ const sep = enc.encode("$@#|");
34
+ const combined = new Uint8Array(content.length + sep.length + pathname.length);
35
+ combined.set(content, 0);
36
+ combined.set(sep, content.length);
37
+ combined.set(enc.encode(pathname), content.length + sep.length);
38
+ return sha256(combined.buffer);
45
39
  }
46
- var SafeContentFrame = class {
47
- constructor(product, options = {}) {
48
- this.product = product;
49
- this.options = options;
50
- }
51
- async renderHtml(html, container, opts) {
52
- return this.render(
53
- new TextEncoder().encode(html),
54
- "text/html; charset=utf-8",
55
- container,
56
- opts
57
- );
58
- }
59
- async renderRaw(content, mimeType, container) {
60
- const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
61
- return this.render(data, mimeType, container);
62
- }
63
- async renderPdf(content, container) {
64
- return this.render(content, "application/pdf", container);
65
- }
66
- async render(content, mimeType, container, opts) {
67
- const origin = window.location.origin;
68
- const salt = this.options.salt ? new TextEncoder().encode(this.options.salt).buffer : this.options.enableBrowserCaching ? await contentSalt(content, location.pathname) : randomSalt();
69
- const hash = await computeOriginHash(this.product, salt, origin);
70
- const shimUrl = `https://${hash}-${PRODUCT_HASH}.${SCF_HOST}/${this.product}/shim.html?origin=${encodeURIComponent(origin)}${this.options.enableBrowserCaching ? "&cache=1" : ""}`;
71
- const iframeOrigin = new URL(shimUrl).origin;
72
- const iframe = document.createElement("iframe");
73
- iframe.setAttribute("sandbox", this.getSandbox());
74
- iframe.style.cssText = "border:none;width:100%;height:100%";
75
- if (this.options.useShadowDom) {
76
- const host = document.createElement("div");
77
- host.attachShadow({ mode: "closed" }).appendChild(iframe);
78
- container.appendChild(host);
79
- } else {
80
- container.appendChild(iframe);
40
+ export class SafeContentFrame {
41
+ product;
42
+ options;
43
+ constructor(product, options = {}) {
44
+ this.product = product;
45
+ this.options = options;
46
+ }
47
+ async renderHtml(html, container, opts) {
48
+ return this.render(new TextEncoder().encode(html), "text/html; charset=utf-8", container, opts);
49
+ }
50
+ async renderRaw(content, mimeType, container) {
51
+ const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
52
+ return this.render(data, mimeType, container);
53
+ }
54
+ async renderPdf(content, container) {
55
+ return this.render(content, "application/pdf", container);
81
56
  }
82
- return new Promise((resolve, reject) => {
83
- const channel = new MessageChannel();
84
- let onLoaded;
85
- const loaded = new Promise((r) => {
86
- onLoaded = r;
87
- });
88
- channel.port1.onmessage = (e) => {
89
- if (e.data?.type === "msg") onLoaded();
90
- else if (e.data?.type === "error") reject(new Error(e.data.message));
91
- };
92
- iframe.onload = () => {
93
- iframe.contentWindow?.postMessage(
94
- {
95
- body: content.buffer.slice(
96
- content.byteOffset,
97
- content.byteOffset + content.byteLength
98
- ),
99
- mimeType,
100
- salt,
101
- unsafeDocumentWrite: opts?.unsafeDocumentWrite
102
- },
103
- iframeOrigin,
104
- [channel.port2]
105
- );
106
- resolve({
107
- iframe,
108
- origin: iframeOrigin,
109
- sendMessage: (data, transfer) => iframe.contentWindow?.postMessage(data, iframeOrigin, transfer),
110
- fullyLoadedPromiseWithTimeout: (ms) => Promise.race([
111
- loaded,
112
- new Promise(
113
- (_, rej) => setTimeout(() => rej(new Error("Timeout")), ms)
114
- )
115
- ]),
116
- dispose: () => iframe.remove()
57
+ async render(content, mimeType, container, opts) {
58
+ const origin = window.location.origin;
59
+ const salt = this.options.salt
60
+ ? new TextEncoder().encode(this.options.salt).buffer
61
+ : this.options.enableBrowserCaching
62
+ ? await contentSalt(content, location.pathname)
63
+ : randomSalt();
64
+ const hash = await computeOriginHash(this.product, salt, origin);
65
+ const shimUrl = `https://${hash}-${PRODUCT_HASH}.${SCF_HOST}/${this.product}/shim.html?origin=${encodeURIComponent(origin)}${this.options.enableBrowserCaching ? "&cache=1" : ""}`;
66
+ const iframeOrigin = new URL(shimUrl).origin;
67
+ const iframe = document.createElement("iframe");
68
+ iframe.setAttribute("sandbox", this.getSandbox());
69
+ iframe.style.cssText = "border:none;width:100%;height:100%";
70
+ if (this.options.useShadowDom) {
71
+ const host = document.createElement("div");
72
+ host.attachShadow({ mode: "closed" }).appendChild(iframe);
73
+ container.appendChild(host);
74
+ }
75
+ else {
76
+ container.appendChild(iframe);
77
+ }
78
+ return new Promise((resolve, reject) => {
79
+ const channel = new MessageChannel();
80
+ let onLoaded;
81
+ const loaded = new Promise((r) => {
82
+ onLoaded = r;
83
+ });
84
+ channel.port1.onmessage = (e) => {
85
+ if (e.data?.type === "msg")
86
+ onLoaded();
87
+ else if (e.data?.type === "error")
88
+ reject(new Error(e.data.message));
89
+ };
90
+ iframe.onload = () => {
91
+ iframe.contentWindow?.postMessage({
92
+ body: content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength),
93
+ mimeType,
94
+ salt,
95
+ unsafeDocumentWrite: opts?.unsafeDocumentWrite,
96
+ }, iframeOrigin, [channel.port2]);
97
+ resolve({
98
+ iframe,
99
+ origin: iframeOrigin,
100
+ sendMessage: (data, transfer) => iframe.contentWindow?.postMessage(data, iframeOrigin, transfer),
101
+ fullyLoadedPromiseWithTimeout: (ms) => Promise.race([
102
+ loaded,
103
+ new Promise((_, rej) => setTimeout(() => rej(new Error("Timeout")), ms)),
104
+ ]),
105
+ dispose: () => iframe.remove(),
106
+ });
107
+ };
108
+ iframe.onerror = () => reject(new Error("Failed to load iframe"));
109
+ iframe.src = shimUrl;
117
110
  });
118
- };
119
- iframe.onerror = () => reject(new Error("Failed to load iframe"));
120
- iframe.src = shimUrl;
121
- });
122
- }
123
- getSandbox() {
124
- const s = new Set(this.options.sandbox || []);
125
- s.add("allow-same-origin");
126
- s.add("allow-scripts");
127
- return [...s].join(" ");
128
- }
129
- };
130
- export {
131
- SafeContentFrame
132
- };
111
+ }
112
+ getSandbox() {
113
+ const s = new Set(this.options.sandbox || []);
114
+ s.add("allow-same-origin");
115
+ s.add("allow-scripts");
116
+ return [...s].join(" ");
117
+ }
118
+ }
119
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,3 @@
1
- declare const enableShadowDom: () => boolean;
2
- declare const unsafeDisableShadowDom: () => boolean;
3
-
4
- export { enableShadowDom, unsafeDisableShadowDom };
1
+ export declare const enableShadowDom: () => boolean;
2
+ export declare const unsafeDisableShadowDom: () => boolean;
3
+ //# sourceMappingURL=shadow_dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow_dom.d.ts","sourceRoot":"","sources":["../src/shadow_dom.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,eAAa,CAAC;AAC1C,eAAO,MAAM,sBAAsB,eAAc,CAAC"}
@@ -1,7 +1,3 @@
1
- // src/shadow_dom.ts
2
- var enableShadowDom = () => true;
3
- var unsafeDisableShadowDom = () => false;
4
- export {
5
- enableShadowDom,
6
- unsafeDisableShadowDom
7
- };
1
+ export const enableShadowDom = () => true;
2
+ export const unsafeDisableShadowDom = () => false;
3
+ //# sourceMappingURL=shadow_dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow_dom.js","sourceRoot":"","sources":["../src/shadow_dom.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC"}
package/package.json CHANGED
@@ -1,15 +1,26 @@
1
1
  {
2
2
  "name": "safe-content-frame",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Secure iframe rendering for untrusted content using SafeContentFrame",
5
+ "keywords": [
6
+ "iframe",
7
+ "sandbox",
8
+ "security",
9
+ "html",
10
+ "content",
11
+ "safe"
12
+ ],
13
+ "author": "AgentbaseAI Inc.",
5
14
  "license": "MIT",
6
15
  "type": "module",
7
16
  "exports": {
8
17
  ".": {
18
+ "aui-source": "./src/index.ts",
9
19
  "types": "./dist/index.d.ts",
10
20
  "default": "./dist/index.js"
11
21
  },
12
22
  "./shadow_dom": {
23
+ "aui-source": "./src/shadow_dom.ts",
13
24
  "types": "./dist/shadow_dom.d.ts",
14
25
  "default": "./dist/shadow_dom.js"
15
26
  }
@@ -17,27 +28,31 @@
17
28
  "main": "./dist/index.js",
18
29
  "types": "./dist/index.d.ts",
19
30
  "files": [
20
- "dist"
31
+ "dist",
32
+ "src",
33
+ "shadow_dom",
34
+ "README.md"
21
35
  ],
36
+ "sideEffects": false,
22
37
  "devDependencies": {
23
- "tsup": "^8.5.1",
24
- "typescript": "^5.9.3",
25
- "vite": "^7.2.7",
38
+ "vite": "^7.3.1",
26
39
  "@assistant-ui/x-buildutils": "0.0.1"
27
40
  },
28
41
  "publishConfig": {
29
42
  "access": "public",
30
43
  "provenance": true
31
44
  },
45
+ "homepage": "https://www.assistant-ui.com/safe-content-frame",
32
46
  "repository": {
33
47
  "type": "git",
34
- "url": "https://github.com/assistant-ui/assistant-ui/tree/main/packages/safe-content-frame"
48
+ "url": "git+https://github.com/assistant-ui/assistant-ui.git",
49
+ "directory": "packages/safe-content-frame"
35
50
  },
36
51
  "bugs": {
37
52
  "url": "https://github.com/assistant-ui/assistant-ui/issues"
38
53
  },
39
54
  "scripts": {
40
- "build": "tsup src/index.ts src/shadow_dom.ts --format esm --dts",
55
+ "build": "aui-build",
41
56
  "dev": "vite demo"
42
57
  }
43
58
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "type": "module",
3
+ "main": "../dist/shadow_dom.js",
4
+ "types": "../dist/shadow_dom.d.ts"
5
+ }
package/src/index.ts ADDED
@@ -0,0 +1,199 @@
1
+ export type SandboxOption =
2
+ | "allow-same-origin"
3
+ | "allow-scripts"
4
+ | "allow-forms"
5
+ | "allow-popups"
6
+ | "allow-modals"
7
+ | "allow-downloads"
8
+ | "allow-popups-to-escape-sandbox";
9
+
10
+ export interface SafeContentFrameOptions {
11
+ useShadowDom?: boolean;
12
+ enableBrowserCaching?: boolean;
13
+ sandbox?: SandboxOption[];
14
+ salt?: string;
15
+ }
16
+
17
+ export interface RenderedFrame {
18
+ iframe: HTMLIFrameElement;
19
+ origin: string;
20
+ sendMessage(data: unknown, transfer?: Transferable[]): void;
21
+ fullyLoadedPromiseWithTimeout(timeoutMs: number): Promise<void>;
22
+ dispose(): void;
23
+ }
24
+
25
+ const SCF_HOST = "scf.auiusercontent.com";
26
+ const PRODUCT_HASH = "h184756";
27
+
28
+ async function sha256(data: ArrayBuffer): Promise<ArrayBuffer> {
29
+ return crypto.subtle.digest("SHA-256", data);
30
+ }
31
+
32
+ async function computeOriginHash(
33
+ product: string,
34
+ salt: ArrayBuffer,
35
+ origin: string,
36
+ ): Promise<string> {
37
+ const enc = new TextEncoder();
38
+ const sep = enc.encode("$@#|");
39
+ const parts = [
40
+ enc.encode(product),
41
+ sep,
42
+ new Uint8Array(salt),
43
+ sep,
44
+ enc.encode(origin),
45
+ ];
46
+ const combined = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
47
+ let offset = 0;
48
+ for (const p of parts) {
49
+ combined.set(p, offset);
50
+ offset += p.length;
51
+ }
52
+
53
+ const hash = new Uint8Array(await sha256(combined.buffer as ArrayBuffer));
54
+ const bigint = hash.reduce(
55
+ (acc, b) => BigInt(256) * acc + BigInt(b),
56
+ BigInt(0),
57
+ );
58
+ return bigint.toString(36).padStart(50, "0").slice(0, 50);
59
+ }
60
+
61
+ function randomSalt(): ArrayBuffer {
62
+ const arr = new Uint8Array(10);
63
+ crypto.getRandomValues(arr);
64
+ return arr.buffer as ArrayBuffer;
65
+ }
66
+
67
+ async function contentSalt(
68
+ content: Uint8Array,
69
+ pathname: string,
70
+ ): Promise<ArrayBuffer> {
71
+ const enc = new TextEncoder();
72
+ const sep = enc.encode("$@#|");
73
+ const combined = new Uint8Array(
74
+ content.length + sep.length + pathname.length,
75
+ );
76
+ combined.set(content, 0);
77
+ combined.set(sep, content.length);
78
+ combined.set(enc.encode(pathname), content.length + sep.length);
79
+ return sha256(combined.buffer as ArrayBuffer);
80
+ }
81
+
82
+ export class SafeContentFrame {
83
+ constructor(
84
+ private product: string,
85
+ private options: SafeContentFrameOptions = {},
86
+ ) {}
87
+
88
+ async renderHtml(
89
+ html: string,
90
+ container: HTMLElement,
91
+ opts?: { unsafeDocumentWrite?: boolean },
92
+ ): Promise<RenderedFrame> {
93
+ return this.render(
94
+ new TextEncoder().encode(html),
95
+ "text/html; charset=utf-8",
96
+ container,
97
+ opts,
98
+ );
99
+ }
100
+
101
+ async renderRaw(
102
+ content: Uint8Array | string,
103
+ mimeType: string,
104
+ container: HTMLElement,
105
+ ): Promise<RenderedFrame> {
106
+ const data =
107
+ typeof content === "string" ? new TextEncoder().encode(content) : content;
108
+ return this.render(data, mimeType, container);
109
+ }
110
+
111
+ async renderPdf(
112
+ content: Uint8Array,
113
+ container: HTMLElement,
114
+ ): Promise<RenderedFrame> {
115
+ return this.render(content, "application/pdf", container);
116
+ }
117
+
118
+ private async render(
119
+ content: Uint8Array,
120
+ mimeType: string,
121
+ container: HTMLElement,
122
+ opts?: { unsafeDocumentWrite?: boolean },
123
+ ): Promise<RenderedFrame> {
124
+ const origin = window.location.origin;
125
+ const salt = this.options.salt
126
+ ? (new TextEncoder().encode(this.options.salt).buffer as ArrayBuffer)
127
+ : this.options.enableBrowserCaching
128
+ ? await contentSalt(content, location.pathname)
129
+ : randomSalt();
130
+
131
+ const hash = await computeOriginHash(this.product, salt, origin);
132
+ const shimUrl = `https://${hash}-${PRODUCT_HASH}.${SCF_HOST}/${this.product}/shim.html?origin=${encodeURIComponent(origin)}${this.options.enableBrowserCaching ? "&cache=1" : ""}`;
133
+ const iframeOrigin = new URL(shimUrl).origin;
134
+
135
+ const iframe = document.createElement("iframe");
136
+ iframe.setAttribute("sandbox", this.getSandbox());
137
+ iframe.style.cssText = "border:none;width:100%;height:100%";
138
+
139
+ if (this.options.useShadowDom) {
140
+ const host = document.createElement("div");
141
+ host.attachShadow({ mode: "closed" }).appendChild(iframe);
142
+ container.appendChild(host);
143
+ } else {
144
+ container.appendChild(iframe);
145
+ }
146
+
147
+ return new Promise((resolve, reject) => {
148
+ const channel = new MessageChannel();
149
+ let onLoaded: () => void;
150
+ const loaded = new Promise<void>((r) => {
151
+ onLoaded = r;
152
+ });
153
+
154
+ channel.port1.onmessage = (e) => {
155
+ if (e.data?.type === "msg") onLoaded();
156
+ else if (e.data?.type === "error") reject(new Error(e.data.message));
157
+ };
158
+
159
+ iframe.onload = () => {
160
+ iframe.contentWindow?.postMessage(
161
+ {
162
+ body: content.buffer.slice(
163
+ content.byteOffset,
164
+ content.byteOffset + content.byteLength,
165
+ ),
166
+ mimeType,
167
+ salt,
168
+ unsafeDocumentWrite: opts?.unsafeDocumentWrite,
169
+ },
170
+ iframeOrigin,
171
+ [channel.port2],
172
+ );
173
+ resolve({
174
+ iframe,
175
+ origin: iframeOrigin,
176
+ sendMessage: (data, transfer) =>
177
+ iframe.contentWindow?.postMessage(data, iframeOrigin, transfer),
178
+ fullyLoadedPromiseWithTimeout: (ms) =>
179
+ Promise.race([
180
+ loaded,
181
+ new Promise<void>((_, rej) =>
182
+ setTimeout(() => rej(new Error("Timeout")), ms),
183
+ ),
184
+ ]),
185
+ dispose: () => iframe.remove(),
186
+ });
187
+ };
188
+ iframe.onerror = () => reject(new Error("Failed to load iframe"));
189
+ iframe.src = shimUrl;
190
+ });
191
+ }
192
+
193
+ private getSandbox(): string {
194
+ const s = new Set(this.options.sandbox || []);
195
+ s.add("allow-same-origin");
196
+ s.add("allow-scripts");
197
+ return [...s].join(" ");
198
+ }
199
+ }
@@ -0,0 +1,2 @@
1
+ export const enableShadowDom = () => true;
2
+ export const unsafeDisableShadowDom = () => false;