@datalyr/react-native 1.2.1 → 1.3.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.
Files changed (40) hide show
  1. package/android/build.gradle +54 -0
  2. package/android/src/main/AndroidManifest.xml +14 -0
  3. package/android/src/main/java/com/datalyr/reactnative/DatalyrNativeModule.java +423 -0
  4. package/android/src/main/java/com/datalyr/reactnative/DatalyrPackage.java +30 -0
  5. package/android/src/main/java/com/datalyr/reactnative/DatalyrPlayInstallReferrerModule.java +229 -0
  6. package/datalyr-react-native.podspec +2 -2
  7. package/ios/DatalyrSKAdNetwork.m +52 -1
  8. package/lib/ConversionValueEncoder.d.ts +13 -1
  9. package/lib/ConversionValueEncoder.js +57 -23
  10. package/lib/datalyr-sdk.d.ts +25 -2
  11. package/lib/datalyr-sdk.js +59 -8
  12. package/lib/index.d.ts +2 -0
  13. package/lib/index.js +1 -0
  14. package/lib/integrations/index.d.ts +3 -1
  15. package/lib/integrations/index.js +2 -1
  16. package/lib/integrations/meta-integration.d.ts +1 -0
  17. package/lib/integrations/meta-integration.js +4 -3
  18. package/lib/integrations/play-install-referrer.d.ts +74 -0
  19. package/lib/integrations/play-install-referrer.js +156 -0
  20. package/lib/integrations/tiktok-integration.d.ts +1 -0
  21. package/lib/integrations/tiktok-integration.js +4 -3
  22. package/lib/journey.d.ts +106 -0
  23. package/lib/journey.js +258 -0
  24. package/lib/native/DatalyrNativeBridge.d.ts +42 -3
  25. package/lib/native/DatalyrNativeBridge.js +63 -9
  26. package/lib/native/SKAdNetworkBridge.d.ts +21 -0
  27. package/lib/native/SKAdNetworkBridge.js +54 -0
  28. package/package.json +8 -3
  29. package/src/ConversionValueEncoder.ts +67 -26
  30. package/src/datalyr-sdk-expo.ts +55 -6
  31. package/src/datalyr-sdk.ts +72 -13
  32. package/src/expo.ts +4 -0
  33. package/src/index.ts +2 -0
  34. package/src/integrations/index.ts +3 -1
  35. package/src/integrations/meta-integration.ts +4 -3
  36. package/src/integrations/play-install-referrer.ts +203 -0
  37. package/src/integrations/tiktok-integration.ts +4 -3
  38. package/src/journey.ts +338 -0
  39. package/src/native/DatalyrNativeBridge.ts +99 -13
  40. package/src/native/SKAdNetworkBridge.ts +86 -2
package/lib/journey.js ADDED
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Journey Tracking Module for React Native
3
+ * Mirrors the Web SDK's journey tracking capabilities:
4
+ * - First-touch attribution with 90-day expiration
5
+ * - Last-touch attribution with 90-day expiration
6
+ * - Up to 30 touchpoints stored
7
+ */
8
+ import { Storage, debugLog, errorLog } from './utils';
9
+ // Storage keys for journey data
10
+ const JOURNEY_STORAGE_KEYS = {
11
+ FIRST_TOUCH: '@datalyr/first_touch',
12
+ LAST_TOUCH: '@datalyr/last_touch',
13
+ JOURNEY: '@datalyr/journey',
14
+ };
15
+ // 90-day attribution window (matching web SDK)
16
+ const ATTRIBUTION_WINDOW_MS = 90 * 24 * 60 * 60 * 1000;
17
+ // Maximum touchpoints to store
18
+ const MAX_TOUCHPOINTS = 30;
19
+ /**
20
+ * Journey manager for tracking customer touchpoints
21
+ */
22
+ export class JourneyManager {
23
+ constructor() {
24
+ this.firstTouch = null;
25
+ this.lastTouch = null;
26
+ this.journey = [];
27
+ this.initialized = false;
28
+ }
29
+ /**
30
+ * Initialize journey tracking by loading persisted data
31
+ */
32
+ async initialize() {
33
+ if (this.initialized)
34
+ return;
35
+ try {
36
+ debugLog('Initializing journey manager...');
37
+ // Load first touch
38
+ const savedFirstTouch = await Storage.getItem(JOURNEY_STORAGE_KEYS.FIRST_TOUCH);
39
+ if (savedFirstTouch && !this.isExpired(savedFirstTouch)) {
40
+ this.firstTouch = savedFirstTouch;
41
+ }
42
+ else if (savedFirstTouch) {
43
+ // Expired, clear it
44
+ await Storage.removeItem(JOURNEY_STORAGE_KEYS.FIRST_TOUCH);
45
+ }
46
+ // Load last touch
47
+ const savedLastTouch = await Storage.getItem(JOURNEY_STORAGE_KEYS.LAST_TOUCH);
48
+ if (savedLastTouch && !this.isExpired(savedLastTouch)) {
49
+ this.lastTouch = savedLastTouch;
50
+ }
51
+ else if (savedLastTouch) {
52
+ await Storage.removeItem(JOURNEY_STORAGE_KEYS.LAST_TOUCH);
53
+ }
54
+ // Load journey
55
+ const savedJourney = await Storage.getItem(JOURNEY_STORAGE_KEYS.JOURNEY);
56
+ if (savedJourney) {
57
+ this.journey = savedJourney;
58
+ }
59
+ this.initialized = true;
60
+ debugLog('Journey manager initialized', {
61
+ hasFirstTouch: !!this.firstTouch,
62
+ hasLastTouch: !!this.lastTouch,
63
+ touchpointCount: this.journey.length,
64
+ });
65
+ }
66
+ catch (error) {
67
+ errorLog('Failed to initialize journey manager:', error);
68
+ }
69
+ }
70
+ /**
71
+ * Check if attribution has expired
72
+ */
73
+ isExpired(attribution) {
74
+ return Date.now() >= attribution.expires_at;
75
+ }
76
+ /**
77
+ * Store first touch attribution (only if not already set or expired)
78
+ */
79
+ async storeFirstTouch(attribution) {
80
+ try {
81
+ // Only store if no valid first touch exists
82
+ if (this.firstTouch && !this.isExpired(this.firstTouch)) {
83
+ debugLog('First touch already exists, not overwriting');
84
+ return;
85
+ }
86
+ const now = Date.now();
87
+ this.firstTouch = {
88
+ ...attribution,
89
+ timestamp: attribution.timestamp || now,
90
+ captured_at: now,
91
+ expires_at: now + ATTRIBUTION_WINDOW_MS,
92
+ };
93
+ await Storage.setItem(JOURNEY_STORAGE_KEYS.FIRST_TOUCH, this.firstTouch);
94
+ debugLog('First touch stored:', this.firstTouch);
95
+ }
96
+ catch (error) {
97
+ errorLog('Failed to store first touch:', error);
98
+ }
99
+ }
100
+ /**
101
+ * Get first touch attribution (null if expired)
102
+ */
103
+ getFirstTouch() {
104
+ if (this.firstTouch && this.isExpired(this.firstTouch)) {
105
+ this.firstTouch = null;
106
+ Storage.removeItem(JOURNEY_STORAGE_KEYS.FIRST_TOUCH).catch(() => { });
107
+ }
108
+ return this.firstTouch;
109
+ }
110
+ /**
111
+ * Store last touch attribution (always updates)
112
+ */
113
+ async storeLastTouch(attribution) {
114
+ try {
115
+ const now = Date.now();
116
+ this.lastTouch = {
117
+ ...attribution,
118
+ timestamp: attribution.timestamp || now,
119
+ captured_at: now,
120
+ expires_at: now + ATTRIBUTION_WINDOW_MS,
121
+ };
122
+ await Storage.setItem(JOURNEY_STORAGE_KEYS.LAST_TOUCH, this.lastTouch);
123
+ debugLog('Last touch stored:', this.lastTouch);
124
+ }
125
+ catch (error) {
126
+ errorLog('Failed to store last touch:', error);
127
+ }
128
+ }
129
+ /**
130
+ * Get last touch attribution (null if expired)
131
+ */
132
+ getLastTouch() {
133
+ if (this.lastTouch && this.isExpired(this.lastTouch)) {
134
+ this.lastTouch = null;
135
+ Storage.removeItem(JOURNEY_STORAGE_KEYS.LAST_TOUCH).catch(() => { });
136
+ }
137
+ return this.lastTouch;
138
+ }
139
+ /**
140
+ * Add a touchpoint to the customer journey
141
+ */
142
+ async addTouchpoint(sessionId, attribution) {
143
+ try {
144
+ const touchpoint = {
145
+ timestamp: Date.now(),
146
+ sessionId,
147
+ source: attribution.source,
148
+ medium: attribution.medium,
149
+ campaign: attribution.campaign,
150
+ clickIdType: attribution.clickIdType,
151
+ };
152
+ this.journey.push(touchpoint);
153
+ // Keep only last MAX_TOUCHPOINTS
154
+ if (this.journey.length > MAX_TOUCHPOINTS) {
155
+ this.journey = this.journey.slice(-MAX_TOUCHPOINTS);
156
+ }
157
+ await Storage.setItem(JOURNEY_STORAGE_KEYS.JOURNEY, this.journey);
158
+ debugLog('Touchpoint added, total:', this.journey.length);
159
+ }
160
+ catch (error) {
161
+ errorLog('Failed to add touchpoint:', error);
162
+ }
163
+ }
164
+ /**
165
+ * Get customer journey (all touchpoints)
166
+ */
167
+ getJourney() {
168
+ return [...this.journey];
169
+ }
170
+ /**
171
+ * Record attribution from a deep link or install
172
+ * Updates first-touch (if not set), last-touch, and adds touchpoint
173
+ */
174
+ async recordAttribution(sessionId, attribution) {
175
+ // Only process if we have meaningful attribution data
176
+ const hasAttribution = attribution.source || attribution.clickId || attribution.campaign || attribution.lyr;
177
+ if (!hasAttribution) {
178
+ debugLog('No attribution data to record');
179
+ return;
180
+ }
181
+ // Store first touch if not set
182
+ if (!this.getFirstTouch()) {
183
+ await this.storeFirstTouch(attribution);
184
+ }
185
+ // Always update last touch
186
+ await this.storeLastTouch(attribution);
187
+ // Add touchpoint
188
+ await this.addTouchpoint(sessionId, attribution);
189
+ }
190
+ /**
191
+ * Get attribution data for events (mirrors Web SDK format)
192
+ */
193
+ getAttributionData() {
194
+ const firstTouch = this.getFirstTouch();
195
+ const lastTouch = this.getLastTouch();
196
+ const journey = this.getJourney();
197
+ return {
198
+ // First touch (with snake_case and camelCase aliases)
199
+ first_touch_source: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.source,
200
+ first_touch_medium: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.medium,
201
+ first_touch_campaign: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.campaign,
202
+ first_touch_timestamp: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.timestamp,
203
+ firstTouchSource: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.source,
204
+ firstTouchMedium: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.medium,
205
+ firstTouchCampaign: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.campaign,
206
+ // Last touch
207
+ last_touch_source: lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.source,
208
+ last_touch_medium: lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.medium,
209
+ last_touch_campaign: lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.campaign,
210
+ last_touch_timestamp: lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.timestamp,
211
+ lastTouchSource: lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.source,
212
+ lastTouchMedium: lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.medium,
213
+ lastTouchCampaign: lastTouch === null || lastTouch === void 0 ? void 0 : lastTouch.campaign,
214
+ // Journey metrics
215
+ touchpoint_count: journey.length,
216
+ touchpointCount: journey.length,
217
+ days_since_first_touch: (firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.timestamp)
218
+ ? Math.floor((Date.now() - firstTouch.timestamp) / 86400000)
219
+ : 0,
220
+ daysSinceFirstTouch: (firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.timestamp)
221
+ ? Math.floor((Date.now() - firstTouch.timestamp) / 86400000)
222
+ : 0,
223
+ };
224
+ }
225
+ /**
226
+ * Clear all journey data (for testing/reset)
227
+ */
228
+ async clearJourney() {
229
+ this.firstTouch = null;
230
+ this.lastTouch = null;
231
+ this.journey = [];
232
+ await Promise.all([
233
+ Storage.removeItem(JOURNEY_STORAGE_KEYS.FIRST_TOUCH),
234
+ Storage.removeItem(JOURNEY_STORAGE_KEYS.LAST_TOUCH),
235
+ Storage.removeItem(JOURNEY_STORAGE_KEYS.JOURNEY),
236
+ ]);
237
+ debugLog('Journey data cleared');
238
+ }
239
+ /**
240
+ * Get journey summary for debugging
241
+ */
242
+ getJourneySummary() {
243
+ const firstTouch = this.getFirstTouch();
244
+ const journey = this.getJourney();
245
+ const sources = [...new Set(journey.map(t => t.source).filter(Boolean))];
246
+ return {
247
+ hasFirstTouch: !!firstTouch,
248
+ hasLastTouch: !!this.getLastTouch(),
249
+ touchpointCount: journey.length,
250
+ daysSinceFirstTouch: (firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.timestamp)
251
+ ? Math.floor((Date.now() - firstTouch.timestamp) / 86400000)
252
+ : 0,
253
+ sources,
254
+ };
255
+ }
256
+ }
257
+ // Export singleton instance
258
+ export const journeyManager = new JourneyManager();
@@ -1,9 +1,13 @@
1
1
  /**
2
- * Native Bridge for Meta, TikTok, and Apple Search Ads
2
+ * Native Bridge for Meta, TikTok, Apple Search Ads, and Play Install Referrer
3
3
  * Uses bundled native modules instead of separate npm packages
4
+ *
5
+ * Supported Platforms:
6
+ * - iOS: Meta SDK, TikTok SDK, Apple Search Ads (AdServices)
7
+ * - Android: Meta SDK, TikTok SDK, Play Install Referrer
4
8
  */
5
9
  /**
6
- * Apple Search Ads attribution data returned from AdServices API
10
+ * Apple Search Ads attribution data returned from AdServices API (iOS only)
7
11
  */
8
12
  export interface AppleSearchAdsAttribution {
9
13
  attribution: boolean;
@@ -19,17 +23,36 @@ export interface AppleSearchAdsAttribution {
19
23
  conversionType?: string;
20
24
  countryOrRegion?: string;
21
25
  }
26
+ /**
27
+ * Play Install Referrer data (Android only)
28
+ */
29
+ export interface PlayInstallReferrerData {
30
+ referrerUrl: string;
31
+ referrerClickTimestamp: number;
32
+ installBeginTimestamp: number;
33
+ installCompleteTimestamp?: number;
34
+ gclid?: string;
35
+ fbclid?: string;
36
+ ttclid?: string;
37
+ utmSource?: string;
38
+ utmMedium?: string;
39
+ utmCampaign?: string;
40
+ utmTerm?: string;
41
+ utmContent?: string;
42
+ referrer?: string;
43
+ }
22
44
  /**
23
45
  * Check if native module is available
24
46
  */
25
47
  export declare const isNativeModuleAvailable: () => boolean;
26
48
  /**
27
- * Get SDK availability status
49
+ * Get SDK availability status for all platforms
28
50
  */
29
51
  export declare const getSDKAvailability: () => Promise<{
30
52
  meta: boolean;
31
53
  tiktok: boolean;
32
54
  appleSearchAds: boolean;
55
+ playInstallReferrer: boolean;
33
56
  }>;
34
57
  export declare const MetaNativeBridge: {
35
58
  initialize(appId: string, clientToken?: string, advertiserTrackingEnabled?: boolean): Promise<boolean>;
@@ -55,3 +78,19 @@ export declare const AppleSearchAdsNativeBridge: {
55
78
  */
56
79
  getAttribution(): Promise<AppleSearchAdsAttribution | null>;
57
80
  };
81
+ export declare const PlayInstallReferrerNativeBridge: {
82
+ /**
83
+ * Check if Play Install Referrer is available
84
+ * Only available on Android with Google Play Services
85
+ */
86
+ isAvailable(): Promise<boolean>;
87
+ /**
88
+ * Get install referrer data from Google Play
89
+ *
90
+ * Returns UTM parameters, click IDs (gclid, fbclid, ttclid), and timestamps
91
+ * from the Google Play Store referrer.
92
+ *
93
+ * Call this on first app launch to capture install attribution.
94
+ */
95
+ getInstallReferrer(): Promise<PlayInstallReferrerData | null>;
96
+ };
@@ -1,10 +1,17 @@
1
1
  /**
2
- * Native Bridge for Meta, TikTok, and Apple Search Ads
2
+ * Native Bridge for Meta, TikTok, Apple Search Ads, and Play Install Referrer
3
3
  * Uses bundled native modules instead of separate npm packages
4
+ *
5
+ * Supported Platforms:
6
+ * - iOS: Meta SDK, TikTok SDK, Apple Search Ads (AdServices)
7
+ * - Android: Meta SDK, TikTok SDK, Play Install Referrer
4
8
  */
9
+ var _a;
5
10
  import { NativeModules, Platform } from 'react-native';
6
- // Native module is only available on iOS
7
- const DatalyrNative = Platform.OS === 'ios' ? NativeModules.DatalyrNative : null;
11
+ // Native modules - available on both iOS and Android
12
+ const DatalyrNative = (_a = NativeModules.DatalyrNative) !== null && _a !== void 0 ? _a : null;
13
+ // Play Install Referrer - Android only
14
+ const DatalyrPlayInstallReferrer = Platform.OS === 'android' ? NativeModules.DatalyrPlayInstallReferrer : null;
8
15
  /**
9
16
  * Check if native module is available
10
17
  */
@@ -12,17 +19,28 @@ export const isNativeModuleAvailable = () => {
12
19
  return DatalyrNative !== null;
13
20
  };
14
21
  /**
15
- * Get SDK availability status
22
+ * Get SDK availability status for all platforms
16
23
  */
17
24
  export const getSDKAvailability = async () => {
25
+ const defaultAvailability = {
26
+ meta: false,
27
+ tiktok: false,
28
+ appleSearchAds: false,
29
+ playInstallReferrer: false,
30
+ };
18
31
  if (!DatalyrNative) {
19
- return { meta: false, tiktok: false, appleSearchAds: false };
32
+ return defaultAvailability;
20
33
  }
21
34
  try {
22
- return await DatalyrNative.getSDKAvailability();
35
+ const result = await DatalyrNative.getSDKAvailability();
36
+ return {
37
+ ...defaultAvailability,
38
+ ...result,
39
+ playInstallReferrer: Platform.OS === 'android' && DatalyrPlayInstallReferrer !== null,
40
+ };
23
41
  }
24
42
  catch (_a) {
25
- return { meta: false, tiktok: false, appleSearchAds: false };
43
+ return defaultAvailability;
26
44
  }
27
45
  };
28
46
  // MARK: - Meta SDK Bridge
@@ -166,7 +184,7 @@ export const TikTokNativeBridge = {
166
184
  }
167
185
  },
168
186
  };
169
- // MARK: - Apple Search Ads Bridge
187
+ // MARK: - Apple Search Ads Bridge (iOS only)
170
188
  export const AppleSearchAdsNativeBridge = {
171
189
  /**
172
190
  * Get Apple Search Ads attribution data
@@ -174,7 +192,7 @@ export const AppleSearchAdsNativeBridge = {
174
192
  * Returns null if user didn't come from Apple Search Ads or on older iOS
175
193
  */
176
194
  async getAttribution() {
177
- if (!DatalyrNative)
195
+ if (!DatalyrNative || Platform.OS !== 'ios')
178
196
  return null;
179
197
  try {
180
198
  return await DatalyrNative.getAppleSearchAdsAttribution();
@@ -185,3 +203,39 @@ export const AppleSearchAdsNativeBridge = {
185
203
  }
186
204
  },
187
205
  };
206
+ // MARK: - Play Install Referrer Bridge (Android only)
207
+ export const PlayInstallReferrerNativeBridge = {
208
+ /**
209
+ * Check if Play Install Referrer is available
210
+ * Only available on Android with Google Play Services
211
+ */
212
+ async isAvailable() {
213
+ if (!DatalyrPlayInstallReferrer || Platform.OS !== 'android')
214
+ return false;
215
+ try {
216
+ return await DatalyrPlayInstallReferrer.isAvailable();
217
+ }
218
+ catch (_a) {
219
+ return false;
220
+ }
221
+ },
222
+ /**
223
+ * Get install referrer data from Google Play
224
+ *
225
+ * Returns UTM parameters, click IDs (gclid, fbclid, ttclid), and timestamps
226
+ * from the Google Play Store referrer.
227
+ *
228
+ * Call this on first app launch to capture install attribution.
229
+ */
230
+ async getInstallReferrer() {
231
+ if (!DatalyrPlayInstallReferrer || Platform.OS !== 'android')
232
+ return null;
233
+ try {
234
+ return await DatalyrPlayInstallReferrer.getInstallReferrer();
235
+ }
236
+ catch (error) {
237
+ console.error('[Datalyr/PlayInstallReferrer] Get referrer failed:', error);
238
+ return null;
239
+ }
240
+ },
241
+ };
@@ -1,4 +1,25 @@
1
+ export type SKANCoarseValue = 'low' | 'medium' | 'high';
2
+ export interface SKANConversionResult {
3
+ fineValue: number;
4
+ coarseValue: SKANCoarseValue;
5
+ lockWindow: boolean;
6
+ priority: number;
7
+ }
1
8
  export declare class SKAdNetworkBridge {
9
+ private static _isSKAN4Available;
10
+ /**
11
+ * SKAN 3.0 - Update conversion value (0-63)
12
+ * @deprecated Use updatePostbackConversionValue for iOS 16.1+
13
+ */
2
14
  static updateConversionValue(value: number): Promise<boolean>;
15
+ /**
16
+ * SKAN 4.0 - Update postback conversion value with coarse value and lock window
17
+ * Falls back to SKAN 3.0 on iOS 14.0-16.0
18
+ */
19
+ static updatePostbackConversionValue(result: SKANConversionResult): Promise<boolean>;
20
+ /**
21
+ * Check if SKAN 4.0 is available (iOS 16.1+)
22
+ */
23
+ static isSKAN4Available(): Promise<boolean>;
3
24
  static isAvailable(): boolean;
4
25
  }
@@ -1,6 +1,10 @@
1
1
  import { NativeModules, Platform } from 'react-native';
2
2
  const { DatalyrSKAdNetwork } = NativeModules;
3
3
  export class SKAdNetworkBridge {
4
+ /**
5
+ * SKAN 3.0 - Update conversion value (0-63)
6
+ * @deprecated Use updatePostbackConversionValue for iOS 16.1+
7
+ */
4
8
  static async updateConversionValue(value) {
5
9
  if (Platform.OS !== 'ios') {
6
10
  return false; // Android doesn't support SKAdNetwork
@@ -19,7 +23,57 @@ export class SKAdNetworkBridge {
19
23
  return false;
20
24
  }
21
25
  }
26
+ /**
27
+ * SKAN 4.0 - Update postback conversion value with coarse value and lock window
28
+ * Falls back to SKAN 3.0 on iOS 14.0-16.0
29
+ */
30
+ static async updatePostbackConversionValue(result) {
31
+ if (Platform.OS !== 'ios') {
32
+ return false; // Android doesn't support SKAdNetwork
33
+ }
34
+ if (!DatalyrSKAdNetwork) {
35
+ console.warn('[Datalyr] SKAdNetwork native module not found. Ensure native bridge is properly configured.');
36
+ return false;
37
+ }
38
+ try {
39
+ const success = await DatalyrSKAdNetwork.updatePostbackConversionValue(result.fineValue, result.coarseValue, result.lockWindow);
40
+ const isSKAN4 = await this.isSKAN4Available();
41
+ if (isSKAN4) {
42
+ console.log(`[Datalyr] SKAN 4.0 postback updated: fineValue=${result.fineValue}, coarseValue=${result.coarseValue}, lockWindow=${result.lockWindow}`);
43
+ }
44
+ else {
45
+ console.log(`[Datalyr] SKAN 3.0 fallback: conversionValue=${result.fineValue}`);
46
+ }
47
+ return success;
48
+ }
49
+ catch (error) {
50
+ console.warn('[Datalyr] Failed to update SKAdNetwork postback conversion value:', error);
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Check if SKAN 4.0 is available (iOS 16.1+)
56
+ */
57
+ static async isSKAN4Available() {
58
+ if (Platform.OS !== 'ios') {
59
+ return false;
60
+ }
61
+ if (this._isSKAN4Available !== null) {
62
+ return this._isSKAN4Available;
63
+ }
64
+ if (!(DatalyrSKAdNetwork === null || DatalyrSKAdNetwork === void 0 ? void 0 : DatalyrSKAdNetwork.isSKAN4Available)) {
65
+ return false;
66
+ }
67
+ try {
68
+ this._isSKAN4Available = await DatalyrSKAdNetwork.isSKAN4Available();
69
+ return this._isSKAN4Available;
70
+ }
71
+ catch (_a) {
72
+ return false;
73
+ }
74
+ }
22
75
  static isAvailable() {
23
76
  return Platform.OS === 'ios' && !!DatalyrSKAdNetwork;
24
77
  }
25
78
  }
79
+ SKAdNetworkBridge._isSKAN4Available = null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datalyr/react-native",
3
- "version": "1.2.1",
4
- "description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking with bundled Meta and TikTok SDKs",
3
+ "version": "1.3.0",
4
+ "description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking with bundled Meta and TikTok SDKs for iOS and Android",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "scripts": {
@@ -15,6 +15,7 @@
15
15
  "lib/",
16
16
  "src/",
17
17
  "ios/",
18
+ "android/",
18
19
  "README.md",
19
20
  "CHANGELOG.md",
20
21
  "LICENSE",
@@ -38,11 +39,15 @@
38
39
  "tiktok-attribution",
39
40
  "google-attribution",
40
41
  "ios-attribution",
42
+ "android-attribution",
41
43
  "apple-search-ads",
44
+ "play-install-referrer",
45
+ "gclid",
42
46
  "conversion-tracking",
43
47
  "revenue-optimization",
44
48
  "deferred-deep-linking",
45
- "fbclid"
49
+ "fbclid",
50
+ "ttclid"
46
51
  ],
47
52
  "author": "Datalyr",
48
53
  "license": "MIT",