@datalyr/react-native 1.2.1 → 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.
Files changed (51) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +145 -9
  3. package/android/build.gradle +54 -0
  4. package/android/src/main/AndroidManifest.xml +14 -0
  5. package/android/src/main/java/com/datalyr/reactnative/DatalyrNativeModule.java +423 -0
  6. package/android/src/main/java/com/datalyr/reactnative/DatalyrPackage.java +30 -0
  7. package/android/src/main/java/com/datalyr/reactnative/DatalyrPlayInstallReferrerModule.java +229 -0
  8. package/datalyr-react-native.podspec +2 -2
  9. package/ios/DatalyrSKAdNetwork.m +400 -1
  10. package/ios/PrivacyInfo.xcprivacy +48 -0
  11. package/lib/ConversionValueEncoder.d.ts +13 -1
  12. package/lib/ConversionValueEncoder.js +57 -23
  13. package/lib/datalyr-sdk.d.ts +31 -2
  14. package/lib/datalyr-sdk.js +138 -30
  15. package/lib/index.d.ts +5 -1
  16. package/lib/index.js +4 -1
  17. package/lib/integrations/index.d.ts +3 -1
  18. package/lib/integrations/index.js +2 -1
  19. package/lib/integrations/meta-integration.d.ts +1 -0
  20. package/lib/integrations/meta-integration.js +4 -3
  21. package/lib/integrations/play-install-referrer.d.ts +78 -0
  22. package/lib/integrations/play-install-referrer.js +166 -0
  23. package/lib/integrations/tiktok-integration.d.ts +1 -0
  24. package/lib/integrations/tiktok-integration.js +4 -3
  25. package/lib/journey.d.ts +106 -0
  26. package/lib/journey.js +258 -0
  27. package/lib/native/DatalyrNativeBridge.d.ts +42 -3
  28. package/lib/native/DatalyrNativeBridge.js +63 -9
  29. package/lib/native/SKAdNetworkBridge.d.ts +142 -0
  30. package/lib/native/SKAdNetworkBridge.js +328 -0
  31. package/lib/network-status.d.ts +84 -0
  32. package/lib/network-status.js +281 -0
  33. package/lib/types.d.ts +51 -0
  34. package/lib/utils.d.ts +6 -1
  35. package/lib/utils.js +52 -2
  36. package/package.json +13 -4
  37. package/src/ConversionValueEncoder.ts +67 -26
  38. package/src/datalyr-sdk-expo.ts +55 -6
  39. package/src/datalyr-sdk.ts +161 -38
  40. package/src/expo.ts +4 -0
  41. package/src/index.ts +7 -1
  42. package/src/integrations/index.ts +3 -1
  43. package/src/integrations/meta-integration.ts +4 -3
  44. package/src/integrations/play-install-referrer.ts +218 -0
  45. package/src/integrations/tiktok-integration.ts +4 -3
  46. package/src/journey.ts +338 -0
  47. package/src/native/DatalyrNativeBridge.ts +99 -13
  48. package/src/native/SKAdNetworkBridge.ts +481 -2
  49. package/src/network-status.ts +312 -0
  50. package/src/types.ts +74 -6
  51. package/src/utils.ts +62 -6
@@ -0,0 +1,84 @@
1
+ export type NetworkState = {
2
+ isConnected: boolean;
3
+ isInternetReachable: boolean | null;
4
+ type: 'wifi' | 'cellular' | 'ethernet' | 'bluetooth' | 'vpn' | 'none' | 'unknown';
5
+ };
6
+ export type NetworkStateListener = (state: NetworkState) => void;
7
+ /**
8
+ * Network status manager that detects online/offline status
9
+ * Uses @react-native-community/netinfo for React Native or expo-network for Expo
10
+ */
11
+ declare class NetworkStatusManager {
12
+ private state;
13
+ private listeners;
14
+ private unsubscribe;
15
+ private initialized;
16
+ private netInfoModule;
17
+ private expoNetworkModule;
18
+ /**
19
+ * Initialize network status monitoring
20
+ * Call this during SDK initialization
21
+ */
22
+ initialize(): Promise<void>;
23
+ /**
24
+ * Initialize with @react-native-community/netinfo
25
+ */
26
+ private initializeWithNetInfo;
27
+ /**
28
+ * Update state from NetInfo response
29
+ */
30
+ private updateStateFromNetInfo;
31
+ /**
32
+ * Map NetInfo type to our simplified type
33
+ */
34
+ private mapNetInfoType;
35
+ /**
36
+ * Initialize with expo-network
37
+ */
38
+ private initializeWithExpoNetwork;
39
+ /**
40
+ * Update state from expo-network response
41
+ */
42
+ private updateStateFromExpoNetwork;
43
+ /**
44
+ * Map expo-network type to our simplified type
45
+ */
46
+ private mapExpoNetworkType;
47
+ /**
48
+ * Poll expo-network for changes (since it doesn't have a listener API)
49
+ */
50
+ private pollingInterval;
51
+ private startExpoNetworkPolling;
52
+ /**
53
+ * Notify all listeners of state change
54
+ */
55
+ private notifyListeners;
56
+ /**
57
+ * Get current network state
58
+ */
59
+ getState(): NetworkState;
60
+ /**
61
+ * Check if device is currently online
62
+ */
63
+ isOnline(): boolean;
64
+ /**
65
+ * Get current network type
66
+ */
67
+ getNetworkType(): NetworkState['type'];
68
+ /**
69
+ * Subscribe to network state changes
70
+ * Returns an unsubscribe function
71
+ */
72
+ subscribe(listener: NetworkStateListener): () => void;
73
+ /**
74
+ * Refresh network state manually
75
+ * Useful when returning from background
76
+ */
77
+ refresh(): Promise<NetworkState>;
78
+ /**
79
+ * Cleanup and stop monitoring
80
+ */
81
+ destroy(): void;
82
+ }
83
+ export declare const networkStatusManager: NetworkStatusManager;
84
+ export default networkStatusManager;
@@ -0,0 +1,281 @@
1
+ import { debugLog, errorLog } from './utils';
2
+ /**
3
+ * Network status manager that detects online/offline status
4
+ * Uses @react-native-community/netinfo for React Native or expo-network for Expo
5
+ */
6
+ class NetworkStatusManager {
7
+ constructor() {
8
+ this.state = {
9
+ isConnected: true, // Default to true until we know otherwise
10
+ isInternetReachable: null,
11
+ type: 'unknown',
12
+ };
13
+ this.listeners = new Set();
14
+ this.unsubscribe = null;
15
+ this.initialized = false;
16
+ this.netInfoModule = null;
17
+ this.expoNetworkModule = null;
18
+ /**
19
+ * Poll expo-network for changes (since it doesn't have a listener API)
20
+ */
21
+ this.pollingInterval = null;
22
+ }
23
+ /**
24
+ * Initialize network status monitoring
25
+ * Call this during SDK initialization
26
+ */
27
+ async initialize() {
28
+ if (this.initialized) {
29
+ return;
30
+ }
31
+ // Try @react-native-community/netinfo first (most common in RN apps)
32
+ try {
33
+ this.netInfoModule = require('@react-native-community/netinfo').default;
34
+ await this.initializeWithNetInfo();
35
+ this.initialized = true;
36
+ debugLog('Network status initialized with @react-native-community/netinfo');
37
+ return;
38
+ }
39
+ catch (_a) {
40
+ // Module not available, try expo-network
41
+ }
42
+ // Try expo-network (for Expo apps)
43
+ try {
44
+ this.expoNetworkModule = require('expo-network');
45
+ await this.initializeWithExpoNetwork();
46
+ this.initialized = true;
47
+ debugLog('Network status initialized with expo-network');
48
+ return;
49
+ }
50
+ catch (_b) {
51
+ // Module not available
52
+ }
53
+ // Fallback: assume online (no network monitoring available)
54
+ debugLog('No network status module available, defaulting to online');
55
+ this.state = {
56
+ isConnected: true,
57
+ isInternetReachable: true,
58
+ type: 'unknown',
59
+ };
60
+ this.initialized = true;
61
+ }
62
+ /**
63
+ * Initialize with @react-native-community/netinfo
64
+ */
65
+ async initializeWithNetInfo() {
66
+ const NetInfo = this.netInfoModule;
67
+ // Get initial state
68
+ try {
69
+ const netState = await NetInfo.fetch();
70
+ this.updateStateFromNetInfo(netState);
71
+ }
72
+ catch (error) {
73
+ errorLog('Failed to fetch initial network state:', error);
74
+ }
75
+ // Subscribe to changes
76
+ this.unsubscribe = NetInfo.addEventListener((netState) => {
77
+ this.updateStateFromNetInfo(netState);
78
+ });
79
+ }
80
+ /**
81
+ * Update state from NetInfo response
82
+ */
83
+ updateStateFromNetInfo(netState) {
84
+ var _a;
85
+ const previouslyConnected = this.state.isConnected;
86
+ this.state = {
87
+ isConnected: (_a = netState.isConnected) !== null && _a !== void 0 ? _a : true,
88
+ isInternetReachable: netState.isInternetReachable,
89
+ type: this.mapNetInfoType(netState.type),
90
+ };
91
+ // Notify listeners if connection status changed
92
+ if (previouslyConnected !== this.state.isConnected) {
93
+ debugLog(`Network status changed: ${this.state.isConnected ? 'online' : 'offline'} (${this.state.type})`);
94
+ this.notifyListeners();
95
+ }
96
+ }
97
+ /**
98
+ * Map NetInfo type to our simplified type
99
+ */
100
+ mapNetInfoType(type) {
101
+ switch (type) {
102
+ case 'wifi':
103
+ return 'wifi';
104
+ case 'cellular':
105
+ return 'cellular';
106
+ case 'ethernet':
107
+ return 'ethernet';
108
+ case 'bluetooth':
109
+ return 'bluetooth';
110
+ case 'vpn':
111
+ return 'vpn';
112
+ case 'none':
113
+ return 'none';
114
+ default:
115
+ return 'unknown';
116
+ }
117
+ }
118
+ /**
119
+ * Initialize with expo-network
120
+ */
121
+ async initializeWithExpoNetwork() {
122
+ const Network = this.expoNetworkModule;
123
+ // Get initial state
124
+ try {
125
+ const networkState = await Network.getNetworkStateAsync();
126
+ this.updateStateFromExpoNetwork(networkState);
127
+ }
128
+ catch (error) {
129
+ errorLog('Failed to fetch initial network state from expo-network:', error);
130
+ }
131
+ // Note: expo-network doesn't have a listener API like netinfo
132
+ // We'll poll periodically or rely on app state changes
133
+ this.startExpoNetworkPolling();
134
+ }
135
+ /**
136
+ * Update state from expo-network response
137
+ */
138
+ updateStateFromExpoNetwork(networkState) {
139
+ var _a, _b;
140
+ const previouslyConnected = this.state.isConnected;
141
+ this.state = {
142
+ isConnected: (_a = networkState.isConnected) !== null && _a !== void 0 ? _a : true,
143
+ isInternetReachable: (_b = networkState.isInternetReachable) !== null && _b !== void 0 ? _b : null,
144
+ type: this.mapExpoNetworkType(networkState.type),
145
+ };
146
+ if (previouslyConnected !== this.state.isConnected) {
147
+ debugLog(`Network status changed: ${this.state.isConnected ? 'online' : 'offline'} (${this.state.type})`);
148
+ this.notifyListeners();
149
+ }
150
+ }
151
+ /**
152
+ * Map expo-network type to our simplified type
153
+ */
154
+ mapExpoNetworkType(type) {
155
+ switch (type) {
156
+ case 'WIFI':
157
+ return 'wifi';
158
+ case 'CELLULAR':
159
+ return 'cellular';
160
+ case 'ETHERNET':
161
+ return 'ethernet';
162
+ case 'BLUETOOTH':
163
+ return 'bluetooth';
164
+ case 'VPN':
165
+ return 'vpn';
166
+ case 'NONE':
167
+ return 'none';
168
+ default:
169
+ return 'unknown';
170
+ }
171
+ }
172
+ startExpoNetworkPolling() {
173
+ // Poll every 5 seconds for network changes
174
+ this.pollingInterval = setInterval(async () => {
175
+ try {
176
+ if (this.expoNetworkModule) {
177
+ const networkState = await this.expoNetworkModule.getNetworkStateAsync();
178
+ this.updateStateFromExpoNetwork(networkState);
179
+ }
180
+ }
181
+ catch (error) {
182
+ // Ignore polling errors
183
+ }
184
+ }, 5000);
185
+ }
186
+ /**
187
+ * Notify all listeners of state change
188
+ */
189
+ notifyListeners() {
190
+ this.listeners.forEach((listener) => {
191
+ try {
192
+ listener(this.state);
193
+ }
194
+ catch (error) {
195
+ errorLog('Error in network state listener:', error);
196
+ }
197
+ });
198
+ }
199
+ /**
200
+ * Get current network state
201
+ */
202
+ getState() {
203
+ return { ...this.state };
204
+ }
205
+ /**
206
+ * Check if device is currently online
207
+ */
208
+ isOnline() {
209
+ // Consider online if connected OR if we're not sure about internet reachability
210
+ return this.state.isConnected && (this.state.isInternetReachable !== false);
211
+ }
212
+ /**
213
+ * Get current network type
214
+ */
215
+ getNetworkType() {
216
+ return this.state.type;
217
+ }
218
+ /**
219
+ * Subscribe to network state changes
220
+ * Returns an unsubscribe function
221
+ */
222
+ subscribe(listener) {
223
+ this.listeners.add(listener);
224
+ // Immediately call with current state
225
+ try {
226
+ listener(this.state);
227
+ }
228
+ catch (error) {
229
+ errorLog('Error calling network state listener:', error);
230
+ }
231
+ // Return unsubscribe function
232
+ return () => {
233
+ this.listeners.delete(listener);
234
+ };
235
+ }
236
+ /**
237
+ * Refresh network state manually
238
+ * Useful when returning from background
239
+ */
240
+ async refresh() {
241
+ if (this.netInfoModule) {
242
+ try {
243
+ const netState = await this.netInfoModule.fetch();
244
+ this.updateStateFromNetInfo(netState);
245
+ }
246
+ catch (error) {
247
+ errorLog('Failed to refresh network state:', error);
248
+ }
249
+ }
250
+ else if (this.expoNetworkModule) {
251
+ try {
252
+ const networkState = await this.expoNetworkModule.getNetworkStateAsync();
253
+ this.updateStateFromExpoNetwork(networkState);
254
+ }
255
+ catch (error) {
256
+ errorLog('Failed to refresh network state from expo-network:', error);
257
+ }
258
+ }
259
+ return this.state;
260
+ }
261
+ /**
262
+ * Cleanup and stop monitoring
263
+ */
264
+ destroy() {
265
+ if (this.unsubscribe) {
266
+ this.unsubscribe();
267
+ this.unsubscribe = null;
268
+ }
269
+ if (this.pollingInterval) {
270
+ clearInterval(this.pollingInterval);
271
+ this.pollingInterval = null;
272
+ }
273
+ this.listeners.clear();
274
+ this.initialized = false;
275
+ debugLog('Network status manager destroyed');
276
+ }
277
+ }
278
+ // Export singleton instance
279
+ export const networkStatusManager = new NetworkStatusManager();
280
+ // Export for direct access
281
+ export default networkStatusManager;
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,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.1",
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",
@@ -54,7 +59,8 @@
54
59
  "peerDependencies": {
55
60
  "react": ">=18.0.0",
56
61
  "react-native": ">=0.72.0",
57
- "react-native-device-info": ">=12.0.0"
62
+ "react-native-device-info": ">=12.0.0",
63
+ "@react-native-community/netinfo": ">=11.0.0"
58
64
  },
59
65
  "dependencies": {
60
66
  "@react-native-async-storage/async-storage": "^2.2.0",
@@ -101,6 +107,9 @@
101
107
  },
102
108
  "react-native-device-info": {
103
109
  "optional": true
110
+ },
111
+ "@react-native-community/netinfo": {
112
+ "optional": true
104
113
  }
105
114
  }
106
115
  }