Skip to content

Commit 9dc4319

Browse files
authored
Support for New Attribution Structure
Support for New Attribution Structure
1 parent b020ddd commit 9dc4319

File tree

10 files changed

+1512
-1389
lines changed

10 files changed

+1512
-1389
lines changed

ApphudSDK.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'ApphudSDK'
3-
s.version = '3.5.9'
3+
s.version = '3.6.0'
44
s.summary = 'Build and Measure In-App Subscriptions on iOS.'
55
s.description = 'Apphud covers every aspect when it comes to In-App Subscriptions from integration to analytics on iOS and Android.'
66
s.homepage = 'https://github.com/apphud/ApphudSDK'

Examples/ApphudDemoSwift/Pods/Pods.xcodeproj/project.pbxproj

Lines changed: 18 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Examples/ApphudDemoSwiftUI/Pods/Pods.xcodeproj/project.pbxproj

Lines changed: 647 additions & 643 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Examples/ApphudDemoVisionOS/ApphudSDKDemo.xcworkspace/contents.xcworkspacedata

Lines changed: 0 additions & 10 deletions
This file was deleted.

Examples/ApphudDemoVisionOS/ApphudSDKDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

Lines changed: 0 additions & 8 deletions
This file was deleted.

Examples/ApphudDemoVisionOS/Pods/Pods.xcodeproj/project.pbxproj

Lines changed: 647 additions & 643 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/Internal/ApphudInternal+Attribution.swift

Lines changed: 104 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,68 @@
99
import Foundation
1010

1111
extension ApphudInternal {
12-
12+
1313
// MARK: - Attribution
14-
internal func addAttribution(rawData: [AnyHashable: Any]?, from provider: ApphudAttributionProvider, identifer: String? = nil, callback: ((Bool) -> Void)?) {
14+
internal func setAttribution(data: ApphudAttributionData, from provider: ApphudAttributionProvider, identifer: String? = nil, callback: ((Bool) -> Void)?) {
1515
performWhenUserRegistered {
1616
Task {
17-
18-
let data = rawData as? [String: any Sendable]
19-
20-
var params: [String: Any] = ["device_id": self.currentDeviceID]
21-
17+
var dict: [String: any Sendable] = data.rawData as? [String: any Sendable] ?? [:]
18+
2219
switch provider {
20+
// ---------- .custom ----------
2321
case .custom:
24-
if let customAttribution = data {
25-
params.merge(customAttribution, uniquingKeysWith: { f, _ in f})
26-
}
22+
dict["identifier"] = identifer
23+
break
24+
25+
// ---------- .voluum ----------
26+
case .voluum:
27+
dict["identifier"] = identifer
28+
break
29+
30+
// ---------- .singular ----------
31+
case .singular:
32+
dict["identifier"] = identifer
33+
break
34+
35+
// ---------- .tenjin ----------
36+
case .tenjin:
37+
dict["identifier"] = identifer
38+
break
39+
40+
// ---------- .tiktok ----------
41+
case .tiktok:
42+
dict["identifier"] = identifer
43+
break
44+
45+
// ---------- .branch ----------
2746
case .branch:
28-
if let customAttribution = data {
29-
let wrappedAttribution = customAttribution["branch_data"] == nil ?
30-
["branch_data": customAttribution] : customAttribution
31-
params.merge(wrappedAttribution, uniquingKeysWith: { f, _ in f})
32-
}
47+
dict["identifier"] = identifer
48+
break
49+
50+
// ---------- .facebook ----------
3351
case .facebook:
34-
guard identifer != nil, self.submittedFacebookAnonId != identifer else {
35-
apphudLog("Facebook Anon ID is nil or didn't change, exiting", forceDisplay: true)
52+
guard let fbIdent = identifer,
53+
self.submittedFacebookAnonId != fbIdent
54+
else {
55+
apphudLog("Facebook Anon ID (identifer field) is nil or didn't change, exiting", forceDisplay: true)
3656
callback?(false)
3757
return
3858
}
39-
params["fb_anon_id"] = identifer
40-
if let customAttribution = data {
41-
params.merge(customAttribution, uniquingKeysWith: { f, _ in f})
42-
}
59+
dict["fb_anon_id"] = fbIdent
60+
61+
// ---------- .firebase ----------
4362
case .firebase:
44-
guard identifer != nil, self.submittedFirebaseId != identifer else {
63+
guard let firebaseId = identifer,
64+
self.submittedFirebaseId != firebaseId
65+
else {
4566
callback?(false)
4667
return
4768
}
48-
params["firebase_id"] = identifer
69+
dict["firebase_id"] = firebaseId
70+
71+
// ---------- .appsFlyer ----------
4972
case .appsFlyer:
50-
guard identifer != nil else {
73+
guard let afIdent = identifer else {
5174
callback?(false)
5275
return
5376
}
@@ -56,40 +79,37 @@ extension ApphudInternal {
5679
callback?(false)
5780
return
5881
}
59-
params["appsflyer_id"] = identifer
6082

61-
if let data = data {
62-
params["appsflyer_data"] = data
83+
dict["appsflyer_id"] = afIdent
6384

64-
guard await self.submittedPreviouslyAF(data: data) else {
65-
apphudLog("Already submitted AppsFlyer attribution, skipping", forceDisplay: true)
66-
callback?(false)
67-
return
68-
}
85+
guard await self.submittedPreviouslyAF(data: dict) else {
86+
apphudLog("Already submitted AppsFlyer attribution, skipping", forceDisplay: true)
87+
callback?(false)
88+
return
6989
}
7090
self.isSendingAppsFlyer = true
91+
92+
// ---------- .adjust ----------
7193
case .adjust:
7294
guard !self.isSendingAdjust else {
7395
apphudLog("Already submitted Adjust attribution, skipping", forceDisplay: true)
7496
callback?(false)
7597
return
7698
}
77-
if var data = data {
78-
if let adid = identifer {
79-
data["adid"] = adid
80-
}
81-
82-
params["adjust_data"] = data
99+
if let adid = identifer {
100+
dict["adid"] = adid
101+
}
83102

84-
guard await self.submittedPreviouslyAdjust(data: data) else {
85-
apphudLog("Already submitted Adjust attribution, skipping", forceDisplay: true)
86-
callback?(false)
87-
return
88-
}
103+
guard await self.submittedPreviouslyAdjust(data: dict) else {
104+
apphudLog("Already submitted Adjust attribution, skipping", forceDisplay: true)
105+
callback?(false)
106+
return
89107
}
90108
self.isSendingAdjust = true
109+
110+
// ---------- .appleAdsAttribution ----------
91111
case .appleAdsAttribution:
92-
guard identifer != nil else {
112+
guard let token = identifer else {
93113
callback?(false)
94114
return
95115
}
@@ -99,28 +119,55 @@ extension ApphudInternal {
99119
return
100120
}
101121

102-
if let searchAdsData = await self.getAppleAttribution(identifer!) {
103-
params["search_ads_data"] = searchAdsData
122+
if let searchAdsData = await self.getAppleAttribution(token) {
123+
for (key, value) in searchAdsData {
124+
dict[key] = value
125+
}
104126
} else {
105127
callback?(false)
106128
return
107129
}
130+
108131
default:
109-
return
132+
break
110133
}
134+
135+
// Create Request params with raw_data
136+
var params: [String: Any] = [
137+
"device_id": self.currentDeviceID,
138+
"provider": provider.toString(),
139+
"raw_data": dict
140+
]
111141

112-
// to avoid 404 problems on backend
142+
var attributionDict: [String: any Sendable] = [:]
143+
if let adNetwork = data.adNetwork { attributionDict["ad_network"] = adNetwork }
144+
if let channel = data.channel { attributionDict["channel"] = channel }
145+
if let campaign = data.campaign { attributionDict["campaign"] = campaign }
146+
if let adSet = data.adSet { attributionDict["ad_set"] = adSet }
147+
if let creative = data.creative { attributionDict["creative"] = creative }
148+
if let keyword = data.keyword { attributionDict["keyword"] = keyword }
149+
if let custom1 = data.custom1 { attributionDict["custom_1"] = custom1 }
150+
if let custom2 = data.custom2 { attributionDict["custom_2"] = custom2 }
151+
152+
params["attribution"] = attributionDict
153+
113154
try? await Task.sleep(nanoseconds: 2_000_000_000)
114-
115-
self.startAttributionRequest(params: params, provider: provider, identifer: identifer) { result in
155+
156+
self.startAttributionRequest(params: params, apiVersion:.APIV2, provider: provider, identifer: identifer) { result in
116157
Task {
117158
if result {
118159
switch provider {
119160
case .appsFlyer:
120-
await ApphudDataActor.shared.setAFData(data)
161+
await ApphudDataActor.shared.setAFData(dict)
121162
case .adjust:
122-
await ApphudDataActor.shared.setAdjustData(data)
123-
default :
163+
await ApphudDataActor.shared.setAdjustData(dict)
164+
case .firebase:
165+
self.submittedFirebaseId = identifer
166+
case .facebook:
167+
self.submittedFacebookAnonId = identifer
168+
case .appleAdsAttribution:
169+
self.didSubmitAppleAdsAttribution = true
170+
default:
124171
break
125172
}
126173
}
@@ -130,7 +177,7 @@ extension ApphudInternal {
130177
}
131178
}
132179
}
133-
180+
134181
func submittedPreviouslyAF(data: [String: any Sendable]) async -> Bool {
135182
return await self.compareAttribution(first: data, second: ApphudDataActor.shared.submittedAFData ?? [:])
136183
}
@@ -146,8 +193,8 @@ extension ApphudInternal {
146193
return !dictionary1.isEqual(to: dictionary2 as! [AnyHashable: Any])
147194
}
148195

149-
func startAttributionRequest(params: [String: Any], provider: ApphudAttributionProvider, identifer: String?, callback: ((Bool) -> Void)?) {
150-
self.httpClient?.startRequest(path: .attribution, params: params, method: .post, retry: true) { (result, _, _, _, _, _, _) in
196+
func startAttributionRequest(params: [String: Any], apiVersion: ApphudHttpClient.ApphudApiVersion = .APIV1, provider: ApphudAttributionProvider, identifer: String?, callback: ((Bool) -> Void)?) {
197+
self.httpClient?.startRequest(path: .attribution, apiVersion: apiVersion, params: params, method: .post, retry: true) { (result, _, _, _, _, _, _) in
151198
switch provider {
152199
case .adjust:
153200
// to avoid sending the same data several times in a row
@@ -208,7 +255,7 @@ extension ApphudInternal {
208255
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
209256
request.httpBody = Data(appleAttibutionToken.utf8)
210257

211-
let response = try? await URLSession.shared.data(for: request, retries: 5, delay: 1.0)
258+
let response = try? await URLSession.shared.data(for: request, retries: 5, delay: 7.0)
212259
if let data = response?.0,
213260
let result = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: any Sendable],
214261
let attribution = result["attribution"] as? Bool {

Sources/Public/Apphud.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Foundation
1414
import UserNotifications
1515
import SwiftUI
1616

17-
internal let apphud_sdk_version = "3.5.9"
17+
internal let apphud_sdk_version = "3.6.0"
1818

1919
// MARK: - Initialization
2020

@@ -776,17 +776,17 @@ final public class Apphud: NSObject {
776776
@objc public static func setAdvertisingIdentifier(_ idfa: String) {}
777777

778778
/**
779-
Submits attribution data to Apphud from your chosen attribution network provider.
779+
Submits attribution data to Apphud
780780

781-
- parameter data: Required. The attribution data dictionary.
781+
- parameter data: Required. The ApphudAttributionData model.
782782
- parameter provider: Required. The name of the attribution provider.
783783
- parameter identifier: Optional. An identifier that matches between Apphud and the Attribution provider.
784784
- parameter callback: Optional. A closure that returns `true` if the data was successfully sent to Apphud.
785785

786786
- Note: Properly setting up attribution data is key for tracking and optimizing user acquisition strategies and measuring the ROI of marketing campaigns.
787787
*/
788-
@objc public static func addAttribution(data: [AnyHashable: Any]?, from provider: ApphudAttributionProvider, identifer: String? = nil, callback: ApphudBoolCallback?) {
789-
ApphudInternal.shared.addAttribution(rawData: data, from: provider, identifer: identifer, callback: callback)
788+
public static func setAttribution(data: ApphudAttributionData, from provider: ApphudAttributionProvider, identifer: String? = nil, callback: ApphudBoolCallback?) {
789+
ApphudInternal.shared.setAttribution(data: data, from: provider, identifer: identifer, callback: callback)
790790
}
791791

792792
/**
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//
2+
// ApphudAttributionData.swift
3+
// ApphudSDK
4+
//
5+
// Created by ren6 on 12/07/2019.
6+
// Copyright © 2019 apphud. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public struct ApphudAttributionData {
12+
13+
/// Raw attribution data received from MMPs, such as AppsFlyer or Branch.
14+
/// Pass only `rawData` if no custom override logic is needed.
15+
public let rawData: [AnyHashable: Any]
16+
17+
/// Overridden ad network responsible for user acquisition (e.g., "Meta Ads", "Google Ads").
18+
/// Leave `nil` if no custom override logic is needed.
19+
public let adNetwork: String?
20+
21+
/// Overridden channel that drove the user acquisition (e.g., "Instagram Feed", "Google UAC").
22+
/// Leave `nil` if no custom override logic is needed.
23+
public let channel: String?
24+
25+
/// Overridden campaign name associated with the attribution data.
26+
/// Leave `nil` if no custom override logic is needed.
27+
public let campaign: String?
28+
29+
/// Overridden ad set name within the campaign.
30+
/// Leave `nil` if no custom override logic is needed.
31+
public let adSet: String?
32+
33+
/// Overridden specific ad creative used in the campaign.
34+
/// Leave `nil` if no custom override logic is needed.
35+
public let creative: String?
36+
37+
/// Overridden keyword associated with the ad campaign (if applicable).
38+
/// Leave `nil` if no custom override logic is needed.
39+
public let keyword: String?
40+
41+
/// Custom attribution parameter for additional tracking or mapping.
42+
/// Use this to store extra attribution data if needed.
43+
public let custom1: String?
44+
45+
/// Another custom attribution parameter for extended tracking or mapping.
46+
/// Use this to store extra attribution data if needed.
47+
public let custom2: String?
48+
49+
public init(
50+
rawData: [AnyHashable: Any],
51+
adNetwork: String? = nil,
52+
channel: String? = nil,
53+
campaign: String? = nil,
54+
adSet: String? = nil,
55+
creative: String? = nil,
56+
keyword: String? = nil,
57+
custom1: String? = nil,
58+
custom2: String? = nil
59+
) {
60+
self.rawData = rawData
61+
self.adNetwork = adNetwork
62+
self.channel = channel
63+
self.campaign = campaign
64+
self.adSet = adSet
65+
self.creative = creative
66+
self.keyword = keyword
67+
self.custom1 = custom1
68+
self.custom2 = custom2
69+
}
70+
}

0 commit comments

Comments
 (0)