aboutsummaryrefslogtreecommitdiffstats
path: root/lib/image.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/image.ts')
-rw-r--r--lib/image.ts50
1 files changed, 50 insertions, 0 deletions
diff --git a/lib/image.ts b/lib/image.ts
new file mode 100644
index 00000000..80bc65c3
--- /dev/null
+++ b/lib/image.ts
@@ -0,0 +1,50 @@
+import { warn } from "$lib/logging";
+
+// Decode + resize an image in a throwaway child process (lib/image-worker.ts)
+// so a native decoder crash cannot take the app down. Resolves with the webp
+// bytes; rejects with a short message on any failure (bad image, crash,
+// timeout). Never import sharp in the main process — that is the whole point.
+const WORKER = process.env.IMAGE_WORKER || "/home/bun/app/lib/image-worker.ts";
+const TIMEOUT_MS = 20_000;
+const MAX_OUTPUT = 20_000_000;
+
+export const convert = async (input: Buffer, width: number): Promise<Buffer> => {
+ const proc = Bun.spawn([process.execPath, WORKER, String(width)], {
+ stdin: new Blob([input]),
+ stdout: "pipe",
+ stderr: "pipe",
+ });
+
+ let timedOut = false;
+ const timer = setTimeout(() => {
+ timedOut = true;
+ proc.kill("SIGKILL");
+ }, TIMEOUT_MS);
+
+ try {
+ const [out, errText, code] = await Promise.all([
+ new Response(proc.stdout).arrayBuffer(),
+ new Response(proc.stderr).text(),
+ proc.exited,
+ ]);
+
+ if (timedOut) throw new Error("image conversion timed out");
+
+ if (code !== 0 || proc.signalCode) {
+ // Exit 2 is the worker's own "sharp rejected this file"; anything else
+ // (signal, Bun panic banner, exit 1) is the decoder dying — log loudly,
+ // this is exactly the event the isolation exists for.
+ const reason = errText.trim().split("\n").filter(Boolean).slice(-1)[0] || "";
+ if (code !== 2)
+ warn(`IMAGE_WORKER_CRASH code=${code} signal=${proc.signalCode} ${reason.slice(0, 200)}`);
+ throw new Error(code === 2 && reason ? reason.slice(0, 200) : "image conversion failed");
+ }
+
+ if (!out.byteLength || out.byteLength > MAX_OUTPUT)
+ throw new Error("image conversion produced no output");
+
+ return Buffer.from(out);
+ } finally {
+ clearTimeout(timer);
+ }
+};