sandboxedjs 0.1.11 → 0.1.13
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 +14 -0
- package/dist/index.cjs +155 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +155 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -620,6 +620,20 @@ Two ways in, both byte-exact:
|
|
|
620
620
|
await box.fs.writeFile("/workspace/uploads/clip.mp4", bytes); // creates missing directories
|
|
621
621
|
```
|
|
622
622
|
|
|
623
|
+
The `File` returned by a browser picker can be passed directly too. It is read
|
|
624
|
+
as bytes and written at the exact container path, including an application-
|
|
625
|
+
generated temporary path:
|
|
626
|
+
|
|
627
|
+
```js
|
|
628
|
+
const picked = input.files[0];
|
|
629
|
+
const sourcePath = "/tmp/transcribe-quran-52fjch/source-1786307713702-baleela.f32le";
|
|
630
|
+
await box.fs.writeFile(sourcePath, picked);
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Do not pass `picked.path` (or any other host path) to a command in the
|
|
634
|
+
container. Browser file pickers provide file data, not a path the sandbox can
|
|
635
|
+
see.
|
|
636
|
+
|
|
623
637
|
```js
|
|
624
638
|
// 2. Through a server running inside the container — a picker in the previewed
|
|
625
639
|
// app posting to its own backend. Publish the port and upload normally.
|
package/dist/index.cjs
CHANGED
|
@@ -23480,6 +23480,14 @@ function installUserland(kernel) {
|
|
|
23480
23480
|
|
|
23481
23481
|
// src/container/fs.ts
|
|
23482
23482
|
init_path();
|
|
23483
|
+
async function bytesFor(data) {
|
|
23484
|
+
if (data instanceof Uint8Array) return data;
|
|
23485
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
23486
|
+
if (ArrayBuffer.isView(data)) {
|
|
23487
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
23488
|
+
}
|
|
23489
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
23490
|
+
}
|
|
23483
23491
|
var ContainerFs = class {
|
|
23484
23492
|
constructor(kernel) {
|
|
23485
23493
|
this.kernel = kernel;
|
|
@@ -23496,10 +23504,12 @@ var ContainerFs = class {
|
|
|
23496
23504
|
const abs = clean(path);
|
|
23497
23505
|
const parent = dirname(abs);
|
|
23498
23506
|
if (!this.vfs.lexists(parent)) this.vfs.mkdir(parent, { recursive: true, mode: 493 });
|
|
23499
|
-
|
|
23507
|
+
const content = typeof data === "string" ? data : await bytesFor(data);
|
|
23508
|
+
this.vfs.writeFile(abs, content, { privileged: true, mode: opts.mode ?? 420 });
|
|
23500
23509
|
}
|
|
23501
23510
|
async appendFile(path, data) {
|
|
23502
|
-
|
|
23511
|
+
const content = typeof data === "string" ? data : await bytesFor(data);
|
|
23512
|
+
this.vfs.appendFile(clean(path), content, { privileged: true });
|
|
23503
23513
|
}
|
|
23504
23514
|
async readdir(path, opts) {
|
|
23505
23515
|
if (opts?.withFileTypes) return this.vfs.readdirWithTypes(clean(path));
|
|
@@ -23665,6 +23675,146 @@ var Session = class {
|
|
|
23665
23675
|
}
|
|
23666
23676
|
};
|
|
23667
23677
|
|
|
23678
|
+
// src/runtime/node-child-process-bridge.ts
|
|
23679
|
+
var KernelChildProcess = class {
|
|
23680
|
+
pid;
|
|
23681
|
+
command;
|
|
23682
|
+
args;
|
|
23683
|
+
parentPid;
|
|
23684
|
+
state = "running";
|
|
23685
|
+
exitCode;
|
|
23686
|
+
stdout = "";
|
|
23687
|
+
stderr = "";
|
|
23688
|
+
listeners = /* @__PURE__ */ new Map();
|
|
23689
|
+
kernel;
|
|
23690
|
+
cred;
|
|
23691
|
+
cwd;
|
|
23692
|
+
env;
|
|
23693
|
+
stdin = new Pipe();
|
|
23694
|
+
process;
|
|
23695
|
+
started = false;
|
|
23696
|
+
cancelled = false;
|
|
23697
|
+
constructor(kernel, cred, config, pid) {
|
|
23698
|
+
this.kernel = kernel;
|
|
23699
|
+
this.cred = cred;
|
|
23700
|
+
this.pid = pid;
|
|
23701
|
+
this.command = config.command;
|
|
23702
|
+
this.args = config.args?.slice() ?? [];
|
|
23703
|
+
this.parentPid = config.parentPid;
|
|
23704
|
+
this.cwd = config.cwd ?? "/";
|
|
23705
|
+
this.env = { ...config.env };
|
|
23706
|
+
}
|
|
23707
|
+
on(event, listener) {
|
|
23708
|
+
let listeners = this.listeners.get(event);
|
|
23709
|
+
if (!listeners) {
|
|
23710
|
+
listeners = /* @__PURE__ */ new Set();
|
|
23711
|
+
this.listeners.set(event, listeners);
|
|
23712
|
+
}
|
|
23713
|
+
listeners.add(listener);
|
|
23714
|
+
return this;
|
|
23715
|
+
}
|
|
23716
|
+
emit(event, ...args) {
|
|
23717
|
+
for (const listener of this.listeners.get(event) ?? []) listener(...args);
|
|
23718
|
+
}
|
|
23719
|
+
exec() {
|
|
23720
|
+
if (this.started) return;
|
|
23721
|
+
this.started = true;
|
|
23722
|
+
queueMicrotask(() => {
|
|
23723
|
+
if (this.cancelled) {
|
|
23724
|
+
this.finish(143);
|
|
23725
|
+
return;
|
|
23726
|
+
}
|
|
23727
|
+
const stdout = new ChildOutput(this, "stdout");
|
|
23728
|
+
const stderr = new ChildOutput(this, "stderr");
|
|
23729
|
+
try {
|
|
23730
|
+
this.process = this.kernel.spawn([this.command, ...this.args], {
|
|
23731
|
+
cwd: this.cwd,
|
|
23732
|
+
env: this.env,
|
|
23733
|
+
cred: this.cred,
|
|
23734
|
+
stdin: this.stdin,
|
|
23735
|
+
stdout,
|
|
23736
|
+
stderr,
|
|
23737
|
+
ppid: 1
|
|
23738
|
+
});
|
|
23739
|
+
void this.process.wait().then((code) => {
|
|
23740
|
+
stdout.end();
|
|
23741
|
+
stderr.end();
|
|
23742
|
+
this.finish(code);
|
|
23743
|
+
});
|
|
23744
|
+
} catch (error) {
|
|
23745
|
+
stderr.write(error instanceof Error ? error.message : String(error));
|
|
23746
|
+
stderr.end();
|
|
23747
|
+
stdout.end();
|
|
23748
|
+
this.finish(1);
|
|
23749
|
+
}
|
|
23750
|
+
});
|
|
23751
|
+
}
|
|
23752
|
+
sendStdin(data) {
|
|
23753
|
+
try {
|
|
23754
|
+
this.stdin.write(data);
|
|
23755
|
+
} catch {
|
|
23756
|
+
}
|
|
23757
|
+
}
|
|
23758
|
+
kill(signal = "SIGTERM") {
|
|
23759
|
+
if (this.process) {
|
|
23760
|
+
this.process.deliver(signal);
|
|
23761
|
+
return;
|
|
23762
|
+
}
|
|
23763
|
+
this.cancelled = true;
|
|
23764
|
+
}
|
|
23765
|
+
finish(code) {
|
|
23766
|
+
if (this.state === "exited") return;
|
|
23767
|
+
this.state = "exited";
|
|
23768
|
+
this.exitCode = code;
|
|
23769
|
+
this.emit("exit", code);
|
|
23770
|
+
}
|
|
23771
|
+
append(stream, text) {
|
|
23772
|
+
this[stream] += text;
|
|
23773
|
+
this.emit(stream, text);
|
|
23774
|
+
}
|
|
23775
|
+
};
|
|
23776
|
+
var ChildOutput = class {
|
|
23777
|
+
constructor(child, stream) {
|
|
23778
|
+
this.child = child;
|
|
23779
|
+
this.stream = stream;
|
|
23780
|
+
}
|
|
23781
|
+
child;
|
|
23782
|
+
stream;
|
|
23783
|
+
isTTY = false;
|
|
23784
|
+
closedState = false;
|
|
23785
|
+
decoder = new TextDecoder();
|
|
23786
|
+
get closed() {
|
|
23787
|
+
return this.closedState;
|
|
23788
|
+
}
|
|
23789
|
+
write(data) {
|
|
23790
|
+
if (this.closedState) return;
|
|
23791
|
+
const text = typeof data === "string" ? data : this.decoder.decode(data, { stream: true });
|
|
23792
|
+
if (text) this.child.append(this.stream, text);
|
|
23793
|
+
}
|
|
23794
|
+
end() {
|
|
23795
|
+
if (this.closedState) return;
|
|
23796
|
+
this.closedState = true;
|
|
23797
|
+
const tail2 = this.decoder.decode();
|
|
23798
|
+
if (tail2) this.child.append(this.stream, tail2);
|
|
23799
|
+
}
|
|
23800
|
+
};
|
|
23801
|
+
var nextBridgePid = 1073741824;
|
|
23802
|
+
function installNodeChildProcessBridge(pod, kernel, cred) {
|
|
23803
|
+
const manager = pod.processManager;
|
|
23804
|
+
const originalSpawn = manager.spawn;
|
|
23805
|
+
manager.spawn = (config) => {
|
|
23806
|
+
const resolved = kernel.resolveExecutable(config.command, config.cwd ?? "/", config.env ?? {}, cred);
|
|
23807
|
+
if (resolved?.kind === "builtin" && resolved.command?.name !== "node" && resolved.command?.name !== "nodejs") {
|
|
23808
|
+
return new KernelChildProcess(kernel, cred, config, nextBridgePid++);
|
|
23809
|
+
}
|
|
23810
|
+
return originalSpawn.call(manager, config);
|
|
23811
|
+
};
|
|
23812
|
+
return () => {
|
|
23813
|
+
if (manager.spawn === originalSpawn) return;
|
|
23814
|
+
manager.spawn = originalSpawn;
|
|
23815
|
+
};
|
|
23816
|
+
}
|
|
23817
|
+
|
|
23668
23818
|
// src/container/container.ts
|
|
23669
23819
|
var Container = class _Container {
|
|
23670
23820
|
kernel;
|
|
@@ -23675,6 +23825,7 @@ var Container = class _Container {
|
|
|
23675
23825
|
hooks;
|
|
23676
23826
|
disposed = false;
|
|
23677
23827
|
defaultSession = null;
|
|
23828
|
+
restoreNodeChildProcessBridge;
|
|
23678
23829
|
constructor(kernel, pod, opts) {
|
|
23679
23830
|
this.kernel = kernel;
|
|
23680
23831
|
this.pod = pod;
|
|
@@ -23703,6 +23854,7 @@ var Container = class _Container {
|
|
|
23703
23854
|
};
|
|
23704
23855
|
if (opts.timeoutMs !== void 0) this.defaults.timeoutMs = opts.timeoutMs;
|
|
23705
23856
|
this.hooks = { onStdout: opts.onStdout, onStderr: opts.onStderr };
|
|
23857
|
+
this.restoreNodeChildProcessBridge = installNodeChildProcessBridge(pod, kernel, cred);
|
|
23706
23858
|
}
|
|
23707
23859
|
// ── boot ──────────────────────────────────────────────────────────────────
|
|
23708
23860
|
static async create(opts = {}) {
|
|
@@ -24113,6 +24265,7 @@ var Container = class _Container {
|
|
|
24113
24265
|
dispose() {
|
|
24114
24266
|
if (this.disposed) return;
|
|
24115
24267
|
this.disposed = true;
|
|
24268
|
+
this.restoreNodeChildProcessBridge();
|
|
24116
24269
|
this.kernel.dispose();
|
|
24117
24270
|
try {
|
|
24118
24271
|
this.pod.teardown();
|