@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 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
 
@@ -24,12 +24,19 @@ RCT_EXPORT_METHOD(updateConversionValue:(NSInteger)value
24
24
  }
25
25
  }
26
26
 
27
- // SKAN 4.0 - New method for iOS 16.1+ with coarse value and lock window support
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
- resolve(@(YES));
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(@(YES));
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>
@@ -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
  */