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 +40 -3
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +40 -3
- package/dist/service-worker.js +22 -7
- package/package.json +1 -1
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.
|
|
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
|
|
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(
|
|
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.
|
|
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
|
|
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(
|
|
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;
|
package/dist/service-worker.js
CHANGED
|
@@ -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 ??
|
|
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
|
-
(
|
|
455
|
-
|
|
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.
|
|
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",
|