Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
85e28d7
feat: introduce IterableAuthManager for authentication handling
lposen Oct 7, 2025
86d9b41
refactor: initialize defaults for logger, config, authManager, and in…
lposen Oct 7, 2025
6b84340
fix: enhance error handling and logging for JWT authentication failures
lposen Oct 7, 2025
4b40861
feat: implement IterableTracking and session management for embedded …
lposen Oct 7, 2025
26ddabd
feat: implement IterableApi class for enhanced tracking and user mana…
lposen Oct 7, 2025
f2aa18d
Merge branch 'jwt/authManager' into loren/embedded/all
lposen Oct 7, 2025
2f19aef
refactor: update Iterable classes to use static logger
lposen Oct 7, 2025
7aef30a
refactor: replace RNIterableAPI calls with IterableApi methods
lposen Oct 7, 2025
8e781df
Merge branch 'MOB-12231-refactor-classes-to-reduce-circular-dependenc…
lposen Oct 7, 2025
0adbc44
feat: introduce tracking manager in Iterable class and refactor track…
lposen Oct 7, 2025
e04f41f
feat: implement embedded messaging functionality with new classes and…
lposen Oct 7, 2025
2106df0
feat: add getEmbeddedMessages method to IterableApi for retrieving em…
lposen Oct 7, 2025
09f371e
feat: add support for enabling embedded messaging and update Iterable…
lposen Oct 7, 2025
1846e74
feat: implement IterableEmbeddedView and related types for embedded m…
lposen Oct 7, 2025
1408854
feat: enhance IterableApi with new tracking methods for embedded sess…
lposen Oct 7, 2025
452444a
feat: update Iterable class tracker with new embedded tracking methods
lposen Oct 7, 2025
a6a45ed
feat: implement getEmbeddedMessages method and enhance serialization …
lposen Oct 7, 2025
d67c1ed
feat: add getEmbeddedMessages method to RNIterableAPIModule for embed…
lposen Oct 7, 2025
a4fe2ac
fix: update enableEmbeddedMessaging configuration to use configReadab…
lposen Oct 7, 2025
bae7dbf
feat: update getEmbeddedMessages method to accept multiple placement IDs
lposen Oct 7, 2025
ec940a9
feat: synchronize embedded messages on initialization
lposen Oct 7, 2025
b24ca2e
feat: add syncEmbeddedMessages and getEmbeddedPlacementIds
lposen Oct 7, 2025
33a9498
feat: add embedded update listener support to RNIterableAPIModule and…
lposen Oct 8, 2025
f31dd72
feat: add start and end session methods for embedded messaging
lposen Oct 8, 2025
76a1ca3
feat: add start and pause impression methods for embedded messaging
lposen Oct 8, 2025
1e349df
feat: add handleEmbeddedClick and trackEmbeddedClick methods
lposen Oct 8, 2025
0e53674
fix: import missing IterableEmbeddedMessage
lposen Oct 8, 2025
3466661
refactor: giving up on the callbacks
lposen Oct 8, 2025
95b8085
refactor: remove embedded update listener methods from RNIterableAPIM…
lposen Oct 8, 2025
7421938
feat: add Embedded component and integrate into navigation
lposen Oct 8, 2025
595560c
feat: enhance Embedded component with new styles and message handling
lposen Oct 8, 2025
22fa618
feat: initial implementation of IterableEmbeddedBanner and IterableEm…
lposen Oct 8, 2025
371fad9
feat: added buttons to banner
lposen Oct 8, 2025
e6c1132
feat: enhance IterableEmbedded components with button click handling …
lposen Oct 8, 2025
14922e0
feat: fix error on embedded button click
lposen Oct 8, 2025
297ae51
feat: improve Embedded component with enhanced message fetching and s…
lposen Oct 9, 2025
13fc4a5
feat: implemented IterableEmbeddedNotification
lposen Oct 9, 2025
56d8dfc
feat: initial card view
lposen Oct 9, 2025
f9b22b7
feat: completed styles for IterableEmbeddedCard
lposen Oct 9, 2025
383e308
feat: refactor embedded click handling and introduce action prefix ut…
lposen Oct 9, 2025
1115268
feat: refactor IterableEmbedded components to utilize useEmbeddedView…
lposen Oct 9, 2025
4732925
feat: add visibility tracking and refactor image dimensions in Iterab…
lposen Oct 9, 2025
8149ed0
feat: request notification permission for Android 13+ in App component
lposen Oct 9, 2025
0ce8ecb
refactor: trying to get session start and end to work
lposen Oct 9, 2025
e0c27e0
refactor: reorganize IterableEmbedded types and interfaces for better…
lposen Oct 9, 2025
24e55d1
feat: consolidate other hooks into the usembedded hook
lposen Oct 9, 2025
0b147e8
feat: add TODOs for tracking session and impression events in Iterabl…
lposen Oct 9, 2025
4b71391
Merge branch 'MOB-12231-refactor-classes-to-reduce-circular-dependenc…
lposen Oct 9, 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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
],
rules: {
'react/react-in-jsx-scope': 'off',
'no-bitwise': 'off',
},
overrides: [
{
Expand Down
37 changes: 37 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Embedded

## TODO
- [ ] Track session start and stop
- [ ] Track pause and start impression
- [ ] Align styles with OOTB view notes
- https://support.iterable.com/hc/en-us/articles/23230946708244-Out-of-the-Box-Views-for-Embedded-Messages
- [ ] Go through [Evans
google doc](https://docs.google.com/document/d/15GNyo2x5QwYBPUliB4JZvXLkb04ZkW96jcbUJ96YrAM/edit?tab=t.0)
and [Slab doc](https://iterable.slab.com/posts/embedded-messaging-rn-sdk-urwffrhx#h8if0-public-methods)
and see if there is anything else to do
- [ ] Add the ability to switch between views in the example app. And the
ability to configure your own JSON.


## Resources
- [RN SDK - In-app review](https://iterable.slab.com/posts/rn-sdk-in-app-review-bl2vp1ds)
- [Google doc](https://docs.google.com/document/d/15GNyo2x5QwYBPUliB4JZvXLkb04ZkW96jcbUJ96YrAM/edit?tab=t.0)
- [Embedded Messaging - RN SDK](https://iterable.slab.com/posts/embedded-messaging-rn-sdk-urwffrhx#h8if0-public-methods)
- [New arch customers](https://docs.google.com/spreadsheets/d/1FzoAH5CAcNy92Km5DLqr8yYvPDBPLTuBXa0_CXZEUCA/edit?gid=60700846#gid=60700846)
- [RN Epic](https://iterable.atlassian.net/browse/MOB-7052)
- [Figma](https://www.figma.com/design/rbDozNjEF9MjwbvSqzrVTv/Flex-Messaging?node-id=3804-186809&p=f)
- [Embedded Messaging: Timeline for a Successful GA Release](https://iterable.slab.com/posts/embedded-messaging-timeline-for-a-successful-ga-release-7f762g1c)
- [Embedded Task Prioritization](https://tables.area120.google.com/u/0/workspace/av3wJDN6_I94tIbdapKOzu/table/9jRcY5gDv2OaTnkceKbOA5)
- [Acceptance Criteria](https://iterable.slab.com/posts/embedded-messaging-acceptance-criteria-80gfn857)
- [Bug Bash Test Cases](https://docs.google.com/spreadsheets/d/1ZrM8vMoMjhK4x18uoqtcOyUqDhFqlwTfrUWAM7csibQ/edit?gid=1805677430#gid=1805677430)
- [Stories for original](https://docs.google.com/spreadsheets/d/1ZrM8vMoMjhK4x18uoqtcOyUqDhFqlwTfrUWAM7csibQ/edit?gid=1805677430#gid=1805677430)
- [Datadog story](https://iterable.atlassian.net/browse/MOB-6926)
- [Yellow Brick Road: Embedded Messaging Mobile SDK](https://iterable.slab.com/posts/yellow-brick-road-embedded-messaging-mobile-sdk-4v032ww9?shr=4v032ww9#h69qk-2024-01-19-ootb-style-layout-conversation)
- [Android SDK Embedded OOTB Constraint
finalization](https://iterable.atlassian.net/browse/MOB-7678)
- [iOS SDK Embedded OOTB Constraint finalization](https://iterable.atlassian.net/browse/MOB-7679)
- [OOTB view bugs review](https://iterable.slab.com/posts/ootb-view-bugs-review-7u978hhy?shr=7u978hhy)
- [Non-RN Embedded Epic](https://iterable.atlassian.net/browse/MOB-5235)
- [OOTB Style/Layout Questions](https://iterable.atlassian.net/browse/MOB-5235)
- [Evan PR](https://github.com/Iterable/react-native-sdk/pull/732)
- [My PR](https://github.com/Iterable/react-native-sdk/pull/730)
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@
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;

import com.iterable.iterableapi.AuthFailure;
import com.iterable.iterableapi.EmbeddedMessageElementsButton;
import com.iterable.iterableapi.InboxSessionManager;
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;
Expand All @@ -45,10 +51,12 @@
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;

public class RNIterableAPIModuleImpl implements IterableUrlHandler, IterableCustomActionHandler, IterableInAppHandler, IterableAuthHandler, IterableInAppManager.Listener {

public class RNIterableAPIModuleImpl implements IterableUrlHandler, IterableCustomActionHandler, IterableInAppHandler, IterableAuthHandler, IterableInAppManager.Listener, IterableEmbeddedUpdateHandler {
public static final String NAME = "RNIterableAPI";

private static String TAG = "RNIterableAPIModule";
Expand Down Expand Up @@ -88,10 +96,15 @@ 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);

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
Expand All @@ -118,6 +131,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.
Expand All @@ -126,6 +143,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
Expand Down Expand Up @@ -629,14 +647,128 @@ 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<IterableEmbeddedMessage> 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<IterableEmbeddedMessage> 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<IterableEmbeddedMessage> 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<Long> 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 startEmbeddedSession() {
IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().startSession();
}

public void endEmbeddedSession() {
IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().endSession();
}

public void startEmbeddedImpression(String messageId, int placementId) {
IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().startImpression(messageId, placementId);
}

public void pauseEmbeddedImpression(String messageId) {
IterableApi.getInstance().getEmbeddedManager().getEmbeddedSessionManager().pauseImpression(messageId);
}

public void trackEmbeddedClick(ReadableMap messageMap, String buttonId, String clickedUrl) {
IterableLogger.d(TAG, "trackEmbeddedClick: buttonId: " + buttonId + " clickedUrl: " + clickedUrl);
IterableEmbeddedMessage message = Serialization.embeddedMessageFromReadableMap(messageMap);
if (message != null) {
IterableApi.getInstance().trackEmbeddedClick(message, buttonId, clickedUrl);
} else {
IterableLogger.e(TAG, "Failed to convert message map to IterableEmbeddedMessage");
}
}

@Override
public void onMessagesUpdated() {
IterableLogger.d(TAG, "onMessagesUpdated");
sendEvent(EventName.receivedIterableEmbeddedMessagesChanged.name(), null);
}

@Override
public void onEmbeddedMessagingDisabled() {
IterableLogger.d(TAG, "onEmbeddedMessagingDisabled");
sendEvent(EventName.receivedIterableEmbeddedMessagesChanged.name(), null);
}

private JSONObject createTestPlacement(int placementId) throws JSONException {
JSONObject placement = new JSONObject();
placement.put("placementId", placementId);
return placement;
}


// ---------------------------------------------------------------------------------------
// endregion
}

enum EventName {
handleUrlCalled,
handleCustomActionCalled,
handleInAppCalled,
handleAuthCalled,
receivedIterableInboxChanged,
handleAuthFailureCalled,
handleAuthSuccessCalled,
handleAuthFailureCalled
handleCustomActionCalled,
handleInAppCalled,
handleUrlCalled,
receivedIterableEmbeddedMessagesChanged,
receivedIterableInboxChanged
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@
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;
import com.iterable.iterableapi.IterableInAppLocation;
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;
Expand Down Expand Up @@ -137,6 +138,27 @@ static JSONArray serializeInAppMessages(List<IterableInAppMessage> inAppMessages
return inAppMessagesJson;
}

static JSONArray serializeEmbeddedMessages(List<IterableEmbeddedMessage> embeddedMessages) {
JSONArray embeddedMessagesJson = new JSONArray();
if (embeddedMessages != null) {
for (IterableEmbeddedMessage message : embeddedMessages) {
JSONObject messageJson = IterableEmbeddedMessage.Companion.toJSONObject(message);
embeddedMessagesJson.put(messageJson);
}
}
return embeddedMessagesJson;
}

static IterableEmbeddedMessage embeddedMessageFromReadableMap(ReadableMap messageMap) {
try {
JSONObject messageJson = convertMapToJson(messageMap);
return IterableEmbeddedMessage.Companion.fromJSONObject(messageJson);
} catch (JSONException e) {
IterableLogger.e(TAG, "Failed to convert ReadableMap to IterableEmbeddedMessage: " + e.getLocalizedMessage());
return null;
}
}

static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableContextMap) {
try {
JSONObject iterableContextJSON = convertMapToJson(iterableContextMap);
Expand Down Expand Up @@ -218,6 +240,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");
Expand Down
43 changes: 43 additions & 0 deletions android/src/newarch/java/com/RNIterableAPIModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.iterable.iterableapi.AuthFailure;
import com.iterable.iterableapi.IterableEmbeddedMessage;
import com.iterable.iterableapi.IterableLogger;
import com.iterable.iterableapi.IterableEmbeddedManager;

import com.iterable.iterableapi.IterableEmbeddedUpdateHandler;


public class RNIterableAPIModule extends NativeRNIterableAPISpec {
private final ReactApplicationContext reactContext;
Expand Down Expand Up @@ -231,4 +236,42 @@ public void sendEvent(@NonNull String eventName, @Nullable Object eventData) {
public void onInboxUpdated() {
moduleImpl.onInboxUpdated();
}

@Override
public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise promise) {
moduleImpl.getEmbeddedMessages(placementIds, promise);
}

public void syncEmbeddedMessages() {
moduleImpl.syncEmbeddedMessages();
}

public void getEmbeddedPlacementIds(Promise promise) {
moduleImpl.getEmbeddedPlacementIds(promise);
}

@Override
public void startEmbeddedImpression(String messageId, int placementId) {
moduleImpl.startEmbeddedImpression(messageId, placementId);
}

@Override
public void pauseEmbeddedImpression(String messageId) {
moduleImpl.pauseEmbeddedImpression(messageId);
}

@Override
public void trackEmbeddedClick(ReadableMap message, String buttonId, String clickedUrl) {
moduleImpl.trackEmbeddedClick(message, buttonId, clickedUrl);
}

@Override
public void startEmbeddedSession() {
moduleImpl.startEmbeddedSession();
}

@Override
public void endEmbeddedSession() {
moduleImpl.endEmbeddedSession();
}
}
Loading
Loading