Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
15e1894
feat: add IterableEmbeddedManager class and update exports for embedd…
lposen Oct 10, 2025
952eebe
Merge branch 'loren/embedded/MOB-12260-create-embedded-tab-in-example…
lposen Oct 11, 2025
2a62913
feat: add embeddedManager property to Iterable class for managing emb…
lposen Oct 11, 2025
789ae83
refactor: remove unused methods and improve documentation in Iterable…
lposen Oct 11, 2025
9940f46
docs: enhance documentation
lposen Oct 11, 2025
e7d4ecb
feat: add embeddedMessagingEnabled property to IterableConfig
lposen Oct 11, 2025
4441759
Merge branch 'loren/embedded/MOB-12260-create-embedded-tab-in-example…
lposen Oct 11, 2025
8201c4d
feat: display embedded manager status in Embedded component
lposen Oct 11, 2025
e13756e
feat: implement setEnabled method for managing embedded manager state
lposen Oct 14, 2025
79926df
Merge branch 'loren/embedded/MOB-12260-create-embedded-tab-in-example…
lposen Nov 19, 2025
5206902
Merge branch 'loren/embedded/MOB-12260-create-embedded-tab-in-example…
lposen Nov 19, 2025
2b4da8a
refactor: wrap components in SafeAreaView for improved layout consist…
lposen Nov 19, 2025
11f8227
feat: refactor IterableEmbeddedManager to use private variable
lposen Nov 19, 2025
935cf22
test: enhance Iterable configuration tests with additional assertions
lposen Nov 19, 2025
047f94b
fix: update embeddedMessagingEnabled configuration to use nullish coa…
lposen Nov 19, 2025
92c8c9e
test: add unit tests for IterableEmbeddedManager and enhance Iterable…
lposen Nov 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 30 additions & 27 deletions example/src/components/Commerce/Commerce.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -32,35 +33,37 @@ export const Commerce = () => {
};

return (
<ScrollView>
<View style={styles.container}>
<Text style={styles.title}>Commerce</Text>
<Text style={styles.subtitle}>
Purchase will be tracked when &quot;Buy&quot; is clicked. See logs for
output.
</Text>
{items.map((item) => (
<View key={item.id} style={styles.cardContainer}>
<View style={styles.infoContainer}>
<View style={styles.imageContainer}>
<Image source={item.icon} style={styles.cardImage} />
</View>
<View style={styles.textContainer}>
<Text style={styles.cardTitle}>{item.name}</Text>
<Text style={styles.cardSubtitle}>{item.subtitle}</Text>
<Text style={styles.price}>${item.price}</Text>
<Pressable
style={styles.button}
onPress={() => handleClick(item)}
>
<Text style={styles.buttonText}>Buy</Text>
</Pressable>
<SafeAreaView>
<ScrollView>
<View style={styles.container}>
<Text style={styles.title}>Commerce</Text>
<Text style={styles.subtitle}>
Purchase will be tracked when &quot;Buy&quot; is clicked. See logs for
output.
</Text>
{items.map((item) => (
<View key={item.id} style={styles.cardContainer}>
<View style={styles.infoContainer}>
<View style={styles.imageContainer}>
<Image source={item.icon} style={styles.cardImage} />
</View>
<View style={styles.textContainer}>
<Text style={styles.cardTitle}>{item.name}</Text>
<Text style={styles.cardSubtitle}>{item.subtitle}</Text>
<Text style={styles.price}>${item.price}</Text>
<Pressable
style={styles.button}
onPress={() => handleClick(item)}
>
<Text style={styles.buttonText}>Buy</Text>
</Pressable>
</View>
</View>
</View>
</View>
))}
</View>
</ScrollView>
))}
</View>
</ScrollView>
</SafeAreaView>
);
};

Expand Down
15 changes: 12 additions & 3 deletions example/src/components/Embedded/Embedded.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
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 (
<View style={styles.container}>
<SafeAreaView style={styles.container}>
<Text style={styles.text}>EMBEDDED</Text>
</View>
<Text style={styles.text}>
Does embedded class exist? {Iterable.embeddedManager ? 'Yes' : 'No'}
</Text>
<Text style={styles.text}>
Is embedded manager enabled?{' '}
{Iterable.embeddedManager.isEnabled ? 'Yes' : 'No'}
</Text>
</SafeAreaView>
);
};

Expand Down
7 changes: 4 additions & 3 deletions example/src/components/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useMemo } from 'react';
import {
ActivityIndicator,
Pressable,
Text,
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';
Expand All @@ -18,7 +19,7 @@ export const Login = ({ navigation }: RootStackScreenProps<Route.Login>) => {
const loginIsEnabled = useMemo(() => apiKey && userId, [apiKey, userId]);

return (
<View style={styles.loginScreenContainer}>
<SafeAreaView style={styles.loginScreenContainer}>
{loginInProgress ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={colors.brandPurple} />
Expand Down Expand Up @@ -66,7 +67,7 @@ export const Login = ({ navigation }: RootStackScreenProps<Route.Login>) => {
</View>
</>
)}
</View>
</SafeAreaView>
);
};

Expand Down
7 changes: 4 additions & 3 deletions example/src/components/User/User.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -18,13 +19,13 @@ export const User = () => {
}, [isLoggedIn]);

return (
<View style={styles.container}>
<SafeAreaView style={styles.container}>
<Text style={styles.appName}>Welcome Iterator</Text>
<Text style={styles.text}>Logged in as {loggedInAs}</Text>
<TouchableOpacity style={styles.button} onPress={logout}>
<Text style={styles.buttonText}>Logout</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};

Expand Down
1 change: 0 additions & 1 deletion example/src/constants/styles/typography.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export const appName: TextStyle = {
fontWeight: 'bold',
fontSize: 14,
width: '100%',
marginTop: 41,
marginBottom: 64,
textTransform: 'uppercase',
letterSpacing: 2,
Expand Down
2 changes: 2 additions & 0 deletions example/src/hooks/useIterableApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ export const IterableAppProvider: FunctionComponent<

config.logLevel = IterableLogLevel.debug;

config.embeddedMessagingEnabled = true;

config.inAppHandler = () => IterableInAppShowResponse.show;

if (
Expand Down
53 changes: 35 additions & 18 deletions src/core/classes/Iterable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down Expand Up @@ -1212,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);
});
});
});
26 changes: 26 additions & 0 deletions src/core/classes/Iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -96,6 +97,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);
* });
Comment on lines +113 to +115
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The documentation example shows calling getMessages() method, but the IterableEmbeddedManager class does not implement this method. The current implementation only has isEnabled property and setEnabled() method.

Either update the example to reflect the current API:

// Check if embedded manager is enabled
if (Iterable.embeddedManager.isEnabled) {
  console.log('Embedded messaging is enabled');
}

Or remove the example until getMessages() is implemented.

Suggested change
* Iterable.embeddedManager.getMessages().then(messages => {
* console.log('Messages:', messages);
* });
* // Check if embedded manager is enabled
* if (Iterable.embeddedManager.isEnabled) {
* console.log('Embedded messaging is enabled');
* }

Copilot uses AI. Check for mistakes.
* ```
*/
static embeddedManager: IterableEmbeddedManager =
new IterableEmbeddedManager();

/**
* Initializes the Iterable React Native SDK in your app's Javascript or Typescript code.
*
Expand Down Expand Up @@ -172,6 +194,10 @@ export class Iterable {

IterableLogger.setLoggingEnabled(config.logReactNativeSdkCalls ?? true);
IterableLogger.setLogLevel(config.logLevel);

Iterable.embeddedManager.setEnabled(
config.embeddedMessagingEnabled ?? false
);
}

this.setupEventHandlers();
Expand Down
11 changes: 11 additions & 0 deletions src/core/classes/IterableConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,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;
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The test that validates default configuration values in Iterable.test.ts does not include verification for the new embeddedMessagingEnabled property. The test should be updated to include:

expect(config.embeddedMessagingEnabled).toBe(false);
expect(configDict.embeddedMessagingEnabled).toBe(false);

This ensures the new configuration option has proper test coverage for its default value and serialization.

Copilot uses AI. Check for mistakes.

/**
* Converts the IterableConfig instance to a dictionary object.
*
Expand Down Expand Up @@ -378,6 +388,7 @@ export class IterableConfig {
pushPlatform: this.pushPlatform,
encryptionEnforced: this.encryptionEnforced,
retryPolicy: this.retryPolicy,
embeddedMessagingEnabled: this.embeddedMessagingEnabled,
};
}
}
59 changes: 59 additions & 0 deletions src/embedded/classes/IterableEmbeddedManager.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});

Loading