From 1ac73703f90c9c5b6e8e2d6536ec02e558b31e5b Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 15:08:27 -0700 Subject: [PATCH 01/37] docs: add Embedded component to example app --- example/src/components/App/App.constants.ts | 1 + example/src/components/App/App.tsx | 45 ++++++++++++++++++- .../components/Embedded/Embedded.styles.ts | 21 +++++++++ example/src/components/Embedded/Embedded.tsx | 13 ++++++ example/src/components/Embedded/index.ts | 2 + example/src/constants/routes.ts | 1 + example/src/constants/styles/index.ts | 1 + example/src/constants/styles/miscElements.ts | 9 ++++ example/src/types/navigation.ts | 1 + 9 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 example/src/components/Embedded/Embedded.styles.ts create mode 100644 example/src/components/Embedded/Embedded.tsx create mode 100644 example/src/components/Embedded/index.ts create mode 100644 example/src/constants/styles/miscElements.ts diff --git a/example/src/components/App/App.constants.ts b/example/src/components/App/App.constants.ts index f84c390cb..4710a6ba9 100644 --- a/example/src/components/App/App.constants.ts +++ b/example/src/components/App/App.constants.ts @@ -2,6 +2,7 @@ import { Route } from '../../constants'; export const routeIcon = { [Route.Commerce]: 'cash-outline', + [Route.Embedded]: 'chatbubble-outline', [Route.Inbox]: 'mail-outline', [Route.User]: 'person-outline', }; diff --git a/example/src/components/App/App.tsx b/example/src/components/App/App.tsx index 42769db1d..d71b9fe95 100644 --- a/example/src/components/App/App.tsx +++ b/example/src/components/App/App.tsx @@ -1,16 +1,59 @@ +/* eslint-disable react-native/split-platform-components */ import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { useEffect } from 'react'; +import { PermissionsAndroid, Platform } from 'react-native'; import { Route } from '../../constants/routes'; import { useIterableApp } from '../../hooks/useIterableApp'; +import type { RootStackParamList } from '../../types'; import { Login } from '../Login'; import { Main } from './Main'; -import type { RootStackParamList } from '../../types'; const Stack = createNativeStackNavigator(); +const requestNotificationPermission = async () => { + if (Platform.OS === 'android') { + const apiLevel = Platform.Version; // Get the Android API level + + if (apiLevel >= 33) { + // Check if Android 13 or higher + try { + const granted = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS, + { + title: 'Notification Permission', + message: + 'This app needs access to your notifications for push, in-app messages, embedded messages and more.', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + } + ); + if (granted === PermissionsAndroid.RESULTS.GRANTED) { + console.log('Notification permission granted'); + } else { + console.log('Notification permission denied'); + } + } catch (err) { + console.warn(err); + } + } else { + // For Android versions below 13, notification permission is generally not required + // or is automatically granted upon app installation. + console.log( + 'Notification permission not required for this Android version.' + ); + } + } +}; + export const App = () => { const { isLoggedIn } = useIterableApp(); + useEffect(() => { + requestNotificationPermission(); + }, []); + return ( {isLoggedIn ? ( diff --git a/example/src/components/Embedded/Embedded.styles.ts b/example/src/components/Embedded/Embedded.styles.ts new file mode 100644 index 000000000..9dc6aea47 --- /dev/null +++ b/example/src/components/Embedded/Embedded.styles.ts @@ -0,0 +1,21 @@ +import { StyleSheet } from 'react-native'; +import { button, buttonText, container, hr } from '../../constants'; + +const styles = StyleSheet.create({ + button, + buttonText, + container: { ...container, paddingHorizontal: 0 }, + embeddedSection: { + display: 'flex', + flexDirection: 'column', + gap: 16, + paddingHorizontal: 16, + }, + hr, + text: { textAlign: 'center' }, + utilitySection: { + paddingHorizontal: 16, + }, +}); + +export default styles; diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx new file mode 100644 index 000000000..806bb0c23 --- /dev/null +++ b/example/src/components/Embedded/Embedded.tsx @@ -0,0 +1,13 @@ +import { Text, View } from 'react-native'; + +import styles from './Embedded.styles'; + +export const Embedded = () => { + return ( + + EMBEDDED + + ); +}; + +export default Embedded; diff --git a/example/src/components/Embedded/index.ts b/example/src/components/Embedded/index.ts new file mode 100644 index 000000000..908767962 --- /dev/null +++ b/example/src/components/Embedded/index.ts @@ -0,0 +1,2 @@ +export * from './Embedded'; +export { default } from './Embedded'; diff --git a/example/src/constants/routes.ts b/example/src/constants/routes.ts index 4af27c548..c2087cacf 100644 --- a/example/src/constants/routes.ts +++ b/example/src/constants/routes.ts @@ -1,5 +1,6 @@ export enum Route { Commerce = 'Commerce', + Embedded = 'Embedded', Inbox = 'Inbox', Login = 'Login', Main = 'Main', diff --git a/example/src/constants/styles/index.ts b/example/src/constants/styles/index.ts index 794f9680c..b8c3bac5e 100644 --- a/example/src/constants/styles/index.ts +++ b/example/src/constants/styles/index.ts @@ -1,5 +1,6 @@ export * from './colors'; export * from './containers'; export * from './formElements'; +export * from './miscElements'; export * from './shadows'; export * from './typography'; diff --git a/example/src/constants/styles/miscElements.ts b/example/src/constants/styles/miscElements.ts new file mode 100644 index 000000000..470605ea7 --- /dev/null +++ b/example/src/constants/styles/miscElements.ts @@ -0,0 +1,9 @@ +import { type ViewStyle } from 'react-native'; +import { colors } from './colors'; + +export const hr: ViewStyle = { + backgroundColor: colors.borderPrimary, + height: 1, + marginVertical: 20, + marginHorizontal: 0, +}; diff --git a/example/src/types/navigation.ts b/example/src/types/navigation.ts index 5b5ad8a50..e3038d088 100644 --- a/example/src/types/navigation.ts +++ b/example/src/types/navigation.ts @@ -10,6 +10,7 @@ import { Route } from '../constants/routes'; export type MainScreenParamList = { [Route.Commerce]: undefined; [Route.Inbox]: undefined; + [Route.Embedded]: undefined; [Route.User]: undefined; }; From 15e18946e72ea8f76fb945d148ccee1c37a1ba13 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 16:34:18 -0700 Subject: [PATCH 02/37] feat: add IterableEmbeddedManager class and update exports for embedded messages --- .../classes/IterableEmbeddedManager.ts | 66 +++++++++++++++++++ src/embedded/classes/index.ts | 1 + src/embedded/index.ts | 1 + src/index.tsx | 1 + 4 files changed, 69 insertions(+) create mode 100644 src/embedded/classes/IterableEmbeddedManager.ts create mode 100644 src/embedded/classes/index.ts create mode 100644 src/embedded/index.ts diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts new file mode 100644 index 000000000..95d558c10 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -0,0 +1,66 @@ +import { IterableLogger } from '../../core/classes/IterableLogger'; + +/** + * Manages the embedded messages from Iterable. + */ +export class IterableEmbeddedManager { + /** + * Refreshes the local cache of your embedded manager system so that it aligns + * with the server. + * + * At key points during your app's lifecycle, you may want to manually refresh + * your app's local cache of embedded messages. For example, as users navigate + * around, on pull-to-refresh, etc. + * + * @returns A promise that returns messages that the user is *eligible* to see. + * + * @example + * ```typescript + * IterableEmbeddedManager.syncMessages().then(messages => { + * console.log('Messages:', messages); + * }); + * ``` + */ + syncMessages() { + IterableLogger.log('IterableEmbeddedManager.syncMessages'); + } + + getMessages(placementIds?: number[] | null) { + IterableLogger.log( + 'IterableEmbeddedManager.getMessages with placementIds', + placementIds + ); + } + + getPlacementIds() { + IterableLogger.log('IterableEmbeddedManager.getEmbeddedPlacementIds'); + } + + startSession() { + IterableLogger.log('IterableEmbeddedManager.startSession'); + } + + endSession() { + IterableLogger.log('IterableEmbeddedManager.endSession'); + } + + startImpression(messageId: string, placementId: number) { + IterableLogger.log( + 'IterableEmbeddedManager.startImpression', + messageId, + placementId + ); + } + + pauseImpression(messageId: string) { + IterableLogger.log('IterableEmbeddedManager.pauseImpression', messageId); + } + + handleClick() { + IterableLogger.log('IterableEmbeddedManager.handleClick'); + } + + trackClick() { + IterableLogger.log('IterableEmbeddedManager.trackClick'); + } +} diff --git a/src/embedded/classes/index.ts b/src/embedded/classes/index.ts new file mode 100644 index 000000000..be2af76f0 --- /dev/null +++ b/src/embedded/classes/index.ts @@ -0,0 +1 @@ +export * from './IterableEmbeddedManager'; diff --git a/src/embedded/index.ts b/src/embedded/index.ts new file mode 100644 index 000000000..d7d17c691 --- /dev/null +++ b/src/embedded/index.ts @@ -0,0 +1 @@ +export * from './classes'; diff --git a/src/index.tsx b/src/index.tsx index 240ac51f5..c9a014340 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -59,3 +59,4 @@ export { type IterableInboxProps, type IterableInboxRowViewModel, } from './inbox'; +export { IterableEmbeddedManager } from './embedded'; From 2a62913003144ad1f481cd1249aaa3ae0f9c5f4c Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 17:05:31 -0700 Subject: [PATCH 03/37] feat: add embeddedManager property to Iterable class for managing embedded messages --- src/core/classes/Iterable.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 9ef784679..ebb928cd6 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -20,6 +20,7 @@ import { IterableAuthResponse } from './IterableAuthResponse'; import type { IterableCommerceItem } from './IterableCommerceItem'; import { IterableConfig } from './IterableConfig'; import { IterableLogger } from './IterableLogger'; +import { IterableEmbeddedManager } from '../../embedded/classes/IterableEmbeddedManager'; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); @@ -79,6 +80,27 @@ export class Iterable { */ static authManager: IterableAuthManager = new IterableAuthManager(); + /** + * Embedded message manager for the current user. + * + * This property provides access to embedded message functionality including + * retrieving messages, displaying messages, removing messages, and more. + * + * **Documentation** + * - [Embedded Messaging Overview](https://support.iterable.com/hc/en-us/articles/23060529977364-Embedded-Messaging-Overview) + * - [Android Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061877893652-Embedded-Messages-with-Iterable-s-Android-SDK) + * - [iOS Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061840746900-Embedded-Messages-with-Iterable-s-iOS-SDK) + * + * @example + * ```typescript + * Iterable.embeddedManager.getMessages().then(messages => { + * console.log('Messages:', messages); + * }); + * ``` + */ + static embeddedManager: IterableEmbeddedManager = + new IterableEmbeddedManager(); + /** * Initializes the Iterable React Native SDK in your app's Javascript or Typescript code. * From 789ae831cebed8d79dc4917850e9134ea6564350 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 20:48:10 -0700 Subject: [PATCH 04/37] refactor: remove unused methods and improve documentation in IterableEmbeddedManager --- .../classes/IterableEmbeddedManager.ts | 68 +++---------------- 1 file changed, 8 insertions(+), 60 deletions(-) diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index 95d558c10..f6c59273b 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -1,66 +1,14 @@ -import { IterableLogger } from '../../core/classes/IterableLogger'; - /** - * Manages the embedded messages from Iterable. + * Manages embedded messages from Iterable. + * + * **Documentation** + * - [Embedded Messaging Overview](https://support.iterable.com/hc/en-us/articles/23060529977364-Embedded-Messaging-Overview) + * - [Android Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061877893652-Embedded-Messages-with-Iterable-s-Android-SDK) + * - [iOS Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061840746900-Embedded-Messages-with-Iterable-s-iOS-SDK) */ export class IterableEmbeddedManager { /** - * Refreshes the local cache of your embedded manager system so that it aligns - * with the server. - * - * At key points during your app's lifecycle, you may want to manually refresh - * your app's local cache of embedded messages. For example, as users navigate - * around, on pull-to-refresh, etc. - * - * @returns A promise that returns messages that the user is *eligible* to see. - * - * @example - * ```typescript - * IterableEmbeddedManager.syncMessages().then(messages => { - * console.log('Messages:', messages); - * }); - * ``` + * Whether the embedded manager is enabled. */ - syncMessages() { - IterableLogger.log('IterableEmbeddedManager.syncMessages'); - } - - getMessages(placementIds?: number[] | null) { - IterableLogger.log( - 'IterableEmbeddedManager.getMessages with placementIds', - placementIds - ); - } - - getPlacementIds() { - IterableLogger.log('IterableEmbeddedManager.getEmbeddedPlacementIds'); - } - - startSession() { - IterableLogger.log('IterableEmbeddedManager.startSession'); - } - - endSession() { - IterableLogger.log('IterableEmbeddedManager.endSession'); - } - - startImpression(messageId: string, placementId: number) { - IterableLogger.log( - 'IterableEmbeddedManager.startImpression', - messageId, - placementId - ); - } - - pauseImpression(messageId: string) { - IterableLogger.log('IterableEmbeddedManager.pauseImpression', messageId); - } - - handleClick() { - IterableLogger.log('IterableEmbeddedManager.handleClick'); - } - - trackClick() { - IterableLogger.log('IterableEmbeddedManager.trackClick'); - } + isEnabled = false; } From 9940f46b1f4c8cec2fe6252e1dc5a0a6bb3e07ea Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 20:54:10 -0700 Subject: [PATCH 05/37] docs: enhance documentation --- src/embedded/classes/IterableEmbeddedManager.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index f6c59273b..333dd3af7 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -1,6 +1,9 @@ /** * Manages embedded messages from Iterable. * + * Provides embedded message functionality including retrieving messages, + * displaying messages, removing messages, and more. + * * **Documentation** * - [Embedded Messaging Overview](https://support.iterable.com/hc/en-us/articles/23060529977364-Embedded-Messaging-Overview) * - [Android Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061877893652-Embedded-Messages-with-Iterable-s-Android-SDK) From e7d4ecbb6e05bffc26f9c8d92bc77abe29de3dd8 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 21:08:42 -0700 Subject: [PATCH 06/37] feat: add embeddedMessagingEnabled property to IterableConfig --- src/core/classes/IterableConfig.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index c8ee67400..cbe3cb665 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -319,6 +319,16 @@ export class IterableConfig { */ encryptionEnforced = false; + /** + * Should the SDK enable and use embedded messaging? + * + * **Documentation** + * - [Embedded Messaging Overview](https://support.iterable.com/hc/en-us/articles/23060529977364-Embedded-Messaging-Overview) + * - [Android Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061877893652-Embedded-Messages-with-Iterable-s-Android-SDK) + * - [iOS Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061840746900-Embedded-Messages-with-Iterable-s-iOS-SDK) + */ + embeddedMessagingEnabled = false; + /** * Converts the IterableConfig instance to a dictionary object. * @@ -368,6 +378,7 @@ export class IterableConfig { pushPlatform: this.pushPlatform, encryptionEnforced: this.encryptionEnforced, retryPolicy: this.retryPolicy, + embeddedMessagingEnabled: this.embeddedMessagingEnabled, }; } } From 12ee8fd000f23d7c50de5fafbec648d19cf2c504 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 21:16:04 -0700 Subject: [PATCH 07/37] feat: enable embedded messaging configuration in RNIterableAPIModuleImpl --- .../com/iterable/reactnative/RNIterableAPIModuleImpl.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 3207bb5dc..5b15e9808 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -88,6 +88,10 @@ public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, S configBuilder.setAuthHandler(this); } + if (configReadableMap.hasKey("enableEmbeddedMessaging")) { + configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging")); + } + IterableApi.initialize(reactContext, apiKey, configBuilder.build()); IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); @@ -118,6 +122,10 @@ public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, configBuilder.setAuthHandler(this); } + if (configReadableMap.hasKey("enableEmbeddedMessaging")) { + configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging")); + } + // NOTE: There does not seem to be a way to set the API endpoint // override in the Android SDK. Check with @Ayyanchira and @evantk91 to // see what the best approach is. From 93402fd9c1ff24b61f7e902be3886b496181487f Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 21:40:36 -0700 Subject: [PATCH 08/37] feat: update IterableConfig with prop that matches android name --- example/src/hooks/useIterableApp.tsx | 2 ++ src/core/classes/IterableConfig.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index d648dd25c..136323dbd 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -134,6 +134,8 @@ export const IterableAppProvider: FunctionComponent< retryBackoff: IterableRetryBackoff.LINEAR, }; + config.enableEmbeddedMessaging = true; + config.onJWTError = (authFailure) => { console.log('onJWTError', authFailure); diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index cbe3cb665..6623108e7 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -327,7 +327,7 @@ export class IterableConfig { * - [Android Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061877893652-Embedded-Messages-with-Iterable-s-Android-SDK) * - [iOS Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061840746900-Embedded-Messages-with-Iterable-s-iOS-SDK) */ - embeddedMessagingEnabled = false; + enableEmbeddedMessaging = false; /** * Converts the IterableConfig instance to a dictionary object. @@ -378,7 +378,7 @@ export class IterableConfig { pushPlatform: this.pushPlatform, encryptionEnforced: this.encryptionEnforced, retryPolicy: this.retryPolicy, - embeddedMessagingEnabled: this.embeddedMessagingEnabled, + enableEmbeddedMessaging: this.enableEmbeddedMessaging, }; } } From b916fb376d8d074adcf1147ccdf5da4b92d93e05 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 21:59:29 -0700 Subject: [PATCH 09/37] feat: implement getEmbeddedPlacementIds --- .../reactnative/RNIterableAPIModuleImpl.java | 22 +++++++++++++++++++ .../newarch/java/com/RNIterableAPIModule.java | 5 +++++ .../oldarch/java/com/RNIterableAPIModule.java | 4 ++++ src/api/NativeRNIterableAPI.ts | 3 +++ src/core/classes/IterableApi.ts | 16 +++++++++++++- .../classes/IterableEmbeddedManager.ts | 11 ++++++++++ 6 files changed, 60 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 5b15e9808..3db33bd8a 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -15,6 +15,7 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; @@ -507,6 +508,27 @@ private static JSONObject optSerializedDataFields(ReadableMap dataFields) { // endregion // --------------------------------------------------------------------------------------- + // region Embedded Messaging + + public void getEmbeddedPlacementIds(Promise promise) { + IterableLogger.d(TAG, "getEmbeddedPlacementIds"); + try { + List placementIds = IterableApi.getInstance().getEmbeddedManager().getPlacementIds(); + WritableArray writableArray = Arguments.createArray(); + if (placementIds != null) { + for (Long placementId : placementIds) { + writableArray.pushDouble(placementId.doubleValue()); + } + } + promise.resolve(writableArray); + } catch (Exception e) { + IterableLogger.e(TAG, "Error getting placement IDs: " + e.getLocalizedMessage()); + promise.reject("", "Failed to get placement IDs: " + e.getLocalizedMessage()); + } + } + + // --------------------------------------------------------------------------------------- + // endregion // region IterableSDK callbacks @Override diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index f145bab10..50e814f0b 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -224,6 +224,11 @@ public void pauseAuthRetries(boolean pauseRetry) { moduleImpl.pauseAuthRetries(pauseRetry); } + @Override + public void getEmbeddedPlacementIds(Promise promise) { + moduleImpl.getEmbeddedPlacementIds(promise); + } + 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 c3a72339b..4555fd71b 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -228,6 +228,10 @@ public void pauseAuthRetries(boolean pauseRetry) { moduleImpl.pauseAuthRetries(pauseRetry); } + @ReactMethod + public void getEmbeddedPlacementIds(Promise promise) { + moduleImpl.getEmbeddedPlacementIds(promise); + } 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 391fadbb7..9dd2af2b7 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -118,6 +118,9 @@ export interface Spec extends TurboModule { passAlongAuthToken(authToken?: string | null): void; pauseAuthRetries(pauseRetry: boolean): void; + // Embedded Messaging + getEmbeddedPlacementIds(): Promise; + // Wake app -- android only wakeApp(): void; diff --git a/src/core/classes/IterableApi.ts b/src/core/classes/IterableApi.ts index fe2b446a3..9825f42ae 100644 --- a/src/core/classes/IterableApi.ts +++ b/src/core/classes/IterableApi.ts @@ -507,7 +507,21 @@ export class IterableApi { // ---- End IN-APP ---- // // ====================================================== // - // ======================= MOSC ======================= // + // ======================= EMBEDDED ===================== // + // ====================================================== // + + /** + * Get the embedded placement IDs. + */ + static getEmbeddedPlacementIds() { + IterableLogger.log('getEmbeddedPlacementIds'); + return RNIterableAPI.getEmbeddedPlacementIds(); + } + + // ---- End EMBEDDED ---- // + + // ====================================================== // + // ======================= MISCELLANEOUS ================ // // ====================================================== // /** diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index 333dd3af7..c4f8ae229 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -1,3 +1,5 @@ +import { IterableApi } from '../../core/classes/IterableApi'; + /** * Manages embedded messages from Iterable. * @@ -14,4 +16,13 @@ export class IterableEmbeddedManager { * Whether the embedded manager is enabled. */ isEnabled = false; + + /** + * Retrieves a list of placement IDs for the embedded manager. + * + * [Placement Documentation](https://support.iterable.com/hc/en-us/articles/23060529977364-Embedded-Messaging-Overview#placements-and-prioritization) + */ + getPlacementIds() { + return IterableApi.getEmbeddedPlacementIds(); + } } From f456e2089523469d1ba82b5ddbb75d417db2ce7a Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 22:02:41 -0700 Subject: [PATCH 10/37] feat: add Embedded tab to Main component in example app --- example/src/components/App/Main.tsx | 8 ++++++++ example/src/components/Embedded/index.ts | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/example/src/components/App/Main.tsx b/example/src/components/App/Main.tsx index 55b0d74e2..07b7b2d2c 100644 --- a/example/src/components/App/Main.tsx +++ b/example/src/components/App/Main.tsx @@ -8,6 +8,7 @@ import { User } from '../User'; import { Inbox } from '../Inbox'; import { useIterableApp } from '../../hooks'; import { Commerce } from '../Commerce'; +import { Embedded } from '../Embedded'; const Tab = createBottomTabNavigator(); @@ -44,6 +45,13 @@ export const Main = () => { }, })} /> + ({ + tabPress: () => setIsInboxTab(false), + })} + /> Date: Fri, 10 Oct 2025 22:16:56 -0700 Subject: [PATCH 11/37] feat: display embedded manager status in Embedded component --- example/src/components/Embedded/Embedded.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 806bb0c23..ce1348e48 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -1,4 +1,5 @@ import { Text, View } from 'react-native'; +import { Iterable } from '@iterable/react-native-sdk'; import styles from './Embedded.styles'; @@ -6,6 +7,13 @@ export const Embedded = () => { return ( EMBEDDED + + Does embedded class exist? {Iterable.embeddedManager ? 'Yes' : 'No'} + + + Is embedded manager enabled? + {Iterable.embeddedManager.isEnabled ? 'Yes' : 'No'} + ); }; From b0e17f99db47c540f2ab66babb7062bcca348bf8 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 22:25:45 -0700 Subject: [PATCH 12/37] feat: add functionality to retrieve and display placement IDs in Embedded component --- example/src/components/Embedded/Embedded.tsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index ce1348e48..4381cc2d1 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -1,9 +1,21 @@ -import { Text, View } from 'react-native'; +import { Text, TouchableOpacity, View } from 'react-native'; +import { useCallback, useState } from 'react'; import { Iterable } from '@iterable/react-native-sdk'; import styles from './Embedded.styles'; export const Embedded = () => { + const [placementIds, setPlacementIds] = useState([]); + const getPlacementIds = useCallback(() => { + Iterable.embeddedManager.getPlacementIds().then((ids: unknown) => { + console.log(ids); + setPlacementIds(ids as number[]); + }); + // .catch((error) => { + // console.error(error); + // }); + }, []); + return ( EMBEDDED @@ -14,6 +26,12 @@ export const Embedded = () => { Is embedded manager enabled? {Iterable.embeddedManager.isEnabled ? 'Yes' : 'No'} + + Placement ids: [{placementIds.join(', ')}] + + + Get placement ids + ); }; From d42dd737e39ed7639cc281c8844983d77d7dc03c Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 22:47:50 -0700 Subject: [PATCH 13/37] feat: implement methods to retrieve and sync embedded messages in RNIterableAPIModuleImpl --- .../reactnative/RNIterableAPIModuleImpl.java | 110 +++++++++++++----- .../iterable/reactnative/Serialization.java | 18 ++- 2 files changed, 98 insertions(+), 30 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 3db33bd8a..19bcbdb72 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -24,10 +24,14 @@ import com.iterable.iterableapi.IterableAction; import com.iterable.iterableapi.IterableActionContext; import com.iterable.iterableapi.IterableApi; +import com.iterable.iterableapi.IterableAttributionInfo; import com.iterable.iterableapi.IterableAuthHandler; import com.iterable.iterableapi.IterableConfig; import com.iterable.iterableapi.IterableCustomActionHandler; -import com.iterable.iterableapi.IterableAttributionInfo; +// import com.iterable.iterableapi.IterableEmbeddedManager; +import com.iterable.iterableapi.IterableEmbeddedMessage; +// import com.iterable.iterableapi.IterableEmbeddedSession; +// import com.iterable.iterableapi.IterableEmbeddedUpdateHandler; import com.iterable.iterableapi.IterableHelper; import com.iterable.iterableapi.IterableInAppCloseAction; import com.iterable.iterableapi.IterableInAppHandler; @@ -46,6 +50,7 @@ import java.util.Map; import java.util.HashMap; import java.util.List; +import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -97,6 +102,7 @@ public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, S IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); IterableApi.getInstance().getInAppManager().addListener(this); + IterableApi.getInstance().getEmbeddedManager().syncMessages(); // MOB-10421: Figure out what the error cases are and handle them appropriately // This is just here to match the TS types and let the JS thread know when we are done initializing @@ -124,8 +130,8 @@ public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, } if (configReadableMap.hasKey("enableEmbeddedMessaging")) { - configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging")); - } + configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging")); + } // NOTE: There does not seem to be a way to set the API endpoint // override in the Android SDK. Check with @Ayyanchira and @evantk91 to @@ -135,6 +141,7 @@ public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); IterableApi.getInstance().getInAppManager().addListener(this); + IterableApi.getInstance().getEmbeddedManager().syncMessages(); // MOB-10421: Figure out what the error cases are and handle them appropriately // This is just here to match the TS types and let the JS thread know when we are done initializing @@ -508,27 +515,6 @@ private static JSONObject optSerializedDataFields(ReadableMap dataFields) { // endregion // --------------------------------------------------------------------------------------- - // region Embedded Messaging - - public void getEmbeddedPlacementIds(Promise promise) { - IterableLogger.d(TAG, "getEmbeddedPlacementIds"); - try { - List placementIds = IterableApi.getInstance().getEmbeddedManager().getPlacementIds(); - WritableArray writableArray = Arguments.createArray(); - if (placementIds != null) { - for (Long placementId : placementIds) { - writableArray.pushDouble(placementId.doubleValue()); - } - } - promise.resolve(writableArray); - } catch (Exception e) { - IterableLogger.e(TAG, "Error getting placement IDs: " + e.getLocalizedMessage()); - promise.reject("", "Failed to get placement IDs: " + e.getLocalizedMessage()); - } - } - - // --------------------------------------------------------------------------------------- - // endregion // region IterableSDK callbacks @Override @@ -659,14 +645,80 @@ public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { public void onInboxUpdated() { sendEvent(EventName.receivedIterableInboxChanged.name(), null); } + // --------------------------------------------------------------------------------------- + // endregion + + // --------------------------------------------------------------------------------------- + // region Embedded messaging + + public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise promise) { + IterableLogger.d(TAG, "getEmbeddedMessages for placements: " + placementIds); + + try { + List allMessages = new ArrayList<>(); + + if (placementIds == null || placementIds.size() == 0) { + // If no placement IDs provided, we need to get messages for all possible placements + // Since the Android SDK requires a placement ID, we'll use 0 as a default + // This might need to be adjusted based on the actual SDK behavior + List messages = IterableApi.getInstance().getEmbeddedManager().getMessages(0L); + if (messages != null) { + allMessages.addAll(messages); + } + } else { + // Convert ReadableArray to individual placement IDs and get messages for each + for (int i = 0; i < placementIds.size(); i++) { + long placementId = placementIds.getInt(i); + List messages = IterableApi.getInstance().getEmbeddedManager().getMessages(placementId); + if (messages != null) { + allMessages.addAll(messages); + } + } + } + + JSONArray embeddedMessageJsonArray = Serialization.serializeEmbeddedMessages(allMessages); + IterableLogger.d(TAG, "Messages for placements: " + embeddedMessageJsonArray); + + promise.resolve(Serialization.convertJsonToArray(embeddedMessageJsonArray)); + } catch (JSONException e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); + } + } + + public void syncEmbeddedMessages() { + IterableLogger.d(TAG, "syncEmbeddedMessages"); + IterableApi.getInstance().getEmbeddedManager().syncMessages(); + } + + public void getEmbeddedPlacementIds(Promise promise) { + IterableLogger.d(TAG, "getEmbeddedPlacementIds"); + try { + List placementIds = IterableApi.getInstance().getEmbeddedManager().getPlacementIds(); + WritableArray writableArray = Arguments.createArray(); + if (placementIds != null) { + for (Long placementId : placementIds) { + writableArray.pushDouble(placementId.doubleValue()); + } + } + promise.resolve(writableArray); + } catch (Exception e) { + IterableLogger.e(TAG, "Error getting placement IDs: " + e.getLocalizedMessage()); + promise.reject("", "Failed to get placement IDs: " + e.getLocalizedMessage()); + } + } + + // --------------------------------------------------------------------------------------- + // endregion } enum EventName { - handleUrlCalled, - handleCustomActionCalled, - handleInAppCalled, handleAuthCalled, - receivedIterableInboxChanged, + handleAuthFailureCalled, handleAuthSuccessCalled, - handleAuthFailureCalled + handleCustomActionCalled, + handleInAppCalled, + handleUrlCalled, + receivedIterableEmbeddedMessagesChanged, + receivedIterableInboxChanged } diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 92c549554..2f0c4e1ca 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -16,6 +16,7 @@ import com.iterable.iterableapi.IterableActionContext; import com.iterable.iterableapi.IterableConfig; import com.iterable.iterableapi.IterableDataRegion; +import com.iterable.iterableapi.IterableEmbeddedMessage; import com.iterable.iterableapi.IterableInAppCloseAction; import com.iterable.iterableapi.IterableInAppDeleteActionType; import com.iterable.iterableapi.IterableInAppHandler; @@ -23,8 +24,8 @@ import com.iterable.iterableapi.IterableInAppMessage; import com.iterable.iterableapi.IterableInboxSession; import com.iterable.iterableapi.IterableLogger; -import com.iterable.iterableapi.RNIterableInternal; import com.iterable.iterableapi.RetryPolicy; +import com.iterable.iterableapi.RNIterableInternal; import org.json.JSONArray; import org.json.JSONException; @@ -137,6 +138,17 @@ static JSONArray serializeInAppMessages(List inAppMessages return inAppMessagesJson; } + static JSONArray serializeEmbeddedMessages(List embeddedMessages) { + JSONArray embeddedMessagesJson = new JSONArray(); + if (embeddedMessages != null) { + for (IterableEmbeddedMessage message : embeddedMessages) { + JSONObject messageJson = IterableEmbeddedMessage.Companion.toJSONObject(message); + embeddedMessagesJson.put(messageJson); + } + } + return embeddedMessagesJson; + } + static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableContextMap) { try { JSONObject iterableContextJSON = convertMapToJson(iterableContextMap); @@ -218,6 +230,10 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte configBuilder.setDataRegion(iterableDataRegion); } + if (iterableContextJSON.has("enableEmbeddedMessaging")) { + configBuilder.setEnableEmbeddedMessaging(iterableContextJSON.optBoolean("enableEmbeddedMessaging")); + } + if (iterableContextJSON.has("retryPolicy")) { JSONObject retryPolicyJson = iterableContextJSON.getJSONObject("retryPolicy"); int maxRetry = retryPolicyJson.getInt("maxRetry"); From 67278b71557021d780e1f5ff1fff6e294ec2b73d Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 23:06:13 -0700 Subject: [PATCH 14/37] refactor: add sync and get messages --- .../reactnative/RNIterableAPIModuleImpl.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 19bcbdb72..1517c717f 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -651,6 +651,28 @@ public void onInboxUpdated() { // --------------------------------------------------------------------------------------- // region Embedded messaging + public void syncEmbeddedMessages() { + IterableLogger.d(TAG, "syncEmbeddedMessages"); + IterableApi.getInstance().getEmbeddedManager().syncMessages(); + } + + public void getEmbeddedPlacementIds(Promise promise) { + IterableLogger.d(TAG, "getEmbeddedPlacementIds"); + try { + List placementIds = IterableApi.getInstance().getEmbeddedManager().getPlacementIds(); + WritableArray writableArray = Arguments.createArray(); + if (placementIds != null) { + for (Long placementId : placementIds) { + writableArray.pushDouble(placementId.doubleValue()); + } + } + promise.resolve(writableArray); + } catch (Exception e) { + IterableLogger.e(TAG, "Error getting placement IDs: " + e.getLocalizedMessage()); + promise.reject("", "Failed to get placement IDs: " + e.getLocalizedMessage()); + } + } + public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise promise) { IterableLogger.d(TAG, "getEmbeddedMessages for placements: " + placementIds); @@ -686,28 +708,6 @@ public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise pr } } - public void syncEmbeddedMessages() { - IterableLogger.d(TAG, "syncEmbeddedMessages"); - IterableApi.getInstance().getEmbeddedManager().syncMessages(); - } - - public void getEmbeddedPlacementIds(Promise promise) { - IterableLogger.d(TAG, "getEmbeddedPlacementIds"); - try { - List placementIds = IterableApi.getInstance().getEmbeddedManager().getPlacementIds(); - WritableArray writableArray = Arguments.createArray(); - if (placementIds != null) { - for (Long placementId : placementIds) { - writableArray.pushDouble(placementId.doubleValue()); - } - } - promise.resolve(writableArray); - } catch (Exception e) { - IterableLogger.e(TAG, "Error getting placement IDs: " + e.getLocalizedMessage()); - promise.reject("", "Failed to get placement IDs: " + e.getLocalizedMessage()); - } - } - // --------------------------------------------------------------------------------------- // endregion } From 06a94fc96405ac3d6e1e56c39504d5a19a094e10 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 23:12:09 -0700 Subject: [PATCH 15/37] refactor: remove unused methodss --- .../reactnative/RNIterableAPIModuleImpl.java | 41 ------------------- .../iterable/reactnative/Serialization.java | 11 ----- 2 files changed, 52 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 1517c717f..fcbbbb2e1 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -650,12 +650,6 @@ public void onInboxUpdated() { // --------------------------------------------------------------------------------------- // region Embedded messaging - - public void syncEmbeddedMessages() { - IterableLogger.d(TAG, "syncEmbeddedMessages"); - IterableApi.getInstance().getEmbeddedManager().syncMessages(); - } - public void getEmbeddedPlacementIds(Promise promise) { IterableLogger.d(TAG, "getEmbeddedPlacementIds"); try { @@ -673,41 +667,6 @@ public void getEmbeddedPlacementIds(Promise promise) { } } - public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise promise) { - IterableLogger.d(TAG, "getEmbeddedMessages for placements: " + placementIds); - - try { - List allMessages = new ArrayList<>(); - - if (placementIds == null || placementIds.size() == 0) { - // If no placement IDs provided, we need to get messages for all possible placements - // Since the Android SDK requires a placement ID, we'll use 0 as a default - // This might need to be adjusted based on the actual SDK behavior - List messages = IterableApi.getInstance().getEmbeddedManager().getMessages(0L); - if (messages != null) { - allMessages.addAll(messages); - } - } else { - // Convert ReadableArray to individual placement IDs and get messages for each - for (int i = 0; i < placementIds.size(); i++) { - long placementId = placementIds.getInt(i); - List messages = IterableApi.getInstance().getEmbeddedManager().getMessages(placementId); - if (messages != null) { - allMessages.addAll(messages); - } - } - } - - JSONArray embeddedMessageJsonArray = Serialization.serializeEmbeddedMessages(allMessages); - IterableLogger.d(TAG, "Messages for placements: " + embeddedMessageJsonArray); - - promise.resolve(Serialization.convertJsonToArray(embeddedMessageJsonArray)); - } catch (JSONException e) { - IterableLogger.e(TAG, e.getLocalizedMessage()); - promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); - } - } - // --------------------------------------------------------------------------------------- // endregion } diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 2f0c4e1ca..bace4c16c 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -138,17 +138,6 @@ static JSONArray serializeInAppMessages(List inAppMessages return inAppMessagesJson; } - static JSONArray serializeEmbeddedMessages(List embeddedMessages) { - JSONArray embeddedMessagesJson = new JSONArray(); - if (embeddedMessages != null) { - for (IterableEmbeddedMessage message : embeddedMessages) { - JSONObject messageJson = IterableEmbeddedMessage.Companion.toJSONObject(message); - embeddedMessagesJson.put(messageJson); - } - } - return embeddedMessagesJson; - } - static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableContextMap) { try { JSONObject iterableContextJSON = convertMapToJson(iterableContextMap); From 80ec4fc37e3e2dfafff0de2dce797d2aa6ba370a Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Fri, 10 Oct 2025 23:14:15 -0700 Subject: [PATCH 16/37] refactor: remove commented-out error handling in Embedded component --- example/src/components/Embedded/Embedded.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 4381cc2d1..39707e2ad 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -11,9 +11,6 @@ export const Embedded = () => { console.log(ids); setPlacementIds(ids as number[]); }); - // .catch((error) => { - // console.error(error); - // }); }, []); return ( From 1bee2d631af79d553ab7b2d0bb5c8e83e9aec085 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 09:38:58 -0700 Subject: [PATCH 17/37] feat: add session management methods to IterableEmbeddedManager --- .../classes/IterableEmbeddedManager.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index c4f8ae229..b485280cf 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -1,4 +1,5 @@ import { IterableApi } from '../../core/classes/IterableApi'; +import { IterableLogger } from '../../core/classes/IterableLogger'; /** * Manages embedded messages from Iterable. @@ -25,4 +26,26 @@ export class IterableEmbeddedManager { getPlacementIds() { return IterableApi.getEmbeddedPlacementIds(); } + + /** + * Starts a session, or a period of time when a user is on a screen or page + * that can display embedded messages. + */ + startSession() { + IterableLogger.log('IterableEmbeddedManager.startSession'); + } + + /** + * Ends a session. + */ + endSession() { + IterableLogger.log('IterableEmbeddedManager.endSession'); + } + + /** + * Tracks an embedded session. + */ + trackSession() { + IterableLogger.log('IterableEmbeddedManager.trackSession'); + } } From 0ba0b7f925ede49b6dcc8277e5c87e698acc0274 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 10:40:22 -0700 Subject: [PATCH 18/37] docs: enhance session management method documentation in IterableEmbeddedManager --- .../classes/IterableEmbeddedManager.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index b485280cf..df52ffd02 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -28,8 +28,18 @@ export class IterableEmbeddedManager { } /** - * Starts a session, or a period of time when a user is on a screen or page - * that can display embedded messages. + * Starts a session. + * + * As session is a period of time when a user is on a screen or page that can + * display embedded messages. + * + * When a user comes to a screen or page in your app where embedded messages + * are displayed (in one or more placements), a session should be started. + * + * @example + * ```typescript + * IterableEmbeddedManager.startSession(); + * ``` */ startSession() { IterableLogger.log('IterableEmbeddedManager.startSession'); @@ -37,6 +47,15 @@ export class IterableEmbeddedManager { /** * Ends a session. + * + * When a user leaves a screen in your app where embedded messages are + * displayed, the session should be ended. This causes the SDK to send + * session and impression data back to the server. + * + * @example + * ```typescript + * IterableEmbeddedManager.endSession(); + * ``` */ endSession() { IterableLogger.log('IterableEmbeddedManager.endSession'); @@ -44,6 +63,11 @@ export class IterableEmbeddedManager { /** * Tracks an embedded session. + * + * @example + * ```typescript + * IterableEmbeddedManager.trackSession(); + * ``` */ trackSession() { IterableLogger.log('IterableEmbeddedManager.trackSession'); From 24d5a7042f373fb1873fd68d232c7416a9bf6247 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 10:55:15 -0700 Subject: [PATCH 19/37] feat: add startEmbeddedSession method --- .../iterable/reactnative/RNIterableAPIModuleImpl.java | 5 +++++ android/src/newarch/java/com/RNIterableAPIModule.java | 5 +++++ android/src/oldarch/java/com/RNIterableAPIModule.java | 5 +++++ example/src/components/Embedded/Embedded.tsx | 10 ++++++++++ src/api/NativeRNIterableAPI.ts | 1 + src/core/classes/IterableApi.ts | 8 ++++++++ src/embedded/classes/IterableEmbeddedManager.ts | 2 +- 7 files changed, 35 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index fcbbbb2e1..f9bddf2c5 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -650,6 +650,11 @@ public void onInboxUpdated() { // --------------------------------------------------------------------------------------- // region Embedded messaging + + public void startEmbeddedSession() { + IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().startSession(); + } + public void getEmbeddedPlacementIds(Promise promise) { IterableLogger.d(TAG, "getEmbeddedPlacementIds"); try { diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index 50e814f0b..3f649f0c6 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -224,6 +224,11 @@ public void pauseAuthRetries(boolean pauseRetry) { moduleImpl.pauseAuthRetries(pauseRetry); } + @Override + public void startEmbeddedSession() { + moduleImpl.startEmbeddedSession(); + } + @Override public void getEmbeddedPlacementIds(Promise promise) { moduleImpl.getEmbeddedPlacementIds(promise); diff --git a/android/src/oldarch/java/com/RNIterableAPIModule.java b/android/src/oldarch/java/com/RNIterableAPIModule.java index 4555fd71b..9da39c755 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -228,6 +228,11 @@ public void pauseAuthRetries(boolean pauseRetry) { moduleImpl.pauseAuthRetries(pauseRetry); } + @ReactMethod + public void startEmbeddedSession() { + moduleImpl.startEmbeddedSession(); + } + @ReactMethod public void getEmbeddedPlacementIds(Promise promise) { moduleImpl.getEmbeddedPlacementIds(promise); diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 39707e2ad..71962f0a7 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -13,6 +13,13 @@ export const Embedded = () => { }); }, []); + const startEmbeddedSession = useCallback(() => { + console.log( + 'startEmbeddedSession --> check android/ios logs to check if it worked' + ); + Iterable.embeddedManager.startSession(); + }, []); + return ( EMBEDDED @@ -29,6 +36,9 @@ export const Embedded = () => { Get placement ids + + Start embedded session + ); }; diff --git a/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index 9dd2af2b7..e157a403f 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -119,6 +119,7 @@ export interface Spec extends TurboModule { pauseAuthRetries(pauseRetry: boolean): void; // Embedded Messaging + startEmbeddedSession(): void; getEmbeddedPlacementIds(): Promise; // Wake app -- android only diff --git a/src/core/classes/IterableApi.ts b/src/core/classes/IterableApi.ts index 9825f42ae..e005d32f1 100644 --- a/src/core/classes/IterableApi.ts +++ b/src/core/classes/IterableApi.ts @@ -510,6 +510,14 @@ export class IterableApi { // ======================= EMBEDDED ===================== // // ====================================================== // + /** + * Starts an embedded session. + */ + static startEmbeddedSession() { + IterableLogger.log('startEmbeddedSession'); + return RNIterableAPI.startEmbeddedSession(); + } + /** * Get the embedded placement IDs. */ diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index df52ffd02..0983303e1 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -42,7 +42,7 @@ export class IterableEmbeddedManager { * ``` */ startSession() { - IterableLogger.log('IterableEmbeddedManager.startSession'); + return IterableApi.startEmbeddedSession(); } /** From be8998a0630693474df2723b547641ea33ad446a Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 10:59:42 -0700 Subject: [PATCH 20/37] feat: add endEmbeddedSession method and update session management in RNIterableAPIModule --- .../iterable/reactnative/RNIterableAPIModuleImpl.java | 6 ++++++ android/src/newarch/java/com/RNIterableAPIModule.java | 5 +++++ android/src/oldarch/java/com/RNIterableAPIModule.java | 5 +++++ example/src/components/Embedded/Embedded.tsx | 10 ++++++++++ src/api/NativeRNIterableAPI.ts | 1 + src/core/classes/IterableApi.ts | 8 ++++++++ src/embedded/classes/IterableEmbeddedManager.ts | 2 +- 7 files changed, 36 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index f9bddf2c5..ece9d8737 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -652,9 +652,15 @@ public void onInboxUpdated() { // region Embedded messaging public void startEmbeddedSession() { + IterableLogger.d(TAG, "startEmbeddedSession"); IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().startSession(); } + public void endEmbeddedSession() { + IterableLogger.d(TAG, "endEmbeddedSession"); + IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().endSession(); + } + public void getEmbeddedPlacementIds(Promise promise) { IterableLogger.d(TAG, "getEmbeddedPlacementIds"); try { diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index 3f649f0c6..4c67af4e5 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -229,6 +229,11 @@ public void startEmbeddedSession() { moduleImpl.startEmbeddedSession(); } + @Override + public void endEmbeddedSession() { + moduleImpl.endEmbeddedSession(); + } + @Override public void getEmbeddedPlacementIds(Promise promise) { moduleImpl.getEmbeddedPlacementIds(promise); diff --git a/android/src/oldarch/java/com/RNIterableAPIModule.java b/android/src/oldarch/java/com/RNIterableAPIModule.java index 9da39c755..868f4051d 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -233,6 +233,11 @@ public void startEmbeddedSession() { moduleImpl.startEmbeddedSession(); } + @ReactMethod + public void endEmbeddedSession() { + moduleImpl.endEmbeddedSession(); + } + @ReactMethod public void getEmbeddedPlacementIds(Promise promise) { moduleImpl.getEmbeddedPlacementIds(promise); diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 71962f0a7..efedac721 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -20,6 +20,13 @@ export const Embedded = () => { Iterable.embeddedManager.startSession(); }, []); + const endEmbeddedSession = useCallback(() => { + console.log( + 'endEmbeddedSession --> check android/ios logs to check if it worked' + ); + Iterable.embeddedManager.endSession(); + }, []); + return ( EMBEDDED @@ -39,6 +46,9 @@ export const Embedded = () => { Start embedded session + + End embedded session + ); }; diff --git a/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index e157a403f..931be6f29 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -120,6 +120,7 @@ export interface Spec extends TurboModule { // Embedded Messaging startEmbeddedSession(): void; + endEmbeddedSession(): void; getEmbeddedPlacementIds(): Promise; // Wake app -- android only diff --git a/src/core/classes/IterableApi.ts b/src/core/classes/IterableApi.ts index e005d32f1..84c8f3a7a 100644 --- a/src/core/classes/IterableApi.ts +++ b/src/core/classes/IterableApi.ts @@ -518,6 +518,14 @@ export class IterableApi { return RNIterableAPI.startEmbeddedSession(); } + /** + * Ends an embedded session. + */ + static endEmbeddedSession() { + IterableLogger.log('endEmbeddedSession'); + return RNIterableAPI.endEmbeddedSession(); + } + /** * Get the embedded placement IDs. */ diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index 0983303e1..9a5f14487 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -58,7 +58,7 @@ export class IterableEmbeddedManager { * ``` */ endSession() { - IterableLogger.log('IterableEmbeddedManager.endSession'); + return IterableApi.endEmbeddedSession(); } /** From cbb547f84ac6aad853774f1ee5f5207e741bd300 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 11:04:24 -0700 Subject: [PATCH 21/37] docs: update IterableEmbeddedManager documentation to clarify enableEmbeddedMessaging flag --- .../classes/IterableEmbeddedManager.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index 9a5f14487..5515cb6ab 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -1,5 +1,4 @@ import { IterableApi } from '../../core/classes/IterableApi'; -import { IterableLogger } from '../../core/classes/IterableLogger'; /** * Manages embedded messages from Iterable. @@ -15,6 +14,9 @@ import { IterableLogger } from '../../core/classes/IterableLogger'; export class IterableEmbeddedManager { /** * Whether the embedded manager is enabled. + * + * This is set through the `enableEmbeddedMessaging` flag in the + * `IterableConfig` class. */ isEnabled = false; @@ -52,6 +54,9 @@ export class IterableEmbeddedManager { * displayed, the session should be ended. This causes the SDK to send * session and impression data back to the server. * + * A session is tracked when it is ended, so you should be able to find + * tracking data after this method is called. + * * @example * ```typescript * IterableEmbeddedManager.endSession(); @@ -60,16 +65,4 @@ export class IterableEmbeddedManager { endSession() { return IterableApi.endEmbeddedSession(); } - - /** - * Tracks an embedded session. - * - * @example - * ```typescript - * IterableEmbeddedManager.trackSession(); - * ``` - */ - trackSession() { - IterableLogger.log('IterableEmbeddedManager.trackSession'); - } } From 760a9e38950623cd0c9b6192c10bfa2e472cf0ce Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 11:18:08 -0700 Subject: [PATCH 22/37] feat: add syncEmbeddedMessages method --- .../reactnative/RNIterableAPIModuleImpl.java | 5 ++++ .../newarch/java/com/RNIterableAPIModule.java | 5 ++++ .../oldarch/java/com/RNIterableAPIModule.java | 5 ++++ example/src/components/Embedded/Embedded.tsx | 8 +++++++ src/api/NativeRNIterableAPI.ts | 1 + src/core/classes/IterableApi.ts | 8 +++++++ .../classes/IterableEmbeddedManager.ts | 23 +++++++++++++++++++ 7 files changed, 55 insertions(+) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index ece9d8737..0d332fddf 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -651,6 +651,11 @@ public void onInboxUpdated() { // --------------------------------------------------------------------------------------- // region Embedded messaging + public void syncEmbeddedMessages() { + IterableLogger.d(TAG, "syncEmbeddedMessages"); + IterableApi.getInstance().getEmbeddedManager().syncMessages(); + } + public void startEmbeddedSession() { IterableLogger.d(TAG, "startEmbeddedSession"); IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().startSession(); diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index 4c67af4e5..ceb653958 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -224,6 +224,11 @@ public void pauseAuthRetries(boolean pauseRetry) { moduleImpl.pauseAuthRetries(pauseRetry); } + @Override + public void syncEmbeddedMessages() { + moduleImpl.syncEmbeddedMessages(); + } + @Override public void startEmbeddedSession() { moduleImpl.startEmbeddedSession(); diff --git a/android/src/oldarch/java/com/RNIterableAPIModule.java b/android/src/oldarch/java/com/RNIterableAPIModule.java index 868f4051d..6ce874896 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -228,6 +228,11 @@ public void pauseAuthRetries(boolean pauseRetry) { moduleImpl.pauseAuthRetries(pauseRetry); } + @ReactMethod + public void syncEmbeddedMessages() { + moduleImpl.syncEmbeddedMessages(); + } + @ReactMethod public void startEmbeddedSession() { moduleImpl.startEmbeddedSession(); diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index efedac721..7f92c154c 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -6,6 +6,11 @@ import styles from './Embedded.styles'; export const Embedded = () => { const [placementIds, setPlacementIds] = useState([]); + + const syncEmbeddedMessages = useCallback(() => { + Iterable.embeddedManager.syncMessages(); + }, []); + const getPlacementIds = useCallback(() => { Iterable.embeddedManager.getPlacementIds().then((ids: unknown) => { console.log(ids); @@ -40,6 +45,9 @@ export const Embedded = () => { Placement ids: [{placementIds.join(', ')}] + + Sync embedded messages + Get placement ids diff --git a/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index 931be6f29..f6c1249e5 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -119,6 +119,7 @@ export interface Spec extends TurboModule { pauseAuthRetries(pauseRetry: boolean): void; // Embedded Messaging + syncEmbeddedMessages(): void; startEmbeddedSession(): void; endEmbeddedSession(): void; getEmbeddedPlacementIds(): Promise; diff --git a/src/core/classes/IterableApi.ts b/src/core/classes/IterableApi.ts index 84c8f3a7a..9f6c90ec7 100644 --- a/src/core/classes/IterableApi.ts +++ b/src/core/classes/IterableApi.ts @@ -510,6 +510,14 @@ export class IterableApi { // ======================= EMBEDDED ===================== // // ====================================================== // + /** + * Syncs embedded local cache with the server. + */ + static syncEmbeddedMessages() { + IterableLogger.log('syncEmbeddedMessages'); + return RNIterableAPI.syncEmbeddedMessages(); + } + /** * Starts an embedded session. */ diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index 5515cb6ab..5a31284ff 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -20,6 +20,29 @@ export class IterableEmbeddedManager { */ isEnabled = false; + /** + * Syncs embedded local cache with the server. + * + * When your app first launches, and each time it comes to the foreground, + * Iterable's iOS SDK automatically refresh a local, on-device cache of + * embedded messages for the signed-in user. These are the messages the + * signed-in user is eligible to see. + * + * At key points during your app's lifecycle, you may want to manually refresh + * your app's local cache of embedded messages. For example, as users navigate + * around, on pull-to-refresh, etc. + * + * However, do not poll for new embedded messages at a regular interval. + * + * @example + * ```typescript + * IterableEmbeddedManager.syncMessages(); + * ``` + */ + syncMessages() { + return IterableApi.syncEmbeddedMessages(); + } + /** * Retrieves a list of placement IDs for the embedded manager. * From 1986caa0442ee36e657e875dce788c81f7763edd Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 12:24:15 -0700 Subject: [PATCH 23/37] feat: implement getEmbeddedMessages method and add example on embedded --- .../reactnative/RNIterableAPIModuleImpl.java | 35 ++++++++ .../iterable/reactnative/Serialization.java | 11 +++ .../newarch/java/com/RNIterableAPIModule.java | 5 ++ .../oldarch/java/com/RNIterableAPIModule.java | 5 ++ example/src/components/Embedded/Embedded.tsx | 81 +++++++++++++------ src/api/NativeRNIterableAPI.ts | 30 +++++++ src/core/classes/IterableApi.ts | 13 +++ .../classes/IterableEmbeddedManager.ts | 13 +++ src/embedded/index.ts | 1 + src/embedded/types/IterableEmbeddedMessage.ts | 14 ++++ .../types/IterableEmbeddedMessageElements.ts | 25 ++++++ .../IterableEmbeddedMessageElementsButton.ts | 10 +++ .../IterableEmbeddedMessageElementsText.ts | 8 ++ .../types/IterableEmbeddedMessageMetadata.ts | 19 +++++ src/embedded/types/index.ts | 5 ++ src/index.tsx | 5 +- 16 files changed, 255 insertions(+), 25 deletions(-) create mode 100644 src/embedded/types/IterableEmbeddedMessage.ts create mode 100644 src/embedded/types/IterableEmbeddedMessageElements.ts create mode 100644 src/embedded/types/IterableEmbeddedMessageElementsButton.ts create mode 100644 src/embedded/types/IterableEmbeddedMessageElementsText.ts create mode 100644 src/embedded/types/IterableEmbeddedMessageMetadata.ts create mode 100644 src/embedded/types/index.ts diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 0d332fddf..61781ecd2 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -683,6 +683,41 @@ public void getEmbeddedPlacementIds(Promise promise) { } } + public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise promise) { + IterableLogger.d(TAG, "getEmbeddedMessages for placements: " + placementIds); + + try { + List allMessages = new ArrayList<>(); + + if (placementIds == null || placementIds.size() == 0) { + // If no placement IDs provided, we need to get messages for all possible placements + // Since the Android SDK requires a placement ID, we'll use 0 as a default + // This might need to be adjusted based on the actual SDK behavior + List messages = IterableApi.getInstance().getEmbeddedManager().getMessages(0L); + if (messages != null) { + allMessages.addAll(messages); + } + } else { + // Convert ReadableArray to individual placement IDs and get messages for each + for (int i = 0; i < placementIds.size(); i++) { + long placementId = placementIds.getInt(i); + List messages = IterableApi.getInstance().getEmbeddedManager().getMessages(placementId); + if (messages != null) { + allMessages.addAll(messages); + } + } + } + + JSONArray embeddedMessageJsonArray = Serialization.serializeEmbeddedMessages(allMessages); + IterableLogger.d(TAG, "Messages for placements: " + embeddedMessageJsonArray); + + promise.resolve(Serialization.convertJsonToArray(embeddedMessageJsonArray)); + } catch (JSONException e) { + IterableLogger.e(TAG, e.getLocalizedMessage()); + promise.reject("", "Failed to fetch messages with error " + e.getLocalizedMessage()); + } + } + // --------------------------------------------------------------------------------------- // endregion } diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index bace4c16c..2f0c4e1ca 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -138,6 +138,17 @@ static JSONArray serializeInAppMessages(List inAppMessages return inAppMessagesJson; } + static JSONArray serializeEmbeddedMessages(List embeddedMessages) { + JSONArray embeddedMessagesJson = new JSONArray(); + if (embeddedMessages != null) { + for (IterableEmbeddedMessage message : embeddedMessages) { + JSONObject messageJson = IterableEmbeddedMessage.Companion.toJSONObject(message); + embeddedMessagesJson.put(messageJson); + } + } + return embeddedMessagesJson; + } + static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableContextMap) { try { JSONObject iterableContextJSON = convertMapToJson(iterableContextMap); diff --git a/android/src/newarch/java/com/RNIterableAPIModule.java b/android/src/newarch/java/com/RNIterableAPIModule.java index ceb653958..ade1996f6 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -244,6 +244,11 @@ public void getEmbeddedPlacementIds(Promise promise) { moduleImpl.getEmbeddedPlacementIds(promise); } + @Override + public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise promise) { + moduleImpl.getEmbeddedMessages(placementIds, promise); + } + 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 6ce874896..468c2e4e4 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -248,6 +248,11 @@ public void getEmbeddedPlacementIds(Promise promise) { moduleImpl.getEmbeddedPlacementIds(promise); } + @ReactMethod + public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise promise) { + moduleImpl.getEmbeddedMessages(placementIds, promise); + } + public void sendEvent(@NonNull String eventName, @Nullable Object eventData) { moduleImpl.sendEvent(eventName, eventData); } diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 7f92c154c..a824a3f24 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -1,20 +1,27 @@ import { Text, TouchableOpacity, View } from 'react-native'; import { useCallback, useState } from 'react'; -import { Iterable } from '@iterable/react-native-sdk'; +import { + Iterable, + type IterableEmbeddedMessage, +} from '@iterable/react-native-sdk'; import styles from './Embedded.styles'; export const Embedded = () => { const [placementIds, setPlacementIds] = useState([]); + const [embeddedMessages, setEmbeddedMessages] = useState< + IterableEmbeddedMessage[] + >([]); const syncEmbeddedMessages = useCallback(() => { Iterable.embeddedManager.syncMessages(); }, []); const getPlacementIds = useCallback(() => { - Iterable.embeddedManager.getPlacementIds().then((ids: unknown) => { + return Iterable.embeddedManager.getPlacementIds().then((ids: unknown) => { console.log(ids); setPlacementIds(ids as number[]); + return ids; }); }, []); @@ -32,31 +39,57 @@ export const Embedded = () => { Iterable.embeddedManager.endSession(); }, []); + const getEmbeddedMessages = useCallback(() => { + getPlacementIds() + .then((ids: number[]) => Iterable.embeddedManager.getMessages(ids)) + .then((messages: IterableEmbeddedMessage[]) => { + setEmbeddedMessages(messages); + console.log(messages); + }); + }, [getPlacementIds]); + return ( EMBEDDED - - Does embedded class exist? {Iterable.embeddedManager ? 'Yes' : 'No'} - - - Is embedded manager enabled? - {Iterable.embeddedManager.isEnabled ? 'Yes' : 'No'} - - - Placement ids: [{placementIds.join(', ')}] - - - Sync embedded messages - - - Get placement ids - - - Start embedded session - - - End embedded session - + + + Does embedded class exist? {Iterable.embeddedManager ? 'Yes' : 'No'} + + + Is embedded manager enabled? + {Iterable.embeddedManager.isEnabled ? 'Yes' : 'No'} + + + Placement ids: [{placementIds.join(', ')}] + + + Sync embedded messages + + + Get placement ids + + + Start embedded session + + + End embedded session + + + Get embedded messages + + + + + {embeddedMessages.map((message) => ( + + Embedded message + metadata.messageId: {message.metadata.messageId} + metadata.placementId: {message.metadata.placementId} + elements.title: {message.elements?.title} + elements.body: {message.elements?.body} + + ))} + ); }; diff --git a/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index f6c1249e5..a9b32a400 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -1,6 +1,33 @@ import type { TurboModule } from 'react-native'; import { TurboModuleRegistry } from 'react-native'; +// NOTE: No types can be imported because of the way new arch works, so we have +// to re-define the types here. +interface EmbeddedMessage { + metadata: { + messageId: string; + placementId: number; + campaignId?: number | null; + isProof?: boolean; + }; + elements: { + buttons?: + | { + id: string; + title?: string | null; + action: { type: string; data?: string } | null; + }[] + | null; + body?: string | null; + mediaUrl?: string | null; + mediaUrlCaption?: string | null; + defaultAction?: { type: string; data?: string } | null; + text?: { id: string; text?: string | null; label?: string | null }[] | null; + title?: string | null; + } | null; + payload?: { [key: string]: string | number | boolean | null } | null; +} + export interface Spec extends TurboModule { // Initialization initializeWithApiKey( @@ -123,6 +150,9 @@ export interface Spec extends TurboModule { startEmbeddedSession(): void; endEmbeddedSession(): void; getEmbeddedPlacementIds(): Promise; + getEmbeddedMessages( + placementIds: number[] | null + ): Promise; // Wake app -- android only wakeApp(): void; diff --git a/src/core/classes/IterableApi.ts b/src/core/classes/IterableApi.ts index 9f6c90ec7..52f54db80 100644 --- a/src/core/classes/IterableApi.ts +++ b/src/core/classes/IterableApi.ts @@ -12,6 +12,7 @@ import { IterableAttributionInfo } from './IterableAttributionInfo'; import type { IterableCommerceItem } from './IterableCommerceItem'; import { IterableConfig } from './IterableConfig'; import { IterableLogger } from './IterableLogger'; +import type { IterableEmbeddedMessage } from '../../embedded/types/IterableEmbeddedMessage'; /** * Contains functions that directly interact with the native layer. @@ -542,6 +543,18 @@ export class IterableApi { return RNIterableAPI.getEmbeddedPlacementIds(); } + /** + * Get the embedded messages. + * + * @returns A Promise that resolves to an array of embedded messages. + */ + static getEmbeddedMessages( + placementIds: number[] | null + ): Promise { + IterableLogger.log('getEmbeddedMessages: ', placementIds); + return RNIterableAPI.getEmbeddedMessages(placementIds); + } + // ---- End EMBEDDED ---- // // ====================================================== // diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index 5a31284ff..1e2806e68 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -1,4 +1,5 @@ import { IterableApi } from '../../core/classes/IterableApi'; +import type { IterableEmbeddedMessage } from '../types/IterableEmbeddedMessage'; /** * Manages embedded messages from Iterable. @@ -52,6 +53,18 @@ export class IterableEmbeddedManager { return IterableApi.getEmbeddedPlacementIds(); } + /** + * Retrieves a list of embedded messages the user is eligible to see. + * + * @param placementIds - The placement IDs to retrieve messages for. + * @returns A Promise that resolves to an array of embedded messages. + */ + getMessages( + placementIds: number[] | null + ): Promise { + return IterableApi.getEmbeddedMessages(placementIds); + } + /** * Starts a session. * diff --git a/src/embedded/index.ts b/src/embedded/index.ts index d7d17c691..15eb796c9 100644 --- a/src/embedded/index.ts +++ b/src/embedded/index.ts @@ -1 +1,2 @@ export * from './classes'; +export * from './types'; diff --git a/src/embedded/types/IterableEmbeddedMessage.ts b/src/embedded/types/IterableEmbeddedMessage.ts new file mode 100644 index 000000000..2f0778305 --- /dev/null +++ b/src/embedded/types/IterableEmbeddedMessage.ts @@ -0,0 +1,14 @@ +import type { IterableEmbeddedMessageElements } from './IterableEmbeddedMessageElements'; +import type { IterableEmbeddedMessageMetadata } from './IterableEmbeddedMessageMetadata'; + +/** + * An embedded message. + */ +export interface IterableEmbeddedMessage { + /** Identifying information about the campaign. */ + metadata: IterableEmbeddedMessageMetadata; + /** What to display, and how to handle interaction. */ + elements?: IterableEmbeddedMessageElements | null; + /** Custom JSON data included with the campaign. */ + payload?: Record | null; +} diff --git a/src/embedded/types/IterableEmbeddedMessageElements.ts b/src/embedded/types/IterableEmbeddedMessageElements.ts new file mode 100644 index 000000000..7eb41056f --- /dev/null +++ b/src/embedded/types/IterableEmbeddedMessageElements.ts @@ -0,0 +1,25 @@ +import type { IterableAction } from '../../core/classes/IterableAction'; +import type { IterableEmbeddedMessageElementsButton } from './IterableEmbeddedMessageElementsButton'; +import type { IterableEmbeddedMessageElementsText } from './IterableEmbeddedMessageElementsText'; + +/** + * The elements of an embedded message. + * + * Includes what to display, and how to handle interaction. + */ +export interface IterableEmbeddedMessageElements { + /** The message's title text. */ + title?: string | null; + /** The message's body text. */ + body?: string | null; + /** The URL of an image associated with the message. */ + mediaUrl?: string | null; + /** Text description of the image. */ + mediaUrlCaption?: string | null; + /** What to do when a user clicks on the message (outside of its buttons). */ + defaultAction?: IterableAction | null; + /** Buttons to display. */ + buttons?: IterableEmbeddedMessageElementsButton[] | null; + /** Extra data fields. Not for display. */ + text?: IterableEmbeddedMessageElementsText[] | null; +} diff --git a/src/embedded/types/IterableEmbeddedMessageElementsButton.ts b/src/embedded/types/IterableEmbeddedMessageElementsButton.ts new file mode 100644 index 000000000..785d5932d --- /dev/null +++ b/src/embedded/types/IterableEmbeddedMessageElementsButton.ts @@ -0,0 +1,10 @@ +import type { IterableAction } from '../../core/classes/IterableAction'; + +export interface IterableEmbeddedMessageElementsButton { + /** The ID. */ + id: string; + /** The title. */ + title?: string | null; + /** The action. */ + action?: IterableAction | null; +} diff --git a/src/embedded/types/IterableEmbeddedMessageElementsText.ts b/src/embedded/types/IterableEmbeddedMessageElementsText.ts new file mode 100644 index 000000000..00b6a8449 --- /dev/null +++ b/src/embedded/types/IterableEmbeddedMessageElementsText.ts @@ -0,0 +1,8 @@ +export interface IterableEmbeddedMessageElementsText { + /** The ID. */ + id: string; + /** The text. */ + text?: string | null; + /** The label. */ + label?: string | null; +} diff --git a/src/embedded/types/IterableEmbeddedMessageMetadata.ts b/src/embedded/types/IterableEmbeddedMessageMetadata.ts new file mode 100644 index 000000000..a4b5ff953 --- /dev/null +++ b/src/embedded/types/IterableEmbeddedMessageMetadata.ts @@ -0,0 +1,19 @@ +/** + * Metadata for an embedded message. + * + * Consists of identifying information about the campaign. + */ +export interface IterableEmbeddedMessageMetadata { + /** The ID of the message. */ + messageId: string; + /** The ID of the placement associated with the message. */ + placementId: number; + /** The ID of the campaign associated with the message. */ + campaignId?: number | null; + /** + * Whether the message is a proof/test message. + * + * EG: Sent directly from a template or campaign edit page. + */ + isProof?: boolean; +} diff --git a/src/embedded/types/index.ts b/src/embedded/types/index.ts new file mode 100644 index 000000000..29b809ebf --- /dev/null +++ b/src/embedded/types/index.ts @@ -0,0 +1,5 @@ +export * from './IterableEmbeddedMessage'; +export * from './IterableEmbeddedMessageElements'; +export * from './IterableEmbeddedMessageElementsButton'; +export * from './IterableEmbeddedMessageElementsText'; +export * from './IterableEmbeddedMessageMetadata'; diff --git a/src/index.tsx b/src/index.tsx index c9a014340..75c8489ec 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -59,4 +59,7 @@ export { type IterableInboxProps, type IterableInboxRowViewModel, } from './inbox'; -export { IterableEmbeddedManager } from './embedded'; +export { + IterableEmbeddedManager, + type IterableEmbeddedMessage, +} from './embedded'; From 94e46a647c737151e1dc56ed5ffde73090270cb9 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 13 Oct 2025 12:30:35 -0700 Subject: [PATCH 24/37] feat: enhance Embedded component with ScrollView and button details display --- example/src/components/Embedded/Embedded.tsx | 36 +++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index a824a3f24..2be959133 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -1,4 +1,4 @@ -import { Text, TouchableOpacity, View } from 'react-native'; +import { ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { useCallback, useState } from 'react'; import { Iterable, @@ -79,17 +79,29 @@ export const Embedded = () => { - - {embeddedMessages.map((message) => ( - - Embedded message - metadata.messageId: {message.metadata.messageId} - metadata.placementId: {message.metadata.placementId} - elements.title: {message.elements?.title} - elements.body: {message.elements?.body} - - ))} - + + + {embeddedMessages.map((message) => ( + + Embedded message + metadata.messageId: {message.metadata.messageId} + metadata.placementId: {message.metadata.placementId} + elements.title: {message.elements?.title} + elements.body: {message.elements?.body} + {(message.elements?.buttons ?? []).map((button, buttonIndex) => ( + + Button {buttonIndex + 1} + button.id: {button.id} + button.title: {button.title} + button.action?.data: {button.action?.data} + button.action?.type: {button.action?.type} + + ))} + payload: {JSON.stringify(message.payload)} + + ))} + + ); }; From e13756e15412e8af05ac486756777148a85923a3 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 14 Oct 2025 16:04:58 -0700 Subject: [PATCH 25/37] feat: implement setEnabled method for managing embedded manager state --- example/src/components/Embedded/Embedded.tsx | 2 +- src/core/classes/Iterable.ts | 4 ++++ src/embedded/classes/IterableEmbeddedManager.ts | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index ce1348e48..0baae1169 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -11,7 +11,7 @@ export const Embedded = () => { Does embedded class exist? {Iterable.embeddedManager ? 'Yes' : 'No'} - Is embedded manager enabled? + Is embedded manager enabled?{' '} {Iterable.embeddedManager.isEnabled ? 'Yes' : 'No'} diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index ebb928cd6..c46c1830b 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -177,6 +177,10 @@ export class Iterable { IterableLogger.setLoggingEnabled(config.logReactNativeSdkCalls ?? true); IterableLogger.setLogLevel(config.logLevel); + + Iterable.embeddedManager.setEnabled( + config.embeddedMessagingEnabled === true + ); } this.setupEventHandlers(); diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index 333dd3af7..f792a62c5 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -14,4 +14,13 @@ export class IterableEmbeddedManager { * Whether the embedded manager is enabled. */ isEnabled = false; + + /** + * Sets whether the embedded manager is enabled. + * + * @param enabled - Whether the embedded manager is enabled. + */ + setEnabled(enabled: boolean) { + this.isEnabled = enabled; + } } From c115493a16a274ebb40081184ba7f37591190679 Mon Sep 17 00:00:00 2001 From: Loren <3190869+lposen@users.noreply.github.com> Date: Tue, 18 Nov 2025 21:13:19 -0800 Subject: [PATCH 26/37] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../com/iterable/reactnative/RNIterableAPIModuleImpl.java | 7 ++----- 1 file changed, 2 insertions(+), 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 1a54d2f16..1dedc7fbc 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -28,10 +28,7 @@ import com.iterable.iterableapi.IterableAuthHandler; import com.iterable.iterableapi.IterableConfig; import com.iterable.iterableapi.IterableCustomActionHandler; -// import com.iterable.iterableapi.IterableEmbeddedManager; import com.iterable.iterableapi.IterableEmbeddedMessage; -// import com.iterable.iterableapi.IterableEmbeddedSession; -// import com.iterable.iterableapi.IterableEmbeddedUpdateHandler; import com.iterable.iterableapi.IterableHelper; import com.iterable.iterableapi.IterableInAppCloseAction; import com.iterable.iterableapi.IterableInAppHandler; @@ -130,8 +127,8 @@ public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, } if (configReadableMap.hasKey("enableEmbeddedMessaging")) { - configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging")); - } + configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging")); + } // NOTE: There does not seem to be a way to set the API endpoint // override in the Android SDK. Check with @Ayyanchira and @evantk91 to From 25ec86def9edb4b9af27311dc42c1067e097a524 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 21:56:24 -0800 Subject: [PATCH 27/37] feat: add chat icon to route configuration in App.constants.ts --- example/src/components/App/App.constants.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/src/components/App/App.constants.ts b/example/src/components/App/App.constants.ts index 25743f261..84f8cf78f 100644 --- a/example/src/components/App/App.constants.ts +++ b/example/src/components/App/App.constants.ts @@ -12,9 +12,11 @@ export const personIcon = export const mailIcon = 'data:image/png;base64,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'; + const chatIcon = 'data:image/png;base64,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' + export const routeIcon = { [Route.Commerce]: cashIcon, - [Route.Embedded]: 'chatbubble-outline', + [Route.Embedded]: chatIcon, [Route.Inbox]: mailIcon, [Route.User]: personIcon, }; From 2b4da8aaaf66637018d4924d6268aa5c382b78fd Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 22:10:17 -0800 Subject: [PATCH 28/37] refactor: wrap components in SafeAreaView for improved layout consistency --- example/src/components/Commerce/Commerce.tsx | 57 ++++++++++---------- example/src/components/Embedded/Embedded.tsx | 7 +-- example/src/components/Login/Login.tsx | 7 +-- example/src/components/User/User.tsx | 7 +-- example/src/constants/styles/typography.ts | 1 - 5 files changed, 42 insertions(+), 37 deletions(-) diff --git a/example/src/components/Commerce/Commerce.tsx b/example/src/components/Commerce/Commerce.tsx index c840f9199..134a37706 100644 --- a/example/src/components/Commerce/Commerce.tsx +++ b/example/src/components/Commerce/Commerce.tsx @@ -1,4 +1,5 @@ import { Alert, Image, Pressable, ScrollView, Text, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; import { Iterable, IterableCommerceItem } from '@iterable/react-native-sdk'; @@ -32,35 +33,37 @@ export const Commerce = () => { }; return ( - - - Commerce - - Purchase will be tracked when "Buy" is clicked. See logs for - output. - - {items.map((item) => ( - - - - - - - {item.name} - {item.subtitle} - ${item.price} - handleClick(item)} - > - Buy - + + + + Commerce + + Purchase will be tracked when "Buy" is clicked. See logs for + output. + + {items.map((item) => ( + + + + + + + {item.name} + {item.subtitle} + ${item.price} + handleClick(item)} + > + Buy + + - - ))} - - + ))} + + + ); }; diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 0baae1169..3306e2cc4 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -1,11 +1,12 @@ -import { Text, View } from 'react-native'; import { Iterable } from '@iterable/react-native-sdk'; +import { Text } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; import styles from './Embedded.styles'; export const Embedded = () => { return ( - + EMBEDDED Does embedded class exist? {Iterable.embeddedManager ? 'Yes' : 'No'} @@ -14,7 +15,7 @@ export const Embedded = () => { Is embedded manager enabled?{' '} {Iterable.embeddedManager.isEnabled ? 'Yes' : 'No'} - + ); }; diff --git a/example/src/components/Login/Login.tsx b/example/src/components/Login/Login.tsx index 9712792ed..988e9c8db 100644 --- a/example/src/components/Login/Login.tsx +++ b/example/src/components/Login/Login.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import { ActivityIndicator, Pressable, @@ -5,7 +6,7 @@ import { TextInput, View, } from 'react-native'; -import { useMemo } from 'react'; +import { SafeAreaView } from 'react-native-safe-area-context'; import { colors, type Route } from '../../constants'; import { useIterableApp } from '../../hooks'; @@ -18,7 +19,7 @@ export const Login = ({ navigation }: RootStackScreenProps) => { const loginIsEnabled = useMemo(() => apiKey && userId, [apiKey, userId]); return ( - + {loginInProgress ? ( @@ -66,7 +67,7 @@ export const Login = ({ navigation }: RootStackScreenProps) => { )} - + ); }; diff --git a/example/src/components/User/User.tsx b/example/src/components/User/User.tsx index 23f8361a5..4f3ee5be1 100644 --- a/example/src/components/User/User.tsx +++ b/example/src/components/User/User.tsx @@ -1,6 +1,7 @@ import { Iterable } from '@iterable/react-native-sdk'; import { useEffect, useState } from 'react'; -import { Text, TouchableOpacity, View } from 'react-native'; +import { Text, TouchableOpacity } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; import { useIterableApp } from '../../hooks'; import styles from './User.styles'; @@ -18,13 +19,13 @@ export const User = () => { }, [isLoggedIn]); return ( - + Welcome Iterator Logged in as {loggedInAs} Logout - + ); }; diff --git a/example/src/constants/styles/typography.ts b/example/src/constants/styles/typography.ts index 09b6c2405..af8a3b8e4 100644 --- a/example/src/constants/styles/typography.ts +++ b/example/src/constants/styles/typography.ts @@ -6,7 +6,6 @@ export const appName: TextStyle = { fontWeight: 'bold', fontSize: 14, width: '100%', - marginTop: 41, marginBottom: 64, textTransform: 'uppercase', letterSpacing: 2, From 11f822736742c27c26190c98a11e109af3d8bc74 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 22:23:27 -0800 Subject: [PATCH 29/37] feat: refactor IterableEmbeddedManager to use private variable --- example/src/hooks/useIterableApp.tsx | 2 ++ src/embedded/classes/IterableEmbeddedManager.ts | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index 9f72f0cf8..f47f58ba1 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -192,6 +192,8 @@ export const IterableAppProvider: FunctionComponent< config.logLevel = IterableLogLevel.debug; + config.embeddedMessagingEnabled = true; + config.inAppHandler = () => IterableInAppShowResponse.show; if ( diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index f792a62c5..13a59ddda 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -13,7 +13,14 @@ export class IterableEmbeddedManager { /** * Whether the embedded manager is enabled. */ - isEnabled = false; + private _isEnabled = false; + + /** + * Gets whether the embedded manager is enabled. + */ + get isEnabled(): boolean { + return this._isEnabled; + } /** * Sets whether the embedded manager is enabled. @@ -21,6 +28,6 @@ export class IterableEmbeddedManager { * @param enabled - Whether the embedded manager is enabled. */ setEnabled(enabled: boolean) { - this.isEnabled = enabled; + this._isEnabled = enabled; } } From 935cf22ec866cc43217e29406ead2d41f17da236 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 22:28:29 -0800 Subject: [PATCH 30/37] test: enhance Iterable configuration tests with additional assertions --- src/core/classes/Iterable.test.ts | 38 ++++++++++++++++--------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/core/classes/Iterable.test.ts b/src/core/classes/Iterable.test.ts index bfe4c26f6..c4ec2c828 100644 --- a/src/core/classes/Iterable.test.ts +++ b/src/core/classes/Iterable.test.ts @@ -300,37 +300,39 @@ describe('Iterable', () => { // WHEN config is initialized const config = new IterableConfig(); // THEN config has default values - expect(config.pushIntegrationName).toBe(undefined); + expect(config.allowedProtocols).toEqual([]); + expect(config.androidSdkUseInMemoryStorageForInApps).toBe(false); + expect(config.authHandler).toBe(undefined); expect(config.autoPushRegistration).toBe(true); expect(config.checkForDeferredDeeplink).toBe(false); - expect(config.inAppDisplayInterval).toBe(30.0); - expect(config.urlHandler).toBe(undefined); expect(config.customActionHandler).toBe(undefined); + expect(config.dataRegion).toBe(IterableDataRegion.US); + expect(config.embeddedMessagingEnabled).toBe(false); + expect(config.encryptionEnforced).toBe(false); + expect(config.expiringAuthTokenRefreshPeriod).toBe(60.0); + expect(config.inAppDisplayInterval).toBe(30.0); expect(config.inAppHandler).toBe(undefined); - expect(config.authHandler).toBe(undefined); expect(config.logLevel).toBe(IterableLogLevel.debug); expect(config.logReactNativeSdkCalls).toBe(true); - expect(config.expiringAuthTokenRefreshPeriod).toBe(60.0); - expect(config.allowedProtocols).toEqual([]); - expect(config.androidSdkUseInMemoryStorageForInApps).toBe(false); + expect(config.pushIntegrationName).toBe(undefined); + expect(config.urlHandler).toBe(undefined); expect(config.useInMemoryStorageForInApps).toBe(false); - expect(config.dataRegion).toBe(IterableDataRegion.US); - expect(config.encryptionEnforced).toBe(false); const configDict = config.toDict(); - expect(configDict.pushIntegrationName).toBe(undefined); + expect(configDict.allowedProtocols).toEqual([]); + expect(configDict.androidSdkUseInMemoryStorageForInApps).toBe(false); + expect(configDict.authHandlerPresent).toBe(false); expect(configDict.autoPushRegistration).toBe(true); - expect(configDict.inAppDisplayInterval).toBe(30.0); - expect(configDict.urlHandlerPresent).toBe(false); expect(configDict.customActionHandlerPresent).toBe(false); + expect(configDict.dataRegion).toBe(IterableDataRegion.US); + expect(configDict.embeddedMessagingEnabled).toBe(false); + expect(configDict.encryptionEnforced).toBe(false); + expect(configDict.expiringAuthTokenRefreshPeriod).toBe(60.0); + expect(configDict.inAppDisplayInterval).toBe(30.0); expect(configDict.inAppHandlerPresent).toBe(false); - expect(configDict.authHandlerPresent).toBe(false); expect(configDict.logLevel).toBe(IterableLogLevel.debug); - expect(configDict.expiringAuthTokenRefreshPeriod).toBe(60.0); - expect(configDict.allowedProtocols).toEqual([]); - expect(configDict.androidSdkUseInMemoryStorageForInApps).toBe(false); + expect(configDict.pushIntegrationName).toBe(undefined); + expect(configDict.urlHandlerPresent).toBe(false); expect(configDict.useInMemoryStorageForInApps).toBe(false); - expect(configDict.dataRegion).toBe(IterableDataRegion.US); - expect(configDict.encryptionEnforced).toBe(false); }); }); From 047f94bb815f22cdc23aac8ca5ee3bdf779c22d0 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 22:28:52 -0800 Subject: [PATCH 31/37] fix: update embeddedMessagingEnabled configuration to use nullish coalescing --- src/core/classes/Iterable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 9985ae41a..fa11f9a16 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -196,7 +196,7 @@ export class Iterable { IterableLogger.setLogLevel(config.logLevel); Iterable.embeddedManager.setEnabled( - config.embeddedMessagingEnabled === true + config.embeddedMessagingEnabled ?? false ); } From 92c8c9ebb4e682d0a0924cbf168616a9945c59f0 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 22:33:45 -0800 Subject: [PATCH 32/37] test: add unit tests for IterableEmbeddedManager and enhance Iterable configuration tests --- src/core/classes/Iterable.test.ts | 15 +++++ .../classes/IterableEmbeddedManager.test.ts | 59 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/embedded/classes/IterableEmbeddedManager.test.ts diff --git a/src/core/classes/Iterable.test.ts b/src/core/classes/Iterable.test.ts index c4ec2c828..1da4e4e00 100644 --- a/src/core/classes/Iterable.test.ts +++ b/src/core/classes/Iterable.test.ts @@ -1214,4 +1214,19 @@ describe('Iterable', () => { }); }); }); + + describe('embeddedManager', () => { + it('should be disabled by default', () => { + const config = new IterableConfig(); + expect(config.embeddedMessagingEnabled).toBe(false); + expect(Iterable.embeddedManager.isEnabled).toBe(false); + }); + + it('should enable embeddedManager when config is set', async () => { + const config = new IterableConfig(); + config.embeddedMessagingEnabled = true; + await Iterable.initialize('test-key', config); + expect(Iterable.embeddedManager.isEnabled).toBe(true); + }); + }); }); diff --git a/src/embedded/classes/IterableEmbeddedManager.test.ts b/src/embedded/classes/IterableEmbeddedManager.test.ts new file mode 100644 index 000000000..96a09ddf4 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedManager.test.ts @@ -0,0 +1,59 @@ +import { IterableEmbeddedManager } from './IterableEmbeddedManager'; + +describe('IterableEmbeddedManager', () => { + let embeddedManager: IterableEmbeddedManager; + + beforeEach(() => { + embeddedManager = new IterableEmbeddedManager(); + }); + + describe('isEnabled', () => { + it('should be false by default', () => { + expect(embeddedManager.isEnabled).toBe(false); + }); + + it('should return true after being enabled', () => { + embeddedManager.setEnabled(true); + expect(embeddedManager.isEnabled).toBe(true); + }); + + it('should return false after being disabled', () => { + embeddedManager.setEnabled(false); + expect(embeddedManager.isEnabled).toBe(false); + }); + }); + + describe('setEnabled', () => { + it('should enable the embedded manager', () => { + embeddedManager.setEnabled(true); + expect(embeddedManager.isEnabled).toBe(true); + }); + + it('should disable the embedded manager', () => { + embeddedManager.setEnabled(false); + expect(embeddedManager.isEnabled).toBe(false); + }); + + it('should toggle enabled state multiple times', () => { + embeddedManager.setEnabled(true); + expect(embeddedManager.isEnabled).toBe(true); + + embeddedManager.setEnabled(false); + expect(embeddedManager.isEnabled).toBe(false); + + embeddedManager.setEnabled(true); + expect(embeddedManager.isEnabled).toBe(true); + }); + + it('should handle setting the same state multiple times', () => { + embeddedManager.setEnabled(true); + embeddedManager.setEnabled(true); + expect(embeddedManager.isEnabled).toBe(true); + + embeddedManager.setEnabled(false); + embeddedManager.setEnabled(false); + expect(embeddedManager.isEnabled).toBe(false); + }); + }); +}); + From efbda573175bd0ca3031d377a2c3e0e5f092dbae Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 23:00:24 -0800 Subject: [PATCH 33/37] feat: update login function to support JWT token retrieval and enhance user identification --- example/src/hooks/useIterableApp.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx index 53de3d126..2a392dd0d 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -123,7 +123,7 @@ export const IterableAppProvider: FunctionComponent< return jwtToken; }, [userId]); - const login = useCallback(() => { + const login = useCallback(async () => { const id = userId ?? process.env.ITBL_ID; if (!id) return Promise.reject('No User ID or Email set'); @@ -132,12 +132,18 @@ export const IterableAppProvider: FunctionComponent< const fn = getIsEmail(id) ? Iterable.setEmail : Iterable.setUserId; - fn(id); + let token; + + if (process.env.ITBL_IS_JWT_ENABLED === 'true' && process.env.ITBL_JWT_SECRET) { + token = await getJwtToken(); + } + + fn(id, token); setIsLoggedIn(true); setLoginInProgress(false); return Promise.resolve(true); - }, [userId]); + }, [getJwtToken, userId]); const initialize = useCallback( (navigation: Navigation) => { From 67e116b94e19790c06675442c41a0484b93dc486 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 23:04:06 -0800 Subject: [PATCH 34/37] fix: update example usage of startSession and endSession methods in IterableEmbeddedManager --- src/embedded/classes/IterableEmbeddedManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index 91cfc9ef5..d36d11885 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -56,7 +56,7 @@ export class IterableEmbeddedManager { * * @example * ```typescript - * IterableEmbeddedManager.startSession(); + * Iterable.embeddedManager.startSession(); * ``` */ startSession() { @@ -75,7 +75,7 @@ export class IterableEmbeddedManager { * * @example * ```typescript - * IterableEmbeddedManager.endSession(); + * Iterable.embeddedManager.endSession(); * ``` */ endSession() { From bab2b356b561541218505e6601b8a27248f6d472 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 23:06:33 -0800 Subject: [PATCH 35/37] test: add unit tests for new embedded session methods in IterableEmbeddedManager --- src/__mocks__/MockRNIterableAPI.ts | 8 ++++ .../classes/IterableEmbeddedManager.test.ts | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/__mocks__/MockRNIterableAPI.ts b/src/__mocks__/MockRNIterableAPI.ts index c7f325677..fc7ed502f 100644 --- a/src/__mocks__/MockRNIterableAPI.ts +++ b/src/__mocks__/MockRNIterableAPI.ts @@ -129,6 +129,14 @@ export class MockRNIterableAPI { static getHtmlInAppContentForMessage = jest.fn(); + static startEmbeddedSession = jest.fn(); + + static endEmbeddedSession = jest.fn(); + + static getEmbeddedPlacementIds = jest + .fn() + .mockResolvedValue([1, 2, 3] as number[]); + // set messages function is to set the messages static property // this is for testing purposes only static setMessages(messages: IterableInAppMessage[]): void { diff --git a/src/embedded/classes/IterableEmbeddedManager.test.ts b/src/embedded/classes/IterableEmbeddedManager.test.ts index 96a09ddf4..c05e38533 100644 --- a/src/embedded/classes/IterableEmbeddedManager.test.ts +++ b/src/embedded/classes/IterableEmbeddedManager.test.ts @@ -1,10 +1,18 @@ +import { MockRNIterableAPI } from '../../__mocks__/MockRNIterableAPI'; import { IterableEmbeddedManager } from './IterableEmbeddedManager'; +// Mock the RNIterableAPI module +jest.mock('../../api', () => ({ + __esModule: true, + default: MockRNIterableAPI, +})); + describe('IterableEmbeddedManager', () => { let embeddedManager: IterableEmbeddedManager; beforeEach(() => { embeddedManager = new IterableEmbeddedManager(); + jest.clearAllMocks(); }); describe('isEnabled', () => { @@ -55,5 +63,40 @@ describe('IterableEmbeddedManager', () => { expect(embeddedManager.isEnabled).toBe(false); }); }); + + describe('getPlacementIds', () => { + it('should call IterableApi.getEmbeddedPlacementIds', async () => { + // WHEN getPlacementIds is called + const result = await embeddedManager.getPlacementIds(); + + // THEN IterableApi.getEmbeddedPlacementIds is called + expect(MockRNIterableAPI.getEmbeddedPlacementIds).toHaveBeenCalledTimes( + 1 + ); + + // AND the result is returned + expect(result).toEqual([1, 2, 3]); + }); + }); + + describe('startSession', () => { + it('should call IterableApi.startEmbeddedSession', () => { + // WHEN startSession is called + embeddedManager.startSession(); + + // THEN IterableApi.startEmbeddedSession is called + expect(MockRNIterableAPI.startEmbeddedSession).toHaveBeenCalledTimes(1); + }); + }); + + describe('endSession', () => { + it('should call IterableApi.endEmbeddedSession', () => { + // WHEN endSession is called + embeddedManager.endSession(); + + // THEN IterableApi.endEmbeddedSession is called + expect(MockRNIterableAPI.endEmbeddedSession).toHaveBeenCalledTimes(1); + }); + }); }); From 0984e9e9f7803b9d048ae48768edbe4f6f10cdc4 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Tue, 18 Nov 2025 23:43:36 -0800 Subject: [PATCH 36/37] test: add unit tests for syncEmbeddedMessages and getEmbeddedMessages in IterableEmbeddedManager --- src/__mocks__/MockRNIterableAPI.ts | 29 ++++++++ .../classes/IterableEmbeddedManager.test.ts | 69 +++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/src/__mocks__/MockRNIterableAPI.ts b/src/__mocks__/MockRNIterableAPI.ts index fc7ed502f..136744ad7 100644 --- a/src/__mocks__/MockRNIterableAPI.ts +++ b/src/__mocks__/MockRNIterableAPI.ts @@ -137,6 +137,35 @@ export class MockRNIterableAPI { .fn() .mockResolvedValue([1, 2, 3] as number[]); + static syncEmbeddedMessages = jest.fn().mockResolvedValue(undefined); + + static getEmbeddedMessages = jest.fn().mockResolvedValue([ + { + metadata: { + messageId: 'msg-1', + campaignId: 123, + placementId: 1, + }, + elements: { + title: 'Test Message 1', + body: 'Test body 1', + }, + payload: { customKey: 'customValue' }, + }, + { + metadata: { + messageId: 'msg-2', + campaignId: 456, + placementId: 2, + }, + elements: { + title: 'Test Message 2', + body: 'Test body 2', + }, + payload: null, + }, + ]); + // set messages function is to set the messages static property // this is for testing purposes only static setMessages(messages: IterableInAppMessage[]): void { diff --git a/src/embedded/classes/IterableEmbeddedManager.test.ts b/src/embedded/classes/IterableEmbeddedManager.test.ts index c05e38533..a3d27804c 100644 --- a/src/embedded/classes/IterableEmbeddedManager.test.ts +++ b/src/embedded/classes/IterableEmbeddedManager.test.ts @@ -64,6 +64,19 @@ describe('IterableEmbeddedManager', () => { }); }); + describe('syncMessages', () => { + it('should call IterableApi.syncEmbeddedMessages', async () => { + // WHEN syncMessages is called + const result = await embeddedManager.syncMessages(); + + // THEN IterableApi.syncEmbeddedMessages is called + expect(MockRNIterableAPI.syncEmbeddedMessages).toHaveBeenCalledTimes(1); + + // AND the result is returned + expect(result).toBeUndefined(); + }); + }); + describe('getPlacementIds', () => { it('should call IterableApi.getEmbeddedPlacementIds', async () => { // WHEN getPlacementIds is called @@ -79,6 +92,61 @@ describe('IterableEmbeddedManager', () => { }); }); + describe('getMessages', () => { + it('should call IterableApi.getEmbeddedMessages with placement IDs', async () => { + // GIVEN placement IDs + const placementIds = [1, 2]; + + // WHEN getMessages is called + const result = await embeddedManager.getMessages(placementIds); + + // THEN IterableApi.getEmbeddedMessages is called with placement IDs + expect(MockRNIterableAPI.getEmbeddedMessages).toHaveBeenCalledTimes(1); + expect(MockRNIterableAPI.getEmbeddedMessages).toHaveBeenCalledWith( + placementIds + ); + + // AND the result contains embedded messages + expect(result).toHaveLength(2); + expect(result[0]).toEqual({ + metadata: { + messageId: 'msg-1', + campaignId: 123, + placementId: 1, + }, + elements: { + title: 'Test Message 1', + body: 'Test body 1', + }, + payload: { customKey: 'customValue' }, + }); + expect(result[1]).toEqual({ + metadata: { + messageId: 'msg-2', + campaignId: 456, + placementId: 2, + }, + elements: { + title: 'Test Message 2', + body: 'Test body 2', + }, + payload: null, + }); + }); + + it('should call IterableApi.getEmbeddedMessages with null placement IDs', async () => { + // WHEN getMessages is called with null + const result = await embeddedManager.getMessages(null); + + // THEN IterableApi.getEmbeddedMessages is called with null + expect(MockRNIterableAPI.getEmbeddedMessages).toHaveBeenCalledTimes(1); + expect(MockRNIterableAPI.getEmbeddedMessages).toHaveBeenCalledWith(null); + + // AND the result is returned + expect(result).toBeDefined(); + }); + }); + describe('startSession', () => { it('should call IterableApi.startEmbeddedSession', () => { // WHEN startSession is called @@ -98,5 +166,6 @@ describe('IterableEmbeddedManager', () => { expect(MockRNIterableAPI.endEmbeddedSession).toHaveBeenCalledTimes(1); }); }); + }); From 62e248f95136dd6bf762a8e3d42485c4d7dce1b5 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Wed, 19 Nov 2025 01:32:56 -0800 Subject: [PATCH 37/37] refactor: remove embedded messaging configuration from RNIterableAPIModuleImpl --- .../com/iterable/reactnative/RNIterableAPIModuleImpl.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 1dedc7fbc..6793ddcf0 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -91,10 +91,6 @@ public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, S configBuilder.setAuthHandler(this); } - if (configReadableMap.hasKey("enableEmbeddedMessaging")) { - configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging")); - } - IterableApi.initialize(reactContext, apiKey, configBuilder.build()); IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version); @@ -126,10 +122,6 @@ public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap, configBuilder.setAuthHandler(this); } - if (configReadableMap.hasKey("enableEmbeddedMessaging")) { - configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging")); - } - // NOTE: There does not seem to be a way to set the API endpoint // override in the Android SDK. Check with @Ayyanchira and @evantk91 to // see what the best approach is.