From f37cc10b03a224b5fb89afe6176582d537c61286 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Thu, 2 Apr 2026 11:27:47 +0100 Subject: [PATCH] fix: add defensive timeout to iOS initialize to prevent promise hang (SDK-392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- ios/RNIterableAPI/ReactIterableAPI.swift | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index c7b5fc745..60c4c665b 100644 --- a/ios/RNIterableAPI/ReactIterableAPI.swift +++ b/ios/RNIterableAPI/ReactIterableAPI.swift @@ -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) + } + 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) }