@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 +8 -0
- package/README.md +30 -1
- package/ios/DatalyrNative.m +4 -0
- package/ios/DatalyrNative.swift +58 -1
- package/lib/datalyr-sdk.d.ts +9 -0
- package/lib/datalyr-sdk.js +32 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -1
- package/lib/integrations/apple-search-ads-integration.d.ts +43 -0
- package/lib/integrations/apple-search-ads-integration.js +106 -0
- package/lib/integrations/index.d.ts +2 -1
- package/lib/integrations/index.js +2 -1
- package/lib/native/DatalyrNativeBridge.d.ts +27 -1
- package/lib/native/DatalyrNativeBridge.js +22 -3
- package/package.json +2 -1
- package/src/datalyr-sdk-expo.ts +43 -3
- package/src/datalyr-sdk.ts +39 -3
- package/src/expo.ts +4 -0
- package/src/index.ts +4 -1
- package/src/integrations/apple-search-ads-integration.ts +119 -0
- package/src/integrations/index.ts +2 -1
- package/src/native/DatalyrNativeBridge.ts +46 -4
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
|
---
|
package/ios/DatalyrNative.m
CHANGED
|
@@ -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
|
package/ios/DatalyrNative.swift
CHANGED
|
@@ -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
|
}
|
package/lib/datalyr-sdk.d.ts
CHANGED
|
@@ -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;
|
package/lib/datalyr-sdk.js
CHANGED
|
@@ -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)
|
|
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)
|
|
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
|
|
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
|
|
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.
|
|
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",
|
package/src/datalyr-sdk-expo.ts
CHANGED
|
@@ -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
|
}
|
package/src/datalyr-sdk.ts
CHANGED
|
@@ -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)
|
|
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
|
|
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
|
+
};
|