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/cjs/index.js
CHANGED
|
@@ -5519,104 +5519,157 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5519
5519
|
|
|
5520
5520
|
/**
|
|
5521
5521
|
* ElementVisibilityTracker - Emits element_view auto events when elements
|
|
5522
|
-
*
|
|
5523
|
-
*
|
|
5524
|
-
*
|
|
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.
|
|
5525
5528
|
*/
|
|
5526
5529
|
startElementVisibilityTracking() {
|
|
5527
5530
|
try {
|
|
5528
5531
|
if (typeof IntersectionObserver === 'undefined') return;
|
|
5529
5532
|
|
|
5530
|
-
//
|
|
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
|
+
|
|
5531
5541
|
const getTrackableElements = () =>
|
|
5532
|
-
document.querySelectorAll(
|
|
5542
|
+
Array.from(document.querySelectorAll(TRACKABLE_SELECTOR)).filter(el => {
|
|
5543
|
+
try { return el.getBoundingClientRect().height >= 20; } catch (_) { return true; }
|
|
5544
|
+
});
|
|
5545
|
+
|
|
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
|
|
5533
5549
|
|
|
5534
|
-
|
|
5535
|
-
|
|
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
|
+
}
|
|
5536
5564
|
|
|
5537
5565
|
const observer = new IntersectionObserver((entries) => {
|
|
5538
5566
|
entries.forEach((entry) => {
|
|
5539
|
-
const el
|
|
5540
|
-
const key = el
|
|
5541
|
-
if (!key || reported.has(key)) return;
|
|
5567
|
+
const el = entry.target;
|
|
5568
|
+
const key = elementKey(el);
|
|
5542
5569
|
|
|
5543
5570
|
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
5544
|
-
// Element entered viewport —
|
|
5545
|
-
if (!
|
|
5546
|
-
|
|
5547
|
-
const timer = setTimeout(() => {
|
|
5548
|
-
if (reported.has(key)) return;
|
|
5549
|
-
reported.add(key);
|
|
5550
|
-
EventsManager.trackAutoEvent('element_view', {
|
|
5551
|
-
time_in_viewport_ms: Date.now() - enterTime,
|
|
5552
|
-
viewport_percent_visible: Math.round(entry.intersectionRatio * 100)
|
|
5553
|
-
}, {
|
|
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
|
-
viewTimers.delete(key);
|
|
5562
|
-
}, 1000);
|
|
5563
|
-
viewTimers.set(key, timer);
|
|
5571
|
+
// Element entered viewport — record entry time
|
|
5572
|
+
if (!enterTimes.has(key)) {
|
|
5573
|
+
enterTimes.set(key, { enterTime: Date.now(), el });
|
|
5564
5574
|
}
|
|
5565
5575
|
} else {
|
|
5566
|
-
// Element left viewport
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
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
|
+
}
|
|
5570
5588
|
}
|
|
5571
5589
|
}
|
|
5572
5590
|
});
|
|
5573
|
-
}, { threshold: 0.5 });
|
|
5591
|
+
}, { threshold: [0.5] });
|
|
5574
5592
|
|
|
5575
|
-
|
|
5576
|
-
|
|
5593
|
+
const observe = (el) => {
|
|
5594
|
+
if (!observedEls.has(el)) { observedEls.add(el); observer.observe(el); }
|
|
5595
|
+
};
|
|
5596
|
+
|
|
5597
|
+
getTrackableElements().forEach(observe);
|
|
5577
5598
|
|
|
5578
5599
|
// Observe elements added to DOM later (SPAs)
|
|
5579
5600
|
if (typeof MutationObserver !== 'undefined') {
|
|
5580
5601
|
new MutationObserver(() => {
|
|
5581
|
-
getTrackableElements().forEach(
|
|
5582
|
-
if (!viewTimers.has(el.id || el.getAttribute('data-cq-track') || el.className)) {
|
|
5583
|
-
observer.observe(el);
|
|
5584
|
-
}
|
|
5585
|
-
});
|
|
5602
|
+
getTrackableElements().forEach(observe);
|
|
5586
5603
|
}).observe(document.body, { childList: true, subtree: true });
|
|
5587
5604
|
}
|
|
5588
|
-
|
|
5589
|
-
|
|
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) {}
|
|
5590
5620
|
},
|
|
5591
5621
|
|
|
5592
5622
|
/**
|
|
5593
5623
|
* PageSummaryTracker — collects high-frequency signals in-memory and flushes
|
|
5594
5624
|
* them as a single 'page_summary' auto-event on page unload.
|
|
5595
5625
|
*
|
|
5596
|
-
*
|
|
5597
|
-
*
|
|
5598
|
-
*
|
|
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)
|
|
5599
5633
|
* - Tab-switch count and hidden time
|
|
5600
|
-
* - Scroll reversal count
|
|
5634
|
+
* - Scroll reversal count + depths at each reversal (scroll_reversal_depths)
|
|
5601
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
|
|
5602
5639
|
*/
|
|
5603
5640
|
startPageSummaryTracking() {
|
|
5604
5641
|
try {
|
|
5605
|
-
const GRID_SIZE =
|
|
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;
|
|
5642
|
+
const GRID_SIZE = 60;
|
|
5615
5643
|
|
|
5616
|
-
//
|
|
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();
|
|
5617
5664
|
let lastSigScrollY = window.scrollY;
|
|
5618
5665
|
let lastSigDir = 0; // 1=down, -1=up
|
|
5619
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
|
+
|
|
5620
5673
|
function getGridCell(pageX, pageY) {
|
|
5621
5674
|
const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
|
|
5622
5675
|
const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
|
|
@@ -5625,6 +5678,39 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5625
5678
|
return col * GRID_SIZE + row;
|
|
5626
5679
|
}
|
|
5627
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
|
+
|
|
5628
5714
|
// ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
|
|
5629
5715
|
let moveThrottle = false;
|
|
5630
5716
|
document.addEventListener('mousemove', (e) => {
|
|
@@ -5635,7 +5721,8 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5635
5721
|
moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
|
|
5636
5722
|
}, { passive: true });
|
|
5637
5723
|
|
|
5638
|
-
// ── Hover dwell → hover_grid
|
|
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']);
|
|
5639
5726
|
const hoverStarts = new Map(); // element → enterTime
|
|
5640
5727
|
document.addEventListener('mouseover', (e) => {
|
|
5641
5728
|
if (e.target && e.target !== document.documentElement) {
|
|
@@ -5654,9 +5741,81 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5654
5741
|
const cy = rect.top + rect.height / 2 + window.scrollY;
|
|
5655
5742
|
const key = getGridCell(cx, cy);
|
|
5656
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
|
+
}
|
|
5657
5760
|
} catch (_) {}
|
|
5658
5761
|
}, { passive: true });
|
|
5659
5762
|
|
|
5763
|
+
// ── Viewport visibility → viewport_element_dwells (replaces individual element_view events)
|
|
5764
|
+
const viewportDwellsMap = new Map(); // elemKey → {tag,id,cls,text,dwell_ms,x,y,w,h}
|
|
5765
|
+
const vpEnterTimes = new Map(); // elemKey → {enterTime,x,y,w,h}
|
|
5766
|
+
try {
|
|
5767
|
+
if (typeof IntersectionObserver !== 'undefined') {
|
|
5768
|
+
const VP_SELECTOR = [
|
|
5769
|
+
'[id]:not([id=""])', '[data-cq-track]',
|
|
5770
|
+
'button', 'a[href]', 'input:not([type=hidden])', 'select', 'textarea',
|
|
5771
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img[alt]',
|
|
5772
|
+
'[role="button"]', '[role="tab"]', '[role="menuitem"]', '[role="link"]'
|
|
5773
|
+
].join(', ');
|
|
5774
|
+
const vpKey = el => `${el.tagName}|${el.id || ''}|${(el.textContent || '').trim().slice(0, 30)}`;
|
|
5775
|
+
const vpObserver = new IntersectionObserver(entries => {
|
|
5776
|
+
entries.forEach(entry => {
|
|
5777
|
+
const el = entry.target;
|
|
5778
|
+
const key = vpKey(el);
|
|
5779
|
+
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
5780
|
+
if (!vpEnterTimes.has(key)) {
|
|
5781
|
+
const r = el.getBoundingClientRect();
|
|
5782
|
+
vpEnterTimes.set(key, {
|
|
5783
|
+
enterTime: Date.now(),
|
|
5784
|
+
x: Math.round(r.left + window.scrollX),
|
|
5785
|
+
y: Math.round(r.top + window.scrollY),
|
|
5786
|
+
w: Math.round(r.width),
|
|
5787
|
+
h: Math.round(r.height),
|
|
5788
|
+
});
|
|
5789
|
+
}
|
|
5790
|
+
} else {
|
|
5791
|
+
const rec = vpEnterTimes.get(key);
|
|
5792
|
+
if (rec) {
|
|
5793
|
+
const dwell = Date.now() - rec.enterTime;
|
|
5794
|
+
vpEnterTimes.delete(key);
|
|
5795
|
+
if (dwell >= 1000) {
|
|
5796
|
+
const ex = viewportDwellsMap.get(key);
|
|
5797
|
+
if (ex) { ex.dwell_ms += dwell; }
|
|
5798
|
+
else {
|
|
5799
|
+
viewportDwellsMap.set(key, {
|
|
5800
|
+
tag: el.tagName.toLowerCase(),
|
|
5801
|
+
id: el.id || '',
|
|
5802
|
+
cls: el.className ? String(el.className).trim().slice(0, 60) : '',
|
|
5803
|
+
text: (el.textContent || '').trim().slice(0, 60),
|
|
5804
|
+
dwell_ms: dwell,
|
|
5805
|
+
x: rec.x, y: rec.y, w: rec.w, h: rec.h,
|
|
5806
|
+
});
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
});
|
|
5812
|
+
}, { threshold: [0.5] });
|
|
5813
|
+
document.querySelectorAll(VP_SELECTOR).forEach(el => {
|
|
5814
|
+
try { if (el.getBoundingClientRect().height >= 20) vpObserver.observe(el); } catch (_) {}
|
|
5815
|
+
});
|
|
5816
|
+
}
|
|
5817
|
+
} catch (_) {}
|
|
5818
|
+
|
|
5660
5819
|
// ── Tab visibility → tab_switches + total_tab_hidden_ms
|
|
5661
5820
|
document.addEventListener('visibilitychange', () => {
|
|
5662
5821
|
if (document.hidden) {
|
|
@@ -5668,14 +5827,15 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5668
5827
|
}
|
|
5669
5828
|
});
|
|
5670
5829
|
|
|
5671
|
-
// ── Scroll reversal detection (direction change
|
|
5830
|
+
// ── Scroll reversal detection (direction change ≥ 200 px) + depth recording
|
|
5672
5831
|
window.addEventListener('scroll', () => {
|
|
5673
5832
|
const curr = window.scrollY;
|
|
5674
5833
|
const diff = curr - lastSigScrollY;
|
|
5675
|
-
if (Math.abs(diff) < 50) return;
|
|
5676
|
-
const dir
|
|
5834
|
+
if (Math.abs(diff) < 50) return;
|
|
5835
|
+
const dir = diff > 0 ? 1 : -1;
|
|
5677
5836
|
if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
|
|
5678
5837
|
scrollReversals++;
|
|
5838
|
+
scrollReversalDepths.push(getScrollDepth());
|
|
5679
5839
|
}
|
|
5680
5840
|
if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
|
|
5681
5841
|
}, { passive: true });
|
|
@@ -5693,7 +5853,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5693
5853
|
if (!t || !fieldFocusTimes.has(t)) return;
|
|
5694
5854
|
const dwell = Date.now() - fieldFocusTimes.get(t);
|
|
5695
5855
|
fieldFocusTimes.delete(t);
|
|
5696
|
-
if (dwell < 100) return;
|
|
5856
|
+
if (dwell < 100) return;
|
|
5697
5857
|
const label = t.id
|
|
5698
5858
|
? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
|
|
5699
5859
|
: (t.placeholder || t.name || t.type || '');
|
|
@@ -5706,6 +5866,26 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5706
5866
|
});
|
|
5707
5867
|
}, true);
|
|
5708
5868
|
|
|
5869
|
+
// ── Exit intent: mouse leaving viewport toward the top (user about to navigate away)
|
|
5870
|
+
// Also fires as a standalone auto-event for direct querying.
|
|
5871
|
+
let lastExitIntentTime = 0;
|
|
5872
|
+
document.addEventListener('mouseleave', (e) => {
|
|
5873
|
+
if (e.clientY > 0 || e.relatedTarget !== null) return; // only top-edge exits
|
|
5874
|
+
const now = Date.now();
|
|
5875
|
+
if (now - lastExitIntentTime < 2000) return; // debounce: max once per 2s
|
|
5876
|
+
lastExitIntentTime = now;
|
|
5877
|
+
exitIntentCount++;
|
|
5878
|
+
exitIntentLastScrollDepth = getScrollDepth();
|
|
5879
|
+
EventsManager.trackAutoEvent('exit_intent', {
|
|
5880
|
+
scroll_depth: exitIntentLastScrollDepth,
|
|
5881
|
+
time_on_page_ms: now - pageLoadTime,
|
|
5882
|
+
scroll_y: window.scrollY,
|
|
5883
|
+
document_height: Math.min(
|
|
5884
|
+
document.documentElement.scrollHeight || document.body.scrollHeight, 25000
|
|
5885
|
+
)
|
|
5886
|
+
}).catch(() => {});
|
|
5887
|
+
});
|
|
5888
|
+
|
|
5709
5889
|
// ── Serialise grid Map to [[col, row, value], ...] sparse array
|
|
5710
5890
|
function serializeGrid(grid) {
|
|
5711
5891
|
const out = [];
|
|
@@ -5722,17 +5902,52 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5722
5902
|
let summarySent = false;
|
|
5723
5903
|
function firePageSummary() {
|
|
5724
5904
|
if (summarySent) return; // only fire once per page lifecycle
|
|
5725
|
-
|
|
5726
|
-
|
|
5905
|
+
const hasData = moveGrid.size > 0 || hoverGrid.size > 0 || fieldDwells.length > 0
|
|
5906
|
+
|| tabSwitches > 0 || scrollReversals > 0 || exitIntentCount > 0
|
|
5907
|
+
|| firstInteractionMs !== null || clsScore > 0 || longTasksCount > 0;
|
|
5908
|
+
if (!hasData) return;
|
|
5727
5909
|
summarySent = true;
|
|
5728
5910
|
try {
|
|
5911
|
+
// Flush still-visible elements into viewportDwellsMap before summarising
|
|
5912
|
+
vpEnterTimes.forEach((rec, key) => {
|
|
5913
|
+
const dwell = Date.now() - rec.enterTime;
|
|
5914
|
+
if (dwell < 1000) return;
|
|
5915
|
+
const ex = viewportDwellsMap.get(key);
|
|
5916
|
+
if (ex) { ex.dwell_ms += dwell; }
|
|
5917
|
+
else {
|
|
5918
|
+
const parts = key.split('|');
|
|
5919
|
+
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 });
|
|
5920
|
+
}
|
|
5921
|
+
});
|
|
5922
|
+
// Serialize viewport_element_dwells: filter ≥ 1000ms, sort desc, cap at 25
|
|
5923
|
+
const viewportElementDwells = Array.from(viewportDwellsMap.values())
|
|
5924
|
+
.filter(e => e.dwell_ms >= 1000)
|
|
5925
|
+
.sort((a, b) => b.dwell_ms - a.dwell_ms)
|
|
5926
|
+
.slice(0, 25);
|
|
5927
|
+
|
|
5928
|
+
// Serialize element_dwells: filter ≥ 300ms, sort desc, cap at 20
|
|
5929
|
+
const elementDwells = Array.from(elementDwellsMap.values())
|
|
5930
|
+
.filter(e => e.dwell_ms >= 300)
|
|
5931
|
+
.sort((a, b) => b.dwell_ms - a.dwell_ms)
|
|
5932
|
+
.slice(0, 20);
|
|
5933
|
+
|
|
5729
5934
|
EventsManager.trackAutoEvent('page_summary', {
|
|
5730
|
-
scroll_reversals:
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5935
|
+
scroll_reversals: scrollReversals,
|
|
5936
|
+
scroll_reversal_depths: scrollReversalDepths,
|
|
5937
|
+
tab_switches: tabSwitches,
|
|
5938
|
+
total_tab_hidden_ms: totalTabHiddenMs,
|
|
5939
|
+
move_grid: serializeGrid(moveGrid),
|
|
5940
|
+
hover_grid: serializeGrid(hoverGrid),
|
|
5941
|
+
field_dwells: fieldDwells,
|
|
5942
|
+
element_dwells: elementDwells,
|
|
5943
|
+
viewport_element_dwells: viewportElementDwells,
|
|
5944
|
+
first_interaction_ms: firstInteractionMs,
|
|
5945
|
+
exit_intent_count: exitIntentCount,
|
|
5946
|
+
exit_intent_last_scroll_depth: exitIntentLastScrollDepth,
|
|
5947
|
+
cls_score: clsScore,
|
|
5948
|
+
long_tasks_count: longTasksCount,
|
|
5949
|
+
max_long_task_ms: maxLongTaskMs,
|
|
5950
|
+
inp_ms: inpMs,
|
|
5736
5951
|
document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
|
|
5737
5952
|
document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
|
|
5738
5953
|
viewport_width: window.innerWidth,
|
|
@@ -5772,7 +5987,8 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5772
5987
|
this.startNetworkTracking();
|
|
5773
5988
|
this.startAdvancedFormTracking();
|
|
5774
5989
|
this.startFormAbandonmentTracking();
|
|
5775
|
-
|
|
5990
|
+
// element_view events replaced by viewport_element_dwells inside page_summary
|
|
5991
|
+
// this.startElementVisibilityTracking();
|
|
5776
5992
|
this.startPageSummaryTracking();
|
|
5777
5993
|
}
|
|
5778
5994
|
};
|