@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.
@@ -1,9 +1,10 @@
1
- import { NativeModules, Platform } from 'react-native';
1
+ import { Platform } from 'react-native';
2
+ import { requireNativeModule } from 'expo-modules-core';
2
3
 
3
- // SKAN 4.0 coarse value type
4
+ // SKAN 4.0 / AdAttributionKit coarse value type
4
5
  export type SKANCoarseValue = 'low' | 'medium' | 'high';
5
6
 
6
- // SKAN 4.0 conversion result
7
+ // SKAN 4.0 / AdAttributionKit conversion result
7
8
  export interface SKANConversionResult {
8
9
  fineValue: number; // 0-63
9
10
  coarseValue: SKANCoarseValue;
@@ -11,19 +12,91 @@ export interface SKANConversionResult {
11
12
  priority: number;
12
13
  }
13
14
 
15
+ // Response from native postback update
16
+ export interface PostbackUpdateResponse {
17
+ success: boolean;
18
+ framework: 'AdAttributionKit' | 'SKAdNetwork';
19
+ fineValue: number;
20
+ coarseValue: string;
21
+ lockWindow: boolean;
22
+ type?: 'reengagement'; // Only for re-engagement updates
23
+ }
24
+
25
+ // Attribution framework info returned by native module
26
+ export interface AttributionFrameworkInfo {
27
+ framework: 'AdAttributionKit' | 'SKAdNetwork' | 'none';
28
+ version: string;
29
+ reengagement_available: boolean;
30
+ overlapping_windows: boolean;
31
+ fine_value_range: { min: number; max: number };
32
+ coarse_values: string[];
33
+ }
34
+
35
+ // Enhanced attribution info for iOS 18.4+ with geo and dev postback support
36
+ export interface EnhancedAttributionInfo extends AttributionFrameworkInfo {
37
+ geo_postback_available: boolean;
38
+ development_postbacks: boolean;
39
+ features: string[];
40
+ }
41
+
42
+ // Postback environment configuration response
43
+ export interface PostbackEnvironmentResponse {
44
+ environment: 'production' | 'sandbox';
45
+ isSandbox: boolean;
46
+ note: string;
47
+ }
48
+
49
+ // Overlapping window postback response (iOS 18.4+)
50
+ export interface OverlappingWindowPostbackResponse {
51
+ success: boolean;
52
+ framework: string;
53
+ version: string;
54
+ fineValue: number;
55
+ coarseValue: string;
56
+ lockWindow: boolean;
57
+ windowIndex: number;
58
+ overlappingWindows: boolean;
59
+ note?: string;
60
+ }
61
+
14
62
  interface SKAdNetworkModule {
15
63
  updateConversionValue(value: number): Promise<boolean>;
16
64
  updatePostbackConversionValue(
17
65
  fineValue: number,
18
66
  coarseValue: string,
19
67
  lockWindow: boolean
20
- ): Promise<boolean>;
68
+ ): Promise<PostbackUpdateResponse>;
69
+ updateReengagementConversionValue(
70
+ fineValue: number,
71
+ coarseValue: string,
72
+ lockWindow: boolean
73
+ ): Promise<PostbackUpdateResponse>;
21
74
  isSKAN4Available(): Promise<boolean>;
75
+ isAdAttributionKitAvailable(): Promise<boolean>;
76
+ isOverlappingWindowsAvailable(): Promise<boolean>;
77
+ registerForAttribution(): Promise<{ framework: string; registered: boolean }>;
78
+ getAttributionInfo(): Promise<AttributionFrameworkInfo>;
79
+ // iOS 18.4+ methods
80
+ isGeoPostbackAvailable(): Promise<boolean>;
81
+ setPostbackEnvironment(environment: string): Promise<PostbackEnvironmentResponse>;
82
+ getEnhancedAttributionInfo(): Promise<EnhancedAttributionInfo>;
83
+ updatePostbackWithWindow(
84
+ fineValue: number,
85
+ coarseValue: string,
86
+ lockWindow: boolean,
87
+ windowIndex: number
88
+ ): Promise<OverlappingWindowPostbackResponse>;
22
89
  }
23
90
 
24
- const { DatalyrSKAdNetwork } = NativeModules as {
25
- DatalyrSKAdNetwork?: SKAdNetworkModule
26
- };
91
+ // SKAdNetwork is iOS-only, use Expo Modules for new arch compatibility
92
+ let DatalyrSKAdNetwork: SKAdNetworkModule | undefined;
93
+ if (Platform.OS === 'ios') {
94
+ try {
95
+ DatalyrSKAdNetwork = requireNativeModule<SKAdNetworkModule>('DatalyrSKAdNetwork');
96
+ } catch {
97
+ // Module not available
98
+ }
99
+ }
27
100
 
28
101
  export class SKAdNetworkBridge {
29
102
  private static _isSKAN4Available: boolean | null = null;
@@ -69,7 +142,7 @@ export class SKAdNetworkBridge {
69
142
  }
70
143
 
71
144
  try {
72
- const success = await DatalyrSKAdNetwork.updatePostbackConversionValue(
145
+ const response = await DatalyrSKAdNetwork.updatePostbackConversionValue(
73
146
  result.fineValue,
74
147
  result.coarseValue,
75
148
  result.lockWindow
@@ -82,7 +155,7 @@ export class SKAdNetworkBridge {
82
155
  console.log(`[Datalyr] SKAN 3.0 fallback: conversionValue=${result.fineValue}`);
83
156
  }
84
157
 
85
- return success;
158
+ return response.success;
86
159
  } catch (error) {
87
160
  console.warn('[Datalyr] Failed to update SKAdNetwork postback conversion value:', error);
88
161
  return false;
@@ -116,4 +189,333 @@ export class SKAdNetworkBridge {
116
189
  static isAvailable(): boolean {
117
190
  return Platform.OS === 'ios' && !!DatalyrSKAdNetwork;
118
191
  }
192
+
193
+ /**
194
+ * Check if AdAttributionKit is available (iOS 17.4+)
195
+ * AdAttributionKit is Apple's replacement for SKAdNetwork with enhanced features
196
+ */
197
+ private static _isAdAttributionKitAvailable: boolean | null = null;
198
+
199
+ static async isAdAttributionKitAvailable(): Promise<boolean> {
200
+ if (Platform.OS !== 'ios') {
201
+ return false;
202
+ }
203
+
204
+ if (this._isAdAttributionKitAvailable !== null) {
205
+ return this._isAdAttributionKitAvailable;
206
+ }
207
+
208
+ if (!DatalyrSKAdNetwork?.isAdAttributionKitAvailable) {
209
+ return false;
210
+ }
211
+
212
+ try {
213
+ this._isAdAttributionKitAvailable = await DatalyrSKAdNetwork.isAdAttributionKitAvailable();
214
+ return this._isAdAttributionKitAvailable;
215
+ } catch {
216
+ return false;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Register for ad network attribution
222
+ * Uses AdAttributionKit on iOS 17.4+, SKAdNetwork on earlier versions
223
+ */
224
+ static async registerForAttribution(): Promise<{ framework: string; registered: boolean } | null> {
225
+ if (Platform.OS !== 'ios') {
226
+ return null;
227
+ }
228
+
229
+ if (!DatalyrSKAdNetwork?.registerForAttribution) {
230
+ console.warn('[Datalyr] Attribution registration not available');
231
+ return null;
232
+ }
233
+
234
+ try {
235
+ const result = await DatalyrSKAdNetwork.registerForAttribution();
236
+ console.log(`[Datalyr] Registered for attribution: ${result.framework}`);
237
+ return result;
238
+ } catch (error) {
239
+ console.warn('[Datalyr] Failed to register for attribution:', error);
240
+ return null;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Get attribution framework info
246
+ * Returns details about which framework is being used and its capabilities
247
+ */
248
+ static async getAttributionInfo(): Promise<AttributionFrameworkInfo | null> {
249
+ if (Platform.OS !== 'ios') {
250
+ return {
251
+ framework: 'none',
252
+ version: '0',
253
+ reengagement_available: false,
254
+ overlapping_windows: false,
255
+ fine_value_range: { min: 0, max: 0 },
256
+ coarse_values: [],
257
+ };
258
+ }
259
+
260
+ if (!DatalyrSKAdNetwork?.getAttributionInfo) {
261
+ return null;
262
+ }
263
+
264
+ try {
265
+ return await DatalyrSKAdNetwork.getAttributionInfo();
266
+ } catch (error) {
267
+ console.warn('[Datalyr] Failed to get attribution info:', error);
268
+ return null;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Check if overlapping conversion windows are available (iOS 18.4+)
274
+ * Overlapping windows allow multiple conversion windows to be active simultaneously
275
+ */
276
+ private static _isOverlappingWindowsAvailable: boolean | null = null;
277
+
278
+ static async isOverlappingWindowsAvailable(): Promise<boolean> {
279
+ if (Platform.OS !== 'ios') {
280
+ return false;
281
+ }
282
+
283
+ if (this._isOverlappingWindowsAvailable !== null) {
284
+ return this._isOverlappingWindowsAvailable;
285
+ }
286
+
287
+ if (!DatalyrSKAdNetwork?.isOverlappingWindowsAvailable) {
288
+ return false;
289
+ }
290
+
291
+ try {
292
+ this._isOverlappingWindowsAvailable = await DatalyrSKAdNetwork.isOverlappingWindowsAvailable();
293
+ return this._isOverlappingWindowsAvailable;
294
+ } catch {
295
+ return false;
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Update conversion value for re-engagement attribution (AdAttributionKit iOS 17.4+ only)
301
+ * Re-engagement tracks users who return to the app via an ad after initial install.
302
+ *
303
+ * @param result - Conversion result with fine value (0-63), coarse value, and lock window
304
+ * @returns Response with framework info, or null if not supported
305
+ */
306
+ static async updateReengagementConversionValue(
307
+ result: SKANConversionResult
308
+ ): Promise<PostbackUpdateResponse | null> {
309
+ if (Platform.OS !== 'ios') {
310
+ return null; // Android doesn't support AdAttributionKit
311
+ }
312
+
313
+ // Check if AdAttributionKit is available (required for re-engagement)
314
+ const isAAKAvailable = await this.isAdAttributionKitAvailable();
315
+ if (!isAAKAvailable) {
316
+ console.warn('[Datalyr] Re-engagement attribution requires iOS 17.4+ (AdAttributionKit)');
317
+ return null;
318
+ }
319
+
320
+ if (!DatalyrSKAdNetwork?.updateReengagementConversionValue) {
321
+ console.warn('[Datalyr] Re-engagement native module not available');
322
+ return null;
323
+ }
324
+
325
+ try {
326
+ const response = await DatalyrSKAdNetwork.updateReengagementConversionValue(
327
+ result.fineValue,
328
+ result.coarseValue,
329
+ result.lockWindow
330
+ );
331
+
332
+ console.log(`[Datalyr] AdAttributionKit re-engagement updated: fineValue=${result.fineValue}, coarseValue=${result.coarseValue}, lockWindow=${result.lockWindow}`);
333
+ return response;
334
+ } catch (error) {
335
+ console.warn('[Datalyr] Failed to update re-engagement conversion value:', error);
336
+ return null;
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Get a summary of attribution capabilities for the current device
342
+ */
343
+ static async getCapabilitiesSummary(): Promise<{
344
+ skadnetwork3: boolean;
345
+ skadnetwork4: boolean;
346
+ adAttributionKit: boolean;
347
+ reengagement: boolean;
348
+ overlappingWindows: boolean;
349
+ geoPostback: boolean;
350
+ developmentPostbacks: boolean;
351
+ framework: string;
352
+ }> {
353
+ const info = await this.getAttributionInfo();
354
+ const isSKAN4 = await this.isSKAN4Available();
355
+ const isAAK = await this.isAdAttributionKitAvailable();
356
+ const isOverlapping = await this.isOverlappingWindowsAvailable();
357
+ const isGeo = await this.isGeoPostbackAvailable();
358
+
359
+ return {
360
+ skadnetwork3: Platform.OS === 'ios',
361
+ skadnetwork4: isSKAN4,
362
+ adAttributionKit: isAAK,
363
+ reengagement: info?.reengagement_available ?? false,
364
+ overlappingWindows: isOverlapping,
365
+ geoPostback: isGeo,
366
+ developmentPostbacks: isGeo, // Same iOS version requirement
367
+ framework: info?.framework ?? 'none',
368
+ };
369
+ }
370
+
371
+ // ===== iOS 18.4+ Features =====
372
+
373
+ /**
374
+ * Check if geo-level postback data is available (iOS 18.4+)
375
+ * Geo postbacks include country code information for regional analytics
376
+ */
377
+ private static _isGeoPostbackAvailable: boolean | null = null;
378
+
379
+ static async isGeoPostbackAvailable(): Promise<boolean> {
380
+ if (Platform.OS !== 'ios') {
381
+ return false;
382
+ }
383
+
384
+ if (this._isGeoPostbackAvailable !== null) {
385
+ return this._isGeoPostbackAvailable;
386
+ }
387
+
388
+ if (!DatalyrSKAdNetwork?.isGeoPostbackAvailable) {
389
+ return false;
390
+ }
391
+
392
+ try {
393
+ this._isGeoPostbackAvailable = await DatalyrSKAdNetwork.isGeoPostbackAvailable();
394
+ return this._isGeoPostbackAvailable;
395
+ } catch {
396
+ return false;
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Set postback environment for testing (iOS 18.4+)
402
+ * Note: Actual sandbox mode requires Developer Mode enabled in iOS Settings
403
+ *
404
+ * @param environment - 'production' or 'sandbox'
405
+ */
406
+ static async setPostbackEnvironment(
407
+ environment: 'production' | 'sandbox'
408
+ ): Promise<PostbackEnvironmentResponse | null> {
409
+ if (Platform.OS !== 'ios') {
410
+ return null;
411
+ }
412
+
413
+ if (!DatalyrSKAdNetwork?.setPostbackEnvironment) {
414
+ console.warn('[Datalyr] Development postbacks require iOS 18.4+');
415
+ return null;
416
+ }
417
+
418
+ try {
419
+ const result = await DatalyrSKAdNetwork.setPostbackEnvironment(environment);
420
+ console.log(`[Datalyr] Postback environment: ${result.environment}`);
421
+ return result;
422
+ } catch (error) {
423
+ console.warn('[Datalyr] Failed to set postback environment:', error);
424
+ return null;
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Get enhanced attribution info including iOS 18.4+ features
430
+ * Returns details about geo postbacks, development mode, and all available features
431
+ */
432
+ static async getEnhancedAttributionInfo(): Promise<EnhancedAttributionInfo | null> {
433
+ if (Platform.OS !== 'ios') {
434
+ return {
435
+ framework: 'none',
436
+ version: '0',
437
+ reengagement_available: false,
438
+ overlapping_windows: false,
439
+ geo_postback_available: false,
440
+ development_postbacks: false,
441
+ fine_value_range: { min: 0, max: 0 },
442
+ coarse_values: [],
443
+ features: [],
444
+ };
445
+ }
446
+
447
+ if (!DatalyrSKAdNetwork?.getEnhancedAttributionInfo) {
448
+ // Fallback to basic info if enhanced not available
449
+ const basicInfo = await this.getAttributionInfo();
450
+ if (basicInfo) {
451
+ return {
452
+ ...basicInfo,
453
+ geo_postback_available: false,
454
+ development_postbacks: false,
455
+ features: [],
456
+ };
457
+ }
458
+ return null;
459
+ }
460
+
461
+ try {
462
+ return await DatalyrSKAdNetwork.getEnhancedAttributionInfo();
463
+ } catch (error) {
464
+ console.warn('[Datalyr] Failed to get enhanced attribution info:', error);
465
+ return null;
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Update postback with overlapping window support (iOS 18.4+)
471
+ * Allows tracking conversions across multiple time windows simultaneously
472
+ *
473
+ * @param result - Conversion result with fine value, coarse value, and lock window
474
+ * @param windowIndex - Window index: 0 (0-2 days), 1 (3-7 days), 2 (8-35 days)
475
+ */
476
+ static async updatePostbackWithWindow(
477
+ result: SKANConversionResult,
478
+ windowIndex: 0 | 1 | 2
479
+ ): Promise<OverlappingWindowPostbackResponse | null> {
480
+ if (Platform.OS !== 'ios') {
481
+ return null;
482
+ }
483
+
484
+ if (!DatalyrSKAdNetwork?.updatePostbackWithWindow) {
485
+ console.warn('[Datalyr] Overlapping windows require iOS 16.1+ (full support on iOS 18.4+)');
486
+ return null;
487
+ }
488
+
489
+ try {
490
+ const response = await DatalyrSKAdNetwork.updatePostbackWithWindow(
491
+ result.fineValue,
492
+ result.coarseValue,
493
+ result.lockWindow,
494
+ windowIndex
495
+ );
496
+
497
+ console.log(`[Datalyr] Postback updated for window ${windowIndex}: fineValue=${result.fineValue}, overlapping=${response.overlappingWindows}`);
498
+ return response;
499
+ } catch (error) {
500
+ console.warn('[Datalyr] Failed to update postback with window:', error);
501
+ return null;
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Enable development/sandbox mode for testing attribution
507
+ * Convenience method that sets sandbox environment
508
+ */
509
+ static async enableDevelopmentMode(): Promise<boolean> {
510
+ const result = await this.setPostbackEnvironment('sandbox');
511
+ return result?.isSandbox ?? false;
512
+ }
513
+
514
+ /**
515
+ * Disable development mode (switch to production)
516
+ */
517
+ static async disableDevelopmentMode(): Promise<boolean> {
518
+ const result = await this.setPostbackEnvironment('production');
519
+ return result !== null && !result.isSandbox;
520
+ }
119
521
  }