sandboxedjs 0.1.45 → 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Shell, a as ShellIO, b as Session, V as Vfs, c as Cred, N as Node, d as Command, K as Kernel, R as RuntimeVolume, e as VolumeStat, f as VolumeStats, g as RuntimeHttpResponse, h as SpawnChild, i as SyncSpawn, j as RuntimePod, k as RuntimePackageInstaller, l as ChildSpawnConfig, m as ChildHandle, n as RuntimeProcess, E as ExecContext, O as OutputStream, C as Container, o as createContainer } from './container-CZn9USBQ.cjs';
2
- export { B as BufferSink, p as CPythonOptions, q as CallbackSink, r as CommandRegistry, s as ContainerFs, t as ContainerOptions, u as ContextInit, D as DirEntry, v as Env, w as ExecOptions, x as ExecResult, F as FileData, y as FileInput, z as FileOutput, G as GroupEntry, H as HttpResponse, I as InputStream, J as Job, A as KernelOptions, L as ListeningPort, M as MountEntry, P as NetInterface, Q as NetworkOptions, T as NetworkStack, U as NullInput, W as NullOutput, X as PYTHON_VERSION, Y as PasswdEntry, Z as Pipe, _ as Process, $ as ProcessKind, a0 as ProcessOptions, a1 as ProcessState, a2 as ProcessTable, a3 as PythonOptions, a4 as ROOT_CRED, a5 as ResolvedExecutable, a6 as RunOptions, a7 as RunResult, a8 as RuntimeProcessManager, a9 as RuntimeProcessResult, aa as SessionInit, ab as SessionResult, ac as SessionRunOptions, ad as ShellExit, ae as ShellInit, af as ShellOptions, ag as SpawnHandle, ah as Stats, ai as Stdio, aj as TeeOutput, ak as UserDatabase, al as Variables, am as VirtualNode, an as VirtualProvider, ao as WriteOptions, ap as applyChmod, aq as braceExpand, ar as captureStdio, as as configureCPython, at as configurePython, au as createChildProcessModule, av as createContext, aw as defineCommand, ax as expandWord, ay as expandWords, az as formatMode, aA as isCPythonAvailable, aB as isPythonAvailable, aC as makeCred, aD as octalMode, aE as parseUmask, aF as resetPidCounter, aG as shellQuote } from './container-CZn9USBQ.cjs';
1
+ import { S as Shell, a as ShellIO, b as Session, V as Vfs, c as Cred, N as Node, d as Command, K as Kernel, R as RuntimeVolume, e as VolumeStat, f as VolumeStats, g as RuntimeHttpResponse, h as SpawnChild, i as SyncSpawn, j as RuntimePod, k as RuntimePackageInstaller, l as ChildSpawnConfig, m as ChildHandle, n as RuntimeProcess, o as RuntimeSocketPeer, p as RuntimeConnection, E as ExecContext, O as OutputStream, C as Container, q as createContainer } from './container-DyRF-bY0.cjs';
2
+ export { B as BufferSink, r as CPythonOptions, s as CallbackSink, t as CommandRegistry, u as ContainerFs, v as ContainerOptions, w as ContextInit, D as DirEntry, x as Env, y as ExecOptions, z as ExecResult, F as FileData, A as FileInput, G as FileOutput, H as GroupEntry, I as HttpResponse, J as InputStream, L as Job, M as KernelOptions, P as ListeningPort, Q as MountEntry, T as NetInterface, U as NetworkOptions, W as NetworkStack, X as NullInput, Y as NullOutput, Z as PYTHON_VERSION, _ as PasswdEntry, $ as Pipe, a0 as Process, a1 as ProcessKind, a2 as ProcessOptions, a3 as ProcessState, a4 as ProcessTable, a5 as PythonOptions, a6 as ROOT_CRED, a7 as ResolvedExecutable, a8 as RunOptions, a9 as RunResult, aa as RuntimeProcessManager, ab as RuntimeProcessResult, ac as SessionInit, ad as SessionResult, ae as SessionRunOptions, af as ShellExit, ag as ShellInit, ah as ShellOptions, ai as SpawnHandle, aj as Stats, ak as Stdio, al as TeeOutput, am as UserDatabase, an as Variables, ao as VirtualNode, ap as VirtualProvider, aq as WriteOptions, ar as applyChmod, as as braceExpand, at as captureStdio, au as configureCPython, av as configurePython, aw as createChildProcessModule, ax as createContext, ay as defineCommand, az as expandWord, aA as expandWords, aB as formatMode, aC as isCPythonAvailable, aD as isPythonAvailable, aE as makeCred, aF as octalMode, aG as parseUmask, aH as resetPidCounter, aI as shellQuote } from './container-DyRF-bY0.cjs';
3
3
  import EventEmitter from 'events/events.js';
4
4
  import streamModule from 'stream-browserify';
5
5
 
@@ -648,8 +648,8 @@ declare class VirtualIncomingMessage extends streamModule.Readable {
648
648
  readonly httpVersionMajor = 1;
649
649
  readonly httpVersionMinor = 1;
650
650
  readonly complete = true;
651
- readonly socket: Record<string, unknown>;
652
- readonly connection: Record<string, unknown>;
651
+ socket: Record<string, unknown> | VirtualSocket;
652
+ get connection(): Record<string, unknown> | VirtualSocket;
653
653
  constructor(init: VirtualRequestInit);
654
654
  _read(): void;
655
655
  setTimeout(_milliseconds: number, callback?: () => void): this;
@@ -683,6 +683,60 @@ declare class VirtualServerResponse extends streamModule.Writable {
683
683
  addTrailers(_headers: Record<string, string>): void;
684
684
  setTimeout(_milliseconds: number, callback?: () => void): this;
685
685
  }
686
+ /** Where a {@link VirtualSocket} puts the bytes the server writes. */
687
+ interface VirtualSocketPeer {
688
+ /** The server sent these. */
689
+ data(bytes: Uint8Array): void;
690
+ /** The server hung up. */
691
+ close(): void;
692
+ }
693
+ /**
694
+ * The server half of a connection that has stopped being HTTP.
695
+ *
696
+ * Everything else in this file models one request and one response, because
697
+ * that is all a container's servers were ever asked for. A protocol upgrade is
698
+ * the case that does not fit: after the `101` there is no request and no
699
+ * response, only two peers writing bytes at each other for as long as they
700
+ * both stay interested.
701
+ *
702
+ * So this is a real `Duplex` rather than another stub. It has to be — the
703
+ * libraries that speak WebSocket do not merely read `req.headers` and reply,
704
+ * they take the socket and run a framing protocol over it, and a stub that
705
+ * accepts `write()` and drops it produces a server that completes its
706
+ * handshake and is then silent forever, which looks exactly like a network
707
+ * problem and is the hardest possible thing to attribute.
708
+ *
709
+ * Writes go out through {@link VirtualSocketPeer}; {@link deliver} pushes what
710
+ * comes back. Nothing here knows what the bytes mean, which is the point: `ws`,
711
+ * `socket.io` and Vite's HMR server all work over it unmodified.
712
+ */
713
+ declare class VirtualSocket extends streamModule.Duplex {
714
+ readonly remoteAddress = "127.0.0.1";
715
+ readonly remotePort = 0;
716
+ readonly localAddress = "127.0.0.1";
717
+ readonly localPort = 0;
718
+ readonly encrypted = false;
719
+ readonly bufferSize = 0;
720
+ private peer;
721
+ /** Set once the peer is told, so `end()` then `destroy()` reports once. */
722
+ private hungUp;
723
+ constructor(peer: VirtualSocketPeer);
724
+ _read(): void;
725
+ _write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
726
+ _final(callback: (error?: Error | null) => void): void;
727
+ _destroy(error: Error | null, callback: (error?: Error | null) => void): void;
728
+ private hangUp;
729
+ /** Bytes arriving from the far end. */
730
+ deliver(bytes: Uint8Array): void;
731
+ /** The far end went away; let the server's stream end cleanly. */
732
+ peerClosed(): void;
733
+ setNoDelay(): this;
734
+ setKeepAlive(): this;
735
+ setTimeout(_milliseconds: number, callback?: () => void): this;
736
+ destroySoon(): void;
737
+ ref(): this;
738
+ unref(): this;
739
+ }
686
740
  declare class VirtualHttpServer extends EventEmitter {
687
741
  private readonly router;
688
742
  readonly owner: string;
@@ -716,6 +770,28 @@ declare class VirtualHttpRouter {
716
770
  closeOwner(owner: string): void;
717
771
  closeAll(): void;
718
772
  request(port: number, init?: VirtualRequestInit): Promise<RuntimeHttpResponse>;
773
+ /**
774
+ * Open a connection that leaves HTTP behind — a WebSocket, in practice.
775
+ *
776
+ * The counterpart to {@link request}, and the thing whose absence made a dev
777
+ * server look broken. Every Node WebSocket library is built the same way: it
778
+ * hands `http.Server` an `upgrade` listener and waits. Nothing here ever
779
+ * emitted one, so `ws` sat holding a server that could not receive a single
780
+ * connection, and Vite lost the channel it uses to tell a page to reload —
781
+ * which is the only way it can recover after re-optimizing dependencies.
782
+ *
783
+ * Null means there is nothing to connect to: no server on the port, or a
784
+ * server that never asked for upgrades. Both are refusals the caller should
785
+ * report rather than wait out.
786
+ */
787
+ connect(port: number, init: VirtualRequestInit, peer: VirtualSocketPeer): VirtualConnection | null;
788
+ }
789
+ /** A connection handed back by {@link VirtualHttpRouter.connect}. */
790
+ interface VirtualConnection {
791
+ /** Bytes from the far end, into the container. */
792
+ send(bytes: Uint8Array): void;
793
+ /** The far end went away. */
794
+ close(): void;
719
795
  }
720
796
 
721
797
  interface CoreModulesOptions {
@@ -1029,6 +1105,7 @@ declare class LocalRuntimePod implements RuntimePod {
1029
1105
  */
1030
1106
  private settle;
1031
1107
  request(_port: number, _init?: Record<string, unknown>): Promise<RuntimeHttpResponse>;
1108
+ connect(port: number, init: Record<string, unknown>, peer: RuntimeSocketPeer): RuntimeConnection | null;
1032
1109
  snapshot(): MemoryVolumeSnapshotEntry[];
1033
1110
  restore(snapshot: unknown): Promise<void>;
1034
1111
  teardown(): void;
@@ -1076,6 +1153,8 @@ declare class WorkerRuntimePod extends LocalRuntimePod {
1076
1153
  private runChildToCompletion;
1077
1154
  private readonly proxies;
1078
1155
  private readonly waiting;
1156
+ /** Upgraded connections, by the id the Worker knows them as. */
1157
+ private readonly upgraded;
1079
1158
  private nextRequestId;
1080
1159
  /**
1081
1160
  * Register a stand-in for a server that is actually running in the Worker.
@@ -1085,6 +1164,14 @@ declare class WorkerRuntimePod extends LocalRuntimePod {
1085
1164
  */
1086
1165
  private proxyPort;
1087
1166
  private forward;
1167
+ /**
1168
+ * Tunnel one upgraded connection to the server that actually holds the port.
1169
+ *
1170
+ * Unlike {@link forward} there is no reply to wait for: both ends write
1171
+ * whenever they have something, until one of them stops. The id is what ties
1172
+ * the two directions together across the Worker boundary.
1173
+ */
1174
+ private upgrade;
1088
1175
  private settleProxied;
1089
1176
  private closeProxies;
1090
1177
  teardown(): void;
@@ -1465,6 +1552,19 @@ interface PreviewOptions {
1465
1552
  * the moment, and it can stop after a few attempts rather than looping.
1466
1553
  */
1467
1554
  onStale?: () => void;
1555
+ /**
1556
+ * Let pages inside the preview open WebSockets to the container.
1557
+ *
1558
+ * On by default. A service worker cannot answer a WebSocket handshake, so
1559
+ * previewed documents are given a `WebSocket` that tunnels through this page
1560
+ * instead — which means the HTML they are served is modified on the way
1561
+ * through, one `<script>` at the top of `<head>`.
1562
+ *
1563
+ * Turn it off to serve documents byte for byte. The cost is that a dev
1564
+ * server loses its HMR channel, and with it the `full-reload` it sends after
1565
+ * re-optimizing dependencies; {@link onStale} is the fallback for that.
1566
+ */
1567
+ websocket?: boolean;
1468
1568
  }
1469
1569
  interface Preview {
1470
1570
  /** The URL an iframe should be pointed at to see `port`. */
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Shell, a as ShellIO, b as Session, V as Vfs, c as Cred, N as Node, d as Command, K as Kernel, R as RuntimeVolume, e as VolumeStat, f as VolumeStats, g as RuntimeHttpResponse, h as SpawnChild, i as SyncSpawn, j as RuntimePod, k as RuntimePackageInstaller, l as ChildSpawnConfig, m as ChildHandle, n as RuntimeProcess, E as ExecContext, O as OutputStream, C as Container, o as createContainer } from './container-CZn9USBQ.js';
2
- export { B as BufferSink, p as CPythonOptions, q as CallbackSink, r as CommandRegistry, s as ContainerFs, t as ContainerOptions, u as ContextInit, D as DirEntry, v as Env, w as ExecOptions, x as ExecResult, F as FileData, y as FileInput, z as FileOutput, G as GroupEntry, H as HttpResponse, I as InputStream, J as Job, A as KernelOptions, L as ListeningPort, M as MountEntry, P as NetInterface, Q as NetworkOptions, T as NetworkStack, U as NullInput, W as NullOutput, X as PYTHON_VERSION, Y as PasswdEntry, Z as Pipe, _ as Process, $ as ProcessKind, a0 as ProcessOptions, a1 as ProcessState, a2 as ProcessTable, a3 as PythonOptions, a4 as ROOT_CRED, a5 as ResolvedExecutable, a6 as RunOptions, a7 as RunResult, a8 as RuntimeProcessManager, a9 as RuntimeProcessResult, aa as SessionInit, ab as SessionResult, ac as SessionRunOptions, ad as ShellExit, ae as ShellInit, af as ShellOptions, ag as SpawnHandle, ah as Stats, ai as Stdio, aj as TeeOutput, ak as UserDatabase, al as Variables, am as VirtualNode, an as VirtualProvider, ao as WriteOptions, ap as applyChmod, aq as braceExpand, ar as captureStdio, as as configureCPython, at as configurePython, au as createChildProcessModule, av as createContext, aw as defineCommand, ax as expandWord, ay as expandWords, az as formatMode, aA as isCPythonAvailable, aB as isPythonAvailable, aC as makeCred, aD as octalMode, aE as parseUmask, aF as resetPidCounter, aG as shellQuote } from './container-CZn9USBQ.js';
1
+ import { S as Shell, a as ShellIO, b as Session, V as Vfs, c as Cred, N as Node, d as Command, K as Kernel, R as RuntimeVolume, e as VolumeStat, f as VolumeStats, g as RuntimeHttpResponse, h as SpawnChild, i as SyncSpawn, j as RuntimePod, k as RuntimePackageInstaller, l as ChildSpawnConfig, m as ChildHandle, n as RuntimeProcess, o as RuntimeSocketPeer, p as RuntimeConnection, E as ExecContext, O as OutputStream, C as Container, q as createContainer } from './container-DyRF-bY0.js';
2
+ export { B as BufferSink, r as CPythonOptions, s as CallbackSink, t as CommandRegistry, u as ContainerFs, v as ContainerOptions, w as ContextInit, D as DirEntry, x as Env, y as ExecOptions, z as ExecResult, F as FileData, A as FileInput, G as FileOutput, H as GroupEntry, I as HttpResponse, J as InputStream, L as Job, M as KernelOptions, P as ListeningPort, Q as MountEntry, T as NetInterface, U as NetworkOptions, W as NetworkStack, X as NullInput, Y as NullOutput, Z as PYTHON_VERSION, _ as PasswdEntry, $ as Pipe, a0 as Process, a1 as ProcessKind, a2 as ProcessOptions, a3 as ProcessState, a4 as ProcessTable, a5 as PythonOptions, a6 as ROOT_CRED, a7 as ResolvedExecutable, a8 as RunOptions, a9 as RunResult, aa as RuntimeProcessManager, ab as RuntimeProcessResult, ac as SessionInit, ad as SessionResult, ae as SessionRunOptions, af as ShellExit, ag as ShellInit, ah as ShellOptions, ai as SpawnHandle, aj as Stats, ak as Stdio, al as TeeOutput, am as UserDatabase, an as Variables, ao as VirtualNode, ap as VirtualProvider, aq as WriteOptions, ar as applyChmod, as as braceExpand, at as captureStdio, au as configureCPython, av as configurePython, aw as createChildProcessModule, ax as createContext, ay as defineCommand, az as expandWord, aA as expandWords, aB as formatMode, aC as isCPythonAvailable, aD as isPythonAvailable, aE as makeCred, aF as octalMode, aG as parseUmask, aH as resetPidCounter, aI as shellQuote } from './container-DyRF-bY0.js';
3
3
  import EventEmitter from 'events/events.js';
4
4
  import streamModule from 'stream-browserify';
5
5
 
@@ -648,8 +648,8 @@ declare class VirtualIncomingMessage extends streamModule.Readable {
648
648
  readonly httpVersionMajor = 1;
649
649
  readonly httpVersionMinor = 1;
650
650
  readonly complete = true;
651
- readonly socket: Record<string, unknown>;
652
- readonly connection: Record<string, unknown>;
651
+ socket: Record<string, unknown> | VirtualSocket;
652
+ get connection(): Record<string, unknown> | VirtualSocket;
653
653
  constructor(init: VirtualRequestInit);
654
654
  _read(): void;
655
655
  setTimeout(_milliseconds: number, callback?: () => void): this;
@@ -683,6 +683,60 @@ declare class VirtualServerResponse extends streamModule.Writable {
683
683
  addTrailers(_headers: Record<string, string>): void;
684
684
  setTimeout(_milliseconds: number, callback?: () => void): this;
685
685
  }
686
+ /** Where a {@link VirtualSocket} puts the bytes the server writes. */
687
+ interface VirtualSocketPeer {
688
+ /** The server sent these. */
689
+ data(bytes: Uint8Array): void;
690
+ /** The server hung up. */
691
+ close(): void;
692
+ }
693
+ /**
694
+ * The server half of a connection that has stopped being HTTP.
695
+ *
696
+ * Everything else in this file models one request and one response, because
697
+ * that is all a container's servers were ever asked for. A protocol upgrade is
698
+ * the case that does not fit: after the `101` there is no request and no
699
+ * response, only two peers writing bytes at each other for as long as they
700
+ * both stay interested.
701
+ *
702
+ * So this is a real `Duplex` rather than another stub. It has to be — the
703
+ * libraries that speak WebSocket do not merely read `req.headers` and reply,
704
+ * they take the socket and run a framing protocol over it, and a stub that
705
+ * accepts `write()` and drops it produces a server that completes its
706
+ * handshake and is then silent forever, which looks exactly like a network
707
+ * problem and is the hardest possible thing to attribute.
708
+ *
709
+ * Writes go out through {@link VirtualSocketPeer}; {@link deliver} pushes what
710
+ * comes back. Nothing here knows what the bytes mean, which is the point: `ws`,
711
+ * `socket.io` and Vite's HMR server all work over it unmodified.
712
+ */
713
+ declare class VirtualSocket extends streamModule.Duplex {
714
+ readonly remoteAddress = "127.0.0.1";
715
+ readonly remotePort = 0;
716
+ readonly localAddress = "127.0.0.1";
717
+ readonly localPort = 0;
718
+ readonly encrypted = false;
719
+ readonly bufferSize = 0;
720
+ private peer;
721
+ /** Set once the peer is told, so `end()` then `destroy()` reports once. */
722
+ private hungUp;
723
+ constructor(peer: VirtualSocketPeer);
724
+ _read(): void;
725
+ _write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
726
+ _final(callback: (error?: Error | null) => void): void;
727
+ _destroy(error: Error | null, callback: (error?: Error | null) => void): void;
728
+ private hangUp;
729
+ /** Bytes arriving from the far end. */
730
+ deliver(bytes: Uint8Array): void;
731
+ /** The far end went away; let the server's stream end cleanly. */
732
+ peerClosed(): void;
733
+ setNoDelay(): this;
734
+ setKeepAlive(): this;
735
+ setTimeout(_milliseconds: number, callback?: () => void): this;
736
+ destroySoon(): void;
737
+ ref(): this;
738
+ unref(): this;
739
+ }
686
740
  declare class VirtualHttpServer extends EventEmitter {
687
741
  private readonly router;
688
742
  readonly owner: string;
@@ -716,6 +770,28 @@ declare class VirtualHttpRouter {
716
770
  closeOwner(owner: string): void;
717
771
  closeAll(): void;
718
772
  request(port: number, init?: VirtualRequestInit): Promise<RuntimeHttpResponse>;
773
+ /**
774
+ * Open a connection that leaves HTTP behind — a WebSocket, in practice.
775
+ *
776
+ * The counterpart to {@link request}, and the thing whose absence made a dev
777
+ * server look broken. Every Node WebSocket library is built the same way: it
778
+ * hands `http.Server` an `upgrade` listener and waits. Nothing here ever
779
+ * emitted one, so `ws` sat holding a server that could not receive a single
780
+ * connection, and Vite lost the channel it uses to tell a page to reload —
781
+ * which is the only way it can recover after re-optimizing dependencies.
782
+ *
783
+ * Null means there is nothing to connect to: no server on the port, or a
784
+ * server that never asked for upgrades. Both are refusals the caller should
785
+ * report rather than wait out.
786
+ */
787
+ connect(port: number, init: VirtualRequestInit, peer: VirtualSocketPeer): VirtualConnection | null;
788
+ }
789
+ /** A connection handed back by {@link VirtualHttpRouter.connect}. */
790
+ interface VirtualConnection {
791
+ /** Bytes from the far end, into the container. */
792
+ send(bytes: Uint8Array): void;
793
+ /** The far end went away. */
794
+ close(): void;
719
795
  }
720
796
 
721
797
  interface CoreModulesOptions {
@@ -1029,6 +1105,7 @@ declare class LocalRuntimePod implements RuntimePod {
1029
1105
  */
1030
1106
  private settle;
1031
1107
  request(_port: number, _init?: Record<string, unknown>): Promise<RuntimeHttpResponse>;
1108
+ connect(port: number, init: Record<string, unknown>, peer: RuntimeSocketPeer): RuntimeConnection | null;
1032
1109
  snapshot(): MemoryVolumeSnapshotEntry[];
1033
1110
  restore(snapshot: unknown): Promise<void>;
1034
1111
  teardown(): void;
@@ -1076,6 +1153,8 @@ declare class WorkerRuntimePod extends LocalRuntimePod {
1076
1153
  private runChildToCompletion;
1077
1154
  private readonly proxies;
1078
1155
  private readonly waiting;
1156
+ /** Upgraded connections, by the id the Worker knows them as. */
1157
+ private readonly upgraded;
1079
1158
  private nextRequestId;
1080
1159
  /**
1081
1160
  * Register a stand-in for a server that is actually running in the Worker.
@@ -1085,6 +1164,14 @@ declare class WorkerRuntimePod extends LocalRuntimePod {
1085
1164
  */
1086
1165
  private proxyPort;
1087
1166
  private forward;
1167
+ /**
1168
+ * Tunnel one upgraded connection to the server that actually holds the port.
1169
+ *
1170
+ * Unlike {@link forward} there is no reply to wait for: both ends write
1171
+ * whenever they have something, until one of them stops. The id is what ties
1172
+ * the two directions together across the Worker boundary.
1173
+ */
1174
+ private upgrade;
1088
1175
  private settleProxied;
1089
1176
  private closeProxies;
1090
1177
  teardown(): void;
@@ -1465,6 +1552,19 @@ interface PreviewOptions {
1465
1552
  * the moment, and it can stop after a few attempts rather than looping.
1466
1553
  */
1467
1554
  onStale?: () => void;
1555
+ /**
1556
+ * Let pages inside the preview open WebSockets to the container.
1557
+ *
1558
+ * On by default. A service worker cannot answer a WebSocket handshake, so
1559
+ * previewed documents are given a `WebSocket` that tunnels through this page
1560
+ * instead — which means the HTML they are served is modified on the way
1561
+ * through, one `<script>` at the top of `<head>`.
1562
+ *
1563
+ * Turn it off to serve documents byte for byte. The cost is that a dev
1564
+ * server loses its HMR channel, and with it the `full-reload` it sends after
1565
+ * re-optimizing dependencies; {@link onStale} is the fallback for that.
1566
+ */
1567
+ websocket?: boolean;
1468
1568
  }
1469
1569
  interface Preview {
1470
1570
  /** The URL an iframe should be pointed at to see `port`. */
package/dist/index.js CHANGED
@@ -23472,8 +23472,17 @@ var VirtualIncomingMessage = class extends streamModule4.Readable {
23472
23472
  httpVersionMajor = 1;
23473
23473
  httpVersionMinor = 1;
23474
23474
  complete = true;
23475
+ /*
23476
+ * Mutable, and `connection` a getter over it, because an upgraded request
23477
+ * carries a real socket rather than the stub. `emit("upgrade", req, socket)`
23478
+ * hands the same object to both places, and a library that reaches it as
23479
+ * `req.socket` — rather than through the argument — has to find the one it
23480
+ * can actually write to.
23481
+ */
23475
23482
  socket = socketStub();
23476
- connection = this.socket;
23483
+ get connection() {
23484
+ return this.socket;
23485
+ }
23477
23486
  constructor(init) {
23478
23487
  super();
23479
23488
  this.method = (init.method ?? "GET").toUpperCase();
@@ -23579,6 +23588,80 @@ var VirtualServerResponse = class extends streamModule4.Writable {
23579
23588
  return this;
23580
23589
  }
23581
23590
  };
23591
+ var VirtualSocket = class extends streamModule4.Duplex {
23592
+ remoteAddress = "127.0.0.1";
23593
+ remotePort = 0;
23594
+ localAddress = "127.0.0.1";
23595
+ localPort = 0;
23596
+ encrypted = false;
23597
+ bufferSize = 0;
23598
+ peer;
23599
+ /** Set once the peer is told, so `end()` then `destroy()` reports once. */
23600
+ hungUp = false;
23601
+ constructor(peer) {
23602
+ super({ allowHalfOpen: false });
23603
+ this.peer = peer;
23604
+ }
23605
+ _read() {
23606
+ }
23607
+ _write(chunk, encoding, callback) {
23608
+ const bytes2 = Buffer2.isBuffer(chunk) ? chunk : Buffer2.from(chunk, encoding);
23609
+ try {
23610
+ this.peer?.data(new Uint8Array(bytes2));
23611
+ callback();
23612
+ } catch (error) {
23613
+ callback(error);
23614
+ }
23615
+ }
23616
+ _final(callback) {
23617
+ this.hangUp();
23618
+ callback();
23619
+ }
23620
+ _destroy(error, callback) {
23621
+ this.hangUp();
23622
+ callback(error);
23623
+ }
23624
+ hangUp() {
23625
+ if (this.hungUp) return;
23626
+ this.hungUp = true;
23627
+ try {
23628
+ this.peer?.close();
23629
+ } catch {
23630
+ }
23631
+ this.peer = null;
23632
+ }
23633
+ /** Bytes arriving from the far end. */
23634
+ deliver(bytes2) {
23635
+ if (this.destroyed || this.hungUp) return;
23636
+ this.push(Buffer2.from(bytes2));
23637
+ }
23638
+ /** The far end went away; let the server's stream end cleanly. */
23639
+ peerClosed() {
23640
+ if (this.destroyed) return;
23641
+ this.push(null);
23642
+ }
23643
+ /* The parts of `net.Socket` a WebSocket library reaches for. None of them
23644
+ * mean anything without a kernel, but every one of them is called. */
23645
+ setNoDelay() {
23646
+ return this;
23647
+ }
23648
+ setKeepAlive() {
23649
+ return this;
23650
+ }
23651
+ setTimeout(_milliseconds, callback) {
23652
+ if (callback) this.once("timeout", callback);
23653
+ return this;
23654
+ }
23655
+ destroySoon() {
23656
+ this.end();
23657
+ }
23658
+ ref() {
23659
+ return this;
23660
+ }
23661
+ unref() {
23662
+ return this;
23663
+ }
23664
+ };
23582
23665
  var VirtualHttpServer = class extends EventEmitter4 {
23583
23666
  constructor(router, owner, listener) {
23584
23667
  super();
@@ -23678,6 +23761,33 @@ var VirtualHttpRouter = class {
23678
23761
  return { statusCode: 500, statusMessage: "Internal Server Error", headers: {}, body: error instanceof Error ? error.message : String(error) };
23679
23762
  }
23680
23763
  }
23764
+ /**
23765
+ * Open a connection that leaves HTTP behind — a WebSocket, in practice.
23766
+ *
23767
+ * The counterpart to {@link request}, and the thing whose absence made a dev
23768
+ * server look broken. Every Node WebSocket library is built the same way: it
23769
+ * hands `http.Server` an `upgrade` listener and waits. Nothing here ever
23770
+ * emitted one, so `ws` sat holding a server that could not receive a single
23771
+ * connection, and Vite lost the channel it uses to tell a page to reload —
23772
+ * which is the only way it can recover after re-optimizing dependencies.
23773
+ *
23774
+ * Null means there is nothing to connect to: no server on the port, or a
23775
+ * server that never asked for upgrades. Both are refusals the caller should
23776
+ * report rather than wait out.
23777
+ */
23778
+ connect(port, init, peer) {
23779
+ const item = this.servers.get(port);
23780
+ if (!item) return null;
23781
+ if (item.server.listenerCount("upgrade") === 0) return null;
23782
+ const request = new VirtualIncomingMessage(init);
23783
+ const socket = new VirtualSocket(peer);
23784
+ request.socket = socket;
23785
+ item.server.emit("upgrade", request, socket, Buffer2.alloc(0));
23786
+ return {
23787
+ send: (bytes2) => socket.deliver(bytes2),
23788
+ close: () => socket.peerClosed()
23789
+ };
23790
+ }
23681
23791
  };
23682
23792
  var VirtualClientResponse = class extends streamModule4.Readable {
23683
23793
  constructor(statusCode, statusMessage, headers, body) {
@@ -27194,6 +27304,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
27194
27304
  async request(_port, _init = {}) {
27195
27305
  return this.router.request(_port, _init);
27196
27306
  }
27307
+ connect(port, init, peer) {
27308
+ return this.router.connect(port, init, peer);
27309
+ }
27197
27310
  snapshot() {
27198
27311
  this.assertActive();
27199
27312
  return this.volume.snapshot();
@@ -27629,6 +27742,15 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
27629
27742
  case "http-response":
27630
27743
  this.settleProxied(Number(message.id), message.response);
27631
27744
  return;
27745
+ case "ws-data":
27746
+ this.upgraded.get(Number(message.id))?.write(new Uint8Array(message.data));
27747
+ return;
27748
+ case "ws-close": {
27749
+ const socket = this.upgraded.get(Number(message.id));
27750
+ this.upgraded.delete(Number(message.id));
27751
+ socket?.end();
27752
+ return;
27753
+ }
27632
27754
  case "child-start":
27633
27755
  this.startChild(worker, children, message, ownedChildren);
27634
27756
  return;
@@ -27717,6 +27839,8 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
27717
27839
  // ── HTTP servers living on another thread ─────────────────────────────────
27718
27840
  proxies = /* @__PURE__ */ new Map();
27719
27841
  waiting = /* @__PURE__ */ new Map();
27842
+ /** Upgraded connections, by the id the Worker knows them as. */
27843
+ upgraded = /* @__PURE__ */ new Map();
27720
27844
  nextRequestId = 1;
27721
27845
  /**
27722
27846
  * Register a stand-in for a server that is actually running in the Worker.
@@ -27729,6 +27853,9 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
27729
27853
  const server = new VirtualHttpServer(this.router, owner, (request, response) => {
27730
27854
  void this.forward(worker, port, request, response);
27731
27855
  });
27856
+ server.on("upgrade", (request, socket) => {
27857
+ this.upgrade(worker, port, request, socket);
27858
+ });
27732
27859
  try {
27733
27860
  server.listen(port);
27734
27861
  this.proxies.set(port, server);
@@ -27759,6 +27886,33 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
27759
27886
  response.writeHead(Number(result.statusCode ?? 200), String(result.statusMessage ?? ""), result.headers ?? {});
27760
27887
  response.end(result.body ?? new Uint8Array());
27761
27888
  }
27889
+ /**
27890
+ * Tunnel one upgraded connection to the server that actually holds the port.
27891
+ *
27892
+ * Unlike {@link forward} there is no reply to wait for: both ends write
27893
+ * whenever they have something, until one of them stops. The id is what ties
27894
+ * the two directions together across the Worker boundary.
27895
+ */
27896
+ upgrade(worker, port, request, socket) {
27897
+ const id = this.nextRequestId++;
27898
+ this.upgraded.set(id, socket);
27899
+ socket.on("data", (chunk) => {
27900
+ const bytes2 = new Uint8Array(chunk);
27901
+ worker.postMessage({ type: "ws-data", id, data: bytes2 });
27902
+ });
27903
+ const drop = () => {
27904
+ if (!this.upgraded.delete(id)) return;
27905
+ worker.postMessage({ type: "ws-close", id });
27906
+ };
27907
+ socket.on("close", drop);
27908
+ socket.on("error", drop);
27909
+ worker.postMessage({
27910
+ type: "ws-open",
27911
+ id,
27912
+ port,
27913
+ init: { method: request.method, path: request.url, headers: request.headers }
27914
+ });
27915
+ }
27762
27916
  settleProxied(id, response) {
27763
27917
  const resolve2 = this.waiting.get(id);
27764
27918
  if (!resolve2) return;
@@ -27771,6 +27925,10 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
27771
27925
  server.close();
27772
27926
  this.proxies.delete(port);
27773
27927
  }
27928
+ for (const [id, socket] of [...this.upgraded]) {
27929
+ this.upgraded.delete(id);
27930
+ socket.destroy();
27931
+ }
27774
27932
  }
27775
27933
  teardown() {
27776
27934
  for (const { worker, server } of [...this.live]) {
@@ -28144,6 +28302,29 @@ var Container = class _Container {
28144
28302
  }
28145
28303
  };
28146
28304
  }
28305
+ /**
28306
+ * Open a connection to a server inside the container that upgrades out of
28307
+ * HTTP — in practice, a WebSocket.
28308
+ *
28309
+ * The counterpart to {@link request}, and the thing a dev server needs that
28310
+ * a request cannot provide. Bytes go in with `send`, come back through
28311
+ * `peer.data`, and neither side interprets them: the container runs whatever
28312
+ * WebSocket library the program chose, and the caller is responsible for
28313
+ * speaking the protocol it answers with.
28314
+ *
28315
+ * Null means there is nothing to talk to — no server on the port, or one
28316
+ * that never registered an `upgrade` handler. Both are worth reporting
28317
+ * rather than waiting out, because neither resolves on its own.
28318
+ */
28319
+ connect(port, init = {}, peer) {
28320
+ this.assertActive();
28321
+ if (!this.pod.connect) return null;
28322
+ return this.pod.connect(
28323
+ port,
28324
+ { method: init.method ?? "GET", path: init.path ?? "/", headers: init.headers ?? {} },
28325
+ peer
28326
+ );
28327
+ }
28147
28328
  /**
28148
28329
  * Deliver a request whose body is bytes, without letting them become text.
28149
28330
  *
@@ -28715,6 +28896,12 @@ init_arith();
28715
28896
  init_expand();
28716
28897
  init_builtins();
28717
28898
 
28899
+ // src/preview/ws-client.ts
28900
+ var WS_OPEN = "sandboxedjs:ws-open";
28901
+ var WS_DATA = "sandboxedjs:ws-data";
28902
+ var WS_CLOSE = "sandboxedjs:ws-close";
28903
+ var WS_ERROR = "sandboxedjs:ws-error";
28904
+
28718
28905
  // src/preview/register.ts
28719
28906
  function serveContainerOn(port, box) {
28720
28907
  port.onmessage = async (event) => {
@@ -28800,7 +28987,7 @@ async function createPreview(box, options = {}) {
28800
28987
  channel = new MessageChannel();
28801
28988
  serveContainerOn(channel.port1, box);
28802
28989
  const target = registration.active ?? worker;
28803
- target.postMessage({ type: "sandboxedjs:connect" }, [channel.port2]);
28990
+ target.postMessage({ type: "sandboxedjs:connect", injectSockets: options.websocket !== false }, [channel.port2]);
28804
28991
  };
28805
28992
  const onWorkerMessage = (event) => {
28806
28993
  const type = event.data?.type;
@@ -28809,6 +28996,56 @@ async function createPreview(box, options = {}) {
28809
28996
  };
28810
28997
  navigator.serviceWorker.addEventListener("message", onWorkerMessage);
28811
28998
  connect();
28999
+ const sockets = /* @__PURE__ */ new Map();
29000
+ const onFrameMessage = (event) => {
29001
+ if (event.origin !== location.origin || !event.source) return;
29002
+ const data = event.data;
29003
+ if (!data || typeof data !== "object") return;
29004
+ if (data.type !== WS_OPEN && data.type !== WS_DATA && data.type !== WS_CLOSE) return;
29005
+ const source = event.source;
29006
+ const id = Number(data.id);
29007
+ const reply = (message, transfer = []) => source.postMessage(message, location.origin, transfer);
29008
+ if (data.type === WS_OPEN) {
29009
+ let table2 = sockets.get(source);
29010
+ if (!table2) sockets.set(source, table2 = /* @__PURE__ */ new Map());
29011
+ let opened = null;
29012
+ try {
29013
+ opened = box.connect(
29014
+ Number(data.port),
29015
+ { method: "GET", path: data.path ?? "/", headers: data.headers ?? {} },
29016
+ {
29017
+ data: (bytes2) => {
29018
+ const carrier = bytes2.slice().buffer;
29019
+ reply({ type: WS_DATA, id, data: carrier }, [carrier]);
29020
+ },
29021
+ close: () => {
29022
+ table2.delete(id);
29023
+ reply({ type: WS_CLOSE, id });
29024
+ }
29025
+ }
29026
+ );
29027
+ } catch (error) {
29028
+ opened = null;
29029
+ reply({ type: WS_ERROR, id, message: error instanceof Error ? error.message : String(error) });
29030
+ return;
29031
+ }
29032
+ if (!opened) {
29033
+ reply({ type: WS_ERROR, id, message: `Nothing is accepting WebSocket connections on port ${data.port}.` });
29034
+ return;
29035
+ }
29036
+ table2.set(id, opened);
29037
+ return;
29038
+ }
29039
+ const table = sockets.get(source);
29040
+ const socket = table?.get(id);
29041
+ if (!socket) return;
29042
+ if (data.type === WS_DATA) socket.send(new Uint8Array(data.data));
29043
+ else {
29044
+ socket.close();
29045
+ table.delete(id);
29046
+ }
29047
+ };
29048
+ if (typeof window !== "undefined") window.addEventListener("message", onFrameMessage);
28812
29049
  const base2 = registration.scope.replace(/\/$/, "");
28813
29050
  const urlFor = (port) => `${base2}/__sbx__/${port}/`;
28814
29051
  return {
@@ -28822,6 +29059,9 @@ async function createPreview(box, options = {}) {
28822
29059
  },
28823
29060
  dispose: async () => {
28824
29061
  navigator.serviceWorker.removeEventListener("message", onWorkerMessage);
29062
+ if (typeof window !== "undefined") window.removeEventListener("message", onFrameMessage);
29063
+ for (const table of sockets.values()) for (const socket of table.values()) socket.close();
29064
+ sockets.clear();
28825
29065
  channel?.port1.close();
28826
29066
  channel = null;
28827
29067
  await registration.unregister();