Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.iterable.iterableapi.IterableConfig;
import com.iterable.iterableapi.IterableCustomActionHandler;
import com.iterable.iterableapi.IterableEmbeddedMessage;
import com.iterable.iterableapi.IterableEmbeddedUpdateHandler;
import com.iterable.iterableapi.IterableHelper;
import com.iterable.iterableapi.IterableInAppCloseAction;
import com.iterable.iterableapi.IterableInAppHandler;
Expand All @@ -52,7 +53,7 @@
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 @@ -125,6 +126,7 @@ public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, S
IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version);

IterableApi.getInstance().getInAppManager().addListener(this);
IterableApi.getInstance().getEmbeddedManager().addUpdateListener(this);
IterableApi.getInstance().getEmbeddedManager().syncMessages();

// MOB-10421: Figure out what the error cases are and handle them appropriately
Expand Down Expand Up @@ -189,6 +191,7 @@ public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap,
IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version);

IterableApi.getInstance().getInAppManager().addListener(this);
IterableApi.getInstance().getEmbeddedManager().addUpdateListener(this);
IterableApi.getInstance().getEmbeddedManager().syncMessages();

// MOB-10421: Figure out what the error cases are and handle them appropriately
Expand Down Expand Up @@ -781,6 +784,18 @@ public void trackEmbeddedClick(ReadableMap messageMap, String buttonId, String c
}
}

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

Choose a reason for hiding this comment

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

This is the callback that should trigger the call on TS level indicating its time to check local messages as sync has been performed.

}

@Override
public void onEmbeddedMessagingDisabled() {
IterableLogger.d(TAG, "onEmbeddedMessagingDisabled");
sendEvent(EventName.receivedIterableEmbeddedMessagingDisabled.name(), null);
Copy link
Member Author

Choose a reason for hiding this comment

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

During the sync, if native code recognize feature disablement, this callback will be invoked. sendEvent will then call method on EventName.receivedIterableEmbeddedMessagingDisabled. TS layer also needs to be there where TS layer have list of listener it calls to indicate this disabled feature notice.

}

// ---------------------------------------------------------------------------------------
// endregion
}
Expand All @@ -793,5 +808,6 @@ enum EventName {
handleInAppCalled,
handleUrlCalled,
receivedIterableEmbeddedMessagesChanged,
receivedIterableEmbeddedMessagingDisabled,
receivedIterableInboxChanged
}
22 changes: 22 additions & 0 deletions src/core/classes/Iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,12 @@ export class Iterable {
RNEventEmitter.removeAllListeners(
IterableEventName.handleAuthFailureCalled
);
RNEventEmitter.removeAllListeners(
IterableEventName.receivedIterableEmbeddedMessagesChanged
);
RNEventEmitter.removeAllListeners(
IterableEventName.receivedIterableEmbeddedMessagingDisabled
);
}

/**
Expand Down Expand Up @@ -1083,6 +1089,22 @@ export class Iterable {
}
);
}

if (Iterable.savedConfig.enableEmbeddedMessaging) {
RNEventEmitter.addListener(
IterableEventName.receivedIterableEmbeddedMessagesChanged,
() => {
Iterable.embeddedManager.notifyMessagesUpdated();
}
);

RNEventEmitter.addListener(
IterableEventName.receivedIterableEmbeddedMessagingDisabled,
() => {
Iterable.embeddedManager.notifyEmbeddedMessagingDisabled();
}
);
}
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/core/enums/IterableEventName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export enum IterableEventName {
handleAuthCalled = 'handleAuthCalled',
/** Event that fires when the Iterable inbox is updated */
receivedIterableInboxChanged = 'receivedIterableInboxChanged',
/** Event that fires when embedded messages are updated */
receivedIterableEmbeddedMessagesChanged = 'receivedIterableEmbeddedMessagesChanged',
/** Event that fires when embedded messaging is disabled */
receivedIterableEmbeddedMessagingDisabled = 'receivedIterableEmbeddedMessagingDisabled',
/** Event that fires when authentication with Iterable succeeds */
handleAuthSuccessCalled = 'handleAuthSuccessCalled',
/** Event that fires when authentication with Iterable fails */
Expand Down
165 changes: 165 additions & 0 deletions src/embedded/classes/IterableEmbeddedManager.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { NativeEventEmitter } from 'react-native';

import { RNIterableAPI } from '../../api';
import { IterableAction } from '../../core/classes/IterableAction';
import { IterableActionContext } from '../../core/classes/IterableActionContext';
import { IterableApi } from '../../core/classes/IterableApi';
import { IterableConfig } from '../../core/classes/IterableConfig';
import { IterableLogger } from '../../core/classes/IterableLogger';
import { IterableActionSource } from '../../core/enums/IterableActionSource';
import { IterableEventName } from '../../core/enums/IterableEventName';
import { callUrlHandler } from '../../core/utils/callUrlHandler';
import { getActionPrefix } from '../../core/utils/getActionPrefix';
import type { IterableEmbeddedMessage } from '../types/IterableEmbeddedMessage';

const RNEventEmitter = new NativeEventEmitter(RNIterableAPI);

/**
* Manages embedded messages from Iterable.
*
Expand All @@ -20,6 +26,16 @@ import type { IterableEmbeddedMessage } from '../types/IterableEmbeddedMessage';
* - [iOS Embedded Messaging](https://support.iterable.com/hc/en-us/articles/23061840746900-Embedded-Messages-with-Iterable-s-iOS-SDK)
*/
export class IterableEmbeddedManager {
/**
* List of listeners for embedded messages updated events.
*/
private _messagesUpdatedListeners: (() => void)[] = [];

/**
* List of listeners for embedded messaging disabled events.
*/
private _embeddedMessagingDisabledListeners: (() => void)[] = [];

/**
* Whether the embedded manager is enabled.
*
Expand Down Expand Up @@ -57,6 +73,28 @@ export class IterableEmbeddedManager {
this._isEnabled = config.enableEmbeddedMessaging ?? false;
}

/**
* Notifies all registered listeners that messages have been updated.
*
* @internal This method is for internal SDK use only.
*/
notifyMessagesUpdated() {
this._messagesUpdatedListeners.forEach((listener) => {
listener();
});
}

/**
* Notifies all registered listeners that embedded messaging has been disabled.
*
* @internal This method is for internal SDK use only.
*/
notifyEmbeddedMessagingDisabled() {
this._embeddedMessagingDisabledListeners.forEach((listener) => {
listener();
});
}

/**
* Syncs embedded local cache with the server.
*
Expand Down Expand Up @@ -273,4 +311,131 @@ export class IterableEmbeddedManager {
callUrlHandler(this._config, clickedUrl, context);
}
}

/**
* Adds a listener for when embedded messages are updated.
*
* This event fires when the embedded message cache is synced and messages
* have changed (new messages added, existing messages removed, etc.).
*
* The listener is added to an internal list and will be called when the
* native SDK detects that messages have been updated.
*
* @param callback - Function to call when messages are updated.
*
* @example
* ```typescript
* const callback = () => {
* // Refresh your UI with updated messages
* Iterable.embeddedManager.getMessages([1, 2, 3]).then(messages => {
* // Update UI with new messages
* });
* };
* Iterable.embeddedManager.addMessagesUpdatedListener(callback);
*
* // Later, remove the listener
* Iterable.embeddedManager.removeMessagesUpdatedListener(callback);
* ```
*/
addMessagesUpdatedListener(callback: () => void) {
Copy link
Contributor

@lposen lposen Jan 13, 2026

Choose a reason for hiding this comment

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

I think we should instead have onEmbeddedMessageUpdate and handle the unsubscribe function internally, as this is how we have done other listeners in the app.

We can then add this to the removeAllEventListeners function in Iterable.ts,

// Add to internal list
this._messagesUpdatedListeners.push(callback);
}

/**
* Adds a listener for when embedded messaging is disabled.
*
* This event fires when embedded messaging is disabled, typically due to
* subscription being inactive or invalid API key during sync.
*
* The listener is added to an internal list and will be called when the
* native SDK detects that embedded messaging has been disabled.
*
* @param callback - Function to call when embedded messaging is disabled.
*
* @example
* ```typescript
* const callback = () => {
* // Handle embedded messaging being disabled
* console.log('Embedded messaging has been disabled');
* // Hide embedded message UI, etc.
* };
* Iterable.embeddedManager.addEmbeddedMessagingDisabledListener(callback);
*
* // Later, remove the listener
* Iterable.embeddedManager.removeEmbeddedMessagingDisabledListener(callback);
* ```
*/
addEmbeddedMessagingDisabledListener(callback: () => void) {
// Add to internal list
this._embeddedMessagingDisabledListeners.push(callback);
}

/**
* Removes a specific listener for when embedded messages are updated.
*
* @param callback - The callback function that was previously registered.
*
* @example
* ```typescript
* const listener = () => {
* console.log('Messages updated');
* };
* Iterable.embeddedManager.addMessagesUpdatedListener(listener);
* // Later, remove the specific listener
* Iterable.embeddedManager.removeMessagesUpdatedListener(listener);
* ```
*/
removeMessagesUpdatedListener(callback: () => void) {
const index = this._messagesUpdatedListeners.indexOf(callback);
if (index > -1) {
this._messagesUpdatedListeners.splice(index, 1);
}
}

/**
* Removes a specific listener for when embedded messaging is disabled.
*
* @param callback - The callback function that was previously registered.
*
* @example
* ```typescript
* const listener = () => {
* console.log('Embedded messaging disabled');
* };
* Iterable.embeddedManager.addEmbeddedMessagingDisabledListener(listener);
* // Later, remove the specific listener
* Iterable.embeddedManager.removeEmbeddedMessagingDisabledListener(listener);
* ```
*/
removeEmbeddedMessagingDisabledListener(callback: () => void) {
const index = this._embeddedMessagingDisabledListeners.indexOf(callback);
if (index > -1) {
this._embeddedMessagingDisabledListeners.splice(index, 1);
}
}

/**
* Removes all listeners for embedded message events.
*
* This removes all listeners for both `receivedIterableEmbeddedMessagesChanged`
* and `receivedIterableEmbeddedMessagingDisabled` events, including the
* internal lists of listeners.
*
* @example
* ```typescript
* Iterable.embeddedManager.removeAllListeners();
* ```
*/
removeAllListeners() {
RNEventEmitter.removeAllListeners(
IterableEventName.receivedIterableEmbeddedMessagesChanged
);
RNEventEmitter.removeAllListeners(
IterableEventName.receivedIterableEmbeddedMessagingDisabled
);
// Clear internal listener lists
this._messagesUpdatedListeners = [];
this._embeddedMessagingDisabledListeners = [];
}
}