@grainql/analytics-web 2.5.3 → 2.6.0
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 +3 -1
- package/dist/activity.js +1 -1
- package/dist/cjs/activity.js +1 -1
- package/dist/cjs/activity.js.map +1 -1
- package/dist/cjs/consent.js +4 -4
- package/dist/cjs/consent.js.map +1 -1
- package/dist/cjs/heartbeat.d.ts.map +1 -1
- package/dist/cjs/heartbeat.js +0 -6
- package/dist/cjs/heartbeat.js.map +1 -1
- package/dist/cjs/heatmap-tracking.d.ts +90 -0
- package/dist/cjs/heatmap-tracking.d.ts.map +1 -0
- package/dist/cjs/heatmap-tracking.js +465 -0
- package/dist/cjs/heatmap-tracking.js.map +1 -0
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interaction-tracking.d.ts.map +1 -1
- package/dist/cjs/interaction-tracking.js +9 -18
- package/dist/cjs/interaction-tracking.js.map +1 -1
- package/dist/cjs/page-tracking.d.ts.map +1 -1
- package/dist/cjs/page-tracking.js +0 -9
- package/dist/cjs/page-tracking.js.map +1 -1
- package/dist/cjs/section-tracking.d.ts.map +1 -1
- package/dist/cjs/section-tracking.js +1 -7
- package/dist/cjs/section-tracking.js.map +1 -1
- package/dist/cjs/types/heatmap-tracking.d.ts +41 -0
- package/dist/cjs/types/heatmap-tracking.d.ts.map +1 -0
- package/dist/cjs/types/heatmap-tracking.js +6 -0
- package/dist/cjs/types/heatmap-tracking.js.map +1 -0
- package/dist/consent.js +4 -4
- package/dist/esm/activity.js +1 -1
- package/dist/esm/activity.js.map +1 -1
- package/dist/esm/consent.js +4 -4
- package/dist/esm/consent.js.map +1 -1
- package/dist/esm/heartbeat.d.ts.map +1 -1
- package/dist/esm/heartbeat.js +0 -6
- package/dist/esm/heartbeat.js.map +1 -1
- package/dist/esm/heatmap-tracking.d.ts +90 -0
- package/dist/esm/heatmap-tracking.d.ts.map +1 -0
- package/dist/esm/heatmap-tracking.js +461 -0
- package/dist/esm/heatmap-tracking.js.map +1 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interaction-tracking.d.ts.map +1 -1
- package/dist/esm/interaction-tracking.js +9 -18
- package/dist/esm/interaction-tracking.js.map +1 -1
- package/dist/esm/page-tracking.d.ts.map +1 -1
- package/dist/esm/page-tracking.js +0 -9
- package/dist/esm/page-tracking.js.map +1 -1
- package/dist/esm/section-tracking.d.ts.map +1 -1
- package/dist/esm/section-tracking.js +1 -7
- package/dist/esm/section-tracking.js.map +1 -1
- package/dist/esm/types/heatmap-tracking.d.ts +41 -0
- package/dist/esm/types/heatmap-tracking.d.ts.map +1 -0
- package/dist/esm/types/heatmap-tracking.js +5 -0
- package/dist/esm/types/heatmap-tracking.js.map +1 -0
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +0 -6
- package/dist/heatmap-tracking.d.ts +90 -0
- package/dist/heatmap-tracking.d.ts.map +1 -0
- package/dist/heatmap-tracking.js +465 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.dev.js +534 -80
- package/dist/index.global.dev.js.map +4 -4
- package/dist/index.global.js +2 -2
- package/dist/index.global.js.map +4 -4
- package/dist/index.js +98 -39
- package/dist/index.mjs +99 -40
- package/dist/interaction-tracking.d.ts.map +1 -1
- package/dist/interaction-tracking.js +9 -18
- package/dist/page-tracking.d.ts.map +1 -1
- package/dist/page-tracking.js +0 -9
- package/dist/section-tracking.d.ts.map +1 -1
- package/dist/section-tracking.js +1 -7
- package/dist/types/heatmap-tracking.d.ts +41 -0
- package/dist/types/heatmap-tracking.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.global.dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Grain Analytics Web SDK v2.
|
|
1
|
+
/* Grain Analytics Web SDK v2.6.0 | MIT License | Development Build */
|
|
2
2
|
"use strict";
|
|
3
3
|
var Grain = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -22,6 +22,438 @@ var Grain = (() => {
|
|
|
22
22
|
};
|
|
23
23
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
24
24
|
|
|
25
|
+
// src/heatmap-tracking.ts
|
|
26
|
+
var heatmap_tracking_exports = {};
|
|
27
|
+
__export(heatmap_tracking_exports, {
|
|
28
|
+
HeatmapTrackingManager: () => HeatmapTrackingManager
|
|
29
|
+
});
|
|
30
|
+
var DEFAULT_OPTIONS, HeatmapTrackingManager;
|
|
31
|
+
var init_heatmap_tracking = __esm({
|
|
32
|
+
"src/heatmap-tracking.ts"() {
|
|
33
|
+
"use strict";
|
|
34
|
+
DEFAULT_OPTIONS = {
|
|
35
|
+
scrollDebounceDelay: 100,
|
|
36
|
+
batchDelay: 2e3,
|
|
37
|
+
maxBatchSize: 20,
|
|
38
|
+
debug: false
|
|
39
|
+
};
|
|
40
|
+
HeatmapTrackingManager = class {
|
|
41
|
+
// 3 seconds - same as section tracking
|
|
42
|
+
constructor(tracker, options = {}) {
|
|
43
|
+
this.isDestroyed = false;
|
|
44
|
+
// Tracking state
|
|
45
|
+
this.currentScrollState = null;
|
|
46
|
+
this.pendingClicks = [];
|
|
47
|
+
this.pendingScrolls = [];
|
|
48
|
+
// Timers
|
|
49
|
+
this.scrollDebounceTimer = null;
|
|
50
|
+
this.batchTimer = null;
|
|
51
|
+
this.scrollTrackingTimer = null;
|
|
52
|
+
this.periodicScrollTimer = null;
|
|
53
|
+
// Scroll tracking
|
|
54
|
+
this.lastScrollPosition = 0;
|
|
55
|
+
this.lastScrollTime = Date.now();
|
|
56
|
+
this.SPLIT_DURATION = 3e3;
|
|
57
|
+
this.tracker = tracker;
|
|
58
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
59
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
60
|
+
if (document.readyState === "loading") {
|
|
61
|
+
document.addEventListener("DOMContentLoaded", () => this.initialize());
|
|
62
|
+
} else {
|
|
63
|
+
setTimeout(() => this.initialize(), 0);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Initialize heatmap tracking
|
|
69
|
+
*/
|
|
70
|
+
initialize() {
|
|
71
|
+
if (this.isDestroyed)
|
|
72
|
+
return;
|
|
73
|
+
this.log("Initializing heatmap tracking");
|
|
74
|
+
this.setupClickTracking();
|
|
75
|
+
this.setupScrollTracking();
|
|
76
|
+
this.startScrollTracking();
|
|
77
|
+
this.setupUnloadHandler();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Setup click event tracking
|
|
81
|
+
*/
|
|
82
|
+
setupClickTracking() {
|
|
83
|
+
if (typeof document === "undefined")
|
|
84
|
+
return;
|
|
85
|
+
const clickHandler = (event) => {
|
|
86
|
+
if (this.isDestroyed)
|
|
87
|
+
return;
|
|
88
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
89
|
+
return;
|
|
90
|
+
this.handleClick(event);
|
|
91
|
+
};
|
|
92
|
+
document.addEventListener("click", clickHandler, { passive: true, capture: true });
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Setup scroll event tracking
|
|
96
|
+
*/
|
|
97
|
+
setupScrollTracking() {
|
|
98
|
+
if (typeof window === "undefined")
|
|
99
|
+
return;
|
|
100
|
+
const scrollHandler = () => {
|
|
101
|
+
if (this.scrollDebounceTimer !== null) {
|
|
102
|
+
clearTimeout(this.scrollDebounceTimer);
|
|
103
|
+
}
|
|
104
|
+
this.scrollDebounceTimer = window.setTimeout(() => {
|
|
105
|
+
this.handleScroll();
|
|
106
|
+
this.scrollDebounceTimer = null;
|
|
107
|
+
}, this.options.scrollDebounceDelay);
|
|
108
|
+
};
|
|
109
|
+
window.addEventListener("scroll", scrollHandler, { passive: true });
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Start periodic scroll state tracking
|
|
113
|
+
*/
|
|
114
|
+
startScrollTracking() {
|
|
115
|
+
if (typeof window === "undefined")
|
|
116
|
+
return;
|
|
117
|
+
this.updateScrollState();
|
|
118
|
+
this.scrollTrackingTimer = window.setInterval(() => {
|
|
119
|
+
if (this.isDestroyed)
|
|
120
|
+
return;
|
|
121
|
+
this.updateScrollState();
|
|
122
|
+
}, 500);
|
|
123
|
+
this.startPeriodicScrollTracking();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Start periodic scroll tracking (sends events every 3 seconds)
|
|
127
|
+
*/
|
|
128
|
+
startPeriodicScrollTracking() {
|
|
129
|
+
if (typeof window === "undefined")
|
|
130
|
+
return;
|
|
131
|
+
this.periodicScrollTimer = window.setInterval(() => {
|
|
132
|
+
if (this.isDestroyed || !this.currentScrollState)
|
|
133
|
+
return;
|
|
134
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
135
|
+
return;
|
|
136
|
+
const currentTime = Date.now();
|
|
137
|
+
const duration = currentTime - this.currentScrollState.entryTime;
|
|
138
|
+
if (duration > 1e3) {
|
|
139
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
140
|
+
const viewportHeight = window.innerHeight;
|
|
141
|
+
const pageHeight = document.documentElement.scrollHeight;
|
|
142
|
+
const scrollData = {
|
|
143
|
+
pageUrl: window.location.href,
|
|
144
|
+
viewportSection: this.currentScrollState.viewportSection,
|
|
145
|
+
scrollDepthPx: scrollY,
|
|
146
|
+
durationMs: duration,
|
|
147
|
+
entryTimestamp: this.currentScrollState.entryTime,
|
|
148
|
+
exitTimestamp: currentTime,
|
|
149
|
+
pageHeight,
|
|
150
|
+
viewportHeight
|
|
151
|
+
};
|
|
152
|
+
this.tracker.trackSystemEvent("_grain_heatmap_scroll", {
|
|
153
|
+
page_url: scrollData.pageUrl,
|
|
154
|
+
viewport_section: scrollData.viewportSection,
|
|
155
|
+
scroll_depth_px: scrollData.scrollDepthPx,
|
|
156
|
+
duration_ms: scrollData.durationMs,
|
|
157
|
+
entry_timestamp: scrollData.entryTimestamp,
|
|
158
|
+
exit_timestamp: scrollData.exitTimestamp,
|
|
159
|
+
page_height: scrollData.pageHeight,
|
|
160
|
+
viewport_height: scrollData.viewportHeight,
|
|
161
|
+
is_split: true
|
|
162
|
+
// Flag to indicate periodic tracking, not final exit
|
|
163
|
+
}, { flush: true });
|
|
164
|
+
this.currentScrollState.entryTime = currentTime;
|
|
165
|
+
}
|
|
166
|
+
}, this.SPLIT_DURATION);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Setup page unload handler to beacon remaining data
|
|
170
|
+
*/
|
|
171
|
+
setupUnloadHandler() {
|
|
172
|
+
if (typeof window === "undefined")
|
|
173
|
+
return;
|
|
174
|
+
const unloadHandler = () => {
|
|
175
|
+
if (this.currentScrollState) {
|
|
176
|
+
const currentTime = Date.now();
|
|
177
|
+
const duration = currentTime - this.currentScrollState.entryTime;
|
|
178
|
+
if (duration > 100) {
|
|
179
|
+
const scrollData = {
|
|
180
|
+
pageUrl: window.location.href,
|
|
181
|
+
viewportSection: this.currentScrollState.viewportSection,
|
|
182
|
+
scrollDepthPx: this.currentScrollState.scrollDepthPx,
|
|
183
|
+
durationMs: duration,
|
|
184
|
+
entryTimestamp: this.currentScrollState.entryTime,
|
|
185
|
+
exitTimestamp: currentTime,
|
|
186
|
+
pageHeight: document.documentElement.scrollHeight,
|
|
187
|
+
viewportHeight: window.innerHeight
|
|
188
|
+
};
|
|
189
|
+
this.pendingScrolls.push(scrollData);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
this.flushPendingEventsWithBeacon();
|
|
193
|
+
};
|
|
194
|
+
window.addEventListener("beforeunload", unloadHandler);
|
|
195
|
+
window.addEventListener("pagehide", unloadHandler);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Handle click event
|
|
199
|
+
*/
|
|
200
|
+
handleClick(event) {
|
|
201
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
202
|
+
return;
|
|
203
|
+
const element = event.target;
|
|
204
|
+
if (!element)
|
|
205
|
+
return;
|
|
206
|
+
const pageUrl = window.location.href;
|
|
207
|
+
const xpath = this.generateXPath(element);
|
|
208
|
+
const viewportX = Math.round(event.clientX);
|
|
209
|
+
const viewportY = Math.round(event.clientY);
|
|
210
|
+
const pageX = Math.round(event.pageX);
|
|
211
|
+
const pageY = Math.round(event.pageY);
|
|
212
|
+
const elementTag = element.tagName?.toLowerCase() || "unknown";
|
|
213
|
+
const elementText = element.textContent?.trim().substring(0, 100);
|
|
214
|
+
const clickData = {
|
|
215
|
+
pageUrl,
|
|
216
|
+
xpath,
|
|
217
|
+
viewportX,
|
|
218
|
+
viewportY,
|
|
219
|
+
pageX,
|
|
220
|
+
pageY,
|
|
221
|
+
elementTag,
|
|
222
|
+
elementText: elementText || void 0,
|
|
223
|
+
timestamp: Date.now()
|
|
224
|
+
};
|
|
225
|
+
const isNavigationLink = element instanceof HTMLAnchorElement && element.href;
|
|
226
|
+
if (isNavigationLink) {
|
|
227
|
+
this.tracker.trackSystemEvent("_grain_heatmap_click", {
|
|
228
|
+
page_url: clickData.pageUrl,
|
|
229
|
+
xpath: clickData.xpath,
|
|
230
|
+
viewport_x: clickData.viewportX,
|
|
231
|
+
viewport_y: clickData.viewportY,
|
|
232
|
+
page_x: clickData.pageX,
|
|
233
|
+
page_y: clickData.pageY,
|
|
234
|
+
element_tag: clickData.elementTag,
|
|
235
|
+
element_text: clickData.elementText,
|
|
236
|
+
timestamp: clickData.timestamp
|
|
237
|
+
}, { flush: true });
|
|
238
|
+
} else {
|
|
239
|
+
this.pendingClicks.push(clickData);
|
|
240
|
+
this.considerBatchFlush();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Handle scroll event
|
|
245
|
+
*/
|
|
246
|
+
handleScroll() {
|
|
247
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
248
|
+
return;
|
|
249
|
+
this.updateScrollState();
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Update current scroll state
|
|
253
|
+
*/
|
|
254
|
+
updateScrollState() {
|
|
255
|
+
if (typeof window === "undefined")
|
|
256
|
+
return;
|
|
257
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
258
|
+
return;
|
|
259
|
+
const currentTime = Date.now();
|
|
260
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
261
|
+
const viewportHeight = window.innerHeight;
|
|
262
|
+
const pageHeight = document.documentElement.scrollHeight;
|
|
263
|
+
const viewportSection = Math.floor(scrollY / viewportHeight);
|
|
264
|
+
if (this.currentScrollState && this.currentScrollState.viewportSection !== viewportSection) {
|
|
265
|
+
const duration = currentTime - this.currentScrollState.entryTime;
|
|
266
|
+
if (duration > 100) {
|
|
267
|
+
const scrollData = {
|
|
268
|
+
pageUrl: window.location.href,
|
|
269
|
+
viewportSection: this.currentScrollState.viewportSection,
|
|
270
|
+
scrollDepthPx: this.currentScrollState.scrollDepthPx,
|
|
271
|
+
durationMs: duration,
|
|
272
|
+
entryTimestamp: this.currentScrollState.entryTime,
|
|
273
|
+
exitTimestamp: currentTime,
|
|
274
|
+
pageHeight,
|
|
275
|
+
viewportHeight
|
|
276
|
+
};
|
|
277
|
+
this.pendingScrolls.push(scrollData);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (!this.currentScrollState || this.currentScrollState.viewportSection !== viewportSection) {
|
|
281
|
+
this.currentScrollState = {
|
|
282
|
+
viewportSection,
|
|
283
|
+
entryTime: currentTime,
|
|
284
|
+
scrollDepthPx: scrollY
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
this.lastScrollPosition = scrollY;
|
|
288
|
+
this.lastScrollTime = currentTime;
|
|
289
|
+
this.considerBatchFlush();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Generate XPath for an element
|
|
293
|
+
*/
|
|
294
|
+
generateXPath(element) {
|
|
295
|
+
if (!element)
|
|
296
|
+
return "";
|
|
297
|
+
if (element.id) {
|
|
298
|
+
return `//*[@id="${element.id}"]`;
|
|
299
|
+
}
|
|
300
|
+
const paths = [];
|
|
301
|
+
let currentElement = element;
|
|
302
|
+
while (currentElement && currentElement.nodeType === Node.ELEMENT_NODE) {
|
|
303
|
+
let index = 0;
|
|
304
|
+
let sibling = currentElement;
|
|
305
|
+
while (sibling) {
|
|
306
|
+
sibling = sibling.previousElementSibling;
|
|
307
|
+
if (sibling && sibling.nodeName === currentElement.nodeName) {
|
|
308
|
+
index++;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const tagName = currentElement.nodeName.toLowerCase();
|
|
312
|
+
const pathIndex = index > 0 ? `[${index + 1}]` : "";
|
|
313
|
+
paths.unshift(`${tagName}${pathIndex}`);
|
|
314
|
+
currentElement = currentElement.parentElement;
|
|
315
|
+
}
|
|
316
|
+
return paths.length ? `/${paths.join("/")}` : "";
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Consider flushing batched events
|
|
320
|
+
*/
|
|
321
|
+
considerBatchFlush() {
|
|
322
|
+
const totalEvents = this.pendingClicks.length + this.pendingScrolls.length;
|
|
323
|
+
if (totalEvents >= this.options.maxBatchSize) {
|
|
324
|
+
this.flushPendingEvents();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (this.batchTimer === null && totalEvents > 0) {
|
|
328
|
+
this.batchTimer = window.setTimeout(() => {
|
|
329
|
+
this.flushPendingEvents();
|
|
330
|
+
this.batchTimer = null;
|
|
331
|
+
}, this.options.batchDelay);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Flush pending events
|
|
336
|
+
*/
|
|
337
|
+
flushPendingEvents() {
|
|
338
|
+
if (this.isDestroyed)
|
|
339
|
+
return;
|
|
340
|
+
if (!this.tracker.hasConsent("analytics")) {
|
|
341
|
+
this.pendingClicks = [];
|
|
342
|
+
this.pendingScrolls = [];
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (this.pendingClicks.length > 0) {
|
|
346
|
+
for (const clickData of this.pendingClicks) {
|
|
347
|
+
this.tracker.trackSystemEvent("_grain_heatmap_click", {
|
|
348
|
+
page_url: clickData.pageUrl,
|
|
349
|
+
xpath: clickData.xpath,
|
|
350
|
+
viewport_x: clickData.viewportX,
|
|
351
|
+
viewport_y: clickData.viewportY,
|
|
352
|
+
page_x: clickData.pageX,
|
|
353
|
+
page_y: clickData.pageY,
|
|
354
|
+
element_tag: clickData.elementTag,
|
|
355
|
+
element_text: clickData.elementText,
|
|
356
|
+
timestamp: clickData.timestamp
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
this.pendingClicks = [];
|
|
360
|
+
}
|
|
361
|
+
if (this.pendingScrolls.length > 0) {
|
|
362
|
+
for (const scrollData of this.pendingScrolls) {
|
|
363
|
+
this.tracker.trackSystemEvent("_grain_heatmap_scroll", {
|
|
364
|
+
page_url: scrollData.pageUrl,
|
|
365
|
+
viewport_section: scrollData.viewportSection,
|
|
366
|
+
scroll_depth_px: scrollData.scrollDepthPx,
|
|
367
|
+
duration_ms: scrollData.durationMs,
|
|
368
|
+
entry_timestamp: scrollData.entryTimestamp,
|
|
369
|
+
exit_timestamp: scrollData.exitTimestamp,
|
|
370
|
+
page_height: scrollData.pageHeight,
|
|
371
|
+
viewport_height: scrollData.viewportHeight
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
this.pendingScrolls = [];
|
|
375
|
+
}
|
|
376
|
+
if (this.batchTimer !== null) {
|
|
377
|
+
clearTimeout(this.batchTimer);
|
|
378
|
+
this.batchTimer = null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Flush pending events with beacon (for page unload)
|
|
383
|
+
*/
|
|
384
|
+
flushPendingEventsWithBeacon() {
|
|
385
|
+
if (!this.tracker.hasConsent("analytics")) {
|
|
386
|
+
this.pendingClicks = [];
|
|
387
|
+
this.pendingScrolls = [];
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (this.pendingClicks.length > 0) {
|
|
391
|
+
for (const clickData of this.pendingClicks) {
|
|
392
|
+
this.tracker.trackSystemEvent("_grain_heatmap_click", {
|
|
393
|
+
page_url: clickData.pageUrl,
|
|
394
|
+
xpath: clickData.xpath,
|
|
395
|
+
viewport_x: clickData.viewportX,
|
|
396
|
+
viewport_y: clickData.viewportY,
|
|
397
|
+
page_x: clickData.pageX,
|
|
398
|
+
page_y: clickData.pageY,
|
|
399
|
+
element_tag: clickData.elementTag,
|
|
400
|
+
element_text: clickData.elementText,
|
|
401
|
+
timestamp: clickData.timestamp
|
|
402
|
+
}, { flush: true });
|
|
403
|
+
}
|
|
404
|
+
this.pendingClicks = [];
|
|
405
|
+
}
|
|
406
|
+
if (this.pendingScrolls.length > 0) {
|
|
407
|
+
for (const scrollData of this.pendingScrolls) {
|
|
408
|
+
this.tracker.trackSystemEvent("_grain_heatmap_scroll", {
|
|
409
|
+
page_url: scrollData.pageUrl,
|
|
410
|
+
viewport_section: scrollData.viewportSection,
|
|
411
|
+
scroll_depth_px: scrollData.scrollDepthPx,
|
|
412
|
+
duration_ms: scrollData.durationMs,
|
|
413
|
+
entry_timestamp: scrollData.entryTimestamp,
|
|
414
|
+
exit_timestamp: scrollData.exitTimestamp,
|
|
415
|
+
page_height: scrollData.pageHeight,
|
|
416
|
+
viewport_height: scrollData.viewportHeight
|
|
417
|
+
}, { flush: true });
|
|
418
|
+
}
|
|
419
|
+
this.pendingScrolls = [];
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Log debug message
|
|
424
|
+
*/
|
|
425
|
+
log(...args) {
|
|
426
|
+
if (this.options.debug) {
|
|
427
|
+
this.tracker.log("[Heatmap Tracking]", ...args);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Destroy the tracking manager
|
|
432
|
+
*/
|
|
433
|
+
destroy() {
|
|
434
|
+
this.isDestroyed = true;
|
|
435
|
+
if (this.scrollDebounceTimer !== null) {
|
|
436
|
+
clearTimeout(this.scrollDebounceTimer);
|
|
437
|
+
this.scrollDebounceTimer = null;
|
|
438
|
+
}
|
|
439
|
+
if (this.batchTimer !== null) {
|
|
440
|
+
clearTimeout(this.batchTimer);
|
|
441
|
+
this.batchTimer = null;
|
|
442
|
+
}
|
|
443
|
+
if (this.scrollTrackingTimer !== null) {
|
|
444
|
+
clearInterval(this.scrollTrackingTimer);
|
|
445
|
+
this.scrollTrackingTimer = null;
|
|
446
|
+
}
|
|
447
|
+
if (this.periodicScrollTimer !== null) {
|
|
448
|
+
clearInterval(this.periodicScrollTimer);
|
|
449
|
+
this.periodicScrollTimer = null;
|
|
450
|
+
}
|
|
451
|
+
this.flushPendingEvents();
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
25
457
|
// src/interaction-tracking.ts
|
|
26
458
|
var interaction_tracking_exports = {};
|
|
27
459
|
__export(interaction_tracking_exports, {
|
|
@@ -62,7 +494,7 @@ var Grain = (() => {
|
|
|
62
494
|
attachAllListeners() {
|
|
63
495
|
if (this.isDestroyed)
|
|
64
496
|
return;
|
|
65
|
-
this.log("Attaching interaction listeners
|
|
497
|
+
this.log("Attaching interaction listeners");
|
|
66
498
|
for (const interaction of this.interactions) {
|
|
67
499
|
this.attachInteractionListener(interaction);
|
|
68
500
|
}
|
|
@@ -92,7 +524,6 @@ var Grain = (() => {
|
|
|
92
524
|
handlers.push({ event: "focus", handler: focusHandler });
|
|
93
525
|
}
|
|
94
526
|
this.attachedListeners.set(element, handlers);
|
|
95
|
-
this.log("Attached listeners to element for:", interaction.eventName);
|
|
96
527
|
}
|
|
97
528
|
/**
|
|
98
529
|
* Handle click event on interaction
|
|
@@ -116,17 +547,12 @@ var Grain = (() => {
|
|
|
116
547
|
...isNavigationLink && { href: element.href },
|
|
117
548
|
timestamp: Date.now()
|
|
118
549
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
this.tracker.track(interaction.eventName, eventProperties);
|
|
550
|
+
const result = this.tracker.track(interaction.eventName, eventProperties, { flush: true });
|
|
551
|
+
if (result instanceof Promise) {
|
|
552
|
+
result.catch((error) => {
|
|
553
|
+
this.log("Failed to track click:", error);
|
|
554
|
+
});
|
|
128
555
|
}
|
|
129
|
-
this.log("Tracked click interaction:", interaction.eventName);
|
|
130
556
|
}
|
|
131
557
|
/**
|
|
132
558
|
* Handle focus event on interaction (for form fields)
|
|
@@ -147,7 +573,6 @@ var Grain = (() => {
|
|
|
147
573
|
element_class: element.className || void 0,
|
|
148
574
|
timestamp: Date.now()
|
|
149
575
|
});
|
|
150
|
-
this.log("Tracked focus interaction:", interaction.eventName);
|
|
151
576
|
}
|
|
152
577
|
/**
|
|
153
578
|
* Find element by XPath selector
|
|
@@ -241,7 +666,6 @@ var Grain = (() => {
|
|
|
241
666
|
element.removeEventListener(event, handler);
|
|
242
667
|
});
|
|
243
668
|
this.attachedListeners.delete(element);
|
|
244
|
-
this.log("Detached listeners from element");
|
|
245
669
|
}
|
|
246
670
|
/**
|
|
247
671
|
* Log debug messages
|
|
@@ -296,11 +720,11 @@ var Grain = (() => {
|
|
|
296
720
|
__export(section_tracking_exports, {
|
|
297
721
|
SectionTrackingManager: () => SectionTrackingManager
|
|
298
722
|
});
|
|
299
|
-
var
|
|
723
|
+
var DEFAULT_OPTIONS2, SectionTrackingManager;
|
|
300
724
|
var init_section_tracking = __esm({
|
|
301
725
|
"src/section-tracking.ts"() {
|
|
302
726
|
"use strict";
|
|
303
|
-
|
|
727
|
+
DEFAULT_OPTIONS2 = {
|
|
304
728
|
minDwellTime: 1e3,
|
|
305
729
|
// 1 second minimum
|
|
306
730
|
scrollVelocityThreshold: 500,
|
|
@@ -334,7 +758,7 @@ var Grain = (() => {
|
|
|
334
758
|
this.SPLIT_DURATION = 3e3;
|
|
335
759
|
this.tracker = tracker;
|
|
336
760
|
this.sections = sections;
|
|
337
|
-
this.options = { ...
|
|
761
|
+
this.options = { ...DEFAULT_OPTIONS2, ...options };
|
|
338
762
|
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
339
763
|
if (document.readyState === "loading") {
|
|
340
764
|
document.addEventListener("DOMContentLoaded", () => this.initialize());
|
|
@@ -349,7 +773,7 @@ var Grain = (() => {
|
|
|
349
773
|
initialize() {
|
|
350
774
|
if (this.isDestroyed)
|
|
351
775
|
return;
|
|
352
|
-
this.log("Initializing section tracking
|
|
776
|
+
this.log("Initializing section tracking");
|
|
353
777
|
this.setupIntersectionObserver();
|
|
354
778
|
this.setupScrollListener();
|
|
355
779
|
this.initializeSections();
|
|
@@ -419,7 +843,6 @@ var Grain = (() => {
|
|
|
419
843
|
if (this.intersectionObserver) {
|
|
420
844
|
this.intersectionObserver.observe(element);
|
|
421
845
|
}
|
|
422
|
-
this.log("Section initialized and observed:", section.sectionName);
|
|
423
846
|
}
|
|
424
847
|
}
|
|
425
848
|
/**
|
|
@@ -449,7 +872,6 @@ var Grain = (() => {
|
|
|
449
872
|
* Handle section entry (became visible)
|
|
450
873
|
*/
|
|
451
874
|
handleSectionEntry(state) {
|
|
452
|
-
this.log("Section entered view:", state.config.sectionName);
|
|
453
875
|
state.entryTime = Date.now();
|
|
454
876
|
state.entryScrollSpeed = this.scrollVelocity;
|
|
455
877
|
state.lastScrollPosition = window.scrollY;
|
|
@@ -500,7 +922,6 @@ var Grain = (() => {
|
|
|
500
922
|
is_split: true
|
|
501
923
|
// Flag to indicate this is a periodic split, not final exit
|
|
502
924
|
});
|
|
503
|
-
this.log("Tracked periodic section view split:", state.config.sectionName, "duration:", duration);
|
|
504
925
|
state.entryTime = now;
|
|
505
926
|
state.entryScrollSpeed = this.scrollVelocity;
|
|
506
927
|
}
|
|
@@ -522,7 +943,6 @@ var Grain = (() => {
|
|
|
522
943
|
* Handle section exit (became invisible)
|
|
523
944
|
*/
|
|
524
945
|
handleSectionExit(state) {
|
|
525
|
-
this.log("Section exited view:", state.config.sectionName);
|
|
526
946
|
this.stopPeriodicTracking(state.config.sectionName);
|
|
527
947
|
if (state.entryTime === null)
|
|
528
948
|
return;
|
|
@@ -604,7 +1024,6 @@ var Grain = (() => {
|
|
|
604
1024
|
*/
|
|
605
1025
|
queueSectionView(viewData) {
|
|
606
1026
|
this.pendingEvents.push(viewData);
|
|
607
|
-
this.log("Queued section view:", viewData.sectionName, "duration:", viewData.duration);
|
|
608
1027
|
if (this.batchTimer === null) {
|
|
609
1028
|
this.batchTimer = window.setTimeout(() => {
|
|
610
1029
|
this.flushPendingEvents();
|
|
@@ -621,7 +1040,6 @@ var Grain = (() => {
|
|
|
621
1040
|
this.pendingEvents = [];
|
|
622
1041
|
return;
|
|
623
1042
|
}
|
|
624
|
-
this.log("Flushing", this.pendingEvents.length, "section view events");
|
|
625
1043
|
for (const viewData of this.pendingEvents) {
|
|
626
1044
|
this.tracker.trackSystemEvent("_grain_section_view", {
|
|
627
1045
|
section_name: viewData.sectionName,
|
|
@@ -782,7 +1200,6 @@ var Grain = (() => {
|
|
|
782
1200
|
this.saveConsentState();
|
|
783
1201
|
}
|
|
784
1202
|
} catch (error) {
|
|
785
|
-
console.error("[Grain Consent] Failed to load consent state:", error);
|
|
786
1203
|
}
|
|
787
1204
|
}
|
|
788
1205
|
/**
|
|
@@ -794,7 +1211,6 @@ var Grain = (() => {
|
|
|
794
1211
|
try {
|
|
795
1212
|
localStorage.setItem(this.storageKey, JSON.stringify(this.consentState));
|
|
796
1213
|
} catch (error) {
|
|
797
|
-
console.error("[Grain Consent] Failed to save consent state:", error);
|
|
798
1214
|
}
|
|
799
1215
|
}
|
|
800
1216
|
/**
|
|
@@ -895,7 +1311,6 @@ var Grain = (() => {
|
|
|
895
1311
|
try {
|
|
896
1312
|
listener(this.consentState);
|
|
897
1313
|
} catch (error) {
|
|
898
|
-
console.error("[Grain Consent] Listener error:", error);
|
|
899
1314
|
}
|
|
900
1315
|
});
|
|
901
1316
|
}
|
|
@@ -909,7 +1324,6 @@ var Grain = (() => {
|
|
|
909
1324
|
localStorage.removeItem(this.storageKey);
|
|
910
1325
|
this.consentState = null;
|
|
911
1326
|
} catch (error) {
|
|
912
|
-
console.error("[Grain Consent] Failed to clear consent:", error);
|
|
913
1327
|
}
|
|
914
1328
|
}
|
|
915
1329
|
};
|
|
@@ -1089,7 +1503,6 @@ var Grain = (() => {
|
|
|
1089
1503
|
try {
|
|
1090
1504
|
listener();
|
|
1091
1505
|
} catch (error) {
|
|
1092
|
-
console.error("[Activity Detector] Listener error:", error);
|
|
1093
1506
|
}
|
|
1094
1507
|
}
|
|
1095
1508
|
}
|
|
@@ -1190,9 +1603,6 @@ var Grain = (() => {
|
|
|
1190
1603
|
}
|
|
1191
1604
|
this.tracker.trackSystemEvent("_grain_heartbeat", properties);
|
|
1192
1605
|
this.lastHeartbeatTime = now;
|
|
1193
|
-
if (this.config.debug) {
|
|
1194
|
-
console.log("[Heartbeat] Sent heartbeat:", properties);
|
|
1195
|
-
}
|
|
1196
1606
|
}
|
|
1197
1607
|
/**
|
|
1198
1608
|
* Destroy the heartbeat manager
|
|
@@ -1205,9 +1615,6 @@ var Grain = (() => {
|
|
|
1205
1615
|
this.heartbeatTimer = null;
|
|
1206
1616
|
}
|
|
1207
1617
|
this.isDestroyed = true;
|
|
1208
|
-
if (this.config.debug) {
|
|
1209
|
-
console.log("[Heartbeat] Destroyed");
|
|
1210
|
-
}
|
|
1211
1618
|
}
|
|
1212
1619
|
};
|
|
1213
1620
|
|
|
@@ -4408,9 +4815,6 @@ var Grain = (() => {
|
|
|
4408
4815
|
properties.viewport = `${window.innerWidth}x${window.innerHeight}`;
|
|
4409
4816
|
}
|
|
4410
4817
|
this.tracker.trackSystemEvent("page_view", properties);
|
|
4411
|
-
if (this.config.debug) {
|
|
4412
|
-
console.log("[Page Tracking] Tracked page view:", properties);
|
|
4413
|
-
}
|
|
4414
4818
|
}
|
|
4415
4819
|
/**
|
|
4416
4820
|
* Extract domain from URL
|
|
@@ -4533,9 +4937,6 @@ var Grain = (() => {
|
|
|
4533
4937
|
}
|
|
4534
4938
|
}
|
|
4535
4939
|
this.tracker.trackSystemEvent("page_view", baseProperties);
|
|
4536
|
-
if (this.config.debug) {
|
|
4537
|
-
console.log("[Page Tracking] Manually tracked page:", baseProperties);
|
|
4538
|
-
}
|
|
4539
4940
|
}
|
|
4540
4941
|
/**
|
|
4541
4942
|
* Get page view count for current session
|
|
@@ -4562,9 +4963,6 @@ var Grain = (() => {
|
|
|
4562
4963
|
window.removeEventListener("hashchange", this.handleHashChange);
|
|
4563
4964
|
}
|
|
4564
4965
|
this.isDestroyed = true;
|
|
4565
|
-
if (this.config.debug) {
|
|
4566
|
-
console.log("[Page Tracking] Destroyed");
|
|
4567
|
-
}
|
|
4568
4966
|
}
|
|
4569
4967
|
};
|
|
4570
4968
|
|
|
@@ -4592,6 +4990,7 @@ var Grain = (() => {
|
|
|
4592
4990
|
// Auto-tracking properties
|
|
4593
4991
|
this.interactionTrackingManager = null;
|
|
4594
4992
|
this.sectionTrackingManager = null;
|
|
4993
|
+
this.heatmapTrackingManager = null;
|
|
4595
4994
|
// Session tracking
|
|
4596
4995
|
this.sessionStartTime = Date.now();
|
|
4597
4996
|
this.sessionEventCount = 0;
|
|
@@ -4627,6 +5026,8 @@ var Grain = (() => {
|
|
|
4627
5026
|
// 5 minutes
|
|
4628
5027
|
enableAutoPageView: true,
|
|
4629
5028
|
stripQueryParams: true,
|
|
5029
|
+
// Heatmap Tracking defaults
|
|
5030
|
+
enableHeatmapTracking: true,
|
|
4630
5031
|
...config,
|
|
4631
5032
|
tenantId: config.tenantId
|
|
4632
5033
|
};
|
|
@@ -4649,6 +5050,9 @@ var Grain = (() => {
|
|
|
4649
5050
|
if (typeof window !== "undefined") {
|
|
4650
5051
|
this.initializeAutomaticTracking();
|
|
4651
5052
|
this.trackSessionStart();
|
|
5053
|
+
if (this.config.enableHeatmapTracking) {
|
|
5054
|
+
this.initializeHeatmapTracking();
|
|
5055
|
+
}
|
|
4652
5056
|
}
|
|
4653
5057
|
this.consentManager.addListener((state) => {
|
|
4654
5058
|
if (state.granted) {
|
|
@@ -4872,6 +5276,8 @@ var Grain = (() => {
|
|
|
4872
5276
|
* Log formatted error gracefully
|
|
4873
5277
|
*/
|
|
4874
5278
|
logError(formattedError) {
|
|
5279
|
+
if (!this.config.debug)
|
|
5280
|
+
return;
|
|
4875
5281
|
const { code, message, digest, timestamp, context } = formattedError;
|
|
4876
5282
|
const errorOutput = {
|
|
4877
5283
|
"\u{1F6A8} Grain Analytics Error": {
|
|
@@ -4889,9 +5295,7 @@ var Grain = (() => {
|
|
|
4889
5295
|
}
|
|
4890
5296
|
};
|
|
4891
5297
|
console.error("\u{1F6A8} Grain Analytics Error:", errorOutput);
|
|
4892
|
-
|
|
4893
|
-
console.error(`[Grain Analytics] ${code}: ${message} (${context}) - Events: ${digest.eventCount}, Props: ${digest.totalProperties}, Size: ${digest.totalSize}B`);
|
|
4894
|
-
}
|
|
5298
|
+
console.error(`[Grain Analytics] ${code}: ${message} (${context}) - Events: ${digest.eventCount}, Props: ${digest.totalProperties}, Size: ${digest.totalSize}B`);
|
|
4895
5299
|
}
|
|
4896
5300
|
/**
|
|
4897
5301
|
* Safely execute a function with error handling
|
|
@@ -4906,10 +5310,40 @@ var Grain = (() => {
|
|
|
4906
5310
|
}
|
|
4907
5311
|
}
|
|
4908
5312
|
formatEvent(event) {
|
|
5313
|
+
const properties = event.properties || {};
|
|
5314
|
+
if (!this.config.disableAutoProperties && typeof window !== "undefined") {
|
|
5315
|
+
const hasConsent = this.consentManager.hasConsent("analytics");
|
|
5316
|
+
const isSystemEvent = event.eventName.startsWith("_grain_");
|
|
5317
|
+
if (!isSystemEvent && hasConsent) {
|
|
5318
|
+
const sessionUTMs = getSessionUTMParameters();
|
|
5319
|
+
if (sessionUTMs) {
|
|
5320
|
+
if (sessionUTMs.utm_source)
|
|
5321
|
+
properties.utm_source = sessionUTMs.utm_source;
|
|
5322
|
+
if (sessionUTMs.utm_medium)
|
|
5323
|
+
properties.utm_medium = sessionUTMs.utm_medium;
|
|
5324
|
+
if (sessionUTMs.utm_campaign)
|
|
5325
|
+
properties.utm_campaign = sessionUTMs.utm_campaign;
|
|
5326
|
+
if (sessionUTMs.utm_term)
|
|
5327
|
+
properties.utm_term = sessionUTMs.utm_term;
|
|
5328
|
+
if (sessionUTMs.utm_content)
|
|
5329
|
+
properties.utm_content = sessionUTMs.utm_content;
|
|
5330
|
+
}
|
|
5331
|
+
const firstTouch = getFirstTouchAttribution(this.config.tenantId);
|
|
5332
|
+
if (firstTouch) {
|
|
5333
|
+
properties.first_touch_source = firstTouch.source;
|
|
5334
|
+
properties.first_touch_medium = firstTouch.medium;
|
|
5335
|
+
properties.first_touch_campaign = firstTouch.campaign;
|
|
5336
|
+
properties.first_touch_referrer_category = firstTouch.referrer_category;
|
|
5337
|
+
}
|
|
5338
|
+
if (!properties.session_id) {
|
|
5339
|
+
properties.session_id = this.getSessionId();
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
4909
5343
|
return {
|
|
4910
5344
|
eventName: event.eventName,
|
|
4911
5345
|
userId: event.userId || this.getEffectiveUserIdInternal(),
|
|
4912
|
-
properties
|
|
5346
|
+
properties
|
|
4913
5347
|
};
|
|
4914
5348
|
}
|
|
4915
5349
|
async getAuthHeaders() {
|
|
@@ -4960,7 +5394,6 @@ var Grain = (() => {
|
|
|
4960
5394
|
try {
|
|
4961
5395
|
const headers = await this.getAuthHeaders();
|
|
4962
5396
|
const url = `${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/multi`;
|
|
4963
|
-
this.log(`Sending ${events.length} events to ${url} (attempt ${attempt + 1})`);
|
|
4964
5397
|
const response = await fetch(url, {
|
|
4965
5398
|
method: "POST",
|
|
4966
5399
|
headers,
|
|
@@ -5025,7 +5458,6 @@ var Grain = (() => {
|
|
|
5025
5458
|
body,
|
|
5026
5459
|
keepalive: true
|
|
5027
5460
|
});
|
|
5028
|
-
this.log(`Successfully sent ${events.length} events via fetch (keepalive)`);
|
|
5029
5461
|
} catch (error) {
|
|
5030
5462
|
const formattedError = this.formatError(error, "sendEventsWithBeacon", events);
|
|
5031
5463
|
this.logError(formattedError);
|
|
@@ -5091,7 +5523,6 @@ var Grain = (() => {
|
|
|
5091
5523
|
debug: this.config.debug
|
|
5092
5524
|
}
|
|
5093
5525
|
);
|
|
5094
|
-
this.log("Heartbeat tracking initialized");
|
|
5095
5526
|
} catch (error) {
|
|
5096
5527
|
this.log("Failed to initialize heartbeat tracking:", error);
|
|
5097
5528
|
}
|
|
@@ -5106,19 +5537,48 @@ var Grain = (() => {
|
|
|
5106
5537
|
tenantId: this.config.tenantId
|
|
5107
5538
|
}
|
|
5108
5539
|
);
|
|
5109
|
-
this.log("Auto page view tracking initialized");
|
|
5110
5540
|
} catch (error) {
|
|
5111
5541
|
this.log("Failed to initialize page view tracking:", error);
|
|
5112
5542
|
}
|
|
5113
5543
|
}
|
|
5114
5544
|
this.initializeAutoTracking();
|
|
5115
5545
|
}
|
|
5546
|
+
/**
|
|
5547
|
+
* Initialize heatmap tracking
|
|
5548
|
+
*/
|
|
5549
|
+
initializeHeatmapTracking() {
|
|
5550
|
+
if (typeof window === "undefined")
|
|
5551
|
+
return;
|
|
5552
|
+
try {
|
|
5553
|
+
this.log("Initializing heatmap tracking");
|
|
5554
|
+
Promise.resolve().then(() => (init_heatmap_tracking(), heatmap_tracking_exports)).then(({ HeatmapTrackingManager: HeatmapTrackingManager2 }) => {
|
|
5555
|
+
try {
|
|
5556
|
+
this.heatmapTrackingManager = new HeatmapTrackingManager2(
|
|
5557
|
+
this,
|
|
5558
|
+
{
|
|
5559
|
+
scrollDebounceDelay: 100,
|
|
5560
|
+
batchDelay: 2e3,
|
|
5561
|
+
maxBatchSize: 20,
|
|
5562
|
+
debug: this.config.debug
|
|
5563
|
+
}
|
|
5564
|
+
);
|
|
5565
|
+
this.log("Heatmap tracking initialized");
|
|
5566
|
+
} catch (error) {
|
|
5567
|
+
this.log("Failed to initialize heatmap tracking:", error);
|
|
5568
|
+
}
|
|
5569
|
+
}).catch((error) => {
|
|
5570
|
+
this.log("Failed to load heatmap tracking module:", error);
|
|
5571
|
+
});
|
|
5572
|
+
} catch (error) {
|
|
5573
|
+
this.log("Failed to initialize heatmap tracking:", error);
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5116
5576
|
/**
|
|
5117
5577
|
* Initialize auto-tracking (interactions and sections)
|
|
5118
5578
|
*/
|
|
5119
5579
|
async initializeAutoTracking() {
|
|
5120
5580
|
try {
|
|
5121
|
-
this.log("Initializing auto-tracking
|
|
5581
|
+
this.log("Initializing auto-tracking");
|
|
5122
5582
|
const userId = this.globalUserId || this.persistentAnonymousUserId || this.generateUUID();
|
|
5123
5583
|
const currentUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
5124
5584
|
const request = {
|
|
@@ -5130,23 +5590,19 @@ var Grain = (() => {
|
|
|
5130
5590
|
};
|
|
5131
5591
|
const headers = await this.getAuthHeaders();
|
|
5132
5592
|
const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
|
|
5133
|
-
this.log("Fetching auto-tracking config from:", url);
|
|
5134
5593
|
const response = await fetch(url, {
|
|
5135
5594
|
method: "POST",
|
|
5136
5595
|
headers,
|
|
5137
5596
|
body: JSON.stringify(request)
|
|
5138
5597
|
});
|
|
5139
5598
|
if (!response.ok) {
|
|
5140
|
-
this.log("Failed to fetch auto-tracking config:", response.status
|
|
5599
|
+
this.log("Failed to fetch auto-tracking config:", response.status);
|
|
5141
5600
|
return;
|
|
5142
5601
|
}
|
|
5143
5602
|
const configResponse = await response.json();
|
|
5144
|
-
this.log("Received config response:", configResponse);
|
|
5145
5603
|
if (configResponse.autoTrackingConfig) {
|
|
5146
|
-
this.log("Auto-tracking config
|
|
5604
|
+
this.log("Auto-tracking config loaded");
|
|
5147
5605
|
this.setupAutoTrackingManagers(configResponse.autoTrackingConfig);
|
|
5148
|
-
} else {
|
|
5149
|
-
this.log("No auto-tracking config in response");
|
|
5150
5606
|
}
|
|
5151
5607
|
} catch (error) {
|
|
5152
5608
|
this.log("Failed to initialize auto-tracking:", error);
|
|
@@ -5156,9 +5612,9 @@ var Grain = (() => {
|
|
|
5156
5612
|
* Setup auto-tracking managers
|
|
5157
5613
|
*/
|
|
5158
5614
|
setupAutoTrackingManagers(config) {
|
|
5159
|
-
this.log("Setting up auto-tracking managers
|
|
5615
|
+
this.log("Setting up auto-tracking managers");
|
|
5160
5616
|
if (config.interactions && config.interactions.length > 0) {
|
|
5161
|
-
this.log("Loading interaction tracking
|
|
5617
|
+
this.log("Loading interaction tracking:", config.interactions.length, "interactions");
|
|
5162
5618
|
Promise.resolve().then(() => (init_interaction_tracking(), interaction_tracking_exports)).then(({ InteractionTrackingManager: InteractionTrackingManager2 }) => {
|
|
5163
5619
|
try {
|
|
5164
5620
|
this.interactionTrackingManager = new InteractionTrackingManager2(
|
|
@@ -5170,18 +5626,16 @@ var Grain = (() => {
|
|
|
5170
5626
|
mutationDebounceDelay: 500
|
|
5171
5627
|
}
|
|
5172
5628
|
);
|
|
5173
|
-
this.log("
|
|
5629
|
+
this.log("Interaction tracking initialized");
|
|
5174
5630
|
} catch (error) {
|
|
5175
|
-
this.log("
|
|
5631
|
+
this.log("Failed to initialize interaction tracking:", error);
|
|
5176
5632
|
}
|
|
5177
5633
|
}).catch((error) => {
|
|
5178
|
-
this.log("
|
|
5634
|
+
this.log("Failed to load interaction tracking module:", error);
|
|
5179
5635
|
});
|
|
5180
|
-
} else {
|
|
5181
|
-
this.log("No interactions configured for auto-tracking");
|
|
5182
5636
|
}
|
|
5183
5637
|
if (config.sections && config.sections.length > 0) {
|
|
5184
|
-
this.log("Loading section tracking
|
|
5638
|
+
this.log("Loading section tracking:", config.sections.length, "sections");
|
|
5185
5639
|
Promise.resolve().then(() => (init_section_tracking(), section_tracking_exports)).then(({ SectionTrackingManager: SectionTrackingManager2 }) => {
|
|
5186
5640
|
try {
|
|
5187
5641
|
this.sectionTrackingManager = new SectionTrackingManager2(
|
|
@@ -5196,15 +5650,13 @@ var Grain = (() => {
|
|
|
5196
5650
|
debug: this.config.debug
|
|
5197
5651
|
}
|
|
5198
5652
|
);
|
|
5199
|
-
this.log("
|
|
5653
|
+
this.log("Section tracking initialized");
|
|
5200
5654
|
} catch (error) {
|
|
5201
|
-
this.log("
|
|
5655
|
+
this.log("Failed to initialize section tracking:", error);
|
|
5202
5656
|
}
|
|
5203
5657
|
}).catch((error) => {
|
|
5204
|
-
this.log("
|
|
5658
|
+
this.log("Failed to load section tracking module:", error);
|
|
5205
5659
|
});
|
|
5206
|
-
} else {
|
|
5207
|
-
this.log("No sections configured for auto-tracking");
|
|
5208
5660
|
}
|
|
5209
5661
|
}
|
|
5210
5662
|
/**
|
|
@@ -5260,7 +5712,7 @@ var Grain = (() => {
|
|
|
5260
5712
|
properties.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
5261
5713
|
}
|
|
5262
5714
|
this.trackSystemEvent("_grain_session_start", properties);
|
|
5263
|
-
this.log("Session started
|
|
5715
|
+
this.log("Session started");
|
|
5264
5716
|
}
|
|
5265
5717
|
/**
|
|
5266
5718
|
* Track session end event
|
|
@@ -5285,7 +5737,7 @@ var Grain = (() => {
|
|
|
5285
5737
|
properties.page_count = pageCount;
|
|
5286
5738
|
}
|
|
5287
5739
|
this.trackSystemEvent("_grain_session_end", properties);
|
|
5288
|
-
this.log("Session ended
|
|
5740
|
+
this.log("Session ended");
|
|
5289
5741
|
}
|
|
5290
5742
|
/**
|
|
5291
5743
|
* Detect browser name
|
|
@@ -5382,7 +5834,7 @@ var Grain = (() => {
|
|
|
5382
5834
|
};
|
|
5383
5835
|
this.eventQueue.push(event);
|
|
5384
5836
|
this.eventCountSinceLastHeartbeat++;
|
|
5385
|
-
this.log(`Queued system event: ${eventName}
|
|
5837
|
+
this.log(`Queued system event: ${eventName}`);
|
|
5386
5838
|
if (this.eventQueue.length >= this.config.batchSize) {
|
|
5387
5839
|
this.flush().catch((error) => {
|
|
5388
5840
|
const formattedError = this.formatError(error, "flush system event");
|
|
@@ -5472,7 +5924,7 @@ var Grain = (() => {
|
|
|
5472
5924
|
this.eventQueue.push(formattedEvent);
|
|
5473
5925
|
this.eventCountSinceLastHeartbeat++;
|
|
5474
5926
|
this.sessionEventCount++;
|
|
5475
|
-
this.log(`Queued event: ${event.eventName}
|
|
5927
|
+
this.log(`Queued event: ${event.eventName}`);
|
|
5476
5928
|
if (opts.flush || this.eventQueue.length >= this.config.batchSize) {
|
|
5477
5929
|
await this.flush();
|
|
5478
5930
|
}
|
|
@@ -5671,7 +6123,6 @@ var Grain = (() => {
|
|
|
5671
6123
|
try {
|
|
5672
6124
|
const headers = await this.getAuthHeaders();
|
|
5673
6125
|
const url = `${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/properties`;
|
|
5674
|
-
this.log(`Setting properties for user ${payload.userId} (attempt ${attempt + 1})`);
|
|
5675
6126
|
const response = await fetch(url, {
|
|
5676
6127
|
method: "POST",
|
|
5677
6128
|
headers,
|
|
@@ -5897,7 +6348,6 @@ var Grain = (() => {
|
|
|
5897
6348
|
try {
|
|
5898
6349
|
const headers = await this.getAuthHeaders();
|
|
5899
6350
|
const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
|
|
5900
|
-
this.log(`Fetching configurations for user ${userId} (attempt ${attempt + 1})`);
|
|
5901
6351
|
const response = await fetch(url, {
|
|
5902
6352
|
method: "POST",
|
|
5903
6353
|
headers,
|
|
@@ -5924,7 +6374,7 @@ var Grain = (() => {
|
|
|
5924
6374
|
if (configResponse.configurations) {
|
|
5925
6375
|
this.updateConfigCache(configResponse, userId);
|
|
5926
6376
|
}
|
|
5927
|
-
this.log(
|
|
6377
|
+
this.log("Successfully fetched configurations");
|
|
5928
6378
|
return configResponse;
|
|
5929
6379
|
} catch (error) {
|
|
5930
6380
|
lastError = error;
|
|
@@ -6173,6 +6623,10 @@ var Grain = (() => {
|
|
|
6173
6623
|
this.sectionTrackingManager.destroy();
|
|
6174
6624
|
this.sectionTrackingManager = null;
|
|
6175
6625
|
}
|
|
6626
|
+
if (this.heatmapTrackingManager) {
|
|
6627
|
+
this.heatmapTrackingManager.destroy();
|
|
6628
|
+
this.heatmapTrackingManager = null;
|
|
6629
|
+
}
|
|
6176
6630
|
if (this.eventQueue.length > 0) {
|
|
6177
6631
|
const eventsToSend = [...this.eventQueue];
|
|
6178
6632
|
this.eventQueue = [];
|