-
Notifications
You must be signed in to change notification settings - Fork 14
fix: add logical to handle local image asset #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,13 +3,38 @@ import UIKit | |||||||
|
|
||||||||
| extension UIImage { | ||||||||
| convenience init?(from dictionary: NSDictionary?) { | ||||||||
| guard | ||||||||
| let dictionary = dictionary, | ||||||||
| let logoName = dictionary["logoName"] as? String | ||||||||
| else { | ||||||||
| return nil | ||||||||
| guard let dictionary = dictionary else { return nil } | ||||||||
|
|
||||||||
| if let logoPath = dictionary["logoPath"] as? String, !logoPath.isEmpty, | ||||||||
| let image = Self.image(fromLogoPath: logoPath), | ||||||||
| let cgImage = image.cgImage { | ||||||||
| self.init(cgImage: cgImage, scale: image.scale, orientation: image.imageOrientation) | ||||||||
| return | ||||||||
| } | ||||||||
|
|
||||||||
| if let logoName = dictionary["logoName"] as? String, !logoName.isEmpty { | ||||||||
| self.init(named: logoName) | ||||||||
| return | ||||||||
| } | ||||||||
|
|
||||||||
| self.init(named: logoName) | ||||||||
| return nil | ||||||||
| } | ||||||||
|
|
||||||||
| private static func image(fromLogoPath logoPath: String) -> UIImage? { | ||||||||
| if logoPath.hasPrefix("file://") { | ||||||||
| let path = logoPath.replacingOccurrences(of: "file://", with: "") | ||||||||
| if let image = UIImage(contentsOfFile: path) { return image } | ||||||||
| } | ||||||||
| else if logoPath.hasPrefix("/") { | ||||||||
| if let image = UIImage(contentsOfFile: logoPath) { return image } | ||||||||
| } | ||||||||
| else if logoPath.hasPrefix("http://") || logoPath.hasPrefix("https://"), | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The HTTP/HTTPS branch performs a synchronous network read with Severity Level: Major
|
||||||||
| else if logoPath.hasPrefix("http://") || logoPath.hasPrefix("https://"), | |
| else if (logoPath.hasPrefix("http://") || logoPath.hasPrefix("https://")), | |
| !Thread.isMainThread, |
Steps of Reproduction ✅
1. Trigger banner display via public API `Usercentrics.showFirstLayer(options)` in
`src/Usercentrics.tsx:38-40`.
2. Native bridge executes `RNUsercentricsModule.showFirstLayer` on `DispatchQueue.main`
(`ios/RNUsercentricsModule.swift:23`, `73-85`), then builds `BannerSettings(from:)`.
3. Settings parsing calls `UIImage(from:)` through `GeneralStyleSettings`
(`ios/Extensions/BannerSettings+Dict.swift:28-37`), then `image(fromLogoPath:)`
(`ios/Extensions/UIImage+UsercentricsLogoDict.swift:23-35`).
4. Provide `logo.logoPath` as `https://...` (same serialized dictionary flow used in tests
at `sample/ios/sampleTests/RNUsercentricsModuleTests.swift:485`); `Data(contentsOf:)` runs
synchronously on main thread and can stall banner rendering/UI.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/Extensions/UIImage+UsercentricsLogoDict.swift
**Line:** 31:31
**Comment:**
*Possible Bug: The HTTP/HTTPS branch performs a synchronous network read with `Data(contentsOf:)`, and this initializer is invoked from the main queue during banner setup. That can block the UI thread and freeze banner presentation. Avoid synchronous network loading in this path (or at least skip it on the main thread) and only use already-local image sources here.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. Main-thread image download 🐞 Bug ➹ Performance
UIImage.image(fromLogoPath:) performs a synchronous Data(contentsOf:) fetch for http(s) paths, and this logo parsing runs on DispatchQueue.main during showFirstLayer/showSecondLayer. A remote logoPath will block the UI thread while downloading, potentially freezing the app and triggering watchdog termination.
Agent Prompt
## Issue description
`UIImage.image(fromLogoPath:)` uses `Data(contentsOf:)` for `http(s)` logo paths. Because `BannerSettings(from:)` is built on `DispatchQueue.main` in `RNUsercentricsModule.showFirstLayer/showSecondLayer`, a remote `logoPath` will synchronously block the main thread.
## Issue Context
- `RNUsercentricsModule.queue` is set to `DispatchQueue.main`, and `BannerSettings(from: dict)` is created inside `queue.async`.
- `BannerSettings` parsing calls `UIImage(from:)` for the logo.
- `UIImage(from:)` calls `image(fromLogoPath:)`, which includes an `http(s)` branch using synchronous `Data(contentsOf:)`.
## Fix Focus Areas
- ios/Extensions/UIImage+UsercentricsLogoDict.swift[31-36]
- ios/RNUsercentricsModule.swift[22-85]
- ios/Extensions/BannerSettings+Dict.swift[27-48]
## Suggested fix approach
- Do not perform any network I/O inside `UIImage` initializers.
- Either:
1) Remove the `http(s)` branch entirely (treat remote URLs as unsupported for `logoPath`), and rely on a separate async loader elsewhere, or
2) Redesign the logo-loading flow to be asynchronous (e.g., pass `URL`/string through settings, fetch via `URLSession` off-main-thread, then update the banner UI when the image arrives).
- If you must keep remote support, ensure the fetch happens off the main thread and has timeouts/error handling, and avoid blocking the caller.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[CRITICAL_BUG] The implementation performs a synchronous network fetch using Data(contentsOf:) (lines 31-36). This will block the calling thread (very likely the main thread) and can freeze the UI or cause the app to be terminated for unresponsiveness. Move remote image loading off the calling thread and make it asynchronous (URLSession dataTask or use an image-loading/caching library). Also avoid initiating network I/O from a synchronous convenience initializer — consider providing an asynchronous factory/completion-based API (e.g. loadImage(from:completion:)) or return a placeholder and fetch the remote image asynchronously.
private static func image(fromLogoPath logoPath: String) -> UIImage? {
if logoPath.hasPrefix("file://") {
let path = logoPath.replacingOccurrences(of: "file://", with: "")
if let image = UIImage(contentsOfFile: path) { return image }
} else if logoPath.hasPrefix("/") {
if let image = UIImage(contentsOfFile: logoPath) { return image }
} else if logoPath.hasPrefix("http://") || logoPath.hasPrefix("https://"),
let url = URL(string: logoPath) {
// Avoid blocking the caller here; return a placeholder instead and
// let the caller load the remote image asynchronously.
return UIImage(named: logoPath)
}
if let image = UIImage(named: logoPath) { return image }
return nil
}
// Example async loader (outside the UIImage extension):
func loadLogo(from logoPath: String, completion: @escaping (UIImage?) -> Void) {
if logoPath.hasPrefix("http://") || logoPath.hasPrefix("https://"),
let url = URL(string: logoPath) {
URLSession.shared.dataTask(with: url) { data, _, _ in
let image = data.flatMap { UIImage(data: $0) }
DispatchQueue.main.async {
completion(image)
}
}.resume()
} else {
completion(UIImage.image(fromLogoPath: logoPath))
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Synchronous network request will block the calling thread.
Data(contentsOf: url) performs a blocking network call. If the initializer is invoked on the main thread (typical for UI setup code), this will freeze the UI and could trigger watchdog termination on slow or unreachable networks.
Consider one of the following approaches:
- Load remote images asynchronously and update the UI via a completion handler or reactive binding.
- If synchronous loading is required, document that this must not be called from the main thread and add an assertion during development.
- Add a timeout to prevent indefinite hangs.
🛡️ Minimal defensive fix: assert off main thread + timeout
else if logoPath.hasPrefix("http://") || logoPath.hasPrefix("https://"),
- let url = URL(string: logoPath),
- let data = try? Data(contentsOf: url),
- let image = UIImage(data: data) {
+ let url = URL(string: logoPath) {
+ `#if` DEBUG
+ assert(!Thread.isMainThread, "Loading remote image synchronously on main thread is not allowed")
+ `#endif`
+ var request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 10)
+ if let data = try? Data(contentsOf: url, options: .uncached) {
+ if let image = UIImage(data: data) { return image }
+ }
return image
}Note: A fully async solution is preferred but may require API changes to the initializer.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/Extensions/UIImage`+UsercentricsLogoDict.swift around lines 31 - 36, The
code currently blocks using Data(contentsOf: url) when logoPath is an http(s)
URL; replace this synchronous network call in the UIImage+UsercentricsLogoDict
extension: either (preferred) change the API to load remote images
asynchronously (add a helper like loadRemoteImageAsync(url:completion:) that
uses URLSession.shared.dataTask(with: URLRequest(url: url, timeoutInterval: X))
and invokes the completion on the main thread to set the image, or (minimal
defensive) assert(!Thread.isMainThread) in the http(s) branch and perform a
synchronous fetch with URLSession and a semaphore/timeout to avoid indefinite
hangs; update the branch that checks logoPath.hasPrefix("http://") ||
logoPath.hasPrefix("https://") to use the chosen approach and remove
Data(contentsOf: url).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Stripping
file://via string replacement can produce invalid filesystem paths for percent-encoded file URLs (for example spaces encoded as%20), causing local images to fail to load. Parse the value as a URL and use its.pathto get a valid decoded file path. [logic error]Severity Level: Major⚠️
Steps of Reproduction ✅
Prompt for AI Agent 🤖