Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions ios/RNIterableAPI/ReactIterableAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -645,21 +645,38 @@ import React
name: Notification.Name.iterableInboxChanged, object: nil)

DispatchQueue.main.async {
var hasResolved = false
let resolveOnce: (Bool) -> Void = { result in
guard !hasResolved else { return }
hasResolved = true
resolver(result)
}

// Defensive timeout: resolve the JS promise if the native SDK callback
// is never invoked (e.g. auth handler event cannot reach JS in New
// Architecture bridgeless mode, or network fetch hangs).
let timeoutWorkItem = DispatchWorkItem {
ITBInfo("initialize timeout reached – resolving promise to unblock JS")
resolveOnce(true)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tells the JS layer initialization succeeded when it actually timed out. With the iOS fix now merged, this timeout should theoretically never fire. But if it does (some other unforeseen hang), masking the failure with true is incorrect.

If the timeout fires, tje initialization did not complete successfully and the app should know.
The point is to unblock the JS promise, bit the result should be accurate.

Suggested change
resolveOnce(true)
resolveOnce(false)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout, but I think true is actually the more accurate result here. Here's why:

In IterableAPI.initialize2 (IterableAPI.swift:126-129), the SDK is initialized synchronously before start() is called:

implementation = InternalIterableAPI(apiKey: apiKey, ...)  // ← this IS init
_ = implementation?.start().onSuccess { callback?(true) }  // ← this is in-app sync

The callback is gated on implementation.start()inAppManager.start()fetcher.fetch() — that's the in-app message sync, not actual SDK initialization. The SDK is fully functional at that point.

Jena confirmed this from the debug session: "we did see the SDK respond to lifecycle events (background, foreground) so it was init despite the hang."

If this timeout fires, the most likely scenario is:

  • The SDK did initialize successfully
  • The callback just never propagated (the Fulfill bug we fixed in the Swift SDK, a network hang, etc.)

Resolving with false would tell the app "Iterable failed to initialize" — which could cause it to skip Iterable functionality entirely, even though the SDK is actually working. That's a worse outcome for the customer.

}
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0, execute: timeoutWorkItem)

IterableAPI.initialize2(
apiKey: apiKey,
launchOptions: launchOptions,
config: iterableConfig,
apiEndPointOverride: apiEndPointOverride
) { result in
resolver(result)
timeoutWorkItem.cancel()
resolveOnce(result)
}

IterableAPI.setDeviceAttribute(name: "reactNativeSDKVersion", value: version)

// Add embedded update listener if any callback is present
let onEmbeddedMessageUpdatePresent = configDict["onEmbeddedMessageUpdatePresent"] as? Bool ?? false
let onEmbeddedMessagingDisabledPresent = configDict["onEmbeddedMessagingDisabledPresent"] as? Bool ?? false

if onEmbeddedMessageUpdatePresent || onEmbeddedMessagingDisabledPresent {
IterableAPI.embeddedManager.addUpdateListener(self)
}
Expand Down
Loading