Skip to content
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -6,7 +16,6 @@
### Fixes
* [SDK-151] Fixed "cannot read property authtoken of undefined" error


## 2.2.0-alpha.0

### Updates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Copy link

Choose a reason for hiding this comment

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

Found 22 lines of identical code in 2 locations (mass = 151) [qlty:identical-code]

}

IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version);

IterableApi.getInstance().getInAppManager().addListener(this);
Expand Down Expand Up @@ -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());
Copy link

Choose a reason for hiding this comment

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

Found 22 lines of identical code in 2 locations (mass = 151) [qlty:identical-code]

}
Comment on lines +162 to +183
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

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

The code in this method (lines 162-183) is an exact duplicate of the reflection logic in initializeWithApiKey (lines 99-120). This violates the DRY (Don't Repeat Yourself) principle and makes maintenance harder.

Consider extracting this logic into a private helper method that can be called from both initialization methods:

private void updateAuthManagerRetryPolicy(IterableConfig config) {
    // Move the try-catch block here
}

Copilot uses AI. Check for mistakes.

IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version);

IterableApi.getInstance().getInAppManager().addListener(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ 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
String retryBackoff = retryPolicyJson.getString("retryBackoff");
RetryPolicy.Type retryPolicyType = RetryPolicy.Type.LINEAR;
if (retryBackoff.equals("EXPONENTIAL")) {
Expand Down
24 changes: 19 additions & 5 deletions example/src/hooks/useIterableApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown>
> = ({ children }) => {
Expand Down Expand Up @@ -141,17 +143,15 @@ export const IterableAppProvider: FunctionComponent<

const initialize = useCallback(
(navigation: Navigation) => {
if (getUserId()) {
login();
}
logout();

const config = new IterableConfig();

config.inAppDisplayInterval = 1.0; // Min gap between in-apps. No need to set this in production.

config.retryPolicy = {
maxRetry: 5,
retryInterval: 10,
retryInterval:5,
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

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

Missing space after colon. Should be retryInterval: 5 for consistency with the code style.

Suggested change
retryInterval:5,
retryInterval: 5,

Copilot uses AI. Check for mistakes.
retryBackoff: IterableRetryBackoff.linear,
};

Expand Down Expand Up @@ -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;
};
}
Expand All @@ -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');
}
Expand All @@ -242,6 +254,8 @@ export const IterableAppProvider: FunctionComponent<
const logout = useCallback(() => {
Iterable.setEmail(null);
Iterable.setUserId(null);
Iterable.logout();
lastTimeStamp = 0;
setIsLoggedIn(false);
}, []);

Expand Down