diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java index 16447ac65..fcef6f436 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java @@ -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; @@ -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"; @@ -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 @@ -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 @@ -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); + } + + @Override + public void onEmbeddedMessagingDisabled() { + IterableLogger.d(TAG, "onEmbeddedMessagingDisabled"); + sendEvent(EventName.receivedIterableEmbeddedMessagingDisabled.name(), null); + } + // --------------------------------------------------------------------------------------- // endregion } @@ -793,5 +808,6 @@ enum EventName { handleInAppCalled, handleUrlCalled, receivedIterableEmbeddedMessagesChanged, + receivedIterableEmbeddedMessagingDisabled, receivedIterableInboxChanged } diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index d9c98a572..a6d53ad5a 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -950,6 +950,12 @@ export class Iterable { RNEventEmitter.removeAllListeners( IterableEventName.handleAuthFailureCalled ); + RNEventEmitter.removeAllListeners( + IterableEventName.receivedIterableEmbeddedMessagesChanged + ); + RNEventEmitter.removeAllListeners( + IterableEventName.receivedIterableEmbeddedMessagingDisabled + ); } /** @@ -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(); + } + ); + } } /** diff --git a/src/core/enums/IterableEventName.ts b/src/core/enums/IterableEventName.ts index 4a44cbb40..0005e582e 100644 --- a/src/core/enums/IterableEventName.ts +++ b/src/core/enums/IterableEventName.ts @@ -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 */ diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts index 277fcf819..96d26134e 100644 --- a/src/embedded/classes/IterableEmbeddedManager.ts +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -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. * @@ -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. * @@ -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. * @@ -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) { + // 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 = []; + } }