@immediately-run/sandpack-client 2.22.0 → 2.22.1
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/node/index.js +2 -2
- package/dist/clients/node/index.mjs +2 -2
- package/dist/clients/runtime/gestureGate.d.ts +19 -0
- package/dist/clients/runtime/iframe-protocol.d.ts +1 -1
- package/dist/clients/runtime/index.d.ts +12 -1
- package/dist/clients/runtime/index.js +140 -21
- package/dist/clients/runtime/index.mjs +139 -22
- package/dist/{consoleHook-BYuGaxe8.mjs → consoleHook-DT9Wd42J.mjs} +1 -1
- package/dist/{consoleHook-BRSVCdB0.js → consoleHook-vrsBdedj.js} +1 -1
- package/dist/{index-CetxPB1x.mjs → index-CSAGdFtt.mjs} +2 -2
- package/dist/{index-dX5nvjo5.js → index-Dnq1i5aM.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{types-KEkDKvIe.js → types-B3g_70H8.js} +34 -7
- package/dist/{types-BgalzxpH.mjs → types-DRg992RB.mjs} +34 -7
- package/dist/types.d.ts +7 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandpack-client",
|
|
3
|
-
"version": "2.22.
|
|
4
|
-
"srcHash": "
|
|
5
|
-
"builtAt": "2026-
|
|
3
|
+
"version": "2.22.1",
|
|
4
|
+
"srcHash": "10bae547420c39b69868a88e4aa7810abbbd8490309a16a6154ca3fdc16e7b4f",
|
|
5
|
+
"builtAt": "2026-09-13T05:42:11.272Z"
|
|
6
6
|
}
|
|
@@ -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-B3g_70H8.js');
|
|
6
6
|
var base = require('../../base-DelKLlDk.js');
|
|
7
|
-
var consoleHook = require('../../consoleHook-
|
|
7
|
+
var consoleHook = require('../../consoleHook-vrsBdedj.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-DRg992RB.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-DT9Wd42J.mjs';
|
|
6
6
|
import 'outvariant';
|
|
7
7
|
import '@zenfs/core';
|
|
8
8
|
import 'dequal';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R3-367 — the getCodeSandboxURL gesture gate, extracted so it is testable
|
|
3
|
+
* without standing the whole runtime graph up (and so the fork's node-env
|
|
4
|
+
* suites can import it too).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Whether a codesandbox.io export may run: the user has been ACTIVE on this
|
|
8
|
+
* page (sticky activation — the fs snapshot can outlive a transient window).
|
|
9
|
+
* False ⇒ app/script-initiated context ⇒ the export must refuse before
|
|
10
|
+
* reading the filesystem.
|
|
11
|
+
*/
|
|
12
|
+
export declare const codeSandboxExportAllowed: () => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Make a gesture-gate refusal observable: a `sandpack-security-violation`
|
|
15
|
+
* CustomEvent the host (site-main) can listen for and journal into its
|
|
16
|
+
* security-events stream. Never throws — observability must not become a
|
|
17
|
+
* second failure mode on the refusal path.
|
|
18
|
+
*/
|
|
19
|
+
export declare const notifyCodeSandboxExportRefused: () => void;
|
|
@@ -6,7 +6,7 @@ export declare class IFrameProtocol {
|
|
|
6
6
|
private globalListenersCount;
|
|
7
7
|
channelListeners: Record<number, ListenerFunction>;
|
|
8
8
|
private channelListenersCount;
|
|
9
|
-
readonly channelId:
|
|
9
|
+
readonly channelId: string;
|
|
10
10
|
constructor(iframe: HTMLIFrameElement, _origin: string);
|
|
11
11
|
cleanup(): void;
|
|
12
12
|
register(port?: MessagePort, config?: Record<string, unknown>, babelPort?: MessagePort): void;
|
|
@@ -4,6 +4,7 @@ import { SandpackClient } from "../base";
|
|
|
4
4
|
import Protocol from "./file-resolver-protocol";
|
|
5
5
|
import { IFrameProtocol } from "./iframe-protocol";
|
|
6
6
|
import { type SandpackRuntimeMessage } from "./types";
|
|
7
|
+
export { codeSandboxExportAllowed, notifyCodeSandboxExportRefused, } from "./gestureGate";
|
|
7
8
|
export declare class SandpackRuntime extends SandpackClient {
|
|
8
9
|
fileResolverProtocol?: Protocol;
|
|
9
10
|
immutableFetchProtocol?: Protocol;
|
|
@@ -81,7 +82,17 @@ export declare class SandpackRuntime extends SandpackClient {
|
|
|
81
82
|
dispatch(message: SandpackRuntimeMessage): void;
|
|
82
83
|
listen(listener: ListenerFunction): UnsubscribeFunction;
|
|
83
84
|
/**
|
|
84
|
-
* Get the URL of the contents of the current sandbox
|
|
85
|
+
* Get the URL of the contents of the current sandbox.
|
|
86
|
+
*
|
|
87
|
+
* R3-367 — GESTURE-GATED. This POSTs the ENTIRE app filesystem to
|
|
88
|
+
* codesandbox.io from the parent page; the fs may contain the user's data
|
|
89
|
+
* (spaces, mounts), so the export is reachable only when the user has been
|
|
90
|
+
* active on the page (`navigator.userActivation.hasBeenActive` — the sticky
|
|
91
|
+
* flag, because the fs snapshot can outlive a transient-activation window).
|
|
92
|
+
* Without any user activation this refuses BEFORE reading the filesystem:
|
|
93
|
+
* nothing is posted, and a `sandpack-security-violation` CustomEvent is
|
|
94
|
+
* dispatched on the window for the host to journal (site-main wires the
|
|
95
|
+
* listener into its security-events seam).
|
|
85
96
|
*/
|
|
86
97
|
getCodeSandboxURL(): Promise<{
|
|
87
98
|
sandboxId: string;
|
|
@@ -2,7 +2,7 @@
|
|
|
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-B3g_70H8.js');
|
|
6
6
|
var base = require('../../base-DelKLlDk.js');
|
|
7
7
|
var iframeFactory = require('../../iframe-factory-Bc7tcyQZ.js');
|
|
8
8
|
var mimeDB = require('mime-db');
|
|
@@ -66,6 +66,21 @@ var Protocol = /** @class */ (function () {
|
|
|
66
66
|
return Protocol;
|
|
67
67
|
}());
|
|
68
68
|
|
|
69
|
+
// R3-367: crypto-random, 64-bit channel id (16 hex chars from 8 random bytes).
|
|
70
|
+
// This used to be Math.floor(Math.random()*1e6) — guessable, and the id was
|
|
71
|
+
// console.logged at registration. SECURITY INVARIANT (do not weaken): the id is
|
|
72
|
+
// a CORRELATION key only — every incoming message is authenticated by the
|
|
73
|
+
// `event.source === this.frameWindow` check in eventListener() below, NEVER by
|
|
74
|
+
// the id alone. The crypto entropy is defense-in-depth on top of that check
|
|
75
|
+
// (the frame-side twin comment lives in the sandbox bundler's protocol layer,
|
|
76
|
+
// R3-352 C1). `string | number` on the wire stays compatible with ids older
|
|
77
|
+
// peers minted as numbers: both sides compare the echoed value they themselves
|
|
78
|
+
// sent, so the type never has to agree across versions.
|
|
79
|
+
var randomChannelId = function () {
|
|
80
|
+
var bytes = new Uint8Array(8);
|
|
81
|
+
crypto.getRandomValues(bytes);
|
|
82
|
+
return Array.from(bytes, function (b) { return b.toString(16).padStart(2, "0"); }).join("");
|
|
83
|
+
};
|
|
69
84
|
var IFrameProtocol = /** @class */ (function () {
|
|
70
85
|
function IFrameProtocol(iframe, _origin) {
|
|
71
86
|
// React to messages from any iframe
|
|
@@ -74,8 +89,9 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
74
89
|
// React to messages from the iframe owned by this instance
|
|
75
90
|
this.channelListeners = {};
|
|
76
91
|
this.channelListenersCount = 0;
|
|
77
|
-
// Random
|
|
78
|
-
|
|
92
|
+
// Random id to identify this instance of the client when messages are coming
|
|
93
|
+
// from multiple iframes — crypto-random (see randomChannelId above).
|
|
94
|
+
this.channelId = randomChannelId();
|
|
79
95
|
this.frameWindow = iframe.contentWindow;
|
|
80
96
|
this.origin = "*"; //origin;
|
|
81
97
|
this.globalListeners = [];
|
|
@@ -101,8 +117,9 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
101
117
|
if (!this.frameWindow) {
|
|
102
118
|
return;
|
|
103
119
|
}
|
|
104
|
-
//
|
|
105
|
-
|
|
120
|
+
// R3-367: no channelId log — the id is not secret-critical, but logging it
|
|
121
|
+
// at registration handed an observer the correlation key for free (the
|
|
122
|
+
// evt.source check below is the actual authentication).
|
|
106
123
|
// Order matters: the bundler reads ports[0] as the fs port and ports[1] as
|
|
107
124
|
// the Babel worker port. `filter` keeps that order as long as the fs port
|
|
108
125
|
// is always present (it is, in normal operation).
|
|
@@ -153,7 +170,11 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
153
170
|
};
|
|
154
171
|
// Handles message windows coming from iframes
|
|
155
172
|
IFrameProtocol.prototype.eventListener = function (evt) {
|
|
156
|
-
//
|
|
173
|
+
// SECURITY INVARIANT (R3-367, twin of the randomChannelId comment): this
|
|
174
|
+
// source check — not the channelId — is what authenticates an incoming
|
|
175
|
+
// message. The id below only routes a message ALREADY accepted here to the
|
|
176
|
+
// owning instance. Weakening or reordering this check would let any iframe
|
|
177
|
+
// on the page speak on the bundler channel.
|
|
157
178
|
if (evt.source !== this.frameWindow) {
|
|
158
179
|
return;
|
|
159
180
|
}
|
|
@@ -190,24 +211,50 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
190
211
|
* registers via `registerImmutableUrlPrefix` (see sandpack-bundler
|
|
191
212
|
* `src/utils/fetch.ts`) — keep the two in sync.
|
|
192
213
|
*/
|
|
193
|
-
/**
|
|
194
|
-
* URL prefixes whose responses never change for a given URL (the URL encodes
|
|
195
|
-
* the exact content version). Only these may be fetched on the iframe's
|
|
196
|
-
* behalf, and they are safe to cache forever.
|
|
197
|
-
*/
|
|
198
214
|
var IMMUTABLE_URL_ALLOWLIST = [
|
|
199
215
|
// Module CDN, exact-versioned package bundles. (NOT /dep_tree/, which
|
|
200
216
|
// resolves semver ranges and changes as new versions publish.)
|
|
201
|
-
|
|
217
|
+
{
|
|
218
|
+
origin: "https://sandpack-cdn-staging.blazingly.io",
|
|
219
|
+
pathPrefix: "/package/",
|
|
220
|
+
},
|
|
202
221
|
// unpkg files, requested by the bundler at registry-resolved exact versions.
|
|
203
|
-
"https://unpkg.com/",
|
|
222
|
+
{ origin: "https://unpkg.com", pathPrefix: "/" },
|
|
204
223
|
// Self-hosted, versioned @immediately-run/sdk builds (SDK_PACKAGING_SPEC
|
|
205
224
|
// §5/§11, Option A). The /v/<version>/ path encodes the exact version, so
|
|
206
225
|
// responses are immutable; the bundler fetches these when an app opts the SDK
|
|
207
226
|
// into immediately.run.resolveFromRegistry. Keep in sync with the prefix the
|
|
208
227
|
// bundler registers via registerImmutableUrlPrefix (sandbox bundler.ts).
|
|
209
|
-
|
|
228
|
+
{
|
|
229
|
+
origin: "https://immediately-run.github.io",
|
|
230
|
+
pathPrefix: "/immediately-run-sdk/v/",
|
|
231
|
+
},
|
|
210
232
|
];
|
|
233
|
+
/**
|
|
234
|
+
* Is a PARSED URL inside the allowlist? Origin is compared exactly (a userinfo
|
|
235
|
+
* or lookalike host cannot match) and the path prefix against the URL parser's
|
|
236
|
+
* NORMALIZED pathname; additionally, any dot segment — literal or
|
|
237
|
+
* percent-encoded, in any hex case — is refused, so no spelling of `..` can
|
|
238
|
+
* cross the prefix boundary after a server-side decode.
|
|
239
|
+
*/
|
|
240
|
+
var inPolicy = function (u) {
|
|
241
|
+
var segments = u.pathname.split("/").map(function (segment) {
|
|
242
|
+
try {
|
|
243
|
+
return decodeURIComponent(segment).toLowerCase();
|
|
244
|
+
}
|
|
245
|
+
catch (_a) {
|
|
246
|
+
return segment.toLowerCase();
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
if (segments.some(function (s) { return s === ".." || s === "."; }))
|
|
250
|
+
return false;
|
|
251
|
+
if (/%2e/.test(u.pathname.toLowerCase()))
|
|
252
|
+
return false;
|
|
253
|
+
return IMMUTABLE_URL_ALLOWLIST.some(function (_a) {
|
|
254
|
+
var origin = _a.origin, pathPrefix = _a.pathPrefix;
|
|
255
|
+
return u.origin === origin && u.pathname.startsWith(pathPrefix);
|
|
256
|
+
});
|
|
257
|
+
};
|
|
211
258
|
var IMMUTABLE_CACHE_NAME = "sandpack-immutable-fetch-v1";
|
|
212
259
|
var serializeResponse = function (res) { return utils.__awaiter(void 0, void 0, void 0, function () {
|
|
213
260
|
var _a;
|
|
@@ -299,14 +346,22 @@ var matchesIntegrity = function (body, integrity) { return utils.__awaiter(void
|
|
|
299
346
|
*/
|
|
300
347
|
function handleImmutableFetch(url, integrity) {
|
|
301
348
|
return utils.__awaiter(this, void 0, void 0, function () {
|
|
302
|
-
var expected, cache, hit, body, res, result, _a;
|
|
349
|
+
var parsed, expected, cache, hit, body, res, result, _a;
|
|
303
350
|
return utils.__generator(this, function (_b) {
|
|
304
351
|
switch (_b.label) {
|
|
305
352
|
case 0:
|
|
306
|
-
if (typeof url !== "string"
|
|
307
|
-
!IMMUTABLE_URL_ALLOWLIST.some(function (prefix) { return url.startsWith(prefix); })) {
|
|
353
|
+
if (typeof url !== "string") {
|
|
308
354
|
throw new Error("URL not allowed for immutable fetch: ".concat(String(url)));
|
|
309
355
|
}
|
|
356
|
+
try {
|
|
357
|
+
parsed = new URL(url);
|
|
358
|
+
}
|
|
359
|
+
catch (_c) {
|
|
360
|
+
throw new Error("URL not allowed for immutable fetch: ".concat(url));
|
|
361
|
+
}
|
|
362
|
+
if (!inPolicy(parsed)) {
|
|
363
|
+
throw new Error("URL not allowed for immutable fetch: ".concat(url));
|
|
364
|
+
}
|
|
310
365
|
expected = typeof integrity === "string" ? integrity : undefined;
|
|
311
366
|
return [4 /*yield*/, openCache()];
|
|
312
367
|
case 1:
|
|
@@ -330,12 +385,21 @@ function handleImmutableFetch(url, integrity) {
|
|
|
330
385
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
331
386
|
_b.sent();
|
|
332
387
|
_b.label = 6;
|
|
333
|
-
case 6: return [4 /*yield*/, fetch(
|
|
388
|
+
case 6: return [4 /*yield*/, fetch(parsed)];
|
|
334
389
|
case 7:
|
|
335
390
|
res = _b.sent();
|
|
336
391
|
if (!res.ok) {
|
|
337
392
|
throw new Error("Immutable fetch failed with status ".concat(res.status, ": ").concat(url));
|
|
338
393
|
}
|
|
394
|
+
// R3-364: the prefix hosts are exact-version content hosts; a redirect that
|
|
395
|
+
// leaves the allowlist means the bytes did NOT come from an in-policy origin —
|
|
396
|
+
// refuse rather than serve (and never cache) them. The browser follows
|
|
397
|
+
// redirects itself (cross-origin `redirect: 'manual'` responses are opaque),
|
|
398
|
+
// so this is the final-URL check: `res.url` is where the bytes actually came
|
|
399
|
+
// from. A response that reports no URL (test doubles) skips the check.
|
|
400
|
+
if (res.redirected && res.url !== "" && !inPolicy(new URL(res.url))) {
|
|
401
|
+
throw new Error("Immutable fetch redirected outside the allowlist: ".concat(res.url));
|
|
402
|
+
}
|
|
339
403
|
return [4 /*yield*/, serializeResponse(res)];
|
|
340
404
|
case 8:
|
|
341
405
|
result = _b.sent();
|
|
@@ -621,9 +685,44 @@ function getExtension(filepath) {
|
|
|
621
685
|
}
|
|
622
686
|
}
|
|
623
687
|
|
|
688
|
+
/**
|
|
689
|
+
* R3-367 — the getCodeSandboxURL gesture gate, extracted so it is testable
|
|
690
|
+
* without standing the whole runtime graph up (and so the fork's node-env
|
|
691
|
+
* suites can import it too).
|
|
692
|
+
*/
|
|
693
|
+
/**
|
|
694
|
+
* Whether a codesandbox.io export may run: the user has been ACTIVE on this
|
|
695
|
+
* page (sticky activation — the fs snapshot can outlive a transient window).
|
|
696
|
+
* False ⇒ app/script-initiated context ⇒ the export must refuse before
|
|
697
|
+
* reading the filesystem.
|
|
698
|
+
*/
|
|
699
|
+
var codeSandboxExportAllowed$1 = function () {
|
|
700
|
+
var _a;
|
|
701
|
+
return ((_a = navigator.userActivation) === null || _a === void 0 ? void 0 : _a.hasBeenActive) === true;
|
|
702
|
+
};
|
|
703
|
+
/**
|
|
704
|
+
* Make a gesture-gate refusal observable: a `sandpack-security-violation`
|
|
705
|
+
* CustomEvent the host (site-main) can listen for and journal into its
|
|
706
|
+
* security-events stream. Never throws — observability must not become a
|
|
707
|
+
* second failure mode on the refusal path.
|
|
708
|
+
*/
|
|
709
|
+
var notifyCodeSandboxExportRefused$1 = function () {
|
|
710
|
+
try {
|
|
711
|
+
window.dispatchEvent(new CustomEvent("sandpack-security-violation", {
|
|
712
|
+
detail: {
|
|
713
|
+
kind: "gesture-gate.getCodeSandboxURL",
|
|
714
|
+
reason: "no user activation",
|
|
715
|
+
},
|
|
716
|
+
}));
|
|
717
|
+
}
|
|
718
|
+
catch (_a) {
|
|
719
|
+
/* no window / dispatch unavailable */
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
|
|
624
723
|
var _a;
|
|
625
724
|
var SUFFIX_PLACEHOLDER = "-{{suffix}}";
|
|
626
|
-
var BUNDLER_URL = "https://".concat((_a = "2.22.
|
|
725
|
+
var BUNDLER_URL = "https://".concat((_a = "2.22.1") === null || _a === void 0 ? void 0 : _a.replace(/\./g, "-")).concat(SUFFIX_PLACEHOLDER, "-sandpack.codesandbox.io/");
|
|
627
726
|
var SandpackRuntime = /** @class */ (function (_super) {
|
|
628
727
|
utils.__extends(SandpackRuntime, _super);
|
|
629
728
|
function SandpackRuntime(selector, sandboxSetup, options) {
|
|
@@ -1122,14 +1221,32 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
1122
1221
|
return this.iframeProtocol.channelListen(listener);
|
|
1123
1222
|
};
|
|
1124
1223
|
/**
|
|
1125
|
-
* Get the URL of the contents of the current sandbox
|
|
1224
|
+
* Get the URL of the contents of the current sandbox.
|
|
1225
|
+
*
|
|
1226
|
+
* R3-367 — GESTURE-GATED. This POSTs the ENTIRE app filesystem to
|
|
1227
|
+
* codesandbox.io from the parent page; the fs may contain the user's data
|
|
1228
|
+
* (spaces, mounts), so the export is reachable only when the user has been
|
|
1229
|
+
* active on the page (`navigator.userActivation.hasBeenActive` — the sticky
|
|
1230
|
+
* flag, because the fs snapshot can outlive a transient-activation window).
|
|
1231
|
+
* Without any user activation this refuses BEFORE reading the filesystem:
|
|
1232
|
+
* nothing is posted, and a `sandpack-security-violation` CustomEvent is
|
|
1233
|
+
* dispatched on the window for the host to journal (site-main wires the
|
|
1234
|
+
* listener into its security-events seam).
|
|
1126
1235
|
*/
|
|
1127
1236
|
SandpackRuntime.prototype.getCodeSandboxURL = function () {
|
|
1128
1237
|
return utils.__awaiter(this, void 0, void 0, function () {
|
|
1129
1238
|
var snapshot, paramFiles, res, sandboxId;
|
|
1130
1239
|
return utils.__generator(this, function (_a) {
|
|
1131
1240
|
switch (_a.label) {
|
|
1132
|
-
case 0:
|
|
1241
|
+
case 0:
|
|
1242
|
+
if (!codeSandboxExportAllowed()) {
|
|
1243
|
+
// No user has ever interacted with this page: an export initiated here
|
|
1244
|
+
// would be app- or script-initiated, not user-initiated. Refuse and make
|
|
1245
|
+
// the refusal observable — BEFORE reading the filesystem.
|
|
1246
|
+
notifyCodeSandboxExportRefused();
|
|
1247
|
+
throw new Error("getCodeSandboxURL requires user activation (R3-367 gesture gate)");
|
|
1248
|
+
}
|
|
1249
|
+
return [4 /*yield*/, snapshotFS(this.sandboxSetup.fs)];
|
|
1133
1250
|
case 1:
|
|
1134
1251
|
snapshot = _a.sent();
|
|
1135
1252
|
paramFiles = Object.keys(snapshot).reduce(function (prev, next) {
|
|
@@ -1209,3 +1326,5 @@ function snapshotFS(fs) {
|
|
|
1209
1326
|
}
|
|
1210
1327
|
|
|
1211
1328
|
exports.SandpackRuntime = SandpackRuntime;
|
|
1329
|
+
exports.codeSandboxExportAllowed = codeSandboxExportAllowed$1;
|
|
1330
|
+
exports.notifyCodeSandboxExportRefused = notifyCodeSandboxExportRefused$1;
|
|
@@ -1,6 +1,6 @@
|
|
|
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-DRg992RB.mjs';
|
|
4
4
|
import { S as SandpackClient } from '../../base-DBh7xJX9.mjs';
|
|
5
5
|
import { c as createSandboxedIframe, e as ensureSandboxed } from '../../iframe-factory-C8M0b9uf.mjs';
|
|
6
6
|
import mimeDB from 'mime-db';
|
|
@@ -64,6 +64,21 @@ var Protocol = /** @class */ (function () {
|
|
|
64
64
|
return Protocol;
|
|
65
65
|
}());
|
|
66
66
|
|
|
67
|
+
// R3-367: crypto-random, 64-bit channel id (16 hex chars from 8 random bytes).
|
|
68
|
+
// This used to be Math.floor(Math.random()*1e6) — guessable, and the id was
|
|
69
|
+
// console.logged at registration. SECURITY INVARIANT (do not weaken): the id is
|
|
70
|
+
// a CORRELATION key only — every incoming message is authenticated by the
|
|
71
|
+
// `event.source === this.frameWindow` check in eventListener() below, NEVER by
|
|
72
|
+
// the id alone. The crypto entropy is defense-in-depth on top of that check
|
|
73
|
+
// (the frame-side twin comment lives in the sandbox bundler's protocol layer,
|
|
74
|
+
// R3-352 C1). `string | number` on the wire stays compatible with ids older
|
|
75
|
+
// peers minted as numbers: both sides compare the echoed value they themselves
|
|
76
|
+
// sent, so the type never has to agree across versions.
|
|
77
|
+
var randomChannelId = function () {
|
|
78
|
+
var bytes = new Uint8Array(8);
|
|
79
|
+
crypto.getRandomValues(bytes);
|
|
80
|
+
return Array.from(bytes, function (b) { return b.toString(16).padStart(2, "0"); }).join("");
|
|
81
|
+
};
|
|
67
82
|
var IFrameProtocol = /** @class */ (function () {
|
|
68
83
|
function IFrameProtocol(iframe, _origin) {
|
|
69
84
|
// React to messages from any iframe
|
|
@@ -72,8 +87,9 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
72
87
|
// React to messages from the iframe owned by this instance
|
|
73
88
|
this.channelListeners = {};
|
|
74
89
|
this.channelListenersCount = 0;
|
|
75
|
-
// Random
|
|
76
|
-
|
|
90
|
+
// Random id to identify this instance of the client when messages are coming
|
|
91
|
+
// from multiple iframes — crypto-random (see randomChannelId above).
|
|
92
|
+
this.channelId = randomChannelId();
|
|
77
93
|
this.frameWindow = iframe.contentWindow;
|
|
78
94
|
this.origin = "*"; //origin;
|
|
79
95
|
this.globalListeners = [];
|
|
@@ -99,8 +115,9 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
99
115
|
if (!this.frameWindow) {
|
|
100
116
|
return;
|
|
101
117
|
}
|
|
102
|
-
//
|
|
103
|
-
|
|
118
|
+
// R3-367: no channelId log — the id is not secret-critical, but logging it
|
|
119
|
+
// at registration handed an observer the correlation key for free (the
|
|
120
|
+
// evt.source check below is the actual authentication).
|
|
104
121
|
// Order matters: the bundler reads ports[0] as the fs port and ports[1] as
|
|
105
122
|
// the Babel worker port. `filter` keeps that order as long as the fs port
|
|
106
123
|
// is always present (it is, in normal operation).
|
|
@@ -151,7 +168,11 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
151
168
|
};
|
|
152
169
|
// Handles message windows coming from iframes
|
|
153
170
|
IFrameProtocol.prototype.eventListener = function (evt) {
|
|
154
|
-
//
|
|
171
|
+
// SECURITY INVARIANT (R3-367, twin of the randomChannelId comment): this
|
|
172
|
+
// source check — not the channelId — is what authenticates an incoming
|
|
173
|
+
// message. The id below only routes a message ALREADY accepted here to the
|
|
174
|
+
// owning instance. Weakening or reordering this check would let any iframe
|
|
175
|
+
// on the page speak on the bundler channel.
|
|
155
176
|
if (evt.source !== this.frameWindow) {
|
|
156
177
|
return;
|
|
157
178
|
}
|
|
@@ -188,24 +209,50 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
188
209
|
* registers via `registerImmutableUrlPrefix` (see sandpack-bundler
|
|
189
210
|
* `src/utils/fetch.ts`) — keep the two in sync.
|
|
190
211
|
*/
|
|
191
|
-
/**
|
|
192
|
-
* URL prefixes whose responses never change for a given URL (the URL encodes
|
|
193
|
-
* the exact content version). Only these may be fetched on the iframe's
|
|
194
|
-
* behalf, and they are safe to cache forever.
|
|
195
|
-
*/
|
|
196
212
|
var IMMUTABLE_URL_ALLOWLIST = [
|
|
197
213
|
// Module CDN, exact-versioned package bundles. (NOT /dep_tree/, which
|
|
198
214
|
// resolves semver ranges and changes as new versions publish.)
|
|
199
|
-
|
|
215
|
+
{
|
|
216
|
+
origin: "https://sandpack-cdn-staging.blazingly.io",
|
|
217
|
+
pathPrefix: "/package/",
|
|
218
|
+
},
|
|
200
219
|
// unpkg files, requested by the bundler at registry-resolved exact versions.
|
|
201
|
-
"https://unpkg.com/",
|
|
220
|
+
{ origin: "https://unpkg.com", pathPrefix: "/" },
|
|
202
221
|
// Self-hosted, versioned @immediately-run/sdk builds (SDK_PACKAGING_SPEC
|
|
203
222
|
// §5/§11, Option A). The /v/<version>/ path encodes the exact version, so
|
|
204
223
|
// responses are immutable; the bundler fetches these when an app opts the SDK
|
|
205
224
|
// into immediately.run.resolveFromRegistry. Keep in sync with the prefix the
|
|
206
225
|
// bundler registers via registerImmutableUrlPrefix (sandbox bundler.ts).
|
|
207
|
-
|
|
226
|
+
{
|
|
227
|
+
origin: "https://immediately-run.github.io",
|
|
228
|
+
pathPrefix: "/immediately-run-sdk/v/",
|
|
229
|
+
},
|
|
208
230
|
];
|
|
231
|
+
/**
|
|
232
|
+
* Is a PARSED URL inside the allowlist? Origin is compared exactly (a userinfo
|
|
233
|
+
* or lookalike host cannot match) and the path prefix against the URL parser's
|
|
234
|
+
* NORMALIZED pathname; additionally, any dot segment — literal or
|
|
235
|
+
* percent-encoded, in any hex case — is refused, so no spelling of `..` can
|
|
236
|
+
* cross the prefix boundary after a server-side decode.
|
|
237
|
+
*/
|
|
238
|
+
var inPolicy = function (u) {
|
|
239
|
+
var segments = u.pathname.split("/").map(function (segment) {
|
|
240
|
+
try {
|
|
241
|
+
return decodeURIComponent(segment).toLowerCase();
|
|
242
|
+
}
|
|
243
|
+
catch (_a) {
|
|
244
|
+
return segment.toLowerCase();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
if (segments.some(function (s) { return s === ".." || s === "."; }))
|
|
248
|
+
return false;
|
|
249
|
+
if (/%2e/.test(u.pathname.toLowerCase()))
|
|
250
|
+
return false;
|
|
251
|
+
return IMMUTABLE_URL_ALLOWLIST.some(function (_a) {
|
|
252
|
+
var origin = _a.origin, pathPrefix = _a.pathPrefix;
|
|
253
|
+
return u.origin === origin && u.pathname.startsWith(pathPrefix);
|
|
254
|
+
});
|
|
255
|
+
};
|
|
209
256
|
var IMMUTABLE_CACHE_NAME = "sandpack-immutable-fetch-v1";
|
|
210
257
|
var serializeResponse = function (res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
211
258
|
var _a;
|
|
@@ -297,14 +344,22 @@ var matchesIntegrity = function (body, integrity) { return __awaiter(void 0, voi
|
|
|
297
344
|
*/
|
|
298
345
|
function handleImmutableFetch(url, integrity) {
|
|
299
346
|
return __awaiter(this, void 0, void 0, function () {
|
|
300
|
-
var expected, cache, hit, body, res, result, _a;
|
|
347
|
+
var parsed, expected, cache, hit, body, res, result, _a;
|
|
301
348
|
return __generator(this, function (_b) {
|
|
302
349
|
switch (_b.label) {
|
|
303
350
|
case 0:
|
|
304
|
-
if (typeof url !== "string"
|
|
305
|
-
!IMMUTABLE_URL_ALLOWLIST.some(function (prefix) { return url.startsWith(prefix); })) {
|
|
351
|
+
if (typeof url !== "string") {
|
|
306
352
|
throw new Error("URL not allowed for immutable fetch: ".concat(String(url)));
|
|
307
353
|
}
|
|
354
|
+
try {
|
|
355
|
+
parsed = new URL(url);
|
|
356
|
+
}
|
|
357
|
+
catch (_c) {
|
|
358
|
+
throw new Error("URL not allowed for immutable fetch: ".concat(url));
|
|
359
|
+
}
|
|
360
|
+
if (!inPolicy(parsed)) {
|
|
361
|
+
throw new Error("URL not allowed for immutable fetch: ".concat(url));
|
|
362
|
+
}
|
|
308
363
|
expected = typeof integrity === "string" ? integrity : undefined;
|
|
309
364
|
return [4 /*yield*/, openCache()];
|
|
310
365
|
case 1:
|
|
@@ -328,12 +383,21 @@ function handleImmutableFetch(url, integrity) {
|
|
|
328
383
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
329
384
|
_b.sent();
|
|
330
385
|
_b.label = 6;
|
|
331
|
-
case 6: return [4 /*yield*/, fetch(
|
|
386
|
+
case 6: return [4 /*yield*/, fetch(parsed)];
|
|
332
387
|
case 7:
|
|
333
388
|
res = _b.sent();
|
|
334
389
|
if (!res.ok) {
|
|
335
390
|
throw new Error("Immutable fetch failed with status ".concat(res.status, ": ").concat(url));
|
|
336
391
|
}
|
|
392
|
+
// R3-364: the prefix hosts are exact-version content hosts; a redirect that
|
|
393
|
+
// leaves the allowlist means the bytes did NOT come from an in-policy origin —
|
|
394
|
+
// refuse rather than serve (and never cache) them. The browser follows
|
|
395
|
+
// redirects itself (cross-origin `redirect: 'manual'` responses are opaque),
|
|
396
|
+
// so this is the final-URL check: `res.url` is where the bytes actually came
|
|
397
|
+
// from. A response that reports no URL (test doubles) skips the check.
|
|
398
|
+
if (res.redirected && res.url !== "" && !inPolicy(new URL(res.url))) {
|
|
399
|
+
throw new Error("Immutable fetch redirected outside the allowlist: ".concat(res.url));
|
|
400
|
+
}
|
|
337
401
|
return [4 /*yield*/, serializeResponse(res)];
|
|
338
402
|
case 8:
|
|
339
403
|
result = _b.sent();
|
|
@@ -619,9 +683,44 @@ function getExtension(filepath) {
|
|
|
619
683
|
}
|
|
620
684
|
}
|
|
621
685
|
|
|
686
|
+
/**
|
|
687
|
+
* R3-367 — the getCodeSandboxURL gesture gate, extracted so it is testable
|
|
688
|
+
* without standing the whole runtime graph up (and so the fork's node-env
|
|
689
|
+
* suites can import it too).
|
|
690
|
+
*/
|
|
691
|
+
/**
|
|
692
|
+
* Whether a codesandbox.io export may run: the user has been ACTIVE on this
|
|
693
|
+
* page (sticky activation — the fs snapshot can outlive a transient window).
|
|
694
|
+
* False ⇒ app/script-initiated context ⇒ the export must refuse before
|
|
695
|
+
* reading the filesystem.
|
|
696
|
+
*/
|
|
697
|
+
var codeSandboxExportAllowed$1 = function () {
|
|
698
|
+
var _a;
|
|
699
|
+
return ((_a = navigator.userActivation) === null || _a === void 0 ? void 0 : _a.hasBeenActive) === true;
|
|
700
|
+
};
|
|
701
|
+
/**
|
|
702
|
+
* Make a gesture-gate refusal observable: a `sandpack-security-violation`
|
|
703
|
+
* CustomEvent the host (site-main) can listen for and journal into its
|
|
704
|
+
* security-events stream. Never throws — observability must not become a
|
|
705
|
+
* second failure mode on the refusal path.
|
|
706
|
+
*/
|
|
707
|
+
var notifyCodeSandboxExportRefused$1 = function () {
|
|
708
|
+
try {
|
|
709
|
+
window.dispatchEvent(new CustomEvent("sandpack-security-violation", {
|
|
710
|
+
detail: {
|
|
711
|
+
kind: "gesture-gate.getCodeSandboxURL",
|
|
712
|
+
reason: "no user activation",
|
|
713
|
+
},
|
|
714
|
+
}));
|
|
715
|
+
}
|
|
716
|
+
catch (_a) {
|
|
717
|
+
/* no window / dispatch unavailable */
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
|
|
622
721
|
var _a;
|
|
623
722
|
var SUFFIX_PLACEHOLDER = "-{{suffix}}";
|
|
624
|
-
var BUNDLER_URL = "https://".concat((_a = "2.22.
|
|
723
|
+
var BUNDLER_URL = "https://".concat((_a = "2.22.1") === null || _a === void 0 ? void 0 : _a.replace(/\./g, "-")).concat(SUFFIX_PLACEHOLDER, "-sandpack.codesandbox.io/");
|
|
625
724
|
var SandpackRuntime = /** @class */ (function (_super) {
|
|
626
725
|
__extends(SandpackRuntime, _super);
|
|
627
726
|
function SandpackRuntime(selector, sandboxSetup, options) {
|
|
@@ -1120,14 +1219,32 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
1120
1219
|
return this.iframeProtocol.channelListen(listener);
|
|
1121
1220
|
};
|
|
1122
1221
|
/**
|
|
1123
|
-
* Get the URL of the contents of the current sandbox
|
|
1222
|
+
* Get the URL of the contents of the current sandbox.
|
|
1223
|
+
*
|
|
1224
|
+
* R3-367 — GESTURE-GATED. This POSTs the ENTIRE app filesystem to
|
|
1225
|
+
* codesandbox.io from the parent page; the fs may contain the user's data
|
|
1226
|
+
* (spaces, mounts), so the export is reachable only when the user has been
|
|
1227
|
+
* active on the page (`navigator.userActivation.hasBeenActive` — the sticky
|
|
1228
|
+
* flag, because the fs snapshot can outlive a transient-activation window).
|
|
1229
|
+
* Without any user activation this refuses BEFORE reading the filesystem:
|
|
1230
|
+
* nothing is posted, and a `sandpack-security-violation` CustomEvent is
|
|
1231
|
+
* dispatched on the window for the host to journal (site-main wires the
|
|
1232
|
+
* listener into its security-events seam).
|
|
1124
1233
|
*/
|
|
1125
1234
|
SandpackRuntime.prototype.getCodeSandboxURL = function () {
|
|
1126
1235
|
return __awaiter(this, void 0, void 0, function () {
|
|
1127
1236
|
var snapshot, paramFiles, res, sandboxId;
|
|
1128
1237
|
return __generator(this, function (_a) {
|
|
1129
1238
|
switch (_a.label) {
|
|
1130
|
-
case 0:
|
|
1239
|
+
case 0:
|
|
1240
|
+
if (!codeSandboxExportAllowed()) {
|
|
1241
|
+
// No user has ever interacted with this page: an export initiated here
|
|
1242
|
+
// would be app- or script-initiated, not user-initiated. Refuse and make
|
|
1243
|
+
// the refusal observable — BEFORE reading the filesystem.
|
|
1244
|
+
notifyCodeSandboxExportRefused();
|
|
1245
|
+
throw new Error("getCodeSandboxURL requires user activation (R3-367 gesture gate)");
|
|
1246
|
+
}
|
|
1247
|
+
return [4 /*yield*/, snapshotFS(this.sandboxSetup.fs)];
|
|
1131
1248
|
case 1:
|
|
1132
1249
|
snapshot = _a.sent();
|
|
1133
1250
|
paramFiles = Object.keys(snapshot).reduce(function (prev, next) {
|
|
@@ -1206,4 +1323,4 @@ function snapshotFS(fs) {
|
|
|
1206
1323
|
});
|
|
1207
1324
|
}
|
|
1208
1325
|
|
|
1209
|
-
export { SandpackRuntime };
|
|
1326
|
+
export { SandpackRuntime, codeSandboxExportAllowed$1 as codeSandboxExportAllowed, notifyCodeSandboxExportRefused$1 as notifyCodeSandboxExportRefused };
|
|
@@ -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-DT9Wd42J.mjs';
|
|
4
4
|
import { S as SandpackClient } from './base-DBh7xJX9.mjs';
|
|
5
5
|
import { c as createSandboxedIframe, e as ensureSandboxed } from './iframe-factory-C8M0b9uf.mjs';
|
|
6
6
|
import 'outvariant';
|
|
7
|
-
import './types-
|
|
7
|
+
import './types-DRg992RB.mjs';
|
|
8
8
|
import '@zenfs/core';
|
|
9
9
|
import 'dequal';
|
|
10
10
|
|
|
@@ -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-vrsBdedj.js');
|
|
6
6
|
var base = require('./base-DelKLlDk.js');
|
|
7
7
|
var iframeFactory = require('./iframe-factory-Bc7tcyQZ.js');
|
|
8
8
|
require('outvariant');
|
|
9
|
-
require('./types-
|
|
9
|
+
require('./types-B3g_70H8.js');
|
|
10
10
|
require('@zenfs/core');
|
|
11
11
|
require('dequal');
|
|
12
12
|
|
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-B3g_70H8.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-Dnq1i5aM.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-DRg992RB.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-CSAGdFtt.mjs').then(function (m) { return m.SandpackStatic; })];
|
|
27
27
|
case 4:
|
|
28
28
|
Client = _c.sent();
|
|
29
29
|
return [3 /*break*/, 7];
|
|
@@ -45,7 +45,7 @@ var GUARDED_WRITE_METHODS = [
|
|
|
45
45
|
* (roadmap R3-110).
|
|
46
46
|
*
|
|
47
47
|
* This is a module-level function (not a class method) and its **only** reference
|
|
48
|
-
* is behind the `if (IS_DEV)` branch in
|
|
48
|
+
* is behind the `if (IS_DEV)` branch in {@link ensureGuard} — so once a consumer's
|
|
49
49
|
* production build folds `IS_DEV` to `false`, the branch and this whole function
|
|
50
50
|
* tree-shake away (a class method would be retained). No-op in production.
|
|
51
51
|
*/
|
|
@@ -73,6 +73,35 @@ function installOutOfBandGuard(fsContext) {
|
|
|
73
73
|
_loop_1(method);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* The true raw write methods per bound context, captured once and shared by every
|
|
78
|
+
* SandpackFS instance that adopts that context. Without this, a second instance's
|
|
79
|
+
* constructor captures the first instance's wrapper as its "raw" method — because the
|
|
80
|
+
* guard has already replaced `fsContext.fs.promises` entries — so SandpackFS's own
|
|
81
|
+
* writes trip the guard once per prior adoption (the R3-614 stacking bug).
|
|
82
|
+
*/
|
|
83
|
+
var RAW = new WeakMap();
|
|
84
|
+
/**
|
|
85
|
+
* Return the context's raw write methods, capturing them and installing the out-of-band
|
|
86
|
+
* guard exactly once per context (see {@link installOutOfBandGuard}). Reads `RAW` first,
|
|
87
|
+
* so adopting the same context any number of times never re-wraps, never re-captures a
|
|
88
|
+
* wrapper as raw, and never disarms a sibling instance.
|
|
89
|
+
*/
|
|
90
|
+
function ensureGuard(fsContext) {
|
|
91
|
+
var existing = RAW.get(fsContext);
|
|
92
|
+
if (existing)
|
|
93
|
+
return existing;
|
|
94
|
+
var p = fsContext.fs.promises;
|
|
95
|
+
var raw = {
|
|
96
|
+
writeFile: p.writeFile.bind(p),
|
|
97
|
+
unlink: p.unlink.bind(p),
|
|
98
|
+
mkdir: p.mkdir.bind(p),
|
|
99
|
+
};
|
|
100
|
+
RAW.set(fsContext, raw);
|
|
101
|
+
if (IS_DEV)
|
|
102
|
+
installOutOfBandGuard(fsContext);
|
|
103
|
+
return raw;
|
|
104
|
+
}
|
|
76
105
|
var mountCounter = 0;
|
|
77
106
|
var normalize = function (path) {
|
|
78
107
|
return path.startsWith("/") ? path : "/".concat(path);
|
|
@@ -119,12 +148,10 @@ var SandpackFS = /** @class */ (function () {
|
|
|
119
148
|
* unmount in {@link dispose}. Unset for an adopted context, whose lifecycle
|
|
120
149
|
* belongs to the caller. */
|
|
121
150
|
this.ownedMountPoint = undefined;
|
|
122
|
-
var
|
|
123
|
-
this.rawWriteFile =
|
|
124
|
-
this.rawUnlink =
|
|
125
|
-
this.rawMkdir =
|
|
126
|
-
if (IS_DEV)
|
|
127
|
-
installOutOfBandGuard(fsContext);
|
|
151
|
+
var raw = ensureGuard(fsContext);
|
|
152
|
+
this.rawWriteFile = raw.writeFile;
|
|
153
|
+
this.rawUnlink = raw.unlink;
|
|
154
|
+
this.rawMkdir = raw.mkdir;
|
|
128
155
|
}
|
|
129
156
|
/**
|
|
130
157
|
* Create the `MessagePort` shared with the child iframe, wiring the iframe's
|
|
@@ -43,7 +43,7 @@ var GUARDED_WRITE_METHODS = [
|
|
|
43
43
|
* (roadmap R3-110).
|
|
44
44
|
*
|
|
45
45
|
* This is a module-level function (not a class method) and its **only** reference
|
|
46
|
-
* is behind the `if (IS_DEV)` branch in
|
|
46
|
+
* is behind the `if (IS_DEV)` branch in {@link ensureGuard} — so once a consumer's
|
|
47
47
|
* production build folds `IS_DEV` to `false`, the branch and this whole function
|
|
48
48
|
* tree-shake away (a class method would be retained). No-op in production.
|
|
49
49
|
*/
|
|
@@ -71,6 +71,35 @@ function installOutOfBandGuard(fsContext) {
|
|
|
71
71
|
_loop_1(method);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The true raw write methods per bound context, captured once and shared by every
|
|
76
|
+
* SandpackFS instance that adopts that context. Without this, a second instance's
|
|
77
|
+
* constructor captures the first instance's wrapper as its "raw" method — because the
|
|
78
|
+
* guard has already replaced `fsContext.fs.promises` entries — so SandpackFS's own
|
|
79
|
+
* writes trip the guard once per prior adoption (the R3-614 stacking bug).
|
|
80
|
+
*/
|
|
81
|
+
var RAW = new WeakMap();
|
|
82
|
+
/**
|
|
83
|
+
* Return the context's raw write methods, capturing them and installing the out-of-band
|
|
84
|
+
* guard exactly once per context (see {@link installOutOfBandGuard}). Reads `RAW` first,
|
|
85
|
+
* so adopting the same context any number of times never re-wraps, never re-captures a
|
|
86
|
+
* wrapper as raw, and never disarms a sibling instance.
|
|
87
|
+
*/
|
|
88
|
+
function ensureGuard(fsContext) {
|
|
89
|
+
var existing = RAW.get(fsContext);
|
|
90
|
+
if (existing)
|
|
91
|
+
return existing;
|
|
92
|
+
var p = fsContext.fs.promises;
|
|
93
|
+
var raw = {
|
|
94
|
+
writeFile: p.writeFile.bind(p),
|
|
95
|
+
unlink: p.unlink.bind(p),
|
|
96
|
+
mkdir: p.mkdir.bind(p),
|
|
97
|
+
};
|
|
98
|
+
RAW.set(fsContext, raw);
|
|
99
|
+
if (IS_DEV)
|
|
100
|
+
installOutOfBandGuard(fsContext);
|
|
101
|
+
return raw;
|
|
102
|
+
}
|
|
74
103
|
var mountCounter = 0;
|
|
75
104
|
var normalize = function (path) {
|
|
76
105
|
return path.startsWith("/") ? path : "/".concat(path);
|
|
@@ -117,12 +146,10 @@ var SandpackFS = /** @class */ (function () {
|
|
|
117
146
|
* unmount in {@link dispose}. Unset for an adopted context, whose lifecycle
|
|
118
147
|
* belongs to the caller. */
|
|
119
148
|
this.ownedMountPoint = undefined;
|
|
120
|
-
var
|
|
121
|
-
this.rawWriteFile =
|
|
122
|
-
this.rawUnlink =
|
|
123
|
-
this.rawMkdir =
|
|
124
|
-
if (IS_DEV)
|
|
125
|
-
installOutOfBandGuard(fsContext);
|
|
149
|
+
var raw = ensureGuard(fsContext);
|
|
150
|
+
this.rawWriteFile = raw.writeFile;
|
|
151
|
+
this.rawUnlink = raw.unlink;
|
|
152
|
+
this.rawMkdir = raw.mkdir;
|
|
126
153
|
}
|
|
127
154
|
/**
|
|
128
155
|
* Create the `MessagePort` shared with the child iframe, wiring the iframe's
|
package/dist/types.d.ts
CHANGED
|
@@ -251,7 +251,13 @@ export type ClientStatus = "initializing" | "installing-dependencies" | "transpi
|
|
|
251
251
|
export type SandpackMessageConsoleMethods = "log" | "debug" | "info" | "warn" | "error" | "table" | "clear" | "time" | "timeEnd" | "count" | "assert";
|
|
252
252
|
export interface BaseSandpackMessage {
|
|
253
253
|
type: string;
|
|
254
|
-
|
|
254
|
+
/**
|
|
255
|
+
* The sender's channel id. Newer peers mint it as a crypto-random hex STRING
|
|
256
|
+
* (R3-367 — 64 bits); ids older peers minted as numbers still flow, because
|
|
257
|
+
* each side compares the value it itself sent. Correlation only — never an
|
|
258
|
+
* authenticator (the evt.source check in IFrameProtocol is).
|
|
259
|
+
*/
|
|
260
|
+
$id?: string | number;
|
|
255
261
|
codesandbox?: boolean;
|
|
256
262
|
/**
|
|
257
263
|
* Transferable objects (e.g. a `MessagePort`) to hand to the iframe alongside
|