@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 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