Skip to content

fix(uploads): HEIC upload 500 — pass iterable Buffer to heic-convert (#135 never worked)#17

Merged
tx-joshg merged 1 commit into
mainfrom
fix/heic-upload-arraybuffer-iterator
May 29, 2026
Merged

fix(uploads): HEIC upload 500 — pass iterable Buffer to heic-convert (#135 never worked)#17
tx-joshg merged 1 commit into
mainfrom
fix/heic-upload-arraybuffer-iterator

Conversation

@tx-joshg

Copy link
Copy Markdown
Owner

What & why

End-to-end testing of the staff-portal I-9 flow surfaced a launch-blocking bug: submitting an I-9 with a HEIC document photo fails with

List C document forward failed: open-i9 upload 500: {"error":"Found non-callable @@iterator"}

/api/uploads forwarded await file.arrayBuffer() — a raw ArrayBuffer — to heic-convert. Internally heic-decode's isHeic() does:

String.fromCharCode(...array.slice(8, 12))   // spreads the input

Spread requires an iterable. A Buffer/Uint8Array is iterable; a raw ArrayBuffer is not, so it throws Found non-callable @@iterator (older V8) / Spread syntax requires ...iterable[Symbol.iterator] to be a function (newer V8) and the upload 500s.

It only triggers on the HEIC/HEIF path, so non-HEIC docs (e.g. a JPEG license) worked — which is why the HEIC support added in #16 (#135) never actually worked on a real iPhone photo.

Contributing cause: @types/heic-convert mistypes buffer as ArrayBufferLike, which is what lured the route into passing a raw ArrayBuffer (type-checks, crashes at runtime).

Fix

  • New src/lib/heic.tsheicToJpeg(input) normalizes Buffer | Uint8Array | ArrayBuffer to an iterable Buffer before calling heic-convert (and casts past the incorrect d.ts).
  • /api/uploads calls the helper.

Tests

Adds node:test (run via tsx, npm test) + a real ~1KB HEIC fixture made with sips. Covers Buffer, ArrayBuffer (the exact regression), and Uint8Array inputs — all must yield a JPEG.

✔ heicToJpeg converts a real HEIC Buffer to a JPEG
✔ heicToJpeg accepts a raw ArrayBuffer (PR #16 regression)
✔ heicToJpeg accepts a Uint8Array view

Verified: tsc --noEmit clean (0 errors), next lint clean, reproduced the original failure and confirmed the fix end-to-end (real HEIC → valid JPEG).

Deploy note

No staff-portal change needed — it correctly forwards the HEIC. After this deploys, re-run the I-9 submit to confirm.

The /api/uploads route forwarded `await file.arrayBuffer()` (a raw
ArrayBuffer) to heic-convert. heic-decode's isHeic() spreads the input
(`String.fromCharCode(...buf.slice(8,12))`), which requires an iterable —
an ArrayBuffer is not iterable, so HEIC uploads threw "Found non-callable
@@iterator" and 500'd. The HEIC support added in PR #16 never actually
worked on a real iPhone photo.

Root contributor: @types/heic-convert mistypes `buffer` as ArrayBufferLike,
which lured the route into passing the wrong shape.

- Add heicToJpeg() helper that normalizes any binary input to an iterable
  Buffer (casting past the incorrect d.ts).
- Route uses the helper.
- Regression coverage via node:test + a real ~1KB HEIC fixture: Buffer,
  ArrayBuffer, and Uint8Array inputs all yield a JPEG. Adds tsx + `npm test`.

Reproduced against a real HEIC; fix verified end-to-end (HEIC -> JPEG).
@tx-joshg tx-joshg merged commit b764703 into main May 29, 2026
5 checks passed
@tx-joshg tx-joshg deleted the fix/heic-upload-arraybuffer-iterator branch May 29, 2026 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant