@tracelog/lib 2.10.0 → 3.0.0-rc.115.8
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 +160 -733
- package/dist/browser/tracelog.esm.js +1348 -2812
- package/dist/browser/tracelog.esm.js.map +1 -1
- package/dist/browser/tracelog.js +2 -2
- package/dist/browser/tracelog.js.map +1 -1
- package/dist/public-api.cjs +2 -2
- package/dist/public-api.cjs.map +1 -1
- package/dist/public-api.d.mts +91 -797
- package/dist/public-api.d.ts +91 -797
- package/dist/public-api.js +2 -2
- package/dist/public-api.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# TraceLog
|
|
2
2
|
|
|
3
|
-
Lightweight web analytics library for tracking user behavior. Works standalone or with
|
|
3
|
+
Lightweight web analytics library for tracking user behavior. Works standalone or with the TraceLog SaaS backend.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Zero-config**
|
|
8
|
-
- **Standalone**
|
|
9
|
-
- **Privacy-first**
|
|
10
|
-
- **Cross-tab sessions**
|
|
11
|
-
- **Event-driven**
|
|
12
|
-
- **Lightweight**
|
|
7
|
+
- **Zero-config** — Auto-captures clicks, scrolls, page views, sessions, performance metrics, and JavaScript errors
|
|
8
|
+
- **Standalone-first** — Works without any backend; opt in to TraceLog SaaS by passing a `projectId`
|
|
9
|
+
- **Privacy-first** — PII sanitization, client-side sampling, `data-tlog-ignore` attribute, sensitive URL param stripping
|
|
10
|
+
- **Cross-tab sessions** — BroadcastChannel sync with localStorage + sessionStorage recovery
|
|
11
|
+
- **Event-driven** — Subscribe via `on()` / `off()` for real-time consumption
|
|
12
|
+
- **Lightweight** — Single dependency (`web-vitals`), ~62KB gzipped
|
|
13
13
|
|
|
14
14
|
## Live Demo
|
|
15
15
|
|
|
@@ -20,6 +20,7 @@ Lightweight web analytics library for tracking user behavior. Works standalone o
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
22
22
|
### NPM (Recommended)
|
|
23
|
+
|
|
23
24
|
```bash
|
|
24
25
|
npm install @tracelog/lib
|
|
25
26
|
```
|
|
@@ -40,6 +41,7 @@ const { sessionId } = await tracelog.init({
|
|
|
40
41
|
```
|
|
41
42
|
|
|
42
43
|
### CDN (Script Tag)
|
|
44
|
+
|
|
43
45
|
```html
|
|
44
46
|
<script src="https://cdn.jsdelivr.net/npm/@tracelog/lib@latest/dist/browser/tracelog.js"></script>
|
|
45
47
|
<script>
|
|
@@ -48,6 +50,7 @@ const { sessionId } = await tracelog.init({
|
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
### CDN (ES Module)
|
|
53
|
+
|
|
51
54
|
```html
|
|
52
55
|
<script type="module">
|
|
53
56
|
import { tracelog } from 'https://cdn.jsdelivr.net/npm/@tracelog/lib@latest/dist/browser/tracelog.esm.js';
|
|
@@ -55,70 +58,52 @@ const { sessionId } = await tracelog.init({
|
|
|
55
58
|
</script>
|
|
56
59
|
```
|
|
57
60
|
|
|
61
|
+
---
|
|
62
|
+
|
|
58
63
|
## Quick Start
|
|
59
64
|
|
|
60
65
|
### Initialization Order
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
Set up listeners **before** calling `init()` so they receive the initial `SESSION_START` and `PAGE_VIEW` events that fire during init.
|
|
63
68
|
|
|
64
69
|
```typescript
|
|
65
|
-
// 1. Obtain user consent
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (!hasConsent) {
|
|
70
|
-
console.log('User declined tracking');
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
70
|
+
// 1. Obtain user consent first (your responsibility — see "User Consent" below)
|
|
71
|
+
const hasConsent = await getUserConsent();
|
|
72
|
+
if (!hasConsent) return;
|
|
73
73
|
|
|
74
|
-
// 2. Register
|
|
74
|
+
// 2. Register listeners (before init)
|
|
75
75
|
tracelog.on('event', (event) => {
|
|
76
76
|
console.log(event.type, event);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
// 3.
|
|
80
|
-
tracelog.
|
|
81
|
-
// Transform events before they're queued
|
|
82
|
-
return { ...event, custom_metadata: { app: 'v1' } };
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// 4. Configure custom headers FOURTH (if using custom backend with auth)
|
|
86
|
-
tracelog.setCustomHeaders(() => ({
|
|
87
|
-
'Authorization': `Bearer ${getAuthToken()}`
|
|
88
|
-
}));
|
|
79
|
+
// 3. Identify the user (optional — can be called before or after init)
|
|
80
|
+
tracelog.identify('cust_123', { name: 'Maria Garcia', plan: 'pro' });
|
|
89
81
|
|
|
90
|
-
//
|
|
91
|
-
await tracelog.init({
|
|
82
|
+
// 4. Initialize (starts tracking immediately)
|
|
83
|
+
const { sessionId } = await tracelog.init({
|
|
92
84
|
integrations: {
|
|
93
|
-
|
|
85
|
+
tracelog: { projectId: 'your-project-id' }
|
|
94
86
|
}
|
|
95
87
|
});
|
|
96
88
|
|
|
97
|
-
//
|
|
98
|
-
tracelog.
|
|
99
|
-
|
|
100
|
-
// 7. Track custom events (after init)
|
|
101
|
-
tracelog.event('button_clicked', {
|
|
102
|
-
buttonId: 'signup-cta',
|
|
103
|
-
source: 'homepage'
|
|
104
|
-
});
|
|
89
|
+
// 5. Track custom events at any point after init
|
|
90
|
+
tracelog.event('button_clicked', { buttonId: 'signup-cta', source: 'homepage' });
|
|
105
91
|
|
|
106
|
-
//
|
|
92
|
+
// 6. On logout: reset identity (clears identity, regenerates UUID, opens a new session)
|
|
107
93
|
await tracelog.resetIdentity();
|
|
108
94
|
|
|
109
|
-
//
|
|
95
|
+
// 7. Cleanup on consent revoke or app unmount
|
|
110
96
|
tracelog.destroy();
|
|
111
97
|
```
|
|
112
98
|
|
|
113
|
-
**
|
|
99
|
+
**Auto-captured events** (no code required):
|
|
114
100
|
|
|
115
|
-
|
|
116
|
-
- Page views & navigation (including SPA routes)
|
|
101
|
+
- Page views & navigation (including SPA route changes)
|
|
117
102
|
- Click interactions
|
|
118
103
|
- Scroll behavior
|
|
119
104
|
- User sessions
|
|
120
105
|
- Web Vitals (LCP, INP, CLS, FCP, TTFB)
|
|
121
|
-
- JavaScript errors
|
|
106
|
+
- JavaScript errors & unhandled promise rejections
|
|
122
107
|
|
|
123
108
|
---
|
|
124
109
|
|
|
@@ -126,53 +111,40 @@ tracelog.destroy();
|
|
|
126
111
|
|
|
127
112
|
| Method | Description |
|
|
128
113
|
|--------|-------------|
|
|
129
|
-
| `init(config?)` | Initialize tracking
|
|
130
|
-
| `event(name, metadata?, options?)` | Track custom
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `setCustomHeaders(provider)` | Add custom HTTP headers to requests (see [Custom Headers](#custom-headers)) |
|
|
140
|
-
| `removeCustomHeaders()` | Remove custom headers provider |
|
|
141
|
-
| `identify(userId, traits?)` | Associate visitor with a known user identity |
|
|
142
|
-
| `resetIdentity()` | Clear identity, regenerate UUID, start new session |
|
|
143
|
-
| `isInitialized()` | Check initialization status |
|
|
144
|
-
| `getSessionId()` | Get current session ID (or null) |
|
|
145
|
-
| `setQaMode(enabled)` | Enable/disable QA mode (console logging) |
|
|
146
|
-
| `destroy()` | Stop tracking and cleanup |
|
|
114
|
+
| `init(config?)` | Initialize tracking. Returns `Promise<{ sessionId }>`. See [Configuration](#configuration). |
|
|
115
|
+
| `event(name, metadata?, options?)` | Track a custom event. `options.critical: true` drains the queue via `sendBeacon` right after tracking, so the batch (the critical event + anything already queued) survives an imminent navigation. Subject to `sendBeacon`'s 64KB cap — oversized batches are persisted to `localStorage` and recovered on next `init()` via their idempotency token; the backend deduplicates by `event.id`. |
|
|
116
|
+
| `on(event, callback)` | Subscribe to events (`'event'` or `'queue'`). Local consumption — independent of backend sends. |
|
|
117
|
+
| `off(event, callback)` | Unsubscribe. Must pass the exact callback reference used in `on()`. |
|
|
118
|
+
| `identify(userId, traits?)` | Associate the current visitor with a known user identity. `traits` accepts a `Record<string, string>`; non-string values are dropped silently. |
|
|
119
|
+
| `resetIdentity()` | Flush pending events under the old identity, clear identity, regenerate the visitor UUID, and start a new session. Use for logout flows. |
|
|
120
|
+
| `isInitialized()` | `true` after a successful `init()`, `false` otherwise (including during teardown). |
|
|
121
|
+
| `getSessionId()` | Current session ID, or `null` if not initialized. |
|
|
122
|
+
| `getUserId()` | Current visitor UUID, or `null` if not initialized. |
|
|
123
|
+
| `destroy()` | Stop tracking, drain pending events via `sendBeacon`, and release all resources. |
|
|
147
124
|
|
|
148
125
|
**→ [Complete API Reference](./API_REFERENCE.md)**
|
|
149
126
|
|
|
150
127
|
---
|
|
151
128
|
|
|
152
|
-
## User Consent
|
|
129
|
+
## User Consent
|
|
153
130
|
|
|
154
|
-
TraceLog
|
|
131
|
+
TraceLog does not ship a consent manager. You are responsible for obtaining consent **before** calling `init()`.
|
|
155
132
|
|
|
156
133
|
```typescript
|
|
157
|
-
// Your responsibility: Obtain consent before initialization
|
|
158
134
|
const userConsent = await showCookieBanner(); // Your consent solution
|
|
159
135
|
|
|
160
136
|
if (userConsent.analytics) {
|
|
161
|
-
// Initialize only after consent
|
|
162
137
|
await tracelog.init({
|
|
163
|
-
integrations: {
|
|
164
|
-
tracelog: { projectId: 'your-project-id' }
|
|
165
|
-
}
|
|
138
|
+
integrations: { tracelog: { projectId: 'your-project-id' } }
|
|
166
139
|
});
|
|
167
140
|
} else {
|
|
168
|
-
// User rejected
|
|
169
|
-
console.log('Analytics consent denied');
|
|
141
|
+
// User rejected — don't initialize
|
|
170
142
|
}
|
|
171
143
|
|
|
172
|
-
// If
|
|
144
|
+
// If consent is revoked later
|
|
173
145
|
function handleConsentRevoke() {
|
|
174
|
-
tracelog.destroy();
|
|
175
|
-
localStorage.clear();
|
|
146
|
+
tracelog.destroy();
|
|
147
|
+
localStorage.clear();
|
|
176
148
|
}
|
|
177
149
|
```
|
|
178
150
|
|
|
@@ -180,140 +152,76 @@ function handleConsentRevoke() {
|
|
|
180
152
|
|
|
181
153
|
## Configuration
|
|
182
154
|
|
|
183
|
-
All configuration is
|
|
155
|
+
All configuration is optional. TraceLog works out-of-the-box with sensible defaults.
|
|
184
156
|
|
|
185
157
|
```typescript
|
|
186
158
|
await tracelog.init({
|
|
187
159
|
// Session
|
|
188
|
-
sessionTimeout: 900000,
|
|
160
|
+
sessionTimeout: 900000, // 15 min (default)
|
|
189
161
|
|
|
190
162
|
// Privacy
|
|
191
|
-
samplingRate: 1.0,
|
|
192
|
-
|
|
163
|
+
samplingRate: 1.0, // 100% (default)
|
|
164
|
+
errorSampling: 1.0, // 100% (default)
|
|
165
|
+
sensitiveQueryParams: ['token'], // Added to the 15-param default deny-list
|
|
193
166
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
167
|
+
// Throttles
|
|
168
|
+
pageViewThrottleMs: 1000, // Min interval between page_view events
|
|
169
|
+
clickThrottleMs: 300, // Min interval between click events per element
|
|
170
|
+
maxSameEventPerMinute: 60, // Per-name custom-event rate cap
|
|
197
171
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
172
|
+
// Batch flush
|
|
173
|
+
sendIntervalMs: 10000, // Default batch interval
|
|
174
|
+
flushOnSpaNavigation: false, // Opt-in: flush after pushState / replaceState / popstate / hashchange
|
|
175
|
+
flushOnPageHidden: true, // Flush when document.hidden becomes true (mobile Safari coverage)
|
|
202
176
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// Events sent to BOTH independently with separate error handling
|
|
207
|
-
},
|
|
177
|
+
// Web Vitals
|
|
178
|
+
webVitalsMode: 'needs-improvement', // 'all' | 'needs-improvement' | 'poor'
|
|
179
|
+
webVitalsThresholds: { LCP: 2500 }, // Optional per-metric overrides
|
|
208
180
|
|
|
209
|
-
//
|
|
210
|
-
|
|
181
|
+
// Global metadata appended to every event
|
|
182
|
+
globalMetadata: {
|
|
183
|
+
env: 'production',
|
|
184
|
+
version: '1.2.0',
|
|
185
|
+
appName: 'MyApp'
|
|
186
|
+
},
|
|
211
187
|
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
188
|
+
// Integration (omit for standalone mode)
|
|
189
|
+
integrations: {
|
|
190
|
+
tracelog: {
|
|
191
|
+
projectId: 'your-project-id',
|
|
192
|
+
shopify: false // Optional: enable Shopify cart attribute linking
|
|
193
|
+
}
|
|
217
194
|
}
|
|
218
195
|
});
|
|
219
196
|
```
|
|
220
197
|
|
|
221
|
-
**→ [Full Configuration
|
|
198
|
+
**→ [Full Configuration Reference](./API_REFERENCE.md#configuration)**
|
|
222
199
|
|
|
223
200
|
---
|
|
224
201
|
|
|
225
202
|
## Automatic Event Types
|
|
226
203
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
|
230
|
-
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
235
|
-
| `web_vitals` | Core Web Vitals (LCP, INP, CLS, FCP, TTFB) |
|
|
236
|
-
| `error` | JavaScript errors, promise rejections |
|
|
237
|
-
| `viewport_visible`| Element visibility (requires `viewport` config) |
|
|
238
|
-
|
|
239
|
-
**Filtering Events:**
|
|
240
|
-
|
|
241
|
-
You can filter specific events before they're sent to your backend using the `beforeSend` transformer. This gives you complete control over what data is transmitted.
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
// Filter out high-volume events (scroll, web_vitals)
|
|
245
|
-
tracelog.setTransformer('beforeSend', (event) => {
|
|
246
|
-
// Skip scroll and web vitals events
|
|
247
|
-
if (['scroll', 'web_vitals'].includes(event.type)) {
|
|
248
|
-
return null; // Returning null excludes the event from being sent
|
|
249
|
-
}
|
|
250
|
-
return event; // Send all other events normally
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
await tracelog.init({
|
|
254
|
-
integrations: {
|
|
255
|
-
custom: { collectApiUrl: 'https://api.example.com' }
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
**Advanced Filtering:**
|
|
261
|
-
|
|
262
|
-
```typescript
|
|
263
|
-
// Conditional filtering based on custom logic
|
|
264
|
-
tracelog.setTransformer('beforeSend', (event) => {
|
|
265
|
-
// Only send errors in production
|
|
266
|
-
if (event.type === 'error' && process.env.NODE_ENV !== 'production') {
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Only send 10% of scroll events (sampling)
|
|
271
|
-
if (event.type === 'scroll' && Math.random() > 0.1) {
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return event;
|
|
276
|
-
});
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
**Multi-Integration Behavior:**
|
|
280
|
-
|
|
281
|
-
```typescript
|
|
282
|
-
// Transformers ONLY apply to custom backends
|
|
283
|
-
// TraceLog SaaS always receives all events unmodified
|
|
284
|
-
tracelog.setTransformer('beforeSend', (event) => {
|
|
285
|
-
if (['scroll', 'web_vitals'].includes(event.type)) {
|
|
286
|
-
return null; // Filtered from custom backend only
|
|
287
|
-
}
|
|
288
|
-
return event;
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
await tracelog.init({
|
|
292
|
-
integrations: {
|
|
293
|
-
tracelog: { projectId: 'proj-123' }, // Gets ALL events (unfiltered)
|
|
294
|
-
custom: { collectApiUrl: 'https://warehouse.com' } // Gets filtered events
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
**Important:** Transformers (`beforeSend`, `beforeBatch`) only apply to **custom backend integrations**. TraceLog SaaS always receives all events unmodified to maintain schema integrity and ensure complete analytics. This behavior is the same as the removed `disabledEvents` configuration.
|
|
204
|
+
| Event Type | What It Tracks |
|
|
205
|
+
|-----------------|-------------------------------------------------|
|
|
206
|
+
| `page_view` | Initial load, SPA route changes, hash changes |
|
|
207
|
+
| `click` | User interactions with elements |
|
|
208
|
+
| `session_start` | New session creation (server infers session end)|
|
|
209
|
+
| `scroll` | Depth and direction per scrollable container |
|
|
210
|
+
| `web_vitals` | Core Web Vitals (LCP, INP, CLS, FCP, TTFB) |
|
|
211
|
+
| `error` | JavaScript errors, unhandled promise rejections |
|
|
300
212
|
|
|
301
|
-
**
|
|
302
|
-
- Reduce bandwidth and backend costs for custom backends
|
|
303
|
-
- Already using Sentry/Datadog for errors (filter out `error` events from custom backend)
|
|
304
|
-
- Data warehouse doesn't need scroll/vitals granularity
|
|
305
|
-
- Minimize custom backend data volume for privacy compliance
|
|
306
|
-
- Custom sampling logic per event type
|
|
213
|
+
**Custom events:**
|
|
307
214
|
|
|
308
|
-
**Note:** Filtered events are still captured locally. Use `tracelog.on('event')` to access all events client-side, even those excluded from backend transmission.
|
|
309
|
-
|
|
310
|
-
**Custom Events:**
|
|
311
215
|
```typescript
|
|
312
216
|
tracelog.event('purchase_completed', {
|
|
313
217
|
orderId: 'ord-123',
|
|
314
218
|
total: 99.99,
|
|
315
219
|
currency: 'USD'
|
|
316
220
|
});
|
|
221
|
+
|
|
222
|
+
// Right before a navigation — guarantee delivery via sendBeacon
|
|
223
|
+
tracelog.event('purchase_completed', { orderId: 'ord-123' }, { critical: true });
|
|
224
|
+
window.location.href = '/thanks';
|
|
317
225
|
```
|
|
318
226
|
|
|
319
227
|
**→ [Event Types Reference](./API_REFERENCE.md#event-types)**
|
|
@@ -322,441 +230,37 @@ tracelog.event('purchase_completed', {
|
|
|
322
230
|
|
|
323
231
|
## Global Metadata
|
|
324
232
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
### Setting Initial Metadata
|
|
328
|
-
|
|
329
|
-
Configure global metadata during initialization:
|
|
233
|
+
Set at `init()` time and attached to every event the library sends. Replace it by destroying and re-initializing TraceLog with a new config (typical at login / logout).
|
|
330
234
|
|
|
331
235
|
```typescript
|
|
332
236
|
await tracelog.init({
|
|
333
237
|
globalMetadata: {
|
|
334
238
|
env: 'production',
|
|
335
239
|
version: '1.2.0',
|
|
336
|
-
|
|
240
|
+
plan: user?.plan ?? 'anonymous'
|
|
337
241
|
}
|
|
338
242
|
});
|
|
339
243
|
```
|
|
340
244
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
**Replace all metadata** (previous keys removed):
|
|
344
|
-
|
|
345
|
-
```typescript
|
|
346
|
-
// User login: Replace with user context
|
|
347
|
-
tracelog.updateGlobalMetadata({
|
|
348
|
-
userId: 'user-456',
|
|
349
|
-
plan: 'premium',
|
|
350
|
-
cohort: 'beta-testers'
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
// User logout: Clear all metadata
|
|
354
|
-
tracelog.updateGlobalMetadata({});
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
**Merge with existing metadata** (preserves other keys):
|
|
358
|
-
|
|
359
|
-
```typescript
|
|
360
|
-
// Add user ID while preserving env and version
|
|
361
|
-
tracelog.mergeGlobalMetadata({ userId: 'user-123' });
|
|
362
|
-
|
|
363
|
-
// Update version while preserving others
|
|
364
|
-
tracelog.mergeGlobalMetadata({ version: '1.3.0' });
|
|
365
|
-
|
|
366
|
-
// Add feature flags
|
|
367
|
-
tracelog.mergeGlobalMetadata({
|
|
368
|
-
feature_new_ui: true,
|
|
369
|
-
feature_dark_mode: false
|
|
370
|
-
});
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
### Use Cases
|
|
374
|
-
|
|
375
|
-
**User Authentication:**
|
|
376
|
-
```typescript
|
|
377
|
-
// Login
|
|
378
|
-
tracelog.mergeGlobalMetadata({
|
|
379
|
-
userId: user.id,
|
|
380
|
-
email: user.email,
|
|
381
|
-
plan: user.subscription.plan
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// Logout
|
|
385
|
-
tracelog.updateGlobalMetadata({});
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
**A/B Testing:**
|
|
389
|
-
```typescript
|
|
390
|
-
tracelog.mergeGlobalMetadata({
|
|
391
|
-
experiment_checkout: 'variant-b',
|
|
392
|
-
experiment_pricing: 'control'
|
|
393
|
-
});
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
**Environment Context:**
|
|
397
|
-
```typescript
|
|
398
|
-
tracelog.mergeGlobalMetadata({
|
|
399
|
-
build: process.env.BUILD_NUMBER,
|
|
400
|
-
region: user.location.region,
|
|
401
|
-
language: navigator.language
|
|
402
|
-
});
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
### Validation Rules
|
|
245
|
+
**Validation rules:**
|
|
406
246
|
|
|
407
|
-
- **Allowed
|
|
408
|
-
- **
|
|
409
|
-
- **Limits**:
|
|
247
|
+
- **Allowed types**: primitives (string, number, boolean), string arrays, nested objects (up to 10 levels)
|
|
248
|
+
- **Disallowed**: functions, symbols, `undefined`, circular references
|
|
249
|
+
- **Limits**: max 100 keys, 48 KB serialized size, 500 items per array, 1000 chars per string
|
|
410
250
|
|
|
411
|
-
**→ [Metadata
|
|
412
|
-
|
|
413
|
-
---
|
|
414
|
-
|
|
415
|
-
## Transformers
|
|
416
|
-
|
|
417
|
-
Transform events dynamically at runtime before they're sent to integrations. Useful for adding custom logic, enrichment, or filtering.
|
|
418
|
-
|
|
419
|
-
**Important**: Transformers are **integration-specific** to protect TraceLog SaaS schema integrity:
|
|
420
|
-
|
|
421
|
-
| Integration | `beforeSend` | `beforeBatch` | Notes |
|
|
422
|
-
|-------------|--------------|---------------|-------|
|
|
423
|
-
| **Standalone (no backend)** | ✅ Applied | ⚠️ Not supported | Only local event emission; `beforeBatch` requires backend |
|
|
424
|
-
| **TraceLog SaaS (only)** | ❌ Silently ignored | ❌ Silently ignored | Schema protection |
|
|
425
|
-
| **Custom Backend (only)** | ✅ Applied | ✅ Applied | Full control |
|
|
426
|
-
| **Multi-Integration** | ⚠️ Custom only | ⚠️ Custom only | SaaS gets original events, custom gets transformed |
|
|
427
|
-
|
|
428
|
-
**Multi-Integration Behavior:**
|
|
429
|
-
- When using both TraceLog SaaS + Custom backend simultaneously
|
|
430
|
-
- SaaS receives **original events** (transformers not applied)
|
|
431
|
-
- Custom backend receives **transformed events**
|
|
432
|
-
- Independent error handling and retry per integration
|
|
433
|
-
|
|
434
|
-
**Event Listeners and Transformers:**
|
|
435
|
-
|
|
436
|
-
Event listeners (`tracelog.on('event', ...)`) receive **original events**, not transformed events. Transformers only affect data sent to backends.
|
|
437
|
-
|
|
438
|
-
```typescript
|
|
439
|
-
tracelog.setTransformer('beforeSend', (data) => {
|
|
440
|
-
if ('type' in data) {
|
|
441
|
-
return { ...data, enrichedField: 'value' };
|
|
442
|
-
}
|
|
443
|
-
return data;
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
tracelog.on('event', (ev) => {
|
|
447
|
-
console.log(ev.enrichedField); // undefined - listeners receive original events
|
|
448
|
-
});
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
**Workaround for GTM/Third-Party Relay:**
|
|
452
|
-
|
|
453
|
-
If you need to forward enriched events to GTM or other systems, apply the transformation in your listener:
|
|
454
|
-
|
|
455
|
-
```typescript
|
|
456
|
-
// Define enrichment function once
|
|
457
|
-
const enrichEvent = (event) => ({
|
|
458
|
-
...event,
|
|
459
|
-
appVersion: '1.0.0',
|
|
460
|
-
environment: 'production'
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
// Use in transformer (for backend)
|
|
464
|
-
tracelog.setTransformer('beforeSend', (data) => {
|
|
465
|
-
if ('type' in data) {
|
|
466
|
-
return enrichEvent(data);
|
|
467
|
-
}
|
|
468
|
-
return data;
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
// Use in listener (for GTM relay)
|
|
472
|
-
tracelog.on('event', (event) => {
|
|
473
|
-
const enrichedEvent = enrichEvent(event);
|
|
474
|
-
window.dataLayer?.push({ event: 'tracelog_event', ...enrichedEvent });
|
|
475
|
-
});
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
### Available Hooks
|
|
479
|
-
|
|
480
|
-
#### `beforeSend` - Per-Event Transformation
|
|
481
|
-
|
|
482
|
-
Transform individual events **before** deduplication, sampling, and queueing.
|
|
483
|
-
|
|
484
|
-
**Timing (depends on integration mode):**
|
|
485
|
-
- **Standalone mode (no backend)**: Runs in `EventManager.buildEventPayload()` before dedup/sampling/queueing
|
|
486
|
-
- **Custom-only mode**: Runs in `EventManager.buildEventPayload()` before dedup/sampling/queueing
|
|
487
|
-
- **Multi-integration mode (SaaS + Custom)**: Runs in `SenderManager` per-integration (SaaS skipped, Custom applied)
|
|
488
|
-
- **TraceLog SaaS-only mode**: Silently ignored (not applied)
|
|
489
|
-
|
|
490
|
-
```typescript
|
|
491
|
-
import { tracelog } from '@tracelog/lib';
|
|
492
|
-
import type { EventData, EventsQueue } from '@tracelog/lib';
|
|
493
|
-
|
|
494
|
-
// Add custom metadata to all events
|
|
495
|
-
tracelog.setTransformer('beforeSend', (data: EventData | EventsQueue) => {
|
|
496
|
-
if ('type' in data) {
|
|
497
|
-
return {
|
|
498
|
-
...data,
|
|
499
|
-
custom_event: {
|
|
500
|
-
...data.custom_event,
|
|
501
|
-
metadata: {
|
|
502
|
-
...data.custom_event?.metadata,
|
|
503
|
-
environment: 'production',
|
|
504
|
-
version: '1.0.0'
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
return data;
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
// Filter out sensitive events
|
|
513
|
-
tracelog.setTransformer('beforeSend', (data) => {
|
|
514
|
-
if ('type' in data && data.custom_event?.name === 'internal_event') {
|
|
515
|
-
return null; // Event will be dropped
|
|
516
|
-
}
|
|
517
|
-
return data;
|
|
518
|
-
});
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
#### `beforeBatch` - Batch Transformation
|
|
522
|
-
|
|
523
|
-
Transform the entire batch before sending to backend. Runs once per batch (every 10s or 50 events).
|
|
524
|
-
|
|
525
|
-
```typescript
|
|
526
|
-
// Add batch-level metadata
|
|
527
|
-
tracelog.setTransformer('beforeBatch', (data) => {
|
|
528
|
-
if ('events' in data) {
|
|
529
|
-
return {
|
|
530
|
-
...data,
|
|
531
|
-
global_metadata: {
|
|
532
|
-
...data.global_metadata,
|
|
533
|
-
batchSize: data.events.length,
|
|
534
|
-
batchTimestamp: Date.now()
|
|
535
|
-
}
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
return data;
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// Filter batch based on conditions
|
|
542
|
-
tracelog.setTransformer('beforeBatch', (data) => {
|
|
543
|
-
if ('events' in data && data.events.length < 5) {
|
|
544
|
-
return null; // Don't send small batches
|
|
545
|
-
}
|
|
546
|
-
return data;
|
|
547
|
-
});
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
### Removing Transformers
|
|
551
|
-
|
|
552
|
-
```typescript
|
|
553
|
-
// Remove specific transformer
|
|
554
|
-
tracelog.removeTransformer('beforeSend');
|
|
555
|
-
tracelog.removeTransformer('beforeBatch');
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
### Error Handling & Validation
|
|
559
|
-
|
|
560
|
-
Transformers are designed to be resilient and flexible:
|
|
561
|
-
|
|
562
|
-
**Input Validation:**
|
|
563
|
-
- **Function type check**: `setTransformer()` throws error if `fn` is not a function
|
|
564
|
-
- **Example**: `tracelog.setTransformer('beforeSend', null)` → Throws `Error: [TraceLog] Transformer must be a function, received: object`
|
|
565
|
-
|
|
566
|
-
**Error Handling:**
|
|
567
|
-
- **Exceptions**: Caught and logged, original event/batch used
|
|
568
|
-
- **Invalid return**: Logged warning, original event/batch used
|
|
569
|
-
- **`null` return**: Event/batch filtered out (intended behavior)
|
|
570
|
-
|
|
571
|
-
**Validation:**
|
|
572
|
-
- **Minimal checks only**: `beforeSend` requires `'type'` field, `beforeBatch` requires `'events'` array
|
|
573
|
-
- **Custom schemas supported**: All other fields optional for maximum flexibility with custom backends
|
|
574
|
-
- **Use case**: Transform data to match your backend's schema (e.g., data warehouses, custom APIs)
|
|
575
|
-
|
|
576
|
-
```typescript
|
|
577
|
-
// Safe transformer - errors won't break tracking
|
|
578
|
-
tracelog.setTransformer('beforeSend', (data) => {
|
|
579
|
-
try {
|
|
580
|
-
// Complex transformation logic
|
|
581
|
-
return transformData(data);
|
|
582
|
-
} catch (error) {
|
|
583
|
-
console.error('Transformer error:', error);
|
|
584
|
-
return data; // Fallback to original
|
|
585
|
-
}
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
// Custom schema example - completely reshape for your backend
|
|
589
|
-
tracelog.setTransformer('beforeSend', (data) => {
|
|
590
|
-
if ('type' in data) {
|
|
591
|
-
return {
|
|
592
|
-
type: 'analytics_event',
|
|
593
|
-
eventName: data.custom_event?.name,
|
|
594
|
-
timestamp: Date.now(),
|
|
595
|
-
// Your custom fields - TraceLog won't reject this!
|
|
596
|
-
customField1: 'value',
|
|
597
|
-
customField2: 123
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
return data;
|
|
601
|
-
});
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
### Use Cases
|
|
605
|
-
|
|
606
|
-
**Data Enrichment:**
|
|
607
|
-
```typescript
|
|
608
|
-
tracelog.setTransformer('beforeSend', (data) => {
|
|
609
|
-
if ('type' in data) {
|
|
610
|
-
return {
|
|
611
|
-
...data,
|
|
612
|
-
custom_event: {
|
|
613
|
-
...data.custom_event,
|
|
614
|
-
metadata: {
|
|
615
|
-
...data.custom_event?.metadata,
|
|
616
|
-
userId: getCurrentUserId(),
|
|
617
|
-
sessionContext: getSessionContext()
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
return data;
|
|
623
|
-
});
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
**Event Filtering:**
|
|
627
|
-
```typescript
|
|
628
|
-
// Filter out bot traffic
|
|
629
|
-
tracelog.setTransformer('beforeBatch', (data) => {
|
|
630
|
-
if ('events' in data) {
|
|
631
|
-
const filteredEvents = data.events.filter(
|
|
632
|
-
event => !isBotUserAgent(navigator.userAgent)
|
|
633
|
-
);
|
|
634
|
-
return { ...data, events: filteredEvents };
|
|
635
|
-
}
|
|
636
|
-
return data;
|
|
637
|
-
});
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
**PII Sanitization (Custom Backend):**
|
|
641
|
-
```typescript
|
|
642
|
-
// Additional sanitization for custom backend
|
|
643
|
-
tracelog.setTransformer('beforeSend', (data) => {
|
|
644
|
-
if ('type' in data && data.custom_event?.metadata) {
|
|
645
|
-
const sanitized = { ...data.custom_event.metadata };
|
|
646
|
-
delete sanitized.email;
|
|
647
|
-
delete sanitized.phone;
|
|
648
|
-
return {
|
|
649
|
-
...data,
|
|
650
|
-
custom_event: {
|
|
651
|
-
...data.custom_event,
|
|
652
|
-
metadata: sanitized
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
return data;
|
|
657
|
-
});
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
---
|
|
661
|
-
|
|
662
|
-
## Custom Headers
|
|
663
|
-
|
|
664
|
-
Add custom HTTP headers to requests sent to custom backends. Useful for authentication, tenant identification, or API versioning.
|
|
665
|
-
|
|
666
|
-
**Important**: Custom headers **only apply to custom backend integrations**. TraceLog SaaS always receives requests without custom headers.
|
|
667
|
-
|
|
668
|
-
### Static Headers (Config)
|
|
669
|
-
|
|
670
|
-
Set fixed headers in configuration:
|
|
671
|
-
|
|
672
|
-
```typescript
|
|
673
|
-
await tracelog.init({
|
|
674
|
-
integrations: {
|
|
675
|
-
custom: {
|
|
676
|
-
collectApiUrl: 'https://api.example.com/collect',
|
|
677
|
-
headers: {
|
|
678
|
-
'X-Tenant-Id': 'tenant-123',
|
|
679
|
-
'X-Brand': 'my-brand',
|
|
680
|
-
'X-API-Version': '2.0'
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
});
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
### Dynamic Headers (Provider)
|
|
688
|
-
|
|
689
|
-
Set headers dynamically at runtime (e.g., auth tokens that expire):
|
|
690
|
-
|
|
691
|
-
```typescript
|
|
692
|
-
// Set before or after init
|
|
693
|
-
tracelog.setCustomHeaders(() => ({
|
|
694
|
-
'Authorization': `Bearer ${getAuthToken()}`,
|
|
695
|
-
'X-Request-ID': crypto.randomUUID()
|
|
696
|
-
}));
|
|
697
|
-
|
|
698
|
-
await tracelog.init({
|
|
699
|
-
integrations: {
|
|
700
|
-
custom: { collectApiUrl: 'https://api.example.com/collect' }
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
### Static + Dynamic Headers
|
|
706
|
-
|
|
707
|
-
Combine both approaches. Dynamic headers override static on key collision:
|
|
708
|
-
|
|
709
|
-
```typescript
|
|
710
|
-
await tracelog.init({
|
|
711
|
-
integrations: {
|
|
712
|
-
custom: {
|
|
713
|
-
collectApiUrl: 'https://api.example.com/collect',
|
|
714
|
-
headers: {
|
|
715
|
-
'X-Brand': 'static-brand', // Static
|
|
716
|
-
'X-Tenant-Id': 'tenant-123' // Static
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
// Dynamic provider overrides 'X-Brand'
|
|
723
|
-
tracelog.setCustomHeaders(() => ({
|
|
724
|
-
'X-Brand': 'dynamic-brand', // Overrides static
|
|
725
|
-
'Authorization': 'Bearer token' // New header
|
|
726
|
-
}));
|
|
727
|
-
|
|
728
|
-
// Result: { 'X-Tenant-Id': 'tenant-123', 'X-Brand': 'dynamic-brand', 'Authorization': 'Bearer token' }
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
### Removing Headers
|
|
732
|
-
|
|
733
|
-
```typescript
|
|
734
|
-
// Remove dynamic provider (static headers from config remain)
|
|
735
|
-
tracelog.removeCustomHeaders();
|
|
736
|
-
```
|
|
737
|
-
|
|
738
|
-
### sendBeacon Limitation
|
|
739
|
-
|
|
740
|
-
⚠️ Custom headers are **NOT applied** to `sendBeacon()` requests (page unload). The browser API doesn't support custom headers. For scenarios requiring headers on all requests:
|
|
741
|
-
- Ensure async sends complete before page unload
|
|
742
|
-
- Use short-lived tokens that don't require refresh per request
|
|
743
|
-
|
|
744
|
-
**→ [Custom Headers API Reference](./API_REFERENCE.md#setcustomheadersprovider-customheadersprovider-void)**
|
|
251
|
+
**→ [Metadata Reference](./API_REFERENCE.md#globalmetadata)**
|
|
745
252
|
|
|
746
253
|
---
|
|
747
254
|
|
|
748
255
|
## Integration Modes
|
|
749
256
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
### 1. Standalone (No Backend)
|
|
257
|
+
### 1. Standalone (no backend)
|
|
753
258
|
|
|
754
|
-
|
|
259
|
+
Default when no `integrations` are configured. Events are captured, queued, and emitted locally — no network requests.
|
|
755
260
|
|
|
756
261
|
```typescript
|
|
757
262
|
await tracelog.init();
|
|
758
263
|
|
|
759
|
-
// Consume events locally
|
|
760
264
|
tracelog.on('event', (event) => {
|
|
761
265
|
myAnalytics.track(event);
|
|
762
266
|
});
|
|
@@ -766,143 +270,76 @@ tracelog.on('queue', (batch) => {
|
|
|
766
270
|
});
|
|
767
271
|
```
|
|
768
272
|
|
|
769
|
-
|
|
770
|
-
- ✅ Events captured and queued normally
|
|
771
|
-
- ✅ `beforeSend` transformer applied (per-event transformation)
|
|
772
|
-
- ⚠️ `beforeBatch` transformer **NOT supported** (no SenderManager created)
|
|
773
|
-
- ✅ Events emitted to local listeners every 10 seconds or 50 events
|
|
774
|
-
- ❌ **NO network requests made** (no backends configured)
|
|
775
|
-
- ✅ Perfect for custom analytics pipelines, testing, or privacy-focused implementations
|
|
273
|
+
Perfect for custom analytics pipelines, testing, or privacy-focused implementations where you want to ship events to your own destination via a listener.
|
|
776
274
|
|
|
777
275
|
### 2. TraceLog SaaS
|
|
778
|
-
```typescript
|
|
779
|
-
await tracelog.init({
|
|
780
|
-
integrations: {
|
|
781
|
-
tracelog: { projectId: 'your-project-id' }
|
|
782
|
-
}
|
|
783
|
-
});
|
|
784
|
-
```
|
|
785
276
|
|
|
786
|
-
### 3. Custom Backend
|
|
787
277
|
```typescript
|
|
788
278
|
await tracelog.init({
|
|
789
279
|
integrations: {
|
|
790
|
-
|
|
791
|
-
collectApiUrl: 'https://api.example.com/collect',
|
|
792
|
-
allowHttp: false, // Only true for local testing
|
|
793
|
-
fetchCredentials: 'include' // Cookie policy: 'include' | 'same-origin' | 'omit'
|
|
794
|
-
}
|
|
280
|
+
tracelog: { projectId: 'your-project-id' }
|
|
795
281
|
}
|
|
796
282
|
});
|
|
797
283
|
```
|
|
798
284
|
|
|
799
|
-
|
|
800
|
-
```typescript
|
|
801
|
-
await tracelog.init({
|
|
802
|
-
integrations: {
|
|
803
|
-
tracelog: { projectId: 'your-project-id' }, // Analytics dashboard
|
|
804
|
-
custom: { collectApiUrl: 'https://warehouse.com' } // Data warehouse
|
|
805
|
-
}
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
// Events sent to BOTH endpoints independently
|
|
809
|
-
// - Independent error handling per integration
|
|
810
|
-
// - Independent retry/persistence per integration
|
|
811
|
-
// - Parallel sending (non-blocking)
|
|
812
|
-
```
|
|
285
|
+
**Domain requirement.** The SaaS endpoint is derived from the host page's domain (`https://{projectId}.{rootDomain}/collect`), so `init()` rejects when called from `localhost` or a raw IP address. For local development, omit `integrations.tracelog` to run in standalone mode, or test against a staging domain mapped via `/etc/hosts`.
|
|
813
286
|
|
|
814
|
-
**→ [Integration
|
|
287
|
+
**→ [Integration Reference](./API_REFERENCE.md#integration-configuration)**
|
|
815
288
|
|
|
816
289
|
---
|
|
817
290
|
|
|
818
291
|
## Error Handling & Reliability
|
|
819
292
|
|
|
820
|
-
|
|
293
|
+
### Automatic retry strategy
|
|
821
294
|
|
|
822
|
-
|
|
295
|
+
**Transient errors** (5xx, timeouts, network failures):
|
|
823
296
|
|
|
824
|
-
|
|
825
|
-
-
|
|
826
|
-
-
|
|
827
|
-
- **Independent retries** per integration (SaaS and Custom retry separately)
|
|
828
|
-
- **Persistence after exhaustion**: Events saved to localStorage for next-page recovery
|
|
297
|
+
- Up to 2 retry attempts (3 total)
|
|
298
|
+
- Exponential backoff with jitter (200–300 ms, 400–500 ms)
|
|
299
|
+
- Persisted to `localStorage` after exhaustion for next-page recovery
|
|
829
300
|
|
|
830
|
-
**Rate
|
|
831
|
-
- **No in-session retries** — arms a 60-second cooldown instead
|
|
832
|
-
- **Cooldown is mirrored to localStorage** and shared across tabs/windows on the same origin (prevents every fresh page load from hammering the server's 429 window)
|
|
833
|
-
- **Events persisted immediately** to localStorage; retried once the cooldown elapses
|
|
834
|
-
- TraceLog SaaS deduplicates retries server-side; custom backends should implement idempotency
|
|
301
|
+
**Rate limit (429):**
|
|
835
302
|
|
|
836
|
-
|
|
837
|
-
-
|
|
838
|
-
-
|
|
839
|
-
-
|
|
303
|
+
- No in-session retries — arms a 60-second cooldown instead
|
|
304
|
+
- Cooldown is mirrored to `localStorage` and shared across tabs/windows on the same origin (prevents every fresh page load from hammering the server during its 429 window)
|
|
305
|
+
- Events are persisted immediately and retried once the cooldown elapses
|
|
306
|
+
- The backend deduplicates retries via the batch idempotency token
|
|
840
307
|
|
|
841
|
-
|
|
842
|
-
// Multi-backend example with automatic retries
|
|
843
|
-
await tracelog.init({
|
|
844
|
-
integrations: {
|
|
845
|
-
tracelog: { projectId: 'project-id' },
|
|
846
|
-
custom: { collectApiUrl: 'https://api.example.com/collect' }
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
// If tracelog SaaS returns 500:
|
|
851
|
-
// - Retries 2 times with backoff (200-300ms, 400-500ms)
|
|
852
|
-
// - If all fail → persists to localStorage for next page
|
|
308
|
+
**Permanent errors** (4xx except 408, 429):
|
|
853
309
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
// - Failed integration recovered on next page load
|
|
857
|
-
```
|
|
310
|
+
- No retries — events are discarded
|
|
311
|
+
- 408 Request Timeout is treated as transient
|
|
858
312
|
|
|
859
|
-
### Error
|
|
313
|
+
### Error classification
|
|
860
314
|
|
|
861
|
-
| Status
|
|
862
|
-
|
|
863
|
-
| **2xx**
|
|
864
|
-
| **4xx** (except 408
|
|
865
|
-
| **408**
|
|
866
|
-
| **429**
|
|
867
|
-
| **5xx**
|
|
868
|
-
| **Network
|
|
869
|
-
| **Timeout**
|
|
315
|
+
| Status | Type | Retries | Persistence |
|
|
316
|
+
|-----------------------|-------------|------------------|-------------------|
|
|
317
|
+
| **2xx** | Success | None | Cleared |
|
|
318
|
+
| **4xx** (except 408/429) | Permanent | ❌ None | ❌ Discarded |
|
|
319
|
+
| **408** | Transient | ✅ Up to 2 | ✅ After exhaustion |
|
|
320
|
+
| **429** | Rate Limit | ❌ None (60s cooldown, shared across tabs) | ✅ Immediate |
|
|
321
|
+
| **5xx** | Transient | ✅ Up to 2 | ✅ After exhaustion |
|
|
322
|
+
| **Network error** | Transient | ✅ Up to 2 | ✅ After exhaustion |
|
|
323
|
+
| **Timeout** | Transient | ✅ Up to 2 | ✅ After exhaustion |
|
|
870
324
|
|
|
871
|
-
###
|
|
325
|
+
### Recovery on page load
|
|
872
326
|
|
|
873
|
-
|
|
874
|
-
- Events removed from queue if **AT LEAST ONE** integration succeeds
|
|
875
|
-
- Failed integrations persist independently for next-page recovery
|
|
876
|
-
- Successful integration doesn't retry (performance optimization)
|
|
327
|
+
Failed events are recovered automatically on the next `init()`:
|
|
877
328
|
|
|
878
|
-
**Example Scenario:**
|
|
879
329
|
```typescript
|
|
880
|
-
//
|
|
881
|
-
//
|
|
882
|
-
// Events removed from queue (SaaS succeeded)
|
|
883
|
-
// Next page load → only Custom integration recovers persisted events
|
|
330
|
+
// Page 1: events fail to send (5xx after retries) → persisted with idempotency token
|
|
331
|
+
// Page 2: init() recovers and resends; backend deduplicates by idempotency token
|
|
884
332
|
```
|
|
885
333
|
|
|
886
|
-
|
|
334
|
+
Multi-tab protection: a 1-second window prevents two tabs from re-sending the same persisted batch simultaneously.
|
|
887
335
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
```typescript
|
|
891
|
-
// Page 1: Events fail to send (5xx error after retries)
|
|
892
|
-
// → Persisted to localStorage per-integration
|
|
893
|
-
|
|
894
|
-
// Page 2: User navigates to new page
|
|
895
|
-
await tracelog.init({ /* same config */ });
|
|
896
|
-
// ✅ Automatically recovers and resends persisted events
|
|
897
|
-
// ✅ Independent recovery per integration
|
|
898
|
-
// ✅ Multi-tab protection (1s window prevents duplicates)
|
|
899
|
-
```
|
|
336
|
+
**Circuit breaker.** After `MAX_CONSECUTIVE_NETWORK_FAILURES` consecutive network-level failures (DNS, connection refused), the sender opens its circuit and skips further requests until `CIRCUIT_BREAKER_COOLDOWN_MS` elapses. A single probe request is then allowed (half-open state) before fully closing.
|
|
900
337
|
|
|
901
338
|
**→ [Full Error Handling Reference](./API_REFERENCE.md#error-handling)**
|
|
902
339
|
|
|
903
|
-
### Session
|
|
340
|
+
### Session continuity across external redirects
|
|
904
341
|
|
|
905
|
-
TraceLog
|
|
342
|
+
TraceLog preserves sessions across external redirects (payment processors, OAuth flows, etc.) with zero developer action. Session data is mirrored to `sessionStorage` alongside `localStorage`, so when a user returns from an external site and `localStorage` is empty, the session is recovered from `sessionStorage` transparently.
|
|
906
343
|
|
|
907
344
|
```typescript
|
|
908
345
|
// No special handling needed before redirect
|
|
@@ -914,7 +351,7 @@ tracelog.event('purchase', { orderId: '12345', amount: 99.99 });
|
|
|
914
351
|
// Same session as before the redirect
|
|
915
352
|
```
|
|
916
353
|
|
|
917
|
-
- Automatic
|
|
354
|
+
- Automatic — no API calls or developer action required
|
|
918
355
|
- `sessionStorage` mirror survives same-tab navigation (cleared on tab close)
|
|
919
356
|
- Session timeout still applies (expired sessions are not recovered)
|
|
920
357
|
|
|
@@ -922,26 +359,26 @@ tracelog.event('purchase', { orderId: '12345', amount: 99.99 });
|
|
|
922
359
|
|
|
923
360
|
## Privacy & Security
|
|
924
361
|
|
|
925
|
-
TraceLog is
|
|
362
|
+
TraceLog is privacy-first by design:
|
|
926
363
|
|
|
927
|
-
- ✅ **PII
|
|
928
|
-
- ✅ **Input
|
|
929
|
-
- ✅ **URL
|
|
930
|
-
- ✅ **Element
|
|
931
|
-
- ✅ **Client-
|
|
364
|
+
- ✅ **PII sanitization** — auto-redacts emails, phones, credit cards, IBANs, API keys, bearer tokens, and connection-string passwords from click text and error messages
|
|
365
|
+
- ✅ **Input protection** — never captures values from `<input>`, `<textarea>`, `<select>`
|
|
366
|
+
- ✅ **URL filtering** — removes 15 default sensitive query params (token, password, auth, secret, api_key, …) plus any you add via `sensitiveQueryParams`
|
|
367
|
+
- ✅ **Element exclusion** — use `data-tlog-ignore` on any container to exclude its contents from click tracking
|
|
368
|
+
- ✅ **Client-side controls** — sampling, dedup, and validation all happen in the browser
|
|
932
369
|
|
|
933
|
-
**Example:**
|
|
934
370
|
```html
|
|
935
|
-
<!-- Exclude sensitive forms -->
|
|
371
|
+
<!-- Exclude sensitive forms entirely -->
|
|
936
372
|
<div data-tlog-ignore>
|
|
937
373
|
<input type="password" name="password">
|
|
938
374
|
<input type="text" name="credit_card">
|
|
939
375
|
</div>
|
|
940
376
|
```
|
|
941
377
|
|
|
942
|
-
**Your
|
|
943
|
-
|
|
944
|
-
-
|
|
378
|
+
**Your responsibilities:**
|
|
379
|
+
|
|
380
|
+
- Obtain user consent before calling `init()` (GDPR / CCPA / LOPD)
|
|
381
|
+
- Avoid PII in custom event metadata (TraceLog only sanitizes element text and error messages)
|
|
945
382
|
- Call `destroy()` on consent revoke
|
|
946
383
|
|
|
947
384
|
**→ [Complete Security Guide](./SECURITY.md)**
|
|
@@ -950,30 +387,20 @@ TraceLog is **privacy-first** by design:
|
|
|
950
387
|
|
|
951
388
|
## QA Mode
|
|
952
389
|
|
|
953
|
-
|
|
390
|
+
QA mode logs custom events to the browser console so you can verify tracking implementation without inspecting the network tab.
|
|
954
391
|
|
|
955
|
-
### URL
|
|
956
|
-
```bash
|
|
957
|
-
# Enable
|
|
958
|
-
?tlog_mode=qa
|
|
392
|
+
### URL activation
|
|
959
393
|
|
|
960
|
-
|
|
961
|
-
?tlog_mode=
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
### Programmatic API
|
|
965
|
-
```typescript
|
|
966
|
-
tracelog.setQaMode(true); // Enable
|
|
967
|
-
tracelog.setQaMode(false); // Disable
|
|
394
|
+
```text
|
|
395
|
+
?tlog_mode=qa # Enable (persists in sessionStorage for the tab)
|
|
396
|
+
?tlog_mode=qa_off # Disable
|
|
968
397
|
```
|
|
969
398
|
|
|
970
|
-
**
|
|
971
|
-
- Custom events logged to browser console
|
|
972
|
-
- Strict validation (throws errors instead of warnings)
|
|
973
|
-
- Session state visible in console
|
|
974
|
-
- Persistent across page reloads (sessionStorage)
|
|
399
|
+
**Effects in QA mode:**
|
|
975
400
|
|
|
976
|
-
|
|
401
|
+
- Custom events logged to console with their name and metadata
|
|
402
|
+
- Strict validation: invalid custom-event payloads throw instead of being silently dropped
|
|
403
|
+
- Persists across navigations within the same tab (cleared on tab close)
|
|
977
404
|
|
|
978
405
|
---
|
|
979
406
|
|
|
@@ -984,7 +411,7 @@ tracelog.setQaMode(false); // Disable
|
|
|
984
411
|
- Safari 12+
|
|
985
412
|
- Edge 79+
|
|
986
413
|
|
|
987
|
-
**SSR/SSG
|
|
414
|
+
**SSR/SSG compatible** — safe to import in Angular Universal, Next.js, Nuxt, SvelteKit. All methods no-op in Node.js.
|
|
988
415
|
|
|
989
416
|
---
|
|
990
417
|
|
|
@@ -992,9 +419,9 @@ tracelog.setQaMode(false); // Disable
|
|
|
992
419
|
|
|
993
420
|
```bash
|
|
994
421
|
npm install # Install dependencies
|
|
995
|
-
npm run build:all # Build ESM + CJS +
|
|
422
|
+
npm run build:all # Build ESM + CJS + browser bundles
|
|
996
423
|
npm run check # Lint + format validation
|
|
997
|
-
npm
|
|
424
|
+
npm test # Run all tests
|
|
998
425
|
npm run test:coverage # Generate coverage report
|
|
999
426
|
```
|
|
1000
427
|
|
|
@@ -1006,10 +433,10 @@ npm run test:coverage # Generate coverage report
|
|
|
1006
433
|
|
|
1007
434
|
| Document | Description |
|
|
1008
435
|
|----------|-------------|
|
|
1009
|
-
| **[API Reference](./API_REFERENCE.md)** | Complete API documentation
|
|
1010
|
-
| **[Best Practices](./BEST_PRACTICES.md)** | Patterns, anti-patterns,
|
|
1011
|
-
| **[Security Guide](./SECURITY.md)** | Privacy, GDPR compliance,
|
|
1012
|
-
| **[Changelog](./CHANGELOG.md)** | Release history
|
|
436
|
+
| **[API Reference](./API_REFERENCE.md)** | Complete API documentation: methods, config options, event types |
|
|
437
|
+
| **[Best Practices](./BEST_PRACTICES.md)** | Patterns, anti-patterns, optimization tips |
|
|
438
|
+
| **[Security Guide](./SECURITY.md)** | Privacy, GDPR compliance, security checklist |
|
|
439
|
+
| **[Changelog](./CHANGELOG.md)** | Release history |
|
|
1013
440
|
| **[Handlers](./src/handlers/README.md)** | Event capture implementation details |
|
|
1014
441
|
| **[Managers](./src/managers/README.md)** | Core component architecture |
|
|
1015
442
|
|