@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/lib/types.d.ts CHANGED
@@ -32,32 +32,83 @@ export interface DeferredDeepLinkResult {
32
32
  adsetId?: string;
33
33
  adId?: string;
34
34
  }
35
+ /**
36
+ * Core SDK Configuration
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * await Datalyr.initialize({
41
+ * apiKey: 'dk_your_api_key',
42
+ * debug: true,
43
+ * enableAutoEvents: true,
44
+ * enableAttribution: true,
45
+ * skadTemplate: 'ecommerce',
46
+ * meta: { appId: 'FB_APP_ID' },
47
+ * tiktok: { appId: 'APP_ID', tiktokAppId: 'TIKTOK_APP_ID' },
48
+ * });
49
+ * ```
50
+ */
35
51
  export interface DatalyrConfig {
52
+ /** Required: API key from Datalyr dashboard (starts with 'dk_') */
36
53
  apiKey: string;
54
+ /** Optional: Workspace ID for multi-workspace setups */
37
55
  workspaceId?: string;
56
+ /** Enable console logging for debugging. Default: false */
38
57
  debug?: boolean;
58
+ /**
59
+ * API endpoint URL. Default: 'https://api.datalyr.com'
60
+ * @deprecated Use `endpoint` instead
61
+ */
39
62
  apiUrl?: string;
63
+ /** API endpoint URL. Default: 'https://api.datalyr.com' */
40
64
  endpoint?: string;
65
+ /** Use server-side tracking. Default: true */
41
66
  useServerTracking?: boolean;
67
+ /** Maximum retry attempts for failed requests. Default: 3 */
42
68
  maxRetries?: number;
69
+ /** Delay between retries in milliseconds. Default: 1000 */
43
70
  retryDelay?: number;
71
+ /** Request timeout in milliseconds. Default: 15000 */
44
72
  timeout?: number;
73
+ /** Number of events per batch. Default: 10 */
45
74
  batchSize?: number;
75
+ /** Interval between automatic flushes in milliseconds. Default: 30000 */
46
76
  flushInterval?: number;
77
+ /** Maximum events to store in queue. Default: 100 */
47
78
  maxQueueSize?: number;
79
+ /**
80
+ * Maximum events to store in queue. Default: 100
81
+ * @deprecated Use `maxQueueSize` instead
82
+ */
48
83
  maxEventQueueSize?: number;
84
+ /** Respect browser Do Not Track setting. Default: true */
49
85
  respectDoNotTrack?: boolean;
86
+ /** Enable automatic event tracking (sessions, app lifecycle). Default: false */
50
87
  enableAutoEvents?: boolean;
88
+ /** Enable attribution tracking (deep links, install referrer). Default: false */
51
89
  enableAttribution?: boolean;
90
+ /** Enable web-to-app attribution matching via email. Default: true */
52
91
  enableWebToAppAttribution?: boolean;
92
+ /**
93
+ * Auto-events configuration
94
+ * @deprecated Use `autoEventConfig` instead
95
+ */
53
96
  autoEvents?: AutoEventConfig;
97
+ /** Auto-events configuration */
54
98
  autoEventConfig?: AutoEventConfig;
99
+ /**
100
+ * Retry configuration
101
+ * @deprecated Use `maxRetries` and `retryDelay` instead
102
+ */
55
103
  retryConfig?: {
56
104
  maxRetries: number;
57
105
  retryDelay: number;
58
106
  };
107
+ /** SKAdNetwork template for automatic conversion value encoding (iOS only) */
59
108
  skadTemplate?: 'ecommerce' | 'gaming' | 'subscription';
109
+ /** Meta (Facebook) SDK Configuration */
60
110
  meta?: MetaConfig;
111
+ /** TikTok SDK Configuration */
61
112
  tiktok?: TikTokConfig;
62
113
  }
63
114
  export interface EventData {
package/lib/utils.d.ts CHANGED
@@ -36,9 +36,14 @@ export declare const getOrCreateAnonymousId: () => Promise<string>;
36
36
  */
37
37
  export declare const getOrCreateSessionId: () => Promise<string>;
38
38
  /**
39
- * Collect comprehensive device information
39
+ * Collect comprehensive device information (cached after first call)
40
+ * Device info is cached because it rarely changes during app session
40
41
  */
41
42
  export declare const getDeviceInfo: () => Promise<DeviceInfoType>;
43
+ /**
44
+ * Clear the cached device info (useful for testing or after app update)
45
+ */
46
+ export declare const clearDeviceInfoCache: () => void;
42
47
  /**
43
48
  * Create fingerprint data for attribution
44
49
  */
package/lib/utils.js CHANGED
@@ -114,10 +114,43 @@ export const getOrCreateSessionId = async () => {
114
114
  return generateSessionId(); // Fallback to memory-only ID
115
115
  }
116
116
  };
117
+ // Cached device info to avoid repeated async calls
118
+ let cachedDeviceInfo = null;
119
+ let deviceInfoPromise = null;
117
120
  /**
118
- * Collect comprehensive device information
121
+ * Collect comprehensive device information (cached after first call)
122
+ * Device info is cached because it rarely changes during app session
119
123
  */
120
124
  export const getDeviceInfo = async () => {
125
+ // Return cached info if available
126
+ if (cachedDeviceInfo) {
127
+ return cachedDeviceInfo;
128
+ }
129
+ // If a fetch is already in progress, wait for it (prevents concurrent fetches)
130
+ if (deviceInfoPromise) {
131
+ return deviceInfoPromise;
132
+ }
133
+ // Start fetching and cache the promise
134
+ deviceInfoPromise = fetchDeviceInfoInternal();
135
+ try {
136
+ cachedDeviceInfo = await deviceInfoPromise;
137
+ return cachedDeviceInfo;
138
+ }
139
+ finally {
140
+ deviceInfoPromise = null;
141
+ }
142
+ };
143
+ /**
144
+ * Clear the cached device info (useful for testing or after app update)
145
+ */
146
+ export const clearDeviceInfoCache = () => {
147
+ cachedDeviceInfo = null;
148
+ deviceInfoPromise = null;
149
+ };
150
+ /**
151
+ * Internal function to fetch device info
152
+ */
153
+ const fetchDeviceInfoInternal = async () => {
121
154
  const { width, height } = Dimensions.get('window');
122
155
  // If DeviceInfo is not available (like in Expo Go), use fallback
123
156
  if (!DeviceInfo) {
@@ -204,11 +237,28 @@ export const createFingerprintData = async () => {
204
237
  },
205
238
  };
206
239
  };
240
+ // Import network status manager for network type detection
241
+ let networkStatusManagerRef = null;
242
+ // Lazy load to avoid circular dependencies
243
+ const getNetworkStatusManager = () => {
244
+ if (!networkStatusManagerRef) {
245
+ try {
246
+ networkStatusManagerRef = require('./network-status').networkStatusManager;
247
+ }
248
+ catch (_a) {
249
+ // Module not loaded yet
250
+ }
251
+ }
252
+ return networkStatusManagerRef;
253
+ };
207
254
  /**
208
255
  * Get network connection type
209
256
  */
210
257
  export const getNetworkType = () => {
211
- // This will be enhanced with react-native-netinfo if needed
258
+ const manager = getNetworkStatusManager();
259
+ if (manager) {
260
+ return manager.getNetworkType();
261
+ }
212
262
  return 'unknown';
213
263
  };
214
264
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalyr/react-native",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
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",
@@ -59,7 +59,8 @@
59
59
  "peerDependencies": {
60
60
  "react": ">=18.0.0",
61
61
  "react-native": ">=0.72.0",
62
- "react-native-device-info": ">=12.0.0"
62
+ "react-native-device-info": ">=12.0.0",
63
+ "@react-native-community/netinfo": ">=11.0.0"
63
64
  },
64
65
  "dependencies": {
65
66
  "@react-native-async-storage/async-storage": "^2.2.0",
@@ -106,6 +107,9 @@
106
107
  },
107
108
  "react-native-device-info": {
108
109
  "optional": true
110
+ },
111
+ "@react-native-community/netinfo": {
112
+ "optional": true
109
113
  }
110
114
  }
111
115
  }
@@ -33,6 +33,7 @@ import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEn
33
33
  import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
34
34
  import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
35
35
  import { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
36
+ import { networkStatusManager } from './network-status';
36
37
 
37
38
  export class DatalyrSDK {
38
39
  private state: SDKState;
@@ -40,6 +41,7 @@ export class DatalyrSDK {
40
41
  private eventQueue: EventQueue;
41
42
  private autoEventsManager: AutoEventsManager | null = null;
42
43
  private appStateSubscription: any = null;
44
+ private networkStatusUnsubscribe: (() => void) | null = null;
43
45
  private static conversionEncoder?: ConversionValueEncoder;
44
46
  private static debugEnabled = false;
45
47
 
@@ -112,21 +114,21 @@ export class DatalyrSDK {
112
114
  maxRetryCount: this.state.config.maxRetries || 3,
113
115
  });
114
116
 
115
- // Initialize visitor ID, anonymous ID and session
116
- this.state.visitorId = await getOrCreateVisitorId();
117
- this.state.anonymousId = await getOrCreateAnonymousId();
118
- this.state.sessionId = await getOrCreateSessionId();
119
-
120
- // Load persisted user data
121
- await this.loadPersistedUserData();
122
-
123
- // Initialize attribution manager
124
- if (this.state.config.enableAttribution) {
125
- await attributionManager.initialize();
126
- }
117
+ // PARALLEL INITIALIZATION: IDs and core managers
118
+ // Run ID creation and core manager initialization in parallel for faster startup
119
+ const [visitorId, anonymousId, sessionId] = await Promise.all([
120
+ getOrCreateVisitorId(),
121
+ getOrCreateAnonymousId(),
122
+ getOrCreateSessionId(),
123
+ // These run concurrently but don't return values we need to capture
124
+ this.loadPersistedUserData(),
125
+ this.state.config.enableAttribution ? attributionManager.initialize() : Promise.resolve(),
126
+ journeyManager.initialize(),
127
+ ]);
127
128
 
128
- // Initialize journey tracking (for first-touch, last-touch, touchpoints)
129
- await journeyManager.initialize();
129
+ this.state.visitorId = visitorId;
130
+ this.state.anonymousId = anonymousId;
131
+ this.state.sessionId = sessionId;
130
132
 
131
133
  // Record initial attribution to journey if this is a new session with attribution
132
134
  const initialAttribution = attributionManager.getAttributionData();
@@ -169,7 +171,7 @@ export class DatalyrSDK {
169
171
  }
170
172
  }, 50);
171
173
 
172
- // Initialize SKAdNetwork conversion encoder
174
+ // Initialize SKAdNetwork conversion encoder (synchronous, no await needed)
173
175
  if (config.skadTemplate) {
174
176
  const template = ConversionTemplates[config.skadTemplate];
175
177
  if (template) {
@@ -183,29 +185,41 @@ export class DatalyrSDK {
183
185
  }
184
186
  }
185
187
 
186
- // Initialize Meta SDK if configured
188
+ // PARALLEL INITIALIZATION: Network monitoring and platform integrations
189
+ // These are independent and can run concurrently for faster startup
190
+ const platformInitPromises: Promise<void>[] = [
191
+ // Network monitoring
192
+ this.initializeNetworkMonitoring(),
193
+ // Apple Search Ads (iOS only)
194
+ appleSearchAdsIntegration.initialize(config.debug),
195
+ // Google Play Install Referrer (Android only)
196
+ playInstallReferrerIntegration.initialize(),
197
+ ];
198
+
199
+ // Add Meta initialization if configured
187
200
  if (config.meta?.appId) {
188
- await metaIntegration.initialize(config.meta, config.debug);
189
-
190
- // Fetch deferred deep link and merge with attribution
191
- if (config.enableAttribution !== false) {
192
- const deferredLink = await metaIntegration.fetchDeferredDeepLink();
193
- if (deferredLink) {
194
- await this.handleDeferredDeepLink(deferredLink);
195
- }
196
- }
201
+ platformInitPromises.push(
202
+ metaIntegration.initialize(config.meta, config.debug).then(async () => {
203
+ // After Meta initializes, fetch deferred deep link
204
+ if (config.enableAttribution !== false) {
205
+ const deferredLink = await metaIntegration.fetchDeferredDeepLink();
206
+ if (deferredLink) {
207
+ await this.handleDeferredDeepLink(deferredLink);
208
+ }
209
+ }
210
+ })
211
+ );
197
212
  }
198
213
 
199
- // Initialize TikTok SDK if configured
214
+ // Add TikTok initialization if configured
200
215
  if (config.tiktok?.appId && config.tiktok?.tiktokAppId) {
201
- await tiktokIntegration.initialize(config.tiktok, config.debug);
216
+ platformInitPromises.push(
217
+ tiktokIntegration.initialize(config.tiktok, config.debug)
218
+ );
202
219
  }
203
220
 
204
- // Initialize Apple Search Ads attribution (iOS only, auto-fetches on init)
205
- await appleSearchAdsIntegration.initialize(config.debug);
206
-
207
- // Initialize Google Play Install Referrer (Android only)
208
- await playInstallReferrerIntegration.initialize();
221
+ // Wait for all platform integrations to complete
222
+ await Promise.all(platformInitPromises);
209
223
 
210
224
  debugLog('Platform integrations initialized', {
211
225
  meta: metaIntegration.isAvailable(),
@@ -1094,6 +1108,45 @@ export class DatalyrSDK {
1094
1108
  }
1095
1109
  }
1096
1110
 
1111
+ /**
1112
+ * Initialize network status monitoring
1113
+ * Automatically updates event queue when network status changes
1114
+ */
1115
+ private async initializeNetworkMonitoring(): Promise<void> {
1116
+ try {
1117
+ await networkStatusManager.initialize();
1118
+
1119
+ // Update event queue with current network status
1120
+ this.state.isOnline = networkStatusManager.isOnline();
1121
+ this.eventQueue.setOnlineStatus(this.state.isOnline);
1122
+
1123
+ // Subscribe to network changes
1124
+ this.networkStatusUnsubscribe = networkStatusManager.subscribe((state) => {
1125
+ const isOnline = state.isConnected && (state.isInternetReachable !== false);
1126
+ this.state.isOnline = isOnline;
1127
+ this.eventQueue.setOnlineStatus(isOnline);
1128
+
1129
+ // Track network status change event (only if SDK is fully initialized)
1130
+ if (this.state.initialized) {
1131
+ this.track('$network_status_change', {
1132
+ is_online: isOnline,
1133
+ network_type: state.type,
1134
+ is_internet_reachable: state.isInternetReachable,
1135
+ }).catch(() => {
1136
+ // Ignore errors for network status events
1137
+ });
1138
+ }
1139
+ });
1140
+
1141
+ debugLog(`Network monitoring initialized, online: ${this.state.isOnline}`);
1142
+ } catch (error) {
1143
+ errorLog('Error initializing network monitoring (non-blocking):', error as Error);
1144
+ // Default to online if monitoring fails
1145
+ this.state.isOnline = true;
1146
+ this.eventQueue.setOnlineStatus(true);
1147
+ }
1148
+ }
1149
+
1097
1150
  /**
1098
1151
  * Set up app state monitoring for lifecycle events (optimized)
1099
1152
  */
@@ -1114,6 +1167,8 @@ export class DatalyrSDK {
1114
1167
  } else if (nextAppState === 'active') {
1115
1168
  // App became active, ensure we have fresh session if needed
1116
1169
  this.refreshSession();
1170
+ // Refresh network status when coming back from background
1171
+ networkStatusManager.refresh();
1117
1172
  // Notify auto-events manager for session handling
1118
1173
  if (this.autoEventsManager) {
1119
1174
  this.autoEventsManager.handleAppForeground();
@@ -1154,6 +1209,15 @@ export class DatalyrSDK {
1154
1209
  this.appStateSubscription = null;
1155
1210
  }
1156
1211
 
1212
+ // Remove network status listener
1213
+ if (this.networkStatusUnsubscribe) {
1214
+ this.networkStatusUnsubscribe();
1215
+ this.networkStatusUnsubscribe = null;
1216
+ }
1217
+
1218
+ // Destroy network status manager
1219
+ networkStatusManager.destroy();
1220
+
1157
1221
  // Destroy event queue
1158
1222
  this.eventQueue.destroy();
1159
1223
 
package/src/index.ts CHANGED
@@ -29,7 +29,11 @@ export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEn
29
29
  export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
30
30
 
31
31
  // Export platform integrations
32
- export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
32
+ export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
33
+
34
+ // Export network status manager
35
+ export { networkStatusManager } from './network-status';
36
+ export type { NetworkState, NetworkStateListener } from './network-status';
33
37
 
34
38
  // Export native bridge types
35
39
  export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
@@ -19,7 +19,9 @@
19
19
  * - referrer_url: Full referrer URL from Play Store
20
20
  * - referrer_click_timestamp: When the referrer link was clicked
21
21
  * - install_begin_timestamp: When the install began
22
- * - gclid: Google Ads click ID (if present)
22
+ * - gclid: Google Ads click ID (standard)
23
+ * - gbraid: Google Ads privacy-safe click ID (iOS App campaigns)
24
+ * - wbraid: Google Ads privacy-safe click ID (Web-to-App campaigns)
23
25
  * - utm_source, utm_medium, utm_campaign, etc.
24
26
  */
25
27
 
@@ -37,6 +39,10 @@ export interface PlayInstallReferrer {
37
39
  installCompleteTimestamp?: number;
38
40
  // Google Ads click ID
39
41
  gclid?: string;
42
+ // Google Ads privacy-safe click IDs (iOS App campaigns)
43
+ gbraid?: string;
44
+ // Google Ads privacy-safe click IDs (Web-to-App campaigns)
45
+ wbraid?: string;
40
46
  // UTM Parameters
41
47
  utmSource?: string;
42
48
  utmMedium?: string;
@@ -94,6 +100,8 @@ class PlayInstallReferrerIntegration {
94
100
  utmSource: this.referrerData.utmSource,
95
101
  utmMedium: this.referrerData.utmMedium,
96
102
  hasGclid: !!this.referrerData.gclid,
103
+ hasGbraid: !!this.referrerData.gbraid,
104
+ hasWbraid: !!this.referrerData.wbraid,
97
105
  });
98
106
  }
99
107
  } catch (error) {
@@ -149,12 +157,15 @@ class PlayInstallReferrerIntegration {
149
157
  params.utmTerm = searchParams.get('utm_term') || undefined;
150
158
  params.utmContent = searchParams.get('utm_content') || undefined;
151
159
 
152
- // Extract click IDs
160
+ // Extract click IDs (gclid, gbraid, wbraid)
153
161
  params.gclid = searchParams.get('gclid') || undefined;
162
+ params.gbraid = searchParams.get('gbraid') || undefined;
163
+ params.wbraid = searchParams.get('wbraid') || undefined;
154
164
 
155
165
  // Store any additional parameters
166
+ const knownParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid', 'gbraid', 'wbraid'];
156
167
  searchParams.forEach((value, key) => {
157
- if (!key.startsWith('utm_') && key !== 'gclid') {
168
+ if (!knownParams.includes(key) && !key.startsWith('utm_')) {
158
169
  params[key] = value;
159
170
  }
160
171
  });
@@ -186,8 +197,12 @@ class PlayInstallReferrerIntegration {
186
197
  referrer_click_timestamp: this.referrerData.referrerClickTimestamp,
187
198
  install_begin_timestamp: this.referrerData.installBeginTimestamp,
188
199
 
189
- // Standard attribution fields
200
+ // Google Ads click IDs (gclid is standard, gbraid/wbraid are privacy-safe alternatives)
190
201
  gclid: this.referrerData.gclid,
202
+ gbraid: this.referrerData.gbraid,
203
+ wbraid: this.referrerData.wbraid,
204
+
205
+ // UTM parameters
191
206
  utm_source: this.referrerData.utmSource,
192
207
  utm_medium: this.referrerData.utmMedium,
193
208
  utm_campaign: this.referrerData.utmCampaign,