@datalyr/web 1.2.2 → 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,26 +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)
22
- - [Event Queue](#event-queue)
23
- - [Offline Support](#offline-support)
24
- - [Privacy and Consent](#privacy-and-consent)
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
+ - [Web-to-App Attribution](#web-to-app-attribution)
25
23
  - [SPA Support](#spa-support)
26
- - [Container Scripts](#container-scripts)
27
24
  - [Framework Integration](#framework-integration)
28
- - [React](#react)
29
- - [Vue](#vue)
30
- - [Next.js](#nextjs)
31
25
  - [TypeScript](#typescript)
32
26
  - [Troubleshooting](#troubleshooting)
33
27
  - [License](#license)
@@ -36,7 +30,7 @@ Browser analytics and attribution SDK for web applications. Track events, identi
36
30
 
37
31
  ## Installation
38
32
 
39
- ### NPM Package
33
+ ### NPM
40
34
 
41
35
  ```bash
42
36
  npm install @datalyr/web
@@ -49,7 +43,16 @@ npm install @datalyr/web
49
43
  data-workspace-id="YOUR_WORKSPACE_ID"></script>
50
44
  ```
51
45
 
52
- 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()` |
53
56
 
54
57
  ---
55
58
 
@@ -58,34 +61,32 @@ The script tag loads externally (zero bundle size) and auto-initializes. Use `wi
58
61
  ```javascript
59
62
  import datalyr from '@datalyr/web';
60
63
 
61
- // Initialize
62
64
  datalyr.init({
63
65
  workspaceId: 'YOUR_WORKSPACE_ID'
64
66
  });
65
67
 
68
+ // Wait for async initialization (encryption, container, initial page view)
69
+ await datalyr.ready();
70
+
66
71
  // Track events
67
72
  datalyr.track('button_clicked', { button: 'signup' });
68
73
 
69
74
  // Identify users
70
75
  datalyr.identify('user_123', { email: 'user@example.com' });
71
76
 
72
- // Track page views
73
- datalyr.page('Pricing', { variant: 'A' });
77
+ // Track page views (page name goes in properties)
78
+ datalyr.page({ title: 'Pricing', variant: 'A' });
74
79
  ```
75
80
 
76
81
  ---
77
82
 
78
83
  ## How It Works
79
84
 
80
- The SDK collects events and sends them to the Datalyr backend for analytics and attribution.
81
-
82
- ### Data Flow
83
-
84
- 1. Events are created with `track()`, `page()`, or `identify()`
85
- 2. Each event includes device info, session data, and attribution parameters
86
- 3. Events are queued locally and sent in batches
87
- 4. If offline, events are stored and sent when connectivity returns
88
- 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.
89
90
 
90
91
  ### Event Payload
91
92
 
@@ -93,25 +94,30 @@ Every event includes:
93
94
 
94
95
  ```javascript
95
96
  {
96
- event: 'purchase', // Event name
97
- properties: { ... }, // Custom properties
97
+ event_name: 'purchase',
98
+ event_data: { ... }, // Custom + attribution + session properties
98
99
 
99
100
  // Identity
100
- anonymous_id: 'anon_xxxxx', // Persistent browser ID
101
- user_id: 'user_123', // Set after identify()
102
- session_id: 'sess_xxxxx', // Current session
103
-
104
- // Context
105
- page_url: 'https://example.com/pricing',
106
- 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',
107
109
  referrer: 'https://google.com',
108
110
 
109
- // Attribution (if captured)
111
+ // Attribution (in event_data, if captured)
110
112
  utm_source: 'facebook',
111
113
  fbclid: 'abc123',
112
114
 
113
- // Timestamps
115
+ // Metadata
116
+ workspace_id: 'wk_xxxxx',
117
+ source: 'web',
114
118
  timestamp: '2024-01-15T10:30:00Z',
119
+ sdk_version: '1.4.0',
120
+ sdk_name: 'datalyr-web-sdk'
115
121
  }
116
122
  ```
117
123
 
@@ -119,53 +125,94 @@ Every event includes:
119
125
 
120
126
  ## Configuration
121
127
 
122
- ```javascript
128
+ All properties of `DatalyrConfig`:
129
+
130
+ ```typescript
123
131
  datalyr.init({
124
132
  // Required
125
133
  workspaceId: string,
126
134
 
127
- // Features
128
- debug?: boolean, // Console logging
129
- trackPageViews?: boolean, // Track initial page view (default: true)
130
- 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
131
138
 
132
- // Event Queue
133
- batchSize?: number, // Events per batch (default: 10)
134
- flushInterval?: number, // Send interval ms (default: 5000)
139
+ // Debugging
140
+ debug?: boolean, // Default: false - Enable console logging
135
141
 
136
- // Session
137
- 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
138
146
 
139
- // Privacy
140
- privacyMode?: 'standard' | 'strict', // Privacy level (default: 'standard')
141
- respectDoNotTrack?: boolean, // Honor browser DNT (default: false)
142
- 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.
143
150
 
144
- // Cookies
145
- cookieDomain?: string | 'auto', // Cookie domain (default: 'auto')
151
+ // Session
152
+ sessionTimeout?: number, // Default: 3600000 (60 min)
153
+ trackSessions?: boolean, // Default: true
146
154
 
147
- // Container
148
- 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
149
158
 
150
- // Custom
151
- trackedParams?: string[], // Additional URL params to track
152
- 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
153
196
  });
154
197
  ```
155
198
 
156
199
  ---
157
200
 
158
- ## Event Tracking
201
+ ## API Reference
202
+
203
+ ### Event Tracking
204
+
205
+ #### `track(eventName, properties?)`
159
206
 
160
- ### Custom Events
207
+ Track a custom event.
161
208
 
162
- Track any action on your website:
209
+ ```typescript
210
+ track(eventName: string, properties?: EventProperties): void
211
+ ```
163
212
 
164
213
  ```javascript
165
- // Simple event
166
214
  datalyr.track('signup_started');
167
215
 
168
- // Event with properties
169
216
  datalyr.track('product_viewed', {
170
217
  product_id: 'SKU123',
171
218
  product_name: 'Blue Shirt',
@@ -173,148 +220,235 @@ datalyr.track('product_viewed', {
173
220
  currency: 'USD',
174
221
  category: 'Apparel',
175
222
  });
176
-
177
- // Event with value
178
- datalyr.track('level_completed', {
179
- level: 5,
180
- score: 1250,
181
- time_seconds: 120,
182
- });
183
223
  ```
184
224
 
185
- ### Page Views
225
+ #### `page(properties?)`
186
226
 
187
- 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
+ ```
188
232
 
189
233
  ```javascript
190
- // Track current page
234
+ // Track current page (all properties auto-captured)
191
235
  datalyr.page();
192
236
 
193
- // Track with name
194
- datalyr.page('Pricing');
237
+ // Override title, add custom properties
238
+ datalyr.page({ title: 'Pricing', variant: 'A' });
195
239
 
196
- // Track with properties
197
- datalyr.page('Product Details', {
198
- product_id: 'SKU123',
199
- source: 'search',
200
- });
240
+ // Add custom properties
241
+ datalyr.page({ product_id: 'SKU123', source: 'search' });
201
242
  ```
202
243
 
203
- ### E-Commerce Events
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.
204
245
 
205
- Standard e-commerce tracking:
206
-
207
- ```javascript
208
- // View product
209
- datalyr.track('Product Viewed', {
210
- product_id: 'SKU123',
211
- product_name: 'Blue Shirt',
212
- price: 29.99,
213
- currency: 'USD'
214
- });
246
+ #### `screen(screenName, properties?)`
215
247
 
216
- // Add to cart
217
- datalyr.track('Add to Cart', {
218
- product_id: 'SKU123',
219
- quantity: 1,
220
- price: 29.99
221
- });
248
+ Track a screen view. Intended for SPAs or hybrid apps.
222
249
 
223
- // Start checkout
224
- datalyr.track('Checkout Started', {
225
- cart_value: 59.98,
226
- currency: 'USD',
227
- item_count: 2
228
- });
250
+ ```typescript
251
+ screen(screenName: string, properties?: Record<string, any>): void
252
+ ```
229
253
 
230
- // Complete purchase
231
- datalyr.track('Purchase', {
232
- order_id: 'ORD-456',
233
- total: 59.98,
234
- currency: 'USD',
235
- items: [
236
- { product_id: 'SKU123', quantity: 2, price: 29.99 }
237
- ]
238
- });
254
+ ```javascript
255
+ datalyr.screen('Dashboard');
256
+ datalyr.screen('Product Details', { product_id: 'SKU123' });
239
257
  ```
240
258
 
241
- ---
259
+ Internally fires a `pageview` event with the `screen` property set.
242
260
 
243
- ## User Identity
261
+ #### `trackAppDownloadClick(options)`
244
262
 
245
- ### 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.
246
264
 
247
- 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
+ ```
248
271
 
249
272
  ```javascript
250
- const anonymousId = datalyr.getAnonymousId();
251
- // 'anon_a1b2c3d4e5f6'
273
+ datalyr.trackAppDownloadClick({
274
+ targetPlatform: 'ios',
275
+ appStoreUrl: 'https://apps.apple.com/app/your-app/id123456789',
276
+ });
252
277
  ```
253
278
 
254
- This ID:
255
- - Persists across browser sessions
256
- - Links events before and after user identification
257
- - Can be passed to your backend for server-side attribution
279
+ For Android Play Store URLs, attribution parameters are automatically encoded into the `referrer` query parameter for deterministic install attribution.
280
+
281
+ ---
258
282
 
259
- ### Identifying Users
283
+ ### User Identity
260
284
 
261
- Link the anonymous ID to a known user:
285
+ #### `identify(userId, traits?)`
286
+
287
+ Link the anonymous visitor to a known user. Rotates the session ID on call (prevents session fixation). User traits are encrypted at rest.
288
+
289
+ ```typescript
290
+ identify(userId: string, traits?: UserTraits): void
291
+ ```
262
292
 
263
293
  ```javascript
264
294
  datalyr.identify('user_123', {
265
295
  email: 'user@example.com',
296
+ name: 'John Doe',
297
+ plan: 'premium',
298
+ company: 'Acme Inc',
266
299
  });
267
300
  ```
268
301
 
269
- After `identify()`:
270
- - All future events include `user_id`
271
- - Historical anonymous events can be linked server-side
272
- - Attribution data is preserved on the user
302
+ #### `alias(userId, previousId?)`
273
303
 
274
- ### User Properties
304
+ Create an alias linking one user ID to another. If `previousId` is omitted, the current anonymous ID is used.
275
305
 
276
- Pass any user attributes:
306
+ ```typescript
307
+ alias(userId: string, previousId?: string): void
308
+ ```
277
309
 
278
310
  ```javascript
279
- datalyr.identify('user_123', {
280
- // Standard fields
281
- email: 'user@example.com',
282
- name: 'John Doe',
283
- phone: '+1234567890',
311
+ datalyr.alias('user_123', 'temp_user_456');
312
+ ```
284
313
 
285
- // Custom fields
286
- plan: 'premium',
287
- company: 'Acme Inc',
288
- signup_date: '2024-01-15',
289
- });
314
+ #### `group(groupId, traits?)`
315
+
316
+ Associate the current user with a group or account.
317
+
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' });
290
324
  ```
291
325
 
292
- ### Logout
326
+ #### `reset()`
293
327
 
294
- Clear user data on logout:
328
+ Clear user identity and start a new session. The anonymous ID is preserved.
295
329
 
296
330
  ```javascript
297
331
  datalyr.reset();
298
332
  ```
299
333
 
300
- This:
301
- - Clears the user ID
302
- - Starts a new session
303
- - Keeps the anonymous ID (same browser)
334
+ #### `getAnonymousId()`
335
+
336
+ Returns the persistent anonymous ID assigned on first visit.
337
+
338
+ ```typescript
339
+ getAnonymousId(): string
340
+ ```
341
+
342
+ #### `getUserId()`
343
+
344
+ Returns the current user ID set by `identify()`, or `null` if not identified.
345
+
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
356
+ ```
357
+
358
+ ---
359
+
360
+ ### Session
361
+
362
+ #### `getSessionId()`
363
+
364
+ Returns the current session ID.
365
+
366
+ ```typescript
367
+ getSessionId(): string
368
+ ```
369
+
370
+ #### `startNewSession()`
371
+
372
+ Force-start a new session. Returns the new session ID.
373
+
374
+ ```typescript
375
+ startNewSession(): string
376
+ ```
377
+
378
+ ```javascript
379
+ const newSessionId = datalyr.startNewSession();
380
+ ```
381
+
382
+ #### `getSessionData()`
383
+
384
+ Returns the current session metadata, or `null` if no active session.
385
+
386
+ ```typescript
387
+ getSessionData(): SessionData | null
388
+ ```
389
+
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
+ ```
401
+
402
+ #### `ready()`
403
+
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
+ ```
304
415
 
305
416
  ---
306
417
 
307
- ## Attribution
418
+ ### Attribution
308
419
 
309
- ### Automatic Capture
420
+ #### `getAttribution()`
310
421
 
311
- The SDK captures attribution from URLs and referrers:
422
+ Returns the current captured attribution data.
423
+
424
+ ```typescript
425
+ getAttribution(): Attribution
426
+ ```
312
427
 
313
428
  ```javascript
314
- const attribution = datalyr.getAttributionData();
429
+ const attribution = datalyr.getAttribution();
430
+ console.log(attribution.source, attribution.medium, attribution.campaign);
315
431
  ```
316
432
 
317
- Captured parameters:
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
+ }
449
+ ```
450
+
451
+ Captured automatically from URLs:
318
452
 
319
453
  | Type | Parameters |
320
454
  |------|------------|
@@ -322,173 +456,313 @@ Captured parameters:
322
456
  | Click IDs | `fbclid`, `gclid`, `ttclid`, `twclid`, `li_fat_id`, `msclkid` |
323
457
  | Referrer | `referrer`, `landing_page` |
324
458
 
325
- Attribution is automatically included in all events and supports:
326
- - First-touch attribution (90-day window)
327
- - Last-touch attribution
328
- - Customer journey tracking (up to 30 touchpoints)
459
+ Attribution supports first-touch (90-day window), last-touch, and journey tracking.
329
460
 
330
- ### Custom Parameters
461
+ #### `setAttribution(attribution)`
331
462
 
332
- Track additional URL parameters:
463
+ Manually set or override attribution data.
464
+
465
+ ```typescript
466
+ setAttribution(attribution: Partial<Attribution>): void
467
+ ```
333
468
 
334
469
  ```javascript
335
- datalyr.init({
336
- workspaceId: 'YOUR_WORKSPACE_ID',
337
- trackedParams: ['ref', 'affiliate_id', 'promo_code']
470
+ datalyr.setAttribution({
471
+ source: 'partner',
472
+ medium: 'referral',
473
+ campaign: 'spring_promo',
338
474
  });
339
475
  ```
340
476
 
341
- ---
342
-
343
- ## Event Queue
477
+ #### `getJourney()`
344
478
 
345
- Events are batched for efficiency.
479
+ Returns the customer journey as an array of touchpoints (up to 30).
346
480
 
347
- ### Configuration
481
+ ```typescript
482
+ getJourney(): TouchPoint[]
483
+ ```
348
484
 
349
- ```javascript
350
- datalyr.init({
351
- workspaceId: 'YOUR_WORKSPACE_ID',
352
- batchSize: 10, // Send when 10 events queued
353
- flushInterval: 5000, // Or every 5 seconds
354
- });
485
+ ```typescript
486
+ interface TouchPoint {
487
+ timestamp: number;
488
+ source?: string;
489
+ medium?: string;
490
+ campaign?: string;
491
+ sessionId: string;
492
+ }
355
493
  ```
356
494
 
357
- ### Manual Flush
495
+ ---
496
+
497
+ ### Super Properties
498
+
499
+ Super properties are included with every subsequent event.
500
+
501
+ #### `setSuperProperties(properties)`
502
+
503
+ Merge properties into the super properties store.
358
504
 
359
- Send all queued events immediately:
505
+ ```typescript
506
+ setSuperProperties(properties: Record<string, any>): void
507
+ ```
360
508
 
361
509
  ```javascript
362
- await datalyr.flush();
510
+ datalyr.setSuperProperties({ app_version: '2.1.0', environment: 'production' });
363
511
  ```
364
512
 
365
- ### Priority Events
513
+ #### `unsetSuperProperty(propertyName)`
366
514
 
367
- Important events like `Purchase`, `Signup`, and `Subscribe` are automatically prioritized for faster delivery.
515
+ Remove a single super property by key.
368
516
 
369
- ---
517
+ ```typescript
518
+ unsetSuperProperty(propertyName: string): void
519
+ ```
370
520
 
371
- ## Offline Support
521
+ ```javascript
522
+ datalyr.unsetSuperProperty('environment');
523
+ ```
372
524
 
373
- When the browser is offline:
374
- - Events are stored locally
375
- - Queue persists across page refreshes
376
- - Events are sent when connectivity returns
525
+ #### `getSuperProperties()`
377
526
 
378
- ```javascript
379
- // Events work seamlessly offline
380
- datalyr.track('Button Clicked'); // Automatically queued if offline
527
+ Returns a copy of the current super properties.
381
528
 
382
- // Manually flush when ready
383
- await datalyr.flush();
529
+ ```typescript
530
+ getSuperProperties(): Record<string, any>
384
531
  ```
385
532
 
386
533
  ---
387
534
 
388
- ## Privacy and Consent
535
+ ### Privacy and Consent
536
+
537
+ #### `optOut()`
389
538
 
390
- ### Opt Out
539
+ Opt the user out of all tracking. Clears the event queue and persists the preference in a cookie.
391
540
 
392
541
  ```javascript
393
- // Opt user out of tracking
394
542
  datalyr.optOut();
543
+ ```
544
+
545
+ #### `optIn()`
546
+
547
+ Opt the user back in.
395
548
 
396
- // Opt user back in
549
+ ```javascript
397
550
  datalyr.optIn();
551
+ ```
552
+
553
+ #### `isOptedOut()`
554
+
555
+ Returns `true` if the user has opted out.
398
556
 
399
- // Check status
400
- const isOptedOut = datalyr.isOptedOut();
557
+ ```typescript
558
+ isOptedOut(): boolean
401
559
  ```
402
560
 
403
- ### Consent Preferences
561
+ #### `setConsent(consent)`
562
+
563
+ Set granular consent preferences.
564
+
565
+ ```typescript
566
+ setConsent(consent: ConsentConfig): void
567
+ ```
404
568
 
405
569
  ```javascript
406
570
  datalyr.setConsent({
407
571
  analytics: true,
408
572
  marketing: false,
409
573
  preferences: true,
410
- sale: false // CCPA "Do Not Sell"
574
+ sale: false // CCPA "Do Not Sell"
411
575
  });
412
576
  ```
413
577
 
414
- ### 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
415
588
 
416
589
  ```javascript
417
- // Standard mode (default)
418
- datalyr.init({
419
- workspaceId: 'YOUR_WORKSPACE_ID',
420
- privacyMode: 'standard'
421
- });
590
+ // Standard mode (default) - normal fingerprinting + tracking
591
+ datalyr.init({ workspaceId: '...', privacyMode: 'standard' });
422
592
 
423
- // Strict mode - minimal data collection
424
- datalyr.init({
425
- workspaceId: 'YOUR_WORKSPACE_ID',
426
- privacyMode: 'strict'
427
- });
593
+ // Strict mode - minimal data collection, no fingerprinting
594
+ datalyr.init({ workspaceId: '...', privacyMode: 'strict' });
428
595
  ```
429
596
 
597
+ The SDK also respects `respectDoNotTrack` and `respectGlobalPrivacyControl` config flags.
598
+
430
599
  ---
431
600
 
432
- ## SPA Support
601
+ ### Queue and Network
602
+
603
+ #### `flush()`
433
604
 
434
- For single-page applications:
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
+ ```
435
610
 
436
611
  ```javascript
437
- datalyr.init({
438
- workspaceId: 'YOUR_WORKSPACE_ID',
439
- trackSPA: true // Auto-track route changes (default: true)
440
- });
612
+ await datalyr.flush();
613
+ ```
441
614
 
442
- // Manual route tracking
443
- datalyr.page('Product Details', {
444
- product_id: 'SKU-123'
445
- });
615
+ #### `getNetworkStatus()`
616
+
617
+ Returns the current network status as tracked by the SDK.
618
+
619
+ ```typescript
620
+ getNetworkStatus(): NetworkStatus
446
621
  ```
447
622
 
623
+ ```typescript
624
+ interface NetworkStatus {
625
+ isOnline: boolean;
626
+ lastOfflineAt: number | null;
627
+ lastOnlineAt: number | null;
628
+ }
629
+ ```
630
+
631
+ Events are automatically stored when offline and sent when connectivity returns.
632
+
448
633
  ---
449
634
 
450
- ## Container Scripts
635
+ ### Container Scripts
451
636
 
452
637
  Container scripts manage third-party tracking pixels (Meta, Google, TikTok) configured in your Datalyr dashboard.
453
638
 
454
- ### Script Tag Installation
639
+ #### `loadScript(scriptId)`
455
640
 
456
- For container scripts, use the container endpoint:
641
+ Trigger a container script by ID.
642
+
643
+ ```typescript
644
+ loadScript(scriptId: string): void
645
+ ```
646
+
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):
457
660
 
458
661
  ```html
459
662
  <script defer src="https://track.datalyr.com/container.js"
460
663
  data-workspace-id="YOUR_WORKSPACE_ID"></script>
461
664
  ```
462
665
 
463
- This loads your configured pixels and tracking scripts automatically.
666
+ ---
667
+
668
+ ### Debugging
669
+
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
+ ```
687
+
688
+ ```javascript
689
+ console.log(datalyr.getErrors());
690
+ ```
691
+
692
+ Enable `debug: true` in config to log all SDK activity to the console.
693
+
694
+ ---
464
695
 
465
- ### NPM SDK with Containers
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
+ ```
466
705
 
467
706
  ```javascript
468
- datalyr.init({
469
- workspaceId: 'YOUR_WORKSPACE_ID',
470
- enableContainer: true // Default: true
471
- });
707
+ datalyr.destroy();
708
+ ```
472
709
 
473
- // Manually trigger a script
474
- datalyr.loadScript('custom-script-id');
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)
724
+
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
+ });
734
+ ```
475
735
 
476
- // Get loaded scripts
477
- const scripts = datalyr.getLoadedScripts();
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
+ });
478
742
  ```
479
743
 
480
- ### Supported Pixels
744
+ ### Requirements
745
+
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.
481
755
 
482
- - Meta (Facebook) Pixel
483
- - Google Analytics / Google Ads
484
- - TikTok Pixel
485
- - Custom scripts and tracking pixels
756
+ ```javascript
757
+ datalyr.init({
758
+ workspaceId: 'YOUR_WORKSPACE_ID',
759
+ trackSPA: true, // default
760
+ trackPageViews: true, // auto-track initial page view, default
761
+ });
486
762
 
487
- Security features:
488
- - HTTPS-only script loading
489
- - XSS protection
490
- - URL validation
491
- - CSP nonce support
763
+ // Manual page tracking
764
+ datalyr.page({ title: 'Product Details', product_id: 'SKU-123' });
765
+ ```
492
766
 
493
767
  ---
494
768
 
@@ -502,13 +776,11 @@ import datalyr from '@datalyr/web';
502
776
 
503
777
  function App() {
504
778
  useEffect(() => {
505
- datalyr.init({
506
- workspaceId: 'YOUR_WORKSPACE_ID'
507
- });
779
+ datalyr.init({ workspaceId: 'YOUR_WORKSPACE_ID' });
508
780
  }, []);
509
781
 
510
782
  const handleClick = () => {
511
- datalyr.track('Button Clicked', { button_name: 'CTA' });
783
+ datalyr.track('button_clicked', { button_name: 'CTA' });
512
784
  };
513
785
 
514
786
  return <button onClick={handleClick}>Click Me</button>;
@@ -523,13 +795,11 @@ import { onMounted } from 'vue';
523
795
  import datalyr from '@datalyr/web';
524
796
 
525
797
  onMounted(() => {
526
- datalyr.init({
527
- workspaceId: 'YOUR_WORKSPACE_ID'
528
- });
798
+ datalyr.init({ workspaceId: 'YOUR_WORKSPACE_ID' });
529
799
  });
530
800
 
531
801
  const trackClick = () => {
532
- datalyr.track('Button Clicked');
802
+ datalyr.track('button_clicked');
533
803
  };
534
804
  </script>
535
805
 
@@ -540,34 +810,32 @@ const trackClick = () => {
540
810
 
541
811
  ### Next.js
542
812
 
543
- ```jsx
813
+ ```tsx
544
814
  // app/providers.tsx
545
815
  'use client';
546
816
 
547
817
  import { useEffect } from 'react';
548
818
  import datalyr from '@datalyr/web';
549
819
 
550
- export function AnalyticsProvider({ children }) {
820
+ export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
551
821
  useEffect(() => {
552
822
  datalyr.init({
553
- workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID,
554
- debug: process.env.NODE_ENV === 'development'
823
+ workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID!,
824
+ debug: process.env.NODE_ENV === 'development',
555
825
  });
556
826
  }, []);
557
827
 
558
- return children;
828
+ return <>{children}</>;
559
829
  }
560
830
 
561
831
  // app/layout.tsx
562
832
  import { AnalyticsProvider } from './providers';
563
833
 
564
- export default function RootLayout({ children }) {
834
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
565
835
  return (
566
836
  <html>
567
837
  <body>
568
- <AnalyticsProvider>
569
- {children}
570
- </AnalyticsProvider>
838
+ <AnalyticsProvider>{children}</AnalyticsProvider>
571
839
  </body>
572
840
  </html>
573
841
  );
@@ -578,77 +846,109 @@ export default function RootLayout({ children }) {
578
846
 
579
847
  ## TypeScript
580
848
 
849
+ All types are exported from the package:
850
+
581
851
  ```typescript
582
- 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
+ ```
583
871
 
584
- // Type-safe event properties
872
+ ```typescript
585
873
  const properties: EventProperties = {
586
874
  product_id: 'SKU-123',
587
875
  price: 99.99,
588
- quantity: 2
876
+ quantity: 2,
589
877
  };
878
+ datalyr.track('product_added', properties);
590
879
 
591
- datalyr.track('Product Added', properties);
592
-
593
- // Type-safe user traits
594
880
  const traits: UserTraits = {
595
881
  email: 'user@example.com',
596
882
  name: 'John Doe',
597
- plan: 'premium'
883
+ plan: 'premium',
598
884
  };
599
-
600
885
  datalyr.identify('user_123', traits);
601
886
  ```
602
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
+
603
901
  ---
604
902
 
605
903
  ## Troubleshooting
606
904
 
607
905
  ### Events not appearing
608
906
 
609
- 1. Check workspace ID is correct
610
- 2. Enable `debug: true`
611
- 3. Verify `datalyr.getAnonymousId()` returns a value
612
- 4. Check Network tab for requests to `ingest.datalyr.com`
613
- 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.
614
912
 
615
913
  ### SDK not initialized
616
914
 
617
915
  ```javascript
618
- // Check initialization
619
916
  console.log('Anonymous ID:', datalyr.getAnonymousId());
620
917
  console.log('Session ID:', datalyr.getSessionId());
918
+ console.log('User ID:', datalyr.getUserId());
919
+ console.log('Network:', datalyr.getNetworkStatus());
621
920
  console.log('Errors:', datalyr.getErrors());
622
921
  ```
623
922
 
624
- 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
+ ```
625
934
 
626
- ### Next.js SSR issues
935
+ ### Next.js SSR
627
936
 
628
- 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:
629
938
 
630
939
  ```jsx
631
940
  'use client';
632
-
633
941
  import { useEffect } from 'react';
634
942
  import datalyr from '@datalyr/web';
635
943
 
636
944
  useEffect(() => {
637
- // SDK methods only work in browser
638
945
  datalyr.init({ workspaceId: '...' });
639
946
  }, []);
640
947
  ```
641
948
 
642
949
  ### Script tag vs NPM
643
950
 
644
- Choose one installation method. Using both causes conflicts.
645
-
646
- | Feature | Script Tag | NPM |
647
- |---------|-----------|-----|
648
- | Bundle Size | 0 (external) | ~15KB |
649
- | TypeScript | No | Yes |
650
- | Access | `window.datalyr` | `import datalyr` |
651
- | Initialize | Automatic | `datalyr.init()` |
951
+ Use one installation method, not both. Using both creates duplicate instances.
652
952
 
653
953
  ---
654
954