From a9378061fa0d97032a7bdbb2170a96e095e5dc86 Mon Sep 17 00:00:00 2001 From: Alan Charles Date: Thu, 24 Jun 2021 08:53:15 -0600 Subject: [PATCH 1/6] initial commit --- .../project.pbxproj | 21 ++++ .../DestinationsExample/AppDelegate.swift | 6 +- .../AppsFlyerDestination.swift | 101 ++++++++++++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 Examples/destination_plugins/AppsFlyerDestination.swift diff --git a/Examples/apps/DestinationsExample/DestinationsExample.xcodeproj/project.pbxproj b/Examples/apps/DestinationsExample/DestinationsExample.xcodeproj/project.pbxproj index 9c0cac1c..cfd83d1e 100644 --- a/Examples/apps/DestinationsExample/DestinationsExample.xcodeproj/project.pbxproj +++ b/Examples/apps/DestinationsExample/DestinationsExample.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ 965DC1232669947F00DDF9C7 /* FirebaseDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965DC1222669947F00DDF9C7 /* FirebaseDestination.swift */; }; 965DC1262671656C00DDF9C7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 965DC1252671656C00DDF9C7 /* GoogleService-Info.plist */; }; 9697C1F52679156C00B87EC1 /* Segment_Logo_Avatar_Grey-1024.png in Resources */ = {isa = PBXBuildFile; fileRef = 9697C1F42679156C00B87EC1 /* Segment_Logo_Avatar_Grey-1024.png */; }; + BA384C9826824F3700AFEA1B /* AppsFlyerLib in Frameworks */ = {isa = PBXBuildFile; productRef = BA384C9726824F3700AFEA1B /* AppsFlyerLib */; }; + BA384C9A2682973300AFEA1B /* AppsFlyerDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA384C992682973300AFEA1B /* AppsFlyerDestination.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -46,6 +48,7 @@ 965DC1222669947F00DDF9C7 /* FirebaseDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseDestination.swift; sourceTree = ""; }; 965DC1252671656C00DDF9C7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 9697C1F42679156C00B87EC1 /* Segment_Logo_Avatar_Grey-1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Segment_Logo_Avatar_Grey-1024.png"; sourceTree = ""; }; + BA384C992682973300AFEA1B /* AppsFlyerDestination.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppsFlyerDestination.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,6 +56,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BA384C9826824F3700AFEA1B /* AppsFlyerLib in Frameworks */, 469EC8E0266828860068F9E3 /* FlurryAnalyticsSPM in Frameworks */, 469EC8D0266066130068F9E3 /* SystemConfiguration.framework in Frameworks */, 965DC1212669942800DDF9C7 /* FirebaseAnalytics in Frameworks */, @@ -110,6 +114,7 @@ 469F7B1E266012CB0038E773 /* destination_plugins */ = { isa = PBXGroup; children = ( + BA384C992682973300AFEA1B /* AppsFlyerDestination.swift */, 469F7B24266013320038E773 /* AdjustDestination.swift */, 965DC0F92668077400DDF9C7 /* AmplitudeSession.swift */, 965DC1222669947F00DDF9C7 /* FirebaseDestination.swift */, @@ -151,6 +156,7 @@ 965DC0FD2668079400DDF9C7 /* Mixpanel */, 469EC8DF266828860068F9E3 /* FlurryAnalyticsSPM */, 965DC1202669942800DDF9C7 /* FirebaseAnalytics */, + BA384C9726824F3700AFEA1B /* AppsFlyerLib */, ); productName = DestinationsExample; productReference = 469F7B04266011690038E773 /* DestinationsExample.app */; @@ -184,6 +190,7 @@ 965DC0FC2668079400DDF9C7 /* XCRemoteSwiftPackageReference "mixpanel-swift" */, 469EC8DE266828860068F9E3 /* XCRemoteSwiftPackageReference "FlurrySwiftPackage" */, 965DC11F2669942800DDF9C7 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + BA384C9626824F3700AFEA1B /* XCRemoteSwiftPackageReference "AppsFlyerFramework" */, ); productRefGroup = 469F7B05266011690038E773 /* Products */; projectDirPath = ""; @@ -214,6 +221,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BA384C9A2682973300AFEA1B /* AppsFlyerDestination.swift in Sources */, 469F7B20266012CB0038E773 /* FlurryDestination.swift in Sources */, 469F7B0C266011690038E773 /* ViewController.swift in Sources */, 965DC0FA2668077400DDF9C7 /* MixpanelDestination.swift in Sources */, @@ -457,6 +465,14 @@ minimumVersion = 8.1.0; }; }; + BA384C9626824F3700AFEA1B /* XCRemoteSwiftPackageReference "AppsFlyerFramework" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/AppsFlyerSDK/AppsFlyerFramework"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 6.3.2; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -484,6 +500,11 @@ package = 965DC11F2669942800DDF9C7 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseAnalytics; }; + BA384C9726824F3700AFEA1B /* AppsFlyerLib */ = { + isa = XCSwiftPackageProductDependency; + package = BA384C9626824F3700AFEA1B /* XCRemoteSwiftPackageReference "AppsFlyerFramework" */; + productName = AppsFlyerLib; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 469F7AFC266011690038E773 /* Project object */; diff --git a/Examples/apps/DestinationsExample/DestinationsExample/AppDelegate.swift b/Examples/apps/DestinationsExample/DestinationsExample/AppDelegate.swift index a915888d..ba56f030 100644 --- a/Examples/apps/DestinationsExample/DestinationsExample/AppDelegate.swift +++ b/Examples/apps/DestinationsExample/DestinationsExample/AppDelegate.swift @@ -16,9 +16,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - let configuration = Configuration(writeKey: "1234") + let configuration = Configuration(writeKey: "U6VRDTUtphNDFkUAyDvCCoHusBXgKtCG") .trackApplicationLifecycleEvents(true) - .flushInterval(10) + .flushInterval(1) analytics = Analytics(configuration: configuration) @@ -37,6 +37,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Add the Firebase destination plugin analytics?.add(plugin: FirebaseDestination(name: "Firebase")) + analytics?.add(plugin: AppsFlyerDestination(name: "AppsFlyer")) + return true } diff --git a/Examples/destination_plugins/AppsFlyerDestination.swift b/Examples/destination_plugins/AppsFlyerDestination.swift new file mode 100644 index 00000000..44ef3a02 --- /dev/null +++ b/Examples/destination_plugins/AppsFlyerDestination.swift @@ -0,0 +1,101 @@ +// +// File.swift +// +// +// Created by Alan Charles on 6/22/21. +// + +import Foundation +import Segment +import AppsFlyerLib + + +internal struct AppsFlyerSettings: Codable { + let appsFlyerDevKey: String + let appleAppID: String + let trackAttributionData: Bool? +} + +@objc +class AppsFlyerDestination: NSObject, DestinationPlugin { + + let timeline: Timeline = Timeline() + let type: PluginType = .destination + let name: String + var analytics: Analytics? + + internal var settings: AppsFlyerSettings? = nil + + required init(name: String) { + self.name = name + } + + public func update(settings: Settings) { + + guard let settings: AppsFlyerSettings = settings.integrationSettings(name: "AppsFlyer") else {return} + self.settings = settings + + + AppsFlyerLib.shared().appsFlyerDevKey = settings.appsFlyerDevKey + AppsFlyerLib.shared().appleAppID = settings.appleAppID + AppsFlyerLib.shared().isDebug = true + AppsFlyerLib.shared().start() + + + + + if let _ = settings.trackAttributionData { + print(settings.trackAttributionData) + AppsFlyerLib.shared().delegate = self + + } + } +} + + +// MARK: - AppsFlyer Delegate conformance + +extension AppsFlyerDestination: AppsFlyerLibDelegate { + func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) { + guard let first_launch_flag = conversionInfo["is_first_launch"] as? Int else { + return + } + + guard let status = conversionInfo["af_status"] as? String else { + return + } + + if(first_launch_flag == 1) { + if(status == "Non-organic") { + if let media_source = conversionInfo["media_source"] , let campaign = conversionInfo["campaign"]{ + + let campaign: [String: Any] = [ + "source": media_source, + "name": campaign + ] + + let properties: [String: Any] = [ + "provider": "AppsFlyer", + "campaign": campaign + ] + + analytics?.track(name: "Install Attributed", properties: properties) + + } + } else { + analytics?.track(name: "Not an Install") + print("This is an organic install.") + } + } else { + print("Not First Launch") + } + + } + + func onConversionDataFail(_ error: Error) { + return + } + + + +} From ca340bb4d82b24087fbd6b49b61c64bfe9983983 Mon Sep 17 00:00:00 2001 From: Alan Charles Date: Thu, 1 Jul 2021 09:58:27 -0600 Subject: [PATCH 2/6] add deeplink and core api for appsflyer func and update iosDelegation method --- .../project.pbxproj | 10 + .../DestinationsExample/AppDelegate.swift | 47 ++- .../Base.lproj/Main.storyboard | 20 +- .../DestinationsExample.entitlements | 10 + .../DestinationsExample/Info.plist | 21 ++ .../DestinationsExample/SceneDelegate.swift | 1 - .../DestinationsExample/ViewController.swift | 4 + .../AdjustDestination.swift | 4 - .../AppsFlyerDestination.swift | 321 ++++++++++++++++-- .../Plugins/Platforms/iOS/iOSDelegation.swift | 6 +- Sources/Segment/Settings.swift | 8 +- 11 files changed, 388 insertions(+), 64 deletions(-) create mode 100644 Examples/apps/DestinationsExample/DestinationsExample/DestinationsExample.entitlements diff --git a/Examples/apps/DestinationsExample/DestinationsExample.xcodeproj/project.pbxproj b/Examples/apps/DestinationsExample/DestinationsExample.xcodeproj/project.pbxproj index cfd83d1e..52b279c1 100644 --- a/Examples/apps/DestinationsExample/DestinationsExample.xcodeproj/project.pbxproj +++ b/Examples/apps/DestinationsExample/DestinationsExample.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ 965DC1252671656C00DDF9C7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 9697C1F42679156C00B87EC1 /* Segment_Logo_Avatar_Grey-1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Segment_Logo_Avatar_Grey-1024.png"; sourceTree = ""; }; BA384C992682973300AFEA1B /* AppsFlyerDestination.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppsFlyerDestination.swift; sourceTree = ""; }; + BA384C9D2686609000AFEA1B /* DestinationsExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DestinationsExample.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -89,6 +90,7 @@ 469F7B06266011690038E773 /* DestinationsExample */ = { isa = PBXGroup; children = ( + BA384C9D2686609000AFEA1B /* DestinationsExample.entitlements */, 469F7B1E266012CB0038E773 /* destination_plugins */, 469F7B07266011690038E773 /* AppDelegate.swift */, 469F7B09266011690038E773 /* SceneDelegate.swift */, @@ -378,7 +380,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = DestinationsExample/DestinationsExample.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = V8R4668S2H; INFOPLIST_FILE = DestinationsExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -386,6 +391,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.segment.DestinationsExample; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -396,7 +402,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = DestinationsExample/DestinationsExample.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = V8R4668S2H; INFOPLIST_FILE = DestinationsExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -404,6 +413,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.segment.DestinationsExample; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/Examples/apps/DestinationsExample/DestinationsExample/AppDelegate.swift b/Examples/apps/DestinationsExample/DestinationsExample/AppDelegate.swift index ba56f030..66fb0007 100644 --- a/Examples/apps/DestinationsExample/DestinationsExample/AppDelegate.swift +++ b/Examples/apps/DestinationsExample/DestinationsExample/AppDelegate.swift @@ -10,13 +10,14 @@ import Segment @main class AppDelegate: UIResponder, UIApplicationDelegate { - + + var analytics: Analytics? = nil - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - let configuration = Configuration(writeKey: "U6VRDTUtphNDFkUAyDvCCoHusBXgKtCG") + let configuration = Configuration(writeKey: "WRITE_KEY") .trackApplicationLifecycleEvents(true) .flushInterval(1) @@ -36,27 +37,55 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Add the Firebase destination plugin analytics?.add(plugin: FirebaseDestination(name: "Firebase")) - + + //Add the AppsFlyer destination plugin analytics?.add(plugin: AppsFlyerDestination(name: "AppsFlyer")) return true } - + // MARK: UISceneSession Lifecycle - + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - - + + //MARK: - Deep Link functionality + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + analytics?.continueUserActivity(userActivity) + return true + } + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { + analytics?.continueUserActivity(userActivity) + return true + } + + func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { + analytics?.openURL(url) + return true + } + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + analytics?.openURL(url, options: options) + return true + } + + // Report Push Notification attribution data for re-engagements + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + // this enables remote notifications for various destinations (appsflyer) + analytics?.receivedRemoteNotification(userInfo: userInfo) + } + } extension UIApplicationDelegate { diff --git a/Examples/apps/DestinationsExample/DestinationsExample/Base.lproj/Main.storyboard b/Examples/apps/DestinationsExample/DestinationsExample/Base.lproj/Main.storyboard index b6af3305..3faa05dd 100644 --- a/Examples/apps/DestinationsExample/DestinationsExample/Base.lproj/Main.storyboard +++ b/Examples/apps/DestinationsExample/DestinationsExample/Base.lproj/Main.storyboard @@ -17,13 +17,13 @@ - + @@ -34,29 +34,29 @@ - + - + - + - +