Skip to content

Commit 2c7a839

Browse files
authored
Merge pull request #742 from Iterable/loren/embedded/MOB-12261-add-embedded-manager-class
[MOB-12261] Add IterableEmbeddedManager class
2 parents f5746ac + b3ce46c commit 2c7a839

File tree

14 files changed

+219
-55
lines changed

14 files changed

+219
-55
lines changed

example/src/components/Commerce/Commerce.tsx

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Alert, Image, Pressable, ScrollView, Text, View } from 'react-native';
2+
import { SafeAreaView } from 'react-native-safe-area-context';
23

34
import { Iterable, IterableCommerceItem } from '@iterable/react-native-sdk';
45

@@ -32,35 +33,37 @@ export const Commerce = () => {
3233
};
3334

3435
return (
35-
<ScrollView>
36-
<View style={styles.container}>
37-
<Text style={styles.title}>Commerce</Text>
38-
<Text style={styles.subtitle}>
39-
Purchase will be tracked when &quot;Buy&quot; is clicked. See logs for
40-
output.
41-
</Text>
42-
{items.map((item) => (
43-
<View key={item.id} style={styles.cardContainer}>
44-
<View style={styles.infoContainer}>
45-
<View style={styles.imageContainer}>
46-
<Image source={item.icon} style={styles.cardImage} />
47-
</View>
48-
<View style={styles.textContainer}>
49-
<Text style={styles.cardTitle}>{item.name}</Text>
50-
<Text style={styles.cardSubtitle}>{item.subtitle}</Text>
51-
<Text style={styles.price}>${item.price}</Text>
52-
<Pressable
53-
style={styles.button}
54-
onPress={() => handleClick(item)}
55-
>
56-
<Text style={styles.buttonText}>Buy</Text>
57-
</Pressable>
36+
<SafeAreaView>
37+
<ScrollView>
38+
<View style={styles.container}>
39+
<Text style={styles.title}>Commerce</Text>
40+
<Text style={styles.subtitle}>
41+
Purchase will be tracked when &quot;Buy&quot; is clicked. See logs for
42+
output.
43+
</Text>
44+
{items.map((item) => (
45+
<View key={item.id} style={styles.cardContainer}>
46+
<View style={styles.infoContainer}>
47+
<View style={styles.imageContainer}>
48+
<Image source={item.icon} style={styles.cardImage} />
49+
</View>
50+
<View style={styles.textContainer}>
51+
<Text style={styles.cardTitle}>{item.name}</Text>
52+
<Text style={styles.cardSubtitle}>{item.subtitle}</Text>
53+
<Text style={styles.price}>${item.price}</Text>
54+
<Pressable
55+
style={styles.button}
56+
onPress={() => handleClick(item)}
57+
>
58+
<Text style={styles.buttonText}>Buy</Text>
59+
</Pressable>
60+
</View>
5861
</View>
5962
</View>
60-
</View>
61-
))}
62-
</View>
63-
</ScrollView>
63+
))}
64+
</View>
65+
</ScrollView>
66+
</SafeAreaView>
6467
);
6568
};
6669

example/src/components/Embedded/Embedded.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
import { Text, View } from 'react-native';
1+
import { Iterable } from '@iterable/react-native-sdk';
2+
import { Text } from 'react-native';
3+
import { SafeAreaView } from 'react-native-safe-area-context';
24

35
import styles from './Embedded.styles';
46

57
export const Embedded = () => {
68
return (
7-
<View style={styles.container}>
9+
<SafeAreaView style={styles.container}>
810
<Text style={styles.text}>EMBEDDED</Text>
9-
</View>
11+
<Text style={styles.text}>
12+
Does embedded class exist? {Iterable.embeddedManager ? 'Yes' : 'No'}
13+
</Text>
14+
<Text style={styles.text}>
15+
Is embedded manager enabled?{' '}
16+
{Iterable.embeddedManager.isEnabled ? 'Yes' : 'No'}
17+
</Text>
18+
</SafeAreaView>
1019
);
1120
};
1221

example/src/components/Login/Login.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { useMemo } from 'react';
12
import {
23
ActivityIndicator,
34
Pressable,
45
Text,
56
TextInput,
67
View,
78
} from 'react-native';
8-
import { useMemo } from 'react';
9+
import { SafeAreaView } from 'react-native-safe-area-context';
910

1011
import { colors, type Route } from '../../constants';
1112
import { useIterableApp } from '../../hooks';
@@ -18,7 +19,7 @@ export const Login = ({ navigation }: RootStackScreenProps<Route.Login>) => {
1819
const loginIsEnabled = useMemo(() => apiKey && userId, [apiKey, userId]);
1920

2021
return (
21-
<View style={styles.loginScreenContainer}>
22+
<SafeAreaView style={styles.loginScreenContainer}>
2223
{loginInProgress ? (
2324
<View style={styles.loadingContainer}>
2425
<ActivityIndicator size="large" color={colors.brandPurple} />
@@ -66,7 +67,7 @@ export const Login = ({ navigation }: RootStackScreenProps<Route.Login>) => {
6667
</View>
6768
</>
6869
)}
69-
</View>
70+
</SafeAreaView>
7071
);
7172
};
7273

example/src/components/User/User.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Iterable } from '@iterable/react-native-sdk';
22
import { useEffect, useState } from 'react';
3-
import { Text, TouchableOpacity, View } from 'react-native';
3+
import { Text, TouchableOpacity } from 'react-native';
4+
import { SafeAreaView } from 'react-native-safe-area-context';
45

56
import { useIterableApp } from '../../hooks';
67
import styles from './User.styles';
@@ -18,13 +19,13 @@ export const User = () => {
1819
}, [isLoggedIn]);
1920

2021
return (
21-
<View style={styles.container}>
22+
<SafeAreaView style={styles.container}>
2223
<Text style={styles.appName}>Welcome Iterator</Text>
2324
<Text style={styles.text}>Logged in as {loggedInAs}</Text>
2425
<TouchableOpacity style={styles.button} onPress={logout}>
2526
<Text style={styles.buttonText}>Logout</Text>
2627
</TouchableOpacity>
27-
</View>
28+
</SafeAreaView>
2829
);
2930
};
3031

example/src/constants/styles/typography.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export const appName: TextStyle = {
66
fontWeight: 'bold',
77
fontSize: 14,
88
width: '100%',
9-
marginTop: 41,
109
marginBottom: 64,
1110
textTransform: 'uppercase',
1211
letterSpacing: 2,

example/src/hooks/useIterableApp.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ export const IterableAppProvider: FunctionComponent<
192192

193193
config.logLevel = IterableLogLevel.debug;
194194

195+
config.embeddedMessagingEnabled = true;
196+
195197
config.inAppHandler = () => IterableInAppShowResponse.show;
196198

197199
if (

src/core/classes/Iterable.test.ts

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -300,37 +300,39 @@ describe('Iterable', () => {
300300
// WHEN config is initialized
301301
const config = new IterableConfig();
302302
// THEN config has default values
303-
expect(config.pushIntegrationName).toBe(undefined);
303+
expect(config.allowedProtocols).toEqual([]);
304+
expect(config.androidSdkUseInMemoryStorageForInApps).toBe(false);
305+
expect(config.authHandler).toBe(undefined);
304306
expect(config.autoPushRegistration).toBe(true);
305307
expect(config.checkForDeferredDeeplink).toBe(false);
306-
expect(config.inAppDisplayInterval).toBe(30.0);
307-
expect(config.urlHandler).toBe(undefined);
308308
expect(config.customActionHandler).toBe(undefined);
309+
expect(config.dataRegion).toBe(IterableDataRegion.US);
310+
expect(config.embeddedMessagingEnabled).toBe(false);
311+
expect(config.encryptionEnforced).toBe(false);
312+
expect(config.expiringAuthTokenRefreshPeriod).toBe(60.0);
313+
expect(config.inAppDisplayInterval).toBe(30.0);
309314
expect(config.inAppHandler).toBe(undefined);
310-
expect(config.authHandler).toBe(undefined);
311315
expect(config.logLevel).toBe(IterableLogLevel.debug);
312316
expect(config.logReactNativeSdkCalls).toBe(true);
313-
expect(config.expiringAuthTokenRefreshPeriod).toBe(60.0);
314-
expect(config.allowedProtocols).toEqual([]);
315-
expect(config.androidSdkUseInMemoryStorageForInApps).toBe(false);
317+
expect(config.pushIntegrationName).toBe(undefined);
318+
expect(config.urlHandler).toBe(undefined);
316319
expect(config.useInMemoryStorageForInApps).toBe(false);
317-
expect(config.dataRegion).toBe(IterableDataRegion.US);
318-
expect(config.encryptionEnforced).toBe(false);
319320
const configDict = config.toDict();
320-
expect(configDict.pushIntegrationName).toBe(undefined);
321+
expect(configDict.allowedProtocols).toEqual([]);
322+
expect(configDict.androidSdkUseInMemoryStorageForInApps).toBe(false);
323+
expect(configDict.authHandlerPresent).toBe(false);
321324
expect(configDict.autoPushRegistration).toBe(true);
322-
expect(configDict.inAppDisplayInterval).toBe(30.0);
323-
expect(configDict.urlHandlerPresent).toBe(false);
324325
expect(configDict.customActionHandlerPresent).toBe(false);
326+
expect(configDict.dataRegion).toBe(IterableDataRegion.US);
327+
expect(configDict.embeddedMessagingEnabled).toBe(false);
328+
expect(configDict.encryptionEnforced).toBe(false);
329+
expect(configDict.expiringAuthTokenRefreshPeriod).toBe(60.0);
330+
expect(configDict.inAppDisplayInterval).toBe(30.0);
325331
expect(configDict.inAppHandlerPresent).toBe(false);
326-
expect(configDict.authHandlerPresent).toBe(false);
327332
expect(configDict.logLevel).toBe(IterableLogLevel.debug);
328-
expect(configDict.expiringAuthTokenRefreshPeriod).toBe(60.0);
329-
expect(configDict.allowedProtocols).toEqual([]);
330-
expect(configDict.androidSdkUseInMemoryStorageForInApps).toBe(false);
333+
expect(configDict.pushIntegrationName).toBe(undefined);
334+
expect(configDict.urlHandlerPresent).toBe(false);
331335
expect(configDict.useInMemoryStorageForInApps).toBe(false);
332-
expect(configDict.dataRegion).toBe(IterableDataRegion.US);
333-
expect(configDict.encryptionEnforced).toBe(false);
334336
});
335337
});
336338

@@ -1212,4 +1214,19 @@ describe('Iterable', () => {
12121214
});
12131215
});
12141216
});
1217+
1218+
describe('embeddedManager', () => {
1219+
it('should be disabled by default', () => {
1220+
const config = new IterableConfig();
1221+
expect(config.embeddedMessagingEnabled).toBe(false);
1222+
expect(Iterable.embeddedManager.isEnabled).toBe(false);
1223+
});
1224+
1225+
it('should enable embeddedManager when config is set', async () => {
1226+
const config = new IterableConfig();
1227+
config.embeddedMessagingEnabled = true;
1228+
await Iterable.initialize('test-key', config);
1229+
expect(Iterable.embeddedManager.isEnabled).toBe(true);
1230+
});
1231+
});
12151232
});

src/core/classes/Iterable.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { IterableAuthResponse } from './IterableAuthResponse';
2020
import type { IterableCommerceItem } from './IterableCommerceItem';
2121
import { IterableConfig } from './IterableConfig';
2222
import { IterableLogger } from './IterableLogger';
23+
import { IterableEmbeddedManager } from '../../embedded/classes/IterableEmbeddedManager';
2324

2425
const RNEventEmitter = new NativeEventEmitter(RNIterableAPI);
2526

@@ -96,6 +97,27 @@ export class Iterable {
9697
*/
9798
static authManager: IterableAuthManager = new IterableAuthManager();
9899

100+
/**
101+
* Embedded message manager for the current user.
102+
*
103+
* This property provides access to embedded message functionality including
104+
* retrieving messages, displaying messages, removing messages, and more.
105+
*
106+
* **Documentation**
107+
* - [Embedded Messaging Overview](https://support.iterable.com/hc/en-us/articles/23060529977364-Embedded-Messaging-Overview)
108+
* - [Android Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061877893652-Embedded-Messages-with-Iterable-s-Android-SDK)
109+
* - [iOS Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061840746900-Embedded-Messages-with-Iterable-s-iOS-SDK)
110+
*
111+
* @example
112+
* ```typescript
113+
* Iterable.embeddedManager.getMessages().then(messages => {
114+
* console.log('Messages:', messages);
115+
* });
116+
* ```
117+
*/
118+
static embeddedManager: IterableEmbeddedManager =
119+
new IterableEmbeddedManager();
120+
99121
/**
100122
* Initializes the Iterable React Native SDK in your app's Javascript or Typescript code.
101123
*
@@ -172,6 +194,10 @@ export class Iterable {
172194

173195
IterableLogger.setLoggingEnabled(config.logReactNativeSdkCalls ?? true);
174196
IterableLogger.setLogLevel(config.logLevel);
197+
198+
Iterable.embeddedManager.setEnabled(
199+
config.embeddedMessagingEnabled ?? false
200+
);
175201
}
176202

177203
this.setupEventHandlers();

src/core/classes/IterableConfig.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,16 @@ export class IterableConfig {
329329
*/
330330
encryptionEnforced = false;
331331

332+
/**
333+
* Should the SDK enable and use embedded messaging?
334+
*
335+
* **Documentation**
336+
* - [Embedded Messaging Overview](https://support.iterable.com/hc/en-us/articles/23060529977364-Embedded-Messaging-Overview)
337+
* - [Android Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061877893652-Embedded-Messages-with-Iterable-s-Android-SDK)
338+
* - [iOS Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061840746900-Embedded-Messages-with-Iterable-s-iOS-SDK)
339+
*/
340+
embeddedMessagingEnabled = false;
341+
332342
/**
333343
* Converts the IterableConfig instance to a dictionary object.
334344
*
@@ -378,6 +388,7 @@ export class IterableConfig {
378388
pushPlatform: this.pushPlatform,
379389
encryptionEnforced: this.encryptionEnforced,
380390
retryPolicy: this.retryPolicy,
391+
embeddedMessagingEnabled: this.embeddedMessagingEnabled,
381392
};
382393
}
383394
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { IterableEmbeddedManager } from './IterableEmbeddedManager';
2+
3+
describe('IterableEmbeddedManager', () => {
4+
let embeddedManager: IterableEmbeddedManager;
5+
6+
beforeEach(() => {
7+
embeddedManager = new IterableEmbeddedManager();
8+
});
9+
10+
describe('isEnabled', () => {
11+
it('should be false by default', () => {
12+
expect(embeddedManager.isEnabled).toBe(false);
13+
});
14+
15+
it('should return true after being enabled', () => {
16+
embeddedManager.setEnabled(true);
17+
expect(embeddedManager.isEnabled).toBe(true);
18+
});
19+
20+
it('should return false after being disabled', () => {
21+
embeddedManager.setEnabled(false);
22+
expect(embeddedManager.isEnabled).toBe(false);
23+
});
24+
});
25+
26+
describe('setEnabled', () => {
27+
it('should enable the embedded manager', () => {
28+
embeddedManager.setEnabled(true);
29+
expect(embeddedManager.isEnabled).toBe(true);
30+
});
31+
32+
it('should disable the embedded manager', () => {
33+
embeddedManager.setEnabled(false);
34+
expect(embeddedManager.isEnabled).toBe(false);
35+
});
36+
37+
it('should toggle enabled state multiple times', () => {
38+
embeddedManager.setEnabled(true);
39+
expect(embeddedManager.isEnabled).toBe(true);
40+
41+
embeddedManager.setEnabled(false);
42+
expect(embeddedManager.isEnabled).toBe(false);
43+
44+
embeddedManager.setEnabled(true);
45+
expect(embeddedManager.isEnabled).toBe(true);
46+
});
47+
48+
it('should handle setting the same state multiple times', () => {
49+
embeddedManager.setEnabled(true);
50+
embeddedManager.setEnabled(true);
51+
expect(embeddedManager.isEnabled).toBe(true);
52+
53+
embeddedManager.setEnabled(false);
54+
embeddedManager.setEnabled(false);
55+
expect(embeddedManager.isEnabled).toBe(false);
56+
});
57+
});
58+
});
59+

0 commit comments

Comments
 (0)