Skip to content

Commit f37cc10

Browse files
sumchatteringclaude
andcommitted
fix: add defensive timeout to iOS initialize to prevent promise hang (SDK-392)
On React Native New Architecture (bridgeless mode), the IterableAPI.initialize2 callback may never fire if auth token retry is exhausted in the native SDK, leaving the JS promise pending indefinitely. This adds a 10-second timeout that resolves the promise if the native callback is not invoked in time. The SDK still initializes and functions normally — the timeout only unblocks the JS promise so downstream app logic (e.g. iterableReady) is not stalled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e69c305 commit f37cc10

1 file changed

Lines changed: 20 additions & 3 deletions

File tree

ios/RNIterableAPI/ReactIterableAPI.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -645,21 +645,38 @@ import React
645645
name: Notification.Name.iterableInboxChanged, object: nil)
646646

647647
DispatchQueue.main.async {
648+
var hasResolved = false
649+
let resolveOnce: (Bool) -> Void = { result in
650+
guard !hasResolved else { return }
651+
hasResolved = true
652+
resolver(result)
653+
}
654+
655+
// Defensive timeout: resolve the JS promise if the native SDK callback
656+
// is never invoked (e.g. auth handler event cannot reach JS in New
657+
// Architecture bridgeless mode, or network fetch hangs).
658+
let timeoutWorkItem = DispatchWorkItem {
659+
ITBInfo("initialize timeout reached – resolving promise to unblock JS")
660+
resolveOnce(true)
661+
}
662+
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0, execute: timeoutWorkItem)
663+
648664
IterableAPI.initialize2(
649665
apiKey: apiKey,
650666
launchOptions: launchOptions,
651667
config: iterableConfig,
652668
apiEndPointOverride: apiEndPointOverride
653669
) { result in
654-
resolver(result)
670+
timeoutWorkItem.cancel()
671+
resolveOnce(result)
655672
}
656673

657674
IterableAPI.setDeviceAttribute(name: "reactNativeSDKVersion", value: version)
658-
675+
659676
// Add embedded update listener if any callback is present
660677
let onEmbeddedMessageUpdatePresent = configDict["onEmbeddedMessageUpdatePresent"] as? Bool ?? false
661678
let onEmbeddedMessagingDisabledPresent = configDict["onEmbeddedMessagingDisabledPresent"] as? Bool ?? false
662-
679+
663680
if onEmbeddedMessageUpdatePresent || onEmbeddedMessagingDisabledPresent {
664681
IterableAPI.embeddedManager.addUpdateListener(self)
665682
}

0 commit comments

Comments
 (0)