From 80d67f60612446ac147fd13ad1125d1ba8a8ca27 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 3 Oct 2025 17:21:51 -0700 Subject: [PATCH 01/10] chore: update Iterable API dependency to version 3.5.10 and add onAuthFailure event handling --- android/build.gradle | 2 +- .../reactnative/RNIterableAPIModuleImpl.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index a4f938ffc..d3cab9036 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.5.10" // api project(":iterableapi") // links to local android SDK repo rather than by release } diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 981358be6..baed02d7a 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,22 @@ 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.handleUrlCalled.name(), eventData); + } catch (JSONException e) { + IterableLogger.v(TAG, "Failed to set authToken"); + } + } + @Override public void onTokenRegistrationSuccessful(String authToken) { IterableLogger.v(TAG, "authToken successfully set"); From 3cb5d4c2794ab23b2e4a2133e844383415099cad Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 3 Oct 2025 17:25:51 -0700 Subject: [PATCH 02/10] feat: implement onAuthFailure method in RNIterableAPIModule for new architecture --- android/src/newarch/java/com/RNIterableAPIModule.java | 5 +++++ android/src/oldarch/java/com/RNIterableAPIModule.java | 4 ++++ src/api/NativeRNIterableAPI.ts | 1 + 3 files changed, 10 insertions(+) diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index 4386e0d7f..0f732b157 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -217,6 +217,11 @@ public void passAlongAuthToken(@Nullable String authToken) { moduleImpl.passAlongAuthToken(authToken); } + @Override + public void onAuthFailure(AuthFailure authFailure) { + moduleImpl.onAuthFailure(authFailure); + } + 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..4cdef83f4 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -223,6 +223,10 @@ public void passAlongAuthToken(@Nullable String authToken) { moduleImpl.passAlongAuthToken(authToken); } + @ReactMethod + public void onAuthFailure(AuthFailure authFailure) { + moduleImpl.onAuthFailure(authFailure); + } public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { moduleImpl.sendEvent(eventName, eventData); diff --git a/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index de903cece..557aab743 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -116,6 +116,7 @@ export interface Spec extends TurboModule { // Auth passAlongAuthToken(authToken?: string | null): void; + onAuthFailure(authFailure: { userKey: string; failedAuthToken: string; failedRequestTime: string; failureReason: string }): void; // Wake app -- android only wakeApp(): void; From f82e23b546890133c20255174531cbbd95d22888 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 3 Oct 2025 17:40:22 -0700 Subject: [PATCH 03/10] feat: enhance auth failure handling with detailed error reporting and new enum for failure reasons --- .../reactnative/RNIterableAPIModuleImpl.java | 2 +- .../iterable/reactnative/Serialization.java | 12 +++--- src/api/NativeRNIterableAPI.ts | 2 +- src/core/classes/IterableConfig.ts | 21 +++++++++- src/core/enums/IterableAuthFailureReason.ts | 39 +++++++++++++++++++ src/core/enums/index.ts | 2 + src/core/types/IterableAuthFailure.tsx | 18 +++++++++ 7 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 src/core/enums/IterableAuthFailureReason.ts create mode 100644 src/core/types/IterableAuthFailure.tsx diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index baed02d7a..d9faf6227 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -583,7 +583,7 @@ public void onAuthFailure(AuthFailure authFailure) { messageJson.put("failedRequestTime", authFailure.failedRequestTime); messageJson.put("failureReason", authFailure.failureReason.name()); WritableMap eventData = Serialization.convertJsonToMap(messageJson); - sendEvent(EventName.handleUrlCalled.name(), eventData); + sendEvent(EventName.handleAuthFailureCalled.name(), eventData); } catch (JSONException e) { IterableLogger.v(TAG, "Failed to set authToken"); } diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 3a1f536a6..7e6b3af5a 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -94,7 +94,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,10 +216,10 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte configBuilder.setDataRegion(iterableDataRegion); } - - if (iterableContextJSON.has("encryptionEnforced")) { - configBuilder.setEncryptionEnforced(iterableContextJSON.optBoolean("encryptionEnforced")); - } + + // if (iterableContextJSON.has("encryptionEnforced")) { + // configBuilder.setEncryptionEnforced(iterableContextJSON.optBoolean("encryptionEnforced")); + // } return configBuilder; } catch (JSONException e) { @@ -286,7 +286,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/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index 557aab743..2a29ce9e6 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -116,7 +116,7 @@ export interface Spec extends TurboModule { // Auth passAlongAuthToken(authToken?: string | null): void; - onAuthFailure(authFailure: { userKey: string; failedAuthToken: string; failedRequestTime: string; failureReason: string }): void; + onAuthFailure(authFailure: { userKey: string; failedAuthToken: string; failedRequestTime: number; failureReason: string }): void; // Wake app -- android only wakeApp(): void; diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index 1c0550b0c..5855c22b2 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -5,6 +5,7 @@ import { IterableLogLevel, IterablePushPlatform, } from '../enums'; +import type { IterableAuthFailure } from '../types/IterableAuthFailure'; import { IterableAction } from './IterableAction'; import type { IterableActionContext } from './IterableActionContext'; import type { IterableAuthResponse } from './IterableAuthResponse'; @@ -204,8 +205,26 @@ export class IterableConfig { * @returns A promise that resolves to an `IterableAuthResponse`, a `string`, * or `undefined`. */ - authHandler?: () => Promise; + authHandler?: () => Promise; + /** + * A callback function which is called when an error occurs while validating a JWT. + * + * The retry for JWT should be automatically handled by the native SDK, so + * this is just for logging/transparency purposes. + * + * @param authFailure - The details of the auth failure. + * + * @example + * ```typescript + * const config = new IterableConfig(); + * config.onJWTError = (authFailure) => { + * console.error('Error fetching JWT:', authFailure); + * }; + * ``` + * @memberof IterableConfig + */ + onJWTError?: (authFailure: IterableAuthFailure) => void; /** * Set the verbosity of Android and iOS project's log system. * diff --git a/src/core/enums/IterableAuthFailureReason.ts b/src/core/enums/IterableAuthFailureReason.ts new file mode 100644 index 000000000..a61f7fa7e --- /dev/null +++ b/src/core/enums/IterableAuthFailureReason.ts @@ -0,0 +1,39 @@ +/** + * The reason for the failure of an authentication attempt. + * + * This is generally related to JWT token validation. + */ +export enum IterableAuthFailureReason { + /** + * An auth token's expiration must be less than one year from its issued-at + * time. + */ + AUTH_TOKEN_EXPIRATION_INVALID, + /** The token has expired. */ + AUTH_TOKEN_EXPIRED, + /** Token has an invalid format (failed a regular expression check). */ + AUTH_TOKEN_FORMAT_INVALID, + /** `onAuthTokenRequested` threw an exception. */ + AUTH_TOKEN_GENERATION_ERROR, + /** Any other error not captured by another constant. */ + AUTH_TOKEN_GENERIC_ERROR, + /** Iterable has invalidated this token and it cannot be used. */ + AUTH_TOKEN_INVALIDATED, + /** The request to Iterable's API did not include a JWT authorization header. */ + AUTH_TOKEN_MISSING, + /** `onAuthTokenRequested` returned a null JWT token. */ + AUTH_TOKEN_NULL, + /** + * Iterable could not decode the token's payload (`iat`, `exp`, `email`, + * or `userId`). + */ + AUTH_TOKEN_PAYLOAD_INVALID, + /** Iterable could not validate the token's authenticity. */ + 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, +} diff --git a/src/core/enums/index.ts b/src/core/enums/index.ts index e95b5350c..f24b73109 100644 --- a/src/core/enums/index.ts +++ b/src/core/enums/index.ts @@ -1,6 +1,8 @@ export * from './IterableActionSource'; +export * from './IterableAuthFailureReason'; export * from './IterableAuthResponseResult'; export * from './IterableDataRegion'; export * from './IterableEventName'; export * from './IterableLogLevel'; export * from './IterablePushPlatform'; + diff --git a/src/core/types/IterableAuthFailure.tsx b/src/core/types/IterableAuthFailure.tsx new file mode 100644 index 000000000..c7323d461 --- /dev/null +++ b/src/core/types/IterableAuthFailure.tsx @@ -0,0 +1,18 @@ +import type { IterableAuthFailureReason } from "../enums/IterableAuthFailureReason"; + +/** + * The details of an auth failure. + */ +export interface IterableAuthFailure { + /** userId or email of the signed-in user */ + userKey: string; + + /** the authToken which caused the failure */ + failedAuthToken: string; + + /** the timestamp of the failed request */ + failedRequestTime: number; + + /** indicates a reason for failure */ + failureReason: IterableAuthFailureReason; +} From 8d013216a23c12af1ae46c1e0e79ba8f1f05234e Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 3 Oct 2025 18:00:51 -0700 Subject: [PATCH 04/10] feat: add retry policy configuration to Iterable SDK with linear and exponential backoff options --- .../com/iterable/reactnative/Serialization.java | 13 +++++++++++++ example/src/hooks/useIterableApp.tsx | 7 +++++++ src/core/classes/IterableConfig.ts | 7 ++++++- src/core/enums/IterableRetryBackoff.ts | 7 +++++++ src/core/enums/index.ts | 1 + ...erableAuthFailure.tsx => IterableAuthFailure.ts} | 0 src/core/types/IterableRetryPolicy.ts | 13 +++++++++++++ src/core/types/index.ts | 3 +++ src/index.tsx | 3 ++- 9 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/core/enums/IterableRetryBackoff.ts rename src/core/types/{IterableAuthFailure.tsx => IterableAuthFailure.ts} (100%) create mode 100644 src/core/types/IterableRetryPolicy.ts diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 7e6b3af5a..3749ea29d 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; @@ -221,6 +222,18 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte // 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; } catch (JSONException e) { e.printStackTrace(); diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index 32270003c..24861e735 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -14,6 +14,7 @@ import { IterableConfig, IterableInAppShowResponse, IterableLogLevel, + IterableRetryBackoff, } from '@iterable/react-native-sdk'; import { Route } from '../constants/routes'; @@ -147,6 +148,12 @@ export const IterableAppProvider: FunctionComponent< config.logLevel = IterableLogLevel.debug; + config.retryPolicy = { + maxRetry: 5, + retryInterval: 10, + retryBackoff: IterableRetryBackoff.LINEAR + }; + config.inAppHandler = () => IterableInAppShowResponse.show; setItblConfig(config); diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index 5855c22b2..dc212b194 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -5,6 +5,7 @@ import { IterableLogLevel, IterablePushPlatform, } from '../enums'; +import type { IterableRetryPolicy } from '../types/IterableRetryPolicy'; import type { IterableAuthFailure } from '../types/IterableAuthFailure'; import { IterableAction } from './IterableAction'; import type { IterableActionContext } from './IterableActionContext'; @@ -222,7 +223,6 @@ export class IterableConfig { * console.error('Error fetching JWT:', authFailure); * }; * ``` - * @memberof IterableConfig */ onJWTError?: (authFailure: IterableAuthFailure) => void; /** @@ -232,6 +232,11 @@ export class IterableConfig { */ logLevel: IterableLogLevel = IterableLogLevel.info; + /** + * The retry policy to use when retrying a request. + */ + retryPolicy?: IterableRetryPolicy; + /** * Set whether the React Native SDK should print function calls to console. * diff --git a/src/core/enums/IterableRetryBackoff.ts b/src/core/enums/IterableRetryBackoff.ts new file mode 100644 index 000000000..b854bbabe --- /dev/null +++ b/src/core/enums/IterableRetryBackoff.ts @@ -0,0 +1,7 @@ +/** + * The type of backoff to use when retrying a request. + */ +export enum IterableRetryBackoff { + LINEAR = 'LINEAR', + EXPONENTIAL = 'EXPONENTIAL', +} diff --git a/src/core/enums/index.ts b/src/core/enums/index.ts index f24b73109..39a96f35d 100644 --- a/src/core/enums/index.ts +++ b/src/core/enums/index.ts @@ -5,4 +5,5 @@ export * from './IterableDataRegion'; export * from './IterableEventName'; export * from './IterableLogLevel'; export * from './IterablePushPlatform'; +export * from './IterableRetryBackoff'; diff --git a/src/core/types/IterableAuthFailure.tsx b/src/core/types/IterableAuthFailure.ts similarity index 100% rename from src/core/types/IterableAuthFailure.tsx rename to src/core/types/IterableAuthFailure.ts diff --git a/src/core/types/IterableRetryPolicy.ts b/src/core/types/IterableRetryPolicy.ts new file mode 100644 index 000000000..0af6587d4 --- /dev/null +++ b/src/core/types/IterableRetryPolicy.ts @@ -0,0 +1,13 @@ +import type { IterableRetryBackoff } from "../enums"; + +export interface IterableRetryPolicy { + /** Number of consecutive JWT refresh retries the SDK should attempt */ + maxRetry: number; + /** + * Duration between JWT refresh retries in seconds + * (starting point for retry backoff) + */ + retryInterval: number; + /** The backoff pattern to apply between retry attempts */ + retryBackoff: IterableRetryBackoff; +} diff --git a/src/core/types/index.ts b/src/core/types/index.ts index f5d846482..9f5c58fd0 100644 --- a/src/core/types/index.ts +++ b/src/core/types/index.ts @@ -1 +1,4 @@ +export * from './IterableAuthFailure'; export * from './IterableEdgeInsetDetails'; +export * from './IterableRetryPolicy'; + diff --git a/src/index.tsx b/src/index.tsx index 885cd74bd..e305ab379 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -18,13 +18,14 @@ export { IterableEventName, IterableLogLevel, IterablePushPlatform, + IterableRetryBackoff, } from './core/enums'; export { useAppStateListener, useDeviceOrientation, type IterableDeviceOrientation, } from './core/hooks'; -export { type IterableEdgeInsetDetails } from './core/types'; +export { type IterableEdgeInsetDetails, type IterableAuthFailure, type IterableRetryPolicy } from './core/types'; export { IterableHtmlInAppContent, IterableInAppCloseSource, From 8ade24d63ee34b6dd63decbc80424f52a5a3082b Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 3 Oct 2025 18:36:31 -0700 Subject: [PATCH 05/10] feat: implement handling of auth failure --- .../reactnative/RNIterableAPIModuleImpl.java | 15 +++++++++++- .../newarch/java/com/RNIterableAPIModule.java | 24 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index d9faf6227..df8f66468 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -589,6 +589,20 @@ public void onAuthFailure(AuthFailure authFailure) { } } + 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()); + } + } + @Override public void onTokenRegistrationSuccessful(String authToken) { IterableLogger.v(TAG, "authToken successfully set"); @@ -596,7 +610,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/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index 0f732b157..94e88895f 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -7,6 +7,8 @@ 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 { private final ReactApplicationContext reactContext; @@ -218,8 +220,26 @@ public void passAlongAuthToken(@Nullable String authToken) { } @Override - public void onAuthFailure(AuthFailure authFailure) { - moduleImpl.onAuthFailure(authFailure); + 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()); + } } public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { From ab554a15e5c34802054cee292197939fb1ed0e52 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 3 Oct 2025 18:46:35 -0700 Subject: [PATCH 06/10] chore: update Iterable-iOS-SDK dependency to version 6.6.1 --- Iterable-React-Native-SDK.podspec | 2 +- example/ios/Podfile | 2 - .../project.pbxproj | 75 +++++++------------ ios/RNIterableAPI/ReactIterableAPI.swift | 16 +++- 4 files changed, 45 insertions(+), 50 deletions(-) diff --git a/Iterable-React-Native-SDK.podspec b/Iterable-React-Native-SDK.podspec index 884417e1f..0d023409f 100644 --- a/Iterable-React-Native-SDK.podspec +++ b/Iterable-React-Native-SDK.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.private_header_files = "ios/**/*.h" # Load Iterables iOS SDK as a dependency - s.dependency "Iterable-iOS-SDK", "6.5.4.1" + s.dependency "Iterable-iOS-SDK", "6.6.1" # Basic Swift support s.pod_target_xcconfig = { diff --git a/example/ios/Podfile b/example/ios/Podfile index 89b12bd69..43e1952fb 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -26,8 +26,6 @@ target 'ReactNativeSdkExample' do :app_path => "#{Pod::Config.instance.installation_root}/.." ) - pod 'Iterable-iOS-SDK', :git => 'https://github.com/Iterable/iterable-swift-sdk.git', :branch => 'hotfix/MOB-12091-temp-fix' - post_install do |installer| # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 react_native_post_install( diff --git a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj index fe2da2b97..fe42e3c57 100644 --- a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 00E356F31AD99517003FC87E /* ReactNativeSdkExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 779227342DFA3FB500D69EC0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 779227332DFA3FB500D69EC0 /* AppDelegate.swift */; }; - 77F63EC390061314C0718D51 /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F395BEFC7809290D1773C84F /* libPods-ReactNativeSdkExample.a */; }; + 7A78ECB858FF7B7F2B3C15FD /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E6013A3CAD2CEA39589EB48 /* libPods-ReactNativeSdkExample.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; A3A40C20801B8F02005FA4C0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -29,19 +29,19 @@ 00E356EE1AD99517003FC87E /* ReactNativeSdkExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactNativeSdkExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactNativeSdkExampleTests.m; sourceTree = ""; }; + 0E6013A3CAD2CEA39589EB48 /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* ReactNativeSdkExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactNativeSdkExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNativeSdkExample/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeSdkExample/Info.plist; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 627A5082522E8122626A42E9 /* Pods-ReactNativeSdkExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.debug.xcconfig"; sourceTree = ""; }; 779227312DFA3FB500D69EC0 /* ReactNativeSdkExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExample-Bridging-Header.h"; sourceTree = ""; }; 779227322DFA3FB500D69EC0 /* ReactNativeSdkExampleTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExampleTests-Bridging-Header.h"; sourceTree = ""; }; 779227332DFA3FB500D69EC0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = ReactNativeSdkExample/AppDelegate.swift; sourceTree = ""; }; + 7C953C8BB795CAD77F34D8D7 /* Pods-ReactNativeSdkExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.release.xcconfig"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeSdkExample/LaunchScreen.storyboard; sourceTree = ""; }; - C37A515B34C484F156F48110 /* Pods-ReactNativeSdkExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - F395BEFC7809290D1773C84F /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + FC1667EC7B1616549D9E12AD /* Pods-ReactNativeSdkExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,7 +56,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 77F63EC390061314C0718D51 /* libPods-ReactNativeSdkExample.a in Frameworks */, + 7A78ECB858FF7B7F2B3C15FD /* libPods-ReactNativeSdkExample.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -99,7 +99,7 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - F395BEFC7809290D1773C84F /* libPods-ReactNativeSdkExample.a */, + 0E6013A3CAD2CEA39589EB48 /* libPods-ReactNativeSdkExample.a */, ); name = Frameworks; sourceTree = ""; @@ -138,8 +138,8 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 627A5082522E8122626A42E9 /* Pods-ReactNativeSdkExample.debug.xcconfig */, - C37A515B34C484F156F48110 /* Pods-ReactNativeSdkExample.release.xcconfig */, + FC1667EC7B1616549D9E12AD /* Pods-ReactNativeSdkExample.debug.xcconfig */, + 7C953C8BB795CAD77F34D8D7 /* Pods-ReactNativeSdkExample.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -169,13 +169,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeSdkExample" */; buildPhases = ( - 00A09C8D745F4A4962CFCB16 /* [CP] Check Pods Manifest.lock */, + 84FB78A43318D70EFC6E5885 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 70E3A2A47E764F7A78602595 /* [CP] Embed Pods Frameworks */, - EDF40E5EF2B0A60C77B1B71B /* [CP] Copy Pods Resources */, + 76DD352201970F7652206F40 /* [CP] Embed Pods Frameworks */, + 92B70AE4A9F5E887121D0B73 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -244,28 +244,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 00A09C8D745F4A4962CFCB16 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -282,41 +260,46 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 24A6D3DBDA584D8F55796A6D /* [CP] Copy Pods Resources */ = { + 76DD352201970F7652206F40 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 89B6BEF2485B9536DDD45973 /* [CP] Embed Pods Frameworks */ = { + 84FB78A43318D70EFC6E5885 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - EDF40E5EF2B0A60C77B1B71B /* [CP] Copy Pods Resources */ = { + 92B70AE4A9F5E887121D0B73 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -423,7 +406,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 627A5082522E8122626A42E9 /* Pods-ReactNativeSdkExample.debug.xcconfig */; + baseConfigurationReference = FC1667EC7B1616549D9E12AD /* Pods-ReactNativeSdkExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -453,7 +436,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C37A515B34C484F156F48110 /* Pods-ReactNativeSdkExample.release.xcconfig */; + baseConfigurationReference = 7C953C8BB795CAD77F34D8D7 /* Pods-ReactNativeSdkExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index 163e34199..d3d0c0963 100644 --- a/ios/RNIterableAPI/ReactIterableAPI.swift +++ b/ios/RNIterableAPI/ReactIterableAPI.swift @@ -483,7 +483,6 @@ import React passedAuthToken = authToken authHandlerSemaphore.signal() } - // MARK: Private private var shouldEmit = false private let _methodQueue = DispatchQueue(label: String(describing: ReactIterableAPI.self)) @@ -662,6 +661,19 @@ extension ReactIterableAPI: IterableInAppDelegate { } extension ReactIterableAPI: IterableAuthDelegate { + public func onAuthFailure(_ authFailure: IterableSDK.AuthFailure) { + ITBInfo() + + var failureDict: [String: Any] = [:] + failureDict["userKey"] = authFailure.userKey + failureDict["failedAuthToken"] = authFailure.failedAuthToken + failureDict["failedRequestTime"] = authFailure.failedRequestTime + failureDict["failureReason"] = authFailure.failureReason.rawValue + + sendEvent(withName: EventName.handleAuthFailureCalled.rawValue, + body: failureDict) + } + public func onAuthTokenRequested(completion: @escaping AuthTokenRetrievalHandler) { ITBInfo() DispatchQueue.global(qos: .userInitiated).async { @@ -682,6 +694,8 @@ extension ReactIterableAPI: IterableAuthDelegate { DispatchQueue.main.async { completion(nil) } + // TODO: RN should be able to handle nil case as well. Or we can wrap this up under one of the existing AuthFailure. But again, its not a authFailure in this one. Its a timeout error. + // TODO: Create a Dictionary representing AuthFailure object due to `null` auth token and pass it in body instead of passing `nil` self.delegate?.sendEvent( withName: EventName.handleAuthFailureCalled.rawValue, body: nil as Any?) From 70dbbc278f3f1db4e7355e5d1dcfd63e8fa1a7da Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 3 Oct 2025 19:40:06 -0700 Subject: [PATCH 07/10] feat: add pauseAuthRetries method to RNIterableAPIModule for both architectures --- .../com/iterable/reactnative/RNIterableAPIModuleImpl.java | 4 ++++ android/src/newarch/java/com/RNIterableAPIModule.java | 5 +++++ android/src/oldarch/java/com/RNIterableAPIModule.java | 5 +++++ ios/RNIterableAPI/RNIterableAPI.mm | 8 ++++++++ ios/RNIterableAPI/ReactIterableAPI.swift | 5 +++++ src/api/NativeRNIterableAPI.ts | 1 + 6 files changed, 28 insertions(+) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index df8f66468..70b0f35f1 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -603,6 +603,10 @@ public void onAuthFailureFromReadableMap(ReadableMap authFailure) { } } + public void pauseAuthRetries(boolean pauseRetry) { + IterableApi.getInstance().pauseAuthRetries(pauseRetry); + } + @Override public void onTokenRegistrationSuccessful(String authToken) { IterableLogger.v(TAG, "authToken successfully set"); diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index 94e88895f..85f2eb0a7 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -242,6 +242,11 @@ public void onAuthFailure(ReadableMap authFailure) { } } + @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 4cdef83f4..b53a8aece 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -228,6 +228,11 @@ public void onAuthFailure(AuthFailure authFailure) { moduleImpl.onAuthFailure(authFailure); } + @ReactMethod + public void pauseAuthRetries(boolean pauseRetry) { + moduleImpl.pauseAuthRetries(pauseRetry); + } + public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { moduleImpl.sendEvent(eventName, eventData); } diff --git a/ios/RNIterableAPI/RNIterableAPI.mm b/ios/RNIterableAPI/RNIterableAPI.mm index a7490f2ee..91955f797 100644 --- a/ios/RNIterableAPI/RNIterableAPI.mm +++ b/ios/RNIterableAPI/RNIterableAPI.mm @@ -273,6 +273,10 @@ - (void)passAlongAuthToken:(NSString *_Nullable)authToken { [_swiftAPI passAlongAuthToken:authToken]; } +- (void)pauseAuthRetries:(BOOL)pauseRetry { + [_swiftAPI pauseAuthRetries:pauseRetry]; +} + - (void)wakeApp { // Placeholder function -- this method is only used in Android } @@ -499,6 +503,10 @@ - (void)wakeApp { [_swiftAPI passAlongAuthToken:authToken]; } +RCT_EXPORT_METHOD(pauseAuthRetries : (BOOL)pauseRetry) { + [_swiftAPI pauseAuthRetries:pauseRetry]; +} + RCT_EXPORT_METHOD(wakeApp) { // Placeholder function -- this method is only used in Android } diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index d3d0c0963..87111c6ba 100644 --- a/ios/RNIterableAPI/ReactIterableAPI.swift +++ b/ios/RNIterableAPI/ReactIterableAPI.swift @@ -703,6 +703,11 @@ extension ReactIterableAPI: IterableAuthDelegate { } } + public func pauseAuthRetries(_ pauseRetry: Bool) { + ITBInfo() + IterableAPI.authManager.pauseAuthRetries(pauseRetry) + } + public func onTokenRegistrationFailed(_ reason: String?) { } } diff --git a/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index 2a29ce9e6..dd6921367 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -117,6 +117,7 @@ export interface Spec extends TurboModule { // Auth passAlongAuthToken(authToken?: string | null): void; onAuthFailure(authFailure: { userKey: string; failedAuthToken: string; failedRequestTime: number; failureReason: string }): void; + pauseAuthRetries(pauseRetry: boolean): void; // Wake app -- android only wakeApp(): void; From e6e821eb4f020ff701551023e7fab5c26b62704e Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 3 Oct 2025 20:06:01 -0700 Subject: [PATCH 08/10] feat: refactor pauseAuthRetries method in ReactIterableAPI to use IterableAPI directly --- ios/RNIterableAPI/ReactIterableAPI.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index 87111c6ba..4d1f52c3d 100644 --- a/ios/RNIterableAPI/ReactIterableAPI.swift +++ b/ios/RNIterableAPI/ReactIterableAPI.swift @@ -562,6 +562,12 @@ import React withName: EventName.receivedIterableInboxChanged.rawValue, body: nil as Any?) } + @objc(pauseAuthRetries:) + public func pauseAuthRetries(pauseRetry: Bool) { + ITBInfo() + IterableAPI.pauseAuthRetries(pauseRetry) + } + private func createLaunchOptions() -> [UIApplication.LaunchOptionsKey: Any]? { guard let bridge = self.bridge else { return nil @@ -703,11 +709,6 @@ extension ReactIterableAPI: IterableAuthDelegate { } } - public func pauseAuthRetries(_ pauseRetry: Bool) { - ITBInfo() - IterableAPI.authManager.pauseAuthRetries(pauseRetry) - } - public func onTokenRegistrationFailed(_ reason: String?) { } } From 0470f8e8e2cf85fb9a76f2c1f02cb64c72965fc8 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 3 Oct 2025 20:17:21 -0700 Subject: [PATCH 09/10] feat: add TODO.md file outlining new Iterable API features and configurations --- TODO.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..54a5c2ff5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,15 @@ +- Iterable API + - `setVisitorUsageTracked` + - `getVisitorUsageTracked` + - `logoutUser` + - `disableDeviceForAllUsers` + - `trackInboxSession` +- Embedded +- `IterableConfig` + - `IterableAPIMobileFrameworkType` + - `expiringAuthTokenRefreshPeriod` + - `eventThresholdLimit` + - `identityResolution` + - `mobileFrameworkInfo` +- Unknown User +- `IterableInAppTriggerType` From d8cb608d60f9d342be96b6ba4b512175d8d80e66 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 6 Oct 2025 14:06:16 -0700 Subject: [PATCH 10/10] feat: enhance AppDelegate --- .../project.pbxproj | 60 +++++------ .../ReactNativeSdkExample/AppDelegate.swift | 99 +++++++++++++++++-- 2 files changed, 123 insertions(+), 36 deletions(-) diff --git a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj index fe42e3c57..8e0d38c68 100644 --- a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj @@ -9,8 +9,8 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* ReactNativeSdkExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 1A990D0CDC805E6B277A757F /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D0C0ACFEE5C77CF4A675B73 /* libPods-ReactNativeSdkExample.a */; }; 779227342DFA3FB500D69EC0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 779227332DFA3FB500D69EC0 /* AppDelegate.swift */; }; - 7A78ECB858FF7B7F2B3C15FD /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E6013A3CAD2CEA39589EB48 /* libPods-ReactNativeSdkExample.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; A3A40C20801B8F02005FA4C0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -29,19 +29,19 @@ 00E356EE1AD99517003FC87E /* ReactNativeSdkExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactNativeSdkExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactNativeSdkExampleTests.m; sourceTree = ""; }; - 0E6013A3CAD2CEA39589EB48 /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* ReactNativeSdkExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactNativeSdkExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNativeSdkExample/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeSdkExample/Info.plist; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 5D0C0ACFEE5C77CF4A675B73 /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6E3EFD52D0FD52FBD6EEE4FD /* Pods-ReactNativeSdkExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.release.xcconfig"; sourceTree = ""; }; 779227312DFA3FB500D69EC0 /* ReactNativeSdkExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExample-Bridging-Header.h"; sourceTree = ""; }; 779227322DFA3FB500D69EC0 /* ReactNativeSdkExampleTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExampleTests-Bridging-Header.h"; sourceTree = ""; }; 779227332DFA3FB500D69EC0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = ReactNativeSdkExample/AppDelegate.swift; sourceTree = ""; }; - 7C953C8BB795CAD77F34D8D7 /* Pods-ReactNativeSdkExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.release.xcconfig"; sourceTree = ""; }; + 7E618CBD06C168ADAA45A7D6 /* Pods-ReactNativeSdkExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.debug.xcconfig"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeSdkExample/LaunchScreen.storyboard; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - FC1667EC7B1616549D9E12AD /* Pods-ReactNativeSdkExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,7 +56,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7A78ECB858FF7B7F2B3C15FD /* libPods-ReactNativeSdkExample.a in Frameworks */, + 1A990D0CDC805E6B277A757F /* libPods-ReactNativeSdkExample.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -99,7 +99,7 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 0E6013A3CAD2CEA39589EB48 /* libPods-ReactNativeSdkExample.a */, + 5D0C0ACFEE5C77CF4A675B73 /* libPods-ReactNativeSdkExample.a */, ); name = Frameworks; sourceTree = ""; @@ -138,8 +138,8 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - FC1667EC7B1616549D9E12AD /* Pods-ReactNativeSdkExample.debug.xcconfig */, - 7C953C8BB795CAD77F34D8D7 /* Pods-ReactNativeSdkExample.release.xcconfig */, + 7E618CBD06C168ADAA45A7D6 /* Pods-ReactNativeSdkExample.debug.xcconfig */, + 6E3EFD52D0FD52FBD6EEE4FD /* Pods-ReactNativeSdkExample.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -169,13 +169,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeSdkExample" */; buildPhases = ( - 84FB78A43318D70EFC6E5885 /* [CP] Check Pods Manifest.lock */, + B3B30202110DC49F0E0CA2D9 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 76DD352201970F7652206F40 /* [CP] Embed Pods Frameworks */, - 92B70AE4A9F5E887121D0B73 /* [CP] Copy Pods Resources */, + 1E8C258AE112C7B549689AE9 /* [CP] Embed Pods Frameworks */, + 8677EE75FBA28E8975AEBDB8 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -260,7 +260,7 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 76DD352201970F7652206F40 /* [CP] Embed Pods Frameworks */ = { + 1E8C258AE112C7B549689AE9 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -277,43 +277,43 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 84FB78A43318D70EFC6E5885 /* [CP] Check Pods Manifest.lock */ = { + 8677EE75FBA28E8975AEBDB8 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 92B70AE4A9F5E887121D0B73 /* [CP] Copy Pods Resources */ = { + B3B30202110DC49F0E0CA2D9 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -406,11 +406,12 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FC1667EC7B1616549D9E12AD /* Pods-ReactNativeSdkExample.debug.xcconfig */; + baseConfigurationReference = 7E618CBD06C168ADAA45A7D6 /* Pods-ReactNativeSdkExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; + "DEBUG_INFORMATION_FORMAT[sdk=iphonesimulator*]" = dwarf; DEVELOPMENT_TEAM = BP98Z28R86; ENABLE_BITCODE = NO; INFOPLIST_FILE = ReactNativeSdkExample/Info.plist; @@ -436,11 +437,12 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7C953C8BB795CAD77F34D8D7 /* Pods-ReactNativeSdkExample.release.xcconfig */; + baseConfigurationReference = 6E3EFD52D0FD52FBD6EEE4FD /* Pods-ReactNativeSdkExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; + "DEBUG_INFORMATION_FORMAT[sdk=iphonesimulator*]" = dwarf; DEVELOPMENT_TEAM = BP98Z28R86; INFOPLIST_FILE = ReactNativeSdkExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; diff --git a/example/ios/ReactNativeSdkExample/AppDelegate.swift b/example/ios/ReactNativeSdkExample/AppDelegate.swift index 5b9504eb5..927836431 100644 --- a/example/ios/ReactNativeSdkExample/AppDelegate.swift +++ b/example/ios/ReactNativeSdkExample/AppDelegate.swift @@ -5,10 +5,13 @@ // Created by Loren Posen on 6/11/25. // +import UIKit import React -import ReactAppDependencyProvider import React_RCTAppDelegate -import UIKit +import ReactAppDependencyProvider +import UserNotifications + +import IterableSDK @main class AppDelegate: UIResponder, UIApplicationDelegate { @@ -21,6 +24,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { + NSLog("FINISHED LAUNCHING WITH OPTIONS") + ITBInfo() + let delegate = ReactNativeDelegate() let factory = RCTReactNativeFactory(delegate: delegate) delegate.dependencyProvider = RCTAppDependencyProvider() @@ -36,8 +42,73 @@ class AppDelegate: UIResponder, UIApplicationDelegate { launchOptions: launchOptions ) + setupUserNotificationCenter() + return true } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + NSLog("REGISTERED FOR REMOTE NOTIFICATIONS") + ITBInfo() + IterableAPI.register(token: deviceToken) + } + + func application(_ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError + error: Error) { + NSLog("FAILED TO REGISTER FOR REMOTE NOTIFICATIONS") + ITBInfo("error: \(error)") + } + + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + NSLog("RECEIVED REMOTE NOTIFICATIONS") + ITBInfo() + IterableAppIntegration.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler) + } + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + NSLog("RECEIVED UNIVERSAL LINK") + NSLog("userActivity: \(userActivity)") + ITBInfo() + guard let url = userActivity.webpageURL else { + return false + } + + return IterableAPI.handle(universalLink: url) + } + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + NSLog("OPEN URL") + NSLog("url: \(url)") + NSLog("options: \(options)") + ITBInfo() + return RCTLinkingManager.application(app, open: url, options: options) + } + + private func setupUserNotificationCenter() { + UNUserNotificationCenter.current().delegate = self + UNUserNotificationCenter.current().getNotificationSettings { settings in + if settings.authorizationStatus != .authorized { + ITBInfo("Not authorized") + // not authorized, ask for permission + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, _ in + ITBInfo("auth: \(success)") + if success { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + // TODO: Handle error etc. + } + } else { + // already authorized + ITBInfo("Already authorized") + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + } + } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { @@ -46,10 +117,24 @@ class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { } override func bundleURL() -> URL? { - #if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") - #else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") - #endif +#if DEBUG + RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#else + Bundle.main.url(forResource: "main", withExtension: "jsbundle") +#endif + } +} + +extension AppDelegate: UNUserNotificationCenterDelegate { + // App is running in the foreground + public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + NSLog("WILL PRESENT NOTIFICATION") + completionHandler([.alert, .badge, .sound]) + } + + // The method will be called on the delegate when the user responded to the notification by opening the application, dismissing the notification or choosing a UNNotificationAction. The delegate must be set before the application returns from applicationDidFinishLaunching:. + public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + NSLog("DID RECEIVE NOTIFICATION RESPONSE") + IterableAppIntegration.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler) } }