From de5ed49cf2f4ff56586284c74f047f596334a1b2 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 19 Nov 2025 12:26:11 -0800 Subject: [PATCH 1/8] fix: convert retry interval from seconds to milliseconds in RetryPolicy configuration --- CHANGELOG.md | 11 ++++++++++- .../java/com/iterable/reactnative/Serialization.java | 7 ++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0336d6cf8..e9ab073de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.2.0-alpha.2 + +### Updates +* Changed `onJWTError` to `onJwtError` +* Changed `IterableRetryBackoff` enum keys to be lowercase for consistency + across application + +### Fixes +* Fixed Android `retryInterval` not being respected - Android native SDK expects milliseconds while iOS expects seconds, added conversion in Android bridge layer + ## 2.2.0-alpha.1 ### Updates @@ -6,7 +16,6 @@ ### Fixes * [SDK-151] Fixed "cannot read property authtoken of undefined" error - ## 2.2.0-alpha.0 ### Updates diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 92c549554..883d96c7e 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -222,12 +222,17 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte JSONObject retryPolicyJson = iterableContextJSON.getJSONObject("retryPolicy"); int maxRetry = retryPolicyJson.getInt("maxRetry"); long retryInterval = retryPolicyJson.getLong("retryInterval"); + + // TODO [SDK-197]: Create consistency between Android and iOS + // instead of converting here + // Convert from seconds to milliseconds for Android native SDK + long retryIntervalMs = retryInterval * 1000; String retryBackoff = retryPolicyJson.getString("retryBackoff"); RetryPolicy.Type retryPolicyType = RetryPolicy.Type.LINEAR; if (retryBackoff.equals("EXPONENTIAL")) { retryPolicyType = RetryPolicy.Type.EXPONENTIAL; } - configBuilder.setAuthRetryPolicy(new RetryPolicy(maxRetry, retryInterval, retryPolicyType)); + configBuilder.setAuthRetryPolicy(new RetryPolicy(maxRetry, retryIntervalMs, retryPolicyType)); } return configBuilder; From e9cb76137b7d363f1389a0719bf6b9a43cf3e5cb Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 20 Nov 2025 17:11:19 -0800 Subject: [PATCH 2/8] fix: update retry policy handling to respect re-initialization --- .../reactnative/RNIterableAPIModuleImpl.java | 59 ++++++++++++++++++- .../iterable/reactnative/Serialization.java | 3 +- example/src/hooks/useIterableApp.tsx | 24 ++++++-- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 57cf9a0b8..e0ef85d43 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -24,6 +24,7 @@ import com.iterable.iterableapi.IterableActionContext; import com.iterable.iterableapi.IterableApi; import com.iterable.iterableapi.IterableAuthHandler; +import com.iterable.iterableapi.IterableAuthManager; import com.iterable.iterableapi.IterableConfig; import com.iterable.iterableapi.IterableCustomActionHandler; import com.iterable.iterableapi.IterableAttributionInfo; @@ -88,7 +89,34 @@ public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, S configBuilder.setAuthHandler(this); } - IterableApi.initialize(reactContext, apiKey, configBuilder.build()); + IterableConfig config = configBuilder.build(); + IterableApi.initialize(reactContext, apiKey, config); + + // Update retry policy on existing authManager if it was already created + // This fixes the issue where retryInterval is not respected after re-initialization + try { + // Use reflection to access package-private fields and methods + java.lang.reflect.Field configRetryPolicyField = config.getClass().getDeclaredField("retryPolicy"); + configRetryPolicyField.setAccessible(true); + Object retryPolicy = configRetryPolicyField.get(config); + + if (retryPolicy != null) { + java.lang.reflect.Method getAuthManagerMethod = IterableApi.getInstance().getClass().getDeclaredMethod("getAuthManager"); + getAuthManagerMethod.setAccessible(true); + IterableAuthManager authManager = (IterableAuthManager) getAuthManagerMethod.invoke(IterableApi.getInstance()); + + if (authManager != null) { + // Update the retry policy field on the authManager + java.lang.reflect.Field authRetryPolicyField = authManager.getClass().getDeclaredField("authRetryPolicy"); + authRetryPolicyField.setAccessible(true); + authRetryPolicyField.set(authManager, retryPolicy); + IterableLogger.d(TAG, "Updated retry policy on existing authManager"); + } + } + } catch (Exception e) { + IterableLogger.e(TAG, "Failed to update retry policy: " + e.getMessage()); + } + IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); IterableApi.getInstance().getInAppManager().addListener(this); @@ -122,7 +150,34 @@ public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, // override in the Android SDK. Check with @Ayyanchira and @evantk91 to // see what the best approach is. - IterableApi.initialize(reactContext, apiKey, configBuilder.build()); + IterableConfig config = configBuilder.build(); + IterableApi.initialize(reactContext, apiKey, config); + + // Update retry policy on existing authManager if it was already created + // This fixes the issue where retryInterval is not respected after re-initialization + try { + // Use reflection to access package-private fields and methods + java.lang.reflect.Field configRetryPolicyField = config.getClass().getDeclaredField("retryPolicy"); + configRetryPolicyField.setAccessible(true); + Object retryPolicy = configRetryPolicyField.get(config); + + if (retryPolicy != null) { + java.lang.reflect.Method getAuthManagerMethod = IterableApi.getInstance().getClass().getDeclaredMethod("getAuthManager"); + getAuthManagerMethod.setAccessible(true); + IterableAuthManager authManager = (IterableAuthManager) getAuthManagerMethod.invoke(IterableApi.getInstance()); + + if (authManager != null) { + // Update the retry policy field on the authManager + java.lang.reflect.Field authRetryPolicyField = authManager.getClass().getDeclaredField("authRetryPolicy"); + authRetryPolicyField.setAccessible(true); + authRetryPolicyField.set(authManager, retryPolicy); + IterableLogger.d(TAG, "Updated retry policy on existing authManager"); + } + } + } catch (Exception e) { + IterableLogger.e(TAG, "Failed to update retry policy: " + e.getMessage()); + } + IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); IterableApi.getInstance().getInAppManager().addListener(this); diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 883d96c7e..a4dae7c00 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -226,13 +226,12 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte // TODO [SDK-197]: Create consistency between Android and iOS // instead of converting here // Convert from seconds to milliseconds for Android native SDK - long retryIntervalMs = retryInterval * 1000; String retryBackoff = retryPolicyJson.getString("retryBackoff"); RetryPolicy.Type retryPolicyType = RetryPolicy.Type.LINEAR; if (retryBackoff.equals("EXPONENTIAL")) { retryPolicyType = RetryPolicy.Type.EXPONENTIAL; } - configBuilder.setAuthRetryPolicy(new RetryPolicy(maxRetry, retryIntervalMs, retryPolicyType)); + configBuilder.setAuthRetryPolicy(new RetryPolicy(maxRetry, retryInterval, retryPolicyType)); } return configBuilder; diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index 9f72f0cf8..aa05146f4 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -89,6 +89,8 @@ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const getIsEmail = (id: string) => EMAIL_REGEX.test(id); +let lastTimeStamp = 0; + export const IterableAppProvider: FunctionComponent< React.PropsWithChildren > = ({ children }) => { @@ -141,9 +143,7 @@ export const IterableAppProvider: FunctionComponent< const initialize = useCallback( (navigation: Navigation) => { - if (getUserId()) { - login(); - } + logout(); const config = new IterableConfig(); @@ -151,7 +151,7 @@ export const IterableAppProvider: FunctionComponent< config.retryPolicy = { maxRetry: 5, - retryInterval: 10, + retryInterval:2, retryBackoff: IterableRetryBackoff.linear, }; @@ -199,8 +199,16 @@ export const IterableAppProvider: FunctionComponent< process.env.ITBL_JWT_SECRET ) { config.authHandler = async () => { + console.group('authHandler'); + const now = Date.now(); + if (lastTimeStamp !== 0) { + console.log('Time since last call:', now - lastTimeStamp); + } + lastTimeStamp = now; + console.groupEnd(); + + // return 'InvalidToken'; // Uncomment this to test the failure callback const token = await getJwtToken(); - // return 'SomethingNotValid'; // Uncomment this to test the failure callback return token; }; } @@ -219,6 +227,10 @@ export const IterableAppProvider: FunctionComponent< .then((isSuccessful) => { setIsInitialized(isSuccessful); + if (isSuccessful && getUserId()) { + return login(); + } + if (!isSuccessful) { return Promise.reject('`Iterable.initialize` failed'); } @@ -242,6 +254,8 @@ export const IterableAppProvider: FunctionComponent< const logout = useCallback(() => { Iterable.setEmail(null); Iterable.setUserId(null); + Iterable.logout(); + lastTimeStamp = 0; setIsLoggedIn(false); }, []); From c48da84f10172b185dac85cefd595bc53ca25735 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 20 Nov 2025 17:11:37 -0800 Subject: [PATCH 3/8] fix: increase retry interval in RetryPolicy configuration from 2 to 5 --- example/src/hooks/useIterableApp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index aa05146f4..1dc981580 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -151,7 +151,7 @@ export const IterableAppProvider: FunctionComponent< config.retryPolicy = { maxRetry: 5, - retryInterval:2, + retryInterval:5, retryBackoff: IterableRetryBackoff.linear, }; From ada0a72e0468c628325cd07d2c952124a9a70251 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 20 Nov 2025 17:24:45 -0800 Subject: [PATCH 4/8] fix: update Android `retryInterval` handling to ensure it is correctly updated on re-initialization --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ab073de..476661592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ across application ### Fixes -* Fixed Android `retryInterval` not being respected - Android native SDK expects milliseconds while iOS expects seconds, added conversion in Android bridge layer +* Fixed Android `retryInterval` not being updated on re-initialization. ## 2.2.0-alpha.1 From 177308b275c216c82008fc51d8922710eb39a234 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 20 Nov 2025 17:26:41 -0800 Subject: [PATCH 5/8] fix: improve handling of retryInterval on re-initialization and add TODO for root cause resolution --- .../com/iterable/reactnative/RNIterableAPIModuleImpl.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index e0ef85d43..98340a1f7 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -93,7 +93,9 @@ public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, S IterableApi.initialize(reactContext, apiKey, config); // Update retry policy on existing authManager if it was already created - // This fixes the issue where retryInterval is not respected after re-initialization + // This fixes the issue where retryInterval is not respected after + // re-initialization + // TODO [SDK-197]: Fix the root cause of this issue, instead of this hack try { // Use reflection to access package-private fields and methods java.lang.reflect.Field configRetryPolicyField = config.getClass().getDeclaredField("retryPolicy"); @@ -154,7 +156,9 @@ public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, IterableApi.initialize(reactContext, apiKey, config); // Update retry policy on existing authManager if it was already created - // This fixes the issue where retryInterval is not respected after re-initialization + // This fixes the issue where retryInterval is not respected after + // re-initialization + // TODO [SDK-197]: Fix the root cause of this issue, instead of this hack try { // Use reflection to access package-private fields and methods java.lang.reflect.Field configRetryPolicyField = config.getClass().getDeclaredField("retryPolicy"); From 45be7edde2f83c2a61b3eeb1a2753dab17836465 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 20 Nov 2025 17:27:51 -0800 Subject: [PATCH 6/8] refactor: remove outdated TODO comments --- .../src/main/java/com/iterable/reactnative/Serialization.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index a4dae7c00..9a1d8b3cd 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -223,9 +223,6 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte int maxRetry = retryPolicyJson.getInt("maxRetry"); long retryInterval = retryPolicyJson.getLong("retryInterval"); - // TODO [SDK-197]: Create consistency between Android and iOS - // instead of converting here - // Convert from seconds to milliseconds for Android native SDK String retryBackoff = retryPolicyJson.getString("retryBackoff"); RetryPolicy.Type retryPolicyType = RetryPolicy.Type.LINEAR; if (retryBackoff.equals("EXPONENTIAL")) { From 0fca9f219e82575dffbaf948022e8f0ff6fd8d3e Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 20 Nov 2025 17:28:17 -0800 Subject: [PATCH 7/8] fix: remove unnecessary blank line in Serialization class --- .../src/main/java/com/iterable/reactnative/Serialization.java | 1 - 1 file changed, 1 deletion(-) diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 9a1d8b3cd..92c549554 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -222,7 +222,6 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte JSONObject retryPolicyJson = iterableContextJSON.getJSONObject("retryPolicy"); int maxRetry = retryPolicyJson.getInt("maxRetry"); long retryInterval = retryPolicyJson.getLong("retryInterval"); - String retryBackoff = retryPolicyJson.getString("retryBackoff"); RetryPolicy.Type retryPolicyType = RetryPolicy.Type.LINEAR; if (retryBackoff.equals("EXPONENTIAL")) { From 1a6d487e9b950449422cf11feb72e3fa24b62320 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Thu, 20 Nov 2025 17:50:46 -0800 Subject: [PATCH 8/8] fix: ensure consistent formatting of retryInterval in RetryPolicy configuration --- example/src/hooks/useIterableApp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index 1dc981580..0022fdb4c 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -151,7 +151,7 @@ export const IterableAppProvider: FunctionComponent< config.retryPolicy = { maxRetry: 5, - retryInterval:5, + retryInterval: 5, retryBackoff: IterableRetryBackoff.linear, };