From b4dfbc7ebb3288dd2eadfe6dec480b77433eff95 Mon Sep 17 00:00:00 2001 From: Jayesh Jain Date: Sun, 12 Jul 2026 18:20:00 +0530 Subject: [PATCH] fix(ios): reject the promise instead of raising NSException in image compression --- ios/Image/ImageCompressor.swift | 51 +++++++++++++++++++-------------- ios/Image/ImageMain.swift | 22 ++++++++------ 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/ios/Image/ImageCompressor.swift b/ios/Image/ImageCompressor.swift index 2bcc790..f8363d1 100644 --- a/ios/Image/ImageCompressor.swift +++ b/ios/Image/ImageCompressor.swift @@ -50,7 +50,7 @@ class ImageCompressor { } - static func manualResize(_ image: UIImage, maxWidth: Int, maxHeight: Int) -> UIImage { + static func manualResize(_ image: UIImage, maxWidth: Int, maxHeight: Int) throws -> UIImage { let targetSize = findTargetSize(image, maxWidth: maxWidth, maxHeight: maxHeight) if let cgImage = image.cgImage { @@ -85,9 +85,10 @@ class ImageCompressor { let error = vImageScale_ARGB8888(&srcBuffer, &targetBuffer, nil, vImage_Flags(kvImageHighQualityResampling)) if error != kvImageNoError { - free(&targetData) - let exception = NSException(name: NSExceptionName(rawValue: "drawing_error"), reason: "Problem while rendering your image", userInfo: nil) - exception.raise() + // Throw instead of raising an NSException (process abort no JS + // catch survives) — the caller rejects the promise. + throw NSError(domain: "drawing_error", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Problem while rendering your image"]) } let targetContext = CGContext(data: &targetData, @@ -170,9 +171,8 @@ class ImageCompressor { return destinationData as Data } - static func writeImage(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool, disablePngTransparency: Bool, isEnableAutoCompress: Bool, actualImagePath: String?)-> String { + static func writeImage(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool, disablePngTransparency: Bool, isEnableAutoCompress: Bool, actualImagePath: String?) throws -> String { var data: Data - var exception: NSException? let normalizedQuality = CGFloat(min(max(quality, 0), 1)) switch OutputType(rawValue: output)! { @@ -216,16 +216,19 @@ class ImageCompressor { return returnablePath } catch { - exception = NSException(name: NSExceptionName(rawValue: "file_error"), reason: "Error writing file", userInfo: nil) - exception?.raise() + // Throw instead of raising an NSException: a raise inside a + // TurboModule invocation aborts the whole app and no JS catch + // can intercept it — the caller rejects the promise instead. + throw NSError(domain: "file_error", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Error writing file: \(error.localizedDescription)"]) } } return "" } - static func manualCompress(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool, disablePngTransparency: Bool, actualImagePath: String?) -> String { - return writeImage(image, output: output, quality: quality, outputExtension: outputExtension, isBase64: isBase64, disablePngTransparency: disablePngTransparency, isEnableAutoCompress: false, actualImagePath: actualImagePath) + static func manualCompress(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool, disablePngTransparency: Bool, actualImagePath: String?) throws -> String { + return try writeImage(image, output: output, quality: quality, outputExtension: outputExtension, isBase64: isBase64, disablePngTransparency: disablePngTransparency, isEnableAutoCompress: false, actualImagePath: actualImagePath) } @@ -294,8 +297,7 @@ class ImageCompressor { } - static func manualCompressHandler(imagePath: String?, base64: String?, options: ImageCompressorOptions) -> String { - var exception: NSException? + static func manualCompressHandler(imagePath: String?, base64: String?, options: ImageCompressorOptions) throws -> String { var image: UIImage? switch options.input { @@ -312,20 +314,23 @@ class ImageCompressor { if let _image = image { image = ImageCompressor.scaleAndRotateImage(_image) let outputExtension = ImageCompressorOptions.getOutputInString(options.output) - let resizedImage = ImageCompressor.manualResize(image!, maxWidth: options.maxWidth, maxHeight: options.maxHeight) + let resizedImage = try ImageCompressor.manualResize(image!, maxWidth: options.maxWidth, maxHeight: options.maxHeight) let isBase64 = options.returnableOutputType == .rbase64 - return ImageCompressor.manualCompress(resizedImage, output: options.output.rawValue, quality: options.quality, outputExtension: outputExtension, isBase64: isBase64,disablePngTransparency: options.disablePngTransparency, actualImagePath: imagePath) + return try ImageCompressor.manualCompress(resizedImage, output: options.output.rawValue, quality: options.quality, outputExtension: outputExtension, isBase64: isBase64,disablePngTransparency: options.disablePngTransparency, actualImagePath: imagePath) } else { - exception = NSException(name: NSExceptionName(rawValue: "unsupported_value"), reason: "Unsupported value type.", userInfo: nil) - exception?.raise() + // Throw instead of raising an NSException (process abort no JS + // catch survives — e.g. a stored file path invalidated by an iOS + // container relocation boot-looped an app at startup). The caller + // rejects the promise with the same name/message. + throw NSError(domain: "unsupported_value", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Unsupported value type."]) } return "" } - static func autoCompressHandler(imagePath: String?, base64: String?, options: ImageCompressorOptions) -> String { - var exception: NSException? + static func autoCompressHandler(imagePath: String?, base64: String?, options: ImageCompressorOptions) throws -> String { var image: UIImage? switch options.input { @@ -372,11 +377,15 @@ class ImageCompressor { let isBase64 = options.returnableOutputType == .rbase64 if let img = UIGraphicsGetImageFromCurrentImageContext() { - return writeImage(img, output: options.output.rawValue, quality: Float(compressionQuality), outputExtension: outputExtension, isBase64: isBase64, disablePngTransparency: options.disablePngTransparency, isEnableAutoCompress: true, actualImagePath: imagePath) + return try writeImage(img, output: options.output.rawValue, quality: Float(compressionQuality), outputExtension: outputExtension, isBase64: isBase64, disablePngTransparency: options.disablePngTransparency, isEnableAutoCompress: true, actualImagePath: imagePath) } } else { - exception = NSException(name: NSExceptionName(rawValue: "unsupported_value"), reason: "Unsupported value type.", userInfo: nil) - exception?.raise() + // Throw instead of raising an NSException (process abort no JS + // catch survives — e.g. a stored file path invalidated by an iOS + // container relocation boot-looped an app at startup). The caller + // rejects the promise with the same name/message. + throw NSError(domain: "unsupported_value", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Unsupported value type."]) } return "" diff --git a/ios/Image/ImageMain.swift b/ios/Image/ImageMain.swift index 2e6e9fc..48f3b79 100644 --- a/ios/Image/ImageMain.swift +++ b/ios/Image/ImageMain.swift @@ -13,21 +13,27 @@ class ImageMain { if options.input != InputType.base64 { ImageCompressor.getAbsoluteImagePath(value, options: options) { absoluteImagePath in - if options.autoCompress { - let result = ImageCompressor.autoCompressHandler(imagePath: absoluteImagePath, base64: nil, options: options) - resolve(result) - } else { - let result = ImageCompressor.manualCompressHandler(imagePath: absoluteImagePath, base64: nil, options: options) - resolve(result) + // The escaping completion runs outside the outer do/catch, + // so failures here need their own catch to reject. + do { + if options.autoCompress { + let result = try ImageCompressor.autoCompressHandler(imagePath: absoluteImagePath, base64: nil, options: options) + resolve(result) + } else { + let result = try ImageCompressor.manualCompressHandler(imagePath: absoluteImagePath, base64: nil, options: options) + resolve(result) + } + } catch { + reject((error as NSError).domain, error.localizedDescription, error) } MediaCache.removeCompletedImagePath(absoluteImagePath) } } else { if options.autoCompress { - let result = ImageCompressor.autoCompressHandler(imagePath: nil, base64: value, options: options) + let result = try ImageCompressor.autoCompressHandler(imagePath: nil, base64: value, options: options) resolve(result) } else { - let result = ImageCompressor.manualCompressHandler(imagePath: nil, base64: value, options: options) + let result = try ImageCompressor.manualCompressHandler(imagePath: nil, base64: value, options: options) resolve(result) } }