@grainql/analytics-web 2.5.4 → 2.7.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/attention-quality.d.ts +128 -0
- package/dist/attention-quality.d.ts.map +1 -0
- package/dist/attention-quality.js +246 -0
- package/dist/cjs/activity.js +1 -1
- package/dist/cjs/activity.js.map +1 -1
- package/dist/cjs/attention-quality.d.ts +128 -0
- package/dist/cjs/attention-quality.d.ts.map +1 -0
- package/dist/cjs/attention-quality.js +246 -0
- package/dist/cjs/attention-quality.js.map +1 -0
- 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 +93 -0
- package/dist/cjs/heatmap-tracking.d.ts.map +1 -0
- package/dist/cjs/heatmap-tracking.js +499 -0
- package/dist/cjs/heatmap-tracking.js.map +1 -0
- package/dist/cjs/index.d.ts +11 -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 +3 -0
- package/dist/cjs/section-tracking.d.ts.map +1 -1
- package/dist/cjs/section-tracking.js +30 -7
- package/dist/cjs/section-tracking.js.map +1 -1
- package/dist/cjs/types/auto-tracking.d.ts +3 -0
- package/dist/cjs/types/auto-tracking.d.ts.map +1 -1
- package/dist/cjs/types/heatmap-tracking.d.ts +44 -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/attention-quality.d.ts +128 -0
- package/dist/esm/attention-quality.d.ts.map +1 -0
- package/dist/esm/attention-quality.js +242 -0
- package/dist/esm/attention-quality.js.map +1 -0
- 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 +93 -0
- package/dist/esm/heatmap-tracking.d.ts.map +1 -0
- package/dist/esm/heatmap-tracking.js +495 -0
- package/dist/esm/heatmap-tracking.js.map +1 -0
- package/dist/esm/index.d.ts +11 -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 +3 -0
- package/dist/esm/section-tracking.d.ts.map +1 -1
- package/dist/esm/section-tracking.js +30 -7
- package/dist/esm/section-tracking.js.map +1 -1
- package/dist/esm/types/auto-tracking.d.ts +3 -0
- package/dist/esm/types/auto-tracking.d.ts.map +1 -1
- package/dist/esm/types/heatmap-tracking.d.ts +44 -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 +93 -0
- package/dist/heatmap-tracking.d.ts.map +1 -0
- package/dist/heatmap-tracking.js +499 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.dev.js +801 -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 +70 -38
- package/dist/index.mjs +70 -38
- 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 +3 -0
- package/dist/section-tracking.d.ts.map +1 -1
- package/dist/section-tracking.js +30 -7
- package/dist/types/auto-tracking.d.ts +3 -0
- package/dist/types/auto-tracking.d.ts.map +1 -1
- package/dist/types/heatmap-tracking.d.ts +44 -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.7.0 | MIT License | Development Build */
|
|
2
2
|
"use strict";
|
|
3
3
|
var Grain = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -22,6 +22,696 @@ var Grain = (() => {
|
|
|
22
22
|
};
|
|
23
23
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
24
24
|
|
|
25
|
+
// src/attention-quality.ts
|
|
26
|
+
var DEFAULT_OPTIONS, AttentionQualityManager;
|
|
27
|
+
var init_attention_quality = __esm({
|
|
28
|
+
"src/attention-quality.ts"() {
|
|
29
|
+
"use strict";
|
|
30
|
+
DEFAULT_OPTIONS = {
|
|
31
|
+
maxSectionDuration: 9e3,
|
|
32
|
+
// 9 seconds
|
|
33
|
+
minScrollDistance: 100,
|
|
34
|
+
// 100 pixels
|
|
35
|
+
idleThreshold: 3e4
|
|
36
|
+
// 30 seconds
|
|
37
|
+
};
|
|
38
|
+
AttentionQualityManager = class {
|
|
39
|
+
constructor(activityDetector, options = {}) {
|
|
40
|
+
this.isDestroyed = false;
|
|
41
|
+
// Page visibility tracking
|
|
42
|
+
this.isPageVisible = true;
|
|
43
|
+
this.visibilityChangeHandler = null;
|
|
44
|
+
// Section attention state
|
|
45
|
+
this.sectionStates = /* @__PURE__ */ new Map();
|
|
46
|
+
// Policies applied reasons (for debugging/traceability)
|
|
47
|
+
this.lastFilterReason = null;
|
|
48
|
+
this.activityDetector = activityDetector;
|
|
49
|
+
this.options = {
|
|
50
|
+
...DEFAULT_OPTIONS,
|
|
51
|
+
...options,
|
|
52
|
+
debug: options.debug ?? false
|
|
53
|
+
};
|
|
54
|
+
this.setupPageVisibilityTracking();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Setup page visibility tracking
|
|
58
|
+
*/
|
|
59
|
+
setupPageVisibilityTracking() {
|
|
60
|
+
if (typeof document === "undefined")
|
|
61
|
+
return;
|
|
62
|
+
this.isPageVisible = document.visibilityState === "visible";
|
|
63
|
+
this.visibilityChangeHandler = () => {
|
|
64
|
+
const wasVisible = this.isPageVisible;
|
|
65
|
+
this.isPageVisible = document.visibilityState === "visible";
|
|
66
|
+
if (!this.isPageVisible && wasVisible) {
|
|
67
|
+
this.log("Page hidden - tracking paused");
|
|
68
|
+
} else if (this.isPageVisible && !wasVisible) {
|
|
69
|
+
this.log("Page visible - tracking resumed");
|
|
70
|
+
this.resetAllSections();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
document.addEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if tracking should be allowed (global check)
|
|
77
|
+
* Returns true if tracking is allowed, false if it should be paused
|
|
78
|
+
*/
|
|
79
|
+
shouldTrack() {
|
|
80
|
+
if (!this.isPageVisible) {
|
|
81
|
+
this.lastFilterReason = "page_hidden";
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (!this.activityDetector.isActive(this.options.idleThreshold)) {
|
|
85
|
+
this.lastFilterReason = "user_idle";
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
this.lastFilterReason = null;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if section view tracking should be allowed for a specific section
|
|
93
|
+
* @param sectionName - Section identifier
|
|
94
|
+
* @param currentScrollY - Current scroll position
|
|
95
|
+
* @returns Object with shouldTrack boolean and optional reason
|
|
96
|
+
*/
|
|
97
|
+
shouldTrackSection(sectionName, currentScrollY) {
|
|
98
|
+
if (!this.shouldTrack()) {
|
|
99
|
+
return {
|
|
100
|
+
shouldTrack: false,
|
|
101
|
+
reason: this.lastFilterReason || "global_policy"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
let state = this.sectionStates.get(sectionName);
|
|
105
|
+
if (!state) {
|
|
106
|
+
state = {
|
|
107
|
+
sectionName,
|
|
108
|
+
currentDuration: 0,
|
|
109
|
+
lastScrollPosition: currentScrollY,
|
|
110
|
+
lastResetTime: Date.now()
|
|
111
|
+
};
|
|
112
|
+
this.sectionStates.set(sectionName, state);
|
|
113
|
+
}
|
|
114
|
+
const scrollDistance = Math.abs(currentScrollY - state.lastScrollPosition);
|
|
115
|
+
const hasScrolledEnough = scrollDistance >= this.options.minScrollDistance;
|
|
116
|
+
if (hasScrolledEnough) {
|
|
117
|
+
this.log(`Section "${sectionName}": Attention reset due to ${Math.round(scrollDistance)}px scroll`);
|
|
118
|
+
state.currentDuration = 0;
|
|
119
|
+
state.lastScrollPosition = currentScrollY;
|
|
120
|
+
state.lastResetTime = Date.now();
|
|
121
|
+
return {
|
|
122
|
+
shouldTrack: true,
|
|
123
|
+
resetAttention: true
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (state.currentDuration >= this.options.maxSectionDuration) {
|
|
127
|
+
return {
|
|
128
|
+
shouldTrack: false,
|
|
129
|
+
reason: "max_duration_reached"
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
shouldTrack: true
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Update section duration (call this when tracking a section view event)
|
|
138
|
+
* @param sectionName - Section identifier
|
|
139
|
+
* @param durationMs - Duration to add to current attention block
|
|
140
|
+
*/
|
|
141
|
+
updateSectionDuration(sectionName, durationMs) {
|
|
142
|
+
const state = this.sectionStates.get(sectionName);
|
|
143
|
+
if (state) {
|
|
144
|
+
state.currentDuration += durationMs;
|
|
145
|
+
if (state.currentDuration >= this.options.maxSectionDuration) {
|
|
146
|
+
this.log(`Section "${sectionName}": Max duration cap reached (${state.currentDuration}ms)`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Reset attention for a specific section (call when user navigates to different section)
|
|
152
|
+
* @param sectionName - Section identifier
|
|
153
|
+
*/
|
|
154
|
+
resetSection(sectionName) {
|
|
155
|
+
const state = this.sectionStates.get(sectionName);
|
|
156
|
+
if (state) {
|
|
157
|
+
this.log(`Section "${sectionName}": Attention reset (section exit)`);
|
|
158
|
+
state.currentDuration = 0;
|
|
159
|
+
state.lastResetTime = Date.now();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Reset all section attention states
|
|
164
|
+
*/
|
|
165
|
+
resetAllSections() {
|
|
166
|
+
this.log("Resetting all section attention states");
|
|
167
|
+
for (const state of this.sectionStates.values()) {
|
|
168
|
+
state.currentDuration = 0;
|
|
169
|
+
state.lastResetTime = Date.now();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get current attention state for a section (for debugging/monitoring)
|
|
174
|
+
*/
|
|
175
|
+
getSectionState(sectionName) {
|
|
176
|
+
return this.sectionStates.get(sectionName);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get reason why last tracking attempt was filtered
|
|
180
|
+
*/
|
|
181
|
+
getLastFilterReason() {
|
|
182
|
+
return this.lastFilterReason;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if scroll tracking should be allowed
|
|
186
|
+
* Similar to shouldTrack() but also checks scroll-specific conditions
|
|
187
|
+
*/
|
|
188
|
+
shouldTrackScroll(previousScrollY, currentScrollY) {
|
|
189
|
+
if (!this.shouldTrack()) {
|
|
190
|
+
return {
|
|
191
|
+
shouldTrack: false,
|
|
192
|
+
reason: this.lastFilterReason || "global_policy"
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const scrollDistance = Math.abs(currentScrollY - previousScrollY);
|
|
196
|
+
if (scrollDistance < 10) {
|
|
197
|
+
return {
|
|
198
|
+
shouldTrack: false,
|
|
199
|
+
reason: "scroll_too_small"
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
shouldTrack: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get all active policies as object (for monitoring/debugging)
|
|
208
|
+
*/
|
|
209
|
+
getPolicies() {
|
|
210
|
+
return {
|
|
211
|
+
maxSectionDuration: this.options.maxSectionDuration,
|
|
212
|
+
minScrollDistance: this.options.minScrollDistance,
|
|
213
|
+
idleThreshold: this.options.idleThreshold
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get current tracking state (for monitoring/debugging)
|
|
218
|
+
*/
|
|
219
|
+
getTrackingState() {
|
|
220
|
+
return {
|
|
221
|
+
isPageVisible: this.isPageVisible,
|
|
222
|
+
isUserActive: this.activityDetector.isActive(this.options.idleThreshold),
|
|
223
|
+
timeSinceLastActivity: this.activityDetector.getTimeSinceLastActivity(),
|
|
224
|
+
activeSections: this.sectionStates.size
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Log debug messages
|
|
229
|
+
*/
|
|
230
|
+
log(...args) {
|
|
231
|
+
if (this.options.debug) {
|
|
232
|
+
console.log("[AttentionQuality]", ...args);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Destroy and cleanup
|
|
237
|
+
*/
|
|
238
|
+
destroy() {
|
|
239
|
+
if (this.isDestroyed)
|
|
240
|
+
return;
|
|
241
|
+
this.isDestroyed = true;
|
|
242
|
+
if (this.visibilityChangeHandler && typeof document !== "undefined") {
|
|
243
|
+
document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
244
|
+
this.visibilityChangeHandler = null;
|
|
245
|
+
}
|
|
246
|
+
this.sectionStates.clear();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// src/heatmap-tracking.ts
|
|
253
|
+
var heatmap_tracking_exports = {};
|
|
254
|
+
__export(heatmap_tracking_exports, {
|
|
255
|
+
HeatmapTrackingManager: () => HeatmapTrackingManager
|
|
256
|
+
});
|
|
257
|
+
var DEFAULT_OPTIONS2, HeatmapTrackingManager;
|
|
258
|
+
var init_heatmap_tracking = __esm({
|
|
259
|
+
"src/heatmap-tracking.ts"() {
|
|
260
|
+
"use strict";
|
|
261
|
+
init_attention_quality();
|
|
262
|
+
DEFAULT_OPTIONS2 = {
|
|
263
|
+
scrollDebounceDelay: 100,
|
|
264
|
+
batchDelay: 2e3,
|
|
265
|
+
maxBatchSize: 20,
|
|
266
|
+
debug: false
|
|
267
|
+
};
|
|
268
|
+
HeatmapTrackingManager = class {
|
|
269
|
+
constructor(tracker, options = {}) {
|
|
270
|
+
this.isDestroyed = false;
|
|
271
|
+
// Tracking state
|
|
272
|
+
this.currentScrollState = null;
|
|
273
|
+
this.pendingClicks = [];
|
|
274
|
+
this.pendingScrolls = [];
|
|
275
|
+
// Timers
|
|
276
|
+
this.scrollDebounceTimer = null;
|
|
277
|
+
this.batchTimer = null;
|
|
278
|
+
this.scrollTrackingTimer = null;
|
|
279
|
+
this.periodicScrollTimer = null;
|
|
280
|
+
// Scroll tracking
|
|
281
|
+
this.lastScrollPosition = 0;
|
|
282
|
+
this.lastScrollTime = Date.now();
|
|
283
|
+
this.SPLIT_DURATION = 3e3;
|
|
284
|
+
this.tracker = tracker;
|
|
285
|
+
this.options = { ...DEFAULT_OPTIONS2, ...options };
|
|
286
|
+
this.attentionQuality = new AttentionQualityManager(
|
|
287
|
+
tracker.getActivityDetector(),
|
|
288
|
+
{
|
|
289
|
+
maxSectionDuration: 9e3,
|
|
290
|
+
// 9 seconds per viewport section
|
|
291
|
+
minScrollDistance: 100,
|
|
292
|
+
// 100 pixels
|
|
293
|
+
idleThreshold: 3e4,
|
|
294
|
+
// 30 seconds
|
|
295
|
+
debug: this.options.debug
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
299
|
+
if (document.readyState === "loading") {
|
|
300
|
+
document.addEventListener("DOMContentLoaded", () => this.initialize());
|
|
301
|
+
} else {
|
|
302
|
+
setTimeout(() => this.initialize(), 0);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Initialize heatmap tracking
|
|
308
|
+
*/
|
|
309
|
+
initialize() {
|
|
310
|
+
if (this.isDestroyed)
|
|
311
|
+
return;
|
|
312
|
+
this.log("Initializing heatmap tracking");
|
|
313
|
+
this.setupClickTracking();
|
|
314
|
+
this.setupScrollTracking();
|
|
315
|
+
this.startScrollTracking();
|
|
316
|
+
this.setupUnloadHandler();
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Setup click event tracking
|
|
320
|
+
*/
|
|
321
|
+
setupClickTracking() {
|
|
322
|
+
if (typeof document === "undefined")
|
|
323
|
+
return;
|
|
324
|
+
const clickHandler = (event) => {
|
|
325
|
+
if (this.isDestroyed)
|
|
326
|
+
return;
|
|
327
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
328
|
+
return;
|
|
329
|
+
this.handleClick(event);
|
|
330
|
+
};
|
|
331
|
+
document.addEventListener("click", clickHandler, { passive: true, capture: true });
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Setup scroll event tracking
|
|
335
|
+
*/
|
|
336
|
+
setupScrollTracking() {
|
|
337
|
+
if (typeof window === "undefined")
|
|
338
|
+
return;
|
|
339
|
+
const scrollHandler = () => {
|
|
340
|
+
if (this.scrollDebounceTimer !== null) {
|
|
341
|
+
clearTimeout(this.scrollDebounceTimer);
|
|
342
|
+
}
|
|
343
|
+
this.scrollDebounceTimer = window.setTimeout(() => {
|
|
344
|
+
this.handleScroll();
|
|
345
|
+
this.scrollDebounceTimer = null;
|
|
346
|
+
}, this.options.scrollDebounceDelay);
|
|
347
|
+
};
|
|
348
|
+
window.addEventListener("scroll", scrollHandler, { passive: true });
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Start periodic scroll state tracking
|
|
352
|
+
*/
|
|
353
|
+
startScrollTracking() {
|
|
354
|
+
if (typeof window === "undefined")
|
|
355
|
+
return;
|
|
356
|
+
this.updateScrollState();
|
|
357
|
+
this.scrollTrackingTimer = window.setInterval(() => {
|
|
358
|
+
if (this.isDestroyed)
|
|
359
|
+
return;
|
|
360
|
+
this.updateScrollState();
|
|
361
|
+
}, 500);
|
|
362
|
+
this.startPeriodicScrollTracking();
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Start periodic scroll tracking (sends events every 3 seconds)
|
|
366
|
+
*/
|
|
367
|
+
startPeriodicScrollTracking() {
|
|
368
|
+
if (typeof window === "undefined")
|
|
369
|
+
return;
|
|
370
|
+
this.periodicScrollTimer = window.setInterval(() => {
|
|
371
|
+
if (this.isDestroyed || !this.currentScrollState)
|
|
372
|
+
return;
|
|
373
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
374
|
+
return;
|
|
375
|
+
if (!this.attentionQuality.shouldTrack()) {
|
|
376
|
+
this.log("Scroll tracking paused:", this.attentionQuality.getLastFilterReason());
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const currentTime = Date.now();
|
|
380
|
+
const duration = currentTime - this.currentScrollState.entryTime;
|
|
381
|
+
if (duration > 1e3) {
|
|
382
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
383
|
+
const viewportHeight = window.innerHeight;
|
|
384
|
+
const pageHeight = document.documentElement.scrollHeight;
|
|
385
|
+
const sectionKey = `viewport_section_${this.currentScrollState.viewportSection}`;
|
|
386
|
+
const attentionCheck = this.attentionQuality.shouldTrackSection(sectionKey, scrollY);
|
|
387
|
+
if (attentionCheck.resetAttention) {
|
|
388
|
+
this.log(`Viewport section ${this.currentScrollState.viewportSection}: Attention reset`);
|
|
389
|
+
this.currentScrollState.entryTime = currentTime;
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (!attentionCheck.shouldTrack) {
|
|
393
|
+
this.log(`Viewport section ${this.currentScrollState.viewportSection}: ${attentionCheck.reason}`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const scrollData = {
|
|
397
|
+
pageUrl: window.location.href,
|
|
398
|
+
viewportSection: this.currentScrollState.viewportSection,
|
|
399
|
+
scrollDepthPx: scrollY,
|
|
400
|
+
durationMs: duration,
|
|
401
|
+
entryTimestamp: this.currentScrollState.entryTime,
|
|
402
|
+
exitTimestamp: currentTime,
|
|
403
|
+
pageHeight,
|
|
404
|
+
viewportHeight
|
|
405
|
+
};
|
|
406
|
+
this.tracker.trackSystemEvent("_grain_heatmap_scroll", {
|
|
407
|
+
page_url: scrollData.pageUrl,
|
|
408
|
+
viewport_section: scrollData.viewportSection,
|
|
409
|
+
scroll_depth_px: scrollData.scrollDepthPx,
|
|
410
|
+
duration_ms: scrollData.durationMs,
|
|
411
|
+
entry_timestamp: scrollData.entryTimestamp,
|
|
412
|
+
exit_timestamp: scrollData.exitTimestamp,
|
|
413
|
+
page_height: scrollData.pageHeight,
|
|
414
|
+
viewport_height: scrollData.viewportHeight,
|
|
415
|
+
is_split: true
|
|
416
|
+
// Flag to indicate periodic tracking, not final exit
|
|
417
|
+
}, { flush: true });
|
|
418
|
+
this.attentionQuality.updateSectionDuration(sectionKey, duration);
|
|
419
|
+
this.currentScrollState.entryTime = currentTime;
|
|
420
|
+
}
|
|
421
|
+
}, this.SPLIT_DURATION);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Setup page unload handler to beacon remaining data
|
|
425
|
+
*/
|
|
426
|
+
setupUnloadHandler() {
|
|
427
|
+
if (typeof window === "undefined")
|
|
428
|
+
return;
|
|
429
|
+
const unloadHandler = () => {
|
|
430
|
+
if (this.currentScrollState) {
|
|
431
|
+
const currentTime = Date.now();
|
|
432
|
+
const duration = currentTime - this.currentScrollState.entryTime;
|
|
433
|
+
if (duration > 100) {
|
|
434
|
+
const scrollData = {
|
|
435
|
+
pageUrl: window.location.href,
|
|
436
|
+
viewportSection: this.currentScrollState.viewportSection,
|
|
437
|
+
scrollDepthPx: this.currentScrollState.scrollDepthPx,
|
|
438
|
+
durationMs: duration,
|
|
439
|
+
entryTimestamp: this.currentScrollState.entryTime,
|
|
440
|
+
exitTimestamp: currentTime,
|
|
441
|
+
pageHeight: document.documentElement.scrollHeight,
|
|
442
|
+
viewportHeight: window.innerHeight
|
|
443
|
+
};
|
|
444
|
+
this.pendingScrolls.push(scrollData);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
this.flushPendingEventsWithBeacon();
|
|
448
|
+
};
|
|
449
|
+
window.addEventListener("beforeunload", unloadHandler);
|
|
450
|
+
window.addEventListener("pagehide", unloadHandler);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Handle click event
|
|
454
|
+
*/
|
|
455
|
+
handleClick(event) {
|
|
456
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
457
|
+
return;
|
|
458
|
+
const element = event.target;
|
|
459
|
+
if (!element)
|
|
460
|
+
return;
|
|
461
|
+
const pageUrl = window.location.href;
|
|
462
|
+
const xpath = this.generateXPath(element);
|
|
463
|
+
const viewportX = Math.round(event.clientX);
|
|
464
|
+
const viewportY = Math.round(event.clientY);
|
|
465
|
+
const pageX = Math.round(event.pageX);
|
|
466
|
+
const pageY = Math.round(event.pageY);
|
|
467
|
+
const elementTag = element.tagName?.toLowerCase() || "unknown";
|
|
468
|
+
const elementText = element.textContent?.trim().substring(0, 100);
|
|
469
|
+
const clickData = {
|
|
470
|
+
pageUrl,
|
|
471
|
+
xpath,
|
|
472
|
+
viewportX,
|
|
473
|
+
viewportY,
|
|
474
|
+
pageX,
|
|
475
|
+
pageY,
|
|
476
|
+
elementTag,
|
|
477
|
+
elementText: elementText || void 0,
|
|
478
|
+
timestamp: Date.now()
|
|
479
|
+
};
|
|
480
|
+
const isNavigationLink = element instanceof HTMLAnchorElement && element.href;
|
|
481
|
+
if (isNavigationLink) {
|
|
482
|
+
this.tracker.trackSystemEvent("_grain_heatmap_click", {
|
|
483
|
+
page_url: clickData.pageUrl,
|
|
484
|
+
xpath: clickData.xpath,
|
|
485
|
+
viewport_x: clickData.viewportX,
|
|
486
|
+
viewport_y: clickData.viewportY,
|
|
487
|
+
page_x: clickData.pageX,
|
|
488
|
+
page_y: clickData.pageY,
|
|
489
|
+
element_tag: clickData.elementTag,
|
|
490
|
+
element_text: clickData.elementText,
|
|
491
|
+
timestamp: clickData.timestamp
|
|
492
|
+
}, { flush: true });
|
|
493
|
+
} else {
|
|
494
|
+
this.pendingClicks.push(clickData);
|
|
495
|
+
this.considerBatchFlush();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Handle scroll event
|
|
500
|
+
*/
|
|
501
|
+
handleScroll() {
|
|
502
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
503
|
+
return;
|
|
504
|
+
this.updateScrollState();
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Update current scroll state
|
|
508
|
+
*/
|
|
509
|
+
updateScrollState() {
|
|
510
|
+
if (typeof window === "undefined")
|
|
511
|
+
return;
|
|
512
|
+
if (!this.tracker.hasConsent("analytics"))
|
|
513
|
+
return;
|
|
514
|
+
const currentTime = Date.now();
|
|
515
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
516
|
+
const viewportHeight = window.innerHeight;
|
|
517
|
+
const pageHeight = document.documentElement.scrollHeight;
|
|
518
|
+
const viewportSection = Math.floor(scrollY / viewportHeight);
|
|
519
|
+
if (this.currentScrollState && this.currentScrollState.viewportSection !== viewportSection) {
|
|
520
|
+
const duration = currentTime - this.currentScrollState.entryTime;
|
|
521
|
+
if (duration > 100) {
|
|
522
|
+
const scrollData = {
|
|
523
|
+
pageUrl: window.location.href,
|
|
524
|
+
viewportSection: this.currentScrollState.viewportSection,
|
|
525
|
+
scrollDepthPx: this.currentScrollState.scrollDepthPx,
|
|
526
|
+
durationMs: duration,
|
|
527
|
+
entryTimestamp: this.currentScrollState.entryTime,
|
|
528
|
+
exitTimestamp: currentTime,
|
|
529
|
+
pageHeight,
|
|
530
|
+
viewportHeight
|
|
531
|
+
};
|
|
532
|
+
this.pendingScrolls.push(scrollData);
|
|
533
|
+
}
|
|
534
|
+
const prevSectionKey = `viewport_section_${this.currentScrollState.viewportSection}`;
|
|
535
|
+
this.attentionQuality.resetSection(prevSectionKey);
|
|
536
|
+
}
|
|
537
|
+
if (!this.currentScrollState || this.currentScrollState.viewportSection !== viewportSection) {
|
|
538
|
+
this.currentScrollState = {
|
|
539
|
+
viewportSection,
|
|
540
|
+
entryTime: currentTime,
|
|
541
|
+
scrollDepthPx: scrollY
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
this.lastScrollPosition = scrollY;
|
|
545
|
+
this.lastScrollTime = currentTime;
|
|
546
|
+
this.considerBatchFlush();
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Generate XPath for an element
|
|
550
|
+
*/
|
|
551
|
+
generateXPath(element) {
|
|
552
|
+
if (!element)
|
|
553
|
+
return "";
|
|
554
|
+
if (element.id) {
|
|
555
|
+
return `//*[@id="${element.id}"]`;
|
|
556
|
+
}
|
|
557
|
+
const paths = [];
|
|
558
|
+
let currentElement = element;
|
|
559
|
+
while (currentElement && currentElement.nodeType === Node.ELEMENT_NODE) {
|
|
560
|
+
let index = 0;
|
|
561
|
+
let sibling = currentElement;
|
|
562
|
+
while (sibling) {
|
|
563
|
+
sibling = sibling.previousElementSibling;
|
|
564
|
+
if (sibling && sibling.nodeName === currentElement.nodeName) {
|
|
565
|
+
index++;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const tagName = currentElement.nodeName.toLowerCase();
|
|
569
|
+
const pathIndex = index > 0 ? `[${index + 1}]` : "";
|
|
570
|
+
paths.unshift(`${tagName}${pathIndex}`);
|
|
571
|
+
currentElement = currentElement.parentElement;
|
|
572
|
+
}
|
|
573
|
+
return paths.length ? `/${paths.join("/")}` : "";
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Consider flushing batched events
|
|
577
|
+
*/
|
|
578
|
+
considerBatchFlush() {
|
|
579
|
+
const totalEvents = this.pendingClicks.length + this.pendingScrolls.length;
|
|
580
|
+
if (totalEvents >= this.options.maxBatchSize) {
|
|
581
|
+
this.flushPendingEvents();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (this.batchTimer === null && totalEvents > 0) {
|
|
585
|
+
this.batchTimer = window.setTimeout(() => {
|
|
586
|
+
this.flushPendingEvents();
|
|
587
|
+
this.batchTimer = null;
|
|
588
|
+
}, this.options.batchDelay);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Flush pending events
|
|
593
|
+
*/
|
|
594
|
+
flushPendingEvents() {
|
|
595
|
+
if (this.isDestroyed)
|
|
596
|
+
return;
|
|
597
|
+
if (!this.tracker.hasConsent("analytics")) {
|
|
598
|
+
this.pendingClicks = [];
|
|
599
|
+
this.pendingScrolls = [];
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (this.pendingClicks.length > 0) {
|
|
603
|
+
for (const clickData of this.pendingClicks) {
|
|
604
|
+
this.tracker.trackSystemEvent("_grain_heatmap_click", {
|
|
605
|
+
page_url: clickData.pageUrl,
|
|
606
|
+
xpath: clickData.xpath,
|
|
607
|
+
viewport_x: clickData.viewportX,
|
|
608
|
+
viewport_y: clickData.viewportY,
|
|
609
|
+
page_x: clickData.pageX,
|
|
610
|
+
page_y: clickData.pageY,
|
|
611
|
+
element_tag: clickData.elementTag,
|
|
612
|
+
element_text: clickData.elementText,
|
|
613
|
+
timestamp: clickData.timestamp
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
this.pendingClicks = [];
|
|
617
|
+
}
|
|
618
|
+
if (this.pendingScrolls.length > 0) {
|
|
619
|
+
for (const scrollData of this.pendingScrolls) {
|
|
620
|
+
this.tracker.trackSystemEvent("_grain_heatmap_scroll", {
|
|
621
|
+
page_url: scrollData.pageUrl,
|
|
622
|
+
viewport_section: scrollData.viewportSection,
|
|
623
|
+
scroll_depth_px: scrollData.scrollDepthPx,
|
|
624
|
+
duration_ms: scrollData.durationMs,
|
|
625
|
+
entry_timestamp: scrollData.entryTimestamp,
|
|
626
|
+
exit_timestamp: scrollData.exitTimestamp,
|
|
627
|
+
page_height: scrollData.pageHeight,
|
|
628
|
+
viewport_height: scrollData.viewportHeight
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
this.pendingScrolls = [];
|
|
632
|
+
}
|
|
633
|
+
if (this.batchTimer !== null) {
|
|
634
|
+
clearTimeout(this.batchTimer);
|
|
635
|
+
this.batchTimer = null;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Flush pending events with beacon (for page unload)
|
|
640
|
+
*/
|
|
641
|
+
flushPendingEventsWithBeacon() {
|
|
642
|
+
if (!this.tracker.hasConsent("analytics")) {
|
|
643
|
+
this.pendingClicks = [];
|
|
644
|
+
this.pendingScrolls = [];
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (this.pendingClicks.length > 0) {
|
|
648
|
+
for (const clickData of this.pendingClicks) {
|
|
649
|
+
this.tracker.trackSystemEvent("_grain_heatmap_click", {
|
|
650
|
+
page_url: clickData.pageUrl,
|
|
651
|
+
xpath: clickData.xpath,
|
|
652
|
+
viewport_x: clickData.viewportX,
|
|
653
|
+
viewport_y: clickData.viewportY,
|
|
654
|
+
page_x: clickData.pageX,
|
|
655
|
+
page_y: clickData.pageY,
|
|
656
|
+
element_tag: clickData.elementTag,
|
|
657
|
+
element_text: clickData.elementText,
|
|
658
|
+
timestamp: clickData.timestamp
|
|
659
|
+
}, { flush: true });
|
|
660
|
+
}
|
|
661
|
+
this.pendingClicks = [];
|
|
662
|
+
}
|
|
663
|
+
if (this.pendingScrolls.length > 0) {
|
|
664
|
+
for (const scrollData of this.pendingScrolls) {
|
|
665
|
+
this.tracker.trackSystemEvent("_grain_heatmap_scroll", {
|
|
666
|
+
page_url: scrollData.pageUrl,
|
|
667
|
+
viewport_section: scrollData.viewportSection,
|
|
668
|
+
scroll_depth_px: scrollData.scrollDepthPx,
|
|
669
|
+
duration_ms: scrollData.durationMs,
|
|
670
|
+
entry_timestamp: scrollData.entryTimestamp,
|
|
671
|
+
exit_timestamp: scrollData.exitTimestamp,
|
|
672
|
+
page_height: scrollData.pageHeight,
|
|
673
|
+
viewport_height: scrollData.viewportHeight
|
|
674
|
+
}, { flush: true });
|
|
675
|
+
}
|
|
676
|
+
this.pendingScrolls = [];
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Log debug message
|
|
681
|
+
*/
|
|
682
|
+
log(...args) {
|
|
683
|
+
if (this.options.debug) {
|
|
684
|
+
this.tracker.log("[Heatmap Tracking]", ...args);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Destroy the tracking manager
|
|
689
|
+
*/
|
|
690
|
+
destroy() {
|
|
691
|
+
this.isDestroyed = true;
|
|
692
|
+
if (this.scrollDebounceTimer !== null) {
|
|
693
|
+
clearTimeout(this.scrollDebounceTimer);
|
|
694
|
+
this.scrollDebounceTimer = null;
|
|
695
|
+
}
|
|
696
|
+
if (this.batchTimer !== null) {
|
|
697
|
+
clearTimeout(this.batchTimer);
|
|
698
|
+
this.batchTimer = null;
|
|
699
|
+
}
|
|
700
|
+
if (this.scrollTrackingTimer !== null) {
|
|
701
|
+
clearInterval(this.scrollTrackingTimer);
|
|
702
|
+
this.scrollTrackingTimer = null;
|
|
703
|
+
}
|
|
704
|
+
if (this.periodicScrollTimer !== null) {
|
|
705
|
+
clearInterval(this.periodicScrollTimer);
|
|
706
|
+
this.periodicScrollTimer = null;
|
|
707
|
+
}
|
|
708
|
+
this.attentionQuality.destroy();
|
|
709
|
+
this.flushPendingEvents();
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
|
|
25
715
|
// src/interaction-tracking.ts
|
|
26
716
|
var interaction_tracking_exports = {};
|
|
27
717
|
__export(interaction_tracking_exports, {
|
|
@@ -62,7 +752,7 @@ var Grain = (() => {
|
|
|
62
752
|
attachAllListeners() {
|
|
63
753
|
if (this.isDestroyed)
|
|
64
754
|
return;
|
|
65
|
-
this.log("Attaching interaction listeners
|
|
755
|
+
this.log("Attaching interaction listeners");
|
|
66
756
|
for (const interaction of this.interactions) {
|
|
67
757
|
this.attachInteractionListener(interaction);
|
|
68
758
|
}
|
|
@@ -92,7 +782,6 @@ var Grain = (() => {
|
|
|
92
782
|
handlers.push({ event: "focus", handler: focusHandler });
|
|
93
783
|
}
|
|
94
784
|
this.attachedListeners.set(element, handlers);
|
|
95
|
-
this.log("Attached listeners to element for:", interaction.eventName);
|
|
96
785
|
}
|
|
97
786
|
/**
|
|
98
787
|
* Handle click event on interaction
|
|
@@ -116,17 +805,12 @@ var Grain = (() => {
|
|
|
116
805
|
...isNavigationLink && { href: element.href },
|
|
117
806
|
timestamp: Date.now()
|
|
118
807
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
this.tracker.track(interaction.eventName, eventProperties);
|
|
808
|
+
const result = this.tracker.track(interaction.eventName, eventProperties, { flush: true });
|
|
809
|
+
if (result instanceof Promise) {
|
|
810
|
+
result.catch((error) => {
|
|
811
|
+
this.log("Failed to track click:", error);
|
|
812
|
+
});
|
|
128
813
|
}
|
|
129
|
-
this.log("Tracked click interaction:", interaction.eventName);
|
|
130
814
|
}
|
|
131
815
|
/**
|
|
132
816
|
* Handle focus event on interaction (for form fields)
|
|
@@ -147,7 +831,6 @@ var Grain = (() => {
|
|
|
147
831
|
element_class: element.className || void 0,
|
|
148
832
|
timestamp: Date.now()
|
|
149
833
|
});
|
|
150
|
-
this.log("Tracked focus interaction:", interaction.eventName);
|
|
151
834
|
}
|
|
152
835
|
/**
|
|
153
836
|
* Find element by XPath selector
|
|
@@ -241,7 +924,6 @@ var Grain = (() => {
|
|
|
241
924
|
element.removeEventListener(event, handler);
|
|
242
925
|
});
|
|
243
926
|
this.attachedListeners.delete(element);
|
|
244
|
-
this.log("Detached listeners from element");
|
|
245
927
|
}
|
|
246
928
|
/**
|
|
247
929
|
* Log debug messages
|
|
@@ -296,11 +978,12 @@ var Grain = (() => {
|
|
|
296
978
|
__export(section_tracking_exports, {
|
|
297
979
|
SectionTrackingManager: () => SectionTrackingManager
|
|
298
980
|
});
|
|
299
|
-
var
|
|
981
|
+
var DEFAULT_OPTIONS3, SectionTrackingManager;
|
|
300
982
|
var init_section_tracking = __esm({
|
|
301
983
|
"src/section-tracking.ts"() {
|
|
302
984
|
"use strict";
|
|
303
|
-
|
|
985
|
+
init_attention_quality();
|
|
986
|
+
DEFAULT_OPTIONS3 = {
|
|
304
987
|
minDwellTime: 1e3,
|
|
305
988
|
// 1 second minimum
|
|
306
989
|
scrollVelocityThreshold: 500,
|
|
@@ -313,7 +996,6 @@ var Grain = (() => {
|
|
|
313
996
|
debug: false
|
|
314
997
|
};
|
|
315
998
|
SectionTrackingManager = class {
|
|
316
|
-
// 3 seconds
|
|
317
999
|
constructor(tracker, sections, options = {}) {
|
|
318
1000
|
this.isDestroyed = false;
|
|
319
1001
|
// Tracking state
|
|
@@ -334,7 +1016,19 @@ var Grain = (() => {
|
|
|
334
1016
|
this.SPLIT_DURATION = 3e3;
|
|
335
1017
|
this.tracker = tracker;
|
|
336
1018
|
this.sections = sections;
|
|
337
|
-
this.options = { ...
|
|
1019
|
+
this.options = { ...DEFAULT_OPTIONS3, ...options };
|
|
1020
|
+
this.attentionQuality = new AttentionQualityManager(
|
|
1021
|
+
tracker.getActivityDetector(),
|
|
1022
|
+
{
|
|
1023
|
+
maxSectionDuration: 9e3,
|
|
1024
|
+
// 9 seconds
|
|
1025
|
+
minScrollDistance: 100,
|
|
1026
|
+
// 100 pixels
|
|
1027
|
+
idleThreshold: 3e4,
|
|
1028
|
+
// 30 seconds
|
|
1029
|
+
debug: this.options.debug
|
|
1030
|
+
}
|
|
1031
|
+
);
|
|
338
1032
|
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
339
1033
|
if (document.readyState === "loading") {
|
|
340
1034
|
document.addEventListener("DOMContentLoaded", () => this.initialize());
|
|
@@ -349,7 +1043,7 @@ var Grain = (() => {
|
|
|
349
1043
|
initialize() {
|
|
350
1044
|
if (this.isDestroyed)
|
|
351
1045
|
return;
|
|
352
|
-
this.log("Initializing section tracking
|
|
1046
|
+
this.log("Initializing section tracking");
|
|
353
1047
|
this.setupIntersectionObserver();
|
|
354
1048
|
this.setupScrollListener();
|
|
355
1049
|
this.initializeSections();
|
|
@@ -419,7 +1113,6 @@ var Grain = (() => {
|
|
|
419
1113
|
if (this.intersectionObserver) {
|
|
420
1114
|
this.intersectionObserver.observe(element);
|
|
421
1115
|
}
|
|
422
|
-
this.log("Section initialized and observed:", section.sectionName);
|
|
423
1116
|
}
|
|
424
1117
|
}
|
|
425
1118
|
/**
|
|
@@ -449,7 +1142,6 @@ var Grain = (() => {
|
|
|
449
1142
|
* Handle section entry (became visible)
|
|
450
1143
|
*/
|
|
451
1144
|
handleSectionEntry(state) {
|
|
452
|
-
this.log("Section entered view:", state.config.sectionName);
|
|
453
1145
|
state.entryTime = Date.now();
|
|
454
1146
|
state.entryScrollSpeed = this.scrollVelocity;
|
|
455
1147
|
state.lastScrollPosition = window.scrollY;
|
|
@@ -470,6 +1162,21 @@ var Grain = (() => {
|
|
|
470
1162
|
const now = Date.now();
|
|
471
1163
|
const duration = now - state.entryTime;
|
|
472
1164
|
if (duration >= this.options.minDwellTime) {
|
|
1165
|
+
const currentScrollY = window.scrollY;
|
|
1166
|
+
const attentionCheck = this.attentionQuality.shouldTrackSection(
|
|
1167
|
+
state.config.sectionName,
|
|
1168
|
+
currentScrollY
|
|
1169
|
+
);
|
|
1170
|
+
if (attentionCheck.resetAttention) {
|
|
1171
|
+
this.log(`Section "${state.config.sectionName}": Attention reset, restarting timer`);
|
|
1172
|
+
state.entryTime = now;
|
|
1173
|
+
state.entryScrollSpeed = this.scrollVelocity;
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (!attentionCheck.shouldTrack) {
|
|
1177
|
+
this.log(`Section "${state.config.sectionName}": Tracking paused - ${attentionCheck.reason}`);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
473
1180
|
const viewData = {
|
|
474
1181
|
sectionName: state.config.sectionName,
|
|
475
1182
|
sectionType: state.config.sectionType,
|
|
@@ -500,7 +1207,7 @@ var Grain = (() => {
|
|
|
500
1207
|
is_split: true
|
|
501
1208
|
// Flag to indicate this is a periodic split, not final exit
|
|
502
1209
|
});
|
|
503
|
-
this.
|
|
1210
|
+
this.attentionQuality.updateSectionDuration(state.config.sectionName, duration);
|
|
504
1211
|
state.entryTime = now;
|
|
505
1212
|
state.entryScrollSpeed = this.scrollVelocity;
|
|
506
1213
|
}
|
|
@@ -522,8 +1229,8 @@ var Grain = (() => {
|
|
|
522
1229
|
* Handle section exit (became invisible)
|
|
523
1230
|
*/
|
|
524
1231
|
handleSectionExit(state) {
|
|
525
|
-
this.log("Section exited view:", state.config.sectionName);
|
|
526
1232
|
this.stopPeriodicTracking(state.config.sectionName);
|
|
1233
|
+
this.attentionQuality.resetSection(state.config.sectionName);
|
|
527
1234
|
if (state.entryTime === null)
|
|
528
1235
|
return;
|
|
529
1236
|
state.exitTime = Date.now();
|
|
@@ -604,7 +1311,6 @@ var Grain = (() => {
|
|
|
604
1311
|
*/
|
|
605
1312
|
queueSectionView(viewData) {
|
|
606
1313
|
this.pendingEvents.push(viewData);
|
|
607
|
-
this.log("Queued section view:", viewData.sectionName, "duration:", viewData.duration);
|
|
608
1314
|
if (this.batchTimer === null) {
|
|
609
1315
|
this.batchTimer = window.setTimeout(() => {
|
|
610
1316
|
this.flushPendingEvents();
|
|
@@ -621,7 +1327,6 @@ var Grain = (() => {
|
|
|
621
1327
|
this.pendingEvents = [];
|
|
622
1328
|
return;
|
|
623
1329
|
}
|
|
624
|
-
this.log("Flushing", this.pendingEvents.length, "section view events");
|
|
625
1330
|
for (const viewData of this.pendingEvents) {
|
|
626
1331
|
this.tracker.trackSystemEvent("_grain_section_view", {
|
|
627
1332
|
section_name: viewData.sectionName,
|
|
@@ -722,6 +1427,7 @@ var Grain = (() => {
|
|
|
722
1427
|
this.intersectionObserver.disconnect();
|
|
723
1428
|
this.intersectionObserver = null;
|
|
724
1429
|
}
|
|
1430
|
+
this.attentionQuality.destroy();
|
|
725
1431
|
this.sectionStates.clear();
|
|
726
1432
|
this.xpathCache.clear();
|
|
727
1433
|
this.pendingEvents = [];
|
|
@@ -782,7 +1488,6 @@ var Grain = (() => {
|
|
|
782
1488
|
this.saveConsentState();
|
|
783
1489
|
}
|
|
784
1490
|
} catch (error) {
|
|
785
|
-
console.error("[Grain Consent] Failed to load consent state:", error);
|
|
786
1491
|
}
|
|
787
1492
|
}
|
|
788
1493
|
/**
|
|
@@ -794,7 +1499,6 @@ var Grain = (() => {
|
|
|
794
1499
|
try {
|
|
795
1500
|
localStorage.setItem(this.storageKey, JSON.stringify(this.consentState));
|
|
796
1501
|
} catch (error) {
|
|
797
|
-
console.error("[Grain Consent] Failed to save consent state:", error);
|
|
798
1502
|
}
|
|
799
1503
|
}
|
|
800
1504
|
/**
|
|
@@ -895,7 +1599,6 @@ var Grain = (() => {
|
|
|
895
1599
|
try {
|
|
896
1600
|
listener(this.consentState);
|
|
897
1601
|
} catch (error) {
|
|
898
|
-
console.error("[Grain Consent] Listener error:", error);
|
|
899
1602
|
}
|
|
900
1603
|
});
|
|
901
1604
|
}
|
|
@@ -909,7 +1612,6 @@ var Grain = (() => {
|
|
|
909
1612
|
localStorage.removeItem(this.storageKey);
|
|
910
1613
|
this.consentState = null;
|
|
911
1614
|
} catch (error) {
|
|
912
|
-
console.error("[Grain Consent] Failed to clear consent:", error);
|
|
913
1615
|
}
|
|
914
1616
|
}
|
|
915
1617
|
};
|
|
@@ -1089,7 +1791,6 @@ var Grain = (() => {
|
|
|
1089
1791
|
try {
|
|
1090
1792
|
listener();
|
|
1091
1793
|
} catch (error) {
|
|
1092
|
-
console.error("[Activity Detector] Listener error:", error);
|
|
1093
1794
|
}
|
|
1094
1795
|
}
|
|
1095
1796
|
}
|
|
@@ -1190,9 +1891,6 @@ var Grain = (() => {
|
|
|
1190
1891
|
}
|
|
1191
1892
|
this.tracker.trackSystemEvent("_grain_heartbeat", properties);
|
|
1192
1893
|
this.lastHeartbeatTime = now;
|
|
1193
|
-
if (this.config.debug) {
|
|
1194
|
-
console.log("[Heartbeat] Sent heartbeat:", properties);
|
|
1195
|
-
}
|
|
1196
1894
|
}
|
|
1197
1895
|
/**
|
|
1198
1896
|
* Destroy the heartbeat manager
|
|
@@ -1205,9 +1903,6 @@ var Grain = (() => {
|
|
|
1205
1903
|
this.heartbeatTimer = null;
|
|
1206
1904
|
}
|
|
1207
1905
|
this.isDestroyed = true;
|
|
1208
|
-
if (this.config.debug) {
|
|
1209
|
-
console.log("[Heartbeat] Destroyed");
|
|
1210
|
-
}
|
|
1211
1906
|
}
|
|
1212
1907
|
};
|
|
1213
1908
|
|
|
@@ -4408,9 +5103,6 @@ var Grain = (() => {
|
|
|
4408
5103
|
properties.viewport = `${window.innerWidth}x${window.innerHeight}`;
|
|
4409
5104
|
}
|
|
4410
5105
|
this.tracker.trackSystemEvent("page_view", properties);
|
|
4411
|
-
if (this.config.debug) {
|
|
4412
|
-
console.log("[Page Tracking] Tracked page view:", properties);
|
|
4413
|
-
}
|
|
4414
5106
|
}
|
|
4415
5107
|
/**
|
|
4416
5108
|
* Extract domain from URL
|
|
@@ -4533,9 +5225,6 @@ var Grain = (() => {
|
|
|
4533
5225
|
}
|
|
4534
5226
|
}
|
|
4535
5227
|
this.tracker.trackSystemEvent("page_view", baseProperties);
|
|
4536
|
-
if (this.config.debug) {
|
|
4537
|
-
console.log("[Page Tracking] Manually tracked page:", baseProperties);
|
|
4538
|
-
}
|
|
4539
5228
|
}
|
|
4540
5229
|
/**
|
|
4541
5230
|
* Get page view count for current session
|
|
@@ -4562,9 +5251,6 @@ var Grain = (() => {
|
|
|
4562
5251
|
window.removeEventListener("hashchange", this.handleHashChange);
|
|
4563
5252
|
}
|
|
4564
5253
|
this.isDestroyed = true;
|
|
4565
|
-
if (this.config.debug) {
|
|
4566
|
-
console.log("[Page Tracking] Destroyed");
|
|
4567
|
-
}
|
|
4568
5254
|
}
|
|
4569
5255
|
};
|
|
4570
5256
|
|
|
@@ -4592,6 +5278,7 @@ var Grain = (() => {
|
|
|
4592
5278
|
// Auto-tracking properties
|
|
4593
5279
|
this.interactionTrackingManager = null;
|
|
4594
5280
|
this.sectionTrackingManager = null;
|
|
5281
|
+
this.heatmapTrackingManager = null;
|
|
4595
5282
|
// Session tracking
|
|
4596
5283
|
this.sessionStartTime = Date.now();
|
|
4597
5284
|
this.sessionEventCount = 0;
|
|
@@ -4627,6 +5314,8 @@ var Grain = (() => {
|
|
|
4627
5314
|
// 5 minutes
|
|
4628
5315
|
enableAutoPageView: true,
|
|
4629
5316
|
stripQueryParams: true,
|
|
5317
|
+
// Heatmap Tracking defaults
|
|
5318
|
+
enableHeatmapTracking: true,
|
|
4630
5319
|
...config,
|
|
4631
5320
|
tenantId: config.tenantId
|
|
4632
5321
|
};
|
|
@@ -4649,6 +5338,9 @@ var Grain = (() => {
|
|
|
4649
5338
|
if (typeof window !== "undefined") {
|
|
4650
5339
|
this.initializeAutomaticTracking();
|
|
4651
5340
|
this.trackSessionStart();
|
|
5341
|
+
if (this.config.enableHeatmapTracking) {
|
|
5342
|
+
this.initializeHeatmapTracking();
|
|
5343
|
+
}
|
|
4652
5344
|
}
|
|
4653
5345
|
this.consentManager.addListener((state) => {
|
|
4654
5346
|
if (state.granted) {
|
|
@@ -4872,6 +5564,8 @@ var Grain = (() => {
|
|
|
4872
5564
|
* Log formatted error gracefully
|
|
4873
5565
|
*/
|
|
4874
5566
|
logError(formattedError) {
|
|
5567
|
+
if (!this.config.debug)
|
|
5568
|
+
return;
|
|
4875
5569
|
const { code, message, digest, timestamp, context } = formattedError;
|
|
4876
5570
|
const errorOutput = {
|
|
4877
5571
|
"\u{1F6A8} Grain Analytics Error": {
|
|
@@ -4889,9 +5583,7 @@ var Grain = (() => {
|
|
|
4889
5583
|
}
|
|
4890
5584
|
};
|
|
4891
5585
|
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
|
-
}
|
|
5586
|
+
console.error(`[Grain Analytics] ${code}: ${message} (${context}) - Events: ${digest.eventCount}, Props: ${digest.totalProperties}, Size: ${digest.totalSize}B`);
|
|
4895
5587
|
}
|
|
4896
5588
|
/**
|
|
4897
5589
|
* Safely execute a function with error handling
|
|
@@ -4990,7 +5682,6 @@ var Grain = (() => {
|
|
|
4990
5682
|
try {
|
|
4991
5683
|
const headers = await this.getAuthHeaders();
|
|
4992
5684
|
const url = `${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/multi`;
|
|
4993
|
-
this.log(`Sending ${events.length} events to ${url} (attempt ${attempt + 1})`);
|
|
4994
5685
|
const response = await fetch(url, {
|
|
4995
5686
|
method: "POST",
|
|
4996
5687
|
headers,
|
|
@@ -5055,7 +5746,6 @@ var Grain = (() => {
|
|
|
5055
5746
|
body,
|
|
5056
5747
|
keepalive: true
|
|
5057
5748
|
});
|
|
5058
|
-
this.log(`Successfully sent ${events.length} events via fetch (keepalive)`);
|
|
5059
5749
|
} catch (error) {
|
|
5060
5750
|
const formattedError = this.formatError(error, "sendEventsWithBeacon", events);
|
|
5061
5751
|
this.logError(formattedError);
|
|
@@ -5121,7 +5811,6 @@ var Grain = (() => {
|
|
|
5121
5811
|
debug: this.config.debug
|
|
5122
5812
|
}
|
|
5123
5813
|
);
|
|
5124
|
-
this.log("Heartbeat tracking initialized");
|
|
5125
5814
|
} catch (error) {
|
|
5126
5815
|
this.log("Failed to initialize heartbeat tracking:", error);
|
|
5127
5816
|
}
|
|
@@ -5136,19 +5825,48 @@ var Grain = (() => {
|
|
|
5136
5825
|
tenantId: this.config.tenantId
|
|
5137
5826
|
}
|
|
5138
5827
|
);
|
|
5139
|
-
this.log("Auto page view tracking initialized");
|
|
5140
5828
|
} catch (error) {
|
|
5141
5829
|
this.log("Failed to initialize page view tracking:", error);
|
|
5142
5830
|
}
|
|
5143
5831
|
}
|
|
5144
5832
|
this.initializeAutoTracking();
|
|
5145
5833
|
}
|
|
5834
|
+
/**
|
|
5835
|
+
* Initialize heatmap tracking
|
|
5836
|
+
*/
|
|
5837
|
+
initializeHeatmapTracking() {
|
|
5838
|
+
if (typeof window === "undefined")
|
|
5839
|
+
return;
|
|
5840
|
+
try {
|
|
5841
|
+
this.log("Initializing heatmap tracking");
|
|
5842
|
+
Promise.resolve().then(() => (init_heatmap_tracking(), heatmap_tracking_exports)).then(({ HeatmapTrackingManager: HeatmapTrackingManager2 }) => {
|
|
5843
|
+
try {
|
|
5844
|
+
this.heatmapTrackingManager = new HeatmapTrackingManager2(
|
|
5845
|
+
this,
|
|
5846
|
+
{
|
|
5847
|
+
scrollDebounceDelay: 100,
|
|
5848
|
+
batchDelay: 2e3,
|
|
5849
|
+
maxBatchSize: 20,
|
|
5850
|
+
debug: this.config.debug
|
|
5851
|
+
}
|
|
5852
|
+
);
|
|
5853
|
+
this.log("Heatmap tracking initialized");
|
|
5854
|
+
} catch (error) {
|
|
5855
|
+
this.log("Failed to initialize heatmap tracking:", error);
|
|
5856
|
+
}
|
|
5857
|
+
}).catch((error) => {
|
|
5858
|
+
this.log("Failed to load heatmap tracking module:", error);
|
|
5859
|
+
});
|
|
5860
|
+
} catch (error) {
|
|
5861
|
+
this.log("Failed to initialize heatmap tracking:", error);
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5146
5864
|
/**
|
|
5147
5865
|
* Initialize auto-tracking (interactions and sections)
|
|
5148
5866
|
*/
|
|
5149
5867
|
async initializeAutoTracking() {
|
|
5150
5868
|
try {
|
|
5151
|
-
this.log("Initializing auto-tracking
|
|
5869
|
+
this.log("Initializing auto-tracking");
|
|
5152
5870
|
const userId = this.globalUserId || this.persistentAnonymousUserId || this.generateUUID();
|
|
5153
5871
|
const currentUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
5154
5872
|
const request = {
|
|
@@ -5160,23 +5878,19 @@ var Grain = (() => {
|
|
|
5160
5878
|
};
|
|
5161
5879
|
const headers = await this.getAuthHeaders();
|
|
5162
5880
|
const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
|
|
5163
|
-
this.log("Fetching auto-tracking config from:", url);
|
|
5164
5881
|
const response = await fetch(url, {
|
|
5165
5882
|
method: "POST",
|
|
5166
5883
|
headers,
|
|
5167
5884
|
body: JSON.stringify(request)
|
|
5168
5885
|
});
|
|
5169
5886
|
if (!response.ok) {
|
|
5170
|
-
this.log("Failed to fetch auto-tracking config:", response.status
|
|
5887
|
+
this.log("Failed to fetch auto-tracking config:", response.status);
|
|
5171
5888
|
return;
|
|
5172
5889
|
}
|
|
5173
5890
|
const configResponse = await response.json();
|
|
5174
|
-
this.log("Received config response:", configResponse);
|
|
5175
5891
|
if (configResponse.autoTrackingConfig) {
|
|
5176
|
-
this.log("Auto-tracking config
|
|
5892
|
+
this.log("Auto-tracking config loaded");
|
|
5177
5893
|
this.setupAutoTrackingManagers(configResponse.autoTrackingConfig);
|
|
5178
|
-
} else {
|
|
5179
|
-
this.log("No auto-tracking config in response");
|
|
5180
5894
|
}
|
|
5181
5895
|
} catch (error) {
|
|
5182
5896
|
this.log("Failed to initialize auto-tracking:", error);
|
|
@@ -5186,9 +5900,9 @@ var Grain = (() => {
|
|
|
5186
5900
|
* Setup auto-tracking managers
|
|
5187
5901
|
*/
|
|
5188
5902
|
setupAutoTrackingManagers(config) {
|
|
5189
|
-
this.log("Setting up auto-tracking managers
|
|
5903
|
+
this.log("Setting up auto-tracking managers");
|
|
5190
5904
|
if (config.interactions && config.interactions.length > 0) {
|
|
5191
|
-
this.log("Loading interaction tracking
|
|
5905
|
+
this.log("Loading interaction tracking:", config.interactions.length, "interactions");
|
|
5192
5906
|
Promise.resolve().then(() => (init_interaction_tracking(), interaction_tracking_exports)).then(({ InteractionTrackingManager: InteractionTrackingManager2 }) => {
|
|
5193
5907
|
try {
|
|
5194
5908
|
this.interactionTrackingManager = new InteractionTrackingManager2(
|
|
@@ -5200,18 +5914,16 @@ var Grain = (() => {
|
|
|
5200
5914
|
mutationDebounceDelay: 500
|
|
5201
5915
|
}
|
|
5202
5916
|
);
|
|
5203
|
-
this.log("
|
|
5917
|
+
this.log("Interaction tracking initialized");
|
|
5204
5918
|
} catch (error) {
|
|
5205
|
-
this.log("
|
|
5919
|
+
this.log("Failed to initialize interaction tracking:", error);
|
|
5206
5920
|
}
|
|
5207
5921
|
}).catch((error) => {
|
|
5208
|
-
this.log("
|
|
5922
|
+
this.log("Failed to load interaction tracking module:", error);
|
|
5209
5923
|
});
|
|
5210
|
-
} else {
|
|
5211
|
-
this.log("No interactions configured for auto-tracking");
|
|
5212
5924
|
}
|
|
5213
5925
|
if (config.sections && config.sections.length > 0) {
|
|
5214
|
-
this.log("Loading section tracking
|
|
5926
|
+
this.log("Loading section tracking:", config.sections.length, "sections");
|
|
5215
5927
|
Promise.resolve().then(() => (init_section_tracking(), section_tracking_exports)).then(({ SectionTrackingManager: SectionTrackingManager2 }) => {
|
|
5216
5928
|
try {
|
|
5217
5929
|
this.sectionTrackingManager = new SectionTrackingManager2(
|
|
@@ -5226,15 +5938,13 @@ var Grain = (() => {
|
|
|
5226
5938
|
debug: this.config.debug
|
|
5227
5939
|
}
|
|
5228
5940
|
);
|
|
5229
|
-
this.log("
|
|
5941
|
+
this.log("Section tracking initialized");
|
|
5230
5942
|
} catch (error) {
|
|
5231
|
-
this.log("
|
|
5943
|
+
this.log("Failed to initialize section tracking:", error);
|
|
5232
5944
|
}
|
|
5233
5945
|
}).catch((error) => {
|
|
5234
|
-
this.log("
|
|
5946
|
+
this.log("Failed to load section tracking module:", error);
|
|
5235
5947
|
});
|
|
5236
|
-
} else {
|
|
5237
|
-
this.log("No sections configured for auto-tracking");
|
|
5238
5948
|
}
|
|
5239
5949
|
}
|
|
5240
5950
|
/**
|
|
@@ -5290,7 +6000,7 @@ var Grain = (() => {
|
|
|
5290
6000
|
properties.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
5291
6001
|
}
|
|
5292
6002
|
this.trackSystemEvent("_grain_session_start", properties);
|
|
5293
|
-
this.log("Session started
|
|
6003
|
+
this.log("Session started");
|
|
5294
6004
|
}
|
|
5295
6005
|
/**
|
|
5296
6006
|
* Track session end event
|
|
@@ -5315,7 +6025,7 @@ var Grain = (() => {
|
|
|
5315
6025
|
properties.page_count = pageCount;
|
|
5316
6026
|
}
|
|
5317
6027
|
this.trackSystemEvent("_grain_session_end", properties);
|
|
5318
|
-
this.log("Session ended
|
|
6028
|
+
this.log("Session ended");
|
|
5319
6029
|
}
|
|
5320
6030
|
/**
|
|
5321
6031
|
* Detect browser name
|
|
@@ -5412,7 +6122,7 @@ var Grain = (() => {
|
|
|
5412
6122
|
};
|
|
5413
6123
|
this.eventQueue.push(event);
|
|
5414
6124
|
this.eventCountSinceLastHeartbeat++;
|
|
5415
|
-
this.log(`Queued system event: ${eventName}
|
|
6125
|
+
this.log(`Queued system event: ${eventName}`);
|
|
5416
6126
|
if (this.eventQueue.length >= this.config.batchSize) {
|
|
5417
6127
|
this.flush().catch((error) => {
|
|
5418
6128
|
const formattedError = this.formatError(error, "flush system event");
|
|
@@ -5447,6 +6157,15 @@ var Grain = (() => {
|
|
|
5447
6157
|
resetEventCountSinceLastHeartbeat() {
|
|
5448
6158
|
this.eventCountSinceLastHeartbeat = 0;
|
|
5449
6159
|
}
|
|
6160
|
+
/**
|
|
6161
|
+
* Get the activity detector (for internal use by tracking managers)
|
|
6162
|
+
*/
|
|
6163
|
+
getActivityDetector() {
|
|
6164
|
+
if (!this.activityDetector) {
|
|
6165
|
+
throw new Error("Activity detector not initialized");
|
|
6166
|
+
}
|
|
6167
|
+
return this.activityDetector;
|
|
6168
|
+
}
|
|
5450
6169
|
/**
|
|
5451
6170
|
* Get the effective user ID (public method)
|
|
5452
6171
|
*/
|
|
@@ -5502,7 +6221,7 @@ var Grain = (() => {
|
|
|
5502
6221
|
this.eventQueue.push(formattedEvent);
|
|
5503
6222
|
this.eventCountSinceLastHeartbeat++;
|
|
5504
6223
|
this.sessionEventCount++;
|
|
5505
|
-
this.log(`Queued event: ${event.eventName}
|
|
6224
|
+
this.log(`Queued event: ${event.eventName}`);
|
|
5506
6225
|
if (opts.flush || this.eventQueue.length >= this.config.batchSize) {
|
|
5507
6226
|
await this.flush();
|
|
5508
6227
|
}
|
|
@@ -5701,7 +6420,6 @@ var Grain = (() => {
|
|
|
5701
6420
|
try {
|
|
5702
6421
|
const headers = await this.getAuthHeaders();
|
|
5703
6422
|
const url = `${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/properties`;
|
|
5704
|
-
this.log(`Setting properties for user ${payload.userId} (attempt ${attempt + 1})`);
|
|
5705
6423
|
const response = await fetch(url, {
|
|
5706
6424
|
method: "POST",
|
|
5707
6425
|
headers,
|
|
@@ -5927,7 +6645,6 @@ var Grain = (() => {
|
|
|
5927
6645
|
try {
|
|
5928
6646
|
const headers = await this.getAuthHeaders();
|
|
5929
6647
|
const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
|
|
5930
|
-
this.log(`Fetching configurations for user ${userId} (attempt ${attempt + 1})`);
|
|
5931
6648
|
const response = await fetch(url, {
|
|
5932
6649
|
method: "POST",
|
|
5933
6650
|
headers,
|
|
@@ -5954,7 +6671,7 @@ var Grain = (() => {
|
|
|
5954
6671
|
if (configResponse.configurations) {
|
|
5955
6672
|
this.updateConfigCache(configResponse, userId);
|
|
5956
6673
|
}
|
|
5957
|
-
this.log(
|
|
6674
|
+
this.log("Successfully fetched configurations");
|
|
5958
6675
|
return configResponse;
|
|
5959
6676
|
} catch (error) {
|
|
5960
6677
|
lastError = error;
|
|
@@ -6203,6 +6920,10 @@ var Grain = (() => {
|
|
|
6203
6920
|
this.sectionTrackingManager.destroy();
|
|
6204
6921
|
this.sectionTrackingManager = null;
|
|
6205
6922
|
}
|
|
6923
|
+
if (this.heatmapTrackingManager) {
|
|
6924
|
+
this.heatmapTrackingManager.destroy();
|
|
6925
|
+
this.heatmapTrackingManager = null;
|
|
6926
|
+
}
|
|
6206
6927
|
if (this.eventQueue.length > 0) {
|
|
6207
6928
|
const eventsToSend = [...this.eventQueue];
|
|
6208
6929
|
this.eventQueue = [];
|