@datalyr/web 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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @datalyr/web
2
2
 
3
- Browser analytics and attribution SDK for web applications. Track events, identify users, and capture attribution data across your website.
3
+ Browser SDK for event tracking, user identity, and attribution. Version 1.4.0.
4
4
 
5
5
  ## Table of Contents
6
6
 
@@ -8,27 +8,20 @@ Browser analytics and attribution SDK for web applications. Track events, identi
8
8
  - [Quick Start](#quick-start)
9
9
  - [How It Works](#how-it-works)
10
10
  - [Configuration](#configuration)
11
- - [Event Tracking](#event-tracking)
12
- - [Custom Events](#custom-events)
13
- - [Page Views](#page-views)
14
- - [E-Commerce Events](#e-commerce-events)
15
- - [User Identity](#user-identity)
16
- - [Anonymous ID](#anonymous-id)
17
- - [Identifying Users](#identifying-users)
18
- - [User Properties](#user-properties)
19
- - [Attribution](#attribution)
20
- - [Automatic Capture](#automatic-capture)
21
- - [Custom Parameters](#custom-parameters)
11
+ - [API Reference](#api-reference)
12
+ - [Event Tracking](#event-tracking)
13
+ - [User Identity](#user-identity)
14
+ - [Session](#session)
15
+ - [Attribution](#attribution)
16
+ - [Super Properties](#super-properties)
17
+ - [Privacy and Consent](#privacy-and-consent)
18
+ - [Queue and Network](#queue-and-network)
19
+ - [Container Scripts](#container-scripts)
20
+ - [Debugging](#debugging)
21
+ - [Lifecycle](#lifecycle)
22
22
  - [Web-to-App Attribution](#web-to-app-attribution)
23
- - [Event Queue](#event-queue)
24
- - [Offline Support](#offline-support)
25
- - [Privacy and Consent](#privacy-and-consent)
26
23
  - [SPA Support](#spa-support)
27
- - [Container Scripts](#container-scripts)
28
24
  - [Framework Integration](#framework-integration)
29
- - [React](#react)
30
- - [Vue](#vue)
31
- - [Next.js](#nextjs)
32
25
  - [TypeScript](#typescript)
33
26
  - [Troubleshooting](#troubleshooting)
34
27
  - [License](#license)
@@ -37,7 +30,7 @@ Browser analytics and attribution SDK for web applications. Track events, identi
37
30
 
38
31
  ## Installation
39
32
 
40
- ### NPM Package
33
+ ### NPM
41
34
 
42
35
  ```bash
43
36
  npm install @datalyr/web
@@ -50,7 +43,16 @@ npm install @datalyr/web
50
43
  data-workspace-id="YOUR_WORKSPACE_ID"></script>
51
44
  ```
52
45
 
53
- The script tag loads externally (zero bundle size) and auto-initializes. Use `window.datalyr` to access the SDK.
46
+ The script tag loads externally (zero bundle size) and auto-initializes. Access the SDK via `window.datalyr`.
47
+
48
+ Choose one installation method. Using both causes conflicts.
49
+
50
+ | Feature | Script Tag | NPM |
51
+ |---------|-----------|-----|
52
+ | Bundle Size | 0 (external) | ~15KB |
53
+ | TypeScript | No | Yes |
54
+ | Access | `window.datalyr` | `import datalyr` |
55
+ | Initialize | Automatic | `datalyr.init()` |
54
56
 
55
57
  ---
56
58
 
@@ -59,34 +61,32 @@ The script tag loads externally (zero bundle size) and auto-initializes. Use `wi
59
61
  ```javascript
60
62
  import datalyr from '@datalyr/web';
61
63
 
62
- // Initialize
63
64
  datalyr.init({
64
65
  workspaceId: 'YOUR_WORKSPACE_ID'
65
66
  });
66
67
 
68
+ // Wait for async initialization (encryption, container, initial page view)
69
+ await datalyr.ready();
70
+
67
71
  // Track events
68
72
  datalyr.track('button_clicked', { button: 'signup' });
69
73
 
70
74
  // Identify users
71
75
  datalyr.identify('user_123', { email: 'user@example.com' });
72
76
 
73
- // Track page views
74
- datalyr.page('Pricing', { variant: 'A' });
77
+ // Track page views (page name goes in properties)
78
+ datalyr.page({ title: 'Pricing', variant: 'A' });
75
79
  ```
76
80
 
77
81
  ---
78
82
 
79
83
  ## How It Works
80
84
 
81
- The SDK collects events and sends them to the Datalyr backend for analytics and attribution.
82
-
83
- ### Data Flow
84
-
85
- 1. Events are created with `track()`, `page()`, or `identify()`
86
- 2. Each event includes device info, session data, and attribution parameters
87
- 3. Events are queued locally and sent in batches
88
- 4. If offline, events are stored and sent when connectivity returns
89
- 5. Events are processed server-side for analytics and attribution reporting
85
+ 1. Events are created with `track()`, `page()`, `identify()`, etc.
86
+ 2. Each event includes device context, session data, and attribution parameters.
87
+ 3. Events are queued locally and sent in batches.
88
+ 4. If the browser is offline, events are stored and sent when connectivity returns.
89
+ 5. Events are processed server-side for analytics and attribution reporting.
90
90
 
91
91
  ### Event Payload
92
92
 
@@ -94,25 +94,30 @@ Every event includes:
94
94
 
95
95
  ```javascript
96
96
  {
97
- event: 'purchase', // Event name
98
- properties: { ... }, // Custom properties
97
+ event_name: 'purchase',
98
+ event_data: { ... }, // Custom + attribution + session properties
99
99
 
100
100
  // Identity
101
- anonymous_id: 'anon_xxxxx', // Persistent browser ID
102
- user_id: 'user_123', // Set after identify()
103
- session_id: 'sess_xxxxx', // Current session
104
-
105
- // Context
106
- page_url: 'https://example.com/pricing',
107
- page_title: 'Pricing',
101
+ anonymous_id: 'anon_xxxxx', // Persistent browser ID
102
+ distinct_id: 'user_123', // user_id if identified, else anonymous_id
103
+ user_id: 'user_123', // Set after identify()
104
+ session_id: 'sess_xxxxx', // Current session
105
+
106
+ // Context (in event_data)
107
+ url: 'https://example.com/pricing',
108
+ title: 'Pricing',
108
109
  referrer: 'https://google.com',
109
110
 
110
- // Attribution (if captured)
111
+ // Attribution (in event_data, if captured)
111
112
  utm_source: 'facebook',
112
113
  fbclid: 'abc123',
113
114
 
114
- // Timestamps
115
+ // Metadata
116
+ workspace_id: 'wk_xxxxx',
117
+ source: 'web',
115
118
  timestamp: '2024-01-15T10:30:00Z',
119
+ sdk_version: '1.4.0',
120
+ sdk_name: 'datalyr-web-sdk'
116
121
  }
117
122
  ```
118
123
 
@@ -120,53 +125,94 @@ Every event includes:
120
125
 
121
126
  ## Configuration
122
127
 
123
- ```javascript
128
+ All properties of `DatalyrConfig`:
129
+
130
+ ```typescript
124
131
  datalyr.init({
125
132
  // Required
126
133
  workspaceId: string,
127
134
 
128
- // Features
129
- debug?: boolean, // Console logging
130
- trackPageViews?: boolean, // Track initial page view (default: true)
131
- trackSPA?: boolean, // Track SPA route changes (default: true)
135
+ // Endpoint
136
+ endpoint?: string, // Default: 'https://ingest.datalyr.com'
137
+ fallbackEndpoints?: string[], // Default: [] - Additional endpoints to try on failure
132
138
 
133
- // Event Queue
134
- batchSize?: number, // Events per batch (default: 10)
135
- flushInterval?: number, // Send interval ms (default: 5000)
139
+ // Debugging
140
+ debug?: boolean, // Default: false - Enable console logging
136
141
 
137
- // Session
138
- sessionTimeout?: number, // Session timeout ms (default: 1800000 / 30 min)
142
+ // Batching
143
+ batchSize?: number, // Default: 10 - Events per batch
144
+ flushInterval?: number, // Default: 5000 - Flush interval in ms
145
+ flushAt?: number, // Default: 10 - Flush when N events queued
139
146
 
140
- // Privacy
141
- privacyMode?: 'standard' | 'strict', // Privacy level (default: 'standard')
142
- respectDoNotTrack?: boolean, // Honor browser DNT (default: false)
143
- respectGlobalPrivacyControl?: boolean, // Honor GPC (default: true)
147
+ // Priority events (bypass normal batching)
148
+ criticalEvents?: string[], // Default: undefined (disabled). Events flushed immediately.
149
+ highPriorityEvents?: string[], // Default: undefined (disabled). Events given queue priority.
144
150
 
145
- // Cookies
146
- cookieDomain?: string | 'auto', // Cookie domain (default: 'auto')
151
+ // Session
152
+ sessionTimeout?: number, // Default: 3600000 (60 min)
153
+ trackSessions?: boolean, // Default: true
147
154
 
148
- // Container
149
- enableContainer?: boolean, // Load container scripts (default: true)
155
+ // Attribution
156
+ attributionWindow?: number, // Default: 7776000000 (90 days)
157
+ trackedParams?: string[], // Default: [] - Additional URL params to capture
150
158
 
151
- // Custom
152
- trackedParams?: string[], // Additional URL params to track
153
- plugins?: Plugin[], // Custom plugins
159
+ // Privacy
160
+ respectDoNotTrack?: boolean, // Default: false - Honor browser DNT header
161
+ respectGlobalPrivacyControl?: boolean, // Default: true - Honor GPC signal
162
+ privacyMode?: 'standard' | 'strict', // Default: 'standard' - 'strict' limits data collection
163
+
164
+ // Cookies / Storage
165
+ cookieDomain?: string | 'auto', // Default: 'auto'
166
+ cookieExpires?: number, // Default: 365 (days)
167
+ secureCookie?: boolean | 'auto', // Default: 'auto'
168
+ sameSite?: 'Strict' | 'Lax' | 'None', // Default: 'Lax'
169
+ cookiePrefix?: string, // Default: '__dl_'
170
+
171
+ // Performance
172
+ enablePerformanceTracking?: boolean, // Default: true - Collect navigation timing metrics
173
+ enableFingerprinting?: boolean, // Default: true - Minimal device fingerprinting (standard mode only)
174
+
175
+ // Retry / Offline
176
+ maxRetries?: number, // Default: 5
177
+ retryDelay?: number, // Default: 1000 (ms)
178
+ maxOfflineQueueSize?: number, // Default: 100
179
+
180
+ // SPA
181
+ trackSPA?: boolean, // Default: true - Auto-track route changes
182
+ trackPageViews?: boolean, // Default: true - Auto-track initial page view
183
+
184
+ // Container scripts
185
+ enableContainer?: boolean, // Default: true - Load third-party scripts from dashboard
186
+
187
+ // Auto-Identify (opt-in)
188
+ autoIdentify?: boolean, // Default: false - Automatically identify users
189
+ autoIdentifyForms?: boolean, // Default: true - Capture email from forms (when autoIdentify enabled)
190
+ autoIdentifyAPI?: boolean, // Default: true - Capture email from API responses (when autoIdentify enabled)
191
+ autoIdentifyShopify?: boolean, // Default: true - Capture email from Shopify endpoints (when autoIdentify enabled)
192
+ autoIdentifyTrustedDomains?: string[], // Default: [] - Additional domains to trust for API capture
193
+
194
+ // Plugins
195
+ plugins?: DatalyrPlugin[], // Default: [] - Custom plugin instances
154
196
  });
155
197
  ```
156
198
 
157
199
  ---
158
200
 
159
- ## Event Tracking
201
+ ## API Reference
160
202
 
161
- ### Custom Events
203
+ ### Event Tracking
162
204
 
163
- Track any action on your website:
205
+ #### `track(eventName, properties?)`
206
+
207
+ Track a custom event.
208
+
209
+ ```typescript
210
+ track(eventName: string, properties?: EventProperties): void
211
+ ```
164
212
 
165
213
  ```javascript
166
- // Simple event
167
214
  datalyr.track('signup_started');
168
215
 
169
- // Event with properties
170
216
  datalyr.track('product_viewed', {
171
217
  product_id: 'SKU123',
172
218
  product_name: 'Blue Shirt',
@@ -174,366 +220,549 @@ datalyr.track('product_viewed', {
174
220
  currency: 'USD',
175
221
  category: 'Apparel',
176
222
  });
177
-
178
- // Event with value
179
- datalyr.track('level_completed', {
180
- level: 5,
181
- score: 1250,
182
- time_seconds: 120,
183
- });
184
223
  ```
185
224
 
186
- ### Page Views
225
+ #### `page(properties?)`
187
226
 
188
- Track navigation:
227
+ Track a page view. The page `title`, `url`, `path`, `search`, and `referrer` are captured automatically. Pass `properties` to add or override values.
228
+
229
+ ```typescript
230
+ page(properties?: PageProperties): void
231
+ ```
189
232
 
190
233
  ```javascript
191
- // Track current page
234
+ // Track current page (all properties auto-captured)
192
235
  datalyr.page();
193
236
 
194
- // Track with name
195
- datalyr.page('Pricing');
237
+ // Override title, add custom properties
238
+ datalyr.page({ title: 'Pricing', variant: 'A' });
196
239
 
197
- // Track with properties
198
- datalyr.page('Product Details', {
199
- product_id: 'SKU123',
200
- source: 'search',
201
- });
240
+ // Add custom properties
241
+ datalyr.page({ product_id: 'SKU123', source: 'search' });
202
242
  ```
203
243
 
204
- ### E-Commerce Events
205
-
206
- Standard e-commerce tracking:
244
+ Note: `page()` does not accept a name string as the first argument. To set the page name, pass it as `title` in the properties object.
207
245
 
208
- ```javascript
209
- // View product
210
- datalyr.track('Product Viewed', {
211
- product_id: 'SKU123',
212
- product_name: 'Blue Shirt',
213
- price: 29.99,
214
- currency: 'USD'
215
- });
246
+ #### `screen(screenName, properties?)`
216
247
 
217
- // Add to cart
218
- datalyr.track('Add to Cart', {
219
- product_id: 'SKU123',
220
- quantity: 1,
221
- price: 29.99
222
- });
248
+ Track a screen view. Intended for SPAs or hybrid apps.
223
249
 
224
- // Start checkout
225
- datalyr.track('Checkout Started', {
226
- cart_value: 59.98,
227
- currency: 'USD',
228
- item_count: 2
229
- });
250
+ ```typescript
251
+ screen(screenName: string, properties?: Record<string, any>): void
252
+ ```
230
253
 
231
- // Complete purchase
232
- datalyr.track('Purchase', {
233
- order_id: 'ORD-456',
234
- total: 59.98,
235
- currency: 'USD',
236
- items: [
237
- { product_id: 'SKU123', quantity: 2, price: 29.99 }
238
- ]
239
- });
254
+ ```javascript
255
+ datalyr.screen('Dashboard');
256
+ datalyr.screen('Product Details', { product_id: 'SKU123' });
240
257
  ```
241
258
 
242
- ---
259
+ Internally fires a `pageview` event with the `screen` property set.
243
260
 
244
- ## User Identity
261
+ #### `trackAppDownloadClick(options)`
245
262
 
246
- ### Anonymous ID
263
+ Track an app download click, then redirect to the app store. Fires a `$app_download_click` event with full attribution, flushes via `sendBeacon`, and redirects.
247
264
 
248
- Every visitor gets a persistent anonymous ID on first visit:
265
+ ```typescript
266
+ trackAppDownloadClick(options: {
267
+ targetPlatform: 'ios' | 'android';
268
+ appStoreUrl: string;
269
+ }): void
270
+ ```
249
271
 
250
272
  ```javascript
251
- const anonymousId = datalyr.getAnonymousId();
252
- // 'anon_a1b2c3d4e5f6'
273
+ datalyr.trackAppDownloadClick({
274
+ targetPlatform: 'ios',
275
+ appStoreUrl: 'https://apps.apple.com/app/your-app/id123456789',
276
+ });
253
277
  ```
254
278
 
255
- This ID:
256
- - Persists across browser sessions
257
- - Links events before and after user identification
258
- - Can be passed to your backend for server-side attribution
259
-
260
- ### Identifying Users
279
+ For Android Play Store URLs, attribution parameters are automatically encoded into the `referrer` query parameter for deterministic install attribution.
261
280
 
262
- Link the anonymous ID to a known user:
281
+ ---
263
282
 
264
- ```javascript
265
- datalyr.identify('user_123', {
266
- email: 'user@example.com',
267
- });
268
- ```
283
+ ### User Identity
269
284
 
270
- After `identify()`:
271
- - All future events include `user_id`
272
- - Historical anonymous events can be linked server-side
273
- - Attribution data is preserved on the user
285
+ #### `identify(userId, traits?)`
274
286
 
275
- ### User Properties
287
+ Link the anonymous visitor to a known user. Rotates the session ID on call (prevents session fixation). User traits are encrypted at rest.
276
288
 
277
- Pass any user attributes:
289
+ ```typescript
290
+ identify(userId: string, traits?: UserTraits): void
291
+ ```
278
292
 
279
293
  ```javascript
280
294
  datalyr.identify('user_123', {
281
- // Standard fields
282
295
  email: 'user@example.com',
283
296
  name: 'John Doe',
284
- phone: '+1234567890',
285
-
286
- // Custom fields
287
297
  plan: 'premium',
288
298
  company: 'Acme Inc',
289
- signup_date: '2024-01-15',
290
299
  });
291
300
  ```
292
301
 
293
- ### Logout
302
+ #### `alias(userId, previousId?)`
294
303
 
295
- Clear user data on logout:
304
+ Create an alias linking one user ID to another. If `previousId` is omitted, the current anonymous ID is used.
305
+
306
+ ```typescript
307
+ alias(userId: string, previousId?: string): void
308
+ ```
296
309
 
297
310
  ```javascript
298
- datalyr.reset();
311
+ datalyr.alias('user_123', 'temp_user_456');
299
312
  ```
300
313
 
301
- This:
302
- - Clears the user ID
303
- - Starts a new session
304
- - Keeps the anonymous ID (same browser)
314
+ #### `group(groupId, traits?)`
305
315
 
306
- ---
316
+ Associate the current user with a group or account.
307
317
 
308
- ## Attribution
318
+ ```typescript
319
+ group(groupId: string, traits?: Record<string, any>): void
320
+ ```
321
+
322
+ ```javascript
323
+ datalyr.group('company_abc', { name: 'Acme Inc', plan: 'enterprise' });
324
+ ```
309
325
 
310
- ### Automatic Capture
326
+ #### `reset()`
311
327
 
312
- The SDK captures attribution from URLs and referrers:
328
+ Clear user identity and start a new session. The anonymous ID is preserved.
313
329
 
314
330
  ```javascript
315
- const attribution = datalyr.getAttributionData();
331
+ datalyr.reset();
316
332
  ```
317
333
 
318
- Captured parameters:
334
+ #### `getAnonymousId()`
319
335
 
320
- | Type | Parameters |
321
- |------|------------|
322
- | UTM | `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term` |
323
- | Click IDs | `fbclid`, `gclid`, `ttclid`, `twclid`, `li_fat_id`, `msclkid` |
324
- | Referrer | `referrer`, `landing_page` |
336
+ Returns the persistent anonymous ID assigned on first visit.
325
337
 
326
- Attribution is automatically included in all events and supports:
327
- - First-touch attribution (90-day window)
328
- - Last-touch attribution
329
- - Customer journey tracking (up to 30 touchpoints)
338
+ ```typescript
339
+ getAnonymousId(): string
340
+ ```
330
341
 
331
- ### Custom Parameters
342
+ #### `getUserId()`
332
343
 
333
- Track additional URL parameters:
344
+ Returns the current user ID set by `identify()`, or `null` if not identified.
334
345
 
335
- ```javascript
336
- datalyr.init({
337
- workspaceId: 'YOUR_WORKSPACE_ID',
338
- trackedParams: ['ref', 'affiliate_id', 'promo_code']
339
- });
346
+ ```typescript
347
+ getUserId(): string | null
348
+ ```
349
+
350
+ #### `getDistinctId()`
351
+
352
+ Returns the distinct ID. This is `userId` if identified, otherwise `anonymousId`.
353
+
354
+ ```typescript
355
+ getDistinctId(): string
340
356
  ```
341
357
 
342
358
  ---
343
359
 
344
- ## Web-to-App Attribution
360
+ ### Session
345
361
 
346
- Track users who click a "Download App" button on your website and attribute their app install back to the original ad click.
362
+ #### `getSessionId()`
347
363
 
348
- ### How It Works
364
+ Returns the current session ID.
349
365
 
350
- 1. User clicks an ad → lands on your prelander page (web SDK loaded, captures `fbclid`, UTMs, IP)
351
- 2. User clicks "Download App" → `trackAppDownloadClick()` fires event with full attribution
352
- 3. User installs from App Store / Play Store
353
- 4. First app open → mobile SDK recovers the web attribution:
354
- - **Android**: Deterministic match via Play Store `referrer` URL parameter (~95% accuracy)
355
- - **iOS**: IP-based match against recent web events within 24 hours (~90%+ accuracy)
366
+ ```typescript
367
+ getSessionId(): string
368
+ ```
356
369
 
357
- ### Usage
370
+ #### `startNewSession()`
371
+
372
+ Force-start a new session. Returns the new session ID.
373
+
374
+ ```typescript
375
+ startNewSession(): string
376
+ ```
358
377
 
359
378
  ```javascript
360
- // On your prelander "Download" button click handler
361
- document.querySelector('#download-btn').addEventListener('click', () => {
362
- datalyr.trackAppDownloadClick({
363
- targetPlatform: 'ios', // or 'android'
364
- appStoreUrl: 'https://apps.apple.com/app/your-app/id123456789',
365
- });
366
- });
379
+ const newSessionId = datalyr.startNewSession();
367
380
  ```
368
381
 
369
- For Android, pass the Play Store URL and the SDK will automatically encode attribution params into the `referrer` parameter:
382
+ #### `getSessionData()`
370
383
 
371
- ```javascript
372
- datalyr.trackAppDownloadClick({
373
- targetPlatform: 'android',
374
- appStoreUrl: 'https://play.google.com/store/apps/details?id=com.yourapp',
375
- });
384
+ Returns the current session metadata, or `null` if no active session.
385
+
386
+ ```typescript
387
+ getSessionData(): SessionData | null
376
388
  ```
377
389
 
378
- The method fires a `$app_download_click` event (with all attribution data), flushes the event queue via `sendBeacon`, then redirects the user to the store URL.
390
+ ```typescript
391
+ interface SessionData {
392
+ id: string;
393
+ startTime: number;
394
+ lastActivity: number;
395
+ pageViews: number;
396
+ events: number;
397
+ duration: number;
398
+ isActive: boolean;
399
+ }
400
+ ```
379
401
 
380
- ### Requirements
402
+ #### `ready()`
381
403
 
382
- - Web SDK must be initialized on the prelander page
383
- - Mobile SDK (`@datalyr/react-native` or `@datalyr/swift`) must be installed in the app
384
- - Attribution is recovered automatically on first app launch — no additional mobile code needed
404
+ Returns a promise that resolves when async initialization is complete (encryption, container loading, initial page view).
405
+
406
+ ```typescript
407
+ ready(): Promise<void>
408
+ ```
409
+
410
+ ```javascript
411
+ datalyr.init({ workspaceId: 'YOUR_WORKSPACE_ID' });
412
+ await datalyr.ready();
413
+ // Encryption is initialized, container is loaded, safe to track
414
+ ```
385
415
 
386
416
  ---
387
417
 
388
- ## Event Queue
418
+ ### Attribution
389
419
 
390
- Events are batched for efficiency.
420
+ #### `getAttribution()`
391
421
 
392
- ### Configuration
422
+ Returns the current captured attribution data.
423
+
424
+ ```typescript
425
+ getAttribution(): Attribution
426
+ ```
393
427
 
394
428
  ```javascript
395
- datalyr.init({
396
- workspaceId: 'YOUR_WORKSPACE_ID',
397
- batchSize: 10, // Send when 10 events queued
398
- flushInterval: 5000, // Or every 5 seconds
399
- });
429
+ const attribution = datalyr.getAttribution();
430
+ console.log(attribution.source, attribution.medium, attribution.campaign);
431
+ ```
432
+
433
+ ```typescript
434
+ interface Attribution {
435
+ source?: string | null;
436
+ medium?: string | null;
437
+ campaign?: string | null;
438
+ term?: string | null;
439
+ content?: string | null;
440
+ clickId?: string | null;
441
+ clickIdType?: string | null; // 'fbclid', 'gclid', etc.
442
+ referrer?: string | null;
443
+ referrerHost?: string | null;
444
+ landingPage?: string | null;
445
+ landingPath?: string | null;
446
+ timestamp?: number;
447
+ [key: string]: any; // Custom tracked params
448
+ }
400
449
  ```
401
450
 
402
- ### Manual Flush
451
+ Captured automatically from URLs:
452
+
453
+ | Type | Parameters |
454
+ |------|------------|
455
+ | UTM | `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term` |
456
+ | Click IDs | `fbclid`, `gclid`, `ttclid`, `twclid`, `li_fat_id`, `msclkid` |
457
+ | Referrer | `referrer`, `landing_page` |
458
+
459
+ Attribution supports first-touch (90-day window), last-touch, and journey tracking.
403
460
 
404
- Send all queued events immediately:
461
+ #### `setAttribution(attribution)`
462
+
463
+ Manually set or override attribution data.
464
+
465
+ ```typescript
466
+ setAttribution(attribution: Partial<Attribution>): void
467
+ ```
405
468
 
406
469
  ```javascript
407
- await datalyr.flush();
470
+ datalyr.setAttribution({
471
+ source: 'partner',
472
+ medium: 'referral',
473
+ campaign: 'spring_promo',
474
+ });
408
475
  ```
409
476
 
410
- ### Priority Events
477
+ #### `getJourney()`
478
+
479
+ Returns the customer journey as an array of touchpoints (up to 30).
411
480
 
412
- Important events like `Purchase`, `Signup`, and `Subscribe` are automatically prioritized for faster delivery.
481
+ ```typescript
482
+ getJourney(): TouchPoint[]
483
+ ```
484
+
485
+ ```typescript
486
+ interface TouchPoint {
487
+ timestamp: number;
488
+ source?: string;
489
+ medium?: string;
490
+ campaign?: string;
491
+ sessionId: string;
492
+ }
493
+ ```
413
494
 
414
495
  ---
415
496
 
416
- ## Offline Support
497
+ ### Super Properties
498
+
499
+ Super properties are included with every subsequent event.
500
+
501
+ #### `setSuperProperties(properties)`
417
502
 
418
- When the browser is offline:
419
- - Events are stored locally
420
- - Queue persists across page refreshes
421
- - Events are sent when connectivity returns
503
+ Merge properties into the super properties store.
504
+
505
+ ```typescript
506
+ setSuperProperties(properties: Record<string, any>): void
507
+ ```
422
508
 
423
509
  ```javascript
424
- // Events work seamlessly offline
425
- datalyr.track('Button Clicked'); // Automatically queued if offline
510
+ datalyr.setSuperProperties({ app_version: '2.1.0', environment: 'production' });
511
+ ```
426
512
 
427
- // Manually flush when ready
428
- await datalyr.flush();
513
+ #### `unsetSuperProperty(propertyName)`
514
+
515
+ Remove a single super property by key.
516
+
517
+ ```typescript
518
+ unsetSuperProperty(propertyName: string): void
519
+ ```
520
+
521
+ ```javascript
522
+ datalyr.unsetSuperProperty('environment');
523
+ ```
524
+
525
+ #### `getSuperProperties()`
526
+
527
+ Returns a copy of the current super properties.
528
+
529
+ ```typescript
530
+ getSuperProperties(): Record<string, any>
429
531
  ```
430
532
 
431
533
  ---
432
534
 
433
- ## Privacy and Consent
535
+ ### Privacy and Consent
434
536
 
435
- ### Opt Out
537
+ #### `optOut()`
538
+
539
+ Opt the user out of all tracking. Clears the event queue and persists the preference in a cookie.
436
540
 
437
541
  ```javascript
438
- // Opt user out of tracking
439
542
  datalyr.optOut();
543
+ ```
544
+
545
+ #### `optIn()`
440
546
 
441
- // Opt user back in
547
+ Opt the user back in.
548
+
549
+ ```javascript
442
550
  datalyr.optIn();
551
+ ```
552
+
553
+ #### `isOptedOut()`
554
+
555
+ Returns `true` if the user has opted out.
443
556
 
444
- // Check status
445
- const isOptedOut = datalyr.isOptedOut();
557
+ ```typescript
558
+ isOptedOut(): boolean
446
559
  ```
447
560
 
448
- ### Consent Preferences
561
+ #### `setConsent(consent)`
562
+
563
+ Set granular consent preferences.
564
+
565
+ ```typescript
566
+ setConsent(consent: ConsentConfig): void
567
+ ```
449
568
 
450
569
  ```javascript
451
570
  datalyr.setConsent({
452
571
  analytics: true,
453
572
  marketing: false,
454
573
  preferences: true,
455
- sale: false // CCPA "Do Not Sell"
574
+ sale: false // CCPA "Do Not Sell"
456
575
  });
457
576
  ```
458
577
 
459
- ### Privacy Modes
578
+ ```typescript
579
+ interface ConsentConfig {
580
+ analytics?: boolean;
581
+ marketing?: boolean;
582
+ preferences?: boolean;
583
+ sale?: boolean;
584
+ }
585
+ ```
586
+
587
+ #### Privacy Modes
460
588
 
461
589
  ```javascript
462
- // Standard mode (default)
463
- datalyr.init({
464
- workspaceId: 'YOUR_WORKSPACE_ID',
465
- privacyMode: 'standard'
466
- });
590
+ // Standard mode (default) - normal fingerprinting + tracking
591
+ datalyr.init({ workspaceId: '...', privacyMode: 'standard' });
467
592
 
468
- // Strict mode - minimal data collection
469
- datalyr.init({
470
- workspaceId: 'YOUR_WORKSPACE_ID',
471
- privacyMode: 'strict'
472
- });
593
+ // Strict mode - minimal data collection, no fingerprinting
594
+ datalyr.init({ workspaceId: '...', privacyMode: 'strict' });
473
595
  ```
474
596
 
597
+ The SDK also respects `respectDoNotTrack` and `respectGlobalPrivacyControl` config flags.
598
+
475
599
  ---
476
600
 
477
- ## SPA Support
601
+ ### Queue and Network
478
602
 
479
- For single-page applications:
603
+ #### `flush()`
604
+
605
+ Manually flush the event queue. Returns a promise that resolves when all queued events are sent.
606
+
607
+ ```typescript
608
+ flush(): Promise<void>
609
+ ```
480
610
 
481
611
  ```javascript
482
- datalyr.init({
483
- workspaceId: 'YOUR_WORKSPACE_ID',
484
- trackSPA: true // Auto-track route changes (default: true)
485
- });
612
+ await datalyr.flush();
613
+ ```
486
614
 
487
- // Manual route tracking
488
- datalyr.page('Product Details', {
489
- product_id: 'SKU-123'
490
- });
615
+ #### `getNetworkStatus()`
616
+
617
+ Returns the current network status as tracked by the SDK.
618
+
619
+ ```typescript
620
+ getNetworkStatus(): NetworkStatus
621
+ ```
622
+
623
+ ```typescript
624
+ interface NetworkStatus {
625
+ isOnline: boolean;
626
+ lastOfflineAt: number | null;
627
+ lastOnlineAt: number | null;
628
+ }
491
629
  ```
492
630
 
631
+ Events are automatically stored when offline and sent when connectivity returns.
632
+
493
633
  ---
494
634
 
495
- ## Container Scripts
635
+ ### Container Scripts
496
636
 
497
637
  Container scripts manage third-party tracking pixels (Meta, Google, TikTok) configured in your Datalyr dashboard.
498
638
 
499
- ### Script Tag Installation
639
+ #### `loadScript(scriptId)`
640
+
641
+ Trigger a container script by ID.
642
+
643
+ ```typescript
644
+ loadScript(scriptId: string): void
645
+ ```
500
646
 
501
- For container scripts, use the container endpoint:
647
+ ```javascript
648
+ datalyr.loadScript('custom-script-id');
649
+ ```
650
+
651
+ #### `getLoadedScripts()`
652
+
653
+ Returns an array of loaded script IDs.
654
+
655
+ ```typescript
656
+ getLoadedScripts(): string[]
657
+ ```
658
+
659
+ Container script tag installation (loads pixels from your dashboard config):
502
660
 
503
661
  ```html
504
662
  <script defer src="https://track.datalyr.com/container.js"
505
663
  data-workspace-id="YOUR_WORKSPACE_ID"></script>
506
664
  ```
507
665
 
508
- This loads your configured pixels and tracking scripts automatically.
666
+ ---
667
+
668
+ ### Debugging
509
669
 
510
- ### NPM SDK with Containers
670
+ #### `getErrors()`
671
+
672
+ Returns an array of SDK errors (up to 50 most recent).
673
+
674
+ ```typescript
675
+ getErrors(): ErrorInfo[]
676
+ ```
677
+
678
+ ```typescript
679
+ interface ErrorInfo {
680
+ message: string;
681
+ stack?: string;
682
+ context?: any;
683
+ timestamp: string;
684
+ url: string;
685
+ }
686
+ ```
511
687
 
512
688
  ```javascript
513
- datalyr.init({
514
- workspaceId: 'YOUR_WORKSPACE_ID',
515
- enableContainer: true // Default: true
516
- });
689
+ console.log(datalyr.getErrors());
690
+ ```
517
691
 
518
- // Manually trigger a script
519
- datalyr.loadScript('custom-script-id');
692
+ Enable `debug: true` in config to log all SDK activity to the console.
693
+
694
+ ---
695
+
696
+ ### Lifecycle
697
+
698
+ #### `destroy()`
699
+
700
+ Tear down the SDK instance. Restores patched `history.pushState` / `replaceState`, removes event listeners, cleans up the queue, session, container iframes, auto-identify listeners, and encryption keys. Resets all in-memory state.
701
+
702
+ ```typescript
703
+ destroy(): void
704
+ ```
705
+
706
+ ```javascript
707
+ datalyr.destroy();
708
+ ```
709
+
710
+ ---
711
+
712
+ ## Web-to-App Attribution
713
+
714
+ Track users who click a "Download App" button on your website and attribute their app install back to the original web session.
715
+
716
+ ### Flow
717
+
718
+ 1. User clicks an ad and lands on your page (web SDK captures `fbclid`, UTMs, etc.)
719
+ 2. User clicks "Download App" -- `trackAppDownloadClick()` fires with full attribution
720
+ 3. User installs from App Store / Play Store
721
+ 4. First app open -- mobile SDK recovers the web attribution:
722
+ - **Android**: Deterministic match via Play Store `referrer` parameter (~95% accuracy)
723
+ - **iOS**: IP-based match against recent web events within 24 hours (~90%+ accuracy)
520
724
 
521
- // Get loaded scripts
522
- const scripts = datalyr.getLoadedScripts();
725
+ ### Usage
726
+
727
+ ```javascript
728
+ document.querySelector('#download-btn').addEventListener('click', () => {
729
+ datalyr.trackAppDownloadClick({
730
+ targetPlatform: 'ios',
731
+ appStoreUrl: 'https://apps.apple.com/app/your-app/id123456789',
732
+ });
733
+ });
523
734
  ```
524
735
 
525
- ### Supported Pixels
736
+ ```javascript
737
+ // Android - attribution params auto-encoded into Play Store referrer
738
+ datalyr.trackAppDownloadClick({
739
+ targetPlatform: 'android',
740
+ appStoreUrl: 'https://play.google.com/store/apps/details?id=com.yourapp',
741
+ });
742
+ ```
526
743
 
527
- - Meta (Facebook) Pixel
528
- - Google Analytics / Google Ads
529
- - TikTok Pixel
530
- - Custom scripts and tracking pixels
744
+ ### Requirements
531
745
 
532
- Security features:
533
- - HTTPS-only script loading
534
- - XSS protection
535
- - URL validation
536
- - CSP nonce support
746
+ - Web SDK initialized on the prelander page
747
+ - Mobile SDK (`@datalyr/react-native` or `@datalyr/swift`) installed in the app
748
+ - Attribution is recovered automatically on first app launch
749
+
750
+ ---
751
+
752
+ ## SPA Support
753
+
754
+ Route changes are tracked automatically when `trackSPA: true` (the default). The SDK patches `history.pushState`, `history.replaceState`, and listens for `popstate` and `hashchange` events. Attribution cache is cleared on each navigation so new URL parameters are captured.
755
+
756
+ ```javascript
757
+ datalyr.init({
758
+ workspaceId: 'YOUR_WORKSPACE_ID',
759
+ trackSPA: true, // default
760
+ trackPageViews: true, // auto-track initial page view, default
761
+ });
762
+
763
+ // Manual page tracking
764
+ datalyr.page({ title: 'Product Details', product_id: 'SKU-123' });
765
+ ```
537
766
 
538
767
  ---
539
768
 
@@ -547,13 +776,11 @@ import datalyr from '@datalyr/web';
547
776
 
548
777
  function App() {
549
778
  useEffect(() => {
550
- datalyr.init({
551
- workspaceId: 'YOUR_WORKSPACE_ID'
552
- });
779
+ datalyr.init({ workspaceId: 'YOUR_WORKSPACE_ID' });
553
780
  }, []);
554
781
 
555
782
  const handleClick = () => {
556
- datalyr.track('Button Clicked', { button_name: 'CTA' });
783
+ datalyr.track('button_clicked', { button_name: 'CTA' });
557
784
  };
558
785
 
559
786
  return <button onClick={handleClick}>Click Me</button>;
@@ -568,13 +795,11 @@ import { onMounted } from 'vue';
568
795
  import datalyr from '@datalyr/web';
569
796
 
570
797
  onMounted(() => {
571
- datalyr.init({
572
- workspaceId: 'YOUR_WORKSPACE_ID'
573
- });
798
+ datalyr.init({ workspaceId: 'YOUR_WORKSPACE_ID' });
574
799
  });
575
800
 
576
801
  const trackClick = () => {
577
- datalyr.track('Button Clicked');
802
+ datalyr.track('button_clicked');
578
803
  };
579
804
  </script>
580
805
 
@@ -585,34 +810,32 @@ const trackClick = () => {
585
810
 
586
811
  ### Next.js
587
812
 
588
- ```jsx
813
+ ```tsx
589
814
  // app/providers.tsx
590
815
  'use client';
591
816
 
592
817
  import { useEffect } from 'react';
593
818
  import datalyr from '@datalyr/web';
594
819
 
595
- export function AnalyticsProvider({ children }) {
820
+ export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
596
821
  useEffect(() => {
597
822
  datalyr.init({
598
- workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID,
599
- debug: process.env.NODE_ENV === 'development'
823
+ workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID!,
824
+ debug: process.env.NODE_ENV === 'development',
600
825
  });
601
826
  }, []);
602
827
 
603
- return children;
828
+ return <>{children}</>;
604
829
  }
605
830
 
606
831
  // app/layout.tsx
607
832
  import { AnalyticsProvider } from './providers';
608
833
 
609
- export default function RootLayout({ children }) {
834
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
610
835
  return (
611
836
  <html>
612
837
  <body>
613
- <AnalyticsProvider>
614
- {children}
615
- </AnalyticsProvider>
838
+ <AnalyticsProvider>{children}</AnalyticsProvider>
616
839
  </body>
617
840
  </html>
618
841
  );
@@ -623,77 +846,109 @@ export default function RootLayout({ children }) {
623
846
 
624
847
  ## TypeScript
625
848
 
849
+ All types are exported from the package:
850
+
626
851
  ```typescript
627
- import datalyr, { EventProperties, UserTraits } from '@datalyr/web';
852
+ import datalyr from '@datalyr/web';
853
+ import type {
854
+ DatalyrConfig,
855
+ EventProperties,
856
+ UserTraits,
857
+ PageProperties,
858
+ SessionData,
859
+ Attribution,
860
+ TouchPoint,
861
+ ConsentConfig,
862
+ DatalyrPlugin,
863
+ FingerprintData,
864
+ IngestEventPayload,
865
+ IngestBatchPayload,
866
+ NetworkStatus,
867
+ ErrorInfo,
868
+ PerformanceMetrics,
869
+ } from '@datalyr/web';
870
+ ```
628
871
 
629
- // Type-safe event properties
872
+ ```typescript
630
873
  const properties: EventProperties = {
631
874
  product_id: 'SKU-123',
632
875
  price: 99.99,
633
- quantity: 2
876
+ quantity: 2,
634
877
  };
878
+ datalyr.track('product_added', properties);
635
879
 
636
- datalyr.track('Product Added', properties);
637
-
638
- // Type-safe user traits
639
880
  const traits: UserTraits = {
640
881
  email: 'user@example.com',
641
882
  name: 'John Doe',
642
- plan: 'premium'
883
+ plan: 'premium',
643
884
  };
644
-
645
885
  datalyr.identify('user_123', traits);
646
886
  ```
647
887
 
888
+ ### Plugin Interface
889
+
890
+ ```typescript
891
+ interface DatalyrPlugin {
892
+ name: string;
893
+ initialize(datalyr: any): void;
894
+ page?(properties: PageProperties): void;
895
+ track?(eventName: string, properties: EventProperties): void;
896
+ identify?(userId: string, traits: UserTraits): void;
897
+ loaded?(): void;
898
+ }
899
+ ```
900
+
648
901
  ---
649
902
 
650
903
  ## Troubleshooting
651
904
 
652
905
  ### Events not appearing
653
906
 
654
- 1. Check workspace ID is correct
655
- 2. Enable `debug: true`
656
- 3. Verify `datalyr.getAnonymousId()` returns a value
657
- 4. Check Network tab for requests to `ingest.datalyr.com`
658
- 5. Disable ad blockers
907
+ 1. Verify `workspaceId` is correct.
908
+ 2. Set `debug: true` and check the console.
909
+ 3. Confirm `datalyr.getAnonymousId()` returns a value.
910
+ 4. Check the Network tab for requests to `ingest.datalyr.com`.
911
+ 5. Disable ad blockers or privacy extensions.
659
912
 
660
913
  ### SDK not initialized
661
914
 
662
915
  ```javascript
663
- // Check initialization
664
916
  console.log('Anonymous ID:', datalyr.getAnonymousId());
665
917
  console.log('Session ID:', datalyr.getSessionId());
918
+ console.log('User ID:', datalyr.getUserId());
919
+ console.log('Network:', datalyr.getNetworkStatus());
666
920
  console.log('Errors:', datalyr.getErrors());
667
921
  ```
668
922
 
669
- If these return `undefined`, the SDK isn't initialized.
923
+ If these return `undefined`, the SDK is not initialized. Call `datalyr.init()` first.
924
+
925
+ ### Async initialization
926
+
927
+ `init()` returns synchronously, but encryption and container loading happen asynchronously. If you need to ensure everything is ready before tracking:
928
+
929
+ ```javascript
930
+ datalyr.init({ workspaceId: '...' });
931
+ await datalyr.ready();
932
+ datalyr.track('post_init_event');
933
+ ```
670
934
 
671
- ### Next.js SSR issues
935
+ ### Next.js SSR
672
936
 
673
- Make sure you're calling SDK methods client-side only:
937
+ SDK methods only work in the browser. Always call them inside `useEffect` or other client-side hooks:
674
938
 
675
939
  ```jsx
676
940
  'use client';
677
-
678
941
  import { useEffect } from 'react';
679
942
  import datalyr from '@datalyr/web';
680
943
 
681
944
  useEffect(() => {
682
- // SDK methods only work in browser
683
945
  datalyr.init({ workspaceId: '...' });
684
946
  }, []);
685
947
  ```
686
948
 
687
949
  ### Script tag vs NPM
688
950
 
689
- Choose one installation method. Using both causes conflicts.
690
-
691
- | Feature | Script Tag | NPM |
692
- |---------|-----------|-----|
693
- | Bundle Size | 0 (external) | ~15KB |
694
- | TypeScript | No | Yes |
695
- | Access | `window.datalyr` | `import datalyr` |
696
- | Initialize | Automatic | `datalyr.init()` |
951
+ Use one installation method, not both. Using both creates duplicate instances.
697
952
 
698
953
  ---
699
954