@datalyr/web 1.2.0 → 1.2.2
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 +373 -814
- package/dist/datalyr.cjs.js +48 -4
- package/dist/datalyr.cjs.js.map +1 -1
- package/dist/datalyr.esm.js +48 -4
- package/dist/datalyr.esm.js.map +1 -1
- package/dist/datalyr.esm.min.js +1 -1
- package/dist/datalyr.esm.min.js.map +1 -1
- package/dist/datalyr.js +48 -4
- package/dist/datalyr.js.map +1 -1
- package/dist/datalyr.min.js +1 -1
- package/dist/datalyr.min.js.map +1 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,493 +1,496 @@
|
|
|
1
1
|
# @datalyr/web
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Browser analytics and attribution SDK for web applications. Track events, identify users, and capture attribution data across your website.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Quick Start](#quick-start)
|
|
9
|
+
- [How It Works](#how-it-works)
|
|
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)
|
|
25
|
+
- [SPA Support](#spa-support)
|
|
26
|
+
- [Container Scripts](#container-scripts)
|
|
27
|
+
- [Framework Integration](#framework-integration)
|
|
28
|
+
- [React](#react)
|
|
29
|
+
- [Vue](#vue)
|
|
30
|
+
- [Next.js](#nextjs)
|
|
31
|
+
- [TypeScript](#typescript)
|
|
32
|
+
- [Troubleshooting](#troubleshooting)
|
|
33
|
+
- [License](#license)
|
|
4
34
|
|
|
5
|
-
|
|
6
|
-
[](https://bundlephobia.com/package/@datalyr/web)
|
|
7
|
-
[](https://www.typescriptlang.org/)
|
|
8
|
-
[](https://github.com/datalyr/web/blob/main/LICENSE)
|
|
9
|
-
|
|
10
|
-
## Features
|
|
11
|
-
|
|
12
|
-
- **Zero Configuration** - Start tracking with just your workspace ID
|
|
13
|
-
- **Lightweight** - ~15KB minified + gzipped with zero dependencies
|
|
14
|
-
- **Privacy First** - GDPR compliant with built-in consent management
|
|
15
|
-
- **Smart Batching** - Optimized network usage with intelligent event queuing
|
|
16
|
-
- **Cross-Platform** - Works on all modern browsers including mobile
|
|
17
|
-
- **Offline Support** - Events are queued and sent when connection restored
|
|
18
|
-
- **Attribution Tracking** - Automatic UTM and click ID capture
|
|
19
|
-
- **Session Management** - Automatic session tracking with configurable timeout
|
|
20
|
-
- **TypeScript Support** - Full type definitions included
|
|
21
|
-
- **Extensible** - Plugin system for custom functionality
|
|
22
|
-
- **Container Scripts** - Securely manage third-party pixels and tracking scripts
|
|
35
|
+
---
|
|
23
36
|
|
|
24
|
-
##
|
|
37
|
+
## Installation
|
|
25
38
|
|
|
26
|
-
|
|
39
|
+
### NPM Package
|
|
27
40
|
|
|
28
|
-
|
|
41
|
+
```bash
|
|
42
|
+
npm install @datalyr/web
|
|
43
|
+
```
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
### Script Tag
|
|
31
46
|
|
|
32
47
|
```html
|
|
33
|
-
<script defer src="https://track.datalyr.com/
|
|
48
|
+
<script defer src="https://track.datalyr.com/dl.js"
|
|
34
49
|
data-workspace-id="YOUR_WORKSPACE_ID"></script>
|
|
35
50
|
```
|
|
36
51
|
|
|
37
|
-
|
|
38
|
-
- Zero bundle size (external script)
|
|
39
|
-
- No npm install needed
|
|
40
|
-
- Works in any HTML page
|
|
41
|
-
- Automatic initialization
|
|
42
|
-
- Perfect for Next.js, React, Vue
|
|
43
|
-
|
|
44
|
-
**API Usage:**
|
|
45
|
-
```javascript
|
|
46
|
-
// Available globally after script loads
|
|
47
|
-
window.datalyr.track('event_name', { key: 'value' })
|
|
48
|
-
window.datalyr.identify('user_123', { email: 'user@example.com' })
|
|
49
|
-
window.datalyr.getVisitorId()
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
[See Script Tag Documentation →](https://docs.datalyr.com/installation/script-tag)
|
|
52
|
+
The script tag loads externally (zero bundle size) and auto-initializes. Use `window.datalyr` to access the SDK.
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
## Quick Start
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
```javascript
|
|
59
|
+
import datalyr from '@datalyr/web';
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// Initialize
|
|
62
|
+
datalyr.init({
|
|
63
|
+
workspaceId: 'YOUR_WORKSPACE_ID'
|
|
64
|
+
});
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- ES module imports
|
|
67
|
-
- Tree-shaking support
|
|
68
|
-
- Better IDE autocomplete
|
|
66
|
+
// Track events
|
|
67
|
+
datalyr.track('button_clicked', { button: 'signup' });
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
import datalyr from '@datalyr/web'
|
|
69
|
+
// Identify users
|
|
70
|
+
datalyr.identify('user_123', { email: 'user@example.com' });
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
datalyr.
|
|
76
|
-
datalyr.identify('user_123', { email: 'user@example.com' })
|
|
72
|
+
// Track page views
|
|
73
|
+
datalyr.page('Pricing', { variant: 'A' });
|
|
77
74
|
```
|
|
78
75
|
|
|
79
76
|
---
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
## How It Works
|
|
82
79
|
|
|
83
|
-
|
|
80
|
+
The SDK collects events and sends them to the Datalyr backend for analytics and attribution.
|
|
84
81
|
|
|
85
|
-
|
|
86
|
-
<!-- DON'T DO THIS -->
|
|
87
|
-
<script src="https://track.datalyr.com/container.js"></script>
|
|
88
|
-
<script>
|
|
89
|
-
import datalyr from '@datalyr/web' // Both methods at once!
|
|
90
|
-
datalyr.init({...})
|
|
91
|
-
</script>
|
|
92
|
-
```
|
|
82
|
+
### Data Flow
|
|
93
83
|
|
|
94
|
-
|
|
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
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
npm install @datalyr/web
|
|
98
|
-
# or
|
|
99
|
-
yarn add @datalyr/web
|
|
100
|
-
# or
|
|
101
|
-
pnpm add @datalyr/web
|
|
102
|
-
```
|
|
90
|
+
### Event Payload
|
|
103
91
|
|
|
104
|
-
|
|
92
|
+
Every event includes:
|
|
105
93
|
|
|
106
94
|
```javascript
|
|
107
|
-
|
|
95
|
+
{
|
|
96
|
+
event: 'purchase', // Event name
|
|
97
|
+
properties: { ... }, // Custom properties
|
|
108
98
|
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
99
|
+
// Identity
|
|
100
|
+
anonymous_id: 'anon_xxxxx', // Persistent browser ID
|
|
101
|
+
user_id: 'user_123', // Set after identify()
|
|
102
|
+
session_id: 'sess_xxxxx', // Current session
|
|
113
103
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
104
|
+
// Context
|
|
105
|
+
page_url: 'https://example.com/pricing',
|
|
106
|
+
page_title: 'Pricing',
|
|
107
|
+
referrer: 'https://google.com',
|
|
119
108
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
109
|
+
// Attribution (if captured)
|
|
110
|
+
utm_source: 'facebook',
|
|
111
|
+
fbclid: 'abc123',
|
|
112
|
+
|
|
113
|
+
// Timestamps
|
|
114
|
+
timestamp: '2024-01-15T10:30:00Z',
|
|
115
|
+
}
|
|
126
116
|
```
|
|
127
117
|
|
|
128
|
-
|
|
118
|
+
---
|
|
129
119
|
|
|
130
|
-
|
|
120
|
+
## Configuration
|
|
131
121
|
|
|
132
122
|
```javascript
|
|
133
|
-
import datalyr from '@datalyr/web';
|
|
134
|
-
|
|
135
|
-
// Initialize the SDK with configuration
|
|
136
123
|
datalyr.init({
|
|
137
|
-
|
|
124
|
+
// Required
|
|
125
|
+
workspaceId: string,
|
|
126
|
+
|
|
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)
|
|
131
|
+
|
|
132
|
+
// Event Queue
|
|
133
|
+
batchSize?: number, // Events per batch (default: 10)
|
|
134
|
+
flushInterval?: number, // Send interval ms (default: 5000)
|
|
135
|
+
|
|
136
|
+
// Session
|
|
137
|
+
sessionTimeout?: number, // Session timeout ms (default: 1800000 / 30 min)
|
|
138
|
+
|
|
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)
|
|
143
|
+
|
|
144
|
+
// Cookies
|
|
145
|
+
cookieDomain?: string | 'auto', // Cookie domain (default: 'auto')
|
|
138
146
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
cookieDomain: 'auto', // Auto-detect for subdomains
|
|
146
|
-
trackSPA: true, // Track SPA route changes
|
|
147
|
-
trackPageViews: true, // Track initial page view
|
|
148
|
-
enableContainer: true // Enable container script loading
|
|
147
|
+
// Container
|
|
148
|
+
enableContainer?: boolean, // Load container scripts (default: true)
|
|
149
|
+
|
|
150
|
+
// Custom
|
|
151
|
+
trackedParams?: string[], // Additional URL params to track
|
|
152
|
+
plugins?: Plugin[], // Custom plugins
|
|
149
153
|
});
|
|
150
154
|
```
|
|
151
155
|
|
|
152
|
-
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Event Tracking
|
|
159
|
+
|
|
160
|
+
### Custom Events
|
|
153
161
|
|
|
154
|
-
Track
|
|
162
|
+
Track any action on your website:
|
|
155
163
|
|
|
156
164
|
```javascript
|
|
157
165
|
// Simple event
|
|
158
|
-
datalyr.track('
|
|
166
|
+
datalyr.track('signup_started');
|
|
159
167
|
|
|
160
168
|
// Event with properties
|
|
161
|
-
datalyr.track('
|
|
162
|
-
product_id: '
|
|
163
|
-
product_name: '
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
169
|
+
datalyr.track('product_viewed', {
|
|
170
|
+
product_id: 'SKU123',
|
|
171
|
+
product_name: 'Blue Shirt',
|
|
172
|
+
price: 29.99,
|
|
173
|
+
currency: 'USD',
|
|
174
|
+
category: 'Apparel',
|
|
167
175
|
});
|
|
168
176
|
|
|
169
|
-
//
|
|
170
|
-
datalyr.track('
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
items: [
|
|
175
|
-
{ product_id: 'SKU-123', quantity: 2, price: 99.99 },
|
|
176
|
-
{ product_id: 'SKU-456', quantity: 1, price: 100.01 }
|
|
177
|
-
]
|
|
177
|
+
// Event with value
|
|
178
|
+
datalyr.track('level_completed', {
|
|
179
|
+
level: 5,
|
|
180
|
+
score: 1250,
|
|
181
|
+
time_seconds: 120,
|
|
178
182
|
});
|
|
179
183
|
```
|
|
180
184
|
|
|
181
|
-
###
|
|
185
|
+
### Page Views
|
|
182
186
|
|
|
183
|
-
|
|
187
|
+
Track navigation:
|
|
184
188
|
|
|
185
189
|
```javascript
|
|
186
|
-
//
|
|
187
|
-
datalyr.
|
|
190
|
+
// Track current page
|
|
191
|
+
datalyr.page();
|
|
188
192
|
|
|
189
|
-
//
|
|
190
|
-
datalyr.
|
|
191
|
-
email: 'user@example.com',
|
|
192
|
-
name: 'John Doe',
|
|
193
|
-
firstName: 'John',
|
|
194
|
-
lastName: 'Doe',
|
|
195
|
-
phone: '+1234567890',
|
|
196
|
-
plan: 'premium',
|
|
197
|
-
createdAt: '2024-01-01'
|
|
198
|
-
});
|
|
193
|
+
// Track with name
|
|
194
|
+
datalyr.page('Pricing');
|
|
199
195
|
|
|
200
|
-
//
|
|
201
|
-
datalyr.
|
|
202
|
-
|
|
203
|
-
|
|
196
|
+
// Track with properties
|
|
197
|
+
datalyr.page('Product Details', {
|
|
198
|
+
product_id: 'SKU123',
|
|
199
|
+
source: 'search',
|
|
204
200
|
});
|
|
205
201
|
```
|
|
206
202
|
|
|
207
|
-
###
|
|
203
|
+
### E-Commerce Events
|
|
208
204
|
|
|
209
|
-
|
|
205
|
+
Standard e-commerce tracking:
|
|
210
206
|
|
|
211
207
|
```javascript
|
|
212
|
-
//
|
|
213
|
-
datalyr.
|
|
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
|
+
});
|
|
214
215
|
|
|
215
|
-
//
|
|
216
|
-
datalyr.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
// Add to cart
|
|
217
|
+
datalyr.track('Add to Cart', {
|
|
218
|
+
product_id: 'SKU123',
|
|
219
|
+
quantity: 1,
|
|
220
|
+
price: 29.99
|
|
220
221
|
});
|
|
221
222
|
|
|
222
|
-
//
|
|
223
|
-
datalyr.
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
// Start checkout
|
|
224
|
+
datalyr.track('Checkout Started', {
|
|
225
|
+
cart_value: 59.98,
|
|
226
|
+
currency: 'USD',
|
|
227
|
+
item_count: 2
|
|
228
|
+
});
|
|
229
|
+
|
|
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
|
+
]
|
|
226
238
|
});
|
|
227
239
|
```
|
|
228
240
|
|
|
229
|
-
|
|
241
|
+
---
|
|
230
242
|
|
|
231
|
-
|
|
232
|
-
// Get current user IDs
|
|
233
|
-
const anonymousId = datalyr.getAnonymousId();
|
|
234
|
-
const userId = datalyr.getUserId();
|
|
235
|
-
const distinctId = datalyr.getDistinctId();
|
|
243
|
+
## User Identity
|
|
236
244
|
|
|
237
|
-
|
|
238
|
-
|
|
245
|
+
### Anonymous ID
|
|
246
|
+
|
|
247
|
+
Every visitor gets a persistent anonymous ID on first visit:
|
|
239
248
|
|
|
240
|
-
|
|
241
|
-
datalyr.
|
|
249
|
+
```javascript
|
|
250
|
+
const anonymousId = datalyr.getAnonymousId();
|
|
251
|
+
// 'anon_a1b2c3d4e5f6'
|
|
242
252
|
```
|
|
243
253
|
|
|
244
|
-
|
|
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
|
|
258
|
+
|
|
259
|
+
### Identifying Users
|
|
245
260
|
|
|
246
|
-
|
|
261
|
+
Link the anonymous ID to a known user:
|
|
247
262
|
|
|
248
263
|
```javascript
|
|
249
|
-
|
|
250
|
-
|
|
264
|
+
datalyr.identify('user_123', {
|
|
265
|
+
email: 'user@example.com',
|
|
266
|
+
});
|
|
267
|
+
```
|
|
251
268
|
|
|
252
|
-
|
|
253
|
-
|
|
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
|
|
254
273
|
|
|
255
|
-
|
|
256
|
-
const isOptedOut = datalyr.isOptedOut();
|
|
274
|
+
### User Properties
|
|
257
275
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
276
|
+
Pass any user attributes:
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
datalyr.identify('user_123', {
|
|
280
|
+
// Standard fields
|
|
281
|
+
email: 'user@example.com',
|
|
282
|
+
name: 'John Doe',
|
|
283
|
+
phone: '+1234567890',
|
|
284
|
+
|
|
285
|
+
// Custom fields
|
|
286
|
+
plan: 'premium',
|
|
287
|
+
company: 'Acme Inc',
|
|
288
|
+
signup_date: '2024-01-15',
|
|
264
289
|
});
|
|
265
290
|
```
|
|
266
291
|
|
|
267
|
-
###
|
|
292
|
+
### Logout
|
|
293
|
+
|
|
294
|
+
Clear user data on logout:
|
|
268
295
|
|
|
269
296
|
```javascript
|
|
270
|
-
|
|
271
|
-
|
|
297
|
+
datalyr.reset();
|
|
298
|
+
```
|
|
272
299
|
|
|
273
|
-
|
|
274
|
-
|
|
300
|
+
This:
|
|
301
|
+
- Clears the user ID
|
|
302
|
+
- Starts a new session
|
|
303
|
+
- Keeps the anonymous ID (same browser)
|
|
275
304
|
|
|
276
|
-
|
|
277
|
-
const sessionData = datalyr.getSessionData();
|
|
278
|
-
```
|
|
305
|
+
---
|
|
279
306
|
|
|
280
|
-
##
|
|
307
|
+
## Attribution
|
|
281
308
|
|
|
282
|
-
###
|
|
309
|
+
### Automatic Capture
|
|
283
310
|
|
|
284
|
-
|
|
311
|
+
The SDK captures attribution from URLs and referrers:
|
|
285
312
|
|
|
286
313
|
```javascript
|
|
287
|
-
|
|
288
|
-
datalyr.track('Purchase', { value: 99.99 });
|
|
289
|
-
datalyr.track('Signup', { plan: 'premium' });
|
|
290
|
-
datalyr.track('Subscribe', { plan: 'annual' });
|
|
291
|
-
|
|
292
|
-
// E-commerce events
|
|
293
|
-
datalyr.track('Add to Cart', { product_id: 'SKU-123' });
|
|
294
|
-
datalyr.track('Begin Checkout', { cart_value: 199.99 });
|
|
314
|
+
const attribution = datalyr.getAttributionData();
|
|
295
315
|
```
|
|
296
316
|
|
|
297
|
-
|
|
317
|
+
Captured parameters:
|
|
298
318
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
- Customer journey touchpoints
|
|
319
|
+
| Type | Parameters |
|
|
320
|
+
|------|------------|
|
|
321
|
+
| UTM | `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term` |
|
|
322
|
+
| Click IDs | `fbclid`, `gclid`, `ttclid`, `twclid`, `li_fat_id`, `msclkid` |
|
|
323
|
+
| Referrer | `referrer`, `landing_page` |
|
|
305
324
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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)
|
|
310
329
|
|
|
311
|
-
|
|
312
|
-
import datalyr from '@datalyr/web';
|
|
330
|
+
### Custom Parameters
|
|
313
331
|
|
|
332
|
+
Track additional URL parameters:
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
314
335
|
datalyr.init({
|
|
315
336
|
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
316
337
|
trackedParams: ['ref', 'affiliate_id', 'promo_code']
|
|
317
338
|
});
|
|
318
339
|
```
|
|
319
340
|
|
|
320
|
-
|
|
341
|
+
---
|
|
321
342
|
|
|
322
|
-
|
|
343
|
+
## Event Queue
|
|
323
344
|
|
|
324
|
-
|
|
325
|
-
|
|
345
|
+
Events are batched for efficiency.
|
|
346
|
+
|
|
347
|
+
### Configuration
|
|
326
348
|
|
|
349
|
+
```javascript
|
|
327
350
|
datalyr.init({
|
|
328
351
|
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
// Manual route tracking
|
|
333
|
-
datalyr.page('Product Details', {
|
|
334
|
-
product_id: 'SKU-123'
|
|
352
|
+
batchSize: 10, // Send when 10 events queued
|
|
353
|
+
flushInterval: 5000, // Or every 5 seconds
|
|
335
354
|
});
|
|
336
355
|
```
|
|
337
356
|
|
|
338
|
-
###
|
|
357
|
+
### Manual Flush
|
|
339
358
|
|
|
340
|
-
|
|
359
|
+
Send all queued events immediately:
|
|
341
360
|
|
|
342
361
|
```javascript
|
|
343
|
-
|
|
362
|
+
await datalyr.flush();
|
|
363
|
+
```
|
|
344
364
|
|
|
345
|
-
|
|
346
|
-
datalyr.init({
|
|
347
|
-
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
348
|
-
privacyMode: 'standard'
|
|
349
|
-
});
|
|
365
|
+
### Priority Events
|
|
350
366
|
|
|
351
|
-
|
|
352
|
-
datalyr.init({
|
|
353
|
-
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
354
|
-
privacyMode: 'strict'
|
|
355
|
-
});
|
|
356
|
-
```
|
|
367
|
+
Important events like `Purchase`, `Signup`, and `Subscribe` are automatically prioritized for faster delivery.
|
|
357
368
|
|
|
358
|
-
|
|
369
|
+
---
|
|
359
370
|
|
|
360
|
-
|
|
371
|
+
## Offline Support
|
|
372
|
+
|
|
373
|
+
When the browser is offline:
|
|
374
|
+
- Events are stored locally
|
|
375
|
+
- Queue persists across page refreshes
|
|
376
|
+
- Events are sent when connectivity returns
|
|
361
377
|
|
|
362
378
|
```javascript
|
|
363
379
|
// Events work seamlessly offline
|
|
364
380
|
datalyr.track('Button Clicked'); // Automatically queued if offline
|
|
365
381
|
|
|
366
|
-
// Manually flush
|
|
382
|
+
// Manually flush when ready
|
|
367
383
|
await datalyr.flush();
|
|
368
384
|
```
|
|
369
385
|
|
|
370
|
-
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Privacy and Consent
|
|
371
389
|
|
|
372
|
-
|
|
390
|
+
### Opt Out
|
|
373
391
|
|
|
374
392
|
```javascript
|
|
375
|
-
|
|
393
|
+
// Opt user out of tracking
|
|
394
|
+
datalyr.optOut();
|
|
395
|
+
|
|
396
|
+
// Opt user back in
|
|
397
|
+
datalyr.optIn();
|
|
398
|
+
|
|
399
|
+
// Check status
|
|
400
|
+
const isOptedOut = datalyr.isOptedOut();
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Consent Preferences
|
|
376
404
|
|
|
377
|
-
|
|
405
|
+
```javascript
|
|
406
|
+
datalyr.setConsent({
|
|
407
|
+
analytics: true,
|
|
408
|
+
marketing: false,
|
|
409
|
+
preferences: true,
|
|
410
|
+
sale: false // CCPA "Do Not Sell"
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Privacy Modes
|
|
415
|
+
|
|
416
|
+
```javascript
|
|
417
|
+
// Standard mode (default)
|
|
378
418
|
datalyr.init({
|
|
379
419
|
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
380
|
-
|
|
420
|
+
privacyMode: 'standard'
|
|
381
421
|
});
|
|
382
422
|
|
|
383
|
-
//
|
|
423
|
+
// Strict mode - minimal data collection
|
|
384
424
|
datalyr.init({
|
|
385
425
|
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
386
|
-
|
|
426
|
+
privacyMode: 'strict'
|
|
387
427
|
});
|
|
388
428
|
```
|
|
389
429
|
|
|
390
|
-
|
|
430
|
+
---
|
|
391
431
|
|
|
392
|
-
|
|
432
|
+
## SPA Support
|
|
393
433
|
|
|
394
|
-
|
|
395
|
-
import datalyr from '@datalyr/web';
|
|
434
|
+
For single-page applications:
|
|
396
435
|
|
|
436
|
+
```javascript
|
|
397
437
|
datalyr.init({
|
|
398
438
|
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
399
|
-
|
|
439
|
+
trackSPA: true // Auto-track route changes (default: true)
|
|
400
440
|
});
|
|
401
441
|
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
datalyr.loadScript('custom-script-id');
|
|
407
|
-
|
|
408
|
-
// Get list of loaded scripts
|
|
409
|
-
const loadedScripts = datalyr.getLoadedScripts();
|
|
442
|
+
// Manual route tracking
|
|
443
|
+
datalyr.page('Product Details', {
|
|
444
|
+
product_id: 'SKU-123'
|
|
445
|
+
});
|
|
410
446
|
```
|
|
411
447
|
|
|
412
|
-
|
|
413
|
-
- **Third-party pixels**: Meta (Facebook), Google, TikTok
|
|
414
|
-
- **Custom scripts**: Inline JavaScript or external scripts
|
|
415
|
-
- **Tracking pixels**: Image-based tracking pixels
|
|
416
|
-
- **Conditional loading**: Based on URL, device type, etc.
|
|
417
|
-
- **Frequency control**: Once per session, once per page, or always
|
|
418
|
-
|
|
419
|
-
**Security Features:**
|
|
420
|
-
- HTTPS-only script loading (except localhost)
|
|
421
|
-
- XSS protection with pattern detection
|
|
422
|
-
- URL validation and protocol blocking
|
|
423
|
-
- CSP nonce support for inline scripts
|
|
424
|
-
- Automatic async loading for better performance
|
|
425
|
-
|
|
426
|
-
### Plugins
|
|
448
|
+
---
|
|
427
449
|
|
|
428
|
-
|
|
450
|
+
## Container Scripts
|
|
429
451
|
|
|
430
|
-
|
|
431
|
-
import datalyr from '@datalyr/web';
|
|
452
|
+
Container scripts manage third-party tracking pixels (Meta, Google, TikTok) configured in your Datalyr dashboard.
|
|
432
453
|
|
|
433
|
-
|
|
434
|
-
const myPlugin = {
|
|
435
|
-
name: 'my-plugin',
|
|
454
|
+
### Script Tag Installation
|
|
436
455
|
|
|
437
|
-
|
|
438
|
-
console.log('Plugin initialized');
|
|
439
|
-
},
|
|
456
|
+
For container scripts, use the container endpoint:
|
|
440
457
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
458
|
+
```html
|
|
459
|
+
<script defer src="https://track.datalyr.com/container.js"
|
|
460
|
+
data-workspace-id="YOUR_WORKSPACE_ID"></script>
|
|
461
|
+
```
|
|
444
462
|
|
|
445
|
-
|
|
446
|
-
console.log('User identified:', userId);
|
|
447
|
-
},
|
|
463
|
+
This loads your configured pixels and tracking scripts automatically.
|
|
448
464
|
|
|
449
|
-
|
|
450
|
-
console.log('Page tracked');
|
|
451
|
-
}
|
|
452
|
-
};
|
|
465
|
+
### NPM SDK with Containers
|
|
453
466
|
|
|
454
|
-
|
|
467
|
+
```javascript
|
|
455
468
|
datalyr.init({
|
|
456
469
|
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
457
|
-
|
|
470
|
+
enableContainer: true // Default: true
|
|
458
471
|
});
|
|
459
|
-
```
|
|
460
472
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
Full TypeScript definitions are included:
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
import datalyr, { EventProperties, UserTraits } from '@datalyr/web';
|
|
473
|
+
// Manually trigger a script
|
|
474
|
+
datalyr.loadScript('custom-script-id');
|
|
467
475
|
|
|
468
|
-
//
|
|
469
|
-
datalyr.
|
|
470
|
-
|
|
471
|
-
});
|
|
476
|
+
// Get loaded scripts
|
|
477
|
+
const scripts = datalyr.getLoadedScripts();
|
|
478
|
+
```
|
|
472
479
|
|
|
473
|
-
|
|
474
|
-
const properties: EventProperties = {
|
|
475
|
-
product_id: 'SKU-123',
|
|
476
|
-
price: 99.99,
|
|
477
|
-
quantity: 2
|
|
478
|
-
};
|
|
480
|
+
### Supported Pixels
|
|
479
481
|
|
|
480
|
-
|
|
482
|
+
- Meta (Facebook) Pixel
|
|
483
|
+
- Google Analytics / Google Ads
|
|
484
|
+
- TikTok Pixel
|
|
485
|
+
- Custom scripts and tracking pixels
|
|
481
486
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
};
|
|
487
|
+
Security features:
|
|
488
|
+
- HTTPS-only script loading
|
|
489
|
+
- XSS protection
|
|
490
|
+
- URL validation
|
|
491
|
+
- CSP nonce support
|
|
488
492
|
|
|
489
|
-
|
|
490
|
-
```
|
|
493
|
+
---
|
|
491
494
|
|
|
492
495
|
## Framework Integration
|
|
493
496
|
|
|
@@ -505,9 +508,7 @@ function App() {
|
|
|
505
508
|
}, []);
|
|
506
509
|
|
|
507
510
|
const handleClick = () => {
|
|
508
|
-
datalyr.track('Button Clicked', {
|
|
509
|
-
button_name: 'CTA'
|
|
510
|
-
});
|
|
511
|
+
datalyr.track('Button Clicked', { button_name: 'CTA' });
|
|
511
512
|
};
|
|
512
513
|
|
|
513
514
|
return <button onClick={handleClick}>Click Me</button>;
|
|
@@ -552,14 +553,6 @@ export function AnalyticsProvider({ children }) {
|
|
|
552
553
|
workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID,
|
|
553
554
|
debug: process.env.NODE_ENV === 'development'
|
|
554
555
|
});
|
|
555
|
-
|
|
556
|
-
// Verify initialization
|
|
557
|
-
console.log('Datalyr initialized');
|
|
558
|
-
console.log('Session ID:', datalyr.getSessionId());
|
|
559
|
-
console.log('Anonymous ID:', datalyr.getAnonymousId());
|
|
560
|
-
|
|
561
|
-
// Track initial page view
|
|
562
|
-
datalyr.page();
|
|
563
556
|
}, []);
|
|
564
557
|
|
|
565
558
|
return children;
|
|
@@ -581,518 +574,84 @@ export default function RootLayout({ children }) {
|
|
|
581
574
|
}
|
|
582
575
|
```
|
|
583
576
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
### Critical: Client-Side Identification for Attribution
|
|
587
|
-
|
|
588
|
-
When implementing authentication flows (login, signup, OAuth), you **MUST** call identify on the client-side (browser) to preserve attribution data. Server-side identification creates new sessions and loses all attribution context.
|
|
589
|
-
|
|
590
|
-
#### The Problem with Server-Side Identify
|
|
591
|
-
|
|
592
|
-
```javascript
|
|
593
|
-
// ❌ WRONG - Server-side identify breaks attribution
|
|
594
|
-
app.post('/api/login', async (req, res) => {
|
|
595
|
-
const user = await authenticate(email, password);
|
|
596
|
-
|
|
597
|
-
// This creates a NEW session without attribution data!
|
|
598
|
-
await serverAnalytics.identify({
|
|
599
|
-
userId: user.id,
|
|
600
|
-
traits: { email: user.email }
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
res.json({ userId: user.id });
|
|
604
|
-
});
|
|
605
|
-
// Result: Lost UTM params, fbclid, gclid, referrer - all gone!
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
#### The Correct Solution
|
|
609
|
-
|
|
610
|
-
```javascript
|
|
611
|
-
// ✅ CORRECT - Client-side identification preserves attribution
|
|
612
|
-
// React/Next.js example
|
|
613
|
-
function LoginPage() {
|
|
614
|
-
const handleLogin = async () => {
|
|
615
|
-
// 1. Authenticate on server
|
|
616
|
-
const response = await fetch('/api/login', {
|
|
617
|
-
method: 'POST',
|
|
618
|
-
body: JSON.stringify({ email, password })
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
const { userId, email, name } = await response.json();
|
|
622
|
-
|
|
623
|
-
// 2. Identify in browser - this preserves attribution!
|
|
624
|
-
datalyr.identify(userId, {
|
|
625
|
-
email: email,
|
|
626
|
-
name: name,
|
|
627
|
-
login_method: 'email'
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
// 3. Then redirect
|
|
631
|
-
router.push('/dashboard');
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// Server endpoint - NO identify, just auth
|
|
636
|
-
app.post('/api/login', async (req, res) => {
|
|
637
|
-
const user = await authenticate(email, password);
|
|
638
|
-
|
|
639
|
-
// Just return user data for client-side identify
|
|
640
|
-
res.json({
|
|
641
|
-
userId: user.id,
|
|
642
|
-
email: user.email,
|
|
643
|
-
name: user.name
|
|
644
|
-
});
|
|
645
|
-
});
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
### Why Client-Side Identification Matters
|
|
649
|
-
|
|
650
|
-
1. **Attribution Preserved**: UTM params, fbclid, gclid, referrer all stay linked to the user
|
|
651
|
-
2. **Session Continuity**: Links anonymous session (with all its context) to identified user
|
|
652
|
-
3. **Cookie Access**: Can read and maintain browser session data
|
|
653
|
-
4. **Accurate Journey**: Tracks the complete anonymous → identified user journey
|
|
654
|
-
|
|
655
|
-
### Implementation Checklist
|
|
656
|
-
|
|
657
|
-
Ensure you call identify **client-side** in these scenarios:
|
|
658
|
-
|
|
659
|
-
- [ ] **After Signup Success**: When account creation API returns success
|
|
660
|
-
- [ ] **After Login Success**: When authentication API returns success
|
|
661
|
-
- [ ] **After OAuth Redirect**: When returning from OAuth provider
|
|
662
|
-
- [ ] **After Magic Link Click**: When magic link is validated
|
|
663
|
-
- [ ] **On App Load**: If user session exists, re-identify
|
|
664
|
-
- [ ] **After Email Verification**: When user verifies their email
|
|
665
|
-
- [ ] **Session Restore**: When detecting existing authenticated session
|
|
666
|
-
|
|
667
|
-
### Example: Complete OAuth Implementation
|
|
668
|
-
|
|
669
|
-
```javascript
|
|
670
|
-
// Client-side: After OAuth redirect
|
|
671
|
-
useEffect(() => {
|
|
672
|
-
// Check if we just came back from OAuth
|
|
673
|
-
if (window.location.search.includes('oauth=success')) {
|
|
674
|
-
// Get user data from your API/session
|
|
675
|
-
fetch('/api/me')
|
|
676
|
-
.then(res => res.json())
|
|
677
|
-
.then(user => {
|
|
678
|
-
// Identify in browser - preserves attribution!
|
|
679
|
-
datalyr.identify(user.id, {
|
|
680
|
-
email: user.email,
|
|
681
|
-
name: user.name,
|
|
682
|
-
auth_method: 'google',
|
|
683
|
-
signup_source: localStorage.getItem('signup_source') || 'organic'
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
// Track the appropriate event
|
|
687
|
-
if (user.isNewUser) {
|
|
688
|
-
datalyr.track('Signup Completed', { method: 'oauth' });
|
|
689
|
-
} else {
|
|
690
|
-
datalyr.track('Login Successful', { method: 'oauth' });
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
}, []);
|
|
695
|
-
|
|
696
|
-
// Server-side: OAuth callback - NO identify!
|
|
697
|
-
export async function GET(request: Request) {
|
|
698
|
-
const { user } = await validateOAuthCallback(request);
|
|
699
|
-
|
|
700
|
-
const existingUser = await getUserByEmail(user.email);
|
|
701
|
-
|
|
702
|
-
if (existingUser) {
|
|
703
|
-
// Set session, but DON'T identify
|
|
704
|
-
await createSession(existingUser.id);
|
|
705
|
-
return redirect('/dashboard?oauth=success');
|
|
706
|
-
} else {
|
|
707
|
-
// Create user, but DON'T identify
|
|
708
|
-
const newUser = await createUser(user);
|
|
709
|
-
await createSession(newUser.id);
|
|
710
|
-
return redirect('/dashboard?oauth=success&new=true');
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
### Server-Side Events (Different from Identify!)
|
|
716
|
-
|
|
717
|
-
Use server-side for **tracking events** with userId (not identify):
|
|
718
|
-
|
|
719
|
-
```javascript
|
|
720
|
-
// ✅ CORRECT - Server-side events with userId
|
|
721
|
-
// Stripe webhook example
|
|
722
|
-
app.post('/webhook/stripe', async (req, res) => {
|
|
723
|
-
const event = stripe.webhooks.constructEvent(req.body, sig, secret);
|
|
724
|
-
|
|
725
|
-
if (event.type === 'checkout.session.completed') {
|
|
726
|
-
const session = event.data.object;
|
|
727
|
-
|
|
728
|
-
// Track purchase event with userId (NOT identify)
|
|
729
|
-
await analytics.track({
|
|
730
|
-
userId: session.client_reference_id, // Your user ID
|
|
731
|
-
event: 'Purchase Completed',
|
|
732
|
-
properties: {
|
|
733
|
-
amount: session.amount_total / 100,
|
|
734
|
-
currency: session.currency,
|
|
735
|
-
plan: session.metadata.plan
|
|
736
|
-
}
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
### What Happens with Server-Side Identify (Wrong!)
|
|
743
|
-
|
|
744
|
-
If you identify server-side:
|
|
745
|
-
- **Lost Attribution**: New session created, no UTM params or click IDs
|
|
746
|
-
- **Broken Journey**: Can't link anonymous browsing to identified user
|
|
747
|
-
- **Multiple Sessions**: Each server identify creates disconnected session
|
|
748
|
-
- **No Context**: Missing referrer, landing page, device info
|
|
749
|
-
|
|
750
|
-
## Best Practices
|
|
751
|
-
|
|
752
|
-
### 1. Initialize Early
|
|
753
|
-
Initialize the SDK as early as possible in your application lifecycle:
|
|
754
|
-
|
|
755
|
-
```javascript
|
|
756
|
-
// In your main entry file
|
|
757
|
-
import datalyr from '@datalyr/web';
|
|
758
|
-
|
|
759
|
-
// Initialize before other code
|
|
760
|
-
datalyr.init({
|
|
761
|
-
workspaceId: 'YOUR_WORKSPACE_ID'
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
// Your app code...
|
|
765
|
-
```
|
|
766
|
-
|
|
767
|
-
### 2. Use Consistent Event Naming
|
|
768
|
-
Adopt a naming convention for your events:
|
|
577
|
+
---
|
|
769
578
|
|
|
770
|
-
|
|
771
|
-
// Good: Consistent, descriptive names
|
|
772
|
-
datalyr.track('Product Viewed');
|
|
773
|
-
datalyr.track('Cart Updated');
|
|
774
|
-
datalyr.track('Checkout Started');
|
|
775
|
-
|
|
776
|
-
// Avoid: Inconsistent naming
|
|
777
|
-
datalyr.track('view_product');
|
|
778
|
-
datalyr.track('Cart-Update');
|
|
779
|
-
datalyr.track('CHECKOUT_START');
|
|
780
|
-
```
|
|
579
|
+
## TypeScript
|
|
781
580
|
|
|
782
|
-
|
|
783
|
-
|
|
581
|
+
```typescript
|
|
582
|
+
import datalyr, { EventProperties, UserTraits } from '@datalyr/web';
|
|
784
583
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
datalyr.track('Product Viewed', {
|
|
584
|
+
// Type-safe event properties
|
|
585
|
+
const properties: EventProperties = {
|
|
788
586
|
product_id: 'SKU-123',
|
|
789
|
-
product_name: 'Premium Widget',
|
|
790
|
-
category: 'Electronics',
|
|
791
587
|
price: 99.99,
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
view_type: 'quick_view'
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
// Avoid: Missing context
|
|
798
|
-
datalyr.track('Product Viewed', {
|
|
799
|
-
id: '123'
|
|
800
|
-
});
|
|
801
|
-
```
|
|
802
|
-
|
|
803
|
-
### 4. ALWAYS Identify in the Browser (Not Server)
|
|
804
|
-
This is critical for attribution:
|
|
805
|
-
|
|
806
|
-
```javascript
|
|
807
|
-
// ✅ CORRECT - Browser identification
|
|
808
|
-
async function handleLogin(email, password) {
|
|
809
|
-
const response = await fetch('/api/login', {
|
|
810
|
-
method: 'POST',
|
|
811
|
-
body: JSON.stringify({ email, password })
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
const user = await response.json();
|
|
815
|
-
|
|
816
|
-
// Identify in browser - preserves attribution!
|
|
817
|
-
datalyr.identify(user.id, {
|
|
818
|
-
email: user.email,
|
|
819
|
-
name: user.name,
|
|
820
|
-
plan: user.subscription
|
|
821
|
-
});
|
|
822
|
-
}
|
|
588
|
+
quantity: 2
|
|
589
|
+
};
|
|
823
590
|
|
|
824
|
-
|
|
825
|
-
// This breaks attribution tracking!
|
|
826
|
-
app.post('/api/login', async (req, res) => {
|
|
827
|
-
const user = await authenticate(req.body);
|
|
591
|
+
datalyr.track('Product Added', properties);
|
|
828
592
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
593
|
+
// Type-safe user traits
|
|
594
|
+
const traits: UserTraits = {
|
|
595
|
+
email: 'user@example.com',
|
|
596
|
+
name: 'John Doe',
|
|
597
|
+
plan: 'premium'
|
|
598
|
+
};
|
|
833
599
|
|
|
834
|
-
|
|
835
|
-
});
|
|
600
|
+
datalyr.identify('user_123', traits);
|
|
836
601
|
```
|
|
837
602
|
|
|
838
|
-
|
|
839
|
-
The SDK handles errors internally, but you can add your own error tracking:
|
|
840
|
-
|
|
841
|
-
```javascript
|
|
842
|
-
window.addEventListener('error', (event) => {
|
|
843
|
-
datalyr.track('JavaScript Error', {
|
|
844
|
-
message: event.message,
|
|
845
|
-
source: event.filename,
|
|
846
|
-
line: event.lineno,
|
|
847
|
-
column: event.colno,
|
|
848
|
-
error: event.error?.toString()
|
|
849
|
-
});
|
|
850
|
-
});
|
|
851
|
-
```
|
|
603
|
+
---
|
|
852
604
|
|
|
853
605
|
## Troubleshooting
|
|
854
606
|
|
|
855
|
-
### Events
|
|
607
|
+
### Events not appearing
|
|
856
608
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
|
860
614
|
|
|
861
|
-
|
|
862
|
-
console.log('Anonymous ID:', datalyr.getAnonymousId()) // Should return "anon_xxxxx"
|
|
863
|
-
console.log('Session ID:', datalyr.getSessionId()) // Should return "sess_xxxxx"
|
|
864
|
-
```
|
|
865
|
-
|
|
866
|
-
If these return `undefined`, the SDK isn't initialized properly.
|
|
615
|
+
### SDK not initialized
|
|
867
616
|
|
|
868
|
-
**Step 2: Check for errors**
|
|
869
617
|
```javascript
|
|
870
|
-
|
|
618
|
+
// Check initialization
|
|
619
|
+
console.log('Anonymous ID:', datalyr.getAnonymousId());
|
|
620
|
+
console.log('Session ID:', datalyr.getSessionId());
|
|
621
|
+
console.log('Errors:', datalyr.getErrors());
|
|
871
622
|
```
|
|
872
623
|
|
|
873
|
-
|
|
874
|
-
```javascript
|
|
875
|
-
datalyr.init({
|
|
876
|
-
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
877
|
-
debug: true // Shows [Datalyr] logs in console
|
|
878
|
-
})
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
**Step 4: Verify network requests**
|
|
882
|
-
1. Open DevTools → Network tab
|
|
883
|
-
2. Filter by "ingest"
|
|
884
|
-
3. Track a test event: `datalyr.track('test', {})`
|
|
885
|
-
4. You should see POST to `ingest.datalyr.com`
|
|
886
|
-
|
|
887
|
-
### Common Errors
|
|
888
|
-
|
|
889
|
-
**"Cannot find module '@datalyr/web'"**
|
|
890
|
-
- Run `npm install @datalyr/web`
|
|
891
|
-
- Restart your dev server
|
|
892
|
-
|
|
893
|
-
**"process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID is undefined"**
|
|
894
|
-
- Create `.env.local` file in project root
|
|
895
|
-
- Add: `NEXT_PUBLIC_DATALYR_WORKSPACE_ID=your_id`
|
|
896
|
-
- Restart dev server (required for env changes)
|
|
897
|
-
|
|
898
|
-
**"datalyr.getAnonymousId() returns undefined"**
|
|
899
|
-
- You're calling it during SSR (server-side rendering)
|
|
900
|
-
- Make sure you're in a Client Component with `'use client'`
|
|
901
|
-
- Call it inside `useEffect` or after component mounts
|
|
902
|
-
|
|
903
|
-
**Events tracked but not in dashboard**
|
|
904
|
-
- Verify workspace ID matches your dashboard
|
|
905
|
-
- Events may take 1-2 minutes to appear
|
|
906
|
-
- Check Network tab - requests should return 200 OK
|
|
907
|
-
- Check if ad blocker is enabled (disable it)
|
|
908
|
-
|
|
909
|
-
### Script Tag vs SDK Confusion
|
|
910
|
-
|
|
911
|
-
**If you're using the script tag:**
|
|
912
|
-
```javascript
|
|
913
|
-
// Check if loaded
|
|
914
|
-
window.datalyr._isLoaded // Should be true
|
|
915
|
-
window.datalyr.getVisitorId() // Should return ID
|
|
916
|
-
|
|
917
|
-
// Use object methods, not function calls
|
|
918
|
-
window.datalyr.track('event', {}) // ✅ Correct
|
|
919
|
-
window.datalyr("track", "event", {}) // ❌ Wrong
|
|
920
|
-
```
|
|
921
|
-
|
|
922
|
-
**If you're using the NPM SDK:**
|
|
923
|
-
```javascript
|
|
924
|
-
import datalyr from '@datalyr/web'
|
|
925
|
-
|
|
926
|
-
// Check if loaded
|
|
927
|
-
datalyr.getAnonymousId() // Should return ID
|
|
928
|
-
datalyr.getSessionId() // Should return ID
|
|
929
|
-
```
|
|
624
|
+
If these return `undefined`, the SDK isn't initialized.
|
|
930
625
|
|
|
931
|
-
|
|
626
|
+
### Next.js SSR issues
|
|
932
627
|
|
|
933
|
-
|
|
628
|
+
Make sure you're calling SDK methods client-side only:
|
|
934
629
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
<!-- In your HTML -->
|
|
938
|
-
<script defer src="https://track.datalyr.com/container.js"
|
|
939
|
-
data-workspace-id="YOUR_WORKSPACE_ID"></script>
|
|
940
|
-
```
|
|
941
|
-
|
|
942
|
-
```javascript
|
|
943
|
-
// In your JavaScript
|
|
944
|
-
window.datalyr.track('Button Clicked', {
|
|
945
|
-
button_name: 'Sign Up'
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
window.datalyr.identify('user_123', {
|
|
949
|
-
email: 'user@example.com'
|
|
950
|
-
});
|
|
951
|
-
```
|
|
952
|
-
|
|
953
|
-
### After (NPM SDK):
|
|
954
|
-
```javascript
|
|
955
|
-
// Remove the script tag from your HTML
|
|
956
|
-
|
|
957
|
-
// Install the package
|
|
958
|
-
// npm install @datalyr/web
|
|
630
|
+
```jsx
|
|
631
|
+
'use client';
|
|
959
632
|
|
|
960
|
-
|
|
633
|
+
import { useEffect } from 'react';
|
|
961
634
|
import datalyr from '@datalyr/web';
|
|
962
635
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
});
|
|
966
|
-
|
|
967
|
-
// In your components
|
|
968
|
-
datalyr.track('Button Clicked', {
|
|
969
|
-
button_name: 'Sign Up'
|
|
970
|
-
});
|
|
971
|
-
|
|
972
|
-
datalyr.identify('user_123', {
|
|
973
|
-
email: 'user@example.com'
|
|
974
|
-
});
|
|
636
|
+
useEffect(() => {
|
|
637
|
+
// SDK methods only work in browser
|
|
638
|
+
datalyr.init({ workspaceId: '...' });
|
|
639
|
+
}, []);
|
|
975
640
|
```
|
|
976
641
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
## API Comparison: Script Tag vs NPM SDK
|
|
980
|
-
|
|
981
|
-
| Feature | Script Tag | NPM SDK |
|
|
982
|
-
|---------|-----------|---------|
|
|
983
|
-
| **Installation** | Add `<script>` to HTML | `npm install @datalyr/web` |
|
|
984
|
-
| **Bundle Size** | 0 (external) | ~15KB |
|
|
985
|
-
| **TypeScript** | No types | ✅ Full types |
|
|
986
|
-
| **Import** | `window.datalyr` | `import datalyr from '@datalyr/web'` |
|
|
987
|
-
| **Initialize** | Automatic | `datalyr.init({...})` |
|
|
988
|
-
| **Track Event** | `window.datalyr.track(...)` | `datalyr.track(...)` |
|
|
989
|
-
| **Identify** | `window.datalyr.identify(...)` | `datalyr.identify(...)` |
|
|
990
|
-
| **Get Visitor ID** | `window.datalyr.getVisitorId()` | `datalyr.getAnonymousId()` |
|
|
991
|
-
| **Get Session** | `window.datalyr._getSessionId()` | `datalyr.getSessionId()` |
|
|
992
|
-
| **Check Loaded** | `window.datalyr._isLoaded` | `datalyr.getAnonymousId() !== undefined` |
|
|
993
|
-
|
|
994
|
-
Both methods use the same tracking backend and provide identical attribution tracking.
|
|
995
|
-
|
|
996
|
-
## Browser Support
|
|
997
|
-
|
|
998
|
-
The SDK supports all modern browsers:
|
|
999
|
-
- Chrome/Edge 90+
|
|
1000
|
-
- Firefox 88+
|
|
1001
|
-
- Safari 14+
|
|
1002
|
-
- Opera 76+
|
|
1003
|
-
|
|
1004
|
-
For older browsers, the SDK will gracefully degrade with fallback implementations.
|
|
642
|
+
### Script tag vs NPM
|
|
1005
643
|
|
|
1006
|
-
|
|
644
|
+
Choose one installation method. Using both causes conflicts.
|
|
1007
645
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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()` |
|
|
1013
652
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
The SDK is designed with privacy in mind:
|
|
1017
|
-
|
|
1018
|
-
- **GDPR Compliant**: Built-in consent management
|
|
1019
|
-
- **CCPA Support**: "Do Not Sell" preference support
|
|
1020
|
-
- **Cookie Control**: Configurable cookie settings
|
|
1021
|
-
- **Data Minimization**: Collect only what you need
|
|
1022
|
-
- **User Rights**: Easy opt-out and data deletion
|
|
1023
|
-
|
|
1024
|
-
## Debugging
|
|
1025
|
-
|
|
1026
|
-
Enable debug mode for detailed logging:
|
|
1027
|
-
|
|
1028
|
-
```javascript
|
|
1029
|
-
import datalyr from '@datalyr/web';
|
|
1030
|
-
|
|
1031
|
-
datalyr.init({
|
|
1032
|
-
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
1033
|
-
debug: true
|
|
1034
|
-
});
|
|
1035
|
-
|
|
1036
|
-
// Check console for detailed logs:
|
|
1037
|
-
// [Datalyr] SDK initialized
|
|
1038
|
-
// [Datalyr] Event tracked: Button Clicked
|
|
1039
|
-
// [Datalyr] Batch sent successfully: 5 events
|
|
1040
|
-
```
|
|
1041
|
-
|
|
1042
|
-
## API Reference
|
|
1043
|
-
|
|
1044
|
-
### Initialization Options
|
|
1045
|
-
|
|
1046
|
-
| Option | Type | Default | Description |
|
|
1047
|
-
|--------|------|---------|-------------|
|
|
1048
|
-
| `workspaceId` | `string` | *required* | Your Datalyr workspace ID |
|
|
1049
|
-
| `endpoint` | `string` | Default API | Custom endpoint URL |
|
|
1050
|
-
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
1051
|
-
| `batchSize` | `number` | `10` | Number of events per batch |
|
|
1052
|
-
| `flushInterval` | `number` | `5000` | Time between flushes (ms) |
|
|
1053
|
-
| `sessionTimeout` | `number` | `1800000` | Session timeout (ms) |
|
|
1054
|
-
| `privacyMode` | `'standard' \| 'strict'` | `'standard'` | Privacy level |
|
|
1055
|
-
| `respectDoNotTrack` | `boolean` | `false` | Honor browser DNT |
|
|
1056
|
-
| `respectGlobalPrivacyControl` | `boolean` | `true` | Honor GPC |
|
|
1057
|
-
| `cookieDomain` | `string \| 'auto'` | `'auto'` | Cookie domain |
|
|
1058
|
-
| `trackSPA` | `boolean` | `true` | Track SPA routes |
|
|
1059
|
-
| `trackPageViews` | `boolean` | `true` | Track page views |
|
|
1060
|
-
| `enableContainer` | `boolean` | `true` | Enable secure container script loading |
|
|
1061
|
-
|
|
1062
|
-
### Methods
|
|
1063
|
-
|
|
1064
|
-
| Method | Description |
|
|
1065
|
-
|--------|-------------|
|
|
1066
|
-
| `init(config)` | Initialize the SDK with configuration |
|
|
1067
|
-
| `track(event, properties?)` | Track an event |
|
|
1068
|
-
| `identify(userId?, traits?)` | Identify a user |
|
|
1069
|
-
| `page(name?, properties?)` | Track a page view |
|
|
1070
|
-
| `alias(userId)` | Create an alias |
|
|
1071
|
-
| `reset()` | Reset identity |
|
|
1072
|
-
| `optOut()` | Opt out of tracking |
|
|
1073
|
-
| `optIn()` | Opt back in |
|
|
1074
|
-
| `setConsent(config)` | Set consent preferences |
|
|
1075
|
-
| `flush()` | Force send events |
|
|
1076
|
-
| `getAnonymousId()` | Get anonymous ID |
|
|
1077
|
-
| `getUserId()` | Get user ID |
|
|
1078
|
-
| `getDistinctId()` | Get distinct ID |
|
|
1079
|
-
| `getSessionId()` | Get session ID |
|
|
1080
|
-
| `getSessionData()` | Get session data |
|
|
1081
|
-
| `getErrors()` | Get SDK errors |
|
|
1082
|
-
| `loadScript(scriptId)` | Manually trigger a container script |
|
|
1083
|
-
| `getLoadedScripts()` | Get list of loaded container scripts |
|
|
1084
|
-
| `destroy()` | Clean up resources |
|
|
1085
|
-
|
|
1086
|
-
## Support
|
|
1087
|
-
|
|
1088
|
-
- **Documentation**: [https://docs.datalyr.com](https://docs.datalyr.com)
|
|
1089
|
-
- **Issues**: [GitHub Issues](https://github.com/datalyr/web/issues)
|
|
1090
|
-
- **Email**: support@datalyr.com
|
|
653
|
+
---
|
|
1091
654
|
|
|
1092
655
|
## License
|
|
1093
656
|
|
|
1094
|
-
MIT
|
|
1095
|
-
|
|
1096
|
-
---
|
|
1097
|
-
|
|
1098
|
-
Built by the Datalyr team
|
|
657
|
+
MIT
|