cryptique-sdk 1.2.17 → 1.2.19
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 +289 -73
- package/lib/esm/index.js +289 -73
- package/lib/umd/index.js +289 -73
- package/package.json +2 -1
package/lib/esm/index.js
CHANGED
|
@@ -5517,104 +5517,157 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5517
5517
|
|
|
5518
5518
|
/**
|
|
5519
5519
|
* ElementVisibilityTracker - Emits element_view auto events when elements
|
|
5520
|
-
*
|
|
5521
|
-
*
|
|
5522
|
-
*
|
|
5520
|
+
* enter the viewport for ≥ 1 second, capturing the actual dwell duration.
|
|
5521
|
+
*
|
|
5522
|
+
* Covers interactive elements (buttons, links, inputs, headings, images) and
|
|
5523
|
+
* any element with an id or data-cq-track attribute. Each viewport entry is
|
|
5524
|
+
* tracked independently — if a user scrolls away and back, both dwells count.
|
|
5525
|
+
* This gives accurate attention data for the heatmap attention overlay.
|
|
5523
5526
|
*/
|
|
5524
5527
|
startElementVisibilityTracking() {
|
|
5525
5528
|
try {
|
|
5526
5529
|
if (typeof IntersectionObserver === 'undefined') return;
|
|
5527
5530
|
|
|
5528
|
-
//
|
|
5531
|
+
// Observe interactive/structural elements in addition to id/data-cq-track
|
|
5532
|
+
const TRACKABLE_SELECTOR = [
|
|
5533
|
+
'[id]:not([id=""])', '[data-cq-track]',
|
|
5534
|
+
'button', 'a[href]', 'input:not([type=hidden])', 'select', 'textarea',
|
|
5535
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img[alt]',
|
|
5536
|
+
'[role="button"]', '[role="tab"]', '[role="menuitem"]', '[role="link"]'
|
|
5537
|
+
].join(', ');
|
|
5538
|
+
|
|
5529
5539
|
const getTrackableElements = () =>
|
|
5530
|
-
document.querySelectorAll(
|
|
5540
|
+
Array.from(document.querySelectorAll(TRACKABLE_SELECTOR)).filter(el => {
|
|
5541
|
+
try { return el.getBoundingClientRect().height >= 20; } catch (_) { return true; }
|
|
5542
|
+
});
|
|
5543
|
+
|
|
5544
|
+
// enterTimes: key → { enterTime, el } — tracks elements currently in viewport
|
|
5545
|
+
const enterTimes = new Map();
|
|
5546
|
+
const observedEls = new WeakSet(); // prevents double-observing same DOM node
|
|
5531
5547
|
|
|
5532
|
-
|
|
5533
|
-
|
|
5548
|
+
function elementKey(el) {
|
|
5549
|
+
return `${el.tagName}|${el.id || ''}|${el.getAttribute('data-cq-track') || ''}|${(el.textContent || '').trim().slice(0, 30)}`;
|
|
5550
|
+
}
|
|
5551
|
+
|
|
5552
|
+
function getElementData(el) {
|
|
5553
|
+
return {
|
|
5554
|
+
element_id: el.id || '',
|
|
5555
|
+
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5556
|
+
element_tag_name: el.tagName || '',
|
|
5557
|
+
element_type: el.type || el.getAttribute('type') || '',
|
|
5558
|
+
element_class: el.className || '',
|
|
5559
|
+
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5560
|
+
};
|
|
5561
|
+
}
|
|
5534
5562
|
|
|
5535
5563
|
const observer = new IntersectionObserver((entries) => {
|
|
5536
5564
|
entries.forEach((entry) => {
|
|
5537
|
-
const el
|
|
5538
|
-
const key = el
|
|
5539
|
-
if (!key || reported.has(key)) return;
|
|
5565
|
+
const el = entry.target;
|
|
5566
|
+
const key = elementKey(el);
|
|
5540
5567
|
|
|
5541
5568
|
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
5542
|
-
// Element entered viewport —
|
|
5543
|
-
if (!
|
|
5544
|
-
|
|
5545
|
-
const timer = setTimeout(() => {
|
|
5546
|
-
if (reported.has(key)) return;
|
|
5547
|
-
reported.add(key);
|
|
5548
|
-
EventsManager.trackAutoEvent('element_view', {
|
|
5549
|
-
time_in_viewport_ms: Date.now() - enterTime,
|
|
5550
|
-
viewport_percent_visible: Math.round(entry.intersectionRatio * 100)
|
|
5551
|
-
}, {
|
|
5552
|
-
element_id: el.id || '',
|
|
5553
|
-
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5554
|
-
element_tag_name: el.tagName || '',
|
|
5555
|
-
element_type: el.type || el.getAttribute('type') || '',
|
|
5556
|
-
element_class: el.className || '',
|
|
5557
|
-
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5558
|
-
});
|
|
5559
|
-
viewTimers.delete(key);
|
|
5560
|
-
}, 1000);
|
|
5561
|
-
viewTimers.set(key, timer);
|
|
5569
|
+
// Element entered viewport — record entry time
|
|
5570
|
+
if (!enterTimes.has(key)) {
|
|
5571
|
+
enterTimes.set(key, { enterTime: Date.now(), el });
|
|
5562
5572
|
}
|
|
5563
5573
|
} else {
|
|
5564
|
-
// Element left viewport
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5574
|
+
// Element left viewport — emit actual dwell if ≥ 1s
|
|
5575
|
+
const record = enterTimes.get(key);
|
|
5576
|
+
if (record) {
|
|
5577
|
+
const dwell = Date.now() - record.enterTime;
|
|
5578
|
+
enterTimes.delete(key);
|
|
5579
|
+
if (dwell >= 1000) {
|
|
5580
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5581
|
+
time_in_viewport_ms: dwell,
|
|
5582
|
+
viewport_percent_visible: Math.round(entry.intersectionRatio * 100),
|
|
5583
|
+
scroll_y_at_view: window.scrollY
|
|
5584
|
+
}, getElementData(el));
|
|
5585
|
+
}
|
|
5568
5586
|
}
|
|
5569
5587
|
}
|
|
5570
5588
|
});
|
|
5571
|
-
}, { threshold: 0.5 });
|
|
5589
|
+
}, { threshold: [0.5] });
|
|
5572
5590
|
|
|
5573
|
-
|
|
5574
|
-
|
|
5591
|
+
const observe = (el) => {
|
|
5592
|
+
if (!observedEls.has(el)) { observedEls.add(el); observer.observe(el); }
|
|
5593
|
+
};
|
|
5594
|
+
|
|
5595
|
+
getTrackableElements().forEach(observe);
|
|
5575
5596
|
|
|
5576
5597
|
// Observe elements added to DOM later (SPAs)
|
|
5577
5598
|
if (typeof MutationObserver !== 'undefined') {
|
|
5578
5599
|
new MutationObserver(() => {
|
|
5579
|
-
getTrackableElements().forEach(
|
|
5580
|
-
if (!viewTimers.has(el.id || el.getAttribute('data-cq-track') || el.className)) {
|
|
5581
|
-
observer.observe(el);
|
|
5582
|
-
}
|
|
5583
|
-
});
|
|
5600
|
+
getTrackableElements().forEach(observe);
|
|
5584
5601
|
}).observe(document.body, { childList: true, subtree: true });
|
|
5585
5602
|
}
|
|
5586
|
-
|
|
5587
|
-
|
|
5603
|
+
|
|
5604
|
+
// On page unload, flush elements still in viewport
|
|
5605
|
+
window.addEventListener('pagehide', () => {
|
|
5606
|
+
enterTimes.forEach(({ enterTime, el }) => {
|
|
5607
|
+
const dwell = Date.now() - enterTime;
|
|
5608
|
+
if (dwell >= 1000) {
|
|
5609
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5610
|
+
time_in_viewport_ms: dwell,
|
|
5611
|
+
viewport_percent_visible: 100,
|
|
5612
|
+
scroll_y_at_view: window.scrollY
|
|
5613
|
+
}, getElementData(el));
|
|
5614
|
+
}
|
|
5615
|
+
});
|
|
5616
|
+
});
|
|
5617
|
+
} catch (error) {}
|
|
5588
5618
|
},
|
|
5589
5619
|
|
|
5590
5620
|
/**
|
|
5591
5621
|
* PageSummaryTracker — collects high-frequency signals in-memory and flushes
|
|
5592
5622
|
* them as a single 'page_summary' auto-event on page unload.
|
|
5593
5623
|
*
|
|
5594
|
-
*
|
|
5595
|
-
*
|
|
5596
|
-
*
|
|
5624
|
+
* High-volume/continuous data lives here (grids, counts) to keep event volume low.
|
|
5625
|
+
* Low-frequency, high-signal moments (exit_intent) are emitted as separate auto-events
|
|
5626
|
+
* but their counters are also included here for correlation.
|
|
5627
|
+
*
|
|
5628
|
+
* Captures:
|
|
5629
|
+
* - Mouse movement grid (move_grid) + hover dwell grid (hover_grid)
|
|
5630
|
+
* - Element-level hover dwells for named/interactive elements (element_dwells)
|
|
5597
5631
|
* - Tab-switch count and hidden time
|
|
5598
|
-
* - Scroll reversal count
|
|
5632
|
+
* - Scroll reversal count + depths at each reversal (scroll_reversal_depths)
|
|
5599
5633
|
* - Per-field focus dwell times
|
|
5634
|
+
* - Exit intent count + scroll depth at last exit intent
|
|
5635
|
+
* - First interaction time (how long before user engaged)
|
|
5636
|
+
* - Performance: CLS score, long task count/max, INP
|
|
5600
5637
|
*/
|
|
5601
5638
|
startPageSummaryTracking() {
|
|
5602
5639
|
try {
|
|
5603
|
-
const GRID_SIZE =
|
|
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;
|
|
5640
|
+
const GRID_SIZE = 60;
|
|
5613
5641
|
|
|
5614
|
-
//
|
|
5642
|
+
// In-memory accumulators — never sent to the server individually
|
|
5643
|
+
const moveGrid = new Map(); // cellKey → move count
|
|
5644
|
+
const hoverGrid = new Map(); // cellKey → total dwell_ms
|
|
5645
|
+
const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
|
|
5646
|
+
const elementDwellsMap = new Map(); // elemKey → {tag, id, text, dwell_ms}
|
|
5647
|
+
const scrollReversalDepths = []; // scroll depth % at each reversal
|
|
5648
|
+
|
|
5649
|
+
let tabSwitches = 0;
|
|
5650
|
+
let totalTabHiddenMs = 0;
|
|
5651
|
+
let tabHiddenTime = null;
|
|
5652
|
+
let scrollReversals = 0;
|
|
5653
|
+
let exitIntentCount = 0;
|
|
5654
|
+
let exitIntentLastScrollDepth = 0;
|
|
5655
|
+
let firstInteractionMs = null;
|
|
5656
|
+
let clsScore = 0;
|
|
5657
|
+
let longTasksCount = 0;
|
|
5658
|
+
let maxLongTaskMs = 0;
|
|
5659
|
+
let inpMs = 0;
|
|
5660
|
+
|
|
5661
|
+
const pageLoadTime = Date.now();
|
|
5615
5662
|
let lastSigScrollY = window.scrollY;
|
|
5616
5663
|
let lastSigDir = 0; // 1=down, -1=up
|
|
5617
5664
|
|
|
5665
|
+
// ── Helpers
|
|
5666
|
+
function getScrollDepth() {
|
|
5667
|
+
const maxScroll = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - window.innerHeight;
|
|
5668
|
+
return maxScroll <= 0 ? 100 : Math.round((window.scrollY / maxScroll) * 100);
|
|
5669
|
+
}
|
|
5670
|
+
|
|
5618
5671
|
function getGridCell(pageX, pageY) {
|
|
5619
5672
|
const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
|
|
5620
5673
|
const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
|
|
@@ -5623,6 +5676,39 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5623
5676
|
return col * GRID_SIZE + row;
|
|
5624
5677
|
}
|
|
5625
5678
|
|
|
5679
|
+
// ── PerformanceObserver: CLS, Long Tasks, INP (best-effort, not all browsers)
|
|
5680
|
+
try {
|
|
5681
|
+
new PerformanceObserver(list => {
|
|
5682
|
+
for (const e of list.getEntries()) {
|
|
5683
|
+
if (!e.hadRecentInput) clsScore = Math.round((clsScore + e.value) * 1000) / 1000;
|
|
5684
|
+
}
|
|
5685
|
+
}).observe({ type: 'layout-shift', buffered: true });
|
|
5686
|
+
} catch (_) {}
|
|
5687
|
+
try {
|
|
5688
|
+
new PerformanceObserver(list => {
|
|
5689
|
+
for (const e of list.getEntries()) {
|
|
5690
|
+
longTasksCount++;
|
|
5691
|
+
if (e.duration > maxLongTaskMs) maxLongTaskMs = Math.round(e.duration);
|
|
5692
|
+
}
|
|
5693
|
+
}).observe({ type: 'longtask', buffered: true });
|
|
5694
|
+
} catch (_) {}
|
|
5695
|
+
try {
|
|
5696
|
+
new PerformanceObserver(list => {
|
|
5697
|
+
for (const e of list.getEntries()) {
|
|
5698
|
+
if (e.duration > inpMs) inpMs = Math.round(e.duration);
|
|
5699
|
+
}
|
|
5700
|
+
}).observe({ type: 'event', durationThreshold: 40, buffered: true });
|
|
5701
|
+
} catch (_) {}
|
|
5702
|
+
|
|
5703
|
+
// ── First interaction: time from page load to first meaningful engagement
|
|
5704
|
+
const markFirstInteraction = () => {
|
|
5705
|
+
if (firstInteractionMs === null) firstInteractionMs = Date.now() - pageLoadTime;
|
|
5706
|
+
};
|
|
5707
|
+
document.addEventListener('click', markFirstInteraction, { once: true, passive: true });
|
|
5708
|
+
document.addEventListener('keydown', markFirstInteraction, { once: true, passive: true });
|
|
5709
|
+
document.addEventListener('touchstart', markFirstInteraction, { once: true, passive: true });
|
|
5710
|
+
window.addEventListener('scroll', markFirstInteraction, { once: true, passive: true });
|
|
5711
|
+
|
|
5626
5712
|
// ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
|
|
5627
5713
|
let moveThrottle = false;
|
|
5628
5714
|
document.addEventListener('mousemove', (e) => {
|
|
@@ -5633,7 +5719,8 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5633
5719
|
moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
|
|
5634
5720
|
}, { passive: true });
|
|
5635
5721
|
|
|
5636
|
-
// ── Hover dwell → hover_grid
|
|
5722
|
+
// ── Hover dwell → hover_grid + element_dwells for named/interactive elements
|
|
5723
|
+
const ELEMENT_DWELL_TAGS = new Set(['BUTTON', 'A', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'INPUT', 'SELECT', 'TEXTAREA', 'IMG', 'LABEL']);
|
|
5637
5724
|
const hoverStarts = new Map(); // element → enterTime
|
|
5638
5725
|
document.addEventListener('mouseover', (e) => {
|
|
5639
5726
|
if (e.target && e.target !== document.documentElement) {
|
|
@@ -5652,9 +5739,81 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5652
5739
|
const cy = rect.top + rect.height / 2 + window.scrollY;
|
|
5653
5740
|
const key = getGridCell(cx, cy);
|
|
5654
5741
|
hoverGrid.set(key, (hoverGrid.get(key) || 0) + dwell);
|
|
5742
|
+
|
|
5743
|
+
// Element-level dwell: track named/interactive elements hovered ≥ 300ms
|
|
5744
|
+
if (dwell >= 300 && (target.id || ELEMENT_DWELL_TAGS.has(target.tagName))) {
|
|
5745
|
+
const elemKey = `${target.tagName}|${target.id || ''}|${(target.textContent || '').trim().slice(0, 30)}`;
|
|
5746
|
+
const existing = elementDwellsMap.get(elemKey);
|
|
5747
|
+
if (existing) {
|
|
5748
|
+
existing.dwell_ms += dwell;
|
|
5749
|
+
} else {
|
|
5750
|
+
elementDwellsMap.set(elemKey, {
|
|
5751
|
+
tag: target.tagName.toLowerCase(),
|
|
5752
|
+
id: target.id || '',
|
|
5753
|
+
text: (target.textContent || '').trim().slice(0, 50),
|
|
5754
|
+
dwell_ms: dwell
|
|
5755
|
+
});
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5655
5758
|
} catch (_) {}
|
|
5656
5759
|
}, { passive: true });
|
|
5657
5760
|
|
|
5761
|
+
// ── Viewport visibility → viewport_element_dwells (replaces individual element_view events)
|
|
5762
|
+
const viewportDwellsMap = new Map(); // elemKey → {tag,id,cls,text,dwell_ms,x,y,w,h}
|
|
5763
|
+
const vpEnterTimes = new Map(); // elemKey → {enterTime,x,y,w,h}
|
|
5764
|
+
try {
|
|
5765
|
+
if (typeof IntersectionObserver !== 'undefined') {
|
|
5766
|
+
const VP_SELECTOR = [
|
|
5767
|
+
'[id]:not([id=""])', '[data-cq-track]',
|
|
5768
|
+
'button', 'a[href]', 'input:not([type=hidden])', 'select', 'textarea',
|
|
5769
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img[alt]',
|
|
5770
|
+
'[role="button"]', '[role="tab"]', '[role="menuitem"]', '[role="link"]'
|
|
5771
|
+
].join(', ');
|
|
5772
|
+
const vpKey = el => `${el.tagName}|${el.id || ''}|${(el.textContent || '').trim().slice(0, 30)}`;
|
|
5773
|
+
const vpObserver = new IntersectionObserver(entries => {
|
|
5774
|
+
entries.forEach(entry => {
|
|
5775
|
+
const el = entry.target;
|
|
5776
|
+
const key = vpKey(el);
|
|
5777
|
+
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
5778
|
+
if (!vpEnterTimes.has(key)) {
|
|
5779
|
+
const r = el.getBoundingClientRect();
|
|
5780
|
+
vpEnterTimes.set(key, {
|
|
5781
|
+
enterTime: Date.now(),
|
|
5782
|
+
x: Math.round(r.left + window.scrollX),
|
|
5783
|
+
y: Math.round(r.top + window.scrollY),
|
|
5784
|
+
w: Math.round(r.width),
|
|
5785
|
+
h: Math.round(r.height),
|
|
5786
|
+
});
|
|
5787
|
+
}
|
|
5788
|
+
} else {
|
|
5789
|
+
const rec = vpEnterTimes.get(key);
|
|
5790
|
+
if (rec) {
|
|
5791
|
+
const dwell = Date.now() - rec.enterTime;
|
|
5792
|
+
vpEnterTimes.delete(key);
|
|
5793
|
+
if (dwell >= 1000) {
|
|
5794
|
+
const ex = viewportDwellsMap.get(key);
|
|
5795
|
+
if (ex) { ex.dwell_ms += dwell; }
|
|
5796
|
+
else {
|
|
5797
|
+
viewportDwellsMap.set(key, {
|
|
5798
|
+
tag: el.tagName.toLowerCase(),
|
|
5799
|
+
id: el.id || '',
|
|
5800
|
+
cls: el.className ? String(el.className).trim().slice(0, 60) : '',
|
|
5801
|
+
text: (el.textContent || '').trim().slice(0, 60),
|
|
5802
|
+
dwell_ms: dwell,
|
|
5803
|
+
x: rec.x, y: rec.y, w: rec.w, h: rec.h,
|
|
5804
|
+
});
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
});
|
|
5810
|
+
}, { threshold: [0.5] });
|
|
5811
|
+
document.querySelectorAll(VP_SELECTOR).forEach(el => {
|
|
5812
|
+
try { if (el.getBoundingClientRect().height >= 20) vpObserver.observe(el); } catch (_) {}
|
|
5813
|
+
});
|
|
5814
|
+
}
|
|
5815
|
+
} catch (_) {}
|
|
5816
|
+
|
|
5658
5817
|
// ── Tab visibility → tab_switches + total_tab_hidden_ms
|
|
5659
5818
|
document.addEventListener('visibilitychange', () => {
|
|
5660
5819
|
if (document.hidden) {
|
|
@@ -5666,14 +5825,15 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5666
5825
|
}
|
|
5667
5826
|
});
|
|
5668
5827
|
|
|
5669
|
-
// ── Scroll reversal detection (direction change
|
|
5828
|
+
// ── Scroll reversal detection (direction change ≥ 200 px) + depth recording
|
|
5670
5829
|
window.addEventListener('scroll', () => {
|
|
5671
5830
|
const curr = window.scrollY;
|
|
5672
5831
|
const diff = curr - lastSigScrollY;
|
|
5673
|
-
if (Math.abs(diff) < 50) return;
|
|
5674
|
-
const dir
|
|
5832
|
+
if (Math.abs(diff) < 50) return;
|
|
5833
|
+
const dir = diff > 0 ? 1 : -1;
|
|
5675
5834
|
if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
|
|
5676
5835
|
scrollReversals++;
|
|
5836
|
+
scrollReversalDepths.push(getScrollDepth());
|
|
5677
5837
|
}
|
|
5678
5838
|
if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
|
|
5679
5839
|
}, { passive: true });
|
|
@@ -5691,7 +5851,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5691
5851
|
if (!t || !fieldFocusTimes.has(t)) return;
|
|
5692
5852
|
const dwell = Date.now() - fieldFocusTimes.get(t);
|
|
5693
5853
|
fieldFocusTimes.delete(t);
|
|
5694
|
-
if (dwell < 100) return;
|
|
5854
|
+
if (dwell < 100) return;
|
|
5695
5855
|
const label = t.id
|
|
5696
5856
|
? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
|
|
5697
5857
|
: (t.placeholder || t.name || t.type || '');
|
|
@@ -5704,6 +5864,26 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5704
5864
|
});
|
|
5705
5865
|
}, true);
|
|
5706
5866
|
|
|
5867
|
+
// ── Exit intent: mouse leaving viewport toward the top (user about to navigate away)
|
|
5868
|
+
// Also fires as a standalone auto-event for direct querying.
|
|
5869
|
+
let lastExitIntentTime = 0;
|
|
5870
|
+
document.addEventListener('mouseleave', (e) => {
|
|
5871
|
+
if (e.clientY > 0 || e.relatedTarget !== null) return; // only top-edge exits
|
|
5872
|
+
const now = Date.now();
|
|
5873
|
+
if (now - lastExitIntentTime < 2000) return; // debounce: max once per 2s
|
|
5874
|
+
lastExitIntentTime = now;
|
|
5875
|
+
exitIntentCount++;
|
|
5876
|
+
exitIntentLastScrollDepth = getScrollDepth();
|
|
5877
|
+
EventsManager.trackAutoEvent('exit_intent', {
|
|
5878
|
+
scroll_depth: exitIntentLastScrollDepth,
|
|
5879
|
+
time_on_page_ms: now - pageLoadTime,
|
|
5880
|
+
scroll_y: window.scrollY,
|
|
5881
|
+
document_height: Math.min(
|
|
5882
|
+
document.documentElement.scrollHeight || document.body.scrollHeight, 25000
|
|
5883
|
+
)
|
|
5884
|
+
}).catch(() => {});
|
|
5885
|
+
});
|
|
5886
|
+
|
|
5707
5887
|
// ── Serialise grid Map to [[col, row, value], ...] sparse array
|
|
5708
5888
|
function serializeGrid(grid) {
|
|
5709
5889
|
const out = [];
|
|
@@ -5720,17 +5900,52 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5720
5900
|
let summarySent = false;
|
|
5721
5901
|
function firePageSummary() {
|
|
5722
5902
|
if (summarySent) return; // only fire once per page lifecycle
|
|
5723
|
-
|
|
5724
|
-
|
|
5903
|
+
const hasData = moveGrid.size > 0 || hoverGrid.size > 0 || fieldDwells.length > 0
|
|
5904
|
+
|| tabSwitches > 0 || scrollReversals > 0 || exitIntentCount > 0
|
|
5905
|
+
|| firstInteractionMs !== null || clsScore > 0 || longTasksCount > 0;
|
|
5906
|
+
if (!hasData) return;
|
|
5725
5907
|
summarySent = true;
|
|
5726
5908
|
try {
|
|
5909
|
+
// Flush still-visible elements into viewportDwellsMap before summarising
|
|
5910
|
+
vpEnterTimes.forEach((rec, key) => {
|
|
5911
|
+
const dwell = Date.now() - rec.enterTime;
|
|
5912
|
+
if (dwell < 1000) return;
|
|
5913
|
+
const ex = viewportDwellsMap.get(key);
|
|
5914
|
+
if (ex) { ex.dwell_ms += dwell; }
|
|
5915
|
+
else {
|
|
5916
|
+
const parts = key.split('|');
|
|
5917
|
+
viewportDwellsMap.set(key, { tag: (parts[0] || '').toLowerCase(), id: parts[1] || '', cls: '', text: parts[2] || '', dwell_ms: dwell, x: rec.x, y: rec.y, w: rec.w, h: rec.h });
|
|
5918
|
+
}
|
|
5919
|
+
});
|
|
5920
|
+
// Serialize viewport_element_dwells: filter ≥ 1000ms, sort desc, cap at 25
|
|
5921
|
+
const viewportElementDwells = Array.from(viewportDwellsMap.values())
|
|
5922
|
+
.filter(e => e.dwell_ms >= 1000)
|
|
5923
|
+
.sort((a, b) => b.dwell_ms - a.dwell_ms)
|
|
5924
|
+
.slice(0, 25);
|
|
5925
|
+
|
|
5926
|
+
// Serialize element_dwells: filter ≥ 300ms, sort desc, cap at 20
|
|
5927
|
+
const elementDwells = Array.from(elementDwellsMap.values())
|
|
5928
|
+
.filter(e => e.dwell_ms >= 300)
|
|
5929
|
+
.sort((a, b) => b.dwell_ms - a.dwell_ms)
|
|
5930
|
+
.slice(0, 20);
|
|
5931
|
+
|
|
5727
5932
|
EventsManager.trackAutoEvent('page_summary', {
|
|
5728
|
-
scroll_reversals:
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5933
|
+
scroll_reversals: scrollReversals,
|
|
5934
|
+
scroll_reversal_depths: scrollReversalDepths,
|
|
5935
|
+
tab_switches: tabSwitches,
|
|
5936
|
+
total_tab_hidden_ms: totalTabHiddenMs,
|
|
5937
|
+
move_grid: serializeGrid(moveGrid),
|
|
5938
|
+
hover_grid: serializeGrid(hoverGrid),
|
|
5939
|
+
field_dwells: fieldDwells,
|
|
5940
|
+
element_dwells: elementDwells,
|
|
5941
|
+
viewport_element_dwells: viewportElementDwells,
|
|
5942
|
+
first_interaction_ms: firstInteractionMs,
|
|
5943
|
+
exit_intent_count: exitIntentCount,
|
|
5944
|
+
exit_intent_last_scroll_depth: exitIntentLastScrollDepth,
|
|
5945
|
+
cls_score: clsScore,
|
|
5946
|
+
long_tasks_count: longTasksCount,
|
|
5947
|
+
max_long_task_ms: maxLongTaskMs,
|
|
5948
|
+
inp_ms: inpMs,
|
|
5734
5949
|
document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
|
|
5735
5950
|
document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
|
|
5736
5951
|
viewport_width: window.innerWidth,
|
|
@@ -5770,7 +5985,8 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5770
5985
|
this.startNetworkTracking();
|
|
5771
5986
|
this.startAdvancedFormTracking();
|
|
5772
5987
|
this.startFormAbandonmentTracking();
|
|
5773
|
-
|
|
5988
|
+
// element_view events replaced by viewport_element_dwells inside page_summary
|
|
5989
|
+
// this.startElementVisibilityTracking();
|
|
5774
5990
|
this.startPageSummaryTracking();
|
|
5775
5991
|
}
|
|
5776
5992
|
};
|