sandboxedjs 0.1.5 → 0.1.7

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 CHANGED
@@ -607,6 +607,30 @@ Those three use dynamic imports, so they only fail if you call them.
607
607
  removed and the seam is there, but treat browser support as "should work, unproven" rather than
608
608
  a tested claim.
609
609
 
610
+ ## Uploading files
611
+
612
+ A file picker in a browser never hands over a path — it hands over the file's bytes. So getting a
613
+ picked file into the container is an ordinary write, and there is nothing to mirror from the host
614
+ disk.
615
+
616
+ Two ways in, both byte-exact:
617
+
618
+ ```js
619
+ // 1. Directly, when your own server received the upload.
620
+ await box.fs.writeFile("/workspace/uploads/clip.mp4", bytes); // creates missing directories
621
+ ```
622
+
623
+ ```js
624
+ // 2. Through a server running inside the container — a picker in the previewed
625
+ // app posting to its own backend. Publish the port and upload normally.
626
+ const bridge = await box.expose(3000);
627
+ await fetch(`${bridge.url}/api/upload`, { method: "POST", body: file });
628
+ ```
629
+
630
+ Either way the file lands on the container's filesystem, and `ffmpeg`, Node and Python all see the
631
+ same bytes. Uploads are held in memory like the rest of the filesystem, so a very large video is
632
+ bounded by RAM.
633
+
610
634
  ## Video and audio
611
635
 
612
636
  `ffmpeg` and `ffprobe` are FFmpeg 5.1 compiled to WebAssembly, mounted on the container's own
package/dist/index.cjs CHANGED
@@ -23981,14 +23981,17 @@ var Container = class _Container {
23981
23981
  /** Send an HTTP request to a server running inside the container. */
23982
23982
  async request(port, init = {}) {
23983
23983
  this.assertActive();
23984
- const res = await this.pod.request(port, {
23984
+ const res = await this.dispatchBinary(port, init) ?? await this.pod.request(port, {
23985
23985
  method: init.method ?? "GET",
23986
23986
  path: init.path ?? "/",
23987
23987
  headers: init.headers ?? {},
23988
23988
  body: init.body ?? null
23989
23989
  });
23990
23990
  const bodyRaw = res.body;
23991
- const bytes = bodyRaw instanceof Uint8Array ? bodyRaw : typeof bodyRaw === "string" ? new TextEncoder().encode(bodyRaw) : new Uint8Array(0);
23991
+ const bytes = bodyRaw instanceof Uint8Array ? bodyRaw : (
23992
+ // The binary dispatcher answers with an ArrayBuffer.
23993
+ bodyRaw instanceof ArrayBuffer ? new Uint8Array(bodyRaw) : typeof bodyRaw === "string" ? new TextEncoder().encode(bodyRaw) : new Uint8Array(0)
23994
+ );
23992
23995
  const body = typeof bodyRaw === "string" ? bodyRaw : new TextDecoder().decode(bytes);
23993
23996
  return {
23994
23997
  status: res.statusCode ?? 200,
@@ -24001,6 +24004,40 @@ var Container = class _Container {
24001
24004
  }
24002
24005
  };
24003
24006
  }
24007
+ /**
24008
+ * Deliver a request whose body is bytes, without letting them become text.
24009
+ *
24010
+ * Nodepod's public `request()` runs the body through `toString("utf8")` on
24011
+ * its way in, so anything above `0x7f` is replaced: a five-byte payload
24012
+ * containing `0x89` and `0xff` arrives as nine. That silently destroys every
24013
+ * upload — an image or a video reaches the server the wrong size and no
24014
+ * longer decodes, with no error raised anywhere.
24015
+ *
24016
+ * Its own dispatcher one layer down does preserve bytes, so a binary body
24017
+ * goes straight there. This reaches past the published surface deliberately,
24018
+ * so it is written to fail soft: any shape it does not recognise returns
24019
+ * `null` and the caller falls back to the ordinary path, which is exactly
24020
+ * the behaviour that existed before. Text bodies never come through here.
24021
+ */
24022
+ async dispatchBinary(port, init) {
24023
+ if (!(init.body instanceof Uint8Array)) return null;
24024
+ const manager = this.pod._processManager;
24025
+ const dispatch = manager?.dispatchHttpRequest;
24026
+ if (typeof dispatch !== "function") return null;
24027
+ try {
24028
+ const response = await dispatch.call(
24029
+ manager,
24030
+ port,
24031
+ init.method ?? "GET",
24032
+ init.path ?? "/",
24033
+ init.headers ?? {},
24034
+ init.body
24035
+ );
24036
+ return response ?? null;
24037
+ } catch {
24038
+ return null;
24039
+ }
24040
+ }
24004
24041
  /** Wait until something inside the container answers on `port`. */
24005
24042
  waitForPort(port, opts = {}) {
24006
24043
  return this.net.waitForPort(port, opts);