@grainql/analytics-web 3.2.0 → 3.2.2
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/dist/cjs/heatmap-tracking.d.ts +21 -0
- package/dist/cjs/heatmap-tracking.d.ts.map +1 -1
- package/dist/cjs/heatmap-tracking.js +136 -1
- package/dist/cjs/heatmap-tracking.js.map +1 -1
- package/dist/cjs/types/heatmap-tracking.d.ts +2 -0
- package/dist/cjs/types/heatmap-tracking.d.ts.map +1 -1
- package/dist/esm/heatmap-tracking.d.ts +21 -0
- package/dist/esm/heatmap-tracking.d.ts.map +1 -1
- package/dist/esm/heatmap-tracking.js +136 -1
- package/dist/esm/heatmap-tracking.js.map +1 -1
- package/dist/esm/types/heatmap-tracking.d.ts +2 -0
- package/dist/esm/types/heatmap-tracking.d.ts.map +1 -1
- package/dist/heatmap-tracking.d.ts +21 -0
- package/dist/heatmap-tracking.d.ts.map +1 -1
- package/dist/heatmap-tracking.js +136 -1
- package/dist/index.global.dev.js +131 -11
- package/dist/index.global.dev.js.map +2 -2
- package/dist/index.global.js +8 -8
- package/dist/index.global.js.map +3 -3
- package/dist/types/heatmap-tracking.d.ts +2 -0
- package/dist/types/heatmap-tracking.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/heatmap-tracking.js
CHANGED
|
@@ -65,8 +65,12 @@ class HeatmapTrackingManager {
|
|
|
65
65
|
// Snapshot capture state
|
|
66
66
|
this.snapshotCaptured = false;
|
|
67
67
|
this.snapshotEnabled = false;
|
|
68
|
+
// Device type detection
|
|
69
|
+
this.deviceType = 'desktop';
|
|
68
70
|
this.tracker = tracker;
|
|
69
71
|
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
72
|
+
// Detect device type on initialization
|
|
73
|
+
this.deviceType = this.detectDeviceType();
|
|
70
74
|
// Initialize attention quality manager
|
|
71
75
|
this.attentionQuality = new attention_quality_1.AttentionQualityManager(tracker.getActivityDetector(), {
|
|
72
76
|
maxSectionDuration: 9000, // 9 seconds per viewport section
|
|
@@ -109,6 +113,34 @@ class HeatmapTrackingManager {
|
|
|
109
113
|
/**
|
|
110
114
|
* Check remote config for snapshot capture enablement
|
|
111
115
|
*/
|
|
116
|
+
/**
|
|
117
|
+
* Detect device type based on viewport width and user agent
|
|
118
|
+
* Mobile: width < 768px OR mobile user agent
|
|
119
|
+
* Desktop: width >= 768px AND non-mobile user agent
|
|
120
|
+
*/
|
|
121
|
+
detectDeviceType() {
|
|
122
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
123
|
+
return 'desktop'; // SSR default
|
|
124
|
+
}
|
|
125
|
+
// Check viewport width
|
|
126
|
+
const width = window.innerWidth || window.screen?.width || 0;
|
|
127
|
+
const isMobileWidth = width < 768;
|
|
128
|
+
// Check user agent for mobile indicators
|
|
129
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
130
|
+
const mobileKeywords = [
|
|
131
|
+
'mobile',
|
|
132
|
+
'android',
|
|
133
|
+
'iphone',
|
|
134
|
+
'ipad',
|
|
135
|
+
'ipod',
|
|
136
|
+
'blackberry',
|
|
137
|
+
'windows phone',
|
|
138
|
+
'webos',
|
|
139
|
+
];
|
|
140
|
+
const isMobileUserAgent = mobileKeywords.some((keyword) => userAgent.includes(keyword));
|
|
141
|
+
// Device is mobile if either width OR user agent indicates mobile
|
|
142
|
+
return isMobileWidth || isMobileUserAgent ? 'mobile' : 'desktop';
|
|
143
|
+
}
|
|
112
144
|
/**
|
|
113
145
|
* Normalize URL by removing query params and hash, and stripping www prefix
|
|
114
146
|
* This ensures heatmap data is aggregated by page, not by URL variations
|
|
@@ -147,6 +179,12 @@ class HeatmapTrackingManager {
|
|
|
147
179
|
async captureSnapshot() {
|
|
148
180
|
if (this.snapshotCaptured || !this.snapshotEnabled)
|
|
149
181
|
return;
|
|
182
|
+
// Check daily snapshot limits before capturing
|
|
183
|
+
if (!this.canUploadSnapshot()) {
|
|
184
|
+
this.log('Snapshot upload limit reached or URL already captured today');
|
|
185
|
+
this.snapshotCaptured = true; // Prevent retry
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
150
188
|
try {
|
|
151
189
|
this.log('Capturing DOM snapshot...');
|
|
152
190
|
// Dynamically import rrweb-snapshot (only if enabled)
|
|
@@ -205,7 +243,8 @@ class HeatmapTrackingManager {
|
|
|
205
243
|
sessionId,
|
|
206
244
|
pageUrl,
|
|
207
245
|
snapshot: JSON.stringify(snapshot),
|
|
208
|
-
timestamp: Date.now()
|
|
246
|
+
timestamp: Date.now(),
|
|
247
|
+
deviceType: this.deviceType
|
|
209
248
|
})
|
|
210
249
|
});
|
|
211
250
|
if (!response.ok) {
|
|
@@ -213,11 +252,97 @@ class HeatmapTrackingManager {
|
|
|
213
252
|
}
|
|
214
253
|
const result = await response.json();
|
|
215
254
|
this.log('Snapshot uploaded successfully:', result);
|
|
255
|
+
// Record successful upload
|
|
256
|
+
this.recordSnapshotUpload(pageUrl);
|
|
216
257
|
}
|
|
217
258
|
catch (error) {
|
|
218
259
|
this.log('Failed to upload snapshot:', error);
|
|
219
260
|
}
|
|
220
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Check if snapshot can be uploaded based on daily limits
|
|
264
|
+
* - Max 5 snapshots per user per day
|
|
265
|
+
* - Same URL (without query params) can't be uploaded twice in same day
|
|
266
|
+
*/
|
|
267
|
+
canUploadSnapshot() {
|
|
268
|
+
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
|
269
|
+
return true; // Allow in SSR/non-browser environments
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const pageUrl = this.normalizeUrl(window.location.href);
|
|
273
|
+
const urlWithoutQuery = this.stripQueryParams(pageUrl);
|
|
274
|
+
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
275
|
+
const storageKey = '_grain_snapshots';
|
|
276
|
+
// Get existing snapshot records
|
|
277
|
+
const stored = localStorage.getItem(storageKey);
|
|
278
|
+
let snapshots = stored ? JSON.parse(stored) : [];
|
|
279
|
+
// Clean up old entries (older than 2 days)
|
|
280
|
+
const twoDaysAgo = Date.now() - (2 * 24 * 60 * 60 * 1000);
|
|
281
|
+
snapshots = snapshots.filter(s => s.timestamp > twoDaysAgo);
|
|
282
|
+
// Get today's snapshots
|
|
283
|
+
const todaySnapshots = snapshots.filter(s => s.date === today);
|
|
284
|
+
// Check daily limit (5 per day)
|
|
285
|
+
if (todaySnapshots.length >= 5) {
|
|
286
|
+
this.log('Daily snapshot limit reached (5/5)');
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
// Check if this URL (without query params) was already uploaded today
|
|
290
|
+
const urlAlreadyUploaded = todaySnapshots.some(s => this.stripQueryParams(s.url) === urlWithoutQuery);
|
|
291
|
+
if (urlAlreadyUploaded) {
|
|
292
|
+
this.log(`Snapshot for ${urlWithoutQuery} already uploaded today`);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
this.log(`Snapshot upload allowed (${todaySnapshots.length}/5 today)`);
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
this.log('Error checking snapshot limits:', error);
|
|
300
|
+
return true; // Allow on error to not break functionality
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Record a successful snapshot upload
|
|
305
|
+
*/
|
|
306
|
+
recordSnapshotUpload(pageUrl) {
|
|
307
|
+
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const today = new Date().toISOString().split('T')[0];
|
|
312
|
+
const storageKey = '_grain_snapshots';
|
|
313
|
+
// Get existing records
|
|
314
|
+
const stored = localStorage.getItem(storageKey);
|
|
315
|
+
let snapshots = stored ? JSON.parse(stored) : [];
|
|
316
|
+
// Add new record
|
|
317
|
+
snapshots.push({
|
|
318
|
+
url: pageUrl,
|
|
319
|
+
date: today,
|
|
320
|
+
timestamp: Date.now()
|
|
321
|
+
});
|
|
322
|
+
// Clean up old entries (older than 2 days)
|
|
323
|
+
const twoDaysAgo = Date.now() - (2 * 24 * 60 * 60 * 1000);
|
|
324
|
+
snapshots = snapshots.filter(s => s.timestamp > twoDaysAgo);
|
|
325
|
+
// Save back to localStorage
|
|
326
|
+
localStorage.setItem(storageKey, JSON.stringify(snapshots));
|
|
327
|
+
this.log(`Snapshot upload recorded: ${snapshots.filter(s => s.date === today).length}/5 today`);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
this.log('Error recording snapshot upload:', error);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Strip query parameters from URL for comparison
|
|
335
|
+
*/
|
|
336
|
+
stripQueryParams(url) {
|
|
337
|
+
try {
|
|
338
|
+
const urlObj = new URL(url);
|
|
339
|
+
return `${urlObj.origin}${urlObj.pathname}`;
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// If URL parsing fails, remove everything after ?
|
|
343
|
+
return url.split('?')[0];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
221
346
|
/**
|
|
222
347
|
* Get API URL from tracker configuration
|
|
223
348
|
*/
|
|
@@ -349,6 +474,7 @@ class HeatmapTrackingManager {
|
|
|
349
474
|
exitTimestamp: currentTime,
|
|
350
475
|
pageHeight,
|
|
351
476
|
viewportHeight,
|
|
477
|
+
deviceType: this.deviceType,
|
|
352
478
|
};
|
|
353
479
|
// Send immediately using beacon to ensure delivery
|
|
354
480
|
this.tracker.trackSystemEvent('_grain_heatmap_scroll', {
|
|
@@ -360,6 +486,7 @@ class HeatmapTrackingManager {
|
|
|
360
486
|
exit_timestamp: scrollData.exitTimestamp,
|
|
361
487
|
page_height: scrollData.pageHeight,
|
|
362
488
|
viewport_height: scrollData.viewportHeight,
|
|
489
|
+
device_type: scrollData.deviceType,
|
|
363
490
|
is_split: true, // Flag to indicate periodic tracking, not final exit
|
|
364
491
|
}, { flush: true });
|
|
365
492
|
// Update attention quality duration tracker
|
|
@@ -390,6 +517,7 @@ class HeatmapTrackingManager {
|
|
|
390
517
|
exitTimestamp: currentTime,
|
|
391
518
|
pageHeight: document.documentElement.scrollHeight,
|
|
392
519
|
viewportHeight: window.innerHeight,
|
|
520
|
+
deviceType: this.deviceType,
|
|
393
521
|
};
|
|
394
522
|
this.pendingScrolls.push(scrollData);
|
|
395
523
|
}
|
|
@@ -448,6 +576,7 @@ class HeatmapTrackingManager {
|
|
|
448
576
|
elementTag,
|
|
449
577
|
elementText: elementText || undefined,
|
|
450
578
|
timestamp: Date.now(),
|
|
579
|
+
deviceType: this.deviceType,
|
|
451
580
|
};
|
|
452
581
|
// Check if this is a navigation link
|
|
453
582
|
const isNavigationLink = element instanceof HTMLAnchorElement && element.href;
|
|
@@ -466,6 +595,7 @@ class HeatmapTrackingManager {
|
|
|
466
595
|
element_tag: clickData.elementTag,
|
|
467
596
|
element_text: clickData.elementText,
|
|
468
597
|
timestamp: clickData.timestamp,
|
|
598
|
+
device_type: clickData.deviceType,
|
|
469
599
|
}, { flush: true });
|
|
470
600
|
}
|
|
471
601
|
else {
|
|
@@ -510,6 +640,7 @@ class HeatmapTrackingManager {
|
|
|
510
640
|
exitTimestamp: currentTime,
|
|
511
641
|
pageHeight,
|
|
512
642
|
viewportHeight,
|
|
643
|
+
deviceType: this.deviceType,
|
|
513
644
|
};
|
|
514
645
|
this.pendingScrolls.push(scrollData);
|
|
515
646
|
}
|
|
@@ -645,6 +776,7 @@ class HeatmapTrackingManager {
|
|
|
645
776
|
element_tag: clickData.elementTag,
|
|
646
777
|
element_text: clickData.elementText,
|
|
647
778
|
timestamp: clickData.timestamp,
|
|
779
|
+
device_type: clickData.deviceType,
|
|
648
780
|
});
|
|
649
781
|
}
|
|
650
782
|
this.pendingClicks = [];
|
|
@@ -661,6 +793,7 @@ class HeatmapTrackingManager {
|
|
|
661
793
|
exit_timestamp: scrollData.exitTimestamp,
|
|
662
794
|
page_height: scrollData.pageHeight,
|
|
663
795
|
viewport_height: scrollData.viewportHeight,
|
|
796
|
+
device_type: scrollData.deviceType,
|
|
664
797
|
});
|
|
665
798
|
}
|
|
666
799
|
this.pendingScrolls = [];
|
|
@@ -693,6 +826,7 @@ class HeatmapTrackingManager {
|
|
|
693
826
|
element_tag: clickData.elementTag,
|
|
694
827
|
element_text: clickData.elementText,
|
|
695
828
|
timestamp: clickData.timestamp,
|
|
829
|
+
device_type: clickData.deviceType,
|
|
696
830
|
}, { flush: true });
|
|
697
831
|
}
|
|
698
832
|
this.pendingClicks = [];
|
|
@@ -709,6 +843,7 @@ class HeatmapTrackingManager {
|
|
|
709
843
|
exit_timestamp: scrollData.exitTimestamp,
|
|
710
844
|
page_height: scrollData.pageHeight,
|
|
711
845
|
viewport_height: scrollData.viewportHeight,
|
|
846
|
+
device_type: scrollData.deviceType,
|
|
712
847
|
}, { flush: true });
|
|
713
848
|
}
|
|
714
849
|
this.pendingScrolls = [];
|
package/dist/index.global.dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Grain Analytics Web SDK v3.2.
|
|
1
|
+
/* Grain Analytics Web SDK v3.2.2 | MIT License | Development Build */
|
|
2
2
|
"use strict";
|
|
3
3
|
var Grain = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -5851,8 +5851,11 @@ var Grain = (() => {
|
|
|
5851
5851
|
// Snapshot capture state
|
|
5852
5852
|
this.snapshotCaptured = false;
|
|
5853
5853
|
this.snapshotEnabled = false;
|
|
5854
|
+
// Device type detection
|
|
5855
|
+
this.deviceType = "desktop";
|
|
5854
5856
|
this.tracker = tracker;
|
|
5855
5857
|
this.options = { ...DEFAULT_OPTIONS2, ...options };
|
|
5858
|
+
this.deviceType = this.detectDeviceType();
|
|
5856
5859
|
this.attentionQuality = new AttentionQualityManager(
|
|
5857
5860
|
tracker.getActivityDetector(),
|
|
5858
5861
|
{
|
|
@@ -5892,6 +5895,33 @@ var Grain = (() => {
|
|
|
5892
5895
|
/**
|
|
5893
5896
|
* Check remote config for snapshot capture enablement
|
|
5894
5897
|
*/
|
|
5898
|
+
/**
|
|
5899
|
+
* Detect device type based on viewport width and user agent
|
|
5900
|
+
* Mobile: width < 768px OR mobile user agent
|
|
5901
|
+
* Desktop: width >= 768px AND non-mobile user agent
|
|
5902
|
+
*/
|
|
5903
|
+
detectDeviceType() {
|
|
5904
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
5905
|
+
return "desktop";
|
|
5906
|
+
}
|
|
5907
|
+
const width = window.innerWidth || window.screen?.width || 0;
|
|
5908
|
+
const isMobileWidth = width < 768;
|
|
5909
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
5910
|
+
const mobileKeywords = [
|
|
5911
|
+
"mobile",
|
|
5912
|
+
"android",
|
|
5913
|
+
"iphone",
|
|
5914
|
+
"ipad",
|
|
5915
|
+
"ipod",
|
|
5916
|
+
"blackberry",
|
|
5917
|
+
"windows phone",
|
|
5918
|
+
"webos"
|
|
5919
|
+
];
|
|
5920
|
+
const isMobileUserAgent = mobileKeywords.some(
|
|
5921
|
+
(keyword) => userAgent.includes(keyword)
|
|
5922
|
+
);
|
|
5923
|
+
return isMobileWidth || isMobileUserAgent ? "mobile" : "desktop";
|
|
5924
|
+
}
|
|
5895
5925
|
/**
|
|
5896
5926
|
* Normalize URL by removing query params and hash, and stripping www prefix
|
|
5897
5927
|
* This ensures heatmap data is aggregated by page, not by URL variations
|
|
@@ -5925,6 +5955,11 @@ var Grain = (() => {
|
|
|
5925
5955
|
async captureSnapshot() {
|
|
5926
5956
|
if (this.snapshotCaptured || !this.snapshotEnabled)
|
|
5927
5957
|
return;
|
|
5958
|
+
if (!this.canUploadSnapshot()) {
|
|
5959
|
+
this.log("Snapshot upload limit reached or URL already captured today");
|
|
5960
|
+
this.snapshotCaptured = true;
|
|
5961
|
+
return;
|
|
5962
|
+
}
|
|
5928
5963
|
try {
|
|
5929
5964
|
this.log("Capturing DOM snapshot...");
|
|
5930
5965
|
const rrwebSnapshot = await Promise.resolve().then(() => (init_rrweb_snapshot(), rrweb_snapshot_exports));
|
|
@@ -5974,7 +6009,8 @@ var Grain = (() => {
|
|
|
5974
6009
|
sessionId,
|
|
5975
6010
|
pageUrl,
|
|
5976
6011
|
snapshot: JSON.stringify(snapshot2),
|
|
5977
|
-
timestamp: Date.now()
|
|
6012
|
+
timestamp: Date.now(),
|
|
6013
|
+
deviceType: this.deviceType
|
|
5978
6014
|
})
|
|
5979
6015
|
}
|
|
5980
6016
|
);
|
|
@@ -5983,10 +6019,84 @@ var Grain = (() => {
|
|
|
5983
6019
|
}
|
|
5984
6020
|
const result2 = await response.json();
|
|
5985
6021
|
this.log("Snapshot uploaded successfully:", result2);
|
|
6022
|
+
this.recordSnapshotUpload(pageUrl);
|
|
5986
6023
|
} catch (error) {
|
|
5987
6024
|
this.log("Failed to upload snapshot:", error);
|
|
5988
6025
|
}
|
|
5989
6026
|
}
|
|
6027
|
+
/**
|
|
6028
|
+
* Check if snapshot can be uploaded based on daily limits
|
|
6029
|
+
* - Max 5 snapshots per user per day
|
|
6030
|
+
* - Same URL (without query params) can't be uploaded twice in same day
|
|
6031
|
+
*/
|
|
6032
|
+
canUploadSnapshot() {
|
|
6033
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
6034
|
+
return true;
|
|
6035
|
+
}
|
|
6036
|
+
try {
|
|
6037
|
+
const pageUrl = this.normalizeUrl(window.location.href);
|
|
6038
|
+
const urlWithoutQuery = this.stripQueryParams(pageUrl);
|
|
6039
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6040
|
+
const storageKey = "_grain_snapshots";
|
|
6041
|
+
const stored = localStorage.getItem(storageKey);
|
|
6042
|
+
let snapshots = stored ? JSON.parse(stored) : [];
|
|
6043
|
+
const twoDaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1e3;
|
|
6044
|
+
snapshots = snapshots.filter((s) => s.timestamp > twoDaysAgo);
|
|
6045
|
+
const todaySnapshots = snapshots.filter((s) => s.date === today);
|
|
6046
|
+
if (todaySnapshots.length >= 5) {
|
|
6047
|
+
this.log("Daily snapshot limit reached (5/5)");
|
|
6048
|
+
return false;
|
|
6049
|
+
}
|
|
6050
|
+
const urlAlreadyUploaded = todaySnapshots.some(
|
|
6051
|
+
(s) => this.stripQueryParams(s.url) === urlWithoutQuery
|
|
6052
|
+
);
|
|
6053
|
+
if (urlAlreadyUploaded) {
|
|
6054
|
+
this.log(`Snapshot for ${urlWithoutQuery} already uploaded today`);
|
|
6055
|
+
return false;
|
|
6056
|
+
}
|
|
6057
|
+
this.log(`Snapshot upload allowed (${todaySnapshots.length}/5 today)`);
|
|
6058
|
+
return true;
|
|
6059
|
+
} catch (error) {
|
|
6060
|
+
this.log("Error checking snapshot limits:", error);
|
|
6061
|
+
return true;
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
/**
|
|
6065
|
+
* Record a successful snapshot upload
|
|
6066
|
+
*/
|
|
6067
|
+
recordSnapshotUpload(pageUrl) {
|
|
6068
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
6069
|
+
return;
|
|
6070
|
+
}
|
|
6071
|
+
try {
|
|
6072
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6073
|
+
const storageKey = "_grain_snapshots";
|
|
6074
|
+
const stored = localStorage.getItem(storageKey);
|
|
6075
|
+
let snapshots = stored ? JSON.parse(stored) : [];
|
|
6076
|
+
snapshots.push({
|
|
6077
|
+
url: pageUrl,
|
|
6078
|
+
date: today,
|
|
6079
|
+
timestamp: Date.now()
|
|
6080
|
+
});
|
|
6081
|
+
const twoDaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1e3;
|
|
6082
|
+
snapshots = snapshots.filter((s) => s.timestamp > twoDaysAgo);
|
|
6083
|
+
localStorage.setItem(storageKey, JSON.stringify(snapshots));
|
|
6084
|
+
this.log(`Snapshot upload recorded: ${snapshots.filter((s) => s.date === today).length}/5 today`);
|
|
6085
|
+
} catch (error) {
|
|
6086
|
+
this.log("Error recording snapshot upload:", error);
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
/**
|
|
6090
|
+
* Strip query parameters from URL for comparison
|
|
6091
|
+
*/
|
|
6092
|
+
stripQueryParams(url) {
|
|
6093
|
+
try {
|
|
6094
|
+
const urlObj = new URL(url);
|
|
6095
|
+
return `${urlObj.origin}${urlObj.pathname}`;
|
|
6096
|
+
} catch {
|
|
6097
|
+
return url.split("?")[0];
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
5990
6100
|
/**
|
|
5991
6101
|
* Get API URL from tracker configuration
|
|
5992
6102
|
*/
|
|
@@ -6102,7 +6212,8 @@ var Grain = (() => {
|
|
|
6102
6212
|
entryTimestamp: this.currentScrollState.entryTime,
|
|
6103
6213
|
exitTimestamp: currentTime,
|
|
6104
6214
|
pageHeight,
|
|
6105
|
-
viewportHeight
|
|
6215
|
+
viewportHeight,
|
|
6216
|
+
deviceType: this.deviceType
|
|
6106
6217
|
};
|
|
6107
6218
|
this.tracker.trackSystemEvent("_grain_heatmap_scroll", {
|
|
6108
6219
|
page_url: scrollData.pageUrl,
|
|
@@ -6113,6 +6224,7 @@ var Grain = (() => {
|
|
|
6113
6224
|
exit_timestamp: scrollData.exitTimestamp,
|
|
6114
6225
|
page_height: scrollData.pageHeight,
|
|
6115
6226
|
viewport_height: scrollData.viewportHeight,
|
|
6227
|
+
device_type: scrollData.deviceType,
|
|
6116
6228
|
is_split: true
|
|
6117
6229
|
// Flag to indicate periodic tracking, not final exit
|
|
6118
6230
|
}, { flush: true });
|
|
@@ -6140,7 +6252,8 @@ var Grain = (() => {
|
|
|
6140
6252
|
entryTimestamp: this.currentScrollState.entryTime,
|
|
6141
6253
|
exitTimestamp: currentTime,
|
|
6142
6254
|
pageHeight: document.documentElement.scrollHeight,
|
|
6143
|
-
viewportHeight: window.innerHeight
|
|
6255
|
+
viewportHeight: window.innerHeight,
|
|
6256
|
+
deviceType: this.deviceType
|
|
6144
6257
|
};
|
|
6145
6258
|
this.pendingScrolls.push(scrollData);
|
|
6146
6259
|
}
|
|
@@ -6189,7 +6302,8 @@ var Grain = (() => {
|
|
|
6189
6302
|
pageY,
|
|
6190
6303
|
elementTag,
|
|
6191
6304
|
elementText: elementText || void 0,
|
|
6192
|
-
timestamp: Date.now()
|
|
6305
|
+
timestamp: Date.now(),
|
|
6306
|
+
deviceType: this.deviceType
|
|
6193
6307
|
};
|
|
6194
6308
|
const isNavigationLink = element instanceof HTMLAnchorElement && element.href;
|
|
6195
6309
|
if (isNavigationLink) {
|
|
@@ -6205,7 +6319,8 @@ var Grain = (() => {
|
|
|
6205
6319
|
page_y: clickData.pageY,
|
|
6206
6320
|
element_tag: clickData.elementTag,
|
|
6207
6321
|
element_text: clickData.elementText,
|
|
6208
|
-
timestamp: clickData.timestamp
|
|
6322
|
+
timestamp: clickData.timestamp,
|
|
6323
|
+
device_type: clickData.deviceType
|
|
6209
6324
|
}, { flush: true });
|
|
6210
6325
|
} else {
|
|
6211
6326
|
this.pendingClicks.push(clickData);
|
|
@@ -6240,7 +6355,8 @@ var Grain = (() => {
|
|
|
6240
6355
|
entryTimestamp: this.currentScrollState.entryTime,
|
|
6241
6356
|
exitTimestamp: currentTime,
|
|
6242
6357
|
pageHeight,
|
|
6243
|
-
viewportHeight
|
|
6358
|
+
viewportHeight,
|
|
6359
|
+
deviceType: this.deviceType
|
|
6244
6360
|
};
|
|
6245
6361
|
this.pendingScrolls.push(scrollData);
|
|
6246
6362
|
}
|
|
@@ -6360,7 +6476,8 @@ var Grain = (() => {
|
|
|
6360
6476
|
page_y: clickData.pageY,
|
|
6361
6477
|
element_tag: clickData.elementTag,
|
|
6362
6478
|
element_text: clickData.elementText,
|
|
6363
|
-
timestamp: clickData.timestamp
|
|
6479
|
+
timestamp: clickData.timestamp,
|
|
6480
|
+
device_type: clickData.deviceType
|
|
6364
6481
|
});
|
|
6365
6482
|
}
|
|
6366
6483
|
this.pendingClicks = [];
|
|
@@ -6375,7 +6492,8 @@ var Grain = (() => {
|
|
|
6375
6492
|
entry_timestamp: scrollData.entryTimestamp,
|
|
6376
6493
|
exit_timestamp: scrollData.exitTimestamp,
|
|
6377
6494
|
page_height: scrollData.pageHeight,
|
|
6378
|
-
viewport_height: scrollData.viewportHeight
|
|
6495
|
+
viewport_height: scrollData.viewportHeight,
|
|
6496
|
+
device_type: scrollData.deviceType
|
|
6379
6497
|
});
|
|
6380
6498
|
}
|
|
6381
6499
|
this.pendingScrolls = [];
|
|
@@ -6403,7 +6521,8 @@ var Grain = (() => {
|
|
|
6403
6521
|
page_y: clickData.pageY,
|
|
6404
6522
|
element_tag: clickData.elementTag,
|
|
6405
6523
|
element_text: clickData.elementText,
|
|
6406
|
-
timestamp: clickData.timestamp
|
|
6524
|
+
timestamp: clickData.timestamp,
|
|
6525
|
+
device_type: clickData.deviceType
|
|
6407
6526
|
}, { flush: true });
|
|
6408
6527
|
}
|
|
6409
6528
|
this.pendingClicks = [];
|
|
@@ -6418,7 +6537,8 @@ var Grain = (() => {
|
|
|
6418
6537
|
entry_timestamp: scrollData.entryTimestamp,
|
|
6419
6538
|
exit_timestamp: scrollData.exitTimestamp,
|
|
6420
6539
|
page_height: scrollData.pageHeight,
|
|
6421
|
-
viewport_height: scrollData.viewportHeight
|
|
6540
|
+
viewport_height: scrollData.viewportHeight,
|
|
6541
|
+
device_type: scrollData.deviceType
|
|
6422
6542
|
}, { flush: true });
|
|
6423
6543
|
}
|
|
6424
6544
|
this.pendingScrolls = [];
|