Skip to content

fix(ios): reject the promise instead of raising NSException in image compression#411

Draft
jayeshbjain wants to merge 1 commit into
numandev1:mainfrom
jayeshbjain:fix/ios-image-compress-reject-instead-of-raise
Draft

fix(ios): reject the promise instead of raising NSException in image compression#411
jayeshbjain wants to merge 1 commit into
numandev1:mainfrom
jayeshbjain:fix/ios-image-compress-reject-instead-of-raise

Conversation

@jayeshbjain

Copy link
Copy Markdown

Problem

The iOS image-compression path raises NSException on several failure conditions (unsupported_value ×2, file_error, drawing_error). An NSException raised inside a TurboModule invocation is not catchable from JavaScript and aborts the whole app (SIGABRT) — Swift do/catch can't intercept ObjC exceptions either, so the existing catch in ImageMain.image_compress never fires.

This is not theoretical: we hit it in production. iOS relocated our app's data container during an OS update (Apple documents that container paths may change between updates), which invalidated a stored absolute file path. Image.compress on that dead path → loadImage returns nil → NSException("unsupported_value") → the app died 991ms after every launch, a permanent boot loop for the user (crash log frames: ImageCompressor.manualCompressHandlergetAbsoluteImagePathCompressor.image_compress).

Fix

Convert all four raise sites to Swift throw NSError(...) and propagate:

  • manualCompressHandler / autoCompressHandler / manualCompress / writeImage / manualResize become throws
  • ImageMain.image_compress: the base64 branch's errors flow to the existing outer catch; the URI branch runs inside getAbsoluteImagePath's escaping closure (outside the outer do/catch), so it gets its own do/catch that calls reject
  • Error domains/messages preserved (unsupported_value / "Unsupported value type." etc.), so callers that match on the message keep working — they now receive a promise rejection instead of a crash

Also removed in passing: free(&targetData) before the drawing_error raise — targetData is a Swift-managed [UInt8] array, so calling free() on its address was undefined behavior; the array is released by ARC.

Behavior change

None on success paths. On the four failure conditions the JS promise rejects (with the same error name/message) instead of the process aborting.

Testing

  • Shipping in our production app as a patch-package patch of 1.19.2 (same change rebased onto main here).
  • Repro for the original crash: call Image.compress('file:///path/that/does/not/exist.jpg', { compressionMethod: 'manual' }) on iOS — before: SIGABRT; after: promise rejection.

Marked as draft for maintainer review of error-domain conventions.

🤖 Generated with Claude Code

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