@gemx-dev/clarity-js 0.8.89 → 0.8.91
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/build/clarity.extended.js +1 -1
- package/build/clarity.insight.js +1 -1
- package/build/clarity.js +87 -51
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +87 -51
- package/build/clarity.performance.js +1 -1
- package/package.json +1 -1
- package/src/clarity.ts +1 -1
- package/src/core/version.ts +1 -1
- package/src/data/gemx.ts +6 -0
- package/src/data/upload.ts +16 -1
- package/src/interaction/click.ts +0 -14
- package/src/interaction/encode.ts +5 -4
- package/src/interaction/index.ts +2 -2
- package/src/layout/gemx-snapshot.ts +28 -9
- package/src/layout/mutation.ts +5 -1
- package/src/layout/selector.ts +14 -1
- package/types/data.d.ts +3 -1
- package/types/global.d.ts +1 -0
|
@@ -36,27 +36,46 @@ function fire(): void {
|
|
|
36
36
|
flush();
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function schedule(trigger: { scrollDepthPercent?: number; clickCount?: number; activeTimeMs?: number; maxPlaybackBytes?: number }): void {
|
|
39
|
+
function schedule(trigger: { scrollDepthPercent?: number; clickCount?: number; activeTimeMs?: number; delayMs?: number; maxPlaybackBytes?: number }): void {
|
|
40
40
|
triggered = false;
|
|
41
|
-
let
|
|
41
|
+
let windowExpired = false;
|
|
42
|
+
let delayScheduled = false;
|
|
43
|
+
|
|
44
|
+
const scheduleDelayedFire = (type: 'scroll' | 'click'): void => {
|
|
45
|
+
if (delayScheduled || windowExpired) return;
|
|
46
|
+
delayScheduled = true;
|
|
47
|
+
console.log(`[GemX Snapshot] ${type} triggered:`);
|
|
48
|
+
setTimeout(fire, trigger.delayMs ?? 0);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (trigger.activeTimeMs) {
|
|
52
|
+
setTimeout((): void => {
|
|
53
|
+
windowExpired = true;
|
|
54
|
+
scroll.onTrustedScroll(null);
|
|
55
|
+
click.onTrustedClick(null);
|
|
56
|
+
}, trigger.activeTimeMs);
|
|
57
|
+
}
|
|
42
58
|
|
|
43
59
|
if (trigger.scrollDepthPercent) {
|
|
44
60
|
scroll.onTrustedScroll((): void => {
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
61
|
+
const docH = document.documentElement.scrollHeight;
|
|
62
|
+
const vpH = window.innerHeight;
|
|
63
|
+
const maxScrollY = docH - vpH;
|
|
64
|
+
if (maxScrollY <= 0) return;
|
|
65
|
+
|
|
66
|
+
const scrollY = "pageYOffset" in window ? Math.round(window.pageYOffset) : Math.round((document.documentElement as HTMLElement).scrollTop);
|
|
67
|
+
const pct = Math.min(100, Math.round(scrollY / maxScrollY * 100));
|
|
68
|
+
if (pct >= trigger.scrollDepthPercent) { scheduleDelayedFire('scroll'); }
|
|
48
69
|
});
|
|
49
70
|
}
|
|
50
71
|
|
|
51
72
|
if (trigger.clickCount) {
|
|
73
|
+
let clickCount = 0;
|
|
52
74
|
click.onTrustedClick((): void => {
|
|
53
75
|
clickCount++;
|
|
54
|
-
if (clickCount >= trigger.clickCount) {
|
|
76
|
+
if (clickCount >= trigger.clickCount) { scheduleDelayedFire('click'); }
|
|
55
77
|
});
|
|
56
78
|
}
|
|
57
|
-
|
|
58
|
-
if (trigger.activeTimeMs) { setTimeout(fire, trigger.activeTimeMs); }
|
|
59
|
-
// maxPlaybackBytes trigger is handled via gemxUpload.start() callback above
|
|
60
79
|
}
|
|
61
80
|
|
|
62
81
|
function flush(): void {
|
package/src/layout/mutation.ts
CHANGED
|
@@ -20,6 +20,8 @@ import traverse from "@src/layout/traverse";
|
|
|
20
20
|
import processNode from "./node";
|
|
21
21
|
import config from "@src/core/config";
|
|
22
22
|
import * as gemx from "@src/data/gemx";
|
|
23
|
+
import * as gemxUpload from "@src/data/gemx-upload";
|
|
24
|
+
|
|
23
25
|
|
|
24
26
|
let observers: Set<MutationObserver> = new Set();
|
|
25
27
|
let mutations: MutationQueue[] = [];
|
|
@@ -129,7 +131,9 @@ async function processMutation(timer: Timer, mutation: MutationRecord, instance:
|
|
|
129
131
|
let target = mutation.target;
|
|
130
132
|
|
|
131
133
|
const ignoredClasses = gemx.get().ignoreMutationClassNames;
|
|
132
|
-
|
|
134
|
+
// Before processed snapshot, we need to check if the mutation is ignored
|
|
135
|
+
const isSnapshotProcessed = gemxUpload.isActive();
|
|
136
|
+
if (!isSnapshotProcessed && ignoredClasses.length > 0 && target instanceof Element) {
|
|
133
137
|
const el = target as Element;
|
|
134
138
|
if (ignoredClasses.some(cls => el.classList.contains(cls))) { return; }
|
|
135
139
|
}
|
package/src/layout/selector.ts
CHANGED
|
@@ -6,9 +6,13 @@ import * as gemx from "@src/data/gemx";
|
|
|
6
6
|
const excludeClassNames = ExcludeClassNamesList;
|
|
7
7
|
|
|
8
8
|
let selectorMap: { [selector: string]: number[] } = {};
|
|
9
|
+
// Tracks how many times each HTML id attribute has been seen during a traversal.
|
|
10
|
+
// If count > 1, the id is duplicated in the DOM and must not be used as a CSS selector.
|
|
11
|
+
let htmlIdCountMap: Map<string, number> = new Map();
|
|
9
12
|
|
|
10
13
|
export function reset(): void {
|
|
11
14
|
selectorMap = {};
|
|
15
|
+
htmlIdCountMap = new Map();
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
export function get(input: SelectorInput, type: Selector): string {
|
|
@@ -54,7 +58,16 @@ export function get(input: SelectorInput, type: Selector): string {
|
|
|
54
58
|
// Update selector to use "id" field when available. There are two exceptions:
|
|
55
59
|
// (1) if "id" appears to be an auto generated string token, e.g. guid or a random id containing digits
|
|
56
60
|
// (2) if "id" appears inside a shadow DOM, in which case we continue to prefix up to shadow DOM to prevent conflicts
|
|
57
|
-
|
|
61
|
+
// (3) if "id" is duplicated in the DOM — using it would resolve to the wrong element during replay.
|
|
62
|
+
// Note: the very first occurrence of a duplicated id may still use #id until a duplicate is encountered.
|
|
63
|
+
if (id && filter(id)) {
|
|
64
|
+
const count = (htmlIdCountMap.get(id) ?? 0) + 1;
|
|
65
|
+
htmlIdCountMap.set(id, count);
|
|
66
|
+
if (count === 1) {
|
|
67
|
+
selector = `${getDomPrefix(prefix)}${Constant.Hash}${id}`;
|
|
68
|
+
}
|
|
69
|
+
// count > 1 means duplicate — fall through to keep the class/position-based selector
|
|
70
|
+
}
|
|
58
71
|
return selector;
|
|
59
72
|
}
|
|
60
73
|
}
|
package/types/data.d.ts
CHANGED
|
@@ -598,11 +598,13 @@ export interface GCMConsentState {
|
|
|
598
598
|
export interface DiscoverTrigger {
|
|
599
599
|
scrollDepthPercent?: number; // trigger khi scroll đến X% chiều cao trang
|
|
600
600
|
clickCount?: number; // trigger sau X lần click
|
|
601
|
-
activeTimeMs?: number; //
|
|
601
|
+
activeTimeMs?: number; // window tối đa để nhận trigger (ms)
|
|
602
|
+
delayMs?: number; // delay thêm sau khi điều kiện thỏa trước khi fire
|
|
602
603
|
maxPlaybackBytes?: number; // trigger khi buffer đạt X bytes
|
|
603
604
|
}
|
|
604
605
|
|
|
605
606
|
export interface GemXConfig {
|
|
607
|
+
debug: boolean;
|
|
606
608
|
excludeClassNames: string[];
|
|
607
609
|
ignoreMutationClassNames: string[];
|
|
608
610
|
discoverTrigger: DiscoverTrigger | null;
|
package/types/global.d.ts
CHANGED