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/example/android/app/build.gradle b/example/android/app/build.gradle index 060f74075..2f3fbe6df 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -77,9 +77,9 @@ android { buildToolsVersion rootProject.ext.buildToolsVersion compileSdk rootProject.ext.compileSdkVersion - namespace "iterable.reactnativesdk.example" + namespace "com.iterable.reactnativesdk.example" defaultConfig { - applicationId "iterable.reactnativesdk.example" + applicationId "com.iterable.reactnativesdk.example" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 diff --git a/example/android/app/src/main/java/iterable/reactnativesdk/example/MainActivity.kt b/example/android/app/src/main/java/iterable/reactnativesdk/example/MainActivity.kt index cab4dc8fe..ba3b1e7dd 100644 --- a/example/android/app/src/main/java/iterable/reactnativesdk/example/MainActivity.kt +++ b/example/android/app/src/main/java/iterable/reactnativesdk/example/MainActivity.kt @@ -1,4 +1,4 @@ -package iterable.reactnativesdk.example +package com.iterable.reactnativesdk.example import android.os.Bundle @@ -29,4 +29,4 @@ class MainActivity : ReactActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(null) } -} \ No newline at end of file +} diff --git a/example/android/app/src/main/java/iterable/reactnativesdk/example/MainApplication.kt b/example/android/app/src/main/java/iterable/reactnativesdk/example/MainApplication.kt index b9074aeb3..905040f6e 100644 --- a/example/android/app/src/main/java/iterable/reactnativesdk/example/MainApplication.kt +++ b/example/android/app/src/main/java/iterable/reactnativesdk/example/MainApplication.kt @@ -1,4 +1,4 @@ -package iterable.reactnativesdk.example +package com.iterable.reactnativesdk.example import android.app.Application import com.facebook.react.PackageList diff --git a/example/ios/Podfile b/example/ios/Podfile index 833bd46c8..46707dff0 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,4 +1,6 @@ -ENV['RCT_NEW_ARCH_ENABLED'] = '1' +require_relative '../node_modules/react-native/scripts/react_native_pods' + +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 +15,7 @@ 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 + use_frameworks! :linkage => :dynamic end target 'ReactNativeSdkExample' do @@ -35,3 +37,7 @@ target 'ReactNativeSdkExample' do ) end end + +target 'ReactNativeSdkExampleNotificationService' do + pod 'Iterable-iOS-AppExtensions' +end diff --git a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj index fe8e4b258..9f3d7f37f 100644 --- a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj @@ -8,11 +8,14 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* ReactNativeSdkExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */; }; + 046F661C66AD386E7EFEC566 /* libPods-ReactNativeSdkExampleNotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DA8EC87B8ACE8C5E0225D9D /* libPods-ReactNativeSdkExampleNotificationService.a */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 7783BEFA2E309B3C00E324D9 /* ReactNativeSdkExampleNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7783BEF32E309B3C00E324D9 /* ReactNativeSdkExampleNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 7783BF042E309D5C00E324D9 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7783BF012E309D5C00E324D9 /* NotificationService.swift */; }; 779227342DFA3FB500D69EC0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 779227332DFA3FB500D69EC0 /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; A3A40C20801B8F02005FA4C0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */; }; - BE277777A73E9108C50DE4EB /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 973DE20AE7D1571DDAC48802 /* libPods-ReactNativeSdkExample.a */; }; + FD43F1873F1819708052D24B /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9CEB5E2C8705D27F7D80E396 /* libPods-ReactNativeSdkExample.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -23,9 +26,31 @@ remoteGlobalIDString = 13B07F861A680F5B00A75B9A; remoteInfo = ReactNativeSdkExample; }; + 7783BEF82E309B3C00E324D9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7783BEF22E309B3C00E324D9; + remoteInfo = ReactNativeSdkExampleNotificationService; + }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 7783BEFF2E309B3C00E324D9 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 7783BEFA2E309B3C00E324D9 /* ReactNativeSdkExampleNotificationService.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 004EBA8EEDF0A79ED94D6C43 /* Pods-ReactNativeSdkExampleNotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExampleNotificationService.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExampleNotificationService/Pods-ReactNativeSdkExampleNotificationService.release.xcconfig"; sourceTree = ""; }; 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 = ""; }; 00E356F21AD99517003FC87E /* ReactNativeSdkExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactNativeSdkExampleTests.m; sourceTree = ""; }; @@ -33,14 +58,21 @@ 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 = ""; }; + 1DA8EC87B8ACE8C5E0225D9D /* libPods-ReactNativeSdkExampleNotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExampleNotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 319A77F763063B5643ADF8DD /* 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 = ""; }; + 30A3498081295FAAD64D1BBF /* Pods-ReactNativeSdkExampleNotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExampleNotificationService.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExampleNotificationService/Pods-ReactNativeSdkExampleNotificationService.debug.xcconfig"; sourceTree = ""; }; + 5FC564E2D39AFBF3B9F9FFF9 /* 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 = ""; }; + 7766E104B85973703DCC532D /* 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 = ""; }; + 7783BEEE2E309AE300E324D9 /* ReactNativeSdkExampleDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ReactNativeSdkExampleDebug.entitlements; path = ReactNativeSdkExample/ReactNativeSdkExampleDebug.entitlements; sourceTree = ""; }; + 7783BEF32E309B3C00E324D9 /* ReactNativeSdkExampleNotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ReactNativeSdkExampleNotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 7783BF002E309D5C00E324D9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7783BF012E309D5C00E324D9 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 7783BF052E30B7F100E324D9 /* ReactNativeSdkExampleRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ReactNativeSdkExampleRelease.entitlements; path = ReactNativeSdkExample/ReactNativeSdkExampleRelease.entitlements; 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 = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeSdkExample/LaunchScreen.storyboard; sourceTree = ""; }; - 8FE34013461CBFBDFF5ADF04 /* 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 = ""; }; - 973DE20AE7D1571DDAC48802 /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9CEB5E2C8705D27F7D80E396 /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -56,7 +88,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - BE277777A73E9108C50DE4EB /* libPods-ReactNativeSdkExample.a in Frameworks */, + FD43F1873F1819708052D24B /* libPods-ReactNativeSdkExample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7783BEF02E309B3C00E324D9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 046F661C66AD386E7EFEC566 /* libPods-ReactNativeSdkExampleNotificationService.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -83,6 +123,8 @@ 13B07FAE1A68108700A75B9A /* ReactNativeSdkExample */ = { isa = PBXGroup; children = ( + 7783BF052E30B7F100E324D9 /* ReactNativeSdkExampleRelease.entitlements */, + 7783BEEE2E309AE300E324D9 /* ReactNativeSdkExampleDebug.entitlements */, 13B07FB51A68108700A75B9A /* Images.xcassets */, 779227332DFA3FB500D69EC0 /* AppDelegate.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, @@ -99,11 +141,21 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 973DE20AE7D1571DDAC48802 /* libPods-ReactNativeSdkExample.a */, + 9CEB5E2C8705D27F7D80E396 /* libPods-ReactNativeSdkExample.a */, + 1DA8EC87B8ACE8C5E0225D9D /* libPods-ReactNativeSdkExampleNotificationService.a */, ); name = Frameworks; sourceTree = ""; }; + 7783BF022E309D5C00E324D9 /* ReactNativeSdkExampleNotificationService */ = { + isa = PBXGroup; + children = ( + 7783BF002E309D5C00E324D9 /* Info.plist */, + 7783BF012E309D5C00E324D9 /* NotificationService.swift */, + ); + path = ReactNativeSdkExampleNotificationService; + sourceTree = ""; + }; 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( @@ -117,6 +169,7 @@ 13B07FAE1A68108700A75B9A /* ReactNativeSdkExample */, 832341AE1AAA6A7D00B99B32 /* Libraries */, 00E356EF1AD99517003FC87E /* ReactNativeSdkExampleTests */, + 7783BF022E309D5C00E324D9 /* ReactNativeSdkExampleNotificationService */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, BBD78D7AC51CEA395F1C20DB /* Pods */, @@ -131,6 +184,7 @@ children = ( 13B07F961A680F5B00A75B9A /* ReactNativeSdkExample.app */, 00E356EE1AD99517003FC87E /* ReactNativeSdkExampleTests.xctest */, + 7783BEF32E309B3C00E324D9 /* ReactNativeSdkExampleNotificationService.appex */, ); name = Products; sourceTree = ""; @@ -138,8 +192,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 8FE34013461CBFBDFF5ADF04 /* Pods-ReactNativeSdkExample.debug.xcconfig */, - 319A77F763063B5643ADF8DD /* Pods-ReactNativeSdkExample.release.xcconfig */, + 7766E104B85973703DCC532D /* Pods-ReactNativeSdkExample.debug.xcconfig */, + 5FC564E2D39AFBF3B9F9FFF9 /* Pods-ReactNativeSdkExample.release.xcconfig */, + 30A3498081295FAAD64D1BBF /* Pods-ReactNativeSdkExampleNotificationService.debug.xcconfig */, + 004EBA8EEDF0A79ED94D6C43 /* Pods-ReactNativeSdkExampleNotificationService.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -169,29 +225,50 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeSdkExample" */; buildPhases = ( - 20B326C04992EEC006FD601B /* [CP] Check Pods Manifest.lock */, + C4AB8A23CDC992EAA87168C1 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - CDB95CBF6F1E1693CBFEC194 /* [CP] Embed Pods Frameworks */, - 84171D58128F84EC4ED7D163 /* [CP] Copy Pods Resources */, + 7783BEFF2E309B3C00E324D9 /* Embed Foundation Extensions */, + D262575156B37EB46EB92655 /* [CP] Embed Pods Frameworks */, + D61849AD8191C48A9FC61A4F /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( + 7783BEF92E309B3C00E324D9 /* PBXTargetDependency */, ); name = ReactNativeSdkExample; productName = ReactNativeSdkExample; productReference = 13B07F961A680F5B00A75B9A /* ReactNativeSdkExample.app */; productType = "com.apple.product-type.application"; }; + 7783BEF22E309B3C00E324D9 /* ReactNativeSdkExampleNotificationService */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7783BEFC2E309B3C00E324D9 /* Build configuration list for PBXNativeTarget "ReactNativeSdkExampleNotificationService" */; + buildPhases = ( + CC15338424B5F5EABA2EEF32 /* [CP] Check Pods Manifest.lock */, + 7783BEEF2E309B3C00E324D9 /* Sources */, + 7783BEF02E309B3C00E324D9 /* Frameworks */, + 7783BEF12E309B3C00E324D9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ReactNativeSdkExampleNotificationService; + productName = ReactNativeSdkExampleNotificationService; + productReference = 7783BEF32E309B3C00E324D9 /* ReactNativeSdkExampleNotificationService.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 +279,9 @@ 13B07F861A680F5B00A75B9A = { LastSwiftMigration = 1640; }; + 7783BEF22E309B3C00E324D9 = { + CreatedOnToolsVersion = 16.4; + }; }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNativeSdkExample" */; @@ -219,6 +299,7 @@ targets = ( 13B07F861A680F5B00A75B9A /* ReactNativeSdkExample */, 00E356ED1AD99517003FC87E /* ReactNativeSdkExampleTests */, + 7783BEF22E309B3C00E324D9 /* ReactNativeSdkExampleNotificationService */, ); }; /* End PBXProject section */ @@ -241,6 +322,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7783BEF12E309B3C00E324D9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -260,7 +348,7 @@ 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"; }; - 20B326C04992EEC006FD601B /* [CP] Check Pods Manifest.lock */ = { + C4AB8A23CDC992EAA87168C1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -282,24 +370,29 @@ 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; }; - 84171D58128F84EC4ED7D163 /* [CP] Copy Pods Resources */ = { + CC15338424B5F5EABA2EEF32 /* [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-ReactNativeSdkExampleNotificationService-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; }; - CDB95CBF6F1E1693CBFEC194 /* [CP] Embed Pods Frameworks */ = { + D262575156B37EB46EB92655 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -316,6 +409,23 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + D61849AD8191C48A9FC61A4F /* [CP] Copy Pods Resources */ = { + 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"; + outputFileListPaths = ( + "${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-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -335,6 +445,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7783BEEF2E309B3C00E324D9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7783BF042E309D5C00E324D9 /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -343,6 +461,11 @@ target = 13B07F861A680F5B00A75B9A /* ReactNativeSdkExample */; targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; }; + 7783BEF92E309B3C00E324D9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7783BEF22E309B3C00E324D9 /* ReactNativeSdkExampleNotificationService */; + targetProxy = 7783BEF82E309B3C00E324D9 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -351,6 +474,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; DEVELOPMENT_TEAM = BP98Z28R86; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -368,7 +492,7 @@ "-lc++", "$(inherited)", ); - PRODUCT_BUNDLE_IDENTIFIER = iterable.reactnativesdk.example; + PRODUCT_BUNDLE_IDENTIFIER = com.iterable.reactnativesdk.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "ReactNativeSdkExampleTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -382,6 +506,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; COPY_PHASE_STRIP = NO; DEVELOPMENT_TEAM = BP98Z28R86; INFOPLIST_FILE = ReactNativeSdkExampleTests/Info.plist; @@ -396,7 +521,7 @@ "-lc++", "$(inherited)", ); - PRODUCT_BUNDLE_IDENTIFIER = iterable.reactnativesdk.example; + PRODUCT_BUNDLE_IDENTIFIER = com.iterable.reactnativesdk.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "ReactNativeSdkExampleTests-Bridging-Header.h"; SWIFT_VERSION = 6.0; @@ -406,10 +531,11 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8FE34013461CBFBDFF5ADF04 /* Pods-ReactNativeSdkExample.debug.xcconfig */; + baseConfigurationReference = 7766E104B85973703DCC532D /* Pods-ReactNativeSdkExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = ReactNativeSdkExample/ReactNativeSdkExampleDebug.entitlements; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = BP98Z28R86; ENABLE_BITCODE = NO; @@ -425,7 +551,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 +562,11 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 319A77F763063B5643ADF8DD /* Pods-ReactNativeSdkExample.release.xcconfig */; + baseConfigurationReference = 5FC564E2D39AFBF3B9F9FFF9 /* Pods-ReactNativeSdkExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = ReactNativeSdkExample/ReactNativeSdkExampleRelease.entitlements; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = BP98Z28R86; INFOPLIST_FILE = ReactNativeSdkExample/Info.plist; @@ -454,7 +581,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 +589,89 @@ }; name = Release; }; + 7783BEFD2E309B3C00E324D9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 30A3498081295FAAD64D1BBF /* Pods-ReactNativeSdkExampleNotificationService.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 = ReactNativeSdkExampleNotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ReactNativeSdkExampleNotificationService; + 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.ReactNativeSdkExampleNotificationService; + 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; + }; + 7783BEFE2E309B3C00E324D9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 004EBA8EEDF0A79ED94D6C43 /* Pods-ReactNativeSdkExampleNotificationService.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 = ReactNativeSdkExampleNotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ReactNativeSdkExampleNotificationService; + 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.ReactNativeSdkExampleNotificationService; + 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 +853,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 7783BEFC2E309B3C00E324D9 /* Build configuration list for PBXNativeTarget "ReactNativeSdkExampleNotificationService" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7783BEFD2E309B3C00E324D9 /* Debug */, + 7783BEFE2E309B3C00E324D9 /* 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 f48c2a9a4..71b87eca3 100644 --- a/example/ios/ReactNativeSdkExample/Info.plist +++ b/example/ios/ReactNativeSdkExample/Info.plist @@ -38,7 +38,7 @@ NSLocationWhenInUseUsageDescription RCTNewArchEnabled - + UIAppFonts AntDesign.ttf @@ -61,6 +61,10 @@ Zocial.ttf Fontisto.ttf + UIBackgroundModes + + remote-notification + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/example/ios/ReactNativeSdkExample/ReactNativeSdkExampleDebug.entitlements b/example/ios/ReactNativeSdkExample/ReactNativeSdkExampleDebug.entitlements new file mode 100644 index 000000000..31c7e997a --- /dev/null +++ b/example/ios/ReactNativeSdkExample/ReactNativeSdkExampleDebug.entitlements @@ -0,0 +1,10 @@ + + + + + aps-environment + development + com.apple.developer.usernotifications.time-sensitive + + + diff --git a/example/ios/ReactNativeSdkExample/ReactNativeSdkExampleRelease.entitlements b/example/ios/ReactNativeSdkExample/ReactNativeSdkExampleRelease.entitlements new file mode 100644 index 000000000..31c7e997a --- /dev/null +++ b/example/ios/ReactNativeSdkExample/ReactNativeSdkExampleRelease.entitlements @@ -0,0 +1,10 @@ + + + + + aps-environment + development + com.apple.developer.usernotifications.time-sensitive + + + diff --git a/example/ios/ReactNativeSdkExampleNotificationService/Info.plist b/example/ios/ReactNativeSdkExampleNotificationService/Info.plist new file mode 100644 index 000000000..9a7b20000 --- /dev/null +++ b/example/ios/ReactNativeSdkExampleNotificationService/Info.plist @@ -0,0 +1,15 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + RCTNewArchEnabled + + + diff --git a/example/ios/ReactNativeSdkExampleNotificationService/NotificationService.swift b/example/ios/ReactNativeSdkExampleNotificationService/NotificationService.swift new file mode 100644 index 000000000..01bf86fb5 --- /dev/null +++ b/example/ios/ReactNativeSdkExampleNotificationService/NotificationService.swift @@ -0,0 +1,4 @@ +import IterableAppExtensions +import UserNotifications + +class NotificationService: ITBNotificationServiceExtension {} 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..b1d879f21 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..1d8471477 --- /dev/null +++ b/example/src/components/Utility/Utility.tsx @@ -0,0 +1,77 @@ +import { Iterable, api } from '@iterable/react-native-sdk'; +import { useEffect } from 'react'; +import { NativeEventEmitter, NativeModules, Text, TouchableOpacity, View } from 'react-native'; + +import styles from './Utility.styles'; + +const { RNIterableAPI } = NativeModules; +const newEmitter = new NativeEventEmitter(api); + +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'); + api.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..6da594db1 100644 --- a/example/src/hooks/useIterableApp.tsx +++ b/example/src/hooks/useIterableApp.tsx @@ -4,7 +4,7 @@ import { createContext, useCallback, useContext, - useState, + useState } from 'react'; import { Alert } from 'react-native'; @@ -13,7 +13,7 @@ import { IterableAction, IterableConfig, IterableInAppShowResponse, - IterableLogLevel, + IterableLogLevel } from '@iterable/react-native-sdk'; import { Route } from '../constants/routes'; 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..fe86dadf9 100644 --- a/ios/RNIterableAPI/RNIterableAPI.h +++ b/ios/RNIterableAPI/RNIterableAPI.h @@ -1,9 +1,28 @@ -// -// RNIterableAPI.h -// RNIterableAPI -// -// Created by Loren Posen on 6/11/25. -// Copyright © 2025 Iterable. All rights reserved. -// +// #if RCT_NEW_ARCH_ENABLED +// #import +// #import +// @interface RNIterableAPI : RCTEventEmitter +// @end +// #else +// #import +// #import +// @interface RNIterableAPI : RCTEventEmitter +// @end +// #endif + +#import +#import + +#if RCT_NEW_ARCH_ENABLED + +#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..6d1ce21b3 100644 --- a/ios/RNIterableAPI/RNIterableAPI.mm +++ b/ios/RNIterableAPI/RNIterableAPI.mm @@ -1,139 +1,293 @@ -// -// 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" + +@interface RNIterableAPI () +@end -RCT_EXTERN_METHOD(setEmail: (NSString *) email - authToken: (NSString *) authToken) +@implementation RNIterableAPI { + ReactIterableAPI *_swiftAPI; +} -RCT_EXTERN_METHOD(getEmail: (RCTPromiseResolveBlock) resolve - rejecter: (RCTPromiseRejectBlock) reject) +#pragma mark - Init / bridge wiring -RCT_EXTERN_METHOD(setUserId: (NSString *) userId - authToken: (NSString *) authToken) +- (instancetype)init { + self = [super init]; + if (self) { + _swiftAPI = [ReactIterableAPI new]; + _swiftAPI.delegate = self; + } + return self; +} -RCT_EXTERN_METHOD(getUserId: (RCTPromiseResolveBlock) resolve - rejecter: (RCTPromiseRejectBlock) reject) +RCT_EXPORT_MODULE() -// MARK: - Iterable API Request Functions ++ (BOOL)requiresMainQueueSetup { + return NO; +} -RCT_EXTERN_METHOD(disableDeviceForCurrentUser) +#pragma mark - TurboModule hook (new arch only) -RCT_EXTERN_METHOD(setInAppShowResponse: (nonnull NSNumber *) inAppShowResponse) +#if RCT_NEW_ARCH_ENABLED +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params { + return std::make_shared(params); +} +#endif -RCT_EXTERN_METHOD(getLastPushPayload: (RCTPromiseResolveBlock) resolve - rejecter: (RCTPromiseRejectBlock) reject) +// Visual Studio, LLVM, Google, Chromium, Mozilla, WebKit, Microsoft, GNU -RCT_EXTERN_METHOD(getAttributionInfo: (RCTPromiseResolveBlock) resolve - rejecter: (RCTPromiseRejectBlock) reject) +#pragma mark - Events -RCT_EXTERN_METHOD(setAttributionInfo: (NSDictionary *) attributionInfo) +- (NSArray *)supportedEvents { + return [ReactIterableAPI supportedEvents]; +} -RCT_EXTERN_METHOD(trackPushOpenWithCampaignId: (nonnull NSNumber *) campaignId +- (void)sendEventWithName:(NSString *_Nonnull)name result:(double)result { + [self sendEventWithName:name body:@(result)]; +} + +#pragma mark - Public API (all paths call into Swift) + +RCT_EXPORT_METHOD(testEventDispatch) { [_swiftAPI testEventDispatch]; } + +RCT_EXPORT_METHOD(initializeWithApiKey: (nonnull NSString *) apiKey + config: (nonnull NSDictionary *) config + version: (nonnull NSString *) version + resolver: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI initializeWithApiKey:apiKey + config:config + version:version + resolver:resolve + rejecter:reject]; +} + +RCT_EXPORT_METHOD(initialize2WithApiKey: (nonnull NSString *) apiKey + config: (nonnull NSDictionary *) config + apiEndPointOverride: (nonnull NSString *) apiEndPoint + version: (nonnull NSString *) version + resolver: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI initialize2WithApiKey:apiKey + config:config + apiEndPointOverride:apiEndPoint + version:version + resolver:resolve + rejecter:reject]; +} + +RCT_EXPORT_METHOD(setEmail: (NSString *) email + authToken: (NSString *) authToken) { + [_swiftAPI setEmail:email authToken:authToken]; +} + +RCT_EXPORT_METHOD(getEmail: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI getEmail:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(setUserId: (NSString *) userId + authToken: (NSString *) authToken) { + [_swiftAPI setUserId:userId authToken:authToken]; +} + +RCT_EXPORT_METHOD(getUserId: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI getUserId:resolve rejecter:reject]; +} + +#pragma mark - Iterable API Request Functions + +RCT_EXPORT_METHOD(disableDeviceForCurrentUser) { + [_swiftAPI disableDeviceForCurrentUser]; +} + +RCT_EXPORT_METHOD(setInAppShowResponse: (nonnull NSNumber *) inAppShowResponse) { + [_swiftAPI setInAppShowResponse:inAppShowResponse]; +} + +RCT_EXPORT_METHOD(getLastPushPayload: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI getLastPushPayload:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(getAttributionInfo : (RCTPromiseResolveBlock) + resolve reject : (RCTPromiseRejectBlock)reject) { + [_swiftAPI getAttributionInfo:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(setAttributionInfo: (NSDictionary *) attributionInfo) { + [_swiftAPI setAttributionInfo:attributionInfo]; +} + +RCT_EXPORT_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 + dataFields: (NSDictionary *) dataFields) { + [_swiftAPI trackPushOpenWithCampaignId:campaignId + templateId:templateId + messageId:messageId + appAlreadyRunning:appAlreadyRunning + dataFields:dataFields]; +} + +RCT_EXPORT_METHOD(updateCart: (NSArray *) items) { + [_swiftAPI updateCart:items]; +} + +RCT_EXPORT_METHOD(trackPurchase: (nonnull NSNumber *) total items: (NSArray *) items - dataFields: (NSDictionary *) dataFields) + dataFields: (NSDictionary *) dataFields) { + [_swiftAPI trackPurchase:total items:items dataFields:dataFields]; +} -RCT_EXTERN_METHOD(trackInAppOpen: (NSString *) messageId - location: (nonnull NSNumber *) location) +RCT_EXPORT_METHOD(trackInAppOpen: (NSString *) messageId + location: (nonnull NSNumber *) location) { + [_swiftAPI trackInAppOpen:messageId location:location]; +} -RCT_EXTERN_METHOD(trackInAppClick: (nonnull NSString *) messageId +RCT_EXPORT_METHOD(trackInAppClick: (nonnull NSString *) messageId location: (nonnull NSNumber *) location - clickedUrl: (nonnull NSString *) clickedUrl) + clickedUrl: (nonnull NSString *) clickedUrl) { + [_swiftAPI trackInAppClick:messageId location:location clickedUrl:clickedUrl]; +} -RCT_EXTERN_METHOD(trackInAppClose: (nonnull NSString *) messageId +RCT_EXPORT_METHOD(trackInAppClose: (nonnull NSString *) messageId location: (nonnull NSNumber *) location source: (nonnull NSNumber *) source - clickedUrl: (NSString *) clickedUrl) - -RCT_EXTERN_METHOD(inAppConsume: (nonnull NSString *) messageId + clickedUrl: (NSString *) clickedUrl) { + [_swiftAPI trackInAppClose:messageId + location:location + source:source + clickedUrl:clickedUrl]; +} + +RCT_EXPORT_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 + source: (nonnull NSNumber *) source) { + [_swiftAPI inAppConsume:messageId location:location source:source]; +} + +RCT_EXPORT_METHOD(trackEvent: (nonnull NSString *) name + dataFields: (NSDictionary *) dataFields) { + [_swiftAPI trackEvent:name dataFields:dataFields]; +} + +RCT_EXPORT_METHOD(updateUser: (nonnull NSDictionary *) dataFields + mergeNestedObjects: (BOOL) mergeNestedObjects) { + [_swiftAPI updateUser:dataFields mergeNestedObjects:mergeNestedObjects]; +} + +RCT_EXPORT_METHOD(updateEmail: (nonnull NSString *) email + authToken: (NSString *) authToken) { + [_swiftAPI updateEmail:email authToken:authToken]; +} + +RCT_EXPORT_METHOD(handleAppLink: (nonnull NSString *) appLink resolver: (RCTPromiseResolveBlock) resolve - rejecter: (RCTPromiseRejectBlock) reject) + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI handleAppLink:appLink resolver:resolve rejecter:reject]; +} -RCT_EXTERN_METHOD(updateSubscriptions: (NSArray *) emailListIds +RCT_EXPORT_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 + templateId: (nonnull NSNumber *) templateId) { + [_swiftAPI updateSubscriptions:emailListIds + unsubscribedChannelIds:unsubscribedChannelIds + unsubscribedMessageTypeIds:unsubscribedMessageTypeIds + subscribedMessageTypeIds:subscribedMessageTypeIds + campaignId:campaignId + templateId:templateId]; +} + +#pragma mark - SDK In-App Manager Functions + +RCT_EXPORT_METHOD(getInAppMessages: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI getInAppMessages:resolve rejecter:reject]; +} + +RCT_EXPORT_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 + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI getHtmlInAppContentForMessage:messageId + resolver:resolve + rejecter:reject]; +} + +RCT_EXPORT_METHOD(getInboxMessages: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI getInboxMessages:resolve rejecter:reject]; +} + +// NOTE: This is not used anywhere on the JS side. +RCT_EXPORT_METHOD(getUnreadInboxMessagesCount: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI getUnreadInboxMessagesCount:resolve rejecter:reject]; +} + +RCT_EXPORT_METHOD(showMessage: (nonnull NSString *) messageId + consume: (BOOL) consume resolver: (RCTPromiseResolveBlock) resolve - rejecter: (RCTPromiseRejectBlock) reject) - -RCT_EXTERN_METHOD(removeMessage: (nonnull NSString *) messageId + rejecter: (RCTPromiseRejectBlock) reject) { + [_swiftAPI showMessage:messageId + consume:consume + resolver:resolve + rejecter:reject]; +} + +RCT_EXPORT_METHOD(removeMessage: (nonnull NSString *) messageId location: (nonnull NSNumber *) location - source: (nonnull NSNumber *) source) + source: (nonnull NSNumber *) source) { + [_swiftAPI removeMessage:messageId location:location source:source]; +} -RCT_EXTERN_METHOD(setReadForMessage: (nonnull NSString *) messageId - read: (BOOL) read) +RCT_EXPORT_METHOD(setReadForMessage: (nonnull NSString *) messageId + read: (BOOL) read) { + [_swiftAPI setReadForMessage:messageId read:read]; +} -RCT_EXTERN_METHOD(setAutoDisplayPaused: (BOOL) paused) +RCT_EXPORT_METHOD(setAutoDisplayPaused: (BOOL) paused) { + [_swiftAPI setAutoDisplayPaused:paused]; +} -// MARK: - SDK Inbox Session Tracking Functions +#pragma mark - SDK Inbox Session Tracking Functions -RCT_EXTERN_METHOD(startSession: (nonnull NSArray *) visibleRows) +RCT_EXPORT_METHOD(startSession: (nonnull NSArray *) visibleRows) { + [_swiftAPI startSession:visibleRows]; +} -RCT_EXTERN_METHOD(endSession) +RCT_EXPORT_METHOD(endSession) { [_swiftAPI endSession]; } -RCT_EXTERN_METHOD(updateVisibleRows: (nonnull NSArray *) visibleRows) +RCT_EXPORT_METHOD(updateVisibleRows: (nonnull NSArray *) visibleRows) { + [_swiftAPI updateVisibleRows:visibleRows]; +} -// MARK: - SDK Auth Manager Functions +#pragma mark - SDK Auth Manager Functions -RCT_EXTERN_METHOD(passAlongAuthToken: (NSString *) authToken) +RCT_EXPORT_METHOD(passAlongAuthToken: (NSString *) authToken) { + [_swiftAPI passAlongAuthToken:authToken]; +} @end diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index 4db314f20..80b9bca9b 100644 --- a/ios/RNIterableAPI/ReactIterableAPI.swift +++ b/ios/RNIterableAPI/ReactIterableAPI.swift @@ -1,686 +1,694 @@ -// -// Created by Tapash Majumder on 3/19/20. -// Copyright © 2020 Iterable. All rights reserved. -// - import Foundation - import IterableSDK +import React + +@objc public protocol ReactIterableAPIDelegate { + func sendEvent(withName: String, body: Any?) +} @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) - } +public class ReactIterableAPI: RCTEventEmitter { + deinit { + NotificationCenter.default.removeObserver(self) + } + + @objc public weak var delegate: ReactIterableAPIDelegate? = nil + + // MARK: - React Native Functions + + @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(initializeWithApiKey:config:version:resolver:rejecter:) + public func initializeWithApiKey( + apiKey: String, + config configDict: NSDictionary, + 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:) + 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() { + + 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) + + 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.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.sendEvent( + withName: EventName.handleAuthSuccessCalled.rawValue, + body: nil as Any?) + } else { + ITBInfo("authTokenRetrieval timed out") + DispatchQueue.main.async { + completion(nil) + } + self.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..039f13a50 100644 --- a/ios/RNIterableAPI/Serialization.swift +++ b/ios/RNIterableAPI/Serialization.swift @@ -4,265 +4,266 @@ // import Foundation - import IterableSDK 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 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 } - - 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) + + 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) + } } 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 - } - - if let pushPlatform = dict["pushPlatform"] as? NSNumber { - switch pushPlatform { - case 0: - config.pushPlatform = .sandbox - case 1: - config.pushPlatform = .production - default: - 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 - } - - if let logLevelNumber = dict["logLevel"] as? NSNumber { - config.logDelegate = createLogDelegate(logLevelNumber: logLevelNumber) - } - - if let useInMemoryStorageForInApp = dict["useInMemoryStorageForInApps"] as? Bool { - config.useInMemoryStorageForInApps = useInMemoryStorageForInApp - } - - if let dataRegion = dict["dataRegion"] as? NSNumber { - switch dataRegion { - case 0: - config.dataRegion = IterableDataRegion.US - case 1: - config.dataRegion = IterableDataRegion.EU - default: - config.dataRegion = IterableDataRegion.US - } - } - - - return config + static func from(dict: NSDictionary?) -> IterableConfig { + let config = IterableConfig() + + guard let dict = dict else { + return config } - - private static func createLogDelegate(logLevelNumber: NSNumber) -> IterableLogDelegate { - DefaultLogDelegate(minLogLevel: LogLevel.from(number: logLevelNumber)) + + if let allowedProtocols = dict["allowedProtocols"] as? [String] { + config.allowedProtocols = allowedProtocols + } + + if let pushIntegrationName = dict["pushIntegrationName"] as? String { + config.pushIntegrationName = pushIntegrationName + } + + if let pushPlatform = dict["pushPlatform"] as? NSNumber { + switch pushPlatform { + case 0: + config.pushPlatform = .sandbox + case 1: + config.pushPlatform = .production + default: + 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 + } + + if let logLevelNumber = dict["logLevel"] as? NSNumber { + config.logDelegate = createLogDelegate(logLevelNumber: logLevelNumber) + } + + if let useInMemoryStorageForInApp = dict["useInMemoryStorageForInApps"] as? Bool { + config.useInMemoryStorageForInApps = useInMemoryStorageForInApp } + + if let dataRegion = dict["dataRegion"] as? NSNumber { + switch dataRegion { + case 0: + config.dataRegion = IterableDataRegion.US + case 1: + config.dataRegion = IterableDataRegion.EU + default: + config.dataRegion = IterableDataRegion.US + } + } + + return config + } + + private static func createLogDelegate(logLevelNumber: NSNumber) -> IterableLogDelegate { + DefaultLogDelegate(minLogLevel: LogLevel.from(number: logLevelNumber)) + } } extension CommerceItem { - static func from(dict: [AnyHashable: Any]) -> 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, - quantity: quantity, - sku: sku, - description: description, - url: url, - imageUrl: imageUrl, - categories: categories, - dataFields: dataFields) + static func from(dict: [AnyHashable: Any]) -> 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, + quantity: quantity, + sku: sku, + description: description, + url: url, + imageUrl: imageUrl, + categories: categories, + dataFields: dataFields) + } } extension IterableInAppTrigger { - func toDict() -> [AnyHashable: Any] { - var dict = [AnyHashable: Any]() - dict["type"] = self.type.rawValue - return dict - } + func toDict() -> [AnyHashable: Any] { + var dict = [AnyHashable: Any]() + dict["type"] = self.type.rawValue + return dict + } } extension UIEdgeInsets { - func toDict() -> [AnyHashable: Any] { - var dict = [AnyHashable: Any]() - dict["top"] = top - dict["left"] = left - dict["bottom"] = bottom - dict["right"] = right - return dict - } + func toDict() -> [AnyHashable: Any] { + var dict = [AnyHashable: Any]() + dict["top"] = top + dict["left"] = left + dict["bottom"] = bottom + dict["right"] = right + return dict + } } extension IterableInboxMetadata { - func toDict() -> [AnyHashable: Any]? { - var dict = [AnyHashable: Any]() - dict["title"] = title - dict["subtitle"] = subtitle - dict["icon"] = icon - return dict - } + func toDict() -> [AnyHashable: Any]? { + var dict = [AnyHashable: Any]() + dict["title"] = title + dict["subtitle"] = subtitle + dict["icon"] = icon + return dict + } } extension IterableInAppMessage { - func toDict() -> [AnyHashable: Any] { - var dict = [AnyHashable: Any]() - dict["messageId"] = messageId - dict["campaignId"] = campaignId - dict["trigger"] = trigger.toDict() - dict["createdAt"] = createdAt.map (SerializationUtil.dateToInt(date:)) - dict["expiresAt"] = expiresAt.map (SerializationUtil.dateToInt(date:)) - dict["saveToInbox"] = saveToInbox - dict["inboxMetadata"] = inboxMetadata?.toDict() ?? nil - dict["customPayload"] = customPayload - dict["read"] = read - dict["priorityLevel"] = priorityLevel - - return dict - } + func toDict() -> [AnyHashable: Any] { + var dict = [AnyHashable: Any]() + dict["messageId"] = messageId + dict["campaignId"] = campaignId + dict["trigger"] = trigger.toDict() + dict["createdAt"] = createdAt.map(SerializationUtil.dateToInt(date:)) + dict["expiresAt"] = expiresAt.map(SerializationUtil.dateToInt(date:)) + dict["saveToInbox"] = saveToInbox + dict["inboxMetadata"] = inboxMetadata?.toDict() ?? nil + dict["customPayload"] = customPayload + dict["read"] = read + dict["priorityLevel"] = priorityLevel + + return dict + } } extension IterableHtmlInAppContent { - func toDict() -> [AnyHashable: Any] { - var dict = [AnyHashable: Any]() - dict["type"] = type.rawValue - dict["edgeInsets"] = edgeInsets.toDict() - dict["html"] = html - return dict - } + func toDict() -> [AnyHashable: Any] { + var dict = [AnyHashable: Any]() + dict["type"] = type.rawValue + dict["edgeInsets"] = edgeInsets.toDict() + dict["html"] = html + return dict + } } extension InAppLocation { - static func from(number: NSNumber) -> InAppLocation { - if let value = number as? Int { - return InAppLocation(rawValue: value) ?? .inApp - } else { - return .inApp - } + static func from(number: NSNumber) -> InAppLocation { + if let value = number as? Int { + return InAppLocation(rawValue: value) ?? .inApp + } else { + return .inApp } + } } extension InAppCloseSource { - static func from(number: NSNumber) -> InAppCloseSource? { - guard let value = number as? Int else { - return nil - } - - return InAppCloseSource(rawValue: value) + static func from(number: NSNumber) -> InAppCloseSource? { + guard let value = number as? Int else { + return nil } + + return InAppCloseSource(rawValue: value) + } } extension InAppDeleteSource { - static func from(number: NSNumber) -> InAppDeleteSource? { - guard let value = number as? Int else { - return nil - } - - return InAppDeleteSource(rawValue: value) + static func from(number: NSNumber) -> InAppDeleteSource? { + guard let value = number as? Int else { + return nil } + + return InAppDeleteSource(rawValue: value) + } } extension InAppShowResponse { - static func from(number: NSNumber) -> InAppShowResponse { - if let value = number as? Int { - return InAppShowResponse(rawValue: value) ?? .show - } else { - return .show - } + static func from(number: NSNumber) -> InAppShowResponse { + if let value = number as? Int { + return InAppShowResponse(rawValue: value) ?? .show + } else { + return .show } + } } extension LogLevel { - static func from(number: NSNumber) -> LogLevel { - if let value = number as? Int { - return LogLevel(rawValue: value) ?? .info - } else { - return .info - } + static func from(number: NSNumber) -> LogLevel { + if let value = number as? Int { + return LogLevel(rawValue: value) ?? .info + } else { + return .info } + } } extension InboxImpressionTracker.RowInfo { - static func from(dict: [AnyHashable: Any]) -> 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 from(dict: [AnyHashable: Any]) -> InboxImpressionTracker.RowInfo? { + guard let messageId = dict["messageId"] as? String else { + return nil } - - static func rowInfos(from rows: [[AnyHashable: Any]]) -> [InboxImpressionTracker.RowInfo] { - return rows.compactMap(InboxImpressionTracker.RowInfo.from(dict:)) + + 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 69b388d95..248414060 100644 --- a/package.json +++ b/package.json @@ -106,8 +106,13 @@ "typescript": "^5.2.2" }, "resolutions": { + "@types/jest": "^29.5.5", + "@types/node": "^24.1.0", "@types/react": "^19.1.0", - "@types/react-test-renderer": "^19.1.0" + "@types/react-native-vector-icons": "^6.4.18", + "@types/react-test-renderer": "^19.1.0", + "@typescript-eslint/eslint-plugin": "^8.13.0", + "@typescript-eslint/parser": "^8.13.0" }, "peerDependencies": { "@react-navigation/native": "*", @@ -179,11 +184,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..157fb1550 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ +import { NativeModules } from 'react-native'; + +export const isTurboModuleEnabled = global.__turboModuleProxy != null; + +const getNativeModule = () => { + if (isTurboModuleEnabled) { + return require('./NativeRNIterableAPI').default; + } + return NativeModules.RNIterableAPI; +}; + +export const api = getNativeModule(); + +export default api; diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 5b8b07fff..73d3707c8 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -1,7 +1,7 @@ import { Linking, NativeEventEmitter, - NativeModules, + // NativeModules, Platform, } from 'react-native'; @@ -14,6 +14,7 @@ import { IterableInAppCloseSource } from '../../inApp/enums/IterableInAppCloseSo import { IterableInAppDeleteSource } from '../../inApp/enums/IterableInAppDeleteSource'; import { IterableInAppLocation } from '../../inApp/enums/IterableInAppLocation'; import { IterableAuthResponseResult, IterableEventName } from '../enums'; +import { api } from '../../api'; import { IterableAction } from './IterableAction'; import { IterableActionContext } from './IterableActionContext'; @@ -23,8 +24,8 @@ import type { IterableCommerceItem } from './IterableCommerceItem'; import { IterableConfig } from './IterableConfig'; import { IterableLogger } from './IterableLogger'; -const RNIterableAPI = NativeModules.RNIterableAPI; -const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); +const RNIterableAPI = api; +const RNEventEmitter = new NativeEventEmitter(api); /* eslint-disable tsdoc/syntax */ /** @@ -181,7 +182,7 @@ export class Iterable { * Iterable.setEmail('my.user.name@gmail.com'); * ``` */ - static setEmail(email?: string | null, authToken?: string | null) { + static setEmail(email?: string | null, authToken?: string | undefined) { Iterable?.logger?.log('setEmail: ' + email); RNIterableAPI.setEmail(email, authToken); @@ -197,7 +198,7 @@ export class Iterable { * }); * ``` */ - static getEmail(): Promise { + static getEmail(): Promise { Iterable?.logger?.log('getEmail'); return RNIterableAPI.getEmail(); diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 000000000..3cd4060e3 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,3 @@ +declare const global: { + __turboModuleProxy?: (moduleName: string) => object | undefined; +}; diff --git a/src/inApp/classes/IterableInAppManager.ts b/src/inApp/classes/IterableInAppManager.ts index 640b99d50..ef436bff9 100644 --- a/src/inApp/classes/IterableInAppManager.ts +++ b/src/inApp/classes/IterableInAppManager.ts @@ -1,5 +1,4 @@ -import { NativeModules } from 'react-native'; - +import { api } from '../../api'; import { Iterable } from '../../core/classes/Iterable'; import type { IterableInAppDeleteSource, @@ -8,7 +7,7 @@ import type { import { IterableHtmlInAppContent } from './IterableHtmlInAppContent'; import { IterableInAppMessage } from './IterableInAppMessage'; -const RNIterableAPI = NativeModules.RNIterableAPI; +const RNIterableAPI = api; /** * Manages in-app messages for the current user. @@ -77,7 +76,7 @@ export class IterableInAppManager { * }); * ``` * - * @param message - The message to show (an {@link_IterableInAppMessage} object) + * @param message - The message to show (an {@link IterableInAppMessage} object) * @param consume - Whether or not the message should be consumed from the user's message queue after being shown. This should be defaulted to true. * * @returns A Promise that resolves to the URL of the button or link the user tapped to close the in-app message. diff --git a/src/inbox/classes/IterableInboxDataModel.ts b/src/inbox/classes/IterableInboxDataModel.ts index fe5ce66fb..79c5b5fd1 100644 --- a/src/inbox/classes/IterableInboxDataModel.ts +++ b/src/inbox/classes/IterableInboxDataModel.ts @@ -1,5 +1,5 @@ -import { NativeModules } from 'react-native'; +import { api } from '../../api'; import { Iterable } from '../../core/classes/Iterable'; import { IterableHtmlInAppContent, @@ -13,7 +13,7 @@ import type { IterableInboxRowViewModel, } from '../types'; -const RNIterableAPI = NativeModules.RNIterableAPI; +const RNIterableAPI = api; /** * 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..d550015f4 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 { api } from '../../api'; import { useAppStateListener, useDeviceOrientation } from '../../core'; // expo throws an error if this is not imported directly due to circular // dependencies @@ -32,7 +32,7 @@ import { type IterableInboxMessageListProps, } from './IterableInboxMessageList'; -const RNIterableAPI = NativeModules.RNIterableAPI; +const RNIterableAPI = api; const RNEventEmitter = new NativeEventEmitter(RNIterableAPI); const DEFAULT_HEADLINE_HEIGHT = 60; diff --git a/src/index.tsx b/src/index.tsx index 885cd74bd..fadfd55f1 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,7 @@ /** * React Native module for Iterable. */ +export {api} from './api'; export { Iterable, IterableAction, @@ -52,3 +53,4 @@ export { type IterableInboxProps, type IterableInboxRowViewModel, } from './inbox'; + diff --git a/tsconfig.json b/tsconfig.json index f9287edfb..33fc6c8fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { + "extends": "@react-native/typescript-config", "compilerOptions": { "rootDir": ".", "paths": { diff --git a/yarn.lock b/yarn.lock index 44aa27320..defc2eaf4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4486,16 +4486,6 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:^29.5.13": - version: 29.5.14 - resolution: "@types/jest@npm:29.5.14" - dependencies: - expect: ^29.0.0 - pretty-format: ^29.0.0 - checksum: 18dba4623f26661641d757c63da2db45e9524c9be96a29ef713c703a9a53792df9ecee9f7365a0858ddbd6440d98fe6b65ca67895ca5884b73cbc7ffc11f3838 - languageName: node - linkType: hard - "@types/jest@npm:^29.5.5": version: 29.5.13 resolution: "@types/jest@npm:29.5.13" @@ -4529,12 +4519,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*": - version: 22.7.4 - resolution: "@types/node@npm:22.7.4" +"@types/node@npm:^24.1.0": + version: 24.1.0 + resolution: "@types/node@npm:24.1.0" dependencies: - undici-types: ~6.19.2 - checksum: a3f4154147639369aed08fe6f8d62eff637cf87b187bb252d7bbccdc82884626007af424b08a653c53f2182adfa0340001b4888cb7cbb942cef351210fc742a5 + undici-types: ~7.8.0 + checksum: 01f9a97909eec619d937af3bc00ac49461e1846656b4d060f648145df06508eb6d77c200637a792481ee93a73976ced46cfbbdfe307e79be0f58ccdc415dfcd2 languageName: node linkType: hard @@ -4635,29 +4625,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^7.1.1": - version: 7.18.0 - resolution: "@typescript-eslint/eslint-plugin@npm:7.18.0" - dependencies: - "@eslint-community/regexpp": ^4.10.0 - "@typescript-eslint/scope-manager": 7.18.0 - "@typescript-eslint/type-utils": 7.18.0 - "@typescript-eslint/utils": 7.18.0 - "@typescript-eslint/visitor-keys": 7.18.0 - graphemer: ^1.4.0 - ignore: ^5.3.1 - natural-compare: ^1.4.0 - ts-api-utils: ^1.3.0 - peerDependencies: - "@typescript-eslint/parser": ^7.0.0 - eslint: ^8.56.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: dfcf150628ca2d4ccdfc20b46b0eae075c2f16ef5e70d9d2f0d746acf4c69a09f962b93befee01a529f14bbeb3e817b5aba287d7dd0edc23396bc5ed1f448c3d - languageName: node - linkType: hard - "@typescript-eslint/eslint-plugin@npm:^8.13.0": version: 8.13.0 resolution: "@typescript-eslint/eslint-plugin@npm:8.13.0" @@ -4681,24 +4648,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:^7.1.1": - version: 7.18.0 - resolution: "@typescript-eslint/parser@npm:7.18.0" - dependencies: - "@typescript-eslint/scope-manager": 7.18.0 - "@typescript-eslint/types": 7.18.0 - "@typescript-eslint/typescript-estree": 7.18.0 - "@typescript-eslint/visitor-keys": 7.18.0 - debug: ^4.3.4 - peerDependencies: - eslint: ^8.56.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 132b56ac3b2d90b588d61d005a70f6af322860974225b60201cbf45abf7304d67b7d8a6f0ade1c188ac4e339884e78d6dcd450417f1481998f9ddd155bab0801 - languageName: node - linkType: hard - "@typescript-eslint/parser@npm:^8.13.0": version: 8.13.0 resolution: "@typescript-eslint/parser@npm:8.13.0" @@ -4727,16 +4676,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:7.18.0": - version: 7.18.0 - resolution: "@typescript-eslint/scope-manager@npm:7.18.0" - dependencies: - "@typescript-eslint/types": 7.18.0 - "@typescript-eslint/visitor-keys": 7.18.0 - checksum: b982c6ac13d8c86bb3b949c6b4e465f3f60557c2ccf4cc229799827d462df56b9e4d3eaed7711d79b875422fc3d71ec1ebcb5195db72134d07c619e3c5506b57 - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:8.13.0": version: 8.13.0 resolution: "@typescript-eslint/scope-manager@npm:8.13.0" @@ -4747,23 +4686,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:7.18.0": - version: 7.18.0 - resolution: "@typescript-eslint/type-utils@npm:7.18.0" - dependencies: - "@typescript-eslint/typescript-estree": 7.18.0 - "@typescript-eslint/utils": 7.18.0 - debug: ^4.3.4 - ts-api-utils: ^1.3.0 - peerDependencies: - eslint: ^8.56.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 68fd5df5146c1a08cde20d59b4b919acab06a1b06194fe4f7ba1b928674880249890785fbbc97394142f2ef5cff5a7fba9b8a940449e7d5605306505348e38bc - languageName: node - linkType: hard - "@typescript-eslint/type-utils@npm:8.13.0": version: 8.13.0 resolution: "@typescript-eslint/type-utils@npm:8.13.0" @@ -4786,13 +4708,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:7.18.0": - version: 7.18.0 - resolution: "@typescript-eslint/types@npm:7.18.0" - checksum: 7df2750cd146a0acd2d843208d69f153b458e024bbe12aab9e441ad2c56f47de3ddfeb329c4d1ea0079e2577fea4b8c1c1ce15315a8d49044586b04fedfe7a4d - languageName: node - linkType: hard - "@typescript-eslint/types@npm:8.13.0": version: 8.13.0 resolution: "@typescript-eslint/types@npm:8.13.0" @@ -4818,25 +4733,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:7.18.0": - version: 7.18.0 - resolution: "@typescript-eslint/typescript-estree@npm:7.18.0" - dependencies: - "@typescript-eslint/types": 7.18.0 - "@typescript-eslint/visitor-keys": 7.18.0 - debug: ^4.3.4 - globby: ^11.1.0 - is-glob: ^4.0.3 - minimatch: ^9.0.4 - semver: ^7.6.0 - ts-api-utils: ^1.3.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: c82d22ec9654973944f779eb4eb94c52f4a6eafaccce2f0231ff7757313f3a0d0256c3252f6dfe6d43f57171d09656478acb49a629a9d0c193fb959bc3f36116 - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:8.13.0": version: 8.13.0 resolution: "@typescript-eslint/typescript-estree@npm:8.13.0" @@ -4856,20 +4752,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:7.18.0": - version: 7.18.0 - resolution: "@typescript-eslint/utils@npm:7.18.0" - dependencies: - "@eslint-community/eslint-utils": ^4.4.0 - "@typescript-eslint/scope-manager": 7.18.0 - "@typescript-eslint/types": 7.18.0 - "@typescript-eslint/typescript-estree": 7.18.0 - peerDependencies: - eslint: ^8.56.0 - checksum: 751dbc816dab8454b7dc6b26a56671dbec08e3f4ef94c2661ce1c0fc48fa2d05a64e03efe24cba2c22d03ba943cd3c5c7a5e1b7b03bbb446728aec1c640bd767 - languageName: node - linkType: hard - "@typescript-eslint/utils@npm:8.13.0, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0": version: 8.13.0 resolution: "@typescript-eslint/utils@npm:8.13.0" @@ -4912,16 +4794,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:7.18.0": - version: 7.18.0 - resolution: "@typescript-eslint/visitor-keys@npm:7.18.0" - dependencies: - "@typescript-eslint/types": 7.18.0 - eslint-visitor-keys: ^3.4.3 - checksum: 6e806a7cdb424c5498ea187a5a11d0fef7e4602a631be413e7d521e5aec1ab46ba00c76cfb18020adaa0a8c9802354a163bfa0deb74baa7d555526c7517bb158 - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:8.13.0": version: 8.13.0 resolution: "@typescript-eslint/visitor-keys@npm:8.13.0" @@ -14443,10 +14315,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.2": - version: 6.19.8 - resolution: "undici-types@npm:6.19.8" - checksum: de51f1b447d22571cf155dfe14ff6d12c5bdaec237c765085b439c38ca8518fc360e88c70f99469162bf2e14188a7b0bcb06e1ed2dc031042b984b0bb9544017 +"undici-types@npm:~7.8.0": + version: 7.8.0 + resolution: "undici-types@npm:7.8.0" + checksum: 59521a5b9b50e72cb838a29466b3557b4eacbc191a83f4df5a2f7b156bc8263072b145dc4bb8ec41da7d56a7e9b178892458da02af769243d57f801a50ac5751 languageName: node linkType: hard