sandboxedjs 0.1.53 → 0.1.55

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
@@ -17804,7 +17804,20 @@ var Op = {
17804
17804
  getcwd: 1281,
17805
17805
  chdir: 1282,
17806
17806
  environ: 1283,
17807
- getrandom: 1536
17807
+ getrandom: 1536,
17808
+ socket: 1792,
17809
+ bind: 1793,
17810
+ listen: 1794,
17811
+ accept: 1795,
17812
+ connect: 1796,
17813
+ send: 1797,
17814
+ recv: 1798,
17815
+ shutdown: 1799,
17816
+ getsockname: 1800,
17817
+ getpeername: 1801,
17818
+ setsockopt: 1802,
17819
+ getsockopt: 1803,
17820
+ socket_close: 1804
17808
17821
  };
17809
17822
  var Errno = {
17810
17823
  EPERM: 1,
@@ -17841,6 +17854,15 @@ var Errno = {
17841
17854
  ENOTEMPTY: 39,
17842
17855
  ELOOP: 40,
17843
17856
  ENODATA: 61,
17857
+ ENOTSOCK: 88,
17858
+ EAFNOSUPPORT: 97,
17859
+ EADDRINUSE: 98,
17860
+ EADDRNOTAVAIL: 99,
17861
+ ENETUNREACH: 101,
17862
+ ECONNRESET: 104,
17863
+ EISCONN: 106,
17864
+ ENOTCONN: 107,
17865
+ ECONNREFUSED: 111,
17844
17866
  EPROTO: 71,
17845
17867
  EOVERFLOW: 75,
17846
17868
  ETIMEDOUT: 110,
@@ -18706,6 +18728,362 @@ function failure(errno) {
18706
18728
  return -Math.abs(errno);
18707
18729
  }
18708
18730
 
18731
+ // src/net/virtual-socket.ts
18732
+ var ByteQueue = class {
18733
+ chunks = [];
18734
+ length = 0;
18735
+ waiters = /* @__PURE__ */ new Set();
18736
+ push(bytes2) {
18737
+ if (bytes2.length === 0) return;
18738
+ const copy = bytes2.slice();
18739
+ this.chunks.push(copy);
18740
+ this.length += copy.length;
18741
+ this.wake();
18742
+ }
18743
+ take(max) {
18744
+ if (max <= 0 || this.length === 0) return new Uint8Array();
18745
+ const out = new Uint8Array(Math.min(max, this.length));
18746
+ let at = 0;
18747
+ while (at < out.length) {
18748
+ const chunk = this.chunks[0];
18749
+ const count = Math.min(chunk.length, out.length - at);
18750
+ out.set(chunk.subarray(0, count), at);
18751
+ at += count;
18752
+ this.length -= count;
18753
+ if (count === chunk.length) this.chunks.shift();
18754
+ else this.chunks[0] = chunk.subarray(count);
18755
+ }
18756
+ return out;
18757
+ }
18758
+ get size() {
18759
+ return this.length;
18760
+ }
18761
+ whenChanged() {
18762
+ return new Promise((resolve3) => this.waiters.add(resolve3));
18763
+ }
18764
+ wake() {
18765
+ for (const resolve3 of this.waiters) resolve3();
18766
+ this.waiters.clear();
18767
+ }
18768
+ };
18769
+ var VirtualTcpConnection = class {
18770
+ incoming = new ByteQueue();
18771
+ peer = null;
18772
+ readClosed = false;
18773
+ writeClosed = false;
18774
+ closed = false;
18775
+ local;
18776
+ remote;
18777
+ constructor(local, remote) {
18778
+ this.local = local;
18779
+ this.remote = remote;
18780
+ }
18781
+ pairWith(peer) {
18782
+ this.peer = peer;
18783
+ }
18784
+ get eof() {
18785
+ return this.readClosed || this.incoming.size === 0 && this.peer?.writeClosed === true;
18786
+ }
18787
+ get readable() {
18788
+ return this.incoming.size > 0 || this.eof;
18789
+ }
18790
+ get writable() {
18791
+ return !this.writeClosed && !this.closed && this.peer !== null && !this.peer.readClosed;
18792
+ }
18793
+ read(maxLength) {
18794
+ if (this.incoming.size > 0) return this.incoming.take(maxLength);
18795
+ if (this.eof) return new Uint8Array();
18796
+ throw new PosixError(Errno.EAGAIN, "socket read would block");
18797
+ }
18798
+ write(bytes2) {
18799
+ if (!this.writable) throw new PosixError(this.closed || this.writeClosed ? Errno.EPIPE : Errno.ECONNRESET);
18800
+ this.peer.incoming.push(bytes2);
18801
+ return bytes2.length;
18802
+ }
18803
+ async waitForChange() {
18804
+ if (this.readable || this.closed) return;
18805
+ await this.incoming.whenChanged();
18806
+ }
18807
+ shutdown(read, write) {
18808
+ if (read) this.readClosed = true;
18809
+ if (write) {
18810
+ this.writeClosed = true;
18811
+ if (this.peer) this.peer.incoming.wake();
18812
+ }
18813
+ this.incoming.wake();
18814
+ this.peer?.incoming.wake();
18815
+ }
18816
+ close() {
18817
+ if (this.closed) return;
18818
+ this.closed = true;
18819
+ this.readClosed = true;
18820
+ this.writeClosed = true;
18821
+ if (this.peer) {
18822
+ this.peer.readClosed = true;
18823
+ this.peer.incoming.wake();
18824
+ }
18825
+ this.incoming.wake();
18826
+ }
18827
+ };
18828
+ var VirtualTcpListener = class {
18829
+ constructor(port, address = "0.0.0.0", backlog = 128) {
18830
+ this.port = port;
18831
+ this.address = address;
18832
+ this.backlog = backlog;
18833
+ }
18834
+ port;
18835
+ address;
18836
+ backlog;
18837
+ pending = [];
18838
+ waiters = /* @__PURE__ */ new Set();
18839
+ closed = false;
18840
+ enqueue(connection) {
18841
+ if (this.closed) {
18842
+ connection.close();
18843
+ throw new PosixError(Errno.ECONNREFUSED, "listener is closed");
18844
+ }
18845
+ if (this.pending.length >= this.backlog) {
18846
+ connection.close();
18847
+ throw new PosixError(Errno.EAGAIN, "listener backlog is full");
18848
+ }
18849
+ this.pending.push(connection);
18850
+ this.wake();
18851
+ }
18852
+ accept() {
18853
+ const connection = this.pending.shift();
18854
+ if (!connection) {
18855
+ if (this.closed) throw new PosixError(Errno.EBADF, "listener is closed");
18856
+ throw new PosixError(Errno.EAGAIN, "listener accept would block");
18857
+ }
18858
+ return { connection, peer: connection.remote };
18859
+ }
18860
+ get readable() {
18861
+ return this.pending.length > 0 || this.closed;
18862
+ }
18863
+ get isClosed() {
18864
+ return this.closed;
18865
+ }
18866
+ async waitForChange() {
18867
+ if (this.readable) return;
18868
+ await new Promise((resolve3) => this.waiters.add(resolve3));
18869
+ }
18870
+ close() {
18871
+ if (this.closed) return;
18872
+ this.closed = true;
18873
+ for (const connection of this.pending.splice(0)) connection.close();
18874
+ this.wake();
18875
+ }
18876
+ wake() {
18877
+ for (const resolve3 of this.waiters) resolve3();
18878
+ this.waiters.clear();
18879
+ }
18880
+ };
18881
+ var VirtualTcpSocket = class _VirtualTcpSocket {
18882
+ constructor(network) {
18883
+ this.network = network;
18884
+ }
18885
+ network;
18886
+ listenerValue = null;
18887
+ connectionValue = null;
18888
+ boundPort = 0;
18889
+ boundAddress = "0.0.0.0";
18890
+ bind(address, port) {
18891
+ if (this.listenerValue || this.connectionValue || this.boundPort !== 0) {
18892
+ throw new PosixError(Errno.EINVAL, "socket is already bound");
18893
+ }
18894
+ this.boundAddress = address;
18895
+ this.boundPort = port;
18896
+ }
18897
+ listen(backlog) {
18898
+ if (this.listenerValue) return;
18899
+ if (this.boundPort === 0) {
18900
+ const port = this.findEphemeralPort();
18901
+ this.boundPort = port;
18902
+ }
18903
+ this.listenerValue = this.network.listen(this.boundPort, this.boundAddress, backlog);
18904
+ }
18905
+ connect(port, address = "127.0.0.1") {
18906
+ if (this.listenerValue || this.connectionValue) throw new PosixError(Errno.EISCONN);
18907
+ this.connectionValue = this.network.connect(port, this.boundPort, this.boundAddress);
18908
+ this.boundPort = this.connectionValue.local.port;
18909
+ this.boundAddress = this.connectionValue.local.address;
18910
+ }
18911
+ accept() {
18912
+ if (!this.listenerValue) throw new PosixError(Errno.EINVAL, "socket is not listening");
18913
+ const accepted = this.listenerValue.accept().connection;
18914
+ const socket = new _VirtualTcpSocket(this.network);
18915
+ socket.connectionValue = accepted;
18916
+ socket.boundPort = accepted.local.port;
18917
+ socket.boundAddress = accepted.local.address;
18918
+ return socket;
18919
+ }
18920
+ get listener() {
18921
+ return this.listenerValue;
18922
+ }
18923
+ get connection() {
18924
+ return this.connectionValue;
18925
+ }
18926
+ get local() {
18927
+ return { address: this.boundAddress, port: this.boundPort };
18928
+ }
18929
+ get remote() {
18930
+ return this.connectionValue?.remote ?? null;
18931
+ }
18932
+ get readable() {
18933
+ return this.listenerValue?.readable ?? this.connectionValue?.readable ?? false;
18934
+ }
18935
+ get writable() {
18936
+ return this.connectionValue?.writable ?? false;
18937
+ }
18938
+ waitForChange() {
18939
+ return (this.listenerValue ?? this.connectionValue)?.waitForChange() ?? Promise.resolve();
18940
+ }
18941
+ read(length) {
18942
+ if (!this.connectionValue) throw new PosixError(Errno.EINVAL);
18943
+ return this.connectionValue.read(length);
18944
+ }
18945
+ write(data) {
18946
+ if (!this.connectionValue) throw new PosixError(Errno.EINVAL);
18947
+ return this.connectionValue.write(data);
18948
+ }
18949
+ shutdown(read, write) {
18950
+ if (!this.connectionValue) throw new PosixError(Errno.ENOTCONN);
18951
+ this.connectionValue.shutdown(read, write);
18952
+ }
18953
+ close() {
18954
+ if (this.listenerValue) this.network.close(this.listenerValue);
18955
+ this.connectionValue?.close();
18956
+ this.listenerValue = null;
18957
+ this.connectionValue = null;
18958
+ }
18959
+ findEphemeralPort() {
18960
+ for (let port = 49152; port <= 65535; port += 1) if (!this.network.hasListener(port)) return port;
18961
+ throw new PosixError(Errno.EADDRINUSE, "no ephemeral ports available");
18962
+ }
18963
+ };
18964
+ var SocketDescription = class _SocketDescription {
18965
+ kind = "socket";
18966
+ flags;
18967
+ resource;
18968
+ closed = false;
18969
+ options = /* @__PURE__ */ new Map();
18970
+ constructor(resource, flags = 0) {
18971
+ this.resource = resource;
18972
+ this.flags = flags;
18973
+ }
18974
+ get listener() {
18975
+ return this.resource.listener;
18976
+ }
18977
+ get connection() {
18978
+ return this.resource.connection;
18979
+ }
18980
+ read(length) {
18981
+ return this.resource.read(length);
18982
+ }
18983
+ write(data) {
18984
+ return this.resource.write(data);
18985
+ }
18986
+ readable() {
18987
+ return this.resource.readable;
18988
+ }
18989
+ writable() {
18990
+ return this.resource.writable;
18991
+ }
18992
+ whenReady() {
18993
+ return this.resource.waitForChange();
18994
+ }
18995
+ stat() {
18996
+ return {
18997
+ ino: 0,
18998
+ mode: 49663,
18999
+ size: 0,
19000
+ uid: 0,
19001
+ gid: 0,
19002
+ nlink: 1,
19003
+ atimeMs: Date.now(),
19004
+ mtimeMs: Date.now(),
19005
+ ctimeMs: Date.now()
19006
+ };
19007
+ }
19008
+ close() {
19009
+ if (this.closed) return;
19010
+ this.closed = true;
19011
+ this.resource.close();
19012
+ }
19013
+ bind(address, port) {
19014
+ this.resource.bind(address, port);
19015
+ }
19016
+ listen(backlog) {
19017
+ this.resource.listen(backlog);
19018
+ }
19019
+ connect(address, port) {
19020
+ this.resource.connect(port, address);
19021
+ }
19022
+ accept() {
19023
+ return new _SocketDescription(this.resource.accept(), this.flags);
19024
+ }
19025
+ shutdown(read, write) {
19026
+ this.resource.shutdown(read, write);
19027
+ }
19028
+ setOption(level, option, value) {
19029
+ this.options.set(`${level}:${option}`, value.slice());
19030
+ }
19031
+ getOption(level, option) {
19032
+ return this.options.get(`${level}:${option}`)?.slice() ?? new Uint8Array(4);
19033
+ }
19034
+ localAddress() {
19035
+ return this.resource.local;
19036
+ }
19037
+ peerAddress() {
19038
+ const peer = this.resource.remote;
19039
+ if (!peer) throw new PosixError(Errno.ENOTCONN);
19040
+ return peer;
19041
+ }
19042
+ };
19043
+ var VirtualTcpNetwork = class {
19044
+ constructor(occupied) {
19045
+ this.occupied = occupied;
19046
+ }
19047
+ occupied;
19048
+ listeners = /* @__PURE__ */ new Map();
19049
+ listen(port, address = "0.0.0.0", backlog = 128) {
19050
+ if (this.listeners.has(port) || this.occupied?.(port)) throw new PosixError(Errno.EADDRINUSE, `port ${port} is already in use`);
19051
+ const listener = new VirtualTcpListener(port, address, backlog);
19052
+ this.listeners.set(port, listener);
19053
+ return listener;
19054
+ }
19055
+ close(listener) {
19056
+ if (this.listeners.get(listener.port) !== listener) return;
19057
+ this.listeners.delete(listener.port);
19058
+ listener.close();
19059
+ }
19060
+ connect(port, localPort = 0, localAddress = "127.0.0.1") {
19061
+ const listener = this.listeners.get(port);
19062
+ if (!listener || listener.isClosed) throw new PosixError(Errno.ECONNREFUSED, `port ${port} is not listening`);
19063
+ const client = new VirtualTcpConnection(
19064
+ { address: localAddress, port: localPort },
19065
+ { address: listener.address, port }
19066
+ );
19067
+ const server = new VirtualTcpConnection(
19068
+ { address: listener.address, port },
19069
+ { address: localAddress, port: localPort }
19070
+ );
19071
+ client.pairWith(server);
19072
+ server.pairWith(client);
19073
+ listener.enqueue(server);
19074
+ return client;
19075
+ }
19076
+ hasListener(port) {
19077
+ return this.listeners.has(port);
19078
+ }
19079
+ ports() {
19080
+ return [...this.listeners.keys()].sort((a, b) => a - b);
19081
+ }
19082
+ closeAll() {
19083
+ for (const listener of [...this.listeners.values()]) this.close(listener);
19084
+ }
19085
+ };
19086
+
18709
19087
  // src/runtime/python/syscall-server.ts
18710
19088
  var MAX_TRANSFER = 8 * 1024 * 1024;
18711
19089
  function createHostAbiServer(proc) {
@@ -18952,12 +19330,76 @@ function createHostAbiServer(proc) {
18952
19330
  crypto.getRandomValues(bytes2);
18953
19331
  return { status: length, payload: bytes2 };
18954
19332
  }
19333
+ /* ----------------------------------------------------------- sockets */
19334
+ case Op.socket: {
19335
+ const family = r.u32();
19336
+ const type = r.u32();
19337
+ const protocol = r.i32();
19338
+ if (family !== 2 || (type & 15) !== 1 || protocol !== 0) throw new PosixError(Errno.EAFNOSUPPORT);
19339
+ if (!proc.sockets) throw new PosixError(Errno.ENOSYS);
19340
+ return { status: proc.table.add(new SocketDescription(new VirtualTcpSocket(proc.sockets))) };
19341
+ }
19342
+ case Op.bind: {
19343
+ const description = socketDescription(proc.table.get(r.i32()));
19344
+ description.bind(r.string(), r.u32());
19345
+ return { status: 0 };
19346
+ }
19347
+ case Op.listen: {
19348
+ const description = socketDescription(proc.table.get(r.i32()));
19349
+ description.listen(Math.max(1, Math.min(1024, r.u32())));
19350
+ return { status: 0 };
19351
+ }
19352
+ case Op.accept: {
19353
+ const description = socketDescription(proc.table.get(r.i32()));
19354
+ const accepted = await acceptBlocking(description);
19355
+ const fd = proc.table.add(accepted);
19356
+ const peer = accepted.peerAddress();
19357
+ return { status: fd, payload: new Writer().string(peer.address).u32(peer.port).finish() };
19358
+ }
19359
+ case Op.connect: {
19360
+ const description = socketDescription(proc.table.get(r.i32()));
19361
+ description.connect(r.string(), r.u32());
19362
+ return { status: 0 };
19363
+ }
19364
+ case Op.send: {
19365
+ const description = socketDescription(proc.table.get(r.i32()));
19366
+ return { status: await writeBlocking(description, r.bytes32()) };
19367
+ }
19368
+ case Op.recv: {
19369
+ const description = socketDescription(proc.table.get(r.i32()));
19370
+ const data = await readBlocking(description, clamp(r.u32()));
19371
+ return { status: data.length, payload: data };
19372
+ }
19373
+ case Op.shutdown: {
19374
+ const description = socketDescription(proc.table.get(r.i32()));
19375
+ const how = r.u32();
19376
+ if (how > 2) throw new PosixError(Errno.EINVAL);
19377
+ description.shutdown(how === 0 || how === 2, how === 1 || how === 2);
19378
+ return { status: 0 };
19379
+ }
19380
+ case Op.getsockname: {
19381
+ const address = socketDescription(proc.table.get(r.i32())).localAddress();
19382
+ return { status: 0, payload: new Writer().string(address.address).u32(address.port).finish() };
19383
+ }
19384
+ case Op.getpeername: {
19385
+ const address = socketDescription(proc.table.get(r.i32())).peerAddress();
19386
+ return { status: 0, payload: new Writer().string(address.address).u32(address.port).finish() };
19387
+ }
19388
+ case Op.setsockopt: {
19389
+ const description = socketDescription(proc.table.get(r.i32()));
19390
+ description.setOption(r.u32(), r.u32(), r.bytes32());
19391
+ return { status: 0 };
19392
+ }
19393
+ case Op.getsockopt: {
19394
+ const description = socketDescription(proc.table.get(r.i32()));
19395
+ return { status: 0, payload: new Writer().bytes32(description.getOption(r.u32(), r.u32())).finish() };
19396
+ }
18955
19397
  default:
18956
19398
  return { status: failure(Errno.ENOSYS) };
18957
19399
  }
18958
19400
  }
18959
19401
  function implemented(capability) {
18960
- return ["files", "descriptors", "pipes", "readiness", "time", "identity", "entropy"].includes(capability);
19402
+ return ["files", "descriptors", "pipes", "readiness", "time", "identity", "entropy"].includes(capability) || capability === "sockets" && proc.sockets !== void 0;
18961
19403
  }
18962
19404
  function resolve3(path) {
18963
19405
  if (path.startsWith("/")) return proc.vfs.resolvePath(path);
@@ -18966,6 +19408,22 @@ function createHostAbiServer(proc) {
18966
19408
  function clamp(length) {
18967
19409
  return Math.min(length, MAX_TRANSFER);
18968
19410
  }
19411
+ function socketDescription(description) {
19412
+ if (!(description instanceof SocketDescription)) throw new PosixError(Errno.ENOTSOCK, "descriptor is not a socket");
19413
+ return description;
19414
+ }
19415
+ async function acceptBlocking(description) {
19416
+ for (; ; ) {
19417
+ try {
19418
+ return description.accept();
19419
+ } catch (error) {
19420
+ const posix = toPosixError(error);
19421
+ if (posix.errno !== Errno.EAGAIN || description.flags & O_NONBLOCK) throw posix;
19422
+ }
19423
+ await Promise.race([description.whenReady(), aborted()]);
19424
+ if (proc.signal.aborted) throw new PosixError(Errno.EINTR);
19425
+ }
19426
+ }
18969
19427
  async function readBlocking(description, length) {
18970
19428
  for (; ; ) {
18971
19429
  try {
@@ -19232,7 +19690,8 @@ async function startPythonProcess(options) {
19232
19690
  table,
19233
19691
  env: options.env,
19234
19692
  cwd: options.cwd,
19235
- signal: controller.signal
19693
+ signal: controller.signal,
19694
+ sockets: options.sockets
19236
19695
  });
19237
19696
  const buffers = createHostTransportBuffers();
19238
19697
  let fault = null;
@@ -19839,7 +20298,8 @@ async function runOwnedPython(ctx, argv) {
19839
20298
  mounts,
19840
20299
  onStdout: (data) => ctx.stdout.write(data),
19841
20300
  onStderr: (data) => ctx.stderr.write(data),
19842
- signal: ctx.signal
20301
+ signal: ctx.signal,
20302
+ sockets: ctx.kernel.pod.sockets
19843
20303
  });
19844
20304
  void pumpStdin(ctx, process2);
19845
20305
  return process2.wait();
@@ -26145,12 +26605,16 @@ var VirtualHttpServer = class extends EventEmitter4__default.default {
26145
26605
  }
26146
26606
  };
26147
26607
  var VirtualHttpRouter = class {
26608
+ constructor(sockets) {
26609
+ this.sockets = sockets;
26610
+ }
26611
+ sockets;
26148
26612
  servers = /* @__PURE__ */ new Map();
26149
26613
  /** Notified when a server begins listening, for `onServerReady`. */
26150
26614
  onListen;
26151
26615
  onClose;
26152
26616
  register(port, server, owner) {
26153
- if (this.servers.has(port)) throw Object.assign(new Error(`listen EADDRINUSE: address already in use 0.0.0.0:${port}`), { code: "EADDRINUSE", port });
26617
+ if (this.servers.has(port) || this.sockets?.hasListener(port)) throw Object.assign(new Error(`listen EADDRINUSE: address already in use 0.0.0.0:${port}`), { code: "EADDRINUSE", port });
26154
26618
  this.servers.set(port, { server, owner });
26155
26619
  this.onListen?.(port);
26156
26620
  }
@@ -29734,8 +30198,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
29734
30198
  volume = new MirroringVolume(new MemoryVolume());
29735
30199
  packages;
29736
30200
  instanceId = `sbx-${Math.random().toString(36).slice(2)}`;
29737
- router = new VirtualHttpRouter();
29738
- proxy = { activePorts: (_instanceId) => this.router.activePorts() };
30201
+ router;
30202
+ sockets;
30203
+ proxy = { activePorts: (_instanceId) => [.../* @__PURE__ */ new Set([...this.router.activePorts(), ...this.sockets.ports()])].sort((a, b) => a - b) };
29739
30204
  /**
29740
30205
  * Bind a port to a server that is not a JavaScript one.
29741
30206
  *
@@ -29802,6 +30267,8 @@ var LocalRuntimePod = class _LocalRuntimePod {
29802
30267
  fetch;
29803
30268
  constructor(options) {
29804
30269
  ensureProcessGlobal();
30270
+ this.sockets = new VirtualTcpNetwork((port) => this.router.activePortsIncludes(port));
30271
+ this.router = new VirtualHttpRouter(this.sockets);
29805
30272
  this.workdir = options.workdir ?? "/";
29806
30273
  this.env = { ...options.env };
29807
30274
  this.aliases = { ...WASM_ALIASES, ...options.aliases };
@@ -29935,8 +30402,10 @@ var LocalRuntimePod = class _LocalRuntimePod {
29935
30402
  await new Promise((resolve3) => setTimeout(resolve3, busy ? 5 : 0));
29936
30403
  }
29937
30404
  }
29938
- async request(_port, _init = {}) {
29939
- return this.router.request(_port, _init);
30405
+ async request(port, init = {}) {
30406
+ if (this.router.activePortsIncludes(port)) return this.router.request(port, init);
30407
+ if (!this.sockets.hasListener(port)) return this.router.request(port, init);
30408
+ return this.requestTcp(port, init);
29940
30409
  }
29941
30410
  connect(port, init, peer) {
29942
30411
  return this.router.connect(port, init, peer);
@@ -29955,8 +30424,46 @@ var LocalRuntimePod = class _LocalRuntimePod {
29955
30424
  for (const process2 of this.running) process2.kill();
29956
30425
  this.running.clear();
29957
30426
  this.router.closeAll();
30427
+ this.sockets.closeAll();
29958
30428
  this.esbuild?.stop();
29959
30429
  }
30430
+ /** HTTP/1.1 client path for a server implemented outside the JS runtime. */
30431
+ async requestTcp(port, init) {
30432
+ const connection = this.sockets.connect(port);
30433
+ const method = String(init.method ?? "GET").toUpperCase();
30434
+ const path = String(init.path ?? "/");
30435
+ const body = init.body == null ? new Uint8Array() : typeof init.body === "string" ? new TextEncoder().encode(init.body) : init.body instanceof ArrayBuffer ? new Uint8Array(init.body) : init.body;
30436
+ const headers = Object.entries(init.headers ?? {}).map(([name, value]) => `${name}: ${String(value)}`);
30437
+ if (!headers.some((line) => line.toLowerCase().startsWith("host:"))) headers.push("Host: localhost");
30438
+ if (!headers.some((line) => line.toLowerCase().startsWith("connection:"))) headers.push("Connection: close");
30439
+ if (body.length && !headers.some((line) => line.toLowerCase().startsWith("content-length:"))) headers.push(`Content-Length: ${body.length}`);
30440
+ const request = new TextEncoder().encode(`${method} ${path} HTTP/1.1\r
30441
+ ${headers.join("\r\n")}\r
30442
+ \r
30443
+ `);
30444
+ connection.write(request);
30445
+ if (body.length) connection.write(body);
30446
+ const bytes2 = [];
30447
+ let combined = new Uint8Array();
30448
+ for (; ; ) {
30449
+ try {
30450
+ const chunk = connection.read(64 * 1024);
30451
+ if (chunk.length === 0) break;
30452
+ bytes2.push(chunk);
30453
+ combined = joinBytes(bytes2);
30454
+ const headerEnd = findBytes(combined, new Uint8Array([13, 10, 13, 10]));
30455
+ if (headerEnd < 0) continue;
30456
+ const headerText = new TextDecoder().decode(combined.subarray(0, headerEnd));
30457
+ const contentLength = /^content-length:\s*(\d+)\s*$/im.exec(headerText)?.[1];
30458
+ if (contentLength !== void 0 && combined.length >= headerEnd + 4 + Number(contentLength)) break;
30459
+ } catch (error) {
30460
+ if (!(error instanceof PosixError) || error.errno !== Errno.EAGAIN) throw error;
30461
+ await connection.waitForChange();
30462
+ }
30463
+ }
30464
+ connection.close();
30465
+ return parseHttpResponse(combined);
30466
+ }
29960
30467
  seed(files) {
29961
30468
  for (const [path, data] of Object.entries(files)) {
29962
30469
  const parts = path.split("/").filter(Boolean);
@@ -29988,6 +30495,43 @@ function formatError(error) {
29988
30495
  function isRecord(value) {
29989
30496
  return typeof value === "object" && value !== null && !Array.isArray(value);
29990
30497
  }
30498
+ function joinBytes(parts) {
30499
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
30500
+ const out = new Uint8Array(total);
30501
+ let at = 0;
30502
+ for (const part of parts) {
30503
+ out.set(part, at);
30504
+ at += part.length;
30505
+ }
30506
+ return out;
30507
+ }
30508
+ function findBytes(haystack, needle) {
30509
+ outer: for (let at = 0; at <= haystack.length - needle.length; at += 1) {
30510
+ for (let i = 0; i < needle.length; i += 1) if (haystack[at + i] !== needle[i]) continue outer;
30511
+ return at;
30512
+ }
30513
+ return -1;
30514
+ }
30515
+ function parseHttpResponse(bytes2) {
30516
+ const marker = new Uint8Array([13, 10, 13, 10]);
30517
+ const headerEnd = findBytes(bytes2, marker);
30518
+ if (headerEnd < 0) throw new Error("Python server returned an incomplete HTTP response");
30519
+ const text2 = new TextDecoder().decode(bytes2.subarray(0, headerEnd));
30520
+ const lines = text2.split("\r\n");
30521
+ const status = /HTTP\/\d\.\d\s+(\d+)(?:\s+(.*))?/.exec(lines.shift() ?? "");
30522
+ if (!status) throw new Error("Python server returned an invalid HTTP status line");
30523
+ const headers = {};
30524
+ for (const line of lines) {
30525
+ const separator = line.indexOf(":");
30526
+ if (separator > 0) headers[line.slice(0, separator).toLowerCase()] = line.slice(separator + 1).trim();
30527
+ }
30528
+ return {
30529
+ statusCode: Number(status[1]),
30530
+ statusMessage: status[2] ?? "",
30531
+ headers,
30532
+ body: bytes2.slice(headerEnd + marker.length)
30533
+ };
30534
+ }
29991
30535
  function packageIsInstalled(volume, cwd, wanted) {
29992
30536
  for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
29993
30537
  if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;