diff --git a/.travis.yml b/.travis.yml index c8d9a52b..9cf3e86e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ deploy: file: - Leanplum.framework.zip - LeanplumLocation.framework.zip - - Release/dynamic/Leanplum.xcframework.zip + - Release/static/Leanplum.xcframework.zip draft: true on: tags: true diff --git a/Leanplum-iOS-SDK.podspec b/Leanplum-iOS-SDK.podspec index 1b049bd2..638595ec 100644 --- a/Leanplum-iOS-SDK.podspec +++ b/Leanplum-iOS-SDK.podspec @@ -32,6 +32,7 @@ Pod::Spec.new do |s| s.resource_bundle = { 'Leanplum-iOS-SDK' => 'LeanplumSDK/LeanplumSDKBundle/Resources/**/*' } + s.dependency 'CleverTap-iOS-SDK', '~> 4.1' s.swift_version = '5.0' end diff --git a/LeanplumSDK/LeanplumSDK.xcodeproj/project.pbxproj b/LeanplumSDK/LeanplumSDK.xcodeproj/project.pbxproj index 9e875916..6ddd4143 100644 --- a/LeanplumSDK/LeanplumSDK.xcodeproj/project.pbxproj +++ b/LeanplumSDK/LeanplumSDK.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -196,10 +196,36 @@ 6A07FDA32811911000995BE3 /* ActionManager+FileDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A07FDA12811911000995BE3 /* ActionManager+FileDownload.swift */; }; 6A07FDAA28352DCE00995BE3 /* ContentMerger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A07FDA928352DCE00995BE3 /* ContentMerger.swift */; }; 6A07FDAB28352DCE00995BE3 /* ContentMerger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A07FDA928352DCE00995BE3 /* ContentMerger.swift */; }; + 6A2109E128BE88D900DBF4A9 /* CTWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2109DF28BE88D900DBF4A9 /* CTWrapper.swift */; }; + 6A2109E228BE88D900DBF4A9 /* CTWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2109DF28BE88D900DBF4A9 /* CTWrapper.swift */; }; + 6A2109E328BE88D900DBF4A9 /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2109E028BE88D900DBF4A9 /* MigrationManager.swift */; }; + 6A2109E428BE88D900DBF4A9 /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2109E028BE88D900DBF4A9 /* MigrationManager.swift */; }; 6A265E2727187EBB0074354F /* NotificationsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A265E2527187EBB0074354F /* NotificationsProxy.swift */; }; 6A265E2827187EBB0074354F /* NotificationsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A265E2527187EBB0074354F /* NotificationsProxy.swift */; }; 6A265E2927187EBB0074354F /* NSObject+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A265E2627187EBB0074354F /* NSObject+Notifications.swift */; }; 6A265E2A27187EBB0074354F /* NSObject+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A265E2627187EBB0074354F /* NSObject+Notifications.swift */; }; + 6A29EAB828EA09470024880E /* MigrationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EAB728EA09470024880E /* MigrationState.swift */; }; + 6A29EAB928EA09470024880E /* MigrationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EAB728EA09470024880E /* MigrationState.swift */; }; + 6A29EABB28EA12D30024880E /* MigrationManager+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EABA28EA12D30024880E /* MigrationManager+Constants.swift */; }; + 6A29EABC28EA12D30024880E /* MigrationManager+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EABA28EA12D30024880E /* MigrationManager+Constants.swift */; }; + 6A29EABE28EA13B60024880E /* PropertyWrappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EABD28EA13B60024880E /* PropertyWrappers.swift */; }; + 6A29EABF28EA13B60024880E /* PropertyWrappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EABD28EA13B60024880E /* PropertyWrappers.swift */; }; + 6A29EAC428EB56AD0024880E /* MigrationManager+ResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EAC328EB56AD0024880E /* MigrationManager+ResponseHandler.swift */; }; + 6A29EAC528EB56AD0024880E /* MigrationManager+ResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EAC328EB56AD0024880E /* MigrationManager+ResponseHandler.swift */; }; + 6A29EAF528EF37090024880E /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EAF428EF37090024880E /* IdentityManager.swift */; }; + 6A29EAF628EF37090024880E /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EAF428EF37090024880E /* IdentityManager.swift */; }; + 6A37A89628EF6E6C00F4339F /* Wrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A89528EF6E6C00F4339F /* Wrapper.swift */; }; + 6A37A89728EF6E6C00F4339F /* Wrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A89528EF6E6C00F4339F /* Wrapper.swift */; }; + 6A37A89928EF738200F4339F /* MigrationManager+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A89828EF738200F4339F /* MigrationManager+API.swift */; }; + 6A37A89A28EF738200F4339F /* MigrationManager+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A89828EF738200F4339F /* MigrationManager+API.swift */; }; + 6A37A89C28EF748800F4339F /* Dictionary+MapKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A89B28EF748800F4339F /* Dictionary+MapKeys.swift */; }; + 6A37A89D28EF748800F4339F /* Dictionary+MapKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A89B28EF748800F4339F /* Dictionary+MapKeys.swift */; }; + 6A37A8A028EF74E900F4339F /* CTWrapper+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A89F28EF74E900F4339F /* CTWrapper+Utilities.swift */; }; + 6A37A8A128EF74E900F4339F /* CTWrapper+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A89F28EF74E900F4339F /* CTWrapper+Utilities.swift */; }; + 6A3C1EB228D7B3C700D51E44 /* CleverTapSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A3C1EB028D7B3C700D51E44 /* CleverTapSDK.framework */; }; + 6A3C1EB328D7B3C700D51E44 /* CleverTapSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6A3C1EB028D7B3C700D51E44 /* CleverTapSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6A3C1EB428D7B3C700D51E44 /* SDWebImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A3C1EB128D7B3C700D51E44 /* SDWebImage.framework */; }; + 6A3C1EB528D7B3C700D51E44 /* SDWebImage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6A3C1EB128D7B3C700D51E44 /* SDWebImage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 6A714AF326F8B317004A34A9 /* LPConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 075AADDA26847EC4007CA1BD /* LPConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6A714AF426F8B317004A34A9 /* LPActionTriggerManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 075AADC726847EC3007CA1BD /* LPActionTriggerManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6A714AF526F8B317004A34A9 /* LPWebInterstitialViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 075AAD7426847EC3007CA1BD /* LPWebInterstitialViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -463,6 +489,7 @@ 6AD978582774F3F000A7C6C6 /* NotificationsProxy+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD978562774F3F000A7C6C6 /* NotificationsProxy+Utilities.swift */; }; 6ADC90C72807461F00CE42C7 /* Dictionary+ValueKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADC90C62807461F00CE42C7 /* Dictionary+ValueKeyPath.swift */; }; 6ADC90C82807461F00CE42C7 /* Dictionary+ValueKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADC90C62807461F00CE42C7 /* Dictionary+ValueKeyPath.swift */; }; + A0371C8C3E1D6D0AB924FF99 /* libPods-Leanplum-Static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 897B64EE76852A68ECECB184 /* libPods-Leanplum-Static.a */; }; C90C489D2707299D00E33F1F /* NotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90C489C2707299D00E33F1F /* NotificationSettings.swift */; }; C90C489E2707299D00E33F1F /* NotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90C489C2707299D00E33F1F /* NotificationSettings.swift */; }; C90C48A027072A0200E33F1F /* UIUserNotificationSettings+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90C489F27072A0200E33F1F /* UIUserNotificationSettings+Transform.swift */; }; @@ -475,6 +502,7 @@ C9374A3A272C36B200F4915A /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9374A38272C36B200F4915A /* Utilities.swift */; }; C9D064DC2775DB1100A7A5F9 /* Dictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D064DB2775DB1100A7A5F9 /* Dictionary+Equatable.swift */; }; C9D064DD2775DB1100A7A5F9 /* Dictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D064DB2775DB1100A7A5F9 /* Dictionary+Equatable.swift */; }; + EF8C42881E834252F5F5ABA6 /* Pods_Leanplum.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C2FB1913D58B5EF901363C9 /* Pods_Leanplum.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -495,6 +523,18 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 6A3C1EB628D7B3C700D51E44 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 6A3C1EB528D7B3C700D51E44 /* SDWebImage.framework in Embed Frameworks */, + 6A3C1EB328D7B3C700D51E44 /* CleverTapSDK.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; 6A714BA526F8B4E6004A34A9 /* Copy Headers */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -752,6 +792,7 @@ 077C538E2685CFB90090D88D /* Leanplum.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Leanplum.modulemap; sourceTree = ""; }; 077C53972685D9DB0090D88D /* Leanplum-iOS-SDK.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = "Leanplum-iOS-SDK.podspec"; path = "../Leanplum-iOS-SDK.podspec"; sourceTree = ""; }; 077C53982685D9DB0090D88D /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Package.swift; sourceTree = ""; }; + 219B9B64F683F15530F5F56E /* Pods-Leanplum.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Leanplum.release.xcconfig"; path = "Target Support Files/Pods-Leanplum/Pods-Leanplum.release.xcconfig"; sourceTree = ""; }; 39C081902781D30300C1DBD6 /* ActionManager+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActionManager+Action.swift"; sourceTree = ""; }; 39C081922781D31F00C1DBD6 /* ActionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionManager.swift; sourceTree = ""; }; 39C081942781D34700C1DBD6 /* ActionManager+ActionDefinition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActionManager+ActionDefinition.swift"; sourceTree = ""; }; @@ -764,11 +805,25 @@ 39C081A22781D40200C1DBD6 /* ActionManager+Definition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActionManager+Definition.swift"; sourceTree = ""; }; 39C081A42781D46200C1DBD6 /* ActionManager+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActionManager+State.swift"; sourceTree = ""; }; 39D0AA1327760842003C7192 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = ""; }; + 55FCDCE6D98B7EDDB33867FA /* Pods-Leanplum-Static.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Leanplum-Static.debug.xcconfig"; path = "Target Support Files/Pods-Leanplum-Static/Pods-Leanplum-Static.debug.xcconfig"; sourceTree = ""; }; 6A07FD9E280F27C700995BE3 /* NSRegularExpression+Matches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+Matches.swift"; sourceTree = ""; }; 6A07FDA12811911000995BE3 /* ActionManager+FileDownload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActionManager+FileDownload.swift"; sourceTree = ""; }; 6A07FDA928352DCE00995BE3 /* ContentMerger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentMerger.swift; sourceTree = ""; }; + 6A2109DF28BE88D900DBF4A9 /* CTWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CTWrapper.swift; sourceTree = ""; }; + 6A2109E028BE88D900DBF4A9 /* MigrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = ""; }; 6A265E2527187EBB0074354F /* NotificationsProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsProxy.swift; sourceTree = ""; }; 6A265E2627187EBB0074354F /* NSObject+Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Notifications.swift"; sourceTree = ""; }; + 6A29EAB728EA09470024880E /* MigrationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationState.swift; sourceTree = ""; }; + 6A29EABA28EA12D30024880E /* MigrationManager+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MigrationManager+Constants.swift"; sourceTree = ""; }; + 6A29EABD28EA13B60024880E /* PropertyWrappers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyWrappers.swift; sourceTree = ""; }; + 6A29EAC328EB56AD0024880E /* MigrationManager+ResponseHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MigrationManager+ResponseHandler.swift"; sourceTree = ""; }; + 6A29EAF428EF37090024880E /* IdentityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManager.swift; sourceTree = ""; }; + 6A37A89528EF6E6C00F4339F /* Wrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wrapper.swift; sourceTree = ""; }; + 6A37A89828EF738200F4339F /* MigrationManager+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MigrationManager+API.swift"; sourceTree = ""; }; + 6A37A89B28EF748800F4339F /* Dictionary+MapKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+MapKeys.swift"; sourceTree = ""; }; + 6A37A89F28EF74E900F4339F /* CTWrapper+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CTWrapper+Utilities.swift"; sourceTree = ""; }; + 6A3C1EB028D7B3C700D51E44 /* CleverTapSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CleverTapSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A3C1EB128D7B3C700D51E44 /* SDWebImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SDWebImage.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6A714BA326F8B317004A34A9 /* Leanplum.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Leanplum.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6A714C0026F8B88A004A34A9 /* NotificationsManager+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationsManager+Utilities.swift"; sourceTree = ""; }; 6A7B2F50289BC8FC00F73EC7 /* LPActionContextNotification.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LPActionContextNotification.h; sourceTree = ""; }; @@ -786,6 +841,10 @@ 6AD978532774F2F700A7C6C6 /* NotificationsProxy+iOS9.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationsProxy+iOS9.swift"; sourceTree = ""; }; 6AD978562774F3F000A7C6C6 /* NotificationsProxy+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationsProxy+Utilities.swift"; sourceTree = ""; }; 6ADC90C62807461F00CE42C7 /* Dictionary+ValueKeyPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+ValueKeyPath.swift"; sourceTree = ""; }; + 6C2FB1913D58B5EF901363C9 /* Pods_Leanplum.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Leanplum.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 897B64EE76852A68ECECB184 /* libPods-Leanplum-Static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Leanplum-Static.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + B335B24E5DF5CE017C0BE58D /* Pods-Leanplum-Static.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Leanplum-Static.release.xcconfig"; path = "Target Support Files/Pods-Leanplum-Static/Pods-Leanplum-Static.release.xcconfig"; sourceTree = ""; }; + C73AA976764CAC7D649E137B /* Pods-Leanplum.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Leanplum.debug.xcconfig"; path = "Target Support Files/Pods-Leanplum/Pods-Leanplum.debug.xcconfig"; sourceTree = ""; }; C90C489C2707299D00E33F1F /* NotificationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettings.swift; sourceTree = ""; }; C90C489F27072A0200E33F1F /* UIUserNotificationSettings+Transform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIUserNotificationSettings+Transform.swift"; sourceTree = ""; }; C90C48A227072A5200E33F1F /* Data+EncodedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+EncodedString.swift"; sourceTree = ""; }; @@ -799,6 +858,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 6A3C1EB228D7B3C700D51E44 /* CleverTapSDK.framework in Frameworks */, + 6A3C1EB428D7B3C700D51E44 /* SDWebImage.framework in Frameworks */, + EF8C42881E834252F5F5ABA6 /* Pods_Leanplum.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -806,6 +868,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A0371C8C3E1D6D0AB924FF99 /* libPods-Leanplum-Static.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -819,6 +882,8 @@ 075AAD0A26847C23007CA1BD /* LeanplumSDK */, 075AAEED26847FE1007CA1BD /* LeanplumSDKBundle */, 075AAD0926847C23007CA1BD /* Products */, + 830C77E30DBF54561D95CE52 /* Pods */, + 34C58C2C6E3180DDD898BF2B /* Frameworks */, ); sourceTree = ""; }; @@ -1240,6 +1305,17 @@ name = Packages; sourceTree = ""; }; + 34C58C2C6E3180DDD898BF2B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6A3C1EB028D7B3C700D51E44 /* CleverTapSDK.framework */, + 6A3C1EB128D7B3C700D51E44 /* SDWebImage.framework */, + 6C2FB1913D58B5EF901363C9 /* Pods_Leanplum.framework */, + 897B64EE76852A68ECECB184 /* libPods-Leanplum-Static.a */, + ); + name = Frameworks; + sourceTree = ""; + }; 39C0818E2781D27700C1DBD6 /* Actions */ = { isa = PBXGroup; children = ( @@ -1269,9 +1345,34 @@ path = Models; sourceTree = ""; }; + 6A2109DE28BE88CA00DBF4A9 /* Migration */ = { + isa = PBXGroup; + children = ( + 6A37A89E28EF749F00F4339F /* Wrapper */, + 6A2109E028BE88D900DBF4A9 /* MigrationManager.swift */, + 6A29EAB728EA09470024880E /* MigrationState.swift */, + 6A29EABA28EA12D30024880E /* MigrationManager+Constants.swift */, + 6A29EAC328EB56AD0024880E /* MigrationManager+ResponseHandler.swift */, + 6A37A89828EF738200F4339F /* MigrationManager+API.swift */, + ); + path = Migration; + sourceTree = ""; + }; + 6A37A89E28EF749F00F4339F /* Wrapper */ = { + isa = PBXGroup; + children = ( + 6A2109DF28BE88D900DBF4A9 /* CTWrapper.swift */, + 6A29EAF428EF37090024880E /* IdentityManager.swift */, + 6A37A89528EF6E6C00F4339F /* Wrapper.swift */, + 6A37A89F28EF74E900F4339F /* CTWrapper+Utilities.swift */, + ); + path = Wrapper; + sourceTree = ""; + }; 6A714BFF26F8B7DC004A34A9 /* ClassesSwift */ = { isa = PBXGroup; children = ( + 6A2109DE28BE88CA00DBF4A9 /* Migration */, 39C0818E2781D27700C1DBD6 /* Actions */, C9D064DA2775C85600A7A5F9 /* Extensions */, 6AD9785B2774FCB500A7C6C6 /* Utilities */, @@ -1315,10 +1416,23 @@ C9374A38272C36B200F4915A /* Utilities.swift */, 6A96032927C69F3C00F34BA0 /* WeakTimer.swift */, 6AB10937284B3FBD00B72FDA /* UIAlert.swift */, + 6A29EABD28EA13B60024880E /* PropertyWrappers.swift */, ); path = Utilities; sourceTree = ""; }; + 830C77E30DBF54561D95CE52 /* Pods */ = { + isa = PBXGroup; + children = ( + C73AA976764CAC7D649E137B /* Pods-Leanplum.debug.xcconfig */, + 219B9B64F683F15530F5F56E /* Pods-Leanplum.release.xcconfig */, + 55FCDCE6D98B7EDDB33867FA /* Pods-Leanplum-Static.debug.xcconfig */, + B335B24E5DF5CE017C0BE58D /* Pods-Leanplum-Static.release.xcconfig */, + ); + name = Pods; + path = ../Pods; + sourceTree = ""; + }; C9D064DA2775C85600A7A5F9 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1330,6 +1444,7 @@ 6A9DEC9D27B54B7200052807 /* LPRequestSender+UUID.swift */, 6ADC90C62807461F00CE42C7 /* Dictionary+ValueKeyPath.swift */, 6A07FD9E280F27C700995BE3 /* NSRegularExpression+Matches.swift */, + 6A37A89B28EF748800F4339F /* Dictionary+MapKeys.swift */, ); path = Extensions; sourceTree = ""; @@ -1520,10 +1635,13 @@ isa = PBXNativeTarget; buildConfigurationList = 075AAD1026847C23007CA1BD /* Build configuration list for PBXNativeTarget "Leanplum" */; buildPhases = ( + 6F7CCB8DAE13D687016AB7D9 /* [CP] Check Pods Manifest.lock */, 075AAD0326847C23007CA1BD /* Headers */, 075AAD0426847C23007CA1BD /* Sources */, 075AAD0526847C23007CA1BD /* Frameworks */, 075AAD0626847C23007CA1BD /* Resources */, + 6A3C1EAF28D7B30400D51E44 /* Copy Frameworks */, + 6A3C1EB628D7B3C700D51E44 /* Embed Frameworks */, ); buildRules = ( ); @@ -1554,11 +1672,13 @@ isa = PBXNativeTarget; buildConfigurationList = 6A714BA026F8B317004A34A9 /* Build configuration list for PBXNativeTarget "Leanplum-Static" */; buildPhases = ( + 3222E433DFAEC0FB8BECFB6B /* [CP] Check Pods Manifest.lock */, 6A714AF226F8B317004A34A9 /* Headers */, 6A714B4C26F8B317004A34A9 /* Sources */, 6A714B9C26F8B317004A34A9 /* Frameworks */, 6A714B9D26F8B317004A34A9 /* Resources */, 6A714BA526F8B4E6004A34A9 /* Copy Headers */, + E7595406E1AAD26E354A140C /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1640,6 +1760,89 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 3222E433DFAEC0FB8BECFB6B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Leanplum-Static-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 6A3C1EAF28D7B30400D51E44 /* Copy Frameworks */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Copy Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Copy dependency Pods Frameworks to top level so they can be found and embedded.\n# This Run Script Phase needs to run before \"Embed Frameworks\" built-in one.\n# When a framework is linked both dynamically and statically, name of the linking is added to the framework folder name\nCOPY_FRAMEWORKS_TO_EMBED=(\"CleverTap-iOS-SDK-framework\" \"SDWebImage-framework\")\n\nfor FRAMEWORK in ${COPY_FRAMEWORKS_TO_EMBED[@]};\ndo\n cp -R -L \"${BUILT_PRODUCTS_DIR}/$FRAMEWORK/\" ${BUILT_PRODUCTS_DIR}/\ndone\n"; + }; + 6F7CCB8DAE13D687016AB7D9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Leanplum-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E7595406E1AAD26E354A140C /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Leanplum-Static/Pods-Leanplum-Static-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Leanplum-Static/Pods-Leanplum-Static-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Leanplum-Static/Pods-Leanplum-Static-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 075AAD0426847C23007CA1BD /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -1647,8 +1850,10 @@ files = ( 075AAECF26847EC4007CA1BD /* LPFeatureFlagManager.m in Sources */, 075AAE0C26847EC4007CA1BD /* LPResponse.m in Sources */, + 6A29EABB28EA12D30024880E /* MigrationManager+Constants.swift in Sources */, 6A98A9BC272A941D00D8753A /* NotificationsManager.swift in Sources */, C9374A39272C36B200F4915A /* Utilities.swift in Sources */, + 6A2109E128BE88D900DBF4A9 /* CTWrapper.swift in Sources */, 39C081952781D34700C1DBD6 /* ActionManager+ActionDefinition.swift in Sources */, 075AAEC426847EC4007CA1BD /* LPEventDataManager.m in Sources */, 6A7B2F54289BC8FC00F73EC7 /* LPActionContextNotification.m in Sources */, @@ -1670,6 +1875,7 @@ 075AAE7F26847EC4007CA1BD /* LPDatabase.m in Sources */, 075AAE1E26847EC4007CA1BD /* LPRequestBatch.m in Sources */, 075AAEB926847EC4007CA1BD /* LPActionContext.m in Sources */, + 6A37A8A028EF74E900F4339F /* CTWrapper+Utilities.swift in Sources */, 075AAE6026847EC4007CA1BD /* LPPushAskToAskMessageTemplate.m in Sources */, 6AD978492774BE7500A7C6C6 /* Logging.swift in Sources */, 075AAEDE26847EC4007CA1BD /* LPConstants.m in Sources */, @@ -1686,18 +1892,23 @@ 075AAEC926847EC4007CA1BD /* LPInternalState.m in Sources */, 6A96032D27C8DD1300F34BA0 /* User.swift in Sources */, 075AAE4E26847EC4007CA1BD /* LPMessageTemplateUtilities.m in Sources */, + 6A37A89928EF738200F4339F /* MigrationManager+API.swift in Sources */, 075AAE9A26847EC4007CA1BD /* Leanplum_Reachability.m in Sources */, 075AAE3426847EC4007CA1BD /* LPPushMessageTemplate.m in Sources */, 075AAED326847EC4007CA1BD /* LeanplumCompatibility.m in Sources */, + 6A29EABE28EA13B60024880E /* PropertyWrappers.swift in Sources */, 6A07FD9F280F27C700995BE3 /* NSRegularExpression+Matches.swift in Sources */, + 6A29EAB828EA09470024880E /* MigrationState.swift in Sources */, 6A9DECA227B6929200052807 /* ApiConfig.swift in Sources */, 075AAEA826847EC4007CA1BD /* LPInbox.m in Sources */, + 6A37A89628EF6E6C00F4339F /* Wrapper.swift in Sources */, C90C489D2707299D00E33F1F /* NotificationSettings.swift in Sources */, 39C081912781D30300C1DBD6 /* ActionManager+Action.swift in Sources */, 075AAED826847EC4007CA1BD /* LPContextualValues.m in Sources */, 39C081992781D39000C1DBD6 /* ActionManager+Triggering.swift in Sources */, 075AAE2A26847EC4007CA1BD /* LPRegisterDevice.m in Sources */, 075AAE7C26847EC4007CA1BD /* LPAES.m in Sources */, + 6A2109E328BE88D900DBF4A9 /* MigrationManager.swift in Sources */, C90C48A027072A0200E33F1F /* UIUserNotificationSettings+Transform.swift in Sources */, 075AAE4326847EC4007CA1BD /* LPInterstitialMessageTemplate.m in Sources */, 075AAE2E26847EC4007CA1BD /* LPCountAggregator.m in Sources */, @@ -1715,6 +1926,7 @@ 075AAEC026847EC4007CA1BD /* LPEventCallback.m in Sources */, 075AAE0826847EC4007CA1BD /* LPRequestBatchFactory.m in Sources */, 6A265E2927187EBB0074354F /* NSObject+Notifications.swift in Sources */, + 6A29EAC428EB56AD0024880E /* MigrationManager+ResponseHandler.swift in Sources */, 39C081972781D36100C1DBD6 /* ActionManager+ActionsTrigger.swift in Sources */, 39C081A52781D46200C1DBD6 /* ActionManager+State.swift in Sources */, 075AADFD26847EC4007CA1BD /* LPRequestFactory.m in Sources */, @@ -1726,10 +1938,12 @@ 075AAE9D26847EC4007CA1BD /* NSTimer+Blocks.m in Sources */, 075AAE2026847EC4007CA1BD /* LPLogManager.m in Sources */, 39C0819F2781D3D000C1DBD6 /* ActionManager+Queue.swift in Sources */, + 6A29EAF528EF37090024880E /* IdentityManager.swift in Sources */, 075AAEB126847EC4007CA1BD /* LPSecuredVars.m in Sources */, 075AAE5C26847EC4007CA1BD /* LPConfirmMessageTemplate.m in Sources */, 6AB10938284B3FBD00B72FDA /* UIAlert.swift in Sources */, 075AAE0126847EC4007CA1BD /* LPRequestSenderTimer.m in Sources */, + 6A37A89C28EF748800F4339F /* Dictionary+MapKeys.swift in Sources */, 6A714C0126F8B88A004A34A9 /* NotificationsManager+Utilities.swift in Sources */, 075AAE2826847EC4007CA1BD /* LPRevenueManager.m in Sources */, 075AAE3226847EC4007CA1BD /* LPAlertMessageTemplate.m in Sources */, @@ -1764,8 +1978,10 @@ files = ( 6A714B4D26F8B317004A34A9 /* LPFeatureFlagManager.m in Sources */, 6A714B4E26F8B317004A34A9 /* LPResponse.m in Sources */, + 6A29EABC28EA12D30024880E /* MigrationManager+Constants.swift in Sources */, 6A98A9BD272A941D00D8753A /* NotificationsManager.swift in Sources */, C9374A3A272C36B200F4915A /* Utilities.swift in Sources */, + 6A2109E228BE88D900DBF4A9 /* CTWrapper.swift in Sources */, 6A714B4F26F8B317004A34A9 /* LPEventDataManager.m in Sources */, 6A714B5026F8B317004A34A9 /* LPInterstitialViewController.m in Sources */, 6A7B2F55289BC8FC00F73EC7 /* LPActionContextNotification.m in Sources */, @@ -1787,6 +2003,7 @@ 6A714B5926F8B317004A34A9 /* LPRequest.m in Sources */, 6A714B5A26F8B317004A34A9 /* LPVar.m in Sources */, C90C48A727072AD800E33F1F /* UNNotificationSettings+Transform.swift in Sources */, + 6A37A8A128EF74E900F4339F /* CTWrapper+Utilities.swift in Sources */, 6A714B5C26F8B317004A34A9 /* LPRegisterForPushMessageTemplate.m in Sources */, 6A714B5D26F8B317004A34A9 /* LPDatabase.m in Sources */, 6A714B5E26F8B317004A34A9 /* LPRequestBatch.m in Sources */, @@ -1804,17 +2021,22 @@ 39C081AA278D995300C1DBD6 /* ActionManager.swift in Sources */, 39C081B3278D995300C1DBD6 /* ActionManager+Action.swift in Sources */, 6A714B6826F8B317004A34A9 /* LPKeychainWrapper.m in Sources */, + 6A37A89A28EF738200F4339F /* MigrationManager+API.swift in Sources */, 6A714B6926F8B317004A34A9 /* LPHitView.m in Sources */, 6A714B6A26F8B317004A34A9 /* Leanplum_WebSocket.m in Sources */, + 6A29EABF28EA13B60024880E /* PropertyWrappers.swift in Sources */, 6A714B6C26F8B317004A34A9 /* LPRichInterstitialMessageTemplate.m in Sources */, + 6A29EAB928EA09470024880E /* MigrationState.swift in Sources */, 6A07FDA0280F27C700995BE3 /* NSRegularExpression+Matches.swift in Sources */, 6A714B6D26F8B317004A34A9 /* LPInternalState.m in Sources */, + 6A37A89728EF6E6C00F4339F /* Wrapper.swift in Sources */, 6A96032E27C8DD1300F34BA0 /* User.swift in Sources */, 6A714B7026F8B317004A34A9 /* LPMessageTemplateUtilities.m in Sources */, 6A714B7126F8B317004A34A9 /* Leanplum_Reachability.m in Sources */, 6A714B7226F8B317004A34A9 /* LPPushMessageTemplate.m in Sources */, 6A714B7326F8B317004A34A9 /* LeanplumCompatibility.m in Sources */, 6A9DECA327B6929300052807 /* ApiConfig.swift in Sources */, + 6A2109E428BE88D900DBF4A9 /* MigrationManager.swift in Sources */, 6A714B7426F8B317004A34A9 /* LPInbox.m in Sources */, C90C489E2707299D00E33F1F /* NotificationSettings.swift in Sources */, 6A714B7526F8B317004A34A9 /* LPContextualValues.m in Sources */, @@ -1832,6 +2054,7 @@ 6A714B7C26F8B317004A34A9 /* FileMD5Hash.c in Sources */, 39D0AA1527760842003C7192 /* Bundle+Extensions.swift in Sources */, 6A714B7D26F8B317004A34A9 /* LPOpenUrlMessageTemplate.m in Sources */, + 6A29EAC528EB56AD0024880E /* MigrationManager+ResponseHandler.swift in Sources */, 6A714B7E26F8B317004A34A9 /* LPEventCallback.m in Sources */, 6A714B7F26F8B317004A34A9 /* LPRequestBatchFactory.m in Sources */, 6A265E2A27187EBB0074354F /* NSObject+Notifications.swift in Sources */, @@ -1843,10 +2066,12 @@ 6A714B8426F8B317004A34A9 /* LPAppIconManager.m in Sources */, 6A714B8526F8B317004A34A9 /* NSTimer+Blocks.m in Sources */, 6A714B8626F8B317004A34A9 /* LPLogManager.m in Sources */, + 6A29EAF628EF37090024880E /* IdentityManager.swift in Sources */, 6A714B8726F8B317004A34A9 /* LPSecuredVars.m in Sources */, 6A714B8826F8B317004A34A9 /* LPConfirmMessageTemplate.m in Sources */, 6AB10939284B3FBD00B72FDA /* UIAlert.swift in Sources */, 6A714B8926F8B317004A34A9 /* LPRequestSenderTimer.m in Sources */, + 6A37A89D28EF748800F4339F /* Dictionary+MapKeys.swift in Sources */, 6A714C0226F8B88A004A34A9 /* NotificationsManager+Utilities.swift in Sources */, 39C081B2278D995300C1DBD6 /* ActionManager+Tracking.swift in Sources */, 6A714B8A26F8B317004A34A9 /* LPRevenueManager.m in Sources */, @@ -2011,6 +2236,7 @@ }; 075AAD1126847C23007CA1BD /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = C73AA976764CAC7D649E137B /* Pods-Leanplum.debug.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ENABLE_MODULES = YES; @@ -2044,6 +2270,7 @@ }; 075AAD1226847C23007CA1BD /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 219B9B64F683F15530F5F56E /* Pods-Leanplum.release.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ENABLE_MODULES = YES; @@ -2118,6 +2345,7 @@ }; 6A714BA126F8B317004A34A9 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 55FCDCE6D98B7EDDB33867FA /* Pods-Leanplum-Static.debug.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ENABLE_MODULES = YES; @@ -2138,7 +2366,6 @@ ); MACH_O_TYPE = staticlib; MODULEMAP_FILE = "LeanplumSDK/Supporting Files/Leanplum.modulemap"; - OTHER_SWIFT_FLAGS = "-Xfrontend -module-interface-preserve-types-as-written"; PRODUCT_BUNDLE_IDENTIFIER = com.leanplum.LeanplumSDK; PRODUCT_NAME = Leanplum; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2152,6 +2379,7 @@ }; 6A714BA226F8B317004A34A9 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = B335B24E5DF5CE017C0BE58D /* Pods-Leanplum-Static.release.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ENABLE_MODULES = YES; @@ -2172,7 +2400,6 @@ ); MACH_O_TYPE = staticlib; MODULEMAP_FILE = "LeanplumSDK/Supporting Files/Leanplum.modulemap"; - OTHER_SWIFT_FLAGS = "-Xfrontend -module-interface-preserve-types-as-written"; PRODUCT_BUNDLE_IDENTIFIER = com.leanplum.LeanplumSDK; PRODUCT_NAME = Leanplum; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/LeanplumSDK/LeanplumSDK/Classes/Internal/Leanplum.m b/LeanplumSDK/LeanplumSDK/Classes/Internal/Leanplum.m index fd153f7b..92b252f4 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Internal/Leanplum.m +++ b/LeanplumSDK/LeanplumSDK/Classes/Internal/Leanplum.m @@ -207,6 +207,7 @@ + (void)setLogLevel:(LPLogLevel)level { LP_TRY [LPLogManager setLogLevel:level]; + [[MigrationManager shared] setLogLevel:level]; LP_END_TRY } @@ -389,9 +390,14 @@ + (void)setDeviceId:(NSString *)deviceId // If Leanplum start has been called already, changing the deviceId results in a new device // Ensure the id is updated and the new device has all attributes set if ([LPInternalState sharedState].hasStarted && ![[Leanplum user].deviceId isEqualToString:deviceId]) { + if ([[MigrationManager shared] useCleverTap]) { + LPLog(LPInfo, @"Setting new device ID is not allowed when migration to CleverTap is turned on."); + return; + } + LPLog(LPInfo, @"Warning: When migration of data to CleverTap is turned on calling this method with different device ID would not work any more."); [self setDeviceIdInternal:deviceId]; } else { - [[Leanplum user] setDeviceId:deviceId]; + [[Leanplum user] setDeviceId:deviceId]; } LP_END_TRY } @@ -734,20 +740,7 @@ + (void)startWithUserId:(NSString *)userId if (IS_NOOP) { state.hasStarted = YES; state.startSuccessful = YES; - [[LPVarCache sharedCache] applyVariableDiffs:@{} - messages:@{} - variants:@[] - localCaps:@[] - regions:@{} - variantDebugInfo:@{} - varsJson:@"" - varsSignature:@""]; - dispatch_async(dispatch_get_main_queue(), ^{ - [self triggerStartResponse:YES]; - [self triggerVariablesChanged]; - [self triggerVariablesChangedAndNoDownloadsPending]; - [[self inbox] updateMessages:[[NSMutableDictionary alloc] init] unreadCount:0]; - }); + [self handleStartNOOP]; return; } @@ -763,7 +756,15 @@ + (void)startWithUserId:(NSString *)userId return; } + // Define Leanplum message templates [LPMessageTemplatesClass sharedTemplates]; + + if ([attributes count] > 0) { + [Leanplum onStartIssued:^{ + [[MigrationManager shared] setUserAttributes:attributes]; + }]; + } + attributes = [self validateAttributes:attributes named:@"userAttributes" allowLists:YES]; if (attributes != nil) { @synchronized([LPInternalState sharedState].userAttributeChanges) { @@ -771,9 +772,12 @@ + (void)startWithUserId:(NSString *)userId } } state.calledStart = YES; + + // TODO: delete dead code dispatch_async(dispatch_get_main_queue(), ^{ [Leanplum trackCrashes]; }); + state.actionManager = [LPActionTriggerManager sharedManager]; [[LPVarCache sharedCache] setSilent:YES]; @@ -793,84 +797,9 @@ + (void)startWithUserId:(NSString *)userId [self triggerVariablesChangedAndNoDownloadsPending]; }]; - // Set device ID. - NSString *deviceId = [Leanplum user].deviceId; - // This is the device ID set when the MAC address is used on iOS 7. - // This is to allow apps who upgrade to the new ID to forget the old one. - if ([deviceId isEqualToString:@"0f607264fc6318a92b9e13c65db7cd3c"]) { - deviceId = nil; - } - if (!deviceId) { - deviceId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; - if (!deviceId) { - deviceId = [[UIDevice currentDevice] leanplum_uniqueGlobalDeviceIdentifier]; - } - [[Leanplum user] setDeviceId:deviceId]; - } - - // Set user ID. - if (!userId) { - userId = [Leanplum user].userId; - if (!userId) { - userId = [Leanplum user].deviceId; - } - } - [[Leanplum user] setUserId:userId]; + [self setupUserWithUserId:userId]; - // Setup parameters. - NSString *versionName = [self appVersion]; - UIDevice *device = [UIDevice currentDevice]; - NSString *currentLocaleString = [self.locale localeIdentifier]; - - // Set the device name. But only if running in development mode. - NSString *deviceName = @""; - if ([LPConstantsState sharedState].isDevelopmentModeEnabled) { - deviceName = device.name ?: @""; - } - NSTimeZone *localTimeZone = [NSTimeZone localTimeZone]; - NSNumber *timezoneOffsetSeconds = - [NSNumber numberWithInteger:[localTimeZone secondsFromGMTForDate:[NSDate date]]]; - NSMutableDictionary *params = [@{ - LP_PARAM_INCLUDE_DEFAULTS: @(NO), - LP_PARAM_VERSION_NAME: versionName, - LP_PARAM_DEVICE_NAME: deviceName, - LP_PARAM_DEVICE_MODEL: [self platform], - LP_PARAM_DEVICE_SYSTEM_NAME: device.systemName, - LP_PARAM_DEVICE_SYSTEM_VERSION: device.systemVersion, - LP_KEY_LOCALE: currentLocaleString, - LP_KEY_TIMEZONE: [localTimeZone name], - LP_KEY_TIMEZONE_OFFSET_SECONDS: timezoneOffsetSeconds, - LP_KEY_COUNTRY: LP_VALUE_DETECT, - LP_KEY_REGION: LP_VALUE_DETECT, - LP_KEY_CITY: LP_VALUE_DETECT, - LP_KEY_LOCATION: LP_VALUE_DETECT, - LP_PARAM_RICH_PUSH_ENABLED: @([self isRichPushEnabled]) - } mutableCopy]; - if ([LPInternalState sharedState].isVariantDebugInfoEnabled) { - params[LP_PARAM_INCLUDE_VARIANT_DEBUG_INFO] = @(YES); - } - - if (attributes != nil) { - params[LP_PARAM_USER_ATTRIBUTES] = attributes ? - [LPJSON stringFromJSON:attributes] : @""; - } - if ([LPConstantsState sharedState].isDevelopmentModeEnabled) { - params[LP_PARAM_DEV_MODE] = @(YES); - } - - NSDictionary *timeParams = [self initializePreLeanplumInstall]; - if (timeParams) { - [params addEntriesFromDictionary:timeParams]; - } - - // Get the current Inbox messages on the device. - params[LP_PARAM_INBOX_MESSAGES] = [self.inbox messagesIds]; - - // Push token. - NSString *pushToken = [[Leanplum user] pushToken]; - if (pushToken) { - params[LP_PARAM_DEVICE_PUSH_TOKEN] = pushToken; - } + NSDictionary* params = [self setupStartParameters:attributes]; // Issue start API call. LPRequest *request = [[LPRequestFactory startWithParams:params] andRequestType:Immediate]; @@ -914,8 +843,6 @@ + (void)startWithUserId:(NSString *)userId [LPConstantsState sharedState].loggingEnabled = YES; } - // TODO: Need to call this if we fix encryption. - // [LPVarCache saveUserAttributes]; [self triggerStartResponse:YES]; // Allow bidirectional realtime variable updates. @@ -996,9 +923,158 @@ + (void)startWithUserId:(NSString *)userId fromMessageId:nil withContextualValues:nil]; }]; - [[LPRequestSender sharedInstance] send:request]; - [self triggerStartIssued]; + + [[MigrationManager shared] fetchMigrationState:^{ + if ([[MigrationManager shared] useCleverTap]) { + [[MigrationManager shared] launch]; + } + + if ([[MigrationManager shared] useLeanplum]) { + [[LPRequestSender sharedInstance] send:request]; + [Leanplum triggerStartIssued]; + } else { + [[LPInternalState sharedState] setCalledStart:YES]; + [[LPInternalState sharedState] setStartSuccessful:YES]; + [Leanplum triggerStartIssued]; + [Leanplum triggerStartResponse:YES]; + } + }]; + + [self addUIApplicationObservers]; + [self swizzleExtensionClose]; + + [self maybeRegisterForNotifications]; + LP_END_TRY + + LP_TRY + // TODO: delete dead code + [LPUtils initExceptionHandling]; + LP_END_TRY +} + ++ (void)handleStartNOOP +{ + [[LPVarCache sharedCache] applyVariableDiffs:@{} + messages:@{} + variants:@[] + localCaps:@[] + regions:@{} + variantDebugInfo:@{} + varsJson:@"" + varsSignature:@""]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self triggerStartResponse:YES]; + [self triggerVariablesChanged]; + [self triggerVariablesChangedAndNoDownloadsPending]; + [[self inbox] updateMessages:[[NSMutableDictionary alloc] init] unreadCount:0]; + }); +} + ++ (void)setupUserWithUserId:(NSString *)userId +{ + // Set device ID. + NSString *deviceId = [Leanplum user].deviceId; + // This is the device ID set when the MAC address is used on iOS 7. + // This is to allow apps who upgrade to the new ID to forget the old one. + if ([deviceId isEqualToString:@"0f607264fc6318a92b9e13c65db7cd3c"]) { + deviceId = nil; + } + if (!deviceId) { + deviceId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; + if (!deviceId) { + deviceId = [[UIDevice currentDevice] leanplum_uniqueGlobalDeviceIdentifier]; + } + [[Leanplum user] setDeviceId:deviceId]; + } + + // Set user ID. + if (!userId) { + userId = [Leanplum user].userId; + if (!userId) { + userId = [Leanplum user].deviceId; + } + } + [[Leanplum user] setUserId:userId]; +} + ++ (NSDictionary *)setupStartParameters:(NSDictionary *)attributes +{ + // Setup parameters. + NSString *versionName = [self appVersion]; + UIDevice *device = [UIDevice currentDevice]; + NSString *currentLocaleString = [self.locale localeIdentifier]; + + // Set the device name. But only if running in development mode. + NSString *deviceName = @""; + if ([LPConstantsState sharedState].isDevelopmentModeEnabled) { + deviceName = device.name ?: @""; + } + NSTimeZone *localTimeZone = [NSTimeZone localTimeZone]; + NSNumber *timezoneOffsetSeconds = + [NSNumber numberWithInteger:[localTimeZone secondsFromGMTForDate:[NSDate date]]]; + NSMutableDictionary *params = [@{ + LP_PARAM_INCLUDE_DEFAULTS: @(NO), + LP_PARAM_VERSION_NAME: versionName, + LP_PARAM_DEVICE_NAME: deviceName, + LP_PARAM_DEVICE_MODEL: [self platform], + LP_PARAM_DEVICE_SYSTEM_NAME: device.systemName, + LP_PARAM_DEVICE_SYSTEM_VERSION: device.systemVersion, + LP_KEY_LOCALE: currentLocaleString, + LP_KEY_TIMEZONE: [localTimeZone name], + LP_KEY_TIMEZONE_OFFSET_SECONDS: timezoneOffsetSeconds, + LP_KEY_COUNTRY: LP_VALUE_DETECT, + LP_KEY_REGION: LP_VALUE_DETECT, + LP_KEY_CITY: LP_VALUE_DETECT, + LP_KEY_LOCATION: LP_VALUE_DETECT, + LP_PARAM_RICH_PUSH_ENABLED: @([self isRichPushEnabled]) + } mutableCopy]; + if ([LPInternalState sharedState].isVariantDebugInfoEnabled) { + params[LP_PARAM_INCLUDE_VARIANT_DEBUG_INFO] = @(YES); + } + + if (attributes != nil) { + params[LP_PARAM_USER_ATTRIBUTES] = attributes ? + [LPJSON stringFromJSON:attributes] : @""; + } + if ([LPConstantsState sharedState].isDevelopmentModeEnabled) { + params[LP_PARAM_DEV_MODE] = @(YES); + } + + NSDictionary *timeParams = [self initializePreLeanplumInstall]; + if (timeParams) { + [params addEntriesFromDictionary:timeParams]; + } + // Get the current Inbox messages on the device. + params[LP_PARAM_INBOX_MESSAGES] = [self.inbox messagesIds]; + + // Push token. + NSString *pushToken = [[Leanplum user] pushToken]; + if (pushToken) { + params[LP_PARAM_DEVICE_PUSH_TOKEN] = pushToken; + } + + return params; +} + ++ (void)swizzleExtensionClose +{ + // Extension close. + if (_extensionContext) { + [LPSwizzle + swizzleMethod:@selector(completeRequestReturningItems:completionHandler:) + withMethod:@selector(leanplum_completeRequestReturningItems:completionHandler:) + error:nil + class:[NSExtensionContext class]]; + [LPSwizzle swizzleMethod:@selector(cancelRequestWithError:) + withMethod:@selector(leanplum_cancelRequestWithError:) + error:nil + class:[NSExtensionContext class]]; + } +} + ++ (void)addUIApplicationObservers +{ // Pause. [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification @@ -1050,25 +1126,6 @@ + (void)startWithUserId:(NSString *)userId [[LPRequestSender sharedInstance] send:request]; LP_END_TRY }]; - - // Extension close. - if (_extensionContext) { - [LPSwizzle - swizzleMethod:@selector(completeRequestReturningItems:completionHandler:) - withMethod:@selector(leanplum_completeRequestReturningItems:completionHandler:) - error:nil - class:[NSExtensionContext class]]; - [LPSwizzle swizzleMethod:@selector(cancelRequestWithError:) - withMethod:@selector(leanplum_cancelRequestWithError:) - error:nil - class:[NSExtensionContext class]]; - } - [self maybeRegisterForNotifications]; - LP_END_TRY - - LP_TRY - [LPUtils initExceptionHandling]; - LP_END_TRY } // On first run with Leanplum, determine if this app was previously installed without Leanplum. @@ -1110,6 +1167,7 @@ + (NSDictionary *)initializePreLeanplumInstall // sending push tokens to server. + (void)maybeRegisterForNotifications { + // if user has registered their own LPMessageTemplates Class userMessageTemplatesClass = NSClassFromString(@"LPMessageTemplates"); if (userMessageTemplatesClass && [[userMessageTemplatesClass sharedTemplates] @@ -1156,6 +1214,7 @@ + (void)resume [[LPRequestSender sharedInstance] send:request]; } +// TODO: delete dead code + (void)trackCrashes { LP_TRY @@ -1684,6 +1743,10 @@ + (void)trackPurchase:(NSString *)event withValue:(double)value if (currencyCode) { arguments[LP_PARAM_CURRENCY_CODE] = currencyCode; } + + [self onStartIssued:^{ + [[MigrationManager shared] trackPurchase:event value:value currencyCode:currencyCode params:params]; + }]; [Leanplum track:event withValue:value @@ -1866,6 +1929,10 @@ + (void)track:(NSString *)event andInfo:(NSString *)info andParameters:(NSDictionary *)params { + [self onStartIssued:^{ + [[MigrationManager shared] track:event value:value info:info params:params]; + }]; + [self track:event withValue:value andInfo:info andArgs:nil andParameters:params]; } @@ -1904,9 +1971,19 @@ + (void)setUserId:(NSString *)userId withUserAttributes:(NSDictionary *)attribut } LP_END_USER_CODE // Catch when setUser is called in start response. LP_TRY - attributes = [self validateAttributes:attributes named:@"userAttributes" allowLists:YES]; + NSDictionary *validAttributes = [self validateAttributes:attributes named:@"userAttributes" allowLists:YES]; [self onStartIssued:^{ - [self setUserIdInternal:userId withAttributes:attributes]; + NSString *currentUserId = [[Leanplum user] userId]; + [self setUserIdInternal:userId withAttributes:validAttributes]; + + if (![userId isEqualToString:currentUserId] && ![userId isEqual: @""]) { + // new userId is passed, login + [[MigrationManager shared] setUserId:userId]; + } + if ([attributes count] > 0) { + // use raw attributes passed instead of validated ones to prevent any transformation + [[MigrationManager shared] setUserAttributes:attributes]; + } }]; LP_END_TRY LP_BEGIN_USER_CODE @@ -1995,6 +2072,7 @@ + (void)setTrafficSourceInfo:(NSDictionary *)info info = [self validateAttributes:info named:@"info" allowLists:NO]; [self onStartIssued:^{ [self setTrafficSourceInfoInternal:info]; + [[MigrationManager shared] setTrafficSourceInfo:info]; }]; LP_END_TRY } @@ -2064,13 +2142,16 @@ + (void)advanceTo:(NSString *)state withInfo:(NSString *)info andParameters:(NSD if (info) { args[LP_PARAM_INFO] = info; } + + NSDictionary *validParams = params; if (params) { - params = [Leanplum validateAttributes:params named:@"params" allowLists:NO]; - args[LP_PARAM_PARAMS] = [LPJSON stringFromJSON:params]; + validParams = [Leanplum validateAttributes:params named:@"params" allowLists:NO]; + args[LP_PARAM_PARAMS] = [LPJSON stringFromJSON:validParams]; } [self onStartIssued:^{ - [self advanceToInternal:state withArgs:args andParameters:params]; + [self advanceToInternal:state withArgs:args andParameters:validParams]; + [[MigrationManager shared] advance:state info:info params:params]; }]; LP_END_TRY @@ -2565,6 +2646,11 @@ + (void)enableProvisionalPushNotifications [[Leanplum notificationsManager] enableProvisionalPush]; } ++ (void)onCleverTapInstanceInitialized:(LeanplumCleverTapInstanceBlock)block +{ + [[MigrationManager shared] setInstanceCallback:block]; +} + - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [[Leanplum notificationsManager].proxy removeDidFinishLaunchingObserver]; diff --git a/LeanplumSDK/LeanplumSDK/Classes/Leanplum.h b/LeanplumSDK/LeanplumSDK/Classes/Leanplum.h index fd1780e0..acad153c 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Leanplum.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Leanplum.h @@ -65,6 +65,8 @@ #import "LPLogManager.h" #import "LPRequestSenderTimer.h" #import "LPRequestSender.h" +// Forward declaration for CleverTap instance +@class CleverTap; NS_ASSUME_NONNULL_BEGIN @@ -146,6 +148,7 @@ typedef BOOL (^LeanplumActionBlock)(LPActionContext* context); typedef void (^LeanplumHandleNotificationBlock)(void); typedef void (^LeanplumShouldHandleNotificationBlock)(NSDictionary *userInfo, LeanplumHandleNotificationBlock response); typedef void (^LeanplumPushSetupBlock)(void); +typedef void (^LeanplumCleverTapInstanceBlock)(CleverTap* instance); /**@}*/ /** @@ -801,6 +804,15 @@ NS_SWIFT_NAME(setDeviceLocation(latitude:longitude:city:region:country:type:)); */ + (void)enableProvisionalPushNotifications API_AVAILABLE(ios(12.0)); +/** + * Block to call when CleverTapAPI instance is created. + * CleverTapSDK must be imported to use this method. + * Use the instance for any CleverTap work. + * + * @param block Null value will remove the callback. + */ ++ (void)onCleverTapInstanceInitialized:(LeanplumCleverTapInstanceBlock)block; + @end NS_ASSUME_NONNULL_END diff --git a/LeanplumSDK/LeanplumSDK/Classes/Managers/LPLogManager.h b/LeanplumSDK/LeanplumSDK/Classes/Managers/LPLogManager.h index 48f2e770..801339ad 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Managers/LPLogManager.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Managers/LPLogManager.h @@ -15,13 +15,13 @@ typedef NS_ENUM(NSUInteger, LPLogLevel) { LPLogLevelError, LPLogLevelInfo, LPLogLevelDebug -} NS_SWIFT_NAME(Leanplum.LogLevel); +} NS_SWIFT_NAME(LeanplumLogLevel); typedef NS_ENUM(NSUInteger, LPLogType) { LPError, LPInfo, LPDebug -} NS_SWIFT_NAME(Leanplum.LogType); +} NS_SWIFT_NAME(LeanplumLogType); @interface LPLogManager : NSObject @property (nonatomic, assign) LPLogLevel logLevel; diff --git a/LeanplumSDK/LeanplumSDK/Classes/Managers/LPRevenueManager.m b/LeanplumSDK/LeanplumSDK/Classes/Managers/LPRevenueManager.m index dcc1bbfd..1557d12e 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Managers/LPRevenueManager.m +++ b/LeanplumSDK/LeanplumSDK/Classes/Managers/LPRevenueManager.m @@ -28,6 +28,7 @@ #import "LPConstants.h" #import "LPUtils.h" #import "LPCountAggregator.h" +#import #pragma mark - SKPaymentQueue(LPSKPaymentQueueExtension) implementation @@ -187,18 +188,36 @@ - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProdu eventName = LP_PURCHASE_EVENT; } + + double value = [product.price doubleValue] * [transaction[@"quantity"] integerValue]; + NSNumber *isSandbox = [NSNumber numberWithBool:[LPConstantsState sharedState].isDevelopmentModeEnabled]; + NSString *transactionId = transaction[@"transactionIdentifier"]; + NSString *receiptData = transaction[@"receiptData"]; + + NSDictionary *params = @{ + @"item": transaction[@"productIdentifier"], + @"quantity": transaction[@"quantity"] + }; + + [Leanplum onStartIssued:^{ + [[MigrationManager shared] trackInAppPurchase:eventName + value:value + currencyCode:currencyCode + iOSTransactionIdentifier:transactionId + iOSReceiptData:receiptData + iOSSandbox:isSandbox + params:params]; + }]; + [Leanplum track:eventName - withValue:[product.price doubleValue] * [transaction[@"quantity"] integerValue] + withValue:value andArgs:@{ LP_PARAM_CURRENCY_CODE: currencyCode, - @"iOSTransactionIdentifier": transaction[@"transactionIdentifier"], - @"iOSReceiptData": transaction[@"receiptData"], - @"iOSSandbox": [NSNumber numberWithBool:[LPConstantsState sharedState].isDevelopmentModeEnabled] + @"iOSTransactionIdentifier": transactionId, + @"iOSReceiptData": receiptData, + @"iOSSandbox": isSandbox } - andParameters:@{ - @"item": transaction[@"productIdentifier"], - @"quantity": transaction[@"quantity"] - }]; + andParameters:params]; [self.transactions removeObjectForKey:transactionIdentifier]; [self saveTransactions]; diff --git a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPNetworkConstants.h b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPNetworkConstants.h index a97b4cbe..ef956482 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPNetworkConstants.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPNetworkConstants.h @@ -39,4 +39,5 @@ static NSString *LP_API_METHOD_LOG = @"log"; static NSString *LP_API_METHOD_GET_INBOX_MESSAGES = @"getNewsfeedMessages"; static NSString *LP_API_METHOD_MARK_INBOX_MESSAGE_AS_READ = @"markNewsfeedMessageAsRead"; static NSString *LP_API_METHOD_DELETE_INBOX_MESSAGE = @"deleteNewsfeedMessage"; +static NSString *LP_API_METHOD_GET_MIGRATE_STATE = @"getMigrateState"; static int LP_MAX_EVENTS_PER_API_CALL = 10000; diff --git a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestFactory.h b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestFactory.h index c2f8ca4c..fe2cf4ae 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestFactory.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestFactory.h @@ -57,6 +57,7 @@ NS_ASSUME_NONNULL_BEGIN + (LPRequest *)getNewsfeedMessagesWithParams:(nullable NSDictionary *)params; + (LPRequest *)markNewsfeedMessageAsReadWithParams:(nullable NSDictionary *)params; + (LPRequest *)deleteNewsfeedMessageWithParams:(nullable NSDictionary *)params; ++ (LPRequest *)getMigrateState; @end diff --git a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestFactory.m b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestFactory.m index 3bd8d440..d7ae562c 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestFactory.m +++ b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestFactory.m @@ -193,6 +193,12 @@ + (LPRequest *)deleteNewsfeedMessageWithParams:(NSDictionary *)params return [LPRequestFactory createPostForApiMethod:LP_API_METHOD_DELETE_INBOX_MESSAGE params:params]; } ++ (LPRequest *)getMigrateState +{ + [[LPCountAggregator sharedAggregator] incrementCount:@"get_migrate_state"]; + return [LPRequestFactory createGetForApiMethod:LP_API_METHOD_GET_MIGRATE_STATE params:nil]; +} + #pragma mark Private methods + (LPRequest *)createGetForApiMethod:(NSString *)apiMethod params:(NSDictionary *)params { diff --git a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSender.h b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSender.h index eac9275a..26267b2a 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSender.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSender.h @@ -32,4 +32,6 @@ - (void)send:(LPRequest *)request; +- (void)sendRequests; + @end diff --git a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSender.m b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSender.m index eb5b550c..d2864569 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSender.m +++ b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSender.m @@ -54,32 +54,40 @@ - (BOOL)updateApiConfig:(id)json; @implementation LPRequestSender + (instancetype)sharedInstance { - static LPRequestSender *sharedManager = nil; + static LPRequestSender *sharedSender = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedManager = [[self alloc] init]; + sharedSender = [[self alloc] init]; }); - return sharedManager; + return sharedSender; } - (id)init { self = [super init]; if (self) { - if (_engine == nil) { - if (!_requestHeaders) { - _requestHeaders = [LPNetworkEngine createHeaders]; - } - _engine = [LPNetworkFactory engineWithCustomHeaderFields:_requestHeaders]; - } - [[LPRequestSenderTimer sharedInstance] start]; - _countAggregator = [LPCountAggregator sharedAggregator]; + [self initialize]; } return self; } +- (void)initialize +{ + if (_engine == nil) { + if (!_requestHeaders) { + _requestHeaders = [LPNetworkEngine createHeaders]; + } + _engine = [LPNetworkFactory engineWithCustomHeaderFields:_requestHeaders]; + } + [[LPRequestSenderTimer sharedInstance] start]; + _countAggregator = [LPCountAggregator sharedAggregator]; +} + - (void)send:(LPRequest *)request { + if (![[MigrationManager shared] useLeanplum]) + return; + [self saveRequest:request]; if ([LPConstantsState sharedState].isDevelopmentModeEnabled || request.requestType == Immediate) { if ([self validateConfigFor:request]) { @@ -121,7 +129,10 @@ - (BOOL)validateConfigFor:(LPRequest *)request - (void)sendNow:(LPRequest *)request { RETURN_IF_TEST_MODE; - + + if (![[MigrationManager shared] useLeanplum]) + return; + [self sendRequests]; [self.countAggregator incrementCount:@"send_now_lp"]; @@ -152,6 +163,10 @@ - (void)saveRequest:(LPRequest *)request NSMutableDictionary *args = [request createArgsDictionary]; args[LP_PARAM_UUID] = uuid; + if ([[MigrationManager shared] useCleverTap]) { + args[MigrationManager.lpCleverTapRequestArg] = @YES; + } + [LPEventDataManager addEvent:args]; [LPEventCallbackManager addEventCallbackAt:count @@ -268,6 +283,8 @@ - (void)sendRequests return; } + [[MigrationManager shared] handleMigrateStateWithMultiApiResponse:json]; + // Delete events on success. [LPRequestBatchFactory deleteFinishedBatch:batch]; @@ -281,6 +298,7 @@ - (void)sendRequests requests:batch.requestsToSend operation:operation]; } + dispatch_semaphore_signal(semaphore); LP_END_TRY @@ -290,7 +308,7 @@ - (void)sendRequests dispatch_semaphore_signal(semaphore); return; } - + // Retry on 500 and other network failures. NSInteger httpStatusCode = completedOperation.HTTPStatusCode; if (httpStatusCode == 408 @@ -327,7 +345,10 @@ - (void)sendRequests // Invoke errors on all requests. [LPEventCallbackManager invokeErrorCallbacksWithError:err]; - [[LPOperationQueue serialQueue] cancelAllOperations]; + // [[LPOperationQueue serialQueue] cancelAllOperations]; + + + dispatch_semaphore_signal(semaphore); LP_END_TRY }]; @@ -345,7 +366,7 @@ - (void)sendRequests NSError *error = [NSError errorWithDomain:@"Leanplum" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Request timed out"}]; [LPEventCallbackManager invokeErrorCallbacksWithError:error]; - [[LPOperationQueue serialQueue] cancelAllOperations]; +// [[LPOperationQueue serialQueue] cancelAllOperations]; LP_END_TRY } LP_END_TRY diff --git a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSenderTimer.h b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSenderTimer.h index fd5155d0..9c99a7a7 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSenderTimer.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSenderTimer.h @@ -23,6 +23,7 @@ typedef enum : NSUInteger { @property (assign) LPEventsUploadInterval timerInterval; + (instancetype)sharedInstance; - (void)start; +- (void)invalidate; @end NS_ASSUME_NONNULL_END diff --git a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSenderTimer.m b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSenderTimer.m index d04c42a9..678461c6 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSenderTimer.m +++ b/LeanplumSDK/LeanplumSDK/Classes/Managers/Networking/LPRequestSenderTimer.m @@ -12,6 +12,10 @@ #import "LPRequestFactory.h" #import "LPRequestSender.h" +@interface LPRequestSenderTimer() +@property (weak) NSTimer *intervalTimer; +@end + @implementation LPRequestSenderTimer + (instancetype)sharedInstance { @@ -37,7 +41,7 @@ - (void)start { NSTimeInterval heartbeatInterval = self.timerInterval * 60; // Heartbeat. - [LPTimerBlocks scheduledTimerWithTimeInterval:heartbeatInterval block:^() { + _intervalTimer = [LPTimerBlocks scheduledTimerWithTimeInterval:heartbeatInterval block:^() { RETURN_IF_NOOP; LP_TRY if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive) { @@ -49,4 +53,9 @@ - (void)start } repeats:YES]; } +- (void)invalidate +{ + [_intervalTimer invalidate]; +} + @end diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Extensions/Dictionary+MapKeys.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Extensions/Dictionary+MapKeys.swift new file mode 100644 index 00000000..cdc59165 --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Extensions/Dictionary+MapKeys.swift @@ -0,0 +1,49 @@ +// +// Dictionary+MapKeys.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 6.10.22. +// + +import Foundation + +extension Dictionary { + /// Transforms dictionary keys without modifying values. + /// Deduplicates transformed keys, by choosing the first value. + /// + /// Example: + /// ``` + /// ["one": 1, "two": 2, "three": 3, "": 4].mapKeys({ $0.first }) + /// // [Optional("o"): 1, Optional("t"): 2, nil: 4] + /// ``` + /// + /// - Parameters: + /// - transform: A closure that accepts each key of the dictionary as + /// its parameter and returns a transformed key of the same or of a different type. + /// - Returns: A dictionary containing the transformed keys and values of this dictionary. + func mapKeys(_ transform: (Key) throws -> T) rethrows -> [T: Value] { + try .init(map { (try transform($0.key), $0.value) }, + uniquingKeysWith: { (a, b) in a }) + } + + /// Transforms dictionary keys without modifying values. + /// Deduplicates transformed keys. + /// + /// Example: + /// ``` + /// ["one": 1, "two": 2, "three": 3, "": 4].mapKeys({ $0.first }, uniquingKeysWith: { max($0, $1) }) + /// // [Optional("o"): 1, Optional("t"): 3, nil: 4] + /// ``` + /// Credits: https://forums.swift.org/t/mapping-dictionary-keys/15342/4 + /// + /// - Parameters: + /// - transform: A closure that accepts each key of the dictionary as + /// its parameter and returns a transformed key of the same or of a different type. + /// - combine:A closure that is called with the values for any duplicate + /// keys that are encountered. The closure returns the desired value for + /// the final dictionary. + /// - Returns: A dictionary containing the transformed keys and values of this dictionary. + func mapKeys(_ transform: (Key) throws -> T, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [T: Value] { + try .init(map { (try transform($0.key), $0.value) }, uniquingKeysWith: combine) + } +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+API.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+API.swift new file mode 100644 index 00000000..fd989a14 --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+API.swift @@ -0,0 +1,65 @@ +// +// MigrationManager+API.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 6.10.22. +// + +@objc public extension MigrationManager { + + func launch() { + wrapper?.launch() + } + + var state: MigrationState { + return migrationState + } + + func track(_ eventName: String?, value: Double, info: String?, params: [String: Any]) { + wrapper?.track(eventName, value: value, info: info, params: params) + } + + func trackPurchase(_ eventName: String?, value: Double, currencyCode: String?, params: [String: Any]) { + wrapper?.trackPurchase(eventName, value: value, currencyCode: currencyCode, params: params) + } + + func trackInAppPurchase(_ eventName: String?, + value: Double, + currencyCode: String?, + iOSTransactionIdentifier: String?, + iOSReceiptData: String?, + iOSSandbox: Bool, + params: [String: Any]) { + wrapper?.trackInAppPurchase(eventName, + value: value, + currencyCode: currencyCode, + iOSTransactionIdentifier: iOSTransactionIdentifier, + iOSReceiptData: iOSReceiptData, + iOSSandbox: iOSSandbox, + params: params) + } + + func advance(_ eventName: String?, info: String?, params: [String: Any]) { + wrapper?.advance(eventName, info: info, params: params) + } + + func setUserAttributes(_ attributes: [AnyHashable: Any]) { + wrapper?.setUserAttributes(attributes) + } + + func setUserId(_ userId: String) { + wrapper?.setUserId(userId) + } + + func setTrafficSourceInfo(_ info: [AnyHashable: Any]) { + wrapper?.setTrafficSourceInfo(info) + } + + func setInstanceCallback(_ callback: @escaping ((Any) -> Void)) { + instanceCallback = callback + } + + func setLogLevel(_ level: LeanplumLogLevel) { + wrapper?.setLogLevel(level) + } +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+Constants.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+Constants.swift new file mode 100644 index 00000000..cccf5589 --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+Constants.swift @@ -0,0 +1,41 @@ +// +// MigrationManager+Constants.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 2.10.22. +// + +import Foundation + +@objc extension MigrationManager { + enum Constants { + static let AccountIdKey = "__leanplum_ct_account_key" + static let HashKey = "__leanplum_ct_hash_key" + static let AccountTokenKey = "__leanplum_ct_account_token" + static let MigrationStateKey = "__leanplum_migration_state" + static let RegionCodeKey = "__leanplum_region_code" + static let AttributeMappingsKey = "__leanplum_attribute_mappings" + + static let MigrateStateResponseParam = "migrateState" + static let MigrateStateNotificationInfo = "migrateState" + static let SdkResponseParam = "sdk" + static let CTResponseParam = "ct" + static let AccountIdResponseParam = "accountId" + static let AccountTokenResponseParam = "token" + static let RegionCodeResponseParam = "regionCode" + static let AttributeMappingsResponseParam = "attributeMappings" + static let HashResponseParam = "sha256"; + + static let CleverTapRequestArg = "ct" + } + + @objc + public class func lpMigrateStateNotificationInfo() -> String { + return Constants.MigrateStateNotificationInfo + } + + @objc + public class func lpCleverTapRequestArg() -> String { + return Constants.CleverTapRequestArg + } +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+ResponseHandler.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+ResponseHandler.swift new file mode 100644 index 00000000..7e00b0ed --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+ResponseHandler.swift @@ -0,0 +1,81 @@ +// +// MigrationManager+ResponseHandler.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 3.10.22. +// + +import Foundation + +@objc public extension MigrationManager { + // migrateState = { + // sha256 = 31484a565dcd3e1672922c7c4166bfeee0f500b6d6473fc412091304cc162ca8; + // }; + @objc + func handleMigrateState(multiApiResponse: Any) { + guard let migrateState = getValue(dict: multiApiResponse, + key: Constants.MigrateStateResponseParam) + else { return } + + if let hash = getValue(dict: migrateState, key: Constants.HashKey) as? String, + hash != self.migrationHash { + Log.debug("[Wrapper] CleverTap Hash changed") + fetchMigrationStateAsync {} + } + } + + // response = ( + // { + // api = { + // events = "lp+ct"; + // profile = "lp+ct"; + // }; + // ct = { + // accountId = "accId"; + // attributeMappings = { + // name1 = "ct-name1"; + // }; + // regionCode = eu1; + // token = "token"; + // }; + // eventsUploadStartedTs = "2022-10-02T17:46:01.356Z"; + // profileUploadStartedTs = "2022-10-02T17:46:01.356Z"; + // reqId = "A285641F-9903-4182-8A10-EB42782CAE69"; + // sdk = "lp+ct"; + // sha256 = 31484a565dcd3e1672922c7c4166bfeee0f500b6d6473fc412091304cc162ca8; + // state = "EVENTS_UPLOAD_STARTED"; + // success = 1; + // } + // ); + func handleGetMigrateState(apiResponse: Any) { + if let ct = getValue(dict: apiResponse, key: Constants.CTResponseParam) { + if let id = getValue(dict: ct, key: Constants.AccountIdResponseParam) as? String { + accountId = id + } + if let token = getValue(dict: ct, key: Constants.AccountTokenResponseParam) as? String { + accountToken = token + } + if let region = getValue(dict: ct, key: Constants.RegionCodeResponseParam) as? String { + regionCode = region + } + if let mappings = getValue(dict: ct, key: Constants.AttributeMappingsResponseParam) as? [String: String] { + attributeMappings = mappings + } + } + + if let sdk = getValue(dict: apiResponse, key: Constants.SdkResponseParam) as? String { + migrationState = MigrationState(stringValue: sdk) + } + if let hash = getValue(dict: apiResponse, key: Constants.HashKey) as? String { + migrationHash = hash + } + } + + private func getValue(dict: Any, key: String) -> Any? { + guard let dict = dict as? [String: Any] else { + return nil + } + + return dict[key] + } +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager.swift new file mode 100644 index 00000000..0ec392bd --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager.swift @@ -0,0 +1,168 @@ +// +// MigrationManager.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 13.07.22. +// + +import Foundation + +@objc public class MigrationManager: NSObject { + + private override init() { + super.init() + } + + @objc public static let shared: MigrationManager = .init() + + var wrapper: Wrapper? + + @StringOptionalUserDefaults(key: Constants.HashKey) + var migrationHash: String? + + @StringOptionalUserDefaults(key: Constants.AccountIdKey) + var accountId: String? + + @StringOptionalUserDefaults(key: Constants.AccountTokenKey) + var accountToken: String? + + @StringOptionalUserDefaults(key: Constants.RegionCodeKey) + var regionCode: String? + + @PropUserDefaults(key: Constants.AttributeMappingsKey, defaultValue: [:]) + var attributeMappings: [String: String] + + @MigrationStateUserDefaults(key: Constants.HashKey, defaultValue: .undefined) + var migrationState: MigrationState { + didSet { + if oldValue != migrationState { + handleMigrationStateChanged(oldValue: oldValue) + } + } + } + + private let lock = NSLock() + + var instanceCallback: ((Any) -> Void)? { + didSet { + wrapper?.setInstanceCallback(instanceCallback) + } + } + + // Expose to ObjC + @objc public var useLeanplum: Bool { + migrationState.useLeanplum + } + + // Expose to ObjC + @objc public var useCleverTap: Bool { + migrationState.useCleverTap + } + + func initWrapper() { + if migrationState.useCleverTap { + guard let id = accountId, let token = accountToken, let accountRegion = regionCode else { + Log.error("[Wrapper] Missing CleverTap Credentials. Cannot initialize CleverTap.") + return + } + guard let user = Leanplum.userId(), let device = Leanplum.deviceId() else { + Log.error("[Wrapper] Missing Leanplum userId and deviceId. Cannot initialize CleverTap.") + return + } + + wrapper = CTWrapper(accountId: id, accountToken: token, + accountRegion: accountRegion, + userId: user, deviceId: device, + callback: instanceCallback) + + if Leanplum.hasStarted() { + Log.debug("[Wrapper] Leanplum has already started, launching CleverTap as well.") + wrapper?.launch() + } + } + } + + func handleMigrationStateChanged(oldValue: MigrationState) { + // Note: It is not possible to return from CT only state since status comes from LP API + + if (!oldValue.useCleverTap && migrationState.useCleverTap) { + // Flush all saved requests to Leanplum + LPRequestSender.sharedInstance().sendRequests() + // Create wrapper + initWrapper() + } + + if (oldValue.useLeanplum && !migrationState.useLeanplum) { + LPOperationQueue.serialQueue().addOperation { + // Flush all saved data to LP + LPRequestSender.sharedInstance().sendRequests() + // Delete LP data + VarCache.shared().clearUserContent() + VarCache.shared().saveDiffs() + } + } + + if (oldValue.useCleverTap && !migrationState.useCleverTap) { + // Remove wrapper + wrapper = nil + } + } + + + // onMigrationStateLoaded + + @objc public func fetchMigrationState(_ completion: @escaping ()->()) { + if migrationState != .undefined { + initWrapper() + completion() + return + } + + fetchMigrationStateClosures.append(completion) + } + + var fetchMigrationStateClosures:[(() -> Void)] = [] { + willSet { + lock.lock() + } + didSet { + defer { + lock.unlock() + } + if oldValue.isEmpty && fetchMigrationStateClosures.count > 0 { + fetchMigrationStateAsync { [weak self] in + self?.triggerFetchMigrationState() + } + } + } + } + + private func triggerFetchMigrationState() { + let closures = fetchMigrationStateClosures + fetchMigrationStateClosures = [] + for closure in closures { + closure() + } + } + + func fetchMigrationStateAsync(completion: @escaping ()->()) { + let request = LPRequestFactory.getMigrateState() + request.requestType = .Immediate + request.onResponse { operation, response in + guard let response = response else { + Log.error("[Wrapper] No response received for getMigrateState") + return + } + + Log.debug("[Wrapper] getMigrateState success: \(response)") + self.handleGetMigrateState(apiResponse: response) + completion() + } + + request.onError { err in + Log.error("[Wrapper] Error on getMigrateState: \(err?.localizedDescription ?? "nil")") + completion() + } + LPRequestSender.sharedInstance().send(request) + } +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationState.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationState.swift new file mode 100644 index 00000000..f09df9b8 --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationState.swift @@ -0,0 +1,44 @@ +// +// MigrationState.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 2.10.22. +// + +import Foundation + +@objc public enum MigrationState: Int, CustomStringConvertible, CaseIterable { + + case undefined = 0, + leanplum, + duplicate, + cleverTap + + public var description: String { + switch self { + case .undefined: + return "undefined" + case .leanplum: + return "lp" + case .duplicate: + return "lp+ct" + case .cleverTap: + return "ct" + } + } + + public init(stringValue: String) { + let value = Self.allCases.first { + $0.description == stringValue + } + self = value ?? .undefined + } + + var useLeanplum: Bool { + self != .cleverTap + } + + var useCleverTap: Bool { + self == .cleverTap || self == .duplicate + } +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/CTWrapper+Utilities.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/CTWrapper+Utilities.swift new file mode 100644 index 00000000..15e514ab --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/CTWrapper+Utilities.swift @@ -0,0 +1,58 @@ +// +// CTWrapper+Utilities.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 6.10.22. +// + +import Foundation +// Use @_implementationOnly to *not* expose CleverTapSDK to the Leanplum-Swift header +@_implementationOnly import CleverTapSDK + +extension CTWrapper { + func isAnyNil(_ value: Any) -> Bool { + if case Optional.none = value { + return true + } + return false + } + + var transformAttributeValues: ((Any) -> Any) { + return { value in + if let arr = value as? Array { + let arrString = arr.map { + String(describing: $0) + } + return ("[\(arrString.joined(separator: ","))]") as Any + } + return value + } + } + + var transformAttributeKeys: ((AnyHashable) -> AnyHashable) { + return { key in + guard let keyStr = key as? String, + let newKey = MigrationManager.shared.attributeMappings[keyStr] + else { + return key + } + + return newKey + } + } +} + +extension CleverTapLogLevel { + init(_ level: LeanplumLogLevel) { + switch level { + case .off: + self = .off + case .error, .info: + self = .info + case .debug: + self = .debug + default: + self = .info + } + } +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/CTWrapper.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/CTWrapper.swift new file mode 100644 index 00000000..2e477834 --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/CTWrapper.swift @@ -0,0 +1,236 @@ +// +// CTWrapper.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 9.07.22. +// + +import Foundation +// Use @_implementationOnly to *not* expose CleverTapSDK to the Leanplum-Swift header +@_implementationOnly import CleverTapSDK + +class CTWrapper: Wrapper { + // MARK: Constants + enum Constants { + static let StatePrefix = "state_" + static let UTMVisitedEvent = "UTM Visited" + + static let ValueParamName = "value" + static let InfoParamName = "info" + static let ChargedEventParam = "event" + static let CurrencyCodeParam = "currencyCode" + static let iOSTransactionIdentifierParam = "iOSTransactionIdentifier" + static let iOSReceiptDataParam = "iOSReceiptData" + static let iOSSandboxParam = "iOSSandbox" + } + + // MARK: Initialization + var cleverTapInstance: CleverTap? + var instanceCallback: ((Any) -> Void)? + + var accountId: String + var accountToken: String + var accountRegion: String + var identityManager: IdentityManager + + public init(accountId: String, + accountToken: String, + accountRegion: String, + userId: String, + deviceId: String, + callback: ((Any) -> Void)?) { + Log.debug("[Wrapper] Wrapper Instantiated") + self.accountId = accountId + self.accountToken = accountToken + self.accountRegion = accountRegion + self.instanceCallback = callback + + identityManager = IdentityManager(userId: userId, deviceId: deviceId) + setLogLevel(LPLogManager.logLevel()) + } + + func launch() { + guard Thread.isMainThread else { + DispatchQueue.main.async { [self] in + launch() + } + return + } + + let config = CleverTapInstanceConfig.init(accountId: accountId, accountToken: accountToken, accountRegion: accountRegion) + config.useCustomCleverTapId = true + config.logLevel = CleverTapLogLevel(LPLogManager.logLevel()) + cleverTapInstance = CleverTap.instance(with: config, andCleverTapID: identityManager.cleverTapID) + cleverTapInstance?.setLibrary("Leanplum") + + Log.debug("[Wrapper] CleverTap instance created with accountId: \(accountId)") + + if !identityManager.isAnonymous { + Log.debug(""" + [Wrapper] will call onUserLogin with \ + Identity: \(identityManager.userId) and cleverTapId: \(identityManager.cleverTapID)" + """) + cleverTapInstance?.onUserLogin(identityManager.profile, + withCleverTapID: identityManager.cleverTapID) + } + triggerInstanceCallback() + } + + private func triggerInstanceCallback() { + guard let callback = instanceCallback, let instance = cleverTapInstance else { + return + } + + callback(instance) + } + + func setInstanceCallback(_ callback: ((Any) -> Void)?) { + instanceCallback = callback + triggerInstanceCallback() + } + + // MARK: Tracking + func track(_ eventName: String?, value: Double, info: String?, params: [String: Any]) { + // message impression events come with event: nil + guard let eventName = eventName else { + return + } + + var eventParams = params.mapValues(transformAttributeValues) + eventParams[Constants.ValueParamName] = value + + if let info = info { + eventParams[Constants.InfoParamName] = info + } + + Log.debug("Leanplum.track will call recordEvent with \(eventName) and \(eventParams)") + cleverTapInstance?.recordEvent(eventName, withProps: eventParams) + } + + func advance(_ stateName: String?, info: String?, params: [String: Any]) { + guard let stateName = stateName else { + return + } + + let eventName = Constants.StatePrefix + stateName + Log.debug("Leanplum.advance will call track with \(eventName) and \(params)") + track(eventName, value: 0.0, info: info, params: params) + } + + func trackPurchase(_ eventName: String?, value: Double, currencyCode: String?, params: [String: Any]) { + guard let eventName = eventName else { + return + } + + var details = params.mapValues(transformAttributeValues) + details[Constants.ChargedEventParam] = eventName + details[Constants.ValueParamName] = value + + if let currencyCode = currencyCode { + details[Constants.CurrencyCodeParam] = currencyCode + } + + let items: [Any] = [] + + Log.debug("[Wrapper] Leanplum.trackPurchase will call recordChargedEvent with \(details) and \(items)") + cleverTapInstance?.recordChargedEvent(withDetails: details, andItems: items) + } + + func trackInAppPurchase(_ eventName: String?, value: Double, currencyCode: String?, + iOSTransactionIdentifier: String?, iOSReceiptData: String?, + iOSSandbox: Bool, params: [String: Any]) { + guard let eventName = eventName else { + return + } + + // item and quantity are already in the parameters + // and they are the only ones + var details = params.mapValues(transformAttributeValues) + details[Constants.ChargedEventParam] = eventName + details[Constants.ValueParamName] = value + + if let currencyCode = currencyCode { + details[Constants.CurrencyCodeParam] = currencyCode + } + if let iOSTransactionIdentifier = iOSTransactionIdentifier { + details[Constants.iOSTransactionIdentifierParam] = iOSTransactionIdentifier + } + if let iOSReceiptData = iOSReceiptData { + details[Constants.iOSReceiptDataParam] = iOSReceiptData + } + details[Constants.iOSSandboxParam] = iOSSandbox + + let items: [Any] = [] + + Log.debug("[Wrapper] Leanplum.trackInAppPurchase will call recordChargedEvent with \(details) and \(items)") + cleverTapInstance?.recordChargedEvent(withDetails: details, andItems: items) + } + + // MARK: User + func setUserAttributes(_ attributes: [AnyHashable: Any]) { + // .compactMapValues { $0 } will not work on not optional type Any which can still hold nil + let profileAttributes = attributes + .filter { !isAnyNil($0.value) } + .mapValues(transformAttributeValues) + .mapKeys(transformAttributeKeys) + + Log.debug("[Wrapper] Leanplum.setUserAttributes will call profilePush with \(profileAttributes)") + cleverTapInstance?.profilePush(profileAttributes) + + attributes + .filter { isAnyNil($0.value) } + .mapKeys(transformAttributeKeys) + .forEach { + Log.debug("[Wrapper] Leanplum.setUserAttributes will call profileRemoveValue forKey: \($0.key)") + cleverTapInstance?.profileRemoveValue(forKey: String(describing: $0.key)) + } + } + + func setUserId(_ userId: String) { + guard userId != identityManager.userId else { return } + + identityManager.setUserId(userId) + + let profile = identityManager.profile + let cleverTapID = identityManager.cleverTapID + + Log.debug(""" + [Wrapper] Leanplum.setUserId will call onUserLogin \ + with identity: \(profile) \ + and CleverTapID: \(cleverTapID)") + """) + cleverTapInstance?.onUserLogin(profile, withCleverTapID: cleverTapID) + } + + // MARK: Traffic Source + func setTrafficSourceInfo(_ info: [AnyHashable: Any]) { + let trafficSourceInfoMappings = [ + "publisherId": "utm_source_id", + "publisherName": "utm_source", + "publisherSubPublisher": "utm_medium", + "publisherSubSite": "utm_subscribe.site", + "publisherSubCampaign": "utm_campaign", + "publisherSubAdGroup": "utm_sourcepublisher.ad_group", + "publisherSubAd": "utm_SourcePublisher.ad" + ] + + let props = info.mapKeys({ key -> AnyHashable in + guard let keyStr = key as? String, + let newKey = trafficSourceInfoMappings[keyStr] + else { + return key + } + return newKey + }) + + Log.debug("[Wrapper] Leanplum.setTrafficSourceInfo will call pushEvent with \(Constants.UTMVisitedEvent) and \(props)") + cleverTapInstance?.recordEvent(Constants.UTMVisitedEvent, withProps: props) + } + + // MARK: Log Level + func setLogLevel(_ level: LeanplumLogLevel) { + let ctLevel = CleverTapLogLevel(level) + CleverTap.setDebugLevel(ctLevel.rawValue) + cleverTapInstance?.config.logLevel = ctLevel + } +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/IdentityManager.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/IdentityManager.swift new file mode 100644 index 00000000..5408b3e8 --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/IdentityManager.swift @@ -0,0 +1,100 @@ +// +// IdentityManager.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 6.10.22. +// + +import Foundation + +/** + * Identity mapping between Leanplum userId and deviceId and CleverTap Identity and CTID. + * + * Mappings: + * - anonymous: + * - non-anonymous to + * + * - Note: On login of anonymous user, a merge should happen. CleverTap SDK allows merges + * only when the CTID remains the same, meaning that the merged profile would get the anonymous + * profile's CTID: . + * In order to keep track which is that userId, it is saved into `anonymousLoginUserId`. + * For this userId, the CTID is always set to deviceId. + * Leanplum UserId can be set through Leanplum.start and Leanplum.setUserId + * + * - Precondition: DeviceId cannot be changed when CT is used, + * since CTID cannot be modified for the same Identity. +*/ +class IdentityManager { + enum Constants { + static let Identity = "Identity" + static let AnonymousLoginUserIdKey = "__leanplum_anonymous_login_user_id" + static let IdentityStateKey = "__leanplum_identity_state" + } + + enum IdentityState: String { + case anonymous = "anonymous" + case identified = "identified" + + func callAsFunction() -> String { + return self.rawValue + } + } + + private(set) var userId: String + private(set) var deviceId: String + + @StringOptionalUserDefaults(key: Constants.AnonymousLoginUserIdKey) + var anonymousLoginUserId: String? + + @StringOptionalUserDefaults(key: Constants.IdentityStateKey) + var state: String? + + init(userId: String, deviceId: String) { + self.userId = userId + self.deviceId = deviceId + + identify() + } + + func setUserId(_ userId: String) { + if (state == IdentityState.anonymous()) { + anonymousLoginUserId = userId + Log.debug("[Wrapper] Anonymous user on device \(deviceId) will be merged to \(userId)") + state = IdentityState.identified() + } + self.userId = userId + } + + func identify() { + if isAnonymous { + state = IdentityState.anonymous() + } else { + identifyNonAnonymous() + } + } + + func identifyNonAnonymous() { + if let state = state, + state == IdentityState.anonymous() { + anonymousLoginUserId = userId + } + state = IdentityState.identified() + } + + var cleverTapID: String { + if userId != anonymousLoginUserId, + userId != deviceId { + return "\(deviceId)_\(userId)" + } + + return deviceId + } + + var profile: [AnyHashable: Any] { + [Constants.Identity: userId] + } + + var isAnonymous: Bool { + userId == deviceId + } +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/Wrapper.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/Wrapper.swift new file mode 100644 index 00000000..f25a964f --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/Wrapper.swift @@ -0,0 +1,37 @@ +// +// Wrapper.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 6.10.22. +// + +protocol Wrapper { + /// Launches the wrapper instance, + /// equivalent to Leanplum start + func launch() + + /// Sets instance callback when wrapper has initialized + func setInstanceCallback(_ callback: ((Any) -> Void)?) + + func track(_ eventName: String?, value: Double, info: String?, params: [String: Any]) + + func trackPurchase(_ eventName: String?, value: Double, currencyCode: String?, params: [String: Any]) + + func advance(_ stateName: String?, info: String?, params: [String: Any]) + + func trackInAppPurchase(_ eventName: String?, + value: Double, + currencyCode: String?, + iOSTransactionIdentifier: String?, + iOSReceiptData: String?, + iOSSandbox: Bool, + params: [String: Any]) + + func setUserAttributes(_ attributes: [AnyHashable: Any]) + + func setUserId(_ userId: String) + + func setTrafficSourceInfo(_ info: [AnyHashable: Any]) + + func setLogLevel(_ level: LeanplumLogLevel) +} diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Utilities/PropertyWrappers.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Utilities/PropertyWrappers.swift new file mode 100644 index 00000000..1c38fa99 --- /dev/null +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Utilities/PropertyWrappers.swift @@ -0,0 +1,64 @@ +// +// PropertyWrappers.swift +// LeanplumSDK +// +// Created by Nikola Zagorchev on 2.10.22. +// + +import Foundation + +@propertyWrapper +struct StringOptionalUserDefaults { + private var key: String + + var wrappedValue: String? { + get { UserDefaults.standard.string(forKey: key) } + set { UserDefaults.standard.setValue(newValue, forKey: key) } + } + + init(key: String) { + self.key = key + } +} + +@propertyWrapper +struct MigrationStateUserDefaults { + private var key: String + private var defaultValue: MigrationState + + public var wrappedValue: MigrationState { + get { + if let value = UserDefaults.standard.string(forKey: key) { + return MigrationState(stringValue: value) + } + return defaultValue + } + set { UserDefaults.standard.setValue(newValue.description, forKey: key) } + } + + init(key: String, defaultValue: MigrationState) { + self.key = key + self.defaultValue = defaultValue + } +} + +@propertyWrapper +struct PropUserDefaults { + private var key: String + private var defaultValue: T + + var wrappedValue: T { + get { + guard let value = UserDefaults.standard.object(forKey: key) as? T + else { return defaultValue } + + return value + } + set { UserDefaults.standard.setValue(newValue, forKey: key) } + } + + init(key: String, defaultValue: T) { + self.key = key + self.defaultValue = defaultValue + } +} diff --git a/LeanplumSDKApp/LeanplumSDKApp.xcodeproj/project.pbxproj b/LeanplumSDKApp/LeanplumSDKApp.xcodeproj/project.pbxproj index d3c2ad4a..3b665c8b 100644 --- a/LeanplumSDKApp/LeanplumSDKApp.xcodeproj/project.pbxproj +++ b/LeanplumSDKApp/LeanplumSDKApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -123,8 +123,13 @@ 6A07FDA52811AF3800995BE3 /* ContentMergerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A07FDA42811AF3800995BE3 /* ContentMergerTest.swift */; }; 6A07FDAE283544CE00995BE3 /* ActionManagerDownloadTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A07FDAD283544CE00995BE3 /* ActionManagerDownloadTest.swift */; }; 6A07FDB0283544E300995BE3 /* DictionaryValueKeyPathTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A07FDAF283544E300995BE3 /* DictionaryValueKeyPathTest.swift */; }; - 6A29EAA828E89D950024880E /* LeanplumLocationAndBeacons.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AF543E728E89C1E0025F2A9 /* LeanplumLocationAndBeacons.framework */; }; + 6A29EACD28EDA8DE0024880E /* CTWrapperTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A29EACC28EDA8DE0024880E /* CTWrapperTest.swift */; }; 6A2FE16027958D6000E4A8FE /* GeofencingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A2FE15F27958D6000E4A8FE /* GeofencingTest.m */; }; + 6A37A8AA28F0078500F4339F /* Migration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A8A928F0078500F4339F /* Migration+Extensions.swift */; }; + 6A37A8AC28F00B5800F4339F /* IdentityManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A8AB28F00B5800F4339F /* IdentityManagerTest.swift */; }; + 6A37A8AE28F0164700F4339F /* IdentityManagerMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A37A8AD28F0164700F4339F /* IdentityManagerMocks.swift */; }; + 6A37A8D128F03C8D00F4339F /* LeanplumLocationAndBeacons.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AF5439728E882CA0025F2A9 /* LeanplumLocationAndBeacons.framework */; }; + 6A37A8D228F03C8D00F4339F /* LeanplumLocationAndBeacons.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6AF5439728E882CA0025F2A9 /* LeanplumLocationAndBeacons.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 6A39C48F27283EE8000D5320 /* NotificationsProxyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A39C48E27283EE8000D5320 /* NotificationsProxyTest.swift */; }; 6A54E9CD273B156600DE0E61 /* NotificationsManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A54E9CC273B156600DE0E61 /* NotificationsManagerTest.swift */; }; 6A81EDDE284681D8001B4BE9 /* TiedPrioritiesDelay.json in Resources */ = {isa = PBXBuildFile; fileRef = 6A81EDDD284681D8001B4BE9 /* TiedPrioritiesDelay.json */; }; @@ -133,6 +138,7 @@ 6A9D0A9A273430A700466133 /* NotificationTestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A9D0A99273430A700466133 /* NotificationTestHelper.swift */; }; 6A9DECA527B6ABFB00052807 /* change_host_response.json in Resources */ = {isa = PBXBuildFile; fileRef = 6A9DECA427B69D8800052807 /* change_host_response.json */; }; 6A9DECA627B6ABFB00052807 /* change_host_response.json in Resources */ = {isa = PBXBuildFile; fileRef = 6A9DECA427B69D8800052807 /* change_host_response.json */; }; + 6AF543A228E883AD0025F2A9 /* LeanplumLocationAndBeacons.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AF5439728E882CA0025F2A9 /* LeanplumLocationAndBeacons.framework */; }; 779F1FAA8AA2F3872AC876B2 /* Pods_LeanplumSDKTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E3C9DA445D159EC6FF97D42D /* Pods_LeanplumSDKTests.framework */; }; C9D064B1275DFB4B00A7A5F9 /* LeanplumNotificationsManagerUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D064B0275DFB4B00A7A5F9 /* LeanplumNotificationsManagerUtilsTest.swift */; }; C9D503672754C9DC0034C5B3 /* PushNotificationSettingsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D503662754C9DC0034C5B3 /* PushNotificationSettingsTest.swift */; }; @@ -146,50 +152,79 @@ remoteGlobalIDString = 075AACE626847BF3007CA1BD; remoteInfo = LeanplumSDKApp; }; - 6A29EAA628E89D280024880E /* PBXContainerItemProxy */ = { + 6A37A8D328F03C9400F4339F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 6AF543DD28E89C1E0025F2A9 /* LeanplumSDKLocation.xcodeproj */; + containerPortal = 6AF5438D28E882CA0025F2A9 /* LeanplumSDKLocation.xcodeproj */; proxyType = 1; remoteGlobalIDString = 6A2FE16627958F0400E4A8FE; remoteInfo = LeanplumLocationAndBeacons; }; - 6AF543E428E89C1E0025F2A9 /* PBXContainerItemProxy */ = { + 6A37A8F628F08B0E00F4339F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 6AF543DD28E89C1E0025F2A9 /* LeanplumSDKLocation.xcodeproj */; + containerPortal = 6A37A8EE28F08B0E00F4339F /* LeanplumSDK.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 075AAD0826847C23007CA1BD; + remoteInfo = Leanplum; + }; + 6A37A8F828F08B0E00F4339F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6A37A8EE28F08B0E00F4339F /* LeanplumSDK.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 075AAEEC26847FE1007CA1BD; + remoteInfo = "Leanplum-Bundle"; + }; + 6A37A8FA28F08B0E00F4339F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6A37A8EE28F08B0E00F4339F /* LeanplumSDK.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 6A714BA326F8B317004A34A9; + remoteInfo = "Leanplum-Static"; + }; + 6AF5439428E882CA0025F2A9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6AF5438D28E882CA0025F2A9 /* LeanplumSDKLocation.xcodeproj */; proxyType = 2; remoteGlobalIDString = 6A2FE146279578F800E4A8FE; remoteInfo = LeanplumLocation; }; - 6AF543E628E89C1E0025F2A9 /* PBXContainerItemProxy */ = { + 6AF5439628E882CA0025F2A9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 6AF543DD28E89C1E0025F2A9 /* LeanplumSDKLocation.xcodeproj */; + containerPortal = 6AF5438D28E882CA0025F2A9 /* LeanplumSDKLocation.xcodeproj */; proxyType = 2; remoteGlobalIDString = 6A2FE17427958F0400E4A8FE; remoteInfo = LeanplumLocationAndBeacons; }; - 6AF543E828E89C1E0025F2A9 /* PBXContainerItemProxy */ = { + 6AF5439828E882CA0025F2A9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 6AF543DD28E89C1E0025F2A9 /* LeanplumSDKLocation.xcodeproj */; + containerPortal = 6AF5438D28E882CA0025F2A9 /* LeanplumSDKLocation.xcodeproj */; proxyType = 2; remoteGlobalIDString = 6AEAEE712795D89D00680F84; remoteInfo = "LeanplumLocation-Static"; }; - 6AF543EA28E89C1E0025F2A9 /* PBXContainerItemProxy */ = { + 6AF5439A28E882CA0025F2A9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 6AF543DD28E89C1E0025F2A9 /* LeanplumSDKLocation.xcodeproj */; + containerPortal = 6AF5438D28E882CA0025F2A9 /* LeanplumSDKLocation.xcodeproj */; proxyType = 2; remoteGlobalIDString = 6AEAEE7F2795D8E700680F84; remoteInfo = "LeanplumLocationAndBeacons-Static"; }; + 6AF543A028E8834E0025F2A9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6AF5438D28E882CA0025F2A9 /* LeanplumSDKLocation.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 6A2FE16627958F0400E4A8FE; + remoteInfo = LeanplumLocationAndBeacons; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 6A54E9D5273D814100DE0E61 /* Embed Frameworks */ = { + 6A37A8CD28F03C2A00F4339F /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( + 6A37A8D228F03C8D00F4339F /* LeanplumLocationAndBeacons.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -291,7 +326,12 @@ 6A07FDA42811AF3800995BE3 /* ContentMergerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentMergerTest.swift; sourceTree = ""; }; 6A07FDAD283544CE00995BE3 /* ActionManagerDownloadTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionManagerDownloadTest.swift; sourceTree = ""; }; 6A07FDAF283544E300995BE3 /* DictionaryValueKeyPathTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryValueKeyPathTest.swift; sourceTree = ""; }; + 6A29EACC28EDA8DE0024880E /* CTWrapperTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTWrapperTest.swift; sourceTree = ""; }; 6A2FE15F27958D6000E4A8FE /* GeofencingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeofencingTest.m; sourceTree = ""; }; + 6A37A8A928F0078500F4339F /* Migration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Migration+Extensions.swift"; sourceTree = ""; }; + 6A37A8AB28F00B5800F4339F /* IdentityManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManagerTest.swift; sourceTree = ""; }; + 6A37A8AD28F0164700F4339F /* IdentityManagerMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManagerMocks.swift; sourceTree = ""; }; + 6A37A8EE28F08B0E00F4339F /* LeanplumSDK.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LeanplumSDK.xcodeproj; path = "/Users/nikolazagorchev/Downloads/Leanplum-iOS-SDK/LeanplumSDK/LeanplumSDK.xcodeproj"; sourceTree = ""; }; 6A39C48D27283EE7000D5320 /* LeanplumSDKTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LeanplumSDKTests-Bridging-Header.h"; sourceTree = ""; }; 6A39C48E27283EE8000D5320 /* NotificationsProxyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsProxyTest.swift; sourceTree = ""; }; 6A54E9CC273B156600DE0E61 /* NotificationsManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsManagerTest.swift; sourceTree = ""; }; @@ -302,7 +342,7 @@ 6A9DECA427B69D8800052807 /* change_host_response.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = change_host_response.json; sourceTree = ""; }; 6AEAEE592795B68700680F84 /* Leanplum.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Leanplum.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6AEAEE612795B72A00680F84 /* LeanplumLocationAndBeacons.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LeanplumLocationAndBeacons.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6AF543DD28E89C1E0025F2A9 /* LeanplumSDKLocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LeanplumSDKLocation.xcodeproj; path = ../LeanplumSDKLocation/LeanplumSDKLocation.xcodeproj; sourceTree = ""; }; + 6AF5438D28E882CA0025F2A9 /* LeanplumSDKLocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LeanplumSDKLocation.xcodeproj; path = ../LeanplumSDKLocation/LeanplumSDKLocation.xcodeproj; sourceTree = ""; }; B6B7D6D71F9664181C09E727 /* Pods-LeanplumSDKTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeanplumSDKTests.release.xcconfig"; path = "Target Support Files/Pods-LeanplumSDKTests/Pods-LeanplumSDKTests.release.xcconfig"; sourceTree = ""; }; C9D064B0275DFB4B00A7A5F9 /* LeanplumNotificationsManagerUtilsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeanplumNotificationsManagerUtilsTest.swift; sourceTree = ""; }; C9D503662754C9DC0034C5B3 /* PushNotificationSettingsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationSettingsTest.swift; sourceTree = ""; }; @@ -314,6 +354,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 6A37A8D128F03C8D00F4339F /* LeanplumLocationAndBeacons.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -321,7 +362,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6A29EAA828E89D950024880E /* LeanplumLocationAndBeacons.framework in Frameworks */, + 6AF543A228E883AD0025F2A9 /* LeanplumLocationAndBeacons.framework in Frameworks */, 779F1FAA8AA2F3872AC876B2 /* Pods_LeanplumSDKTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -332,12 +373,13 @@ 075AACDE26847BF3007CA1BD = { isa = PBXGroup; children = ( - 6AF543DD28E89C1E0025F2A9 /* LeanplumSDKLocation.xcodeproj */, + 6AF5438D28E882CA0025F2A9 /* LeanplumSDKLocation.xcodeproj */, 075AACE926847BF3007CA1BD /* LeanplumSDKApp */, 075AAFBD26849AC1007CA1BD /* LeanplumSDKTests */, 075AACE826847BF3007CA1BD /* Products */, 075AAFB326849A74007CA1BD /* Frameworks */, E0CB0FD8F4E44175F7CB3EEF /* Pods */, + 6A37A8EE28F08B0E00F4339F /* LeanplumSDK.xcodeproj */, ); sourceTree = ""; }; @@ -412,6 +454,7 @@ 075AAFC726849AE9007CA1BD /* Classes */ = { isa = PBXGroup; children = ( + 6A37A8A228F0074700F4339F /* Migration */, 6A07FDAC2835441200995BE3 /* Notifications */, 6A07FDA42811AF3800995BE3 /* ContentMergerTest.swift */, 39C081A6278D939800C1DBD6 /* Actions */, @@ -588,13 +631,34 @@ path = LocationAndBeacons; sourceTree = ""; }; - 6AF543DE28E89C1E0025F2A9 /* Products */ = { + 6A37A8A228F0074700F4339F /* Migration */ = { isa = PBXGroup; children = ( - 6AF543E528E89C1E0025F2A9 /* LeanplumLocation.framework */, - 6AF543E728E89C1E0025F2A9 /* LeanplumLocationAndBeacons.framework */, - 6AF543E928E89C1E0025F2A9 /* LeanplumLocation.framework */, - 6AF543EB28E89C1E0025F2A9 /* LeanplumLocationAndBeacons.framework */, + 6A29EACC28EDA8DE0024880E /* CTWrapperTest.swift */, + 6A37A8A928F0078500F4339F /* Migration+Extensions.swift */, + 6A37A8AB28F00B5800F4339F /* IdentityManagerTest.swift */, + 6A37A8AD28F0164700F4339F /* IdentityManagerMocks.swift */, + ); + path = Migration; + sourceTree = ""; + }; + 6A37A8EF28F08B0E00F4339F /* Products */ = { + isa = PBXGroup; + children = ( + 6A37A8F728F08B0E00F4339F /* Leanplum.framework */, + 6A37A8F928F08B0E00F4339F /* Leanplum-iOS-SDK.bundle */, + 6A37A8FB28F08B0E00F4339F /* Leanplum.framework */, + ); + name = Products; + sourceTree = ""; + }; + 6AF5438E28E882CA0025F2A9 /* Products */ = { + isa = PBXGroup; + children = ( + 6AF5439528E882CA0025F2A9 /* LeanplumLocation.framework */, + 6AF5439728E882CA0025F2A9 /* LeanplumLocationAndBeacons.framework */, + 6AF5439928E882CA0025F2A9 /* LeanplumLocation.framework */, + 6AF5439B28E882CA0025F2A9 /* LeanplumLocationAndBeacons.framework */, ); name = Products; sourceTree = ""; @@ -619,11 +683,13 @@ 075AACE326847BF3007CA1BD /* Sources */, 075AACE426847BF3007CA1BD /* Frameworks */, 075AACE526847BF3007CA1BD /* Resources */, - 6A54E9D5273D814100DE0E61 /* Embed Frameworks */, + 6A37A8CD28F03C2A00F4339F /* Embed Frameworks */, + 6A37A8D028F03C5A00F4339F /* Codesign Embedded Frameworks */, ); buildRules = ( ); dependencies = ( + 6A37A8D428F03C9400F4339F /* PBXTargetDependency */, ); name = LeanplumSDKApp; productName = LeanplumSDKApp; @@ -643,7 +709,7 @@ buildRules = ( ); dependencies = ( - 6A29EAA728E89D280024880E /* PBXTargetDependency */, + 6AF543A128E8834E0025F2A9 /* PBXTargetDependency */, 075AAFC226849AC1007CA1BD /* PBXTargetDependency */, ); name = LeanplumSDKTests; @@ -684,8 +750,12 @@ projectDirPath = ""; projectReferences = ( { - ProductGroup = 6AF543DE28E89C1E0025F2A9 /* Products */; - ProjectRef = 6AF543DD28E89C1E0025F2A9 /* LeanplumSDKLocation.xcodeproj */; + ProductGroup = 6A37A8EF28F08B0E00F4339F /* Products */; + ProjectRef = 6A37A8EE28F08B0E00F4339F /* LeanplumSDK.xcodeproj */; + }, + { + ProductGroup = 6AF5438E28E882CA0025F2A9 /* Products */; + ProjectRef = 6AF5438D28E882CA0025F2A9 /* LeanplumSDKLocation.xcodeproj */; }, ); projectRoot = ""; @@ -697,32 +767,53 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 6AF543E528E89C1E0025F2A9 /* LeanplumLocation.framework */ = { + 6A37A8F728F08B0E00F4339F /* Leanplum.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Leanplum.framework; + remoteRef = 6A37A8F628F08B0E00F4339F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6A37A8F928F08B0E00F4339F /* Leanplum-iOS-SDK.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "Leanplum-iOS-SDK.bundle"; + remoteRef = 6A37A8F828F08B0E00F4339F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6A37A8FB28F08B0E00F4339F /* Leanplum.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Leanplum.framework; + remoteRef = 6A37A8FA28F08B0E00F4339F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6AF5439528E882CA0025F2A9 /* LeanplumLocation.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = LeanplumLocation.framework; - remoteRef = 6AF543E428E89C1E0025F2A9 /* PBXContainerItemProxy */; + remoteRef = 6AF5439428E882CA0025F2A9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 6AF543E728E89C1E0025F2A9 /* LeanplumLocationAndBeacons.framework */ = { + 6AF5439728E882CA0025F2A9 /* LeanplumLocationAndBeacons.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = LeanplumLocationAndBeacons.framework; - remoteRef = 6AF543E628E89C1E0025F2A9 /* PBXContainerItemProxy */; + remoteRef = 6AF5439628E882CA0025F2A9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 6AF543E928E89C1E0025F2A9 /* LeanplumLocation.framework */ = { + 6AF5439928E882CA0025F2A9 /* LeanplumLocation.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = LeanplumLocation.framework; - remoteRef = 6AF543E828E89C1E0025F2A9 /* PBXContainerItemProxy */; + remoteRef = 6AF5439828E882CA0025F2A9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 6AF543EB28E89C1E0025F2A9 /* LeanplumLocationAndBeacons.framework */ = { + 6AF5439B28E882CA0025F2A9 /* LeanplumLocationAndBeacons.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = LeanplumLocationAndBeacons.framework; - remoteRef = 6AF543EA28E89C1E0025F2A9 /* PBXContainerItemProxy */; + remoteRef = 6AF5439A28E882CA0025F2A9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ @@ -826,6 +917,25 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 6A37A8D028F03C5A00F4339F /* Codesign Embedded Frameworks */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Codesign Embedded Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Populate a variable with the current code signing identity if it's available in the environment.\nSIGNING_IDENTITY=\"${EXPANDED_CODE_SIGN_IDENTITY:-$CODE_SIGN_IDENTITY}\"\n\n# Populate a variable with the current code signing flags and options in the environment.\nOTHER_CODE_SIGN_FLAGS=${OTHER_CODE_SIGN_FLAGS:-}\nTARGET_FRAMEWORKS_PATH=\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/\"\n# Re-sign the packaged frameworks using the application's details.\nif [ -n \"${SIGNING_IDENTITY}\" ]; then\n if [[ -d $TARGET_FRAMEWORKS_PATH ]]\n then\n find \"$TARGET_FRAMEWORKS_PATH\" \\\n -name \"*.framework\" \\\n -type d \\\n -exec codesign ${OTHER_CODE_SIGN_FLAGS} \\\n --force \\\n --sign \"${SIGNING_IDENTITY}\" \\\n --options runtime \\\n --deep \\\n {} \\;\n fi\nfi\n"; + }; CCAE818569231C43601E87E0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -895,9 +1005,11 @@ 075AB11B2684AAD9007CA1BD /* LPRequestBatchTest.m in Sources */, 075AB11E2684AAD9007CA1BD /* LPCountAggregatorTest.m in Sources */, 075AB11D2684AAD9007CA1BD /* LeanplumTest.m in Sources */, + 6A37A8AC28F00B5800F4339F /* IdentityManagerTest.swift in Sources */, 075AB11F2684AAD9007CA1BD /* LPNetworkOperationTest.m in Sources */, 075AB1292684AAD9007CA1BD /* LPRequestSender+Categories.m in Sources */, 6A07FDB0283544E300995BE3 /* DictionaryValueKeyPathTest.swift in Sources */, + 6A37A8AA28F0078500F4339F /* Migration+Extensions.swift in Sources */, 075AB1252684AAD9007CA1BD /* LPNetworkEngine+Category.m in Sources */, 6A2FE16027958D6000E4A8FE /* GeofencingTest.m in Sources */, 075AB10F2684AAD9007CA1BD /* LPEventCallbackManagerTest.m in Sources */, @@ -915,8 +1027,10 @@ 075AB1152684AAD9007CA1BD /* LPActionTriggerManagerTest.m in Sources */, 075AB1272684AAD9007CA1BD /* LPNetworkOperation+Category.m in Sources */, C9D064B1275DFB4B00A7A5F9 /* LeanplumNotificationsManagerUtilsTest.swift in Sources */, + 6A29EACD28EDA8DE0024880E /* CTWrapperTest.swift in Sources */, 075AB10D2684AAD9007CA1BD /* LPAppIconManagerTest.m in Sources */, 6A9D0A9627342BA300466133 /* NotificationsProxyiOS9Test.swift in Sources */, + 6A37A8AE28F0164700F4339F /* IdentityManagerMocks.swift in Sources */, 075AB10B2684AAD9007CA1BD /* LPJSONTest.m in Sources */, 075AB1172684AAD9007CA1BD /* LPRequestTest.m in Sources */, 6A9D0A9827342EE300466133 /* NotificationsManagerMock.swift in Sources */, @@ -936,10 +1050,15 @@ target = 075AACE626847BF3007CA1BD /* LeanplumSDKApp */; targetProxy = 075AAFC126849AC1007CA1BD /* PBXContainerItemProxy */; }; - 6A29EAA728E89D280024880E /* PBXTargetDependency */ = { + 6A37A8D428F03C9400F4339F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = LeanplumLocationAndBeacons; + targetProxy = 6A37A8D328F03C9400F4339F /* PBXContainerItemProxy */; + }; + 6AF543A128E8834E0025F2A9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = LeanplumLocationAndBeacons; - targetProxy = 6A29EAA628E89D280024880E /* PBXContainerItemProxy */; + targetProxy = 6AF543A028E8834E0025F2A9 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ diff --git a/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/CTWrapperTest.swift b/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/CTWrapperTest.swift new file mode 100644 index 00000000..2999ecc0 --- /dev/null +++ b/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/CTWrapperTest.swift @@ -0,0 +1,87 @@ +// +// WrapperTest.swift +// LeanplumSDKTests +// +// Created by Nikola Zagorchev on 5.10.22. +// + +import Foundation +import XCTest +@testable import Leanplum + +class WrapperTest: XCTestCase { + + static let attributeMappings = ["lpName": "ctName", "lpName2": "ctName2"] + let wrapper = CTWrapper(accountId: "", accountToken: "", accountRegion: "", userId: "", deviceId: "", callback: nil) + + override class func setUp() { + MigrationManager.shared.attributeMappings = attributeMappings + } + + func testValuesIsNil() { + let notNil = "some" + let notNilOptional:String? = "some" + let nilString: String? = nil + let `nil` = nilString as Any + + XCTAssertFalse(wrapper.isAnyNil(notNil)) + XCTAssertFalse(wrapper.isAnyNil(notNilOptional)) + XCTAssertTrue(wrapper.isAnyNil(nilString)) + XCTAssertTrue(wrapper.isAnyNil(`nil`)) + } + + func testAttributeKeys() { + let attributes = ["lpName": "ct value", + "lpName1": "ct value 1", + "lpName2": "ct value 2"] as [AnyHashable : Any] + + + let actual = attributes.mapKeys(wrapper.transformAttributeKeys) + + let expected = ["ctName": "ct value", + "lpName1": "ct value 1", + "ctName2": "ct value 2"] as [AnyHashable : Any] + + XCTAssertTrue(actual.isEqual(expected)) + } + + func testAttributeKeysDuplicates() { + let attributes = ["lpName": "ct value", + "ctName": "ct value", + "lpName2": "ct value 2"] as [AnyHashable : Any] + + + let actual = attributes.mapKeys(wrapper.transformAttributeKeys) + // Dictionary is unordered + let expected = ["ctName": "ct value", + "ctName2": "ct value 2"] as [AnyHashable : Any] + + XCTAssertTrue(actual.isEqual(expected)) + } + + func testAttributeValues() { + let attributes = ["lpName": "ct value", + "number": 4, + "arr": ["a", 1, "b", 2], + "arrStr": ["a", "b"], + "arrNumber": [0.5, 1.2, 2.5], + "lpName2": "ct value 2", + "empty": nil] as [AnyHashable : Any] + + + let actual = attributes.mapValues(wrapper.transformAttributeValues) + .mapKeys(wrapper.transformAttributeKeys) + + let expected = ["ctName": "ct value", + "number": 4, + "arr": "[a,1,b,2]", + "arrStr": "[a,b]", + "arrNumber": "[0.5,1.2,2.5]", + "ctName2": "ct value 2", + "empty": nil] as [AnyHashable : Any] + + print(actual) + print(expected) + XCTAssertTrue(actual.isEqual(expected)) + } +} diff --git a/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/IdentityManagerMocks.swift b/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/IdentityManagerMocks.swift new file mode 100644 index 00000000..51c23abc --- /dev/null +++ b/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/IdentityManagerMocks.swift @@ -0,0 +1,61 @@ +// +// IdentityManagerMocks.swift +// LeanplumSDKTests +// +// Created by Nikola Zagorchev on 7.10.22. +// + +import Foundation +@testable import Leanplum + +class IdentityManagerMock: IdentityManager { + var _anonymousLoginUserId: String? + override var anonymousLoginUserId: String? { + get { + return _anonymousLoginUserId + } + set { + _anonymousLoginUserId = newValue + } + } + + var _state: String? + override var state: String? { + get { + return _state + } + set { + _state = newValue + } + } +} + +class IdentityManagerMockStatic: IdentityManager { + static var _anonymousLoginUserId: String? + override var anonymousLoginUserId: String? { + get { + return IdentityManagerMockStatic._anonymousLoginUserId + } + set { + IdentityManagerMockStatic._anonymousLoginUserId = newValue + } + } + + static var _state: String? + override var state: String? { + get { + return IdentityManagerMockStatic._state + } + set { + IdentityManagerMockStatic._state = newValue + } + } + + init(userId: String, deviceId: String, anonymousLoginUserId: String?, state: String?) { + // Needs to be set before call to super.init + IdentityManagerMockStatic._anonymousLoginUserId = anonymousLoginUserId + IdentityManagerMockStatic._state = state + + super.init(userId: userId, deviceId: deviceId) + } +} diff --git a/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/IdentityManagerTest.swift b/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/IdentityManagerTest.swift new file mode 100644 index 00000000..eae3ca06 --- /dev/null +++ b/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/IdentityManagerTest.swift @@ -0,0 +1,98 @@ +// +// IdentityManagerTest.swift +// LeanplumSDKTests +// +// Created by Nikola Zagorchev on 7.10.22. +// + +import Foundation +import XCTest +@testable import Leanplum + +class IdentityManagerTest: XCTestCase { + + func testProfile() { + let identityManager = IdentityManagerMock(userId: "deviceId", deviceId: "deviceId") + + let identityManagerUser = IdentityManagerMock(userId: "userId", deviceId: "deviceId") + + XCTAssertTrue(identityManager.profile.isEqual(["Identity": "deviceId"])) + XCTAssertTrue(identityManagerUser.profile.isEqual(["Identity": "userId"])) + } + + func testAnonymous() { + let identityManager = IdentityManagerMock(userId: "deviceId", deviceId: "deviceId") + + XCTAssertTrue(identityManager.isAnonymous) + XCTAssertEqual(identityManager.state, IdentityManager.IdentityState.anonymous()) + XCTAssertEqual(identityManager.cleverTapID, "deviceId") + } + + func testIdentified() { + let identityManager = IdentityManagerMock(userId: "userId", deviceId: "deviceId") + + XCTAssertFalse(identityManager.isAnonymous) + XCTAssertEqual(identityManager.state, IdentityManager.IdentityState.identified()) + XCTAssertEqual(identityManager.cleverTapID, "deviceId_userId") + } + + func testIdentifiedNewUser() { + let identityManager = IdentityManagerMock(userId: "userId", deviceId: "deviceId") + + identityManager.setUserId("userId2") + + XCTAssertFalse(identityManager.isAnonymous) + XCTAssertEqual(identityManager.state, IdentityManager.IdentityState.identified()) + XCTAssertEqual(identityManager.anonymousLoginUserId, nil) + XCTAssertEqual(identityManager.cleverTapID, "deviceId_userId2") + } + + func testAnonymousLogin() { + let identityManager = IdentityManagerMock(userId: "deviceId", deviceId: "deviceId") + + identityManager.setUserId("userId") + + XCTAssertFalse(identityManager.isAnonymous) + XCTAssertEqual(identityManager.state, IdentityManager.IdentityState.identified()) + XCTAssertEqual(identityManager.anonymousLoginUserId, "userId") + XCTAssertEqual(identityManager.cleverTapID, "deviceId") + } + + func testAnonymousLoginNewUser() { + let identityManager = IdentityManagerMock(userId: "deviceId", deviceId: "deviceId") + + identityManager.setUserId("userId") + + identityManager.setUserId("userId2") + + XCTAssertFalse(identityManager.isAnonymous) + XCTAssertEqual(identityManager.state, IdentityManager.IdentityState.identified()) + XCTAssertEqual(identityManager.anonymousLoginUserId, "userId") + XCTAssertEqual(identityManager.cleverTapID, "deviceId_userId2") + } + + func testAnonymousLoginBack() { + let identityManager = IdentityManagerMock(userId: "deviceId", deviceId: "deviceId") + + identityManager.setUserId("userId") + + identityManager.setUserId("userId2") + + identityManager.setUserId("userId") + + XCTAssertFalse(identityManager.isAnonymous) + XCTAssertEqual(identityManager.state, IdentityManager.IdentityState.identified()) + XCTAssertEqual(identityManager.cleverTapID, "deviceId") + } + + func testAnonymousLoginStart() { + let initialIdentityManager = IdentityManagerMock(userId: "deviceId", deviceId: "deviceId") + + let identityManager = IdentityManagerMockStatic(userId: "userId", deviceId: initialIdentityManager.deviceId, anonymousLoginUserId: initialIdentityManager.anonymousLoginUserId, state: initialIdentityManager.state) + + XCTAssertFalse(identityManager.isAnonymous) + XCTAssertEqual(identityManager.state, IdentityManager.IdentityState.identified()) + XCTAssertEqual(identityManager.anonymousLoginUserId, "userId") + XCTAssertEqual(identityManager.cleverTapID, "deviceId") + } +} diff --git a/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/Migration+Extensions.swift b/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/Migration+Extensions.swift new file mode 100644 index 00000000..44e925e0 --- /dev/null +++ b/LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/Migration+Extensions.swift @@ -0,0 +1,16 @@ +// +// Migration+Extensions.swift +// LeanplumSDKTests +// +// Created by Nikola Zagorchev on 7.10.22. +// + +import Foundation +@testable import Leanplum + +@objc public extension MigrationManager { + @available(iOS 13.0, *) + func setMigrationState(_ state: MigrationState) { + migrationState = state + } +} diff --git a/LeanplumSDKApp/LeanplumSDKTests/Classes/Utilities/LeanplumHelper.m b/LeanplumSDKApp/LeanplumSDKTests/Classes/Utilities/LeanplumHelper.m index ca4b8a8b..476080b6 100644 --- a/LeanplumSDKApp/LeanplumSDKTests/Classes/Utilities/LeanplumHelper.m +++ b/LeanplumSDKApp/LeanplumSDKTests/Classes/Utilities/LeanplumHelper.m @@ -40,6 +40,7 @@ #import "LPNetworkOperation+Category.h" #import #import +#import "LeanplumSDKTests-Swift.h" // Keys also used in Leanplum-Info.plist file NSString *APPLICATION_ID = @"app_nLiaLr3lXvCjXhsztS1Gw8j281cPLO6sZetTDxYnaSk"; @@ -94,10 +95,14 @@ + (void)setup_method_swizzling { + (void)setup_development_test { [Leanplum setLogLevel:LPLogLevelDebug]; [Leanplum setAppId:APPLICATION_ID withDevelopmentKey:DEVELOPMENT_KEY]; + + [[MigrationManager shared] setMigrationState:MigrationStateLeanplum]; } + (void)setup_production_test { [Leanplum setAppId:APPLICATION_ID withProductionKey:PRODUCTION_KEY]; + + [[MigrationManager shared] setMigrationState:MigrationStateLeanplum]; } + (BOOL)start_development_test { diff --git a/LeanplumSDKLocation/LeanplumSDKLocation.xcodeproj/project.pbxproj b/LeanplumSDKLocation/LeanplumSDKLocation.xcodeproj/project.pbxproj index b74f1746..cee48b37 100644 --- a/LeanplumSDKLocation/LeanplumSDKLocation.xcodeproj/project.pbxproj +++ b/LeanplumSDKLocation/LeanplumSDKLocation.xcodeproj/project.pbxproj @@ -991,4 +991,4 @@ /* End XCConfigurationList section */ }; rootObject = 6A2FE13D279578F800E4A8FE /* Project object */; -} +} \ No newline at end of file diff --git a/LeanplumTargetWrapper/Dummy.swift b/LeanplumTargetWrapper/Dummy.swift new file mode 100644 index 00000000..b739a9fe --- /dev/null +++ b/LeanplumTargetWrapper/Dummy.swift @@ -0,0 +1 @@ +// Empty source file for SwiftPM binary target dependencies wrapper \ No newline at end of file diff --git a/Package.swift b/Package.swift index 277b6a88..40627eab 100644 --- a/Package.swift +++ b/Package.swift @@ -3,20 +3,31 @@ import PackageDescription let package = Package( name: "Leanplum", + platforms: [ + .iOS(.v9) + ], products: [ - .library(name: "Leanplum", targets: ["Leanplum"]), + .library(name: "Leanplum", targets: ["LeanplumTargetWrapper"]), .library(name: "LeanplumLocation", targets: ["LeanplumLocation"]) ], + dependencies: [ + .package(url: "https://github.com/CleverTap/clevertap-ios-sdk", from: "4.1.1") + ], targets: [ .binaryTarget( name: "Leanplum", url: "https://github.com/Leanplum/Leanplum-iOS-SDK/releases/download/5.0.0/Leanplum.xcframework.zip", checksum: "b49fd986f4e2394a0f46d455705085c3c39acb122d58f7098a230452a2e1b17d" ), - .target( - name: "LeanplumLocation", - path: "LeanplumSDKLocation", - publicHeadersPath: "LeanplumSDKLocation/include" - ) + .target( + name: "LeanplumLocation", + dependencies: ["Leanplum"], + path: "LeanplumSDKLocation", + publicHeadersPath: "LeanplumSDKLocation/include" + ), + .target(name: "LeanplumTargetWrapper", + dependencies: ["Leanplum", .product(name: "CleverTapSDK", package: "clevertap-ios-sdk")], + path: "LeanplumTargetWrapper" + ) ] ) diff --git a/Podfile b/Podfile index 7783466f..1515a69a 100644 --- a/Podfile +++ b/Podfile @@ -1,11 +1,40 @@ platform :ios, '9.0' -use_frameworks! +use_modular_headers! workspace 'Leanplum.xcworkspace' target 'LeanplumSDKTests' do project 'LeanplumSDKApp/LeanplumSDKApp.xcodeproj' + use_frameworks! pod 'OCMock', '~> 3.3.1' pod 'OHHTTPStubs', '~> 9.0.0' end + +target 'Leanplum' do + project 'LeanplumSDK/LeanplumSDK.xcodeproj' + workspace 'Leanplum.xcworkspace' + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for Leanplum + pod 'CleverTap-iOS-SDK', '~> 4.1.1' +end +# + +target 'Leanplum-Static' do + project 'LeanplumSDK/LeanplumSDK.xcodeproj' + + # Pods for Leanplum-Static + pod 'CleverTap-iOS-SDK', '~> 4.1.1' +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle" + target.build_configurations.each do |config| + config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' + end + end + end +end diff --git a/Tools/build.sh b/Tools/build.sh index d2beaf53..d02bca24 100755 --- a/Tools/build.sh +++ b/Tools/build.sh @@ -64,6 +64,9 @@ run() { ####################################### main() { rm -rf Release + + pod install + build_ios_dylib 'Leanplum' 'Release/dynamic/LeanplumSDK' build_ios_dylib 'LeanplumLocation' 'Release/dynamic/LeanplumSDKLocation' build_ios_dylib 'LeanplumLocationAndBeacons' 'Release/dynamic/LeanplumSDKLocationAndBeacons' @@ -210,6 +213,12 @@ build_ios_static() { # https://stackoverflow.com/a/62310245 find Release/static/Leanplum.xcframework -name "*.swiftinterface" -exec sed -i -e "s/Leanplum\.//g" {} \; + # Remove module name from xcframework swiftinterface + # It prevents error X is not a member of type Leanplum.Leanplum + # This happens when a class name is same as the module name + # https://stackoverflow.com/a/62310245 + find Release/static/Leanplum.xcframework -name "*.swiftinterface" -exec sed -i -e "s/Leanplum\.//g" {} \; + echo "Removing arm64 from simulator slice ..." lipo -remove arm64 \ $archivePath-iphonesimulator.xcarchive/Products/Library/Frameworks/$productName.framework/$productName \ @@ -254,8 +263,8 @@ zip_ios() { $(find . -maxdepth 2 -type d -name "LeanplumLocation*") mv LeanplumLocation.framework.zip .. - echo "zipping dynamic xcframework for SPM" - cd dynamic + echo "zipping static xcframework for SPM" + cd static zip -q -r Leanplum.xcframework.zip \ Leanplum.xcframework cd ../.. @@ -265,7 +274,7 @@ update_spm_info(){ echo "updating SPM checksum and url" package_file=Package.swift package_tmp_file=Package_tmp.swift - checksum=`swift package compute-checksum Release/dynamic/Leanplum.xcframework.zip` + checksum=`swift package compute-checksum Release/static/Leanplum.xcframework.zip` awk -v value="\"$checksum\"" '!x{x=sub(/checksum:.*/, "checksum: "value)}1' $package_file > $package_tmp_file \ && mv $package_tmp_file $package_file