diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 94bb1d929..10fa97f03 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -658,6 +658,16 @@ public void endEmbeddedSession() { IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().endSession(); } + public void startEmbeddedImpression(String messageId, int placementId) { + IterableLogger.d(TAG, "startEmbeddedImpression"); + IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().startImpression(messageId, placementId); + } + + public void pauseEmbeddedImpression(String messageId) { + IterableLogger.d(TAG, "pauseEmbeddedImpression"); + IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().pauseImpression(messageId); + } + 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 ade1996f6..457788ba8 100644 --- a/android/src/newarch/java/com/RNIterableAPIModule.java +++ b/android/src/newarch/java/com/RNIterableAPIModule.java @@ -239,6 +239,16 @@ public void endEmbeddedSession() { moduleImpl.endEmbeddedSession(); } + @Override + public void startEmbeddedImpression(String messageId, double placementId) { + moduleImpl.startEmbeddedImpression(messageId, (int) placementId); + } + + @Override + public void pauseEmbeddedImpression(String messageId) { + moduleImpl.pauseEmbeddedImpression(messageId); + } + @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 468c2e4e4..fb6c76d76 100644 --- a/android/src/oldarch/java/com/RNIterableAPIModule.java +++ b/android/src/oldarch/java/com/RNIterableAPIModule.java @@ -243,6 +243,16 @@ public void endEmbeddedSession() { moduleImpl.endEmbeddedSession(); } + @ReactMethod + public void startEmbeddedImpression(String messageId, double placementId) { + moduleImpl.startEmbeddedImpression(messageId, (int) placementId); + } + + @ReactMethod + public void pauseEmbeddedImpression(String messageId) { + moduleImpl.pauseEmbeddedImpression(messageId); + } + @ReactMethod public void getEmbeddedPlacementIds(Promise promise) { moduleImpl.getEmbeddedPlacementIds(promise); diff --git a/example/src/components/Embedded/Embedded.styles.ts b/example/src/components/Embedded/Embedded.styles.ts index 9dc6aea47..56241c676 100644 --- a/example/src/components/Embedded/Embedded.styles.ts +++ b/example/src/components/Embedded/Embedded.styles.ts @@ -1,5 +1,5 @@ import { StyleSheet } from 'react-native'; -import { button, buttonText, container, hr } from '../../constants'; +import { button, buttonText, container, hr, link } from '../../constants'; const styles = StyleSheet.create({ button, @@ -11,7 +11,17 @@ const styles = StyleSheet.create({ gap: 16, paddingHorizontal: 16, }, + embeddedTitle: { + fontSize: 16, + fontWeight: 'bold', + lineHeight: 20, + }, + embeddedTitleContainer: { + display: 'flex', + flexDirection: 'row', + }, hr, + link, text: { textAlign: 'center' }, utilitySection: { paddingHorizontal: 16, diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 384b6675f..cf74d579c 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -49,6 +49,26 @@ export const Embedded = () => { }); }, [getPlacementIds]); + const startEmbeddedImpression = useCallback( + (message: IterableEmbeddedMessage) => { + console.log(`startEmbeddedImpression`, message); + Iterable.embeddedManager.startImpression( + message.metadata.messageId, + // TODO: check if this should be changed to a number, as per the type + Number(message.metadata.placementId) + ); + }, + [] + ); + + const pauseEmbeddedImpression = useCallback( + (message: IterableEmbeddedMessage) => { + console.log(`pauseEmbeddedImpression:`, message); + Iterable.embeddedManager.pauseImpression(message.metadata.messageId); + }, + [] + ); + return ( EMBEDDED @@ -64,19 +84,19 @@ export const Embedded = () => { Placement ids: [{placementIds.join(', ')}] - Sync embedded messages + Sync messages Get placement ids - Start embedded session + Start session - End embedded session + End session - Get embedded messages + Get messages @@ -84,7 +104,21 @@ export const Embedded = () => { {embeddedMessages.map((message) => ( - Embedded message + + Embedded message | + startEmbeddedImpression(message)} + > + Start impression + + | + pauseEmbeddedImpression(message)} + > + Pause impression + + + metadata.messageId: {message.metadata.messageId} metadata.placementId: {message.metadata.placementId} elements.title: {message.elements?.title} diff --git a/example/src/constants/styles/typography.ts b/example/src/constants/styles/typography.ts index af8a3b8e4..d28b2f328 100644 --- a/example/src/constants/styles/typography.ts +++ b/example/src/constants/styles/typography.ts @@ -56,3 +56,8 @@ export const requiredStar: TextStyle = { ...label, color: colors.textDestructive, }; + +export const link: TextStyle = { + color: colors.textInteractive, + textDecorationLine: 'underline', +}; diff --git a/src/__mocks__/MockRNIterableAPI.ts b/src/__mocks__/MockRNIterableAPI.ts index 136744ad7..23b871ddf 100644 --- a/src/__mocks__/MockRNIterableAPI.ts +++ b/src/__mocks__/MockRNIterableAPI.ts @@ -166,6 +166,10 @@ export class MockRNIterableAPI { }, ]); + static startEmbeddedImpression = jest.fn(); + + static pauseEmbeddedImpression = jest.fn(); + // 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/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts index a9b32a400..6e03cb2b7 100644 --- a/src/api/NativeRNIterableAPI.ts +++ b/src/api/NativeRNIterableAPI.ts @@ -149,6 +149,8 @@ export interface Spec extends TurboModule { syncEmbeddedMessages(): void; startEmbeddedSession(): void; endEmbeddedSession(): void; + startEmbeddedImpression(messageId: string, placementId: number): void; + pauseEmbeddedImpression(messageId: string): void; getEmbeddedPlacementIds(): Promise; getEmbeddedMessages( placementIds: number[] | null diff --git a/src/core/classes/IterableApi.ts b/src/core/classes/IterableApi.ts index 52f54db80..bbe687605 100644 --- a/src/core/classes/IterableApi.ts +++ b/src/core/classes/IterableApi.ts @@ -535,6 +535,22 @@ export class IterableApi { return RNIterableAPI.endEmbeddedSession(); } + /** + * Starts an embedded impression. + */ + static startEmbeddedImpression(messageId: string, placementId: number) { + IterableLogger.log('startEmbeddedImpression: ', messageId, placementId); + return RNIterableAPI.startEmbeddedImpression(messageId, placementId); + } + + /** + * Pauses an embedded impression. + */ + static pauseEmbeddedImpression(messageId: string) { + IterableLogger.log('pauseEmbeddedImpression: ', messageId); + return RNIterableAPI.pauseEmbeddedImpression(messageId); + } + /** * Get the embedded placement IDs. */ diff --git a/src/embedded/classes/IterableEmbeddedManager.test.ts b/src/embedded/classes/IterableEmbeddedManager.test.ts index a3d27804c..5a0e15f4e 100644 --- a/src/embedded/classes/IterableEmbeddedManager.test.ts +++ b/src/embedded/classes/IterableEmbeddedManager.test.ts @@ -167,5 +167,92 @@ describe('IterableEmbeddedManager', () => { }); }); + describe('startImpression', () => { + it('should call IterableApi.startEmbeddedImpression with messageId and placementId', () => { + // GIVEN a message ID and placement ID + const messageId = 'message-123'; + const placementId = 456; + + // WHEN startImpression is called + embeddedManager.startImpression(messageId, placementId); + + // THEN IterableApi.startEmbeddedImpression is called with the correct parameters + expect(MockRNIterableAPI.startEmbeddedImpression).toHaveBeenCalledTimes( + 1 + ); + expect(MockRNIterableAPI.startEmbeddedImpression).toHaveBeenCalledWith( + messageId, + placementId + ); + }); + + it('should handle multiple impression starts', () => { + // GIVEN multiple messages + const messageId1 = 'message-1'; + const placementId1 = 100; + const messageId2 = 'message-2'; + const placementId2 = 200; + + // WHEN startImpression is called multiple times + embeddedManager.startImpression(messageId1, placementId1); + embeddedManager.startImpression(messageId2, placementId2); + + // THEN IterableApi.startEmbeddedImpression is called twice + expect(MockRNIterableAPI.startEmbeddedImpression).toHaveBeenCalledTimes( + 2 + ); + expect(MockRNIterableAPI.startEmbeddedImpression).toHaveBeenNthCalledWith( + 1, + messageId1, + placementId1 + ); + expect(MockRNIterableAPI.startEmbeddedImpression).toHaveBeenNthCalledWith( + 2, + messageId2, + placementId2 + ); + }); + }); + + describe('pauseImpression', () => { + it('should call IterableApi.pauseEmbeddedImpression with messageId', () => { + // GIVEN a message ID + const messageId = 'message-123'; + + // WHEN pauseImpression is called + embeddedManager.pauseImpression(messageId); + + // THEN IterableApi.pauseEmbeddedImpression is called with the correct parameter + expect(MockRNIterableAPI.pauseEmbeddedImpression).toHaveBeenCalledTimes( + 1 + ); + expect(MockRNIterableAPI.pauseEmbeddedImpression).toHaveBeenCalledWith( + messageId + ); + }); + + it('should handle multiple impression pauses', () => { + // GIVEN multiple message IDs + const messageId1 = 'message-1'; + const messageId2 = 'message-2'; + + // WHEN pauseImpression is called multiple times + embeddedManager.pauseImpression(messageId1); + embeddedManager.pauseImpression(messageId2); + + // THEN IterableApi.pauseEmbeddedImpression is called twice + expect(MockRNIterableAPI.pauseEmbeddedImpression).toHaveBeenCalledTimes( + 2 + ); + expect(MockRNIterableAPI.pauseEmbeddedImpression).toHaveBeenNthCalledWith( + 1, + messageId1 + ); + expect(MockRNIterableAPI.pauseEmbeddedImpression).toHaveBeenNthCalledWith( + 2, + messageId2 + ); + }); + }); }); diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index f2ed0ccc9..03013ed40 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -31,6 +31,9 @@ export class IterableEmbeddedManager { /** * Sets whether the embedded manager is enabled. * + * @internal This method is for internal SDK use only and should not be called + * by SDK consumers, as it is meant to be called at initialization time. + * * @param enabled - Whether the embedded manager is enabled. */ setEnabled(enabled: boolean) { @@ -41,7 +44,7 @@ export class IterableEmbeddedManager { * 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 + * Iterable's Native SDKs 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. * @@ -64,6 +67,13 @@ export class IterableEmbeddedManager { * 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) + * + * @example + * ```typescript + * Iterable.embeddedManager.getPlacementIds().then(placementIds => { + * console.log('Placement IDs:', placementIds); + * }); + * ``` */ getPlacementIds() { return IterableApi.getEmbeddedPlacementIds(); @@ -74,6 +84,13 @@ export class IterableEmbeddedManager { * * @param placementIds - The placement IDs to retrieve messages for. * @returns A Promise that resolves to an array of embedded messages. + * + * @example + * ```typescript + * Iterable.embeddedManager.getMessages([1, 2, 3]).then(messages => { + * console.log('Messages:', messages); + * }); + * ``` */ getMessages( placementIds: number[] | null @@ -84,7 +101,7 @@ export class IterableEmbeddedManager { /** * Starts a session. * - * As session is a period of time when a user is on a screen or page that can + * A 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 @@ -104,7 +121,7 @@ export class IterableEmbeddedManager { * * 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. + * session an 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. @@ -117,4 +134,44 @@ export class IterableEmbeddedManager { endSession() { return IterableApi.endEmbeddedSession(); } + /** + * Starts an embedded impression. + * + * An impression represents the on-screen appearances of a given embedded message, + * in context of a session. + * + * Each impression tracks: + * - The total number of times a message appears during a session. + * - The total amount of time that message was visible, across all its + * appearances in the session. + * + * Be sure to start and pause impressions when your app goes to and from the + * background, too. + * + * @example + * ```typescript + * Iterable.embeddedManager.startImpression(messageId, placementId); + * ``` + */ + startImpression(messageId: string, placementId: number) { + return IterableApi.startEmbeddedImpression(messageId, placementId); + } + + /** + * Pauses an embedded impression. + * + * An impression represents the on-screen appearances of a given embedded message, + * in context of a session. + * + * And impression should be paused when the message is no longer visible, + * including when your app goes to the background. + * + * @example + * ```typescript + * Iterable.embeddedManager.pauseImpression(messageId); + * ``` + */ + pauseImpression(messageId: string) { + return IterableApi.pauseEmbeddedImpression(messageId); + } }