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/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=
|
|
94
|
-
BACKUP_URL: "https://ipinfo.io/json?token=
|
|
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:
|
|
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
|
}
|
|
@@ -5504,74 +5517,376 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5504
5517
|
|
|
5505
5518
|
/**
|
|
5506
5519
|
* ElementVisibilityTracker - Emits element_view auto events when elements
|
|
5507
|
-
*
|
|
5508
|
-
*
|
|
5509
|
-
*
|
|
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.
|
|
5510
5526
|
*/
|
|
5511
5527
|
startElementVisibilityTracking() {
|
|
5512
5528
|
try {
|
|
5513
5529
|
if (typeof IntersectionObserver === 'undefined') return;
|
|
5514
5530
|
|
|
5515
|
-
//
|
|
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
|
+
|
|
5516
5539
|
const getTrackableElements = () =>
|
|
5517
|
-
document.querySelectorAll(
|
|
5540
|
+
Array.from(document.querySelectorAll(TRACKABLE_SELECTOR)).filter(el => {
|
|
5541
|
+
try { return el.getBoundingClientRect().height >= 20; } catch (_) { return true; }
|
|
5542
|
+
});
|
|
5518
5543
|
|
|
5519
|
-
|
|
5520
|
-
const
|
|
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
|
|
5547
|
+
|
|
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
|
+
}
|
|
5521
5562
|
|
|
5522
5563
|
const observer = new IntersectionObserver((entries) => {
|
|
5523
5564
|
entries.forEach((entry) => {
|
|
5524
|
-
const el
|
|
5525
|
-
const key = el
|
|
5526
|
-
if (!key || reported.has(key)) return;
|
|
5565
|
+
const el = entry.target;
|
|
5566
|
+
const key = elementKey(el);
|
|
5527
5567
|
|
|
5528
5568
|
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
5529
|
-
// Element entered viewport —
|
|
5530
|
-
if (!
|
|
5531
|
-
|
|
5532
|
-
const timer = setTimeout(() => {
|
|
5533
|
-
if (reported.has(key)) return;
|
|
5534
|
-
reported.add(key);
|
|
5535
|
-
EventsManager.trackAutoEvent('element_view', {
|
|
5536
|
-
time_in_viewport_ms: Date.now() - enterTime,
|
|
5537
|
-
viewport_percent_visible: Math.round(entry.intersectionRatio * 100)
|
|
5538
|
-
}, {
|
|
5539
|
-
element_id: el.id || '',
|
|
5540
|
-
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5541
|
-
element_tag_name: el.tagName || '',
|
|
5542
|
-
element_type: el.type || el.getAttribute('type') || '',
|
|
5543
|
-
element_class: el.className || '',
|
|
5544
|
-
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5545
|
-
});
|
|
5546
|
-
viewTimers.delete(key);
|
|
5547
|
-
}, 1000);
|
|
5548
|
-
viewTimers.set(key, timer);
|
|
5569
|
+
// Element entered viewport — record entry time
|
|
5570
|
+
if (!enterTimes.has(key)) {
|
|
5571
|
+
enterTimes.set(key, { enterTime: Date.now(), el });
|
|
5549
5572
|
}
|
|
5550
5573
|
} else {
|
|
5551
|
-
// Element left viewport
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
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
|
+
}
|
|
5555
5586
|
}
|
|
5556
5587
|
}
|
|
5557
5588
|
});
|
|
5558
|
-
}, { threshold: 0.5 });
|
|
5589
|
+
}, { threshold: [0.5] });
|
|
5590
|
+
|
|
5591
|
+
const observe = (el) => {
|
|
5592
|
+
if (!observedEls.has(el)) { observedEls.add(el); observer.observe(el); }
|
|
5593
|
+
};
|
|
5559
5594
|
|
|
5560
|
-
|
|
5561
|
-
getTrackableElements().forEach(el => observer.observe(el));
|
|
5595
|
+
getTrackableElements().forEach(observe);
|
|
5562
5596
|
|
|
5563
5597
|
// Observe elements added to DOM later (SPAs)
|
|
5564
5598
|
if (typeof MutationObserver !== 'undefined') {
|
|
5565
5599
|
new MutationObserver(() => {
|
|
5566
|
-
getTrackableElements().forEach(
|
|
5567
|
-
if (!viewTimers.has(el.id || el.getAttribute('data-cq-track') || el.className)) {
|
|
5568
|
-
observer.observe(el);
|
|
5569
|
-
}
|
|
5570
|
-
});
|
|
5600
|
+
getTrackableElements().forEach(observe);
|
|
5571
5601
|
}).observe(document.body, { childList: true, subtree: true });
|
|
5572
5602
|
}
|
|
5573
|
-
|
|
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) {}
|
|
5618
|
+
},
|
|
5619
|
+
|
|
5620
|
+
/**
|
|
5621
|
+
* PageSummaryTracker — collects high-frequency signals in-memory and flushes
|
|
5622
|
+
* them as a single 'page_summary' auto-event on page unload.
|
|
5623
|
+
*
|
|
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)
|
|
5631
|
+
* - Tab-switch count and hidden time
|
|
5632
|
+
* - Scroll reversal count + depths at each reversal (scroll_reversal_depths)
|
|
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
|
|
5637
|
+
*/
|
|
5638
|
+
startPageSummaryTracking() {
|
|
5639
|
+
try {
|
|
5640
|
+
const GRID_SIZE = 40;
|
|
5641
|
+
|
|
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();
|
|
5662
|
+
let lastSigScrollY = window.scrollY;
|
|
5663
|
+
let lastSigDir = 0; // 1=down, -1=up
|
|
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
|
+
|
|
5671
|
+
function getGridCell(pageX, pageY) {
|
|
5672
|
+
const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
|
|
5673
|
+
const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
|
|
5674
|
+
const col = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageX / docW) * GRID_SIZE)));
|
|
5675
|
+
const row = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageY / docH) * GRID_SIZE)));
|
|
5676
|
+
return col * GRID_SIZE + row;
|
|
5677
|
+
}
|
|
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
|
+
|
|
5712
|
+
// ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
|
|
5713
|
+
let moveThrottle = false;
|
|
5714
|
+
document.addEventListener('mousemove', (e) => {
|
|
5715
|
+
if (moveThrottle) return;
|
|
5716
|
+
moveThrottle = true;
|
|
5717
|
+
setTimeout(() => { moveThrottle = false; }, 100);
|
|
5718
|
+
const key = getGridCell(e.pageX, e.pageY);
|
|
5719
|
+
moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
|
|
5720
|
+
}, { passive: true });
|
|
5721
|
+
|
|
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']);
|
|
5724
|
+
const hoverStarts = new Map(); // element → enterTime
|
|
5725
|
+
document.addEventListener('mouseover', (e) => {
|
|
5726
|
+
if (e.target && e.target !== document.documentElement) {
|
|
5727
|
+
hoverStarts.set(e.target, Date.now());
|
|
5728
|
+
}
|
|
5729
|
+
}, { passive: true });
|
|
5730
|
+
document.addEventListener('mouseout', (e) => {
|
|
5731
|
+
const target = e.target;
|
|
5732
|
+
if (!target || !hoverStarts.has(target)) return;
|
|
5733
|
+
const dwell = Date.now() - hoverStarts.get(target);
|
|
5734
|
+
hoverStarts.delete(target);
|
|
5735
|
+
if (dwell < 100) return; // skip accidental hovers
|
|
5736
|
+
try {
|
|
5737
|
+
const rect = target.getBoundingClientRect();
|
|
5738
|
+
const cx = rect.left + rect.width / 2 + window.scrollX;
|
|
5739
|
+
const cy = rect.top + rect.height / 2 + window.scrollY;
|
|
5740
|
+
const key = getGridCell(cx, cy);
|
|
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
|
+
}
|
|
5758
|
+
} catch (_) {}
|
|
5759
|
+
}, { passive: true });
|
|
5760
|
+
|
|
5761
|
+
// ── Tab visibility → tab_switches + total_tab_hidden_ms
|
|
5762
|
+
document.addEventListener('visibilitychange', () => {
|
|
5763
|
+
if (document.hidden) {
|
|
5764
|
+
tabHiddenTime = Date.now();
|
|
5765
|
+
tabSwitches++;
|
|
5766
|
+
} else if (tabHiddenTime !== null) {
|
|
5767
|
+
totalTabHiddenMs += Date.now() - tabHiddenTime;
|
|
5768
|
+
tabHiddenTime = null;
|
|
5769
|
+
}
|
|
5770
|
+
});
|
|
5771
|
+
|
|
5772
|
+
// ── Scroll reversal detection (direction change ≥ 200 px) + depth recording
|
|
5773
|
+
window.addEventListener('scroll', () => {
|
|
5774
|
+
const curr = window.scrollY;
|
|
5775
|
+
const diff = curr - lastSigScrollY;
|
|
5776
|
+
if (Math.abs(diff) < 50) return;
|
|
5777
|
+
const dir = diff > 0 ? 1 : -1;
|
|
5778
|
+
if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
|
|
5779
|
+
scrollReversals++;
|
|
5780
|
+
scrollReversalDepths.push(getScrollDepth());
|
|
5781
|
+
}
|
|
5782
|
+
if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
|
|
5783
|
+
}, { passive: true });
|
|
5784
|
+
|
|
5785
|
+
// ── Field dwell → per-field focus duration (no content captured)
|
|
5786
|
+
const fieldFocusTimes = new Map();
|
|
5787
|
+
document.addEventListener('focus', (e) => {
|
|
5788
|
+
const t = e.target;
|
|
5789
|
+
if (!t || !['INPUT', 'SELECT', 'TEXTAREA'].includes(t.tagName)) return;
|
|
5790
|
+
if (t.type === 'hidden') return;
|
|
5791
|
+
fieldFocusTimes.set(t, Date.now());
|
|
5792
|
+
}, true);
|
|
5793
|
+
document.addEventListener('blur', (e) => {
|
|
5794
|
+
const t = e.target;
|
|
5795
|
+
if (!t || !fieldFocusTimes.has(t)) return;
|
|
5796
|
+
const dwell = Date.now() - fieldFocusTimes.get(t);
|
|
5797
|
+
fieldFocusTimes.delete(t);
|
|
5798
|
+
if (dwell < 100) return;
|
|
5799
|
+
const label = t.id
|
|
5800
|
+
? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
|
|
5801
|
+
: (t.placeholder || t.name || t.type || '');
|
|
5802
|
+
fieldDwells.push({
|
|
5803
|
+
field_id: (t.id || t.name || t.type || '').substring(0, 100),
|
|
5804
|
+
field_label: (label || '').substring(0, 100),
|
|
5805
|
+
field_type: t.type || t.tagName.toLowerCase(),
|
|
5806
|
+
dwell_ms: dwell,
|
|
5807
|
+
was_filled: !!(t.value && t.value.length > 0),
|
|
5808
|
+
});
|
|
5809
|
+
}, true);
|
|
5810
|
+
|
|
5811
|
+
// ── Exit intent: mouse leaving viewport toward the top (user about to navigate away)
|
|
5812
|
+
// Also fires as a standalone auto-event for direct querying.
|
|
5813
|
+
let lastExitIntentTime = 0;
|
|
5814
|
+
document.addEventListener('mouseleave', (e) => {
|
|
5815
|
+
if (e.clientY > 0 || e.relatedTarget !== null) return; // only top-edge exits
|
|
5816
|
+
const now = Date.now();
|
|
5817
|
+
if (now - lastExitIntentTime < 2000) return; // debounce: max once per 2s
|
|
5818
|
+
lastExitIntentTime = now;
|
|
5819
|
+
exitIntentCount++;
|
|
5820
|
+
exitIntentLastScrollDepth = getScrollDepth();
|
|
5821
|
+
EventsManager.trackAutoEvent('exit_intent', {
|
|
5822
|
+
scroll_depth: exitIntentLastScrollDepth,
|
|
5823
|
+
time_on_page_ms: now - pageLoadTime,
|
|
5824
|
+
scroll_y: window.scrollY,
|
|
5825
|
+
document_height: Math.min(
|
|
5826
|
+
document.documentElement.scrollHeight || document.body.scrollHeight, 25000
|
|
5827
|
+
)
|
|
5828
|
+
}).catch(() => {});
|
|
5829
|
+
});
|
|
5830
|
+
|
|
5831
|
+
// ── Serialise grid Map to [[col, row, value], ...] sparse array
|
|
5832
|
+
function serializeGrid(grid) {
|
|
5833
|
+
const out = [];
|
|
5834
|
+
grid.forEach((val, key) => {
|
|
5835
|
+
if (val > 0) {
|
|
5836
|
+
const col = Math.floor(key / GRID_SIZE);
|
|
5837
|
+
const row = key % GRID_SIZE;
|
|
5838
|
+
out.push([col, row, Math.round(val)]);
|
|
5839
|
+
}
|
|
5840
|
+
});
|
|
5841
|
+
return out;
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5844
|
+
let summarySent = false;
|
|
5845
|
+
function firePageSummary() {
|
|
5846
|
+
if (summarySent) return; // only fire once per page lifecycle
|
|
5847
|
+
const hasData = moveGrid.size > 0 || hoverGrid.size > 0 || fieldDwells.length > 0
|
|
5848
|
+
|| tabSwitches > 0 || scrollReversals > 0 || exitIntentCount > 0
|
|
5849
|
+
|| firstInteractionMs !== null || clsScore > 0 || longTasksCount > 0;
|
|
5850
|
+
if (!hasData) return;
|
|
5851
|
+
summarySent = true;
|
|
5852
|
+
try {
|
|
5853
|
+
// Serialize element_dwells: filter ≥ 300ms, sort desc, cap at 20
|
|
5854
|
+
const elementDwells = Array.from(elementDwellsMap.values())
|
|
5855
|
+
.filter(e => e.dwell_ms >= 300)
|
|
5856
|
+
.sort((a, b) => b.dwell_ms - a.dwell_ms)
|
|
5857
|
+
.slice(0, 20);
|
|
5858
|
+
|
|
5859
|
+
EventsManager.trackAutoEvent('page_summary', {
|
|
5860
|
+
scroll_reversals: scrollReversals,
|
|
5861
|
+
scroll_reversal_depths: scrollReversalDepths,
|
|
5862
|
+
tab_switches: tabSwitches,
|
|
5863
|
+
total_tab_hidden_ms: totalTabHiddenMs,
|
|
5864
|
+
move_grid: serializeGrid(moveGrid),
|
|
5865
|
+
hover_grid: serializeGrid(hoverGrid),
|
|
5866
|
+
field_dwells: fieldDwells,
|
|
5867
|
+
element_dwells: elementDwells,
|
|
5868
|
+
first_interaction_ms: firstInteractionMs,
|
|
5869
|
+
exit_intent_count: exitIntentCount,
|
|
5870
|
+
exit_intent_last_scroll_depth: exitIntentLastScrollDepth,
|
|
5871
|
+
cls_score: clsScore,
|
|
5872
|
+
long_tasks_count: longTasksCount,
|
|
5873
|
+
max_long_task_ms: maxLongTaskMs,
|
|
5874
|
+
inp_ms: inpMs,
|
|
5875
|
+
document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
|
|
5876
|
+
document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
|
|
5877
|
+
viewport_width: window.innerWidth,
|
|
5878
|
+
viewport_height: window.innerHeight,
|
|
5879
|
+
});
|
|
5880
|
+
} catch (_) {}
|
|
5574
5881
|
}
|
|
5882
|
+
|
|
5883
|
+
// Fire on hard navigation / tab close (pagehide is more reliable than beforeunload)
|
|
5884
|
+
window.addEventListener('pagehide', firePageSummary);
|
|
5885
|
+
// Also fire when tab becomes hidden (covers SPA navigation that doesn't fire pagehide)
|
|
5886
|
+
document.addEventListener('visibilitychange', () => {
|
|
5887
|
+
if (document.hidden) firePageSummary();
|
|
5888
|
+
});
|
|
5889
|
+
} catch (error) {}
|
|
5575
5890
|
},
|
|
5576
5891
|
|
|
5577
5892
|
/**
|
|
@@ -5597,6 +5912,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5597
5912
|
this.startAdvancedFormTracking();
|
|
5598
5913
|
this.startFormAbandonmentTracking();
|
|
5599
5914
|
this.startElementVisibilityTracking();
|
|
5915
|
+
this.startPageSummaryTracking();
|
|
5600
5916
|
}
|
|
5601
5917
|
};
|
|
5602
5918
|
|
|
@@ -6425,7 +6741,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6425
6741
|
// Performance
|
|
6426
6742
|
'page_performance': 'performance',
|
|
6427
6743
|
// Visibility
|
|
6428
|
-
'element_view': 'visibility'
|
|
6744
|
+
'element_view': 'visibility',
|
|
6745
|
+
// Page summary (aggregate)
|
|
6746
|
+
'page_summary': 'interaction',
|
|
6747
|
+
// Clipboard & context
|
|
6748
|
+
'copy_action': 'interaction',
|
|
6749
|
+
'context_menu': 'interaction',
|
|
6429
6750
|
};
|
|
6430
6751
|
|
|
6431
6752
|
return categoryMap[eventName] || 'other';
|
|
@@ -8222,6 +8543,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
8222
8543
|
* The script.js file is inlined during the build process by Rollup.
|
|
8223
8544
|
*/
|
|
8224
8545
|
|
|
8546
|
+
|
|
8225
8547
|
// Create a wrapper that provides programmatic initialization
|
|
8226
8548
|
const CryptiqueSDK = {
|
|
8227
8549
|
/**
|