sandboxedjs 0.1.45 → 0.1.47
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/README.md +23 -3
- package/dist/agent.d.cts +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/{container-CZn9USBQ.d.cts → container-DyRF-bY0.d.cts} +52 -1
- package/dist/{container-CZn9USBQ.d.ts → container-DyRF-bY0.d.ts} +52 -1
- package/dist/index.cjs +295 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -4
- package/dist/index.d.ts +121 -4
- package/dist/index.js +295 -6
- package/dist/index.js.map +1 -1
- package/dist/service-worker.js +347 -8
- package/dist/service-worker.js.map +1 -1
- package/dist/worker-entry.js +133 -1
- package/dist/worker-entry.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -669,9 +669,10 @@ These are runtime lifecycle rules, not Vite-specific command replacements.
|
|
|
669
669
|
|
|
670
670
|
**Not done — these things still stand between this and a complete browser IDE.**
|
|
671
671
|
|
|
672
|
-
- *Preview isolation
|
|
673
|
-
and
|
|
674
|
-
|
|
672
|
+
- *Preview isolation.* `createPreview()` registers the bundled service worker and routes HTTP
|
|
673
|
+
and WebSocket traffic into the container. It is an origin-local preview for trusted code:
|
|
674
|
+
scripts in the frame share your origin. Serve it from a separate origin for anything you
|
|
675
|
+
did not write.
|
|
675
676
|
- *Complete worker isolation.* Worker-capable programs run off the main thread, but host-backed
|
|
676
677
|
compiler modules can require the local runtime. Container code is not a security boundary
|
|
677
678
|
from the host page; do not treat it as one.
|
|
@@ -846,6 +847,25 @@ URLs — `/src/main.js`, `/@vite/client` — resolve without rewriting anything.
|
|
|
846
847
|
> page it serves. Use it for code you trust; for anything else, serve the preview from a separate
|
|
847
848
|
> origin (a subdomain pointed at the same app) or stay with option 2.
|
|
848
849
|
|
|
850
|
+
**WebSockets, and why the HTML is modified.** A service worker can answer `fetch`; it cannot
|
|
851
|
+
answer a WebSocket handshake, because no browser ever shipped that. So a previewed page is handed
|
|
852
|
+
a `WebSocket` that tunnels through the host page into the container, injected as one `<script>` at
|
|
853
|
+
the top of `<head>`. URLs that do not name the container keep the native implementation.
|
|
854
|
+
|
|
855
|
+
```ts
|
|
856
|
+
const preview = await createPreview(box, { websocket: false }); // serve HTML byte for byte
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
This is what makes HMR work. Without it Vite loses more than hot reload: it loses the only channel
|
|
860
|
+
it has for telling a page that its dependency hashes are stale, which it needs after re-optimizing
|
|
861
|
+
dependencies — and the frame is then stranded on `504 Outdated Optimize Dep` with every module
|
|
862
|
+
failing and nothing to explain it. If you turn injection off, pass `onStale` to reload the frame
|
|
863
|
+
yourself.
|
|
864
|
+
|
|
865
|
+
The container side is a real socket, not a Vite shim: `http` servers emit `upgrade` with a duplex
|
|
866
|
+
socket, so `ws` and `socket.io` work unmodified. To speak to one from your own code rather than
|
|
867
|
+
from a previewed page, use `box.connect(port, init, peer)`.
|
|
868
|
+
|
|
849
869
|
Service workers need a secure context, and some embedded browsers disable them entirely — hence
|
|
850
870
|
the `null` return rather than a throw. Responses are served with both
|
|
851
871
|
`Cross-Origin-Resource-Policy` and `Cross-Origin-Embedder-Policy`, so a preview still frames
|
package/dist/agent.d.cts
CHANGED
package/dist/agent.d.ts
CHANGED
|
@@ -146,6 +146,16 @@ interface RuntimePackageInstaller {
|
|
|
146
146
|
/** Create a view that installs into another project root. */
|
|
147
147
|
forCwd?(cwd: string): RuntimePackageInstaller;
|
|
148
148
|
}
|
|
149
|
+
/** Where bytes written by an upgraded server inside the container come out. */
|
|
150
|
+
interface RuntimeSocketPeer {
|
|
151
|
+
data(bytes: Uint8Array): void;
|
|
152
|
+
close(): void;
|
|
153
|
+
}
|
|
154
|
+
/** The caller's end of a connection opened with {@link RuntimePod.connect}. */
|
|
155
|
+
interface RuntimeConnection {
|
|
156
|
+
send(bytes: Uint8Array): void;
|
|
157
|
+
close(): void;
|
|
158
|
+
}
|
|
149
159
|
/** The process-manager protocol a container's kernel bridge substitutes for. */
|
|
150
160
|
interface RuntimeProcessManager {
|
|
151
161
|
spawn(config: ChildSpawnConfig): ChildHandle;
|
|
@@ -160,6 +170,14 @@ interface RuntimePod {
|
|
|
160
170
|
};
|
|
161
171
|
spawn(command: string, args?: string[], options?: Record<string, unknown>): Promise<RuntimeProcess>;
|
|
162
172
|
request(port: number, init?: Record<string, unknown>): Promise<RuntimeHttpResponse>;
|
|
173
|
+
/**
|
|
174
|
+
* Open a connection that upgrades out of HTTP, or null if nothing takes one.
|
|
175
|
+
*
|
|
176
|
+
* Optional because a pod that only ever answers requests is still a usable
|
|
177
|
+
* pod — a caller treats the absence as "no WebSocket here" rather than as a
|
|
178
|
+
* broken implementation.
|
|
179
|
+
*/
|
|
180
|
+
connect?(port: number, init: Record<string, unknown>, peer: RuntimeSocketPeer): RuntimeConnection | null;
|
|
163
181
|
snapshot(options?: Record<string, unknown>): unknown;
|
|
164
182
|
restore(snapshot: unknown, options?: Record<string, unknown>): Promise<void>;
|
|
165
183
|
teardown(): void;
|
|
@@ -1705,6 +1723,20 @@ interface HttpResponse {
|
|
|
1705
1723
|
bytes: Uint8Array;
|
|
1706
1724
|
json<T = unknown>(): T;
|
|
1707
1725
|
}
|
|
1726
|
+
/** Where bytes written by an upgraded server inside the container come out. */
|
|
1727
|
+
interface SocketPeer {
|
|
1728
|
+
/** The server sent these. */
|
|
1729
|
+
data(bytes: Uint8Array): void;
|
|
1730
|
+
/** The server hung up. */
|
|
1731
|
+
close(): void;
|
|
1732
|
+
}
|
|
1733
|
+
/** The caller's end of a connection opened with {@link Container.connect}. */
|
|
1734
|
+
interface SocketConnection {
|
|
1735
|
+
/** Send bytes to the server. */
|
|
1736
|
+
send(bytes: Uint8Array): void;
|
|
1737
|
+
/** Hang up. */
|
|
1738
|
+
close(): void;
|
|
1739
|
+
}
|
|
1708
1740
|
declare class Container {
|
|
1709
1741
|
readonly kernel: Kernel;
|
|
1710
1742
|
readonly pod: RuntimePod;
|
|
@@ -1758,6 +1790,25 @@ declare class Container {
|
|
|
1758
1790
|
headers?: Record<string, string>;
|
|
1759
1791
|
body?: string | Uint8Array;
|
|
1760
1792
|
}): Promise<HttpResponse>;
|
|
1793
|
+
/**
|
|
1794
|
+
* Open a connection to a server inside the container that upgrades out of
|
|
1795
|
+
* HTTP — in practice, a WebSocket.
|
|
1796
|
+
*
|
|
1797
|
+
* The counterpart to {@link request}, and the thing a dev server needs that
|
|
1798
|
+
* a request cannot provide. Bytes go in with `send`, come back through
|
|
1799
|
+
* `peer.data`, and neither side interprets them: the container runs whatever
|
|
1800
|
+
* WebSocket library the program chose, and the caller is responsible for
|
|
1801
|
+
* speaking the protocol it answers with.
|
|
1802
|
+
*
|
|
1803
|
+
* Null means there is nothing to talk to — no server on the port, or one
|
|
1804
|
+
* that never registered an `upgrade` handler. Both are worth reporting
|
|
1805
|
+
* rather than waiting out, because neither resolves on its own.
|
|
1806
|
+
*/
|
|
1807
|
+
connect(port: number, init: {
|
|
1808
|
+
method?: string;
|
|
1809
|
+
path?: string;
|
|
1810
|
+
headers?: Record<string, string>;
|
|
1811
|
+
} | undefined, peer: SocketPeer): SocketConnection | null;
|
|
1761
1812
|
/**
|
|
1762
1813
|
* Deliver a request whose body is bytes, without letting them become text.
|
|
1763
1814
|
*
|
|
@@ -1808,4 +1859,4 @@ declare class Container {
|
|
|
1808
1859
|
/** Boot a container. The one function most callers need. */
|
|
1809
1860
|
declare function createContainer(opts?: ContainerOptions): Promise<Container>;
|
|
1810
1861
|
|
|
1811
|
-
export {
|
|
1862
|
+
export { Pipe as $, FileInput as A, BufferSink as B, Container as C, type DirEntry as D, type ExecContext as E, type FileData as F, FileOutput as G, type GroupEntry as H, type HttpResponse as I, type InputStream as J, Kernel as K, type Job as L, type KernelOptions as M, type Node as N, type OutputStream as O, type ListeningPort as P, type MountEntry as Q, type RuntimeVolume as R, Shell as S, type NetInterface as T, type NetworkOptions as U, Vfs as V, NetworkStack as W, NullInput as X, NullOutput as Y, PYTHON_VERSION as Z, type PasswdEntry as _, type ShellIO as a, Process as a0, type ProcessKind as a1, type ProcessOptions as a2, type ProcessState as a3, ProcessTable as a4, type PythonOptions as a5, ROOT_CRED as a6, type ResolvedExecutable as a7, type RunOptions as a8, type RunResult as a9, expandWords as aA, formatMode as aB, isCPythonAvailable as aC, isPythonAvailable as aD, makeCred as aE, octalMode as aF, parseUmask as aG, resetPidCounter as aH, shellQuote as aI, type RuntimeProcessManager as aa, type RuntimeProcessResult as ab, type SessionInit as ac, type SessionResult as ad, type SessionRunOptions as ae, ShellExit as af, type ShellInit as ag, type ShellOptions as ah, type SpawnHandle as ai, Stats as aj, type Stdio as ak, TeeOutput as al, UserDatabase as am, Variables as an, type VirtualNode as ao, type VirtualProvider as ap, type WriteOptions as aq, applyChmod as ar, braceExpand as as, captureStdio as at, configureCPython as au, configurePython as av, createChildProcessModule as aw, createContext as ax, defineCommand as ay, expandWord as az, Session as b, type Cred as c, type Command as d, type VolumeStat as e, type VolumeStats as f, type RuntimeHttpResponse as g, type SpawnChild as h, type SyncSpawn as i, type RuntimePod as j, type RuntimePackageInstaller as k, type ChildSpawnConfig as l, type ChildHandle as m, type RuntimeProcess as n, type RuntimeSocketPeer as o, type RuntimeConnection as p, createContainer as q, type CPythonOptions as r, CallbackSink as s, CommandRegistry as t, ContainerFs as u, type ContainerOptions as v, type ContextInit as w, type Env as x, type ExecOptions as y, type ExecResult as z };
|
|
@@ -146,6 +146,16 @@ interface RuntimePackageInstaller {
|
|
|
146
146
|
/** Create a view that installs into another project root. */
|
|
147
147
|
forCwd?(cwd: string): RuntimePackageInstaller;
|
|
148
148
|
}
|
|
149
|
+
/** Where bytes written by an upgraded server inside the container come out. */
|
|
150
|
+
interface RuntimeSocketPeer {
|
|
151
|
+
data(bytes: Uint8Array): void;
|
|
152
|
+
close(): void;
|
|
153
|
+
}
|
|
154
|
+
/** The caller's end of a connection opened with {@link RuntimePod.connect}. */
|
|
155
|
+
interface RuntimeConnection {
|
|
156
|
+
send(bytes: Uint8Array): void;
|
|
157
|
+
close(): void;
|
|
158
|
+
}
|
|
149
159
|
/** The process-manager protocol a container's kernel bridge substitutes for. */
|
|
150
160
|
interface RuntimeProcessManager {
|
|
151
161
|
spawn(config: ChildSpawnConfig): ChildHandle;
|
|
@@ -160,6 +170,14 @@ interface RuntimePod {
|
|
|
160
170
|
};
|
|
161
171
|
spawn(command: string, args?: string[], options?: Record<string, unknown>): Promise<RuntimeProcess>;
|
|
162
172
|
request(port: number, init?: Record<string, unknown>): Promise<RuntimeHttpResponse>;
|
|
173
|
+
/**
|
|
174
|
+
* Open a connection that upgrades out of HTTP, or null if nothing takes one.
|
|
175
|
+
*
|
|
176
|
+
* Optional because a pod that only ever answers requests is still a usable
|
|
177
|
+
* pod — a caller treats the absence as "no WebSocket here" rather than as a
|
|
178
|
+
* broken implementation.
|
|
179
|
+
*/
|
|
180
|
+
connect?(port: number, init: Record<string, unknown>, peer: RuntimeSocketPeer): RuntimeConnection | null;
|
|
163
181
|
snapshot(options?: Record<string, unknown>): unknown;
|
|
164
182
|
restore(snapshot: unknown, options?: Record<string, unknown>): Promise<void>;
|
|
165
183
|
teardown(): void;
|
|
@@ -1705,6 +1723,20 @@ interface HttpResponse {
|
|
|
1705
1723
|
bytes: Uint8Array;
|
|
1706
1724
|
json<T = unknown>(): T;
|
|
1707
1725
|
}
|
|
1726
|
+
/** Where bytes written by an upgraded server inside the container come out. */
|
|
1727
|
+
interface SocketPeer {
|
|
1728
|
+
/** The server sent these. */
|
|
1729
|
+
data(bytes: Uint8Array): void;
|
|
1730
|
+
/** The server hung up. */
|
|
1731
|
+
close(): void;
|
|
1732
|
+
}
|
|
1733
|
+
/** The caller's end of a connection opened with {@link Container.connect}. */
|
|
1734
|
+
interface SocketConnection {
|
|
1735
|
+
/** Send bytes to the server. */
|
|
1736
|
+
send(bytes: Uint8Array): void;
|
|
1737
|
+
/** Hang up. */
|
|
1738
|
+
close(): void;
|
|
1739
|
+
}
|
|
1708
1740
|
declare class Container {
|
|
1709
1741
|
readonly kernel: Kernel;
|
|
1710
1742
|
readonly pod: RuntimePod;
|
|
@@ -1758,6 +1790,25 @@ declare class Container {
|
|
|
1758
1790
|
headers?: Record<string, string>;
|
|
1759
1791
|
body?: string | Uint8Array;
|
|
1760
1792
|
}): Promise<HttpResponse>;
|
|
1793
|
+
/**
|
|
1794
|
+
* Open a connection to a server inside the container that upgrades out of
|
|
1795
|
+
* HTTP — in practice, a WebSocket.
|
|
1796
|
+
*
|
|
1797
|
+
* The counterpart to {@link request}, and the thing a dev server needs that
|
|
1798
|
+
* a request cannot provide. Bytes go in with `send`, come back through
|
|
1799
|
+
* `peer.data`, and neither side interprets them: the container runs whatever
|
|
1800
|
+
* WebSocket library the program chose, and the caller is responsible for
|
|
1801
|
+
* speaking the protocol it answers with.
|
|
1802
|
+
*
|
|
1803
|
+
* Null means there is nothing to talk to — no server on the port, or one
|
|
1804
|
+
* that never registered an `upgrade` handler. Both are worth reporting
|
|
1805
|
+
* rather than waiting out, because neither resolves on its own.
|
|
1806
|
+
*/
|
|
1807
|
+
connect(port: number, init: {
|
|
1808
|
+
method?: string;
|
|
1809
|
+
path?: string;
|
|
1810
|
+
headers?: Record<string, string>;
|
|
1811
|
+
} | undefined, peer: SocketPeer): SocketConnection | null;
|
|
1761
1812
|
/**
|
|
1762
1813
|
* Deliver a request whose body is bytes, without letting them become text.
|
|
1763
1814
|
*
|
|
@@ -1808,4 +1859,4 @@ declare class Container {
|
|
|
1808
1859
|
/** Boot a container. The one function most callers need. */
|
|
1809
1860
|
declare function createContainer(opts?: ContainerOptions): Promise<Container>;
|
|
1810
1861
|
|
|
1811
|
-
export {
|
|
1862
|
+
export { Pipe as $, FileInput as A, BufferSink as B, Container as C, type DirEntry as D, type ExecContext as E, type FileData as F, FileOutput as G, type GroupEntry as H, type HttpResponse as I, type InputStream as J, Kernel as K, type Job as L, type KernelOptions as M, type Node as N, type OutputStream as O, type ListeningPort as P, type MountEntry as Q, type RuntimeVolume as R, Shell as S, type NetInterface as T, type NetworkOptions as U, Vfs as V, NetworkStack as W, NullInput as X, NullOutput as Y, PYTHON_VERSION as Z, type PasswdEntry as _, type ShellIO as a, Process as a0, type ProcessKind as a1, type ProcessOptions as a2, type ProcessState as a3, ProcessTable as a4, type PythonOptions as a5, ROOT_CRED as a6, type ResolvedExecutable as a7, type RunOptions as a8, type RunResult as a9, expandWords as aA, formatMode as aB, isCPythonAvailable as aC, isPythonAvailable as aD, makeCred as aE, octalMode as aF, parseUmask as aG, resetPidCounter as aH, shellQuote as aI, type RuntimeProcessManager as aa, type RuntimeProcessResult as ab, type SessionInit as ac, type SessionResult as ad, type SessionRunOptions as ae, ShellExit as af, type ShellInit as ag, type ShellOptions as ah, type SpawnHandle as ai, Stats as aj, type Stdio as ak, TeeOutput as al, UserDatabase as am, Variables as an, type VirtualNode as ao, type VirtualProvider as ap, type WriteOptions as aq, applyChmod as ar, braceExpand as as, captureStdio as at, configureCPython as au, configurePython as av, createChildProcessModule as aw, createContext as ax, defineCommand as ay, expandWord as az, Session as b, type Cred as c, type Command as d, type VolumeStat as e, type VolumeStats as f, type RuntimeHttpResponse as g, type SpawnChild as h, type SyncSpawn as i, type RuntimePod as j, type RuntimePackageInstaller as k, type ChildSpawnConfig as l, type ChildHandle as m, type RuntimeProcess as n, type RuntimeSocketPeer as o, type RuntimeConnection as p, createContainer as q, type CPythonOptions as r, CallbackSink as s, CommandRegistry as t, ContainerFs as u, type ContainerOptions as v, type ContextInit as w, type Env as x, type ExecOptions as y, type ExecResult as z };
|
package/dist/index.cjs
CHANGED
|
@@ -20318,6 +20318,8 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
|
|
|
20318
20318
|
fetcher;
|
|
20319
20319
|
metadata = /* @__PURE__ */ new Map();
|
|
20320
20320
|
tarballs = /* @__PURE__ */ new Map();
|
|
20321
|
+
/** Single-version manifests, for the fields the packument omits. */
|
|
20322
|
+
details = /* @__PURE__ */ new Map();
|
|
20321
20323
|
forCwd(cwd) {
|
|
20322
20324
|
return new _CleanPackageInstaller(this.volume, { ...this.options, cwd });
|
|
20323
20325
|
}
|
|
@@ -20343,10 +20345,17 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
|
|
|
20343
20345
|
const version = resolveVersion(metadata, range);
|
|
20344
20346
|
const manifest = metadata.versions[version];
|
|
20345
20347
|
if (!manifest) throw new Error(`No matching version found for ${name}@${range}`);
|
|
20346
|
-
if (!supportsPlatform(manifest)) {
|
|
20347
|
-
throw new Error(`${name}@${version} is not compatible with linux/x64/glibc`);
|
|
20348
|
-
}
|
|
20349
20348
|
const identity = `${name}@${version}`;
|
|
20349
|
+
if (!supportsPlatform(manifest)) throw new IncompatiblePlatform(identity, "");
|
|
20350
|
+
if (manifest.os?.length || manifest.cpu?.length) {
|
|
20351
|
+
const detail = await this.platformDetail(name, version);
|
|
20352
|
+
if (!platformListAllows(detail.libc, PLATFORM.libc)) {
|
|
20353
|
+
throw new IncompatiblePlatform(identity, ` (needs ${detail.libc?.join(", ")})`);
|
|
20354
|
+
}
|
|
20355
|
+
if (detail.main?.endsWith(".node")) {
|
|
20356
|
+
throw new IncompatiblePlatform(identity, " (native addon; this runtime cannot dlopen)");
|
|
20357
|
+
}
|
|
20358
|
+
}
|
|
20350
20359
|
const target = join(modulesRoot, name);
|
|
20351
20360
|
const installed2 = this.tryReadJson(join(target, "package.json"));
|
|
20352
20361
|
if (installed2?.version === version) return installed2;
|
|
@@ -20368,11 +20377,44 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
|
|
|
20368
20377
|
try {
|
|
20369
20378
|
await this.installAt(dependency, dependencyRange, childRoot, options, nextAncestry);
|
|
20370
20379
|
} catch (error) {
|
|
20380
|
+
if (error instanceof IncompatiblePlatform) continue;
|
|
20371
20381
|
options.onProgress?.(`Skipped optional ${dependency}: ${error instanceof Error ? error.message : String(error)}`);
|
|
20372
20382
|
}
|
|
20373
20383
|
}
|
|
20374
20384
|
return manifest;
|
|
20375
20385
|
}
|
|
20386
|
+
/**
|
|
20387
|
+
* The `libc` and `main` fields, which the abbreviated packument leaves out.
|
|
20388
|
+
*
|
|
20389
|
+
* Fetched per version rather than as a full packument: the single-version
|
|
20390
|
+
* document is a few kilobytes, while the full one for a popular package runs
|
|
20391
|
+
* to megabytes. Only packages that already declare `os` or `cpu` ask for it,
|
|
20392
|
+
* so an ordinary install of pure-JavaScript dependencies makes no extra
|
|
20393
|
+
* requests at all — it is the prebuilt binaries, a handful per project, that
|
|
20394
|
+
* need the answer.
|
|
20395
|
+
*
|
|
20396
|
+
* A failure here is not fatal. Being unable to read `libc` puts the check
|
|
20397
|
+
* back where it was before this existed, which is worth strictly less than
|
|
20398
|
+
* being right and strictly more than refusing to install.
|
|
20399
|
+
*/
|
|
20400
|
+
async platformDetail(name, version) {
|
|
20401
|
+
const key = `${name}@${version}`;
|
|
20402
|
+
let request = this.details.get(key);
|
|
20403
|
+
if (!request) {
|
|
20404
|
+
request = (async () => {
|
|
20405
|
+
try {
|
|
20406
|
+
const encoded = name.startsWith("@") ? name.replace("/", "%2F") : encodeURIComponent(name);
|
|
20407
|
+
const response = await this.fetcher(`${this.registry}/${encoded}/${version}`);
|
|
20408
|
+
if (!response.ok) return {};
|
|
20409
|
+
return await response.json();
|
|
20410
|
+
} catch {
|
|
20411
|
+
return {};
|
|
20412
|
+
}
|
|
20413
|
+
})();
|
|
20414
|
+
this.details.set(key, request);
|
|
20415
|
+
}
|
|
20416
|
+
return request;
|
|
20417
|
+
}
|
|
20376
20418
|
async getMetadata(name) {
|
|
20377
20419
|
let request = this.metadata.get(name);
|
|
20378
20420
|
if (!request) {
|
|
@@ -20440,8 +20482,15 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
|
|
|
20440
20482
|
}
|
|
20441
20483
|
}
|
|
20442
20484
|
};
|
|
20485
|
+
var PLATFORM = { os: "linux", cpu: "x64", libc: "glibc" };
|
|
20486
|
+
var IncompatiblePlatform = class extends Error {
|
|
20487
|
+
constructor(identity, detail) {
|
|
20488
|
+
super(`${identity} is not compatible with ${PLATFORM.os}/${PLATFORM.cpu}/${PLATFORM.libc}${detail}`);
|
|
20489
|
+
this.name = "IncompatiblePlatform";
|
|
20490
|
+
}
|
|
20491
|
+
};
|
|
20443
20492
|
function supportsPlatform(manifest) {
|
|
20444
|
-
return
|
|
20493
|
+
return platformListAllows(manifest.os, PLATFORM.os) && platformListAllows(manifest.cpu, PLATFORM.cpu) && platformListAllows(manifest.libc, PLATFORM.libc);
|
|
20445
20494
|
}
|
|
20446
20495
|
function platformListAllows(values, current) {
|
|
20447
20496
|
if (!values?.length) return true;
|
|
@@ -23489,8 +23538,17 @@ var VirtualIncomingMessage = class extends streamModule4__default.default.Readab
|
|
|
23489
23538
|
httpVersionMajor = 1;
|
|
23490
23539
|
httpVersionMinor = 1;
|
|
23491
23540
|
complete = true;
|
|
23541
|
+
/*
|
|
23542
|
+
* Mutable, and `connection` a getter over it, because an upgraded request
|
|
23543
|
+
* carries a real socket rather than the stub. `emit("upgrade", req, socket)`
|
|
23544
|
+
* hands the same object to both places, and a library that reaches it as
|
|
23545
|
+
* `req.socket` — rather than through the argument — has to find the one it
|
|
23546
|
+
* can actually write to.
|
|
23547
|
+
*/
|
|
23492
23548
|
socket = socketStub();
|
|
23493
|
-
connection
|
|
23549
|
+
get connection() {
|
|
23550
|
+
return this.socket;
|
|
23551
|
+
}
|
|
23494
23552
|
constructor(init) {
|
|
23495
23553
|
super();
|
|
23496
23554
|
this.method = (init.method ?? "GET").toUpperCase();
|
|
@@ -23596,6 +23654,80 @@ var VirtualServerResponse = class extends streamModule4__default.default.Writabl
|
|
|
23596
23654
|
return this;
|
|
23597
23655
|
}
|
|
23598
23656
|
};
|
|
23657
|
+
var VirtualSocket = class extends streamModule4__default.default.Duplex {
|
|
23658
|
+
remoteAddress = "127.0.0.1";
|
|
23659
|
+
remotePort = 0;
|
|
23660
|
+
localAddress = "127.0.0.1";
|
|
23661
|
+
localPort = 0;
|
|
23662
|
+
encrypted = false;
|
|
23663
|
+
bufferSize = 0;
|
|
23664
|
+
peer;
|
|
23665
|
+
/** Set once the peer is told, so `end()` then `destroy()` reports once. */
|
|
23666
|
+
hungUp = false;
|
|
23667
|
+
constructor(peer) {
|
|
23668
|
+
super({ allowHalfOpen: false });
|
|
23669
|
+
this.peer = peer;
|
|
23670
|
+
}
|
|
23671
|
+
_read() {
|
|
23672
|
+
}
|
|
23673
|
+
_write(chunk, encoding, callback) {
|
|
23674
|
+
const bytes2 = Buffer2.isBuffer(chunk) ? chunk : Buffer2.from(chunk, encoding);
|
|
23675
|
+
try {
|
|
23676
|
+
this.peer?.data(new Uint8Array(bytes2));
|
|
23677
|
+
callback();
|
|
23678
|
+
} catch (error) {
|
|
23679
|
+
callback(error);
|
|
23680
|
+
}
|
|
23681
|
+
}
|
|
23682
|
+
_final(callback) {
|
|
23683
|
+
this.hangUp();
|
|
23684
|
+
callback();
|
|
23685
|
+
}
|
|
23686
|
+
_destroy(error, callback) {
|
|
23687
|
+
this.hangUp();
|
|
23688
|
+
callback(error);
|
|
23689
|
+
}
|
|
23690
|
+
hangUp() {
|
|
23691
|
+
if (this.hungUp) return;
|
|
23692
|
+
this.hungUp = true;
|
|
23693
|
+
try {
|
|
23694
|
+
this.peer?.close();
|
|
23695
|
+
} catch {
|
|
23696
|
+
}
|
|
23697
|
+
this.peer = null;
|
|
23698
|
+
}
|
|
23699
|
+
/** Bytes arriving from the far end. */
|
|
23700
|
+
deliver(bytes2) {
|
|
23701
|
+
if (this.destroyed || this.hungUp) return;
|
|
23702
|
+
this.push(Buffer2.from(bytes2));
|
|
23703
|
+
}
|
|
23704
|
+
/** The far end went away; let the server's stream end cleanly. */
|
|
23705
|
+
peerClosed() {
|
|
23706
|
+
if (this.destroyed) return;
|
|
23707
|
+
this.push(null);
|
|
23708
|
+
}
|
|
23709
|
+
/* The parts of `net.Socket` a WebSocket library reaches for. None of them
|
|
23710
|
+
* mean anything without a kernel, but every one of them is called. */
|
|
23711
|
+
setNoDelay() {
|
|
23712
|
+
return this;
|
|
23713
|
+
}
|
|
23714
|
+
setKeepAlive() {
|
|
23715
|
+
return this;
|
|
23716
|
+
}
|
|
23717
|
+
setTimeout(_milliseconds, callback) {
|
|
23718
|
+
if (callback) this.once("timeout", callback);
|
|
23719
|
+
return this;
|
|
23720
|
+
}
|
|
23721
|
+
destroySoon() {
|
|
23722
|
+
this.end();
|
|
23723
|
+
}
|
|
23724
|
+
ref() {
|
|
23725
|
+
return this;
|
|
23726
|
+
}
|
|
23727
|
+
unref() {
|
|
23728
|
+
return this;
|
|
23729
|
+
}
|
|
23730
|
+
};
|
|
23599
23731
|
var VirtualHttpServer = class extends EventEmitter4__default.default {
|
|
23600
23732
|
constructor(router, owner, listener) {
|
|
23601
23733
|
super();
|
|
@@ -23695,6 +23827,33 @@ var VirtualHttpRouter = class {
|
|
|
23695
23827
|
return { statusCode: 500, statusMessage: "Internal Server Error", headers: {}, body: error instanceof Error ? error.message : String(error) };
|
|
23696
23828
|
}
|
|
23697
23829
|
}
|
|
23830
|
+
/**
|
|
23831
|
+
* Open a connection that leaves HTTP behind — a WebSocket, in practice.
|
|
23832
|
+
*
|
|
23833
|
+
* The counterpart to {@link request}, and the thing whose absence made a dev
|
|
23834
|
+
* server look broken. Every Node WebSocket library is built the same way: it
|
|
23835
|
+
* hands `http.Server` an `upgrade` listener and waits. Nothing here ever
|
|
23836
|
+
* emitted one, so `ws` sat holding a server that could not receive a single
|
|
23837
|
+
* connection, and Vite lost the channel it uses to tell a page to reload —
|
|
23838
|
+
* which is the only way it can recover after re-optimizing dependencies.
|
|
23839
|
+
*
|
|
23840
|
+
* Null means there is nothing to connect to: no server on the port, or a
|
|
23841
|
+
* server that never asked for upgrades. Both are refusals the caller should
|
|
23842
|
+
* report rather than wait out.
|
|
23843
|
+
*/
|
|
23844
|
+
connect(port, init, peer) {
|
|
23845
|
+
const item = this.servers.get(port);
|
|
23846
|
+
if (!item) return null;
|
|
23847
|
+
if (item.server.listenerCount("upgrade") === 0) return null;
|
|
23848
|
+
const request = new VirtualIncomingMessage(init);
|
|
23849
|
+
const socket = new VirtualSocket(peer);
|
|
23850
|
+
request.socket = socket;
|
|
23851
|
+
item.server.emit("upgrade", request, socket, Buffer2.alloc(0));
|
|
23852
|
+
return {
|
|
23853
|
+
send: (bytes2) => socket.deliver(bytes2),
|
|
23854
|
+
close: () => socket.peerClosed()
|
|
23855
|
+
};
|
|
23856
|
+
}
|
|
23698
23857
|
};
|
|
23699
23858
|
var VirtualClientResponse = class extends streamModule4__default.default.Readable {
|
|
23700
23859
|
constructor(statusCode, statusMessage, headers, body) {
|
|
@@ -27211,6 +27370,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
27211
27370
|
async request(_port, _init = {}) {
|
|
27212
27371
|
return this.router.request(_port, _init);
|
|
27213
27372
|
}
|
|
27373
|
+
connect(port, init, peer) {
|
|
27374
|
+
return this.router.connect(port, init, peer);
|
|
27375
|
+
}
|
|
27214
27376
|
snapshot() {
|
|
27215
27377
|
this.assertActive();
|
|
27216
27378
|
return this.volume.snapshot();
|
|
@@ -27646,6 +27808,15 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
27646
27808
|
case "http-response":
|
|
27647
27809
|
this.settleProxied(Number(message.id), message.response);
|
|
27648
27810
|
return;
|
|
27811
|
+
case "ws-data":
|
|
27812
|
+
this.upgraded.get(Number(message.id))?.write(new Uint8Array(message.data));
|
|
27813
|
+
return;
|
|
27814
|
+
case "ws-close": {
|
|
27815
|
+
const socket = this.upgraded.get(Number(message.id));
|
|
27816
|
+
this.upgraded.delete(Number(message.id));
|
|
27817
|
+
socket?.end();
|
|
27818
|
+
return;
|
|
27819
|
+
}
|
|
27649
27820
|
case "child-start":
|
|
27650
27821
|
this.startChild(worker, children, message, ownedChildren);
|
|
27651
27822
|
return;
|
|
@@ -27734,6 +27905,8 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
27734
27905
|
// ── HTTP servers living on another thread ─────────────────────────────────
|
|
27735
27906
|
proxies = /* @__PURE__ */ new Map();
|
|
27736
27907
|
waiting = /* @__PURE__ */ new Map();
|
|
27908
|
+
/** Upgraded connections, by the id the Worker knows them as. */
|
|
27909
|
+
upgraded = /* @__PURE__ */ new Map();
|
|
27737
27910
|
nextRequestId = 1;
|
|
27738
27911
|
/**
|
|
27739
27912
|
* Register a stand-in for a server that is actually running in the Worker.
|
|
@@ -27746,6 +27919,9 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
27746
27919
|
const server = new VirtualHttpServer(this.router, owner, (request, response) => {
|
|
27747
27920
|
void this.forward(worker, port, request, response);
|
|
27748
27921
|
});
|
|
27922
|
+
server.on("upgrade", (request, socket) => {
|
|
27923
|
+
this.upgrade(worker, port, request, socket);
|
|
27924
|
+
});
|
|
27749
27925
|
try {
|
|
27750
27926
|
server.listen(port);
|
|
27751
27927
|
this.proxies.set(port, server);
|
|
@@ -27776,6 +27952,33 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
27776
27952
|
response.writeHead(Number(result.statusCode ?? 200), String(result.statusMessage ?? ""), result.headers ?? {});
|
|
27777
27953
|
response.end(result.body ?? new Uint8Array());
|
|
27778
27954
|
}
|
|
27955
|
+
/**
|
|
27956
|
+
* Tunnel one upgraded connection to the server that actually holds the port.
|
|
27957
|
+
*
|
|
27958
|
+
* Unlike {@link forward} there is no reply to wait for: both ends write
|
|
27959
|
+
* whenever they have something, until one of them stops. The id is what ties
|
|
27960
|
+
* the two directions together across the Worker boundary.
|
|
27961
|
+
*/
|
|
27962
|
+
upgrade(worker, port, request, socket) {
|
|
27963
|
+
const id = this.nextRequestId++;
|
|
27964
|
+
this.upgraded.set(id, socket);
|
|
27965
|
+
socket.on("data", (chunk) => {
|
|
27966
|
+
const bytes2 = new Uint8Array(chunk);
|
|
27967
|
+
worker.postMessage({ type: "ws-data", id, data: bytes2 });
|
|
27968
|
+
});
|
|
27969
|
+
const drop = () => {
|
|
27970
|
+
if (!this.upgraded.delete(id)) return;
|
|
27971
|
+
worker.postMessage({ type: "ws-close", id });
|
|
27972
|
+
};
|
|
27973
|
+
socket.on("close", drop);
|
|
27974
|
+
socket.on("error", drop);
|
|
27975
|
+
worker.postMessage({
|
|
27976
|
+
type: "ws-open",
|
|
27977
|
+
id,
|
|
27978
|
+
port,
|
|
27979
|
+
init: { method: request.method, path: request.url, headers: request.headers }
|
|
27980
|
+
});
|
|
27981
|
+
}
|
|
27779
27982
|
settleProxied(id, response) {
|
|
27780
27983
|
const resolve2 = this.waiting.get(id);
|
|
27781
27984
|
if (!resolve2) return;
|
|
@@ -27788,6 +27991,10 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
27788
27991
|
server.close();
|
|
27789
27992
|
this.proxies.delete(port);
|
|
27790
27993
|
}
|
|
27994
|
+
for (const [id, socket] of [...this.upgraded]) {
|
|
27995
|
+
this.upgraded.delete(id);
|
|
27996
|
+
socket.destroy();
|
|
27997
|
+
}
|
|
27791
27998
|
}
|
|
27792
27999
|
teardown() {
|
|
27793
28000
|
for (const { worker, server } of [...this.live]) {
|
|
@@ -28161,6 +28368,29 @@ var Container = class _Container {
|
|
|
28161
28368
|
}
|
|
28162
28369
|
};
|
|
28163
28370
|
}
|
|
28371
|
+
/**
|
|
28372
|
+
* Open a connection to a server inside the container that upgrades out of
|
|
28373
|
+
* HTTP — in practice, a WebSocket.
|
|
28374
|
+
*
|
|
28375
|
+
* The counterpart to {@link request}, and the thing a dev server needs that
|
|
28376
|
+
* a request cannot provide. Bytes go in with `send`, come back through
|
|
28377
|
+
* `peer.data`, and neither side interprets them: the container runs whatever
|
|
28378
|
+
* WebSocket library the program chose, and the caller is responsible for
|
|
28379
|
+
* speaking the protocol it answers with.
|
|
28380
|
+
*
|
|
28381
|
+
* Null means there is nothing to talk to — no server on the port, or one
|
|
28382
|
+
* that never registered an `upgrade` handler. Both are worth reporting
|
|
28383
|
+
* rather than waiting out, because neither resolves on its own.
|
|
28384
|
+
*/
|
|
28385
|
+
connect(port, init = {}, peer) {
|
|
28386
|
+
this.assertActive();
|
|
28387
|
+
if (!this.pod.connect) return null;
|
|
28388
|
+
return this.pod.connect(
|
|
28389
|
+
port,
|
|
28390
|
+
{ method: init.method ?? "GET", path: init.path ?? "/", headers: init.headers ?? {} },
|
|
28391
|
+
peer
|
|
28392
|
+
);
|
|
28393
|
+
}
|
|
28164
28394
|
/**
|
|
28165
28395
|
* Deliver a request whose body is bytes, without letting them become text.
|
|
28166
28396
|
*
|
|
@@ -28732,6 +28962,12 @@ init_arith();
|
|
|
28732
28962
|
init_expand();
|
|
28733
28963
|
init_builtins();
|
|
28734
28964
|
|
|
28965
|
+
// src/preview/ws-client.ts
|
|
28966
|
+
var WS_OPEN = "sandboxedjs:ws-open";
|
|
28967
|
+
var WS_DATA = "sandboxedjs:ws-data";
|
|
28968
|
+
var WS_CLOSE = "sandboxedjs:ws-close";
|
|
28969
|
+
var WS_ERROR = "sandboxedjs:ws-error";
|
|
28970
|
+
|
|
28735
28971
|
// src/preview/register.ts
|
|
28736
28972
|
function serveContainerOn(port, box) {
|
|
28737
28973
|
port.onmessage = async (event) => {
|
|
@@ -28817,7 +29053,7 @@ async function createPreview(box, options = {}) {
|
|
|
28817
29053
|
channel = new MessageChannel();
|
|
28818
29054
|
serveContainerOn(channel.port1, box);
|
|
28819
29055
|
const target = registration.active ?? worker;
|
|
28820
|
-
target.postMessage({ type: "sandboxedjs:connect" }, [channel.port2]);
|
|
29056
|
+
target.postMessage({ type: "sandboxedjs:connect", injectSockets: options.websocket !== false }, [channel.port2]);
|
|
28821
29057
|
};
|
|
28822
29058
|
const onWorkerMessage = (event) => {
|
|
28823
29059
|
const type = event.data?.type;
|
|
@@ -28826,6 +29062,56 @@ async function createPreview(box, options = {}) {
|
|
|
28826
29062
|
};
|
|
28827
29063
|
navigator.serviceWorker.addEventListener("message", onWorkerMessage);
|
|
28828
29064
|
connect();
|
|
29065
|
+
const sockets = /* @__PURE__ */ new Map();
|
|
29066
|
+
const onFrameMessage = (event) => {
|
|
29067
|
+
if (event.origin !== location.origin || !event.source) return;
|
|
29068
|
+
const data = event.data;
|
|
29069
|
+
if (!data || typeof data !== "object") return;
|
|
29070
|
+
if (data.type !== WS_OPEN && data.type !== WS_DATA && data.type !== WS_CLOSE) return;
|
|
29071
|
+
const source = event.source;
|
|
29072
|
+
const id = Number(data.id);
|
|
29073
|
+
const reply = (message, transfer = []) => source.postMessage(message, location.origin, transfer);
|
|
29074
|
+
if (data.type === WS_OPEN) {
|
|
29075
|
+
let table2 = sockets.get(source);
|
|
29076
|
+
if (!table2) sockets.set(source, table2 = /* @__PURE__ */ new Map());
|
|
29077
|
+
let opened = null;
|
|
29078
|
+
try {
|
|
29079
|
+
opened = box.connect(
|
|
29080
|
+
Number(data.port),
|
|
29081
|
+
{ method: "GET", path: data.path ?? "/", headers: data.headers ?? {} },
|
|
29082
|
+
{
|
|
29083
|
+
data: (bytes2) => {
|
|
29084
|
+
const carrier = bytes2.slice().buffer;
|
|
29085
|
+
reply({ type: WS_DATA, id, data: carrier }, [carrier]);
|
|
29086
|
+
},
|
|
29087
|
+
close: () => {
|
|
29088
|
+
table2.delete(id);
|
|
29089
|
+
reply({ type: WS_CLOSE, id });
|
|
29090
|
+
}
|
|
29091
|
+
}
|
|
29092
|
+
);
|
|
29093
|
+
} catch (error) {
|
|
29094
|
+
opened = null;
|
|
29095
|
+
reply({ type: WS_ERROR, id, message: error instanceof Error ? error.message : String(error) });
|
|
29096
|
+
return;
|
|
29097
|
+
}
|
|
29098
|
+
if (!opened) {
|
|
29099
|
+
reply({ type: WS_ERROR, id, message: `Nothing is accepting WebSocket connections on port ${data.port}.` });
|
|
29100
|
+
return;
|
|
29101
|
+
}
|
|
29102
|
+
table2.set(id, opened);
|
|
29103
|
+
return;
|
|
29104
|
+
}
|
|
29105
|
+
const table = sockets.get(source);
|
|
29106
|
+
const socket = table?.get(id);
|
|
29107
|
+
if (!socket) return;
|
|
29108
|
+
if (data.type === WS_DATA) socket.send(new Uint8Array(data.data));
|
|
29109
|
+
else {
|
|
29110
|
+
socket.close();
|
|
29111
|
+
table.delete(id);
|
|
29112
|
+
}
|
|
29113
|
+
};
|
|
29114
|
+
if (typeof window !== "undefined") window.addEventListener("message", onFrameMessage);
|
|
28829
29115
|
const base2 = registration.scope.replace(/\/$/, "");
|
|
28830
29116
|
const urlFor = (port) => `${base2}/__sbx__/${port}/`;
|
|
28831
29117
|
return {
|
|
@@ -28839,6 +29125,9 @@ async function createPreview(box, options = {}) {
|
|
|
28839
29125
|
},
|
|
28840
29126
|
dispose: async () => {
|
|
28841
29127
|
navigator.serviceWorker.removeEventListener("message", onWorkerMessage);
|
|
29128
|
+
if (typeof window !== "undefined") window.removeEventListener("message", onFrameMessage);
|
|
29129
|
+
for (const table of sockets.values()) for (const socket of table.values()) socket.close();
|
|
29130
|
+
sockets.clear();
|
|
28842
29131
|
channel?.port1.close();
|
|
28843
29132
|
channel = null;
|
|
28844
29133
|
await registration.unregister();
|