@datalyr/react-native 1.3.0 → 1.3.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 +19 -0
- package/README.md +145 -9
- package/ios/DatalyrSKAdNetwork.m +351 -3
- package/ios/PrivacyInfo.xcprivacy +48 -0
- package/lib/datalyr-sdk.d.ts +6 -0
- package/lib/datalyr-sdk.js +84 -27
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -1
- package/lib/integrations/play-install-referrer.d.ts +5 -1
- package/lib/integrations/play-install-referrer.js +14 -4
- package/lib/native/SKAdNetworkBridge.d.ts +121 -0
- package/lib/native/SKAdNetworkBridge.js +276 -2
- package/lib/network-status.d.ts +84 -0
- package/lib/network-status.js +281 -0
- package/lib/types.d.ts +51 -0
- package/lib/utils.d.ts +6 -1
- package/lib/utils.js +52 -2
- package/package.json +6 -2
- package/src/datalyr-sdk.ts +96 -32
- package/src/index.ts +5 -1
- package/src/integrations/play-install-referrer.ts +19 -4
- package/src/native/SKAdNetworkBridge.ts +400 -5
- package/src/network-status.ts +312 -0
- package/src/types.ts +74 -6
- package/src/utils.ts +62 -6
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
707
|
+
### Debug Logging
|
|
572
708
|
|
|
573
|
-
|
|
709
|
+
Look for `[Datalyr]` prefixed messages in console.
|
|
574
710
|
|
|
575
711
|
---
|
|
576
712
|
|
package/ios/DatalyrSKAdNetwork.m
CHANGED
|
@@ -24,12 +24,19 @@ RCT_EXPORT_METHOD(updateConversionValue:(NSInteger)value
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// SKAN 4.0 -
|
|
27
|
+
// SKAN 4.0 / AdAttributionKit - Method for iOS 16.1+ with coarse value and lock window support
|
|
28
|
+
// On iOS 17.4+, this uses AdAttributionKit under the hood
|
|
28
29
|
RCT_EXPORT_METHOD(updatePostbackConversionValue:(NSInteger)fineValue
|
|
29
30
|
coarseValue:(NSString *)coarseValue
|
|
30
31
|
lockWindow:(BOOL)lockWindow
|
|
31
32
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
32
33
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
34
|
+
// Validate fine value range
|
|
35
|
+
if (fineValue < 0 || fineValue > 63) {
|
|
36
|
+
reject(@"invalid_value", @"Conversion value must be between 0 and 63", nil);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
if (@available(iOS 16.1, *)) {
|
|
34
41
|
// Convert string to SKAdNetwork.CoarseConversionValue
|
|
35
42
|
SKAdNetworkCoarseConversionValue coarse;
|
|
@@ -48,14 +55,31 @@ RCT_EXPORT_METHOD(updatePostbackConversionValue:(NSInteger)fineValue
|
|
|
48
55
|
if (error) {
|
|
49
56
|
reject(@"skadnetwork_error", error.localizedDescription, error);
|
|
50
57
|
} else {
|
|
51
|
-
|
|
58
|
+
// Return framework info along with success
|
|
59
|
+
NSString *framework = @"SKAdNetwork";
|
|
60
|
+
if (@available(iOS 17.4, *)) {
|
|
61
|
+
framework = @"AdAttributionKit";
|
|
62
|
+
}
|
|
63
|
+
resolve(@{
|
|
64
|
+
@"success": @(YES),
|
|
65
|
+
@"framework": framework,
|
|
66
|
+
@"fineValue": @(fineValue),
|
|
67
|
+
@"coarseValue": coarseValue,
|
|
68
|
+
@"lockWindow": @(lockWindow)
|
|
69
|
+
});
|
|
52
70
|
}
|
|
53
71
|
}];
|
|
54
72
|
} else if (@available(iOS 14.0, *)) {
|
|
55
73
|
// Fallback to SKAN 3.0 for iOS 14.0-16.0
|
|
56
74
|
@try {
|
|
57
75
|
[SKAdNetwork updateConversionValue:fineValue];
|
|
58
|
-
resolve(@
|
|
76
|
+
resolve(@{
|
|
77
|
+
@"success": @(YES),
|
|
78
|
+
@"framework": @"SKAdNetwork",
|
|
79
|
+
@"fineValue": @(fineValue),
|
|
80
|
+
@"coarseValue": @"n/a",
|
|
81
|
+
@"lockWindow": @(NO)
|
|
82
|
+
});
|
|
59
83
|
} @catch (NSException *exception) {
|
|
60
84
|
reject(@"skadnetwork_error", exception.reason, nil);
|
|
61
85
|
}
|
|
@@ -74,4 +98,328 @@ RCT_EXPORT_METHOD(isSKAN4Available:(RCTPromiseResolveBlock)resolve
|
|
|
74
98
|
}
|
|
75
99
|
}
|
|
76
100
|
|
|
101
|
+
// Check if AdAttributionKit is available (iOS 17.4+)
|
|
102
|
+
RCT_EXPORT_METHOD(isAdAttributionKitAvailable:(RCTPromiseResolveBlock)resolve
|
|
103
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
104
|
+
if (@available(iOS 17.4, *)) {
|
|
105
|
+
resolve(@(YES));
|
|
106
|
+
} else {
|
|
107
|
+
resolve(@(NO));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check if overlapping windows are available (iOS 18.4+)
|
|
112
|
+
RCT_EXPORT_METHOD(isOverlappingWindowsAvailable:(RCTPromiseResolveBlock)resolve
|
|
113
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
114
|
+
if (@available(iOS 18.4, *)) {
|
|
115
|
+
resolve(@(YES));
|
|
116
|
+
} else {
|
|
117
|
+
resolve(@(NO));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Register for ad network attribution (supports both AdAttributionKit and SKAdNetwork)
|
|
122
|
+
RCT_EXPORT_METHOD(registerForAttribution:(RCTPromiseResolveBlock)resolve
|
|
123
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
124
|
+
if (@available(iOS 17.4, *)) {
|
|
125
|
+
// AdAttributionKit registration via initial conversion value update
|
|
126
|
+
[SKAdNetwork updatePostbackConversionValue:0
|
|
127
|
+
coarseValue:SKAdNetworkCoarseConversionValueLow
|
|
128
|
+
lockWindow:NO
|
|
129
|
+
completionHandler:^(NSError * _Nullable error) {
|
|
130
|
+
if (error) {
|
|
131
|
+
reject(@"attribution_error", error.localizedDescription, error);
|
|
132
|
+
} else {
|
|
133
|
+
resolve(@{@"framework": @"AdAttributionKit", @"registered": @(YES)});
|
|
134
|
+
}
|
|
135
|
+
}];
|
|
136
|
+
} else if (@available(iOS 14.0, *)) {
|
|
137
|
+
// Legacy SKAdNetwork registration
|
|
138
|
+
[SKAdNetwork registerAppForAdNetworkAttribution];
|
|
139
|
+
resolve(@{@"framework": @"SKAdNetwork", @"registered": @(YES)});
|
|
140
|
+
} else {
|
|
141
|
+
reject(@"ios_version_error", @"Attribution requires iOS 14.0+", nil);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Get attribution framework info
|
|
146
|
+
RCT_EXPORT_METHOD(getAttributionInfo:(RCTPromiseResolveBlock)resolve
|
|
147
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
148
|
+
NSMutableDictionary *info = [NSMutableDictionary dictionary];
|
|
149
|
+
|
|
150
|
+
if (@available(iOS 17.4, *)) {
|
|
151
|
+
info[@"framework"] = @"AdAttributionKit";
|
|
152
|
+
info[@"version"] = @"1.0";
|
|
153
|
+
info[@"reengagement_available"] = @(YES);
|
|
154
|
+
info[@"fine_value_range"] = @{@"min": @0, @"max": @63};
|
|
155
|
+
info[@"coarse_values"] = @[@"low", @"medium", @"high"];
|
|
156
|
+
if (@available(iOS 18.4, *)) {
|
|
157
|
+
info[@"overlapping_windows"] = @(YES);
|
|
158
|
+
} else {
|
|
159
|
+
info[@"overlapping_windows"] = @(NO);
|
|
160
|
+
}
|
|
161
|
+
} else if (@available(iOS 16.1, *)) {
|
|
162
|
+
info[@"framework"] = @"SKAdNetwork";
|
|
163
|
+
info[@"version"] = @"4.0";
|
|
164
|
+
info[@"reengagement_available"] = @(NO);
|
|
165
|
+
info[@"overlapping_windows"] = @(NO);
|
|
166
|
+
info[@"fine_value_range"] = @{@"min": @0, @"max": @63};
|
|
167
|
+
info[@"coarse_values"] = @[@"low", @"medium", @"high"];
|
|
168
|
+
} else if (@available(iOS 14.0, *)) {
|
|
169
|
+
info[@"framework"] = @"SKAdNetwork";
|
|
170
|
+
info[@"version"] = @"3.0";
|
|
171
|
+
info[@"reengagement_available"] = @(NO);
|
|
172
|
+
info[@"overlapping_windows"] = @(NO);
|
|
173
|
+
info[@"fine_value_range"] = @{@"min": @0, @"max": @63};
|
|
174
|
+
info[@"coarse_values"] = @[];
|
|
175
|
+
} else {
|
|
176
|
+
info[@"framework"] = @"none";
|
|
177
|
+
info[@"version"] = @"0";
|
|
178
|
+
info[@"reengagement_available"] = @(NO);
|
|
179
|
+
info[@"overlapping_windows"] = @(NO);
|
|
180
|
+
info[@"fine_value_range"] = @{@"min": @0, @"max": @0};
|
|
181
|
+
info[@"coarse_values"] = @[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
resolve(info);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Update conversion value for re-engagement (AdAttributionKit iOS 17.4+ only)
|
|
188
|
+
// Re-engagement tracks users who return to the app via an ad after initial install
|
|
189
|
+
RCT_EXPORT_METHOD(updateReengagementConversionValue:(NSInteger)fineValue
|
|
190
|
+
coarseValue:(NSString *)coarseValue
|
|
191
|
+
lockWindow:(BOOL)lockWindow
|
|
192
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
193
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
194
|
+
if (@available(iOS 17.4, *)) {
|
|
195
|
+
// Validate fine value range
|
|
196
|
+
if (fineValue < 0 || fineValue > 63) {
|
|
197
|
+
reject(@"invalid_value", @"Conversion value must be between 0 and 63", nil);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Convert string to SKAdNetwork.CoarseConversionValue
|
|
202
|
+
SKAdNetworkCoarseConversionValue coarse;
|
|
203
|
+
if ([coarseValue isEqualToString:@"high"]) {
|
|
204
|
+
coarse = SKAdNetworkCoarseConversionValueHigh;
|
|
205
|
+
} else if ([coarseValue isEqualToString:@"medium"]) {
|
|
206
|
+
coarse = SKAdNetworkCoarseConversionValueMedium;
|
|
207
|
+
} else {
|
|
208
|
+
coarse = SKAdNetworkCoarseConversionValueLow;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// In AdAttributionKit, re-engagement uses the same API
|
|
212
|
+
// The framework distinguishes based on user attribution state
|
|
213
|
+
[SKAdNetwork updatePostbackConversionValue:fineValue
|
|
214
|
+
coarseValue:coarse
|
|
215
|
+
lockWindow:lockWindow
|
|
216
|
+
completionHandler:^(NSError * _Nullable error) {
|
|
217
|
+
if (error) {
|
|
218
|
+
reject(@"reengagement_error", error.localizedDescription, error);
|
|
219
|
+
} else {
|
|
220
|
+
resolve(@{
|
|
221
|
+
@"success": @(YES),
|
|
222
|
+
@"type": @"reengagement",
|
|
223
|
+
@"framework": @"AdAttributionKit",
|
|
224
|
+
@"fineValue": @(fineValue),
|
|
225
|
+
@"coarseValue": coarseValue,
|
|
226
|
+
@"lockWindow": @(lockWindow)
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}];
|
|
230
|
+
} else {
|
|
231
|
+
reject(@"unsupported", @"Re-engagement attribution requires iOS 17.4+ (AdAttributionKit)", nil);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// iOS 18.4+ - Check if geo-level postback data is available
|
|
236
|
+
RCT_EXPORT_METHOD(isGeoPostbackAvailable:(RCTPromiseResolveBlock)resolve
|
|
237
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
238
|
+
if (@available(iOS 18.4, *)) {
|
|
239
|
+
resolve(@(YES));
|
|
240
|
+
} else {
|
|
241
|
+
resolve(@(NO));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// iOS 18.4+ - Set postback environment for testing
|
|
246
|
+
// environment: "production" or "sandbox"
|
|
247
|
+
RCT_EXPORT_METHOD(setPostbackEnvironment:(NSString *)environment
|
|
248
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
249
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
250
|
+
if (@available(iOS 18.4, *)) {
|
|
251
|
+
// Note: In iOS 18.4+, development postbacks are controlled via
|
|
252
|
+
// the developer mode setting in device settings, not programmatically.
|
|
253
|
+
// This method validates the environment string and logs for debugging.
|
|
254
|
+
BOOL isSandbox = [environment isEqualToString:@"sandbox"];
|
|
255
|
+
NSLog(@"[Datalyr] Postback environment set to: %@ (note: actual sandbox mode is controlled via device Developer Mode)", environment);
|
|
256
|
+
resolve(@{
|
|
257
|
+
@"environment": environment,
|
|
258
|
+
@"isSandbox": @(isSandbox),
|
|
259
|
+
@"note": @"Enable Developer Mode in iOS Settings for sandbox postbacks"
|
|
260
|
+
});
|
|
261
|
+
} else {
|
|
262
|
+
reject(@"unsupported", @"Development postbacks require iOS 18.4+", nil);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// iOS 18.4+ - Get enhanced attribution info including geo availability
|
|
267
|
+
RCT_EXPORT_METHOD(getEnhancedAttributionInfo:(RCTPromiseResolveBlock)resolve
|
|
268
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
269
|
+
NSMutableDictionary *info = [NSMutableDictionary dictionary];
|
|
270
|
+
|
|
271
|
+
if (@available(iOS 18.4, *)) {
|
|
272
|
+
info[@"framework"] = @"AdAttributionKit";
|
|
273
|
+
info[@"version"] = @"2.0";
|
|
274
|
+
info[@"reengagement_available"] = @(YES);
|
|
275
|
+
info[@"overlapping_windows"] = @(YES);
|
|
276
|
+
info[@"geo_postback_available"] = @(YES);
|
|
277
|
+
info[@"development_postbacks"] = @(YES);
|
|
278
|
+
info[@"fine_value_range"] = @{@"min": @0, @"max": @63};
|
|
279
|
+
info[@"coarse_values"] = @[@"low", @"medium", @"high"];
|
|
280
|
+
info[@"features"] = @[
|
|
281
|
+
@"overlapping_windows",
|
|
282
|
+
@"geo_level_postbacks",
|
|
283
|
+
@"development_postbacks",
|
|
284
|
+
@"reengagement"
|
|
285
|
+
];
|
|
286
|
+
} else if (@available(iOS 17.4, *)) {
|
|
287
|
+
info[@"framework"] = @"AdAttributionKit";
|
|
288
|
+
info[@"version"] = @"1.0";
|
|
289
|
+
info[@"reengagement_available"] = @(YES);
|
|
290
|
+
info[@"overlapping_windows"] = @(NO);
|
|
291
|
+
info[@"geo_postback_available"] = @(NO);
|
|
292
|
+
info[@"development_postbacks"] = @(NO);
|
|
293
|
+
info[@"fine_value_range"] = @{@"min": @0, @"max": @63};
|
|
294
|
+
info[@"coarse_values"] = @[@"low", @"medium", @"high"];
|
|
295
|
+
info[@"features"] = @[@"reengagement"];
|
|
296
|
+
} else if (@available(iOS 16.1, *)) {
|
|
297
|
+
info[@"framework"] = @"SKAdNetwork";
|
|
298
|
+
info[@"version"] = @"4.0";
|
|
299
|
+
info[@"reengagement_available"] = @(NO);
|
|
300
|
+
info[@"overlapping_windows"] = @(NO);
|
|
301
|
+
info[@"geo_postback_available"] = @(NO);
|
|
302
|
+
info[@"development_postbacks"] = @(NO);
|
|
303
|
+
info[@"fine_value_range"] = @{@"min": @0, @"max": @63};
|
|
304
|
+
info[@"coarse_values"] = @[@"low", @"medium", @"high"];
|
|
305
|
+
info[@"features"] = @[];
|
|
306
|
+
} else if (@available(iOS 14.0, *)) {
|
|
307
|
+
info[@"framework"] = @"SKAdNetwork";
|
|
308
|
+
info[@"version"] = @"3.0";
|
|
309
|
+
info[@"reengagement_available"] = @(NO);
|
|
310
|
+
info[@"overlapping_windows"] = @(NO);
|
|
311
|
+
info[@"geo_postback_available"] = @(NO);
|
|
312
|
+
info[@"development_postbacks"] = @(NO);
|
|
313
|
+
info[@"fine_value_range"] = @{@"min": @0, @"max": @63};
|
|
314
|
+
info[@"coarse_values"] = @[];
|
|
315
|
+
info[@"features"] = @[];
|
|
316
|
+
} else {
|
|
317
|
+
info[@"framework"] = @"none";
|
|
318
|
+
info[@"version"] = @"0";
|
|
319
|
+
info[@"reengagement_available"] = @(NO);
|
|
320
|
+
info[@"overlapping_windows"] = @(NO);
|
|
321
|
+
info[@"geo_postback_available"] = @(NO);
|
|
322
|
+
info[@"development_postbacks"] = @(NO);
|
|
323
|
+
info[@"fine_value_range"] = @{@"min": @0, @"max": @0};
|
|
324
|
+
info[@"coarse_values"] = @[];
|
|
325
|
+
info[@"features"] = @[];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
resolve(info);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// iOS 18.4+ - Update postback with overlapping window support
|
|
332
|
+
// windowIndex: 0 = first window (0-2 days), 1 = second window (3-7 days), 2 = third window (8-35 days)
|
|
333
|
+
RCT_EXPORT_METHOD(updatePostbackWithWindow:(NSInteger)fineValue
|
|
334
|
+
coarseValue:(NSString *)coarseValue
|
|
335
|
+
lockWindow:(BOOL)lockWindow
|
|
336
|
+
windowIndex:(NSInteger)windowIndex
|
|
337
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
338
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
339
|
+
// Validate fine value range
|
|
340
|
+
if (fineValue < 0 || fineValue > 63) {
|
|
341
|
+
reject(@"invalid_value", @"Conversion value must be between 0 and 63", nil);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Validate window index
|
|
346
|
+
if (windowIndex < 0 || windowIndex > 2) {
|
|
347
|
+
reject(@"invalid_window", @"Window index must be 0, 1, or 2", nil);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (@available(iOS 18.4, *)) {
|
|
352
|
+
// Convert string to SKAdNetwork.CoarseConversionValue
|
|
353
|
+
SKAdNetworkCoarseConversionValue coarse;
|
|
354
|
+
if ([coarseValue isEqualToString:@"high"]) {
|
|
355
|
+
coarse = SKAdNetworkCoarseConversionValueHigh;
|
|
356
|
+
} else if ([coarseValue isEqualToString:@"medium"]) {
|
|
357
|
+
coarse = SKAdNetworkCoarseConversionValueMedium;
|
|
358
|
+
} else {
|
|
359
|
+
coarse = SKAdNetworkCoarseConversionValueLow;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// iOS 18.4 uses the same API but handles overlapping windows automatically
|
|
363
|
+
// based on timing. The windowIndex is for SDK tracking purposes.
|
|
364
|
+
[SKAdNetwork updatePostbackConversionValue:fineValue
|
|
365
|
+
coarseValue:coarse
|
|
366
|
+
lockWindow:lockWindow
|
|
367
|
+
completionHandler:^(NSError * _Nullable error) {
|
|
368
|
+
if (error) {
|
|
369
|
+
reject(@"postback_error", error.localizedDescription, error);
|
|
370
|
+
} else {
|
|
371
|
+
resolve(@{
|
|
372
|
+
@"success": @(YES),
|
|
373
|
+
@"framework": @"AdAttributionKit",
|
|
374
|
+
@"version": @"2.0",
|
|
375
|
+
@"fineValue": @(fineValue),
|
|
376
|
+
@"coarseValue": coarseValue,
|
|
377
|
+
@"lockWindow": @(lockWindow),
|
|
378
|
+
@"windowIndex": @(windowIndex),
|
|
379
|
+
@"overlappingWindows": @(YES)
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}];
|
|
383
|
+
} else if (@available(iOS 16.1, *)) {
|
|
384
|
+
// Fallback for iOS 16.1-18.3 (no overlapping windows)
|
|
385
|
+
SKAdNetworkCoarseConversionValue coarse;
|
|
386
|
+
if ([coarseValue isEqualToString:@"high"]) {
|
|
387
|
+
coarse = SKAdNetworkCoarseConversionValueHigh;
|
|
388
|
+
} else if ([coarseValue isEqualToString:@"medium"]) {
|
|
389
|
+
coarse = SKAdNetworkCoarseConversionValueMedium;
|
|
390
|
+
} else {
|
|
391
|
+
coarse = SKAdNetworkCoarseConversionValueLow;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
[SKAdNetwork updatePostbackConversionValue:fineValue
|
|
395
|
+
coarseValue:coarse
|
|
396
|
+
lockWindow:lockWindow
|
|
397
|
+
completionHandler:^(NSError * _Nullable error) {
|
|
398
|
+
if (error) {
|
|
399
|
+
reject(@"postback_error", error.localizedDescription, error);
|
|
400
|
+
} else {
|
|
401
|
+
NSString *framework = @"SKAdNetwork";
|
|
402
|
+
NSString *version = @"4.0";
|
|
403
|
+
if (@available(iOS 17.4, *)) {
|
|
404
|
+
framework = @"AdAttributionKit";
|
|
405
|
+
version = @"1.0";
|
|
406
|
+
}
|
|
407
|
+
resolve(@{
|
|
408
|
+
@"success": @(YES),
|
|
409
|
+
@"framework": framework,
|
|
410
|
+
@"version": version,
|
|
411
|
+
@"fineValue": @(fineValue),
|
|
412
|
+
@"coarseValue": coarseValue,
|
|
413
|
+
@"lockWindow": @(lockWindow),
|
|
414
|
+
@"windowIndex": @(windowIndex),
|
|
415
|
+
@"overlappingWindows": @(NO),
|
|
416
|
+
@"note": @"Overlapping windows require iOS 18.4+"
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}];
|
|
420
|
+
} else {
|
|
421
|
+
reject(@"unsupported", @"This method requires iOS 16.1+", nil);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
77
425
|
@end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>NSPrivacyTracking</key>
|
|
6
|
+
<false/>
|
|
7
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
8
|
+
<array/>
|
|
9
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
10
|
+
<array>
|
|
11
|
+
<dict>
|
|
12
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
13
|
+
<string>NSPrivacyCollectedDataTypeDeviceID</string>
|
|
14
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
15
|
+
<false/>
|
|
16
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
17
|
+
<false/>
|
|
18
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
19
|
+
<array>
|
|
20
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
21
|
+
</array>
|
|
22
|
+
</dict>
|
|
23
|
+
<dict>
|
|
24
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
25
|
+
<string>NSPrivacyCollectedDataTypeProductInteraction</string>
|
|
26
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
27
|
+
<false/>
|
|
28
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
29
|
+
<false/>
|
|
30
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
31
|
+
<array>
|
|
32
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
33
|
+
</array>
|
|
34
|
+
</dict>
|
|
35
|
+
</array>
|
|
36
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
37
|
+
<array>
|
|
38
|
+
<dict>
|
|
39
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
40
|
+
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
41
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
42
|
+
<array>
|
|
43
|
+
<string>CA92.1</string>
|
|
44
|
+
</array>
|
|
45
|
+
</dict>
|
|
46
|
+
</array>
|
|
47
|
+
</dict>
|
|
48
|
+
</plist>
|
package/lib/datalyr-sdk.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class DatalyrSDK {
|
|
|
8
8
|
private eventQueue;
|
|
9
9
|
private autoEventsManager;
|
|
10
10
|
private appStateSubscription;
|
|
11
|
+
private networkStatusUnsubscribe;
|
|
11
12
|
private static conversionEncoder?;
|
|
12
13
|
private static debugEnabled;
|
|
13
14
|
constructor();
|
|
@@ -193,6 +194,11 @@ export declare class DatalyrSDK {
|
|
|
193
194
|
* Persist user data to storage
|
|
194
195
|
*/
|
|
195
196
|
private persistUserData;
|
|
197
|
+
/**
|
|
198
|
+
* Initialize network status monitoring
|
|
199
|
+
* Automatically updates event queue when network status changes
|
|
200
|
+
*/
|
|
201
|
+
private initializeNetworkMonitoring;
|
|
196
202
|
/**
|
|
197
203
|
* Set up app state monitoring for lifecycle events (optimized)
|
|
198
204
|
*/
|