@huloglobal/vendure-plugin-visitor-analytics 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +150 -121
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,34 +1,18 @@
1
1
  # @huloglobal/vendure-plugin-visitor-analytics
2
2
 
3
- Full-funnel visitor analytics for Vendure storefronts. Captures every
4
- pageview, time-on-page, and exit point; bundles a per-visitor profile
5
- drawer with parsed user-agent, MaxMind geo enrichment and a complete
6
- event timeline; stitches guest browsing to signed-in browsing across
7
- sessions so the journey survives login.
3
+ Self-hosted full-funnel visitor analytics for Vendure storefronts.
4
+ Pageviews, time-on-page, exit pages, configurable funnel, UTM
5
+ attribution, conversion goals with URL-glob matching, bot detection,
6
+ and a per-visitor profile drawer with parsed user-agent and MaxMind
7
+ geo. Privacy-first defaults: DNT, IP anonymisation, optional consent
8
+ gate.
8
9
 
9
10
  Maintained by Wayne Garrison.
10
11
 
11
- ## What you get
12
-
13
- - **Ingest endpoint** at `POST /ees/track` that takes a small batch of
14
- events from the storefront. Issues a long-lived visitor cookie
15
- (`ees_vid`, 2 years) and a sliding session cookie (`ees_sid`,
16
- 30-minute idle).
17
- - **Auto-enrichment** at ingest time:
18
- - User-agent parsed via `ua-parser-js` → browser / browser version /
19
- OS / OS version / device type. Bots auto-detected.
20
- - IP-to-geo via MaxMind GeoLite2-City (no MaxMind account required,
21
- DB fetched at install via `geolite2-redist`). Or use the upstream
22
- proxy's resolved country / region when Cloudflare / Akamai /
23
- Fastly is in front — saves the lookup.
24
- - Raw IP is kept; a SHA-256 salted hash is stored alongside for
25
- spot-the-same-bot work.
26
- - **Admin endpoints**: summary tiles, top pages, exit pages, funnel,
27
- recent visitors, per-visitor profile + journey timeline. All
28
- paginated.
29
- - **Admin UI**: top-line tiles, funnel bars, top + exit page tables,
30
- recent visitors with a clickable profile drawer showing every field
31
- + per-session breakdown + the full event timeline.
12
+ ## Buy
13
+
14
+ 7-day free trial then **£9.95/month**, or **£199 one-off lifetime** at
15
+ [elite.charity/licence/buy/vendure-plugin-visitor-analytics](https://elite.charity/licence/buy/vendure-plugin-visitor-analytics).
32
16
 
33
17
  ## Install
34
18
 
@@ -36,132 +20,177 @@ Maintained by Wayne Garrison.
36
20
  yarn add @huloglobal/vendure-plugin-visitor-analytics
37
21
  ```
38
22
 
39
- ## Wire up
40
-
41
23
  ```ts
42
24
  import { VisitorAnalyticsPlugin } from '@huloglobal/vendure-plugin-visitor-analytics';
43
25
 
44
26
  export const config: VendureConfig = {
45
- plugins: [
46
- VisitorAnalyticsPlugin.init({
47
- publicBaseUrl: 'https://shop.example.com',
48
- licenceKey: process.env.HULO_LICENCE_KEY,
49
- }),
50
- ],
27
+ plugins: [
28
+ VisitorAnalyticsPlugin.init({
29
+ publicBaseUrl: 'https://shop.example.com',
30
+ licenceKey: process.env.HULO_LICENCE_KEY_VISITOR_ANALYTICS,
31
+
32
+ // -- Privacy (defaults shown) --
33
+ honorDoNotTrack: true,
34
+ anonymizeIp: true,
35
+ requireConsent: false,
36
+ dropBotEvents: false,
37
+
38
+ // -- Security (recommended in production) --
39
+ signingSecret: process.env.HULO_VISITOR_SIGNING_SECRET,
40
+ corsAllowedOrigins: [
41
+ 'https://shop.example.com',
42
+ 'https://www.example.com',
43
+ ],
44
+ rateLimit: { capacity: 240, windowMs: 60_000 },
45
+
46
+ // -- Retention (opt-in) --
47
+ retention: { days: 365, maxRows: 50_000_000 },
48
+ }),
49
+ ],
51
50
  };
52
51
  ```
53
52
 
54
- Add to your admin-ui compile step:
53
+ Add `VisitorAnalyticsPlugin.uiExtensions` to your `compileUiExtensions`
54
+ config.
55
+
56
+ ## Storefront snippet
55
57
 
56
58
  ```ts
57
- import { VisitorAnalyticsPlugin } from '@huloglobal/vendure-plugin-visitor-analytics';
59
+ // utils/visitor-tracking.ts
60
+ const ENDPOINT = 'https://shop.example.com/ees/track';
61
+ const CHANNEL_ID = 1;
62
+ let queue: any[] = [];
63
+ let flushTimer: any;
64
+
65
+ export function recordPageview(url: string, title: string) {
66
+ queue.push({ type: 'pageview', url, title, clientTs: Date.now() });
67
+ scheduleFlush();
68
+ }
58
69
 
59
- compileUiExtensions({
60
- outputPath: 'admin-ui',
61
- extensions: [VisitorAnalyticsPlugin.uiExtensions /* + your other extensions */],
62
- });
63
- ```
70
+ export function recordEvent(type: string, meta: any) {
71
+ queue.push({
72
+ type, url: location.pathname + location.search,
73
+ meta, clientTs: Date.now(),
74
+ });
75
+ scheduleFlush();
76
+ }
64
77
 
65
- ## Storefront integration
78
+ function scheduleFlush() {
79
+ clearTimeout(flushTimer);
80
+ flushTimer = setTimeout(flush, 1000);
81
+ }
82
+ function flush() {
83
+ if (!queue.length) return;
84
+ const body = JSON.stringify({ channelId: CHANNEL_ID, events: queue });
85
+ queue = [];
86
+ navigator.sendBeacon?.(ENDPOINT, body) ||
87
+ fetch(ENDPOINT, {
88
+ method: 'POST', body,
89
+ headers: { 'content-type': 'application/json' }, keepalive: true,
90
+ });
91
+ }
92
+ ```
66
93
 
67
- The plugin ships only the backend; the storefront emits events. A Qwik
68
- storefront example:
94
+ Call `recordPageview()` on every route change. For custom events
95
+ (add-to-cart, search, signup, …) call `recordEvent(type, meta)` at the
96
+ appropriate point.
69
97
 
70
- ```ts
71
- // utils/tracker.ts
72
- const TRACK_URL = 'https://shop.example.com/ees/track';
73
- let lastPath = '';
74
- let pageOpenedAt = 0;
75
-
76
- export function recordPageView(): void {
77
- const url = location.pathname + location.search;
78
- const events: any[] = [];
79
- if (lastPath && lastPath !== url) {
80
- events.push({ type: 'unload', url: lastPath, timeOnPageMs: Date.now() - pageOpenedAt });
81
- }
82
- events.push({ type: 'pageview', url, title: document.title, referrer: document.referrer });
83
- lastPath = url;
84
- pageOpenedAt = Date.now();
85
- fetch(TRACK_URL, {
86
- method: 'POST',
87
- credentials: 'include',
88
- headers: { 'content-type': 'application/json' },
89
- body: JSON.stringify({ channelId: 1, events }),
90
- keepalive: true,
91
- }).catch(() => undefined);
92
- }
98
+ ## Feature tour
93
99
 
94
- // On unload, prefer sendBeacon — it survives tab-close.
95
- window.addEventListener('pagehide', () => {
96
- const blob = new Blob([JSON.stringify({
97
- channelId: 1,
98
- events: [{ type: 'unload', url: lastPath, timeOnPageMs: Date.now() - pageOpenedAt }],
99
- })], { type: 'application/json' });
100
- navigator.sendBeacon(TRACK_URL, blob);
101
- });
102
- ```
100
+ ### Lightweight ingest
103
101
 
104
- ## Custom events
102
+ - `POST /ees/track` accepts a batch of up to 50 events at once.
103
+ - Visitor + session cookies (`ees_vid`, `ees_sid`) issued + refreshed
104
+ automatically. When `signingSecret` is set, cookies are HMAC-signed
105
+ and tampered values are rejected — the visitor gets a fresh id.
106
+ - `Secure` flag is set automatically when serving over HTTPS.
105
107
 
106
- Fire a `recordEvent(type, meta?)` call from your storefront whenever a
107
- visitor does something interesting — add-to-cart, search, signup,
108
- quote-request — and the event shows up in the admin "Top events" table
109
- straight away.
108
+ ### Auto-enrichment
110
109
 
111
- ```ts
112
- function recordEvent(type, meta) {
113
- navigator.sendBeacon(TRACK_URL, new Blob([JSON.stringify({
114
- channelId: 1,
115
- events: [{ type, url: location.pathname + location.search, meta }],
116
- })], { type: 'application/json' }));
117
- }
110
+ Per event:
118
111
 
119
- // Add to cart
120
- recordEvent('add_to_cart', { sku: 'WIN11-PRO', priceWithTax: 28900 });
112
+ - **User-agent** parsed via `ua-parser-js` → browser, version, OS,
113
+ device.
114
+ - **Geo** via MaxMind GeoLite2-City (no MaxMind account required — DB
115
+ fetched at install via `geolite2-redist`). Skipped when the upstream
116
+ proxy already provides a country (Cloudflare, Akamai, Fastly).
117
+ - **UTM attribution** parsed server-side from every pageview URL:
118
+ `utmSource`, `utmMedium`, `utmCampaign`, `utmTerm`, `utmContent`. Plus
119
+ `referrerDomain` for grouping by source even when UTM is absent.
120
+ - **Bot flag** — known crawler / monitoring / library UAs (Googlebot,
121
+ Bingbot, UptimeRobot, Datadog, curl, Puppeteer, …) marked `isBot=true`.
121
122
 
122
- // Search query submitted
123
- recordEvent('search', { query: 'windows server 2022' });
123
+ ### Configurable conversion goals
124
124
 
125
- // Quote request from the contact form
126
- recordEvent('quote_request', { customerEmail });
125
+ A goal is a URL glob that, when matched, counts the visitor as having
126
+ converted. Supports `*` (within segment) and `**` (across segments).
127
127
 
128
- // Newsletter signup
129
- recordEvent('newsletter_signup', { source: 'footer' });
128
+ ```bash
129
+ curl -X POST https://shop.example.com/ees/goals -H 'content-type: application/json' \
130
+ -d '{"channelId":1,"name":"Checkout completed","urlPattern":"/checkout/thank-you/*","valueMinor":5000}'
130
131
  ```
131
132
 
132
- The full meta blob is persisted as JSON on the event row so you can
133
- slice on it later from the admin UI.
133
+ Stats at `GET /ees/goals/stats?days=30&channelId=1`.
134
134
 
135
- ## UTM attribution
135
+ ### Privacy controls
136
136
 
137
- The plugin parses `utm_source` / `utm_medium` / `utm_campaign` /
138
- `utm_term` / `utm_content` from the URL of every incoming event and
139
- captures the referrer domain alongside. The admin "Traffic sources"
140
- table groups visitors by `(source, medium)` so you can see which
141
- campaigns convert.
137
+ - `honorDoNotTrack: true` (default) — `DNT: 1` and `Sec-GPC: 1` requests
138
+ get a 200 with `{stored:0, skipped:'dnt'}`.
139
+ - `anonymizeIp: true` (default) — IPv4 last octet dropped before
140
+ storage; IPv6 reduced to the first 3 hextets. `ipHash` still uses the
141
+ raw IP so unique-visitor counts stay accurate.
142
+ - `requireConsent: false` (default) — flip on to require a `consent: true`
143
+ body field or an `ees_consent=1` cookie before ingest.
144
+ - `dropBotEvents: false` (default) — flip on to skip bot UAs entirely.
142
145
 
143
- Drop `?utm_source=google&utm_medium=cpc&utm_campaign=spring24` onto any
144
- inbound link and it surfaces automatically — no extra config.
146
+ ### Live-now widget
145
147
 
146
- ## Live now widget
148
+ SSE stream at `GET /ees/visitors/live` pushes the active-visitor count
149
+ and the 20 most recent URLs every 5 seconds. Auto-reconnects.
147
150
 
148
- The admin dashboard's top tile streams the currently-active visitor
149
- count + the URLs they're on in real time via Server-Sent Events
150
- (`GET /ees/visitors/live`). Updates every 5 seconds, reconnects
151
- automatically if the connection drops. Active = at least one event in
152
- the last 5 minutes.
151
+ ### Per-visitor journey
153
152
 
154
- ## Init options
153
+ Click any visitor for the full timeline: pages, custom events,
154
+ time-on-page, country, browser, OS.
155
155
 
156
- | Option | Type | Required | Description |
157
- | --- | --- | --- | --- |
158
- | `publicBaseUrl` | `string` | yes | Public hostname of your Vendure server (must match licence). |
159
- | `licenceKey` | `string` | no* | JWT licence key. Without it ingest works but the admin dashboards return 403. |
156
+ ### CSV export
157
+
158
+ `GET /ees/visitors/export.csv?days=N` (max 90 days) returns the raw
159
+ events with full enrichment.
160
160
 
161
- \* Required for production use. Buy at
162
- `https://elite-software.co.uk/licence/buy/vendure-plugin-visitor-analytics`.
161
+ ## HTTP endpoints
162
+
163
+ | Method | Path | Auth | Purpose |
164
+ | --- | --- | --- | --- |
165
+ | `POST` | `/ees/track` | public | ingest batch of events |
166
+ | `GET` | `/ees/visitors/summary` | admin | top-line + daily series |
167
+ | `GET` | `/ees/visitors/sources` | admin | top sources by visits |
168
+ | `GET` | `/ees/visitors/top-pages` | admin | most-visited URLs |
169
+ | `GET` | `/ees/visitors/funnel` | admin | configurable funnel |
170
+ | `GET` | `/ees/visitors/exit-pages` | admin | top exit pages |
171
+ | `GET` | `/ees/visitors/top-events` | admin | top custom events |
172
+ | `GET` | `/ees/visitors/live` | admin | SSE live-now stream |
173
+ | `GET` | `/ees/visitors/journey/:visitorId` | admin | per-visitor timeline |
174
+ | `GET` | `/ees/visitors/recent` | admin | recent events |
175
+ | `GET` | `/ees/visitors/export.csv` | admin | CSV export |
176
+ | `GET` | `/ees/goals` | admin | list conversion goals |
177
+ | `POST` | `/ees/goals` | admin | create a goal |
178
+ | `PUT` | `/ees/goals/:id` | admin | update a goal |
179
+ | `DELETE` | `/ees/goals/:id` | admin | delete a goal |
180
+ | `GET` | `/ees/goals/stats` | admin | per-goal completion stats |
181
+ | `GET` | `/ees/visitors/status` | admin | version + update status |
182
+
183
+ ## Documentation
184
+
185
+ User manual + screenshots:
186
+ [huloglobal.com/vendure-plugins/visitor-analytics/docs/](https://huloglobal.com/vendure-plugins/visitor-analytics/docs/)
187
+
188
+ ## Lost your licence key?
189
+
190
+ Re-send every active key on file at
191
+ [elite.charity/licence/forgot](https://elite.charity/licence/forgot).
163
192
 
164
193
  ## Licence
165
194
 
166
- Commercial — see [LICENSE](./LICENSE). Requires an active subscription
167
- ($9.95/mo) or a perpetual licence.
195
+ Commercial. Buy at
196
+ [elite.charity/licence/buy/vendure-plugin-visitor-analytics](https://elite.charity/licence/buy/vendure-plugin-visitor-analytics).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huloglobal/vendure-plugin-visitor-analytics",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Full-funnel visitor analytics for Vendure storefronts \u2014 pageviews, time-on-page, session journey, exit pages, funnel drop-off, and per-visitor profile drawer with parsed user-agent + MaxMind GeoLite2 enrichment. Auto-issues visitor + session cookies on first request; logs guest and signed-in events against the same visitor id so the journey survives login.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Wayne Garrison <wayne@garrison.me.uk>",