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.js CHANGED
@@ -17787,7 +17787,20 @@ var Op = {
17787
17787
  getcwd: 1281,
17788
17788
  chdir: 1282,
17789
17789
  environ: 1283,
17790
- getrandom: 1536
17790
+ getrandom: 1536,
17791
+ socket: 1792,
17792
+ bind: 1793,
17793
+ listen: 1794,
17794
+ accept: 1795,
17795
+ connect: 1796,
17796
+ send: 1797,
17797
+ recv: 1798,
17798
+ shutdown: 1799,
17799
+ getsockname: 1800,
17800
+ getpeername: 1801,
17801
+ setsockopt: 1802,
17802
+ getsockopt: 1803,
17803
+ socket_close: 1804
17791
17804
  };
17792
17805
  var Errno = {
17793
17806
  EPERM: 1,
@@ -17824,6 +17837,15 @@ var Errno = {
17824
17837
  ENOTEMPTY: 39,
17825
17838
  ELOOP: 40,
17826
17839
  ENODATA: 61,
17840
+ ENOTSOCK: 88,
17841
+ EAFNOSUPPORT: 97,
17842
+ EADDRINUSE: 98,
17843
+ EADDRNOTAVAIL: 99,
17844
+ ENETUNREACH: 101,
17845
+ ECONNRESET: 104,
17846
+ EISCONN: 106,
17847
+ ENOTCONN: 107,
17848
+ ECONNREFUSED: 111,
17827
17849
  EPROTO: 71,
17828
17850
  EOVERFLOW: 75,
17829
17851
  ETIMEDOUT: 110,
@@ -18689,6 +18711,362 @@ function failure(errno) {
18689
18711
  return -Math.abs(errno);
18690
18712
  }
18691
18713
 
18714
+ // src/net/virtual-socket.ts
18715
+ var ByteQueue = class {
18716
+ chunks = [];
18717
+ length = 0;
18718
+ waiters = /* @__PURE__ */ new Set();
18719
+ push(bytes2) {
18720
+ if (bytes2.length === 0) return;
18721
+ const copy = bytes2.slice();
18722
+ this.chunks.push(copy);
18723
+ this.length += copy.length;
18724
+ this.wake();
18725
+ }
18726
+ take(max) {
18727
+ if (max <= 0 || this.length === 0) return new Uint8Array();
18728
+ const out = new Uint8Array(Math.min(max, this.length));
18729
+ let at = 0;
18730
+ while (at < out.length) {
18731
+ const chunk = this.chunks[0];
18732
+ const count = Math.min(chunk.length, out.length - at);
18733
+ out.set(chunk.subarray(0, count), at);
18734
+ at += count;
18735
+ this.length -= count;
18736
+ if (count === chunk.length) this.chunks.shift();
18737
+ else this.chunks[0] = chunk.subarray(count);
18738
+ }
18739
+ return out;
18740
+ }
18741
+ get size() {
18742
+ return this.length;
18743
+ }
18744
+ whenChanged() {
18745
+ return new Promise((resolve3) => this.waiters.add(resolve3));
18746
+ }
18747
+ wake() {
18748
+ for (const resolve3 of this.waiters) resolve3();
18749
+ this.waiters.clear();
18750
+ }
18751
+ };
18752
+ var VirtualTcpConnection = class {
18753
+ incoming = new ByteQueue();
18754
+ peer = null;
18755
+ readClosed = false;
18756
+ writeClosed = false;
18757
+ closed = false;
18758
+ local;
18759
+ remote;
18760
+ constructor(local, remote) {
18761
+ this.local = local;
18762
+ this.remote = remote;
18763
+ }
18764
+ pairWith(peer) {
18765
+ this.peer = peer;
18766
+ }
18767
+ get eof() {
18768
+ return this.readClosed || this.incoming.size === 0 && this.peer?.writeClosed === true;
18769
+ }
18770
+ get readable() {
18771
+ return this.incoming.size > 0 || this.eof;
18772
+ }
18773
+ get writable() {
18774
+ return !this.writeClosed && !this.closed && this.peer !== null && !this.peer.readClosed;
18775
+ }
18776
+ read(maxLength) {
18777
+ if (this.incoming.size > 0) return this.incoming.take(maxLength);
18778
+ if (this.eof) return new Uint8Array();
18779
+ throw new PosixError(Errno.EAGAIN, "socket read would block");
18780
+ }
18781
+ write(bytes2) {
18782
+ if (!this.writable) throw new PosixError(this.closed || this.writeClosed ? Errno.EPIPE : Errno.ECONNRESET);
18783
+ this.peer.incoming.push(bytes2);
18784
+ return bytes2.length;
18785
+ }
18786
+ async waitForChange() {
18787
+ if (this.readable || this.closed) return;
18788
+ await this.incoming.whenChanged();
18789
+ }
18790
+ shutdown(read, write) {
18791
+ if (read) this.readClosed = true;
18792
+ if (write) {
18793
+ this.writeClosed = true;
18794
+ if (this.peer) this.peer.incoming.wake();
18795
+ }
18796
+ this.incoming.wake();
18797
+ this.peer?.incoming.wake();
18798
+ }
18799
+ close() {
18800
+ if (this.closed) return;
18801
+ this.closed = true;
18802
+ this.readClosed = true;
18803
+ this.writeClosed = true;
18804
+ if (this.peer) {
18805
+ this.peer.readClosed = true;
18806
+ this.peer.incoming.wake();
18807
+ }
18808
+ this.incoming.wake();
18809
+ }
18810
+ };
18811
+ var VirtualTcpListener = class {
18812
+ constructor(port, address = "0.0.0.0", backlog = 128) {
18813
+ this.port = port;
18814
+ this.address = address;
18815
+ this.backlog = backlog;
18816
+ }
18817
+ port;
18818
+ address;
18819
+ backlog;
18820
+ pending = [];
18821
+ waiters = /* @__PURE__ */ new Set();
18822
+ closed = false;
18823
+ enqueue(connection) {
18824
+ if (this.closed) {
18825
+ connection.close();
18826
+ throw new PosixError(Errno.ECONNREFUSED, "listener is closed");
18827
+ }
18828
+ if (this.pending.length >= this.backlog) {
18829
+ connection.close();
18830
+ throw new PosixError(Errno.EAGAIN, "listener backlog is full");
18831
+ }
18832
+ this.pending.push(connection);
18833
+ this.wake();
18834
+ }
18835
+ accept() {
18836
+ const connection = this.pending.shift();
18837
+ if (!connection) {
18838
+ if (this.closed) throw new PosixError(Errno.EBADF, "listener is closed");
18839
+ throw new PosixError(Errno.EAGAIN, "listener accept would block");
18840
+ }
18841
+ return { connection, peer: connection.remote };
18842
+ }
18843
+ get readable() {
18844
+ return this.pending.length > 0 || this.closed;
18845
+ }
18846
+ get isClosed() {
18847
+ return this.closed;
18848
+ }
18849
+ async waitForChange() {
18850
+ if (this.readable) return;
18851
+ await new Promise((resolve3) => this.waiters.add(resolve3));
18852
+ }
18853
+ close() {
18854
+ if (this.closed) return;
18855
+ this.closed = true;
18856
+ for (const connection of this.pending.splice(0)) connection.close();
18857
+ this.wake();
18858
+ }
18859
+ wake() {
18860
+ for (const resolve3 of this.waiters) resolve3();
18861
+ this.waiters.clear();
18862
+ }
18863
+ };
18864
+ var VirtualTcpSocket = class _VirtualTcpSocket {
18865
+ constructor(network) {
18866
+ this.network = network;
18867
+ }
18868
+ network;
18869
+ listenerValue = null;
18870
+ connectionValue = null;
18871
+ boundPort = 0;
18872
+ boundAddress = "0.0.0.0";
18873
+ bind(address, port) {
18874
+ if (this.listenerValue || this.connectionValue || this.boundPort !== 0) {
18875
+ throw new PosixError(Errno.EINVAL, "socket is already bound");
18876
+ }
18877
+ this.boundAddress = address;
18878
+ this.boundPort = port;
18879
+ }
18880
+ listen(backlog) {
18881
+ if (this.listenerValue) return;
18882
+ if (this.boundPort === 0) {
18883
+ const port = this.findEphemeralPort();
18884
+ this.boundPort = port;
18885
+ }
18886
+ this.listenerValue = this.network.listen(this.boundPort, this.boundAddress, backlog);
18887
+ }
18888
+ connect(port, address = "127.0.0.1") {
18889
+ if (this.listenerValue || this.connectionValue) throw new PosixError(Errno.EISCONN);
18890
+ this.connectionValue = this.network.connect(port, this.boundPort, this.boundAddress);
18891
+ this.boundPort = this.connectionValue.local.port;
18892
+ this.boundAddress = this.connectionValue.local.address;
18893
+ }
18894
+ accept() {
18895
+ if (!this.listenerValue) throw new PosixError(Errno.EINVAL, "socket is not listening");
18896
+ const accepted = this.listenerValue.accept().connection;
18897
+ const socket = new _VirtualTcpSocket(this.network);
18898
+ socket.connectionValue = accepted;
18899
+ socket.boundPort = accepted.local.port;
18900
+ socket.boundAddress = accepted.local.address;
18901
+ return socket;
18902
+ }
18903
+ get listener() {
18904
+ return this.listenerValue;
18905
+ }
18906
+ get connection() {
18907
+ return this.connectionValue;
18908
+ }
18909
+ get local() {
18910
+ return { address: this.boundAddress, port: this.boundPort };
18911
+ }
18912
+ get remote() {
18913
+ return this.connectionValue?.remote ?? null;
18914
+ }
18915
+ get readable() {
18916
+ return this.listenerValue?.readable ?? this.connectionValue?.readable ?? false;
18917
+ }
18918
+ get writable() {
18919
+ return this.connectionValue?.writable ?? false;
18920
+ }
18921
+ waitForChange() {
18922
+ return (this.listenerValue ?? this.connectionValue)?.waitForChange() ?? Promise.resolve();
18923
+ }
18924
+ read(length) {
18925
+ if (!this.connectionValue) throw new PosixError(Errno.EINVAL);
18926
+ return this.connectionValue.read(length);
18927
+ }
18928
+ write(data) {
18929
+ if (!this.connectionValue) throw new PosixError(Errno.EINVAL);
18930
+ return this.connectionValue.write(data);
18931
+ }
18932
+ shutdown(read, write) {
18933
+ if (!this.connectionValue) throw new PosixError(Errno.ENOTCONN);
18934
+ this.connectionValue.shutdown(read, write);
18935
+ }
18936
+ close() {
18937
+ if (this.listenerValue) this.network.close(this.listenerValue);
18938
+ this.connectionValue?.close();
18939
+ this.listenerValue = null;
18940
+ this.connectionValue = null;
18941
+ }
18942
+ findEphemeralPort() {
18943
+ for (let port = 49152; port <= 65535; port += 1) if (!this.network.hasListener(port)) return port;
18944
+ throw new PosixError(Errno.EADDRINUSE, "no ephemeral ports available");
18945
+ }
18946
+ };
18947
+ var SocketDescription = class _SocketDescription {
18948
+ kind = "socket";
18949
+ flags;
18950
+ resource;
18951
+ closed = false;
18952
+ options = /* @__PURE__ */ new Map();
18953
+ constructor(resource, flags = 0) {
18954
+ this.resource = resource;
18955
+ this.flags = flags;
18956
+ }
18957
+ get listener() {
18958
+ return this.resource.listener;
18959
+ }
18960
+ get connection() {
18961
+ return this.resource.connection;
18962
+ }
18963
+ read(length) {
18964
+ return this.resource.read(length);
18965
+ }
18966
+ write(data) {
18967
+ return this.resource.write(data);
18968
+ }
18969
+ readable() {
18970
+ return this.resource.readable;
18971
+ }
18972
+ writable() {
18973
+ return this.resource.writable;
18974
+ }
18975
+ whenReady() {
18976
+ return this.resource.waitForChange();
18977
+ }
18978
+ stat() {
18979
+ return {
18980
+ ino: 0,
18981
+ mode: 49663,
18982
+ size: 0,
18983
+ uid: 0,
18984
+ gid: 0,
18985
+ nlink: 1,
18986
+ atimeMs: Date.now(),
18987
+ mtimeMs: Date.now(),
18988
+ ctimeMs: Date.now()
18989
+ };
18990
+ }
18991
+ close() {
18992
+ if (this.closed) return;
18993
+ this.closed = true;
18994
+ this.resource.close();
18995
+ }
18996
+ bind(address, port) {
18997
+ this.resource.bind(address, port);
18998
+ }
18999
+ listen(backlog) {
19000
+ this.resource.listen(backlog);
19001
+ }
19002
+ connect(address, port) {
19003
+ this.resource.connect(port, address);
19004
+ }
19005
+ accept() {
19006
+ return new _SocketDescription(this.resource.accept(), this.flags);
19007
+ }
19008
+ shutdown(read, write) {
19009
+ this.resource.shutdown(read, write);
19010
+ }
19011
+ setOption(level, option, value) {
19012
+ this.options.set(`${level}:${option}`, value.slice());
19013
+ }
19014
+ getOption(level, option) {
19015
+ return this.options.get(`${level}:${option}`)?.slice() ?? new Uint8Array(4);
19016
+ }
19017
+ localAddress() {
19018
+ return this.resource.local;
19019
+ }
19020
+ peerAddress() {
19021
+ const peer = this.resource.remote;
19022
+ if (!peer) throw new PosixError(Errno.ENOTCONN);
19023
+ return peer;
19024
+ }
19025
+ };
19026
+ var VirtualTcpNetwork = class {
19027
+ constructor(occupied) {
19028
+ this.occupied = occupied;
19029
+ }
19030
+ occupied;
19031
+ listeners = /* @__PURE__ */ new Map();
19032
+ listen(port, address = "0.0.0.0", backlog = 128) {
19033
+ if (this.listeners.has(port) || this.occupied?.(port)) throw new PosixError(Errno.EADDRINUSE, `port ${port} is already in use`);
19034
+ const listener = new VirtualTcpListener(port, address, backlog);
19035
+ this.listeners.set(port, listener);
19036
+ return listener;
19037
+ }
19038
+ close(listener) {
19039
+ if (this.listeners.get(listener.port) !== listener) return;
19040
+ this.listeners.delete(listener.port);
19041
+ listener.close();
19042
+ }
19043
+ connect(port, localPort = 0, localAddress = "127.0.0.1") {
19044
+ const listener = this.listeners.get(port);
19045
+ if (!listener || listener.isClosed) throw new PosixError(Errno.ECONNREFUSED, `port ${port} is not listening`);
19046
+ const client = new VirtualTcpConnection(
19047
+ { address: localAddress, port: localPort },
19048
+ { address: listener.address, port }
19049
+ );
19050
+ const server = new VirtualTcpConnection(
19051
+ { address: listener.address, port },
19052
+ { address: localAddress, port: localPort }
19053
+ );
19054
+ client.pairWith(server);
19055
+ server.pairWith(client);
19056
+ listener.enqueue(server);
19057
+ return client;
19058
+ }
19059
+ hasListener(port) {
19060
+ return this.listeners.has(port);
19061
+ }
19062
+ ports() {
19063
+ return [...this.listeners.keys()].sort((a, b) => a - b);
19064
+ }
19065
+ closeAll() {
19066
+ for (const listener of [...this.listeners.values()]) this.close(listener);
19067
+ }
19068
+ };
19069
+
18692
19070
  // src/runtime/python/syscall-server.ts
18693
19071
  var MAX_TRANSFER = 8 * 1024 * 1024;
18694
19072
  function createHostAbiServer(proc) {
@@ -18935,12 +19313,76 @@ function createHostAbiServer(proc) {
18935
19313
  crypto.getRandomValues(bytes2);
18936
19314
  return { status: length, payload: bytes2 };
18937
19315
  }
19316
+ /* ----------------------------------------------------------- sockets */
19317
+ case Op.socket: {
19318
+ const family = r.u32();
19319
+ const type = r.u32();
19320
+ const protocol = r.i32();
19321
+ if (family !== 2 || (type & 15) !== 1 || protocol !== 0) throw new PosixError(Errno.EAFNOSUPPORT);
19322
+ if (!proc.sockets) throw new PosixError(Errno.ENOSYS);
19323
+ return { status: proc.table.add(new SocketDescription(new VirtualTcpSocket(proc.sockets))) };
19324
+ }
19325
+ case Op.bind: {
19326
+ const description = socketDescription(proc.table.get(r.i32()));
19327
+ description.bind(r.string(), r.u32());
19328
+ return { status: 0 };
19329
+ }
19330
+ case Op.listen: {
19331
+ const description = socketDescription(proc.table.get(r.i32()));
19332
+ description.listen(Math.max(1, Math.min(1024, r.u32())));
19333
+ return { status: 0 };
19334
+ }
19335
+ case Op.accept: {
19336
+ const description = socketDescription(proc.table.get(r.i32()));
19337
+ const accepted = await acceptBlocking(description);
19338
+ const fd = proc.table.add(accepted);
19339
+ const peer = accepted.peerAddress();
19340
+ return { status: fd, payload: new Writer().string(peer.address).u32(peer.port).finish() };
19341
+ }
19342
+ case Op.connect: {
19343
+ const description = socketDescription(proc.table.get(r.i32()));
19344
+ description.connect(r.string(), r.u32());
19345
+ return { status: 0 };
19346
+ }
19347
+ case Op.send: {
19348
+ const description = socketDescription(proc.table.get(r.i32()));
19349
+ return { status: await writeBlocking(description, r.bytes32()) };
19350
+ }
19351
+ case Op.recv: {
19352
+ const description = socketDescription(proc.table.get(r.i32()));
19353
+ const data = await readBlocking(description, clamp(r.u32()));
19354
+ return { status: data.length, payload: data };
19355
+ }
19356
+ case Op.shutdown: {
19357
+ const description = socketDescription(proc.table.get(r.i32()));
19358
+ const how = r.u32();
19359
+ if (how > 2) throw new PosixError(Errno.EINVAL);
19360
+ description.shutdown(how === 0 || how === 2, how === 1 || how === 2);
19361
+ return { status: 0 };
19362
+ }
19363
+ case Op.getsockname: {
19364
+ const address = socketDescription(proc.table.get(r.i32())).localAddress();
19365
+ return { status: 0, payload: new Writer().string(address.address).u32(address.port).finish() };
19366
+ }
19367
+ case Op.getpeername: {
19368
+ const address = socketDescription(proc.table.get(r.i32())).peerAddress();
19369
+ return { status: 0, payload: new Writer().string(address.address).u32(address.port).finish() };
19370
+ }
19371
+ case Op.setsockopt: {
19372
+ const description = socketDescription(proc.table.get(r.i32()));
19373
+ description.setOption(r.u32(), r.u32(), r.bytes32());
19374
+ return { status: 0 };
19375
+ }
19376
+ case Op.getsockopt: {
19377
+ const description = socketDescription(proc.table.get(r.i32()));
19378
+ return { status: 0, payload: new Writer().bytes32(description.getOption(r.u32(), r.u32())).finish() };
19379
+ }
18938
19380
  default:
18939
19381
  return { status: failure(Errno.ENOSYS) };
18940
19382
  }
18941
19383
  }
18942
19384
  function implemented(capability) {
18943
- return ["files", "descriptors", "pipes", "readiness", "time", "identity", "entropy"].includes(capability);
19385
+ return ["files", "descriptors", "pipes", "readiness", "time", "identity", "entropy"].includes(capability) || capability === "sockets" && proc.sockets !== void 0;
18944
19386
  }
18945
19387
  function resolve3(path) {
18946
19388
  if (path.startsWith("/")) return proc.vfs.resolvePath(path);
@@ -18949,6 +19391,22 @@ function createHostAbiServer(proc) {
18949
19391
  function clamp(length) {
18950
19392
  return Math.min(length, MAX_TRANSFER);
18951
19393
  }
19394
+ function socketDescription(description) {
19395
+ if (!(description instanceof SocketDescription)) throw new PosixError(Errno.ENOTSOCK, "descriptor is not a socket");
19396
+ return description;
19397
+ }
19398
+ async function acceptBlocking(description) {
19399
+ for (; ; ) {
19400
+ try {
19401
+ return description.accept();
19402
+ } catch (error) {
19403
+ const posix = toPosixError(error);
19404
+ if (posix.errno !== Errno.EAGAIN || description.flags & O_NONBLOCK) throw posix;
19405
+ }
19406
+ await Promise.race([description.whenReady(), aborted()]);
19407
+ if (proc.signal.aborted) throw new PosixError(Errno.EINTR);
19408
+ }
19409
+ }
18952
19410
  async function readBlocking(description, length) {
18953
19411
  for (; ; ) {
18954
19412
  try {
@@ -19215,7 +19673,8 @@ async function startPythonProcess(options) {
19215
19673
  table,
19216
19674
  env: options.env,
19217
19675
  cwd: options.cwd,
19218
- signal: controller.signal
19676
+ signal: controller.signal,
19677
+ sockets: options.sockets
19219
19678
  });
19220
19679
  const buffers = createHostTransportBuffers();
19221
19680
  let fault = null;
@@ -19822,7 +20281,8 @@ async function runOwnedPython(ctx, argv) {
19822
20281
  mounts,
19823
20282
  onStdout: (data) => ctx.stdout.write(data),
19824
20283
  onStderr: (data) => ctx.stderr.write(data),
19825
- signal: ctx.signal
20284
+ signal: ctx.signal,
20285
+ sockets: ctx.kernel.pod.sockets
19826
20286
  });
19827
20287
  void pumpStdin(ctx, process2);
19828
20288
  return process2.wait();
@@ -26128,12 +26588,16 @@ var VirtualHttpServer = class extends EventEmitter4 {
26128
26588
  }
26129
26589
  };
26130
26590
  var VirtualHttpRouter = class {
26591
+ constructor(sockets) {
26592
+ this.sockets = sockets;
26593
+ }
26594
+ sockets;
26131
26595
  servers = /* @__PURE__ */ new Map();
26132
26596
  /** Notified when a server begins listening, for `onServerReady`. */
26133
26597
  onListen;
26134
26598
  onClose;
26135
26599
  register(port, server, owner) {
26136
- if (this.servers.has(port)) throw Object.assign(new Error(`listen EADDRINUSE: address already in use 0.0.0.0:${port}`), { code: "EADDRINUSE", port });
26600
+ 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 });
26137
26601
  this.servers.set(port, { server, owner });
26138
26602
  this.onListen?.(port);
26139
26603
  }
@@ -29717,8 +30181,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
29717
30181
  volume = new MirroringVolume(new MemoryVolume());
29718
30182
  packages;
29719
30183
  instanceId = `sbx-${Math.random().toString(36).slice(2)}`;
29720
- router = new VirtualHttpRouter();
29721
- proxy = { activePorts: (_instanceId) => this.router.activePorts() };
30184
+ router;
30185
+ sockets;
30186
+ proxy = { activePorts: (_instanceId) => [.../* @__PURE__ */ new Set([...this.router.activePorts(), ...this.sockets.ports()])].sort((a, b) => a - b) };
29722
30187
  /**
29723
30188
  * Bind a port to a server that is not a JavaScript one.
29724
30189
  *
@@ -29785,6 +30250,8 @@ var LocalRuntimePod = class _LocalRuntimePod {
29785
30250
  fetch;
29786
30251
  constructor(options) {
29787
30252
  ensureProcessGlobal();
30253
+ this.sockets = new VirtualTcpNetwork((port) => this.router.activePortsIncludes(port));
30254
+ this.router = new VirtualHttpRouter(this.sockets);
29788
30255
  this.workdir = options.workdir ?? "/";
29789
30256
  this.env = { ...options.env };
29790
30257
  this.aliases = { ...WASM_ALIASES, ...options.aliases };
@@ -29918,8 +30385,10 @@ var LocalRuntimePod = class _LocalRuntimePod {
29918
30385
  await new Promise((resolve3) => setTimeout(resolve3, busy ? 5 : 0));
29919
30386
  }
29920
30387
  }
29921
- async request(_port, _init = {}) {
29922
- return this.router.request(_port, _init);
30388
+ async request(port, init = {}) {
30389
+ if (this.router.activePortsIncludes(port)) return this.router.request(port, init);
30390
+ if (!this.sockets.hasListener(port)) return this.router.request(port, init);
30391
+ return this.requestTcp(port, init);
29923
30392
  }
29924
30393
  connect(port, init, peer) {
29925
30394
  return this.router.connect(port, init, peer);
@@ -29938,8 +30407,46 @@ var LocalRuntimePod = class _LocalRuntimePod {
29938
30407
  for (const process2 of this.running) process2.kill();
29939
30408
  this.running.clear();
29940
30409
  this.router.closeAll();
30410
+ this.sockets.closeAll();
29941
30411
  this.esbuild?.stop();
29942
30412
  }
30413
+ /** HTTP/1.1 client path for a server implemented outside the JS runtime. */
30414
+ async requestTcp(port, init) {
30415
+ const connection = this.sockets.connect(port);
30416
+ const method = String(init.method ?? "GET").toUpperCase();
30417
+ const path = String(init.path ?? "/");
30418
+ 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;
30419
+ const headers = Object.entries(init.headers ?? {}).map(([name, value]) => `${name}: ${String(value)}`);
30420
+ if (!headers.some((line) => line.toLowerCase().startsWith("host:"))) headers.push("Host: localhost");
30421
+ if (!headers.some((line) => line.toLowerCase().startsWith("connection:"))) headers.push("Connection: close");
30422
+ if (body.length && !headers.some((line) => line.toLowerCase().startsWith("content-length:"))) headers.push(`Content-Length: ${body.length}`);
30423
+ const request = new TextEncoder().encode(`${method} ${path} HTTP/1.1\r
30424
+ ${headers.join("\r\n")}\r
30425
+ \r
30426
+ `);
30427
+ connection.write(request);
30428
+ if (body.length) connection.write(body);
30429
+ const bytes2 = [];
30430
+ let combined = new Uint8Array();
30431
+ for (; ; ) {
30432
+ try {
30433
+ const chunk = connection.read(64 * 1024);
30434
+ if (chunk.length === 0) break;
30435
+ bytes2.push(chunk);
30436
+ combined = joinBytes(bytes2);
30437
+ const headerEnd = findBytes(combined, new Uint8Array([13, 10, 13, 10]));
30438
+ if (headerEnd < 0) continue;
30439
+ const headerText = new TextDecoder().decode(combined.subarray(0, headerEnd));
30440
+ const contentLength = /^content-length:\s*(\d+)\s*$/im.exec(headerText)?.[1];
30441
+ if (contentLength !== void 0 && combined.length >= headerEnd + 4 + Number(contentLength)) break;
30442
+ } catch (error) {
30443
+ if (!(error instanceof PosixError) || error.errno !== Errno.EAGAIN) throw error;
30444
+ await connection.waitForChange();
30445
+ }
30446
+ }
30447
+ connection.close();
30448
+ return parseHttpResponse(combined);
30449
+ }
29943
30450
  seed(files) {
29944
30451
  for (const [path, data] of Object.entries(files)) {
29945
30452
  const parts = path.split("/").filter(Boolean);
@@ -29971,6 +30478,43 @@ function formatError(error) {
29971
30478
  function isRecord(value) {
29972
30479
  return typeof value === "object" && value !== null && !Array.isArray(value);
29973
30480
  }
30481
+ function joinBytes(parts) {
30482
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
30483
+ const out = new Uint8Array(total);
30484
+ let at = 0;
30485
+ for (const part of parts) {
30486
+ out.set(part, at);
30487
+ at += part.length;
30488
+ }
30489
+ return out;
30490
+ }
30491
+ function findBytes(haystack, needle) {
30492
+ outer: for (let at = 0; at <= haystack.length - needle.length; at += 1) {
30493
+ for (let i = 0; i < needle.length; i += 1) if (haystack[at + i] !== needle[i]) continue outer;
30494
+ return at;
30495
+ }
30496
+ return -1;
30497
+ }
30498
+ function parseHttpResponse(bytes2) {
30499
+ const marker = new Uint8Array([13, 10, 13, 10]);
30500
+ const headerEnd = findBytes(bytes2, marker);
30501
+ if (headerEnd < 0) throw new Error("Python server returned an incomplete HTTP response");
30502
+ const text2 = new TextDecoder().decode(bytes2.subarray(0, headerEnd));
30503
+ const lines = text2.split("\r\n");
30504
+ const status = /HTTP\/\d\.\d\s+(\d+)(?:\s+(.*))?/.exec(lines.shift() ?? "");
30505
+ if (!status) throw new Error("Python server returned an invalid HTTP status line");
30506
+ const headers = {};
30507
+ for (const line of lines) {
30508
+ const separator = line.indexOf(":");
30509
+ if (separator > 0) headers[line.slice(0, separator).toLowerCase()] = line.slice(separator + 1).trim();
30510
+ }
30511
+ return {
30512
+ statusCode: Number(status[1]),
30513
+ statusMessage: status[2] ?? "",
30514
+ headers,
30515
+ body: bytes2.slice(headerEnd + marker.length)
30516
+ };
30517
+ }
29974
30518
  function packageIsInstalled(volume, cwd, wanted) {
29975
30519
  for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
29976
30520
  if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;