@datalyr/api 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -92
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,195 +1,391 @@
|
|
|
1
1
|
# @datalyr/api
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Server-side analytics and attribution SDK for Node.js. Track events, identify users, and preserve attribution data from your backend.
|
|
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
|
+
- [User Identity](#user-identity)
|
|
15
|
+
- [Anonymous ID](#anonymous-id)
|
|
16
|
+
- [Identifying Users](#identifying-users)
|
|
17
|
+
- [Groups](#groups)
|
|
18
|
+
- [Attribution Preservation](#attribution-preservation)
|
|
19
|
+
- [Event Queue](#event-queue)
|
|
20
|
+
- [Framework Examples](#framework-examples)
|
|
21
|
+
- [Express.js](#expressjs)
|
|
22
|
+
- [Stripe Webhooks](#stripe-webhooks)
|
|
23
|
+
- [TypeScript](#typescript)
|
|
24
|
+
- [Troubleshooting](#troubleshooting)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
|
|
27
|
+
---
|
|
4
28
|
|
|
5
29
|
## Installation
|
|
6
30
|
|
|
7
31
|
```bash
|
|
8
32
|
npm install @datalyr/api
|
|
9
|
-
# or
|
|
10
|
-
yarn add @datalyr/api
|
|
11
|
-
# or
|
|
12
|
-
pnpm add @datalyr/api
|
|
13
33
|
```
|
|
14
34
|
|
|
35
|
+
---
|
|
36
|
+
|
|
15
37
|
## Quick Start
|
|
16
38
|
|
|
17
39
|
```javascript
|
|
18
|
-
const { Datalyr } = require('@datalyr/api');
|
|
19
|
-
// or
|
|
20
40
|
import { Datalyr } from '@datalyr/api';
|
|
21
41
|
|
|
22
|
-
// Initialize
|
|
23
|
-
const datalyr = new Datalyr('
|
|
42
|
+
// Initialize
|
|
43
|
+
const datalyr = new Datalyr('dk_your_api_key');
|
|
44
|
+
|
|
45
|
+
// Track events
|
|
46
|
+
await datalyr.track('user_123', 'button_clicked', { button: 'signup' });
|
|
47
|
+
|
|
48
|
+
// Identify users
|
|
49
|
+
await datalyr.identify('user_123', { email: 'user@example.com' });
|
|
50
|
+
|
|
51
|
+
// Clean up on shutdown
|
|
52
|
+
await datalyr.close();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## How It Works
|
|
58
|
+
|
|
59
|
+
The SDK collects events and sends them to the Datalyr backend for analytics and attribution.
|
|
60
|
+
|
|
61
|
+
### Data Flow
|
|
62
|
+
|
|
63
|
+
1. Events are created with `track()`, `identify()`, `page()`, or `group()`
|
|
64
|
+
2. Events are queued locally and sent in batches
|
|
65
|
+
3. Batches are sent when queue reaches 20 events or every 10 seconds
|
|
66
|
+
4. Failed requests are retried with exponential backoff
|
|
67
|
+
5. Events are processed server-side for analytics and attribution reporting
|
|
68
|
+
|
|
69
|
+
### Event Payload
|
|
70
|
+
|
|
71
|
+
Every event includes:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
{
|
|
75
|
+
event: 'purchase', // Event name
|
|
76
|
+
properties: { ... }, // Custom properties
|
|
77
|
+
|
|
78
|
+
// Identity
|
|
79
|
+
anonymous_id: 'uuid', // Persistent ID
|
|
80
|
+
user_id: 'user_123', // Set after identify()
|
|
81
|
+
|
|
82
|
+
// Timestamps
|
|
83
|
+
timestamp: '2024-01-15T10:30:00Z',
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
const datalyr = new Datalyr({
|
|
93
|
+
// Required
|
|
94
|
+
apiKey: string,
|
|
95
|
+
|
|
96
|
+
// Optional
|
|
97
|
+
host?: string, // Custom endpoint (default: https://ingest.datalyr.com)
|
|
98
|
+
flushAt?: number, // Batch size (default: 20)
|
|
99
|
+
flushInterval?: number, // Send interval ms (default: 10000)
|
|
100
|
+
timeout?: number, // Request timeout ms (default: 10000)
|
|
101
|
+
retryLimit?: number, // Max retries (default: 3)
|
|
102
|
+
maxQueueSize?: number, // Max queued events (default: 1000)
|
|
103
|
+
debug?: boolean, // Console logging (default: false)
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Event Tracking
|
|
110
|
+
|
|
111
|
+
### Custom Events
|
|
112
|
+
|
|
113
|
+
Track any action in your application:
|
|
24
114
|
|
|
25
|
-
|
|
115
|
+
```javascript
|
|
116
|
+
// Simple event
|
|
117
|
+
await datalyr.track('user_123', 'signup_started');
|
|
118
|
+
|
|
119
|
+
// Event with properties
|
|
120
|
+
await datalyr.track('user_123', 'product_viewed', {
|
|
121
|
+
product_id: 'SKU123',
|
|
122
|
+
product_name: 'Blue Shirt',
|
|
123
|
+
price: 29.99,
|
|
124
|
+
currency: 'USD',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Purchase event
|
|
26
128
|
await datalyr.track('user_123', 'Purchase Completed', {
|
|
27
|
-
|
|
129
|
+
order_id: 'ORD-456',
|
|
130
|
+
total: 99.99,
|
|
28
131
|
currency: 'USD',
|
|
29
|
-
|
|
132
|
+
items: ['SKU123', 'SKU456'],
|
|
30
133
|
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Page Views
|
|
137
|
+
|
|
138
|
+
Track server-rendered page views:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
await datalyr.page('user_123', 'Homepage', {
|
|
142
|
+
url: 'https://example.com',
|
|
143
|
+
referrer: 'https://google.com',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await datalyr.page('user_123', 'Product Details', {
|
|
147
|
+
url: 'https://example.com/products/123',
|
|
148
|
+
product_id: 'SKU123',
|
|
149
|
+
});
|
|
150
|
+
```
|
|
31
151
|
|
|
32
|
-
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## User Identity
|
|
155
|
+
|
|
156
|
+
### Anonymous ID
|
|
157
|
+
|
|
158
|
+
The SDK generates a persistent anonymous ID:
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
const anonymousId = datalyr.getAnonymousId();
|
|
162
|
+
// 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
For attribution preservation, pass the anonymous ID from your browser/mobile SDK instead.
|
|
166
|
+
|
|
167
|
+
### Identifying Users
|
|
168
|
+
|
|
169
|
+
Link events to a known user:
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
33
172
|
await datalyr.identify('user_123', {
|
|
34
173
|
email: 'user@example.com',
|
|
35
174
|
name: 'John Doe',
|
|
36
|
-
plan: 'premium'
|
|
175
|
+
plan: 'premium',
|
|
37
176
|
});
|
|
177
|
+
```
|
|
38
178
|
|
|
39
|
-
|
|
40
|
-
await datalyr.page('user_123', 'Homepage', {
|
|
41
|
-
url: 'https://example.com',
|
|
42
|
-
referrer: 'https://google.com'
|
|
43
|
-
});
|
|
179
|
+
### Groups
|
|
44
180
|
|
|
45
|
-
|
|
181
|
+
Associate users with companies or teams:
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
46
184
|
await datalyr.group('user_123', 'company_456', {
|
|
47
185
|
name: 'Acme Corp',
|
|
48
|
-
industry: 'Technology'
|
|
186
|
+
industry: 'Technology',
|
|
187
|
+
employees: 50,
|
|
49
188
|
});
|
|
50
|
-
|
|
51
|
-
// Clean up when done
|
|
52
|
-
await datalyr.close();
|
|
53
189
|
```
|
|
54
190
|
|
|
55
|
-
|
|
191
|
+
---
|
|
56
192
|
|
|
57
|
-
|
|
193
|
+
## Attribution Preservation
|
|
194
|
+
|
|
195
|
+
Pass the anonymous ID from browser/mobile SDKs to preserve attribution data:
|
|
58
196
|
|
|
59
197
|
```javascript
|
|
60
|
-
//
|
|
198
|
+
// Object signature with anonymousId
|
|
61
199
|
await datalyr.track({
|
|
62
200
|
event: 'Purchase Completed',
|
|
63
201
|
userId: 'user_123',
|
|
64
|
-
anonymousId: req.body.anonymous_id, // From browser
|
|
202
|
+
anonymousId: req.body.anonymous_id, // From browser SDK
|
|
65
203
|
properties: {
|
|
66
204
|
amount: 99.99,
|
|
67
|
-
currency: 'USD'
|
|
68
|
-
}
|
|
205
|
+
currency: 'USD',
|
|
206
|
+
},
|
|
69
207
|
});
|
|
208
|
+
```
|
|
70
209
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
210
|
+
This links server-side events to the user's browser session, preserving:
|
|
211
|
+
- UTM parameters (utm_source, utm_medium, utm_campaign)
|
|
212
|
+
- Click IDs (fbclid, gclid, ttclid)
|
|
213
|
+
- Referrer and landing page
|
|
214
|
+
- Customer journey touchpoints
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Event Queue
|
|
219
|
+
|
|
220
|
+
Events are batched for efficiency.
|
|
221
|
+
|
|
222
|
+
### Configuration
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
const datalyr = new Datalyr({
|
|
226
|
+
apiKey: 'dk_your_api_key',
|
|
227
|
+
flushAt: 20, // Send when 20 events queued
|
|
228
|
+
flushInterval: 10000, // Or every 10 seconds
|
|
74
229
|
});
|
|
230
|
+
```
|
|
75
231
|
|
|
76
|
-
|
|
77
|
-
|
|
232
|
+
### Manual Flush
|
|
233
|
+
|
|
234
|
+
Send all queued events immediately:
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
await datalyr.flush();
|
|
78
238
|
```
|
|
79
239
|
|
|
80
|
-
###
|
|
240
|
+
### Graceful Shutdown
|
|
241
|
+
|
|
242
|
+
Always close the client on application shutdown:
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
process.on('SIGTERM', async () => {
|
|
246
|
+
await datalyr.close(); // Flushes remaining events
|
|
247
|
+
process.exit(0);
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Framework Examples
|
|
254
|
+
|
|
255
|
+
### Express.js
|
|
81
256
|
|
|
82
257
|
```javascript
|
|
258
|
+
import express from 'express';
|
|
259
|
+
import { Datalyr } from '@datalyr/api';
|
|
260
|
+
|
|
261
|
+
const app = express();
|
|
262
|
+
const datalyr = new Datalyr('dk_your_api_key');
|
|
263
|
+
|
|
83
264
|
app.post('/api/purchase', async (req, res) => {
|
|
84
|
-
const { items, anonymous_id } = req.body;
|
|
85
|
-
|
|
86
|
-
// Track with anonymous_id to preserve attribution
|
|
265
|
+
const { items, anonymous_id } = req.body;
|
|
266
|
+
|
|
267
|
+
// Track with anonymous_id to preserve attribution
|
|
87
268
|
await datalyr.track({
|
|
88
269
|
event: 'Purchase Completed',
|
|
89
270
|
userId: req.user?.id,
|
|
90
|
-
anonymousId: anonymous_id,
|
|
271
|
+
anonymousId: anonymous_id,
|
|
91
272
|
properties: {
|
|
92
273
|
total: calculateTotal(items),
|
|
93
|
-
|
|
94
|
-
}
|
|
274
|
+
item_count: items.length,
|
|
275
|
+
},
|
|
95
276
|
});
|
|
96
|
-
|
|
277
|
+
|
|
97
278
|
res.json({ success: true });
|
|
98
279
|
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Key Benefits:
|
|
102
|
-
- **Attribution Preservation**: Never lose fbclid, gclid, ttclid, or lyr tracking
|
|
103
|
-
- **Complete Journey**: Track users from web → server → mobile
|
|
104
|
-
- **Flexible API**: Support both legacy and new tracking methods
|
|
105
280
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
apiKey: 'your_api_key_here',
|
|
111
|
-
host: 'https://api.datalyr.com', // Optional: custom host
|
|
112
|
-
flushAt: 20, // Optional: batch size (default: 20)
|
|
113
|
-
flushInterval: 10000, // Optional: batch interval in ms (default: 10000)
|
|
114
|
-
debug: true, // Optional: enable debug logging (default: false)
|
|
115
|
-
timeout: 10000, // Optional: request timeout in ms (default: 10000)
|
|
116
|
-
retryLimit: 3, // Optional: max retries (default: 3)
|
|
117
|
-
maxQueueSize: 1000 // Optional: max events in queue (default: 1000)
|
|
281
|
+
// Graceful shutdown
|
|
282
|
+
process.on('SIGTERM', async () => {
|
|
283
|
+
await datalyr.close();
|
|
284
|
+
process.exit(0);
|
|
118
285
|
});
|
|
119
286
|
```
|
|
120
287
|
|
|
121
|
-
|
|
288
|
+
### Stripe Webhooks
|
|
122
289
|
|
|
123
290
|
```javascript
|
|
124
|
-
|
|
125
|
-
|
|
291
|
+
import { Datalyr } from '@datalyr/api';
|
|
292
|
+
import Stripe from 'stripe';
|
|
126
293
|
|
|
127
|
-
const datalyr = new Datalyr(
|
|
294
|
+
const datalyr = new Datalyr('dk_your_api_key');
|
|
295
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
|
128
296
|
|
|
129
297
|
app.post('/webhooks/stripe', async (req, res) => {
|
|
130
298
|
const sig = req.headers['stripe-signature'];
|
|
131
299
|
const event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
|
|
132
|
-
|
|
300
|
+
|
|
133
301
|
switch (event.type) {
|
|
134
302
|
case 'checkout.session.completed':
|
|
303
|
+
const session = event.data.object;
|
|
135
304
|
await datalyr.track(
|
|
136
|
-
|
|
305
|
+
session.client_reference_id,
|
|
137
306
|
'Purchase Completed',
|
|
138
307
|
{
|
|
139
|
-
amount:
|
|
140
|
-
currency:
|
|
141
|
-
|
|
308
|
+
amount: session.amount_total / 100,
|
|
309
|
+
currency: session.currency,
|
|
310
|
+
stripe_session_id: session.id,
|
|
142
311
|
}
|
|
143
312
|
);
|
|
144
313
|
break;
|
|
145
|
-
|
|
314
|
+
|
|
146
315
|
case 'customer.subscription.created':
|
|
316
|
+
const subscription = event.data.object;
|
|
147
317
|
await datalyr.track(
|
|
148
|
-
|
|
318
|
+
subscription.metadata.userId,
|
|
149
319
|
'Subscription Started',
|
|
150
320
|
{
|
|
151
|
-
plan:
|
|
152
|
-
mrr:
|
|
153
|
-
interval:
|
|
321
|
+
plan: subscription.items.data[0].price.nickname,
|
|
322
|
+
mrr: subscription.items.data[0].price.unit_amount / 100,
|
|
323
|
+
interval: subscription.items.data[0].price.recurring.interval,
|
|
154
324
|
}
|
|
155
325
|
);
|
|
156
326
|
break;
|
|
157
327
|
}
|
|
158
|
-
|
|
328
|
+
|
|
159
329
|
res.json({ received: true });
|
|
160
330
|
});
|
|
161
331
|
```
|
|
162
332
|
|
|
163
|
-
|
|
333
|
+
---
|
|
164
334
|
|
|
165
|
-
|
|
335
|
+
## TypeScript
|
|
166
336
|
|
|
167
|
-
|
|
337
|
+
```typescript
|
|
338
|
+
import { Datalyr, TrackOptions, IdentifyOptions } from '@datalyr/api';
|
|
168
339
|
|
|
169
|
-
|
|
340
|
+
const datalyr = new Datalyr('dk_your_api_key');
|
|
170
341
|
|
|
171
|
-
|
|
342
|
+
// Type-safe tracking
|
|
343
|
+
const trackOptions: TrackOptions = {
|
|
344
|
+
event: 'Purchase Completed',
|
|
345
|
+
userId: 'user_123',
|
|
346
|
+
anonymousId: 'anon_456',
|
|
347
|
+
properties: {
|
|
348
|
+
amount: 99.99,
|
|
349
|
+
currency: 'USD',
|
|
350
|
+
},
|
|
351
|
+
};
|
|
172
352
|
|
|
173
|
-
|
|
353
|
+
await datalyr.track(trackOptions);
|
|
354
|
+
```
|
|
174
355
|
|
|
175
|
-
|
|
356
|
+
---
|
|
176
357
|
|
|
177
|
-
|
|
358
|
+
## Troubleshooting
|
|
178
359
|
|
|
179
|
-
|
|
360
|
+
### Events not appearing
|
|
180
361
|
|
|
181
|
-
|
|
362
|
+
1. Check API key starts with `dk_`
|
|
363
|
+
2. Enable `debug: true`
|
|
364
|
+
3. Call `flush()` to force send
|
|
365
|
+
4. Check server logs for errors
|
|
182
366
|
|
|
183
|
-
|
|
367
|
+
### Request timeouts
|
|
184
368
|
|
|
185
|
-
|
|
369
|
+
```javascript
|
|
370
|
+
const datalyr = new Datalyr({
|
|
371
|
+
apiKey: 'dk_your_api_key',
|
|
372
|
+
timeout: 30000, // Increase timeout
|
|
373
|
+
retryLimit: 5, // More retries
|
|
374
|
+
});
|
|
375
|
+
```
|
|
186
376
|
|
|
187
|
-
|
|
377
|
+
### Queue full
|
|
188
378
|
|
|
189
|
-
|
|
379
|
+
```javascript
|
|
380
|
+
const datalyr = new Datalyr({
|
|
381
|
+
apiKey: 'dk_your_api_key',
|
|
382
|
+
maxQueueSize: 5000, // Increase queue size
|
|
383
|
+
flushAt: 50, // Larger batches
|
|
384
|
+
});
|
|
385
|
+
```
|
|
190
386
|
|
|
191
|
-
|
|
387
|
+
---
|
|
192
388
|
|
|
193
389
|
## License
|
|
194
390
|
|
|
195
|
-
MIT
|
|
391
|
+
MIT
|