sandboxedjs 0.1.99 → 0.2.0

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
@@ -20959,7 +20959,7 @@ var wheels_default = {
20959
20959
 
20960
20960
  // package.json
20961
20961
  var package_default = {
20962
- version: "0.1.99"};
20962
+ version: "0.2.0"};
20963
20963
 
20964
20964
  // src/python/extension-abi.ts
20965
20965
  var EXTENSION_ABI = {
@@ -36993,6 +36993,10 @@ ${headers.join("\r\n")}\r
36993
36993
  const headerEnd = findBytes(combined, new Uint8Array([13, 10, 13, 10]));
36994
36994
  if (headerEnd < 0) continue;
36995
36995
  const headerText = new TextDecoder().decode(combined.subarray(0, headerEnd));
36996
+ if (CHUNKED.test(headerText)) {
36997
+ if (decodeChunked(combined.subarray(headerEnd + 4)).complete) break;
36998
+ continue;
36999
+ }
36996
37000
  const contentLength = /^content-length:\s*(\d+)\s*$/im.exec(headerText)?.[1];
36997
37001
  if (contentLength !== void 0 && combined.length >= headerEnd + 4 + Number(contentLength)) break;
36998
37002
  } catch (error) {
@@ -37064,13 +37068,36 @@ function parseHttpResponse(bytes2) {
37064
37068
  const separator = line.indexOf(":");
37065
37069
  if (separator > 0) headers[line.slice(0, separator).toLowerCase()] = line.slice(separator + 1).trim();
37066
37070
  }
37071
+ let body = bytes2.slice(headerEnd + marker.length);
37072
+ if (CHUNKED.test(headers["transfer-encoding"] ?? "")) {
37073
+ body = decodeChunked(body).body;
37074
+ delete headers["transfer-encoding"];
37075
+ headers["content-length"] = String(body.length);
37076
+ }
37067
37077
  return {
37068
37078
  statusCode: Number(status[1]),
37069
37079
  statusMessage: status[2] ?? "",
37070
37080
  headers,
37071
- body: bytes2.slice(headerEnd + marker.length)
37081
+ body
37072
37082
  };
37073
37083
  }
37084
+ var CHUNKED = /^(?:transfer-encoding:.*)?\bchunked\b/im;
37085
+ function decodeChunked(bytes2) {
37086
+ const parts = [];
37087
+ const crlf = new Uint8Array([13, 10]);
37088
+ for (let at = 0; ; ) {
37089
+ const lineEnd = findBytes(bytes2.subarray(at), crlf);
37090
+ if (lineEnd < 0) return { body: joinBytes(parts), complete: false };
37091
+ const header = new TextDecoder().decode(bytes2.subarray(at, at + lineEnd)).split(";")[0].trim();
37092
+ const size = /^[0-9a-f]+$/i.test(header) ? Number.parseInt(header, 16) : Number.NaN;
37093
+ if (!Number.isInteger(size)) throw new Error("Python server returned an invalid chunked response");
37094
+ at += lineEnd + crlf.length;
37095
+ if (size === 0) return { body: joinBytes(parts), complete: true };
37096
+ if (at + size + crlf.length > bytes2.length) return { body: joinBytes(parts), complete: false };
37097
+ parts.push(bytes2.subarray(at, at + size));
37098
+ at += size + crlf.length;
37099
+ }
37100
+ }
37074
37101
  function packageIsInstalled(volume, cwd, wanted) {
37075
37102
  for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
37076
37103
  if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;
@@ -38746,7 +38773,17 @@ async function createPreview(box, options = {}) {
38746
38773
  channel = new MessageChannel();
38747
38774
  serveContainerOn(channel.port1, box);
38748
38775
  const target = registration.active ?? worker;
38749
- target.postMessage({ type: "sandboxedjs:connect", injectSockets: options.websocket !== false }, [channel.port2]);
38776
+ target.postMessage(
38777
+ {
38778
+ type: "sandboxedjs:connect",
38779
+ injectSockets: options.websocket !== false,
38780
+ /* Sent with the channel, like `injectSockets`, because the worker is
38781
+ * restarted at the browser's convenience and remembers nothing: every
38782
+ * connect has to carry the settings again. */
38783
+ ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
38784
+ },
38785
+ [channel.port2]
38786
+ );
38750
38787
  };
38751
38788
  const onWorkerMessage = (event) => {
38752
38789
  const type = event.data?.type;
package/dist/index.d.cts CHANGED
@@ -1678,6 +1678,16 @@ interface PreviewOptions {
1678
1678
  * re-optimizing dependencies; {@link onStale} is the fallback for that.
1679
1679
  */
1680
1680
  websocket?: boolean;
1681
+ /**
1682
+ * How long a previewed request may take before the worker reports a timeout.
1683
+ *
1684
+ * Defaults to five minutes. Responses cross this bridge whole rather than
1685
+ * streamed, so this covers the *entire* answer: a model generating tokens, a
1686
+ * server-sent-event endpoint running to its last event, a cold build. Raise
1687
+ * it for work that legitimately takes longer; lower it when a preview should
1688
+ * fail fast.
1689
+ */
1690
+ timeoutMs?: number;
1681
1691
  }
1682
1692
  interface Preview {
1683
1693
  /** The URL an iframe should be pointed at to see `port`. */
package/dist/index.d.ts CHANGED
@@ -1678,6 +1678,16 @@ interface PreviewOptions {
1678
1678
  * re-optimizing dependencies; {@link onStale} is the fallback for that.
1679
1679
  */
1680
1680
  websocket?: boolean;
1681
+ /**
1682
+ * How long a previewed request may take before the worker reports a timeout.
1683
+ *
1684
+ * Defaults to five minutes. Responses cross this bridge whole rather than
1685
+ * streamed, so this covers the *entire* answer: a model generating tokens, a
1686
+ * server-sent-event endpoint running to its last event, a cold build. Raise
1687
+ * it for work that legitimately takes longer; lower it when a preview should
1688
+ * fail fast.
1689
+ */
1690
+ timeoutMs?: number;
1681
1691
  }
1682
1692
  interface Preview {
1683
1693
  /** The URL an iframe should be pointed at to see `port`. */
package/dist/index.js CHANGED
@@ -20942,7 +20942,7 @@ var wheels_default = {
20942
20942
 
20943
20943
  // package.json
20944
20944
  var package_default = {
20945
- version: "0.1.99"};
20945
+ version: "0.2.0"};
20946
20946
 
20947
20947
  // src/python/extension-abi.ts
20948
20948
  var EXTENSION_ABI = {
@@ -36976,6 +36976,10 @@ ${headers.join("\r\n")}\r
36976
36976
  const headerEnd = findBytes(combined, new Uint8Array([13, 10, 13, 10]));
36977
36977
  if (headerEnd < 0) continue;
36978
36978
  const headerText = new TextDecoder().decode(combined.subarray(0, headerEnd));
36979
+ if (CHUNKED.test(headerText)) {
36980
+ if (decodeChunked(combined.subarray(headerEnd + 4)).complete) break;
36981
+ continue;
36982
+ }
36979
36983
  const contentLength = /^content-length:\s*(\d+)\s*$/im.exec(headerText)?.[1];
36980
36984
  if (contentLength !== void 0 && combined.length >= headerEnd + 4 + Number(contentLength)) break;
36981
36985
  } catch (error) {
@@ -37047,13 +37051,36 @@ function parseHttpResponse(bytes2) {
37047
37051
  const separator = line.indexOf(":");
37048
37052
  if (separator > 0) headers[line.slice(0, separator).toLowerCase()] = line.slice(separator + 1).trim();
37049
37053
  }
37054
+ let body = bytes2.slice(headerEnd + marker.length);
37055
+ if (CHUNKED.test(headers["transfer-encoding"] ?? "")) {
37056
+ body = decodeChunked(body).body;
37057
+ delete headers["transfer-encoding"];
37058
+ headers["content-length"] = String(body.length);
37059
+ }
37050
37060
  return {
37051
37061
  statusCode: Number(status[1]),
37052
37062
  statusMessage: status[2] ?? "",
37053
37063
  headers,
37054
- body: bytes2.slice(headerEnd + marker.length)
37064
+ body
37055
37065
  };
37056
37066
  }
37067
+ var CHUNKED = /^(?:transfer-encoding:.*)?\bchunked\b/im;
37068
+ function decodeChunked(bytes2) {
37069
+ const parts = [];
37070
+ const crlf = new Uint8Array([13, 10]);
37071
+ for (let at = 0; ; ) {
37072
+ const lineEnd = findBytes(bytes2.subarray(at), crlf);
37073
+ if (lineEnd < 0) return { body: joinBytes(parts), complete: false };
37074
+ const header = new TextDecoder().decode(bytes2.subarray(at, at + lineEnd)).split(";")[0].trim();
37075
+ const size = /^[0-9a-f]+$/i.test(header) ? Number.parseInt(header, 16) : Number.NaN;
37076
+ if (!Number.isInteger(size)) throw new Error("Python server returned an invalid chunked response");
37077
+ at += lineEnd + crlf.length;
37078
+ if (size === 0) return { body: joinBytes(parts), complete: true };
37079
+ if (at + size + crlf.length > bytes2.length) return { body: joinBytes(parts), complete: false };
37080
+ parts.push(bytes2.subarray(at, at + size));
37081
+ at += size + crlf.length;
37082
+ }
37083
+ }
37057
37084
  function packageIsInstalled(volume, cwd, wanted) {
37058
37085
  for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
37059
37086
  if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;
@@ -38729,7 +38756,17 @@ async function createPreview(box, options = {}) {
38729
38756
  channel = new MessageChannel();
38730
38757
  serveContainerOn(channel.port1, box);
38731
38758
  const target = registration.active ?? worker;
38732
- target.postMessage({ type: "sandboxedjs:connect", injectSockets: options.websocket !== false }, [channel.port2]);
38759
+ target.postMessage(
38760
+ {
38761
+ type: "sandboxedjs:connect",
38762
+ injectSockets: options.websocket !== false,
38763
+ /* Sent with the channel, like `injectSockets`, because the worker is
38764
+ * restarted at the browser's convenience and remembers nothing: every
38765
+ * connect has to carry the settings again. */
38766
+ ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
38767
+ },
38768
+ [channel.port2]
38769
+ );
38733
38770
  };
38734
38771
  const onWorkerMessage = (event) => {
38735
38772
  const type = event.data?.type;
@@ -365,6 +365,7 @@ function containerTarget(value) {
365
365
  }
366
366
  var OUTDATED = /outdated optimize dep/i;
367
367
  var NOT_CONNECTED = 503;
368
+ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
368
369
  var PreviewRouter = class {
369
370
  constructor(options2) {
370
371
  this.options = options2;
@@ -447,16 +448,15 @@ var PreviewRouter = class {
447
448
  { id, port, path, method: request.method, headers: headerRecord(request.headers), ...sent ? { body: sent } : {} },
448
449
  sent ? [sent] : []
449
450
  );
450
- const timeoutMs = this.options.timeoutMs ?? 3e4;
451
+ const timeoutMs = this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
452
+ let timer;
451
453
  const result = await Promise.race([
452
454
  answered,
453
- new Promise(
454
- (resolve) => setTimeout(
455
- () => resolve({ status: 504, statusText: "Gateway Timeout", headers: {}, body: new ArrayBuffer(0) }),
456
- timeoutMs
457
- )
458
- )
455
+ new Promise((resolve) => {
456
+ timer = setTimeout(() => resolve(timedOut(port, path, timeoutMs)), timeoutMs);
457
+ })
459
458
  ]);
459
+ clearTimeout(timer);
460
460
  this.pending.delete(id);
461
461
  if (result.status === 504 && OUTDATED.test(result.statusText)) {
462
462
  this.options.onStale?.(clientId);
@@ -468,6 +468,19 @@ var PreviewRouter = class {
468
468
  return new Response(body, { status: result.status, statusText: result.statusText, headers });
469
469
  }
470
470
  };
471
+ function timedOut(port, path, timeoutMs) {
472
+ const message = `The sandbox preview gave up waiting for ${path} on port ${port} after ${Math.round(timeoutMs / 1e3)}s.
473
+
474
+ The server inside the container has not finished this response. Preview responses are delivered whole rather than streamed, so a long one \u2014 a model's answer, a server-sent-event stream \u2014 is outstanding until it ends.
475
+
476
+ Raise createPreview(box, { timeoutMs }) if this work legitimately takes longer.`;
477
+ return {
478
+ status: 504,
479
+ statusText: "Gateway Timeout",
480
+ headers: { "content-type": "text/plain; charset=utf-8" },
481
+ body: new TextEncoder().encode(message).buffer
482
+ };
483
+ }
471
484
  function isolated(response) {
472
485
  const headers = new Headers(response.headers);
473
486
  headers.set("Content-Type", "text/plain; charset=utf-8");
@@ -553,6 +566,8 @@ worker.addEventListener("message", (event) => {
553
566
  host = event.ports[0] ?? null;
554
567
  if (!host) return;
555
568
  options.injectSockets = event.data.injectSockets !== false;
569
+ const timeoutMs = event.data.timeoutMs;
570
+ if (typeof timeoutMs === "number" && timeoutMs > 0) options.timeoutMs = timeoutMs;
556
571
  host.onmessage = (message) => {
557
572
  const reply = message.data;
558
573
  router.settle(reply.id, reply.response);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandboxedjs",
3
- "version": "0.1.99",
3
+ "version": "0.2.0",
4
4
  "description": "A Linux-like container that runs entirely inside Node.js — POSIX shell, ~140 coreutils, Node.js and Python runtimes, virtual filesystem and networking. No Docker, no VM, no native modules.",
5
5
  "type": "module",
6
6
  "license": "MIT",