@datalyr/react-native 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to the Datalyr React Native SDK will be documented in this f
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.1] - 2025-01
9
+
10
+ ### Added
11
+ - Apple Search Ads attribution support via AdServices framework (iOS 14.3+)
12
+ - `getAppleSearchAdsAttribution()` method to retrieve attribution data
13
+ - Automatic capture of Apple Search Ads attribution on SDK initialization
14
+ - Apple Search Ads data included in all events (asa_campaign_id, asa_keyword, etc.)
15
+
8
16
  ## [1.2.0] - 2025-01
9
17
 
10
18
  ### Added
package/README.md CHANGED
@@ -23,6 +23,9 @@ Mobile analytics and attribution SDK for React Native and Expo. Track events, id
23
23
  - [Auto Events](#auto-events)
24
24
  - [SKAdNetwork](#skadnetwork)
25
25
  - [Platform Integrations](#platform-integrations)
26
+ - [Meta](#meta-facebook)
27
+ - [TikTok](#tiktok)
28
+ - [Apple Search Ads](#apple-search-ads)
26
29
  - [Expo Support](#expo-support)
27
30
  - [TypeScript](#typescript)
28
31
  - [Troubleshooting](#troubleshooting)
@@ -482,6 +485,32 @@ await Datalyr.initialize({
482
485
  });
483
486
  ```
484
487
 
488
+ ### Apple Search Ads
489
+
490
+ Attribution for users who install from Apple Search Ads (iOS 14.3+). Automatically fetched on initialization.
491
+
492
+ ```typescript
493
+ // Check if user came from Apple Search Ads
494
+ const asaAttribution = Datalyr.getAppleSearchAdsAttribution();
495
+
496
+ if (asaAttribution?.attribution) {
497
+ console.log(asaAttribution.campaignId); // Campaign ID
498
+ console.log(asaAttribution.campaignName); // Campaign name
499
+ console.log(asaAttribution.adGroupId); // Ad group ID
500
+ console.log(asaAttribution.keyword); // Search keyword
501
+ console.log(asaAttribution.clickDate); // Click date
502
+ }
503
+ ```
504
+
505
+ Attribution data is automatically included in all events with the `asa_` prefix:
506
+ - `asa_campaign_id`, `asa_campaign_name`
507
+ - `asa_ad_group_id`, `asa_ad_group_name`
508
+ - `asa_keyword_id`, `asa_keyword`
509
+ - `asa_org_id`, `asa_org_name`
510
+ - `asa_click_date`, `asa_conversion_type`
511
+
512
+ No additional configuration needed. The SDK uses Apple's AdServices API.
513
+
485
514
  ### App Tracking Transparency
486
515
 
487
516
  Update after ATT dialog:
@@ -495,7 +524,7 @@ await Datalyr.updateTrackingAuthorization(status === 'granted');
495
524
 
496
525
  ```typescript
497
526
  const status = Datalyr.getPlatformIntegrationStatus();
498
- // { meta: true, tiktok: true }
527
+ // { meta: true, tiktok: true, appleSearchAds: true }
499
528
  ```
500
529
 
501
530
  ---
@@ -67,4 +67,8 @@ RCT_EXTERN_METHOD(updateTikTokTrackingAuthorization:(BOOL)enabled
67
67
  RCT_EXTERN_METHOD(getSDKAvailability:(RCTPromiseResolveBlock)resolve
68
68
  reject:(RCTPromiseRejectBlock)reject)
69
69
 
70
+ // Apple Search Ads Attribution
71
+ RCT_EXTERN_METHOD(getAppleSearchAdsAttribution:(RCTPromiseResolveBlock)resolve
72
+ reject:(RCTPromiseRejectBlock)reject)
73
+
70
74
  @end
@@ -1,6 +1,7 @@
1
1
  import Foundation
2
2
  import FBSDKCoreKit
3
3
  import TikTokBusinessSDK
4
+ import AdServices
4
5
 
5
6
  @objc(DatalyrNative)
6
7
  class DatalyrNative: NSObject {
@@ -269,7 +270,63 @@ class DatalyrNative: NSObject {
269
270
  ) {
270
271
  resolve([
271
272
  "meta": true,
272
- "tiktok": true
273
+ "tiktok": true,
274
+ "appleSearchAds": true
273
275
  ])
274
276
  }
277
+
278
+ // MARK: - Apple Search Ads Attribution
279
+
280
+ @objc func getAppleSearchAdsAttribution(
281
+ _ resolve: @escaping RCTPromiseResolveBlock,
282
+ reject: @escaping RCTPromiseRejectBlock
283
+ ) {
284
+ // AdServices is available on iOS 14.3+
285
+ if #available(iOS 14.3, *) {
286
+ do {
287
+ // Get the attribution token from AdServices
288
+ let token = try AAAttribution.attributionToken()
289
+
290
+ // Send token to Apple's API to get attribution data
291
+ var request = URLRequest(url: URL(string: "https://api-adservices.apple.com/api/v1/")!)
292
+ request.httpMethod = "POST"
293
+ request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
294
+ request.httpBody = token.data(using: .utf8)
295
+
296
+ let task = URLSession.shared.dataTask(with: request) { data, response, error in
297
+ if let error = error {
298
+ // Network error - resolve with nil instead of rejecting
299
+ resolve(nil)
300
+ return
301
+ }
302
+
303
+ guard let data = data else {
304
+ resolve(nil)
305
+ return
306
+ }
307
+
308
+ do {
309
+ if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
310
+ // Return attribution data
311
+ // Includes: attribution, orgId, orgName, campaignId, campaignName,
312
+ // adGroupId, adGroupName, clickDate, conversionType, etc.
313
+ resolve(json)
314
+ } else {
315
+ resolve(nil)
316
+ }
317
+ } catch {
318
+ resolve(nil)
319
+ }
320
+ }
321
+ task.resume()
322
+
323
+ } catch {
324
+ // Attribution token not available (user didn't come from Apple Search Ads)
325
+ resolve(nil)
326
+ }
327
+ } else {
328
+ // iOS version too old
329
+ resolve(nil)
330
+ }
331
+ }
275
332
  }
@@ -1,6 +1,7 @@
1
1
  import { DatalyrConfig, EventData, UserProperties, AutoEventConfig, DeferredDeepLinkResult } from './types';
2
2
  import { AttributionData } from './attribution';
3
3
  import { SessionData } from './auto-events';
4
+ import { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
4
5
  export declare class DatalyrSDK {
5
6
  private state;
6
7
  private httpClient;
@@ -138,7 +139,13 @@ export declare class DatalyrSDK {
138
139
  getPlatformIntegrationStatus(): {
139
140
  meta: boolean;
140
141
  tiktok: boolean;
142
+ appleSearchAds: boolean;
141
143
  };
144
+ /**
145
+ * Get Apple Search Ads attribution data
146
+ * Returns attribution if user installed via Apple Search Ads, null otherwise
147
+ */
148
+ getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null;
142
149
  /**
143
150
  * Update tracking authorization status on all platform SDKs
144
151
  * Call this AFTER the user responds to the ATT permission dialog
@@ -234,7 +241,9 @@ export declare class Datalyr {
234
241
  static getPlatformIntegrationStatus(): {
235
242
  meta: boolean;
236
243
  tiktok: boolean;
244
+ appleSearchAds: boolean;
237
245
  };
246
+ static getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null;
238
247
  static updateTrackingAuthorization(enabled: boolean): Promise<void>;
239
248
  }
240
249
  export default datalyr;
@@ -6,7 +6,7 @@ import { attributionManager } from './attribution';
6
6
  import { AutoEventsManager } from './auto-events';
7
7
  import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
8
8
  import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
9
- import { metaIntegration, tiktokIntegration } from './integrations';
9
+ import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
10
10
  export class DatalyrSDK {
11
11
  constructor() {
12
12
  this.autoEventsManager = null;
@@ -132,9 +132,12 @@ export class DatalyrSDK {
132
132
  if (((_c = config.tiktok) === null || _c === void 0 ? void 0 : _c.appId) && ((_d = config.tiktok) === null || _d === void 0 ? void 0 : _d.tiktokAppId)) {
133
133
  await tiktokIntegration.initialize(config.tiktok, config.debug);
134
134
  }
135
+ // Initialize Apple Search Ads attribution (iOS only, auto-fetches on init)
136
+ await appleSearchAdsIntegration.initialize(config.debug);
135
137
  debugLog('Platform integrations initialized', {
136
138
  meta: metaIntegration.isAvailable(),
137
139
  tiktok: tiktokIntegration.isAvailable(),
140
+ appleSearchAds: appleSearchAdsIntegration.isAvailable(),
138
141
  });
139
142
  // SDK initialized successfully - set state before tracking install event
140
143
  this.state.initialized = true;
@@ -672,8 +675,16 @@ export class DatalyrSDK {
672
675
  return {
673
676
  meta: metaIntegration.isAvailable(),
674
677
  tiktok: tiktokIntegration.isAvailable(),
678
+ appleSearchAds: appleSearchAdsIntegration.isAvailable(),
675
679
  };
676
680
  }
681
+ /**
682
+ * Get Apple Search Ads attribution data
683
+ * Returns attribution if user installed via Apple Search Ads, null otherwise
684
+ */
685
+ getAppleSearchAdsAttribution() {
686
+ return appleSearchAdsIntegration.getAttributionData();
687
+ }
677
688
  /**
678
689
  * Update tracking authorization status on all platform SDKs
679
690
  * Call this AFTER the user responds to the ATT permission dialog
@@ -745,6 +756,21 @@ export class DatalyrSDK {
745
756
  const deviceInfo = await getDeviceInfo();
746
757
  const fingerprintData = await createFingerprintData();
747
758
  const attributionData = attributionManager.getAttributionData();
759
+ // Get Apple Search Ads attribution if available
760
+ const asaAttribution = appleSearchAdsIntegration.getAttributionData();
761
+ const asaData = (asaAttribution === null || asaAttribution === void 0 ? void 0 : asaAttribution.attribution) ? {
762
+ asa_campaign_id: asaAttribution.campaignId,
763
+ asa_campaign_name: asaAttribution.campaignName,
764
+ asa_ad_group_id: asaAttribution.adGroupId,
765
+ asa_ad_group_name: asaAttribution.adGroupName,
766
+ asa_keyword_id: asaAttribution.keywordId,
767
+ asa_keyword: asaAttribution.keyword,
768
+ asa_org_id: asaAttribution.orgId,
769
+ asa_org_name: asaAttribution.orgName,
770
+ asa_click_date: asaAttribution.clickDate,
771
+ asa_conversion_type: asaAttribution.conversionType,
772
+ asa_country_or_region: asaAttribution.countryOrRegion,
773
+ } : {};
748
774
  const payload = {
749
775
  workspaceId: this.state.config.workspaceId || 'mobile_sdk',
750
776
  visitorId: this.state.visitorId,
@@ -766,6 +792,8 @@ export class DatalyrSDK {
766
792
  timestamp: Date.now(),
767
793
  // Attribution data
768
794
  ...attributionData,
795
+ // Apple Search Ads attribution
796
+ ...asaData,
769
797
  },
770
798
  fingerprintData,
771
799
  source: 'mobile_app',
@@ -999,6 +1027,9 @@ export class Datalyr {
999
1027
  static getPlatformIntegrationStatus() {
1000
1028
  return datalyr.getPlatformIntegrationStatus();
1001
1029
  }
1030
+ static getAppleSearchAdsAttribution() {
1031
+ return datalyr.getAppleSearchAdsAttribution();
1032
+ }
1002
1033
  static async updateTrackingAuthorization(enabled) {
1003
1034
  await datalyr.updateTrackingAuthorization(enabled);
1004
1035
  }
package/lib/index.d.ts CHANGED
@@ -10,5 +10,6 @@ export * from './event-queue';
10
10
  export { DatalyrSDK };
11
11
  export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
12
12
  export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
13
- export { metaIntegration, tiktokIntegration } from './integrations';
13
+ export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
14
+ export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
14
15
  export default DatalyrSDK;
package/lib/index.js CHANGED
@@ -19,6 +19,6 @@ export { DatalyrSDK };
19
19
  export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
20
20
  export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
21
21
  // Export platform integrations
22
- export { metaIntegration, tiktokIntegration } from './integrations';
22
+ export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
23
23
  // Default export for compatibility
24
24
  export default DatalyrSDK;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Apple Search Ads Attribution Integration
3
+ * Uses AdServices framework (iOS 14.3+) to capture attribution from App Store search ads
4
+ */
5
+ import { AppleSearchAdsAttribution } from '../native/DatalyrNativeBridge';
6
+ /**
7
+ * Apple Search Ads Integration class
8
+ * Fetches attribution data for users who installed via Apple Search Ads
9
+ */
10
+ export declare class AppleSearchAdsIntegration {
11
+ private attributionData;
12
+ private fetched;
13
+ private available;
14
+ private debug;
15
+ /**
16
+ * Initialize and fetch Apple Search Ads attribution
17
+ */
18
+ initialize(debug?: boolean): Promise<void>;
19
+ /**
20
+ * Fetch attribution data from Apple's AdServices API
21
+ * Call this during app initialization
22
+ */
23
+ fetchAttribution(): Promise<AppleSearchAdsAttribution | null>;
24
+ /**
25
+ * Get cached attribution data
26
+ */
27
+ getAttributionData(): AppleSearchAdsAttribution | null;
28
+ /**
29
+ * Check if user came from Apple Search Ads
30
+ */
31
+ hasAttribution(): boolean;
32
+ /**
33
+ * Check if Apple Search Ads is available (iOS 14.3+)
34
+ */
35
+ isAvailable(): boolean;
36
+ /**
37
+ * Check if attribution has been fetched
38
+ */
39
+ hasFetched(): boolean;
40
+ private log;
41
+ private logError;
42
+ }
43
+ export declare const appleSearchAdsIntegration: AppleSearchAdsIntegration;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Apple Search Ads Attribution Integration
3
+ * Uses AdServices framework (iOS 14.3+) to capture attribution from App Store search ads
4
+ */
5
+ import { Platform } from 'react-native';
6
+ import { AppleSearchAdsNativeBridge, isNativeModuleAvailable } from '../native/DatalyrNativeBridge';
7
+ /**
8
+ * Apple Search Ads Integration class
9
+ * Fetches attribution data for users who installed via Apple Search Ads
10
+ */
11
+ export class AppleSearchAdsIntegration {
12
+ constructor() {
13
+ this.attributionData = null;
14
+ this.fetched = false;
15
+ this.available = false;
16
+ this.debug = false;
17
+ }
18
+ /**
19
+ * Initialize and fetch Apple Search Ads attribution
20
+ */
21
+ async initialize(debug = false) {
22
+ this.debug = debug;
23
+ // Only available on iOS via native module
24
+ if (Platform.OS !== 'ios') {
25
+ this.log('Apple Search Ads only available on iOS');
26
+ return;
27
+ }
28
+ this.available = isNativeModuleAvailable();
29
+ if (!this.available) {
30
+ this.log('Apple Search Ads native module not available');
31
+ return;
32
+ }
33
+ // Automatically fetch attribution on init
34
+ await this.fetchAttribution();
35
+ }
36
+ /**
37
+ * Fetch attribution data from Apple's AdServices API
38
+ * Call this during app initialization
39
+ */
40
+ async fetchAttribution() {
41
+ var _a;
42
+ if (!this.available) {
43
+ return null;
44
+ }
45
+ // Only fetch once
46
+ if (this.fetched) {
47
+ return this.attributionData;
48
+ }
49
+ try {
50
+ this.attributionData = await AppleSearchAdsNativeBridge.getAttribution();
51
+ this.fetched = true;
52
+ if ((_a = this.attributionData) === null || _a === void 0 ? void 0 : _a.attribution) {
53
+ this.log('Apple Search Ads attribution found:', {
54
+ campaignId: this.attributionData.campaignId,
55
+ campaignName: this.attributionData.campaignName,
56
+ adGroupId: this.attributionData.adGroupId,
57
+ keyword: this.attributionData.keyword,
58
+ });
59
+ }
60
+ else {
61
+ this.log('No Apple Search Ads attribution (user did not come from search ad)');
62
+ }
63
+ return this.attributionData;
64
+ }
65
+ catch (error) {
66
+ this.logError('Failed to fetch Apple Search Ads attribution:', error);
67
+ this.fetched = true;
68
+ return null;
69
+ }
70
+ }
71
+ /**
72
+ * Get cached attribution data
73
+ */
74
+ getAttributionData() {
75
+ return this.attributionData;
76
+ }
77
+ /**
78
+ * Check if user came from Apple Search Ads
79
+ */
80
+ hasAttribution() {
81
+ var _a;
82
+ return ((_a = this.attributionData) === null || _a === void 0 ? void 0 : _a.attribution) === true;
83
+ }
84
+ /**
85
+ * Check if Apple Search Ads is available (iOS 14.3+)
86
+ */
87
+ isAvailable() {
88
+ return this.available;
89
+ }
90
+ /**
91
+ * Check if attribution has been fetched
92
+ */
93
+ hasFetched() {
94
+ return this.fetched;
95
+ }
96
+ log(message, data) {
97
+ if (this.debug) {
98
+ console.log(`[Datalyr/AppleSearchAds] ${message}`, data || '');
99
+ }
100
+ }
101
+ logError(message, error) {
102
+ console.error(`[Datalyr/AppleSearchAds] ${message}`, error);
103
+ }
104
+ }
105
+ // Export singleton instance
106
+ export const appleSearchAdsIntegration = new AppleSearchAdsIntegration();
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Platform SDK Integrations
3
- * Meta (Facebook) and TikTok SDK wrappers for deferred deep linking and event forwarding
3
+ * Meta (Facebook), TikTok, and Apple Search Ads SDK wrappers
4
4
  */
5
5
  export { MetaIntegration, metaIntegration } from './meta-integration';
6
6
  export { TikTokIntegration, tiktokIntegration } from './tiktok-integration';
7
+ export { AppleSearchAdsIntegration, appleSearchAdsIntegration } from './apple-search-ads-integration';
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Platform SDK Integrations
3
- * Meta (Facebook) and TikTok SDK wrappers for deferred deep linking and event forwarding
3
+ * Meta (Facebook), TikTok, and Apple Search Ads SDK wrappers
4
4
  */
5
5
  export { MetaIntegration, metaIntegration } from './meta-integration';
6
6
  export { TikTokIntegration, tiktokIntegration } from './tiktok-integration';
7
+ export { AppleSearchAdsIntegration, appleSearchAdsIntegration } from './apple-search-ads-integration';
@@ -1,7 +1,24 @@
1
1
  /**
2
- * Native Bridge for Meta and TikTok SDKs
2
+ * Native Bridge for Meta, TikTok, and Apple Search Ads
3
3
  * Uses bundled native modules instead of separate npm packages
4
4
  */
5
+ /**
6
+ * Apple Search Ads attribution data returned from AdServices API
7
+ */
8
+ export interface AppleSearchAdsAttribution {
9
+ attribution: boolean;
10
+ orgId?: number;
11
+ orgName?: string;
12
+ campaignId?: number;
13
+ campaignName?: string;
14
+ adGroupId?: number;
15
+ adGroupName?: string;
16
+ keywordId?: number;
17
+ keyword?: string;
18
+ clickDate?: string;
19
+ conversionType?: string;
20
+ countryOrRegion?: string;
21
+ }
5
22
  /**
6
23
  * Check if native module is available
7
24
  */
@@ -12,6 +29,7 @@ export declare const isNativeModuleAvailable: () => boolean;
12
29
  export declare const getSDKAvailability: () => Promise<{
13
30
  meta: boolean;
14
31
  tiktok: boolean;
32
+ appleSearchAds: boolean;
15
33
  }>;
16
34
  export declare const MetaNativeBridge: {
17
35
  initialize(appId: string, clientToken?: string, advertiserTrackingEnabled?: boolean): Promise<boolean>;
@@ -29,3 +47,11 @@ export declare const TikTokNativeBridge: {
29
47
  logout(): Promise<boolean>;
30
48
  updateTrackingAuthorization(enabled: boolean): Promise<boolean>;
31
49
  };
50
+ export declare const AppleSearchAdsNativeBridge: {
51
+ /**
52
+ * Get Apple Search Ads attribution data
53
+ * Uses AdServices framework (iOS 14.3+)
54
+ * Returns null if user didn't come from Apple Search Ads or on older iOS
55
+ */
56
+ getAttribution(): Promise<AppleSearchAdsAttribution | null>;
57
+ };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Native Bridge for Meta and TikTok SDKs
2
+ * Native Bridge for Meta, TikTok, and Apple Search Ads
3
3
  * Uses bundled native modules instead of separate npm packages
4
4
  */
5
5
  import { NativeModules, Platform } from 'react-native';
@@ -16,13 +16,13 @@ export const isNativeModuleAvailable = () => {
16
16
  */
17
17
  export const getSDKAvailability = async () => {
18
18
  if (!DatalyrNative) {
19
- return { meta: false, tiktok: false };
19
+ return { meta: false, tiktok: false, appleSearchAds: false };
20
20
  }
21
21
  try {
22
22
  return await DatalyrNative.getSDKAvailability();
23
23
  }
24
24
  catch (_a) {
25
- return { meta: false, tiktok: false };
25
+ return { meta: false, tiktok: false, appleSearchAds: false };
26
26
  }
27
27
  };
28
28
  // MARK: - Meta SDK Bridge
@@ -166,3 +166,22 @@ export const TikTokNativeBridge = {
166
166
  }
167
167
  },
168
168
  };
169
+ // MARK: - Apple Search Ads Bridge
170
+ export const AppleSearchAdsNativeBridge = {
171
+ /**
172
+ * Get Apple Search Ads attribution data
173
+ * Uses AdServices framework (iOS 14.3+)
174
+ * Returns null if user didn't come from Apple Search Ads or on older iOS
175
+ */
176
+ async getAttribution() {
177
+ if (!DatalyrNative)
178
+ return null;
179
+ try {
180
+ return await DatalyrNative.getAppleSearchAdsAttribution();
181
+ }
182
+ catch (error) {
183
+ console.error('[Datalyr/AppleSearchAds] Get attribution failed:', error);
184
+ return null;
185
+ }
186
+ },
187
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalyr/react-native",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking with bundled Meta and TikTok SDKs",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -38,6 +38,7 @@
38
38
  "tiktok-attribution",
39
39
  "google-attribution",
40
40
  "ios-attribution",
41
+ "apple-search-ads",
41
42
  "conversion-tracking",
42
43
  "revenue-optimization",
43
44
  "deferred-deep-linking",
@@ -33,8 +33,9 @@ import { attributionManager, AttributionData } from './attribution';
33
33
  import { createAutoEventsManager, AutoEventsManager } from './auto-events';
34
34
  import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
35
35
  import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
36
- import { metaIntegration, tiktokIntegration } from './integrations';
36
+ import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
37
37
  import { DeferredDeepLinkResult } from './types';
38
+ import { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
38
39
 
39
40
  export class DatalyrSDKExpo {
40
41
  private state: SDKState;
@@ -176,6 +177,14 @@ export class DatalyrSDKExpo {
176
177
  }
177
178
  }
178
179
 
180
+ // Initialize Apple Search Ads attribution (iOS only, auto-fetches on init)
181
+ await appleSearchAdsIntegration.initialize(config.debug);
182
+ debugLog('Platform integrations initialized', {
183
+ meta: metaIntegration.isAvailable(),
184
+ tiktok: tiktokIntegration.isAvailable(),
185
+ appleSearchAds: appleSearchAdsIntegration.isAvailable(),
186
+ });
187
+
179
188
  this.state.initialized = true;
180
189
 
181
190
  if (attributionManager.isInstall()) {
@@ -636,13 +645,22 @@ export class DatalyrSDKExpo {
636
645
  return null;
637
646
  }
638
647
 
639
- getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean } {
648
+ getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean; appleSearchAds: boolean } {
640
649
  return {
641
650
  meta: metaIntegration.isAvailable(),
642
651
  tiktok: tiktokIntegration.isAvailable(),
652
+ appleSearchAds: appleSearchAdsIntegration.isAvailable(),
643
653
  };
644
654
  }
645
655
 
656
+ /**
657
+ * Get Apple Search Ads attribution data
658
+ * Returns attribution if user installed via Apple Search Ads, null otherwise
659
+ */
660
+ getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null {
661
+ return appleSearchAdsIntegration.getAttributionData();
662
+ }
663
+
646
664
  async updateTrackingAuthorization(authorized: boolean): Promise<void> {
647
665
  if (metaIntegration.isAvailable()) {
648
666
  metaIntegration.updateTrackingAuthorization(authorized);
@@ -694,6 +712,22 @@ export class DatalyrSDKExpo {
694
712
  const attributionData = attributionManager.getAttributionData();
695
713
  const networkType = await getNetworkType();
696
714
 
715
+ // Get Apple Search Ads attribution if available
716
+ const asaAttribution = appleSearchAdsIntegration.getAttributionData();
717
+ const asaData = asaAttribution?.attribution ? {
718
+ asa_campaign_id: asaAttribution.campaignId,
719
+ asa_campaign_name: asaAttribution.campaignName,
720
+ asa_ad_group_id: asaAttribution.adGroupId,
721
+ asa_ad_group_name: asaAttribution.adGroupName,
722
+ asa_keyword_id: asaAttribution.keywordId,
723
+ asa_keyword: asaAttribution.keyword,
724
+ asa_org_id: asaAttribution.orgId,
725
+ asa_org_name: asaAttribution.orgName,
726
+ asa_click_date: asaAttribution.clickDate,
727
+ asa_conversion_type: asaAttribution.conversionType,
728
+ asa_country_or_region: asaAttribution.countryOrRegion,
729
+ } : {};
730
+
697
731
  const payload: EventPayload = {
698
732
  workspaceId: this.state.config.workspaceId || 'mobile_sdk',
699
733
  visitorId: this.state.visitorId,
@@ -713,6 +747,8 @@ export class DatalyrSDKExpo {
713
747
  timestamp: Date.now(),
714
748
  sdk_variant: 'expo',
715
749
  ...attributionData,
750
+ // Apple Search Ads attribution
751
+ ...asaData,
716
752
  },
717
753
  fingerprintData,
718
754
  source: 'mobile_app',
@@ -945,10 +981,14 @@ export class DatalyrExpo {
945
981
  return datalyrExpo.getDeferredAttributionData();
946
982
  }
947
983
 
948
- static getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean } {
984
+ static getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean; appleSearchAds: boolean } {
949
985
  return datalyrExpo.getPlatformIntegrationStatus();
950
986
  }
951
987
 
988
+ static getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null {
989
+ return datalyrExpo.getAppleSearchAdsAttribution();
990
+ }
991
+
952
992
  static async updateTrackingAuthorization(authorized: boolean): Promise<void> {
953
993
  await datalyrExpo.updateTrackingAuthorization(authorized);
954
994
  }
@@ -30,7 +30,8 @@ import { attributionManager, AttributionData } from './attribution';
30
30
  import { createAutoEventsManager, AutoEventsManager, SessionData } from './auto-events';
31
31
  import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
32
32
  import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
33
- import { metaIntegration, tiktokIntegration } from './integrations';
33
+ import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
34
+ import { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
34
35
 
35
36
  export class DatalyrSDK {
36
37
  private state: SDKState;
@@ -181,9 +182,13 @@ export class DatalyrSDK {
181
182
  await tiktokIntegration.initialize(config.tiktok, config.debug);
182
183
  }
183
184
 
185
+ // Initialize Apple Search Ads attribution (iOS only, auto-fetches on init)
186
+ await appleSearchAdsIntegration.initialize(config.debug);
187
+
184
188
  debugLog('Platform integrations initialized', {
185
189
  meta: metaIntegration.isAvailable(),
186
190
  tiktok: tiktokIntegration.isAvailable(),
191
+ appleSearchAds: appleSearchAdsIntegration.isAvailable(),
187
192
  });
188
193
 
189
194
  // SDK initialized successfully - set state before tracking install event
@@ -832,13 +837,22 @@ export class DatalyrSDK {
832
837
  /**
833
838
  * Get platform integration status
834
839
  */
835
- getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean } {
840
+ getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean; appleSearchAds: boolean } {
836
841
  return {
837
842
  meta: metaIntegration.isAvailable(),
838
843
  tiktok: tiktokIntegration.isAvailable(),
844
+ appleSearchAds: appleSearchAdsIntegration.isAvailable(),
839
845
  };
840
846
  }
841
847
 
848
+ /**
849
+ * Get Apple Search Ads attribution data
850
+ * Returns attribution if user installed via Apple Search Ads, null otherwise
851
+ */
852
+ getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null {
853
+ return appleSearchAdsIntegration.getAttributionData();
854
+ }
855
+
842
856
  /**
843
857
  * Update tracking authorization status on all platform SDKs
844
858
  * Call this AFTER the user responds to the ATT permission dialog
@@ -919,6 +933,22 @@ export class DatalyrSDK {
919
933
  const fingerprintData = await createFingerprintData();
920
934
  const attributionData = attributionManager.getAttributionData();
921
935
 
936
+ // Get Apple Search Ads attribution if available
937
+ const asaAttribution = appleSearchAdsIntegration.getAttributionData();
938
+ const asaData = asaAttribution?.attribution ? {
939
+ asa_campaign_id: asaAttribution.campaignId,
940
+ asa_campaign_name: asaAttribution.campaignName,
941
+ asa_ad_group_id: asaAttribution.adGroupId,
942
+ asa_ad_group_name: asaAttribution.adGroupName,
943
+ asa_keyword_id: asaAttribution.keywordId,
944
+ asa_keyword: asaAttribution.keyword,
945
+ asa_org_id: asaAttribution.orgId,
946
+ asa_org_name: asaAttribution.orgName,
947
+ asa_click_date: asaAttribution.clickDate,
948
+ asa_conversion_type: asaAttribution.conversionType,
949
+ asa_country_or_region: asaAttribution.countryOrRegion,
950
+ } : {};
951
+
922
952
  const payload: EventPayload = {
923
953
  workspaceId: this.state.config.workspaceId || 'mobile_sdk',
924
954
  visitorId: this.state.visitorId,
@@ -940,6 +970,8 @@ export class DatalyrSDK {
940
970
  timestamp: Date.now(),
941
971
  // Attribution data
942
972
  ...attributionData,
973
+ // Apple Search Ads attribution
974
+ ...asaData,
943
975
  },
944
976
  fingerprintData,
945
977
  source: 'mobile_app',
@@ -1239,10 +1271,14 @@ export class Datalyr {
1239
1271
  return datalyr.getDeferredAttributionData();
1240
1272
  }
1241
1273
 
1242
- static getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean } {
1274
+ static getPlatformIntegrationStatus(): { meta: boolean; tiktok: boolean; appleSearchAds: boolean } {
1243
1275
  return datalyr.getPlatformIntegrationStatus();
1244
1276
  }
1245
1277
 
1278
+ static getAppleSearchAdsAttribution(): AppleSearchAdsAttribution | null {
1279
+ return datalyr.getAppleSearchAdsAttribution();
1280
+ }
1281
+
1246
1282
  static async updateTrackingAuthorization(enabled: boolean): Promise<void> {
1247
1283
  await datalyr.updateTrackingAuthorization(enabled);
1248
1284
  }
package/src/expo.ts CHANGED
@@ -52,5 +52,9 @@ export * from './event-queue';
52
52
  export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
53
53
  export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
54
54
 
55
+ // Export platform integrations
56
+ export { appleSearchAdsIntegration } from './integrations';
57
+ export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
58
+
55
59
  // Default export
56
60
  export default datalyrExpo;
package/src/index.ts CHANGED
@@ -27,7 +27,10 @@ export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEn
27
27
  export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
28
28
 
29
29
  // Export platform integrations
30
- export { metaIntegration, tiktokIntegration } from './integrations';
30
+ export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
31
+
32
+ // Export native bridge types
33
+ export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
31
34
 
32
35
  // Default export for compatibility
33
36
  export default DatalyrSDK;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Apple Search Ads Attribution Integration
3
+ * Uses AdServices framework (iOS 14.3+) to capture attribution from App Store search ads
4
+ */
5
+
6
+ import { Platform } from 'react-native';
7
+ import { AppleSearchAdsNativeBridge, AppleSearchAdsAttribution, isNativeModuleAvailable } from '../native/DatalyrNativeBridge';
8
+
9
+ /**
10
+ * Apple Search Ads Integration class
11
+ * Fetches attribution data for users who installed via Apple Search Ads
12
+ */
13
+ export class AppleSearchAdsIntegration {
14
+ private attributionData: AppleSearchAdsAttribution | null = null;
15
+ private fetched: boolean = false;
16
+ private available: boolean = false;
17
+ private debug: boolean = false;
18
+
19
+ /**
20
+ * Initialize and fetch Apple Search Ads attribution
21
+ */
22
+ async initialize(debug: boolean = false): Promise<void> {
23
+ this.debug = debug;
24
+
25
+ // Only available on iOS via native module
26
+ if (Platform.OS !== 'ios') {
27
+ this.log('Apple Search Ads only available on iOS');
28
+ return;
29
+ }
30
+
31
+ this.available = isNativeModuleAvailable();
32
+
33
+ if (!this.available) {
34
+ this.log('Apple Search Ads native module not available');
35
+ return;
36
+ }
37
+
38
+ // Automatically fetch attribution on init
39
+ await this.fetchAttribution();
40
+ }
41
+
42
+ /**
43
+ * Fetch attribution data from Apple's AdServices API
44
+ * Call this during app initialization
45
+ */
46
+ async fetchAttribution(): Promise<AppleSearchAdsAttribution | null> {
47
+ if (!this.available) {
48
+ return null;
49
+ }
50
+
51
+ // Only fetch once
52
+ if (this.fetched) {
53
+ return this.attributionData;
54
+ }
55
+
56
+ try {
57
+ this.attributionData = await AppleSearchAdsNativeBridge.getAttribution();
58
+ this.fetched = true;
59
+
60
+ if (this.attributionData?.attribution) {
61
+ this.log('Apple Search Ads attribution found:', {
62
+ campaignId: this.attributionData.campaignId,
63
+ campaignName: this.attributionData.campaignName,
64
+ adGroupId: this.attributionData.adGroupId,
65
+ keyword: this.attributionData.keyword,
66
+ });
67
+ } else {
68
+ this.log('No Apple Search Ads attribution (user did not come from search ad)');
69
+ }
70
+
71
+ return this.attributionData;
72
+ } catch (error) {
73
+ this.logError('Failed to fetch Apple Search Ads attribution:', error);
74
+ this.fetched = true;
75
+ return null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get cached attribution data
81
+ */
82
+ getAttributionData(): AppleSearchAdsAttribution | null {
83
+ return this.attributionData;
84
+ }
85
+
86
+ /**
87
+ * Check if user came from Apple Search Ads
88
+ */
89
+ hasAttribution(): boolean {
90
+ return this.attributionData?.attribution === true;
91
+ }
92
+
93
+ /**
94
+ * Check if Apple Search Ads is available (iOS 14.3+)
95
+ */
96
+ isAvailable(): boolean {
97
+ return this.available;
98
+ }
99
+
100
+ /**
101
+ * Check if attribution has been fetched
102
+ */
103
+ hasFetched(): boolean {
104
+ return this.fetched;
105
+ }
106
+
107
+ private log(message: string, data?: any): void {
108
+ if (this.debug) {
109
+ console.log(`[Datalyr/AppleSearchAds] ${message}`, data || '');
110
+ }
111
+ }
112
+
113
+ private logError(message: string, error: any): void {
114
+ console.error(`[Datalyr/AppleSearchAds] ${message}`, error);
115
+ }
116
+ }
117
+
118
+ // Export singleton instance
119
+ export const appleSearchAdsIntegration = new AppleSearchAdsIntegration();
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Platform SDK Integrations
3
- * Meta (Facebook) and TikTok SDK wrappers for deferred deep linking and event forwarding
3
+ * Meta (Facebook), TikTok, and Apple Search Ads SDK wrappers
4
4
  */
5
5
 
6
6
  export { MetaIntegration, metaIntegration } from './meta-integration';
7
7
  export { TikTokIntegration, tiktokIntegration } from './tiktok-integration';
8
+ export { AppleSearchAdsIntegration, appleSearchAdsIntegration } from './apple-search-ads-integration';
@@ -1,10 +1,28 @@
1
1
  /**
2
- * Native Bridge for Meta and TikTok SDKs
2
+ * Native Bridge for Meta, TikTok, and Apple Search Ads
3
3
  * Uses bundled native modules instead of separate npm packages
4
4
  */
5
5
 
6
6
  import { NativeModules, Platform } from 'react-native';
7
7
 
8
+ /**
9
+ * Apple Search Ads attribution data returned from AdServices API
10
+ */
11
+ export interface AppleSearchAdsAttribution {
12
+ attribution: boolean;
13
+ orgId?: number;
14
+ orgName?: string;
15
+ campaignId?: number;
16
+ campaignName?: string;
17
+ adGroupId?: number;
18
+ adGroupName?: string;
19
+ keywordId?: number;
20
+ keyword?: string;
21
+ clickDate?: string;
22
+ conversionType?: string;
23
+ countryOrRegion?: string;
24
+ }
25
+
8
26
  interface DatalyrNativeModule {
9
27
  // Meta SDK Methods
10
28
  initializeMetaSDK(
@@ -48,8 +66,11 @@ interface DatalyrNativeModule {
48
66
  logoutTikTok(): Promise<boolean>;
49
67
  updateTikTokTrackingAuthorization(enabled: boolean): Promise<boolean>;
50
68
 
69
+ // Apple Search Ads Methods
70
+ getAppleSearchAdsAttribution(): Promise<AppleSearchAdsAttribution | null>;
71
+
51
72
  // SDK Availability
52
- getSDKAvailability(): Promise<{ meta: boolean; tiktok: boolean }>;
73
+ getSDKAvailability(): Promise<{ meta: boolean; tiktok: boolean; appleSearchAds: boolean }>;
53
74
  }
54
75
 
55
76
  // Native module is only available on iOS
@@ -69,15 +90,16 @@ export const isNativeModuleAvailable = (): boolean => {
69
90
  export const getSDKAvailability = async (): Promise<{
70
91
  meta: boolean;
71
92
  tiktok: boolean;
93
+ appleSearchAds: boolean;
72
94
  }> => {
73
95
  if (!DatalyrNative) {
74
- return { meta: false, tiktok: false };
96
+ return { meta: false, tiktok: false, appleSearchAds: false };
75
97
  }
76
98
 
77
99
  try {
78
100
  return await DatalyrNative.getSDKAvailability();
79
101
  } catch {
80
- return { meta: false, tiktok: false };
102
+ return { meta: false, tiktok: false, appleSearchAds: false };
81
103
  }
82
104
  };
83
105
 
@@ -269,3 +291,23 @@ export const TikTokNativeBridge = {
269
291
  }
270
292
  },
271
293
  };
294
+
295
+ // MARK: - Apple Search Ads Bridge
296
+
297
+ export const AppleSearchAdsNativeBridge = {
298
+ /**
299
+ * Get Apple Search Ads attribution data
300
+ * Uses AdServices framework (iOS 14.3+)
301
+ * Returns null if user didn't come from Apple Search Ads or on older iOS
302
+ */
303
+ async getAttribution(): Promise<AppleSearchAdsAttribution | null> {
304
+ if (!DatalyrNative) return null;
305
+
306
+ try {
307
+ return await DatalyrNative.getAppleSearchAdsAttribution();
308
+ } catch (error) {
309
+ console.error('[Datalyr/AppleSearchAds] Get attribution failed:', error);
310
+ return null;
311
+ }
312
+ },
313
+ };