@walkeros/web-destination-snowplow 0.0.8-next-1769158278557
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 +1117 -0
- package/dist/dev.d.mts +938 -0
- package/dist/dev.d.ts +938 -0
- package/dist/dev.js +1 -0
- package/dist/dev.js.map +1 -0
- package/dist/dev.mjs +1 -0
- package/dist/dev.mjs.map +1 -0
- package/dist/examples/index.d.mts +790 -0
- package/dist/examples/index.d.ts +790 -0
- package/dist/examples/index.js +1525 -0
- package/dist/examples/index.mjs +1501 -0
- package/dist/index.browser.js +1 -0
- package/dist/index.d.mts +1544 -0
- package/dist/index.d.ts +1544 -0
- package/dist/index.es5.js +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
# @walkeros/web-destination-snowplow
|
|
2
|
+
|
|
3
|
+
Snowplow Analytics destination for walkerOS - send events to your Snowplow
|
|
4
|
+
collector for powerful, privacy-first analytics.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @walkeros/collector @walkeros/web-destination-snowplow
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { startFlow } from '@walkeros/collector';
|
|
16
|
+
import { destinationSnowplow } from '@walkeros/web-destination-snowplow';
|
|
17
|
+
|
|
18
|
+
const { elb } = await startFlow({
|
|
19
|
+
destinations: {
|
|
20
|
+
snowplow: {
|
|
21
|
+
destination: destinationSnowplow,
|
|
22
|
+
config: {
|
|
23
|
+
settings: {
|
|
24
|
+
collectorUrl: 'https://collector.yourdomain.com',
|
|
25
|
+
appId: 'my-web-app',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Track events
|
|
33
|
+
await elb('page view', { title: 'Home' });
|
|
34
|
+
await elb('product view', { id: 'P123', name: 'Laptop', price: 999 });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
### Settings
|
|
40
|
+
|
|
41
|
+
#### Required Settings
|
|
42
|
+
|
|
43
|
+
- **collectorUrl** (string): Your Snowplow collector endpoint URL
|
|
44
|
+
|
|
45
|
+
#### Optional Settings
|
|
46
|
+
|
|
47
|
+
- **appId** (string): Application identifier. Default: `'walkerOS'`
|
|
48
|
+
- **trackerName** (string): Tracker instance name. Default: `'sp'`
|
|
49
|
+
- **platform** (string): Platform identifier. Default: `'web'`
|
|
50
|
+
- **scriptUrl** (string): Custom URL for the Snowplow tracker script. Used when
|
|
51
|
+
`loadScript: true`. Always pin to a specific version in production.
|
|
52
|
+
- **eventMethod** ('struct' | 'self'): Event tracking method. Default:
|
|
53
|
+
`'struct'`
|
|
54
|
+
- `'struct'`: Use structured events (category/action/label/property/value)
|
|
55
|
+
- `'self'`: Use self-describing events (schema-based)
|
|
56
|
+
- **schema** (string): Iglu schema URI for self-describing events
|
|
57
|
+
- **pageViewTracking** (boolean): Enable automatic page view tracking. Default:
|
|
58
|
+
`false`
|
|
59
|
+
- **trackPageView** (boolean): Track page view on tracker initialization. When
|
|
60
|
+
`true`, calls `trackPageView()` immediately after init. Default: `false`
|
|
61
|
+
- **pageViewEvent** (string): Event name that triggers `trackPageView()`. When a
|
|
62
|
+
walkerOS event matches this name, Snowplow's native page view tracking is
|
|
63
|
+
used. Must be explicitly set (no default).
|
|
64
|
+
- **userId** (string | Mapping.Value): User ID for cross-session user stitching.
|
|
65
|
+
Called once via `setUserId()` on the first event where the value resolves.
|
|
66
|
+
- **anonymousTracking** (boolean | object): Enable anonymous tracking mode.
|
|
67
|
+
- `true`: Basic anonymous tracking (no user identifiers)
|
|
68
|
+
- `{ withServerAnonymisation: true }`: Also anonymize IP on server
|
|
69
|
+
- `{ withSessionTracking: true }`: Keep session tracking in anonymous mode
|
|
70
|
+
|
|
71
|
+
### Event Mapping
|
|
72
|
+
|
|
73
|
+
Transform walkerOS events into Snowplow structured events:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
config: {
|
|
77
|
+
settings: {
|
|
78
|
+
collectorUrl: 'https://collector.example.com',
|
|
79
|
+
appId: 'my-app',
|
|
80
|
+
},
|
|
81
|
+
mapping: {
|
|
82
|
+
product: {
|
|
83
|
+
view: {
|
|
84
|
+
data: {
|
|
85
|
+
map: {
|
|
86
|
+
category: { value: 'product' },
|
|
87
|
+
action: { value: 'view' },
|
|
88
|
+
property: 'data.name',
|
|
89
|
+
value: 'data.price',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
order: {
|
|
95
|
+
complete: {
|
|
96
|
+
data: {
|
|
97
|
+
map: {
|
|
98
|
+
category: { value: 'ecommerce' },
|
|
99
|
+
action: { value: 'purchase' },
|
|
100
|
+
property: 'data.id',
|
|
101
|
+
value: 'data.total',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## How It Works
|
|
111
|
+
|
|
112
|
+
This destination integrates with the Snowplow JavaScript Tracker using the
|
|
113
|
+
`window.snowplow()` queue function, similar to how Google Analytics uses
|
|
114
|
+
`gtag()` or Meta Pixel uses `fbq()`.
|
|
115
|
+
|
|
116
|
+
### Tracker Interface
|
|
117
|
+
|
|
118
|
+
The Snowplow tracker exposes a global queue function:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
// Initialization
|
|
122
|
+
window.snowplow('newTracker', 'sp', 'https://collector.example.com', {
|
|
123
|
+
appId: 'my-app',
|
|
124
|
+
platform: 'web',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Tracking events
|
|
128
|
+
window.snowplow('trackPageView');
|
|
129
|
+
window.snowplow(
|
|
130
|
+
'trackStructEvent',
|
|
131
|
+
'category',
|
|
132
|
+
'action',
|
|
133
|
+
'label',
|
|
134
|
+
'property',
|
|
135
|
+
value,
|
|
136
|
+
);
|
|
137
|
+
window.snowplow('trackSelfDescribingEvent', {
|
|
138
|
+
event: {
|
|
139
|
+
schema: 'iglu:vendor/name/jsonschema/1-0-0',
|
|
140
|
+
data: {
|
|
141
|
+
/* event data */
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Integration with walkerOS
|
|
148
|
+
|
|
149
|
+
This destination automatically:
|
|
150
|
+
|
|
151
|
+
1. **Initializes the tracker** - Calls `newTracker()` with your configuration
|
|
152
|
+
2. **Maps events** - Transforms walkerOS events into Snowplow format
|
|
153
|
+
3. **Sends events** - Calls the appropriate Snowplow tracking method
|
|
154
|
+
|
|
155
|
+
You don't need to interact with `window.snowplow()` directly - just use the
|
|
156
|
+
walkerOS `elb()` function.
|
|
157
|
+
|
|
158
|
+
### Script Loading
|
|
159
|
+
|
|
160
|
+
The Snowplow tracker script can be loaded automatically:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
config: {
|
|
164
|
+
settings: {
|
|
165
|
+
collectorUrl: 'https://collector.example.com',
|
|
166
|
+
},
|
|
167
|
+
loadScript: true, // Automatically loads Snowplow tracker from CDN
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Or manually:
|
|
172
|
+
|
|
173
|
+
```html
|
|
174
|
+
<script src="https://cdn.jsdelivr.net/npm/@snowplow/javascript-tracker@latest/dist/sp.js"></script>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Custom Script URL
|
|
178
|
+
|
|
179
|
+
By default, the tracker loads from the jsdelivr CDN with `@latest`. For
|
|
180
|
+
production, always pin to a specific version using the `scriptUrl` setting:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
config: {
|
|
184
|
+
settings: {
|
|
185
|
+
collectorUrl: 'https://collector.example.com',
|
|
186
|
+
scriptUrl: 'https://cdn.jsdelivr.net/npm/@snowplow/javascript-tracker@3.24.0/dist/sp.js',
|
|
187
|
+
},
|
|
188
|
+
loadScript: true,
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Security Warning:** Using `@latest` in production is not recommended as it can
|
|
193
|
+
introduce breaking changes or security vulnerabilities without notice. Always
|
|
194
|
+
pin to a specific version.
|
|
195
|
+
|
|
196
|
+
## Examples
|
|
197
|
+
|
|
198
|
+
### Structured Events (Default)
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// Configure for structured events
|
|
202
|
+
config: {
|
|
203
|
+
settings: {
|
|
204
|
+
collectorUrl: 'https://collector.example.com',
|
|
205
|
+
eventMethod: 'struct', // Default
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Track events
|
|
210
|
+
await elb('product view', { id: 'P123', name: 'Laptop', price: 999 });
|
|
211
|
+
// Sends: category='product', action='view', property='Laptop', value=999
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Self-Describing Events
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// Configure for self-describing events
|
|
218
|
+
config: {
|
|
219
|
+
settings: {
|
|
220
|
+
collectorUrl: 'https://collector.example.com',
|
|
221
|
+
eventMethod: 'self',
|
|
222
|
+
schema: 'iglu:com.mycompany/product_view/jsonschema/1-0-0',
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Track events
|
|
227
|
+
await elb('product view', { id: 'P123', price: 999 });
|
|
228
|
+
// Sends self-describing event with your schema
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Page View Tracking
|
|
232
|
+
|
|
233
|
+
**Option 1: Auto-track on init**
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
config: {
|
|
237
|
+
settings: {
|
|
238
|
+
collectorUrl: 'https://collector.example.com',
|
|
239
|
+
trackPageView: true, // Calls trackPageView() immediately after init
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Option 2: Track via walkerOS event**
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
config: {
|
|
248
|
+
settings: {
|
|
249
|
+
collectorUrl: 'https://collector.example.com',
|
|
250
|
+
pageViewEvent: 'page view', // Explicit - triggers trackPageView()
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Then in your app:
|
|
255
|
+
await elb('page view', { title: document.title });
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Option 3: Custom event name**
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
config: {
|
|
262
|
+
settings: {
|
|
263
|
+
collectorUrl: 'https://collector.example.com',
|
|
264
|
+
pageViewEvent: 'screen view', // Custom event name for SPAs/mobile
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
await elb('screen view', { screenName: 'Home' });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### E-commerce Tracking
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// Product view
|
|
275
|
+
await elb('product view', {
|
|
276
|
+
id: 'P123',
|
|
277
|
+
name: 'Laptop',
|
|
278
|
+
price: 999,
|
|
279
|
+
category: 'Electronics',
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Add to cart
|
|
283
|
+
await elb('product add', {
|
|
284
|
+
id: 'P123',
|
|
285
|
+
quantity: 1,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Purchase
|
|
289
|
+
await elb('order complete', {
|
|
290
|
+
id: 'ORDER123',
|
|
291
|
+
total: 1999,
|
|
292
|
+
currency: 'USD',
|
|
293
|
+
items: 2,
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Media Tracking
|
|
298
|
+
|
|
299
|
+
Track video and audio playback events using Snowplow's media tracking schemas:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import {
|
|
303
|
+
MEDIA_SCHEMAS,
|
|
304
|
+
MEDIA_ACTIONS,
|
|
305
|
+
} from '@walkeros/web-destination-snowplow';
|
|
306
|
+
|
|
307
|
+
// Track video play
|
|
308
|
+
await elb('video play', {
|
|
309
|
+
id: 'video-123',
|
|
310
|
+
title: 'Product Demo',
|
|
311
|
+
currentTime: 0,
|
|
312
|
+
duration: 120,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Track video progress (25%, 50%, 75% milestones)
|
|
316
|
+
await elb('video progress', {
|
|
317
|
+
id: 'video-123',
|
|
318
|
+
percentProgress: 25,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Track video pause
|
|
322
|
+
await elb('video pause', {
|
|
323
|
+
id: 'video-123',
|
|
324
|
+
currentTime: 45,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Track video complete
|
|
328
|
+
await elb('video end', {
|
|
329
|
+
id: 'video-123',
|
|
330
|
+
duration: 120,
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### Media Mapping Configuration
|
|
335
|
+
|
|
336
|
+
Configure media event mappings with the `MEDIA_SCHEMAS` constants:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { MEDIA_SCHEMAS } from '@walkeros/web-destination-snowplow';
|
|
340
|
+
|
|
341
|
+
config: {
|
|
342
|
+
mapping: {
|
|
343
|
+
video: {
|
|
344
|
+
play: {
|
|
345
|
+
settings: {
|
|
346
|
+
schema: MEDIA_SCHEMAS.PLAY,
|
|
347
|
+
},
|
|
348
|
+
data: {
|
|
349
|
+
map: {
|
|
350
|
+
label: 'data.title',
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
pause: {
|
|
355
|
+
settings: {
|
|
356
|
+
schema: MEDIA_SCHEMAS.PAUSE,
|
|
357
|
+
},
|
|
358
|
+
data: {
|
|
359
|
+
map: {
|
|
360
|
+
currentTime: 'data.currentTime',
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
progress: {
|
|
365
|
+
settings: {
|
|
366
|
+
schema: MEDIA_SCHEMAS.PERCENT_PROGRESS,
|
|
367
|
+
},
|
|
368
|
+
data: {
|
|
369
|
+
map: {
|
|
370
|
+
percentProgress: 'data.percentProgress',
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
end: {
|
|
375
|
+
settings: {
|
|
376
|
+
schema: MEDIA_SCHEMAS.END,
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### Ad Tracking
|
|
385
|
+
|
|
386
|
+
Track video advertisements with pre-roll, mid-roll, and post-roll ad events:
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
import { MEDIA_SCHEMAS } from '@walkeros/web-destination-snowplow';
|
|
390
|
+
|
|
391
|
+
config: {
|
|
392
|
+
mapping: {
|
|
393
|
+
ad: {
|
|
394
|
+
break_start: {
|
|
395
|
+
settings: {
|
|
396
|
+
schema: MEDIA_SCHEMAS.AD_BREAK_START,
|
|
397
|
+
},
|
|
398
|
+
data: {
|
|
399
|
+
map: {
|
|
400
|
+
breakId: 'data.breakId',
|
|
401
|
+
breakType: 'data.breakType', // 'preroll', 'midroll', 'postroll'
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
start: {
|
|
406
|
+
settings: {
|
|
407
|
+
schema: MEDIA_SCHEMAS.AD_START,
|
|
408
|
+
},
|
|
409
|
+
data: {
|
|
410
|
+
map: {
|
|
411
|
+
adId: 'data.adId',
|
|
412
|
+
name: 'data.name',
|
|
413
|
+
duration: 'data.duration',
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
complete: {
|
|
418
|
+
settings: {
|
|
419
|
+
schema: MEDIA_SCHEMAS.AD_COMPLETE,
|
|
420
|
+
},
|
|
421
|
+
data: {
|
|
422
|
+
map: {
|
|
423
|
+
adId: 'data.adId',
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
skip: {
|
|
428
|
+
settings: {
|
|
429
|
+
schema: MEDIA_SCHEMAS.AD_SKIP,
|
|
430
|
+
},
|
|
431
|
+
data: {
|
|
432
|
+
map: {
|
|
433
|
+
adId: 'data.adId',
|
|
434
|
+
percentProgress: 'data.percentProgress',
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
#### Media Player Context
|
|
444
|
+
|
|
445
|
+
Attach media player state as context to your events:
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
config: {
|
|
449
|
+
mapping: {
|
|
450
|
+
video: {
|
|
451
|
+
play: {
|
|
452
|
+
settings: {
|
|
453
|
+
schema: MEDIA_SCHEMAS.PLAY,
|
|
454
|
+
context: [
|
|
455
|
+
{
|
|
456
|
+
schema: MEDIA_SCHEMAS.MEDIA_PLAYER,
|
|
457
|
+
data: {
|
|
458
|
+
map: {
|
|
459
|
+
currentTime: 'data.currentTime',
|
|
460
|
+
duration: 'data.duration',
|
|
461
|
+
muted: 'data.muted',
|
|
462
|
+
volume: 'data.volume',
|
|
463
|
+
playbackRate: 'data.playbackRate',
|
|
464
|
+
paused: { value: false },
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
#### Available Media Schemas
|
|
477
|
+
|
|
478
|
+
| Schema | Description | Use Case |
|
|
479
|
+
| ------------------- | -------------------------- | --------------------- |
|
|
480
|
+
| `PLAY` | Playback started | Video/audio play |
|
|
481
|
+
| `PAUSE` | Playback paused | User pauses content |
|
|
482
|
+
| `END` | Playback ended | Video/audio completed |
|
|
483
|
+
| `SEEK_START` | User started seeking | Scrubbing timeline |
|
|
484
|
+
| `SEEK_END` | User ended seeking | Scrubbing complete |
|
|
485
|
+
| `BUFFER_START` | Buffering started | Loading content |
|
|
486
|
+
| `BUFFER_END` | Buffering ended | Content ready |
|
|
487
|
+
| `QUALITY_CHANGE` | Video quality changed | Adaptive streaming |
|
|
488
|
+
| `FULLSCREEN_CHANGE` | Fullscreen mode toggled | User interaction |
|
|
489
|
+
| `VOLUME_CHANGE` | Volume level changed | User adjustment |
|
|
490
|
+
| `PERCENT_PROGRESS` | Progress milestone reached | 25%, 50%, 75% markers |
|
|
491
|
+
| `ERROR` | Playback error occurred | Error tracking |
|
|
492
|
+
| `AD_BREAK_START` | Ad break started | Pre/mid/post-roll |
|
|
493
|
+
| `AD_START` | Individual ad started | Ad impression |
|
|
494
|
+
| `AD_COMPLETE` | Individual ad completed | Ad view completion |
|
|
495
|
+
| `AD_SKIP` | User skipped ad | Ad engagement |
|
|
496
|
+
|
|
497
|
+
## Snowplow Event Types
|
|
498
|
+
|
|
499
|
+
### Structured Events
|
|
500
|
+
|
|
501
|
+
Structured events follow Snowplow's category/action/label/property/value
|
|
502
|
+
pattern. Use the `struct` mapping property to send structured events:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
mapping: {
|
|
506
|
+
button: {
|
|
507
|
+
click: {
|
|
508
|
+
settings: {
|
|
509
|
+
struct: {
|
|
510
|
+
category: { value: 'ui' },
|
|
511
|
+
action: { value: 'click' },
|
|
512
|
+
label: 'data.button_name',
|
|
513
|
+
property: 'data.section',
|
|
514
|
+
value: 'data.position',
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
When `struct` is configured, the destination calls `trackStructEvent` directly,
|
|
523
|
+
bypassing self-describing events entirely. This is ideal for:
|
|
524
|
+
|
|
525
|
+
- Simple interactions that don't need schema validation
|
|
526
|
+
- Lightweight event tracking
|
|
527
|
+
- Google Analytics-style category/action tracking
|
|
528
|
+
|
|
529
|
+
**Available fields:**
|
|
530
|
+
|
|
531
|
+
- **category** (required): Event category (e.g., 'ui', 'video', 'cta')
|
|
532
|
+
- **action** (required): Action performed (e.g., 'click', 'play', 'submit')
|
|
533
|
+
- **label** (optional): Additional context string
|
|
534
|
+
- **property** (optional): Property name string
|
|
535
|
+
- **value** (optional): Numeric value (automatically converted from string)
|
|
536
|
+
|
|
537
|
+
### Self-Describing Events
|
|
538
|
+
|
|
539
|
+
Self-describing events use Iglu schemas for structured data:
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
{
|
|
543
|
+
schema: 'iglu:com.example/event/jsonschema/1-0-0',
|
|
544
|
+
data: {
|
|
545
|
+
// Your event data
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Built-in Contexts
|
|
551
|
+
|
|
552
|
+
Enable automatic context entities to enrich your events:
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
config: {
|
|
556
|
+
settings: {
|
|
557
|
+
collectorUrl: 'https://collector.example.com',
|
|
558
|
+
contexts: {
|
|
559
|
+
webPage: true, // Page view ID (links events to page views)
|
|
560
|
+
session: true, // Session tracking (client_session schema)
|
|
561
|
+
browser: true, // Browser info (viewport, language, device)
|
|
562
|
+
geolocation: true // User location (requires permission)
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
| Context | Schema | Description |
|
|
569
|
+
| ------------- | --------------------------- | ------------------------------- |
|
|
570
|
+
| `webPage` | `web_page/1-0-0` | Unique page view ID |
|
|
571
|
+
| `session` | `client_session/1-0-2` | Session ID, index, timestamps |
|
|
572
|
+
| `browser` | `browser_context/2-0-0` | Viewport, language, device info |
|
|
573
|
+
| `geolocation` | `geolocation_context/1-1-0` | Latitude, longitude |
|
|
574
|
+
|
|
575
|
+
## User Identity & Privacy
|
|
576
|
+
|
|
577
|
+
### Cross-Session User Stitching
|
|
578
|
+
|
|
579
|
+
Use `userId` to link events across sessions when users log in:
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
config: {
|
|
583
|
+
settings: {
|
|
584
|
+
collectorUrl: 'https://collector.example.com',
|
|
585
|
+
userId: 'user.id', // From walkerOS user object
|
|
586
|
+
},
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Anonymous browsing - events tracked without user_id
|
|
590
|
+
await elb('page view');
|
|
591
|
+
|
|
592
|
+
// User logs in - set walkerOS user
|
|
593
|
+
elb('walker user', { id: 'user-abc123' });
|
|
594
|
+
|
|
595
|
+
// Next event triggers setUserId, all subsequent events include user_id
|
|
596
|
+
await elb('product view', { id: 'P123' });
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
The `userId` setting supports walkerOS mapping syntax:
|
|
600
|
+
|
|
601
|
+
- `'user.id'` - From walkerOS user object (recommended)
|
|
602
|
+
- `'globals.user_id'` - From globals
|
|
603
|
+
- `{ value: 'static-id' }` - Static value (rare)
|
|
604
|
+
|
|
605
|
+
### Anonymous Tracking
|
|
606
|
+
|
|
607
|
+
Enable anonymous tracking for privacy-focused collection or before consent:
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
config: {
|
|
611
|
+
settings: {
|
|
612
|
+
collectorUrl: 'https://collector.example.com',
|
|
613
|
+
anonymousTracking: true, // Basic anonymous tracking
|
|
614
|
+
},
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Or with fine-grained control:
|
|
618
|
+
config: {
|
|
619
|
+
settings: {
|
|
620
|
+
collectorUrl: 'https://collector.example.com',
|
|
621
|
+
anonymousTracking: {
|
|
622
|
+
withServerAnonymisation: true, // Anonymize IP on server
|
|
623
|
+
withSessionTracking: true, // Keep session context
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### Runtime Privacy Controls
|
|
630
|
+
|
|
631
|
+
Control tracking modes at runtime using exported utility functions:
|
|
632
|
+
|
|
633
|
+
```typescript
|
|
634
|
+
import {
|
|
635
|
+
clearUserData,
|
|
636
|
+
enableAnonymousTracking,
|
|
637
|
+
disableAnonymousTracking,
|
|
638
|
+
} from '@walkeros/web-destination-snowplow';
|
|
639
|
+
|
|
640
|
+
// User withdraws consent - clear all identifiers
|
|
641
|
+
clearUserData();
|
|
642
|
+
|
|
643
|
+
// Switch to anonymous mode mid-session
|
|
644
|
+
enableAnonymousTracking({ withServerAnonymisation: true });
|
|
645
|
+
|
|
646
|
+
// User grants consent - resume normal tracking
|
|
647
|
+
disableAnonymousTracking();
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
## Consent Tracking
|
|
651
|
+
|
|
652
|
+
Track GDPR/CCPA consent events using Snowplow's Enhanced Consent plugin. The
|
|
653
|
+
destination automatically reacts to walkerOS consent events and sends the
|
|
654
|
+
appropriate consent tracking calls.
|
|
655
|
+
|
|
656
|
+
### Prerequisites
|
|
657
|
+
|
|
658
|
+
Load the Enhanced Consent plugin:
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
import { EnhancedConsentPlugin } from '@snowplow/browser-plugin-enhanced-consent';
|
|
662
|
+
|
|
663
|
+
config: {
|
|
664
|
+
settings: {
|
|
665
|
+
collectorUrl: 'https://collector.example.com',
|
|
666
|
+
plugins: [EnhancedConsentPlugin()],
|
|
667
|
+
consent: {
|
|
668
|
+
required: ['analytics', 'marketing'],
|
|
669
|
+
basisForProcessing: 'consent',
|
|
670
|
+
consentUrl: 'https://example.com/privacy',
|
|
671
|
+
consentVersion: '2.0',
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
}
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Configuration
|
|
678
|
+
|
|
679
|
+
| Option | Type | Description |
|
|
680
|
+
| -------------------- | ---------- | ------------------------------------ |
|
|
681
|
+
| `required` | `string[]` | walkerOS consent groups to check |
|
|
682
|
+
| `basisForProcessing` | `string` | GDPR basis (consent, contract, etc.) |
|
|
683
|
+
| `consentUrl` | `string` | Privacy policy URL |
|
|
684
|
+
| `consentVersion` | `string` | Policy version |
|
|
685
|
+
| `domainsApplied` | `string[]` | Domains where consent applies |
|
|
686
|
+
| `gdprApplies` | `boolean` | Whether GDPR applies |
|
|
687
|
+
|
|
688
|
+
### How It Works
|
|
689
|
+
|
|
690
|
+
The destination listens for walkerOS consent events and maps them to Snowplow:
|
|
691
|
+
|
|
692
|
+
| walkerOS Consent State | Snowplow Method |
|
|
693
|
+
| --------------------------- | ---------------------- |
|
|
694
|
+
| All required scopes granted | `trackConsentAllow` |
|
|
695
|
+
| All required scopes denied | `trackConsentDeny` |
|
|
696
|
+
| Partial consent (mixed) | `trackConsentSelected` |
|
|
697
|
+
|
|
698
|
+
### Example
|
|
699
|
+
|
|
700
|
+
```typescript
|
|
701
|
+
// Configure consent tracking
|
|
702
|
+
const { elb } = await startFlow({
|
|
703
|
+
destinations: {
|
|
704
|
+
snowplow: {
|
|
705
|
+
code: destinationSnowplow,
|
|
706
|
+
config: {
|
|
707
|
+
settings: {
|
|
708
|
+
collectorUrl: 'https://collector.example.com',
|
|
709
|
+
consent: {
|
|
710
|
+
required: ['analytics', 'marketing'],
|
|
711
|
+
basisForProcessing: 'consent',
|
|
712
|
+
consentUrl: 'https://example.com/privacy',
|
|
713
|
+
consentVersion: '2.0',
|
|
714
|
+
domainsApplied: ['example.com'],
|
|
715
|
+
gdprApplies: true,
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
// User accepts all
|
|
724
|
+
await elb('walker consent', { analytics: true, marketing: true });
|
|
725
|
+
// → trackConsentAllow called
|
|
726
|
+
|
|
727
|
+
// User accepts some
|
|
728
|
+
await elb('walker consent', { analytics: true, marketing: false });
|
|
729
|
+
// → trackConsentSelected called
|
|
730
|
+
|
|
731
|
+
// User rejects all
|
|
732
|
+
await elb('walker consent', { analytics: false, marketing: false });
|
|
733
|
+
// → trackConsentDeny called
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### Consent Schema Constants
|
|
737
|
+
|
|
738
|
+
```typescript
|
|
739
|
+
import { CONSENT_SCHEMAS } from '@walkeros/web-destination-snowplow';
|
|
740
|
+
|
|
741
|
+
CONSENT_SCHEMAS.PREFERENCES; // consent_preferences/1-0-0
|
|
742
|
+
CONSENT_SCHEMAS.CMP_VISIBLE; // cmp_visible/1-0-0
|
|
743
|
+
CONSENT_SCHEMAS.DOCUMENT; // consent_document/1-0-0
|
|
744
|
+
CONSENT_SCHEMAS.GDPR; // gdpr/1-0-0
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
## Schema Constants
|
|
748
|
+
|
|
749
|
+
The package exports pre-defined Snowplow schema URIs for convenience:
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
import {
|
|
753
|
+
SCHEMAS,
|
|
754
|
+
ACTIONS,
|
|
755
|
+
WEB_SCHEMAS,
|
|
756
|
+
CONSENT_SCHEMAS,
|
|
757
|
+
MEDIA_SCHEMAS,
|
|
758
|
+
MEDIA_ACTIONS,
|
|
759
|
+
} from '@walkeros/web-destination-snowplow';
|
|
760
|
+
|
|
761
|
+
// Ecommerce schemas
|
|
762
|
+
SCHEMAS.PRODUCT; // 'iglu:com.snowplowanalytics.snowplow.ecommerce/product/jsonschema/1-0-0'
|
|
763
|
+
SCHEMAS.TRANSACTION; // 'iglu:com.snowplowanalytics.snowplow.ecommerce/transaction/jsonschema/1-0-0'
|
|
764
|
+
|
|
765
|
+
// Ecommerce actions
|
|
766
|
+
ACTIONS.ADD_TO_CART; // 'add_to_cart'
|
|
767
|
+
ACTIONS.TRANSACTION; // 'transaction'
|
|
768
|
+
|
|
769
|
+
// Web event schemas
|
|
770
|
+
WEB_SCHEMAS.LINK_CLICK; // 'iglu:com.snowplowanalytics.snowplow/link_click/jsonschema/1-0-1'
|
|
771
|
+
WEB_SCHEMAS.SUBMIT_FORM; // 'iglu:com.snowplowanalytics.snowplow/submit_form/jsonschema/1-0-0'
|
|
772
|
+
WEB_SCHEMAS.SITE_SEARCH; // 'iglu:com.snowplowanalytics.snowplow/site_search/jsonschema/1-0-0'
|
|
773
|
+
WEB_SCHEMAS.TIMING; // 'iglu:com.snowplowanalytics.snowplow/timing/jsonschema/1-0-0'
|
|
774
|
+
WEB_SCHEMAS.WEB_VITALS; // 'iglu:com.snowplowanalytics.snowplow/web_vitals/jsonschema/1-0-0'
|
|
775
|
+
|
|
776
|
+
// Web context schemas
|
|
777
|
+
WEB_SCHEMAS.WEB_PAGE; // 'iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0'
|
|
778
|
+
WEB_SCHEMAS.BROWSER; // 'iglu:com.snowplowanalytics.snowplow/browser_context/jsonschema/2-0-0'
|
|
779
|
+
WEB_SCHEMAS.CLIENT_SESSION; // 'iglu:com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-2'
|
|
780
|
+
|
|
781
|
+
// Media event schemas
|
|
782
|
+
MEDIA_SCHEMAS.PLAY; // 'iglu:com.snowplowanalytics.snowplow.media/play_event/jsonschema/1-0-0'
|
|
783
|
+
MEDIA_SCHEMAS.PAUSE; // 'iglu:com.snowplowanalytics.snowplow.media/pause_event/jsonschema/1-0-0'
|
|
784
|
+
MEDIA_SCHEMAS.END; // 'iglu:com.snowplowanalytics.snowplow.media/end_event/jsonschema/1-0-0'
|
|
785
|
+
MEDIA_SCHEMAS.SEEK_START; // 'iglu:com.snowplowanalytics.snowplow.media/seek_start_event/jsonschema/1-0-0'
|
|
786
|
+
MEDIA_SCHEMAS.BUFFER_START; // 'iglu:com.snowplowanalytics.snowplow.media/buffer_start_event/jsonschema/1-0-0'
|
|
787
|
+
MEDIA_SCHEMAS.QUALITY_CHANGE; // 'iglu:com.snowplowanalytics.snowplow.media/quality_change_event/jsonschema/1-0-0'
|
|
788
|
+
MEDIA_SCHEMAS.FULLSCREEN_CHANGE; // 'iglu:com.snowplowanalytics.snowplow.media/fullscreen_change_event/jsonschema/1-0-0'
|
|
789
|
+
MEDIA_SCHEMAS.PERCENT_PROGRESS; // 'iglu:com.snowplowanalytics.snowplow.media/percent_progress_event/jsonschema/1-0-0'
|
|
790
|
+
MEDIA_SCHEMAS.ERROR; // 'iglu:com.snowplowanalytics.snowplow.media/error_event/jsonschema/1-0-0'
|
|
791
|
+
|
|
792
|
+
// Media ad schemas
|
|
793
|
+
MEDIA_SCHEMAS.AD_BREAK_START; // 'iglu:com.snowplowanalytics.snowplow.media/ad_break_start_event/jsonschema/1-0-0'
|
|
794
|
+
MEDIA_SCHEMAS.AD_START; // 'iglu:com.snowplowanalytics.snowplow.media/ad_start_event/jsonschema/1-0-0'
|
|
795
|
+
MEDIA_SCHEMAS.AD_COMPLETE; // 'iglu:com.snowplowanalytics.snowplow.media/ad_complete_event/jsonschema/1-0-0'
|
|
796
|
+
MEDIA_SCHEMAS.AD_SKIP; // 'iglu:com.snowplowanalytics.snowplow.media/ad_skip_event/jsonschema/1-0-0'
|
|
797
|
+
|
|
798
|
+
// Media context schemas
|
|
799
|
+
MEDIA_SCHEMAS.MEDIA_PLAYER; // 'iglu:com.snowplowanalytics.snowplow/media_player/jsonschema/1-0-0'
|
|
800
|
+
MEDIA_SCHEMAS.SESSION; // 'iglu:com.snowplowanalytics.snowplow.media/session/jsonschema/1-0-0'
|
|
801
|
+
MEDIA_SCHEMAS.AD; // 'iglu:com.snowplowanalytics.snowplow.media/ad/jsonschema/1-0-0'
|
|
802
|
+
MEDIA_SCHEMAS.AD_BREAK; // 'iglu:com.snowplowanalytics.snowplow.media/ad_break/jsonschema/1-0-0'
|
|
803
|
+
|
|
804
|
+
// Media action types (for use with mapping.name)
|
|
805
|
+
MEDIA_ACTIONS.PLAY; // 'play'
|
|
806
|
+
MEDIA_ACTIONS.PAUSE; // 'pause'
|
|
807
|
+
MEDIA_ACTIONS.AD_START; // 'ad_start'
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Use these constants in your mapping configuration to ensure correct schema URIs.
|
|
811
|
+
|
|
812
|
+
## Advanced Usage
|
|
813
|
+
|
|
814
|
+
### Multiple Trackers
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
config: {
|
|
818
|
+
settings: {
|
|
819
|
+
collectorUrl: 'https://collector.example.com',
|
|
820
|
+
trackerName: 'mainTracker',
|
|
821
|
+
},
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Can run multiple instances with different tracker names
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
### Custom Mapping with Functions
|
|
828
|
+
|
|
829
|
+
```typescript
|
|
830
|
+
config: {
|
|
831
|
+
mapping: {
|
|
832
|
+
product: {
|
|
833
|
+
view: {
|
|
834
|
+
data: {
|
|
835
|
+
map: {
|
|
836
|
+
category: { value: 'product' },
|
|
837
|
+
action: { value: 'view' },
|
|
838
|
+
property: 'data.name',
|
|
839
|
+
value: {
|
|
840
|
+
fn: (event) => event.data.price * 1.2, // Add tax
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
}
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
## Integration with Snowplow Pipeline
|
|
851
|
+
|
|
852
|
+
This destination works with any standard Snowplow pipeline:
|
|
853
|
+
|
|
854
|
+
1. **Tracker** (this destination) → Sends events
|
|
855
|
+
2. **Collector** → Receives and validates events
|
|
856
|
+
3. **Enrich** → Enriches events with additional data
|
|
857
|
+
4. **Storage** → Loads into your data warehouse (Redshift, BigQuery, Snowflake,
|
|
858
|
+
etc.)
|
|
859
|
+
|
|
860
|
+
Make sure your `collectorUrl` points to your Snowplow collector endpoint.
|
|
861
|
+
|
|
862
|
+
## Troubleshooting
|
|
863
|
+
|
|
864
|
+
### Events not appearing in Snowplow
|
|
865
|
+
|
|
866
|
+
1. **Check Collector URL**: Verify your collector URL is correct
|
|
867
|
+
|
|
868
|
+
```typescript
|
|
869
|
+
settings: {
|
|
870
|
+
collectorUrl: 'https://collector.example.com', // Should not include /i or /com.snowplowanalytics.snowplow/tp2
|
|
871
|
+
}
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
2. **Check Browser Console**: Look for Snowplow errors
|
|
875
|
+
- Open DevTools → Console
|
|
876
|
+
- Look for `[Snowplow]` prefixed messages
|
|
877
|
+
|
|
878
|
+
3. **Verify Network Requests**: Check Network tab in DevTools
|
|
879
|
+
- Look for requests to your collector URL
|
|
880
|
+
- Check request payload
|
|
881
|
+
|
|
882
|
+
4. **Test with Simple Event**:
|
|
883
|
+
```typescript
|
|
884
|
+
await elb('page view', { title: 'Test' });
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
### Initialization Errors
|
|
888
|
+
|
|
889
|
+
If you see `[Snowplow] Collector URL is required`:
|
|
890
|
+
|
|
891
|
+
- Ensure `collectorUrl` is provided in settings
|
|
892
|
+
- Check for typos in configuration
|
|
893
|
+
|
|
894
|
+
### Schema Validation Errors
|
|
895
|
+
|
|
896
|
+
For self-describing events, ensure:
|
|
897
|
+
|
|
898
|
+
- Schema URI is correctly formatted: `iglu:vendor/name/format/version`
|
|
899
|
+
- Schema exists in your Iglu registry
|
|
900
|
+
- Event data matches the schema definition
|
|
901
|
+
|
|
902
|
+
## Local Testing with Docker
|
|
903
|
+
|
|
904
|
+
You can test your walkerOS Snowplow integration locally using **Snowplow
|
|
905
|
+
Micro**, a lightweight Docker-based collector that validates and enriches events
|
|
906
|
+
just like a real Snowplow pipeline.
|
|
907
|
+
|
|
908
|
+
### Quick Start with Snowplow Micro
|
|
909
|
+
|
|
910
|
+
1. **Start Snowplow Micro**:
|
|
911
|
+
|
|
912
|
+
```bash
|
|
913
|
+
docker run -p 9090:9090 snowplow/snowplow-micro:3.0.1
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
2. **Configure walkerOS to use Micro**:
|
|
917
|
+
|
|
918
|
+
```typescript
|
|
919
|
+
const { elb } = await startFlow({
|
|
920
|
+
destinations: {
|
|
921
|
+
snowplow: {
|
|
922
|
+
destination: destinationSnowplow,
|
|
923
|
+
config: {
|
|
924
|
+
settings: {
|
|
925
|
+
collectorUrl: 'localhost:9090', // Point to Micro
|
|
926
|
+
appId: 'test-app',
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
});
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
3. **Send test events**:
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
await elb('page view', { title: 'Test Page' });
|
|
938
|
+
await elb('product view', { id: 'P123', price: 999 });
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
4. **Inspect events**:
|
|
942
|
+
- **Web UI**: Open http://localhost:9090/micro/ui in your browser
|
|
943
|
+
- **API**: Query events via REST endpoints
|
|
944
|
+
|
|
945
|
+
### Snowplow Micro API Endpoints
|
|
946
|
+
|
|
947
|
+
Snowplow Micro provides several endpoints for inspecting tracked events:
|
|
948
|
+
|
|
949
|
+
```bash
|
|
950
|
+
# Get all events (good + bad)
|
|
951
|
+
curl http://localhost:9090/micro/all
|
|
952
|
+
|
|
953
|
+
# Get successfully validated events
|
|
954
|
+
curl http://localhost:9090/micro/good
|
|
955
|
+
|
|
956
|
+
# Get events that failed validation
|
|
957
|
+
curl http://localhost:9090/micro/bad
|
|
958
|
+
|
|
959
|
+
# Reset the event cache
|
|
960
|
+
curl -X POST http://localhost:9090/micro/reset
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
### Example Response
|
|
964
|
+
|
|
965
|
+
When you query `/micro/good`, you'll see events in this format:
|
|
966
|
+
|
|
967
|
+
```json
|
|
968
|
+
[
|
|
969
|
+
{
|
|
970
|
+
"event": "unstruct",
|
|
971
|
+
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
972
|
+
"app_id": "test-app",
|
|
973
|
+
"platform": "web",
|
|
974
|
+
"unstruct_event": {
|
|
975
|
+
"schema": "iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0",
|
|
976
|
+
"data": {
|
|
977
|
+
"schema": "iglu:com.example/product_view/jsonschema/1-0-0",
|
|
978
|
+
"data": {
|
|
979
|
+
"id": "P123",
|
|
980
|
+
"price": 999
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
]
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
### Advanced Docker Usage
|
|
989
|
+
|
|
990
|
+
**Export events to TSV**:
|
|
991
|
+
|
|
992
|
+
```bash
|
|
993
|
+
docker run -p 9090:9090 snowplow/snowplow-micro:3.0.1 --output-tsv > events.tsv
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
**Export events to JSON**:
|
|
997
|
+
|
|
998
|
+
```bash
|
|
999
|
+
docker run -p 9090:9090 snowplow/snowplow-micro:3.0.1 --output-json > events.json
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
**Use custom port**:
|
|
1003
|
+
|
|
1004
|
+
```bash
|
|
1005
|
+
docker run -p 5000:9090 snowplow/snowplow-micro:3.0.1
|
|
1006
|
+
# Then set collectorUrl to 'localhost:5000'
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
### Automated Testing
|
|
1010
|
+
|
|
1011
|
+
Integrate Snowplow Micro into your test suite:
|
|
1012
|
+
|
|
1013
|
+
```typescript
|
|
1014
|
+
// test/snowplow.test.ts
|
|
1015
|
+
import { startFlow } from '@walkeros/collector';
|
|
1016
|
+
import { destinationSnowplow } from '@walkeros/web-destination-snowplow';
|
|
1017
|
+
|
|
1018
|
+
describe('Snowplow Integration', () => {
|
|
1019
|
+
let elb;
|
|
1020
|
+
|
|
1021
|
+
beforeAll(async () => {
|
|
1022
|
+
({ elb } = await startFlow({
|
|
1023
|
+
destinations: {
|
|
1024
|
+
snowplow: {
|
|
1025
|
+
destination: destinationSnowplow,
|
|
1026
|
+
config: {
|
|
1027
|
+
settings: {
|
|
1028
|
+
collectorUrl: 'localhost:9090',
|
|
1029
|
+
appId: 'test-app',
|
|
1030
|
+
},
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
1033
|
+
},
|
|
1034
|
+
}));
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
afterEach(async () => {
|
|
1038
|
+
// Reset Micro between tests
|
|
1039
|
+
await fetch('http://localhost:9090/micro/reset', { method: 'POST' });
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
test('tracks page view events', async () => {
|
|
1043
|
+
await elb('page view', { title: 'Home' });
|
|
1044
|
+
|
|
1045
|
+
// Wait a bit for event to be processed
|
|
1046
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1047
|
+
|
|
1048
|
+
const response = await fetch('http://localhost:9090/micro/good');
|
|
1049
|
+
const events = await response.json();
|
|
1050
|
+
|
|
1051
|
+
expect(events).toHaveLength(1);
|
|
1052
|
+
expect(events[0].event).toBe('page_view');
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
test('tracks product events', async () => {
|
|
1056
|
+
await elb('product view', { id: 'P123', price: 999 });
|
|
1057
|
+
|
|
1058
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1059
|
+
|
|
1060
|
+
const response = await fetch('http://localhost:9090/micro/good');
|
|
1061
|
+
const events = await response.json();
|
|
1062
|
+
|
|
1063
|
+
expect(events).toHaveLength(1);
|
|
1064
|
+
expect(events[0].app_id).toBe('test-app');
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
### Integration with E2E Testing
|
|
1070
|
+
|
|
1071
|
+
Use Snowplow Micro with Cypress, Playwright, or other E2E frameworks:
|
|
1072
|
+
|
|
1073
|
+
```javascript
|
|
1074
|
+
// cypress/e2e/tracking.cy.js
|
|
1075
|
+
describe('Snowplow Tracking', () => {
|
|
1076
|
+
beforeEach(() => {
|
|
1077
|
+
// Reset Micro before each test
|
|
1078
|
+
cy.request('POST', 'http://localhost:9090/micro/reset');
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
it('tracks user journey', () => {
|
|
1082
|
+
cy.visit('/');
|
|
1083
|
+
cy.get('[data-elbaction="click"]').click();
|
|
1084
|
+
|
|
1085
|
+
// Verify events in Micro
|
|
1086
|
+
cy.request('http://localhost:9090/micro/good').then((response) => {
|
|
1087
|
+
expect(response.body).to.have.length.greaterThan(0);
|
|
1088
|
+
});
|
|
1089
|
+
});
|
|
1090
|
+
});
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
### Benefits of Testing with Micro
|
|
1094
|
+
|
|
1095
|
+
- ✅ **No Cloud Setup**: Test locally without Snowplow cloud infrastructure
|
|
1096
|
+
- ✅ **Fast Feedback**: Instant validation of tracking implementation
|
|
1097
|
+
- ✅ **Event Inspection**: See exactly what data is being sent
|
|
1098
|
+
- ✅ **Schema Validation**: Catch schema errors before production
|
|
1099
|
+
- ✅ **CI/CD Integration**: Run in Docker containers in your pipeline
|
|
1100
|
+
- ✅ **No Data Costs**: Test without sending data to production
|
|
1101
|
+
|
|
1102
|
+
### Resources
|
|
1103
|
+
|
|
1104
|
+
- [Snowplow Micro Documentation](https://docs.snowplow.io/docs/data-product-studio/data-quality/snowplow-micro/)
|
|
1105
|
+
- [Snowplow Micro on Docker Hub](https://hub.docker.com/r/snowplow/snowplow-micro)
|
|
1106
|
+
- [Automated Testing Guide](https://docs.snowplow.io/docs/data-product-studio/data-quality/snowplow-micro/automated-testing/)
|
|
1107
|
+
|
|
1108
|
+
## Resources
|
|
1109
|
+
|
|
1110
|
+
- [Snowplow Documentation](https://docs.snowplow.io/)
|
|
1111
|
+
- [Snowplow Browser Tracker](https://docs.snowplow.io/docs/sources/trackers/web-trackers/)
|
|
1112
|
+
- [walkerOS Documentation](https://docs.elbwalker.com)
|
|
1113
|
+
- [GitHub Repository](https://github.com/elbwalker/walkerOS)
|
|
1114
|
+
|
|
1115
|
+
## License
|
|
1116
|
+
|
|
1117
|
+
MIT
|