@datalyr/react-native 1.3.0 → 1.4.0

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,25 @@ 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.3.1] - 2026-01
9
+
10
+ ### Added
11
+ - **iOS 18.4+ Features** - Geo-level postbacks, development postbacks, overlapping windows
12
+ - **Privacy Manifest** (`ios/PrivacyInfo.xcprivacy`) - Required for App Store compliance
13
+ - **Network Status Detection** - Automatic online/offline handling with queue sync
14
+ - `SKAdNetworkBridge` iOS 18.4+ methods:
15
+ - `isGeoPostbackAvailable()` - Check for geo-level postback support
16
+ - `setPostbackEnvironment()` - Configure sandbox/production mode
17
+ - `getEnhancedAttributionInfo()` - Full feature matrix by iOS version
18
+ - `updatePostbackWithWindow()` - Overlapping window support
19
+ - `enableDevelopmentMode()` / `disableDevelopmentMode()` - Convenience methods
20
+ - Migration guides from AppsFlyer and Adjust
21
+ - Comprehensive troubleshooting section in README
22
+
23
+ ### Changed
24
+ - Parallel SDK initialization for faster startup
25
+ - Enhanced TypeScript types for iOS 18.4+ responses
26
+
8
27
  ## [1.2.1] - 2025-01
9
28
 
10
29
  ### Added
package/README.md CHANGED
@@ -553,24 +553,160 @@ import {
553
553
 
554
554
  ---
555
555
 
556
+ ## Migrating from AppsFlyer / Adjust
557
+
558
+ Datalyr provides similar functionality with a simpler integration.
559
+
560
+ ### From AppsFlyer
561
+
562
+ ```typescript
563
+ // BEFORE: AppsFlyer
564
+ import appsFlyer from 'react-native-appsflyer';
565
+ appsFlyer.logEvent('af_purchase', { af_revenue: 99.99, af_currency: 'USD' });
566
+
567
+ // AFTER: Datalyr
568
+ import { Datalyr } from '@datalyr/react-native';
569
+ await Datalyr.trackPurchase(99.99, 'USD', 'product_id');
570
+ ```
571
+
572
+ ### From Adjust
573
+
574
+ ```typescript
575
+ // BEFORE: Adjust
576
+ import { Adjust, AdjustEvent } from 'react-native-adjust';
577
+ const event = new AdjustEvent('abc123');
578
+ event.setRevenue(99.99, 'USD');
579
+ Adjust.trackEvent(event);
580
+
581
+ // AFTER: Datalyr
582
+ import { Datalyr } from '@datalyr/react-native';
583
+ await Datalyr.trackPurchase(99.99, 'USD');
584
+ ```
585
+
586
+ ### Event Mapping
587
+
588
+ | AppsFlyer | Adjust | Datalyr |
589
+ |-----------|--------|---------|
590
+ | `af_purchase` | `PURCHASE` | `trackPurchase()` |
591
+ | `af_add_to_cart` | `ADD_TO_CART` | `trackAddToCart()` |
592
+ | `af_initiated_checkout` | `INITIATE_CHECKOUT` | `trackInitiateCheckout()` |
593
+ | `af_complete_registration` | `COMPLETE_REGISTRATION` | `trackCompleteRegistration()` |
594
+ | `af_content_view` | `VIEW_CONTENT` | `trackViewContent()` |
595
+ | `af_subscribe` | `SUBSCRIBE` | `trackSubscription()` |
596
+
597
+ ### Migration Checklist
598
+
599
+ - [ ] Remove old SDK: `npm uninstall react-native-appsflyer`
600
+ - [ ] Install Datalyr: `npm install @datalyr/react-native`
601
+ - [ ] Run `cd ios && pod install`
602
+ - [ ] Replace initialization and event tracking code
603
+ - [ ] Verify events in Datalyr dashboard
604
+
605
+ ---
606
+
556
607
  ## Troubleshooting
557
608
 
558
- ### Events not appearing
609
+ ### Events Not Appearing
610
+
611
+ **1. Check SDK Status**
612
+ ```typescript
613
+ const status = Datalyr.getStatus();
614
+ console.log('Initialized:', status.initialized);
615
+ console.log('Queue size:', status.queueStats.queueSize);
616
+ console.log('Online:', status.queueStats.isOnline);
617
+ ```
618
+
619
+ **2. Enable Debug Mode**
620
+ ```typescript
621
+ await Datalyr.initialize({
622
+ apiKey: 'dk_your_api_key',
623
+ debug: true,
624
+ });
625
+ ```
626
+
627
+ **3. Force Flush**
628
+ ```typescript
629
+ await Datalyr.flush();
630
+ ```
631
+
632
+ **4. Verify API Key** - Should start with `dk_`
559
633
 
560
- 1. Check API key starts with `dk_`
561
- 2. Enable `debug: true`
562
- 3. Check `Datalyr.getStatus()` for queue info
563
- 4. Verify network connectivity
634
+ ### iOS Build Errors
564
635
 
565
- ### iOS build errors
636
+ ```bash
637
+ cd ios
638
+ pod deintegrate
639
+ pod cache clean --all
640
+ pod install
641
+ ```
566
642
 
643
+ **Clean Reset**
567
644
  ```bash
568
- cd ios && pod deintegrate && pod install
645
+ rm -rf node_modules ios/Pods ios/Podfile.lock
646
+ npm install && cd ios && pod install
647
+ ```
648
+
649
+ ### Android Build Errors
650
+
651
+ ```bash
652
+ cd android && ./gradlew clean
653
+ npx react-native run-android
654
+ ```
655
+
656
+ ### Meta SDK Not Working
657
+
658
+ Verify Info.plist:
659
+ ```xml
660
+ <key>FacebookAppID</key>
661
+ <string>YOUR_APP_ID</string>
662
+ <key>FacebookClientToken</key>
663
+ <string>YOUR_CLIENT_TOKEN</string>
664
+ ```
665
+
666
+ Check status: `Datalyr.getPlatformIntegrationStatus()`
667
+
668
+ ### TikTok SDK Not Working
669
+
670
+ ```typescript
671
+ await Datalyr.initialize({
672
+ apiKey: 'dk_your_api_key',
673
+ tiktok: {
674
+ appId: 'your_app_id',
675
+ tiktokAppId: '7123456789012345',
676
+ },
677
+ });
678
+ ```
679
+
680
+ ### SKAdNetwork Not Updating
681
+
682
+ 1. iOS 14.0+ required (16.1+ for SKAN 4.0)
683
+ 2. Set `skadTemplate` in config
684
+ 3. Use `trackWithSKAdNetwork()` instead of `track()`
685
+
686
+ ### Attribution Not Captured
687
+
688
+ ```typescript
689
+ await Datalyr.initialize({
690
+ apiKey: 'dk_your_api_key',
691
+ enableAttribution: true,
692
+ });
693
+
694
+ // Check data
695
+ const attribution = Datalyr.getAttributionData();
696
+ ```
697
+
698
+ ### App Tracking Transparency (iOS 14.5+)
699
+
700
+ ```typescript
701
+ import { requestTrackingPermissionsAsync } from 'expo-tracking-transparency';
702
+
703
+ const { status } = await requestTrackingPermissionsAsync();
704
+ await Datalyr.updateTrackingAuthorization(status === 'granted');
569
705
  ```
570
706
 
571
- ### Meta/TikTok not working
707
+ ### Debug Logging
572
708
 
573
- Verify Info.plist contains required keys (see Installation).
709
+ Look for `[Datalyr]` prefixed messages in console.
574
710
 
575
711
  ---
576
712
 
@@ -19,7 +19,7 @@ Pod::Spec.new do |s|
19
19
  s.source_files = "ios/**/*.{h,m,swift}"
20
20
  s.swift_version = "5.0"
21
21
 
22
- s.dependency "React-Core"
22
+ s.dependency "ExpoModulesCore"
23
23
  s.dependency "FBSDKCoreKit", "~> 18.0"
24
24
  s.dependency "TikTokBusinessSDK", "~> 1.6"
25
25
 
@@ -0,0 +1,6 @@
1
+ {
2
+ "platforms": ["ios"],
3
+ "ios": {
4
+ "modules": ["DatalyrNativeModule", "DatalyrSKAdNetworkModule"]
5
+ }
6
+ }
@@ -0,0 +1,221 @@
1
+ import ExpoModulesCore
2
+ import FBSDKCoreKit
3
+ import TikTokBusinessSDK
4
+ import AdServices
5
+
6
+ public class DatalyrNativeModule: Module {
7
+ public func definition() -> ModuleDefinition {
8
+ Name("DatalyrNative")
9
+
10
+ // MARK: - Meta (Facebook) SDK Methods
11
+
12
+ AsyncFunction("initializeMetaSDK") { (appId: String, clientToken: String?, advertiserTrackingEnabled: Bool, promise: Promise) in
13
+ DispatchQueue.main.async {
14
+ Settings.shared.appID = appId
15
+
16
+ if let token = clientToken, !token.isEmpty {
17
+ Settings.shared.clientToken = token
18
+ }
19
+
20
+ Settings.shared.isAdvertiserTrackingEnabled = advertiserTrackingEnabled
21
+ Settings.shared.isAdvertiserIDCollectionEnabled = advertiserTrackingEnabled
22
+
23
+ ApplicationDelegate.shared.application(
24
+ UIApplication.shared,
25
+ didFinishLaunchingWithOptions: nil
26
+ )
27
+
28
+ promise.resolve(true)
29
+ }
30
+ }
31
+
32
+ AsyncFunction("fetchDeferredAppLink") { (promise: Promise) in
33
+ AppLinkUtility.fetchDeferredAppLink { url, error in
34
+ if error != nil {
35
+ promise.resolve(nil)
36
+ return
37
+ }
38
+
39
+ if let url = url {
40
+ promise.resolve(url.absoluteString)
41
+ } else {
42
+ promise.resolve(nil)
43
+ }
44
+ }
45
+ }
46
+
47
+ AsyncFunction("logMetaEvent") { (eventName: String, valueToSum: Double?, parameters: [String: Any]?, promise: Promise) in
48
+ var params: [AppEvents.ParameterName: Any] = [:]
49
+
50
+ if let dict = parameters {
51
+ for (key, value) in dict {
52
+ params[AppEvents.ParameterName(key)] = value
53
+ }
54
+ }
55
+
56
+ if let value = valueToSum {
57
+ AppEvents.shared.logEvent(AppEvents.Name(eventName), valueToSum: value, parameters: params)
58
+ } else if params.isEmpty {
59
+ AppEvents.shared.logEvent(AppEvents.Name(eventName))
60
+ } else {
61
+ AppEvents.shared.logEvent(AppEvents.Name(eventName), parameters: params)
62
+ }
63
+
64
+ promise.resolve(true)
65
+ }
66
+
67
+ AsyncFunction("logMetaPurchase") { (amount: Double, currency: String, parameters: [String: Any]?, promise: Promise) in
68
+ var params: [AppEvents.ParameterName: Any] = [:]
69
+
70
+ if let dict = parameters {
71
+ for (key, value) in dict {
72
+ params[AppEvents.ParameterName(key)] = value
73
+ }
74
+ }
75
+
76
+ AppEvents.shared.logPurchase(amount: amount, currency: currency, parameters: params)
77
+ promise.resolve(true)
78
+ }
79
+
80
+ AsyncFunction("setMetaUserData") { (userData: [String: Any], promise: Promise) in
81
+ AppEvents.shared.setUserData(userData["email"] as? String, forType: .email)
82
+ AppEvents.shared.setUserData(userData["firstName"] as? String, forType: .firstName)
83
+ AppEvents.shared.setUserData(userData["lastName"] as? String, forType: .lastName)
84
+ AppEvents.shared.setUserData(userData["phone"] as? String, forType: .phone)
85
+ AppEvents.shared.setUserData(userData["dateOfBirth"] as? String, forType: .dateOfBirth)
86
+ AppEvents.shared.setUserData(userData["gender"] as? String, forType: .gender)
87
+ AppEvents.shared.setUserData(userData["city"] as? String, forType: .city)
88
+ AppEvents.shared.setUserData(userData["state"] as? String, forType: .state)
89
+ AppEvents.shared.setUserData(userData["zip"] as? String, forType: .zip)
90
+ AppEvents.shared.setUserData(userData["country"] as? String, forType: .country)
91
+
92
+ promise.resolve(true)
93
+ }
94
+
95
+ AsyncFunction("clearMetaUserData") { (promise: Promise) in
96
+ AppEvents.shared.clearUserData()
97
+ promise.resolve(true)
98
+ }
99
+
100
+ AsyncFunction("updateMetaTrackingAuthorization") { (enabled: Bool, promise: Promise) in
101
+ Settings.shared.isAdvertiserTrackingEnabled = enabled
102
+ Settings.shared.isAdvertiserIDCollectionEnabled = enabled
103
+ promise.resolve(true)
104
+ }
105
+
106
+ // MARK: - TikTok SDK Methods
107
+
108
+ AsyncFunction("initializeTikTokSDK") { (appId: String, tiktokAppId: String, accessToken: String?, debug: Bool, promise: Promise) in
109
+ DispatchQueue.main.async {
110
+ let config = TikTokConfig(appId: appId, tiktokAppId: tiktokAppId)
111
+
112
+ if let token = accessToken, !token.isEmpty {
113
+ config?.accessToken = token
114
+ }
115
+
116
+ if debug {
117
+ config?.setLogLevel(.debug)
118
+ }
119
+
120
+ if let validConfig = config {
121
+ TikTokBusiness.initializeSdk(validConfig)
122
+ promise.resolve(true)
123
+ } else {
124
+ promise.reject("tiktok_init_error", "Failed to create TikTok config")
125
+ }
126
+ }
127
+ }
128
+
129
+ AsyncFunction("trackTikTokEvent") { (eventName: String, eventId: String?, properties: [String: Any]?, promise: Promise) in
130
+ let event: TikTokBaseEvent
131
+
132
+ if let eid = eventId, !eid.isEmpty {
133
+ event = TikTokBaseEvent(eventName: eventName, eventId: eid)
134
+ } else {
135
+ event = TikTokBaseEvent(eventName: eventName)
136
+ }
137
+
138
+ if let dict = properties {
139
+ for (key, value) in dict {
140
+ event.addProperty(withKey: key, value: value)
141
+ }
142
+ }
143
+
144
+ TikTokBusiness.trackTTEvent(event)
145
+ promise.resolve(true)
146
+ }
147
+
148
+ AsyncFunction("identifyTikTokUser") { (externalId: String, externalUserName: String, phoneNumber: String, email: String, promise: Promise) in
149
+ TikTokBusiness.identify(
150
+ withExternalID: externalId.isEmpty ? nil : externalId,
151
+ externalUserName: externalUserName.isEmpty ? nil : externalUserName,
152
+ phoneNumber: phoneNumber.isEmpty ? nil : phoneNumber,
153
+ email: email.isEmpty ? nil : email
154
+ )
155
+ promise.resolve(true)
156
+ }
157
+
158
+ AsyncFunction("logoutTikTok") { (promise: Promise) in
159
+ TikTokBusiness.logout()
160
+ promise.resolve(true)
161
+ }
162
+
163
+ AsyncFunction("updateTikTokTrackingAuthorization") { (enabled: Bool, promise: Promise) in
164
+ // TikTok SDK handles ATT automatically, but we track the change
165
+ promise.resolve(true)
166
+ }
167
+
168
+ // MARK: - SDK Availability Check
169
+
170
+ AsyncFunction("getSDKAvailability") { (promise: Promise) in
171
+ promise.resolve([
172
+ "meta": true,
173
+ "tiktok": true,
174
+ "appleSearchAds": true
175
+ ])
176
+ }
177
+
178
+ // MARK: - Apple Search Ads Attribution
179
+
180
+ AsyncFunction("getAppleSearchAdsAttribution") { (promise: Promise) in
181
+ if #available(iOS 14.3, *) {
182
+ do {
183
+ let token = try AAAttribution.attributionToken()
184
+
185
+ var request = URLRequest(url: URL(string: "https://api-adservices.apple.com/api/v1/")!)
186
+ request.httpMethod = "POST"
187
+ request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
188
+ request.httpBody = token.data(using: .utf8)
189
+
190
+ let task = URLSession.shared.dataTask(with: request) { data, response, error in
191
+ if error != nil {
192
+ promise.resolve(nil)
193
+ return
194
+ }
195
+
196
+ guard let data = data else {
197
+ promise.resolve(nil)
198
+ return
199
+ }
200
+
201
+ do {
202
+ if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
203
+ promise.resolve(json)
204
+ } else {
205
+ promise.resolve(nil)
206
+ }
207
+ } catch {
208
+ promise.resolve(nil)
209
+ }
210
+ }
211
+ task.resume()
212
+
213
+ } catch {
214
+ promise.resolve(nil)
215
+ }
216
+ } else {
217
+ promise.resolve(nil)
218
+ }
219
+ }
220
+ }
221
+ }