From c74c67eb75aba85ab1397eb575f80fc79ce2b00c Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Mon, 24 Jul 2023 11:45:06 +0530 Subject: [PATCH 01/14] feat(SDK-2517): Added SwiftUI sample app - Added swiftUI sample app to support our core SDK. - Added approach to track screen views in swiftUI from app side. --- SwiftUIStarter/NotificationService/Info.plist | 17 + .../NotificationService.swift | 18 + SwiftUIStarter/Podfile | 14 + .../SwiftUIStarter.xcodeproj/project.pbxproj | 647 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/NotificationService.xcscheme | 96 +++ .../xcschemes/SwiftUIStarter.xcscheme | 77 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../SwiftUIStarter/AppDelegate.swift | 54 ++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/100.png | Bin 0 -> 3234 bytes .../AppIcon.appiconset/1024.png | Bin 0 -> 34611 bytes .../AppIcon.appiconset/114.png | Bin 0 -> 3661 bytes .../AppIcon.appiconset/120.png | Bin 0 -> 3852 bytes .../AppIcon.appiconset/144.png | Bin 0 -> 4690 bytes .../AppIcon.appiconset/152.png | Bin 0 -> 4981 bytes .../AppIcon.appiconset/167.png | Bin 0 -> 5600 bytes .../AppIcon.appiconset/180.png | Bin 0 -> 5896 bytes .../Assets.xcassets/AppIcon.appiconset/20.png | Bin 0 -> 650 bytes .../Assets.xcassets/AppIcon.appiconset/29.png | Bin 0 -> 954 bytes .../Assets.xcassets/AppIcon.appiconset/40.png | Bin 0 -> 1290 bytes .../Assets.xcassets/AppIcon.appiconset/50.png | Bin 0 -> 1589 bytes .../Assets.xcassets/AppIcon.appiconset/57.png | Bin 0 -> 1766 bytes .../Assets.xcassets/AppIcon.appiconset/58.png | Bin 0 -> 1814 bytes .../Assets.xcassets/AppIcon.appiconset/60.png | Bin 0 -> 1850 bytes .../Assets.xcassets/AppIcon.appiconset/72.png | Bin 0 -> 2289 bytes .../Assets.xcassets/AppIcon.appiconset/76.png | Bin 0 -> 2439 bytes .../Assets.xcassets/AppIcon.appiconset/80.png | Bin 0 -> 2547 bytes .../Assets.xcassets/AppIcon.appiconset/87.png | Bin 0 -> 2764 bytes .../AppIcon.appiconset/Contents.json | 1 + .../Assets.xcassets/Contents.json | 6 + .../logo.imageset/Contents.json | 52 ++ .../Logo_Logotype Inverse Dark BG@2x.png | Bin 0 -> 8664 bytes .../Logo_Logotype White BG@2x.png | Bin 0 -> 9111 bytes .../SwiftUIStarter/CTAppInbox.swift | 27 + .../CTAppInboxRepresentable.swift | 21 + .../CTCallbackCoordinator.swift | 14 + .../SwiftUIStarter/CTHomeScreen.swift | 250 +++++++ .../SwiftUIStarter/CTViewModifier.swift | 48 ++ .../CTWebViewRepresentable.swift | 41 ++ .../SwiftUIStarter/ContentView.swift | 15 + .../SwiftUIStarter/HomeScreen.swift | 216 ++++++ SwiftUIStarter/SwiftUIStarter/Info.plist | 16 + .../Preview Assets.xcassets/Contents.json | 6 + .../SwiftUIStarter.entitlements | 8 + .../SwiftUIStarter/SwiftUIStarterApp.swift | 12 + .../SwiftUIStarter/sampleHTMLCode.html | 158 +++++ 49 files changed, 1858 insertions(+) create mode 100644 SwiftUIStarter/NotificationService/Info.plist create mode 100644 SwiftUIStarter/NotificationService/NotificationService.swift create mode 100644 SwiftUIStarter/Podfile create mode 100644 SwiftUIStarter/SwiftUIStarter.xcodeproj/project.pbxproj create mode 100644 SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme create mode 100644 SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/SwiftUIStarter.xcscheme create mode 100644 SwiftUIStarter/SwiftUIStarter.xcworkspace/contents.xcworkspacedata create mode 100644 SwiftUIStarter/SwiftUIStarter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 SwiftUIStarter/SwiftUIStarter/AppDelegate.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/100.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/1024.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/114.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/120.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/144.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/152.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/167.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/180.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/20.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/29.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/40.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/50.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/57.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/58.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/60.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/72.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/76.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/80.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/87.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/Contents.json create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Contents.json create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Logo_Logotype Inverse Dark BG@2x.png create mode 100644 SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Logo_Logotype White BG@2x.png create mode 100644 SwiftUIStarter/SwiftUIStarter/CTAppInbox.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/CTAppInboxRepresentable.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/CTCallbackCoordinator.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/CTHomeScreen.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/CTViewModifier.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/CTWebViewRepresentable.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/ContentView.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/HomeScreen.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/Info.plist create mode 100644 SwiftUIStarter/SwiftUIStarter/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 SwiftUIStarter/SwiftUIStarter/SwiftUIStarter.entitlements create mode 100644 SwiftUIStarter/SwiftUIStarter/SwiftUIStarterApp.swift create mode 100644 SwiftUIStarter/SwiftUIStarter/sampleHTMLCode.html diff --git a/SwiftUIStarter/NotificationService/Info.plist b/SwiftUIStarter/NotificationService/Info.plist new file mode 100644 index 00000000..c837dcdc --- /dev/null +++ b/SwiftUIStarter/NotificationService/Info.plist @@ -0,0 +1,17 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + CleverTapAccountID + W9R-486-4W5Z + CleverTapToken + 6b4-2c0 + + diff --git a/SwiftUIStarter/NotificationService/NotificationService.swift b/SwiftUIStarter/NotificationService/NotificationService.swift new file mode 100644 index 00000000..18a039ef --- /dev/null +++ b/SwiftUIStarter/NotificationService/NotificationService.swift @@ -0,0 +1,18 @@ +import CTNotificationService +import CleverTapSDK + +class NotificationService: CTNotificationServiceExtension { + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + CleverTap.setDebugLevel(2) + // While running the Application add CleverTap Account ID and Account token in your .plist file + CleverTap.sharedInstance()?.recordEvent("testEventFromNotificationService") + let profile: Dictionary = [ + "Identity": 63344 as AnyObject, + "Email": "test63344@gmail.com" as AnyObject] + CleverTap.sharedInstance()?.profilePush(profile) + // call to record the Notification viewed + CleverTap.sharedInstance()?.recordNotificationViewedEvent(withData: request.content.userInfo) + super.didReceive(request, withContentHandler: contentHandler) + } +} diff --git a/SwiftUIStarter/Podfile b/SwiftUIStarter/Podfile new file mode 100644 index 00000000..7a195d0a --- /dev/null +++ b/SwiftUIStarter/Podfile @@ -0,0 +1,14 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '13.0' + +target 'SwiftUIStarter' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + pod 'CleverTap-iOS-SDK', '5.0.1' + + target 'NotificationService' do + pod 'CTNotificationService' + end + +end diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.pbxproj b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ceb36edf --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.pbxproj @@ -0,0 +1,647 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 480395022A6C75D900C4D254 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480395012A6C75D900C4D254 /* NotificationService.swift */; }; + 480395062A6C75D900C4D254 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 480394FF2A6C75D900C4D254 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 489C5FFC2A6C5CCE00440747 /* CTWebViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 489C5FFB2A6C5CCE00440747 /* CTWebViewRepresentable.swift */; }; + 489C5FFE2A6C5E1A00440747 /* sampleHTMLCode.html in Resources */ = {isa = PBXBuildFile; fileRef = 489C5FFD2A6C5E1A00440747 /* sampleHTMLCode.html */; }; + 489C60002A6C616F00440747 /* CTCallbackCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 489C5FFF2A6C616F00440747 /* CTCallbackCoordinator.swift */; }; + 489C60022A6C699300440747 /* CTViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 489C60012A6C699300440747 /* CTViewModifier.swift */; }; + 48E0B3422A66FE5F009C4312 /* SwiftUIStarterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3412A66FE5F009C4312 /* SwiftUIStarterApp.swift */; }; + 48E0B3442A66FE5F009C4312 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3432A66FE5F009C4312 /* ContentView.swift */; }; + 48E0B3462A66FE61009C4312 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48E0B3452A66FE61009C4312 /* Assets.xcassets */; }; + 48E0B3492A66FE61009C4312 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48E0B3482A66FE61009C4312 /* Preview Assets.xcassets */; }; + 48E0B3522A6708B5009C4312 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3512A6708B5009C4312 /* HomeScreen.swift */; }; + 48E0B3542A67166C009C4312 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3532A67166C009C4312 /* AppDelegate.swift */; }; + 48E0B35A2A69C5B5009C4312 /* CTAppInboxRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3592A69C5B5009C4312 /* CTAppInboxRepresentable.swift */; }; + 48E0B35E2A69D47F009C4312 /* Pods_SwiftUIStarter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 334E3ED58090A5AEEF23410D /* Pods_SwiftUIStarter.framework */; }; + A983E8E0003C52EE342CBDB9 /* Pods_SwiftUIStarter_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA9862E5A057F98569E2EEBD /* Pods_SwiftUIStarter_NotificationService.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 480395042A6C75D900C4D254 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 48E0B3362A66FE5F009C4312 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 480394FE2A6C75D900C4D254; + remoteInfo = NotificationService; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 480395072A6C75D900C4D254 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 480395062A6C75D900C4D254 /* NotificationService.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 13D9D8717B26488C092AB5E7 /* Pods-SwiftUIStarter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftUIStarter.release.xcconfig"; path = "Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter.release.xcconfig"; sourceTree = ""; }; + 334E3ED58090A5AEEF23410D /* Pods_SwiftUIStarter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftUIStarter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 480394FF2A6C75D900C4D254 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 480395012A6C75D900C4D254 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 480395032A6C75D900C4D254 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 489C5FFB2A6C5CCE00440747 /* CTWebViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTWebViewRepresentable.swift; sourceTree = ""; }; + 489C5FFD2A6C5E1A00440747 /* sampleHTMLCode.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = sampleHTMLCode.html; sourceTree = ""; }; + 489C5FFF2A6C616F00440747 /* CTCallbackCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTCallbackCoordinator.swift; sourceTree = ""; }; + 489C60012A6C699300440747 /* CTViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTViewModifier.swift; sourceTree = ""; }; + 48E0B33E2A66FE5F009C4312 /* SwiftUIStarter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIStarter.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 48E0B3412A66FE5F009C4312 /* SwiftUIStarterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIStarterApp.swift; sourceTree = ""; }; + 48E0B3432A66FE5F009C4312 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 48E0B3452A66FE61009C4312 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 48E0B3482A66FE61009C4312 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 48E0B3512A6708B5009C4312 /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; + 48E0B3532A67166C009C4312 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 48E0B3552A67187F009C4312 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 48E0B3562A6718B6009C4312 /* SwiftUIStarter.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftUIStarter.entitlements; sourceTree = ""; }; + 48E0B3592A69C5B5009C4312 /* CTAppInboxRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTAppInboxRepresentable.swift; sourceTree = ""; }; + 48E0B3612A69D697009C4312 /* CleverTapSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CleverTapSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 48E0B3652A69D757009C4312 /* CleverTapSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CleverTapSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A530F6FFBC26113F8B9C9B13 /* Pods-SwiftUIStarter-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftUIStarter-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-SwiftUIStarter-NotificationService/Pods-SwiftUIStarter-NotificationService.debug.xcconfig"; sourceTree = ""; }; + AA9862E5A057F98569E2EEBD /* Pods_SwiftUIStarter_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftUIStarter_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C738698B25C9108B38EA207C /* Pods-SwiftUIStarter.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftUIStarter.debug.xcconfig"; path = "Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter.debug.xcconfig"; sourceTree = ""; }; + D6E36C7887B8C53444BCF27F /* Pods-SwiftUIStarter-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftUIStarter-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-SwiftUIStarter-NotificationService/Pods-SwiftUIStarter-NotificationService.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 480394FC2A6C75D900C4D254 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A983E8E0003C52EE342CBDB9 /* Pods_SwiftUIStarter_NotificationService.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 48E0B33B2A66FE5F009C4312 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 48E0B35E2A69D47F009C4312 /* Pods_SwiftUIStarter.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 480395002A6C75D900C4D254 /* NotificationService */ = { + isa = PBXGroup; + children = ( + 480395012A6C75D900C4D254 /* NotificationService.swift */, + 480395032A6C75D900C4D254 /* Info.plist */, + ); + path = NotificationService; + sourceTree = ""; + }; + 48E0B3352A66FE5F009C4312 = { + isa = PBXGroup; + children = ( + 48E0B3402A66FE5F009C4312 /* SwiftUIStarter */, + 480395002A6C75D900C4D254 /* NotificationService */, + 48E0B33F2A66FE5F009C4312 /* Products */, + 575B211555AFBB0B1F132EC0 /* Pods */, + 5715B45A7DE1C4DD1431D551 /* Frameworks */, + ); + sourceTree = ""; + }; + 48E0B33F2A66FE5F009C4312 /* Products */ = { + isa = PBXGroup; + children = ( + 48E0B33E2A66FE5F009C4312 /* SwiftUIStarter.app */, + 480394FF2A6C75D900C4D254 /* NotificationService.appex */, + ); + name = Products; + sourceTree = ""; + }; + 48E0B3402A66FE5F009C4312 /* SwiftUIStarter */ = { + isa = PBXGroup; + children = ( + 489C5FFD2A6C5E1A00440747 /* sampleHTMLCode.html */, + 48E0B3562A6718B6009C4312 /* SwiftUIStarter.entitlements */, + 48E0B3552A67187F009C4312 /* Info.plist */, + 48E0B3412A66FE5F009C4312 /* SwiftUIStarterApp.swift */, + 48E0B3432A66FE5F009C4312 /* ContentView.swift */, + 48E0B3452A66FE61009C4312 /* Assets.xcassets */, + 48E0B3472A66FE61009C4312 /* Preview Content */, + 48E0B3512A6708B5009C4312 /* HomeScreen.swift */, + 48E0B3532A67166C009C4312 /* AppDelegate.swift */, + 48E0B3592A69C5B5009C4312 /* CTAppInboxRepresentable.swift */, + 489C5FFB2A6C5CCE00440747 /* CTWebViewRepresentable.swift */, + 489C5FFF2A6C616F00440747 /* CTCallbackCoordinator.swift */, + 489C60012A6C699300440747 /* CTViewModifier.swift */, + ); + path = SwiftUIStarter; + sourceTree = ""; + }; + 48E0B3472A66FE61009C4312 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 48E0B3482A66FE61009C4312 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 5715B45A7DE1C4DD1431D551 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 48E0B3652A69D757009C4312 /* CleverTapSDK.framework */, + 48E0B3612A69D697009C4312 /* CleverTapSDK.framework */, + 334E3ED58090A5AEEF23410D /* Pods_SwiftUIStarter.framework */, + AA9862E5A057F98569E2EEBD /* Pods_SwiftUIStarter_NotificationService.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 575B211555AFBB0B1F132EC0 /* Pods */ = { + isa = PBXGroup; + children = ( + C738698B25C9108B38EA207C /* Pods-SwiftUIStarter.debug.xcconfig */, + 13D9D8717B26488C092AB5E7 /* Pods-SwiftUIStarter.release.xcconfig */, + A530F6FFBC26113F8B9C9B13 /* Pods-SwiftUIStarter-NotificationService.debug.xcconfig */, + D6E36C7887B8C53444BCF27F /* Pods-SwiftUIStarter-NotificationService.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 480394FE2A6C75D900C4D254 /* NotificationService */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4803950A2A6C75D900C4D254 /* Build configuration list for PBXNativeTarget "NotificationService" */; + buildPhases = ( + 2EF2C24BBEEC32DB1ABD9B34 /* [CP] Check Pods Manifest.lock */, + 480394FB2A6C75D900C4D254 /* Sources */, + 480394FC2A6C75D900C4D254 /* Frameworks */, + 480394FD2A6C75D900C4D254 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NotificationService; + productName = NotificationService; + productReference = 480394FF2A6C75D900C4D254 /* NotificationService.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 48E0B33D2A66FE5F009C4312 /* SwiftUIStarter */ = { + isa = PBXNativeTarget; + buildConfigurationList = 48E0B34C2A66FE61009C4312 /* Build configuration list for PBXNativeTarget "SwiftUIStarter" */; + buildPhases = ( + 4BE244E5E28319E6D713411D /* [CP] Check Pods Manifest.lock */, + 48E0B33A2A66FE5F009C4312 /* Sources */, + 48E0B33B2A66FE5F009C4312 /* Frameworks */, + 48E0B33C2A66FE5F009C4312 /* Resources */, + 6CF66421FB07ED9538E0E987 /* [CP] Embed Pods Frameworks */, + 480395072A6C75D900C4D254 /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 480395052A6C75D900C4D254 /* PBXTargetDependency */, + ); + name = SwiftUIStarter; + productName = SwiftUIStarter; + productReference = 48E0B33E2A66FE5F009C4312 /* SwiftUIStarter.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 48E0B3362A66FE5F009C4312 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; + TargetAttributes = { + 480394FE2A6C75D900C4D254 = { + CreatedOnToolsVersion = 14.3; + }; + 48E0B33D2A66FE5F009C4312 = { + CreatedOnToolsVersion = 14.3; + }; + }; + }; + buildConfigurationList = 48E0B3392A66FE5F009C4312 /* Build configuration list for PBXProject "SwiftUIStarter" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 48E0B3352A66FE5F009C4312; + productRefGroup = 48E0B33F2A66FE5F009C4312 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 48E0B33D2A66FE5F009C4312 /* SwiftUIStarter */, + 480394FE2A6C75D900C4D254 /* NotificationService */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 480394FD2A6C75D900C4D254 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 48E0B33C2A66FE5F009C4312 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 48E0B3492A66FE61009C4312 /* Preview Assets.xcassets in Resources */, + 48E0B3462A66FE61009C4312 /* Assets.xcassets in Resources */, + 489C5FFE2A6C5E1A00440747 /* sampleHTMLCode.html in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2EF2C24BBEEC32DB1ABD9B34 /* [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-SwiftUIStarter-NotificationService-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; + }; + 4BE244E5E28319E6D713411D /* [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-SwiftUIStarter-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; + }; + 6CF66421FB07ED9538E0E987 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 12; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 480394FB2A6C75D900C4D254 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 480395022A6C75D900C4D254 /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 48E0B33A2A66FE5F009C4312 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 489C60022A6C699300440747 /* CTViewModifier.swift in Sources */, + 48E0B3522A6708B5009C4312 /* HomeScreen.swift in Sources */, + 48E0B3542A67166C009C4312 /* AppDelegate.swift in Sources */, + 48E0B35A2A69C5B5009C4312 /* CTAppInboxRepresentable.swift in Sources */, + 489C60002A6C616F00440747 /* CTCallbackCoordinator.swift in Sources */, + 48E0B3442A66FE5F009C4312 /* ContentView.swift in Sources */, + 48E0B3422A66FE5F009C4312 /* SwiftUIStarterApp.swift in Sources */, + 489C5FFC2A6C5CCE00440747 /* CTWebViewRepresentable.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 480395052A6C75D900C4D254 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 480394FE2A6C75D900C4D254 /* NotificationService */; + targetProxy = 480395042A6C75D900C4D254 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 480395082A6C75D900C4D254 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A530F6FFBC26113F8B9C9B13 /* Pods-SwiftUIStarter-NotificationService.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = A5J34NR598; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.demo.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 480395092A6C75D900C4D254 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D6E36C7887B8C53444BCF27F /* Pods-SwiftUIStarter-NotificationService.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = A5J34NR598; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.demo.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 48E0B34A2A66FE61009C4312 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 48E0B34B2A66FE61009C4312 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 48E0B34D2A66FE61009C4312 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C738698B25C9108B38EA207C /* Pods-SwiftUIStarter.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SwiftUIStarter/SwiftUIStarter.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SwiftUIStarter/Preview Content\""; + DEVELOPMENT_TEAM = A5J34NR598; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SwiftUIStarter/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 48E0B34E2A66FE61009C4312 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 13D9D8717B26488C092AB5E7 /* Pods-SwiftUIStarter.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SwiftUIStarter/SwiftUIStarter.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SwiftUIStarter/Preview Content\""; + DEVELOPMENT_TEAM = A5J34NR598; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SwiftUIStarter/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4803950A2A6C75D900C4D254 /* Build configuration list for PBXNativeTarget "NotificationService" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 480395082A6C75D900C4D254 /* Debug */, + 480395092A6C75D900C4D254 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 48E0B3392A66FE5F009C4312 /* Build configuration list for PBXProject "SwiftUIStarter" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 48E0B34A2A66FE61009C4312 /* Debug */, + 48E0B34B2A66FE61009C4312 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 48E0B34C2A66FE61009C4312 /* Build configuration list for PBXNativeTarget "SwiftUIStarter" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 48E0B34D2A66FE61009C4312 /* Debug */, + 48E0B34E2A66FE61009C4312 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 48E0B3362A66FE5F009C4312 /* Project object */; +} diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme b/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme new file mode 100644 index 00000000..33c53587 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/SwiftUIStarter.xcscheme b/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/SwiftUIStarter.xcscheme new file mode 100644 index 00000000..ad682d9d --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/SwiftUIStarter.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcworkspace/contents.xcworkspacedata b/SwiftUIStarter/SwiftUIStarter.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..b6edc1ce --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SwiftUIStarter/SwiftUIStarter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SwiftUIStarter/SwiftUIStarter/AppDelegate.swift b/SwiftUIStarter/SwiftUIStarter/AppDelegate.swift new file mode 100644 index 00000000..9c930323 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/AppDelegate.swift @@ -0,0 +1,54 @@ +import UserNotifications +import CleverTapSDK + +class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + registerForPush() + CleverTap.setDebugLevel(2) + CleverTap.autoIntegrate() + CleverTap.sharedInstance()?.enableDeviceNetworkInfoReporting(true) + return true + } + + func registerForPush() { + // Register for Push notifications + UNUserNotificationCenter.current().delegate = self + // request Permissions + UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .badge, .alert], completionHandler: {granted, error in + if granted { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + }) + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + NSLog("%@: failed to register for remote notifications: %@", self.description, error.localizedDescription) + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + NSLog("%@: registered for remote notifications: %@", self.description, deviceToken.description) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + NSLog("%@: did receive notification response: %@", self.description, response.notification.request.content.userInfo) + completionHandler() + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + NSLog("%@: will present notification: %@", self.description, notification.request.content.userInfo) + completionHandler([.badge, .sound, .alert]) + } + + func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable : Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + NSLog("%@: did receive remote notification completionhandler: %@", self.description, userInfo) + completionHandler(UIBackgroundFetchResult.noData) + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AccentColor.colorset/Contents.json b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/100.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000000000000000000000000000000000000..982c064eddddf2e52ab1c846cd6e799c84c609a1 GIT binary patch literal 3234 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 UYGpzvRR91007*qoM6N<$g2SQGCjbBd literal 0 HcmV?d00001 diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/152.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000000000000000000000000000000000000..d11511eb92e1b091671eb71cf981ab15c5a9e940 GIT binary patch literal 4981 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 literal 0 HcmV?d00001 diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/167.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000000000000000000000000000000000000..a1b6f81fc3e4cfb1730779a04ffc3e3ddb855d1e GIT binary patch literal 5600 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 A-2eap literal 0 HcmV?d00001 diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/50.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000000000000000000000000000000000000..ac24dd43cd2c968cdb59d6a41860eee1aff565a8 GIT binary patch literal 1589 zcmV-52Fm$~P)kPz&)=xoiNnbBeSUq$jE@Bw-?{X#=wk5K#~#)B|0Ev8iA1{D3SzIdpivK z_s<*bpO}E<&>=9rKH;pe>76^HXTQ1qEuD5eO|Q zBv9@wED$nA6d}*f!qnUhy3 zNFc1T&zwQ#;lteXq$zWKJ^WKsoT+59!OPp{ZQDWv5dejL=lCzZ~q9b zd-q^DeHub;?!pPJsV!T=id}ZOP%t!v3{C9gr5D0)4X-oFo#nnM8o$w|%%-z*+&J7c_F@lL_#gY0lbH8!)!O`Ae9 zqBV^c3vyapk#*t(uk2LO?fw0j?d*iOWJzpIBNbhz6N~okL(ZvF{Q6s48@$h-t6(C~ zA)DN|F>KSwM-0CoLRJ=*j*LJIYU9n#&2Z9Z)J$q^d?aB;EK(>17$;+wjg3KEwv2z| ze)teGO-(985ri)L7!HVLRG{ZFHZ&lwtLw*LMSWLG3!D!gsC6cAFcGrfqmStA$({BgRxqX*V6;z!GrT5M&7jc^}&Aa z8Wgu1LT08q0taUx?OKu`JDvQVp}d@X-2Lbg2Su!!jvRqJH;1_!H`I`da717qq5;BV zOyPRSzjzUgs;X3NkcR~U-j^?79~{K@Cr=>F%y4h>rGa3Kho`=z1g6GD7%D6IZRhAH z%qLIsVU0l0^Tbj#NrZhUEsdNJ+LeS!F)Pc+>+FP)G8f?sBmlZ55T5aI_+Gz;WHxg^ zq$^!f5i*L4kycdn<2yPXB&!vl$SLF`olboxHli5T*oTsm=zs*k&_)y@S&fazX=#Cw zovj)d7=uIUogBd^WCCGh>FR=gU;si42Yc-O?*P%lg^o)`o zrOv<+t9FY3Q)aUD_rvq-nR+@I1p_}jioIXIK6*xie?_O`0;iX5#R@K9Ls=Qp|N4uI zm)8LT;q^kYSm1l}2H&4Ph3oNScwfEZ%*5k}HFh8xJER?thgWAVR-=)pPMT%NSlR7{ zY_stfuSt5n3R0^hp<*$Dd+go1b+IuLyh(78qx0cFQqT^*83|cCiPQugfsv6s!rm1Z z#}CNQpftATg%S9>AqFk{3KV<$r+`G_#yCF1-mYEyXBdg2k&*mV>HwKovnDkdnOMF0 ne;|>>Zla!HZ&s~J2}b?~*Mml~P2hx<00000NkvXXu0mjfE!gng literal 0 HcmV?d00001 diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/57.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000000000000000000000000000000000000..390fe663c0f929cab0b4720140df71ade2bfd10a GIT binary patch literal 1766 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 Eg4myLIRF3v literal 0 HcmV?d00001 diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/60.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000000000000000000000000000000000000..e4d591b7f63c25beac173e5ef74cbaaae4b0ad38 GIT binary patch literal 1850 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 LNkvXXu0mjf8yqyX literal 0 HcmV?d00001 diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/76.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000000000000000000000000000000000000..9ee46360ecd91569529584c3168c0a376cebc356 GIT binary patch literal 2439 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 FV1nUap~V0I literal 0 HcmV?d00001 diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/80.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000000000000000000000000000000000000..654e3716c26366d68443feaead0c5247e70d2778 GIT binary patch literal 2547 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 JPDHLkV1mdN*7N`X literal 0 HcmV?d00001 diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/87.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000000000000000000000000000000000000..47e09b97212f76f9d580e9bcbeb1066ad58ef996 GIT binary patch literal 2764 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 CleverTapInboxViewController { + let style = CleverTapInboxStyleConfig() + style.title = "App Inbox" + style.navigationTintColor = .black + style.messageTags = ["tag1", "tag2"] + let inboxVC: CleverTapInboxViewController = (CleverTap.sharedInstance()?.newInboxViewController(with: style, andDelegate: context.coordinator))! + return inboxVC + } + + func updateUIViewController(_ uiViewController: CleverTapInboxViewController, context: Context) { + // Updates the state of the specified view controller with new information from SwiftUI. + } + + func makeCoordinator() -> CTAppInboxCoordinator { + CTAppInboxCoordinator() + } + + class CTAppInboxCoordinator: NSObject, CleverTapInboxViewControllerDelegate { + func messageDidSelect(_ message: CleverTapInboxMessage, at index: Int32, withButtonIndex buttonIndex: Int32) { + // This is called when an inbox message or button is clicked + } + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/CTAppInboxRepresentable.swift b/SwiftUIStarter/SwiftUIStarter/CTAppInboxRepresentable.swift new file mode 100644 index 00000000..c1a391fa --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTAppInboxRepresentable.swift @@ -0,0 +1,21 @@ +import SwiftUI +import CleverTapSDK + +struct CTAppInboxRepresentable: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> CleverTapInboxViewController { + let style = CleverTapInboxStyleConfig() + style.title = "App Inbox" + style.navigationTintColor = .black + style.messageTags = ["tag1", "tag2"] + let inboxVC: CleverTapInboxViewController = (CleverTap.sharedInstance()?.newInboxViewController(with: style, andDelegate: context.coordinator))! + return inboxVC + } + + func updateUIViewController(_ uiViewController: CleverTapInboxViewController, context: Context) { + // Updates the state of the specified view controller with new information from SwiftUI. + } + + func makeCoordinator() -> CTCallbackCoordinator { + CTCallbackCoordinator() + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/CTCallbackCoordinator.swift b/SwiftUIStarter/SwiftUIStarter/CTCallbackCoordinator.swift new file mode 100644 index 00000000..021c0b6d --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTCallbackCoordinator.swift @@ -0,0 +1,14 @@ +import CleverTapSDK + +class CTCallbackCoordinator: NSObject, CleverTapPushPermissionDelegate, CleverTapInboxViewControllerDelegate { + // MARK: CleverTapPushPermissionDelegate + func onPushPermissionResponse(_ accepted: Bool) { + print("Push Permission response called ---> accepted = \(accepted)") + } + + // MARK: CleverTapInboxViewControllerDelegate + func messageDidSelect(_ message: CleverTapInboxMessage, at index: Int32, withButtonIndex buttonIndex: Int32) { + // This is called when an inbox message or button is clicked + print("App Inbox tapped with button index: \(buttonIndex)") + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/CTHomeScreen.swift b/SwiftUIStarter/SwiftUIStarter/CTHomeScreen.swift new file mode 100644 index 00000000..13ee0f3d --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTHomeScreen.swift @@ -0,0 +1,250 @@ +import SwiftUI +import CleverTapSDK + +var cleverTapAdditionalInstance: CleverTap = { + let ctConfig = CleverTapInstanceConfig.init(accountId: "ZWW-WWW-WWRZ", accountToken: "000-001") + return CleverTap.instance(with: ctConfig) + }() + +struct HomeScreen: View { + let eventList = [ "Record User Profile", + "Record User Profile with Properties", + "Record User Event called Product Viewed", + "Record User Event with Properties", + "Record User Charged Event", + "Record User Event to an Additional Instance", + "Show App Inbox", + "Analytics in a WebView", + "Increment User Profile Property", + "Decrement User Profile Property", + "Activate Custom domain proxy", + "Local Half Interstitial Push Primer" + ] + + @State private var viewDidLoad = false + + var body: some View { + NavigationView { + VStack { + Image("logo") + .scaledToFit() + .frame(height: 72) + List { + ForEach(0 ..< eventList.count, id: \.self) { index in + HStack { + Button("\(eventList[index])") { + buttonAction(index: index) + } + Spacer() + if (eventList[index] == "Show App Inbox") { + // Show App Inbox controller + NavigationLink(destination: CTAppInboxRepresentable()) { } + } else if (eventList[index] == "Analytics in a WebView") { + // Show Web View + NavigationLink(destination: CTWebViewRepresentable()) { } + } + } + } + } + } + .onAppear() { + print("~~~ onAppear") + if viewDidLoad == false { + viewDidLoad = true + self.registerAppInbox() + self.initializeAppInbox() + print("~~~ viewDidLoad") + } + } + } + } + + func registerAppInbox() { + CleverTap.sharedInstance()?.registerInboxUpdatedBlock({ + let messageCount = CleverTap.sharedInstance()?.getInboxMessageCount() + let unreadCount = CleverTap.sharedInstance()?.getInboxMessageUnreadCount() + print("Inbox Message:\(String(describing: messageCount))/\(String(describing: unreadCount)) unread") + }) + } + + func initializeAppInbox() { + CleverTap.sharedInstance()?.initializeInbox(callback: ({ (success) in + let messageCount = CleverTap.sharedInstance()?.getInboxMessageCount() + let unreadCount = CleverTap.sharedInstance()?.getInboxMessageUnreadCount() + print("Inbox Message:\(String(describing: messageCount))/\(String(describing: unreadCount)) unread") + })) + } +} + +func buttonAction(index: Int) { + switch(index) { + case 0: + recordUserProfile() + break; + case 1: + recordUserProfileWithProperties() + break; + case 2: + recordUserEventWithoutProperties() + break; + case 3: + recordUserEventWithProperties() + break; + case 4: + recordUserChargedEvent() + break; + case 5: + recordUserEventforAdditionalInstance() + break; + case 8: + incrementUserProfileProperty() + break; + case 9: + decrementUserProfileProperty() + break; + case 10: + activateCustomDomain() + break; + case 11: + createLocalHalfInterstitialPushPrimer() + break; + default: + break; + } +} + +func recordUserProfile() { + // each of the below mentioned fields are optional + // if set, these populate demographic information in the Dashboard + let dob = NSDateComponents() + dob.day = 24 + dob.month = 5 + dob.year = 1993 + let d = NSCalendar.current.date(from: dob as DateComponents) + let profile: Dictionary = [ + "Name": "Nishant" as AnyObject, // String + "Identity": 6196032 as AnyObject, // String or number + "Email": "testnishant@gmail.com" as AnyObject, // Email address of the user + "Phone": "+1415501234" as AnyObject, // Phone (with the country code, starting with +) + "Gender": "M" as AnyObject, // Can be either M or F + "Employed": "Y" as AnyObject, // Can be either Y or N + "Education": "Graduate" as AnyObject, // Can be either School, College or Graduate + "Married": "Y" as AnyObject, // Can be either Y or N + "DOB": d! as AnyObject, // Date of Birth. An NSDate object + "Age": 26 as AnyObject, // Not required if DOB is set + "Tz":"Asia/Kolkata" as AnyObject, //an abbreviation such as "PST", a full name such as "America/Los_Angeles", + //or a custom ID such as "GMT-8:00" + "Photo": "www.foobar.com/image.jpeg" as AnyObject, // URL to the Image + + // optional fields. controls whether the user will be sent email, push etc. + "MSG-email": false as AnyObject, // Disable email notifications + "MSG-push": true as AnyObject, // Enable push notifications + "MSG-sms": false as AnyObject, // Disable SMS notifications + + //custom fields + "score": 15 as AnyObject, + "cost": 10.5 as AnyObject + ] + CleverTap.sharedInstance()?.profilePush(profile) +} + +func recordUserProfileWithProperties() { + /// To set a multi-value property + CleverTap.sharedInstance()?.profileSetMultiValues(["bag", "shoes"], forKey: "myStuff") + + /// To add an additional value(s) to a multi-value property + // CleverTap.sharedInstance()?.profileAddMultiValue("coat", forKey: "myStuff") + // CleverTap.sharedInstance()?.profileAddMultiValues(["socks", "scarf"], forKey: "myStuff") + + /// To remove a value(s) from a multi-value property + // CleverTap.sharedInstance()?.profileRemoveMultiValue("bag", forKey: "myStuff") + // CleverTap.sharedInstance()?.profileRemoveMultiValues(["shoes", "coat"], forKey: "myStuff") + + /// To remove the value of a property (scalar or multi-value) + // CleverTap.sharedInstance()?.profileRemoveValue(forKey: "myStuff") +} + +func recordUserEventWithoutProperties() { + // event without properties + CleverTap.sharedInstance()?.recordEvent("Product viewed") +} + +func recordUserEventWithProperties() { + // event with properties + let props = [ + "Product name": "Casio Chronograph Watch", + "Category": "Mens Accessories", + "Price": 59.99, + "Date": NSDate() + ] as [String : Any] + CleverTap.sharedInstance()?.recordEvent("Product viewed", withProps: props) +} + +func recordUserChargedEvent() { + //charged event + let chargeDetails = [ + "Amount": 300, + "Payment mode": "Credit Card", + "Charged ID": 24052013 + ] as [String : Any] + + let item1 = [ + "Category": "books", + "Book name": "The Millionaire next door", + "Quantity": 1 + ] as [String : Any] + + let item2 = [ + "Category": "books", + "Book name": "Achieving inner zen", + "Quantity": 1 + ] as [String : Any] + + let item3 = [ + "Category": "books", + "Book name": "Chuck it, let's do it", + "Quantity": 5 + ] as [String : Any] + + CleverTap.sharedInstance()?.recordChargedEvent(withDetails: chargeDetails, andItems: [item1, item2, item3]) +} + +func recordUserEventforAdditionalInstance() { + cleverTapAdditionalInstance.recordEvent("TestCT1WProps", withProps: ["one": NSNumber.init(integerLiteral: 1)]) + cleverTapAdditionalInstance.profileSetMultiValues(["a"], forKey: "letters") +} + +func incrementUserProfileProperty() { + CleverTap.sharedInstance()?.profileIncrementValue(by: NSNumber(value: 1), forKey: "score") +} + +func decrementUserProfileProperty() { + CleverTap.sharedInstance()?.profileDecrementValue(by: NSNumber(value: 1), forKey: "score") +} + +func activateCustomDomain() { +// let storyBoard = UIStoryboard.init(name: "Main", bundle: nil) +// let customDomainVC = storyBoard.instantiateViewController(withIdentifier: "CustomDomainVC") +// self.navigationController?.pushViewController(customDomainVC, animated: true) +} + +func createLocalHalfInterstitialPushPrimer() { + CleverTap.sharedInstance()?.getNotificationPermissionStatus(completionHandler: { status in + if status == .notDetermined || status == .denied { + let localInAppBuilder = CTLocalInApp(inAppType: CTLocalInAppType.HALF_INTERSTITIAL, titleText: "Get Notified", messageText: "Please enable notifications on your device to use Push Notifications.", followDeviceOrientation: true, positiveBtnText: "Allow", negativeBtnText: "Cancel") + localInAppBuilder.setFallbackToSettings(true) + localInAppBuilder.setImageUrl("https://icons.iconarchive.com/icons/treetog/junior/64/camera-icon.png") + CleverTap.sharedInstance()?.promptPushPrimer(localInAppBuilder.getSettings()) + } else { + print("Push Persmission is already enabled.") + } + }) +} + +#if DEBUG +struct CTHomeScreen_Previews: PreviewProvider { + static var previews: some View { + CTHomeScreen() + } +} +#endif diff --git a/SwiftUIStarter/SwiftUIStarter/CTViewModifier.swift b/SwiftUIStarter/SwiftUIStarter/CTViewModifier.swift new file mode 100644 index 00000000..ab876306 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTViewModifier.swift @@ -0,0 +1,48 @@ +#if canImport(SwiftUI) +import SwiftUI +import CleverTapSDK + +@available(iOS 13, *) +internal struct CTViewModifier: ViewModifier { + @State private var viewDidLoad = false + let screenName: String + + func body(content: Content) -> some View { + content.onAppear { + if viewDidLoad == false { + // `viewDidLoad` eqivalent in SwiftUI + viewDidLoad = true + if (screenName == "Home Screen") { + self.registerAppInbox() + self.initializeAppInbox() + } + // Record any CleverTap events here. + CleverTap.sharedInstance()?.recordEvent(screenName) + } + } + } + + func registerAppInbox() { + CleverTap.sharedInstance()?.registerInboxUpdatedBlock({ + let messageCount = CleverTap.sharedInstance()?.getInboxMessageCount() + let unreadCount = CleverTap.sharedInstance()?.getInboxMessageUnreadCount() + print("Inbox Message:\(String(describing: messageCount))/\(String(describing: unreadCount)) unread") + }) + } + + func initializeAppInbox() { + CleverTap.sharedInstance()?.initializeInbox(callback: ({ (success) in + let messageCount = CleverTap.sharedInstance()?.getInboxMessageCount() + let unreadCount = CleverTap.sharedInstance()?.getInboxMessageUnreadCount() + print("Inbox Message:\(String(describing: messageCount))/\(String(describing: unreadCount)) unread") + })) + } +} + +@available(iOS 13, *) +public extension View { + func recordScreenView(screenName: String) -> some View { + self.modifier(CTViewModifier(screenName: screenName)) + } +} +#endif diff --git a/SwiftUIStarter/SwiftUIStarter/CTWebViewRepresentable.swift b/SwiftUIStarter/SwiftUIStarter/CTWebViewRepresentable.swift new file mode 100644 index 00000000..e65aee13 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTWebViewRepresentable.swift @@ -0,0 +1,41 @@ +import SwiftUI +import WebKit +import CleverTapSDK + +struct CTWebViewRepresentable: UIViewControllerRepresentable { + typealias UIViewControllerType = CTWebviewVC + func makeUIViewController(context: Context) -> CTWebviewVC { + let webViewVC = CTWebviewVC() + return webViewVC + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + // Updates the state of the specified view controller with new information from SwiftUI. + } +} + +class CTWebviewVC: UIViewController { + var webView: WKWebView! + + override func viewDidLoad() { + super.viewDidLoad() + addWebview() + } + + func addWebview() { + let ctInterface: CleverTapJSInterface = CleverTapJSInterface(config: nil) + self.webView = WKWebView (frame: self.view.frame) + self.webView.configuration.userContentController.add(ctInterface, name: "clevertap") + self.webView.loadHTMLString(self.htmlStringFromFile(with: "sampleHTMLCode"), baseURL: nil) + self.view.addSubview(self.webView) + } + + private func htmlStringFromFile(with name: String) -> String { + let path = Bundle.main.path(forResource: name, ofType: "html") + if let result = try? String(contentsOfFile: path!, encoding: String.Encoding.utf8) { + return result + } + return "" + } +} + diff --git a/SwiftUIStarter/SwiftUIStarter/ContentView.swift b/SwiftUIStarter/SwiftUIStarter/ContentView.swift new file mode 100644 index 00000000..fffcf854 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/ContentView.swift @@ -0,0 +1,15 @@ +import SwiftUI + +struct ContentView: View { + var body: some View { + HomeScreen() + } +} + +#if DEBUG +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} +#endif diff --git a/SwiftUIStarter/SwiftUIStarter/HomeScreen.swift b/SwiftUIStarter/SwiftUIStarter/HomeScreen.swift new file mode 100644 index 00000000..0bba97ed --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/HomeScreen.swift @@ -0,0 +1,216 @@ +import SwiftUI +import CleverTapSDK + +var cleverTapAdditionalInstance: CleverTap = { + let ctConfig = CleverTapInstanceConfig.init(accountId: "ZWW-WWW-WWRZ", accountToken: "000-001") + return CleverTap.instance(with: ctConfig) + }() + +struct HomeScreen: View { + let eventList = [ "Record User Profile", + "Record User Profile with Properties", + "Record User Event called Product Viewed", + "Record User Event with Properties", + "Record User Charged Event", + "Record User Event to an Additional Instance", + "Show App Inbox", + "Analytics in a WebView", + "Increment User Profile Property", + "Decrement User Profile Property", + "Local Half Interstitial Push Primer" + ] + + var body: some View { + NavigationView { + VStack { + Image("logo") + .scaledToFit() + .frame(height: 72) + List { + ForEach(0 ..< eventList.count, id: \.self) { index in + HStack { + Button("\(eventList[index])") { + buttonAction(index: index) + } + Spacer() + if (eventList[index] == "Show App Inbox") { + // Show App Inbox controller + NavigationLink(destination: CTAppInboxRepresentable().recordScreenView(screenName: "CT App Inbox")) { + } + } else if (eventList[index] == "Analytics in a WebView") { + // Show Web View + NavigationLink(destination: CTWebViewRepresentable().recordScreenView(screenName: "CT Web View")) { + } + } + } + } + } + } + .recordScreenView(screenName: "Home Screen") + } + } +} + +func buttonAction(index: Int) { + switch(index) { + case 0: + recordUserProfile() + break; + case 1: + recordUserProfileWithProperties() + break; + case 2: + recordUserEventWithoutProperties() + break; + case 3: + recordUserEventWithProperties() + break; + case 4: + recordUserChargedEvent() + break; + case 5: + recordUserEventforAdditionalInstance() + break; + case 8: + incrementUserProfileProperty() + break; + case 9: + decrementUserProfileProperty() + break; + case 10: + createLocalHalfInterstitialPushPrimer() + break; + default: + break; + } +} + +func recordUserProfile() { + // each of the below mentioned fields are optional + // if set, these populate demographic information in the Dashboard + let dob = NSDateComponents() + dob.day = 24 + dob.month = 5 + dob.year = 1993 + let d = NSCalendar.current.date(from: dob as DateComponents) + let profile: Dictionary = [ + "Name": "Nishant" as AnyObject, // String + "Identity": 6196032 as AnyObject, // String or number + "Email": "testnishant@gmail.com" as AnyObject, // Email address of the user + "Phone": "+1415501234" as AnyObject, // Phone (with the country code, starting with +) + "Gender": "M" as AnyObject, // Can be either M or F + "Employed": "Y" as AnyObject, // Can be either Y or N + "Education": "Graduate" as AnyObject, // Can be either School, College or Graduate + "Married": "Y" as AnyObject, // Can be either Y or N + "DOB": d! as AnyObject, // Date of Birth. An NSDate object + "Age": 26 as AnyObject, // Not required if DOB is set + "Tz":"Asia/Kolkata" as AnyObject, //an abbreviation such as "PST", a full name such as "America/Los_Angeles", + //or a custom ID such as "GMT-8:00" + "Photo": "www.foobar.com/image.jpeg" as AnyObject, // URL to the Image + + // optional fields. controls whether the user will be sent email, push etc. + "MSG-email": false as AnyObject, // Disable email notifications + "MSG-push": true as AnyObject, // Enable push notifications + "MSG-sms": false as AnyObject, // Disable SMS notifications + + //custom fields + "score": 15 as AnyObject, + "cost": 10.5 as AnyObject + ] + CleverTap.sharedInstance()?.profilePush(profile) +} + +func recordUserProfileWithProperties() { + /// To set a multi-value property + CleverTap.sharedInstance()?.profileSetMultiValues(["bag", "shoes"], forKey: "myStuff") + + /// To add an additional value(s) to a multi-value property + CleverTap.sharedInstance()?.profileAddMultiValue("coat", forKey: "myStuff") + CleverTap.sharedInstance()?.profileAddMultiValues(["socks", "scarf"], forKey: "myStuff") + + /// To remove a value(s) from a multi-value property + CleverTap.sharedInstance()?.profileRemoveMultiValue("bag", forKey: "myStuff") + CleverTap.sharedInstance()?.profileRemoveMultiValues(["shoes", "coat"], forKey: "myStuff") + + /// To remove the value of a property (scalar or multi-value) + CleverTap.sharedInstance()?.profileRemoveValue(forKey: "myStuff") +} + +func recordUserEventWithoutProperties() { + // event without properties + CleverTap.sharedInstance()?.recordEvent("Product viewed") +} + +func recordUserEventWithProperties() { + // event with properties + let props = [ + "Product name": "Casio Chronograph Watch", + "Category": "Mens Accessories", + "Price": 59.99, + "Date": NSDate() + ] as [String : Any] + CleverTap.sharedInstance()?.recordEvent("Product viewed", withProps: props) +} + +func recordUserChargedEvent() { + //charged event + let chargeDetails = [ + "Amount": 300, + "Payment mode": "Credit Card", + "Charged ID": 24052013 + ] as [String : Any] + + let item1 = [ + "Category": "books", + "Book name": "The Millionaire next door", + "Quantity": 1 + ] as [String : Any] + + let item2 = [ + "Category": "books", + "Book name": "Achieving inner zen", + "Quantity": 1 + ] as [String : Any] + + let item3 = [ + "Category": "books", + "Book name": "Chuck it, let's do it", + "Quantity": 5 + ] as [String : Any] + + CleverTap.sharedInstance()?.recordChargedEvent(withDetails: chargeDetails, andItems: [item1, item2, item3]) +} + +func recordUserEventforAdditionalInstance() { + cleverTapAdditionalInstance.recordEvent("TestCT1WProps", withProps: ["one": NSNumber.init(integerLiteral: 1)]) + cleverTapAdditionalInstance.profileSetMultiValues(["a"], forKey: "letters") +} + +func incrementUserProfileProperty() { + CleverTap.sharedInstance()?.profileIncrementValue(by: NSNumber(value: 1), forKey: "score") +} + +func decrementUserProfileProperty() { + CleverTap.sharedInstance()?.profileDecrementValue(by: NSNumber(value: 1), forKey: "score") +} + +func createLocalHalfInterstitialPushPrimer() { + CleverTap.sharedInstance()?.getNotificationPermissionStatus(completionHandler: { status in + if status == .notDetermined || status == .denied { + let localInAppBuilder = CTLocalInApp(inAppType: CTLocalInAppType.HALF_INTERSTITIAL, titleText: "Get Notified", messageText: "Please enable notifications on your device to use Push Notifications.", followDeviceOrientation: true, positiveBtnText: "Allow", negativeBtnText: "Cancel") + localInAppBuilder.setFallbackToSettings(true) + localInAppBuilder.setImageUrl("https://icons.iconarchive.com/icons/treetog/junior/64/camera-icon.png") + CleverTap.sharedInstance()?.promptPushPrimer(localInAppBuilder.getSettings()) + } else { + print("Push Persmission is already enabled.") + } + }) +} + +#if DEBUG +struct CTHomeScreen_Previews: PreviewProvider { + static var previews: some View { + HomeScreen() + } +} +#endif diff --git a/SwiftUIStarter/SwiftUIStarter/Info.plist b/SwiftUIStarter/SwiftUIStarter/Info.plist new file mode 100644 index 00000000..51ef168b --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/Info.plist @@ -0,0 +1,16 @@ + + + + + CleverTapAccountID + W9R-486-4W5Z + CleverTapToken + 6b4-2c0 + UIBackgroundModes + + fetch + processing + remote-notification + + + diff --git a/SwiftUIStarter/SwiftUIStarter/Preview Content/Preview Assets.xcassets/Contents.json b/SwiftUIStarter/SwiftUIStarter/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/SwiftUIStarter.entitlements b/SwiftUIStarter/SwiftUIStarter/SwiftUIStarter.entitlements new file mode 100644 index 00000000..903def2a --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/SwiftUIStarter.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/SwiftUIStarter/SwiftUIStarter/SwiftUIStarterApp.swift b/SwiftUIStarter/SwiftUIStarter/SwiftUIStarterApp.swift new file mode 100644 index 00000000..8d38ad53 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/SwiftUIStarterApp.swift @@ -0,0 +1,12 @@ +import SwiftUI + +@main +struct SwiftUIStarterApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/sampleHTMLCode.html b/SwiftUIStarter/SwiftUIStarter/sampleHTMLCode.html new file mode 100644 index 00000000..cea72b9f --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/sampleHTMLCode.html @@ -0,0 +1,158 @@ + +
+ + + + + +

CleverTap Webview



+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ + + + + From 01a8f1c02ee2dae13d1bfabff4b96a0f577ec10a Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Thu, 3 Aug 2023 18:16:51 +0530 Subject: [PATCH 02/14] task(SDK-3111): Added initial commit for encryption in iOS for PII data --- CleverTapSDK.xcodeproj/project.pbxproj | 8 + CleverTapSDK/CTAES.h | 18 +++ CleverTapSDK/CTAES.m | 149 ++++++++++++++++++ CleverTapSDK/CTConstants.h | 2 + CleverTapSDK/CTLoginInfoProvider.m | 7 +- CleverTapSDK/CTPlistInfo.h | 1 + CleverTapSDK/CTPlistInfo.m | 2 + CleverTapSDK/CleverTap.h | 5 + CleverTapSDK/CleverTapInstanceConfig.h | 19 +++ CleverTapSDK/CleverTapInstanceConfig.m | 11 ++ .../ObjCStarter.xcodeproj/project.pbxproj | 2 + ObjCStarter/ObjCStarter/Info.plist | 2 + 12 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 CleverTapSDK/CTAES.h create mode 100644 CleverTapSDK/CTAES.m diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 9d321a22..0f8cc4ff 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -162,6 +162,8 @@ 32394C2729FA278C00956058 /* CTUriHelperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32394C2629FA278C00956058 /* CTUriHelperTest.m */; }; 32790957299CC099001FE140 /* CTUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32790956299CC099001FE140 /* CTUtilsTest.m */; }; 32790959299F4B29001FE140 /* CTDeviceInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32790958299F4B29001FE140 /* CTDeviceInfoTest.m */; }; + 4803951B2A7ABAD200C4D254 /* CTAES.m in Sources */ = {isa = PBXBuildFile; fileRef = 480395192A7ABAD200C4D254 /* CTAES.m */; }; + 4803951C2A7ABAD200C4D254 /* CTAES.h in Headers */ = {isa = PBXBuildFile; fileRef = 4803951A2A7ABAD200C4D254 /* CTAES.h */; }; 4808030E292EB4FB00C06E2F /* CleverTap+PushPermission.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */; }; 48080311292EB50D00C06E2F /* CTLocalInApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030F292EB50D00C06E2F /* CTLocalInApp.h */; settings = {ATTRIBUTES = (Public, ); }; }; 48080312292EB50D00C06E2F /* CTLocalInApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 48080310292EB50D00C06E2F /* CTLocalInApp.m */; }; @@ -613,6 +615,8 @@ 32790956299CC099001FE140 /* CTUtilsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTUtilsTest.m; sourceTree = ""; }; 32790958299F4B29001FE140 /* CTDeviceInfoTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTDeviceInfoTest.m; sourceTree = ""; }; 37D3A7E5589257CFA37243C3 /* libPods-shared-CleverTapSDKUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-shared-CleverTapSDKUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 480395192A7ABAD200C4D254 /* CTAES.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTAES.m; sourceTree = ""; }; + 4803951A2A7ABAD200C4D254 /* CTAES.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTAES.h; sourceTree = ""; }; 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CleverTap+PushPermission.h"; sourceTree = ""; }; 4808030F292EB50D00C06E2F /* CTLocalInApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTLocalInApp.h; sourceTree = ""; }; 48080310292EB50D00C06E2F /* CTLocalInApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTLocalInApp.m; sourceTree = ""; }; @@ -1222,6 +1226,8 @@ D0C7BBBF207D82C0001345EF /* CleverTapSDK */ = { isa = PBXGroup; children = ( + 4803951A2A7ABAD200C4D254 /* CTAES.h */, + 480395192A7ABAD200C4D254 /* CTAES.m */, 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */, 4E838C4429A0C94B00ED0875 /* CleverTap+CTVar.h */, 4E64855A287440BA00C2F409 /* AmazonRootCA1.cer */, @@ -1518,6 +1524,7 @@ 07B94550219EA39000D4C542 /* CleverTap+Inbox.h in Headers */, 071EB4F9217F6427008F0FAB /* CTNotificationButton.h in Headers */, 48080311292EB50D00C06E2F /* CTLocalInApp.h in Headers */, + 4803951C2A7ABAD200C4D254 /* CTAES.h in Headers */, D0213D4D207D905800FE5740 /* CleverTapTrackedViewController.h in Headers */, 4E25E3C5278887A70008C888 /* CTFlexibleIdentityRepo.h in Headers */, 071EB512217F6427008F0FAB /* CTInAppHTMLViewController.h in Headers */, @@ -2019,6 +2026,7 @@ 07B9454D219EA34300D4C542 /* Inbox.xcdatamodeld in Sources */, D0213D51207D905800FE5740 /* CleverTapUTMDetail.m in Sources */, D0596EFB208A6A9000A80753 /* CTSwizzle.m in Sources */, + 4803951B2A7ABAD200C4D254 /* CTAES.m in Sources */, D032F3A92093EC9700F98D74 /* CTValidationResult.m in Sources */, 071EB501217F6427008F0FAB /* CTFooterViewController.m in Sources */, D0A84ADD209136D500191B1F /* CTLogger.m in Sources */, diff --git a/CleverTapSDK/CTAES.h b/CleverTapSDK/CTAES.h new file mode 100644 index 00000000..baac1de0 --- /dev/null +++ b/CleverTapSDK/CTAES.h @@ -0,0 +1,18 @@ +#import +#import "CleverTap.h" + +@interface CTAES : NSObject + +/** + * Returns AES128 encrypted string using the crypto framework. + */ +- (NSString *)getEncryptedString:(NSString *)identifier; + +/** + * Returns AES128 decrypted string using the crypto framework. + */ +- (NSString *)getDecryptedString:(NSString *)identifier; + +- (instancetype)initWithAccountID:(NSString *)accountID encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel; + +@end diff --git a/CleverTapSDK/CTAES.m b/CleverTapSDK/CTAES.m new file mode 100644 index 00000000..f4bc4750 --- /dev/null +++ b/CleverTapSDK/CTAES.m @@ -0,0 +1,149 @@ +#import +#import "CTAES.h" +#import "CTPreferences.h" + +NSString *const kENCRYPTION_KEY = @"CLTAP_ENCRYPTION_KEY"; +NSString *const kCacheGUIDS = @"CachedGUIDS"; + +@interface CTAES () {} +@property (nonatomic, strong) NSString *accountID; +@property (nonatomic, assign) CleverTapEncryptionLevel encryptionLevel; +@end + +@implementation CTAES + +- (instancetype)initWithAccountID:(NSString *)accountID + encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { + if (self = [super init]) { + _accountID = accountID; + [self updateEncryptionLevel:encryptionLevel]; + } + return self; +} + +- (void)updateEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { + _encryptionLevel = encryptionLevel; + long lastEncryptionLevel = [CTPreferences getIntForKey:[self getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID] withResetValue:0]; + if (lastEncryptionLevel != _encryptionLevel) { + [CTPreferences putInt:_encryptionLevel forKey:[self getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID]]; + [self updateValues]; + } +} + +- (void)updateValues { + NSDictionary *cachedGUIDS = [CTPreferences getObjectForKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + if (!cachedGUIDS) cachedGUIDS = @{}; + NSMutableDictionary *newCache = [NSMutableDictionary new]; + if (_encryptionLevel == CleverTapEncryptionOff) { + [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { + NSString *key = [self getCachedKey:cachedKey]; + NSString *identifier = [self getCachedIdentifier:cachedKey]; + NSString *decryptedString = [self getDecryptedString:identifier]; + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, decryptedString]; + newCache[cacheKey] = value; + }]; + [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + } else if (_encryptionLevel == CleverTapEncryptionOn) { + [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { + NSString *key = [self getCachedKey:cachedKey]; + NSString *identifier = [self getCachedIdentifier:cachedKey]; + NSString *encryptedString = [self getEncryptedString:identifier]; + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedString]; + newCache[cacheKey] = value; + }]; + [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + } +} + +- (NSString *)getEncryptedString:(NSString *)identifier { + NSString *encryptedString = identifier; + if (_encryptionLevel == CleverTapEncryptionOn) { + NSData *dataValue = [identifier dataUsingEncoding:NSUTF8StringEncoding]; + NSData *encryptedData = [self convertData:dataValue withOperation:kCCEncrypt]; + encryptedString = [encryptedData base64EncodedStringWithOptions:kNilOptions]; + } + return encryptedString; +} + +- (NSString *)getDecryptedString:(NSString *)identifier { + NSData *dataValue = [[NSData alloc] initWithBase64EncodedString:identifier options:kNilOptions]; + NSData *decryptedData = [self convertData:dataValue withOperation:kCCDecrypt]; + NSString *decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; + return decryptedString; +} + +- (NSData *)convertData:(NSData *)data + withOperation:(CCOperation)operation { + // TODO: will update key and identifier. + NSData *outputData = [self AES128WithOperation:operation + key:@"TestKey" + identifier:@"TestIdentifier" + data:data]; + return outputData; +} + +- (NSData *)AES128WithOperation:(CCOperation)operation + key:(NSString *)key + identifier:(NSString *)identifier + data:(NSData *)data { + // Note: The key will be 0's but we intentionally are keeping it this way to maintain + // compatibility. The correct code is: + // char keyPtr[[key length] + 1]; + char keyCString[kCCKeySizeAES128 + 1]; + memset(keyCString, 0, sizeof(keyCString)); + [key getCString:keyCString maxLength:sizeof(keyCString) encoding:NSUTF8StringEncoding]; + + char identifierCString[kCCBlockSizeAES128 + 1]; + memset(identifierCString, 0, sizeof(identifierCString)); + [identifier getCString:identifierCString + maxLength:sizeof(identifierCString) + encoding:NSUTF8StringEncoding]; + + size_t outputAvailableSize = [data length] + kCCBlockSizeAES128; + void *output = malloc(outputAvailableSize); + + size_t outputMovedSize = 0; + CCCryptorStatus cryptStatus = CCCrypt(operation, + kCCAlgorithmAES128, + kCCOptionPKCS7Padding, + keyCString, + kCCBlockSizeAES128, + identifierCString, + [data bytes], + [data length], + output, + outputAvailableSize, + &outputMovedSize); + + if (cryptStatus != kCCSuccess) { + free(output); + return nil; + } + + return [NSData dataWithBytesNoCopy:output length:outputMovedSize]; +} + +- (NSString *)getKeyWithSuffix:(NSString *)suffix + accountID:(NSString *)accountID { + return [NSString stringWithFormat:@"%@:%@", accountID, suffix]; +} + +- (NSString *)getCachedKey:(NSString *)value { + if ([value rangeOfString:@"_"].length > 0) { + NSUInteger index = [value rangeOfString:@"_"].location; + return [value substringToIndex:index]; + } else { + return nil; + } +} + +- (NSString *)getCachedIdentifier:(NSString *)value { + if ([value rangeOfString:@"_"].length > 0) { + NSUInteger index = [value rangeOfString:@"_"].location; + return [value substringFromIndex:index+1]; + } else { + return nil; + } +} + +@end diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 51a702d3..29dfce36 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -152,4 +152,6 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_DEFINE_VARS_URL @"/defineVars" +#define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" + diff --git a/CleverTapSDK/CTLoginInfoProvider.m b/CleverTapSDK/CTLoginInfoProvider.m index 78b8d217..55017aeb 100644 --- a/CleverTapSDK/CTLoginInfoProvider.m +++ b/CleverTapSDK/CTLoginInfoProvider.m @@ -10,6 +10,7 @@ #import "CTPreferences.h" #import "CTConstants.h" #import "CleverTapInstanceConfigPrivate.h" +#import "CTAES.h" NSString *const kCachedGUIDS = @"CachedGUIDS"; NSString *const kCachedIdentities = @"CachedIdentities"; @@ -36,7 +37,8 @@ - (void)cacheGUID:(NSString *)guid forKey:(NSString *)key andIdentifier:(NSStrin NSDictionary *cache = [self getCachedGUIDs]; if (!cache) cache = @{}; NSMutableDictionary *newCache = [NSMutableDictionary dictionaryWithDictionary:cache]; - NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, identifier]; + NSString *encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier]; newCache[cacheKey] = guid; [self setCachedGUIDs:newCache]; } @@ -71,7 +73,8 @@ - (NSString *)getGUIDforKey:(NSString *)key andIdentifier:(NSString *)identifier if (!key || !identifier) return nil; NSDictionary *cache = [self getCachedGUIDs]; - NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, identifier]; + NSString *encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier]; if (!cache) return nil; else return cache[cacheKey]; } diff --git a/CleverTapSDK/CTPlistInfo.h b/CleverTapSDK/CTPlistInfo.h index 2b0691be..5a2f0c20 100644 --- a/CleverTapSDK/CTPlistInfo.h +++ b/CleverTapSDK/CTPlistInfo.h @@ -12,6 +12,7 @@ @property (nonatomic, assign, readonly) BOOL useCustomCleverTapId; @property (nonatomic, assign, readonly) BOOL beta; @property (nonatomic, assign, readonly) BOOL disableIDFV; +@property (nonatomic, strong, readonly, nullable) NSString *encryptionLevel; + (instancetype _Nullable)sharedInstance; - (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token region:(NSString * _Nullable)region; diff --git a/CleverTapSDK/CTPlistInfo.m b/CleverTapSDK/CTPlistInfo.m index 3564d03a..d232ca44 100644 --- a/CleverTapSDK/CTPlistInfo.m +++ b/CleverTapSDK/CTPlistInfo.m @@ -90,6 +90,8 @@ - (instancetype)init { // Fetch IDFV Flag from INFO.PLIST NSString *disableIDFV = [CTPlistInfo getMetaDataForAttribute:CLTAP_DISABLE_IDFV_LABEL]; _disableIDFV = (disableIDFV && [disableIDFV isEqualToString:@"1"]); + + _encryptionLevel = [CTPlistInfo getMetaDataForAttribute:CLTAP_ENCRYPTION_LEVEL]; } return self; } diff --git a/CleverTapSDK/CleverTap.h b/CleverTapSDK/CleverTap.h index 4f00cab7..85dad188 100644 --- a/CleverTapSDK/CleverTap.h +++ b/CleverTapSDK/CleverTap.h @@ -54,6 +54,11 @@ typedef NS_ENUM(int, CTSignedCallEvent) { SIGNED_CALL_END_EVENT }; +typedef NS_ENUM(int, CleverTapEncryptionLevel) { + CleverTapEncryptionOff = 0, + CleverTapEncryptionOn = 1 +}; + @interface CleverTap : NSObject #pragma mark - Properties diff --git a/CleverTapSDK/CleverTapInstanceConfig.h b/CleverTapSDK/CleverTapInstanceConfig.h index 02559fb6..dfef71bf 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.h +++ b/CleverTapSDK/CleverTapInstanceConfig.h @@ -1,5 +1,6 @@ #import #import "CleverTap.h" +@class CTAES; @interface CleverTapInstanceConfig : NSObject @@ -16,6 +17,8 @@ @property (nonatomic, assign) BOOL disableIDFV; @property (nonatomic, assign) CleverTapLogLevel logLevel; @property (nonatomic, strong, nullable) NSArray *identityKeys; +@property (nonatomic, assign) CleverTapEncryptionLevel encryptionLevel; +@property (nonatomic, strong, readonly, nullable) CTAES *aesCrypt; - (instancetype _Nonnull) init __unavailable; @@ -35,4 +38,20 @@ accountToken:(NSString* _Nonnull)accountToken proxyDomain:(NSString* _Nonnull)proxyDomain spikyProxyDomain:(NSString* _Nonnull)spikyProxyDomain; + +/*! + @method + + @abstract + Set the encryption level + + @discussion + Set using CleverTapEncryptionLevel enum values (or the corresponding int values). + + CleverTapEncryptionOff - turns off all encryption. + CleverTapEncryptionOn - turns enxryption on for PII data + + @param encryptionLevel the encryption level to set + */ +- (void)setEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel; @end diff --git a/CleverTapSDK/CleverTapInstanceConfig.m b/CleverTapSDK/CleverTapInstanceConfig.m index 72bac34e..2532c74e 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.m +++ b/CleverTapSDK/CleverTapInstanceConfig.m @@ -2,6 +2,7 @@ #import "CleverTapInstanceConfigPrivate.h" #import "CTPlistInfo.h" #import "CTConstants.h" +#import "CTAES.h" @implementation CleverTapInstanceConfig @@ -197,6 +198,8 @@ - (void) setupPlistData:(BOOL)isDefault { _enablePersonalization = YES; _logLevel = 0; _beta = plist.beta; + _encryptionLevel = isDefault ? [plist.encryptionLevel intValue] : CleverTapEncryptionOff; + _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel]; } - (void) checkIfAvailableAccountId:(NSString *)accountId @@ -209,4 +212,12 @@ - (void) checkIfAvailableAccountId:(NSString *)accountId CleverTapLogStaticInfo("CleverTap accountToken is empty"); } } + +- (void)setEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { + if(_isDefaultInstance) { + _encryptionLevel = CleverTapEncryptionOff; + } else { + _encryptionLevel = encryptionLevel; + } +} @end diff --git a/ObjCStarter/ObjCStarter.xcodeproj/project.pbxproj b/ObjCStarter/ObjCStarter.xcodeproj/project.pbxproj index 952e65e6..e05d003d 100644 --- a/ObjCStarter/ObjCStarter.xcodeproj/project.pbxproj +++ b/ObjCStarter/ObjCStarter.xcodeproj/project.pbxproj @@ -893,6 +893,7 @@ CODE_SIGN_ENTITLEMENTS = ObjCStarter/ObjCStarter.entitlements; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = A5J34NR598; + GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = ObjCStarter/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -914,6 +915,7 @@ CODE_SIGN_ENTITLEMENTS = ObjCStarter/ObjCStarter.entitlements; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = A5J34NR598; + GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = ObjCStarter/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/ObjCStarter/ObjCStarter/Info.plist b/ObjCStarter/ObjCStarter/Info.plist index e50b15fd..3414cdae 100644 --- a/ObjCStarter/ObjCStarter/Info.plist +++ b/ObjCStarter/ObjCStarter/Info.plist @@ -45,5 +45,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + CleverTapEncryptionLevel + 1 From f827904b88068825dbb23a3d0604b57dfacb3a85 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Mon, 7 Aug 2023 15:19:48 +0530 Subject: [PATCH 03/14] Added support for multi instance encryption, handled null case while encryption --- CleverTapSDK/CTAES.m | 81 +++++++++++++++++--------- CleverTapSDK/CTConstants.h | 1 + CleverTapSDK/CTLoginInfoProvider.m | 11 +++- CleverTapSDK/CleverTapInstanceConfig.h | 2 +- CleverTapSDK/CleverTapInstanceConfig.m | 15 +++-- 5 files changed, 75 insertions(+), 35 deletions(-) diff --git a/CleverTapSDK/CTAES.m b/CleverTapSDK/CTAES.m index f4bc4750..600c3521 100644 --- a/CleverTapSDK/CTAES.m +++ b/CleverTapSDK/CTAES.m @@ -1,8 +1,11 @@ #import #import "CTAES.h" +#import "CTConstants.h" #import "CTPreferences.h" NSString *const kENCRYPTION_KEY = @"CLTAP_ENCRYPTION_KEY"; +NSString *const kCRYPT_KEY_PREFIX = @"Lq3fz"; +NSString *const kCRYPT_KEY_SUFFIX = @"bLti2"; NSString *const kCacheGUIDS = @"CachedGUIDS"; @interface CTAES () {} @@ -32,43 +35,59 @@ - (void)updateEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { - (void)updateValues { NSDictionary *cachedGUIDS = [CTPreferences getObjectForKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; - if (!cachedGUIDS) cachedGUIDS = @{}; - NSMutableDictionary *newCache = [NSMutableDictionary new]; - if (_encryptionLevel == CleverTapEncryptionOff) { - [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { - NSString *key = [self getCachedKey:cachedKey]; - NSString *identifier = [self getCachedIdentifier:cachedKey]; - NSString *decryptedString = [self getDecryptedString:identifier]; - NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, decryptedString]; - newCache[cacheKey] = value; - }]; - [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; - } else if (_encryptionLevel == CleverTapEncryptionOn) { - [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { - NSString *key = [self getCachedKey:cachedKey]; - NSString *identifier = [self getCachedIdentifier:cachedKey]; - NSString *encryptedString = [self getEncryptedString:identifier]; - NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedString]; - newCache[cacheKey] = value; - }]; - [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + if (cachedGUIDS) { + NSMutableDictionary *newCache = [NSMutableDictionary new]; + if (_encryptionLevel == CleverTapEncryptionOff) { + [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { + NSString *key = [self getCachedKey:cachedKey]; + NSString *identifier = [self getCachedIdentifier:cachedKey]; + NSString *decryptedString = [self getDecryptedString:identifier]; + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, decryptedString]; + newCache[cacheKey] = value; + }]; + [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + } else if (_encryptionLevel == CleverTapEncryptionOn) { + [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { + NSString *key = [self getCachedKey:cachedKey]; + NSString *identifier = [self getCachedIdentifier:cachedKey]; + NSString *encryptedString = [self getEncryptedString:identifier]; + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedString]; + newCache[cacheKey] = value; + }]; + [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + } } } - (NSString *)getEncryptedString:(NSString *)identifier { NSString *encryptedString = identifier; if (_encryptionLevel == CleverTapEncryptionOn) { - NSData *dataValue = [identifier dataUsingEncoding:NSUTF8StringEncoding]; - NSData *encryptedData = [self convertData:dataValue withOperation:kCCEncrypt]; - encryptedString = [encryptedData base64EncodedStringWithOptions:kNilOptions]; + @try { + NSData *dataValue = [identifier dataUsingEncoding:NSUTF8StringEncoding]; + NSData *encryptedData = [self convertData:dataValue withOperation:kCCEncrypt]; + if (encryptedData) { + encryptedString = [encryptedData base64EncodedStringWithOptions:kNilOptions]; + } + } @catch (NSException *e) { + CleverTapLogStaticInternal(@"Error: %@ while encrypting the string: %@", e.debugDescription, identifier); + return identifier; + } } return encryptedString; } - (NSString *)getDecryptedString:(NSString *)identifier { - NSData *dataValue = [[NSData alloc] initWithBase64EncodedString:identifier options:kNilOptions]; - NSData *decryptedData = [self convertData:dataValue withOperation:kCCDecrypt]; - NSString *decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; + NSString *decryptedString = identifier; + @try { + NSData *dataValue = [[NSData alloc] initWithBase64EncodedString:identifier options:kNilOptions]; + NSData *decryptedData = [self convertData:dataValue withOperation:kCCDecrypt]; + if (decryptedData) { + decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; + } + } @catch (NSException *e) { + CleverTapLogStaticInternal(@"Error: %@ while decrypting the string: %@", e.debugDescription, identifier); + return identifier; + } return decryptedString; } @@ -76,8 +95,8 @@ - (NSData *)convertData:(NSData *)data withOperation:(CCOperation)operation { // TODO: will update key and identifier. NSData *outputData = [self AES128WithOperation:operation - key:@"TestKey" - identifier:@"TestIdentifier" + key:[self generateKeyPassword] + identifier:CLTAP_ENCRYPTION_IV data:data]; return outputData; } @@ -116,6 +135,7 @@ - (NSData *)AES128WithOperation:(CCOperation)operation &outputMovedSize); if (cryptStatus != kCCSuccess) { + CleverTapLogStaticInternal(@"Failed to encode/deocde the string with error code: %d", cryptStatus); free(output); return nil; } @@ -146,4 +166,9 @@ - (NSString *)getCachedIdentifier:(NSString *)value { } } +- (NSString *)generateKeyPassword { + NSString *keyPassword = [NSString stringWithFormat:@"%@%@%@",kCRYPT_KEY_PREFIX, _accountID, kCRYPT_KEY_SUFFIX]; + return keyPassword; +} + @end diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 29dfce36..acd1f1d8 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -153,5 +153,6 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_DEFINE_VARS_URL @"/defineVars" #define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" +#define CLTAP_ENCRYPTION_IV @"__CL3>3Rt#P__1V_" diff --git a/CleverTapSDK/CTLoginInfoProvider.m b/CleverTapSDK/CTLoginInfoProvider.m index 55017aeb..513ca52e 100644 --- a/CleverTapSDK/CTLoginInfoProvider.m +++ b/CleverTapSDK/CTLoginInfoProvider.m @@ -37,7 +37,11 @@ - (void)cacheGUID:(NSString *)guid forKey:(NSString *)key andIdentifier:(NSStrin NSDictionary *cache = [self getCachedGUIDs]; if (!cache) cache = @{}; NSMutableDictionary *newCache = [NSMutableDictionary dictionaryWithDictionary:cache]; - NSString *encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; + + NSString *encryptedIdentifier = identifier; + if (!self.config.aesCrypt) { + encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; + } NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier]; newCache[cacheKey] = guid; [self setCachedGUIDs:newCache]; @@ -73,7 +77,10 @@ - (NSString *)getGUIDforKey:(NSString *)key andIdentifier:(NSString *)identifier if (!key || !identifier) return nil; NSDictionary *cache = [self getCachedGUIDs]; - NSString *encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; + NSString *encryptedIdentifier = identifier; + if (!self.config.aesCrypt) { + encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; + } NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier]; if (!cache) return nil; else return cache[cacheKey]; diff --git a/CleverTapSDK/CleverTapInstanceConfig.h b/CleverTapSDK/CleverTapInstanceConfig.h index dfef71bf..e78e7a1a 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.h +++ b/CleverTapSDK/CleverTapInstanceConfig.h @@ -18,7 +18,7 @@ @property (nonatomic, assign) CleverTapLogLevel logLevel; @property (nonatomic, strong, nullable) NSArray *identityKeys; @property (nonatomic, assign) CleverTapEncryptionLevel encryptionLevel; -@property (nonatomic, strong, readonly, nullable) CTAES *aesCrypt; +@property (nonatomic, strong, nullable) CTAES *aesCrypt; - (instancetype _Nonnull) init __unavailable; diff --git a/CleverTapSDK/CleverTapInstanceConfig.m b/CleverTapSDK/CleverTapInstanceConfig.m index 2532c74e..b2695f54 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.m +++ b/CleverTapSDK/CleverTapInstanceConfig.m @@ -26,6 +26,8 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeBool: _isCreatedPostAppLaunched forKey:@"isCreatedPostAppLaunched"]; [coder encodeBool: _beta forKey:@"beta"]; [coder encodeBool: _wv_init forKey:@"wv_init"]; + [coder encodeBool: _encryptionLevel forKey:@"encryptionLevel"]; + [coder encodeBool: _aesCrypt forKey:@"aesCrypt"]; } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { @@ -48,6 +50,8 @@ - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { _isCreatedPostAppLaunched = [coder decodeBoolForKey:@"isCreatedPostAppLaunched"]; _beta = [coder decodeBoolForKey:@"beta"]; _wv_init = [coder decodeBoolForKey:@"wv_init"]; + _encryptionLevel = [coder decodeIntForKey:@"encryptionLevel"]; + _aesCrypt = [coder decodeObjectForKey:@"aesCrypt"]; } return self; } @@ -177,6 +181,8 @@ - (instancetype)copyWithZone:(NSZone*)zone { copy.disableIDFV = self.disableIDFV; copy.identityKeys = self.identityKeys; copy.beta = self.beta; + copy.encryptionLevel = self.encryptionLevel; + copy.aesCrypt = self.aesCrypt; return copy; } @@ -199,7 +205,9 @@ - (void) setupPlistData:(BOOL)isDefault { _logLevel = 0; _beta = plist.beta; _encryptionLevel = isDefault ? [plist.encryptionLevel intValue] : CleverTapEncryptionOff; - _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel]; + if (isDefault) { + _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel]; + } } - (void) checkIfAvailableAccountId:(NSString *)accountId @@ -214,10 +222,9 @@ - (void) checkIfAvailableAccountId:(NSString *)accountId } - (void)setEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { - if(_isDefaultInstance) { - _encryptionLevel = CleverTapEncryptionOff; - } else { + if (!_isDefaultInstance) { _encryptionLevel = encryptionLevel; + _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel]; } } @end From a9ce26a99ea54d175be71943736cd13100787fea Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Thu, 10 Aug 2023 18:10:27 +0530 Subject: [PATCH 04/14] Added support for encryption in local data store for PII data. --- CleverTapSDK/CTAES.m | 10 ++++------ CleverTapSDK/CTLocalDataStore.m | 26 +++++++++++++++++++++++++- CleverTapSDK/CTLoginInfoProvider.m | 4 ++-- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/CleverTapSDK/CTAES.m b/CleverTapSDK/CTAES.m index 600c3521..427c1822 100644 --- a/CleverTapSDK/CTAES.m +++ b/CleverTapSDK/CTAES.m @@ -29,11 +29,11 @@ - (void)updateEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { long lastEncryptionLevel = [CTPreferences getIntForKey:[self getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID] withResetValue:0]; if (lastEncryptionLevel != _encryptionLevel) { [CTPreferences putInt:_encryptionLevel forKey:[self getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID]]; - [self updateValues]; + [self updatePreferencesValues]; } } -- (void)updateValues { +- (void)updatePreferencesValues { NSDictionary *cachedGUIDS = [CTPreferences getObjectForKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; if (cachedGUIDS) { NSMutableDictionary *newCache = [NSMutableDictionary new]; @@ -45,7 +45,6 @@ - (void)updateValues { NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, decryptedString]; newCache[cacheKey] = value; }]; - [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; } else if (_encryptionLevel == CleverTapEncryptionOn) { [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { NSString *key = [self getCachedKey:cachedKey]; @@ -54,8 +53,8 @@ - (void)updateValues { NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedString]; newCache[cacheKey] = value; }]; - [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; } + [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; } } @@ -81,7 +80,7 @@ - (NSString *)getDecryptedString:(NSString *)identifier { @try { NSData *dataValue = [[NSData alloc] initWithBase64EncodedString:identifier options:kNilOptions]; NSData *decryptedData = [self convertData:dataValue withOperation:kCCDecrypt]; - if (decryptedData) { + if (decryptedData && decryptedData.length > 0) { decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; } } @catch (NSException *e) { @@ -93,7 +92,6 @@ - (NSString *)getDecryptedString:(NSString *)identifier { - (NSData *)convertData:(NSData *)data withOperation:(CCOperation)operation { - // TODO: will update key and identifier. NSData *outputData = [self AES128WithOperation:operation key:[self generateKeyPassword] identifier:CLTAP_ENCRYPTION_IV diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 69b73fce..65794932 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -7,6 +7,7 @@ #import "CleverTapInstanceConfig.h" #import "CleverTapInstanceConfigPrivate.h" #import "CTLoginInfoProvider.h" +#import "CTAES.h" static const void *const kProfileBackgroundQueueKey = &kProfileBackgroundQueueKey; static const double kProfilePersistenceIntervalSeconds = 30.f; @@ -656,7 +657,8 @@ - (void)_persistLocalProfileAsync { self->lastProfilePersistenceTime = @([[[NSDate alloc] init] timeIntervalSince1970]); } - [CTPreferences archiveObject:_profile forFileName:[self profileFileName]]; + NSMutableDictionary *updatedProfile = [self cryptValuesIfNeeded:_profile]; + [CTPreferences archiveObject:updatedProfile forFileName:[self profileFileName]]; }]; } @@ -807,4 +809,26 @@ - (NSDictionary*)generateBaseProfile { return profile; } +- (NSMutableDictionary *)cryptValuesIfNeeded:(NSMutableDictionary *)profile { + NSArray *piiData = @[CLTAP_USER_NAME, CLTAP_USER_EMAIL, CLTAP_USER_PHONE, CLTAP_PROFILE_IDENTITY_KEY]; + NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; + for (NSString *key in profile) { + if ([piiData containsObject:key] && self.config.aesCrypt) { + NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; + NSString *encryptedString; + if (self.config.encryptionLevel == CleverTapEncryptionOn) { + encryptedString = [self.config.aesCrypt getEncryptedString:value]; + } else if (self.config.encryptionLevel == CleverTapEncryptionOff) { + encryptedString = [self.config.aesCrypt getDecryptedString:value]; + } else { + encryptedString = value; + } + updatedProfile[key] = encryptedString; + } else { + updatedProfile[key] = profile[key]; + } + } + return updatedProfile; +} + @end diff --git a/CleverTapSDK/CTLoginInfoProvider.m b/CleverTapSDK/CTLoginInfoProvider.m index 513ca52e..04aec108 100644 --- a/CleverTapSDK/CTLoginInfoProvider.m +++ b/CleverTapSDK/CTLoginInfoProvider.m @@ -39,7 +39,7 @@ - (void)cacheGUID:(NSString *)guid forKey:(NSString *)key andIdentifier:(NSStrin NSMutableDictionary *newCache = [NSMutableDictionary dictionaryWithDictionary:cache]; NSString *encryptedIdentifier = identifier; - if (!self.config.aesCrypt) { + if (self.config.aesCrypt) { encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; } NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier]; @@ -78,7 +78,7 @@ - (NSString *)getGUIDforKey:(NSString *)key andIdentifier:(NSString *)identifier NSDictionary *cache = [self getCachedGUIDs]; NSString *encryptedIdentifier = identifier; - if (!self.config.aesCrypt) { + if (self.config.aesCrypt) { encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; } NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier]; From b87d30071340af8ecd97423e5db5cfdd2a5c097e Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Thu, 10 Aug 2023 19:14:02 +0530 Subject: [PATCH 05/14] Updated logic for plist flag to accept only 0 and 1 --- CleverTapSDK/CTAES.m | 6 +++--- CleverTapSDK/CTConstants.h | 2 +- CleverTapSDK/CTLocalDataStore.m | 4 ++-- CleverTapSDK/CTPlistInfo.h | 3 ++- CleverTapSDK/CTPlistInfo.m | 15 ++++++++++++++- CleverTapSDK/CleverTap.h | 4 ++-- CleverTapSDK/CleverTapInstanceConfig.h | 4 ++-- CleverTapSDK/CleverTapInstanceConfig.m | 2 +- ObjCStarter/ObjCStarter/Info.plist | 2 +- 9 files changed, 28 insertions(+), 14 deletions(-) diff --git a/CleverTapSDK/CTAES.m b/CleverTapSDK/CTAES.m index 427c1822..36979041 100644 --- a/CleverTapSDK/CTAES.m +++ b/CleverTapSDK/CTAES.m @@ -37,7 +37,7 @@ - (void)updatePreferencesValues { NSDictionary *cachedGUIDS = [CTPreferences getObjectForKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; if (cachedGUIDS) { NSMutableDictionary *newCache = [NSMutableDictionary new]; - if (_encryptionLevel == CleverTapEncryptionOff) { + if (_encryptionLevel == CleverTapEncryptionNone) { [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { NSString *key = [self getCachedKey:cachedKey]; NSString *identifier = [self getCachedIdentifier:cachedKey]; @@ -45,7 +45,7 @@ - (void)updatePreferencesValues { NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, decryptedString]; newCache[cacheKey] = value; }]; - } else if (_encryptionLevel == CleverTapEncryptionOn) { + } else if (_encryptionLevel == CleverTapEncryptionMedium) { [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { NSString *key = [self getCachedKey:cachedKey]; NSString *identifier = [self getCachedIdentifier:cachedKey]; @@ -60,7 +60,7 @@ - (void)updatePreferencesValues { - (NSString *)getEncryptedString:(NSString *)identifier { NSString *encryptedString = identifier; - if (_encryptionLevel == CleverTapEncryptionOn) { + if (_encryptionLevel == CleverTapEncryptionMedium) { @try { NSData *dataValue = [identifier dataUsingEncoding:NSUTF8StringEncoding]; NSData *encryptedData = [self convertData:dataValue withOperation:kCCEncrypt]; diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index acd1f1d8..17e98fbb 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -152,7 +152,7 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_DEFINE_VARS_URL @"/defineVars" -#define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" +#define CLTAP_ENCRYPTION_LEVEL @"CLEVERTAP_ENCRYPTION_LEVEL" #define CLTAP_ENCRYPTION_IV @"__CL3>3Rt#P__1V_" diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 65794932..7afa8298 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -816,9 +816,9 @@ - (NSMutableDictionary *)cryptValuesIfNeeded:(NSMutableDictionary *)profile { if ([piiData containsObject:key] && self.config.aesCrypt) { NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; NSString *encryptedString; - if (self.config.encryptionLevel == CleverTapEncryptionOn) { + if (self.config.encryptionLevel == CleverTapEncryptionMedium) { encryptedString = [self.config.aesCrypt getEncryptedString:value]; - } else if (self.config.encryptionLevel == CleverTapEncryptionOff) { + } else if (self.config.encryptionLevel == CleverTapEncryptionNone) { encryptedString = [self.config.aesCrypt getDecryptedString:value]; } else { encryptedString = value; diff --git a/CleverTapSDK/CTPlistInfo.h b/CleverTapSDK/CTPlistInfo.h index 5a2f0c20..dad3fb00 100644 --- a/CleverTapSDK/CTPlistInfo.h +++ b/CleverTapSDK/CTPlistInfo.h @@ -1,4 +1,5 @@ #import +#import "CleverTap.h" @interface CTPlistInfo : NSObject @@ -12,7 +13,7 @@ @property (nonatomic, assign, readonly) BOOL useCustomCleverTapId; @property (nonatomic, assign, readonly) BOOL beta; @property (nonatomic, assign, readonly) BOOL disableIDFV; -@property (nonatomic, strong, readonly, nullable) NSString *encryptionLevel; +@property (nonatomic, readonly) CleverTapEncryptionLevel encryptionLevel; + (instancetype _Nullable)sharedInstance; - (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token region:(NSString * _Nullable)region; diff --git a/CleverTapSDK/CTPlistInfo.m b/CleverTapSDK/CTPlistInfo.m index d232ca44..17bf2f96 100644 --- a/CleverTapSDK/CTPlistInfo.m +++ b/CleverTapSDK/CTPlistInfo.m @@ -91,7 +91,8 @@ - (instancetype)init { NSString *disableIDFV = [CTPlistInfo getMetaDataForAttribute:CLTAP_DISABLE_IDFV_LABEL]; _disableIDFV = (disableIDFV && [disableIDFV isEqualToString:@"1"]); - _encryptionLevel = [CTPlistInfo getMetaDataForAttribute:CLTAP_ENCRYPTION_LEVEL]; + NSString *encryptionLevel = [CTPlistInfo getMetaDataForAttribute:CLTAP_ENCRYPTION_LEVEL]; + [self setEncryption:encryptionLevel]; } return self; } @@ -114,4 +115,16 @@ - (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSStri _proxyDomain = proxyDomain; _spikyProxyDomain = spikyProxyDomain; } + +- (void)setEncryption:(NSString *)encryptionLevel { + if (encryptionLevel && [encryptionLevel isEqualToString:@"0"]) { + _encryptionLevel = CleverTapEncryptionNone; + } else if (encryptionLevel && [encryptionLevel isEqualToString:@"1"]) { + _encryptionLevel = CleverTapEncryptionMedium; + } else { + _encryptionLevel = CleverTapEncryptionNone; + CleverTapLogStaticInternal(@"Supported encryption levels are only 0 and 1. Setting it to 0 by default"); + } +} + @end diff --git a/CleverTapSDK/CleverTap.h b/CleverTapSDK/CleverTap.h index 85dad188..818aeffb 100644 --- a/CleverTapSDK/CleverTap.h +++ b/CleverTapSDK/CleverTap.h @@ -55,8 +55,8 @@ typedef NS_ENUM(int, CTSignedCallEvent) { }; typedef NS_ENUM(int, CleverTapEncryptionLevel) { - CleverTapEncryptionOff = 0, - CleverTapEncryptionOn = 1 + CleverTapEncryptionNone = 0, + CleverTapEncryptionMedium = 1 }; @interface CleverTap : NSObject diff --git a/CleverTapSDK/CleverTapInstanceConfig.h b/CleverTapSDK/CleverTapInstanceConfig.h index e78e7a1a..a849595a 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.h +++ b/CleverTapSDK/CleverTapInstanceConfig.h @@ -48,8 +48,8 @@ @discussion Set using CleverTapEncryptionLevel enum values (or the corresponding int values). - CleverTapEncryptionOff - turns off all encryption. - CleverTapEncryptionOn - turns enxryption on for PII data + CleverTapEncryptionNone - turns off all encryption. + CleverTapEncryptionMedium - turns enxryption on for PII data @param encryptionLevel the encryption level to set */ diff --git a/CleverTapSDK/CleverTapInstanceConfig.m b/CleverTapSDK/CleverTapInstanceConfig.m index b2695f54..e7929b66 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.m +++ b/CleverTapSDK/CleverTapInstanceConfig.m @@ -204,7 +204,7 @@ - (void) setupPlistData:(BOOL)isDefault { _enablePersonalization = YES; _logLevel = 0; _beta = plist.beta; - _encryptionLevel = isDefault ? [plist.encryptionLevel intValue] : CleverTapEncryptionOff; + _encryptionLevel = isDefault ? plist.encryptionLevel : CleverTapEncryptionNone; if (isDefault) { _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel]; } diff --git a/ObjCStarter/ObjCStarter/Info.plist b/ObjCStarter/ObjCStarter/Info.plist index 3414cdae..7b9ab536 100644 --- a/ObjCStarter/ObjCStarter/Info.plist +++ b/ObjCStarter/ObjCStarter/Info.plist @@ -45,7 +45,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CleverTapEncryptionLevel + CLEVERTAP_ENCRYPTION_LEVEL 1 From c4933c26387c49e39ad2e91286bbeeb41abaeeb3 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Fri, 11 Aug 2023 10:58:47 +0530 Subject: [PATCH 06/14] Update CleverTapInstanceConfig.h --- CleverTapSDK/CleverTapInstanceConfig.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CleverTapSDK/CleverTapInstanceConfig.h b/CleverTapSDK/CleverTapInstanceConfig.h index a849595a..fbb6c420 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.h +++ b/CleverTapSDK/CleverTapInstanceConfig.h @@ -46,10 +46,10 @@ Set the encryption level @discussion - Set using CleverTapEncryptionLevel enum values (or the corresponding int values). + Set the encryption level using CleverTapEncryptionLevel enum values (or the corresponding int values). CleverTapEncryptionNone - turns off all encryption. - CleverTapEncryptionMedium - turns enxryption on for PII data + CleverTapEncryptionMedium - turns encryption on for PII data @param encryptionLevel the encryption level to set */ From c2d6ffab9b89341ebf38a431a8a3d8365aef6369 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Fri, 11 Aug 2023 12:23:50 +0530 Subject: [PATCH 07/14] Added logic to store decrypted values always for localProfileForSession --- CleverTapSDK/CTConstants.h | 1 + CleverTapSDK/CTLocalDataStore.m | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 17e98fbb..79f844dc 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -154,5 +154,6 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_ENCRYPTION_LEVEL @"CLEVERTAP_ENCRYPTION_LEVEL" #define CLTAP_ENCRYPTION_IV @"__CL3>3Rt#P__1V_" +#define CLTAP_ENCRYPTION_PII_DATA (@[@"Identity", @"userEmail", @"userPhone", @"userName"]); diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 7afa8298..a48c22a5 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -24,6 +24,7 @@ @interface CTLocalDataStore() { @property (nonatomic, strong) CleverTapInstanceConfig *config; @property (nonatomic, strong) CTDeviceInfo *deviceInfo; +@property (nonatomic, strong) NSArray *piiKeys; @end @@ -37,6 +38,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:( _backgroundQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.profileBackgroundQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(_backgroundQueue, kProfileBackgroundQueueKey, (__bridge void *)self, NULL); lastProfilePersistenceTime = 0; + _piiKeys = CLTAP_ENCRYPTION_PII_DATA; [self runOnBackgroundQueue:^{ @synchronized (self->localProfileForSession) { self->localProfileForSession = [self _inflateLocalProfile]; @@ -625,7 +627,8 @@ - (NSMutableDictionary *)_inflateLocalProfile { if (!_profile) { _profile = [NSMutableDictionary dictionary]; } - return _profile; + NSMutableDictionary *updatedProfile = [self decryptPIIDataIfEncrypted:_profile]; + return updatedProfile; } - (void)persistLocalProfileIfRequired { @@ -809,11 +812,29 @@ - (NSDictionary*)generateBaseProfile { return profile; } +- (NSMutableDictionary *)decryptPIIDataIfEncrypted:(NSMutableDictionary *)profile { + NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; + if (self.config.aesCrypt && self.config.encryptionLevel == CleverTapEncryptionMedium) { + // If Encyption is Medium, store the local profile data in decrypted values. + for (NSString *key in profile) { + if ([_piiKeys containsObject:key]) { + NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; + NSString *decryptedString = [self.config.aesCrypt getDecryptedString:value]; + updatedProfile[key] = decryptedString; + } else { + updatedProfile[key] = profile[key]; + } + } + } else { + updatedProfile = profile; + } + return updatedProfile; +} + - (NSMutableDictionary *)cryptValuesIfNeeded:(NSMutableDictionary *)profile { - NSArray *piiData = @[CLTAP_USER_NAME, CLTAP_USER_EMAIL, CLTAP_USER_PHONE, CLTAP_PROFILE_IDENTITY_KEY]; NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; for (NSString *key in profile) { - if ([piiData containsObject:key] && self.config.aesCrypt) { + if ([_piiKeys containsObject:key] && self.config.aesCrypt) { NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; NSString *encryptedString; if (self.config.encryptionLevel == CleverTapEncryptionMedium) { From 19ee91a1cc976bb5fda16e95eca9ef48687cb2ce Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Fri, 11 Aug 2023 16:26:06 +0530 Subject: [PATCH 08/14] feat(SDK-3166): Added custom key-value in app inbox message - It can be accessed directly using property customData. --- .../Inbox/models/CleverTapInboxMessage.m | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/CleverTapSDK/Inbox/models/CleverTapInboxMessage.m b/CleverTapSDK/Inbox/models/CleverTapInboxMessage.m index 5bbe8387..a5fcb4f0 100755 --- a/CleverTapSDK/Inbox/models/CleverTapInboxMessage.m +++ b/CleverTapSDK/Inbox/models/CleverTapInboxMessage.m @@ -21,9 +21,9 @@ - (instancetype)initWithJSON:(NSDictionary *)json { if (campaignId) { _campaignId = campaignId; } - NSDictionary *customData = json[@"kv"]; + NSArray *customData = json[@"msg"][@"custom_kv"]; if (customData) { - _customData = customData; + _customData = [self getMessageCustomKV:customData]; } NSArray *tags = json[@"msg"][@"tags"]; if (tags) { @@ -110,4 +110,22 @@ - (NSString *)relativeDateStringForDate:(NSDate *)date } } +- (NSDictionary *)getMessageCustomKV:(NSArray *)data { + NSMutableDictionary *customKV = [NSMutableDictionary new]; + for (NSUInteger i = 0; i < [data count]; ++i) { + NSDictionary *kv = data[i]; + if ([kv objectForKey:@"key"]) { + NSString *key = kv[@"key"]; + if ([kv objectForKey:@"value"]) { + NSDictionary *value = kv[@"value"]; + if ([value objectForKey:@"text"]) { + NSString *text = value[@"text"]; + customKV[key] = text; + } + } + } + } + return customKV; +} + @end From 8b43bccc7c7d0715c5d8f1946a9c0e7ff92c027f Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 16 Aug 2023 14:08:16 +0530 Subject: [PATCH 09/14] minor fixes for always storing decrypted local db values, updated encryption plist key --- CleverTapSDK/CTAES.m | 5 ++++- CleverTapSDK/CTConstants.h | 2 +- CleverTapSDK/CTLocalDataStore.m | 14 +++++++------- ObjCStarter/ObjCStarter/Info.plist | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CleverTapSDK/CTAES.m b/CleverTapSDK/CTAES.m index 36979041..64bca3dc 100644 --- a/CleverTapSDK/CTAES.m +++ b/CleverTapSDK/CTAES.m @@ -81,7 +81,10 @@ - (NSString *)getDecryptedString:(NSString *)identifier { NSData *dataValue = [[NSData alloc] initWithBase64EncodedString:identifier options:kNilOptions]; NSData *decryptedData = [self convertData:dataValue withOperation:kCCDecrypt]; if (decryptedData && decryptedData.length > 0) { - decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; + NSString *utf8EncodedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; + if (utf8EncodedString) { + decryptedString = utf8EncodedString; + } } } @catch (NSException *e) { CleverTapLogStaticInternal(@"Error: %@ while decrypting the string: %@", e.debugDescription, identifier); diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 79f844dc..1383b7fa 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -152,7 +152,7 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_DEFINE_VARS_URL @"/defineVars" -#define CLTAP_ENCRYPTION_LEVEL @"CLEVERTAP_ENCRYPTION_LEVEL" +#define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" #define CLTAP_ENCRYPTION_IV @"__CL3>3Rt#P__1V_" #define CLTAP_ENCRYPTION_PII_DATA (@[@"Identity", @"userEmail", @"userPhone", @"userName"]); diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index a48c22a5..01a0ea1f 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -814,8 +814,8 @@ - (NSDictionary*)generateBaseProfile { - (NSMutableDictionary *)decryptPIIDataIfEncrypted:(NSMutableDictionary *)profile { NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; - if (self.config.aesCrypt && self.config.encryptionLevel == CleverTapEncryptionMedium) { - // If Encyption is Medium, store the local profile data in decrypted values. + if (self.config.aesCrypt) { + // Always store the local profile data in decrypted values. for (NSString *key in profile) { if ([_piiKeys containsObject:key]) { NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; @@ -836,15 +836,15 @@ - (NSMutableDictionary *)cryptValuesIfNeeded:(NSMutableDictionary *)profile { for (NSString *key in profile) { if ([_piiKeys containsObject:key] && self.config.aesCrypt) { NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; - NSString *encryptedString; + NSString *cryptedString; if (self.config.encryptionLevel == CleverTapEncryptionMedium) { - encryptedString = [self.config.aesCrypt getEncryptedString:value]; + cryptedString = [self.config.aesCrypt getEncryptedString:value]; } else if (self.config.encryptionLevel == CleverTapEncryptionNone) { - encryptedString = [self.config.aesCrypt getDecryptedString:value]; + cryptedString = [self.config.aesCrypt getDecryptedString:value]; } else { - encryptedString = value; + cryptedString = value; } - updatedProfile[key] = encryptedString; + updatedProfile[key] = cryptedString; } else { updatedProfile[key] = profile[key]; } diff --git a/ObjCStarter/ObjCStarter/Info.plist b/ObjCStarter/ObjCStarter/Info.plist index 7b9ab536..3414cdae 100644 --- a/ObjCStarter/ObjCStarter/Info.plist +++ b/ObjCStarter/ObjCStarter/Info.plist @@ -45,7 +45,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CLEVERTAP_ENCRYPTION_LEVEL + CleverTapEncryptionLevel 1 From a57052c905044b83eaaaa3d469e864250354026e Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 16 Aug 2023 17:19:45 +0530 Subject: [PATCH 10/14] Updated logic for decrypt local db values on app launch, minor code refactor --- CleverTapSDK/CTAES.m | 13 ++++------- CleverTapSDK/CTLocalDataStore.m | 39 +++++++++++++++++---------------- CleverTapSDK/CTUtils.h | 1 + CleverTapSDK/CTUtils.m | 5 +++++ 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/CleverTapSDK/CTAES.m b/CleverTapSDK/CTAES.m index 64bca3dc..cf135741 100644 --- a/CleverTapSDK/CTAES.m +++ b/CleverTapSDK/CTAES.m @@ -2,6 +2,7 @@ #import "CTAES.h" #import "CTConstants.h" #import "CTPreferences.h" +#import "CTUtils.h" NSString *const kENCRYPTION_KEY = @"CLTAP_ENCRYPTION_KEY"; NSString *const kCRYPT_KEY_PREFIX = @"Lq3fz"; @@ -26,15 +27,14 @@ - (instancetype)initWithAccountID:(NSString *)accountID - (void)updateEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { _encryptionLevel = encryptionLevel; - long lastEncryptionLevel = [CTPreferences getIntForKey:[self getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID] withResetValue:0]; + long lastEncryptionLevel = [CTPreferences getIntForKey:[CTUtils getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID] withResetValue:0]; if (lastEncryptionLevel != _encryptionLevel) { - [CTPreferences putInt:_encryptionLevel forKey:[self getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID]]; [self updatePreferencesValues]; } } - (void)updatePreferencesValues { - NSDictionary *cachedGUIDS = [CTPreferences getObjectForKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + NSDictionary *cachedGUIDS = [CTPreferences getObjectForKey:[CTUtils getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; if (cachedGUIDS) { NSMutableDictionary *newCache = [NSMutableDictionary new]; if (_encryptionLevel == CleverTapEncryptionNone) { @@ -54,7 +54,7 @@ - (void)updatePreferencesValues { newCache[cacheKey] = value; }]; } - [CTPreferences putObject:newCache forKey:[self getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + [CTPreferences putObject:newCache forKey:[CTUtils getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; } } @@ -144,11 +144,6 @@ - (NSData *)AES128WithOperation:(CCOperation)operation return [NSData dataWithBytesNoCopy:output length:outputMovedSize]; } -- (NSString *)getKeyWithSuffix:(NSString *)suffix - accountID:(NSString *)accountID { - return [NSString stringWithFormat:@"%@:%@", accountID, suffix]; -} - - (NSString *)getCachedKey:(NSString *)value { if ([value rangeOfString:@"_"].length > 0) { NSUInteger index = [value rangeOfString:@"_"].location; diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 01a0ea1f..4dcac0b4 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -8,12 +8,15 @@ #import "CleverTapInstanceConfigPrivate.h" #import "CTLoginInfoProvider.h" #import "CTAES.h" +#import "CTPreferences.h" +#import "CTUtils.h" static const void *const kProfileBackgroundQueueKey = &kProfileBackgroundQueueKey; static const double kProfilePersistenceIntervalSeconds = 30.f; NSString* const kWR_KEY_EVENTS = @"local_events_cache"; NSString* const kLocalCacheLastSync = @"local_cache_last_sync"; NSString* const kLocalCacheExpiry = @"local_cache_expiry"; +NSString *const CT_ENCRYPTION_KEY = @"CLTAP_ENCRYPTION_KEY"; @interface CTLocalDataStore() { NSMutableDictionary *localProfileUpdateExpiryStore; @@ -813,9 +816,11 @@ - (NSDictionary*)generateBaseProfile { } - (NSMutableDictionary *)decryptPIIDataIfEncrypted:(NSMutableDictionary *)profile { - NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; - if (self.config.aesCrypt) { + long lastEncryptionLevel = [CTPreferences getIntForKey:[CTUtils getKeyWithSuffix:CT_ENCRYPTION_KEY accountID:self.config.accountId] withResetValue:0]; + [CTPreferences putInt:self.config.encryptionLevel forKey:[CTUtils getKeyWithSuffix:CT_ENCRYPTION_KEY accountID:self.config.accountId]]; + if (lastEncryptionLevel == CleverTapEncryptionMedium && self.config.aesCrypt) { // Always store the local profile data in decrypted values. + NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; for (NSString *key in profile) { if ([_piiKeys containsObject:key]) { NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; @@ -825,31 +830,27 @@ - (NSMutableDictionary *)decryptPIIDataIfEncrypted:(NSMutableDictionary *)profil updatedProfile[key] = profile[key]; } } - } else { - updatedProfile = profile; + return updatedProfile; } - return updatedProfile; + + return profile; } - (NSMutableDictionary *)cryptValuesIfNeeded:(NSMutableDictionary *)profile { - NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; - for (NSString *key in profile) { - if ([_piiKeys containsObject:key] && self.config.aesCrypt) { - NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; - NSString *cryptedString; - if (self.config.encryptionLevel == CleverTapEncryptionMedium) { - cryptedString = [self.config.aesCrypt getEncryptedString:value]; - } else if (self.config.encryptionLevel == CleverTapEncryptionNone) { - cryptedString = [self.config.aesCrypt getDecryptedString:value]; + if (self.config.encryptionLevel == CleverTapEncryptionMedium && self.config.aesCrypt) { + NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; + for (NSString *key in profile) { + if ([_piiKeys containsObject:key]) { + NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; + updatedProfile[key] = [self.config.aesCrypt getEncryptedString:value]; } else { - cryptedString = value; + updatedProfile[key] = profile[key]; } - updatedProfile[key] = cryptedString; - } else { - updatedProfile[key] = profile[key]; } + return updatedProfile; } - return updatedProfile; + + return profile; } @end diff --git a/CleverTapSDK/CTUtils.h b/CleverTapSDK/CTUtils.h index 126ec336..d7923d2b 100644 --- a/CleverTapSDK/CTUtils.h +++ b/CleverTapSDK/CTUtils.h @@ -7,4 +7,5 @@ + (double)toTwoPlaces:(double)x; + (BOOL)isNullOrEmpty:(id)obj; + (NSString *)jsonObjectToString:(id)object; ++ (NSString *)getKeyWithSuffix:(NSString *)suffix accountID:(NSString *)accountID; @end diff --git a/CleverTapSDK/CTUtils.m b/CleverTapSDK/CTUtils.m index 590de0da..5c0505a4 100644 --- a/CleverTapSDK/CTUtils.m +++ b/CleverTapSDK/CTUtils.m @@ -85,4 +85,9 @@ + (NSString *)jsonObjectToString:(id)object { } } ++ (NSString *)getKeyWithSuffix:(NSString *)suffix + accountID:(NSString *)accountID { + return [NSString stringWithFormat:@"%@:%@", accountID, suffix]; +} + @end From 949f59cf1c780894d7d397a90a08feca4fba627a Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 16 Aug 2023 18:18:31 +0530 Subject: [PATCH 11/14] Added changelog and doc for Encryption --- CHANGELOG.md | 9 +++++++++ docs/Encryption.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 docs/Encryption.md diff --git a/CHANGELOG.md b/CHANGELOG.md index a8aa6928..ccbcaa46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +### [Version 5.2.0](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/5.2.0) (August 16, 2023) + +#### Added +- Adds support for encryption of PII data wiz. Email, Identity, Name and Phone. + Please refer to [Encryption.md](/docs/Encryption.md) file to read more on how to + enable/disable encryption. +- Adds support for custom KV pairs common to all inbox messages in AppInbox. +- Adds sample SwiftUIStarter app to support CleverTap iOS SDK for SwiftUI, added steps to track screen views in SwiftUI. + ### [Version 5.1.1](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/5.1.1) (July 13, 2023) #### Fixed diff --git a/docs/Encryption.md b/docs/Encryption.md new file mode 100644 index 00000000..32fa348d --- /dev/null +++ b/docs/Encryption.md @@ -0,0 +1,29 @@ +## Encryption of PII data + +PII data is stored across the SDK and could be sensitive information. +From CleverTap iOS SDK v5.2.0 onwards, you can enable encryption for PII data wiz. **Email, Identity, Name and Phone**. + +Currently 2 levels of encryption are supported i.e None(0) and Medium(1). Encryption level is None by default. +**None** - All stored data is in plaintext +**Medium** - PII data is encrypted completely. + +### Default Instance: +The only way to set encryption level for default instance is from the `info.plist`. Add the `CleverTapEncryptionLevel` String key to info.plist file where value `1` means Medium and `0` means None. Encryption Level will be None if any other value is provided. + +### Additional Instance: +Different instances can have different encryption levels. To set an encryption level for an additional instance. +```objc +// Objective-C + +CleverTapInstanceConfig *ctConfig = [[CleverTapInstanceConfig alloc] initWithAccountId:@"ADDITIONAL_CLEVERTAP_ACCOUNT_ID" accountToken:@"ADDITIONAL_CLEVERTAP_ACCOUNT_TOKEN"]; +ctConfig.encryptionLevel = CleverTapEncryptionMedium; +CleverTap *additionalCleverTapInstance = [CleverTap instanceWithConfig:ctConfig]; +``` + +```swift +// Swift + +let ctConfig = CleverTapInstanceConfig.init(accountId: "ADDITIONAL_CLEVERTAP_ACCOUNT_ID", accountToken: "ADDITIONAL_CLEVERTAP_ACCOUNT_TOKEN") +ctConfig.encryptionLevel = CleverTapEncryptionLevel.medium +let cleverTapAdditionalInstance = CleverTap.instance(with: ctConfig) +``` \ No newline at end of file From 44e8e373789994acb4c5fe7551fc998622391df2 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 16 Aug 2023 18:23:34 +0530 Subject: [PATCH 12/14] Updated version and Readme file --- CleverTapSDK/CleverTapBuildInfo.h | 2 +- README.md | 1 + sdk-version.txt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CleverTapSDK/CleverTapBuildInfo.h b/CleverTapSDK/CleverTapBuildInfo.h index 4b78e0a6..3e8fd9b5 100644 --- a/CleverTapSDK/CleverTapBuildInfo.h +++ b/CleverTapSDK/CleverTapBuildInfo.h @@ -1 +1 @@ -#define WR_SDK_REVISION @"50101" +#define WR_SDK_REVISION @"50200" diff --git a/README.md b/README.md index 1c560a99..48fa2bd3 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ CleverTap iOS SDK supports creating remote config variables, refer [Remote Confi * A [demo application](/ObjCStarter) showing the integration of our SDK in Objective-C language. * A [demo application](/SwiftStarter) showing the integration of our SDK in Swift language. * A [demo application](/SPMStarter) showing the installation of our SDK via Swift Package Manager. +* A [demo application](/SwiftUIStarter) showing the installation of our SDK in Swift UI Application. ## 🆕 Change Log diff --git a/sdk-version.txt b/sdk-version.txt index 3bff0591..7cbea073 100644 --- a/sdk-version.txt +++ b/sdk-version.txt @@ -1 +1 @@ -5.1.1 \ No newline at end of file +5.2.0 \ No newline at end of file From 017159efce3901f9e6576b753aac21032da2c4d9 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 16 Aug 2023 19:33:49 +0530 Subject: [PATCH 13/14] Added docs for SwiftUI sample app. --- docs/SwiftUI.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/SwiftUI.md diff --git a/docs/SwiftUI.md b/docs/SwiftUI.md new file mode 100644 index 00000000..08de48ba --- /dev/null +++ b/docs/SwiftUI.md @@ -0,0 +1,89 @@ +## Integration in SwiftUI App +CleverTap iOS SDK can be integrated in SwiftUI sample app. + +#### AppDelegate in SwiftUI +SwiftUI provides a way to use AppDelegate within SwiftUI life cycle by using `@UIApplicationDelegateAdaptor`. Create a file e.g. `AppDelegate.swift` then create a class of AppDelegate and attach it with struct main entry point by `@UIApplicationDelegateAdaptor` property wrapper, Refer sample app for more details. + +```swift +import UserNotifications +import CleverTapSDK + +class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + registerForPush() + CleverTap.setDebugLevel(2) + CleverTap.autoIntegrate() + CleverTap.sharedInstance()?.enableDeviceNetworkInfoReporting(true) + return true + } + + func registerForPush() { + // Register for Push notifications + UNUserNotificationCenter.current().delegate = self + // request Permissions + UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .badge, .alert], completionHandler: {granted, error in + if granted { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + }) + } +} +``` + +#### App Inbox in SwiftUI +App Inbox controller can be added using `UIViewControllerRepresentable` and its callback methods can be used using `makeCoordinator` method. Refer example app for more details. + +```swift +struct CTAppInboxRepresentable: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> CleverTapInboxViewController { + let style = CleverTapInboxStyleConfig() + let inboxVC: CleverTapInboxViewController = (CleverTap.sharedInstance()?.newInboxViewController(with: style, andDelegate: context.coordinator))! + return inboxVC + } + + func updateUIViewController(_ uiViewController: CleverTapInboxViewController, context: Context) { + // Updates the state of the specified view controller with new information from SwiftUI. + } + + func makeCoordinator() -> CTCallbackCoordinator { + // Callback class + } +} +``` + +#### Track Screen Views in SwiftUI +There is no direct replacement for `viewDidLoad()` method in SwiftUI, but we can acheive same behaviour using `onAppear` modifier. Refer example for more details: + +```swift +#if canImport(SwiftUI) +import SwiftUI +import CleverTapSDK + +@available(iOS 13, *) +internal struct CTViewModifier: ViewModifier { + @State private var viewDidLoad = false + let screenName: String + + func body(content: Content) -> some View { + content.onAppear { + if viewDidLoad == false { + // `viewDidLoad` eqivalent in SwiftUI + viewDidLoad = true + // Record any CleverTap events here. + CleverTap.sharedInstance()?.recordEvent(screenName) + } + } + } +} + +@available(iOS 13, *) +public extension View { + func recordScreenView(screenName: String) -> some View { + self.modifier(CTViewModifier(screenName: screenName)) + } +} +#endif + +``` \ No newline at end of file From 1000f748732007b48a3269fc9e8212e4eae89a34 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 16 Aug 2023 21:15:03 +0530 Subject: [PATCH 14/14] minor fixes and doc change. --- CHANGELOG.md | 2 +- CleverTapSDK/CTAES.h | 2 +- CleverTapSDK/CTAES.m | 10 +++++++++- CleverTapSDK/CleverTapInstanceConfig.m | 6 ++++-- docs/Encryption.md | 2 +- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccbcaa46..1f4c9354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. Please refer to [Encryption.md](/docs/Encryption.md) file to read more on how to enable/disable encryption. - Adds support for custom KV pairs common to all inbox messages in AppInbox. -- Adds sample SwiftUIStarter app to support CleverTap iOS SDK for SwiftUI, added steps to track screen views in SwiftUI. +- Adds sample SwiftUIStarter app to support CleverTap iOS SDK for SwiftUI, added steps to track screen views in SwiftUI. Refer to [SwiftUI doc](/docs/SwiftUI.md) for more details. ### [Version 5.1.1](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/5.1.1) (July 13, 2023) diff --git a/CleverTapSDK/CTAES.h b/CleverTapSDK/CTAES.h index baac1de0..32c5cbef 100644 --- a/CleverTapSDK/CTAES.h +++ b/CleverTapSDK/CTAES.h @@ -13,6 +13,6 @@ */ - (NSString *)getDecryptedString:(NSString *)identifier; -- (instancetype)initWithAccountID:(NSString *)accountID encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel; +- (instancetype)initWithAccountID:(NSString *)accountID encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel isDefaultInstance:(BOOL)isDefaultInstance; @end diff --git a/CleverTapSDK/CTAES.m b/CleverTapSDK/CTAES.m index cf135741..5fec8326 100644 --- a/CleverTapSDK/CTAES.m +++ b/CleverTapSDK/CTAES.m @@ -12,14 +12,17 @@ @interface CTAES () {} @property (nonatomic, strong) NSString *accountID; @property (nonatomic, assign) CleverTapEncryptionLevel encryptionLevel; +@property (nonatomic, assign) BOOL isDefaultInstance; @end @implementation CTAES - (instancetype)initWithAccountID:(NSString *)accountID - encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { + encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel + isDefaultInstance:(BOOL)isDefaultInstance { if (self = [super init]) { _accountID = accountID; + _isDefaultInstance = isDefaultInstance; [self updateEncryptionLevel:encryptionLevel]; } return self; @@ -29,7 +32,12 @@ - (void)updateEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { _encryptionLevel = encryptionLevel; long lastEncryptionLevel = [CTPreferences getIntForKey:[CTUtils getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID] withResetValue:0]; if (lastEncryptionLevel != _encryptionLevel) { + CleverTapLogStaticInternal(@"CleverTap Encryption level changed for account: %@ to: %d", _accountID, _encryptionLevel); [self updatePreferencesValues]; + if (!_isDefaultInstance) { + // For Default instance, we are updating this after updating Local DB values on App Launch. + [CTPreferences putInt:_encryptionLevel forKey:[CTUtils getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID]]; + } } } diff --git a/CleverTapSDK/CleverTapInstanceConfig.m b/CleverTapSDK/CleverTapInstanceConfig.m index e7929b66..05e565cc 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.m +++ b/CleverTapSDK/CleverTapInstanceConfig.m @@ -206,7 +206,7 @@ - (void) setupPlistData:(BOOL)isDefault { _beta = plist.beta; _encryptionLevel = isDefault ? plist.encryptionLevel : CleverTapEncryptionNone; if (isDefault) { - _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel]; + _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel isDefaultInstance:isDefault]; } } @@ -224,7 +224,9 @@ - (void) checkIfAvailableAccountId:(NSString *)accountId - (void)setEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { if (!_isDefaultInstance) { _encryptionLevel = encryptionLevel; - _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel]; + _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel isDefaultInstance:_isDefaultInstance]; + } else { + CleverTapLogStaticInfo("CleverTap Encryption level for default instance can't be updated from setEncryptionLevel method"); } } @end diff --git a/docs/Encryption.md b/docs/Encryption.md index 32fa348d..b2d61af4 100644 --- a/docs/Encryption.md +++ b/docs/Encryption.md @@ -16,7 +16,7 @@ Different instances can have different encryption levels. To set an encryption l // Objective-C CleverTapInstanceConfig *ctConfig = [[CleverTapInstanceConfig alloc] initWithAccountId:@"ADDITIONAL_CLEVERTAP_ACCOUNT_ID" accountToken:@"ADDITIONAL_CLEVERTAP_ACCOUNT_TOKEN"]; -ctConfig.encryptionLevel = CleverTapEncryptionMedium; +[ctConfig setEncryptionLevel:CleverTapEncryptionMedium]; CleverTap *additionalCleverTapInstance = [CleverTap instanceWithConfig:ctConfig]; ```