@datalyr/react-native 1.0.5 → 1.1.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.
package/README.md CHANGED
@@ -14,6 +14,7 @@ Official Datalyr SDK for React Native & Expo - Mobile attribution tracking and a
14
14
  - 💾 **Offline Support** - Events saved and retried when reconnected
15
15
  - 🔒 **Privacy First** - GDPR/CCPA compliant
16
16
  - ⚡ **Lightweight** - < 100KB, minimal battery impact
17
+ - 🆔 **Identity Resolution** - Persistent anonymous ID links web → mobile → server events
17
18
 
18
19
  ## Installation
19
20
 
@@ -164,6 +165,35 @@ await Datalyr.setAttributionData({
164
165
  });
165
166
  ```
166
167
 
168
+ ## Identity Resolution (New in v1.1.0)
169
+
170
+ The SDK now includes persistent anonymous IDs for complete user journey tracking:
171
+
172
+ ```typescript
173
+ // Get anonymous ID (persists across app sessions)
174
+ const anonymousId = Datalyr.getAnonymousId();
175
+
176
+ // Pass to your backend for attribution preservation
177
+ await fetch('/api/purchase', {
178
+ method: 'POST',
179
+ body: JSON.stringify({
180
+ items: cart,
181
+ anonymous_id: anonymousId // Links server events to mobile events
182
+ })
183
+ });
184
+
185
+ // Identity is automatically linked when you identify a user
186
+ await Datalyr.identify('user_123', {
187
+ email: 'user@example.com'
188
+ });
189
+ // This creates a $identify event that links anonymous_id to user_id
190
+ ```
191
+
192
+ ### Key Benefits:
193
+ - **Attribution Preservation**: Never lose fbclid, gclid, ttclid, or lyr tracking
194
+ - **Complete Journey**: Track users from web → app → server
195
+ - **Automatic Linking**: Identity resolution happens automatically
196
+
167
197
  ## Session Management
168
198
 
169
199
  Sessions are tracked automatically with a 30-minute timeout.
@@ -45,11 +45,16 @@ export declare class DatalyrSDK {
45
45
  initialized: boolean;
46
46
  workspaceId: string;
47
47
  visitorId: string;
48
+ anonymousId: string;
48
49
  sessionId: string;
49
50
  currentUserId?: string;
50
51
  queueStats: any;
51
52
  attribution: any;
52
53
  };
54
+ /**
55
+ * Get the persistent anonymous ID
56
+ */
57
+ getAnonymousId(): string;
53
58
  /**
54
59
  * Get detailed attribution data
55
60
  */
@@ -151,11 +156,13 @@ export declare class Datalyr {
151
156
  initialized: boolean;
152
157
  workspaceId: string;
153
158
  visitorId: string;
159
+ anonymousId: string;
154
160
  sessionId: string;
155
161
  currentUserId?: string;
156
162
  queueStats: any;
157
163
  attribution: any;
158
164
  };
165
+ static getAnonymousId(): string;
159
166
  static getAttributionData(): AttributionData;
160
167
  static setAttributionData(data: Partial<AttributionData>): Promise<void>;
161
168
  static getCurrentSession(): SessionData | null;
@@ -1,5 +1,5 @@
1
1
  import { Platform, AppState } from 'react-native';
2
- import { getOrCreateVisitorId, getOrCreateSessionId, createFingerprintData, generateUUID, getDeviceInfo, getNetworkType, validateEventName, validateEventData, debugLog, errorLog, Storage, STORAGE_KEYS, } from './utils';
2
+ import { getOrCreateVisitorId, getOrCreateAnonymousId, getOrCreateSessionId, createFingerprintData, generateUUID, getDeviceInfo, getNetworkType, validateEventName, validateEventData, debugLog, errorLog, Storage, STORAGE_KEYS, } from './utils';
3
3
  import { createHttpClient, HttpClient } from './http-client';
4
4
  import { createEventQueue, EventQueue } from './event-queue';
5
5
  import { attributionManager } from './attribution';
@@ -27,6 +27,7 @@ export class DatalyrSDK {
27
27
  respectDoNotTrack: true,
28
28
  },
29
29
  visitorId: '',
30
+ anonymousId: '', // Persistent anonymous identifier
30
31
  sessionId: '',
31
32
  userProperties: {},
32
33
  eventQueue: [],
@@ -70,8 +71,9 @@ export class DatalyrSDK {
70
71
  flushInterval: this.state.config.flushInterval || 30000,
71
72
  maxRetryCount: this.state.config.maxRetries || 3,
72
73
  });
73
- // Initialize visitor ID and session
74
+ // Initialize visitor ID, anonymous ID and session
74
75
  this.state.visitorId = await getOrCreateVisitorId();
76
+ this.state.anonymousId = await getOrCreateAnonymousId();
75
77
  this.state.sessionId = await getOrCreateSessionId();
76
78
  // Load persisted user data
77
79
  await this.loadPersistedUserData();
@@ -128,6 +130,7 @@ export class DatalyrSDK {
128
130
  debugLog('Datalyr SDK initialized successfully', {
129
131
  workspaceId: this.state.config.workspaceId,
130
132
  visitorId: this.state.visitorId,
133
+ anonymousId: this.state.anonymousId,
131
134
  sessionId: this.state.sessionId,
132
135
  });
133
136
  }
@@ -191,9 +194,10 @@ export class DatalyrSDK {
191
194
  this.state.userProperties = { ...this.state.userProperties, ...properties };
192
195
  // Persist user data
193
196
  await this.persistUserData();
194
- // Track identify event
195
- await this.track('identify', {
197
+ // Track $identify event for identity resolution
198
+ await this.track('$identify', {
196
199
  userId,
200
+ anonymous_id: this.state.anonymousId,
197
201
  ...properties
198
202
  });
199
203
  }
@@ -214,6 +218,7 @@ export class DatalyrSDK {
214
218
  newUserId,
215
219
  previousId: previousId || this.state.visitorId,
216
220
  visitorId: this.state.visitorId,
221
+ anonymousId: this.state.anonymousId, // Include for identity resolution
217
222
  };
218
223
  debugLog('Aliasing user:', aliasData);
219
224
  // Track alias event
@@ -265,12 +270,19 @@ export class DatalyrSDK {
265
270
  initialized: this.state.initialized,
266
271
  workspaceId: this.state.config.workspaceId || '',
267
272
  visitorId: this.state.visitorId,
273
+ anonymousId: this.state.anonymousId,
268
274
  sessionId: this.state.sessionId,
269
275
  currentUserId: this.state.currentUserId,
270
276
  queueStats: this.eventQueue.getStats(),
271
277
  attribution: attributionManager.getAttributionSummary(),
272
278
  };
273
279
  }
280
+ /**
281
+ * Get the persistent anonymous ID
282
+ */
283
+ getAnonymousId() {
284
+ return this.state.anonymousId;
285
+ }
274
286
  /**
275
287
  * Get detailed attribution data
276
288
  */
@@ -383,11 +395,14 @@ export class DatalyrSDK {
383
395
  const payload = {
384
396
  workspaceId: this.state.config.workspaceId || 'mobile_sdk',
385
397
  visitorId: this.state.visitorId,
398
+ anonymousId: this.state.anonymousId, // Include persistent anonymous ID
386
399
  sessionId: this.state.sessionId,
387
400
  eventId: generateUUID(),
388
401
  eventName,
389
402
  eventData: {
390
403
  ...eventData,
404
+ // Include anonymous_id in event data for attribution
405
+ anonymous_id: this.state.anonymousId,
391
406
  // Auto-captured mobile data
392
407
  platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
393
408
  os_version: deviceInfo.osVersion,
@@ -578,6 +593,9 @@ export class Datalyr {
578
593
  static getStatus() {
579
594
  return datalyr.getStatus();
580
595
  }
596
+ static getAnonymousId() {
597
+ return datalyr.getAnonymousId();
598
+ }
581
599
  static getAttributionData() {
582
600
  return datalyr.getAttributionData();
583
601
  }
@@ -167,6 +167,7 @@ export class HttpClient {
167
167
  const testPayload = {
168
168
  workspaceId: 'test',
169
169
  visitorId: 'test',
170
+ anonymousId: 'test',
170
171
  sessionId: 'test',
171
172
  eventId: 'test',
172
173
  eventName: 'connection_test',
package/lib/types.d.ts CHANGED
@@ -59,6 +59,7 @@ export interface FingerprintData {
59
59
  export interface EventPayload {
60
60
  workspaceId: string;
61
61
  visitorId: string;
62
+ anonymousId: string;
62
63
  sessionId: string;
63
64
  eventId: string;
64
65
  eventName: string;
@@ -81,6 +82,7 @@ export interface SDKState {
81
82
  initialized: boolean;
82
83
  config: DatalyrConfig;
83
84
  visitorId: string;
85
+ anonymousId: string;
84
86
  sessionId: string;
85
87
  currentUserId?: string;
86
88
  userProperties: UserProperties;
package/lib/utils.d.ts CHANGED
@@ -2,6 +2,7 @@ import 'react-native-get-random-values';
2
2
  import { DeviceInfo as DeviceInfoType, FingerprintData } from './types';
3
3
  export declare const STORAGE_KEYS: {
4
4
  VISITOR_ID: string;
5
+ ANONYMOUS_ID: string;
5
6
  SESSION_ID: string;
6
7
  USER_ID: string;
7
8
  USER_PROPERTIES: string;
@@ -25,6 +26,11 @@ export declare const hashString: (str: string) => string;
25
26
  * Get or create a persistent visitor ID
26
27
  */
27
28
  export declare const getOrCreateVisitorId: () => Promise<string>;
29
+ /**
30
+ * Get or create a persistent anonymous ID
31
+ * This ID persists across app reinstalls and never changes
32
+ */
33
+ export declare const getOrCreateAnonymousId: () => Promise<string>;
28
34
  /**
29
35
  * Get or create a session ID (with session timeout logic)
30
36
  */
package/lib/utils.js CHANGED
@@ -13,6 +13,7 @@ import 'react-native-get-random-values'; // Required for uuid
13
13
  // Storage Keys
14
14
  export const STORAGE_KEYS = {
15
15
  VISITOR_ID: '@datalyr/visitor_id',
16
+ ANONYMOUS_ID: '@datalyr/anonymous_id', // Persistent anonymous identifier
16
17
  SESSION_ID: '@datalyr/session_id',
17
18
  USER_ID: '@datalyr/user_id',
18
19
  USER_PROPERTIES: '@datalyr/user_properties',
@@ -63,6 +64,25 @@ export const getOrCreateVisitorId = async () => {
63
64
  return generateUUID(); // Fallback to memory-only ID
64
65
  }
65
66
  };
67
+ /**
68
+ * Get or create a persistent anonymous ID
69
+ * This ID persists across app reinstalls and never changes
70
+ */
71
+ export const getOrCreateAnonymousId = async () => {
72
+ try {
73
+ let anonymousId = await AsyncStorage.getItem(STORAGE_KEYS.ANONYMOUS_ID);
74
+ if (!anonymousId) {
75
+ // Generate anonymous_id with anon_ prefix to match web SDK
76
+ anonymousId = `anon_${generateUUID()}`;
77
+ await AsyncStorage.setItem(STORAGE_KEYS.ANONYMOUS_ID, anonymousId);
78
+ }
79
+ return anonymousId;
80
+ }
81
+ catch (error) {
82
+ console.warn('Failed to get/create anonymous ID:', error);
83
+ return `anon_${generateUUID()}`; // Fallback to memory-only ID
84
+ }
85
+ };
66
86
  /**
67
87
  * Get or create a session ID (with session timeout logic)
68
88
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalyr/react-native",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -10,6 +10,7 @@ import {
10
10
  } from './types';
11
11
  import {
12
12
  getOrCreateVisitorId,
13
+ getOrCreateAnonymousId,
13
14
  getOrCreateSessionId,
14
15
  createFingerprintData,
15
16
  generateUUID,
@@ -56,6 +57,7 @@ export class DatalyrSDK {
56
57
  respectDoNotTrack: true,
57
58
  },
58
59
  visitorId: '',
60
+ anonymousId: '', // Persistent anonymous identifier
59
61
  sessionId: '',
60
62
  userProperties: {},
61
63
  eventQueue: [],
@@ -106,8 +108,9 @@ export class DatalyrSDK {
106
108
  maxRetryCount: this.state.config.maxRetries || 3,
107
109
  });
108
110
 
109
- // Initialize visitor ID and session
111
+ // Initialize visitor ID, anonymous ID and session
110
112
  this.state.visitorId = await getOrCreateVisitorId();
113
+ this.state.anonymousId = await getOrCreateAnonymousId();
111
114
  this.state.sessionId = await getOrCreateSessionId();
112
115
 
113
116
  // Load persisted user data
@@ -174,6 +177,7 @@ export class DatalyrSDK {
174
177
  debugLog('Datalyr SDK initialized successfully', {
175
178
  workspaceId: this.state.config.workspaceId,
176
179
  visitorId: this.state.visitorId,
180
+ anonymousId: this.state.anonymousId,
177
181
  sessionId: this.state.sessionId,
178
182
  });
179
183
 
@@ -251,9 +255,10 @@ export class DatalyrSDK {
251
255
  // Persist user data
252
256
  await this.persistUserData();
253
257
 
254
- // Track identify event
255
- await this.track('identify', {
258
+ // Track $identify event for identity resolution
259
+ await this.track('$identify', {
256
260
  userId,
261
+ anonymous_id: this.state.anonymousId,
257
262
  ...properties
258
263
  });
259
264
 
@@ -276,6 +281,7 @@ export class DatalyrSDK {
276
281
  newUserId,
277
282
  previousId: previousId || this.state.visitorId,
278
283
  visitorId: this.state.visitorId,
284
+ anonymousId: this.state.anonymousId, // Include for identity resolution
279
285
  };
280
286
 
281
287
  debugLog('Aliasing user:', aliasData);
@@ -335,6 +341,7 @@ export class DatalyrSDK {
335
341
  initialized: boolean;
336
342
  workspaceId: string;
337
343
  visitorId: string;
344
+ anonymousId: string;
338
345
  sessionId: string;
339
346
  currentUserId?: string;
340
347
  queueStats: any;
@@ -344,6 +351,7 @@ export class DatalyrSDK {
344
351
  initialized: this.state.initialized,
345
352
  workspaceId: this.state.config.workspaceId || '',
346
353
  visitorId: this.state.visitorId,
354
+ anonymousId: this.state.anonymousId,
347
355
  sessionId: this.state.sessionId,
348
356
  currentUserId: this.state.currentUserId,
349
357
  queueStats: this.eventQueue.getStats(),
@@ -351,6 +359,13 @@ export class DatalyrSDK {
351
359
  };
352
360
  }
353
361
 
362
+ /**
363
+ * Get the persistent anonymous ID
364
+ */
365
+ getAnonymousId(): string {
366
+ return this.state.anonymousId;
367
+ }
368
+
354
369
  /**
355
370
  * Get detailed attribution data
356
371
  */
@@ -489,11 +504,14 @@ export class DatalyrSDK {
489
504
  const payload: EventPayload = {
490
505
  workspaceId: this.state.config.workspaceId || 'mobile_sdk',
491
506
  visitorId: this.state.visitorId,
507
+ anonymousId: this.state.anonymousId, // Include persistent anonymous ID
492
508
  sessionId: this.state.sessionId,
493
509
  eventId: generateUUID(),
494
510
  eventName,
495
511
  eventData: {
496
512
  ...eventData,
513
+ // Include anonymous_id in event data for attribution
514
+ anonymous_id: this.state.anonymousId,
497
515
  // Auto-captured mobile data
498
516
  platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
499
517
  os_version: deviceInfo.osVersion,
@@ -721,6 +739,10 @@ export class Datalyr {
721
739
  return datalyr.getStatus();
722
740
  }
723
741
 
742
+ static getAnonymousId(): string {
743
+ return datalyr.getAnonymousId();
744
+ }
745
+
724
746
  static getAttributionData(): AttributionData {
725
747
  return datalyr.getAttributionData();
726
748
  }
@@ -213,6 +213,7 @@ export class HttpClient {
213
213
  const testPayload: EventPayload = {
214
214
  workspaceId: 'test',
215
215
  visitorId: 'test',
216
+ anonymousId: 'test',
216
217
  sessionId: 'test',
217
218
  eventId: 'test',
218
219
  eventName: 'connection_test',
package/src/types.ts CHANGED
@@ -66,6 +66,7 @@ export interface FingerprintData {
66
66
  export interface EventPayload {
67
67
  workspaceId: string;
68
68
  visitorId: string;
69
+ anonymousId: string; // Persistent anonymous identifier
69
70
  sessionId: string;
70
71
  eventId: string;
71
72
  eventName: string;
@@ -92,6 +93,7 @@ export interface SDKState {
92
93
  initialized: boolean;
93
94
  config: DatalyrConfig;
94
95
  visitorId: string;
96
+ anonymousId: string; // Persistent anonymous identifier
95
97
  sessionId: string;
96
98
  currentUserId?: string;
97
99
  userProperties: UserProperties;
package/src/utils.ts CHANGED
@@ -16,6 +16,7 @@ import { DeviceInfo as DeviceInfoType, FingerprintData } from './types';
16
16
  // Storage Keys
17
17
  export const STORAGE_KEYS = {
18
18
  VISITOR_ID: '@datalyr/visitor_id',
19
+ ANONYMOUS_ID: '@datalyr/anonymous_id', // Persistent anonymous identifier
19
20
  SESSION_ID: '@datalyr/session_id',
20
21
  USER_ID: '@datalyr/user_id',
21
22
  USER_PROPERTIES: '@datalyr/user_properties',
@@ -71,6 +72,25 @@ export const getOrCreateVisitorId = async (): Promise<string> => {
71
72
  }
72
73
  };
73
74
 
75
+ /**
76
+ * Get or create a persistent anonymous ID
77
+ * This ID persists across app reinstalls and never changes
78
+ */
79
+ export const getOrCreateAnonymousId = async (): Promise<string> => {
80
+ try {
81
+ let anonymousId = await AsyncStorage.getItem(STORAGE_KEYS.ANONYMOUS_ID);
82
+ if (!anonymousId) {
83
+ // Generate anonymous_id with anon_ prefix to match web SDK
84
+ anonymousId = `anon_${generateUUID()}`;
85
+ await AsyncStorage.setItem(STORAGE_KEYS.ANONYMOUS_ID, anonymousId);
86
+ }
87
+ return anonymousId;
88
+ } catch (error) {
89
+ console.warn('Failed to get/create anonymous ID:', error);
90
+ return `anon_${generateUUID()}`; // Fallback to memory-only ID
91
+ }
92
+ };
93
+
74
94
  /**
75
95
  * Get or create a session ID (with session timeout logic)
76
96
  */