managarten/services/mana-media/apps/api/package.json
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

34 lines
836 B
JSON

{
"name": "@mana-media/api",
"version": "0.2.0",
"private": true,
"scripts": {
"dev": "bun run --hot src/index.ts",
"start": "bun run src/index.ts",
"type-check": "tsc --noEmit",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:migrate": "bun run src/db/migrate.ts",
"db:studio": "drizzle-kit studio"
},
"dependencies": {
"bullmq": "^5.34.0",
"drizzle-orm": "^0.38.3",
"exifr": "^7.1.3",
"heic-convert": "^2.1.0",
"hono": "^4.7.0",
"mime-types": "^2.1.35",
"minio": "^8.0.0",
"postgres": "^3.4.5",
"prom-client": "^15.1.0",
"sharp": "^0.33.0"
},
"devDependencies": {
"@mana/shared-drizzle-config": "workspace:*",
"@types/heic-convert": "^2.1.0",
"@types/mime-types": "^2.1.4",
"@types/node": "^22.0.0",
"drizzle-kit": "^0.30.1",
"typescript": "^5.7.0"
}
}