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.js
CHANGED
|
@@ -4778,7 +4778,15 @@ var Pipe = class _Pipe {
|
|
|
4778
4778
|
* whole line on every keystroke — so the terminal must stop echoing, or
|
|
4779
4779
|
* every character appears twice.
|
|
4780
4780
|
*/
|
|
4781
|
-
|
|
4781
|
+
raw = false;
|
|
4782
|
+
onRawMode;
|
|
4783
|
+
get rawMode() {
|
|
4784
|
+
return this.raw;
|
|
4785
|
+
}
|
|
4786
|
+
set rawMode(enabled) {
|
|
4787
|
+
this.raw = enabled;
|
|
4788
|
+
this.onRawMode?.(enabled);
|
|
4789
|
+
}
|
|
4782
4790
|
columns;
|
|
4783
4791
|
rows;
|
|
4784
4792
|
get closed() {
|
|
@@ -5906,13 +5914,19 @@ var Kernel = class {
|
|
|
5906
5914
|
stderr: opts.stderr ?? new NullOutput()
|
|
5907
5915
|
}
|
|
5908
5916
|
});
|
|
5917
|
+
const cancelWithParent = () => {
|
|
5918
|
+
if (parent?.termSignal) proc.deliver(parent.termSignal);
|
|
5919
|
+
};
|
|
5920
|
+
parent?.signal.addEventListener("abort", cancelWithParent, { once: true });
|
|
5921
|
+
if (parent?.signal.aborted) cancelWithParent();
|
|
5922
|
+
void proc.wait().then(() => parent?.signal.removeEventListener("abort", cancelWithParent));
|
|
5909
5923
|
let timer;
|
|
5910
5924
|
if (opts.timeoutMs !== void 0) {
|
|
5911
5925
|
timer = setTimeout(() => {
|
|
5912
5926
|
proc.deliver("SIGKILL");
|
|
5913
5927
|
}, opts.timeoutMs);
|
|
5914
5928
|
}
|
|
5915
|
-
void this.dispatch(proc).then((code) => {
|
|
5929
|
+
void (proc.signal.aborted ? Promise.resolve(proc.exitCode ?? 137) : this.dispatch(proc)).then((code) => {
|
|
5916
5930
|
proc.exit(code);
|
|
5917
5931
|
}).catch((e) => {
|
|
5918
5932
|
try {
|
|
@@ -7904,6 +7918,22 @@ var Shell = class _Shell {
|
|
|
7904
7918
|
}
|
|
7905
7919
|
return this.lastStatus;
|
|
7906
7920
|
}
|
|
7921
|
+
/** Signal the current terminal pipeline without terminating the interactive shell. */
|
|
7922
|
+
interruptForeground(signal, stdin) {
|
|
7923
|
+
const processes = this.kernel.procs.list().filter((process2) => process2.running);
|
|
7924
|
+
const selected = new Set(processes.filter((process2) => process2.ppid === this.proc.pid && process2.stdin === stdin).map((process2) => process2.pid));
|
|
7925
|
+
for (let changed = true; changed; ) {
|
|
7926
|
+
changed = false;
|
|
7927
|
+
const outputs = processes.filter((process2) => selected.has(process2.pid)).flatMap((process2) => [process2.stdout, process2.stderr]);
|
|
7928
|
+
for (const process2 of processes) {
|
|
7929
|
+
if (!selected.has(process2.pid) && (selected.has(process2.ppid) || outputs.some((output) => output === process2.stdin))) {
|
|
7930
|
+
selected.add(process2.pid);
|
|
7931
|
+
changed = true;
|
|
7932
|
+
}
|
|
7933
|
+
}
|
|
7934
|
+
}
|
|
7935
|
+
for (const process2 of processes) if (selected.has(process2.pid)) process2.deliver(signal);
|
|
7936
|
+
}
|
|
7907
7937
|
get isExiting() {
|
|
7908
7938
|
return this.exiting;
|
|
7909
7939
|
}
|
|
@@ -8553,6 +8583,7 @@ sys ${fmt2(Math.floor(ms * 0.2))}
|
|
|
8553
8583
|
for (const assignment of assignments) {
|
|
8554
8584
|
env2[assignment.name] = await expandWordToString(assignment.value, this.expandContext());
|
|
8555
8585
|
}
|
|
8586
|
+
if (this.proc.signal.aborted) return this.proc.exitCode ?? 137;
|
|
8556
8587
|
const proc = this.kernel.spawn(words, {
|
|
8557
8588
|
cwd: this.cwd,
|
|
8558
8589
|
env: env2,
|
|
@@ -8810,7 +8841,7 @@ async function runShell(ctxName, ctx) {
|
|
|
8810
8841
|
const args = ctx.args.slice();
|
|
8811
8842
|
let command;
|
|
8812
8843
|
let scriptPath;
|
|
8813
|
-
let
|
|
8844
|
+
let readStdin = false;
|
|
8814
8845
|
let interactive = false;
|
|
8815
8846
|
let login = ctxName.startsWith("-");
|
|
8816
8847
|
const options = {};
|
|
@@ -8823,7 +8854,7 @@ async function runShell(ctxName, ctx) {
|
|
|
8823
8854
|
break;
|
|
8824
8855
|
}
|
|
8825
8856
|
if (arg === "-s") {
|
|
8826
|
-
|
|
8857
|
+
readStdin = true;
|
|
8827
8858
|
continue;
|
|
8828
8859
|
}
|
|
8829
8860
|
if (arg === "-i") {
|
|
@@ -8860,7 +8891,7 @@ async function runShell(ctxName, ctx) {
|
|
|
8860
8891
|
break;
|
|
8861
8892
|
}
|
|
8862
8893
|
const rest = args.slice(i);
|
|
8863
|
-
if (command === void 0 && !
|
|
8894
|
+
if (command === void 0 && !readStdin && rest.length > 0) {
|
|
8864
8895
|
scriptPath = rest[0];
|
|
8865
8896
|
}
|
|
8866
8897
|
const positional = command !== void 0 ? rest.slice(1) : scriptPath !== void 0 ? rest.slice(1) : rest;
|
|
@@ -17755,6 +17786,143 @@ function mountContainerFs(FS, opts) {
|
|
|
17755
17786
|
}
|
|
17756
17787
|
}
|
|
17757
17788
|
|
|
17789
|
+
// src/runtime/python-syscalls.ts
|
|
17790
|
+
var EMPTY = new Uint8Array(0);
|
|
17791
|
+
function createStdinHost(ctx) {
|
|
17792
|
+
let pending = EMPTY;
|
|
17793
|
+
let eof = false;
|
|
17794
|
+
const take = (size) => {
|
|
17795
|
+
const n = Math.min(size, pending.length);
|
|
17796
|
+
const out = pending.subarray(0, n);
|
|
17797
|
+
pending = pending.subarray(n);
|
|
17798
|
+
return out;
|
|
17799
|
+
};
|
|
17800
|
+
return {
|
|
17801
|
+
/**
|
|
17802
|
+
* Drain a non-interactive stdin up front.
|
|
17803
|
+
*
|
|
17804
|
+
* A pipe ends, so reading it eagerly costs nothing and leaves every read
|
|
17805
|
+
* answerable without suspending — which is what keeps piped input working
|
|
17806
|
+
* on hosts with no stack-switching. A terminal never ends, so priming one
|
|
17807
|
+
* would hang before the program had printed its prompt.
|
|
17808
|
+
*/
|
|
17809
|
+
async prime() {
|
|
17810
|
+
if (ctx.stdin.isTTY || ctx.stdin.interactive) return;
|
|
17811
|
+
try {
|
|
17812
|
+
pending = await ctx.stdin.readAll();
|
|
17813
|
+
} catch {
|
|
17814
|
+
pending = EMPTY;
|
|
17815
|
+
}
|
|
17816
|
+
eof = true;
|
|
17817
|
+
},
|
|
17818
|
+
buffered(size) {
|
|
17819
|
+
if (pending.length > 0) return take(size);
|
|
17820
|
+
return eof ? EMPTY : void 0;
|
|
17821
|
+
},
|
|
17822
|
+
async read(size) {
|
|
17823
|
+
if (pending.length > 0) return take(size);
|
|
17824
|
+
if (eof) return EMPTY;
|
|
17825
|
+
const chunk = await ctx.stdin.read(size);
|
|
17826
|
+
if (chunk === null || chunk.length === 0) {
|
|
17827
|
+
eof = true;
|
|
17828
|
+
return EMPTY;
|
|
17829
|
+
}
|
|
17830
|
+
pending = chunk;
|
|
17831
|
+
return take(size);
|
|
17832
|
+
},
|
|
17833
|
+
isatty() {
|
|
17834
|
+
return Boolean(ctx.stdin.isTTY);
|
|
17835
|
+
}
|
|
17836
|
+
};
|
|
17837
|
+
}
|
|
17838
|
+
var BRIDGE_PY = `
|
|
17839
|
+
import builtins, io, sys
|
|
17840
|
+
import _sbx_host as _host
|
|
17841
|
+
from pyodide.ffi import can_run_sync, run_sync
|
|
17842
|
+
|
|
17843
|
+
|
|
17844
|
+
def _to_bytes(value):
|
|
17845
|
+
if value is None:
|
|
17846
|
+
return b""
|
|
17847
|
+
to_bytes = getattr(value, "to_bytes", None)
|
|
17848
|
+
if to_bytes is not None:
|
|
17849
|
+
return to_bytes()
|
|
17850
|
+
return bytes(value.to_py())
|
|
17851
|
+
|
|
17852
|
+
|
|
17853
|
+
class _SbxStdin(io.RawIOBase):
|
|
17854
|
+
"""The container's standard input, as a blocking raw stream."""
|
|
17855
|
+
|
|
17856
|
+
def readable(self):
|
|
17857
|
+
return True
|
|
17858
|
+
|
|
17859
|
+
def fileno(self):
|
|
17860
|
+
return 0
|
|
17861
|
+
|
|
17862
|
+
def isatty(self):
|
|
17863
|
+
return bool(_host.isatty())
|
|
17864
|
+
|
|
17865
|
+
def readinto(self, target):
|
|
17866
|
+
want = len(target)
|
|
17867
|
+
if want == 0:
|
|
17868
|
+
return 0
|
|
17869
|
+
ready = _host.buffered(want)
|
|
17870
|
+
if ready is None:
|
|
17871
|
+
if not can_run_sync():
|
|
17872
|
+
raise OSError(
|
|
17873
|
+
"this host cannot wait for interactive input: WebAssembly "
|
|
17874
|
+
"stack switching (JSPI) is unavailable. Pipe the input in, "
|
|
17875
|
+
"or use a browser or Node build that supports it."
|
|
17876
|
+
)
|
|
17877
|
+
ready = run_sync(_host.read(want))
|
|
17878
|
+
data = _to_bytes(ready)
|
|
17879
|
+
target[: len(data)] = data
|
|
17880
|
+
return len(data)
|
|
17881
|
+
|
|
17882
|
+
|
|
17883
|
+
def _install():
|
|
17884
|
+
stdin = io.TextIOWrapper(
|
|
17885
|
+
io.BufferedReader(_SbxStdin()), encoding="utf-8", errors="replace", line_buffering=True
|
|
17886
|
+
)
|
|
17887
|
+
sys.stdin = stdin
|
|
17888
|
+
sys.__stdin__ = stdin
|
|
17889
|
+
|
|
17890
|
+
def input(prompt=""):
|
|
17891
|
+
# CPython writes the prompt to stdout and strips exactly one trailing
|
|
17892
|
+
# newline; anything else changes what a program reads back.
|
|
17893
|
+
if prompt != "":
|
|
17894
|
+
sys.stdout.write(str(prompt))
|
|
17895
|
+
sys.stdout.flush()
|
|
17896
|
+
line = sys.stdin.readline()
|
|
17897
|
+
if not line:
|
|
17898
|
+
raise EOFError("EOF when reading a line")
|
|
17899
|
+
if line.endswith("\\n"):
|
|
17900
|
+
line = line[:-1]
|
|
17901
|
+
if line.endswith("\\r"):
|
|
17902
|
+
line = line[:-1]
|
|
17903
|
+
return line
|
|
17904
|
+
|
|
17905
|
+
builtins.input = input
|
|
17906
|
+
|
|
17907
|
+
|
|
17908
|
+
_install()
|
|
17909
|
+
`;
|
|
17910
|
+
function installSyscallBridge(py) {
|
|
17911
|
+
const slot = py.__sbxStdin ??= { active: null };
|
|
17912
|
+
py.registerJsModule("_sbx_host", {
|
|
17913
|
+
/* Not `?.` and `??`: `buffered` returns undefined to mean "you will have
|
|
17914
|
+
* to wait", and a nullish default would quietly turn that into end-of-file —
|
|
17915
|
+
* which is exactly the silent EOF this bridge exists to remove. */
|
|
17916
|
+
buffered: (size) => slot.active ? slot.active.buffered(size) : EMPTY,
|
|
17917
|
+
read: (size) => slot.active ? slot.active.read(size) : Promise.resolve(EMPTY),
|
|
17918
|
+
isatty: () => slot.active?.isatty() ?? false
|
|
17919
|
+
});
|
|
17920
|
+
py.runPython(BRIDGE_PY);
|
|
17921
|
+
}
|
|
17922
|
+
function bindStdin(py, host2) {
|
|
17923
|
+
py.__sbxStdin.active = host2;
|
|
17924
|
+
}
|
|
17925
|
+
|
|
17758
17926
|
// src/runtime/cpython.ts
|
|
17759
17927
|
var RESERVED_FOR_INTERPRETER = /* @__PURE__ */ new Set(["lib", "dev", "proc"]);
|
|
17760
17928
|
var pyodideModule = null;
|
|
@@ -17827,6 +17995,7 @@ async function interpreterFor(ctx) {
|
|
|
17827
17995
|
});
|
|
17828
17996
|
mountContainerDirs(py, ctx);
|
|
17829
17997
|
installRunner(py);
|
|
17998
|
+
installSyscallBridge(py);
|
|
17830
17999
|
return py;
|
|
17831
18000
|
})();
|
|
17832
18001
|
interpreters.set(ctx.vfs, starting);
|
|
@@ -17921,7 +18090,7 @@ function reportError(ctx, error) {
|
|
|
17921
18090
|
`);
|
|
17922
18091
|
return 1;
|
|
17923
18092
|
}
|
|
17924
|
-
async function runCPythonProgram(ctx, source, argv, scriptDir
|
|
18093
|
+
async function runCPythonProgram(ctx, source, argv, scriptDir = null) {
|
|
17925
18094
|
let py;
|
|
17926
18095
|
try {
|
|
17927
18096
|
py = await interpreterFor(ctx);
|
|
@@ -17942,7 +18111,6 @@ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
|
|
|
17942
18111
|
});
|
|
17943
18112
|
} catch {
|
|
17944
18113
|
}
|
|
17945
|
-
const encoder8 = new TextEncoder();
|
|
17946
18114
|
const decoder8 = new TextDecoder();
|
|
17947
18115
|
py.setStdout({
|
|
17948
18116
|
write: (buffer) => {
|
|
@@ -17956,20 +18124,17 @@ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
|
|
|
17956
18124
|
return buffer.length;
|
|
17957
18125
|
}
|
|
17958
18126
|
});
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
} else {
|
|
17971
|
-
py.setStdin({ read: () => 0 });
|
|
17972
|
-
}
|
|
18127
|
+
const stdinHost = createStdinHost(ctx);
|
|
18128
|
+
await stdinHost.prime();
|
|
18129
|
+
bindStdin(py, stdinHost);
|
|
18130
|
+
py.setStdin({
|
|
18131
|
+
read: (buffer) => {
|
|
18132
|
+
const ready = stdinHost.buffered(buffer.length);
|
|
18133
|
+
if (!ready || ready.length === 0) return 0;
|
|
18134
|
+
buffer.set(ready);
|
|
18135
|
+
return ready.length;
|
|
18136
|
+
}
|
|
18137
|
+
});
|
|
17973
18138
|
try {
|
|
17974
18139
|
bootstrap(py, ctx, argv, scriptDir);
|
|
17975
18140
|
} catch (error) {
|
|
@@ -18102,11 +18267,6 @@ function configurePython(options = {}) {
|
|
|
18102
18267
|
configureCPython({ indexURL: options.indexURL, moduleURL: options.pyodideURL });
|
|
18103
18268
|
}
|
|
18104
18269
|
var isPythonAvailable = isCPythonAvailable;
|
|
18105
|
-
async function readStdin(ctx) {
|
|
18106
|
-
if (ctx.stdin.isTTY || ctx.stdin.interactive) return null;
|
|
18107
|
-
const bytes2 = await ctx.stdin.readAll();
|
|
18108
|
-
return bytes2.length ? new TextDecoder().decode(bytes2) : null;
|
|
18109
|
-
}
|
|
18110
18270
|
var python = defineCommand({
|
|
18111
18271
|
name: "python3",
|
|
18112
18272
|
path: "/usr/bin/python3",
|
|
@@ -18155,7 +18315,7 @@ compatible packages can be installed with pip (micropip).`,
|
|
|
18155
18315
|
}
|
|
18156
18316
|
const rest = argv.slice(i);
|
|
18157
18317
|
if (command !== void 0) {
|
|
18158
|
-
return (await runCPythonProgram(ctx, command, ["-c", ...rest], null
|
|
18318
|
+
return (await runCPythonProgram(ctx, command, ["-c", ...rest], null)).exitCode;
|
|
18159
18319
|
}
|
|
18160
18320
|
if (moduleName !== void 0) {
|
|
18161
18321
|
const program = `
|
|
@@ -18167,16 +18327,16 @@ except ImportError:
|
|
|
18167
18327
|
print("/usr/bin/python3: No module named " + ${JSON.stringify(moduleName)}, file=sys.stderr)
|
|
18168
18328
|
raise SystemExit(1)
|
|
18169
18329
|
`;
|
|
18170
|
-
return (await runCPythonProgram(ctx, program, [moduleName, ...rest], null
|
|
18330
|
+
return (await runCPythonProgram(ctx, program, [moduleName, ...rest], null)).exitCode;
|
|
18171
18331
|
}
|
|
18172
18332
|
if (script === void 0) {
|
|
18173
18333
|
if (ctx.stdin.isTTY) return runCPythonRepl(ctx);
|
|
18174
18334
|
const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
|
|
18175
|
-
return source2.trim() ? (await runCPythonProgram(ctx, source2, ["", ...rest]
|
|
18335
|
+
return source2.trim() ? (await runCPythonProgram(ctx, source2, ["", ...rest])).exitCode : 0;
|
|
18176
18336
|
}
|
|
18177
18337
|
if (script === "-") {
|
|
18178
18338
|
const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
|
|
18179
|
-
return (await runCPythonProgram(ctx, source2, ["-", ...rest]
|
|
18339
|
+
return (await runCPythonProgram(ctx, source2, ["-", ...rest])).exitCode;
|
|
18180
18340
|
}
|
|
18181
18341
|
const abs = ctx.path(script);
|
|
18182
18342
|
let source;
|
|
@@ -18187,7 +18347,7 @@ except ImportError:
|
|
|
18187
18347
|
`);
|
|
18188
18348
|
return 2;
|
|
18189
18349
|
}
|
|
18190
|
-
return (await runCPythonProgram(ctx, source, [abs, ...rest], dirname(abs)
|
|
18350
|
+
return (await runCPythonProgram(ctx, source, [abs, ...rest], dirname(abs))).exitCode;
|
|
18191
18351
|
}
|
|
18192
18352
|
});
|
|
18193
18353
|
function printHelp2(ctx) {
|
|
@@ -19699,6 +19859,7 @@ var KernelChildProcess = class {
|
|
|
19699
19859
|
this.stdin.isTTY = true;
|
|
19700
19860
|
this.stdin.interactive = true;
|
|
19701
19861
|
}
|
|
19862
|
+
this.stdin.onRawMode = (enabled) => this.emit("rawmode", enabled);
|
|
19702
19863
|
this.kernel = kernel;
|
|
19703
19864
|
this.cred = cred;
|
|
19704
19865
|
this.pid = pid;
|
|
@@ -19815,7 +19976,7 @@ function installNodeChildProcessBridge(pod, kernel, cred) {
|
|
|
19815
19976
|
const originalSpawn = manager.spawn;
|
|
19816
19977
|
manager.spawn = (config) => {
|
|
19817
19978
|
const resolved = kernel.resolveExecutable(config.command, config.cwd ?? "/", config.env ?? {}, cred);
|
|
19818
|
-
if (resolved?.kind === "builtin"
|
|
19979
|
+
if (resolved?.kind === "builtin") {
|
|
19819
19980
|
return new KernelChildProcess(kernel, cred, config, nextBridgePid++);
|
|
19820
19981
|
}
|
|
19821
19982
|
return originalSpawn.call(manager, config);
|
|
@@ -19826,6 +19987,68 @@ function installNodeChildProcessBridge(pod, kernel, cred) {
|
|
|
19826
19987
|
};
|
|
19827
19988
|
}
|
|
19828
19989
|
|
|
19990
|
+
// src/runtime/host-module-tracker.ts
|
|
19991
|
+
var HostModuleTracker = class {
|
|
19992
|
+
active = 0;
|
|
19993
|
+
disposed = false;
|
|
19994
|
+
dispose() {
|
|
19995
|
+
this.disposed = true;
|
|
19996
|
+
}
|
|
19997
|
+
wrapped = /* @__PURE__ */ new WeakMap();
|
|
19998
|
+
originals = /* @__PURE__ */ new WeakMap();
|
|
19999
|
+
pending = () => this.active;
|
|
20000
|
+
unwrap = (value) => value && (typeof value === "object" || typeof value === "function") ? this.originals.get(value) ?? value : value;
|
|
20001
|
+
wrap(value) {
|
|
20002
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return value;
|
|
20003
|
+
const object = value;
|
|
20004
|
+
if (Array.isArray(value) || ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return value;
|
|
20005
|
+
if (this.wrapped.has(object)) return this.wrapped.get(object);
|
|
20006
|
+
const tracker = this;
|
|
20007
|
+
const proxy = new Proxy(object, {
|
|
20008
|
+
// CommonJS native loaders re-export by assigning exports back onto the
|
|
20009
|
+
// binding object. Never store a process-owned proxy in a shared module.
|
|
20010
|
+
set(target, key, member) {
|
|
20011
|
+
return Reflect.set(target, key, tracker.unwrap(member), target);
|
|
20012
|
+
},
|
|
20013
|
+
defineProperty(target, key, descriptor) {
|
|
20014
|
+
return Reflect.defineProperty(target, key, "value" in descriptor ? { ...descriptor, value: tracker.unwrap(descriptor.value) } : descriptor);
|
|
20015
|
+
},
|
|
20016
|
+
get(target, key) {
|
|
20017
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
|
|
20018
|
+
const member = Reflect.get(target, key, target);
|
|
20019
|
+
if (descriptor && !descriptor.configurable && "value" in descriptor && !descriptor.writable) return member;
|
|
20020
|
+
return typeof member === "function" ? tracker.wrap(member) : member;
|
|
20021
|
+
},
|
|
20022
|
+
apply(target, receiver, args) {
|
|
20023
|
+
const result = Reflect.apply(target, tracker.unwrap(receiver), args.map(tracker.unwrap));
|
|
20024
|
+
if (result && typeof result.then === "function") {
|
|
20025
|
+
tracker.active++;
|
|
20026
|
+
return Promise.resolve(result).then(
|
|
20027
|
+
(value2) => {
|
|
20028
|
+
tracker.active--;
|
|
20029
|
+
return tracker.disposed ? new Promise(() => {
|
|
20030
|
+
}) : tracker.wrap(value2);
|
|
20031
|
+
},
|
|
20032
|
+
(error) => {
|
|
20033
|
+
tracker.active--;
|
|
20034
|
+
if (tracker.disposed) return new Promise(() => {
|
|
20035
|
+
});
|
|
20036
|
+
throw error;
|
|
20037
|
+
}
|
|
20038
|
+
);
|
|
20039
|
+
}
|
|
20040
|
+
return tracker.wrap(result);
|
|
20041
|
+
},
|
|
20042
|
+
construct(target, args, newTarget) {
|
|
20043
|
+
return tracker.wrap(Reflect.construct(target, args.map(tracker.unwrap), tracker.unwrap(newTarget)));
|
|
20044
|
+
}
|
|
20045
|
+
});
|
|
20046
|
+
this.wrapped.set(object, proxy);
|
|
20047
|
+
this.originals.set(proxy, object);
|
|
20048
|
+
return proxy;
|
|
20049
|
+
}
|
|
20050
|
+
};
|
|
20051
|
+
|
|
19829
20052
|
// src/runtime/commonjs-engine.ts
|
|
19830
20053
|
init_path();
|
|
19831
20054
|
var HELPERS = {
|
|
@@ -21563,6 +21786,7 @@ var VirtualHttpServer = class extends EventEmitter4 {
|
|
|
21563
21786
|
owner;
|
|
21564
21787
|
listening = false;
|
|
21565
21788
|
portValue = null;
|
|
21789
|
+
referenced = true;
|
|
21566
21790
|
listen(...args) {
|
|
21567
21791
|
const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
|
|
21568
21792
|
const first = args[0];
|
|
@@ -21591,11 +21815,16 @@ var VirtualHttpServer = class extends EventEmitter4 {
|
|
|
21591
21815
|
return this.portValue === null ? null : { address: "0.0.0.0", family: "IPv4", port: this.portValue };
|
|
21592
21816
|
}
|
|
21593
21817
|
ref() {
|
|
21818
|
+
this.referenced = true;
|
|
21594
21819
|
return this;
|
|
21595
21820
|
}
|
|
21596
21821
|
unref() {
|
|
21822
|
+
this.referenced = false;
|
|
21597
21823
|
return this;
|
|
21598
21824
|
}
|
|
21825
|
+
hasRef() {
|
|
21826
|
+
return this.referenced;
|
|
21827
|
+
}
|
|
21599
21828
|
setTimeout(_milliseconds, callback) {
|
|
21600
21829
|
if (callback) this.on("timeout", callback);
|
|
21601
21830
|
return this;
|
|
@@ -21605,13 +21834,16 @@ var VirtualHttpRouter = class {
|
|
|
21605
21834
|
servers = /* @__PURE__ */ new Map();
|
|
21606
21835
|
/** Notified when a server begins listening, for `onServerReady`. */
|
|
21607
21836
|
onListen;
|
|
21837
|
+
onClose;
|
|
21608
21838
|
register(port, server, owner) {
|
|
21609
21839
|
if (this.servers.has(port)) throw Object.assign(new Error(`listen EADDRINUSE: address already in use 0.0.0.0:${port}`), { code: "EADDRINUSE", port });
|
|
21610
21840
|
this.servers.set(port, { server, owner });
|
|
21611
21841
|
this.onListen?.(port);
|
|
21612
21842
|
}
|
|
21613
21843
|
unregister(port, server) {
|
|
21614
|
-
if (this.servers.get(port)?.server
|
|
21844
|
+
if (this.servers.get(port)?.server !== server) return;
|
|
21845
|
+
this.servers.delete(port);
|
|
21846
|
+
this.onClose?.(port);
|
|
21615
21847
|
}
|
|
21616
21848
|
/** Whether anything in this container is listening on `port`. */
|
|
21617
21849
|
activePortsIncludes(port) {
|
|
@@ -21620,6 +21852,9 @@ var VirtualHttpRouter = class {
|
|
|
21620
21852
|
activePorts(owner) {
|
|
21621
21853
|
return [...this.servers].filter(([, item]) => owner === void 0 || item.owner === owner).map(([port]) => port).sort((a, b) => a - b);
|
|
21622
21854
|
}
|
|
21855
|
+
referencedPorts(owner) {
|
|
21856
|
+
return [...this.servers].filter(([, item]) => item.owner === owner && item.server.hasRef()).map(([port]) => port);
|
|
21857
|
+
}
|
|
21623
21858
|
closeOwner(owner) {
|
|
21624
21859
|
for (const { server, owner: value } of [...this.servers.values()]) if (value === owner) server.close();
|
|
21625
21860
|
}
|
|
@@ -22031,6 +22266,108 @@ function hashFor(algorithm) {
|
|
|
22031
22266
|
function toBytes2(data, encoding) {
|
|
22032
22267
|
return typeof data === "string" ? Buffer2.from(data, encoding) : data;
|
|
22033
22268
|
}
|
|
22269
|
+
|
|
22270
|
+
// src/runtime/sync-channel.ts
|
|
22271
|
+
var STATE = 0;
|
|
22272
|
+
var LENGTH = 1;
|
|
22273
|
+
var MORE = 2;
|
|
22274
|
+
var CONTROL_WORDS = 4;
|
|
22275
|
+
var STATE_REQUEST = 1;
|
|
22276
|
+
var STATE_RESPONSE = 2;
|
|
22277
|
+
var STATE_CONTINUE = 3;
|
|
22278
|
+
var STATE_CLOSED = 4;
|
|
22279
|
+
function createSyncChannelBuffers(capacityBytes = 1 << 20) {
|
|
22280
|
+
return {
|
|
22281
|
+
control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
|
|
22282
|
+
data: new SharedArrayBuffer(capacityBytes)
|
|
22283
|
+
};
|
|
22284
|
+
}
|
|
22285
|
+
function syncChannelUnavailableReason() {
|
|
22286
|
+
if (typeof globalThis.crossOriginIsolated === "boolean" && !globalThis.crossOriginIsolated) {
|
|
22287
|
+
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.";
|
|
22288
|
+
}
|
|
22289
|
+
if (typeof SharedArrayBuffer !== "function") return "SharedArrayBuffer is unavailable in this host.";
|
|
22290
|
+
if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return "Atomics.wait is unavailable in this host.";
|
|
22291
|
+
return null;
|
|
22292
|
+
}
|
|
22293
|
+
function syncChannelSupported() {
|
|
22294
|
+
return syncChannelUnavailableReason() === null;
|
|
22295
|
+
}
|
|
22296
|
+
var SyncChannelServer = class {
|
|
22297
|
+
constructor(buffers, handle) {
|
|
22298
|
+
this.handle = handle;
|
|
22299
|
+
this.control = new Int32Array(buffers.control);
|
|
22300
|
+
this.data = new Uint8Array(buffers.data);
|
|
22301
|
+
this.capacity = this.data.length;
|
|
22302
|
+
}
|
|
22303
|
+
handle;
|
|
22304
|
+
control;
|
|
22305
|
+
data;
|
|
22306
|
+
capacity;
|
|
22307
|
+
/** Request chunks gathered so far, and the response still to be sent. */
|
|
22308
|
+
incoming = [];
|
|
22309
|
+
outgoing = null;
|
|
22310
|
+
sent = 0;
|
|
22311
|
+
closed = false;
|
|
22312
|
+
/** Call from the wake message the client sends after each chunk. */
|
|
22313
|
+
async pump() {
|
|
22314
|
+
if (this.closed) return;
|
|
22315
|
+
const state = Atomics.load(this.control, STATE);
|
|
22316
|
+
if (state === STATE_REQUEST) {
|
|
22317
|
+
const size = Atomics.load(this.control, LENGTH);
|
|
22318
|
+
this.incoming.push(this.data.slice(0, size));
|
|
22319
|
+
if (Atomics.load(this.control, MORE) === 1) {
|
|
22320
|
+
this.publish(STATE_CONTINUE);
|
|
22321
|
+
return;
|
|
22322
|
+
}
|
|
22323
|
+
const request = concat3(this.incoming);
|
|
22324
|
+
this.incoming = [];
|
|
22325
|
+
let response;
|
|
22326
|
+
try {
|
|
22327
|
+
response = await this.handle(request);
|
|
22328
|
+
} catch {
|
|
22329
|
+
response = new Uint8Array();
|
|
22330
|
+
}
|
|
22331
|
+
if (this.closed) return;
|
|
22332
|
+
this.outgoing = response;
|
|
22333
|
+
this.sent = 0;
|
|
22334
|
+
this.sendChunk();
|
|
22335
|
+
return;
|
|
22336
|
+
}
|
|
22337
|
+
if (state === STATE_CONTINUE) this.sendChunk();
|
|
22338
|
+
}
|
|
22339
|
+
/** Release a blocked client, e.g. when the container is torn down. */
|
|
22340
|
+
close() {
|
|
22341
|
+
if (this.closed) return;
|
|
22342
|
+
this.closed = true;
|
|
22343
|
+
Atomics.store(this.control, STATE, STATE_CLOSED);
|
|
22344
|
+
Atomics.notify(this.control, STATE);
|
|
22345
|
+
}
|
|
22346
|
+
sendChunk() {
|
|
22347
|
+
const payload = this.outgoing ?? new Uint8Array();
|
|
22348
|
+
const size = Math.min(this.capacity, payload.length - this.sent);
|
|
22349
|
+
this.data.set(payload.subarray(this.sent, this.sent + size), 0);
|
|
22350
|
+
this.sent += size;
|
|
22351
|
+
Atomics.store(this.control, LENGTH, size);
|
|
22352
|
+
Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
|
|
22353
|
+
this.publish(STATE_RESPONSE);
|
|
22354
|
+
}
|
|
22355
|
+
publish(state) {
|
|
22356
|
+
Atomics.store(this.control, STATE, state);
|
|
22357
|
+
Atomics.notify(this.control, STATE);
|
|
22358
|
+
}
|
|
22359
|
+
};
|
|
22360
|
+
function concat3(parts) {
|
|
22361
|
+
if (parts.length === 1) return parts[0];
|
|
22362
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
22363
|
+
const joined = new Uint8Array(total);
|
|
22364
|
+
let at = 0;
|
|
22365
|
+
for (const part of parts) {
|
|
22366
|
+
joined.set(part, at);
|
|
22367
|
+
at += part.length;
|
|
22368
|
+
}
|
|
22369
|
+
return joined;
|
|
22370
|
+
}
|
|
22034
22371
|
var ChildProcess = class extends EventEmitter4 {
|
|
22035
22372
|
stdout = new streamModule4.PassThrough();
|
|
22036
22373
|
stderr = new streamModule4.PassThrough();
|
|
@@ -22235,7 +22572,7 @@ function unavailable(name) {
|
|
|
22235
22572
|
const list = Array.isArray(args[1]) ? args[1].map(String) : [];
|
|
22236
22573
|
const command = file3 ? [file3, ...list].join(" ") : void 0;
|
|
22237
22574
|
const error = new Error(
|
|
22238
|
-
`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.
|
|
22575
|
+
`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.")
|
|
22239
22576
|
);
|
|
22240
22577
|
error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
|
|
22241
22578
|
throw error;
|
|
@@ -22523,6 +22860,11 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
22523
22860
|
edit(sequence, key) {
|
|
22524
22861
|
if (this.closed) return;
|
|
22525
22862
|
const name = key.name;
|
|
22863
|
+
if (key.ctrl && name === "c") {
|
|
22864
|
+
if (this.listenerCount("SIGINT") > 0) this.emit("SIGINT");
|
|
22865
|
+
else this.close();
|
|
22866
|
+
return;
|
|
22867
|
+
}
|
|
22526
22868
|
if (name === "return" || name === "enter") {
|
|
22527
22869
|
const value = this.line;
|
|
22528
22870
|
this.line = "";
|
|
@@ -22618,7 +22960,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
22618
22960
|
};
|
|
22619
22961
|
const createInterface = (options, output) => {
|
|
22620
22962
|
if (options && typeof options === "object" && !options.on) {
|
|
22621
|
-
const resolvedOutput2 = options.output
|
|
22963
|
+
const resolvedOutput2 = options.output;
|
|
22622
22964
|
const instance = new Interface(
|
|
22623
22965
|
options.input ?? defaultInput(),
|
|
22624
22966
|
resolvedOutput2,
|
|
@@ -22627,7 +22969,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
22627
22969
|
if (typeof options.prompt === "string") instance.setPrompt(options.prompt);
|
|
22628
22970
|
return instance;
|
|
22629
22971
|
}
|
|
22630
|
-
const resolvedOutput = output
|
|
22972
|
+
const resolvedOutput = output;
|
|
22631
22973
|
return new Interface(options ?? defaultInput(), resolvedOutput, isTerminal(void 0, resolvedOutput));
|
|
22632
22974
|
};
|
|
22633
22975
|
const noop = () => {
|
|
@@ -22896,7 +23238,7 @@ function createCoreModules(options) {
|
|
|
22896
23238
|
"stream/promises": createStreamPromises(),
|
|
22897
23239
|
string_decoder: stringDecoderModule,
|
|
22898
23240
|
timers: { ...timersModule, ...timers.api },
|
|
22899
|
-
"timers/promises": createTimerPromises(),
|
|
23241
|
+
"timers/promises": createTimerPromises(timers.api),
|
|
22900
23242
|
tty: { isatty: () => false, ReadStream: streamModule4.Readable, WriteStream: streamModule4.Writable },
|
|
22901
23243
|
url: url_module_default,
|
|
22902
23244
|
util: util_module_default,
|
|
@@ -23641,10 +23983,10 @@ function createTrackedTimers() {
|
|
|
23641
23983
|
cancelAll
|
|
23642
23984
|
};
|
|
23643
23985
|
}
|
|
23644
|
-
function createTimerPromises() {
|
|
23986
|
+
function createTimerPromises(timers) {
|
|
23645
23987
|
return {
|
|
23646
|
-
setTimeout: (delay, value) => new Promise((resolve2) => setTimeout(resolve2, delay, value)),
|
|
23647
|
-
setImmediate: (value) => new Promise((resolve2) =>
|
|
23988
|
+
setTimeout: (delay, value) => new Promise((resolve2) => timers.setTimeout(resolve2, delay, value)),
|
|
23989
|
+
setImmediate: (value) => new Promise((resolve2) => timers.setImmediate(resolve2, value))
|
|
23648
23990
|
};
|
|
23649
23991
|
}
|
|
23650
23992
|
function createAsyncHooksModule() {
|
|
@@ -24352,11 +24694,8 @@ function ensureProcessGlobal() {
|
|
|
24352
24694
|
// src/runtime/host-rolldown.ts
|
|
24353
24695
|
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'] }";
|
|
24354
24696
|
async function loadHostRolldownBinding() {
|
|
24355
|
-
|
|
24356
|
-
|
|
24357
|
-
"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."
|
|
24358
|
-
);
|
|
24359
|
-
}
|
|
24697
|
+
const reason = syncChannelUnavailableReason();
|
|
24698
|
+
if (reason) throw new Error(`SandboxedJs threaded WASM unavailable: ${reason}`);
|
|
24360
24699
|
try {
|
|
24361
24700
|
const loaded = await import('@rolldown/binding-wasm32-wasi');
|
|
24362
24701
|
return "__fs" in loaded ? { ...loaded } : loaded.default ?? loaded;
|
|
@@ -24364,9 +24703,9 @@ async function loadHostRolldownBinding() {
|
|
|
24364
24703
|
if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
|
|
24365
24704
|
console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
|
|
24366
24705
|
}
|
|
24367
|
-
const
|
|
24706
|
+
const reason2 = error instanceof Error ? error.message : String(error);
|
|
24368
24707
|
throw new Error(
|
|
24369
|
-
`The optional Rolldown WASI binding could not be loaded: ${
|
|
24708
|
+
`The optional Rolldown WASI binding could not be loaded: ${reason2}` + (typeof window === "undefined" ? "" : `
|
|
24370
24709
|
|
|
24371
24710
|
${BUNDLER_HINT}`),
|
|
24372
24711
|
{ cause: error }
|
|
@@ -24477,6 +24816,8 @@ var LocalProcess = class extends EventEmitter4 {
|
|
|
24477
24816
|
}
|
|
24478
24817
|
/** End the process now, for an asynchronous `process.exit`. */
|
|
24479
24818
|
exitNow(code) {
|
|
24819
|
+
this.killed = true;
|
|
24820
|
+
this.cleanup?.();
|
|
24480
24821
|
this.finish(code);
|
|
24481
24822
|
this.resolveKilled();
|
|
24482
24823
|
}
|
|
@@ -24660,6 +25001,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24660
25001
|
)
|
|
24661
25002
|
};
|
|
24662
25003
|
disposed = false;
|
|
25004
|
+
running = /* @__PURE__ */ new Set();
|
|
24663
25005
|
workdir;
|
|
24664
25006
|
env;
|
|
24665
25007
|
aliases;
|
|
@@ -24701,8 +25043,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24701
25043
|
const cwd = typeof options.cwd === "string" ? options.cwd : this.workdir;
|
|
24702
25044
|
const env2 = { ...this.env, ...isRecord(options.env) ? options.env : {} };
|
|
24703
25045
|
const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
|
|
24704
|
-
|
|
25046
|
+
const process2 = new LocalProcess(async (proc) => {
|
|
24705
25047
|
await this.prepareRolldown(cwd, env2);
|
|
25048
|
+
if (proc.isKilled()) return 137;
|
|
24706
25049
|
const untrack = trackProcess(proc);
|
|
24707
25050
|
let requestedExit = 0;
|
|
24708
25051
|
let engine;
|
|
@@ -24730,7 +25073,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24730
25073
|
onRawMode: (enabled) => proc.emit("rawmode", enabled)
|
|
24731
25074
|
});
|
|
24732
25075
|
proc.acceptInput((data) => core.writeStdin(data), () => core.endStdin());
|
|
25076
|
+
const hostWork = new HostModuleTracker();
|
|
24733
25077
|
proc.onKill(() => {
|
|
25078
|
+
hostWork.dispose();
|
|
24734
25079
|
core.cancelTimers();
|
|
24735
25080
|
this.router.closeOwner(owner);
|
|
24736
25081
|
});
|
|
@@ -24739,21 +25084,22 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24739
25084
|
builtins: core.builtins,
|
|
24740
25085
|
globals: core.globals,
|
|
24741
25086
|
aliases: this.aliases,
|
|
24742
|
-
overrides: this.modules
|
|
25087
|
+
overrides: Object.fromEntries(Object.entries(this.modules).map(([key, value]) => [key, hostWork.wrap(value)]))
|
|
24743
25088
|
});
|
|
24744
25089
|
try {
|
|
24745
|
-
await engine.run(script);
|
|
24746
|
-
await this.settle(owner, core.pendingHandles, core.
|
|
24747
|
-
if (this.router.activePorts(owner).length) {
|
|
24748
|
-
await proc.waitForKill();
|
|
24749
|
-
return 137;
|
|
24750
|
-
}
|
|
25090
|
+
await Promise.race([engine.run(script), proc.waitForKill()]);
|
|
25091
|
+
await this.settle(owner, core.pendingHandles, core.readingStdin, () => core.pendingRequests() + hostWork.pending(), () => proc.isKilled());
|
|
24751
25092
|
return requestedExit;
|
|
24752
25093
|
} finally {
|
|
25094
|
+
hostWork.dispose();
|
|
25095
|
+
core.cancelTimers();
|
|
24753
25096
|
untrack();
|
|
24754
25097
|
this.router.closeOwner(owner);
|
|
24755
25098
|
}
|
|
24756
25099
|
});
|
|
25100
|
+
this.running.add(process2);
|
|
25101
|
+
void process2.completion.then(() => this.running.delete(process2));
|
|
25102
|
+
return process2;
|
|
24757
25103
|
}
|
|
24758
25104
|
/**
|
|
24759
25105
|
* Rolldown's JavaScript API synchronously requires its compiled binding.
|
|
@@ -24789,19 +25135,13 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24789
25135
|
* A plain script that has genuinely finished falls straight through both,
|
|
24790
25136
|
* costing a handful of empty turns.
|
|
24791
25137
|
*/
|
|
24792
|
-
async settle(owner, pendingHandles,
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
|
|
24796
|
-
|
|
24797
|
-
|
|
24798
|
-
|
|
24799
|
-
while (!this.router.activePorts(owner).length && !killed() && Date.now() < deadline) {
|
|
24800
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
24801
|
-
}
|
|
24802
|
-
}
|
|
24803
|
-
while (!this.router.activePorts(owner).length && !killed() && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
|
|
24804
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
25138
|
+
async settle(owner, pendingHandles, readingStdin, pendingRequests, killed) {
|
|
25139
|
+
let idleTurns = 0;
|
|
25140
|
+
while (!killed()) {
|
|
25141
|
+
const busy = this.router.referencedPorts(owner).length > 0 || pendingHandles() > 0 || pendingRequests() > 0 || readingStdin();
|
|
25142
|
+
idleTurns = busy ? 0 : idleTurns + 1;
|
|
25143
|
+
if (idleTurns >= DRAIN_TURNS) return;
|
|
25144
|
+
await new Promise((resolve2) => setTimeout(resolve2, busy ? 5 : 0));
|
|
24805
25145
|
}
|
|
24806
25146
|
}
|
|
24807
25147
|
async request(_port, _init = {}) {
|
|
@@ -24818,6 +25158,8 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24818
25158
|
}
|
|
24819
25159
|
teardown() {
|
|
24820
25160
|
this.disposed = true;
|
|
25161
|
+
for (const process2 of this.running) process2.kill();
|
|
25162
|
+
this.running.clear();
|
|
24821
25163
|
this.router.closeAll();
|
|
24822
25164
|
this.esbuild?.stop();
|
|
24823
25165
|
}
|
|
@@ -24887,103 +25229,6 @@ function attachRolldownMirror(binding, volume, root) {
|
|
|
24887
25229
|
volume.attach(fs, root);
|
|
24888
25230
|
}
|
|
24889
25231
|
|
|
24890
|
-
// src/runtime/sync-channel.ts
|
|
24891
|
-
var STATE = 0;
|
|
24892
|
-
var LENGTH = 1;
|
|
24893
|
-
var MORE = 2;
|
|
24894
|
-
var CONTROL_WORDS = 4;
|
|
24895
|
-
var STATE_REQUEST = 1;
|
|
24896
|
-
var STATE_RESPONSE = 2;
|
|
24897
|
-
var STATE_CONTINUE = 3;
|
|
24898
|
-
var STATE_CLOSED = 4;
|
|
24899
|
-
function createSyncChannelBuffers(capacityBytes = 1 << 20) {
|
|
24900
|
-
return {
|
|
24901
|
-
control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
|
|
24902
|
-
data: new SharedArrayBuffer(capacityBytes)
|
|
24903
|
-
};
|
|
24904
|
-
}
|
|
24905
|
-
function syncChannelSupported() {
|
|
24906
|
-
if (typeof SharedArrayBuffer !== "function") return false;
|
|
24907
|
-
if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return false;
|
|
24908
|
-
if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) return false;
|
|
24909
|
-
return true;
|
|
24910
|
-
}
|
|
24911
|
-
var SyncChannelServer = class {
|
|
24912
|
-
constructor(buffers, handle) {
|
|
24913
|
-
this.handle = handle;
|
|
24914
|
-
this.control = new Int32Array(buffers.control);
|
|
24915
|
-
this.data = new Uint8Array(buffers.data);
|
|
24916
|
-
this.capacity = this.data.length;
|
|
24917
|
-
}
|
|
24918
|
-
handle;
|
|
24919
|
-
control;
|
|
24920
|
-
data;
|
|
24921
|
-
capacity;
|
|
24922
|
-
/** Request chunks gathered so far, and the response still to be sent. */
|
|
24923
|
-
incoming = [];
|
|
24924
|
-
outgoing = null;
|
|
24925
|
-
sent = 0;
|
|
24926
|
-
closed = false;
|
|
24927
|
-
/** Call from the wake message the client sends after each chunk. */
|
|
24928
|
-
async pump() {
|
|
24929
|
-
if (this.closed) return;
|
|
24930
|
-
const state = Atomics.load(this.control, STATE);
|
|
24931
|
-
if (state === STATE_REQUEST) {
|
|
24932
|
-
const size = Atomics.load(this.control, LENGTH);
|
|
24933
|
-
this.incoming.push(this.data.slice(0, size));
|
|
24934
|
-
if (Atomics.load(this.control, MORE) === 1) {
|
|
24935
|
-
this.publish(STATE_CONTINUE);
|
|
24936
|
-
return;
|
|
24937
|
-
}
|
|
24938
|
-
const request = concat3(this.incoming);
|
|
24939
|
-
this.incoming = [];
|
|
24940
|
-
let response;
|
|
24941
|
-
try {
|
|
24942
|
-
response = await this.handle(request);
|
|
24943
|
-
} catch {
|
|
24944
|
-
response = new Uint8Array();
|
|
24945
|
-
}
|
|
24946
|
-
if (this.closed) return;
|
|
24947
|
-
this.outgoing = response;
|
|
24948
|
-
this.sent = 0;
|
|
24949
|
-
this.sendChunk();
|
|
24950
|
-
return;
|
|
24951
|
-
}
|
|
24952
|
-
if (state === STATE_CONTINUE) this.sendChunk();
|
|
24953
|
-
}
|
|
24954
|
-
/** Release a blocked client, e.g. when the container is torn down. */
|
|
24955
|
-
close() {
|
|
24956
|
-
if (this.closed) return;
|
|
24957
|
-
this.closed = true;
|
|
24958
|
-
Atomics.store(this.control, STATE, STATE_CLOSED);
|
|
24959
|
-
Atomics.notify(this.control, STATE);
|
|
24960
|
-
}
|
|
24961
|
-
sendChunk() {
|
|
24962
|
-
const payload = this.outgoing ?? new Uint8Array();
|
|
24963
|
-
const size = Math.min(this.capacity, payload.length - this.sent);
|
|
24964
|
-
this.data.set(payload.subarray(this.sent, this.sent + size), 0);
|
|
24965
|
-
this.sent += size;
|
|
24966
|
-
Atomics.store(this.control, LENGTH, size);
|
|
24967
|
-
Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
|
|
24968
|
-
this.publish(STATE_RESPONSE);
|
|
24969
|
-
}
|
|
24970
|
-
publish(state) {
|
|
24971
|
-
Atomics.store(this.control, STATE, state);
|
|
24972
|
-
Atomics.notify(this.control, STATE);
|
|
24973
|
-
}
|
|
24974
|
-
};
|
|
24975
|
-
function concat3(parts) {
|
|
24976
|
-
if (parts.length === 1) return parts[0];
|
|
24977
|
-
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
24978
|
-
const joined = new Uint8Array(total);
|
|
24979
|
-
let at = 0;
|
|
24980
|
-
for (const part of parts) {
|
|
24981
|
-
joined.set(part, at);
|
|
24982
|
-
at += part.length;
|
|
24983
|
-
}
|
|
24984
|
-
return joined;
|
|
24985
|
-
}
|
|
24986
|
-
|
|
24987
25232
|
// src/runtime/remote-volume.ts
|
|
24988
25233
|
var encoder7 = new TextEncoder();
|
|
24989
25234
|
var decoder7 = new TextDecoder();
|
|
@@ -25078,24 +25323,32 @@ function defaultWorkerUrl() {
|
|
|
25078
25323
|
return new URL("./worker-entry.js", import.meta.url);
|
|
25079
25324
|
}
|
|
25080
25325
|
function hasDomWorker() {
|
|
25081
|
-
return typeof Worker === "function"
|
|
25326
|
+
return typeof Worker === "function";
|
|
25082
25327
|
}
|
|
25083
25328
|
async function startRuntimeWorker(options = {}) {
|
|
25084
25329
|
const url = options.url ?? defaultWorkerUrl();
|
|
25085
25330
|
const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25089
|
-
|
|
25331
|
+
try {
|
|
25332
|
+
await new Promise((resolve2, reject) => {
|
|
25333
|
+
const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
|
|
25334
|
+
worker.onMessage((message) => {
|
|
25335
|
+
if (message?.type === "sandboxedjs:ready") {
|
|
25336
|
+
clearTimeout(timer);
|
|
25337
|
+
resolve2();
|
|
25338
|
+
}
|
|
25339
|
+
});
|
|
25340
|
+
worker.onError((error) => {
|
|
25090
25341
|
clearTimeout(timer);
|
|
25091
|
-
|
|
25092
|
-
}
|
|
25093
|
-
});
|
|
25094
|
-
worker.onError((error) => {
|
|
25095
|
-
clearTimeout(timer);
|
|
25096
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
25342
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
25343
|
+
});
|
|
25097
25344
|
});
|
|
25098
|
-
})
|
|
25345
|
+
} catch (error) {
|
|
25346
|
+
try {
|
|
25347
|
+
await worker.terminate();
|
|
25348
|
+
} catch {
|
|
25349
|
+
}
|
|
25350
|
+
throw error;
|
|
25351
|
+
}
|
|
25099
25352
|
return worker;
|
|
25100
25353
|
}
|
|
25101
25354
|
async function startDomWorker(url) {
|
|
@@ -25156,6 +25409,8 @@ var WorkerProcess = class extends EventEmitter4 {
|
|
|
25156
25409
|
started = false;
|
|
25157
25410
|
pendingInput = [];
|
|
25158
25411
|
inputEnded = false;
|
|
25412
|
+
inheritedInput;
|
|
25413
|
+
ownRawMode = false;
|
|
25159
25414
|
on(event, listener) {
|
|
25160
25415
|
return super.on(event, listener);
|
|
25161
25416
|
}
|
|
@@ -25181,13 +25436,38 @@ var WorkerProcess = class extends EventEmitter4 {
|
|
|
25181
25436
|
* program is still loading, and dropping them loses the answer to a prompt.
|
|
25182
25437
|
*/
|
|
25183
25438
|
write(data) {
|
|
25439
|
+
if (this.inheritedInput) {
|
|
25440
|
+
this.inheritedInput.sendStdin(data);
|
|
25441
|
+
return;
|
|
25442
|
+
}
|
|
25184
25443
|
if (this.started) this.worker.postMessage({ type: "stdin", data });
|
|
25185
25444
|
else this.pendingInput.push(data);
|
|
25186
25445
|
}
|
|
25187
25446
|
endInput() {
|
|
25188
25447
|
this.inputEnded = true;
|
|
25448
|
+
if (this.inheritedInput) {
|
|
25449
|
+
this.inheritedInput.endStdin?.();
|
|
25450
|
+
return;
|
|
25451
|
+
}
|
|
25189
25452
|
if (this.started) this.worker.postMessage({ type: "stdin-end" });
|
|
25190
25453
|
}
|
|
25454
|
+
rawMode(enabled) {
|
|
25455
|
+
this.ownRawMode = enabled;
|
|
25456
|
+
if (!this.inheritedInput) this.emit("rawmode", enabled);
|
|
25457
|
+
}
|
|
25458
|
+
/** The parent is blocked in Atomics.wait, so inherited input must bypass it. */
|
|
25459
|
+
inheritInput(child) {
|
|
25460
|
+
this.inheritedInput = child;
|
|
25461
|
+
this.emit("rawmode", false);
|
|
25462
|
+
child.on("rawmode", (enabled) => {
|
|
25463
|
+
if (this.inheritedInput === child) this.emit("rawmode", Boolean(enabled));
|
|
25464
|
+
});
|
|
25465
|
+
return () => {
|
|
25466
|
+
if (this.inheritedInput !== child) return;
|
|
25467
|
+
this.inheritedInput = void 0;
|
|
25468
|
+
this.emit("rawmode", this.ownRawMode);
|
|
25469
|
+
};
|
|
25470
|
+
}
|
|
25191
25471
|
kill(_signal = "SIGTERM") {
|
|
25192
25472
|
this.worker.postMessage({ type: "kill" });
|
|
25193
25473
|
this.finish(137);
|
|
@@ -25208,25 +25488,30 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25208
25488
|
super(options);
|
|
25209
25489
|
this.workerUrl = options.workerUrl;
|
|
25210
25490
|
}
|
|
25211
|
-
/**
|
|
25212
|
-
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
* and keeps working.
|
|
25219
|
-
*/
|
|
25220
|
-
static async tryBoot(options = {}) {
|
|
25221
|
-
if (!syncChannelSupported()) return null;
|
|
25222
|
-
if (options.modules && Object.keys(options.modules).length > 0) return null;
|
|
25491
|
+
/** Boot without silently dropping synchronous child-process support. */
|
|
25492
|
+
static async boot(options = {}) {
|
|
25493
|
+
const reason = syncChannelUnavailableReason();
|
|
25494
|
+
if (reason) throw new Error(`SandboxedJs worker runtime unavailable: ${reason}`);
|
|
25495
|
+
if (options.modules && Object.keys(options.modules).length > 0) {
|
|
25496
|
+
throw new Error("SandboxedJs worker runtime cannot clone host-supplied modules. Use isolation: 'realm' explicitly.");
|
|
25497
|
+
}
|
|
25223
25498
|
const pod = new _WorkerRuntimePod(options);
|
|
25224
25499
|
try {
|
|
25225
25500
|
const probe = await startRuntimeWorker({ ...options.workerUrl ? { url: options.workerUrl } : {}, timeoutMs: 1e4 });
|
|
25226
25501
|
await probe.terminate();
|
|
25227
25502
|
return pod;
|
|
25228
|
-
} catch {
|
|
25503
|
+
} catch (cause) {
|
|
25229
25504
|
pod.teardown();
|
|
25505
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
25506
|
+
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 });
|
|
25507
|
+
}
|
|
25508
|
+
}
|
|
25509
|
+
/** Compatibility mode, with an observable explanation for every fallback. */
|
|
25510
|
+
static async tryBoot(options = {}, onFallback = (error) => console.warn(`[sandboxedjs] Falling back to the realm runtime; synchronous child processes are unavailable. ${error.message}`)) {
|
|
25511
|
+
try {
|
|
25512
|
+
return await _WorkerRuntimePod.boot(options);
|
|
25513
|
+
} catch (error) {
|
|
25514
|
+
onFallback(error instanceof Error ? error : new Error(String(error)));
|
|
25230
25515
|
return null;
|
|
25231
25516
|
}
|
|
25232
25517
|
}
|
|
@@ -25249,11 +25534,14 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25249
25534
|
const streams = { target: null };
|
|
25250
25535
|
const server = new SyncChannelServer(buffers, serveSyncSyscalls({
|
|
25251
25536
|
volume: this.volume,
|
|
25252
|
-
spawnChild: (request) => this.runChildToCompletion(request, streams.target)
|
|
25537
|
+
spawnChild: (request) => this.runChildToCompletion(request, streams.target, ownedChildren)
|
|
25253
25538
|
}));
|
|
25254
25539
|
const entry = { worker, server };
|
|
25255
25540
|
this.live.add(entry);
|
|
25541
|
+
const ownedChildren = /* @__PURE__ */ new Set();
|
|
25256
25542
|
const process2 = new WorkerProcess(worker, () => {
|
|
25543
|
+
for (const child of ownedChildren) child.kill("SIGTERM");
|
|
25544
|
+
ownedChildren.clear();
|
|
25257
25545
|
this.live.delete(entry);
|
|
25258
25546
|
server.close();
|
|
25259
25547
|
this.closeProxies(owner);
|
|
@@ -25274,7 +25562,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25274
25562
|
process2.error(String(message.text));
|
|
25275
25563
|
return;
|
|
25276
25564
|
case "rawmode":
|
|
25277
|
-
process2.
|
|
25565
|
+
process2.rawMode(Boolean(message.enabled));
|
|
25278
25566
|
return;
|
|
25279
25567
|
case "exit":
|
|
25280
25568
|
process2.finish(Number(message.code));
|
|
@@ -25282,11 +25570,20 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25282
25570
|
case "listen":
|
|
25283
25571
|
this.proxyPort(Number(message.port), owner, worker);
|
|
25284
25572
|
return;
|
|
25573
|
+
case "close-port": {
|
|
25574
|
+
const port = Number(message.port);
|
|
25575
|
+
const proxy = this.proxies.get(port);
|
|
25576
|
+
if (proxy?.owner === owner) {
|
|
25577
|
+
proxy.close();
|
|
25578
|
+
this.proxies.delete(port);
|
|
25579
|
+
}
|
|
25580
|
+
return;
|
|
25581
|
+
}
|
|
25285
25582
|
case "http-response":
|
|
25286
25583
|
this.settleProxied(Number(message.id), message.response);
|
|
25287
25584
|
return;
|
|
25288
25585
|
case "child-start":
|
|
25289
|
-
this.startChild(worker, children, message);
|
|
25586
|
+
this.startChild(worker, children, message, ownedChildren);
|
|
25290
25587
|
return;
|
|
25291
25588
|
case "child-stdin":
|
|
25292
25589
|
children.get(message.id)?.sendStdin?.(String(message.data));
|
|
@@ -25317,16 +25614,21 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25317
25614
|
return process2;
|
|
25318
25615
|
}
|
|
25319
25616
|
/** Start an asynchronous child on the host's behalf and relay its events. */
|
|
25320
|
-
startChild(worker, children, message) {
|
|
25617
|
+
startChild(worker, children, message, owned) {
|
|
25321
25618
|
const handle = this.processManager.spawn(message.config);
|
|
25322
25619
|
children.set(message.id, handle);
|
|
25620
|
+
owned.add(handle);
|
|
25621
|
+
handle.on("exit", () => {
|
|
25622
|
+
owned.delete(handle);
|
|
25623
|
+
children.delete(message.id);
|
|
25624
|
+
});
|
|
25323
25625
|
for (const event of ["stdout", "stderr", "exit"]) {
|
|
25324
25626
|
handle.on(event, (value) => worker.postMessage({ type: "child-event", id: message.id, event, value }));
|
|
25325
25627
|
}
|
|
25326
25628
|
handle.exec();
|
|
25327
25629
|
}
|
|
25328
25630
|
/** Run a child to completion and collect it, for the guest's `spawnSync`. */
|
|
25329
|
-
runChildToCompletion(request, streamTo) {
|
|
25631
|
+
runChildToCompletion(request, streamTo, owned) {
|
|
25330
25632
|
return new Promise((resolve2) => {
|
|
25331
25633
|
let handle;
|
|
25332
25634
|
try {
|
|
@@ -25342,9 +25644,13 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25342
25644
|
resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure.code ? { code: failure.code } : {}, message: failure.message } });
|
|
25343
25645
|
return;
|
|
25344
25646
|
}
|
|
25647
|
+
owned.add(handle);
|
|
25648
|
+
handle.on("exit", () => owned.delete(handle));
|
|
25345
25649
|
let stdout = "";
|
|
25346
25650
|
let stderr = "";
|
|
25347
25651
|
const live = request.inheritStdio ? streamTo : null;
|
|
25652
|
+
const restoreInput = live?.inheritInput(handle);
|
|
25653
|
+
if (restoreInput) handle.on("exit", restoreInput);
|
|
25348
25654
|
handle.on("stdout", (text2) => {
|
|
25349
25655
|
stdout += text2;
|
|
25350
25656
|
live?.output(text2);
|
|
@@ -25498,7 +25804,7 @@ var Container = class _Container {
|
|
|
25498
25804
|
...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
|
|
25499
25805
|
};
|
|
25500
25806
|
const workerOptions = { ...podOptions, ...opts.workerUrl ? { workerUrl: opts.workerUrl } : {} };
|
|
25501
|
-
const pod = opts.pod ?? (opts.isolation === "realm" ? null : await WorkerRuntimePod.tryBoot(workerOptions)) ?? await LocalRuntimePod.boot(podOptions);
|
|
25807
|
+
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);
|
|
25502
25808
|
const kernel = new Kernel({
|
|
25503
25809
|
pod,
|
|
25504
25810
|
hostname: opts.hostname ?? "sandbox",
|
|
@@ -26006,7 +26312,7 @@ var Terminal = class {
|
|
|
26006
26312
|
if (this.running) {
|
|
26007
26313
|
const raw = this.currentStdin?.rawMode === true;
|
|
26008
26314
|
if (ch === CTRL_C && !raw) {
|
|
26009
|
-
this.session.
|
|
26315
|
+
if (this.currentStdin) this.session.shell.interruptForeground("SIGINT", this.currentStdin);
|
|
26010
26316
|
this.write("^C\r\n");
|
|
26011
26317
|
return;
|
|
26012
26318
|
}
|
|
@@ -26396,7 +26702,7 @@ function serveContainerOn(port, box) {
|
|
|
26396
26702
|
}
|
|
26397
26703
|
async function createPreview(box, options = {}) {
|
|
26398
26704
|
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
|
|
26399
|
-
const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js", import.meta.url);
|
|
26705
|
+
const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js?no-inline", import.meta.url);
|
|
26400
26706
|
let registration;
|
|
26401
26707
|
try {
|
|
26402
26708
|
registration = await navigator.serviceWorker.register(scriptUrl, {
|