sandboxedjs 0.1.95 → 0.1.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4273,7 +4273,8 @@ function md5Hex(input) {
4273
4273
  var isNode, zlibPromise, cryptoPromise, SUBTLE_NAMES, UnsupportedAlgorithmError;
4274
4274
  var init_binary = __esm({
4275
4275
  "src/util/binary.ts"() {
4276
- isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
4276
+ isNode = typeof window === "undefined" && typeof document === "undefined" && typeof process !== "undefined" && process.versions != null && process.versions.node != null && /* A shim has no `release.name`, and Node's has said "node" since v3. */
4277
+ process.release?.name === "node";
4277
4278
  zlibPromise = null;
4278
4279
  cryptoPromise = null;
4279
4280
  SUBTLE_NAMES = {
@@ -20958,7 +20959,7 @@ var wheels_default = {
20958
20959
 
20959
20960
  // package.json
20960
20961
  var package_default = {
20961
- version: "0.1.95"};
20962
+ version: "0.1.99"};
20962
20963
 
20963
20964
  // src/python/extension-abi.ts
20964
20965
  var EXTENSION_ABI = {
@@ -23933,9 +23934,7 @@ async function installRequirements(options) {
23933
23934
  continue;
23934
23935
  }
23935
23936
  options.progress.downloading(distribution.name, distribution.version);
23936
- const bytes2 = await options.client.bytes(distribution.url, { timeoutMs: 12e4 });
23937
- requireArchive(distribution, bytes2);
23938
- verifyDigest(distribution, bytes2);
23937
+ const bytes2 = await download(options.client, distribution);
23939
23938
  const files = stageWheel(readZip(bytes2));
23940
23939
  staged.push(...distribution.kind === "substituted" ? substituteFiles(distribution.name, files, SITE_PACKAGES) : files);
23941
23940
  installed3.push({ name: distribution.name, version: distribution.version });
@@ -23951,18 +23950,41 @@ function requireArchive(distribution, bytes2) {
23951
23950
  `${distribution.filename} did not arrive as a wheel from ${distribution.url}: ` + (looksLikeMarkup ? "the server answered with an HTML page, which usually means the wheel is not published at that address and the server returned its index page instead" : `expected a zip archive, got ${bytes2.length} bytes beginning ${JSON.stringify(opening)}`)
23952
23951
  );
23953
23952
  }
23954
- function verifyDigest(distribution, bytes2) {
23953
+ async function download(client, distribution) {
23954
+ const bytes2 = await client.bytes(distribution.url, { timeoutMs: 12e4 });
23955
+ requireArchive(distribution, bytes2);
23956
+ const mismatch = digestMismatch(distribution, bytes2);
23957
+ if (!mismatch) return bytes2;
23958
+ const fresh = await refetch(client, distribution);
23959
+ if (fresh && !digestMismatch(distribution, fresh)) {
23960
+ requireArchive(distribution, fresh);
23961
+ return fresh;
23962
+ }
23963
+ throw new Error(
23964
+ `${mismatch} ${fresh ? "A retry past any cache returned the same bytes, so the index and the file it names disagree." : "A retry past any cache could not be made, so this may be a cached copy of an older build; clearing the cache, or asking the index's host to purge it, is worth trying before the index is blamed."}`
23965
+ );
23966
+ }
23967
+ async function refetch(client, distribution) {
23968
+ const separator = distribution.url.includes("?") ? "&" : "?";
23969
+ try {
23970
+ const bytes2 = await client.bytes(
23971
+ `${distribution.url}${separator}sha256=${distribution.sha256}`,
23972
+ { timeoutMs: 6e4 }
23973
+ );
23974
+ return bytes2.length > 1 && bytes2[0] === 80 && bytes2[1] === 75 ? bytes2 : null;
23975
+ } catch {
23976
+ return null;
23977
+ }
23978
+ }
23979
+ function digestMismatch(distribution, bytes2) {
23955
23980
  if (!distribution.sha256) {
23956
23981
  throw new Error(
23957
23982
  `${distribution.filename} was published without a sha256 digest, so it cannot be verified`
23958
23983
  );
23959
23984
  }
23960
23985
  const actual = [...sha256.sha256(bytes2)].map((b) => b.toString(16).padStart(2, "0")).join("");
23961
- if (actual !== distribution.sha256) {
23962
- throw new Error(
23963
- `${distribution.filename} failed verification: the index published sha256 ${distribution.sha256} but the download hashes to ${actual}`
23964
- );
23965
- }
23986
+ if (actual === distribution.sha256) return null;
23987
+ return `${distribution.filename} failed verification: the index published sha256 ${distribution.sha256} but the download hashes to ${actual}.`;
23966
23988
  }
23967
23989
  function stageWheel(entries, resolved) {
23968
23990
  const staged = [];
@@ -28043,12 +28065,19 @@ async function installRelease(ctx, index, release, seen, options) {
28043
28065
  const base2 = registryBase(ctx).replace(/\/$/, "");
28044
28066
  const url = `${base2}/${release.filename}`;
28045
28067
  ctx.line(`Fetching ${release.name} ${release.version}`);
28046
- const archive = await download(url);
28047
- const digest2 = await digestHex("sha256", archive);
28068
+ let archive = await download2(url);
28069
+ let digest2 = await digestHex("sha256", archive);
28048
28070
  if (digest2 !== release.sha256) {
28049
- throw new AppRegistryError(
28050
- `${release.filename} failed verification: the registry publishes sha256 ${release.sha256} but the download hashes to ${digest2}`
28051
- );
28071
+ const retried = await download2(`${url}${url.includes("?") ? "&" : "?"}sha256=${release.sha256}`).catch(() => null);
28072
+ const retriedDigest = retried && await digestHex("sha256", retried);
28073
+ if (retried && retriedDigest === release.sha256) {
28074
+ archive = retried;
28075
+ digest2 = retriedDigest;
28076
+ } else {
28077
+ throw new AppRegistryError(
28078
+ `${release.filename} failed verification: the registry publishes sha256 ${release.sha256} but the download hashes to ${digest2}. ` + (retried ? "A retry past any cache returned the same bytes, so the registry and the file it names disagree." : "A retry past any cache could not be made; a cached copy of an older build would look like this, so clearing the cache is worth trying before the registry is blamed.")
28079
+ );
28080
+ }
28052
28081
  }
28053
28082
  const verified = await verify(ctx, archive, release, options);
28054
28083
  const root = `${APPS_ROOT}/${release.name}`;
@@ -28075,7 +28104,7 @@ async function installRelease(ctx, index, release, seen, options) {
28075
28104
  }
28076
28105
  if (manifest.docs) ctx.line(` docs: ${root}/${manifest.docs}`);
28077
28106
  }
28078
- async function download(url) {
28107
+ async function download2(url) {
28079
28108
  const response = await fetch(url);
28080
28109
  if (!response.ok) {
28081
28110
  throw new AppRegistryError(`${url} answered ${response.status} ${response.statusText}`);
@@ -38630,6 +38659,22 @@ var WS_DATA = "sandboxedjs:ws-data";
38630
38659
  var WS_CLOSE = "sandboxedjs:ws-close";
38631
38660
  var WS_ERROR = "sandboxedjs:ws-error";
38632
38661
 
38662
+ // src/preview/router.ts
38663
+ var LOOPBACK_HOST = /^(?:localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[?::1\]?)$/i;
38664
+ function containerTarget(value) {
38665
+ let url;
38666
+ try {
38667
+ url = new URL(value.trim());
38668
+ } catch {
38669
+ return null;
38670
+ }
38671
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
38672
+ if (!LOOPBACK_HOST.test(url.hostname)) return null;
38673
+ const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
38674
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
38675
+ return { port, path: `${url.pathname}${url.search}` };
38676
+ }
38677
+
38633
38678
  // src/preview/register.ts
38634
38679
  function serveContainerOn(port, box) {
38635
38680
  port.onmessage = async (event) => {
@@ -38664,20 +38709,6 @@ function serveContainerOn(port, box) {
38664
38709
  };
38665
38710
  port.start?.();
38666
38711
  }
38667
- var LOOPBACK_HOST = /^(?:localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[?::1\]?)$/i;
38668
- function containerTarget(value) {
38669
- let url;
38670
- try {
38671
- url = new URL(value.trim());
38672
- } catch {
38673
- return null;
38674
- }
38675
- if (url.protocol !== "http:" && url.protocol !== "https:") return null;
38676
- if (!LOOPBACK_HOST.test(url.hostname)) return null;
38677
- const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
38678
- if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
38679
- return { port, path: `${url.pathname}${url.search}` };
38680
- }
38681
38712
  async function createPreview(box, options = {}) {
38682
38713
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
38683
38714
  const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js?no-inline", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
package/dist/index.d.cts CHANGED
@@ -1590,19 +1590,22 @@ declare function startRuntimeWorker(options?: {
1590
1590
  declare function syncChannelSupported(): boolean;
1591
1591
 
1592
1592
  /**
1593
- * Wiring a container's HTTP servers up to real URLs in the page.
1593
+ * The routing a preview service worker does, separated from the worker itself.
1594
1594
  *
1595
- * The service worker does the routing; this is the half that lives in the page
1596
- * and actually knows about the container.
1595
+ * A service worker is awkward to test it needs a browser, a registration and
1596
+ * a secure context — but almost none of what makes this correct is about being
1597
+ * a service worker. The interesting part is the bookkeeping: which client is
1598
+ * previewing which port, which requests belong to a preview at all, and how a
1599
+ * response is rebuilt on the way back. That lives here, where it can be
1600
+ * exercised directly.
1597
1601
  *
1598
- * **Read the origin note before using this.** A preview served this way runs on
1599
- * *your* origin, so scripts inside it can reach `window.parent`, your cookies
1600
- * and your `localStorage` the sandbox contains the program's *filesystem and
1601
- * process table*, not the page it serves. For code you did not write, either
1602
- * host the preview on a separate origin, or use {@link renderInto}, which puts
1603
- * the response in an iframe with no origin at all.
1602
+ * The rule this implements, and the reason it exists: requests are routed by
1603
+ * **who is asking**, not by what the path looks like. An iframe navigates once
1604
+ * to `/__sbx__/<port>/`; every later request from that client however
1605
+ * absolute its path is recognised by its client id. Nothing has to rewrite
1606
+ * `/src/main.js` or `/@vite/client`, which is what makes a real dev server
1607
+ * work rather than only a single page.
1604
1608
  */
1605
-
1606
1609
  /** A container-local server, named by port rather than by an address. */
1607
1610
  interface ContainerTarget {
1608
1611
  port: number;
@@ -1618,10 +1621,31 @@ interface ContainerTarget {
1618
1621
  * is listening, so a frame pointed at it fails with "refused to connect"
1619
1622
  * rather than failing visibly as a routing mistake.
1620
1623
  *
1621
- * Exported separately from {@link Preview.resolve} because it is pure, and a
1622
- * translation table is worth testing without a service worker in the way.
1624
+ * The same translation serves two callers. A host reading an address out of a
1625
+ * build log needs it to point a frame somewhere (`Preview.resolve`), and the
1626
+ * service worker needs it for the other direction: a page *inside* a preview
1627
+ * asking its own backend for `http://localhost:8000/api` means the container's
1628
+ * port 8000, not the reader's machine.
1629
+ *
1630
+ * Kept pure, and kept here rather than beside the host half, because the
1631
+ * worker bundles this module and not that one.
1623
1632
  */
1624
1633
  declare function containerTarget(value: string): ContainerTarget | null;
1634
+
1635
+ /**
1636
+ * Wiring a container's HTTP servers up to real URLs in the page.
1637
+ *
1638
+ * The service worker does the routing; this is the half that lives in the page
1639
+ * and actually knows about the container.
1640
+ *
1641
+ * **Read the origin note before using this.** A preview served this way runs on
1642
+ * *your* origin, so scripts inside it can reach `window.parent`, your cookies
1643
+ * and your `localStorage` — the sandbox contains the program's *filesystem and
1644
+ * process table*, not the page it serves. For code you did not write, either
1645
+ * host the preview on a separate origin, or use {@link renderInto}, which puts
1646
+ * the response in an iframe with no origin at all.
1647
+ */
1648
+
1625
1649
  interface PreviewOptions {
1626
1650
  /** Where the worker script lives; defaults to the copy shipped beside the bundle. */
1627
1651
  scriptUrl?: string | URL;
package/dist/index.d.ts CHANGED
@@ -1590,19 +1590,22 @@ declare function startRuntimeWorker(options?: {
1590
1590
  declare function syncChannelSupported(): boolean;
1591
1591
 
1592
1592
  /**
1593
- * Wiring a container's HTTP servers up to real URLs in the page.
1593
+ * The routing a preview service worker does, separated from the worker itself.
1594
1594
  *
1595
- * The service worker does the routing; this is the half that lives in the page
1596
- * and actually knows about the container.
1595
+ * A service worker is awkward to test it needs a browser, a registration and
1596
+ * a secure context — but almost none of what makes this correct is about being
1597
+ * a service worker. The interesting part is the bookkeeping: which client is
1598
+ * previewing which port, which requests belong to a preview at all, and how a
1599
+ * response is rebuilt on the way back. That lives here, where it can be
1600
+ * exercised directly.
1597
1601
  *
1598
- * **Read the origin note before using this.** A preview served this way runs on
1599
- * *your* origin, so scripts inside it can reach `window.parent`, your cookies
1600
- * and your `localStorage` the sandbox contains the program's *filesystem and
1601
- * process table*, not the page it serves. For code you did not write, either
1602
- * host the preview on a separate origin, or use {@link renderInto}, which puts
1603
- * the response in an iframe with no origin at all.
1602
+ * The rule this implements, and the reason it exists: requests are routed by
1603
+ * **who is asking**, not by what the path looks like. An iframe navigates once
1604
+ * to `/__sbx__/<port>/`; every later request from that client however
1605
+ * absolute its path is recognised by its client id. Nothing has to rewrite
1606
+ * `/src/main.js` or `/@vite/client`, which is what makes a real dev server
1607
+ * work rather than only a single page.
1604
1608
  */
1605
-
1606
1609
  /** A container-local server, named by port rather than by an address. */
1607
1610
  interface ContainerTarget {
1608
1611
  port: number;
@@ -1618,10 +1621,31 @@ interface ContainerTarget {
1618
1621
  * is listening, so a frame pointed at it fails with "refused to connect"
1619
1622
  * rather than failing visibly as a routing mistake.
1620
1623
  *
1621
- * Exported separately from {@link Preview.resolve} because it is pure, and a
1622
- * translation table is worth testing without a service worker in the way.
1624
+ * The same translation serves two callers. A host reading an address out of a
1625
+ * build log needs it to point a frame somewhere (`Preview.resolve`), and the
1626
+ * service worker needs it for the other direction: a page *inside* a preview
1627
+ * asking its own backend for `http://localhost:8000/api` means the container's
1628
+ * port 8000, not the reader's machine.
1629
+ *
1630
+ * Kept pure, and kept here rather than beside the host half, because the
1631
+ * worker bundles this module and not that one.
1623
1632
  */
1624
1633
  declare function containerTarget(value: string): ContainerTarget | null;
1634
+
1635
+ /**
1636
+ * Wiring a container's HTTP servers up to real URLs in the page.
1637
+ *
1638
+ * The service worker does the routing; this is the half that lives in the page
1639
+ * and actually knows about the container.
1640
+ *
1641
+ * **Read the origin note before using this.** A preview served this way runs on
1642
+ * *your* origin, so scripts inside it can reach `window.parent`, your cookies
1643
+ * and your `localStorage` — the sandbox contains the program's *filesystem and
1644
+ * process table*, not the page it serves. For code you did not write, either
1645
+ * host the preview on a separate origin, or use {@link renderInto}, which puts
1646
+ * the response in an iframe with no origin at all.
1647
+ */
1648
+
1625
1649
  interface PreviewOptions {
1626
1650
  /** Where the worker script lives; defaults to the copy shipped beside the bundle. */
1627
1651
  scriptUrl?: string | URL;
package/dist/index.js CHANGED
@@ -4256,7 +4256,8 @@ function md5Hex(input) {
4256
4256
  var isNode, zlibPromise, cryptoPromise, SUBTLE_NAMES, UnsupportedAlgorithmError;
4257
4257
  var init_binary = __esm({
4258
4258
  "src/util/binary.ts"() {
4259
- isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
4259
+ isNode = typeof window === "undefined" && typeof document === "undefined" && typeof process !== "undefined" && process.versions != null && process.versions.node != null && /* A shim has no `release.name`, and Node's has said "node" since v3. */
4260
+ process.release?.name === "node";
4260
4261
  zlibPromise = null;
4261
4262
  cryptoPromise = null;
4262
4263
  SUBTLE_NAMES = {
@@ -20941,7 +20942,7 @@ var wheels_default = {
20941
20942
 
20942
20943
  // package.json
20943
20944
  var package_default = {
20944
- version: "0.1.95"};
20945
+ version: "0.1.99"};
20945
20946
 
20946
20947
  // src/python/extension-abi.ts
20947
20948
  var EXTENSION_ABI = {
@@ -23916,9 +23917,7 @@ async function installRequirements(options) {
23916
23917
  continue;
23917
23918
  }
23918
23919
  options.progress.downloading(distribution.name, distribution.version);
23919
- const bytes2 = await options.client.bytes(distribution.url, { timeoutMs: 12e4 });
23920
- requireArchive(distribution, bytes2);
23921
- verifyDigest(distribution, bytes2);
23920
+ const bytes2 = await download(options.client, distribution);
23922
23921
  const files = stageWheel(readZip(bytes2));
23923
23922
  staged.push(...distribution.kind === "substituted" ? substituteFiles(distribution.name, files, SITE_PACKAGES) : files);
23924
23923
  installed3.push({ name: distribution.name, version: distribution.version });
@@ -23934,18 +23933,41 @@ function requireArchive(distribution, bytes2) {
23934
23933
  `${distribution.filename} did not arrive as a wheel from ${distribution.url}: ` + (looksLikeMarkup ? "the server answered with an HTML page, which usually means the wheel is not published at that address and the server returned its index page instead" : `expected a zip archive, got ${bytes2.length} bytes beginning ${JSON.stringify(opening)}`)
23935
23934
  );
23936
23935
  }
23937
- function verifyDigest(distribution, bytes2) {
23936
+ async function download(client, distribution) {
23937
+ const bytes2 = await client.bytes(distribution.url, { timeoutMs: 12e4 });
23938
+ requireArchive(distribution, bytes2);
23939
+ const mismatch = digestMismatch(distribution, bytes2);
23940
+ if (!mismatch) return bytes2;
23941
+ const fresh = await refetch(client, distribution);
23942
+ if (fresh && !digestMismatch(distribution, fresh)) {
23943
+ requireArchive(distribution, fresh);
23944
+ return fresh;
23945
+ }
23946
+ throw new Error(
23947
+ `${mismatch} ${fresh ? "A retry past any cache returned the same bytes, so the index and the file it names disagree." : "A retry past any cache could not be made, so this may be a cached copy of an older build; clearing the cache, or asking the index's host to purge it, is worth trying before the index is blamed."}`
23948
+ );
23949
+ }
23950
+ async function refetch(client, distribution) {
23951
+ const separator = distribution.url.includes("?") ? "&" : "?";
23952
+ try {
23953
+ const bytes2 = await client.bytes(
23954
+ `${distribution.url}${separator}sha256=${distribution.sha256}`,
23955
+ { timeoutMs: 6e4 }
23956
+ );
23957
+ return bytes2.length > 1 && bytes2[0] === 80 && bytes2[1] === 75 ? bytes2 : null;
23958
+ } catch {
23959
+ return null;
23960
+ }
23961
+ }
23962
+ function digestMismatch(distribution, bytes2) {
23938
23963
  if (!distribution.sha256) {
23939
23964
  throw new Error(
23940
23965
  `${distribution.filename} was published without a sha256 digest, so it cannot be verified`
23941
23966
  );
23942
23967
  }
23943
23968
  const actual = [...sha256(bytes2)].map((b) => b.toString(16).padStart(2, "0")).join("");
23944
- if (actual !== distribution.sha256) {
23945
- throw new Error(
23946
- `${distribution.filename} failed verification: the index published sha256 ${distribution.sha256} but the download hashes to ${actual}`
23947
- );
23948
- }
23969
+ if (actual === distribution.sha256) return null;
23970
+ return `${distribution.filename} failed verification: the index published sha256 ${distribution.sha256} but the download hashes to ${actual}.`;
23949
23971
  }
23950
23972
  function stageWheel(entries, resolved) {
23951
23973
  const staged = [];
@@ -28026,12 +28048,19 @@ async function installRelease(ctx, index, release, seen, options) {
28026
28048
  const base2 = registryBase(ctx).replace(/\/$/, "");
28027
28049
  const url = `${base2}/${release.filename}`;
28028
28050
  ctx.line(`Fetching ${release.name} ${release.version}`);
28029
- const archive = await download(url);
28030
- const digest2 = await digestHex("sha256", archive);
28051
+ let archive = await download2(url);
28052
+ let digest2 = await digestHex("sha256", archive);
28031
28053
  if (digest2 !== release.sha256) {
28032
- throw new AppRegistryError(
28033
- `${release.filename} failed verification: the registry publishes sha256 ${release.sha256} but the download hashes to ${digest2}`
28034
- );
28054
+ const retried = await download2(`${url}${url.includes("?") ? "&" : "?"}sha256=${release.sha256}`).catch(() => null);
28055
+ const retriedDigest = retried && await digestHex("sha256", retried);
28056
+ if (retried && retriedDigest === release.sha256) {
28057
+ archive = retried;
28058
+ digest2 = retriedDigest;
28059
+ } else {
28060
+ throw new AppRegistryError(
28061
+ `${release.filename} failed verification: the registry publishes sha256 ${release.sha256} but the download hashes to ${digest2}. ` + (retried ? "A retry past any cache returned the same bytes, so the registry and the file it names disagree." : "A retry past any cache could not be made; a cached copy of an older build would look like this, so clearing the cache is worth trying before the registry is blamed.")
28062
+ );
28063
+ }
28035
28064
  }
28036
28065
  const verified = await verify(ctx, archive, release, options);
28037
28066
  const root = `${APPS_ROOT}/${release.name}`;
@@ -28058,7 +28087,7 @@ async function installRelease(ctx, index, release, seen, options) {
28058
28087
  }
28059
28088
  if (manifest.docs) ctx.line(` docs: ${root}/${manifest.docs}`);
28060
28089
  }
28061
- async function download(url) {
28090
+ async function download2(url) {
28062
28091
  const response = await fetch(url);
28063
28092
  if (!response.ok) {
28064
28093
  throw new AppRegistryError(`${url} answered ${response.status} ${response.statusText}`);
@@ -38613,6 +38642,22 @@ var WS_DATA = "sandboxedjs:ws-data";
38613
38642
  var WS_CLOSE = "sandboxedjs:ws-close";
38614
38643
  var WS_ERROR = "sandboxedjs:ws-error";
38615
38644
 
38645
+ // src/preview/router.ts
38646
+ var LOOPBACK_HOST = /^(?:localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[?::1\]?)$/i;
38647
+ function containerTarget(value) {
38648
+ let url;
38649
+ try {
38650
+ url = new URL(value.trim());
38651
+ } catch {
38652
+ return null;
38653
+ }
38654
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
38655
+ if (!LOOPBACK_HOST.test(url.hostname)) return null;
38656
+ const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
38657
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
38658
+ return { port, path: `${url.pathname}${url.search}` };
38659
+ }
38660
+
38616
38661
  // src/preview/register.ts
38617
38662
  function serveContainerOn(port, box) {
38618
38663
  port.onmessage = async (event) => {
@@ -38647,20 +38692,6 @@ function serveContainerOn(port, box) {
38647
38692
  };
38648
38693
  port.start?.();
38649
38694
  }
38650
- var LOOPBACK_HOST = /^(?:localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[?::1\]?)$/i;
38651
- function containerTarget(value) {
38652
- let url;
38653
- try {
38654
- url = new URL(value.trim());
38655
- } catch {
38656
- return null;
38657
- }
38658
- if (url.protocol !== "http:" && url.protocol !== "https:") return null;
38659
- if (!LOOPBACK_HOST.test(url.hostname)) return null;
38660
- const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
38661
- if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
38662
- return { port, path: `${url.pathname}${url.search}` };
38663
- }
38664
38695
  async function createPreview(box, options = {}) {
38665
38696
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
38666
38697
  const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js?no-inline", import.meta.url);
@@ -772,7 +772,8 @@ function createSbxFs(FS, client, errnoCodes) {
772
772
  }
773
773
 
774
774
  // src/util/binary.ts
775
- typeof process !== "undefined" && process.versions != null && process.versions.node != null;
775
+ typeof window === "undefined" && typeof document === "undefined" && typeof process !== "undefined" && process.versions != null && process.versions.node != null && /* A shim has no `release.name`, and Node's has said "node" since v3. */
776
+ process.release?.name === "node";
776
777
  async function nodeBuiltin(name) {
777
778
  const specifier = `node:${name}`;
778
779
  return await import(
@@ -349,6 +349,20 @@ function previewSocketSource() {
349
349
 
350
350
  // src/preview/router.ts
351
351
  var CLAIM = /(?:^|\/)__sbx__\/(\d+)(?:\/|$)/;
352
+ var LOOPBACK_HOST = /^(?:localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[?::1\]?)$/i;
353
+ function containerTarget(value) {
354
+ let url;
355
+ try {
356
+ url = new URL(value.trim());
357
+ } catch {
358
+ return null;
359
+ }
360
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
361
+ if (!LOOPBACK_HOST.test(url.hostname)) return null;
362
+ const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
363
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
364
+ return { port, path: `${url.pathname}${url.search}` };
365
+ }
352
366
  var OUTDATED = /outdated optimize dep/i;
353
367
  var NOT_CONNECTED = 503;
354
368
  var PreviewRouter = class {
@@ -549,7 +563,13 @@ worker.addEventListener("message", (event) => {
549
563
  });
550
564
  worker.addEventListener("fetch", (event) => {
551
565
  const url = new URL(event.request.url);
552
- if (url.origin !== worker.location.origin) return;
566
+ if (url.origin !== worker.location.origin) {
567
+ const port2 = router.portFor(event.clientId);
568
+ const target = port2 === void 0 ? null : containerTarget(url.href);
569
+ if (!target) return;
570
+ event.respondWith(router.fetch(target.port, target.path, event.request, event.clientId));
571
+ return;
572
+ }
553
573
  const claimed = router.claimedPort(url.pathname);
554
574
  if (claimed !== null && event.request.mode === "navigate") {
555
575
  router.bind(event.resultingClientId || event.clientId, claimed);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandboxedjs",
3
- "version": "0.1.95",
3
+ "version": "0.1.99",
4
4
  "description": "A Linux-like container that runs entirely inside Node.js — POSIX shell, ~140 coreutils, Node.js and Python runtimes, virtual filesystem and networking. No Docker, no VM, no native modules.",
5
5
  "type": "module",
6
6
  "license": "MIT",