cryptique-sdk 1.2.16 → 1.2.17

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/lib/cjs/index.js CHANGED
@@ -92,8 +92,8 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
92
92
 
93
93
  // Geolocation API
94
94
  GEOLOCATION: {
95
- PRIMARY_URL: "https://ipinfo.io/json?token=73937f74acc045",
96
- BACKUP_URL: "https://ipinfo.io/json?token=73937f74acc045&http=1.1",
95
+ PRIMARY_URL: "https://ipinfo.io/json?token=8fc6409059aa39",
96
+ BACKUP_URL: "https://ipinfo.io/json?token=8fc6409059aa39&http=1.1",
97
97
  TIMEOUT_MS: 5000 // 5 second timeout
98
98
  },
99
99
 
@@ -4999,19 +4999,26 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
4999
4999
  startCopyPasteTracking() {
5000
5000
  try {
5001
5001
  document.addEventListener('copy', (event) => {
5002
+ const sel = window.getSelection();
5002
5003
  const copyData = {
5003
5004
  type: 'copy_action',
5004
- selectedText: window.getSelection().toString().substring(0, 100),
5005
+ selectedText: sel ? sel.toString().substring(0, 100) : '',
5005
5006
  target: {
5006
5007
  tagName: event.target?.tagName || '',
5007
5008
  id: event.target?.id || ''
5008
5009
  },
5009
5010
  path: window.location.pathname
5010
5011
  };
5011
-
5012
5012
  InteractionManager.add('copyPasteEvents', copyData);
5013
+ // Fire structured auto-event (low volume — only when user explicitly copies)
5014
+ EventsManager.trackAutoEvent('copy_action', {
5015
+ text_length: sel ? sel.toString().length : 0,
5016
+ element_id: event.target?.id || '',
5017
+ element_tag: event.target?.tagName?.toLowerCase() || '',
5018
+ element_class: (event.target?.className || '').split(' ').filter(Boolean).slice(0, 3).join(' '),
5019
+ });
5013
5020
  });
5014
-
5021
+
5015
5022
  document.addEventListener('paste', (event) => {
5016
5023
  const pasteData = {
5017
5024
  type: 'paste_action',
@@ -5021,7 +5028,6 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
5021
5028
  },
5022
5029
  path: window.location.pathname
5023
5030
  };
5024
-
5025
5031
  InteractionManager.add('copyPasteEvents', pasteData);
5026
5032
  });
5027
5033
  } catch (error) {
@@ -5047,8 +5053,15 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
5047
5053
  },
5048
5054
  path: window.location.pathname
5049
5055
  };
5050
-
5051
5056
  InteractionManager.add('contextMenuEvents', contextData);
5057
+ // Fire structured auto-event (low volume — only when user right-clicks)
5058
+ EventsManager.trackAutoEvent('context_menu', {
5059
+ element_tag: event.target?.tagName?.toLowerCase() || '',
5060
+ element_id: event.target?.id || '',
5061
+ element_class: (event.target?.className || '').split(' ').filter(Boolean).slice(0, 3).join(' '),
5062
+ page_x: event.pageX,
5063
+ page_y: event.pageY,
5064
+ });
5052
5065
  });
5053
5066
  } catch (error) {
5054
5067
  }
@@ -5576,6 +5589,167 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
5576
5589
  }
5577
5590
  },
5578
5591
 
5592
+ /**
5593
+ * PageSummaryTracker — collects high-frequency signals in-memory and flushes
5594
+ * them as a single 'page_summary' auto-event on page unload.
5595
+ *
5596
+ * This keeps event counts low (1 per page visit) while capturing:
5597
+ * - Mouse movement grid (move_grid)
5598
+ * - Hover dwell grid (hover_grid)
5599
+ * - Tab-switch count and hidden time
5600
+ * - Scroll reversal count
5601
+ * - Per-field focus dwell times
5602
+ */
5603
+ startPageSummaryTracking() {
5604
+ try {
5605
+ const GRID_SIZE = 40;
5606
+ // In-memory accumulators — never sent to the server individually
5607
+ const moveGrid = new Map(); // cellKey → move count
5608
+ const hoverGrid = new Map(); // cellKey → total dwell_ms
5609
+ const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
5610
+
5611
+ let tabSwitches = 0;
5612
+ let totalTabHiddenMs = 0;
5613
+ let tabHiddenTime = null;
5614
+ let scrollReversals = 0;
5615
+
5616
+ // Track last significant scroll position for reversal detection
5617
+ let lastSigScrollY = window.scrollY;
5618
+ let lastSigDir = 0; // 1=down, -1=up
5619
+
5620
+ function getGridCell(pageX, pageY) {
5621
+ const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
5622
+ const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
5623
+ const col = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageX / docW) * GRID_SIZE)));
5624
+ const row = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageY / docH) * GRID_SIZE)));
5625
+ return col * GRID_SIZE + row;
5626
+ }
5627
+
5628
+ // ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
5629
+ let moveThrottle = false;
5630
+ document.addEventListener('mousemove', (e) => {
5631
+ if (moveThrottle) return;
5632
+ moveThrottle = true;
5633
+ setTimeout(() => { moveThrottle = false; }, 100);
5634
+ const key = getGridCell(e.pageX, e.pageY);
5635
+ moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
5636
+ }, { passive: true });
5637
+
5638
+ // ── Hover dwell → hover_grid (track all elements, filter < 100 ms)
5639
+ const hoverStarts = new Map(); // element → enterTime
5640
+ document.addEventListener('mouseover', (e) => {
5641
+ if (e.target && e.target !== document.documentElement) {
5642
+ hoverStarts.set(e.target, Date.now());
5643
+ }
5644
+ }, { passive: true });
5645
+ document.addEventListener('mouseout', (e) => {
5646
+ const target = e.target;
5647
+ if (!target || !hoverStarts.has(target)) return;
5648
+ const dwell = Date.now() - hoverStarts.get(target);
5649
+ hoverStarts.delete(target);
5650
+ if (dwell < 100) return; // skip accidental hovers
5651
+ try {
5652
+ const rect = target.getBoundingClientRect();
5653
+ const cx = rect.left + rect.width / 2 + window.scrollX;
5654
+ const cy = rect.top + rect.height / 2 + window.scrollY;
5655
+ const key = getGridCell(cx, cy);
5656
+ hoverGrid.set(key, (hoverGrid.get(key) || 0) + dwell);
5657
+ } catch (_) {}
5658
+ }, { passive: true });
5659
+
5660
+ // ── Tab visibility → tab_switches + total_tab_hidden_ms
5661
+ document.addEventListener('visibilitychange', () => {
5662
+ if (document.hidden) {
5663
+ tabHiddenTime = Date.now();
5664
+ tabSwitches++;
5665
+ } else if (tabHiddenTime !== null) {
5666
+ totalTabHiddenMs += Date.now() - tabHiddenTime;
5667
+ tabHiddenTime = null;
5668
+ }
5669
+ });
5670
+
5671
+ // ── Scroll reversal detection (direction change > 200 px)
5672
+ window.addEventListener('scroll', () => {
5673
+ const curr = window.scrollY;
5674
+ const diff = curr - lastSigScrollY;
5675
+ if (Math.abs(diff) < 50) return; // ignore tiny movements
5676
+ const dir = diff > 0 ? 1 : -1;
5677
+ if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
5678
+ scrollReversals++;
5679
+ }
5680
+ if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
5681
+ }, { passive: true });
5682
+
5683
+ // ── Field dwell → per-field focus duration (no content captured)
5684
+ const fieldFocusTimes = new Map();
5685
+ document.addEventListener('focus', (e) => {
5686
+ const t = e.target;
5687
+ if (!t || !['INPUT', 'SELECT', 'TEXTAREA'].includes(t.tagName)) return;
5688
+ if (t.type === 'hidden') return;
5689
+ fieldFocusTimes.set(t, Date.now());
5690
+ }, true);
5691
+ document.addEventListener('blur', (e) => {
5692
+ const t = e.target;
5693
+ if (!t || !fieldFocusTimes.has(t)) return;
5694
+ const dwell = Date.now() - fieldFocusTimes.get(t);
5695
+ fieldFocusTimes.delete(t);
5696
+ if (dwell < 100) return; // skip accidental focus
5697
+ const label = t.id
5698
+ ? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
5699
+ : (t.placeholder || t.name || t.type || '');
5700
+ fieldDwells.push({
5701
+ field_id: (t.id || t.name || t.type || '').substring(0, 100),
5702
+ field_label: (label || '').substring(0, 100),
5703
+ field_type: t.type || t.tagName.toLowerCase(),
5704
+ dwell_ms: dwell,
5705
+ was_filled: !!(t.value && t.value.length > 0),
5706
+ });
5707
+ }, true);
5708
+
5709
+ // ── Serialise grid Map to [[col, row, value], ...] sparse array
5710
+ function serializeGrid(grid) {
5711
+ const out = [];
5712
+ grid.forEach((val, key) => {
5713
+ if (val > 0) {
5714
+ const col = Math.floor(key / GRID_SIZE);
5715
+ const row = key % GRID_SIZE;
5716
+ out.push([col, row, Math.round(val)]);
5717
+ }
5718
+ });
5719
+ return out;
5720
+ }
5721
+
5722
+ let summarySent = false;
5723
+ function firePageSummary() {
5724
+ if (summarySent) return; // only fire once per page lifecycle
5725
+ if (moveGrid.size === 0 && hoverGrid.size === 0 && fieldDwells.length === 0
5726
+ && tabSwitches === 0 && scrollReversals === 0) return;
5727
+ summarySent = true;
5728
+ try {
5729
+ EventsManager.trackAutoEvent('page_summary', {
5730
+ scroll_reversals: scrollReversals,
5731
+ tab_switches: tabSwitches,
5732
+ total_tab_hidden_ms: totalTabHiddenMs,
5733
+ move_grid: serializeGrid(moveGrid),
5734
+ hover_grid: serializeGrid(hoverGrid),
5735
+ field_dwells: fieldDwells,
5736
+ document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
5737
+ document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
5738
+ viewport_width: window.innerWidth,
5739
+ viewport_height: window.innerHeight,
5740
+ });
5741
+ } catch (_) {}
5742
+ }
5743
+
5744
+ // Fire on hard navigation / tab close (pagehide is more reliable than beforeunload)
5745
+ window.addEventListener('pagehide', firePageSummary);
5746
+ // Also fire when tab becomes hidden (covers SPA navigation that doesn't fire pagehide)
5747
+ document.addEventListener('visibilitychange', () => {
5748
+ if (document.hidden) firePageSummary();
5749
+ });
5750
+ } catch (error) {}
5751
+ },
5752
+
5579
5753
  /**
5580
5754
  * Initialize all event trackers
5581
5755
  *
@@ -5599,6 +5773,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
5599
5773
  this.startAdvancedFormTracking();
5600
5774
  this.startFormAbandonmentTracking();
5601
5775
  this.startElementVisibilityTracking();
5776
+ this.startPageSummaryTracking();
5602
5777
  }
5603
5778
  };
5604
5779
 
@@ -6427,7 +6602,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
6427
6602
  // Performance
6428
6603
  'page_performance': 'performance',
6429
6604
  // Visibility
6430
- 'element_view': 'visibility'
6605
+ 'element_view': 'visibility',
6606
+ // Page summary (aggregate)
6607
+ 'page_summary': 'interaction',
6608
+ // Clipboard & context
6609
+ 'copy_action': 'interaction',
6610
+ 'context_menu': 'interaction',
6431
6611
  };
6432
6612
 
6433
6613
  return categoryMap[eventName] || 'other';
@@ -8224,6 +8404,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
8224
8404
  * The script.js file is inlined during the build process by Rollup.
8225
8405
  */
8226
8406
 
8407
+
8227
8408
  // Create a wrapper that provides programmatic initialization
8228
8409
  const CryptiqueSDK = {
8229
8410
  /**
package/lib/esm/index.js CHANGED
@@ -90,8 +90,8 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
90
90
 
91
91
  // Geolocation API
92
92
  GEOLOCATION: {
93
- PRIMARY_URL: "https://ipinfo.io/json?token=73937f74acc045",
94
- BACKUP_URL: "https://ipinfo.io/json?token=73937f74acc045&http=1.1",
93
+ PRIMARY_URL: "https://ipinfo.io/json?token=8fc6409059aa39",
94
+ BACKUP_URL: "https://ipinfo.io/json?token=8fc6409059aa39&http=1.1",
95
95
  TIMEOUT_MS: 5000 // 5 second timeout
96
96
  },
97
97
 
@@ -4997,19 +4997,26 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
4997
4997
  startCopyPasteTracking() {
4998
4998
  try {
4999
4999
  document.addEventListener('copy', (event) => {
5000
+ const sel = window.getSelection();
5000
5001
  const copyData = {
5001
5002
  type: 'copy_action',
5002
- selectedText: window.getSelection().toString().substring(0, 100),
5003
+ selectedText: sel ? sel.toString().substring(0, 100) : '',
5003
5004
  target: {
5004
5005
  tagName: event.target?.tagName || '',
5005
5006
  id: event.target?.id || ''
5006
5007
  },
5007
5008
  path: window.location.pathname
5008
5009
  };
5009
-
5010
5010
  InteractionManager.add('copyPasteEvents', copyData);
5011
+ // Fire structured auto-event (low volume — only when user explicitly copies)
5012
+ EventsManager.trackAutoEvent('copy_action', {
5013
+ text_length: sel ? sel.toString().length : 0,
5014
+ element_id: event.target?.id || '',
5015
+ element_tag: event.target?.tagName?.toLowerCase() || '',
5016
+ element_class: (event.target?.className || '').split(' ').filter(Boolean).slice(0, 3).join(' '),
5017
+ });
5011
5018
  });
5012
-
5019
+
5013
5020
  document.addEventListener('paste', (event) => {
5014
5021
  const pasteData = {
5015
5022
  type: 'paste_action',
@@ -5019,7 +5026,6 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
5019
5026
  },
5020
5027
  path: window.location.pathname
5021
5028
  };
5022
-
5023
5029
  InteractionManager.add('copyPasteEvents', pasteData);
5024
5030
  });
5025
5031
  } catch (error) {
@@ -5045,8 +5051,15 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
5045
5051
  },
5046
5052
  path: window.location.pathname
5047
5053
  };
5048
-
5049
5054
  InteractionManager.add('contextMenuEvents', contextData);
5055
+ // Fire structured auto-event (low volume — only when user right-clicks)
5056
+ EventsManager.trackAutoEvent('context_menu', {
5057
+ element_tag: event.target?.tagName?.toLowerCase() || '',
5058
+ element_id: event.target?.id || '',
5059
+ element_class: (event.target?.className || '').split(' ').filter(Boolean).slice(0, 3).join(' '),
5060
+ page_x: event.pageX,
5061
+ page_y: event.pageY,
5062
+ });
5050
5063
  });
5051
5064
  } catch (error) {
5052
5065
  }
@@ -5574,6 +5587,167 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
5574
5587
  }
5575
5588
  },
5576
5589
 
5590
+ /**
5591
+ * PageSummaryTracker — collects high-frequency signals in-memory and flushes
5592
+ * them as a single 'page_summary' auto-event on page unload.
5593
+ *
5594
+ * This keeps event counts low (1 per page visit) while capturing:
5595
+ * - Mouse movement grid (move_grid)
5596
+ * - Hover dwell grid (hover_grid)
5597
+ * - Tab-switch count and hidden time
5598
+ * - Scroll reversal count
5599
+ * - Per-field focus dwell times
5600
+ */
5601
+ startPageSummaryTracking() {
5602
+ try {
5603
+ const GRID_SIZE = 40;
5604
+ // In-memory accumulators — never sent to the server individually
5605
+ const moveGrid = new Map(); // cellKey → move count
5606
+ const hoverGrid = new Map(); // cellKey → total dwell_ms
5607
+ const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
5608
+
5609
+ let tabSwitches = 0;
5610
+ let totalTabHiddenMs = 0;
5611
+ let tabHiddenTime = null;
5612
+ let scrollReversals = 0;
5613
+
5614
+ // Track last significant scroll position for reversal detection
5615
+ let lastSigScrollY = window.scrollY;
5616
+ let lastSigDir = 0; // 1=down, -1=up
5617
+
5618
+ function getGridCell(pageX, pageY) {
5619
+ const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
5620
+ const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
5621
+ const col = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageX / docW) * GRID_SIZE)));
5622
+ const row = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageY / docH) * GRID_SIZE)));
5623
+ return col * GRID_SIZE + row;
5624
+ }
5625
+
5626
+ // ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
5627
+ let moveThrottle = false;
5628
+ document.addEventListener('mousemove', (e) => {
5629
+ if (moveThrottle) return;
5630
+ moveThrottle = true;
5631
+ setTimeout(() => { moveThrottle = false; }, 100);
5632
+ const key = getGridCell(e.pageX, e.pageY);
5633
+ moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
5634
+ }, { passive: true });
5635
+
5636
+ // ── Hover dwell → hover_grid (track all elements, filter < 100 ms)
5637
+ const hoverStarts = new Map(); // element → enterTime
5638
+ document.addEventListener('mouseover', (e) => {
5639
+ if (e.target && e.target !== document.documentElement) {
5640
+ hoverStarts.set(e.target, Date.now());
5641
+ }
5642
+ }, { passive: true });
5643
+ document.addEventListener('mouseout', (e) => {
5644
+ const target = e.target;
5645
+ if (!target || !hoverStarts.has(target)) return;
5646
+ const dwell = Date.now() - hoverStarts.get(target);
5647
+ hoverStarts.delete(target);
5648
+ if (dwell < 100) return; // skip accidental hovers
5649
+ try {
5650
+ const rect = target.getBoundingClientRect();
5651
+ const cx = rect.left + rect.width / 2 + window.scrollX;
5652
+ const cy = rect.top + rect.height / 2 + window.scrollY;
5653
+ const key = getGridCell(cx, cy);
5654
+ hoverGrid.set(key, (hoverGrid.get(key) || 0) + dwell);
5655
+ } catch (_) {}
5656
+ }, { passive: true });
5657
+
5658
+ // ── Tab visibility → tab_switches + total_tab_hidden_ms
5659
+ document.addEventListener('visibilitychange', () => {
5660
+ if (document.hidden) {
5661
+ tabHiddenTime = Date.now();
5662
+ tabSwitches++;
5663
+ } else if (tabHiddenTime !== null) {
5664
+ totalTabHiddenMs += Date.now() - tabHiddenTime;
5665
+ tabHiddenTime = null;
5666
+ }
5667
+ });
5668
+
5669
+ // ── Scroll reversal detection (direction change > 200 px)
5670
+ window.addEventListener('scroll', () => {
5671
+ const curr = window.scrollY;
5672
+ const diff = curr - lastSigScrollY;
5673
+ if (Math.abs(diff) < 50) return; // ignore tiny movements
5674
+ const dir = diff > 0 ? 1 : -1;
5675
+ if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
5676
+ scrollReversals++;
5677
+ }
5678
+ if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
5679
+ }, { passive: true });
5680
+
5681
+ // ── Field dwell → per-field focus duration (no content captured)
5682
+ const fieldFocusTimes = new Map();
5683
+ document.addEventListener('focus', (e) => {
5684
+ const t = e.target;
5685
+ if (!t || !['INPUT', 'SELECT', 'TEXTAREA'].includes(t.tagName)) return;
5686
+ if (t.type === 'hidden') return;
5687
+ fieldFocusTimes.set(t, Date.now());
5688
+ }, true);
5689
+ document.addEventListener('blur', (e) => {
5690
+ const t = e.target;
5691
+ if (!t || !fieldFocusTimes.has(t)) return;
5692
+ const dwell = Date.now() - fieldFocusTimes.get(t);
5693
+ fieldFocusTimes.delete(t);
5694
+ if (dwell < 100) return; // skip accidental focus
5695
+ const label = t.id
5696
+ ? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
5697
+ : (t.placeholder || t.name || t.type || '');
5698
+ fieldDwells.push({
5699
+ field_id: (t.id || t.name || t.type || '').substring(0, 100),
5700
+ field_label: (label || '').substring(0, 100),
5701
+ field_type: t.type || t.tagName.toLowerCase(),
5702
+ dwell_ms: dwell,
5703
+ was_filled: !!(t.value && t.value.length > 0),
5704
+ });
5705
+ }, true);
5706
+
5707
+ // ── Serialise grid Map to [[col, row, value], ...] sparse array
5708
+ function serializeGrid(grid) {
5709
+ const out = [];
5710
+ grid.forEach((val, key) => {
5711
+ if (val > 0) {
5712
+ const col = Math.floor(key / GRID_SIZE);
5713
+ const row = key % GRID_SIZE;
5714
+ out.push([col, row, Math.round(val)]);
5715
+ }
5716
+ });
5717
+ return out;
5718
+ }
5719
+
5720
+ let summarySent = false;
5721
+ function firePageSummary() {
5722
+ if (summarySent) return; // only fire once per page lifecycle
5723
+ if (moveGrid.size === 0 && hoverGrid.size === 0 && fieldDwells.length === 0
5724
+ && tabSwitches === 0 && scrollReversals === 0) return;
5725
+ summarySent = true;
5726
+ try {
5727
+ EventsManager.trackAutoEvent('page_summary', {
5728
+ scroll_reversals: scrollReversals,
5729
+ tab_switches: tabSwitches,
5730
+ total_tab_hidden_ms: totalTabHiddenMs,
5731
+ move_grid: serializeGrid(moveGrid),
5732
+ hover_grid: serializeGrid(hoverGrid),
5733
+ field_dwells: fieldDwells,
5734
+ document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
5735
+ document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
5736
+ viewport_width: window.innerWidth,
5737
+ viewport_height: window.innerHeight,
5738
+ });
5739
+ } catch (_) {}
5740
+ }
5741
+
5742
+ // Fire on hard navigation / tab close (pagehide is more reliable than beforeunload)
5743
+ window.addEventListener('pagehide', firePageSummary);
5744
+ // Also fire when tab becomes hidden (covers SPA navigation that doesn't fire pagehide)
5745
+ document.addEventListener('visibilitychange', () => {
5746
+ if (document.hidden) firePageSummary();
5747
+ });
5748
+ } catch (error) {}
5749
+ },
5750
+
5577
5751
  /**
5578
5752
  * Initialize all event trackers
5579
5753
  *
@@ -5597,6 +5771,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
5597
5771
  this.startAdvancedFormTracking();
5598
5772
  this.startFormAbandonmentTracking();
5599
5773
  this.startElementVisibilityTracking();
5774
+ this.startPageSummaryTracking();
5600
5775
  }
5601
5776
  };
5602
5777
 
@@ -6425,7 +6600,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
6425
6600
  // Performance
6426
6601
  'page_performance': 'performance',
6427
6602
  // Visibility
6428
- 'element_view': 'visibility'
6603
+ 'element_view': 'visibility',
6604
+ // Page summary (aggregate)
6605
+ 'page_summary': 'interaction',
6606
+ // Clipboard & context
6607
+ 'copy_action': 'interaction',
6608
+ 'context_menu': 'interaction',
6429
6609
  };
6430
6610
 
6431
6611
  return categoryMap[eventName] || 'other';
@@ -8222,6 +8402,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
8222
8402
  * The script.js file is inlined during the build process by Rollup.
8223
8403
  */
8224
8404
 
8405
+
8225
8406
  // Create a wrapper that provides programmatic initialization
8226
8407
  const CryptiqueSDK = {
8227
8408
  /**
package/lib/umd/index.js CHANGED
@@ -96,8 +96,8 @@
96
96
 
97
97
  // Geolocation API
98
98
  GEOLOCATION: {
99
- PRIMARY_URL: "https://ipinfo.io/json?token=73937f74acc045",
100
- BACKUP_URL: "https://ipinfo.io/json?token=73937f74acc045&http=1.1",
99
+ PRIMARY_URL: "https://ipinfo.io/json?token=8fc6409059aa39",
100
+ BACKUP_URL: "https://ipinfo.io/json?token=8fc6409059aa39&http=1.1",
101
101
  TIMEOUT_MS: 5000 // 5 second timeout
102
102
  },
103
103
 
@@ -5003,19 +5003,26 @@
5003
5003
  startCopyPasteTracking() {
5004
5004
  try {
5005
5005
  document.addEventListener('copy', (event) => {
5006
+ const sel = window.getSelection();
5006
5007
  const copyData = {
5007
5008
  type: 'copy_action',
5008
- selectedText: window.getSelection().toString().substring(0, 100),
5009
+ selectedText: sel ? sel.toString().substring(0, 100) : '',
5009
5010
  target: {
5010
5011
  tagName: event.target?.tagName || '',
5011
5012
  id: event.target?.id || ''
5012
5013
  },
5013
5014
  path: window.location.pathname
5014
5015
  };
5015
-
5016
5016
  InteractionManager.add('copyPasteEvents', copyData);
5017
+ // Fire structured auto-event (low volume — only when user explicitly copies)
5018
+ EventsManager.trackAutoEvent('copy_action', {
5019
+ text_length: sel ? sel.toString().length : 0,
5020
+ element_id: event.target?.id || '',
5021
+ element_tag: event.target?.tagName?.toLowerCase() || '',
5022
+ element_class: (event.target?.className || '').split(' ').filter(Boolean).slice(0, 3).join(' '),
5023
+ });
5017
5024
  });
5018
-
5025
+
5019
5026
  document.addEventListener('paste', (event) => {
5020
5027
  const pasteData = {
5021
5028
  type: 'paste_action',
@@ -5025,7 +5032,6 @@
5025
5032
  },
5026
5033
  path: window.location.pathname
5027
5034
  };
5028
-
5029
5035
  InteractionManager.add('copyPasteEvents', pasteData);
5030
5036
  });
5031
5037
  } catch (error) {
@@ -5051,8 +5057,15 @@
5051
5057
  },
5052
5058
  path: window.location.pathname
5053
5059
  };
5054
-
5055
5060
  InteractionManager.add('contextMenuEvents', contextData);
5061
+ // Fire structured auto-event (low volume — only when user right-clicks)
5062
+ EventsManager.trackAutoEvent('context_menu', {
5063
+ element_tag: event.target?.tagName?.toLowerCase() || '',
5064
+ element_id: event.target?.id || '',
5065
+ element_class: (event.target?.className || '').split(' ').filter(Boolean).slice(0, 3).join(' '),
5066
+ page_x: event.pageX,
5067
+ page_y: event.pageY,
5068
+ });
5056
5069
  });
5057
5070
  } catch (error) {
5058
5071
  }
@@ -5580,6 +5593,167 @@
5580
5593
  }
5581
5594
  },
5582
5595
 
5596
+ /**
5597
+ * PageSummaryTracker — collects high-frequency signals in-memory and flushes
5598
+ * them as a single 'page_summary' auto-event on page unload.
5599
+ *
5600
+ * This keeps event counts low (1 per page visit) while capturing:
5601
+ * - Mouse movement grid (move_grid)
5602
+ * - Hover dwell grid (hover_grid)
5603
+ * - Tab-switch count and hidden time
5604
+ * - Scroll reversal count
5605
+ * - Per-field focus dwell times
5606
+ */
5607
+ startPageSummaryTracking() {
5608
+ try {
5609
+ const GRID_SIZE = 40;
5610
+ // In-memory accumulators — never sent to the server individually
5611
+ const moveGrid = new Map(); // cellKey → move count
5612
+ const hoverGrid = new Map(); // cellKey → total dwell_ms
5613
+ const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
5614
+
5615
+ let tabSwitches = 0;
5616
+ let totalTabHiddenMs = 0;
5617
+ let tabHiddenTime = null;
5618
+ let scrollReversals = 0;
5619
+
5620
+ // Track last significant scroll position for reversal detection
5621
+ let lastSigScrollY = window.scrollY;
5622
+ let lastSigDir = 0; // 1=down, -1=up
5623
+
5624
+ function getGridCell(pageX, pageY) {
5625
+ const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
5626
+ const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
5627
+ const col = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageX / docW) * GRID_SIZE)));
5628
+ const row = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageY / docH) * GRID_SIZE)));
5629
+ return col * GRID_SIZE + row;
5630
+ }
5631
+
5632
+ // ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
5633
+ let moveThrottle = false;
5634
+ document.addEventListener('mousemove', (e) => {
5635
+ if (moveThrottle) return;
5636
+ moveThrottle = true;
5637
+ setTimeout(() => { moveThrottle = false; }, 100);
5638
+ const key = getGridCell(e.pageX, e.pageY);
5639
+ moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
5640
+ }, { passive: true });
5641
+
5642
+ // ── Hover dwell → hover_grid (track all elements, filter < 100 ms)
5643
+ const hoverStarts = new Map(); // element → enterTime
5644
+ document.addEventListener('mouseover', (e) => {
5645
+ if (e.target && e.target !== document.documentElement) {
5646
+ hoverStarts.set(e.target, Date.now());
5647
+ }
5648
+ }, { passive: true });
5649
+ document.addEventListener('mouseout', (e) => {
5650
+ const target = e.target;
5651
+ if (!target || !hoverStarts.has(target)) return;
5652
+ const dwell = Date.now() - hoverStarts.get(target);
5653
+ hoverStarts.delete(target);
5654
+ if (dwell < 100) return; // skip accidental hovers
5655
+ try {
5656
+ const rect = target.getBoundingClientRect();
5657
+ const cx = rect.left + rect.width / 2 + window.scrollX;
5658
+ const cy = rect.top + rect.height / 2 + window.scrollY;
5659
+ const key = getGridCell(cx, cy);
5660
+ hoverGrid.set(key, (hoverGrid.get(key) || 0) + dwell);
5661
+ } catch (_) {}
5662
+ }, { passive: true });
5663
+
5664
+ // ── Tab visibility → tab_switches + total_tab_hidden_ms
5665
+ document.addEventListener('visibilitychange', () => {
5666
+ if (document.hidden) {
5667
+ tabHiddenTime = Date.now();
5668
+ tabSwitches++;
5669
+ } else if (tabHiddenTime !== null) {
5670
+ totalTabHiddenMs += Date.now() - tabHiddenTime;
5671
+ tabHiddenTime = null;
5672
+ }
5673
+ });
5674
+
5675
+ // ── Scroll reversal detection (direction change > 200 px)
5676
+ window.addEventListener('scroll', () => {
5677
+ const curr = window.scrollY;
5678
+ const diff = curr - lastSigScrollY;
5679
+ if (Math.abs(diff) < 50) return; // ignore tiny movements
5680
+ const dir = diff > 0 ? 1 : -1;
5681
+ if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
5682
+ scrollReversals++;
5683
+ }
5684
+ if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
5685
+ }, { passive: true });
5686
+
5687
+ // ── Field dwell → per-field focus duration (no content captured)
5688
+ const fieldFocusTimes = new Map();
5689
+ document.addEventListener('focus', (e) => {
5690
+ const t = e.target;
5691
+ if (!t || !['INPUT', 'SELECT', 'TEXTAREA'].includes(t.tagName)) return;
5692
+ if (t.type === 'hidden') return;
5693
+ fieldFocusTimes.set(t, Date.now());
5694
+ }, true);
5695
+ document.addEventListener('blur', (e) => {
5696
+ const t = e.target;
5697
+ if (!t || !fieldFocusTimes.has(t)) return;
5698
+ const dwell = Date.now() - fieldFocusTimes.get(t);
5699
+ fieldFocusTimes.delete(t);
5700
+ if (dwell < 100) return; // skip accidental focus
5701
+ const label = t.id
5702
+ ? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
5703
+ : (t.placeholder || t.name || t.type || '');
5704
+ fieldDwells.push({
5705
+ field_id: (t.id || t.name || t.type || '').substring(0, 100),
5706
+ field_label: (label || '').substring(0, 100),
5707
+ field_type: t.type || t.tagName.toLowerCase(),
5708
+ dwell_ms: dwell,
5709
+ was_filled: !!(t.value && t.value.length > 0),
5710
+ });
5711
+ }, true);
5712
+
5713
+ // ── Serialise grid Map to [[col, row, value], ...] sparse array
5714
+ function serializeGrid(grid) {
5715
+ const out = [];
5716
+ grid.forEach((val, key) => {
5717
+ if (val > 0) {
5718
+ const col = Math.floor(key / GRID_SIZE);
5719
+ const row = key % GRID_SIZE;
5720
+ out.push([col, row, Math.round(val)]);
5721
+ }
5722
+ });
5723
+ return out;
5724
+ }
5725
+
5726
+ let summarySent = false;
5727
+ function firePageSummary() {
5728
+ if (summarySent) return; // only fire once per page lifecycle
5729
+ if (moveGrid.size === 0 && hoverGrid.size === 0 && fieldDwells.length === 0
5730
+ && tabSwitches === 0 && scrollReversals === 0) return;
5731
+ summarySent = true;
5732
+ try {
5733
+ EventsManager.trackAutoEvent('page_summary', {
5734
+ scroll_reversals: scrollReversals,
5735
+ tab_switches: tabSwitches,
5736
+ total_tab_hidden_ms: totalTabHiddenMs,
5737
+ move_grid: serializeGrid(moveGrid),
5738
+ hover_grid: serializeGrid(hoverGrid),
5739
+ field_dwells: fieldDwells,
5740
+ document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
5741
+ document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
5742
+ viewport_width: window.innerWidth,
5743
+ viewport_height: window.innerHeight,
5744
+ });
5745
+ } catch (_) {}
5746
+ }
5747
+
5748
+ // Fire on hard navigation / tab close (pagehide is more reliable than beforeunload)
5749
+ window.addEventListener('pagehide', firePageSummary);
5750
+ // Also fire when tab becomes hidden (covers SPA navigation that doesn't fire pagehide)
5751
+ document.addEventListener('visibilitychange', () => {
5752
+ if (document.hidden) firePageSummary();
5753
+ });
5754
+ } catch (error) {}
5755
+ },
5756
+
5583
5757
  /**
5584
5758
  * Initialize all event trackers
5585
5759
  *
@@ -5603,6 +5777,7 @@
5603
5777
  this.startAdvancedFormTracking();
5604
5778
  this.startFormAbandonmentTracking();
5605
5779
  this.startElementVisibilityTracking();
5780
+ this.startPageSummaryTracking();
5606
5781
  }
5607
5782
  };
5608
5783
 
@@ -6431,7 +6606,12 @@
6431
6606
  // Performance
6432
6607
  'page_performance': 'performance',
6433
6608
  // Visibility
6434
- 'element_view': 'visibility'
6609
+ 'element_view': 'visibility',
6610
+ // Page summary (aggregate)
6611
+ 'page_summary': 'interaction',
6612
+ // Clipboard & context
6613
+ 'copy_action': 'interaction',
6614
+ 'context_menu': 'interaction',
6435
6615
  };
6436
6616
 
6437
6617
  return categoryMap[eventName] || 'other';
@@ -8228,6 +8408,7 @@
8228
8408
  * The script.js file is inlined during the build process by Rollup.
8229
8409
  */
8230
8410
 
8411
+
8231
8412
  // Create a wrapper that provides programmatic initialization
8232
8413
  const CryptiqueSDK = {
8233
8414
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cryptique-sdk",
3
- "version": "1.2.16",
3
+ "version": "1.2.17",
4
4
  "type": "module",
5
5
  "description": "Cryptique Analytics SDK - Comprehensive web analytics and user tracking for modern web applications",
6
6
  "main": "lib/cjs/index.js",