diff --git a/BridgelessEventEmitterHost.swift b/BridgelessEventEmitterHost.swift
new file mode 100644
index 000000000..f3b587646
--- /dev/null
+++ b/BridgelessEventEmitterHost.swift
@@ -0,0 +1,22 @@
+import Foundation
+import React
+
+@objc(BridgelessEventEmitterHost)
+class BridgelessEventEmitterHost: NSObject {
+ private let appContext: RCTAppContext
+
+ init(appContext: RCTAppContext) {
+ self.appContext = appContext
+ super.init()
+ NSLog("*** BridgelessEventEmitterHost initialized ***")
+ }
+
+ func send(name: String, body: Any?) {
+ appContext.emit(eventName: name, payload: body)
+ }
+
+ func getShouldEmit() -> Bool {
+ // Optional logic if you want to control flow
+ return true
+ }
+}
diff --git a/Iterable-React-Native-SDK.podspec b/Iterable-React-Native-SDK.podspec
index 14c6d5966..e074bb340 100644
--- a/Iterable-React-Native-SDK.podspec
+++ b/Iterable-React-Native-SDK.podspec
@@ -1,7 +1,6 @@
require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
-folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
Pod::Spec.new do |s|
s.name = "Iterable-React-Native-SDK"
@@ -14,31 +13,20 @@ Pod::Spec.new do |s|
s.platforms = { :ios => min_ios_version_supported }
s.source = { :git => "https://github.com/Iterable/react-native-sdk.git", :tag => "#{s.version}" }
- s.source_files = "ios/**/*.{h,m,mm,swift}"
-
- # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
- # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
- if respond_to?(:install_modules_dependencies, true)
- install_modules_dependencies(s)
- else
- s.dependency "React-Core"
-
- # Don't install the dependencies when we run `pod install` in the old architecture.
- if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
- s.pod_target_xcconfig = {
- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
- "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
- }
- s.dependency "React-Codegen"
- s.dependency "RCT-Folly"
- s.dependency "RCTRequired"
- s.dependency "RCTTypeSafety"
- s.dependency "ReactCommon/turbomodule/core"
- end
- end
+ s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
+ s.private_header_files = "ios/**/*.h"
+ # Load Iterables iOS SDK as a dependency
s.dependency "Iterable-iOS-SDK", "6.5.4"
-
+
+ # Basic Swift support
+ s.pod_target_xcconfig = {
+ 'DEFINES_MODULE' => 'YES',
+ 'CLANG_ENABLE_MODULES' => 'YES',
+ 'SWIFT_VERSION' => '5.0',
+ "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
+ }
+
+ install_modules_dependencies(s)
+
end
diff --git a/RNIterableAPI+Module.swift b/RNIterableAPI+Module.swift
new file mode 100644
index 000000000..e69de29bb
diff --git a/example/ios/NotificationExt/Info.plist b/example/ios/NotificationExt/Info.plist
new file mode 100644
index 000000000..57421ebf9
--- /dev/null
+++ b/example/ios/NotificationExt/Info.plist
@@ -0,0 +1,13 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.usernotifications.service
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).NotificationService
+
+
+
diff --git a/example/ios/NotificationExt/NotificationService.swift b/example/ios/NotificationExt/NotificationService.swift
new file mode 100644
index 000000000..d6cac6f1e
--- /dev/null
+++ b/example/ios/NotificationExt/NotificationService.swift
@@ -0,0 +1,40 @@
+// //
+// // NotificationService.swift
+// // NotificationExt
+// //
+// // Created by Loren Posen on 7/24/25.
+// //
+
+// import UserNotifications
+
+// class NotificationService: UNNotificationServiceExtension {
+
+// var contentHandler: ((UNNotificationContent) -> Void)?
+// var bestAttemptContent: UNMutableNotificationContent?
+
+// override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
+// self.contentHandler = contentHandler
+// bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
+
+// if let bestAttemptContent = bestAttemptContent {
+// // Modify the notification content here...
+// bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
+
+// contentHandler(bestAttemptContent)
+// }
+// }
+
+// override func serviceExtensionTimeWillExpire() {
+// // Called just before the extension will be terminated by the system.
+// // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
+// if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
+// contentHandler(bestAttemptContent)
+// }
+// }
+
+// }
+
+import UserNotifications
+import IterableAppExtensions
+
+class NotificationService: ITBNotificationServiceExtension { }
diff --git a/example/ios/Podfile b/example/ios/Podfile
index 833bd46c8..ecddcd509 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -1,4 +1,4 @@
-ENV['RCT_NEW_ARCH_ENABLED'] = '1'
+ENV['RCT_NEW_ARCH_ENABLED'] = '0'
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
@@ -13,7 +13,9 @@ prepare_react_native_project!
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
- use_frameworks! :linkage => linkage.to_sym
+ # IMPORTANT: New Architecture Issue solution
+ # This is needed to use the Swift code from Iterable-iOS-SDK in the RNIterableAPI module
+ use_frameworks! :linkage => :dynamic
end
target 'ReactNativeSdkExample' do
@@ -35,3 +37,7 @@ target 'ReactNativeSdkExample' do
)
end
end
+
+target 'NotificationExt' do
+ pod 'Iterable-iOS-AppExtensions'
+end
diff --git a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj
index c6390b76b..d397e3e01 100644
--- a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj
+++ b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj
@@ -10,9 +10,12 @@
00E356F31AD99517003FC87E /* ReactNativeSdkExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
779227342DFA3FB500D69EC0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 779227332DFA3FB500D69EC0 /* AppDelegate.swift */; };
+ 77DB813A2E330329004FEDA8 /* NotificationExt.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 77DB81332E330329004FEDA8 /* NotificationExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 77DB81432E33050B004FEDA8 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77DB81412E33050B004FEDA8 /* NotificationService.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
A3A40C20801B8F02005FA4C0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */; };
- D8AD8DD7C4BBDAA8AE397A1B /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BA21919EC27D7F5BAE79A81 /* libPods-ReactNativeSdkExample.a */; };
+ BFAED3EB36E530C28BF8BC22 /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 97D33EB1A903B7D4D2CB8319 /* libPods-ReactNativeSdkExample.a */; };
+ E93759FF06B2B594995B2D8A /* libPods-NotificationExt.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55EA585EF01B9EE9C9EA4954 /* libPods-NotificationExt.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -23,8 +26,29 @@
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = ReactNativeSdkExample;
};
+ 77DB81382E330329004FEDA8 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 77DB81322E330329004FEDA8;
+ remoteInfo = NotificationExt;
+ };
/* End PBXContainerItemProxy section */
+/* Begin PBXCopyFilesBuildPhase section */
+ 77DB813F2E330329004FEDA8 /* Embed Foundation Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ 77DB813A2E330329004FEDA8 /* NotificationExt.appex in Embed Foundation Extensions */,
+ );
+ name = "Embed Foundation Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
/* Begin PBXFileReference section */
00E356EE1AD99517003FC87E /* ReactNativeSdkExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactNativeSdkExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
@@ -33,15 +57,22 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNativeSdkExample/Images.xcassets; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeSdkExample/Info.plist; sourceTree = ""; };
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; };
- 1BA21919EC27D7F5BAE79A81 /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 146FE919EE16677339560C8B /* 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 = ""; };
1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; };
- 24F802EFDCFB094D34916C72 /* Pods-ReactNativeSdkExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.release.xcconfig"; sourceTree = ""; };
+ 55EA585EF01B9EE9C9EA4954 /* libPods-NotificationExt.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NotificationExt.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 5AD7E4C807981D45AFFE46B0 /* Pods-ReactNativeSdkExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.release.xcconfig"; sourceTree = ""; };
779227312DFA3FB500D69EC0 /* ReactNativeSdkExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExample-Bridging-Header.h"; sourceTree = ""; };
779227322DFA3FB500D69EC0 /* ReactNativeSdkExampleTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExampleTests-Bridging-Header.h"; sourceTree = ""; };
779227332DFA3FB500D69EC0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = ReactNativeSdkExample/AppDelegate.swift; sourceTree = ""; };
+ 77DB812E2E3302B4004FEDA8 /* ReactNativeSdkExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ReactNativeSdkExample.entitlements; path = ReactNativeSdkExample/ReactNativeSdkExample.entitlements; sourceTree = ""; };
+ 77DB81332E330329004FEDA8 /* NotificationExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationExt.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 77DB81402E33050B004FEDA8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 77DB81412E33050B004FEDA8 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeSdkExample/LaunchScreen.storyboard; sourceTree = ""; };
+ 9517CD9EA7E72B78695AEEF6 /* Pods-NotificationExt.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationExt.release.xcconfig"; path = "Target Support Files/Pods-NotificationExt/Pods-NotificationExt.release.xcconfig"; sourceTree = ""; };
+ 97D33EB1A903B7D4D2CB8319 /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ B907B5B39032E170236A4FAE /* Pods-NotificationExt.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationExt.debug.xcconfig"; path = "Target Support Files/Pods-NotificationExt/Pods-NotificationExt.debug.xcconfig"; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
- EDCEA27594161CE66029771A /* 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 = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -56,7 +87,15 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- D8AD8DD7C4BBDAA8AE397A1B /* libPods-ReactNativeSdkExample.a in Frameworks */,
+ BFAED3EB36E530C28BF8BC22 /* libPods-ReactNativeSdkExample.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 77DB81302E330329004FEDA8 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E93759FF06B2B594995B2D8A /* libPods-NotificationExt.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -83,6 +122,7 @@
13B07FAE1A68108700A75B9A /* ReactNativeSdkExample */ = {
isa = PBXGroup;
children = (
+ 77DB812E2E3302B4004FEDA8 /* ReactNativeSdkExample.entitlements */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
779227332DFA3FB500D69EC0 /* AppDelegate.swift */,
13B07FB61A68108700A75B9A /* Info.plist */,
@@ -99,11 +139,21 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
- 1BA21919EC27D7F5BAE79A81 /* libPods-ReactNativeSdkExample.a */,
+ 55EA585EF01B9EE9C9EA4954 /* libPods-NotificationExt.a */,
+ 97D33EB1A903B7D4D2CB8319 /* libPods-ReactNativeSdkExample.a */,
);
name = Frameworks;
sourceTree = "";
};
+ 77DB81422E33050B004FEDA8 /* NotificationExt */ = {
+ isa = PBXGroup;
+ children = (
+ 77DB81402E33050B004FEDA8 /* Info.plist */,
+ 77DB81412E33050B004FEDA8 /* NotificationService.swift */,
+ );
+ path = NotificationExt;
+ sourceTree = "";
+ };
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
@@ -117,6 +167,7 @@
13B07FAE1A68108700A75B9A /* ReactNativeSdkExample */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* ReactNativeSdkExampleTests */,
+ 77DB81422E33050B004FEDA8 /* NotificationExt */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
BBD78D7AC51CEA395F1C20DB /* Pods */,
@@ -131,6 +182,7 @@
children = (
13B07F961A680F5B00A75B9A /* ReactNativeSdkExample.app */,
00E356EE1AD99517003FC87E /* ReactNativeSdkExampleTests.xctest */,
+ 77DB81332E330329004FEDA8 /* NotificationExt.appex */,
);
name = Products;
sourceTree = "";
@@ -138,8 +190,10 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
- EDCEA27594161CE66029771A /* Pods-ReactNativeSdkExample.debug.xcconfig */,
- 24F802EFDCFB094D34916C72 /* Pods-ReactNativeSdkExample.release.xcconfig */,
+ B907B5B39032E170236A4FAE /* Pods-NotificationExt.debug.xcconfig */,
+ 9517CD9EA7E72B78695AEEF6 /* Pods-NotificationExt.release.xcconfig */,
+ 146FE919EE16677339560C8B /* Pods-ReactNativeSdkExample.debug.xcconfig */,
+ 5AD7E4C807981D45AFFE46B0 /* Pods-ReactNativeSdkExample.release.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -169,29 +223,50 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeSdkExample" */;
buildPhases = (
- F706883CA13F4E50A06B85B2 /* [CP] Check Pods Manifest.lock */,
+ 20B797F7313F2F12DC05685C /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
- 2128D4591E26BD193F1293F1 /* [CP] Embed Pods Frameworks */,
- BBD6908F2EAA6D3A6C078EA9 /* [CP] Copy Pods Resources */,
+ 77DB813F2E330329004FEDA8 /* Embed Foundation Extensions */,
+ FD3DA20D23B354970BCA7230 /* [CP] Embed Pods Frameworks */,
+ 125B666575EF47D7991C7194 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
+ 77DB81392E330329004FEDA8 /* PBXTargetDependency */,
);
name = ReactNativeSdkExample;
productName = ReactNativeSdkExample;
productReference = 13B07F961A680F5B00A75B9A /* ReactNativeSdkExample.app */;
productType = "com.apple.product-type.application";
};
+ 77DB81322E330329004FEDA8 /* NotificationExt */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 77DB813C2E330329004FEDA8 /* Build configuration list for PBXNativeTarget "NotificationExt" */;
+ buildPhases = (
+ F957C8DF4D8BEC9A3C70947C /* [CP] Check Pods Manifest.lock */,
+ 77DB812F2E330329004FEDA8 /* Sources */,
+ 77DB81302E330329004FEDA8 /* Frameworks */,
+ 77DB81312E330329004FEDA8 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = NotificationExt;
+ productName = NotificationExt;
+ productReference = 77DB81332E330329004FEDA8 /* NotificationExt.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
+ LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1210;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
@@ -202,6 +277,9 @@
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1640;
};
+ 77DB81322E330329004FEDA8 = {
+ CreatedOnToolsVersion = 16.4;
+ };
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNativeSdkExample" */;
@@ -219,6 +297,7 @@
targets = (
13B07F861A680F5B00A75B9A /* ReactNativeSdkExample */,
00E356ED1AD99517003FC87E /* ReactNativeSdkExampleTests */,
+ 77DB81322E330329004FEDA8 /* NotificationExt */,
);
};
/* End PBXProject section */
@@ -241,6 +320,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 77DB81312E330329004FEDA8 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -260,41 +346,46 @@
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
- 2128D4591E26BD193F1293F1 /* [CP] Embed Pods Frameworks */ = {
+ 125B666575EF47D7991C7194 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-input-files.xcfilelist",
);
- name = "[CP] Embed Pods Frameworks";
+ name = "[CP] Copy Pods Resources";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- BBD6908F2EAA6D3A6C078EA9 /* [CP] Copy Pods Resources */ = {
+ 20B797F7313F2F12DC05685C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-input-files.xcfilelist",
);
- name = "[CP] Copy Pods Resources";
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n";
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- F706883CA13F4E50A06B85B2 /* [CP] Check Pods Manifest.lock */ = {
+ F957C8DF4D8BEC9A3C70947C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -309,13 +400,30 @@
outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-ReactNativeSdkExample-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-NotificationExt-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
+ FD3DA20D23B354970BCA7230 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -335,6 +443,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 77DB812F2E330329004FEDA8 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 77DB81432E33050B004FEDA8 /* NotificationService.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -343,6 +459,11 @@
target = 13B07F861A680F5B00A75B9A /* ReactNativeSdkExample */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
+ 77DB81392E330329004FEDA8 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 77DB81322E330329004FEDA8 /* NotificationExt */;
+ targetProxy = 77DB81382E330329004FEDA8 /* PBXContainerItemProxy */;
+ };
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@@ -406,10 +527,11 @@
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = EDCEA27594161CE66029771A /* Pods-ReactNativeSdkExample.debug.xcconfig */;
+ baseConfigurationReference = 146FE919EE16677339560C8B /* Pods-ReactNativeSdkExample.debug.xcconfig */;
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;
@@ -425,7 +547,7 @@
"-ObjC",
"-lc++",
);
- PRODUCT_BUNDLE_IDENTIFIER = iterable.reactnativesdk.example;
+ PRODUCT_BUNDLE_IDENTIFIER = com.iterable.reactnativesdk.example;
PRODUCT_NAME = ReactNativeSdkExample;
SWIFT_OBJC_BRIDGING_HEADER = "ReactNativeSdkExample-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -436,10 +558,11 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 24F802EFDCFB094D34916C72 /* Pods-ReactNativeSdkExample.release.xcconfig */;
+ baseConfigurationReference = 5AD7E4C807981D45AFFE46B0 /* Pods-ReactNativeSdkExample.release.xcconfig */;
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;
@@ -454,7 +577,7 @@
"-ObjC",
"-lc++",
);
- PRODUCT_BUNDLE_IDENTIFIER = iterable.reactnativesdk.example;
+ PRODUCT_BUNDLE_IDENTIFIER = com.iterable.reactnativesdk.example;
PRODUCT_NAME = ReactNativeSdkExample;
SWIFT_OBJC_BRIDGING_HEADER = "ReactNativeSdkExample-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -462,6 +585,89 @@
};
name = Release;
};
+ 77DB813D2E330329004FEDA8 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = B907B5B39032E170236A4FAE /* Pods-NotificationExt.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = BP98Z28R86;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = NotificationExt/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = NotificationExt;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.iterable.reactnativesdk.example.NotificationExt;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 77DB813E2E330329004FEDA8 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9517CD9EA7E72B78695AEEF6 /* Pods-NotificationExt.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = BP98Z28R86;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = NotificationExt/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = NotificationExt;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.iterable.reactnativesdk.example.NotificationExt;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -643,6 +849,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 77DB813C2E330329004FEDA8 /* Build configuration list for PBXNativeTarget "NotificationExt" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 77DB813D2E330329004FEDA8 /* Debug */,
+ 77DB813E2E330329004FEDA8 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNativeSdkExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/example/ios/ReactNativeSdkExample/AppDelegate.swift b/example/ios/ReactNativeSdkExample/AppDelegate.swift
index 1f3fbca7c..fafc0c46a 100644
--- a/example/ios/ReactNativeSdkExample/AppDelegate.swift
+++ b/example/ios/ReactNativeSdkExample/AppDelegate.swift
@@ -5,10 +5,12 @@
// Created by Loren Posen on 6/11/25.
//
-import UIKit
+import IterableSDK
import React
-import React_RCTAppDelegate
import ReactAppDependencyProvider
+import React_RCTAppDelegate
+import UIKit
+import UserNotifications
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
@@ -17,7 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?
- func application(
+ public func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
@@ -36,8 +38,96 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
launchOptions: launchOptions
)
+ UNUserNotificationCenter.current().delegate = self
+
+ /**
+ * Request permissions for push notifications.
+ * @see Step 3.5.5 of https://support.iterable.com/hc/en-us/articles/360045714132-Installing-Iterable-s-React-Native-SDK#step-3-5-set-up-support-for-push-notifications
+ */
+ requestPushPermissions(application)
+
return true
}
+
+ /**
+ * Add support for in-app messages
+ * @see Step 3.6 of https://support.iterable.com/hc/en-us/articles/360045714132-Installing-Iterable-s-React-Native-SDK#step-3-6-add-support-for-in-app-messages
+ */
+ public func application(
+ _ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
+ fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
+ ) {
+ IterableAppIntegration.application(
+ application, didReceiveRemoteNotification: userInfo,
+ fetchCompletionHandler: completionHandler
+ )
+ NSLog("didReceiveRemoteNotification: \(userInfo)")
+ }
+
+ public func application(
+ _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
+ ) {
+ /**
+ * Register the device token with Iterable.
+ * @see Step 3.5.4 of https://support.iterable.com/hc/en-us/articles/360045714132-Installing-Iterable-s-React-Native-SDK#step-3-5-set-up-support-for-push-notifications
+ */
+ IterableAPI.register(token: deviceToken)
+ NSLog("didRegisterForRemoteNotificationsWithDeviceToken: \(deviceToken)")
+ }
+
+ /**
+ * Add support for deep links
+ * @see Step 3.7 of https://support.iterable.com/hc/en-us/articles/360045714132-Installing-Iterable-s-React-Native-SDK#step-3-7-add-support-for-deep-links
+ */
+ public func application(
+ _ application: UIApplication,
+ continue userActivity: NSUserActivity,
+ restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
+ ) -> Bool {
+ return RCTLinkingManager.application(
+ application,
+ continue: userActivity,
+ restorationHandler: restorationHandler
+ )
+ }
+
+ /**
+ * Add support for deep links
+ * @see Step 3.7 of https://support.iterable.com/hc/en-us/articles/360045714132-Installing-Iterable-s-React-Native-SDK#step-3-7-add-support-for-deep-links
+ */
+ public func application(
+ _ app: UIApplication,
+ open url: URL,
+ options: [UIApplication.OpenURLOptionsKey: Any] = [:]
+ ) -> Bool {
+ return RCTLinkingManager.application(app, open: url, options: options)
+ }
+
+ public func requestPushPermissions(_ application: UIApplication) {
+ UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) {
+ granted, _ in
+ DispatchQueue.main.async {
+ if granted {
+ application.registerForRemoteNotifications()
+ } else {
+ NSLog("Push permission denied")
+ }
+ }
+ }
+ // UNUserNotificationCenter.current().getNotificationSettings { (settings) in
+ // if settings.authorizationStatus != .authorized {
+ // NSLog("Not authorized")
+ // // not authorized, ask for permission
+ // UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) {
+ // (success, error) in
+ // NSLog("auth: \(success)")
+ // }
+ // } else {
+ // // already authorized
+ // NSLog("Already authorized")
+ // }
+ // }
+ }
}
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
@@ -46,11 +136,31 @@ class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
}
override func bundleURL() -> URL? {
-#if DEBUG
- RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
-#else
- Bundle.main.url(forResource: "main", withExtension: "jsbundle")
-#endif
+ #if DEBUG
+ RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
+ #else
+ Bundle.main.url(forResource: "main", withExtension: "jsbundle")
+ #endif
}
}
+/// * Handle incoming push notifications and enable push notification tracking.
+/// * @see Step 3.5.5 of https://support.iterable.com/hc/en-us/articles/360045714132-Installing-Iterable-s-React-Native-SDK#step-3-5-set-up-support-for-push-notifications
+extension AppDelegate: UNUserNotificationCenterDelegate {
+ public func userNotificationCenter(
+ _: UNUserNotificationCenter, willPresent _: UNNotification,
+ withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
+ ) {
+ completionHandler([.badge, .banner, .list, .sound])
+ NSLog("willPresent")
+ }
+
+ public func userNotificationCenter(
+ _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse,
+ withCompletionHandler completionHandler: @escaping () -> Void
+ ) {
+ IterableAppIntegration.userNotificationCenter(
+ center, didReceive: response, withCompletionHandler: completionHandler)
+ NSLog("didReceive")
+ }
+}
diff --git a/example/ios/ReactNativeSdkExample/Info.plist b/example/ios/ReactNativeSdkExample/Info.plist
index 4e0430cdd..02f744389 100644
--- a/example/ios/ReactNativeSdkExample/Info.plist
+++ b/example/ios/ReactNativeSdkExample/Info.plist
@@ -22,15 +22,14 @@
????
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
- LSRequiresIPhoneOS
-
LSApplicationQueriesSchemes
- javascript
+ javascript
+ LSRequiresIPhoneOS
+
NSAppTransportSecurity
-
NSAllowsArbitraryLoads
NSAllowsLocalNetworking
@@ -38,20 +37,6 @@
NSLocationWhenInUseUsageDescription
- UILaunchStoryboardName
- LaunchScreen
- UIRequiredDeviceCapabilities
-
- arm64
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UIViewControllerBasedStatusBarAppearance
-
UIAppFonts
AntDesign.ttf
@@ -74,5 +59,23 @@
Zocial.ttf
Fontisto.ttf
+ UIBackgroundModes
+
+ remote-notification
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
diff --git a/example/ios/ReactNativeSdkExample/ReactNativeSdkExample.entitlements b/example/ios/ReactNativeSdkExample/ReactNativeSdkExample.entitlements
new file mode 100644
index 000000000..31c7e997a
--- /dev/null
+++ b/example/ios/ReactNativeSdkExample/ReactNativeSdkExample.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ aps-environment
+ development
+ com.apple.developer.usernotifications.time-sensitive
+
+
+
diff --git a/example/src/components/App/App.constants.ts b/example/src/components/App/App.constants.ts
index f84c390cb..ca1bbdd41 100644
--- a/example/src/components/App/App.constants.ts
+++ b/example/src/components/App/App.constants.ts
@@ -4,4 +4,5 @@ export const routeIcon = {
[Route.Commerce]: 'cash-outline',
[Route.Inbox]: 'mail-outline',
[Route.User]: 'person-outline',
+ [Route.Utility]: 'build-outline',
};
diff --git a/example/src/components/App/Main.tsx b/example/src/components/App/Main.tsx
index 55b0d74e2..31190661d 100644
--- a/example/src/components/App/Main.tsx
+++ b/example/src/components/App/Main.tsx
@@ -8,6 +8,7 @@ import { User } from '../User';
import { Inbox } from '../Inbox';
import { useIterableApp } from '../../hooks';
import { Commerce } from '../Commerce';
+import { Utility } from '../Utility';
const Tab = createBottomTabNavigator();
@@ -58,6 +59,13 @@ export const Main = () => {
tabPress: () => setIsInboxTab(false),
})}
/>
+ ({
+ tabPress: () => setIsInboxTab(false),
+ })}
+ />
>
);
diff --git a/example/src/components/Utility/Utility.styles.ts b/example/src/components/Utility/Utility.styles.ts
new file mode 100644
index 000000000..0ec5c0cbc
--- /dev/null
+++ b/example/src/components/Utility/Utility.styles.ts
@@ -0,0 +1,17 @@
+import { StyleSheet, type TextStyle } from 'react-native';
+import { appNameSmall, buttonBlock, buttonText, container } from '../../constants';
+
+const text: TextStyle = {
+ textAlign: 'center',
+ marginBottom: 20,
+};
+
+const styles = StyleSheet.create({
+ appName: appNameSmall,
+ button:buttonBlock,
+ buttonText,
+ container,
+ text,
+});
+
+export default styles;
diff --git a/example/src/components/Utility/Utility.tsx b/example/src/components/Utility/Utility.tsx
new file mode 100644
index 000000000..218f8719a
--- /dev/null
+++ b/example/src/components/Utility/Utility.tsx
@@ -0,0 +1,76 @@
+import { Iterable, RNIterableAPI } from '@iterable/react-native-sdk';
+import { useEffect } from 'react';
+import { NativeEventEmitter, Text, TouchableOpacity, View } from 'react-native';
+
+import styles from './Utility.styles';
+
+const newEmitter = new NativeEventEmitter(RNIterableAPI);
+
+export const Utility = () => {
+ useEffect(() => {
+ console.log(`🚀 > RNIterableAPI:`, RNIterableAPI);
+
+ const newSub = newEmitter.addListener('onTestEventDispatch', (event) => {
+ console.log('*** ITBL JS *** RECEIVED onTestEventDispatch:', event);
+ });
+ return () => {
+ newSub.remove();
+ };
+ }, []);
+
+ return (
+
+ Utility
+ {
+ Iterable.getEmail().then((email) => {
+ console.log('Iterable.getEmail() --> email', email);
+ });
+ }}>
+ Iterable.getEmail()aeff
+
+ {
+ Iterable.getUserId().then((userId) => {
+ console.log('Iterable.getUserId() --> userId', userId);
+ });
+ }}>
+ Iterable.getUserId()
+
+ {
+ Iterable.getAttributionInfo().then((attributionInfo) => {
+ console.log('Iterable.getAttributionInfo() --> attributionInfo', attributionInfo);
+ });
+ }}>
+ Iterable.getAttributionInfo()
+
+ {
+ Iterable.setAttributionInfo({
+ campaignId: 123,
+ templateId: 456,
+ messageId: '789',
+ });
+ }}>
+ Iterable.setAttributionInfo()
+
+ {
+ Iterable.disableDeviceForCurrentUser();
+ }}>
+ Iterable.disableDeviceForCurrentUser()
+
+ {
+ Iterable.getLastPushPayload().then((lastPushPayload) => {
+ console.log('Iterable.getLastPushPayload() --> lastPushPayload', lastPushPayload);
+ });
+ }}>
+ Iterable.getLastPushPayload()
+
+ {
+ console.log('*** ITBL JS *** SENDING testEventDispatch');
+ RNIterableAPI.testEventDispatch();
+ }}>
+ dispatch test event
+
+
+ );
+};
+
+export default Utility;
diff --git a/example/src/components/Utility/index.ts b/example/src/components/Utility/index.ts
new file mode 100644
index 000000000..acc470409
--- /dev/null
+++ b/example/src/components/Utility/index.ts
@@ -0,0 +1,2 @@
+export * from './Utility';
+export { default } from './Utility';
diff --git a/example/src/constants/routes.ts b/example/src/constants/routes.ts
index 4af27c548..317ea5a2a 100644
--- a/example/src/constants/routes.ts
+++ b/example/src/constants/routes.ts
@@ -4,4 +4,5 @@ export enum Route {
Login = 'Login',
Main = 'Main',
User = 'User',
+ Utility = 'Utility',
}
diff --git a/example/src/hooks/useIterableApp.tsx b/example/src/hooks/useIterableApp.tsx
index ca115a48c..682db228f 100644
--- a/example/src/hooks/useIterableApp.tsx
+++ b/example/src/hooks/useIterableApp.tsx
@@ -4,6 +4,7 @@ import {
createContext,
useCallback,
useContext,
+ useEffect,
useState,
} from 'react';
import { Alert } from 'react-native';
@@ -14,10 +15,14 @@ import {
IterableConfig,
IterableInAppShowResponse,
IterableLogLevel,
+ RNIterableAPI,
} from '@iterable/react-native-sdk';
import { Route } from '../constants/routes';
import type { RootStackParamList } from '../types/navigation';
+import { NativeEventEmitter } from 'react-native';
+
+const RNEventEmitter = new NativeEventEmitter(RNIterableAPI);
type Navigation = StackNavigationProp;
@@ -99,6 +104,13 @@ export const IterableAppProvider: FunctionComponent<
const [userId, setUserId] = useState(process.env.ITBL_ID);
const [loginInProgress, setLoginInProgress] = useState(false);
+ useEffect(() => {
+ console.log('*** EXAMPLE JS SETTING EVENT LISTENER *** : receivedIterableInboxChanged', RNEventEmitter);
+ RNEventEmitter.addListener('receivedIterableInboxChanged', (event) => {
+ console.log('*** EXAMPLE JS EVENT RECEIVED *** : receivedIterableInboxChanged', event);
+ });
+ }, []);
+
const getUserId = useCallback(() => userId ?? process.env.ITBL_ID, [userId]);
const login = useCallback(() => {
diff --git a/example/src/types/navigation.ts b/example/src/types/navigation.ts
index 5b5ad8a50..6435adb0d 100644
--- a/example/src/types/navigation.ts
+++ b/example/src/types/navigation.ts
@@ -11,6 +11,7 @@ export type MainScreenParamList = {
[Route.Commerce]: undefined;
[Route.Inbox]: undefined;
[Route.User]: undefined;
+ [Route.Utility]: undefined;
};
export type RootStackParamList = {
diff --git a/ios/RNIterableAPI/RNIterableAPI.h b/ios/RNIterableAPI/RNIterableAPI.h
index 26bbf81fe..84c8c1d87 100644
--- a/ios/RNIterableAPI/RNIterableAPI.h
+++ b/ios/RNIterableAPI/RNIterableAPI.h
@@ -1,9 +1,18 @@
-//
-// RNIterableAPI.h
-// RNIterableAPI
-//
-// Created by Loren Posen on 6/11/25.
-// Copyright © 2025 Iterable. All rights reserved.
-//
+#import
+#import
+
+#if RCT_NEW_ARCH_ENABLED
+
+#import
+#import
+#import
+#import
+@interface RNIterableAPI : RCTEventEmitter
+
+#else
#import
+@interface RNIterableAPI : RCTEventEmitter
+
+#endif
+@end
diff --git a/ios/RNIterableAPI/RNIterableAPI.mm b/ios/RNIterableAPI/RNIterableAPI.mm
index dc40a6e12..a0dc695e5 100644
--- a/ios/RNIterableAPI/RNIterableAPI.mm
+++ b/ios/RNIterableAPI/RNIterableAPI.mm
@@ -1,139 +1,606 @@
-//
-// Created by Tapash Majumder on 3/19/20.
-// Copyright © 2020 Iterable. All rights reserved.
-//
#import "RNIterableAPI.h"
-@interface RCT_EXTERN_REMAP_MODULE(RNIterableAPI, ReactIterableAPI, NSObject)
+#if RCT_NEW_ARCH_ENABLED
+ #import "RNIterableAPISpec.h"
+#endif
-// MARK: - Native SDK Functions
+#import // umbrella (Objective-C) header
-RCT_EXTERN_METHOD(initializeWithApiKey: (nonnull NSString *) apiKey
- config: (nonnull NSDictionary *) config
- version: (nonnull NSString *) version
- resolver: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
+// Forward-declare the Swift protocols/enum used in the Swift header.
+@protocol IterableInAppDelegate;
+@protocol IterableCustomActionDelegate;
+@protocol IterableAuthDelegate;
+@protocol IterableURLDelegate;
+typedef NS_ENUM(NSInteger, InAppShowResponse) {
+ show = 0,
+ skip = 1,
+};
-RCT_EXTERN_METHOD(initialize2WithApiKey: (nonnull NSString *) apiKey
- config: (nonnull NSDictionary *) config
- apiEndPointOverride: (nonnull NSString *) apiEndPoint
- version: (nonnull NSString *) version
- resolver: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
+#import "Iterable_React_Native_SDK-Swift.h"
-RCT_EXTERN_METHOD(setEmail: (NSString *) email
- authToken: (NSString *) authToken)
-
-RCT_EXTERN_METHOD(getEmail: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-RCT_EXTERN_METHOD(setUserId: (NSString *) userId
- authToken: (NSString *) authToken)
-
-RCT_EXTERN_METHOD(getUserId: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-// MARK: - Iterable API Request Functions
-
-RCT_EXTERN_METHOD(disableDeviceForCurrentUser)
-
-RCT_EXTERN_METHOD(setInAppShowResponse: (nonnull NSNumber *) inAppShowResponse)
-
-RCT_EXTERN_METHOD(getLastPushPayload: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-RCT_EXTERN_METHOD(getAttributionInfo: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-RCT_EXTERN_METHOD(setAttributionInfo: (NSDictionary *) attributionInfo)
-
-RCT_EXTERN_METHOD(trackPushOpenWithCampaignId: (nonnull NSNumber *) campaignId
- templateId: (nonnull NSNumber *) templateId
- messageId: (nonnull NSString *) messageId
- appAlreadyRunning: (BOOL) appAlreadyRunning
- dataFields: (NSDictionary *) dataFields)
-
-RCT_EXTERN_METHOD(updateCart: (NSArray *) items)
-
-RCT_EXTERN_METHOD(trackPurchase: (nonnull NSNumber *) total
- items: (NSArray *) items
- dataFields: (NSDictionary *) dataFields)
-
-RCT_EXTERN_METHOD(trackInAppOpen: (NSString *) messageId
- location: (nonnull NSNumber *) location)
-
-RCT_EXTERN_METHOD(trackInAppClick: (nonnull NSString *) messageId
- location: (nonnull NSNumber *) location
- clickedUrl: (nonnull NSString *) clickedUrl)
-
-RCT_EXTERN_METHOD(trackInAppClose: (nonnull NSString *) messageId
- location: (nonnull NSNumber *) location
- source: (nonnull NSNumber *) source
- clickedUrl: (NSString *) clickedUrl)
-
-RCT_EXTERN_METHOD(inAppConsume: (nonnull NSString *) messageId
- location: (nonnull NSNumber *) location
- source: (nonnull NSNumber *) source)
-
-RCT_EXTERN_METHOD(trackEvent: (nonnull NSString *) name
- dataFields: (NSDictionary *) dataFields)
-
-RCT_EXTERN_METHOD(updateUser: (nonnull NSDictionary *) dataFields
- mergeNestedObjects: (BOOL) mergeNestedObjects)
-
-RCT_EXTERN_METHOD(updateEmail: (nonnull NSString *) email
- authToken: (NSString *) authToken)
-
-RCT_EXTERN_METHOD(handleAppLink: (nonnull NSString *) appLink
- resolver: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-RCT_EXTERN_METHOD(updateSubscriptions: (NSArray *) emailListIds
- unsubscribedChannelIds: (NSArray *) unsubscribedChannelIds
- unsubscribedMessageTypeIds: (NSArray *) unsubscribedMessageTypeIds
- subscribedMessageTypeIds: (NSArray *) subscribedMessageTypeIds
- campaignId: (nonnull NSNumber *) campaignId
- templateId: (nonnull NSNumber *) templateId)
-
-// MARK: - SDK In-App Manager Functions
-
-RCT_EXTERN_METHOD(getInAppMessages: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-RCT_EXTERN_METHOD(getHtmlInAppContentForMessage: (nonnull NSString *) messageId
- resolver: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-RCT_EXTERN_METHOD(getInboxMessages: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-RCT_EXTERN_METHOD(getUnreadInboxMessagesCount: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-RCT_EXTERN_METHOD(showMessage: (nonnull NSString *) messageId
- consume: (nonnull BOOL) consume
- resolver: (RCTPromiseResolveBlock) resolve
- rejecter: (RCTPromiseRejectBlock) reject)
-
-RCT_EXTERN_METHOD(removeMessage: (nonnull NSString *) messageId
- location: (nonnull NSNumber *) location
- source: (nonnull NSNumber *) source)
-
-RCT_EXTERN_METHOD(setReadForMessage: (nonnull NSString *) messageId
- read: (BOOL) read)
-
-RCT_EXTERN_METHOD(setAutoDisplayPaused: (BOOL) paused)
-
-// MARK: - SDK Inbox Session Tracking Functions
-
-RCT_EXTERN_METHOD(startSession: (nonnull NSArray *) visibleRows)
-
-RCT_EXTERN_METHOD(endSession)
-
-RCT_EXTERN_METHOD(updateVisibleRows: (nonnull NSArray *) visibleRows)
-
-// MARK: - SDK Auth Manager Functions
+@interface RNIterableAPI ()
+@end
-RCT_EXTERN_METHOD(passAlongAuthToken: (NSString *) authToken)
+@implementation RNIterableAPI {
+ ReactIterableAPI *_swiftAPI;
+}
+
+- (instancetype)init {
+ self = [super init];
+ if(self) {
+ // Option 2.B - Instantiate the Calculator and set the delegate
+ _swiftAPI = [ReactIterableAPI new];
+ _swiftAPI.delegate = self;
+ }
+ return self;
+}
+
+RCT_EXPORT_MODULE()
+
+// - (NSArray *)supportedEvents {
+// return [_swiftAPI supportedEvents];
+// }
+
+
+- (NSArray *)supportedEvents {
+ return [ReactIterableAPI supportedEvents];
+}
+
+- (void)sendEventWithName:(NSString * _Nonnull)name result:(double)result {
+ [self sendEventWithName:name body:@(result)];
+}
+
+#if RCT_NEW_ARCH_ENABLED
+- (void)startObserving {
+ NSLog(@"ReactNativeSdk startObserving");
+ [(ReactIterableAPI *)_swiftAPI startObserving];
+}
+
+- (void)stopObserving {
+ NSLog(@"ReactNativeSdk stopObserving");
+ [(ReactIterableAPI *)_swiftAPI stopObserving];
+}
+
+- (void)hello {
+ NSLog(@"Hello from Objective-C");
+ [(ReactIterableAPI *)_swiftAPI hello];
+}
+
+- (void)testEventDispatch {
+ NSLog(@"***ITBL OBJ-C*** testEventDispatch");
+ [_swiftAPI testEventDispatch];
+}
+
+- (void)initializeWithApiKey:(NSString *)apiKey
+ config:(NSDictionary *)config
+ version:(NSString *)version
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk initializeWithApiKey");
+ [_swiftAPI initializeWithApiKey:apiKey
+ config:config
+ version:version
+ resolver:resolve
+ rejecter:reject];
+}
+
+- (void)initialize2WithApiKey:(NSString *)apiKey
+ config:(NSDictionary *)config
+ version:(NSString *)version
+ apiEndPointOverride:(NSString *)apiEndPointOverride
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk initialize2WithApiKey");
+ [_swiftAPI initialize2WithApiKey:apiKey
+ config:config
+ apiEndPointOverride:apiEndPointOverride
+ version:version
+ resolver:resolve
+ rejecter:reject];
+}
+
+- (void)setEmail:(NSString * _Nullable)email
+ authToken:(NSString * _Nullable)authToken
+{
+ NSLog(@"ReactNativeSdk setEmail");
+ [_swiftAPI setEmail:email authToken:authToken];
+}
+
+- (void)getEmail:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk getEmail");
+ [_swiftAPI getEmail:resolve rejecter:reject];
+}
+
+- (void)setUserId:(NSString * _Nullable)userId
+ authToken:(NSString * _Nullable)authToken
+{
+ NSLog(@"ReactNativeSdk setUserId");
+ [_swiftAPI setUserId:userId authToken:authToken];
+}
+
+- (void)getUserId:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk getUserId");
+ [_swiftAPI getUserId:resolve rejecter:reject];
+}
+
+- (void)setInAppShowResponse:(NSNumber *)inAppShowResponse
+{
+ NSLog(@"ReactNativeSdk setInAppShowResponse");
+ [_swiftAPI setInAppShowResponse:inAppShowResponse];
+}
+
+- (void)getInAppMessages:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk getInAppMessages");
+ [_swiftAPI getInAppMessages:resolve rejecter:reject];
+}
+
+- (void)getInboxMessages:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk getInboxMessages");
+ [_swiftAPI getInboxMessages:resolve rejecter:reject];
+}
+
+// NOTE: This is not used anywhere on the JS side.
+- (void)getUnreadInboxMessagesCount:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk getUnreadInboxMessagesCount");
+ [_swiftAPI getUnreadInboxMessagesCount:resolve rejecter:reject];
+}
+
+- (void)showMessage:(NSString *)messageId
+ consume:(BOOL)consume
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk showMessage");
+ [_swiftAPI showMessage:messageId consume:consume resolver:resolve rejecter:reject];
+}
+
+- (void)removeMessage:(NSString *)messageId
+ location:(NSNumber *)location
+ source:(NSNumber *)source
+{
+ NSLog(@"ReactNativeSdk removeMessage");
+ [_swiftAPI removeMessage:messageId location:location source:source];
+}
+
+- (void)setReadForMessage:(NSString *)messageId
+ read:(BOOL)read
+{
+ NSLog(@"ReactNativeSdk setReadForMessage");
+ [_swiftAPI setReadForMessage:messageId read:read];
+}
+
+- (void)setAutoDisplayPaused:(BOOL)autoDisplayPaused
+{
+ NSLog(@"ReactNativeSdk setAutoDisplayPaused");
+ [_swiftAPI setAutoDisplayPaused:autoDisplayPaused];
+}
+
+- (void)trackEvent:(NSString *)name
+ dataFields:(NSDictionary *)dataFields
+{
+ NSLog(@"ReactNativeSdk trackEvent");
+ [_swiftAPI trackEvent:name dataFields:dataFields];
+}
+
+- (void)trackPushOpenWithCampaignId:(NSNumber *)campaignId
+ templateId:(NSNumber *)templateId
+ messageId:(NSString *)messageId
+ appAlreadyRunning:(BOOL)appAlreadyRunning
+ dataFields:(NSDictionary *)dataFields
+{
+ NSLog(@"ReactNativeSdk trackPushOpenWithCampaignId");
+ [_swiftAPI trackPushOpenWithCampaignId:campaignId templateId:templateId messageId:messageId appAlreadyRunning:appAlreadyRunning dataFields:dataFields];
+}
+
+- (void)trackInAppOpen:(NSString *)messageId
+ location:(NSNumber *)location
+{
+ NSLog(@"ReactNativeSdk trackInAppOpen");
+ [_swiftAPI trackInAppOpen:messageId location:location];
+}
+
+- (void)trackInAppClick:(NSString *)messageId
+ location:(NSNumber *)location
+ clickedUrl:(NSString *)clickedUrl
+{
+ NSLog(@"ReactNativeSdk trackInAppClick");
+ [_swiftAPI trackInAppClick:messageId location:location clickedUrl:clickedUrl];
+}
+
+- (void)trackInAppClose:(NSString *)messageId
+ location:(NSNumber *)location
+ source:(NSNumber *)source
+ clickedUrl:(NSString *)clickedUrl
+{
+ NSLog(@"ReactNativeSdk trackInAppClose");
+ [_swiftAPI trackInAppClose:messageId location:location source:source clickedUrl:clickedUrl];
+}
+
+- (void)inAppConsume:(NSString *)messageId
+ location:(NSNumber *)location
+ source:(NSNumber *)source
+{
+ NSLog(@"ReactNativeSdk inAppConsume");
+ [_swiftAPI inAppConsume:messageId location:location source:source];
+}
+
+- (void)updateCart:(NSArray *)items
+{
+ NSLog(@"ReactNativeSdk updateCart");
+ [_swiftAPI updateCart:items];
+}
+
+- (void)trackPurchase:(NSNumber *)total
+ items:(NSArray *)items
+ dataFields:(NSDictionary *)dataFields
+{
+ NSLog(@"ReactNativeSdk trackPurchase");
+ [_swiftAPI trackPurchase:total items:items dataFields:dataFields];
+}
+
+- (void)updateUser:(NSDictionary *)dataFields
+ mergeNestedObjects:(BOOL)mergeNestedObjects
+{
+ NSLog(@"ReactNativeSdk updateUser");
+ [_swiftAPI updateUser:dataFields mergeNestedObjects:mergeNestedObjects];
+}
+
+- (void)updateEmail:(NSString *)email
+ authToken:(NSString *)authToken
+{
+ NSLog(@"ReactNativeSdk updateEmail");
+ [_swiftAPI updateEmail:email authToken:authToken];
+}
+
+- (void)getAttributionInfo:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk getAttributionInfo");
+ [_swiftAPI getAttributionInfo:resolve rejecter:reject];
+}
+
+- (void)setAttributionInfo:(NSDictionary *)attributionInfo
+{
+ NSLog(@"ReactNativeSdk setAttributionInfo");
+ [_swiftAPI setAttributionInfo:attributionInfo];
+}
+
+- (void)disableDeviceForCurrentUser
+{
+ NSLog(@"ReactNativeSdk disableDeviceForCurrentUser");
+ [_swiftAPI disableDeviceForCurrentUser];
+}
+
+- (void)getLastPushPayload:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk getLastPushPayload");
+ [_swiftAPI getLastPushPayload:resolve rejecter:reject];
+}
+
+- (void)getHtmlInAppContentForMessage:(NSString *)messageId
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk getHtmlInAppContentForMessage");
+ [_swiftAPI getHtmlInAppContentForMessage:messageId resolver:resolve rejecter:reject];
+}
+
+- (void)handleAppLink:(NSString *)appLink
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+{
+ NSLog(@"ReactNativeSdk handleAppLink");
+ [_swiftAPI handleAppLink:appLink resolver:resolve rejecter:reject];
+}
+
+- (void)updateSubscriptions:(NSArray *)emailListIds
+ unsubscribedChannelIds:(NSArray *)unsubscribedChannelIds
+ unsubscribedMessageTypeIds:(NSArray *)unsubscribedMessageTypeIds
+ subscribedMessageTypeIds:(NSArray *)subscribedMessageTypeIds
+ campaignId:(NSNumber *)campaignId
+ templateId:(NSNumber *)templateId
+{
+ NSLog(@"ReactNativeSdk updateSubscriptions");
+ [_swiftAPI updateSubscriptions:emailListIds unsubscribedChannelIds:unsubscribedChannelIds unsubscribedMessageTypeIds:unsubscribedMessageTypeIds subscribedMessageTypeIds:subscribedMessageTypeIds campaignId:campaignId templateId:templateId];
+}
+
+- (void)startSession:(NSArray *)visibleRows
+{
+ NSLog(@"ReactNativeSdk startSession");
+ [_swiftAPI startSession:visibleRows];
+}
+
+- (void)endSession
+{
+ NSLog(@"ReactNativeSdk endSession");
+ [_swiftAPI endSession];
+}
+
+- (void)updateVisibleRows:(NSArray *)visibleRows
+{
+ NSLog(@"ReactNativeSdk updateVisibleRows");
+ [_swiftAPI updateVisibleRows:visibleRows];
+}
+
+- (void)passAlongAuthToken:(NSString *)authToken
+{
+ NSLog(@"ReactNativeSdk passAlongAuthToken");
+ [_swiftAPI passAlongAuthToken:authToken];
+}
+
+- (std::shared_ptr)getTurboModule:
+ (const facebook::react::ObjCTurboModule::InitParams &)params
+{
+ return std::make_shared(params);
+}
+#else
+
+RCT_EXPORT_METHOD(startObserving) {
+ NSLog(@"ReactNativeSdk startObserving");
+ [(ReactIterableAPI *)_swiftAPI startObserving];
+}
+
+RCT_EXPORT_METHOD(stopObserving) {
+ NSLog(@"ReactNativeSdk stopObserving");
+ [(ReactIterableAPI *)_swiftAPI stopObserving];
+}
+
+RCT_EXPORT_METHOD(hello) {
+ NSLog(@"***ITBL OBJ-C*** hello");
+ [_swiftAPI hello];
+}
+
+RCT_EXPORT_METHOD(testEventDispatch) {
+ NSLog(@"***ITBL OBJ-C*** testEventDispatch");
+ [_swiftAPI testEventDispatch];
+}
+
+RCT_EXPORT_METHOD(initializeWithApiKey:(NSString *)apiKey
+ config:(NSDictionary *)config
+ version:(NSString *)version
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk initializeWithApiKey");
+ [_swiftAPI initializeWithApiKey:apiKey
+ config:config
+ version:version
+ resolver:resolve
+ rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(initialize2WithApiKey:(NSString *)apiKey
+ config:(NSDictionary *)config
+ version:(NSString *)version
+ apiEndPointOverride:(NSString *)apiEndPointOverride
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk initialize2WithApiKey");
+ [_swiftAPI initialize2WithApiKey:apiKey
+ config:config
+ apiEndPointOverride:apiEndPointOverride
+ version:version
+ resolver:resolve
+ rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(setEmail:(NSString * _Nullable)email
+ authToken:(NSString * _Nullable)authToken) {
+ NSLog(@"ReactNativeSdk setEmail");
+ [_swiftAPI setEmail:email authToken:authToken];
+}
+
+RCT_EXPORT_METHOD(getEmail:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk getEmail");
+ [_swiftAPI getEmail:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(setUserId:(NSString * _Nullable)userId
+ authToken:(NSString * _Nullable)authToken) {
+ NSLog(@"ReactNativeSdk setUserId");
+ [_swiftAPI setUserId:userId authToken:authToken];
+}
+
+RCT_EXPORT_METHOD(getUserId:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk getUserId");
+ [_swiftAPI getUserId:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(setInAppShowResponse:(NSNumber *)inAppShowResponse) {
+ NSLog(@"ReactNativeSdk setInAppShowResponse");
+ [_swiftAPI setInAppShowResponse:inAppShowResponse];
+}
+
+RCT_EXPORT_METHOD(getInAppMessages:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk getInAppMessages");
+ [_swiftAPI getInAppMessages:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(getInboxMessages:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk getInboxMessages");
+ [_swiftAPI getInboxMessages:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(getUnreadInboxMessagesCount:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk getUnreadInboxMessagesCount");
+ [_swiftAPI getUnreadInboxMessagesCount:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(showMessage:(NSString *)messageId
+ consume:(BOOL)consume
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk showMessage");
+ [_swiftAPI showMessage:messageId consume:consume resolver:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(removeMessage:(NSString *)messageId
+ location:(NSNumber *)location
+ source:(NSNumber *)source) {
+ NSLog(@"ReactNativeSdk removeMessage");
+ [_swiftAPI removeMessage:messageId location:location source:source];
+}
+
+RCT_EXPORT_METHOD(setReadForMessage:(NSString *)messageId
+ read:(BOOL)read) {
+ NSLog(@"ReactNativeSdk setReadForMessage");
+ [_swiftAPI setReadForMessage:messageId read:read];
+}
+
+RCT_EXPORT_METHOD(setAutoDisplayPaused:(BOOL)autoDisplayPaused) {
+ NSLog(@"ReactNativeSdk setAutoDisplayPaused");
+ [_swiftAPI setAutoDisplayPaused:autoDisplayPaused];
+}
+
+RCT_EXPORT_METHOD(trackEvent:(NSString *)name
+ dataFields:(NSDictionary *)dataFields) {
+ NSLog(@"ReactNativeSdk trackEvent");
+ [_swiftAPI trackEvent:name dataFields:dataFields];
+}
+
+RCT_EXPORT_METHOD(trackPushOpenWithCampaignId:(NSNumber *)campaignId
+ templateId:(NSNumber *)templateId
+ messageId:(NSString *)messageId
+ appAlreadyRunning:(BOOL)appAlreadyRunning
+ dataFields:(NSDictionary *)dataFields) {
+ NSLog(@"ReactNativeSdk trackPushOpenWithCampaignId");
+ [_swiftAPI trackPushOpenWithCampaignId:campaignId templateId:templateId messageId:messageId appAlreadyRunning:appAlreadyRunning dataFields:dataFields];
+}
+
+RCT_EXPORT_METHOD(trackInAppOpen:(NSString *)messageId
+ location:(NSNumber *)location) {
+ NSLog(@"ReactNativeSdk trackInAppOpen");
+ [_swiftAPI trackInAppOpen:messageId location:location];
+}
+
+RCT_EXPORT_METHOD(trackInAppClick:(NSString *)messageId
+ location:(NSNumber *)location
+ clickedUrl:(NSString *)clickedUrl) {
+ NSLog(@"ReactNativeSdk trackInAppClick");
+ [_swiftAPI trackInAppClick:messageId location:location clickedUrl:clickedUrl];
+}
+
+RCT_EXPORT_METHOD(trackInAppClose:(NSString *)messageId
+ location:(NSNumber *)location
+ source:(NSNumber *)source
+ clickedUrl:(NSString *)clickedUrl) {
+ NSLog(@"ReactNativeSdk trackInAppClose");
+ [_swiftAPI trackInAppClose:messageId location:location source:source clickedUrl:clickedUrl];
+}
+
+RCT_EXPORT_METHOD(inAppConsume:(NSString *)messageId
+ location:(NSNumber *)location
+ source:(NSNumber *)source) {
+ NSLog(@"ReactNativeSdk inAppConsume");
+ [_swiftAPI inAppConsume:messageId location:location source:source];
+}
+
+RCT_EXPORT_METHOD(updateCart:(NSArray *)items) {
+ NSLog(@"ReactNativeSdk updateCart");
+ [_swiftAPI updateCart:items];
+}
+
+RCT_EXPORT_METHOD(trackPurchase:(NSNumber *)total
+ items:(NSArray *)items
+ dataFields:(NSDictionary *)dataFields) {
+ NSLog(@"ReactNativeSdk trackPurchase");
+ [_swiftAPI trackPurchase:total items:items dataFields:dataFields];
+}
+
+RCT_EXPORT_METHOD(updateUser:(NSDictionary *)dataFields
+ mergeNestedObjects:(BOOL)mergeNestedObjects) {
+ NSLog(@"ReactNativeSdk updateUser");
+ [_swiftAPI updateUser:dataFields mergeNestedObjects:mergeNestedObjects];
+}
+ RCT_EXPORT_METHOD(updateEmail:(NSString *)email
+ authToken:(NSString *)authToken) {
+ NSLog(@"ReactNativeSdk updateEmail");
+ [_swiftAPI updateEmail:email authToken:authToken];
+}
+
+RCT_EXPORT_METHOD(getAttributionInfo:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk getAttributionInfo");
+ [_swiftAPI getAttributionInfo:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(setAttributionInfo:(NSDictionary *)attributionInfo) {
+ NSLog(@"ReactNativeSdk setAttributionInfo");
+ [_swiftAPI setAttributionInfo:attributionInfo];
+}
+
+RCT_EXPORT_METHOD(disableDeviceForCurrentUser) {
+ NSLog(@"ReactNativeSdk disableDeviceForCurrentUser");
+ [_swiftAPI disableDeviceForCurrentUser];
+}
+
+RCT_EXPORT_METHOD(getLastPushPayload:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk getLastPushPayload");
+ [_swiftAPI getLastPushPayload:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(getHtmlInAppContentForMessage:(NSString *)messageId
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk getHtmlInAppContentForMessage");
+ [_swiftAPI getHtmlInAppContentForMessage:messageId resolver:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(handleAppLink:(NSString *)appLink
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject) {
+ NSLog(@"ReactNativeSdk handleAppLink");
+ [_swiftAPI handleAppLink:appLink resolver:resolve rejecter:reject];
+}
+
+RCT_EXPORT_METHOD(updateSubscriptions:(NSArray *)emailListIds
+ unsubscribedChannelIds:(NSArray *)unsubscribedChannelIds
+ unsubscribedMessageTypeIds:(NSArray *)unsubscribedMessageTypeIds
+ subscribedMessageTypeIds:(NSArray *)subscribedMessageTypeIds
+ campaignId:(NSNumber *)campaignId
+ templateId:(NSNumber *)templateId) {
+ NSLog(@"ReactNativeSdk updateSubscriptions");
+ [_swiftAPI updateSubscriptions:emailListIds unsubscribedChannelIds:unsubscribedChannelIds unsubscribedMessageTypeIds:unsubscribedMessageTypeIds subscribedMessageTypeIds:subscribedMessageTypeIds campaignId:campaignId templateId:templateId];
+}
+
+RCT_EXPORT_METHOD(startSession:(NSArray *)visibleRows) {
+ NSLog(@"ReactNativeSdk startSession");
+ [_swiftAPI startSession:visibleRows];
+}
+
+RCT_EXPORT_METHOD(endSession) {
+ NSLog(@"ReactNativeSdk endSession");
+ [_swiftAPI endSession];
+}
+
+RCT_EXPORT_METHOD(updateVisibleRows:(NSArray *)visibleRows) {
+ NSLog(@"ReactNativeSdk updateVisibleRows");
+ [_swiftAPI updateVisibleRows:visibleRows];
+}
+
+RCT_EXPORT_METHOD(passAlongAuthToken:(NSString *)authToken) {
+ NSLog(@"ReactNativeSdk passAlongAuthToken");
+ [_swiftAPI passAlongAuthToken:authToken];
+}
+
+#endif
@end
diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift
index 4db314f20..a1a135562 100644
--- a/ios/RNIterableAPI/ReactIterableAPI.swift
+++ b/ios/RNIterableAPI/ReactIterableAPI.swift
@@ -1,686 +1,700 @@
-//
-// Created by Tapash Majumder on 3/19/20.
-// Copyright © 2020 Iterable. All rights reserved.
-//
-
import Foundation
-
import IterableSDK
+import React
-@objc(ReactIterableAPI)
-class ReactIterableAPI: RCTEventEmitter {
- deinit {
- NotificationCenter.default.removeObserver(self)
- }
-
- // MARK: - React Native Functions
-
- @objc static override func moduleName() -> String! {
- return "RNIterableAPI"
- }
-
- override var methodQueue: DispatchQueue! {
- _methodQueue
- }
-
- @objc override static func requiresMainQueueSetup() -> Bool {
- false
- }
-
- enum EventName: String, CaseIterable {
- case handleUrlCalled
- case handleCustomActionCalled
- case handleInAppCalled
- case handleAuthCalled
- case receivedIterableInboxChanged
- case handleAuthSuccessCalled
- case handleAuthFailureCalled
- }
-
- override func supportedEvents() -> [String]! {
- var result = [String]()
-
- EventName.allCases.forEach {
- result.append($0.rawValue)
- }
-
- return result
- }
-
- override func startObserving() {
- ITBInfo()
-
- shouldEmit = true
- }
-
- override func stopObserving() {
- ITBInfo()
-
- shouldEmit = false
- }
-
- // MARK: - Native SDK Functions
-
- @objc(initializeWithApiKey:config:version:resolver:rejecter:)
- func initialize(apiKey: String,
- config configDict: [AnyHashable: Any],
- version: String,
- resolver: @escaping RCTPromiseResolveBlock,
- rejecter: @escaping RCTPromiseRejectBlock) {
- ITBInfo()
-
- initialize(withApiKey: apiKey,
- config: configDict,
- version: version,
- resolver: resolver,
- rejecter: rejecter)
- }
-
- @objc(initialize2WithApiKey:config:apiEndPointOverride:version:resolver:rejecter:)
- func initialize2(apiKey: String,
- config configDict: [AnyHashable: Any],
- version: String,
- apiEndPointOverride: String,
- resolver: @escaping RCTPromiseResolveBlock,
- rejecter: @escaping RCTPromiseRejectBlock) {
- ITBInfo()
-
- initialize(withApiKey: apiKey,
- config: configDict,
- version: version,
- apiEndPointOverride: apiEndPointOverride,
- resolver: resolver,
- rejecter: rejecter)
- }
-
- @objc(setEmail:)
- func set(email: String?) {
- ITBInfo()
-
- IterableAPI.email = email
- }
-
- @objc(setEmail:authToken:)
- func set(email: String?, authToken: String?) {
- ITBInfo()
-
- IterableAPI.setEmail(email, authToken)
- }
-
- @objc(getEmail:rejecter:)
- func getEmail(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- resolver(IterableAPI.email)
- }
-
- @objc(setUserId:)
- func set(userId: String?) {
- ITBInfo()
-
- IterableAPI.userId = userId
- }
-
- @objc(setUserId:authToken:)
- func set(userId: String?, authToken: String?) {
- ITBInfo()
-
- IterableAPI.setUserId(userId, authToken)
- }
-
- @objc(getUserId:rejecter:)
- func getUserId(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- resolver(IterableAPI.userId)
- }
-
- // MARK: - Iterable API Request Functions
-
- @objc(setInAppShowResponse:)
- func set(inAppShowResponse number: NSNumber) {
- ITBInfo()
-
- self.inAppShowResponse = InAppShowResponse.from(number: number)
-
- inAppHandlerSemaphore.signal()
- }
-
- @objc(disableDeviceForCurrentUser)
- func disableDeviceForCurrentUser() {
- ITBInfo()
-
- IterableAPI.disableDeviceForCurrentUser()
- }
-
- @objc(getLastPushPayload:rejecter:)
- func getLastPushPayload(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- resolver(IterableAPI.lastPushPayload)
- }
-
- @objc(getAttributionInfo:rejecter:)
- func getAttributionInfo(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- resolver(IterableAPI.attributionInfo.map(SerializationUtil.encodableToDictionary))
- }
-
- @objc(setAttributionInfo:)
- func set(attributionInfo dict: [AnyHashable: Any]?) {
- ITBInfo()
-
- guard let dict = dict else {
- IterableAPI.attributionInfo = nil
- return
- }
-
- IterableAPI.attributionInfo = SerializationUtil.dictionaryToDecodable(dict: dict)
- }
-
- @objc(trackPushOpenWithCampaignId:templateId:messageId:appAlreadyRunning:dataFields:)
- func trackPushOpen(campaignId: NSNumber,
- templateId: NSNumber?,
- messageId: String,
- appAlreadyRunning: Bool,
- dataFields: [AnyHashable: Any]?) {
- ITBInfo()
-
- IterableAPI.track(pushOpen: campaignId,
- templateId: templateId,
- messageId: messageId,
- appAlreadyRunning: appAlreadyRunning,
- dataFields: dataFields)
- }
-
- @objc(updateCart:)
- func updateCart(items: [[AnyHashable: Any]]) {
- ITBInfo()
-
- IterableAPI.updateCart(items: items.compactMap(CommerceItem.from(dict:)))
- }
-
- @objc(trackPurchase:items:dataFields:)
- func trackPurchase(total: NSNumber,
- items: [[AnyHashable: Any]],
- dataFields: [AnyHashable: Any]?) {
- ITBInfo()
-
- IterableAPI.track(purchase: total,
- items: items.compactMap(CommerceItem.from(dict:)),
- dataFields: dataFields)
- }
-
- @objc(trackInAppOpen:location:)
- func trackInAppOpen(messageId: String,
- location locationNumber: NSNumber) {
- ITBInfo()
-
- guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
- ITBError("Could not find message with id: \(messageId)")
- return
- }
-
- IterableAPI.track(inAppOpen: message, location: InAppLocation.from(number: locationNumber))
- }
-
- @objc(trackInAppClick:location:clickedUrl:)
- func trackInAppClick(messageId: String,
- location locationNumber: NSNumber,
- clickedUrl: String) {
- ITBInfo()
-
- guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
- ITBError("Could not find message with id: \(messageId)")
- return
- }
-
- IterableAPI.track(inAppClick: message, location: InAppLocation.from(number: locationNumber), clickedUrl: clickedUrl)
- }
-
- @objc(trackInAppClose:location:source:clickedUrl:)
- func trackInAppClose(messageId: String,
- location locationNumber: NSNumber,
- source sourceNumber: NSNumber,
- clickedUrl: String?) {
- ITBInfo()
-
- guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
- ITBError("Could not find message with id: \(messageId)")
- return
- }
-
- if let inAppCloseSource = InAppCloseSource.from(number: sourceNumber) {
- IterableAPI.track(inAppClose: message,
- location: InAppLocation.from(number: locationNumber),
- source: inAppCloseSource,
- clickedUrl: clickedUrl)
- } else {
- IterableAPI.track(inAppClose: message,
- location: InAppLocation.from(number: locationNumber),
- clickedUrl: clickedUrl)
- }
- }
-
- @objc(inAppConsume:location:source:)
- func inAppConsume(messageId: String,
- location locationNumber: NSNumber,
- source sourceNumber: NSNumber) {
- ITBInfo()
-
- guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
- ITBError("Could not find message with id: \(messageId)")
- return
- }
-
- if let inAppDeleteSource = InAppDeleteSource.from(number: sourceNumber) {
- IterableAPI.inAppConsume(message: message,
- location: InAppLocation.from(number: locationNumber),
- source: inAppDeleteSource)
- } else {
- IterableAPI.inAppConsume(message: message,
- location: InAppLocation.from(number: locationNumber))
- }
- }
-
- @objc(getHtmlInAppContentForMessage:resolver:rejecter:)
- func getHtmlInAppContent(messageId: String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
- ITBError("Could not find message with id: \(messageId)")
- rejecter("", "Could not find message with id: \(messageId)", nil)
- return
- }
-
- guard let content = message.content as? IterableHtmlInAppContent else {
- ITBError("Could not parse message content as HTML")
- rejecter("", "Could not parse message content as HTML", nil)
- return
- }
-
- resolver(content.toDict())
- }
-
- @objc(trackEvent:dataFields:)
- func trackEvent(name: String, dataFields: [AnyHashable: Any]?) {
- ITBInfo()
-
- IterableAPI.track(event: name, dataFields: dataFields)
- }
-
- @objc(updateUser:mergeNestedObjects:)
- func updateUser(dataFields: [AnyHashable: Any], mergeNestedObjects: Bool) {
- ITBInfo()
-
- IterableAPI.updateUser(dataFields, mergeNestedObjects: mergeNestedObjects)
- }
-
- @objc(updateEmail:authToken:)
- func updateEmail(email: String, with authToken: String?) {
- ITBInfo()
-
- if let authToken = authToken {
- IterableAPI.updateEmail(email, withToken: authToken, onSuccess: nil, onFailure: nil)
- } else {
- IterableAPI.updateEmail(email, onSuccess: nil, onFailure: nil)
- }
- }
-
- @objc(handleAppLink:resolver:rejecter:)
- func handle(appLink: String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- if let url = URL(string: appLink) {
- resolver(IterableAPI.handle(universalLink: url))
- } else {
- rejecter("", "invalid URL", nil)
- }
+@objc public protocol ReactIterableAPIDelegate {
+ func sendEvent(withName: String, body: Any?)
+}
+
+@objc public class ReactIterableAPI: RCTEventEmitter {
+ deinit {
+ NotificationCenter.default.removeObserver(self)
+ }
+
+ @objc public weak var delegate: ReactIterableAPIDelegate? = nil
+
+ @objc override public class func moduleName() -> String! {
+ return "RNIterableAPI"
+ }
+
+ override open var methodQueue: DispatchQueue! {
+ _methodQueue
+ }
+
+ @objc override static public func requiresMainQueueSetup() -> Bool {
+ false
+ }
+
+ enum EventName: String, CaseIterable {
+ case handleUrlCalled
+ case handleCustomActionCalled
+ case handleInAppCalled
+ case handleAuthCalled
+ case receivedIterableInboxChanged
+ case handleAuthSuccessCalled
+ case handleAuthFailureCalled
+ case onTestEventDispatch
+ }
+
+ @objc public static var supportedEvents: [String] {
+ return EventName.allCases.map(\.rawValue)
+ }
+
+ override public func startObserving() {
+ ITBInfo()
+
+ shouldEmit = true
+ }
+
+ override public func stopObserving() {
+ ITBInfo()
+
+ shouldEmit = false
+ }
+
+ // MARK: - Native SDK Functions
+
+ @objc public func hello() {
+ print("Hello from Swift Again")
+ }
+
+ @objc(initializeWithApiKey:config:version:resolver:rejecter:)
+ public func initializeWithApiKey(
+ apiKey: String,
+ config configDict: NSDictionary,
+ version: String,
+ resolver: @escaping RCTPromiseResolveBlock,
+ rejecter: @escaping RCTPromiseRejectBlock
+ ) {
+ NSLog("initializeWithApiKey called from swift")
+ ITBInfo()
+
+ initialize(
+ withApiKey: apiKey,
+ config: configDict,
+ version: version,
+ resolver: resolver,
+ rejecter: rejecter)
+ }
+
+ @objc(initialize2WithApiKey:config:apiEndPointOverride:version:resolver:rejecter:)
+ public func initialize2WithApiKey(
+ apiKey: String,
+ config configDict: NSDictionary,
+ version: String,
+ apiEndPointOverride: String,
+ resolver: @escaping RCTPromiseResolveBlock,
+ rejecter: @escaping RCTPromiseRejectBlock
+ ) {
+ ITBInfo()
+
+ initialize(
+ withApiKey: apiKey,
+ config: configDict,
+ version: version,
+ apiEndPointOverride: apiEndPointOverride,
+ resolver: resolver,
+ rejecter: rejecter)
+ }
+
+ @objc(setEmail:)
+ public func setEmail(email: String?) {
+ ITBInfo()
+ IterableAPI.email = email
+ }
+
+ @objc(setEmail:authToken:)
+ public func setEmail(email: String?, authToken: String?) {
+ ITBInfo()
+ IterableAPI.setEmail(email, authToken)
+ }
+
+ @objc(getEmail:rejecter:)
+ public func getEmail(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
+ ITBInfo()
+ resolver(IterableAPI.email)
+ }
+
+ @objc(setUserId:)
+ public func setUserId(userId: String?) {
+ ITBInfo()
+ IterableAPI.userId = userId
+ }
+
+ @objc(setUserId:authToken:)
+ public func setUserId(userId: String?, authToken: String?) {
+ ITBInfo()
+ IterableAPI.setUserId(userId, authToken)
+ }
+
+ @objc(getUserId:rejecter:)
+ public func getUserId(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
+ ITBInfo()
+ resolver(IterableAPI.userId)
+ }
+
+ // MARK: - Iterable API Request Functions
+
+ @objc(setInAppShowResponse:)
+ public func setInAppShowResponse(inAppShowResponse number: NSNumber) {
+ ITBInfo()
+ self.inAppShowResponse = InAppShowResponse.from(number: number)
+ inAppHandlerSemaphore.signal()
+ }
+
+ @objc(disableDeviceForCurrentUser)
+ public func disableDeviceForCurrentUser() {
+ ITBInfo()
+ IterableAPI.disableDeviceForCurrentUser()
+ }
+
+ @objc(getLastPushPayload:rejecter:)
+ public func getLastPushPayload(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock)
+ {
+ ITBInfo()
+ resolver(IterableAPI.lastPushPayload)
+ }
+
+ @objc(getAttributionInfo:rejecter:)
+ public func getAttributionInfo(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock)
+ {
+ ITBInfo()
+ resolver(IterableAPI.attributionInfo.map(SerializationUtil.encodableToDictionary))
+ }
+
+ @objc(setAttributionInfo:)
+ public func setAttributionInfo(attributionInfo dict: NSDictionary?) {
+ ITBInfo()
+ guard let dict = dict else {
+ IterableAPI.attributionInfo = nil
+ return
+ }
+ IterableAPI.attributionInfo = SerializationUtil.dictionaryToDecodable(
+ dict: dict as! [AnyHashable: Any])
+ }
+
+ @objc(trackPushOpenWithCampaignId:templateId:messageId:appAlreadyRunning:dataFields:)
+ public func trackPushOpenWithCampaignId(
+ campaignId: NSNumber,
+ templateId: NSNumber?,
+ messageId: String,
+ appAlreadyRunning: Bool,
+ dataFields: NSDictionary?
+ ) {
+ ITBInfo()
+ let swiftDict = dataFields as? [AnyHashable: Any]
+
+ IterableAPI.track(
+ pushOpen: campaignId,
+ templateId: templateId,
+ messageId: messageId,
+ appAlreadyRunning: appAlreadyRunning,
+ dataFields: swiftDict)
+ }
+
+ @objc(updateCart:)
+ public func updateCart(items: [[AnyHashable: Any]]) {
+ ITBInfo()
+ IterableAPI.updateCart(items: items.compactMap(CommerceItem.from(dict:)))
+ }
+
+ @objc(trackPurchase:items:dataFields:)
+ public func trackPurchase(
+ total: NSNumber,
+ items: [[AnyHashable: Any]],
+ dataFields: [AnyHashable: Any]?
+ ) {
+ ITBInfo()
+ IterableAPI.track(
+ purchase: total,
+ items: items.compactMap(CommerceItem.from(dict:)),
+ dataFields: dataFields)
+ }
+
+ @objc(trackInAppOpen:location:)
+ public func trackInAppOpen(
+ messageId: String,
+ location locationNumber: NSNumber
+ ) {
+ ITBInfo()
+ guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
+ ITBError("Could not find message with id: \(messageId)")
+ return
+ }
+ IterableAPI.track(inAppOpen: message, location: InAppLocation.from(number: locationNumber))
+ }
+
+ @objc(trackInAppClick:location:clickedUrl:)
+ public func trackInAppClick(
+ messageId: String,
+ location locationNumber: NSNumber,
+ clickedUrl: String
+ ) {
+ ITBInfo()
+ guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
+ ITBError("Could not find message with id: \(messageId)")
+ return
+ }
+ IterableAPI.track(
+ inAppClick: message, location: InAppLocation.from(number: locationNumber),
+ clickedUrl: clickedUrl)
+ }
+
+ @objc(trackInAppClose:location:source:clickedUrl:)
+ public func trackInAppClose(
+ messageId: String,
+ location locationNumber: NSNumber,
+ source sourceNumber: NSNumber,
+ clickedUrl: String?
+ ) {
+ ITBInfo()
+ guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
+ ITBError("Could not find message with id: \(messageId)")
+ return
+ }
+ if let inAppCloseSource = InAppCloseSource.from(number: sourceNumber) {
+ IterableAPI.track(
+ inAppClose: message,
+ location: InAppLocation.from(number: locationNumber),
+ source: inAppCloseSource,
+ clickedUrl: clickedUrl)
+ } else {
+ IterableAPI.track(
+ inAppClose: message,
+ location: InAppLocation.from(number: locationNumber),
+ clickedUrl: clickedUrl)
+ }
+ }
+
+ @objc(inAppConsume:location:source:)
+ public func inAppConsume(
+ messageId: String,
+ location locationNumber: NSNumber,
+ source sourceNumber: NSNumber
+ ) {
+ ITBInfo()
+ guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
+ ITBError("Could not find message with id: \(messageId)")
+ return
+ }
+ if let inAppDeleteSource = InAppDeleteSource.from(number: sourceNumber) {
+ IterableAPI.inAppConsume(
+ message: message,
+ location: InAppLocation.from(number: locationNumber),
+ source: inAppDeleteSource)
+ } else {
+ IterableAPI.inAppConsume(
+ message: message,
+ location: InAppLocation.from(number: locationNumber))
+ }
+ }
+
+ @objc(getHtmlInAppContentForMessage:resolver:rejecter:)
+ public func getHtmlInAppContent(
+ messageId: String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock
+ ) {
+ ITBInfo()
+ guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
+ ITBError("Could not find message with id: \(messageId)")
+ rejecter(
+ "", "Could not find message with id: \(messageId)",
+ NSError(domain: "", code: 0, userInfo: nil))
+ return
+ }
+ guard let content = message.content as? IterableHtmlInAppContent else {
+ ITBError("Could not parse message content as HTML")
+ rejecter(
+ "", "Could not parse message content as HTML", NSError(domain: "", code: 0, userInfo: nil))
+ return
+ }
+ resolver(content.toDict())
+ }
+
+ @objc(trackEvent:dataFields:)
+ public func trackEvent(name: String, dataFields: NSDictionary?) {
+ ITBInfo()
+
+ IterableAPI.track(event: name, dataFields: dataFields as? [AnyHashable: Any])
+ }
+
+ @objc(updateUser:mergeNestedObjects:)
+ public func updateUser(dataFields: NSDictionary, mergeNestedObjects: Bool) {
+ ITBInfo()
+ IterableAPI.updateUser(
+ (dataFields as? [AnyHashable: Any])!, mergeNestedObjects: mergeNestedObjects)
+ }
+
+ @objc(updateEmail:authToken:)
+ public func updateEmail(email: String, with authToken: String?) {
+ ITBInfo()
+ if let authToken = authToken {
+ IterableAPI.updateEmail(email, withToken: authToken, onSuccess: nil, onFailure: nil)
+ } else {
+ IterableAPI.updateEmail(email, onSuccess: nil, onFailure: nil)
+ }
+ }
+
+ @objc(handleAppLink:resolver:rejecter:)
+ public func handle(
+ appLink: String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock
+ ) {
+ ITBInfo()
+ if let url = URL(string: appLink) {
+ resolver(IterableAPI.handle(universalLink: url))
+ } else {
+ rejecter("", "invalid URL", NSError(domain: "", code: 0, userInfo: nil))
+ }
+ }
+
+ // MARK: - SDK In-App Manager Functions
+
+ @objc(getInAppMessages:rejecter:)
+ public func getInAppMessages(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
+ ITBInfo()
+ resolver(IterableAPI.inAppManager.getMessages().map { $0.toDict() })
+ }
+
+ @objc(getInboxMessages:rejecter:)
+ public func getInboxMessages(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
+ ITBInfo()
+ resolver(IterableAPI.inAppManager.getInboxMessages().map { $0.toDict() })
+ }
+
+ @objc(getUnreadInboxMessagesCount:rejecter:)
+ public func getUnreadInboxMessagesCount(
+ resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock
+ ) {
+ ITBInfo()
+ resolver(IterableAPI.inAppManager.getUnreadInboxMessagesCount())
+ }
+
+ @objc(showMessage:consume:resolver:rejecter:)
+ public func showMessage(
+ messageId: String, consume: Bool, resolver: @escaping RCTPromiseResolveBlock,
+ rejecter: RCTPromiseRejectBlock
+ ) {
+ ITBInfo()
+ guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
+ ITBError("Could not find message with id: \(messageId)")
+ return
+ }
+ IterableAPI.inAppManager.show(message: message, consume: consume) { (url) in
+ resolver(url.map({ $0.absoluteString }))
+ }
+ }
+
+ @objc(removeMessage:location:source:)
+ public func removeMessage(
+ messageId: String, location locationNumber: NSNumber, source sourceNumber: NSNumber
+ ) {
+ ITBInfo()
+ guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
+ ITBError("Could not find message with id: \(messageId)")
+ return
+ }
+ if let inAppDeleteSource = InAppDeleteSource.from(number: sourceNumber) {
+ IterableAPI.inAppManager.remove(
+ message: message,
+ location: InAppLocation.from(number: locationNumber),
+ source: inAppDeleteSource)
+ } else {
+ IterableAPI.inAppManager.remove(
+ message: message,
+ location: InAppLocation.from(number: locationNumber))
+ }
+ }
+
+ @objc(
+ updateSubscriptions:unsubscribedChannelIds:unsubscribedMessageTypeIds:subscribedMessageTypeIds:
+ campaignId:templateId:
+ )
+ public func updateSubscriptions(
+ emailListIds: [NSNumber]?,
+ unsubscribedChannelIds: [NSNumber]?,
+ unsubscribedMessageTypeIds: [NSNumber]?,
+ subscribedMessageTypeIds: [NSNumber]?,
+ campaignId: NSNumber,
+ templateId: NSNumber
+ ) {
+ ITBInfo()
+ let finalCampaignId: NSNumber? = campaignId.intValue <= 0 ? nil : campaignId
+ let finalTemplateId: NSNumber? = templateId.intValue <= 0 ? nil : templateId
+ IterableAPI.updateSubscriptions(
+ emailListIds,
+ unsubscribedChannelIds: unsubscribedChannelIds,
+ unsubscribedMessageTypeIds: unsubscribedMessageTypeIds,
+ subscribedMessageTypeIds: subscribedMessageTypeIds,
+ campaignId: finalCampaignId,
+ templateId: finalTemplateId)
+ }
+
+ @objc(setReadForMessage:read:)
+ public func setReadForMessage(for messageId: String, read: Bool) {
+ ITBInfo()
+ guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
+ ITBError("Could not find message with id: \(messageId)")
+ return
+ }
+ IterableAPI.inAppManager.set(read: read, forMessage: message)
+ }
+
+ @objc(setAutoDisplayPaused:)
+ public func setAutoDisplayPaused(autoDisplayPaused: Bool) {
+ ITBInfo()
+ DispatchQueue.main.async {
+ IterableAPI.inAppManager.isAutoDisplayPaused = autoDisplayPaused
}
-
- // MARK: - SDK In-App Manager Functions
-
- @objc(getInAppMessages:rejecter:)
- func getInAppMessages(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- resolver(IterableAPI.inAppManager.getMessages().map { $0.toDict() })
- }
-
- @objc(getInboxMessages:rejecter:)
- func getInboxMessages(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- resolver(IterableAPI.inAppManager.getInboxMessages().map{ $0.toDict() })
- }
-
- @objc(getUnreadInboxMessagesCount:rejecter:)
- func getUnreadInboxMessagesCount(resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- resolver(IterableAPI.inAppManager.getUnreadInboxMessagesCount())
- }
-
- @objc(showMessage:consume:resolver:rejecter:)
- func show(messageId: String, consume: Bool, resolver: @escaping RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
- ITBInfo()
-
- guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
- ITBError("Could not find message with id: \(messageId)")
- return
- }
-
- IterableAPI.inAppManager.show(message: message, consume: consume) { (url) in
- resolver(url.map({$0.absoluteString}))
- }
+ }
+
+ // MARK: - SDK Inbox Session Tracking Functions
+
+ @objc(startSession:)
+ public func startSession(visibleRows: [[AnyHashable: Any]]) {
+ let serializedRows = InboxImpressionTracker.RowInfo.rowInfos(from: visibleRows)
+ inboxSessionManager.startSession(visibleRows: serializedRows)
+ }
+
+ @objc(endSession)
+ public func endSession() {
+ guard let sessionInfo = inboxSessionManager.endSession() else {
+ ITBError("Could not find session info")
+ return
+ }
+ let inboxSession = IterableInboxSession(
+ id: sessionInfo.startInfo.id,
+ sessionStartTime: sessionInfo.startInfo.startTime,
+ sessionEndTime: Date(),
+ startTotalMessageCount: sessionInfo.startInfo.totalMessageCount,
+ startUnreadMessageCount: sessionInfo.startInfo.unreadMessageCount,
+ endTotalMessageCount: IterableAPI.inAppManager.getInboxMessages().count,
+ endUnreadMessageCount: IterableAPI.inAppManager.getUnreadInboxMessagesCount(),
+ impressions: sessionInfo.impressions.map { $0.toIterableInboxImpression() })
+ IterableAPI.track(inboxSession: inboxSession)
+ }
+
+ @objc(updateVisibleRows:)
+ public func updateVisibleRows(visibleRows: [[AnyHashable: Any]]) {
+ let serializedRows = InboxImpressionTracker.RowInfo.rowInfos(from: visibleRows)
+ inboxSessionManager.updateVisibleRows(visibleRows: serializedRows)
+ }
+
+ @objc(testEventDispatch)
+ public func testEventDispatch() {
+ NSLog("***ITBL SWIFT*** shouldEmit: \(shouldEmit)")
+ NSLog("***ITBL SWIFT*** testEventDispatch", EventName.onTestEventDispatch.rawValue)
+ delegate?.sendEvent(withName: EventName.onTestEventDispatch.rawValue, body: 0)
+ }
+
+ // MARK: - SDK Auth Manager Functions
+
+ @objc(passAlongAuthToken:)
+ public func passAlongAuthToken(authToken: String?) {
+ ITBInfo()
+ passedAuthToken = authToken
+ authHandlerSemaphore.signal()
+ }
+
+ // MARK: Private
+ private var shouldEmit = false
+ private let _methodQueue = DispatchQueue(label: String(describing: ReactIterableAPI.self))
+
+ // Handling in-app delegate
+ private var inAppShowResponse = InAppShowResponse.show
+ private var inAppHandlerSemaphore = DispatchSemaphore(value: 0)
+
+ private var passedAuthToken: String?
+ private var authHandlerSemaphore = DispatchSemaphore(value: 0)
+
+ private let inboxSessionManager = InboxSessionManager()
+
+ @objc func initialize(
+ withApiKey apiKey: String,
+ config configDict: NSDictionary,
+ version: String,
+ apiEndPointOverride: String? = nil,
+ resolver: @escaping RCTPromiseResolveBlock,
+ rejecter: @escaping RCTPromiseRejectBlock
+ ) {
+ ITBInfo()
+ let launchOptions = createLaunchOptions()
+ let iterableConfig = IterableConfig.from(
+ dict: configDict as? [AnyHashable : Any]
+ )
+
+ if let urlHandlerPresent = configDict["urlHandlerPresent"] as? Bool, urlHandlerPresent == true {
+ iterableConfig.urlDelegate = self
}
-
- @objc(removeMessage:location:source:)
- func remove(messageId: String, location locationNumber: NSNumber, source sourceNumber: NSNumber) {
- ITBInfo()
-
- guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
- ITBError("Could not find message with id: \(messageId)")
- return
- }
-
- if let inAppDeleteSource = InAppDeleteSource.from(number: sourceNumber) {
- IterableAPI.inAppManager.remove(message: message,
- location: InAppLocation.from(number: locationNumber),
- source: inAppDeleteSource)
- } else {
- IterableAPI.inAppManager.remove(message: message,
- location: InAppLocation.from(number: locationNumber))
- }
+
+ if let customActionHandlerPresent = configDict["customActionHandlerPresent"] as? Bool,
+ customActionHandlerPresent == true
+ {
+ iterableConfig.customActionDelegate = self
}
-
- @objc(updateSubscriptions:unsubscribedChannelIds:unsubscribedMessageTypeIds:subscribedMessageTypeIds:campaignId:templateId:)
- func updateSubscriptions(emailListIds: [NSNumber]?,
- unsubscribedChannelIds: [NSNumber]?,
- unsubscribedMessageTypeIds: [NSNumber]?,
- subscribedMessageTypeIds: [NSNumber]?,
- campaignId: NSNumber,
- templateId: NSNumber) {
- ITBInfo()
-
- let finalCampaignId: NSNumber? = campaignId.intValue <= 0 ? nil : campaignId
- let finalTemplateId: NSNumber? = templateId.intValue <= 0 ? nil : templateId
-
- IterableAPI.updateSubscriptions(emailListIds,
- unsubscribedChannelIds: unsubscribedChannelIds,
- unsubscribedMessageTypeIds: unsubscribedMessageTypeIds,
- subscribedMessageTypeIds: subscribedMessageTypeIds,
- campaignId: finalCampaignId,
- templateId: finalTemplateId)
- }
-
- @objc(setReadForMessage:read:)
- func setRead(for messageId: String, read: Bool) {
- ITBInfo()
-
- guard let message = IterableAPI.inAppManager.getMessage(withId: messageId) else {
- ITBError("Could not find message with id: \(messageId)")
- return
- }
-
- IterableAPI.inAppManager.set(read: read, forMessage: message)
- }
-
- @objc(setAutoDisplayPaused:)
- func set(autoDisplayPaused: Bool) {
- ITBInfo()
-
- DispatchQueue.main.async {
- IterableAPI.inAppManager.isAutoDisplayPaused = autoDisplayPaused
- }
+
+ if let inAppHandlerPresent = configDict["inAppHandlerPresent"] as? Bool,
+ inAppHandlerPresent == true
+ {
+ iterableConfig.inAppDelegate = self
}
-
- // MARK: - SDK Inbox Session Tracking Functions
-
- @objc(startSession:)
- func startSession(visibleRows: [[AnyHashable: Any]]) {
- let serializedRows = InboxImpressionTracker.RowInfo.rowInfos(from: visibleRows)
-
- inboxSessionManager.startSession(visibleRows: serializedRows)
- }
-
- @objc(endSession)
- func endSession() {
- guard let sessionInfo = inboxSessionManager.endSession() else {
- ITBError("Could not find session info")
- return
- }
-
- let inboxSession = IterableInboxSession(id: sessionInfo.startInfo.id,
- sessionStartTime: sessionInfo.startInfo.startTime,
- sessionEndTime: Date(),
- startTotalMessageCount: sessionInfo.startInfo.totalMessageCount,
- startUnreadMessageCount: sessionInfo.startInfo.unreadMessageCount,
- endTotalMessageCount: IterableAPI.inAppManager.getInboxMessages().count,
- endUnreadMessageCount: IterableAPI.inAppManager.getUnreadInboxMessagesCount(),
- impressions: sessionInfo.impressions.map { $0.toIterableInboxImpression() })
-
- IterableAPI.track(inboxSession: inboxSession)
- }
-
- @objc(updateVisibleRows:)
- func updateVisibleRows(visibleRows: [[AnyHashable: Any]]) {
- let serializedRows = InboxImpressionTracker.RowInfo.rowInfos(from: visibleRows)
-
- inboxSessionManager.updateVisibleRows(visibleRows: serializedRows)
- }
-
- // MARK: - SDK Auth Manager Functions
-
- @objc(passAlongAuthToken:)
- func passAlong(authToken: String?) {
- ITBInfo()
-
- passedAuthToken = authToken
-
- authHandlerSemaphore.signal()
- }
-
- // MARK: Private
- private var shouldEmit = false
- private let _methodQueue = DispatchQueue(label: String(describing: ReactIterableAPI.self))
-
- // Handling in-app delegate
- private var inAppShowResponse = InAppShowResponse.show
- private var inAppHandlerSemaphore = DispatchSemaphore(value: 0)
-
- private var passedAuthToken: String?
- private var authHandlerSemaphore = DispatchSemaphore(value: 0)
-
- private let inboxSessionManager = InboxSessionManager()
-
- private func initialize(withApiKey apiKey: String,
- config configDict: [AnyHashable: Any],
- version: String,
- apiEndPointOverride: String? = nil,
- resolver: @escaping RCTPromiseResolveBlock,
- rejecter: @escaping RCTPromiseRejectBlock) {
- ITBInfo()
-
- let launchOptions = createLaunchOptions()
- let iterableConfig = IterableConfig.from(dict: configDict)
-
- if let urlHandlerPresent = configDict["urlHandlerPresent"] as? Bool, urlHandlerPresent == true {
- iterableConfig.urlDelegate = self
- }
-
- if let customActionHandlerPresent = configDict["customActionHandlerPresent"] as? Bool, customActionHandlerPresent == true {
- iterableConfig.customActionDelegate = self
- }
-
- if let inAppHandlerPresent = configDict["inAppHandlerPresent"] as? Bool, inAppHandlerPresent == true {
- iterableConfig.inAppDelegate = self
- }
-
- if let authHandlerPresent = configDict["authHandlerPresent"] as? Bool, authHandlerPresent {
- iterableConfig.authDelegate = self
- }
-
- // connect new inbox in-app payloads to the RN SDK
- NotificationCenter.default.addObserver(self, selector: #selector(receivedIterableInboxChanged), name: Notification.Name.iterableInboxChanged, object: nil)
-
- DispatchQueue.main.async {
- IterableAPI.initialize2(apiKey: apiKey,
- launchOptions: launchOptions,
- config: iterableConfig,
- apiEndPointOverride: apiEndPointOverride) { result in
- resolver(result)
- }
-
- IterableAPI.setDeviceAttribute(name: "reactNativeSDKVersion", value: version)
- }
+
+ if let authHandlerPresent = configDict["authHandlerPresent"] as? Bool, authHandlerPresent {
+ iterableConfig.authDelegate = self
}
-
- @objc(receivedIterableInboxChanged)
- private func receivedIterableInboxChanged() {
- guard shouldEmit else {
- return
- }
-
- sendEvent(withName: EventName.receivedIterableInboxChanged.rawValue, body: nil)
+
+ // connect new inbox in-app payloads to the RN SDK
+ NotificationCenter.default.addObserver(
+ self, selector: #selector(receivedIterableInboxChanged),
+ name: Notification.Name.iterableInboxChanged, object: nil)
+
+ DispatchQueue.main.async {
+ IterableAPI.initialize2(
+ apiKey: apiKey,
+ launchOptions: launchOptions,
+ config: iterableConfig,
+ apiEndPointOverride: apiEndPointOverride
+ ) { result in
+ resolver(result)
+ }
+
+ IterableAPI.setDeviceAttribute(name: "reactNativeSDKVersion", value: version)
}
-
- private func createLaunchOptions() -> [UIApplication.LaunchOptionsKey: Any]? {
- guard let bridge = bridge else {
- return nil
- }
-
- return ReactIterableAPI.createLaunchOptions(bridgeLaunchOptions: bridge.launchOptions)
- }
-
- private static func createLaunchOptions(bridgeLaunchOptions: [AnyHashable: Any]?) -> [UIApplication.LaunchOptionsKey: Any]? {
- guard let bridgeLaunchOptions = bridgeLaunchOptions,
- let remoteNotification = bridgeLaunchOptions[UIApplication.LaunchOptionsKey.remoteNotification.rawValue] else {
- return nil
- }
-
- var result = [UIApplication.LaunchOptionsKey: Any]()
- result[UIApplication.LaunchOptionsKey.remoteNotification] = remoteNotification
-
- return result
+ }
+
+ @objc(receivedIterableInboxChanged)
+ func receivedIterableInboxChanged() {
+ guard shouldEmit else {
+ return
+ }
+ delegate?.sendEvent(
+ withName: EventName.receivedIterableInboxChanged.rawValue, body: nil as Any?)
+ }
+
+ private func createLaunchOptions() -> [UIApplication.LaunchOptionsKey: Any]? {
+ guard let bridge = self.bridge else {
+ return nil
}
+ return ReactIterableAPI.createLaunchOptions(bridgeLaunchOptions: bridge.launchOptions)
+ }
+
+ private static func createLaunchOptions(bridgeLaunchOptions: [AnyHashable: Any]?)
+ -> [UIApplication.LaunchOptionsKey: Any]?
+ {
+ guard let bridgeLaunchOptions = bridgeLaunchOptions,
+ let remoteNotification = bridgeLaunchOptions[
+ UIApplication.LaunchOptionsKey.remoteNotification.rawValue]
+ else {
+ return nil
+ }
+ var result = [UIApplication.LaunchOptionsKey: Any]()
+ result[UIApplication.LaunchOptionsKey.remoteNotification] = remoteNotification
+ return result
+ }
}
extension ReactIterableAPI: IterableURLDelegate {
- func handle(iterableURL url: URL, inContext context: IterableActionContext) -> Bool {
- ITBInfo()
-
- guard shouldEmit else {
- return false
- }
-
- let contextDict = ReactIterableAPI.contextToDictionary(context: context)
- sendEvent(withName: EventName.handleUrlCalled.rawValue,
- body: ["url": url.absoluteString,
- "context": contextDict] as [String : Any])
-
- return true
- }
-
- private static func contextToDictionary(context: IterableActionContext) -> [AnyHashable: Any] {
- var result = [AnyHashable: Any]()
-
- let actionDict = actionToDictionary(action: context.action)
- result["action"] = actionDict
- result["source"] = context.source.rawValue
-
- return result
- }
-
- private static func actionToDictionary(action: IterableAction) -> [AnyHashable: Any] {
- var actionDict = [AnyHashable: Any]()
-
- actionDict["type"] = action.type
-
- if let data = action.data {
- actionDict["data"] = data
- }
-
- if let userInput = action.userInput {
- actionDict["userInput"] = userInput
- }
-
- return actionDict
+ public func handle(iterableURL url: URL, inContext context: IterableActionContext) -> Bool {
+ ITBInfo()
+ guard shouldEmit else {
+ return false
+ }
+ let contextDict = ReactIterableAPI.contextToDictionary(context: context)
+ delegate?.sendEvent(
+ withName: EventName.handleUrlCalled.rawValue,
+ body: [
+ "url": url.absoluteString,
+ "context": contextDict,
+ ] as [String: Any])
+ return true
+ }
+
+ private static func contextToDictionary(context: IterableActionContext) -> [AnyHashable: Any] {
+ var result = [AnyHashable: Any]()
+ let actionDict = actionToDictionary(action: context.action)
+ result["action"] = actionDict
+ result["source"] = context.source.rawValue
+ return result
+ }
+
+ private static func actionToDictionary(action: IterableAction) -> [AnyHashable: Any] {
+ var actionDict = [AnyHashable: Any]()
+ actionDict["type"] = action.type
+ if let data = action.data {
+ actionDict["data"] = data
+ }
+ if let userInput = action.userInput {
+ actionDict["userInput"] = userInput
}
+ return actionDict
+ }
}
extension ReactIterableAPI: IterableCustomActionDelegate {
- func handle(iterableCustomAction action: IterableAction, inContext context: IterableActionContext) -> Bool {
- ITBInfo()
-
- let actionDict = ReactIterableAPI.actionToDictionary(action: action)
- let contextDict = ReactIterableAPI.contextToDictionary(context: context)
-
- sendEvent(withName: EventName.handleCustomActionCalled.rawValue,
- body: ["action": actionDict,
- "context": contextDict])
-
- return true
- }
+ public func handle(
+ iterableCustomAction action: IterableAction, inContext context: IterableActionContext
+ )
+ -> Bool
+ {
+ ITBInfo()
+ let actionDict = ReactIterableAPI.actionToDictionary(action: action)
+ let contextDict = ReactIterableAPI.contextToDictionary(context: context)
+ delegate?.sendEvent(
+ withName: EventName.handleCustomActionCalled.rawValue,
+ body: [
+ "action": actionDict,
+ "context": contextDict,
+ ])
+ return true
+ }
}
extension ReactIterableAPI: IterableInAppDelegate {
- func onNew(message: IterableInAppMessage) -> InAppShowResponse {
- ITBInfo()
-
- guard shouldEmit else {
- return .show
- }
-
- sendEvent(withName: EventName.handleInAppCalled.rawValue,
- body: message.toDict())
-
- let timeoutResult = inAppHandlerSemaphore.wait(timeout: .now() + 2.0)
-
- if timeoutResult == .success {
- ITBInfo("inAppShowResponse: \(inAppShowResponse == .show)")
- return inAppShowResponse
- } else {
- ITBInfo("timed out")
- return .show
- }
- }
+ public func onNew(message: IterableInAppMessage) -> InAppShowResponse {
+ ITBInfo()
+ guard shouldEmit else {
+ return .show
+ }
+ delegate?.sendEvent(
+ withName: EventName.handleInAppCalled.rawValue,
+ body: message.toDict())
+ let timeoutResult = inAppHandlerSemaphore.wait(timeout: .now() + 2.0)
+ if timeoutResult == .success {
+ ITBInfo("inAppShowResponse: \(inAppShowResponse == .show)")
+ return inAppShowResponse
+ } else {
+ ITBInfo("timed out")
+ return .show
+ }
+ }
}
extension ReactIterableAPI: IterableAuthDelegate {
- func onAuthTokenRequested(completion: @escaping AuthTokenRetrievalHandler) {
- ITBInfo()
-
- DispatchQueue.global(qos: .userInitiated).async {
- self.sendEvent(withName: EventName.handleAuthCalled.rawValue,
- body: nil)
-
- let authTokenRetrievalResult = self.authHandlerSemaphore.wait(timeout: .now() + 30.0)
-
- if authTokenRetrievalResult == .success {
- ITBInfo("authTokenRetrieval successful")
-
- DispatchQueue.main.async {
- completion(self.passedAuthToken)
- }
-
- self.sendEvent(withName: EventName.handleAuthSuccessCalled.rawValue,
- body: nil)
- } else {
- ITBInfo("authTokenRetrieval timed out")
-
- DispatchQueue.main.async {
- completion(nil)
- }
-
- self.sendEvent(withName: EventName.handleAuthFailureCalled.rawValue,
- body: nil)
- }
+ public func onAuthTokenRequested(completion: @escaping AuthTokenRetrievalHandler) {
+ ITBInfo()
+ DispatchQueue.global(qos: .userInitiated).async {
+ self.delegate?.sendEvent(
+ withName: EventName.handleAuthCalled.rawValue,
+ body: nil as Any?)
+ let authTokenRetrievalResult = self.authHandlerSemaphore.wait(timeout: .now() + 30.0)
+ if authTokenRetrievalResult == .success {
+ ITBInfo("authTokenRetrieval successful")
+ DispatchQueue.main.async {
+ completion(self.passedAuthToken)
}
+ self.delegate?.sendEvent(
+ withName: EventName.handleAuthSuccessCalled.rawValue,
+ body: nil as Any?)
+ } else {
+ ITBInfo("authTokenRetrieval timed out")
+ DispatchQueue.main.async {
+ completion(nil)
+ }
+ self.delegate?.sendEvent(
+ withName: EventName.handleAuthFailureCalled.rawValue,
+ body: nil as Any?)
+ }
}
-
- func onTokenRegistrationFailed(_ reason: String?) {
-
- }
+ }
+
+ public func onTokenRegistrationFailed(_ reason: String?) {
+ }
}
diff --git a/ios/RNIterableAPI/Serialization.swift b/ios/RNIterableAPI/Serialization.swift
index cb27026be..6ab712e39 100644
--- a/ios/RNIterableAPI/Serialization.swift
+++ b/ios/RNIterableAPI/Serialization.swift
@@ -11,26 +11,26 @@ struct SerializationUtil {
static func dateToInt(date: Date) -> Int {
Int(date.timeIntervalSince1970 * 1000)
}
-
+
static func intToDate(int: Int) -> Date {
let seconds = Double(int) / 1000.0 // ms -> seconds
-
+
return Date(timeIntervalSince1970: seconds)
}
-
+
static func encodableToDictionary(encodable: T) -> [String: Any]? where T: Encodable {
guard let data = try? JSONEncoder().encode(encodable) else {
return nil
}
-
+
return try? JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any]
}
-
+
static func dictionaryToDecodable(dict: [AnyHashable: Any]) -> T? where T: Decodable {
guard let data = try? JSONSerialization.data(withJSONObject: dict, options: []) else {
return nil
}
-
+
return try? JSONDecoder().decode(T.self, from: data)
}
}
@@ -38,15 +38,15 @@ struct SerializationUtil {
extension IterableConfig {
static func from(dict: [AnyHashable: Any]?) -> IterableConfig {
let config = IterableConfig()
-
+
guard let dict = dict else {
return config
}
-
+
if let allowedProtocols = dict["allowedProtocols"] as? [String] {
config.allowedProtocols = allowedProtocols
}
-
+
if let pushIntegrationName = dict["pushIntegrationName"] as? String {
config.pushIntegrationName = pushIntegrationName
}
@@ -61,15 +61,15 @@ extension IterableConfig {
config.pushPlatform = .auto
}
}
-
+
if let autoPushRegistration = dict["autoPushRegistration"] as? Bool {
config.autoPushRegistration = autoPushRegistration
}
-
+
if let inAppDisplayInterval = dict["inAppDisplayInterval"] as? Double {
config.inAppDisplayInterval = inAppDisplayInterval
}
-
+
if let expiringAuthTokenRefreshPeriod = dict["expiringAuthTokenRefreshPeriod"] as? TimeInterval {
config.expiringAuthTokenRefreshPeriod = expiringAuthTokenRefreshPeriod
}
@@ -77,7 +77,7 @@ extension IterableConfig {
if let logLevelNumber = dict["logLevel"] as? NSNumber {
config.logDelegate = createLogDelegate(logLevelNumber: logLevelNumber)
}
-
+
if let useInMemoryStorageForInApp = dict["useInMemoryStorageForInApps"] as? Bool {
config.useInMemoryStorageForInApps = useInMemoryStorageForInApp
}
@@ -92,11 +92,11 @@ extension IterableConfig {
config.dataRegion = IterableDataRegion.US
}
}
-
-
+
+
return config
}
-
+
private static func createLogDelegate(logLevelNumber: NSNumber) -> IterableLogDelegate {
DefaultLogDelegate(minLogLevel: LogLevel.from(number: logLevelNumber))
}
@@ -107,26 +107,26 @@ extension CommerceItem {
guard let id = dict["id"] as? String else {
return nil
}
-
+
guard let name = dict["name"] as? String else {
return nil
}
-
+
guard let price = dict["price"] as? NSNumber else {
return nil
}
-
+
guard let quantity = dict["quantity"] as? UInt else {
return nil
}
-
+
let sku = dict["sku"] as? String
let description = dict["description"] as? String
let url = dict["url"] as? String
let imageUrl = dict["imageUrl"] as? String
let categories = dict["categories"] as? [String]
let dataFields = dict["dataFields"] as? [AnyHashable: Any]
-
+
return CommerceItem(id: id,
name: name,
price: price,
@@ -182,7 +182,7 @@ extension IterableInAppMessage {
dict["customPayload"] = customPayload
dict["read"] = read
dict["priorityLevel"] = priorityLevel
-
+
return dict
}
}
@@ -212,7 +212,7 @@ extension InAppCloseSource {
guard let value = number as? Int else {
return nil
}
-
+
return InAppCloseSource(rawValue: value)
}
}
@@ -222,7 +222,7 @@ extension InAppDeleteSource {
guard let value = number as? Int else {
return nil
}
-
+
return InAppDeleteSource(rawValue: value)
}
}
@@ -252,16 +252,16 @@ extension InboxImpressionTracker.RowInfo {
guard let messageId = dict["messageId"] as? String else {
return nil
}
-
+
guard let silentInbox = dict["silentInbox"] as? Bool else {
return nil
}
-
+
let rowInfo = InboxImpressionTracker.RowInfo(messageId: messageId, silentInbox: silentInbox)
-
+
return rowInfo
}
-
+
static func rowInfos(from rows: [[AnyHashable: Any]]) -> [InboxImpressionTracker.RowInfo] {
return rows.compactMap(InboxImpressionTracker.RowInfo.from(dict:))
}
diff --git a/package.json b/package.json
index 4a8479336..de88c62cf 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,9 @@
"devDependencies": {
"@commitlint/config-conventional": "^19.6.0",
"@evilmartians/lefthook": "^1.5.0",
+ "@react-native-community/cli": "18.0.0",
+ "@react-native-community/cli-platform-android": "18.0.0",
+ "@react-native-community/cli-platform-ios": "18.0.0",
"@react-native/babel-preset": "0.79.3",
"@react-native/eslint-config": "0.79.3",
"@react-native/metro-config": "0.79.3",
@@ -177,11 +180,17 @@
]
},
"codegenConfig": {
- "name": "RNIterableSpec",
+ "name": "RNIterableAPISpec",
"type": "modules",
- "jsSrcsDir": "src",
+ "jsSrcsDir": "src/api/",
"android": {
"javaPackageName": "com.iterable.reactnative"
+ },
+ "ios": {
+ "modules": {
+ "RNIterableAPI": "RNIterableAPI",
+ "ReactIterableAPI": "ReactIterableAPI"
+ }
}
},
"create-react-native-library": {
diff --git a/src/api/NativeRNIterableAPI.ts b/src/api/NativeRNIterableAPI.ts
new file mode 100644
index 000000000..9c62a3934
--- /dev/null
+++ b/src/api/NativeRNIterableAPI.ts
@@ -0,0 +1,131 @@
+import type { TurboModule } from 'react-native';
+import { TurboModuleRegistry } from 'react-native';
+
+export interface Spec extends TurboModule {
+ // Initialization
+ initializeWithApiKey(
+ apiKey: string,
+ config: { [key: string]: string | number | boolean },
+ version: string
+ ): Promise;
+
+ initialize2WithApiKey(
+ apiKey: string,
+ config: { [key: string]: string | number | boolean },
+ apiEndPointOverride: string,
+ version: string
+ ): Promise;
+
+ // User management
+ setEmail(email: string | null, authToken?: string | null): void;
+ getEmail(): Promise;
+ setUserId(userId?: string | null, authToken?: string | null): void;
+ getUserId(): Promise;
+
+ // In-app messaging
+ setInAppShowResponse(number: number): void;
+ getInAppMessages(): Promise<{ [key: string]: string | number | boolean }[]>;
+ getInboxMessages(): Promise<{ [key: string]: string | number | boolean }[]>;
+ getUnreadInboxMessagesCount(): Promise;
+ showMessage(messageId: string, consume: boolean): Promise;
+ removeMessage(messageId: string, location: number, source: number): void;
+ setReadForMessage(messageId: string, read: boolean): void;
+ setAutoDisplayPaused(autoDisplayPaused: boolean): void;
+
+ // Tracking
+ trackEvent(
+ name: string,
+ dataFields?: { [key: string]: string | number | boolean }
+ ): void;
+ trackPushOpenWithCampaignId(
+ campaignId: number,
+ templateId: number | null,
+ messageId: string,
+ appAlreadyRunning: boolean,
+ dataFields?: { [key: string]: string | number | boolean }
+ ): void;
+ trackInAppOpen(messageId: string, location: number): void;
+ trackInAppClick(
+ messageId: string,
+ location: number,
+ clickedUrl: string
+ ): void;
+ trackInAppClose(
+ messageId: string,
+ location: number,
+ source: number,
+ clickedUrl?: string
+ ): void;
+ inAppConsume(messageId: string, location: number, source: number): void;
+
+ // Commerce
+ updateCart(items: { [key: string]: string | number | boolean }[]): void;
+ trackPurchase(
+ total: number,
+ items: { [key: string]: string | number | boolean }[],
+ dataFields?: { [key: string]: string | number | boolean }
+ ): void;
+
+ // User data
+ updateUser(
+ dataFields: { [key: string]: string | number | boolean },
+ mergeNestedObjects: boolean
+ ): void;
+ updateEmail(email: string, authToken?: string): void;
+
+ // Attribution
+ getAttributionInfo(): Promise<{
+ [key: string]: string | number | boolean;
+ } | null>;
+ setAttributionInfo(
+ dict: { [key: string]: string | number | boolean } | null
+ ): void;
+
+ // Device management
+ disableDeviceForCurrentUser(): void;
+ getLastPushPayload(): Promise<{
+ [key: string]: string | number | boolean;
+ } | null>;
+
+ // Content
+ getHtmlInAppContentForMessage(
+ messageId: string
+ ): Promise<{ [key: string]: string | number | boolean }>;
+
+ // App links
+ handleAppLink(appLink: string): Promise;
+
+ // Subscriptions
+ updateSubscriptions(
+ emailListIds: number[] | null,
+ unsubscribedChannelIds: number[] | null,
+ unsubscribedMessageTypeIds: number[] | null,
+ subscribedMessageTypeIds: number[] | null,
+ campaignId: number,
+ templateId: number
+ ): void;
+
+ // Session tracking
+ startSession(
+ visibleRows: { [key: string]: string | number | boolean }[]
+ ): void;
+ endSession(): void;
+ updateVisibleRows(
+ visibleRows: { [key: string]: string | number | boolean }[]
+ ): void;
+
+ // Auth
+ passAlongAuthToken(authToken: string | null): void;
+
+ // // Wake app -- android only
+ // wakeApp(): void;
+
+
+ // REQUIRED for RCTEventEmitter
+ addListener(eventName: string): void;
+ removeListeners(count: number): void;
+
+ // testing
+ testEventDispatch(): void;
+}
+export default TurboModuleRegistry.getEnforcing('RNIterableAPI');
diff --git a/src/api/index.ts b/src/api/index.ts
new file mode 100644
index 000000000..9c327891b
--- /dev/null
+++ b/src/api/index.ts
@@ -0,0 +1,6 @@
+import { NativeModules } from 'react-native';
+import BridgelessModule from './NativeRNIterableAPI';
+
+export const RNIterableAPI = BridgelessModule ?? NativeModules.RNIterableAPI;
+
+export default RNIterableAPI;
diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts
index 5b8b07fff..772373aa2 100644
--- a/src/core/classes/Iterable.ts
+++ b/src/core/classes/Iterable.ts
@@ -1,14 +1,14 @@
import {
Linking,
NativeEventEmitter,
- NativeModules,
- Platform,
+ Platform
} from 'react-native';
import { buildInfo } from '../../itblBuildInfo';
// TODO: Organize these so that there are no circular dependencies
// See https://github.com/expo/expo/issues/35100
+import { RNIterableAPI } from '../../api';
import { IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage';
import { IterableInAppCloseSource } from '../../inApp/enums/IterableInAppCloseSource';
import { IterableInAppDeleteSource } from '../../inApp/enums/IterableInAppDeleteSource';
@@ -23,7 +23,6 @@ import type { IterableCommerceItem } from './IterableCommerceItem';
import { IterableConfig } from './IterableConfig';
import { IterableLogger } from './IterableLogger';
-const RNIterableAPI = NativeModules.RNIterableAPI;
const RNEventEmitter = new NativeEventEmitter(RNIterableAPI);
/* eslint-disable tsdoc/syntax */
diff --git a/src/inApp/classes/IterableInAppManager.ts b/src/inApp/classes/IterableInAppManager.ts
index 640b99d50..0c7ac37cb 100644
--- a/src/inApp/classes/IterableInAppManager.ts
+++ b/src/inApp/classes/IterableInAppManager.ts
@@ -1,5 +1,5 @@
-import { NativeModules } from 'react-native';
+import { RNIterableAPI } from '../../api';
import { Iterable } from '../../core/classes/Iterable';
import type {
IterableInAppDeleteSource,
@@ -8,7 +8,6 @@ import type {
import { IterableHtmlInAppContent } from './IterableHtmlInAppContent';
import { IterableInAppMessage } from './IterableInAppMessage';
-const RNIterableAPI = NativeModules.RNIterableAPI;
/**
* Manages in-app messages for the current user.
diff --git a/src/inbox/classes/IterableInboxDataModel.ts b/src/inbox/classes/IterableInboxDataModel.ts
index fe5ce66fb..a664a1b90 100644
--- a/src/inbox/classes/IterableInboxDataModel.ts
+++ b/src/inbox/classes/IterableInboxDataModel.ts
@@ -1,5 +1,5 @@
-import { NativeModules } from 'react-native';
+import { RNIterableAPI } from '../../api';
import { Iterable } from '../../core/classes/Iterable';
import {
IterableHtmlInAppContent,
@@ -8,12 +8,12 @@ import {
IterableInAppMessage,
type IterableHtmlInAppContentRaw,
} from '../../inApp';
+
import type {
IterableInboxImpressionRowInfo,
IterableInboxRowViewModel,
} from '../types';
-const RNIterableAPI = NativeModules.RNIterableAPI;
/**
* The `IterableInboxDataModel` class provides methods to manage and manipulate
diff --git a/src/inbox/components/IterableInbox.tsx b/src/inbox/components/IterableInbox.tsx
index 545403e03..a9529af7b 100644
--- a/src/inbox/components/IterableInbox.tsx
+++ b/src/inbox/components/IterableInbox.tsx
@@ -3,14 +3,14 @@ import { useEffect, useState } from 'react';
import {
Animated,
NativeEventEmitter,
- NativeModules,
Platform,
StyleSheet,
Text,
- View,
+ View
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
+import { RNIterableAPI } from '../../api';
import { useAppStateListener, useDeviceOrientation } from '../../core';
// expo throws an error if this is not imported directly due to circular
// dependencies
@@ -32,7 +32,6 @@ import {
type IterableInboxMessageListProps,
} from './IterableInboxMessageList';
-const RNIterableAPI = NativeModules.RNIterableAPI;
const RNEventEmitter = new NativeEventEmitter(RNIterableAPI);
const DEFAULT_HEADLINE_HEIGHT = 60;
@@ -326,6 +325,7 @@ export const IterableInbox = ({
function addInboxChangedListener() {
RNEventEmitter.addListener('receivedIterableInboxChanged', () => {
+ console.log('*** ITBL JS *** receivedIterableInboxChanged');
fetchInboxMessages();
});
}
diff --git a/src/index.tsx b/src/index.tsx
index 885cd74bd..8dad4859a 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,6 +1,7 @@
/**
* React Native module for Iterable.
*/
+export { RNIterableAPI } from './api';
export {
Iterable,
IterableAction,
diff --git a/yarn.lock b/yarn.lock
index 241f48812..722390fcc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3107,6 +3107,9 @@ __metadata:
dependencies:
"@commitlint/config-conventional": ^19.6.0
"@evilmartians/lefthook": ^1.5.0
+ "@react-native-community/cli": 18.0.0
+ "@react-native-community/cli-platform-android": 18.0.0
+ "@react-native-community/cli-platform-ios": 18.0.0
"@react-native/babel-preset": 0.79.3
"@react-native/eslint-config": 0.79.3
"@react-native/metro-config": 0.79.3