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.
@@ -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 === server) this.servers.delete(port);
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. Run it with the asynchronous form (spawn/exec/execFile), or run the command from the container shell.`
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 ?? defaultOutput();
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 ?? defaultOutput();
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) => setTimeout(resolve2, 0, value))
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
- for (let turn = 0; turn < DRAIN_TURNS; turn++) {
30720
- if (router.activePorts(owner).length || killed()) return;
30721
- await new Promise((resolve2) => setTimeout(resolve2, 0));
30722
- }
30723
- if (core.pendingHandles() === 0 && core.pendingRequests() === 0 && core.pendingUnrefed() > 0 && !core.readingStdin()) {
30724
- const deadline = Date.now() + 1e3;
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) {