managarten/services/mana-media
Till JS dff02d24a9 fix(mana-media): HEIC uploads from Chrome — sniff + transcode at the edge
iPhone HEIC photos uploaded through Chrome on macOS landed as
`mimeType: application/octet-stream` because Chrome doesn't recognise
the HEIC MIME and `file.type` was empty. The transform endpoint then
refused with `Transform only supported for images` (HTTP 400) and
the wardrobe Try-On flow surfaced this as `mana-media transform
failed for <id>: HTTP 400`. Even fixing the MIME wouldn't have been
enough — sharp's prebuilt binary ships the heif container format
without a HEVC decoder plugin (libde265 is omitted for patent
reasons), so the actual decode would still throw.

Three-part fix at the upload edge:

1. New `services/sniff.ts` — magic-byte sniffer for image MIMEs.
   Reads the first ~16 bytes and recognises JPEG, PNG, GIF, WebP,
   BMP, TIFF, HEIC, HEIF, AVIF. Returns `null` for everything else
   so the caller can fall back to whatever the browser claimed.

2. Upload route — sniffs every upload before passing the buffer to
   `uploadService.upload`. Trusts magic bytes over `file.type` so
   Chrome's empty-type HEIC still lands with `image/heic`. Removes
   the entire class of `application/octet-stream` rows for files
   that are obviously images.

3. HEIC/HEIF transcoded to JPEG at upload via the new
   `heic-convert` dependency (pure-JS WASM, no system libs needed).
   The original buffer is replaced with the JPEG bytes, the MIME
   becomes `image/jpeg`, and the filename's `.HEIC` extension is
   rewritten to `.jpg`. Downstream code (process pipeline, transform
   endpoint, sharp) then deals exclusively with formats sharp can
   actually decode. Failure path returns HTTP 500 with a clear
   `HEIC conversion failed` error so the client knows it wasn't a
   generic crash.

Bonus, transform endpoint hardening: `mimeType.startsWith('image/')`
gate now also accepts a row whose stored MIME is wrong (legacy
`application/octet-stream` from before this fix) when the actual
bytes sniff as an image. Lets old broken rows still serve where
the format itself is decodable; the upload-side fix prevents new
ones from existing.

Sharp 0.33 on this machine reports `heif: 1.18.2` for the container
but rejects the actual HEVC compressed bitstream — confirmed by the
exact error string `No decoding plugin installed for this
compression format (11.6003)`. Going through `heic-convert` first
sidesteps that entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:46:13 +02:00
..
apps/api fix(mana-media): HEIC uploads from Chrome — sniff + transcode at the edge 2026-04-25 13:46:13 +02:00
packages/client chore(workspace): remove redundant nested lockfiles + workspace.yaml 2026-04-09 11:57:11 +02:00
.env.example feat(mana-media): add unified media processing platform MVP 2026-02-01 03:25:53 +01:00
CLAUDE.md refactor: rename nutriphi module to food (Essen) 2026-04-14 15:30:07 +02:00
docker-compose.yml feat(mana-media): add unified media processing platform MVP 2026-02-01 03:25:53 +01:00
drizzle.config.ts feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
nest-cli.json feat(mana-media): add centralized media storage with NutriPhi integration 2026-02-02 17:30:14 +01:00
package.json refactor(mana-media): migrate from NestJS to Hono/Bun 2026-03-28 18:12:42 +01:00
tsconfig.json refactor(mana-media): migrate from NestJS to Hono/Bun 2026-03-28 18:12:42 +01:00