@huloglobal/vendure-plugin-visitor-analytics 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -0
- package/dist/hulo-storefront.d.ts +17 -0
- package/dist/hulo-storefront.d.ts.map +1 -0
- package/dist/hulo-storefront.js +289 -0
- package/dist/hulo-storefront.js.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -0
- package/dist/plugin.js.map +1 -1
- package/dist/storefront-helper.controller.d.ts +16 -0
- package/dist/storefront-helper.controller.d.ts.map +1 -0
- package/dist/storefront-helper.controller.js +56 -0
- package/dist/storefront-helper.controller.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,87 @@ documented here. The format follows
|
|
|
5
5
|
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project
|
|
6
6
|
adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.8.0] — 2026-07-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
**Cart abandonment — end-to-end.**
|
|
13
|
+
- New `AbandonedCart` entity, keyed on session id. One row per
|
|
14
|
+
abandoned session, refreshed in place until it either converts
|
|
15
|
+
(order placed) or expires (recovery window elapses).
|
|
16
|
+
- `AbandonedCartService.scan()` — periodic sweep finds sessions with
|
|
17
|
+
`cart_snapshot` events but no `checkout_completed` in the
|
|
18
|
+
abandonment window (default 30 min). Auto-promotes previously
|
|
19
|
+
abandoned rows to `converted` when the customer later checks out.
|
|
20
|
+
- Ships with a boot-time timer (worker-only, 5-minute cadence).
|
|
21
|
+
Idempotent — safe to horizontally scale, only the worker runs it.
|
|
22
|
+
- Signed recovery links — `POST /ees/abandoned-carts/:id/recovery-link`
|
|
23
|
+
returns a time-bounded opaque token the storefront exchanges via
|
|
24
|
+
`GET /ees/recover-cart?t=…` to rebuild the exact cart. Storefront
|
|
25
|
+
never sees the underlying items until the token is presented.
|
|
26
|
+
- Slack notification for high-value abandonments — configurable
|
|
27
|
+
threshold and webhook via the `abandonment` plugin option.
|
|
28
|
+
- Admin API:
|
|
29
|
+
- `GET /ees/abandoned-carts` — paginated list with status,
|
|
30
|
+
value and email filters.
|
|
31
|
+
- `GET /ees/abandoned-carts/summary` — totals, recovery rate,
|
|
32
|
+
recovered vs. lost value in the window.
|
|
33
|
+
- `GET /ees/abandoned-carts/:id` — detail incl. parsed items.
|
|
34
|
+
- `POST /ees/abandoned-carts/:id/status` — mark recovered /
|
|
35
|
+
dismissed / re-open.
|
|
36
|
+
- `GET /ees/abandoned-carts/export.csv` — CSV export.
|
|
37
|
+
|
|
38
|
+
**Co-view product recommendations.**
|
|
39
|
+
- New `ProductCoView` aggregate table. Scanner walks recent
|
|
40
|
+
`product_view` events per session, extracts every ordered pair,
|
|
41
|
+
and increments a per-triple counter. Bounded to 20 events per
|
|
42
|
+
session so runaway bot sessions can't skew the table.
|
|
43
|
+
- Denormalised — both `(A, B)` and `(B, A)` stored — so read-side
|
|
44
|
+
lookups are one indexed scan.
|
|
45
|
+
- Runs every 6 hours on the worker; also exposed as
|
|
46
|
+
`GET /ees/recommendations/aggregate-now` for admins to kick a
|
|
47
|
+
fresh run after a data backfill.
|
|
48
|
+
- Public read endpoints (safe from the storefront):
|
|
49
|
+
- `GET /ees/recommendations/also-viewed?productId=…` — the
|
|
50
|
+
"customers who viewed X also viewed…" rail on a product page.
|
|
51
|
+
- `GET /ees/recommendations/personal?visitorId=…` — personalised
|
|
52
|
+
recs for a returning visitor, from their last 10 product views
|
|
53
|
+
over 30 days. Excludes seeds.
|
|
54
|
+
- `GET /ees/recommendations/trending?hours=24` — most-viewed
|
|
55
|
+
products in the window. Reflects real interest, not search-
|
|
56
|
+
console clicks.
|
|
57
|
+
|
|
58
|
+
**Site search analytics.**
|
|
59
|
+
- Reads back over `visitor_event` where the storefront has fired
|
|
60
|
+
`hulo.search(query, resultsCount)`. Zero new schema.
|
|
61
|
+
- `GET /ees/search-analytics/top` — top queries by volume with
|
|
62
|
+
average results count.
|
|
63
|
+
- `GET /ees/search-analytics/no-results` — top zero-result queries.
|
|
64
|
+
Direct catalogue-gap intel.
|
|
65
|
+
- `GET /ees/search-analytics/conversion` — of sessions that
|
|
66
|
+
searched, what fraction went on to fire `add_to_cart`.
|
|
67
|
+
|
|
68
|
+
**Journey drawer buffs.**
|
|
69
|
+
- Rage-click + dead-click aggregation, keyed on URL. Store-wide
|
|
70
|
+
hot-spot lists for pages where visitors are stuck or frustrated.
|
|
71
|
+
- Per-visitor session summary with a heuristic `intent` label
|
|
72
|
+
(`purchase` / `abandon` / `frustrate` / `consider` / `browse` /
|
|
73
|
+
`bounce`) computed from event history — one glance per session
|
|
74
|
+
in the Journey drawer instead of scrolling event rows.
|
|
75
|
+
|
|
76
|
+
**Storefront helper events (documented in the README).**
|
|
77
|
+
- `hulo.cartSnapshot({ currency, totalMinor, itemCount, items, email })`
|
|
78
|
+
- `hulo.productView(productId, productVariantId?)`
|
|
79
|
+
- `hulo.search(query, resultsCount)`
|
|
80
|
+
- `hulo.rageClick(url, selector?)` / `hulo.deadClick(url, selector)`
|
|
81
|
+
- `hulo.checkoutCompleted()`
|
|
82
|
+
- All flow into the existing `POST /ees/track` endpoint with a
|
|
83
|
+
standard shape, so admins can also fire them from any language.
|
|
84
|
+
|
|
85
|
+
### Changed
|
|
86
|
+
- `checkout_completed` is now a first-class recognised event type
|
|
87
|
+
— the abandonment scanner uses it to auto-close matched rows.
|
|
88
|
+
|
|
8
89
|
## [0.7.0] — 2026-07-04
|
|
9
90
|
|
|
10
91
|
### Added
|
|
@@ -149,6 +230,7 @@ adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
149
230
|
- Licence verification via `@huloglobal/vendure-licence-sdk` with
|
|
150
231
|
revocation polling.
|
|
151
232
|
|
|
233
|
+
[0.8.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.8.0
|
|
152
234
|
[0.7.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.7.0
|
|
153
235
|
[0.6.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.6.0
|
|
154
236
|
[0.5.0]: https://github.com/exceeded/vendure-plugin-visitor-analytics/releases/tag/v0.5.0
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single-file storefront helper the plugin serves at /ees/hulo.js.
|
|
3
|
+
*
|
|
4
|
+
* Written as a string constant (not a bundled module) because:
|
|
5
|
+
* - It's tiny (< 6kB minified) — a bundler adds more than the code
|
|
6
|
+
* - It ships to the browser verbatim, no compile step
|
|
7
|
+
* - Customers can inspect / audit it without a source map
|
|
8
|
+
* - It has ZERO npm dependencies at runtime
|
|
9
|
+
*
|
|
10
|
+
* The controller wraps this in a small template that injects the
|
|
11
|
+
* public base URL and a default channel id, so a storefront paste
|
|
12
|
+
* of `<script src="https://backend/ees/hulo.js"></script>` Just Works
|
|
13
|
+
* with no config. Advanced users can call `hulo.configure({ ... })`
|
|
14
|
+
* to override.
|
|
15
|
+
*/
|
|
16
|
+
export declare const HULO_STOREFRONT_JS: (backendBaseUrl: string, defaultChannelId?: number) => string;
|
|
17
|
+
//# sourceMappingURL=hulo-storefront.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hulo-storefront.d.ts","sourceRoot":"","sources":["../src/hulo-storefront.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,GAAI,gBAAgB,MAAM,EAAE,yBAAoB,KAAG,MA4QjF,CAAC"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HULO_STOREFRONT_JS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The single-file storefront helper the plugin serves at /ees/hulo.js.
|
|
6
|
+
*
|
|
7
|
+
* Written as a string constant (not a bundled module) because:
|
|
8
|
+
* - It's tiny (< 6kB minified) — a bundler adds more than the code
|
|
9
|
+
* - It ships to the browser verbatim, no compile step
|
|
10
|
+
* - Customers can inspect / audit it without a source map
|
|
11
|
+
* - It has ZERO npm dependencies at runtime
|
|
12
|
+
*
|
|
13
|
+
* The controller wraps this in a small template that injects the
|
|
14
|
+
* public base URL and a default channel id, so a storefront paste
|
|
15
|
+
* of `<script src="https://backend/ees/hulo.js"></script>` Just Works
|
|
16
|
+
* with no config. Advanced users can call `hulo.configure({ ... })`
|
|
17
|
+
* to override.
|
|
18
|
+
*/
|
|
19
|
+
const HULO_STOREFRONT_JS = (backendBaseUrl, defaultChannelId = 1) => `
|
|
20
|
+
// hulo storefront helper — @huloglobal/vendure-plugin-visitor-analytics
|
|
21
|
+
// Public domain when served from your own store; do not resell.
|
|
22
|
+
(function (global) {
|
|
23
|
+
var CONFIG = {
|
|
24
|
+
endpoint: ${JSON.stringify(backendBaseUrl.replace(/\/$/, '') + '/ees/track')},
|
|
25
|
+
recoverEndpoint: ${JSON.stringify(backendBaseUrl.replace(/\/$/, '') + '/ees/recover-cart')},
|
|
26
|
+
channelId: ${defaultChannelId},
|
|
27
|
+
debug: false,
|
|
28
|
+
autoRageClick: true,
|
|
29
|
+
autoDeadClick: true,
|
|
30
|
+
autoScroll: false, // not implemented yet — flag reserved
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ── queue + batched send ─────────────────────────────────────────
|
|
34
|
+
// Every helper below pushes into a queue. Flush on requestIdleCallback
|
|
35
|
+
// or 2s max. Coalesces bursts of events into one HTTP roundtrip.
|
|
36
|
+
var QUEUE = [];
|
|
37
|
+
var FLUSH_TIMER = null;
|
|
38
|
+
function enqueue(event) {
|
|
39
|
+
event.url = event.url || location.href;
|
|
40
|
+
event.channelId = event.channelId || CONFIG.channelId;
|
|
41
|
+
QUEUE.push(event);
|
|
42
|
+
scheduleFlush();
|
|
43
|
+
if (CONFIG.debug) console.log('[hulo]', event);
|
|
44
|
+
}
|
|
45
|
+
function scheduleFlush() {
|
|
46
|
+
if (FLUSH_TIMER) return;
|
|
47
|
+
FLUSH_TIMER = setTimeout(flush, 1500);
|
|
48
|
+
}
|
|
49
|
+
function flush() {
|
|
50
|
+
FLUSH_TIMER = null;
|
|
51
|
+
if (!QUEUE.length) return;
|
|
52
|
+
var batch = QUEUE.splice(0, QUEUE.length);
|
|
53
|
+
var payload = JSON.stringify({ events: batch });
|
|
54
|
+
// Prefer sendBeacon on unload (survives page transitions) and
|
|
55
|
+
// fall back to fetch keepalive for normal calls.
|
|
56
|
+
try {
|
|
57
|
+
if (navigator.sendBeacon) {
|
|
58
|
+
var blob = new Blob([payload], { type: 'application/json' });
|
|
59
|
+
if (navigator.sendBeacon(CONFIG.endpoint, blob)) return;
|
|
60
|
+
}
|
|
61
|
+
} catch (_e) {}
|
|
62
|
+
try {
|
|
63
|
+
fetch(CONFIG.endpoint, {
|
|
64
|
+
method: 'POST', credentials: 'include', keepalive: true,
|
|
65
|
+
headers: { 'content-type': 'application/json' },
|
|
66
|
+
body: payload,
|
|
67
|
+
}).catch(function () { /* silent */ });
|
|
68
|
+
} catch (_e) {}
|
|
69
|
+
}
|
|
70
|
+
// Flush before the page dies.
|
|
71
|
+
window.addEventListener('pagehide', flush, { capture: true });
|
|
72
|
+
window.addEventListener('beforeunload', flush, { capture: true });
|
|
73
|
+
|
|
74
|
+
// ── public API ───────────────────────────────────────────────────
|
|
75
|
+
var hulo = {
|
|
76
|
+
configure: function (opts) { Object.assign(CONFIG, opts || {}); },
|
|
77
|
+
flush: flush,
|
|
78
|
+
|
|
79
|
+
/** Fire once per pageview. Optional — the ingest endpoint infers
|
|
80
|
+
* pageviews from the referrer / URL, but calling this explicitly
|
|
81
|
+
* captures the title too. */
|
|
82
|
+
pageview: function (extra) {
|
|
83
|
+
enqueue(Object.assign({ type: 'pageview', title: document.title }, extra || {}));
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/** Product detail viewed. Store the product id you would use to
|
|
87
|
+
* fetch the product server-side (Vendure product.id). If your
|
|
88
|
+
* storefront uses variants primarily, pass variantId too. */
|
|
89
|
+
productView: function (productId, productVariantId) {
|
|
90
|
+
if (!productId && productId !== 0) return;
|
|
91
|
+
enqueue({
|
|
92
|
+
type: 'event',
|
|
93
|
+
meta: JSON.stringify({
|
|
94
|
+
eventType: 'product_view',
|
|
95
|
+
productId: Number(productId),
|
|
96
|
+
productVariantId: productVariantId != null ? Number(productVariantId) : undefined,
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/** Item added to cart. Fire on every add — not just the first. */
|
|
102
|
+
addToCart: function (variantId, qty, unitPriceMinor) {
|
|
103
|
+
enqueue({
|
|
104
|
+
type: 'event',
|
|
105
|
+
meta: JSON.stringify({
|
|
106
|
+
eventType: 'add_to_cart',
|
|
107
|
+
variantId: variantId != null ? Number(variantId) : undefined,
|
|
108
|
+
qty: qty != null ? Number(qty) : 1,
|
|
109
|
+
unitPriceMinor: unitPriceMinor != null ? Number(unitPriceMinor) : undefined,
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/** Cart snapshot — fire on every cart change (add / remove / qty).
|
|
115
|
+
* The abandonment scanner reads only this event, so fire it
|
|
116
|
+
* liberally. Payload shape:
|
|
117
|
+
* { currency, totalMinor, itemCount, items: [{ variantId, name, qty, unitPriceMinor }], email? } */
|
|
118
|
+
cartSnapshot: function (cart) {
|
|
119
|
+
if (!cart || !cart.items || !cart.items.length) return;
|
|
120
|
+
enqueue({
|
|
121
|
+
type: 'event',
|
|
122
|
+
meta: JSON.stringify({
|
|
123
|
+
eventType: 'cart_snapshot',
|
|
124
|
+
currency: (cart.currency || 'GBP').toUpperCase().slice(0, 3),
|
|
125
|
+
totalMinor: Number(cart.totalMinor || 0),
|
|
126
|
+
itemCount: Number(cart.itemCount || cart.items.length),
|
|
127
|
+
items: cart.items.map(function (i) {
|
|
128
|
+
return {
|
|
129
|
+
variantId: i.variantId != null ? Number(i.variantId) : undefined,
|
|
130
|
+
productId: i.productId != null ? Number(i.productId) : undefined,
|
|
131
|
+
name: i.name != null ? String(i.name).slice(0, 200) : undefined,
|
|
132
|
+
qty: Number(i.qty != null ? i.qty : (i.quantity != null ? i.quantity : 1)),
|
|
133
|
+
unitPriceMinor: Number(i.unitPriceMinor != null ? i.unitPriceMinor : (i.unitPrice != null ? i.unitPrice : 0)),
|
|
134
|
+
sku: i.sku != null ? String(i.sku).slice(0, 100) : undefined,
|
|
135
|
+
};
|
|
136
|
+
}).slice(0, 100),
|
|
137
|
+
email: cart.email || undefined,
|
|
138
|
+
countryCode: cart.countryCode || undefined,
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/** Storefront site search executed. Pass the raw query + the
|
|
144
|
+
* resulting hit count so no-result queries can be aggregated. */
|
|
145
|
+
search: function (query, resultsCount) {
|
|
146
|
+
var q = String(query || '').trim();
|
|
147
|
+
if (!q) return;
|
|
148
|
+
enqueue({
|
|
149
|
+
type: 'event',
|
|
150
|
+
meta: JSON.stringify({
|
|
151
|
+
eventType: 'search',
|
|
152
|
+
query: q.slice(0, 200),
|
|
153
|
+
resultsCount: Math.max(0, Number(resultsCount || 0)),
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/** Checkout finished — fires the counter that closes any open
|
|
159
|
+
* abandoned-cart row for this session. Call from your thank-you
|
|
160
|
+
* page. Optionally pass the order code + total. */
|
|
161
|
+
checkoutCompleted: function (orderCode, totalMinor) {
|
|
162
|
+
enqueue({
|
|
163
|
+
type: 'event',
|
|
164
|
+
meta: JSON.stringify({
|
|
165
|
+
eventType: 'checkout_completed',
|
|
166
|
+
orderCode: orderCode ? String(orderCode).slice(0, 64) : undefined,
|
|
167
|
+
totalMinor: totalMinor != null ? Number(totalMinor) : undefined,
|
|
168
|
+
}),
|
|
169
|
+
});
|
|
170
|
+
flush(); // don't wait — the visitor may close the tab immediately
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/** Rage-click event — fire it yourself if you have a better signal
|
|
174
|
+
* than the built-in detector. */
|
|
175
|
+
rageClick: function (selector, extraMeta) {
|
|
176
|
+
enqueue({
|
|
177
|
+
type: 'event',
|
|
178
|
+
meta: JSON.stringify(Object.assign({
|
|
179
|
+
eventType: 'rage_click',
|
|
180
|
+
selector: selector ? String(selector).slice(0, 200) : undefined,
|
|
181
|
+
}, extraMeta || {})),
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/** Click landed on a non-interactive element. */
|
|
186
|
+
deadClick: function (selector, extraMeta) {
|
|
187
|
+
enqueue({
|
|
188
|
+
type: 'event',
|
|
189
|
+
meta: JSON.stringify(Object.assign({
|
|
190
|
+
eventType: 'dead_click',
|
|
191
|
+
selector: selector ? String(selector).slice(0, 200) : undefined,
|
|
192
|
+
}, extraMeta || {})),
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Restore a cart from a signed recovery token. Fetches the items
|
|
198
|
+
* from the backend and returns them. Your storefront is responsible
|
|
199
|
+
* for calling its own Vendure order API to actually re-add them,
|
|
200
|
+
* because different storefronts frame that call differently.
|
|
201
|
+
*/
|
|
202
|
+
restoreCart: function (token) {
|
|
203
|
+
var t = String(token || '').trim();
|
|
204
|
+
if (!t) return Promise.reject(new Error('missing-token'));
|
|
205
|
+
return fetch(CONFIG.recoverEndpoint + '?t=' + encodeURIComponent(t), {
|
|
206
|
+
credentials: 'include',
|
|
207
|
+
}).then(function (r) { return r.json(); });
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// ── auto rage-click detector ─────────────────────────────────────
|
|
212
|
+
// Any pointerdown ≥ 3 within 500ms in a 20x20px zone counts.
|
|
213
|
+
if (CONFIG.autoRageClick) {
|
|
214
|
+
var recent = [];
|
|
215
|
+
document.addEventListener('pointerdown', function (e) {
|
|
216
|
+
var now = Date.now();
|
|
217
|
+
recent = recent.filter(function (r) { return now - r.t < 500; });
|
|
218
|
+
recent.push({ t: now, x: e.clientX, y: e.clientY, target: e.target });
|
|
219
|
+
var close = recent.filter(function (r) {
|
|
220
|
+
return Math.abs(r.x - e.clientX) < 20 && Math.abs(r.y - e.clientY) < 20;
|
|
221
|
+
});
|
|
222
|
+
if (close.length >= 3) {
|
|
223
|
+
hulo.rageClick(cssPathOf(e.target));
|
|
224
|
+
recent = []; // reset so we don't fire again on the 4th, 5th, etc.
|
|
225
|
+
}
|
|
226
|
+
}, { capture: true, passive: true });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── auto dead-click detector ─────────────────────────────────────
|
|
230
|
+
// A click that lands on an element with no href / not a button /
|
|
231
|
+
// not a form control and doesn't produce a navigation within 400ms.
|
|
232
|
+
if (CONFIG.autoDeadClick) {
|
|
233
|
+
document.addEventListener('click', function (e) {
|
|
234
|
+
var target = e.target;
|
|
235
|
+
if (!target || target.nodeType !== 1) return;
|
|
236
|
+
if (isInteractive(target)) return;
|
|
237
|
+
var beforeUrl = location.href;
|
|
238
|
+
var beforeScroll = window.scrollY;
|
|
239
|
+
setTimeout(function () {
|
|
240
|
+
if (location.href !== beforeUrl) return;
|
|
241
|
+
if (Math.abs(window.scrollY - beforeScroll) > 50) return;
|
|
242
|
+
// Was there an ancestor with a click handler? Best-effort:
|
|
243
|
+
// walk up and check for onclick, role='button', tabindex.
|
|
244
|
+
var node = target;
|
|
245
|
+
for (var i = 0; i < 4 && node; i++) {
|
|
246
|
+
if (isInteractive(node) || node.onclick) return;
|
|
247
|
+
node = node.parentElement;
|
|
248
|
+
}
|
|
249
|
+
hulo.deadClick(cssPathOf(target));
|
|
250
|
+
}, 400);
|
|
251
|
+
}, { capture: true, passive: true });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isInteractive(el) {
|
|
255
|
+
if (!el || !el.tagName) return false;
|
|
256
|
+
var tag = el.tagName.toLowerCase();
|
|
257
|
+
if (tag === 'a' || tag === 'button' || tag === 'input' || tag === 'select'
|
|
258
|
+
|| tag === 'textarea' || tag === 'label' || tag === 'summary'
|
|
259
|
+
|| tag === 'details') return true;
|
|
260
|
+
if (el.getAttribute && (el.getAttribute('role') === 'button'
|
|
261
|
+
|| el.getAttribute('role') === 'link'
|
|
262
|
+
|| el.hasAttribute('tabindex'))) return true;
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function cssPathOf(el) {
|
|
267
|
+
if (!el || el.nodeType !== 1) return '';
|
|
268
|
+
var parts = [];
|
|
269
|
+
while (el && el.nodeType === 1 && parts.length < 5) {
|
|
270
|
+
var p = el.tagName.toLowerCase();
|
|
271
|
+
if (el.id) { p += '#' + el.id; parts.unshift(p); break; }
|
|
272
|
+
if (el.className && typeof el.className === 'string') {
|
|
273
|
+
var cls = el.className.trim().split(/\\s+/).slice(0, 2).join('.');
|
|
274
|
+
if (cls) p += '.' + cls;
|
|
275
|
+
}
|
|
276
|
+
parts.unshift(p);
|
|
277
|
+
el = el.parentElement;
|
|
278
|
+
}
|
|
279
|
+
return parts.join(' > ');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
global.hulo = hulo;
|
|
283
|
+
// Auto pageview on load — most callers want it.
|
|
284
|
+
if (document.readyState === 'complete') hulo.pageview();
|
|
285
|
+
else window.addEventListener('load', function () { hulo.pageview(); });
|
|
286
|
+
})(window);
|
|
287
|
+
`;
|
|
288
|
+
exports.HULO_STOREFRONT_JS = HULO_STOREFRONT_JS;
|
|
289
|
+
//# sourceMappingURL=hulo-storefront.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hulo-storefront.js","sourceRoot":"","sources":["../src/hulo-storefront.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;GAcG;AACI,MAAM,kBAAkB,GAAG,CAAC,cAAsB,EAAE,gBAAgB,GAAG,CAAC,EAAU,EAAE,CAAC;;;;;gBAK5E,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,YAAY,CAAC;uBACzD,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,mBAAmB,CAAC;iBAC7E,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqQhC,CAAC;AA5QW,QAAA,kBAAkB,sBA4Q7B"}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,IAAI,EAAiB,MAAM,eAAe,CAAC;AACxE,OAAO,EAAmC,aAAa,EAAE,gBAAgB,EAAqB,aAAa,EAA4C,MAAM,iCAAiC,CAAC;AAM/L,OAAO,EAAwB,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,IAAI,EAAiB,MAAM,eAAe,CAAC;AACxE,OAAO,EAAmC,aAAa,EAAE,gBAAgB,EAAqB,aAAa,EAA4C,MAAM,iCAAiC,CAAC;AAM/L,OAAO,EAAwB,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAiBpF,MAAM,WAAW,6BAA6B;IAC1C,qEAAqE;IACrE,aAAa,EAAE,MAAM,CAAC;IACtB;;;gDAG4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB;6EACyE;IACzE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;yEAEqE;IACrE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;qEAEiE;IACjE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;6CAEyC;IACzC,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,mDAAmD;IACnD,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD;;yEAEqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;2EAEuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAG9B,8DAA8D;IAC9D,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAG7B;;4DAEwD;IACxD,WAAW,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAgBD,QAAA,MAAM,eAAe,EAAE,6BAMtB,CAAC;AAGF,wBAAgB,UAAU,IAAI,OAAO,eAAe,GAAG,6BAA6B,CAEnF;AAED;;;;;;;;;;;GAWG;AACH,qBA0Ba,sBAAsB;IAC/B,OAAO,CAAC,MAAM,CAAC,UAAU,CAAkC;IAC3D,OAAO,CAAC,MAAM,CAAC,aAAa,CAA8B;IAC1D,OAAO,CAAC,MAAM,CAAC,SAAS,CAA0B;IAClD,OAAO,CAAC,MAAM,CAAC,aAAa,CAA8B;IAE1D,MAAM,CAAC,gBAAgB,IAAI,aAAa,GAAG,IAAI;IAC/C,MAAM,CAAC,iBAAiB,IAAI,MAAM;IAClC,MAAM,CAAC,cAAc,IAAI,MAAM;IAC/B,MAAM,CAAC,gBAAgB,IAAI,aAAa,GAAG,IAAI;IAE/C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,6BAA6B,GAAG,IAAI,CAAC,sBAAsB,CAAC;IAoDjF,MAAM,CAAC,YAAY;;;;;;;;MAUjB;CACL"}
|
package/dist/plugin.js
CHANGED
|
@@ -27,6 +27,7 @@ const abandoned_cart_controller_1 = require("./abandoned-cart.controller");
|
|
|
27
27
|
const recommendations_controller_1 = require("./recommendations.controller");
|
|
28
28
|
const search_analytics_controller_1 = require("./search-analytics.controller");
|
|
29
29
|
const journey_buffs_controller_1 = require("./journey-buffs.controller");
|
|
30
|
+
const storefront_helper_controller_1 = require("./storefront-helper.controller");
|
|
30
31
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
31
32
|
const PKG_VERSION = require('../package.json').version;
|
|
32
33
|
const PKG_NAME = '@huloglobal/vendure-plugin-visitor-analytics';
|
|
@@ -148,6 +149,7 @@ exports.VisitorAnalyticsPlugin = VisitorAnalyticsPlugin = VisitorAnalyticsPlugin
|
|
|
148
149
|
recommendations_controller_1.RecommendationsController,
|
|
149
150
|
search_analytics_controller_1.SearchAnalyticsController,
|
|
150
151
|
journey_buffs_controller_1.JourneyBuffsController,
|
|
152
|
+
storefront_helper_controller_1.StorefrontHelperController,
|
|
151
153
|
],
|
|
152
154
|
entities: [visitor_event_entity_1.VisitorEvent, conversion_goal_entity_1.ConversionGoal, abandoned_cart_entity_1.AbandonedCart, product_co_view_entity_1.ProductCoView],
|
|
153
155
|
compatibility: '^3.0.0',
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";;;;;;;;;;AAgGA,gCAEC;AAlGD,wCAAwE;AACxE,yEAA+L;AAC/L,qEAA0D;AAC1D,iEAAsD;AACtD,mEAAwD;AACxD,qEAAyD;AACzD,yEAAoE;AACpE,qEAAoF;AACpF,uEAAmE;AACnE,yEAAoE;AACpE,mEAA8D;AAC9D,qEAAgE;AAChE,2CAA4F;AAC5F,+EAA0E;AAC1E,2EAAsE;AACtE,6EAAyE;AACzE,+EAA0E;AAC1E,yEAAoE;AACpE,iFAA4E;AAE5E,8DAA8D;AAC9D,MAAM,WAAW,GAAW,OAAO,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC;AAC/D,MAAM,QAAQ,GAAG,8CAA8C,CAAC;AAmDhE,MAAM,eAAe,GAAG;;;;;;;;yBAQC,CAAC;AAE1B,MAAM,SAAS,GAAG,kCAAkC,CAAC;AACrD,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B;OACvD,4CAA4C,CAAC;AAEpD,MAAM,eAAe,GAAkC;IACnD,aAAa,EAAE,uBAAuB;IACtC,eAAe,EAAE,IAAI;IACrB,WAAW,EAAE,IAAI;IACjB,cAAc,EAAE,KAAK;IACrB,aAAa,EAAE,KAAK;CACvB,CAAC;AAEF,IAAI,aAAa,GAAkC,eAAsB,CAAC;AAC1E,SAAgB,UAAU;IACtB,OAAO,EAAE,GAAG,eAAe,EAAE,GAAG,aAAa,EAAS,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;GAWG;AA2BI,IAAM,sBAAsB,8BAA5B,MAAM,sBAAsB;IAM/B,MAAM,CAAC,gBAAgB,KAA2B,OAAO,wBAAsB,CAAC,aAAa,CAAC,CAAC,CAAC;IAChG,MAAM,CAAC,iBAAiB,KAAa,OAAO,WAAW,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,cAAc,KAAa,OAAO,QAAQ,CAAC,CAAC,CAAC;IACpD,MAAM,CAAC,gBAAgB,KAA2B,OAAO,wBAAsB,CAAC,aAAa,CAAC,CAAC,CAAC;IAEhG,MAAM,CAAC,IAAI,CAAC,OAAsC;QAC9C,aAAa,GAAG,OAAO,CAAC;QAExB,yDAAyD;QACzD,iDAAiD;QACjD,IAAA,+CAAyB,EAAC;YACtB,iBAAiB,EAAE,QAAQ;YAC3B,oBAAoB,EAAE,WAAW;YACjC,cAAc,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE;SACjD,CAAC,CAAC;QAEH,IAAI,CAAC,wBAAsB,CAAC,UAAU,EAAE,CAAC;YACrC,wBAAsB,CAAC,UAAU,GAAG,IAAI,uCAAiB,CAAC,cAAc,CAAC,CAAC;YAC1E,wBAAsB,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAC9C,CAAC;QACD,IAAI,CAAC,wBAAsB,CAAC,aAAa,EAAE,CAAC;YACxC,wBAAsB,CAAC,aAAa,GAAG,IAAI,mCAAa,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YAChF,wBAAsB,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;QACjD,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;aACrC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,IAAA,mCAAa,EAAC;YACzB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,QAAQ,EAAE,SAAS;YACnB,IAAI;YACJ,SAAS,EAAE,eAAe;YAC1B,UAAU,EAAE,wBAAsB,CAAC,UAAU,CAAC,aAAa,EAAE;SAChE,CAAC,CAAC;QACH,wBAAsB,CAAC,aAAa,GAAG,MAAM,CAAC;QAE9C,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAChB,sCAAsC;YACtC,OAAO,CAAC,IAAI,CACR,kDAAkD,MAAM,CAAC,OAAO,EAAE;gBAClE,2KAA2K,SAAS,EAAE,CACzL,CAAC;QACN,CAAC;QAED,IAAI,CAAC,wBAAsB,CAAC,SAAS,EAAE,CAAC;YACpC,wBAAsB,CAAC,SAAS,GAAG,IAAI,+BAAS,CAAC;gBAC7C,WAAW,EAAE,QAAQ;gBACrB,cAAc,EAAE,WAAW;gBAC3B,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,oBAAoB,EAAE,IAAA,0CAAoB,EAAC,eAAe,CAAC;aAC9D,CAAC,CAAC;YACH,wBAAsB,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QAC7C,CAAC;QAED,OAAO,wBAAsB,CAAC;IAClC,CAAC;;AA7DQ,wDAAsB;AAChB,iCAAU,GAA6B,IAAI,AAAjC,CAAkC;AAC5C,oCAAa,GAAyB,IAAI,AAA7B,CAA8B;AAC3C,gCAAS,GAAqB,IAAI,AAAzB,CAA0B;AACnC,oCAAa,GAAyB,IAAI,AAA7B,CAA8B;AA2DnD,mCAAY,GAAG;IAClB,aAAa,EAAE,SAAS,GAAG,QAAQ;IACnC,SAAS,EAAE;QACP;YACI,IAAI,EAAE,MAAe;YACrB,KAAK,EAAE,UAAU;YACjB,gBAAgB,EAAE,oBAAoB;YACtC,YAAY,EAAE,gBAAgB;SACjC;KACJ;CACJ,AAVkB,CAUjB;iCAzEO,sBAAsB;IA1BlC,IAAA,oBAAa,EAAC;QACX,OAAO,EAAE,CAAC,yBAAkB,CAAC;QAC7B,SAAS,EAAE;YACP,iDAAsB;YACtB,yCAA6B;YAC7B,6CAAoB;YACpB,gDAAsB;YACtB,iDAAsB;YACtB,2CAAmB;YACnB,6CAAoB;SACvB;QACD,WAAW,EAAE;YACT,uDAAyB;YACzB,mDAAuB;YACvB,sDAAyB;YACzB,uDAAyB;YACzB,iDAAsB;YACtB,yDAA0B;SAC7B;QACD,QAAQ,EAAE,CAAC,mCAAY,EAAE,uCAAc,EAAE,qCAAa,EAAE,sCAAa,CAAC;QACtE,aAAa,EAAE,QAAQ;QACvB,kBAAkB,EAAE;YAChB,MAAM,EAAE,0CAA8B;YACtC,SAAS,EAAE,CAAC,yCAA6B,CAAC;SAC7C;KACJ,CAAC;GACW,sBAAsB,CA0ElC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Serves the storefront helper at `/ees/hulo.js`. Storefronts embed
|
|
4
|
+
* one script tag and every helper (cartSnapshot, productView, search,
|
|
5
|
+
* rageClick+deadClick auto-detectors, checkoutCompleted, restoreCart)
|
|
6
|
+
* is available on `window.hulo`.
|
|
7
|
+
*
|
|
8
|
+
* Cache: 10-minute browser TTL + 24-hour SWR. The file's content is
|
|
9
|
+
* a function of plugin options; storefronts don't need to bust the
|
|
10
|
+
* cache to see config changes because the URL stays stable and 10
|
|
11
|
+
* minutes is short enough that dashboard tweaks propagate promptly.
|
|
12
|
+
*/
|
|
13
|
+
export declare class StorefrontHelperController {
|
|
14
|
+
hulo(req: Request, res: Response): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=storefront-helper.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storefront-helper.controller.d.ts","sourceRoot":"","sources":["../src/storefront-helper.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAI5C;;;;;;;;;;GAUG;AACH,qBACa,0BAA0B;IAEnC,IAAI,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;CAYjD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.StorefrontHelperController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const hulo_storefront_1 = require("./hulo-storefront");
|
|
18
|
+
const plugin_1 = require("./plugin");
|
|
19
|
+
/**
|
|
20
|
+
* Serves the storefront helper at `/ees/hulo.js`. Storefronts embed
|
|
21
|
+
* one script tag and every helper (cartSnapshot, productView, search,
|
|
22
|
+
* rageClick+deadClick auto-detectors, checkoutCompleted, restoreCart)
|
|
23
|
+
* is available on `window.hulo`.
|
|
24
|
+
*
|
|
25
|
+
* Cache: 10-minute browser TTL + 24-hour SWR. The file's content is
|
|
26
|
+
* a function of plugin options; storefronts don't need to bust the
|
|
27
|
+
* cache to see config changes because the URL stays stable and 10
|
|
28
|
+
* minutes is short enough that dashboard tweaks propagate promptly.
|
|
29
|
+
*/
|
|
30
|
+
let StorefrontHelperController = class StorefrontHelperController {
|
|
31
|
+
hulo(req, res) {
|
|
32
|
+
const opts = (0, plugin_1.getOptions)();
|
|
33
|
+
const base = opts.publicBaseUrl
|
|
34
|
+
|| `https://${req.headers.host || 'localhost'}`;
|
|
35
|
+
const js = (0, hulo_storefront_1.HULO_STOREFRONT_JS)(base, 1);
|
|
36
|
+
res.setHeader('content-type', 'application/javascript; charset=utf-8');
|
|
37
|
+
res.setHeader('cache-control', 'public, max-age=600, stale-while-revalidate=86400');
|
|
38
|
+
// Storefront lives on a different origin from the backend —
|
|
39
|
+
// static-JS CORS is safe (no credentials) but be explicit.
|
|
40
|
+
res.setHeader('access-control-allow-origin', '*');
|
|
41
|
+
res.send(js);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
exports.StorefrontHelperController = StorefrontHelperController;
|
|
45
|
+
__decorate([
|
|
46
|
+
(0, common_1.Get)('hulo.js'),
|
|
47
|
+
__param(0, (0, common_1.Req)()),
|
|
48
|
+
__param(1, (0, common_1.Res)()),
|
|
49
|
+
__metadata("design:type", Function),
|
|
50
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
51
|
+
__metadata("design:returntype", void 0)
|
|
52
|
+
], StorefrontHelperController.prototype, "hulo", null);
|
|
53
|
+
exports.StorefrontHelperController = StorefrontHelperController = __decorate([
|
|
54
|
+
(0, common_1.Controller)('ees')
|
|
55
|
+
], StorefrontHelperController);
|
|
56
|
+
//# sourceMappingURL=storefront-helper.controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storefront-helper.controller.js","sourceRoot":"","sources":["../src/storefront-helper.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA2D;AAE3D,uDAAuD;AACvD,qCAAsC;AAEtC;;;;;;;;;;GAUG;AAEI,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IAEnC,IAAI,CAAQ,GAAY,EAAS,GAAa;QAC1C,MAAM,IAAI,GAAG,IAAA,mBAAU,GAAE,CAAC;QAC1B,MAAM,IAAI,GAAI,IAAY,CAAC,aAAa;eACjC,WAAW,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;QACpD,MAAM,EAAE,GAAG,IAAA,oCAAkB,EAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACvC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,uCAAuC,CAAC,CAAC;QACvE,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,mDAAmD,CAAC,CAAC;QACpF,4DAA4D;QAC5D,2DAA2D;QAC3D,GAAG,CAAC,SAAS,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;QAClD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;CACJ,CAAA;AAdY,gEAA0B;AAEnC;IADC,IAAA,YAAG,EAAC,SAAS,CAAC;IACT,WAAA,IAAA,YAAG,GAAE,CAAA;IAAgB,WAAA,IAAA,YAAG,GAAE,CAAA;;;;sDAW/B;qCAbQ,0BAA0B;IADtC,IAAA,mBAAU,EAAC,KAAK,CAAC;GACL,0BAA0B,CActC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huloglobal/vendure-plugin-visitor-analytics",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.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": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Wayne Garrison <wayne@garrison.me.uk>",
|