diff --git a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java index 9b0bed33..29a97706 100644 --- a/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java +++ b/android/src/main/java/com/iterable/reactnative/RNIterableAPIModule.java @@ -482,6 +482,36 @@ public void updateVisibleRows(ReadableArray visibleRows) { // --------------------------------------------------------------------------------------- // endregion + // --------------------------------------------------------------------------------------- + // region Embedded APIs + + @ReactMethod + public void getEmbeddedPlacements(Promise promise) { + IterableLogger.d(TAG, "getEmbeddedPlacements"); + + JSONArray testPlacements = new JSONArray(); + int[] testPlacementIds = {808, 1121, 112}; + + try { + for (int placementId : testPlacementIds) { + testPlacements.put(createTestPlacement(placementId)); + } + + promise.resolve(Serialization.convertJsonToArray(testPlacements)); + } catch (JSONException e) { + promise.reject("", "Failed to create test placements"); + } + } + + private JSONObject createTestPlacement(int placementId) throws JSONException { + JSONObject placement = new JSONObject(); + placement.put("placementId", placementId); + return placement; + } + + // --------------------------------------------------------------------------------------- + // endregion + // --------------------------------------------------------------------------------------- // region Private Serialization Functions diff --git a/android/src/main/java/com/iterable/reactnative/Serialization.java b/android/src/main/java/com/iterable/reactnative/Serialization.java index 3a1f536a..19b82182 100644 --- a/android/src/main/java/com/iterable/reactnative/Serialization.java +++ b/android/src/main/java/com/iterable/reactnative/Serialization.java @@ -221,6 +221,10 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte configBuilder.setEncryptionEnforced(iterableContextJSON.optBoolean("encryptionEnforced")); } + if (iterableContextJSON.has("enableEmbeddedMessaging")) { + configBuilder.setEnableEmbeddedMessaging(iterableContextJSON.optBoolean("enableEmbeddedMessaging")); + } + return configBuilder; } catch (JSONException e) { e.printStackTrace(); diff --git a/ios/RNIterableAPI/RNIterableAPI.m b/ios/RNIterableAPI/RNIterableAPI.m index 8850c755..68c90b76 100644 --- a/ios/RNIterableAPI/RNIterableAPI.m +++ b/ios/RNIterableAPI/RNIterableAPI.m @@ -133,6 +133,11 @@ @interface RCT_EXTERN_REMAP_MODULE(RNIterableAPI, ReactIterableAPI, NSObject) RCT_EXTERN_METHOD(updateVisibleRows: (nonnull NSArray *) visibleRows) +// MARK: - SDK Embedded Manager Functions + +RCT_EXTERN_METHOD(getEmbeddedPlacements: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) reject) + // MARK: - SDK Auth Manager Functions RCT_EXTERN_METHOD(passAlongAuthToken: (NSString *) authToken) diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index 4db314f2..02b8fd2d 100644 --- a/ios/RNIterableAPI/ReactIterableAPI.swift +++ b/ios/RNIterableAPI/ReactIterableAPI.swift @@ -470,6 +470,22 @@ class ReactIterableAPI: RCTEventEmitter { inboxSessionManager.updateVisibleRows(visibleRows: serializedRows) } + + // MARK: - SDK Embedded Manager Functions + + @objc(getEmbeddedPlacements:rejecter:) + func getEmbeddedPlacements(resolver: @escaping RCTPromiseResolveBlock, + rejecter: @escaping RCTPromiseRejectBlock) { + ITBInfo() + + // Create test data + let testPlacements: [[String: Any]] = [ + ["placementId": "meow"], + ["placementId": "woof woof"] + ] + + resolver(testPlacements) + } // MARK: - SDK Auth Manager Functions diff --git a/ios/RNIterableAPI/Serialization.swift b/ios/RNIterableAPI/Serialization.swift index cb27026b..ea82bcf3 100644 --- a/ios/RNIterableAPI/Serialization.swift +++ b/ios/RNIterableAPI/Serialization.swift @@ -92,7 +92,10 @@ extension IterableConfig { config.dataRegion = IterableDataRegion.US } } - + + if let enableEmbeddedMessaging = dict["enableEmbeddedMesssaging"] as? Bool { + config.enableEmbeddedMessaging = enableEmbeddedMessaging + } return config } diff --git a/src/__tests__/IterableEmbeddedMessageMetadata.test.ts b/src/__tests__/IterableEmbeddedMessageMetadata.test.ts new file mode 100644 index 00000000..f57786a9 --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageMetadata.test.ts @@ -0,0 +1,58 @@ +import { IterableEmbeddedMessageMetadata } from '../embedded/classes/IterableEmbeddedMessageMetadata'; +import { Iterable } from '../core'; + +describe('IterableEmbeddedMessage', () => { + test('should create an instance of IterableEmbeddedMessageMetadata from a dictionary', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageMetadata_fromDict_valid_dictionary' + ); + + const dict = { + messageId: '123', + placementId: 456, + campaignId: 789, + isProof: false, + }; + + const result = IterableEmbeddedMessageMetadata.fromDict(dict); + + expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(result.messageId).toBe('123'); + expect(result.placementId).toBe(456); + expect(result.campaignId).toBe(789); + expect(result.isProof).toBe(false); + }); + + test('should handle optional fields', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageMetadata_fromDict_optional_fields_omitted' + ); + + const dict = { + messageId: '123', + placementId: 456, + }; + + const result = IterableEmbeddedMessageMetadata.fromDict(dict); + + expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(result.messageId).toBe('123'); + expect(result.placementId).toBe(456); + expect(result.campaignId).toBeUndefined(); + expect(result.isProof).toBe(false); + }); + + test('should throw an error if messageId is not provided', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageMetadata_fromDict_missing_messageId' + ); + + const dict = { + placementId: 456, + }; + + expect(() => { + IterableEmbeddedMessageMetadata.fromDict(dict); + }).toThrow('messageId and placementId are required'); + }); +}); diff --git a/src/core/classes/IterableConfig.ts b/src/core/classes/IterableConfig.ts index 1c0550b0..55539139 100644 --- a/src/core/classes/IterableConfig.ts +++ b/src/core/classes/IterableConfig.ts @@ -294,6 +294,13 @@ export class IterableConfig { */ encryptionEnforced = false; + /** + * This specifies whether the SDK should enable embedded messaging. + * + * By default, the SDK will not enable embedded messaging. + */ + enableEmbeddedMessaging = false; + /** * Converts the IterableConfig instance to a dictionary object. * @@ -342,6 +349,7 @@ export class IterableConfig { dataRegion: this.dataRegion, pushPlatform: this.pushPlatform, encryptionEnforced: this.encryptionEnforced, + enableEmbeddedMessaging: this.enableEmbeddedMessaging, }; } } diff --git a/src/embedded/classes/IterableEmbeddedManager.ts b/src/embedded/classes/IterableEmbeddedManager.ts new file mode 100644 index 00000000..e570d750 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedManager.ts @@ -0,0 +1,24 @@ +import { NativeModules } from 'react-native'; + +import { Iterable } from '../../core/classes/Iterable'; +import { IterableEmbeddedPlacement } from './IterableEmbeddedPlacement'; + +const RNIterableAPI = NativeModules.RNIterableAPI; + +/** + * Manages embedded messages for the current user. + * + * This class provides methods to interact with embedded messages, including retrieving placements. + */ +export class IterableEmbeddedManager { + /** + * Retrieve the current user's list of embedded placements. + * + * @returns A Promise that resolves to an array of embedded placements. + */ + getPlacements(): Promise { + Iterable?.logger?.log('EmbeddedManager.getPlacements'); + + return RNIterableAPI.getEmbeddedPlacements(); + } +} diff --git a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts new file mode 100644 index 00000000..02bc032d --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts @@ -0,0 +1,65 @@ +/** + * Metadata for an embedded message. + */ +export class IterableEmbeddedMessageMetadata { + /** The ID for the embedded message */ + readonly messageId: string; + /** The placement ID for the embedded message */ + readonly placementId: number; + /** The campaign ID for the embedded message */ + readonly campaignId?: number; + /** Whether the embedded message is a proof */ + readonly isProof: boolean; + + /** + * Constructs an instance of IterableEmbeddedMessageMetadata. + * + * @param messageId - The ID for the embedded message. + * @param placementId - The placement ID for the embedded message. + * @param campaignId - The campaign ID for the embedded message. + * @param isProof - Whether the embedded message is a proof. + */ + constructor( + messageId: string, + placementId: number, + campaignId: number | undefined, + isProof: boolean = false + ) { + this.messageId = messageId; + this.placementId = placementId; + this.campaignId = campaignId; + this.isProof = isProof; + } + + /** + * Creates an instance of `IterableEmbeddedMessageMetadata` from a dictionary object. + * + * @param dict - The dictionary objectcontaining the metadata properties. + * This corresponds to the properties in {@link IterableEmbeddedMessageMetadata} + * + * @returns A new instance of `IterableEmbeddedMessageMetadata` with the provided properties. + */ + static fromDict( + dict: Partial + ): IterableEmbeddedMessageMetadata { + if (!dict.messageId || !dict.placementId) { + throw new Error('messageId and placementId are required'); + } + return new IterableEmbeddedMessageMetadata( + dict.messageId, + dict.placementId, + dict.campaignId, + dict.isProof + ); + } +} + +/** + * An interface defining the dictionary object containing the metadata properties for an embedded message. + */ +export interface EmbeddedMessageMetadataDict { + messageId: string; + placementId: number; + campaignId?: number; + isProof?: boolean; +} diff --git a/src/embedded/classes/IterableEmbeddedPlacement.ts b/src/embedded/classes/IterableEmbeddedPlacement.ts new file mode 100644 index 00000000..6a02aabf --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedPlacement.ts @@ -0,0 +1,11 @@ +/** + * Iterable embedded placement + * Contains placement id and the associated embedded messages + */ +export class IterableEmbeddedPlacement { + readonly placementId: number; + + constructor(placementId: number) { + this.placementId = placementId; + } +} diff --git a/src/embedded/classes/index.ts b/src/embedded/classes/index.ts new file mode 100644 index 00000000..9b605f65 --- /dev/null +++ b/src/embedded/classes/index.ts @@ -0,0 +1,2 @@ +export * from './IterableEmbeddedManager'; +export * from './IterableEmbeddedPlacement'; diff --git a/src/embedded/index.ts b/src/embedded/index.ts new file mode 100644 index 00000000..d7d17c69 --- /dev/null +++ b/src/embedded/index.ts @@ -0,0 +1 @@ +export * from './classes'; diff --git a/src/index.tsx b/src/index.tsx index 885cd74b..8f13478c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -25,6 +25,10 @@ export { type IterableDeviceOrientation, } from './core/hooks'; export { type IterableEdgeInsetDetails } from './core/types'; +export { + IterableEmbeddedManager, + IterableEmbeddedPlacement, +} from './embedded/classes'; export { IterableHtmlInAppContent, IterableInAppCloseSource,