sandboxedjs 0.1.30 → 0.1.32
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/README.md +85 -92
- package/bin/sandboxedjs-serve.mjs +24 -0
- package/dist/browser-host.cjs +112 -0
- package/dist/browser-host.cjs.map +1 -0
- package/dist/browser-host.d.cts +25 -0
- package/dist/browser-host.d.ts +25 -0
- package/dist/browser-host.js +109 -0
- package/dist/browser-host.js.map +1 -0
- package/dist/index.cjs +504 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -26
- package/dist/index.d.ts +26 -26
- package/dist/index.js +504 -198
- package/dist/index.js.map +1 -1
- package/dist/worker-entry.js +130 -109
- package/dist/worker-entry.js.map +1 -1
- package/package.json +9 -3
package/dist/worker-entry.js
CHANGED
|
@@ -27251,6 +27251,7 @@ var VirtualHttpServer = class extends import_events.default {
|
|
|
27251
27251
|
owner;
|
|
27252
27252
|
listening = false;
|
|
27253
27253
|
portValue = null;
|
|
27254
|
+
referenced = true;
|
|
27254
27255
|
listen(...args) {
|
|
27255
27256
|
const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
|
|
27256
27257
|
const first = args[0];
|
|
@@ -27279,11 +27280,16 @@ var VirtualHttpServer = class extends import_events.default {
|
|
|
27279
27280
|
return this.portValue === null ? null : { address: "0.0.0.0", family: "IPv4", port: this.portValue };
|
|
27280
27281
|
}
|
|
27281
27282
|
ref() {
|
|
27283
|
+
this.referenced = true;
|
|
27282
27284
|
return this;
|
|
27283
27285
|
}
|
|
27284
27286
|
unref() {
|
|
27287
|
+
this.referenced = false;
|
|
27285
27288
|
return this;
|
|
27286
27289
|
}
|
|
27290
|
+
hasRef() {
|
|
27291
|
+
return this.referenced;
|
|
27292
|
+
}
|
|
27287
27293
|
setTimeout(_milliseconds, callback) {
|
|
27288
27294
|
if (callback) this.on("timeout", callback);
|
|
27289
27295
|
return this;
|
|
@@ -27293,13 +27299,16 @@ var VirtualHttpRouter = class {
|
|
|
27293
27299
|
servers = /* @__PURE__ */ new Map();
|
|
27294
27300
|
/** Notified when a server begins listening, for `onServerReady`. */
|
|
27295
27301
|
onListen;
|
|
27302
|
+
onClose;
|
|
27296
27303
|
register(port, server, owner) {
|
|
27297
27304
|
if (this.servers.has(port)) throw Object.assign(new Error(`listen EADDRINUSE: address already in use 0.0.0.0:${port}`), { code: "EADDRINUSE", port });
|
|
27298
27305
|
this.servers.set(port, { server, owner });
|
|
27299
27306
|
this.onListen?.(port);
|
|
27300
27307
|
}
|
|
27301
27308
|
unregister(port, server) {
|
|
27302
|
-
if (this.servers.get(port)?.server
|
|
27309
|
+
if (this.servers.get(port)?.server !== server) return;
|
|
27310
|
+
this.servers.delete(port);
|
|
27311
|
+
this.onClose?.(port);
|
|
27303
27312
|
}
|
|
27304
27313
|
/** Whether anything in this container is listening on `port`. */
|
|
27305
27314
|
activePortsIncludes(port) {
|
|
@@ -27308,6 +27317,9 @@ var VirtualHttpRouter = class {
|
|
|
27308
27317
|
activePorts(owner) {
|
|
27309
27318
|
return [...this.servers].filter(([, item]) => owner === void 0 || item.owner === owner).map(([port]) => port).sort((a, b) => a - b);
|
|
27310
27319
|
}
|
|
27320
|
+
referencedPorts(owner) {
|
|
27321
|
+
return [...this.servers].filter(([, item]) => item.owner === owner && item.server.hasRef()).map(([port]) => port);
|
|
27322
|
+
}
|
|
27311
27323
|
closeOwner(owner) {
|
|
27312
27324
|
for (const { server, owner: value } of [...this.servers.values()]) if (value === owner) server.close();
|
|
27313
27325
|
}
|
|
@@ -28582,6 +28594,102 @@ function toBytes2(data2, encoding) {
|
|
|
28582
28594
|
return typeof data2 === "string" ? Buffer2.from(data2, encoding) : data2;
|
|
28583
28595
|
}
|
|
28584
28596
|
|
|
28597
|
+
// src/runtime/sync-channel.ts
|
|
28598
|
+
var STATE = 0;
|
|
28599
|
+
var LENGTH2 = 1;
|
|
28600
|
+
var MORE = 2;
|
|
28601
|
+
var STATE_IDLE = 0;
|
|
28602
|
+
var STATE_REQUEST = 1;
|
|
28603
|
+
var STATE_RESPONSE = 2;
|
|
28604
|
+
var STATE_CONTINUE = 3;
|
|
28605
|
+
var WAIT_SLICE_MS = 250;
|
|
28606
|
+
function syncChannelUnavailableReason() {
|
|
28607
|
+
if (typeof globalThis.crossOriginIsolated === "boolean" && !globalThis.crossOriginIsolated) {
|
|
28608
|
+
return "The browser host is not cross-origin isolated. Serve the host app over HTTPS or localhost with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. For a built app use sandboxedjs-serve <directory>. Embedded browsers or iframe policies may also prevent isolation.";
|
|
28609
|
+
}
|
|
28610
|
+
if (typeof SharedArrayBuffer !== "function") return "SharedArrayBuffer is unavailable in this host.";
|
|
28611
|
+
if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return "Atomics.wait is unavailable in this host.";
|
|
28612
|
+
return null;
|
|
28613
|
+
}
|
|
28614
|
+
var SyncChannelClosedError = class extends Error {
|
|
28615
|
+
code = "ERR_SYNC_CHANNEL_CLOSED";
|
|
28616
|
+
constructor() {
|
|
28617
|
+
super("the sandbox runtime host went away while a synchronous call was waiting");
|
|
28618
|
+
}
|
|
28619
|
+
};
|
|
28620
|
+
var SyncChannelClient = class {
|
|
28621
|
+
constructor(buffers, wake) {
|
|
28622
|
+
this.wake = wake;
|
|
28623
|
+
this.control = new Int32Array(buffers.control);
|
|
28624
|
+
this.data = new Uint8Array(buffers.data);
|
|
28625
|
+
this.capacity = this.data.length;
|
|
28626
|
+
}
|
|
28627
|
+
wake;
|
|
28628
|
+
control;
|
|
28629
|
+
data;
|
|
28630
|
+
capacity;
|
|
28631
|
+
/** Send a request and block until the whole response has come back. */
|
|
28632
|
+
call(request) {
|
|
28633
|
+
this.send(request);
|
|
28634
|
+
return this.receive();
|
|
28635
|
+
}
|
|
28636
|
+
/** Write the request across, a window at a time. */
|
|
28637
|
+
send(request) {
|
|
28638
|
+
let offset2 = 0;
|
|
28639
|
+
for (; ; ) {
|
|
28640
|
+
const size = Math.min(this.capacity, request.length - offset2);
|
|
28641
|
+
this.data.set(request.subarray(offset2, offset2 + size), 0);
|
|
28642
|
+
offset2 += size;
|
|
28643
|
+
Atomics.store(this.control, LENGTH2, size);
|
|
28644
|
+
Atomics.store(this.control, MORE, offset2 < request.length ? 1 : 0);
|
|
28645
|
+
this.publish(STATE_REQUEST);
|
|
28646
|
+
const next = this.waitWhile(STATE_REQUEST);
|
|
28647
|
+
if (next === STATE_RESPONSE) return;
|
|
28648
|
+
if (next !== STATE_CONTINUE) throw new SyncChannelClosedError();
|
|
28649
|
+
}
|
|
28650
|
+
}
|
|
28651
|
+
/** Read the response back, a window at a time. */
|
|
28652
|
+
receive() {
|
|
28653
|
+
const parts = [];
|
|
28654
|
+
let total = 0;
|
|
28655
|
+
for (; ; ) {
|
|
28656
|
+
const size = Atomics.load(this.control, LENGTH2);
|
|
28657
|
+
parts.push(this.data.slice(0, size));
|
|
28658
|
+
total += size;
|
|
28659
|
+
if (Atomics.load(this.control, MORE) === 0) break;
|
|
28660
|
+
this.publish(STATE_CONTINUE);
|
|
28661
|
+
if (this.waitWhile(STATE_CONTINUE) !== STATE_RESPONSE) throw new SyncChannelClosedError();
|
|
28662
|
+
}
|
|
28663
|
+
Atomics.store(this.control, STATE, STATE_IDLE);
|
|
28664
|
+
if (parts.length === 1) return parts[0];
|
|
28665
|
+
const joined = new Uint8Array(total);
|
|
28666
|
+
let at2 = 0;
|
|
28667
|
+
for (const part of parts) {
|
|
28668
|
+
joined.set(part, at2);
|
|
28669
|
+
at2 += part.length;
|
|
28670
|
+
}
|
|
28671
|
+
return joined;
|
|
28672
|
+
}
|
|
28673
|
+
publish(state) {
|
|
28674
|
+
Atomics.store(this.control, STATE, state);
|
|
28675
|
+
Atomics.notify(this.control, STATE);
|
|
28676
|
+
this.wake();
|
|
28677
|
+
}
|
|
28678
|
+
/**
|
|
28679
|
+
* Block until the state moves off `state`.
|
|
28680
|
+
*
|
|
28681
|
+
* Waking periodically rather than waiting forever is what lets a torn-down
|
|
28682
|
+
* host release a worker instead of stranding it.
|
|
28683
|
+
*/
|
|
28684
|
+
waitWhile(state) {
|
|
28685
|
+
for (; ; ) {
|
|
28686
|
+
Atomics.wait(this.control, STATE, state, WAIT_SLICE_MS);
|
|
28687
|
+
const current2 = Atomics.load(this.control, STATE);
|
|
28688
|
+
if (current2 !== state) return current2;
|
|
28689
|
+
}
|
|
28690
|
+
}
|
|
28691
|
+
};
|
|
28692
|
+
|
|
28585
28693
|
// src/runtime/child-process-module.ts
|
|
28586
28694
|
var import_events2 = __toESM(require_events());
|
|
28587
28695
|
var import_stream_browserify3 = __toESM(require_stream_browserify());
|
|
@@ -28789,7 +28897,7 @@ function unavailable(name) {
|
|
|
28789
28897
|
const list = Array.isArray(args[1]) ? args[1].map(String) : [];
|
|
28790
28898
|
const command = file ? [file, ...list].join(" ") : void 0;
|
|
28791
28899
|
const error = new Error(
|
|
28792
|
-
`child_process.${name} is not supported in the sandboxed runtime` + (command ? ` (tried to run: ${command})` : "") + `: the caller, the child and the event loop share one thread, so blocking the caller would also stop the child.
|
|
28900
|
+
`child_process.${name} is not supported in the sandboxed runtime` + (command ? ` (tried to run: ${command})` : "") + `: the caller, the child and the event loop share one thread, so blocking the caller would also stop the child. Enable the SandboxedJs worker runtime with createContainer({ isolation: "worker" }) and fix any boot prerequisite it reports. ` + (syncChannelUnavailableReason() ?? "This process is using the realm runtime, which cannot provide synchronous child processes.")
|
|
28793
28901
|
);
|
|
28794
28902
|
error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
|
|
28795
28903
|
throw error;
|
|
@@ -29115,6 +29223,11 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
29115
29223
|
edit(sequence, key) {
|
|
29116
29224
|
if (this.closed) return;
|
|
29117
29225
|
const name = key.name;
|
|
29226
|
+
if (key.ctrl && name === "c") {
|
|
29227
|
+
if (this.listenerCount("SIGINT") > 0) this.emit("SIGINT");
|
|
29228
|
+
else this.close();
|
|
29229
|
+
return;
|
|
29230
|
+
}
|
|
29118
29231
|
if (name === "return" || name === "enter") {
|
|
29119
29232
|
const value = this.line;
|
|
29120
29233
|
this.line = "";
|
|
@@ -29210,7 +29323,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
29210
29323
|
};
|
|
29211
29324
|
const createInterface = (options, output) => {
|
|
29212
29325
|
if (options && typeof options === "object" && !options.on) {
|
|
29213
|
-
const resolvedOutput2 = options.output
|
|
29326
|
+
const resolvedOutput2 = options.output;
|
|
29214
29327
|
const instance = new Interface(
|
|
29215
29328
|
options.input ?? defaultInput(),
|
|
29216
29329
|
resolvedOutput2,
|
|
@@ -29219,7 +29332,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
29219
29332
|
if (typeof options.prompt === "string") instance.setPrompt(options.prompt);
|
|
29220
29333
|
return instance;
|
|
29221
29334
|
}
|
|
29222
|
-
const resolvedOutput = output
|
|
29335
|
+
const resolvedOutput = output;
|
|
29223
29336
|
return new Interface(options ?? defaultInput(), resolvedOutput, isTerminal(void 0, resolvedOutput));
|
|
29224
29337
|
};
|
|
29225
29338
|
const noop = () => {
|
|
@@ -29492,7 +29605,7 @@ function createCoreModules(options) {
|
|
|
29492
29605
|
"stream/promises": createStreamPromises(),
|
|
29493
29606
|
string_decoder: import_string_decoder.default,
|
|
29494
29607
|
timers: { ...import_timers_browserify.default, ...timers.api },
|
|
29495
|
-
"timers/promises": createTimerPromises(),
|
|
29608
|
+
"timers/promises": createTimerPromises(timers.api),
|
|
29496
29609
|
tty: { isatty: () => false, ReadStream: import_stream_browserify4.default.Readable, WriteStream: import_stream_browserify4.default.Writable },
|
|
29497
29610
|
url: url_module_default,
|
|
29498
29611
|
util: util_module_default,
|
|
@@ -30237,10 +30350,10 @@ function createTrackedTimers() {
|
|
|
30237
30350
|
cancelAll
|
|
30238
30351
|
};
|
|
30239
30352
|
}
|
|
30240
|
-
function createTimerPromises() {
|
|
30353
|
+
function createTimerPromises(timers) {
|
|
30241
30354
|
return {
|
|
30242
|
-
setTimeout: (delay, value) => new Promise((resolve2) => setTimeout(resolve2, delay, value)),
|
|
30243
|
-
setImmediate: (value) => new Promise((resolve2) =>
|
|
30355
|
+
setTimeout: (delay, value) => new Promise((resolve2) => timers.setTimeout(resolve2, delay, value)),
|
|
30356
|
+
setImmediate: (value) => new Promise((resolve2) => timers.setImmediate(resolve2, value))
|
|
30244
30357
|
};
|
|
30245
30358
|
}
|
|
30246
30359
|
function createAsyncHooksModule() {
|
|
@@ -30491,94 +30604,6 @@ var RemoteVolume = class {
|
|
|
30491
30604
|
}
|
|
30492
30605
|
};
|
|
30493
30606
|
|
|
30494
|
-
// src/runtime/sync-channel.ts
|
|
30495
|
-
var STATE = 0;
|
|
30496
|
-
var LENGTH2 = 1;
|
|
30497
|
-
var MORE = 2;
|
|
30498
|
-
var STATE_IDLE = 0;
|
|
30499
|
-
var STATE_REQUEST = 1;
|
|
30500
|
-
var STATE_RESPONSE = 2;
|
|
30501
|
-
var STATE_CONTINUE = 3;
|
|
30502
|
-
var WAIT_SLICE_MS = 250;
|
|
30503
|
-
var SyncChannelClosedError = class extends Error {
|
|
30504
|
-
code = "ERR_SYNC_CHANNEL_CLOSED";
|
|
30505
|
-
constructor() {
|
|
30506
|
-
super("the sandbox runtime host went away while a synchronous call was waiting");
|
|
30507
|
-
}
|
|
30508
|
-
};
|
|
30509
|
-
var SyncChannelClient = class {
|
|
30510
|
-
constructor(buffers, wake) {
|
|
30511
|
-
this.wake = wake;
|
|
30512
|
-
this.control = new Int32Array(buffers.control);
|
|
30513
|
-
this.data = new Uint8Array(buffers.data);
|
|
30514
|
-
this.capacity = this.data.length;
|
|
30515
|
-
}
|
|
30516
|
-
wake;
|
|
30517
|
-
control;
|
|
30518
|
-
data;
|
|
30519
|
-
capacity;
|
|
30520
|
-
/** Send a request and block until the whole response has come back. */
|
|
30521
|
-
call(request) {
|
|
30522
|
-
this.send(request);
|
|
30523
|
-
return this.receive();
|
|
30524
|
-
}
|
|
30525
|
-
/** Write the request across, a window at a time. */
|
|
30526
|
-
send(request) {
|
|
30527
|
-
let offset2 = 0;
|
|
30528
|
-
for (; ; ) {
|
|
30529
|
-
const size = Math.min(this.capacity, request.length - offset2);
|
|
30530
|
-
this.data.set(request.subarray(offset2, offset2 + size), 0);
|
|
30531
|
-
offset2 += size;
|
|
30532
|
-
Atomics.store(this.control, LENGTH2, size);
|
|
30533
|
-
Atomics.store(this.control, MORE, offset2 < request.length ? 1 : 0);
|
|
30534
|
-
this.publish(STATE_REQUEST);
|
|
30535
|
-
const next = this.waitWhile(STATE_REQUEST);
|
|
30536
|
-
if (next === STATE_RESPONSE) return;
|
|
30537
|
-
if (next !== STATE_CONTINUE) throw new SyncChannelClosedError();
|
|
30538
|
-
}
|
|
30539
|
-
}
|
|
30540
|
-
/** Read the response back, a window at a time. */
|
|
30541
|
-
receive() {
|
|
30542
|
-
const parts = [];
|
|
30543
|
-
let total = 0;
|
|
30544
|
-
for (; ; ) {
|
|
30545
|
-
const size = Atomics.load(this.control, LENGTH2);
|
|
30546
|
-
parts.push(this.data.slice(0, size));
|
|
30547
|
-
total += size;
|
|
30548
|
-
if (Atomics.load(this.control, MORE) === 0) break;
|
|
30549
|
-
this.publish(STATE_CONTINUE);
|
|
30550
|
-
if (this.waitWhile(STATE_CONTINUE) !== STATE_RESPONSE) throw new SyncChannelClosedError();
|
|
30551
|
-
}
|
|
30552
|
-
Atomics.store(this.control, STATE, STATE_IDLE);
|
|
30553
|
-
if (parts.length === 1) return parts[0];
|
|
30554
|
-
const joined = new Uint8Array(total);
|
|
30555
|
-
let at2 = 0;
|
|
30556
|
-
for (const part of parts) {
|
|
30557
|
-
joined.set(part, at2);
|
|
30558
|
-
at2 += part.length;
|
|
30559
|
-
}
|
|
30560
|
-
return joined;
|
|
30561
|
-
}
|
|
30562
|
-
publish(state) {
|
|
30563
|
-
Atomics.store(this.control, STATE, state);
|
|
30564
|
-
Atomics.notify(this.control, STATE);
|
|
30565
|
-
this.wake();
|
|
30566
|
-
}
|
|
30567
|
-
/**
|
|
30568
|
-
* Block until the state moves off `state`.
|
|
30569
|
-
*
|
|
30570
|
-
* Waking periodically rather than waiting forever is what lets a torn-down
|
|
30571
|
-
* host release a worker instead of stranding it.
|
|
30572
|
-
*/
|
|
30573
|
-
waitWhile(state) {
|
|
30574
|
-
for (; ; ) {
|
|
30575
|
-
Atomics.wait(this.control, STATE, state, WAIT_SLICE_MS);
|
|
30576
|
-
const current2 = Atomics.load(this.control, STATE);
|
|
30577
|
-
if (current2 !== state) return current2;
|
|
30578
|
-
}
|
|
30579
|
-
}
|
|
30580
|
-
};
|
|
30581
|
-
|
|
30582
30607
|
// src/runtime/sync-syscalls.ts
|
|
30583
30608
|
var SPAWN_OP = "spawnSync";
|
|
30584
30609
|
function spawnSyncOverChannel(channel, request) {
|
|
@@ -30655,6 +30680,7 @@ async function run(port, start) {
|
|
|
30655
30680
|
const router = new VirtualHttpRouter();
|
|
30656
30681
|
const owner = "worker";
|
|
30657
30682
|
router.onListen = (listening) => port.postMessage({ type: "listen", port: listening });
|
|
30683
|
+
router.onClose = (closed) => port.postMessage({ type: "close-port", port: closed });
|
|
30658
30684
|
let requestedExit = 0;
|
|
30659
30685
|
let engine;
|
|
30660
30686
|
let finished = false;
|
|
@@ -30683,6 +30709,8 @@ async function run(port, start) {
|
|
|
30683
30709
|
const finish = (code) => {
|
|
30684
30710
|
if (finished) return;
|
|
30685
30711
|
finished = true;
|
|
30712
|
+
core.cancelTimers();
|
|
30713
|
+
router.closeOwner(owner);
|
|
30686
30714
|
port.postMessage({ type: "exit", code });
|
|
30687
30715
|
};
|
|
30688
30716
|
port.onMessage((message) => {
|
|
@@ -30704,7 +30732,6 @@ async function run(port, start) {
|
|
|
30704
30732
|
try {
|
|
30705
30733
|
await engine.run(start.script);
|
|
30706
30734
|
await settle(router, owner, core, () => finished);
|
|
30707
|
-
if (router.activePorts(owner).length) return;
|
|
30708
30735
|
finish(requestedExit);
|
|
30709
30736
|
} catch (error) {
|
|
30710
30737
|
if (error instanceof ProcessExit) {
|
|
@@ -30716,18 +30743,12 @@ async function run(port, start) {
|
|
|
30716
30743
|
}
|
|
30717
30744
|
}
|
|
30718
30745
|
async function settle(router, owner, core, killed) {
|
|
30719
|
-
|
|
30720
|
-
|
|
30721
|
-
|
|
30722
|
-
|
|
30723
|
-
|
|
30724
|
-
|
|
30725
|
-
while (!router.activePorts(owner).length && !killed() && Date.now() < deadline) {
|
|
30726
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
30727
|
-
}
|
|
30728
|
-
}
|
|
30729
|
-
while (!router.activePorts(owner).length && !killed() && (core.pendingHandles() > 0 || core.pendingRequests() > 0 || core.readingStdin())) {
|
|
30730
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
30746
|
+
let idleTurns = 0;
|
|
30747
|
+
while (!killed()) {
|
|
30748
|
+
const busy = router.referencedPorts(owner).length > 0 || core.pendingHandles() > 0 || core.pendingRequests() > 0 || core.readingStdin();
|
|
30749
|
+
idleTurns = busy ? 0 : idleTurns + 1;
|
|
30750
|
+
if (idleTurns >= DRAIN_TURNS) return;
|
|
30751
|
+
await new Promise((resolve2) => setTimeout(resolve2, busy ? 5 : 0));
|
|
30731
30752
|
}
|
|
30732
30753
|
}
|
|
30733
30754
|
async function answer(port, router, message) {
|