@grainql/analytics-web 3.2.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -205,7 +237,8 @@ class HeatmapTrackingManager {
205
237
  sessionId,
206
238
  pageUrl,
207
239
  snapshot: JSON.stringify(snapshot),
208
- timestamp: Date.now()
240
+ timestamp: Date.now(),
241
+ deviceType: this.deviceType
209
242
  })
210
243
  });
211
244
  if (!response.ok) {
@@ -349,6 +382,7 @@ class HeatmapTrackingManager {
349
382
  exitTimestamp: currentTime,
350
383
  pageHeight,
351
384
  viewportHeight,
385
+ deviceType: this.deviceType,
352
386
  };
353
387
  // Send immediately using beacon to ensure delivery
354
388
  this.tracker.trackSystemEvent('_grain_heatmap_scroll', {
@@ -360,6 +394,7 @@ class HeatmapTrackingManager {
360
394
  exit_timestamp: scrollData.exitTimestamp,
361
395
  page_height: scrollData.pageHeight,
362
396
  viewport_height: scrollData.viewportHeight,
397
+ device_type: scrollData.deviceType,
363
398
  is_split: true, // Flag to indicate periodic tracking, not final exit
364
399
  }, { flush: true });
365
400
  // Update attention quality duration tracker
@@ -390,6 +425,7 @@ class HeatmapTrackingManager {
390
425
  exitTimestamp: currentTime,
391
426
  pageHeight: document.documentElement.scrollHeight,
392
427
  viewportHeight: window.innerHeight,
428
+ deviceType: this.deviceType,
393
429
  };
394
430
  this.pendingScrolls.push(scrollData);
395
431
  }
@@ -448,6 +484,7 @@ class HeatmapTrackingManager {
448
484
  elementTag,
449
485
  elementText: elementText || undefined,
450
486
  timestamp: Date.now(),
487
+ deviceType: this.deviceType,
451
488
  };
452
489
  // Check if this is a navigation link
453
490
  const isNavigationLink = element instanceof HTMLAnchorElement && element.href;
@@ -466,6 +503,7 @@ class HeatmapTrackingManager {
466
503
  element_tag: clickData.elementTag,
467
504
  element_text: clickData.elementText,
468
505
  timestamp: clickData.timestamp,
506
+ device_type: clickData.deviceType,
469
507
  }, { flush: true });
470
508
  }
471
509
  else {
@@ -510,6 +548,7 @@ class HeatmapTrackingManager {
510
548
  exitTimestamp: currentTime,
511
549
  pageHeight,
512
550
  viewportHeight,
551
+ deviceType: this.deviceType,
513
552
  };
514
553
  this.pendingScrolls.push(scrollData);
515
554
  }
@@ -645,6 +684,7 @@ class HeatmapTrackingManager {
645
684
  element_tag: clickData.elementTag,
646
685
  element_text: clickData.elementText,
647
686
  timestamp: clickData.timestamp,
687
+ device_type: clickData.deviceType,
648
688
  });
649
689
  }
650
690
  this.pendingClicks = [];
@@ -661,6 +701,7 @@ class HeatmapTrackingManager {
661
701
  exit_timestamp: scrollData.exitTimestamp,
662
702
  page_height: scrollData.pageHeight,
663
703
  viewport_height: scrollData.viewportHeight,
704
+ device_type: scrollData.deviceType,
664
705
  });
665
706
  }
666
707
  this.pendingScrolls = [];
@@ -693,6 +734,7 @@ class HeatmapTrackingManager {
693
734
  element_tag: clickData.elementTag,
694
735
  element_text: clickData.elementText,
695
736
  timestamp: clickData.timestamp,
737
+ device_type: clickData.deviceType,
696
738
  }, { flush: true });
697
739
  }
698
740
  this.pendingClicks = [];
@@ -709,6 +751,7 @@ class HeatmapTrackingManager {
709
751
  exit_timestamp: scrollData.exitTimestamp,
710
752
  page_height: scrollData.pageHeight,
711
753
  viewport_height: scrollData.viewportHeight,
754
+ device_type: scrollData.deviceType,
712
755
  }, { flush: true });
713
756
  }
714
757
  this.pendingScrolls = [];
@@ -1,4 +1,4 @@
1
- /* Grain Analytics Web SDK v3.2.0 | MIT License | Development Build */
1
+ /* Grain Analytics Web SDK v3.2.1 | 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
@@ -5974,7 +6004,8 @@ var Grain = (() => {
5974
6004
  sessionId,
5975
6005
  pageUrl,
5976
6006
  snapshot: JSON.stringify(snapshot2),
5977
- timestamp: Date.now()
6007
+ timestamp: Date.now(),
6008
+ deviceType: this.deviceType
5978
6009
  })
5979
6010
  }
5980
6011
  );
@@ -6102,7 +6133,8 @@ var Grain = (() => {
6102
6133
  entryTimestamp: this.currentScrollState.entryTime,
6103
6134
  exitTimestamp: currentTime,
6104
6135
  pageHeight,
6105
- viewportHeight
6136
+ viewportHeight,
6137
+ deviceType: this.deviceType
6106
6138
  };
6107
6139
  this.tracker.trackSystemEvent("_grain_heatmap_scroll", {
6108
6140
  page_url: scrollData.pageUrl,
@@ -6113,6 +6145,7 @@ var Grain = (() => {
6113
6145
  exit_timestamp: scrollData.exitTimestamp,
6114
6146
  page_height: scrollData.pageHeight,
6115
6147
  viewport_height: scrollData.viewportHeight,
6148
+ device_type: scrollData.deviceType,
6116
6149
  is_split: true
6117
6150
  // Flag to indicate periodic tracking, not final exit
6118
6151
  }, { flush: true });
@@ -6140,7 +6173,8 @@ var Grain = (() => {
6140
6173
  entryTimestamp: this.currentScrollState.entryTime,
6141
6174
  exitTimestamp: currentTime,
6142
6175
  pageHeight: document.documentElement.scrollHeight,
6143
- viewportHeight: window.innerHeight
6176
+ viewportHeight: window.innerHeight,
6177
+ deviceType: this.deviceType
6144
6178
  };
6145
6179
  this.pendingScrolls.push(scrollData);
6146
6180
  }
@@ -6189,7 +6223,8 @@ var Grain = (() => {
6189
6223
  pageY,
6190
6224
  elementTag,
6191
6225
  elementText: elementText || void 0,
6192
- timestamp: Date.now()
6226
+ timestamp: Date.now(),
6227
+ deviceType: this.deviceType
6193
6228
  };
6194
6229
  const isNavigationLink = element instanceof HTMLAnchorElement && element.href;
6195
6230
  if (isNavigationLink) {
@@ -6205,7 +6240,8 @@ var Grain = (() => {
6205
6240
  page_y: clickData.pageY,
6206
6241
  element_tag: clickData.elementTag,
6207
6242
  element_text: clickData.elementText,
6208
- timestamp: clickData.timestamp
6243
+ timestamp: clickData.timestamp,
6244
+ device_type: clickData.deviceType
6209
6245
  }, { flush: true });
6210
6246
  } else {
6211
6247
  this.pendingClicks.push(clickData);
@@ -6240,7 +6276,8 @@ var Grain = (() => {
6240
6276
  entryTimestamp: this.currentScrollState.entryTime,
6241
6277
  exitTimestamp: currentTime,
6242
6278
  pageHeight,
6243
- viewportHeight
6279
+ viewportHeight,
6280
+ deviceType: this.deviceType
6244
6281
  };
6245
6282
  this.pendingScrolls.push(scrollData);
6246
6283
  }
@@ -6360,7 +6397,8 @@ var Grain = (() => {
6360
6397
  page_y: clickData.pageY,
6361
6398
  element_tag: clickData.elementTag,
6362
6399
  element_text: clickData.elementText,
6363
- timestamp: clickData.timestamp
6400
+ timestamp: clickData.timestamp,
6401
+ device_type: clickData.deviceType
6364
6402
  });
6365
6403
  }
6366
6404
  this.pendingClicks = [];
@@ -6375,7 +6413,8 @@ var Grain = (() => {
6375
6413
  entry_timestamp: scrollData.entryTimestamp,
6376
6414
  exit_timestamp: scrollData.exitTimestamp,
6377
6415
  page_height: scrollData.pageHeight,
6378
- viewport_height: scrollData.viewportHeight
6416
+ viewport_height: scrollData.viewportHeight,
6417
+ device_type: scrollData.deviceType
6379
6418
  });
6380
6419
  }
6381
6420
  this.pendingScrolls = [];
@@ -6403,7 +6442,8 @@ var Grain = (() => {
6403
6442
  page_y: clickData.pageY,
6404
6443
  element_tag: clickData.elementTag,
6405
6444
  element_text: clickData.elementText,
6406
- timestamp: clickData.timestamp
6445
+ timestamp: clickData.timestamp,
6446
+ device_type: clickData.deviceType
6407
6447
  }, { flush: true });
6408
6448
  }
6409
6449
  this.pendingClicks = [];
@@ -6418,7 +6458,8 @@ var Grain = (() => {
6418
6458
  entry_timestamp: scrollData.entryTimestamp,
6419
6459
  exit_timestamp: scrollData.exitTimestamp,
6420
6460
  page_height: scrollData.pageHeight,
6421
- viewport_height: scrollData.viewportHeight
6461
+ viewport_height: scrollData.viewportHeight,
6462
+ device_type: scrollData.deviceType
6422
6463
  }, { flush: true });
6423
6464
  }
6424
6465
  this.pendingScrolls = [];