From 8975ac16852e364e34fad1a62cf56810d0280bb4 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 6 Oct 2025 20:07:03 -0700 Subject: [PATCH 01/10] chore: update Iterable API dependency version to 3.6.1 --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index a4f938ffc..d546cce98 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -105,7 +105,7 @@ def kotlin_version = getExtOrDefault("kotlinVersion") dependencies { implementation "com.facebook.react:react-android" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - api "com.iterable:iterableapi:3.5.2" + api "com.iterable:iterableapi:3.6.1" // api project(":iterableapi") // links to local android SDK repo rather than by release } From ab8efaa943999e82b4791b139b6f68fb004e843b Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 6 Oct 2025 20:33:33 -0700 Subject: [PATCH 02/10] feat: implement onAuthFailure handling and pauseAuthRetries method in RNIterableAPIModule --- .../reactnative/RNIterableAPIModuleImpl.java | 36 ++++++++++++++++++- .../iterable/reactnative/Serialization.java | 24 ++++++++++--- .../newarch/java/com/RNIterableAPIModule.java | 29 +++++++++++++++ .../oldarch/java/com/RNIterableAPIModule.java | 10 ++++++ 4 files changed, 93 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 981358be6..01e28f9fa 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -18,6 +18,7 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.iterable.iterableapi.AuthFailure; import com.iterable.iterableapi.InboxSessionManager; import com.iterable.iterableapi.IterableAction; import com.iterable.iterableapi.IterableActionContext; @@ -572,6 +573,40 @@ public String onAuthTokenRequested() { } } + @Override + public void onAuthFailure(AuthFailure authFailure) { + // Create a JSON object for the authFailure object + JSONObject messageJson = new JSONObject(); + try { + messageJson.put("userKey", authFailure.userKey); + messageJson.put("failedAuthToken", authFailure.failedAuthToken); + messageJson.put("failedRequestTime", authFailure.failedRequestTime); + messageJson.put("failureReason", authFailure.failureReason.name()); + WritableMap eventData = Serialization.convertJsonToMap(messageJson); + sendEvent(EventName.handleAuthFailureCalled.name(), eventData); + } catch (Exception e) { + IterableLogger.v(TAG, "Failed to set authToken" + e.getMessage()); + } + } + + public void onAuthFailureFromReadableMap(ReadableMap authFailure) { + // Handle auth failure from ReadableMap (for new architecture) + try { + WritableMap eventData = Arguments.createMap(); + eventData.putString("userKey", authFailure.getString("userKey")); + eventData.putString("failedAuthToken", authFailure.getString("failedAuthToken")); + eventData.putDouble("failedRequestTime", authFailure.getDouble("failedRequestTime")); + eventData.putString("failureReason", authFailure.getString("failureReason")); + sendEvent(EventName.handleAuthFailureCalled.name(), eventData); + } catch (Exception e) { + IterableLogger.e(TAG, "Failed to process auth failure from ReadableMap: " + e.getMessage()); + } + } + + public void pauseAuthRetries(boolean pauseRetry) { + IterableApi.getInstance().pauseAuthRetries(pauseRetry); + } + @Override public void onTokenRegistrationSuccessful(String authToken) { IterableLogger.v(TAG, "authToken successfully set"); @@ -579,7 +614,6 @@ public void onTokenRegistrationSuccessful(String authToken) { sendEvent(EventName.handleAuthSuccessCalled.name(), null); } - @Override public void onTokenRegistrationFailed(Throwable object) { IterableLogger.v(TAG, "Failed to set authToken"); sendEvent(EventName.handleAuthFailureCalled.name(), null); diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 3a1f536a6..6a2c85285 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -24,6 +24,7 @@ import com.iterable.iterableapi.IterableInboxSession; import com.iterable.iterableapi.IterableLogger; import com.iterable.iterableapi.RNIterableInternal; +import com.iterable.iterableapi.RetryPolicy; import org.json.JSONArray; import org.json.JSONException; @@ -94,7 +95,7 @@ static CommerceItem commerceItemFromMap(JSONObject itemMap) throws JSONException categories[i] = categoriesArray.getString(i); } } - + return new CommerceItem(itemMap.getString("id"), itemMap.getString("name"), itemMap.getDouble("price"), @@ -216,9 +217,22 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte configBuilder.setDataRegion(iterableDataRegion); } - - if (iterableContextJSON.has("encryptionEnforced")) { - configBuilder.setEncryptionEnforced(iterableContextJSON.optBoolean("encryptionEnforced")); + + // Note: setEncryptionEnforced method is not available in Android SDK + // if (iterableContextJSON.has("encryptionEnforced")) { + // configBuilder.setEncryptionEnforced(iterableContextJSON.optBoolean("encryptionEnforced")); + // } + + if (iterableContextJSON.has("retryPolicy")) { + 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")) { + retryPolicyType = RetryPolicy.Type.EXPONENTIAL; + } + configBuilder.setAuthRetryPolicy(new RetryPolicy(maxRetry, retryInterval, retryPolicyType)); } return configBuilder; @@ -286,7 +300,7 @@ static List impressionsFromReadableArray(Readab // --------------------------------------------------------------------------------------- // region React Native JSON conversion methods // obtained from https://gist.github.com/viperwarp/2beb6bbefcc268dee7ad - + static WritableMap convertJsonToMap(JSONObject jsonObject) throws JSONException { WritableMap map = new WritableNativeMap(); diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index 4386e0d7f..987750131 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -7,6 +7,7 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.iterable.iterableapi.IterableLogger; public class RNIterableAPIModule extends NativeRNIterableAPISpec { private final ReactApplicationContext reactContext; @@ -217,6 +218,34 @@ public void passAlongAuthToken(@Nullable String authToken) { moduleImpl.passAlongAuthToken(authToken); } + @Override + public void onAuthFailure(ReadableMap authFailure) { + // The implementation expects an AuthFailure object, but we need to create one from the ReadableMap + // Since we don't have access to the AuthFailure constructor, we'll need to handle this differently + // For now, let's create a simple approach that matches the expected interface + try { + // Create a mock AuthFailure object with the data from ReadableMap + // This is a workaround since we can't directly instantiate AuthFailure + String userKey = authFailure.getString("userKey"); + String failedAuthToken = authFailure.getString("failedAuthToken"); + long failedRequestTime = (long) authFailure.getDouble("failedRequestTime"); + String failureReasonStr = authFailure.getString("failureReason"); + + // Create a simple AuthFailure-like object or handle the conversion + // Since we can't access the AuthFailure constructor, we'll need to modify the implementation + // to handle ReadableMap directly or find another approach + moduleImpl.onAuthFailureFromReadableMap(authFailure); + } catch (Exception e) { + // Handle conversion error + IterableLogger.e("RNIterableAPIModule", "Failed to process auth failure: " + e.getMessage()); + } + } + + @Override + public void pauseAuthRetries(boolean pauseRetry) { + moduleImpl.pauseAuthRetries(pauseRetry); + } + public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { moduleImpl.sendEvent(eventName, eventData); } diff --git a/android/src/oldarch/java/com/RNIterableAPIModule.java b/android/src/oldarch/java/com/RNIterableAPIModule.java index 27b04ea17..517266e08 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -223,6 +223,16 @@ public void passAlongAuthToken(@Nullable String authToken) { moduleImpl.passAlongAuthToken(authToken); } + @ReactMethod + public void onAuthFailure(AuthFailure authFailure) { + moduleImpl.onAuthFailureFromReadableMap(authFailure); + } + + @ReactMethod + public void pauseAuthRetries(boolean pauseRetry) { + moduleImpl.pauseAuthRetries(pauseRetry); + } + public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { moduleImpl.sendEvent(eventName, eventData); From 1972fbb1df736c882749035883be4f440b5ac8b6 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 6 Oct 2025 20:45:09 -0700 Subject: [PATCH 03/10] feat: fix nullpointerexception on com.iterable.iterableapi.IterableInAppMessage.getMessageId() --- .../iterable/reactnative/Serialization.java | 17 +++++++++-- src/inApp/classes/IterableInAppMessage.ts | 7 ++++- .../components/IterableInboxMessageList.tsx | 30 ++++++++++++++----- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 6a2c85285..d88feda92 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -271,7 +271,13 @@ static JSONObject actionContextToJson(IterableActionContext iterableActionContex } static IterableInboxSession.Impression inboxImpressionFromMap(JSONObject impressionMap) throws JSONException { - return new IterableInboxSession.Impression(impressionMap.getString("messageId"), + // Add null check for messageId to prevent NullPointerException + String messageId = impressionMap.optString("messageId", null); + if (messageId == null || messageId.isEmpty()) { + throw new JSONException("messageId is null or empty"); + } + + return new IterableInboxSession.Impression(messageId, impressionMap.getBoolean("silentInbox"), impressionMap.optInt("displayCount", 0), (float) impressionMap.optDouble("duration", 0) @@ -285,8 +291,13 @@ static List impressionsFromReadableArray(Readab JSONArray impressionJsonArray = convertArrayToJson(array); for (int i = 0; i < impressionJsonArray.length(); i++) { - JSONObject impressionObj = impressionJsonArray.getJSONObject(i); - list.add(inboxImpressionFromMap(impressionObj)); + try { + JSONObject impressionObj = impressionJsonArray.getJSONObject(i); + list.add(inboxImpressionFromMap(impressionObj)); + } catch (JSONException e) { + // Skip invalid entries instead of failing completely + IterableLogger.w(TAG, "Skipping invalid impression at index " + i + ": " + e.getLocalizedMessage()); + } } } catch (JSONException e) { IterableLogger.e(TAG, "Failed converting to JSONObject"); diff --git a/src/inApp/classes/IterableInAppMessage.ts b/src/inApp/classes/IterableInAppMessage.ts index 8a5b816bf..921da1a0a 100644 --- a/src/inApp/classes/IterableInAppMessage.ts +++ b/src/inApp/classes/IterableInAppMessage.ts @@ -133,9 +133,14 @@ export class IterableInAppMessage { * * @param viewToken - The `ViewToken` containing the in-app message data. * @returns A new instance of `IterableInAppMessage` populated with data from the `viewToken`. + * @throws Error if the viewToken or its item or inAppMessage is null/undefined. */ static fromViewToken(viewToken: ViewToken) { - const inAppMessage = viewToken.item.inAppMessage as IterableInAppMessage; + if (!viewToken?.item?.inAppMessage) { + throw new Error('Invalid ViewToken: missing item or inAppMessage'); + } + + const inAppMessage = viewToken?.item?.inAppMessage as IterableInAppMessage; return new IterableInAppMessage( inAppMessage.messageId, diff --git a/src/inbox/components/IterableInboxMessageList.tsx b/src/inbox/components/IterableInboxMessageList.tsx index 95d6707c5..b74cc7097 100644 --- a/src/inbox/components/IterableInboxMessageList.tsx +++ b/src/inbox/components/IterableInboxMessageList.tsx @@ -95,16 +95,30 @@ export const IterableInboxMessageList = ({ function getRowInfosFromViewTokens( viewTokens: Array ): Array { - return viewTokens.map(function (viewToken) { - const inAppMessage = IterableInAppMessage.fromViewToken(viewToken); + return viewTokens + .filter((viewToken) => { + // Filter out viewTokens that don't have valid items or inAppMessage + return viewToken?.item?.inAppMessage?.messageId; + }) + .map(function (viewToken) { + try { + const inAppMessage = IterableInAppMessage.fromViewToken(viewToken); - const impression = { - messageId: inAppMessage.messageId, - silentInbox: inAppMessage.isSilentInbox(), - } as IterableInboxImpressionRowInfo; + const impression = { + messageId: inAppMessage?.messageId, + silentInbox: inAppMessage?.isSilentInbox(), + } as IterableInboxImpressionRowInfo; - return impression; - }); + return impression; + } catch (error) { + // Log the error and return null to be filtered out + console.warn('Failed to create impression from ViewToken:', error); + return null; + } + }) + .filter( + (impression) => impression !== null + ) as Array; } const inboxSessionViewabilityConfig: ViewabilityConfig = { From 2cd8253899e1d672ba2c019a2de1a278557863e5 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 6 Oct 2025 20:46:35 -0700 Subject: [PATCH 04/10] refactor: remove commented-out encryptionEnforced code from Serialization class --- .../main/java/com/iterable/reactnative/Serialization.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index d88feda92..92c549554 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -218,11 +218,6 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte configBuilder.setDataRegion(iterableDataRegion); } - // Note: setEncryptionEnforced method is not available in Android SDK - // if (iterableContextJSON.has("encryptionEnforced")) { - // configBuilder.setEncryptionEnforced(iterableContextJSON.optBoolean("encryptionEnforced")); - // } - if (iterableContextJSON.has("retryPolicy")) { JSONObject retryPolicyJson = iterableContextJSON.getJSONObject("retryPolicy"); int maxRetry = retryPolicyJson.getInt("maxRetry"); From ef86b15216f23391421fb1c0eb8529c2672b5ca4 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 6 Oct 2025 21:20:59 -0700 Subject: [PATCH 05/10] refactor: fix onAuthFailure call --- .../reactnative/RNIterableAPIModuleImpl.java | 18 ++------------ .../newarch/java/com/RNIterableAPIModule.java | 24 +------------------ .../oldarch/java/com/RNIterableAPIModule.java | 5 ---- 3 files changed, 3 insertions(+), 44 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 01e28f9fa..3207bb5dc 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -584,22 +584,8 @@ public void onAuthFailure(AuthFailure authFailure) { messageJson.put("failureReason", authFailure.failureReason.name()); WritableMap eventData = Serialization.convertJsonToMap(messageJson); sendEvent(EventName.handleAuthFailureCalled.name(), eventData); - } catch (Exception e) { - IterableLogger.v(TAG, "Failed to set authToken" + e.getMessage()); - } - } - - public void onAuthFailureFromReadableMap(ReadableMap authFailure) { - // Handle auth failure from ReadableMap (for new architecture) - try { - WritableMap eventData = Arguments.createMap(); - eventData.putString("userKey", authFailure.getString("userKey")); - eventData.putString("failedAuthToken", authFailure.getString("failedAuthToken")); - eventData.putDouble("failedRequestTime", authFailure.getDouble("failedRequestTime")); - eventData.putString("failureReason", authFailure.getString("failureReason")); - sendEvent(EventName.handleAuthFailureCalled.name(), eventData); - } catch (Exception e) { - IterableLogger.e(TAG, "Failed to process auth failure from ReadableMap: " + e.getMessage()); + } catch (JSONException e) { + IterableLogger.v(TAG, "Failed to set authToken"); } } diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index 987750131..f145bab10 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -7,6 +7,7 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.iterable.iterableapi.AuthFailure; import com.iterable.iterableapi.IterableLogger; public class RNIterableAPIModule extends NativeRNIterableAPISpec { @@ -218,29 +219,6 @@ public void passAlongAuthToken(@Nullable String authToken) { moduleImpl.passAlongAuthToken(authToken); } - @Override - public void onAuthFailure(ReadableMap authFailure) { - // The implementation expects an AuthFailure object, but we need to create one from the ReadableMap - // Since we don't have access to the AuthFailure constructor, we'll need to handle this differently - // For now, let's create a simple approach that matches the expected interface - try { - // Create a mock AuthFailure object with the data from ReadableMap - // This is a workaround since we can't directly instantiate AuthFailure - String userKey = authFailure.getString("userKey"); - String failedAuthToken = authFailure.getString("failedAuthToken"); - long failedRequestTime = (long) authFailure.getDouble("failedRequestTime"); - String failureReasonStr = authFailure.getString("failureReason"); - - // Create a simple AuthFailure-like object or handle the conversion - // Since we can't access the AuthFailure constructor, we'll need to modify the implementation - // to handle ReadableMap directly or find another approach - moduleImpl.onAuthFailureFromReadableMap(authFailure); - } catch (Exception e) { - // Handle conversion error - IterableLogger.e("RNIterableAPIModule", "Failed to process auth failure: " + e.getMessage()); - } - } - @Override public void pauseAuthRetries(boolean pauseRetry) { moduleImpl.pauseAuthRetries(pauseRetry); diff --git a/android/src/oldarch/java/com/RNIterableAPIModule.java b/android/src/oldarch/java/com/RNIterableAPIModule.java index 517266e08..c3a72339b 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -223,11 +223,6 @@ public void passAlongAuthToken(@Nullable String authToken) { moduleImpl.passAlongAuthToken(authToken); } - @ReactMethod - public void onAuthFailure(AuthFailure authFailure) { - moduleImpl.onAuthFailureFromReadableMap(authFailure); - } - @ReactMethod public void pauseAuthRetries(boolean pauseRetry) { moduleImpl.pauseAuthRetries(pauseRetry); From a7804e54d3f5680afb247cc9d62a0001d5526770 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 6 Oct 2025 21:46:23 -0700 Subject: [PATCH 06/10] refactor: simplify authHandler type and standardize IterableAuthFailureReason values --- src/core/classes/IterableConfig.ts | 4 +--- src/core/enums/IterableAuthFailureReason.ts | 22 ++++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index c45558ac0..b690cdcb6 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -204,9 +204,7 @@ export class IterableConfig { * @returns A promise that resolves to an `IterableAuthResponse`, a `string`, * or `undefined`. */ - authHandler?: () => Promise< - IterableAuthResponse | IterableAuthFailure | string | undefined - >; + authHandler?: () => Promise; /** * A callback function that is called when the SDK encounters an error while diff --git a/src/core/enums/IterableAuthFailureReason.ts b/src/core/enums/IterableAuthFailureReason.ts index a61f7fa7e..a86c6f782 100644 --- a/src/core/enums/IterableAuthFailureReason.ts +++ b/src/core/enums/IterableAuthFailureReason.ts @@ -8,32 +8,32 @@ export enum IterableAuthFailureReason { * An auth token's expiration must be less than one year from its issued-at * time. */ - AUTH_TOKEN_EXPIRATION_INVALID, + AUTH_TOKEN_EXPIRATION_INVALID = 'AUTH_TOKEN_EXPIRATION_INVALID', /** The token has expired. */ - AUTH_TOKEN_EXPIRED, + AUTH_TOKEN_EXPIRED = 'AUTH_TOKEN_EXPIRED', /** Token has an invalid format (failed a regular expression check). */ - AUTH_TOKEN_FORMAT_INVALID, + AUTH_TOKEN_FORMAT_INVALID = 'AUTH_TOKEN_FORMAT_INVALID', /** `onAuthTokenRequested` threw an exception. */ - AUTH_TOKEN_GENERATION_ERROR, + AUTH_TOKEN_GENERATION_ERROR = 'AUTH_TOKEN_GENERATION_ERROR', /** Any other error not captured by another constant. */ - AUTH_TOKEN_GENERIC_ERROR, + AUTH_TOKEN_GENERIC_ERROR = 'AUTH_TOKEN_GENERIC_ERROR', /** Iterable has invalidated this token and it cannot be used. */ - AUTH_TOKEN_INVALIDATED, + AUTH_TOKEN_INVALIDATED = 'AUTH_TOKEN_INVALIDATED', /** The request to Iterable's API did not include a JWT authorization header. */ - AUTH_TOKEN_MISSING, + AUTH_TOKEN_MISSING = 'AUTH_TOKEN_MISSING', /** `onAuthTokenRequested` returned a null JWT token. */ - AUTH_TOKEN_NULL, + AUTH_TOKEN_NULL = 'AUTH_TOKEN_NULL', /** * Iterable could not decode the token's payload (`iat`, `exp`, `email`, * or `userId`). */ - AUTH_TOKEN_PAYLOAD_INVALID, + AUTH_TOKEN_PAYLOAD_INVALID = 'AUTH_TOKEN_PAYLOAD_INVALID', /** Iterable could not validate the token's authenticity. */ - AUTH_TOKEN_SIGNATURE_INVALID, + AUTH_TOKEN_SIGNATURE_INVALID = 'AUTH_TOKEN_SIGNATURE_INVALID', /** * The token doesn't include an `email` or a `userId`. Or, one of these * values is included, but it references a user that isn't in the Iterable * project. */ - AUTH_TOKEN_USER_KEY_INVALID, + AUTH_TOKEN_USER_KEY_INVALID = 'AUTH_TOKEN_USER_KEY_INVALID', } From 050ad2233501c47bd4e97255d98ef289e2a04f80 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 6 Oct 2025 22:03:13 -0700 Subject: [PATCH 07/10] feat: enhance JWT error handling with detailed alerts for auth failures --- example/src/hooks/useIterableApp.tsx | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index f19daef5d..e561b22f8 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -1,10 +1,10 @@ import type { StackNavigationProp } from '@react-navigation/stack'; import { - type FunctionComponent, createContext, useCallback, useContext, useState, + type FunctionComponent, } from 'react'; import { Alert } from 'react-native'; @@ -135,6 +135,10 @@ export const IterableAppProvider: FunctionComponent< config.onJWTError = (authFailure) => { console.error('Error fetching JWT:', authFailure); + Alert.alert( + `Error fetching JWT: ${authFailure.failureReason}`, + `Token: ${authFailure.failedAuthToken}` + ); }; config.urlHandler = (url: string) => { @@ -162,6 +166,22 @@ export const IterableAppProvider: FunctionComponent< config.inAppHandler = () => IterableInAppShowResponse.show; + // NOTE: Uncomment to test authHandler failure + // config.authHandler = () => { + // console.log(`authHandler`); + + // return Promise.resolve({ + // authToken: 'SomethingNotValid', + // successCallback: () => { + // console.log(`authHandler > success`); + // }, + // // This is not firing + // failureCallback: () => { + // console.log(`authHandler > failure`); + // }, + // }); + // }; + setItblConfig(config); const key = apiKey ?? process.env.ITBL_API_KEY; From 3737d5766bfea8c49b806ccc800a5729185bfebe Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 12:57:23 -0700 Subject: [PATCH 08/10] fix: improve null safety in IterableInAppMessage.fromViewToken method --- src/inApp/classes/IterableInAppMessage.ts | 24 ++++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/inApp/classes/IterableInAppMessage.ts b/src/inApp/classes/IterableInAppMessage.ts index 921da1a0a..f372043b6 100644 --- a/src/inApp/classes/IterableInAppMessage.ts +++ b/src/inApp/classes/IterableInAppMessage.ts @@ -136,23 +136,19 @@ export class IterableInAppMessage { * @throws Error if the viewToken or its item or inAppMessage is null/undefined. */ static fromViewToken(viewToken: ViewToken) { - if (!viewToken?.item?.inAppMessage) { - throw new Error('Invalid ViewToken: missing item or inAppMessage'); - } - const inAppMessage = viewToken?.item?.inAppMessage as IterableInAppMessage; return new IterableInAppMessage( - inAppMessage.messageId, - inAppMessage.campaignId, - inAppMessage.trigger, - inAppMessage.createdAt, - inAppMessage.expiresAt, - inAppMessage.saveToInbox, - inAppMessage.inboxMetadata, - inAppMessage.customPayload, - inAppMessage.read, - inAppMessage.priorityLevel + inAppMessage?.messageId, + inAppMessage?.campaignId, + inAppMessage?.trigger, + inAppMessage?.createdAt, + inAppMessage?.expiresAt, + inAppMessage?.saveToInbox, + inAppMessage?.inboxMetadata, + inAppMessage?.customPayload, + inAppMessage?.read, + inAppMessage?.priorityLevel ); } From 67a806d5a05d46cc0933bdfbed34084450cc9eb9 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 09:54:01 -0700 Subject: [PATCH 09/10] fix: removed comment description that no longer applies --- src/inApp/classes/IterableInAppMessage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/inApp/classes/IterableInAppMessage.ts b/src/inApp/classes/IterableInAppMessage.ts index f372043b6..c77b08e63 100644 --- a/src/inApp/classes/IterableInAppMessage.ts +++ b/src/inApp/classes/IterableInAppMessage.ts @@ -133,7 +133,6 @@ export class IterableInAppMessage { * * @param viewToken - The `ViewToken` containing the in-app message data. * @returns A new instance of `IterableInAppMessage` populated with data from the `viewToken`. - * @throws Error if the viewToken or its item or inAppMessage is null/undefined. */ static fromViewToken(viewToken: ViewToken) { const inAppMessage = viewToken?.item?.inAppMessage as IterableInAppMessage; From 0191b7bb6f9e68efa31a61036cf8af9b56f46386 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 14 Oct 2025 13:59:01 -0700 Subject: [PATCH 10/10] chore: removed onTokenRegistrationFailed method as per PR comment --- .../com/iterable/reactnative/RNIterableAPIModuleImpl.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 3207bb5dc..57cf9a0b8 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -600,11 +600,6 @@ public void onTokenRegistrationSuccessful(String authToken) { sendEvent(EventName.handleAuthSuccessCalled.name(), null); } - public void onTokenRegistrationFailed(Throwable object) { - IterableLogger.v(TAG, "Failed to set authToken"); - sendEvent(EventName.handleAuthFailureCalled.name(), null); - } - public void addListener(String eventName) { // Keep: Required for RN built in Event Emitter Calls. }