Skip to content
Draft
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
96 changes: 96 additions & 0 deletions Build ReactNativeSdkExample_2026-01-07T10-43-40.txt

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
13B07F961A680F5B00A75B9A /* ReactNativeSdkExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactNativeSdkExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNativeSdkExample/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeSdkExample/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* ReactNativeSdkExample.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; name = ReactNativeSdkExample.entitlements; path = ReactNativeSdkExample/ReactNativeSdkExample.entitlements; sourceTree = "<group>"; };
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
3A95ED4563D4389808EDEA8F /* Pods-ReactNativeSdkExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.debug.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -91,6 +92,7 @@
children = (
13B07FB51A68108700A75B9A /* Images.xcassets */,
779227332DFA3FB500D69EC0 /* AppDelegate.swift */,
13B07FB71A68108700A75B9A /* ReactNativeSdkExample.entitlements */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
Expand Down Expand Up @@ -277,14 +279,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n";
Expand Down Expand Up @@ -320,14 +318,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n";
Expand Down Expand Up @@ -430,6 +424,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ReactNativeSdkExample/ReactNativeSdkExample.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = BP98Z28R86;
ENABLE_BITCODE = NO;
Expand Down Expand Up @@ -460,6 +455,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ReactNativeSdkExample/ReactNativeSdkExample.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = BP98Z28R86;
INFOPLIST_FILE = ReactNativeSdkExample/Info.plist;
Expand Down Expand Up @@ -555,7 +551,10 @@
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
OTHER_LDFLAGS = "$(inherited) ";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
Expand Down Expand Up @@ -628,7 +627,10 @@
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
OTHER_LDFLAGS = "$(inherited) ";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.web-browser</key>
<true/>
</dict>
</plist>
6 changes: 5 additions & 1 deletion example/src/hooks/useIterableApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
useState,
type FunctionComponent,
} from 'react';
import { Alert } from 'react-native';
import { Alert, Platform } from 'react-native';

import {
Iterable,
Expand Down Expand Up @@ -252,6 +252,10 @@ export const IterableAppProvider: FunctionComponent<
'`Iterable.initialize` failed with the following error',
err
);
if (Platform.OS === 'ios' && getUserId()) {
setIsInitialized(true);
return login();
}
setIsInitialized(false);
setLoginInProgress(false);
return Promise.reject(err);
Expand Down
69 changes: 69 additions & 0 deletions ios/RNIterableAPI/RNIterableAPI.mm
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,43 @@ - (void)pauseAuthRetries:(BOOL)pauseRetry {
[_swiftAPI pauseAuthRetries:pauseRetry];
}

- (void)syncEmbeddedMessages {
[_swiftAPI syncEmbeddedMessages];
}

- (void)startEmbeddedSession {
[_swiftAPI startEmbeddedSession];
}

- (void)endEmbeddedSession {
[_swiftAPI endEmbeddedSession];
}

- (void)startEmbeddedImpression:(NSString *)messageId placementId:(double)placementId {
[_swiftAPI startEmbeddedImpression:messageId placementId:placementId];
}

- (void)pauseEmbeddedImpression:(NSString *)messageId {
[_swiftAPI pauseEmbeddedImpression:messageId];
}

- (void)getEmbeddedPlacementIds:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
[_swiftAPI getEmbeddedPlacementIds:resolve rejecter:reject];
}

- (void)getEmbeddedMessages:(NSArray *_Nullable)placementIds
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
[_swiftAPI getEmbeddedMessages:placementIds resolver:resolve rejecter:reject];
}

- (void)trackEmbeddedClick:(NSDictionary *)message
buttonId:(NSString *_Nullable)buttonId
clickedUrl:(NSString *_Nullable)clickedUrl {
[_swiftAPI trackEmbeddedClick:message buttonId:buttonId clickedUrl:clickedUrl];
}

- (void)wakeApp {
// Placeholder function -- this method is only used in Android
}
Expand Down Expand Up @@ -507,6 +544,38 @@ - (void)wakeApp {
[_swiftAPI pauseAuthRetries:pauseRetry];
}

RCT_EXPORT_METHOD(syncEmbeddedMessages) {
[_swiftAPI syncEmbeddedMessages];
}

RCT_EXPORT_METHOD(startEmbeddedSession) {
[_swiftAPI startEmbeddedSession];
}

RCT_EXPORT_METHOD(endEmbeddedSession) {
[_swiftAPI endEmbeddedSession];
}

RCT_EXPORT_METHOD(startEmbeddedImpression : (NSString *)messageId placementId : (double)placementId) {
[_swiftAPI startEmbeddedImpression:messageId placementId:placementId];
}

RCT_EXPORT_METHOD(pauseEmbeddedImpression : (NSString *)messageId) {
[_swiftAPI pauseEmbeddedImpression:messageId];
}

RCT_EXPORT_METHOD(getEmbeddedPlacementIds : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
[_swiftAPI getEmbeddedPlacementIds:resolve rejecter:reject];
}

RCT_EXPORT_METHOD(getEmbeddedMessages : (NSArray *_Nullable)placementIds resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
[_swiftAPI getEmbeddedMessages:placementIds resolver:resolve rejecter:reject];
}

RCT_EXPORT_METHOD(trackEmbeddedClick : (NSDictionary *)message buttonId : (NSString *_Nullable)buttonId clickedUrl : (NSString *_Nullable)clickedUrl) {
[_swiftAPI trackEmbeddedClick:message buttonId:buttonId clickedUrl:clickedUrl];
}

RCT_EXPORT_METHOD(wakeApp) {
// Placeholder function -- this method is only used in Android
}
Expand Down
99 changes: 99 additions & 0 deletions ios/RNIterableAPI/ReactIterableAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,105 @@ import React
IterableAPI.pauseAuthRetries(pauseRetry)
}

// MARK: - SDK Embedded Messaging Functions

@objc(syncEmbeddedMessages)
public func syncEmbeddedMessages() {
ITBInfo()
IterableAPI.embeddedManager.syncMessages { }
}

@objc(startEmbeddedSession)
public func startEmbeddedSession() {
ITBInfo()
EmbeddedSessionManager.shared.startSession()
}

@objc(endEmbeddedSession)
public func endEmbeddedSession() {
ITBInfo()
EmbeddedSessionManager.shared.endSession()
}

@objc(startEmbeddedImpression:placementId:)
public func startEmbeddedImpression(messageId: String, placementId: Double) {
ITBInfo()
EmbeddedSessionManager.shared.startImpression(messageId: messageId, placementId: placementId)
}

@objc(pauseEmbeddedImpression:)
public func pauseEmbeddedImpression(messageId: String) {
ITBInfo()
EmbeddedSessionManager.shared.pauseImpression(messageId: messageId)
}

@objc(getEmbeddedPlacementIds:rejecter:)
public func getEmbeddedPlacementIds(
resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock
) {
ITBInfo()
let messages = IterableAPI.embeddedManager.getMessages()
let placementIds = Set(messages.compactMap { $0.metadata.placementId })
let sortedPlacementIds = Array(placementIds).sorted()
resolver(sortedPlacementIds)
}

@objc(getEmbeddedMessages:resolver:rejecter:)
public func getEmbeddedMessages(
placementIds: [NSNumber]?, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock
) {
ITBInfo()
var messages: [IterableEmbeddedMessage] = []

if let placementIds = placementIds, !placementIds.isEmpty {
// Get messages for specific placement IDs
for placementId in placementIds {
let placementMessages = IterableAPI.embeddedManager.getMessages(
for: placementId.intValue
)
messages.append(contentsOf: placementMessages)
}
} else {
// Get all messages
messages = IterableAPI.embeddedManager.getMessages()
}

resolver(messages.map { $0.toDict() })
}

@objc(trackEmbeddedClick:buttonId:clickedUrl:)
public func trackEmbeddedClick(
message: NSDictionary, buttonId: String?, clickedUrl: String?
) {
ITBInfo()

// Extract message ID from the dictionary
guard let messageDict = message as? [AnyHashable: Any],
let metadataDict = messageDict["metadata"] as? [AnyHashable: Any],
let messageId = metadataDict["messageId"] as? String else {
ITBError("Could not extract messageId from message dictionary")
return
}

// Find the message in the embedded manager's cache
let messages = IterableAPI.embeddedManager.getMessages()
guard let embeddedMessage = messages.first(where: { $0.metadata.messageId == messageId }) else {
ITBError("Could not find embedded message with id: \(messageId)")
return
}

guard let clickedUrl = clickedUrl else {
ITBError("clickedUrl is required for trackEmbeddedClick")
return
}

IterableAPI.track(
embeddedMessageClick: embeddedMessage,
buttonIdentifier: buttonId,
clickedUrl: clickedUrl
)
}

// MARK: Private
private var shouldEmit = false
private let _methodQueue = DispatchQueue(label: String(describing: ReactIterableAPI.self))
Expand Down
28 changes: 28 additions & 0 deletions ios/RNIterableAPI/Serialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ extension IterableConfig {
config.useInMemoryStorageForInApps = useInMemoryStorageForInApp
}

if let enableEmbeddedMessaging = dict["enableEmbeddedMessaging"] as? Bool {
config.enableEmbeddedMessaging = enableEmbeddedMessaging
}

if let dataRegion = dict["dataRegion"] as? NSNumber {
switch dataRegion {
case 0:
Expand Down Expand Up @@ -279,3 +283,27 @@ extension InboxImpressionTracker.RowInfo {
return rows.compactMap(InboxImpressionTracker.RowInfo.from(dict:))
}
}

extension IterableEmbeddedMessage {
func toDict() -> [AnyHashable: Any] {
var dict = [AnyHashable: Any]()

// Serialize metadata (which is Codable)
if let metadataDict = SerializationUtil.encodableToDictionary(encodable: metadata) {
dict["metadata"] = metadataDict
}

// Serialize elements if present (which is Codable)
if let elements = elements,
let elementsDict = SerializationUtil.encodableToDictionary(encodable: elements) {
dict["elements"] = elementsDict
}

// Add payload directly
if let payload = payload {
dict["payload"] = payload
}

return dict
}
}