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/index.cjs
CHANGED
|
@@ -4795,7 +4795,15 @@ var Pipe = class _Pipe {
|
|
|
4795
4795
|
* whole line on every keystroke — so the terminal must stop echoing, or
|
|
4796
4796
|
* every character appears twice.
|
|
4797
4797
|
*/
|
|
4798
|
-
|
|
4798
|
+
raw = false;
|
|
4799
|
+
onRawMode;
|
|
4800
|
+
get rawMode() {
|
|
4801
|
+
return this.raw;
|
|
4802
|
+
}
|
|
4803
|
+
set rawMode(enabled) {
|
|
4804
|
+
this.raw = enabled;
|
|
4805
|
+
this.onRawMode?.(enabled);
|
|
4806
|
+
}
|
|
4799
4807
|
columns;
|
|
4800
4808
|
rows;
|
|
4801
4809
|
get closed() {
|
|
@@ -5923,13 +5931,19 @@ var Kernel = class {
|
|
|
5923
5931
|
stderr: opts.stderr ?? new NullOutput()
|
|
5924
5932
|
}
|
|
5925
5933
|
});
|
|
5934
|
+
const cancelWithParent = () => {
|
|
5935
|
+
if (parent?.termSignal) proc.deliver(parent.termSignal);
|
|
5936
|
+
};
|
|
5937
|
+
parent?.signal.addEventListener("abort", cancelWithParent, { once: true });
|
|
5938
|
+
if (parent?.signal.aborted) cancelWithParent();
|
|
5939
|
+
void proc.wait().then(() => parent?.signal.removeEventListener("abort", cancelWithParent));
|
|
5926
5940
|
let timer;
|
|
5927
5941
|
if (opts.timeoutMs !== void 0) {
|
|
5928
5942
|
timer = setTimeout(() => {
|
|
5929
5943
|
proc.deliver("SIGKILL");
|
|
5930
5944
|
}, opts.timeoutMs);
|
|
5931
5945
|
}
|
|
5932
|
-
void this.dispatch(proc).then((code) => {
|
|
5946
|
+
void (proc.signal.aborted ? Promise.resolve(proc.exitCode ?? 137) : this.dispatch(proc)).then((code) => {
|
|
5933
5947
|
proc.exit(code);
|
|
5934
5948
|
}).catch((e) => {
|
|
5935
5949
|
try {
|
|
@@ -7921,6 +7935,22 @@ var Shell = class _Shell {
|
|
|
7921
7935
|
}
|
|
7922
7936
|
return this.lastStatus;
|
|
7923
7937
|
}
|
|
7938
|
+
/** Signal the current terminal pipeline without terminating the interactive shell. */
|
|
7939
|
+
interruptForeground(signal, stdin) {
|
|
7940
|
+
const processes = this.kernel.procs.list().filter((process2) => process2.running);
|
|
7941
|
+
const selected = new Set(processes.filter((process2) => process2.ppid === this.proc.pid && process2.stdin === stdin).map((process2) => process2.pid));
|
|
7942
|
+
for (let changed = true; changed; ) {
|
|
7943
|
+
changed = false;
|
|
7944
|
+
const outputs = processes.filter((process2) => selected.has(process2.pid)).flatMap((process2) => [process2.stdout, process2.stderr]);
|
|
7945
|
+
for (const process2 of processes) {
|
|
7946
|
+
if (!selected.has(process2.pid) && (selected.has(process2.ppid) || outputs.some((output) => output === process2.stdin))) {
|
|
7947
|
+
selected.add(process2.pid);
|
|
7948
|
+
changed = true;
|
|
7949
|
+
}
|
|
7950
|
+
}
|
|
7951
|
+
}
|
|
7952
|
+
for (const process2 of processes) if (selected.has(process2.pid)) process2.deliver(signal);
|
|
7953
|
+
}
|
|
7924
7954
|
get isExiting() {
|
|
7925
7955
|
return this.exiting;
|
|
7926
7956
|
}
|
|
@@ -8570,6 +8600,7 @@ sys ${fmt2(Math.floor(ms * 0.2))}
|
|
|
8570
8600
|
for (const assignment of assignments) {
|
|
8571
8601
|
env2[assignment.name] = await expandWordToString(assignment.value, this.expandContext());
|
|
8572
8602
|
}
|
|
8603
|
+
if (this.proc.signal.aborted) return this.proc.exitCode ?? 137;
|
|
8573
8604
|
const proc = this.kernel.spawn(words, {
|
|
8574
8605
|
cwd: this.cwd,
|
|
8575
8606
|
env: env2,
|
|
@@ -8827,7 +8858,7 @@ async function runShell(ctxName, ctx) {
|
|
|
8827
8858
|
const args = ctx.args.slice();
|
|
8828
8859
|
let command;
|
|
8829
8860
|
let scriptPath;
|
|
8830
|
-
let
|
|
8861
|
+
let readStdin = false;
|
|
8831
8862
|
let interactive = false;
|
|
8832
8863
|
let login = ctxName.startsWith("-");
|
|
8833
8864
|
const options = {};
|
|
@@ -8840,7 +8871,7 @@ async function runShell(ctxName, ctx) {
|
|
|
8840
8871
|
break;
|
|
8841
8872
|
}
|
|
8842
8873
|
if (arg === "-s") {
|
|
8843
|
-
|
|
8874
|
+
readStdin = true;
|
|
8844
8875
|
continue;
|
|
8845
8876
|
}
|
|
8846
8877
|
if (arg === "-i") {
|
|
@@ -8877,7 +8908,7 @@ async function runShell(ctxName, ctx) {
|
|
|
8877
8908
|
break;
|
|
8878
8909
|
}
|
|
8879
8910
|
const rest = args.slice(i);
|
|
8880
|
-
if (command === void 0 && !
|
|
8911
|
+
if (command === void 0 && !readStdin && rest.length > 0) {
|
|
8881
8912
|
scriptPath = rest[0];
|
|
8882
8913
|
}
|
|
8883
8914
|
const positional = command !== void 0 ? rest.slice(1) : scriptPath !== void 0 ? rest.slice(1) : rest;
|
|
@@ -17772,6 +17803,143 @@ function mountContainerFs(FS, opts) {
|
|
|
17772
17803
|
}
|
|
17773
17804
|
}
|
|
17774
17805
|
|
|
17806
|
+
// src/runtime/python-syscalls.ts
|
|
17807
|
+
var EMPTY = new Uint8Array(0);
|
|
17808
|
+
function createStdinHost(ctx) {
|
|
17809
|
+
let pending = EMPTY;
|
|
17810
|
+
let eof = false;
|
|
17811
|
+
const take = (size) => {
|
|
17812
|
+
const n = Math.min(size, pending.length);
|
|
17813
|
+
const out = pending.subarray(0, n);
|
|
17814
|
+
pending = pending.subarray(n);
|
|
17815
|
+
return out;
|
|
17816
|
+
};
|
|
17817
|
+
return {
|
|
17818
|
+
/**
|
|
17819
|
+
* Drain a non-interactive stdin up front.
|
|
17820
|
+
*
|
|
17821
|
+
* A pipe ends, so reading it eagerly costs nothing and leaves every read
|
|
17822
|
+
* answerable without suspending — which is what keeps piped input working
|
|
17823
|
+
* on hosts with no stack-switching. A terminal never ends, so priming one
|
|
17824
|
+
* would hang before the program had printed its prompt.
|
|
17825
|
+
*/
|
|
17826
|
+
async prime() {
|
|
17827
|
+
if (ctx.stdin.isTTY || ctx.stdin.interactive) return;
|
|
17828
|
+
try {
|
|
17829
|
+
pending = await ctx.stdin.readAll();
|
|
17830
|
+
} catch {
|
|
17831
|
+
pending = EMPTY;
|
|
17832
|
+
}
|
|
17833
|
+
eof = true;
|
|
17834
|
+
},
|
|
17835
|
+
buffered(size) {
|
|
17836
|
+
if (pending.length > 0) return take(size);
|
|
17837
|
+
return eof ? EMPTY : void 0;
|
|
17838
|
+
},
|
|
17839
|
+
async read(size) {
|
|
17840
|
+
if (pending.length > 0) return take(size);
|
|
17841
|
+
if (eof) return EMPTY;
|
|
17842
|
+
const chunk = await ctx.stdin.read(size);
|
|
17843
|
+
if (chunk === null || chunk.length === 0) {
|
|
17844
|
+
eof = true;
|
|
17845
|
+
return EMPTY;
|
|
17846
|
+
}
|
|
17847
|
+
pending = chunk;
|
|
17848
|
+
return take(size);
|
|
17849
|
+
},
|
|
17850
|
+
isatty() {
|
|
17851
|
+
return Boolean(ctx.stdin.isTTY);
|
|
17852
|
+
}
|
|
17853
|
+
};
|
|
17854
|
+
}
|
|
17855
|
+
var BRIDGE_PY = `
|
|
17856
|
+
import builtins, io, sys
|
|
17857
|
+
import _sbx_host as _host
|
|
17858
|
+
from pyodide.ffi import can_run_sync, run_sync
|
|
17859
|
+
|
|
17860
|
+
|
|
17861
|
+
def _to_bytes(value):
|
|
17862
|
+
if value is None:
|
|
17863
|
+
return b""
|
|
17864
|
+
to_bytes = getattr(value, "to_bytes", None)
|
|
17865
|
+
if to_bytes is not None:
|
|
17866
|
+
return to_bytes()
|
|
17867
|
+
return bytes(value.to_py())
|
|
17868
|
+
|
|
17869
|
+
|
|
17870
|
+
class _SbxStdin(io.RawIOBase):
|
|
17871
|
+
"""The container's standard input, as a blocking raw stream."""
|
|
17872
|
+
|
|
17873
|
+
def readable(self):
|
|
17874
|
+
return True
|
|
17875
|
+
|
|
17876
|
+
def fileno(self):
|
|
17877
|
+
return 0
|
|
17878
|
+
|
|
17879
|
+
def isatty(self):
|
|
17880
|
+
return bool(_host.isatty())
|
|
17881
|
+
|
|
17882
|
+
def readinto(self, target):
|
|
17883
|
+
want = len(target)
|
|
17884
|
+
if want == 0:
|
|
17885
|
+
return 0
|
|
17886
|
+
ready = _host.buffered(want)
|
|
17887
|
+
if ready is None:
|
|
17888
|
+
if not can_run_sync():
|
|
17889
|
+
raise OSError(
|
|
17890
|
+
"this host cannot wait for interactive input: WebAssembly "
|
|
17891
|
+
"stack switching (JSPI) is unavailable. Pipe the input in, "
|
|
17892
|
+
"or use a browser or Node build that supports it."
|
|
17893
|
+
)
|
|
17894
|
+
ready = run_sync(_host.read(want))
|
|
17895
|
+
data = _to_bytes(ready)
|
|
17896
|
+
target[: len(data)] = data
|
|
17897
|
+
return len(data)
|
|
17898
|
+
|
|
17899
|
+
|
|
17900
|
+
def _install():
|
|
17901
|
+
stdin = io.TextIOWrapper(
|
|
17902
|
+
io.BufferedReader(_SbxStdin()), encoding="utf-8", errors="replace", line_buffering=True
|
|
17903
|
+
)
|
|
17904
|
+
sys.stdin = stdin
|
|
17905
|
+
sys.__stdin__ = stdin
|
|
17906
|
+
|
|
17907
|
+
def input(prompt=""):
|
|
17908
|
+
# CPython writes the prompt to stdout and strips exactly one trailing
|
|
17909
|
+
# newline; anything else changes what a program reads back.
|
|
17910
|
+
if prompt != "":
|
|
17911
|
+
sys.stdout.write(str(prompt))
|
|
17912
|
+
sys.stdout.flush()
|
|
17913
|
+
line = sys.stdin.readline()
|
|
17914
|
+
if not line:
|
|
17915
|
+
raise EOFError("EOF when reading a line")
|
|
17916
|
+
if line.endswith("\\n"):
|
|
17917
|
+
line = line[:-1]
|
|
17918
|
+
if line.endswith("\\r"):
|
|
17919
|
+
line = line[:-1]
|
|
17920
|
+
return line
|
|
17921
|
+
|
|
17922
|
+
builtins.input = input
|
|
17923
|
+
|
|
17924
|
+
|
|
17925
|
+
_install()
|
|
17926
|
+
`;
|
|
17927
|
+
function installSyscallBridge(py) {
|
|
17928
|
+
const slot = py.__sbxStdin ??= { active: null };
|
|
17929
|
+
py.registerJsModule("_sbx_host", {
|
|
17930
|
+
/* Not `?.` and `??`: `buffered` returns undefined to mean "you will have
|
|
17931
|
+
* to wait", and a nullish default would quietly turn that into end-of-file —
|
|
17932
|
+
* which is exactly the silent EOF this bridge exists to remove. */
|
|
17933
|
+
buffered: (size) => slot.active ? slot.active.buffered(size) : EMPTY,
|
|
17934
|
+
read: (size) => slot.active ? slot.active.read(size) : Promise.resolve(EMPTY),
|
|
17935
|
+
isatty: () => slot.active?.isatty() ?? false
|
|
17936
|
+
});
|
|
17937
|
+
py.runPython(BRIDGE_PY);
|
|
17938
|
+
}
|
|
17939
|
+
function bindStdin(py, host2) {
|
|
17940
|
+
py.__sbxStdin.active = host2;
|
|
17941
|
+
}
|
|
17942
|
+
|
|
17775
17943
|
// src/runtime/cpython.ts
|
|
17776
17944
|
var RESERVED_FOR_INTERPRETER = /* @__PURE__ */ new Set(["lib", "dev", "proc"]);
|
|
17777
17945
|
var pyodideModule = null;
|
|
@@ -17844,6 +18012,7 @@ async function interpreterFor(ctx) {
|
|
|
17844
18012
|
});
|
|
17845
18013
|
mountContainerDirs(py, ctx);
|
|
17846
18014
|
installRunner(py);
|
|
18015
|
+
installSyscallBridge(py);
|
|
17847
18016
|
return py;
|
|
17848
18017
|
})();
|
|
17849
18018
|
interpreters.set(ctx.vfs, starting);
|
|
@@ -17938,7 +18107,7 @@ function reportError(ctx, error) {
|
|
|
17938
18107
|
`);
|
|
17939
18108
|
return 1;
|
|
17940
18109
|
}
|
|
17941
|
-
async function runCPythonProgram(ctx, source, argv, scriptDir
|
|
18110
|
+
async function runCPythonProgram(ctx, source, argv, scriptDir = null) {
|
|
17942
18111
|
let py;
|
|
17943
18112
|
try {
|
|
17944
18113
|
py = await interpreterFor(ctx);
|
|
@@ -17959,7 +18128,6 @@ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
|
|
|
17959
18128
|
});
|
|
17960
18129
|
} catch {
|
|
17961
18130
|
}
|
|
17962
|
-
const encoder8 = new TextEncoder();
|
|
17963
18131
|
const decoder8 = new TextDecoder();
|
|
17964
18132
|
py.setStdout({
|
|
17965
18133
|
write: (buffer) => {
|
|
@@ -17973,20 +18141,17 @@ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
|
|
|
17973
18141
|
return buffer.length;
|
|
17974
18142
|
}
|
|
17975
18143
|
});
|
|
17976
|
-
|
|
17977
|
-
|
|
17978
|
-
|
|
17979
|
-
|
|
17980
|
-
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
|
|
17985
|
-
|
|
17986
|
-
|
|
17987
|
-
} else {
|
|
17988
|
-
py.setStdin({ read: () => 0 });
|
|
17989
|
-
}
|
|
18144
|
+
const stdinHost = createStdinHost(ctx);
|
|
18145
|
+
await stdinHost.prime();
|
|
18146
|
+
bindStdin(py, stdinHost);
|
|
18147
|
+
py.setStdin({
|
|
18148
|
+
read: (buffer) => {
|
|
18149
|
+
const ready = stdinHost.buffered(buffer.length);
|
|
18150
|
+
if (!ready || ready.length === 0) return 0;
|
|
18151
|
+
buffer.set(ready);
|
|
18152
|
+
return ready.length;
|
|
18153
|
+
}
|
|
18154
|
+
});
|
|
17990
18155
|
try {
|
|
17991
18156
|
bootstrap(py, ctx, argv, scriptDir);
|
|
17992
18157
|
} catch (error) {
|
|
@@ -18119,11 +18284,6 @@ function configurePython(options = {}) {
|
|
|
18119
18284
|
configureCPython({ indexURL: options.indexURL, moduleURL: options.pyodideURL });
|
|
18120
18285
|
}
|
|
18121
18286
|
var isPythonAvailable = isCPythonAvailable;
|
|
18122
|
-
async function readStdin(ctx) {
|
|
18123
|
-
if (ctx.stdin.isTTY || ctx.stdin.interactive) return null;
|
|
18124
|
-
const bytes2 = await ctx.stdin.readAll();
|
|
18125
|
-
return bytes2.length ? new TextDecoder().decode(bytes2) : null;
|
|
18126
|
-
}
|
|
18127
18287
|
var python = defineCommand({
|
|
18128
18288
|
name: "python3",
|
|
18129
18289
|
path: "/usr/bin/python3",
|
|
@@ -18172,7 +18332,7 @@ compatible packages can be installed with pip (micropip).`,
|
|
|
18172
18332
|
}
|
|
18173
18333
|
const rest = argv.slice(i);
|
|
18174
18334
|
if (command !== void 0) {
|
|
18175
|
-
return (await runCPythonProgram(ctx, command, ["-c", ...rest], null
|
|
18335
|
+
return (await runCPythonProgram(ctx, command, ["-c", ...rest], null)).exitCode;
|
|
18176
18336
|
}
|
|
18177
18337
|
if (moduleName !== void 0) {
|
|
18178
18338
|
const program = `
|
|
@@ -18184,16 +18344,16 @@ except ImportError:
|
|
|
18184
18344
|
print("/usr/bin/python3: No module named " + ${JSON.stringify(moduleName)}, file=sys.stderr)
|
|
18185
18345
|
raise SystemExit(1)
|
|
18186
18346
|
`;
|
|
18187
|
-
return (await runCPythonProgram(ctx, program, [moduleName, ...rest], null
|
|
18347
|
+
return (await runCPythonProgram(ctx, program, [moduleName, ...rest], null)).exitCode;
|
|
18188
18348
|
}
|
|
18189
18349
|
if (script === void 0) {
|
|
18190
18350
|
if (ctx.stdin.isTTY) return runCPythonRepl(ctx);
|
|
18191
18351
|
const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
|
|
18192
|
-
return source2.trim() ? (await runCPythonProgram(ctx, source2, ["", ...rest]
|
|
18352
|
+
return source2.trim() ? (await runCPythonProgram(ctx, source2, ["", ...rest])).exitCode : 0;
|
|
18193
18353
|
}
|
|
18194
18354
|
if (script === "-") {
|
|
18195
18355
|
const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
|
|
18196
|
-
return (await runCPythonProgram(ctx, source2, ["-", ...rest]
|
|
18356
|
+
return (await runCPythonProgram(ctx, source2, ["-", ...rest])).exitCode;
|
|
18197
18357
|
}
|
|
18198
18358
|
const abs = ctx.path(script);
|
|
18199
18359
|
let source;
|
|
@@ -18204,7 +18364,7 @@ except ImportError:
|
|
|
18204
18364
|
`);
|
|
18205
18365
|
return 2;
|
|
18206
18366
|
}
|
|
18207
|
-
return (await runCPythonProgram(ctx, source, [abs, ...rest], dirname(abs)
|
|
18367
|
+
return (await runCPythonProgram(ctx, source, [abs, ...rest], dirname(abs))).exitCode;
|
|
18208
18368
|
}
|
|
18209
18369
|
});
|
|
18210
18370
|
function printHelp2(ctx) {
|
|
@@ -19716,6 +19876,7 @@ var KernelChildProcess = class {
|
|
|
19716
19876
|
this.stdin.isTTY = true;
|
|
19717
19877
|
this.stdin.interactive = true;
|
|
19718
19878
|
}
|
|
19879
|
+
this.stdin.onRawMode = (enabled) => this.emit("rawmode", enabled);
|
|
19719
19880
|
this.kernel = kernel;
|
|
19720
19881
|
this.cred = cred;
|
|
19721
19882
|
this.pid = pid;
|
|
@@ -19832,7 +19993,7 @@ function installNodeChildProcessBridge(pod, kernel, cred) {
|
|
|
19832
19993
|
const originalSpawn = manager.spawn;
|
|
19833
19994
|
manager.spawn = (config) => {
|
|
19834
19995
|
const resolved = kernel.resolveExecutable(config.command, config.cwd ?? "/", config.env ?? {}, cred);
|
|
19835
|
-
if (resolved?.kind === "builtin"
|
|
19996
|
+
if (resolved?.kind === "builtin") {
|
|
19836
19997
|
return new KernelChildProcess(kernel, cred, config, nextBridgePid++);
|
|
19837
19998
|
}
|
|
19838
19999
|
return originalSpawn.call(manager, config);
|
|
@@ -19843,6 +20004,68 @@ function installNodeChildProcessBridge(pod, kernel, cred) {
|
|
|
19843
20004
|
};
|
|
19844
20005
|
}
|
|
19845
20006
|
|
|
20007
|
+
// src/runtime/host-module-tracker.ts
|
|
20008
|
+
var HostModuleTracker = class {
|
|
20009
|
+
active = 0;
|
|
20010
|
+
disposed = false;
|
|
20011
|
+
dispose() {
|
|
20012
|
+
this.disposed = true;
|
|
20013
|
+
}
|
|
20014
|
+
wrapped = /* @__PURE__ */ new WeakMap();
|
|
20015
|
+
originals = /* @__PURE__ */ new WeakMap();
|
|
20016
|
+
pending = () => this.active;
|
|
20017
|
+
unwrap = (value) => value && (typeof value === "object" || typeof value === "function") ? this.originals.get(value) ?? value : value;
|
|
20018
|
+
wrap(value) {
|
|
20019
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return value;
|
|
20020
|
+
const object = value;
|
|
20021
|
+
if (Array.isArray(value) || ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return value;
|
|
20022
|
+
if (this.wrapped.has(object)) return this.wrapped.get(object);
|
|
20023
|
+
const tracker = this;
|
|
20024
|
+
const proxy = new Proxy(object, {
|
|
20025
|
+
// CommonJS native loaders re-export by assigning exports back onto the
|
|
20026
|
+
// binding object. Never store a process-owned proxy in a shared module.
|
|
20027
|
+
set(target, key, member) {
|
|
20028
|
+
return Reflect.set(target, key, tracker.unwrap(member), target);
|
|
20029
|
+
},
|
|
20030
|
+
defineProperty(target, key, descriptor) {
|
|
20031
|
+
return Reflect.defineProperty(target, key, "value" in descriptor ? { ...descriptor, value: tracker.unwrap(descriptor.value) } : descriptor);
|
|
20032
|
+
},
|
|
20033
|
+
get(target, key) {
|
|
20034
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
|
|
20035
|
+
const member = Reflect.get(target, key, target);
|
|
20036
|
+
if (descriptor && !descriptor.configurable && "value" in descriptor && !descriptor.writable) return member;
|
|
20037
|
+
return typeof member === "function" ? tracker.wrap(member) : member;
|
|
20038
|
+
},
|
|
20039
|
+
apply(target, receiver, args) {
|
|
20040
|
+
const result = Reflect.apply(target, tracker.unwrap(receiver), args.map(tracker.unwrap));
|
|
20041
|
+
if (result && typeof result.then === "function") {
|
|
20042
|
+
tracker.active++;
|
|
20043
|
+
return Promise.resolve(result).then(
|
|
20044
|
+
(value2) => {
|
|
20045
|
+
tracker.active--;
|
|
20046
|
+
return tracker.disposed ? new Promise(() => {
|
|
20047
|
+
}) : tracker.wrap(value2);
|
|
20048
|
+
},
|
|
20049
|
+
(error) => {
|
|
20050
|
+
tracker.active--;
|
|
20051
|
+
if (tracker.disposed) return new Promise(() => {
|
|
20052
|
+
});
|
|
20053
|
+
throw error;
|
|
20054
|
+
}
|
|
20055
|
+
);
|
|
20056
|
+
}
|
|
20057
|
+
return tracker.wrap(result);
|
|
20058
|
+
},
|
|
20059
|
+
construct(target, args, newTarget) {
|
|
20060
|
+
return tracker.wrap(Reflect.construct(target, args.map(tracker.unwrap), tracker.unwrap(newTarget)));
|
|
20061
|
+
}
|
|
20062
|
+
});
|
|
20063
|
+
this.wrapped.set(object, proxy);
|
|
20064
|
+
this.originals.set(proxy, object);
|
|
20065
|
+
return proxy;
|
|
20066
|
+
}
|
|
20067
|
+
};
|
|
20068
|
+
|
|
19846
20069
|
// src/runtime/commonjs-engine.ts
|
|
19847
20070
|
init_path();
|
|
19848
20071
|
var HELPERS = {
|
|
@@ -21580,6 +21803,7 @@ var VirtualHttpServer = class extends EventEmitter4__default.default {
|
|
|
21580
21803
|
owner;
|
|
21581
21804
|
listening = false;
|
|
21582
21805
|
portValue = null;
|
|
21806
|
+
referenced = true;
|
|
21583
21807
|
listen(...args) {
|
|
21584
21808
|
const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
|
|
21585
21809
|
const first = args[0];
|
|
@@ -21608,11 +21832,16 @@ var VirtualHttpServer = class extends EventEmitter4__default.default {
|
|
|
21608
21832
|
return this.portValue === null ? null : { address: "0.0.0.0", family: "IPv4", port: this.portValue };
|
|
21609
21833
|
}
|
|
21610
21834
|
ref() {
|
|
21835
|
+
this.referenced = true;
|
|
21611
21836
|
return this;
|
|
21612
21837
|
}
|
|
21613
21838
|
unref() {
|
|
21839
|
+
this.referenced = false;
|
|
21614
21840
|
return this;
|
|
21615
21841
|
}
|
|
21842
|
+
hasRef() {
|
|
21843
|
+
return this.referenced;
|
|
21844
|
+
}
|
|
21616
21845
|
setTimeout(_milliseconds, callback) {
|
|
21617
21846
|
if (callback) this.on("timeout", callback);
|
|
21618
21847
|
return this;
|
|
@@ -21622,13 +21851,16 @@ var VirtualHttpRouter = class {
|
|
|
21622
21851
|
servers = /* @__PURE__ */ new Map();
|
|
21623
21852
|
/** Notified when a server begins listening, for `onServerReady`. */
|
|
21624
21853
|
onListen;
|
|
21854
|
+
onClose;
|
|
21625
21855
|
register(port, server, owner) {
|
|
21626
21856
|
if (this.servers.has(port)) throw Object.assign(new Error(`listen EADDRINUSE: address already in use 0.0.0.0:${port}`), { code: "EADDRINUSE", port });
|
|
21627
21857
|
this.servers.set(port, { server, owner });
|
|
21628
21858
|
this.onListen?.(port);
|
|
21629
21859
|
}
|
|
21630
21860
|
unregister(port, server) {
|
|
21631
|
-
if (this.servers.get(port)?.server
|
|
21861
|
+
if (this.servers.get(port)?.server !== server) return;
|
|
21862
|
+
this.servers.delete(port);
|
|
21863
|
+
this.onClose?.(port);
|
|
21632
21864
|
}
|
|
21633
21865
|
/** Whether anything in this container is listening on `port`. */
|
|
21634
21866
|
activePortsIncludes(port) {
|
|
@@ -21637,6 +21869,9 @@ var VirtualHttpRouter = class {
|
|
|
21637
21869
|
activePorts(owner) {
|
|
21638
21870
|
return [...this.servers].filter(([, item]) => owner === void 0 || item.owner === owner).map(([port]) => port).sort((a, b) => a - b);
|
|
21639
21871
|
}
|
|
21872
|
+
referencedPorts(owner) {
|
|
21873
|
+
return [...this.servers].filter(([, item]) => item.owner === owner && item.server.hasRef()).map(([port]) => port);
|
|
21874
|
+
}
|
|
21640
21875
|
closeOwner(owner) {
|
|
21641
21876
|
for (const { server, owner: value } of [...this.servers.values()]) if (value === owner) server.close();
|
|
21642
21877
|
}
|
|
@@ -22048,6 +22283,108 @@ function hashFor(algorithm) {
|
|
|
22048
22283
|
function toBytes2(data, encoding) {
|
|
22049
22284
|
return typeof data === "string" ? Buffer2.from(data, encoding) : data;
|
|
22050
22285
|
}
|
|
22286
|
+
|
|
22287
|
+
// src/runtime/sync-channel.ts
|
|
22288
|
+
var STATE = 0;
|
|
22289
|
+
var LENGTH = 1;
|
|
22290
|
+
var MORE = 2;
|
|
22291
|
+
var CONTROL_WORDS = 4;
|
|
22292
|
+
var STATE_REQUEST = 1;
|
|
22293
|
+
var STATE_RESPONSE = 2;
|
|
22294
|
+
var STATE_CONTINUE = 3;
|
|
22295
|
+
var STATE_CLOSED = 4;
|
|
22296
|
+
function createSyncChannelBuffers(capacityBytes = 1 << 20) {
|
|
22297
|
+
return {
|
|
22298
|
+
control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
|
|
22299
|
+
data: new SharedArrayBuffer(capacityBytes)
|
|
22300
|
+
};
|
|
22301
|
+
}
|
|
22302
|
+
function syncChannelUnavailableReason() {
|
|
22303
|
+
if (typeof globalThis.crossOriginIsolated === "boolean" && !globalThis.crossOriginIsolated) {
|
|
22304
|
+
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.";
|
|
22305
|
+
}
|
|
22306
|
+
if (typeof SharedArrayBuffer !== "function") return "SharedArrayBuffer is unavailable in this host.";
|
|
22307
|
+
if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return "Atomics.wait is unavailable in this host.";
|
|
22308
|
+
return null;
|
|
22309
|
+
}
|
|
22310
|
+
function syncChannelSupported() {
|
|
22311
|
+
return syncChannelUnavailableReason() === null;
|
|
22312
|
+
}
|
|
22313
|
+
var SyncChannelServer = class {
|
|
22314
|
+
constructor(buffers, handle) {
|
|
22315
|
+
this.handle = handle;
|
|
22316
|
+
this.control = new Int32Array(buffers.control);
|
|
22317
|
+
this.data = new Uint8Array(buffers.data);
|
|
22318
|
+
this.capacity = this.data.length;
|
|
22319
|
+
}
|
|
22320
|
+
handle;
|
|
22321
|
+
control;
|
|
22322
|
+
data;
|
|
22323
|
+
capacity;
|
|
22324
|
+
/** Request chunks gathered so far, and the response still to be sent. */
|
|
22325
|
+
incoming = [];
|
|
22326
|
+
outgoing = null;
|
|
22327
|
+
sent = 0;
|
|
22328
|
+
closed = false;
|
|
22329
|
+
/** Call from the wake message the client sends after each chunk. */
|
|
22330
|
+
async pump() {
|
|
22331
|
+
if (this.closed) return;
|
|
22332
|
+
const state = Atomics.load(this.control, STATE);
|
|
22333
|
+
if (state === STATE_REQUEST) {
|
|
22334
|
+
const size = Atomics.load(this.control, LENGTH);
|
|
22335
|
+
this.incoming.push(this.data.slice(0, size));
|
|
22336
|
+
if (Atomics.load(this.control, MORE) === 1) {
|
|
22337
|
+
this.publish(STATE_CONTINUE);
|
|
22338
|
+
return;
|
|
22339
|
+
}
|
|
22340
|
+
const request = concat3(this.incoming);
|
|
22341
|
+
this.incoming = [];
|
|
22342
|
+
let response;
|
|
22343
|
+
try {
|
|
22344
|
+
response = await this.handle(request);
|
|
22345
|
+
} catch {
|
|
22346
|
+
response = new Uint8Array();
|
|
22347
|
+
}
|
|
22348
|
+
if (this.closed) return;
|
|
22349
|
+
this.outgoing = response;
|
|
22350
|
+
this.sent = 0;
|
|
22351
|
+
this.sendChunk();
|
|
22352
|
+
return;
|
|
22353
|
+
}
|
|
22354
|
+
if (state === STATE_CONTINUE) this.sendChunk();
|
|
22355
|
+
}
|
|
22356
|
+
/** Release a blocked client, e.g. when the container is torn down. */
|
|
22357
|
+
close() {
|
|
22358
|
+
if (this.closed) return;
|
|
22359
|
+
this.closed = true;
|
|
22360
|
+
Atomics.store(this.control, STATE, STATE_CLOSED);
|
|
22361
|
+
Atomics.notify(this.control, STATE);
|
|
22362
|
+
}
|
|
22363
|
+
sendChunk() {
|
|
22364
|
+
const payload = this.outgoing ?? new Uint8Array();
|
|
22365
|
+
const size = Math.min(this.capacity, payload.length - this.sent);
|
|
22366
|
+
this.data.set(payload.subarray(this.sent, this.sent + size), 0);
|
|
22367
|
+
this.sent += size;
|
|
22368
|
+
Atomics.store(this.control, LENGTH, size);
|
|
22369
|
+
Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
|
|
22370
|
+
this.publish(STATE_RESPONSE);
|
|
22371
|
+
}
|
|
22372
|
+
publish(state) {
|
|
22373
|
+
Atomics.store(this.control, STATE, state);
|
|
22374
|
+
Atomics.notify(this.control, STATE);
|
|
22375
|
+
}
|
|
22376
|
+
};
|
|
22377
|
+
function concat3(parts) {
|
|
22378
|
+
if (parts.length === 1) return parts[0];
|
|
22379
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
22380
|
+
const joined = new Uint8Array(total);
|
|
22381
|
+
let at = 0;
|
|
22382
|
+
for (const part of parts) {
|
|
22383
|
+
joined.set(part, at);
|
|
22384
|
+
at += part.length;
|
|
22385
|
+
}
|
|
22386
|
+
return joined;
|
|
22387
|
+
}
|
|
22051
22388
|
var ChildProcess = class extends EventEmitter4__default.default {
|
|
22052
22389
|
stdout = new streamModule4__default.default.PassThrough();
|
|
22053
22390
|
stderr = new streamModule4__default.default.PassThrough();
|
|
@@ -22252,7 +22589,7 @@ function unavailable(name) {
|
|
|
22252
22589
|
const list = Array.isArray(args[1]) ? args[1].map(String) : [];
|
|
22253
22590
|
const command = file3 ? [file3, ...list].join(" ") : void 0;
|
|
22254
22591
|
const error = new Error(
|
|
22255
|
-
`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.
|
|
22592
|
+
`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.")
|
|
22256
22593
|
);
|
|
22257
22594
|
error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
|
|
22258
22595
|
throw error;
|
|
@@ -22540,6 +22877,11 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
22540
22877
|
edit(sequence, key) {
|
|
22541
22878
|
if (this.closed) return;
|
|
22542
22879
|
const name = key.name;
|
|
22880
|
+
if (key.ctrl && name === "c") {
|
|
22881
|
+
if (this.listenerCount("SIGINT") > 0) this.emit("SIGINT");
|
|
22882
|
+
else this.close();
|
|
22883
|
+
return;
|
|
22884
|
+
}
|
|
22543
22885
|
if (name === "return" || name === "enter") {
|
|
22544
22886
|
const value = this.line;
|
|
22545
22887
|
this.line = "";
|
|
@@ -22635,7 +22977,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
22635
22977
|
};
|
|
22636
22978
|
const createInterface = (options, output) => {
|
|
22637
22979
|
if (options && typeof options === "object" && !options.on) {
|
|
22638
|
-
const resolvedOutput2 = options.output
|
|
22980
|
+
const resolvedOutput2 = options.output;
|
|
22639
22981
|
const instance = new Interface(
|
|
22640
22982
|
options.input ?? defaultInput(),
|
|
22641
22983
|
resolvedOutput2,
|
|
@@ -22644,7 +22986,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
22644
22986
|
if (typeof options.prompt === "string") instance.setPrompt(options.prompt);
|
|
22645
22987
|
return instance;
|
|
22646
22988
|
}
|
|
22647
|
-
const resolvedOutput = output
|
|
22989
|
+
const resolvedOutput = output;
|
|
22648
22990
|
return new Interface(options ?? defaultInput(), resolvedOutput, isTerminal(void 0, resolvedOutput));
|
|
22649
22991
|
};
|
|
22650
22992
|
const noop = () => {
|
|
@@ -22913,7 +23255,7 @@ function createCoreModules(options) {
|
|
|
22913
23255
|
"stream/promises": createStreamPromises(),
|
|
22914
23256
|
string_decoder: stringDecoderModule__default.default,
|
|
22915
23257
|
timers: { ...timersModule__default.default, ...timers.api },
|
|
22916
|
-
"timers/promises": createTimerPromises(),
|
|
23258
|
+
"timers/promises": createTimerPromises(timers.api),
|
|
22917
23259
|
tty: { isatty: () => false, ReadStream: streamModule4__default.default.Readable, WriteStream: streamModule4__default.default.Writable },
|
|
22918
23260
|
url: url_module_default,
|
|
22919
23261
|
util: util_module_default,
|
|
@@ -23658,10 +24000,10 @@ function createTrackedTimers() {
|
|
|
23658
24000
|
cancelAll
|
|
23659
24001
|
};
|
|
23660
24002
|
}
|
|
23661
|
-
function createTimerPromises() {
|
|
24003
|
+
function createTimerPromises(timers) {
|
|
23662
24004
|
return {
|
|
23663
|
-
setTimeout: (delay, value) => new Promise((resolve2) => setTimeout(resolve2, delay, value)),
|
|
23664
|
-
setImmediate: (value) => new Promise((resolve2) =>
|
|
24005
|
+
setTimeout: (delay, value) => new Promise((resolve2) => timers.setTimeout(resolve2, delay, value)),
|
|
24006
|
+
setImmediate: (value) => new Promise((resolve2) => timers.setImmediate(resolve2, value))
|
|
23665
24007
|
};
|
|
23666
24008
|
}
|
|
23667
24009
|
function createAsyncHooksModule() {
|
|
@@ -24369,11 +24711,8 @@ function ensureProcessGlobal() {
|
|
|
24369
24711
|
// src/runtime/host-rolldown.ts
|
|
24370
24712
|
var BUNDLER_HINT = "If this is a bundler pre-bundling the worker away, exclude the binding from dependency optimisation \u2014 in Vite:\n\n optimizeDeps: { exclude: ['@rolldown/binding-wasm32-wasi'] }";
|
|
24371
24713
|
async function loadHostRolldownBinding() {
|
|
24372
|
-
|
|
24373
|
-
|
|
24374
|
-
"Vite 8/Rolldown requires cross-origin isolation. Serve the app with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers."
|
|
24375
|
-
);
|
|
24376
|
-
}
|
|
24714
|
+
const reason = syncChannelUnavailableReason();
|
|
24715
|
+
if (reason) throw new Error(`SandboxedJs threaded WASM unavailable: ${reason}`);
|
|
24377
24716
|
try {
|
|
24378
24717
|
const loaded = await import('@rolldown/binding-wasm32-wasi');
|
|
24379
24718
|
return "__fs" in loaded ? { ...loaded } : loaded.default ?? loaded;
|
|
@@ -24381,9 +24720,9 @@ async function loadHostRolldownBinding() {
|
|
|
24381
24720
|
if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
|
|
24382
24721
|
console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
|
|
24383
24722
|
}
|
|
24384
|
-
const
|
|
24723
|
+
const reason2 = error instanceof Error ? error.message : String(error);
|
|
24385
24724
|
throw new Error(
|
|
24386
|
-
`The optional Rolldown WASI binding could not be loaded: ${
|
|
24725
|
+
`The optional Rolldown WASI binding could not be loaded: ${reason2}` + (typeof window === "undefined" ? "" : `
|
|
24387
24726
|
|
|
24388
24727
|
${BUNDLER_HINT}`),
|
|
24389
24728
|
{ cause: error }
|
|
@@ -24494,6 +24833,8 @@ var LocalProcess = class extends EventEmitter4__default.default {
|
|
|
24494
24833
|
}
|
|
24495
24834
|
/** End the process now, for an asynchronous `process.exit`. */
|
|
24496
24835
|
exitNow(code) {
|
|
24836
|
+
this.killed = true;
|
|
24837
|
+
this.cleanup?.();
|
|
24497
24838
|
this.finish(code);
|
|
24498
24839
|
this.resolveKilled();
|
|
24499
24840
|
}
|
|
@@ -24677,6 +25018,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24677
25018
|
)
|
|
24678
25019
|
};
|
|
24679
25020
|
disposed = false;
|
|
25021
|
+
running = /* @__PURE__ */ new Set();
|
|
24680
25022
|
workdir;
|
|
24681
25023
|
env;
|
|
24682
25024
|
aliases;
|
|
@@ -24718,8 +25060,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24718
25060
|
const cwd = typeof options.cwd === "string" ? options.cwd : this.workdir;
|
|
24719
25061
|
const env2 = { ...this.env, ...isRecord(options.env) ? options.env : {} };
|
|
24720
25062
|
const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
|
|
24721
|
-
|
|
25063
|
+
const process2 = new LocalProcess(async (proc) => {
|
|
24722
25064
|
await this.prepareRolldown(cwd, env2);
|
|
25065
|
+
if (proc.isKilled()) return 137;
|
|
24723
25066
|
const untrack = trackProcess(proc);
|
|
24724
25067
|
let requestedExit = 0;
|
|
24725
25068
|
let engine;
|
|
@@ -24747,7 +25090,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24747
25090
|
onRawMode: (enabled) => proc.emit("rawmode", enabled)
|
|
24748
25091
|
});
|
|
24749
25092
|
proc.acceptInput((data) => core.writeStdin(data), () => core.endStdin());
|
|
25093
|
+
const hostWork = new HostModuleTracker();
|
|
24750
25094
|
proc.onKill(() => {
|
|
25095
|
+
hostWork.dispose();
|
|
24751
25096
|
core.cancelTimers();
|
|
24752
25097
|
this.router.closeOwner(owner);
|
|
24753
25098
|
});
|
|
@@ -24756,21 +25101,22 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24756
25101
|
builtins: core.builtins,
|
|
24757
25102
|
globals: core.globals,
|
|
24758
25103
|
aliases: this.aliases,
|
|
24759
|
-
overrides: this.modules
|
|
25104
|
+
overrides: Object.fromEntries(Object.entries(this.modules).map(([key, value]) => [key, hostWork.wrap(value)]))
|
|
24760
25105
|
});
|
|
24761
25106
|
try {
|
|
24762
|
-
await engine.run(script);
|
|
24763
|
-
await this.settle(owner, core.pendingHandles, core.
|
|
24764
|
-
if (this.router.activePorts(owner).length) {
|
|
24765
|
-
await proc.waitForKill();
|
|
24766
|
-
return 137;
|
|
24767
|
-
}
|
|
25107
|
+
await Promise.race([engine.run(script), proc.waitForKill()]);
|
|
25108
|
+
await this.settle(owner, core.pendingHandles, core.readingStdin, () => core.pendingRequests() + hostWork.pending(), () => proc.isKilled());
|
|
24768
25109
|
return requestedExit;
|
|
24769
25110
|
} finally {
|
|
25111
|
+
hostWork.dispose();
|
|
25112
|
+
core.cancelTimers();
|
|
24770
25113
|
untrack();
|
|
24771
25114
|
this.router.closeOwner(owner);
|
|
24772
25115
|
}
|
|
24773
25116
|
});
|
|
25117
|
+
this.running.add(process2);
|
|
25118
|
+
void process2.completion.then(() => this.running.delete(process2));
|
|
25119
|
+
return process2;
|
|
24774
25120
|
}
|
|
24775
25121
|
/**
|
|
24776
25122
|
* Rolldown's JavaScript API synchronously requires its compiled binding.
|
|
@@ -24806,19 +25152,13 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24806
25152
|
* A plain script that has genuinely finished falls straight through both,
|
|
24807
25153
|
* costing a handful of empty turns.
|
|
24808
25154
|
*/
|
|
24809
|
-
async settle(owner, pendingHandles,
|
|
24810
|
-
|
|
24811
|
-
|
|
24812
|
-
|
|
24813
|
-
|
|
24814
|
-
|
|
24815
|
-
|
|
24816
|
-
while (!this.router.activePorts(owner).length && !killed() && Date.now() < deadline) {
|
|
24817
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
24818
|
-
}
|
|
24819
|
-
}
|
|
24820
|
-
while (!this.router.activePorts(owner).length && !killed() && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
|
|
24821
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
25155
|
+
async settle(owner, pendingHandles, readingStdin, pendingRequests, killed) {
|
|
25156
|
+
let idleTurns = 0;
|
|
25157
|
+
while (!killed()) {
|
|
25158
|
+
const busy = this.router.referencedPorts(owner).length > 0 || pendingHandles() > 0 || pendingRequests() > 0 || readingStdin();
|
|
25159
|
+
idleTurns = busy ? 0 : idleTurns + 1;
|
|
25160
|
+
if (idleTurns >= DRAIN_TURNS) return;
|
|
25161
|
+
await new Promise((resolve2) => setTimeout(resolve2, busy ? 5 : 0));
|
|
24822
25162
|
}
|
|
24823
25163
|
}
|
|
24824
25164
|
async request(_port, _init = {}) {
|
|
@@ -24835,6 +25175,8 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24835
25175
|
}
|
|
24836
25176
|
teardown() {
|
|
24837
25177
|
this.disposed = true;
|
|
25178
|
+
for (const process2 of this.running) process2.kill();
|
|
25179
|
+
this.running.clear();
|
|
24838
25180
|
this.router.closeAll();
|
|
24839
25181
|
this.esbuild?.stop();
|
|
24840
25182
|
}
|
|
@@ -24904,103 +25246,6 @@ function attachRolldownMirror(binding, volume, root) {
|
|
|
24904
25246
|
volume.attach(fs, root);
|
|
24905
25247
|
}
|
|
24906
25248
|
|
|
24907
|
-
// src/runtime/sync-channel.ts
|
|
24908
|
-
var STATE = 0;
|
|
24909
|
-
var LENGTH = 1;
|
|
24910
|
-
var MORE = 2;
|
|
24911
|
-
var CONTROL_WORDS = 4;
|
|
24912
|
-
var STATE_REQUEST = 1;
|
|
24913
|
-
var STATE_RESPONSE = 2;
|
|
24914
|
-
var STATE_CONTINUE = 3;
|
|
24915
|
-
var STATE_CLOSED = 4;
|
|
24916
|
-
function createSyncChannelBuffers(capacityBytes = 1 << 20) {
|
|
24917
|
-
return {
|
|
24918
|
-
control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
|
|
24919
|
-
data: new SharedArrayBuffer(capacityBytes)
|
|
24920
|
-
};
|
|
24921
|
-
}
|
|
24922
|
-
function syncChannelSupported() {
|
|
24923
|
-
if (typeof SharedArrayBuffer !== "function") return false;
|
|
24924
|
-
if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return false;
|
|
24925
|
-
if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) return false;
|
|
24926
|
-
return true;
|
|
24927
|
-
}
|
|
24928
|
-
var SyncChannelServer = class {
|
|
24929
|
-
constructor(buffers, handle) {
|
|
24930
|
-
this.handle = handle;
|
|
24931
|
-
this.control = new Int32Array(buffers.control);
|
|
24932
|
-
this.data = new Uint8Array(buffers.data);
|
|
24933
|
-
this.capacity = this.data.length;
|
|
24934
|
-
}
|
|
24935
|
-
handle;
|
|
24936
|
-
control;
|
|
24937
|
-
data;
|
|
24938
|
-
capacity;
|
|
24939
|
-
/** Request chunks gathered so far, and the response still to be sent. */
|
|
24940
|
-
incoming = [];
|
|
24941
|
-
outgoing = null;
|
|
24942
|
-
sent = 0;
|
|
24943
|
-
closed = false;
|
|
24944
|
-
/** Call from the wake message the client sends after each chunk. */
|
|
24945
|
-
async pump() {
|
|
24946
|
-
if (this.closed) return;
|
|
24947
|
-
const state = Atomics.load(this.control, STATE);
|
|
24948
|
-
if (state === STATE_REQUEST) {
|
|
24949
|
-
const size = Atomics.load(this.control, LENGTH);
|
|
24950
|
-
this.incoming.push(this.data.slice(0, size));
|
|
24951
|
-
if (Atomics.load(this.control, MORE) === 1) {
|
|
24952
|
-
this.publish(STATE_CONTINUE);
|
|
24953
|
-
return;
|
|
24954
|
-
}
|
|
24955
|
-
const request = concat3(this.incoming);
|
|
24956
|
-
this.incoming = [];
|
|
24957
|
-
let response;
|
|
24958
|
-
try {
|
|
24959
|
-
response = await this.handle(request);
|
|
24960
|
-
} catch {
|
|
24961
|
-
response = new Uint8Array();
|
|
24962
|
-
}
|
|
24963
|
-
if (this.closed) return;
|
|
24964
|
-
this.outgoing = response;
|
|
24965
|
-
this.sent = 0;
|
|
24966
|
-
this.sendChunk();
|
|
24967
|
-
return;
|
|
24968
|
-
}
|
|
24969
|
-
if (state === STATE_CONTINUE) this.sendChunk();
|
|
24970
|
-
}
|
|
24971
|
-
/** Release a blocked client, e.g. when the container is torn down. */
|
|
24972
|
-
close() {
|
|
24973
|
-
if (this.closed) return;
|
|
24974
|
-
this.closed = true;
|
|
24975
|
-
Atomics.store(this.control, STATE, STATE_CLOSED);
|
|
24976
|
-
Atomics.notify(this.control, STATE);
|
|
24977
|
-
}
|
|
24978
|
-
sendChunk() {
|
|
24979
|
-
const payload = this.outgoing ?? new Uint8Array();
|
|
24980
|
-
const size = Math.min(this.capacity, payload.length - this.sent);
|
|
24981
|
-
this.data.set(payload.subarray(this.sent, this.sent + size), 0);
|
|
24982
|
-
this.sent += size;
|
|
24983
|
-
Atomics.store(this.control, LENGTH, size);
|
|
24984
|
-
Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
|
|
24985
|
-
this.publish(STATE_RESPONSE);
|
|
24986
|
-
}
|
|
24987
|
-
publish(state) {
|
|
24988
|
-
Atomics.store(this.control, STATE, state);
|
|
24989
|
-
Atomics.notify(this.control, STATE);
|
|
24990
|
-
}
|
|
24991
|
-
};
|
|
24992
|
-
function concat3(parts) {
|
|
24993
|
-
if (parts.length === 1) return parts[0];
|
|
24994
|
-
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
24995
|
-
const joined = new Uint8Array(total);
|
|
24996
|
-
let at = 0;
|
|
24997
|
-
for (const part of parts) {
|
|
24998
|
-
joined.set(part, at);
|
|
24999
|
-
at += part.length;
|
|
25000
|
-
}
|
|
25001
|
-
return joined;
|
|
25002
|
-
}
|
|
25003
|
-
|
|
25004
25249
|
// src/runtime/remote-volume.ts
|
|
25005
25250
|
var encoder7 = new TextEncoder();
|
|
25006
25251
|
var decoder7 = new TextDecoder();
|
|
@@ -25095,24 +25340,32 @@ function defaultWorkerUrl() {
|
|
|
25095
25340
|
return new URL("./worker-entry.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
25096
25341
|
}
|
|
25097
25342
|
function hasDomWorker() {
|
|
25098
|
-
return typeof Worker === "function"
|
|
25343
|
+
return typeof Worker === "function";
|
|
25099
25344
|
}
|
|
25100
25345
|
async function startRuntimeWorker(options = {}) {
|
|
25101
25346
|
const url = options.url ?? defaultWorkerUrl();
|
|
25102
25347
|
const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
|
|
25103
|
-
|
|
25104
|
-
|
|
25105
|
-
|
|
25106
|
-
|
|
25348
|
+
try {
|
|
25349
|
+
await new Promise((resolve2, reject) => {
|
|
25350
|
+
const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
|
|
25351
|
+
worker.onMessage((message) => {
|
|
25352
|
+
if (message?.type === "sandboxedjs:ready") {
|
|
25353
|
+
clearTimeout(timer);
|
|
25354
|
+
resolve2();
|
|
25355
|
+
}
|
|
25356
|
+
});
|
|
25357
|
+
worker.onError((error) => {
|
|
25107
25358
|
clearTimeout(timer);
|
|
25108
|
-
|
|
25109
|
-
}
|
|
25110
|
-
});
|
|
25111
|
-
worker.onError((error) => {
|
|
25112
|
-
clearTimeout(timer);
|
|
25113
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
25359
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
25360
|
+
});
|
|
25114
25361
|
});
|
|
25115
|
-
})
|
|
25362
|
+
} catch (error) {
|
|
25363
|
+
try {
|
|
25364
|
+
await worker.terminate();
|
|
25365
|
+
} catch {
|
|
25366
|
+
}
|
|
25367
|
+
throw error;
|
|
25368
|
+
}
|
|
25116
25369
|
return worker;
|
|
25117
25370
|
}
|
|
25118
25371
|
async function startDomWorker(url) {
|
|
@@ -25173,6 +25426,8 @@ var WorkerProcess = class extends EventEmitter4__default.default {
|
|
|
25173
25426
|
started = false;
|
|
25174
25427
|
pendingInput = [];
|
|
25175
25428
|
inputEnded = false;
|
|
25429
|
+
inheritedInput;
|
|
25430
|
+
ownRawMode = false;
|
|
25176
25431
|
on(event, listener) {
|
|
25177
25432
|
return super.on(event, listener);
|
|
25178
25433
|
}
|
|
@@ -25198,13 +25453,38 @@ var WorkerProcess = class extends EventEmitter4__default.default {
|
|
|
25198
25453
|
* program is still loading, and dropping them loses the answer to a prompt.
|
|
25199
25454
|
*/
|
|
25200
25455
|
write(data) {
|
|
25456
|
+
if (this.inheritedInput) {
|
|
25457
|
+
this.inheritedInput.sendStdin(data);
|
|
25458
|
+
return;
|
|
25459
|
+
}
|
|
25201
25460
|
if (this.started) this.worker.postMessage({ type: "stdin", data });
|
|
25202
25461
|
else this.pendingInput.push(data);
|
|
25203
25462
|
}
|
|
25204
25463
|
endInput() {
|
|
25205
25464
|
this.inputEnded = true;
|
|
25465
|
+
if (this.inheritedInput) {
|
|
25466
|
+
this.inheritedInput.endStdin?.();
|
|
25467
|
+
return;
|
|
25468
|
+
}
|
|
25206
25469
|
if (this.started) this.worker.postMessage({ type: "stdin-end" });
|
|
25207
25470
|
}
|
|
25471
|
+
rawMode(enabled) {
|
|
25472
|
+
this.ownRawMode = enabled;
|
|
25473
|
+
if (!this.inheritedInput) this.emit("rawmode", enabled);
|
|
25474
|
+
}
|
|
25475
|
+
/** The parent is blocked in Atomics.wait, so inherited input must bypass it. */
|
|
25476
|
+
inheritInput(child) {
|
|
25477
|
+
this.inheritedInput = child;
|
|
25478
|
+
this.emit("rawmode", false);
|
|
25479
|
+
child.on("rawmode", (enabled) => {
|
|
25480
|
+
if (this.inheritedInput === child) this.emit("rawmode", Boolean(enabled));
|
|
25481
|
+
});
|
|
25482
|
+
return () => {
|
|
25483
|
+
if (this.inheritedInput !== child) return;
|
|
25484
|
+
this.inheritedInput = void 0;
|
|
25485
|
+
this.emit("rawmode", this.ownRawMode);
|
|
25486
|
+
};
|
|
25487
|
+
}
|
|
25208
25488
|
kill(_signal = "SIGTERM") {
|
|
25209
25489
|
this.worker.postMessage({ type: "kill" });
|
|
25210
25490
|
this.finish(137);
|
|
@@ -25225,25 +25505,30 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25225
25505
|
super(options);
|
|
25226
25506
|
this.workerUrl = options.workerUrl;
|
|
25227
25507
|
}
|
|
25228
|
-
/**
|
|
25229
|
-
|
|
25230
|
-
|
|
25231
|
-
|
|
25232
|
-
|
|
25233
|
-
|
|
25234
|
-
|
|
25235
|
-
* and keeps working.
|
|
25236
|
-
*/
|
|
25237
|
-
static async tryBoot(options = {}) {
|
|
25238
|
-
if (!syncChannelSupported()) return null;
|
|
25239
|
-
if (options.modules && Object.keys(options.modules).length > 0) return null;
|
|
25508
|
+
/** Boot without silently dropping synchronous child-process support. */
|
|
25509
|
+
static async boot(options = {}) {
|
|
25510
|
+
const reason = syncChannelUnavailableReason();
|
|
25511
|
+
if (reason) throw new Error(`SandboxedJs worker runtime unavailable: ${reason}`);
|
|
25512
|
+
if (options.modules && Object.keys(options.modules).length > 0) {
|
|
25513
|
+
throw new Error("SandboxedJs worker runtime cannot clone host-supplied modules. Use isolation: 'realm' explicitly.");
|
|
25514
|
+
}
|
|
25240
25515
|
const pod = new _WorkerRuntimePod(options);
|
|
25241
25516
|
try {
|
|
25242
25517
|
const probe = await startRuntimeWorker({ ...options.workerUrl ? { url: options.workerUrl } : {}, timeoutMs: 1e4 });
|
|
25243
25518
|
await probe.terminate();
|
|
25244
25519
|
return pod;
|
|
25245
|
-
} catch {
|
|
25520
|
+
} catch (cause) {
|
|
25246
25521
|
pod.teardown();
|
|
25522
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
25523
|
+
throw new Error(`SandboxedJs guest worker failed to start: ${detail}. Check that worker-entry.js is served as JavaScript; set workerUrl if your bundler moved it.`, { cause });
|
|
25524
|
+
}
|
|
25525
|
+
}
|
|
25526
|
+
/** Compatibility mode, with an observable explanation for every fallback. */
|
|
25527
|
+
static async tryBoot(options = {}, onFallback = (error) => console.warn(`[sandboxedjs] Falling back to the realm runtime; synchronous child processes are unavailable. ${error.message}`)) {
|
|
25528
|
+
try {
|
|
25529
|
+
return await _WorkerRuntimePod.boot(options);
|
|
25530
|
+
} catch (error) {
|
|
25531
|
+
onFallback(error instanceof Error ? error : new Error(String(error)));
|
|
25247
25532
|
return null;
|
|
25248
25533
|
}
|
|
25249
25534
|
}
|
|
@@ -25266,11 +25551,14 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25266
25551
|
const streams = { target: null };
|
|
25267
25552
|
const server = new SyncChannelServer(buffers, serveSyncSyscalls({
|
|
25268
25553
|
volume: this.volume,
|
|
25269
|
-
spawnChild: (request) => this.runChildToCompletion(request, streams.target)
|
|
25554
|
+
spawnChild: (request) => this.runChildToCompletion(request, streams.target, ownedChildren)
|
|
25270
25555
|
}));
|
|
25271
25556
|
const entry = { worker, server };
|
|
25272
25557
|
this.live.add(entry);
|
|
25558
|
+
const ownedChildren = /* @__PURE__ */ new Set();
|
|
25273
25559
|
const process2 = new WorkerProcess(worker, () => {
|
|
25560
|
+
for (const child of ownedChildren) child.kill("SIGTERM");
|
|
25561
|
+
ownedChildren.clear();
|
|
25274
25562
|
this.live.delete(entry);
|
|
25275
25563
|
server.close();
|
|
25276
25564
|
this.closeProxies(owner);
|
|
@@ -25291,7 +25579,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25291
25579
|
process2.error(String(message.text));
|
|
25292
25580
|
return;
|
|
25293
25581
|
case "rawmode":
|
|
25294
|
-
process2.
|
|
25582
|
+
process2.rawMode(Boolean(message.enabled));
|
|
25295
25583
|
return;
|
|
25296
25584
|
case "exit":
|
|
25297
25585
|
process2.finish(Number(message.code));
|
|
@@ -25299,11 +25587,20 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25299
25587
|
case "listen":
|
|
25300
25588
|
this.proxyPort(Number(message.port), owner, worker);
|
|
25301
25589
|
return;
|
|
25590
|
+
case "close-port": {
|
|
25591
|
+
const port = Number(message.port);
|
|
25592
|
+
const proxy = this.proxies.get(port);
|
|
25593
|
+
if (proxy?.owner === owner) {
|
|
25594
|
+
proxy.close();
|
|
25595
|
+
this.proxies.delete(port);
|
|
25596
|
+
}
|
|
25597
|
+
return;
|
|
25598
|
+
}
|
|
25302
25599
|
case "http-response":
|
|
25303
25600
|
this.settleProxied(Number(message.id), message.response);
|
|
25304
25601
|
return;
|
|
25305
25602
|
case "child-start":
|
|
25306
|
-
this.startChild(worker, children, message);
|
|
25603
|
+
this.startChild(worker, children, message, ownedChildren);
|
|
25307
25604
|
return;
|
|
25308
25605
|
case "child-stdin":
|
|
25309
25606
|
children.get(message.id)?.sendStdin?.(String(message.data));
|
|
@@ -25334,16 +25631,21 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25334
25631
|
return process2;
|
|
25335
25632
|
}
|
|
25336
25633
|
/** Start an asynchronous child on the host's behalf and relay its events. */
|
|
25337
|
-
startChild(worker, children, message) {
|
|
25634
|
+
startChild(worker, children, message, owned) {
|
|
25338
25635
|
const handle = this.processManager.spawn(message.config);
|
|
25339
25636
|
children.set(message.id, handle);
|
|
25637
|
+
owned.add(handle);
|
|
25638
|
+
handle.on("exit", () => {
|
|
25639
|
+
owned.delete(handle);
|
|
25640
|
+
children.delete(message.id);
|
|
25641
|
+
});
|
|
25340
25642
|
for (const event of ["stdout", "stderr", "exit"]) {
|
|
25341
25643
|
handle.on(event, (value) => worker.postMessage({ type: "child-event", id: message.id, event, value }));
|
|
25342
25644
|
}
|
|
25343
25645
|
handle.exec();
|
|
25344
25646
|
}
|
|
25345
25647
|
/** Run a child to completion and collect it, for the guest's `spawnSync`. */
|
|
25346
|
-
runChildToCompletion(request, streamTo) {
|
|
25648
|
+
runChildToCompletion(request, streamTo, owned) {
|
|
25347
25649
|
return new Promise((resolve2) => {
|
|
25348
25650
|
let handle;
|
|
25349
25651
|
try {
|
|
@@ -25359,9 +25661,13 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25359
25661
|
resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure.code ? { code: failure.code } : {}, message: failure.message } });
|
|
25360
25662
|
return;
|
|
25361
25663
|
}
|
|
25664
|
+
owned.add(handle);
|
|
25665
|
+
handle.on("exit", () => owned.delete(handle));
|
|
25362
25666
|
let stdout = "";
|
|
25363
25667
|
let stderr = "";
|
|
25364
25668
|
const live = request.inheritStdio ? streamTo : null;
|
|
25669
|
+
const restoreInput = live?.inheritInput(handle);
|
|
25670
|
+
if (restoreInput) handle.on("exit", restoreInput);
|
|
25365
25671
|
handle.on("stdout", (text2) => {
|
|
25366
25672
|
stdout += text2;
|
|
25367
25673
|
live?.output(text2);
|
|
@@ -25515,7 +25821,7 @@ var Container = class _Container {
|
|
|
25515
25821
|
...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
|
|
25516
25822
|
};
|
|
25517
25823
|
const workerOptions = { ...podOptions, ...opts.workerUrl ? { workerUrl: opts.workerUrl } : {} };
|
|
25518
|
-
const pod = opts.pod ?? (opts.isolation === "realm" ? null : await WorkerRuntimePod.tryBoot(workerOptions)) ?? await LocalRuntimePod.boot(podOptions);
|
|
25824
|
+
const pod = opts.pod ?? (opts.isolation === "realm" ? null : opts.isolation === "worker" ? await WorkerRuntimePod.boot(workerOptions) : await WorkerRuntimePod.tryBoot(workerOptions, opts.onRuntimeFallback)) ?? await LocalRuntimePod.boot(podOptions);
|
|
25519
25825
|
const kernel = new Kernel({
|
|
25520
25826
|
pod,
|
|
25521
25827
|
hostname: opts.hostname ?? "sandbox",
|
|
@@ -26023,7 +26329,7 @@ var Terminal = class {
|
|
|
26023
26329
|
if (this.running) {
|
|
26024
26330
|
const raw = this.currentStdin?.rawMode === true;
|
|
26025
26331
|
if (ch === CTRL_C && !raw) {
|
|
26026
|
-
this.session.
|
|
26332
|
+
if (this.currentStdin) this.session.shell.interruptForeground("SIGINT", this.currentStdin);
|
|
26027
26333
|
this.write("^C\r\n");
|
|
26028
26334
|
return;
|
|
26029
26335
|
}
|
|
@@ -26413,7 +26719,7 @@ function serveContainerOn(port, box) {
|
|
|
26413
26719
|
}
|
|
26414
26720
|
async function createPreview(box, options = {}) {
|
|
26415
26721
|
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
|
|
26416
|
-
const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
26722
|
+
const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js?no-inline", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
26417
26723
|
let registration;
|
|
26418
26724
|
try {
|
|
26419
26725
|
registration = await navigator.serviceWorker.register(scriptUrl, {
|