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