cryptique-sdk 1.2.16 → 1.2.18
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 +373 -51
- package/lib/esm/index.js +373 -51
- package/lib/umd/index.js +373 -51
- package/package.json +2 -1
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=
|
|
96
|
-
BACKUP_URL: "https://ipinfo.io/json?token=
|
|
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:
|
|
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
|
}
|
|
@@ -5506,74 +5519,376 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5506
5519
|
|
|
5507
5520
|
/**
|
|
5508
5521
|
* ElementVisibilityTracker - Emits element_view auto events when elements
|
|
5509
|
-
*
|
|
5510
|
-
*
|
|
5511
|
-
*
|
|
5522
|
+
* enter the viewport for ≥ 1 second, capturing the actual dwell duration.
|
|
5523
|
+
*
|
|
5524
|
+
* Covers interactive elements (buttons, links, inputs, headings, images) and
|
|
5525
|
+
* any element with an id or data-cq-track attribute. Each viewport entry is
|
|
5526
|
+
* tracked independently — if a user scrolls away and back, both dwells count.
|
|
5527
|
+
* This gives accurate attention data for the heatmap attention overlay.
|
|
5512
5528
|
*/
|
|
5513
5529
|
startElementVisibilityTracking() {
|
|
5514
5530
|
try {
|
|
5515
5531
|
if (typeof IntersectionObserver === 'undefined') return;
|
|
5516
5532
|
|
|
5517
|
-
//
|
|
5533
|
+
// Observe interactive/structural elements in addition to id/data-cq-track
|
|
5534
|
+
const TRACKABLE_SELECTOR = [
|
|
5535
|
+
'[id]:not([id=""])', '[data-cq-track]',
|
|
5536
|
+
'button', 'a[href]', 'input:not([type=hidden])', 'select', 'textarea',
|
|
5537
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img[alt]',
|
|
5538
|
+
'[role="button"]', '[role="tab"]', '[role="menuitem"]', '[role="link"]'
|
|
5539
|
+
].join(', ');
|
|
5540
|
+
|
|
5518
5541
|
const getTrackableElements = () =>
|
|
5519
|
-
document.querySelectorAll(
|
|
5542
|
+
Array.from(document.querySelectorAll(TRACKABLE_SELECTOR)).filter(el => {
|
|
5543
|
+
try { return el.getBoundingClientRect().height >= 20; } catch (_) { return true; }
|
|
5544
|
+
});
|
|
5520
5545
|
|
|
5521
|
-
|
|
5522
|
-
const
|
|
5546
|
+
// enterTimes: key → { enterTime, el } — tracks elements currently in viewport
|
|
5547
|
+
const enterTimes = new Map();
|
|
5548
|
+
const observedEls = new WeakSet(); // prevents double-observing same DOM node
|
|
5549
|
+
|
|
5550
|
+
function elementKey(el) {
|
|
5551
|
+
return `${el.tagName}|${el.id || ''}|${el.getAttribute('data-cq-track') || ''}|${(el.textContent || '').trim().slice(0, 30)}`;
|
|
5552
|
+
}
|
|
5553
|
+
|
|
5554
|
+
function getElementData(el) {
|
|
5555
|
+
return {
|
|
5556
|
+
element_id: el.id || '',
|
|
5557
|
+
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5558
|
+
element_tag_name: el.tagName || '',
|
|
5559
|
+
element_type: el.type || el.getAttribute('type') || '',
|
|
5560
|
+
element_class: el.className || '',
|
|
5561
|
+
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5562
|
+
};
|
|
5563
|
+
}
|
|
5523
5564
|
|
|
5524
5565
|
const observer = new IntersectionObserver((entries) => {
|
|
5525
5566
|
entries.forEach((entry) => {
|
|
5526
|
-
const el
|
|
5527
|
-
const key = el
|
|
5528
|
-
if (!key || reported.has(key)) return;
|
|
5567
|
+
const el = entry.target;
|
|
5568
|
+
const key = elementKey(el);
|
|
5529
5569
|
|
|
5530
5570
|
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
5531
|
-
// Element entered viewport —
|
|
5532
|
-
if (!
|
|
5533
|
-
|
|
5534
|
-
const timer = setTimeout(() => {
|
|
5535
|
-
if (reported.has(key)) return;
|
|
5536
|
-
reported.add(key);
|
|
5537
|
-
EventsManager.trackAutoEvent('element_view', {
|
|
5538
|
-
time_in_viewport_ms: Date.now() - enterTime,
|
|
5539
|
-
viewport_percent_visible: Math.round(entry.intersectionRatio * 100)
|
|
5540
|
-
}, {
|
|
5541
|
-
element_id: el.id || '',
|
|
5542
|
-
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5543
|
-
element_tag_name: el.tagName || '',
|
|
5544
|
-
element_type: el.type || el.getAttribute('type') || '',
|
|
5545
|
-
element_class: el.className || '',
|
|
5546
|
-
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5547
|
-
});
|
|
5548
|
-
viewTimers.delete(key);
|
|
5549
|
-
}, 1000);
|
|
5550
|
-
viewTimers.set(key, timer);
|
|
5571
|
+
// Element entered viewport — record entry time
|
|
5572
|
+
if (!enterTimes.has(key)) {
|
|
5573
|
+
enterTimes.set(key, { enterTime: Date.now(), el });
|
|
5551
5574
|
}
|
|
5552
5575
|
} else {
|
|
5553
|
-
// Element left viewport
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5576
|
+
// Element left viewport — emit actual dwell if ≥ 1s
|
|
5577
|
+
const record = enterTimes.get(key);
|
|
5578
|
+
if (record) {
|
|
5579
|
+
const dwell = Date.now() - record.enterTime;
|
|
5580
|
+
enterTimes.delete(key);
|
|
5581
|
+
if (dwell >= 1000) {
|
|
5582
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5583
|
+
time_in_viewport_ms: dwell,
|
|
5584
|
+
viewport_percent_visible: Math.round(entry.intersectionRatio * 100),
|
|
5585
|
+
scroll_y_at_view: window.scrollY
|
|
5586
|
+
}, getElementData(el));
|
|
5587
|
+
}
|
|
5557
5588
|
}
|
|
5558
5589
|
}
|
|
5559
5590
|
});
|
|
5560
|
-
}, { threshold: 0.5 });
|
|
5591
|
+
}, { threshold: [0.5] });
|
|
5592
|
+
|
|
5593
|
+
const observe = (el) => {
|
|
5594
|
+
if (!observedEls.has(el)) { observedEls.add(el); observer.observe(el); }
|
|
5595
|
+
};
|
|
5561
5596
|
|
|
5562
|
-
|
|
5563
|
-
getTrackableElements().forEach(el => observer.observe(el));
|
|
5597
|
+
getTrackableElements().forEach(observe);
|
|
5564
5598
|
|
|
5565
5599
|
// Observe elements added to DOM later (SPAs)
|
|
5566
5600
|
if (typeof MutationObserver !== 'undefined') {
|
|
5567
5601
|
new MutationObserver(() => {
|
|
5568
|
-
getTrackableElements().forEach(
|
|
5569
|
-
if (!viewTimers.has(el.id || el.getAttribute('data-cq-track') || el.className)) {
|
|
5570
|
-
observer.observe(el);
|
|
5571
|
-
}
|
|
5572
|
-
});
|
|
5602
|
+
getTrackableElements().forEach(observe);
|
|
5573
5603
|
}).observe(document.body, { childList: true, subtree: true });
|
|
5574
5604
|
}
|
|
5575
|
-
|
|
5605
|
+
|
|
5606
|
+
// On page unload, flush elements still in viewport
|
|
5607
|
+
window.addEventListener('pagehide', () => {
|
|
5608
|
+
enterTimes.forEach(({ enterTime, el }) => {
|
|
5609
|
+
const dwell = Date.now() - enterTime;
|
|
5610
|
+
if (dwell >= 1000) {
|
|
5611
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5612
|
+
time_in_viewport_ms: dwell,
|
|
5613
|
+
viewport_percent_visible: 100,
|
|
5614
|
+
scroll_y_at_view: window.scrollY
|
|
5615
|
+
}, getElementData(el));
|
|
5616
|
+
}
|
|
5617
|
+
});
|
|
5618
|
+
});
|
|
5619
|
+
} catch (error) {}
|
|
5620
|
+
},
|
|
5621
|
+
|
|
5622
|
+
/**
|
|
5623
|
+
* PageSummaryTracker — collects high-frequency signals in-memory and flushes
|
|
5624
|
+
* them as a single 'page_summary' auto-event on page unload.
|
|
5625
|
+
*
|
|
5626
|
+
* High-volume/continuous data lives here (grids, counts) to keep event volume low.
|
|
5627
|
+
* Low-frequency, high-signal moments (exit_intent) are emitted as separate auto-events
|
|
5628
|
+
* but their counters are also included here for correlation.
|
|
5629
|
+
*
|
|
5630
|
+
* Captures:
|
|
5631
|
+
* - Mouse movement grid (move_grid) + hover dwell grid (hover_grid)
|
|
5632
|
+
* - Element-level hover dwells for named/interactive elements (element_dwells)
|
|
5633
|
+
* - Tab-switch count and hidden time
|
|
5634
|
+
* - Scroll reversal count + depths at each reversal (scroll_reversal_depths)
|
|
5635
|
+
* - Per-field focus dwell times
|
|
5636
|
+
* - Exit intent count + scroll depth at last exit intent
|
|
5637
|
+
* - First interaction time (how long before user engaged)
|
|
5638
|
+
* - Performance: CLS score, long task count/max, INP
|
|
5639
|
+
*/
|
|
5640
|
+
startPageSummaryTracking() {
|
|
5641
|
+
try {
|
|
5642
|
+
const GRID_SIZE = 40;
|
|
5643
|
+
|
|
5644
|
+
// In-memory accumulators — never sent to the server individually
|
|
5645
|
+
const moveGrid = new Map(); // cellKey → move count
|
|
5646
|
+
const hoverGrid = new Map(); // cellKey → total dwell_ms
|
|
5647
|
+
const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
|
|
5648
|
+
const elementDwellsMap = new Map(); // elemKey → {tag, id, text, dwell_ms}
|
|
5649
|
+
const scrollReversalDepths = []; // scroll depth % at each reversal
|
|
5650
|
+
|
|
5651
|
+
let tabSwitches = 0;
|
|
5652
|
+
let totalTabHiddenMs = 0;
|
|
5653
|
+
let tabHiddenTime = null;
|
|
5654
|
+
let scrollReversals = 0;
|
|
5655
|
+
let exitIntentCount = 0;
|
|
5656
|
+
let exitIntentLastScrollDepth = 0;
|
|
5657
|
+
let firstInteractionMs = null;
|
|
5658
|
+
let clsScore = 0;
|
|
5659
|
+
let longTasksCount = 0;
|
|
5660
|
+
let maxLongTaskMs = 0;
|
|
5661
|
+
let inpMs = 0;
|
|
5662
|
+
|
|
5663
|
+
const pageLoadTime = Date.now();
|
|
5664
|
+
let lastSigScrollY = window.scrollY;
|
|
5665
|
+
let lastSigDir = 0; // 1=down, -1=up
|
|
5666
|
+
|
|
5667
|
+
// ── Helpers
|
|
5668
|
+
function getScrollDepth() {
|
|
5669
|
+
const maxScroll = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - window.innerHeight;
|
|
5670
|
+
return maxScroll <= 0 ? 100 : Math.round((window.scrollY / maxScroll) * 100);
|
|
5671
|
+
}
|
|
5672
|
+
|
|
5673
|
+
function getGridCell(pageX, pageY) {
|
|
5674
|
+
const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
|
|
5675
|
+
const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
|
|
5676
|
+
const col = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageX / docW) * GRID_SIZE)));
|
|
5677
|
+
const row = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageY / docH) * GRID_SIZE)));
|
|
5678
|
+
return col * GRID_SIZE + row;
|
|
5679
|
+
}
|
|
5680
|
+
|
|
5681
|
+
// ── PerformanceObserver: CLS, Long Tasks, INP (best-effort, not all browsers)
|
|
5682
|
+
try {
|
|
5683
|
+
new PerformanceObserver(list => {
|
|
5684
|
+
for (const e of list.getEntries()) {
|
|
5685
|
+
if (!e.hadRecentInput) clsScore = Math.round((clsScore + e.value) * 1000) / 1000;
|
|
5686
|
+
}
|
|
5687
|
+
}).observe({ type: 'layout-shift', buffered: true });
|
|
5688
|
+
} catch (_) {}
|
|
5689
|
+
try {
|
|
5690
|
+
new PerformanceObserver(list => {
|
|
5691
|
+
for (const e of list.getEntries()) {
|
|
5692
|
+
longTasksCount++;
|
|
5693
|
+
if (e.duration > maxLongTaskMs) maxLongTaskMs = Math.round(e.duration);
|
|
5694
|
+
}
|
|
5695
|
+
}).observe({ type: 'longtask', buffered: true });
|
|
5696
|
+
} catch (_) {}
|
|
5697
|
+
try {
|
|
5698
|
+
new PerformanceObserver(list => {
|
|
5699
|
+
for (const e of list.getEntries()) {
|
|
5700
|
+
if (e.duration > inpMs) inpMs = Math.round(e.duration);
|
|
5701
|
+
}
|
|
5702
|
+
}).observe({ type: 'event', durationThreshold: 40, buffered: true });
|
|
5703
|
+
} catch (_) {}
|
|
5704
|
+
|
|
5705
|
+
// ── First interaction: time from page load to first meaningful engagement
|
|
5706
|
+
const markFirstInteraction = () => {
|
|
5707
|
+
if (firstInteractionMs === null) firstInteractionMs = Date.now() - pageLoadTime;
|
|
5708
|
+
};
|
|
5709
|
+
document.addEventListener('click', markFirstInteraction, { once: true, passive: true });
|
|
5710
|
+
document.addEventListener('keydown', markFirstInteraction, { once: true, passive: true });
|
|
5711
|
+
document.addEventListener('touchstart', markFirstInteraction, { once: true, passive: true });
|
|
5712
|
+
window.addEventListener('scroll', markFirstInteraction, { once: true, passive: true });
|
|
5713
|
+
|
|
5714
|
+
// ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
|
|
5715
|
+
let moveThrottle = false;
|
|
5716
|
+
document.addEventListener('mousemove', (e) => {
|
|
5717
|
+
if (moveThrottle) return;
|
|
5718
|
+
moveThrottle = true;
|
|
5719
|
+
setTimeout(() => { moveThrottle = false; }, 100);
|
|
5720
|
+
const key = getGridCell(e.pageX, e.pageY);
|
|
5721
|
+
moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
|
|
5722
|
+
}, { passive: true });
|
|
5723
|
+
|
|
5724
|
+
// ── Hover dwell → hover_grid + element_dwells for named/interactive elements
|
|
5725
|
+
const ELEMENT_DWELL_TAGS = new Set(['BUTTON', 'A', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'INPUT', 'SELECT', 'TEXTAREA', 'IMG', 'LABEL']);
|
|
5726
|
+
const hoverStarts = new Map(); // element → enterTime
|
|
5727
|
+
document.addEventListener('mouseover', (e) => {
|
|
5728
|
+
if (e.target && e.target !== document.documentElement) {
|
|
5729
|
+
hoverStarts.set(e.target, Date.now());
|
|
5730
|
+
}
|
|
5731
|
+
}, { passive: true });
|
|
5732
|
+
document.addEventListener('mouseout', (e) => {
|
|
5733
|
+
const target = e.target;
|
|
5734
|
+
if (!target || !hoverStarts.has(target)) return;
|
|
5735
|
+
const dwell = Date.now() - hoverStarts.get(target);
|
|
5736
|
+
hoverStarts.delete(target);
|
|
5737
|
+
if (dwell < 100) return; // skip accidental hovers
|
|
5738
|
+
try {
|
|
5739
|
+
const rect = target.getBoundingClientRect();
|
|
5740
|
+
const cx = rect.left + rect.width / 2 + window.scrollX;
|
|
5741
|
+
const cy = rect.top + rect.height / 2 + window.scrollY;
|
|
5742
|
+
const key = getGridCell(cx, cy);
|
|
5743
|
+
hoverGrid.set(key, (hoverGrid.get(key) || 0) + dwell);
|
|
5744
|
+
|
|
5745
|
+
// Element-level dwell: track named/interactive elements hovered ≥ 300ms
|
|
5746
|
+
if (dwell >= 300 && (target.id || ELEMENT_DWELL_TAGS.has(target.tagName))) {
|
|
5747
|
+
const elemKey = `${target.tagName}|${target.id || ''}|${(target.textContent || '').trim().slice(0, 30)}`;
|
|
5748
|
+
const existing = elementDwellsMap.get(elemKey);
|
|
5749
|
+
if (existing) {
|
|
5750
|
+
existing.dwell_ms += dwell;
|
|
5751
|
+
} else {
|
|
5752
|
+
elementDwellsMap.set(elemKey, {
|
|
5753
|
+
tag: target.tagName.toLowerCase(),
|
|
5754
|
+
id: target.id || '',
|
|
5755
|
+
text: (target.textContent || '').trim().slice(0, 50),
|
|
5756
|
+
dwell_ms: dwell
|
|
5757
|
+
});
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5760
|
+
} catch (_) {}
|
|
5761
|
+
}, { passive: true });
|
|
5762
|
+
|
|
5763
|
+
// ── Tab visibility → tab_switches + total_tab_hidden_ms
|
|
5764
|
+
document.addEventListener('visibilitychange', () => {
|
|
5765
|
+
if (document.hidden) {
|
|
5766
|
+
tabHiddenTime = Date.now();
|
|
5767
|
+
tabSwitches++;
|
|
5768
|
+
} else if (tabHiddenTime !== null) {
|
|
5769
|
+
totalTabHiddenMs += Date.now() - tabHiddenTime;
|
|
5770
|
+
tabHiddenTime = null;
|
|
5771
|
+
}
|
|
5772
|
+
});
|
|
5773
|
+
|
|
5774
|
+
// ── Scroll reversal detection (direction change ≥ 200 px) + depth recording
|
|
5775
|
+
window.addEventListener('scroll', () => {
|
|
5776
|
+
const curr = window.scrollY;
|
|
5777
|
+
const diff = curr - lastSigScrollY;
|
|
5778
|
+
if (Math.abs(diff) < 50) return;
|
|
5779
|
+
const dir = diff > 0 ? 1 : -1;
|
|
5780
|
+
if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
|
|
5781
|
+
scrollReversals++;
|
|
5782
|
+
scrollReversalDepths.push(getScrollDepth());
|
|
5783
|
+
}
|
|
5784
|
+
if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
|
|
5785
|
+
}, { passive: true });
|
|
5786
|
+
|
|
5787
|
+
// ── Field dwell → per-field focus duration (no content captured)
|
|
5788
|
+
const fieldFocusTimes = new Map();
|
|
5789
|
+
document.addEventListener('focus', (e) => {
|
|
5790
|
+
const t = e.target;
|
|
5791
|
+
if (!t || !['INPUT', 'SELECT', 'TEXTAREA'].includes(t.tagName)) return;
|
|
5792
|
+
if (t.type === 'hidden') return;
|
|
5793
|
+
fieldFocusTimes.set(t, Date.now());
|
|
5794
|
+
}, true);
|
|
5795
|
+
document.addEventListener('blur', (e) => {
|
|
5796
|
+
const t = e.target;
|
|
5797
|
+
if (!t || !fieldFocusTimes.has(t)) return;
|
|
5798
|
+
const dwell = Date.now() - fieldFocusTimes.get(t);
|
|
5799
|
+
fieldFocusTimes.delete(t);
|
|
5800
|
+
if (dwell < 100) return;
|
|
5801
|
+
const label = t.id
|
|
5802
|
+
? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
|
|
5803
|
+
: (t.placeholder || t.name || t.type || '');
|
|
5804
|
+
fieldDwells.push({
|
|
5805
|
+
field_id: (t.id || t.name || t.type || '').substring(0, 100),
|
|
5806
|
+
field_label: (label || '').substring(0, 100),
|
|
5807
|
+
field_type: t.type || t.tagName.toLowerCase(),
|
|
5808
|
+
dwell_ms: dwell,
|
|
5809
|
+
was_filled: !!(t.value && t.value.length > 0),
|
|
5810
|
+
});
|
|
5811
|
+
}, true);
|
|
5812
|
+
|
|
5813
|
+
// ── Exit intent: mouse leaving viewport toward the top (user about to navigate away)
|
|
5814
|
+
// Also fires as a standalone auto-event for direct querying.
|
|
5815
|
+
let lastExitIntentTime = 0;
|
|
5816
|
+
document.addEventListener('mouseleave', (e) => {
|
|
5817
|
+
if (e.clientY > 0 || e.relatedTarget !== null) return; // only top-edge exits
|
|
5818
|
+
const now = Date.now();
|
|
5819
|
+
if (now - lastExitIntentTime < 2000) return; // debounce: max once per 2s
|
|
5820
|
+
lastExitIntentTime = now;
|
|
5821
|
+
exitIntentCount++;
|
|
5822
|
+
exitIntentLastScrollDepth = getScrollDepth();
|
|
5823
|
+
EventsManager.trackAutoEvent('exit_intent', {
|
|
5824
|
+
scroll_depth: exitIntentLastScrollDepth,
|
|
5825
|
+
time_on_page_ms: now - pageLoadTime,
|
|
5826
|
+
scroll_y: window.scrollY,
|
|
5827
|
+
document_height: Math.min(
|
|
5828
|
+
document.documentElement.scrollHeight || document.body.scrollHeight, 25000
|
|
5829
|
+
)
|
|
5830
|
+
}).catch(() => {});
|
|
5831
|
+
});
|
|
5832
|
+
|
|
5833
|
+
// ── Serialise grid Map to [[col, row, value], ...] sparse array
|
|
5834
|
+
function serializeGrid(grid) {
|
|
5835
|
+
const out = [];
|
|
5836
|
+
grid.forEach((val, key) => {
|
|
5837
|
+
if (val > 0) {
|
|
5838
|
+
const col = Math.floor(key / GRID_SIZE);
|
|
5839
|
+
const row = key % GRID_SIZE;
|
|
5840
|
+
out.push([col, row, Math.round(val)]);
|
|
5841
|
+
}
|
|
5842
|
+
});
|
|
5843
|
+
return out;
|
|
5844
|
+
}
|
|
5845
|
+
|
|
5846
|
+
let summarySent = false;
|
|
5847
|
+
function firePageSummary() {
|
|
5848
|
+
if (summarySent) return; // only fire once per page lifecycle
|
|
5849
|
+
const hasData = moveGrid.size > 0 || hoverGrid.size > 0 || fieldDwells.length > 0
|
|
5850
|
+
|| tabSwitches > 0 || scrollReversals > 0 || exitIntentCount > 0
|
|
5851
|
+
|| firstInteractionMs !== null || clsScore > 0 || longTasksCount > 0;
|
|
5852
|
+
if (!hasData) return;
|
|
5853
|
+
summarySent = true;
|
|
5854
|
+
try {
|
|
5855
|
+
// Serialize element_dwells: filter ≥ 300ms, sort desc, cap at 20
|
|
5856
|
+
const elementDwells = Array.from(elementDwellsMap.values())
|
|
5857
|
+
.filter(e => e.dwell_ms >= 300)
|
|
5858
|
+
.sort((a, b) => b.dwell_ms - a.dwell_ms)
|
|
5859
|
+
.slice(0, 20);
|
|
5860
|
+
|
|
5861
|
+
EventsManager.trackAutoEvent('page_summary', {
|
|
5862
|
+
scroll_reversals: scrollReversals,
|
|
5863
|
+
scroll_reversal_depths: scrollReversalDepths,
|
|
5864
|
+
tab_switches: tabSwitches,
|
|
5865
|
+
total_tab_hidden_ms: totalTabHiddenMs,
|
|
5866
|
+
move_grid: serializeGrid(moveGrid),
|
|
5867
|
+
hover_grid: serializeGrid(hoverGrid),
|
|
5868
|
+
field_dwells: fieldDwells,
|
|
5869
|
+
element_dwells: elementDwells,
|
|
5870
|
+
first_interaction_ms: firstInteractionMs,
|
|
5871
|
+
exit_intent_count: exitIntentCount,
|
|
5872
|
+
exit_intent_last_scroll_depth: exitIntentLastScrollDepth,
|
|
5873
|
+
cls_score: clsScore,
|
|
5874
|
+
long_tasks_count: longTasksCount,
|
|
5875
|
+
max_long_task_ms: maxLongTaskMs,
|
|
5876
|
+
inp_ms: inpMs,
|
|
5877
|
+
document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
|
|
5878
|
+
document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
|
|
5879
|
+
viewport_width: window.innerWidth,
|
|
5880
|
+
viewport_height: window.innerHeight,
|
|
5881
|
+
});
|
|
5882
|
+
} catch (_) {}
|
|
5576
5883
|
}
|
|
5884
|
+
|
|
5885
|
+
// Fire on hard navigation / tab close (pagehide is more reliable than beforeunload)
|
|
5886
|
+
window.addEventListener('pagehide', firePageSummary);
|
|
5887
|
+
// Also fire when tab becomes hidden (covers SPA navigation that doesn't fire pagehide)
|
|
5888
|
+
document.addEventListener('visibilitychange', () => {
|
|
5889
|
+
if (document.hidden) firePageSummary();
|
|
5890
|
+
});
|
|
5891
|
+
} catch (error) {}
|
|
5577
5892
|
},
|
|
5578
5893
|
|
|
5579
5894
|
/**
|
|
@@ -5599,6 +5914,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5599
5914
|
this.startAdvancedFormTracking();
|
|
5600
5915
|
this.startFormAbandonmentTracking();
|
|
5601
5916
|
this.startElementVisibilityTracking();
|
|
5917
|
+
this.startPageSummaryTracking();
|
|
5602
5918
|
}
|
|
5603
5919
|
};
|
|
5604
5920
|
|
|
@@ -6427,7 +6743,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6427
6743
|
// Performance
|
|
6428
6744
|
'page_performance': 'performance',
|
|
6429
6745
|
// Visibility
|
|
6430
|
-
'element_view': 'visibility'
|
|
6746
|
+
'element_view': 'visibility',
|
|
6747
|
+
// Page summary (aggregate)
|
|
6748
|
+
'page_summary': 'interaction',
|
|
6749
|
+
// Clipboard & context
|
|
6750
|
+
'copy_action': 'interaction',
|
|
6751
|
+
'context_menu': 'interaction',
|
|
6431
6752
|
};
|
|
6432
6753
|
|
|
6433
6754
|
return categoryMap[eventName] || 'other';
|
|
@@ -8224,6 +8545,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
8224
8545
|
* The script.js file is inlined during the build process by Rollup.
|
|
8225
8546
|
*/
|
|
8226
8547
|
|
|
8548
|
+
|
|
8227
8549
|
// Create a wrapper that provides programmatic initialization
|
|
8228
8550
|
const CryptiqueSDK = {
|
|
8229
8551
|
/**
|