clarity-js 0.8.42 → 0.8.44
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/README.md +26 -26
- package/build/clarity.extended.js +1 -1
- package/build/clarity.insight.js +1 -1
- package/build/clarity.js +6101 -6071
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +6101 -6071
- package/build/clarity.performance.js +1 -1
- package/package.json +70 -70
- package/rollup.config.ts +161 -161
- package/src/clarity.ts +65 -65
- package/src/core/api.ts +8 -8
- package/src/core/config.ts +29 -29
- package/src/core/copy.ts +3 -3
- package/src/core/dynamic.ts +13 -7
- package/src/core/event.ts +53 -53
- package/src/core/hash.ts +19 -19
- package/src/core/history.ts +71 -71
- package/src/core/index.ts +81 -81
- package/src/core/measure.ts +19 -19
- package/src/core/report.ts +28 -28
- package/src/core/scrub.ts +204 -202
- package/src/core/task.ts +181 -181
- package/src/core/throttle.ts +46 -46
- package/src/core/time.ts +26 -26
- package/src/core/timeout.ts +10 -10
- package/src/core/version.ts +2 -2
- package/src/data/baseline.ts +162 -162
- package/src/data/compress.ts +31 -31
- package/src/data/consent.ts +77 -77
- package/src/data/cookie.ts +90 -0
- package/src/data/custom.ts +23 -23
- package/src/data/dimension.ts +53 -53
- package/src/data/encode.ts +155 -155
- package/src/data/envelope.ts +53 -53
- package/src/data/extract.ts +211 -211
- package/src/data/index.ts +51 -50
- package/src/data/limit.ts +44 -44
- package/src/data/metadata.ts +321 -408
- package/src/data/metric.ts +51 -51
- package/src/data/ping.ts +36 -36
- package/src/data/signal.ts +30 -30
- package/src/data/summary.ts +34 -34
- package/src/data/token.ts +39 -39
- package/src/data/upgrade.ts +44 -44
- package/src/data/upload.ts +333 -333
- package/src/data/util.ts +18 -0
- package/src/data/variable.ts +83 -83
- package/src/diagnostic/encode.ts +40 -40
- package/src/diagnostic/fraud.ts +36 -36
- package/src/diagnostic/index.ts +13 -13
- package/src/diagnostic/internal.ts +28 -28
- package/src/diagnostic/script.ts +35 -35
- package/src/dynamic/agent/blank.ts +2 -2
- package/src/dynamic/agent/crisp.ts +40 -40
- package/src/dynamic/agent/encode.ts +25 -25
- package/src/dynamic/agent/index.ts +8 -8
- package/src/dynamic/agent/livechat.ts +58 -58
- package/src/dynamic/agent/tidio.ts +44 -44
- package/src/global.ts +6 -6
- package/src/index.ts +9 -9
- package/src/insight/blank.ts +14 -14
- package/src/insight/encode.ts +60 -60
- package/src/insight/snapshot.ts +114 -114
- package/src/interaction/change.ts +38 -38
- package/src/interaction/click.ts +173 -173
- package/src/interaction/clipboard.ts +32 -32
- package/src/interaction/encode.ts +210 -210
- package/src/interaction/index.ts +60 -60
- package/src/interaction/input.ts +57 -57
- package/src/interaction/pointer.ts +137 -137
- package/src/interaction/resize.ts +50 -50
- package/src/interaction/scroll.ts +129 -129
- package/src/interaction/selection.ts +66 -66
- package/src/interaction/submit.ts +30 -30
- package/src/interaction/timeline.ts +69 -69
- package/src/interaction/unload.ts +26 -26
- package/src/interaction/visibility.ts +27 -27
- package/src/layout/animation.ts +133 -133
- package/src/layout/custom.ts +42 -42
- package/src/layout/discover.ts +31 -31
- package/src/layout/document.ts +46 -46
- package/src/layout/dom.ts +439 -439
- package/src/layout/encode.ts +154 -154
- package/src/layout/index.ts +42 -42
- package/src/layout/mutation.ts +411 -411
- package/src/layout/node.ts +294 -294
- package/src/layout/offset.ts +19 -19
- package/src/layout/region.ts +151 -151
- package/src/layout/schema.ts +63 -63
- package/src/layout/selector.ts +82 -82
- package/src/layout/style.ts +159 -159
- package/src/layout/target.ts +32 -32
- package/src/layout/traverse.ts +27 -27
- package/src/performance/blank.ts +9 -9
- package/src/performance/encode.ts +31 -31
- package/src/performance/index.ts +12 -12
- package/src/performance/interaction.ts +125 -125
- package/src/performance/navigation.ts +31 -31
- package/src/performance/observer.ts +112 -112
- package/src/queue.ts +33 -33
- package/test/core.test.ts +139 -139
- package/test/helper.ts +162 -162
- package/test/html/core.html +27 -27
- package/test/stub.test.ts +7 -7
- package/test/tsconfig.test.json +5 -5
- package/tsconfig.json +21 -21
- package/tslint.json +32 -32
- package/types/agent.d.ts +39 -39
- package/types/core.d.ts +150 -150
- package/types/data.d.ts +572 -571
- package/types/diagnostic.d.ts +24 -24
- package/types/global.d.ts +30 -30
- package/types/index.d.ts +40 -40
- package/types/interaction.d.ts +177 -177
- package/types/layout.d.ts +276 -276
- package/types/performance.d.ts +31 -31
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
import { PerformanceEventTiming, Interaction } from '@clarity-types/data';
|
|
2
|
-
|
|
3
|
-
// Estimate variables to keep track of interactions
|
|
4
|
-
let interactionCountEstimate = 0;
|
|
5
|
-
let minKnownInteractionId = Infinity;
|
|
6
|
-
let maxKnownInteractionId = 0;
|
|
7
|
-
|
|
8
|
-
let prevInteractionCount = 0; // Used to track interaction count between pages
|
|
9
|
-
|
|
10
|
-
const MAX_INTERACTIONS_TO_CONSIDER = 10; // Maximum number of interactions we consider for INP
|
|
11
|
-
const DEFAULT_DURATION_THRESHOLD = 40; // Threshold to ignore very short interactions
|
|
12
|
-
|
|
13
|
-
// List to store the longest interaction events
|
|
14
|
-
const longestInteractionList: Interaction[] = [];
|
|
15
|
-
// Map to track interactions by their ID, ensuring we handle duplicates
|
|
16
|
-
const longestInteractionMap: Map<number, Interaction> = new Map();
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Update the approx number of interactions estimate count if the interactionCount is not supported.
|
|
20
|
-
* The difference between `maxKnownInteractionId` and `minKnownInteractionId` gives us a rough range of how many interactions have occurred.
|
|
21
|
-
* Dividing by 7 helps approximate the interaction count more accurately, since interaction IDs are spread out across a large range.
|
|
22
|
-
*/
|
|
23
|
-
const countInteractions = (entry: PerformanceEventTiming) => {
|
|
24
|
-
if ('interactionCount' in performance) {
|
|
25
|
-
interactionCountEstimate = performance.interactionCount as number;
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (entry.interactionId) {
|
|
30
|
-
minKnownInteractionId = Math.min(
|
|
31
|
-
minKnownInteractionId,
|
|
32
|
-
entry.interactionId
|
|
33
|
-
);
|
|
34
|
-
maxKnownInteractionId = Math.max(
|
|
35
|
-
maxKnownInteractionId,
|
|
36
|
-
entry.interactionId
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
interactionCountEstimate = maxKnownInteractionId
|
|
40
|
-
? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1
|
|
41
|
-
: 0;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const getInteractionCount = () => {
|
|
46
|
-
return interactionCountEstimate || 0;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const getInteractionCountForNavigation = () => {
|
|
50
|
-
return getInteractionCount() - prevInteractionCount;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Estimates the 98th percentile (P98) of the longest interactions by selecting
|
|
55
|
-
* the candidate interaction based on the current interaction count.
|
|
56
|
-
* Dividing by 50 is a heuristic to estimate the 98th percentile (P98) interaction.
|
|
57
|
-
* This assumes one out of every 50 interactions represents the P98 interaction.
|
|
58
|
-
* By dividing the total interaction count by 50, we get an index to approximate
|
|
59
|
-
* the slowest 2% of interactions, helping identify a likely P98 candidate.
|
|
60
|
-
*/
|
|
61
|
-
export const estimateP98LongestInteraction = () => {
|
|
62
|
-
if(!longestInteractionList.length){
|
|
63
|
-
return -1;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const candidateInteractionIndex = Math.min(
|
|
67
|
-
longestInteractionList.length - 1,
|
|
68
|
-
Math.floor(getInteractionCountForNavigation() / 50)
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
return longestInteractionList[candidateInteractionIndex].latency;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Resets the interaction tracking, usually called after navigation to a new page.
|
|
76
|
-
*/
|
|
77
|
-
export const resetInteractions = () => {
|
|
78
|
-
prevInteractionCount = getInteractionCount();
|
|
79
|
-
longestInteractionList.length = 0;
|
|
80
|
-
longestInteractionMap.clear();
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Processes a PerformanceEventTiming entry by updating the longest interaction list.
|
|
85
|
-
*/
|
|
86
|
-
export const processInteractionEntry = (entry: PerformanceEventTiming) => {
|
|
87
|
-
// Ignore entries with 0 interactionId or very short durations
|
|
88
|
-
if (!entry.interactionId || entry.duration < DEFAULT_DURATION_THRESHOLD) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
countInteractions(entry);
|
|
93
|
-
|
|
94
|
-
const minLongestInteraction =
|
|
95
|
-
longestInteractionList[longestInteractionList.length - 1];
|
|
96
|
-
|
|
97
|
-
const existingInteraction = longestInteractionMap.get(entry.interactionId!);
|
|
98
|
-
|
|
99
|
-
// Either update existing, add new, or replace shortest interaction if necessary
|
|
100
|
-
if (
|
|
101
|
-
existingInteraction ||
|
|
102
|
-
longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER ||
|
|
103
|
-
entry.duration > minLongestInteraction?.latency
|
|
104
|
-
) {
|
|
105
|
-
if (!existingInteraction) {
|
|
106
|
-
const interaction = {
|
|
107
|
-
id: entry.interactionId,
|
|
108
|
-
latency: entry.duration,
|
|
109
|
-
};
|
|
110
|
-
longestInteractionMap.set(interaction.id, interaction);
|
|
111
|
-
longestInteractionList.push(interaction);
|
|
112
|
-
} else if (entry.duration > existingInteraction.latency) {
|
|
113
|
-
existingInteraction.latency = entry.duration;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
longestInteractionList.sort((a, b) => b.latency - a.latency);
|
|
117
|
-
|
|
118
|
-
// Trim the list to the maximum number of interactions to consider
|
|
119
|
-
if (longestInteractionList.length > MAX_INTERACTIONS_TO_CONSIDER) {
|
|
120
|
-
longestInteractionList
|
|
121
|
-
.splice(MAX_INTERACTIONS_TO_CONSIDER)
|
|
122
|
-
.forEach((i) => longestInteractionMap.delete(i.id));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
1
|
+
import { PerformanceEventTiming, Interaction } from '@clarity-types/data';
|
|
2
|
+
|
|
3
|
+
// Estimate variables to keep track of interactions
|
|
4
|
+
let interactionCountEstimate = 0;
|
|
5
|
+
let minKnownInteractionId = Infinity;
|
|
6
|
+
let maxKnownInteractionId = 0;
|
|
7
|
+
|
|
8
|
+
let prevInteractionCount = 0; // Used to track interaction count between pages
|
|
9
|
+
|
|
10
|
+
const MAX_INTERACTIONS_TO_CONSIDER = 10; // Maximum number of interactions we consider for INP
|
|
11
|
+
const DEFAULT_DURATION_THRESHOLD = 40; // Threshold to ignore very short interactions
|
|
12
|
+
|
|
13
|
+
// List to store the longest interaction events
|
|
14
|
+
const longestInteractionList: Interaction[] = [];
|
|
15
|
+
// Map to track interactions by their ID, ensuring we handle duplicates
|
|
16
|
+
const longestInteractionMap: Map<number, Interaction> = new Map();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Update the approx number of interactions estimate count if the interactionCount is not supported.
|
|
20
|
+
* The difference between `maxKnownInteractionId` and `minKnownInteractionId` gives us a rough range of how many interactions have occurred.
|
|
21
|
+
* Dividing by 7 helps approximate the interaction count more accurately, since interaction IDs are spread out across a large range.
|
|
22
|
+
*/
|
|
23
|
+
const countInteractions = (entry: PerformanceEventTiming) => {
|
|
24
|
+
if ('interactionCount' in performance) {
|
|
25
|
+
interactionCountEstimate = performance.interactionCount as number;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (entry.interactionId) {
|
|
30
|
+
minKnownInteractionId = Math.min(
|
|
31
|
+
minKnownInteractionId,
|
|
32
|
+
entry.interactionId
|
|
33
|
+
);
|
|
34
|
+
maxKnownInteractionId = Math.max(
|
|
35
|
+
maxKnownInteractionId,
|
|
36
|
+
entry.interactionId
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
interactionCountEstimate = maxKnownInteractionId
|
|
40
|
+
? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1
|
|
41
|
+
: 0;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getInteractionCount = () => {
|
|
46
|
+
return interactionCountEstimate || 0;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getInteractionCountForNavigation = () => {
|
|
50
|
+
return getInteractionCount() - prevInteractionCount;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Estimates the 98th percentile (P98) of the longest interactions by selecting
|
|
55
|
+
* the candidate interaction based on the current interaction count.
|
|
56
|
+
* Dividing by 50 is a heuristic to estimate the 98th percentile (P98) interaction.
|
|
57
|
+
* This assumes one out of every 50 interactions represents the P98 interaction.
|
|
58
|
+
* By dividing the total interaction count by 50, we get an index to approximate
|
|
59
|
+
* the slowest 2% of interactions, helping identify a likely P98 candidate.
|
|
60
|
+
*/
|
|
61
|
+
export const estimateP98LongestInteraction = () => {
|
|
62
|
+
if(!longestInteractionList.length){
|
|
63
|
+
return -1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const candidateInteractionIndex = Math.min(
|
|
67
|
+
longestInteractionList.length - 1,
|
|
68
|
+
Math.floor(getInteractionCountForNavigation() / 50)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return longestInteractionList[candidateInteractionIndex].latency;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resets the interaction tracking, usually called after navigation to a new page.
|
|
76
|
+
*/
|
|
77
|
+
export const resetInteractions = () => {
|
|
78
|
+
prevInteractionCount = getInteractionCount();
|
|
79
|
+
longestInteractionList.length = 0;
|
|
80
|
+
longestInteractionMap.clear();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Processes a PerformanceEventTiming entry by updating the longest interaction list.
|
|
85
|
+
*/
|
|
86
|
+
export const processInteractionEntry = (entry: PerformanceEventTiming) => {
|
|
87
|
+
// Ignore entries with 0 interactionId or very short durations
|
|
88
|
+
if (!entry.interactionId || entry.duration < DEFAULT_DURATION_THRESHOLD) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
countInteractions(entry);
|
|
93
|
+
|
|
94
|
+
const minLongestInteraction =
|
|
95
|
+
longestInteractionList[longestInteractionList.length - 1];
|
|
96
|
+
|
|
97
|
+
const existingInteraction = longestInteractionMap.get(entry.interactionId!);
|
|
98
|
+
|
|
99
|
+
// Either update existing, add new, or replace shortest interaction if necessary
|
|
100
|
+
if (
|
|
101
|
+
existingInteraction ||
|
|
102
|
+
longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER ||
|
|
103
|
+
entry.duration > minLongestInteraction?.latency
|
|
104
|
+
) {
|
|
105
|
+
if (!existingInteraction) {
|
|
106
|
+
const interaction = {
|
|
107
|
+
id: entry.interactionId,
|
|
108
|
+
latency: entry.duration,
|
|
109
|
+
};
|
|
110
|
+
longestInteractionMap.set(interaction.id, interaction);
|
|
111
|
+
longestInteractionList.push(interaction);
|
|
112
|
+
} else if (entry.duration > existingInteraction.latency) {
|
|
113
|
+
existingInteraction.latency = entry.duration;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
longestInteractionList.sort((a, b) => b.latency - a.latency);
|
|
117
|
+
|
|
118
|
+
// Trim the list to the maximum number of interactions to consider
|
|
119
|
+
if (longestInteractionList.length > MAX_INTERACTIONS_TO_CONSIDER) {
|
|
120
|
+
longestInteractionList
|
|
121
|
+
.splice(MAX_INTERACTIONS_TO_CONSIDER)
|
|
122
|
+
.forEach((i) => longestInteractionMap.delete(i.id));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { Event } from "@clarity-types/data";
|
|
2
|
-
import { NavigationData } from "@clarity-types/performance";
|
|
3
|
-
import encode from "./encode";
|
|
4
|
-
|
|
5
|
-
export let data: NavigationData = null;
|
|
6
|
-
|
|
7
|
-
export function reset(): void {
|
|
8
|
-
data = null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function compute(entry: PerformanceNavigationTiming): void {
|
|
12
|
-
data = {
|
|
13
|
-
fetchStart: Math.round(entry.fetchStart),
|
|
14
|
-
connectStart: Math.round(entry.connectStart),
|
|
15
|
-
connectEnd: Math.round(entry.connectEnd),
|
|
16
|
-
requestStart: Math.round(entry.requestStart),
|
|
17
|
-
responseStart: Math.round(entry.responseStart),
|
|
18
|
-
responseEnd: Math.round(entry.responseEnd),
|
|
19
|
-
domInteractive: Math.round(entry.domInteractive),
|
|
20
|
-
domComplete: Math.round(entry.domComplete),
|
|
21
|
-
loadEventStart: Math.round(entry.loadEventStart),
|
|
22
|
-
loadEventEnd: Math.round(entry.loadEventEnd),
|
|
23
|
-
redirectCount: Math.round(entry.redirectCount),
|
|
24
|
-
size: entry.transferSize ? entry.transferSize : 0,
|
|
25
|
-
type: entry.type,
|
|
26
|
-
protocol: entry.nextHopProtocol,
|
|
27
|
-
encodedSize: entry.encodedBodySize ? entry.encodedBodySize : 0,
|
|
28
|
-
decodedSize: entry.decodedBodySize ? entry.decodedBodySize : 0
|
|
29
|
-
};
|
|
30
|
-
encode(Event.Navigation);
|
|
31
|
-
}
|
|
1
|
+
import { Event } from "@clarity-types/data";
|
|
2
|
+
import { NavigationData } from "@clarity-types/performance";
|
|
3
|
+
import encode from "./encode";
|
|
4
|
+
|
|
5
|
+
export let data: NavigationData = null;
|
|
6
|
+
|
|
7
|
+
export function reset(): void {
|
|
8
|
+
data = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function compute(entry: PerformanceNavigationTiming): void {
|
|
12
|
+
data = {
|
|
13
|
+
fetchStart: Math.round(entry.fetchStart),
|
|
14
|
+
connectStart: Math.round(entry.connectStart),
|
|
15
|
+
connectEnd: Math.round(entry.connectEnd),
|
|
16
|
+
requestStart: Math.round(entry.requestStart),
|
|
17
|
+
responseStart: Math.round(entry.responseStart),
|
|
18
|
+
responseEnd: Math.round(entry.responseEnd),
|
|
19
|
+
domInteractive: Math.round(entry.domInteractive),
|
|
20
|
+
domComplete: Math.round(entry.domComplete),
|
|
21
|
+
loadEventStart: Math.round(entry.loadEventStart),
|
|
22
|
+
loadEventEnd: Math.round(entry.loadEventEnd),
|
|
23
|
+
redirectCount: Math.round(entry.redirectCount),
|
|
24
|
+
size: entry.transferSize ? entry.transferSize : 0,
|
|
25
|
+
type: entry.type,
|
|
26
|
+
protocol: entry.nextHopProtocol,
|
|
27
|
+
encodedSize: entry.encodedBodySize ? entry.encodedBodySize : 0,
|
|
28
|
+
decodedSize: entry.decodedBodySize ? entry.decodedBodySize : 0
|
|
29
|
+
};
|
|
30
|
+
encode(Event.Navigation);
|
|
31
|
+
}
|
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
import { Code, Constant, Dimension, Metric, Severity, PerformanceEventTiming } from "@clarity-types/data";
|
|
2
|
-
import config from "@src/core/config";
|
|
3
|
-
import { bind } from "@src/core/event";
|
|
4
|
-
import measure from "@src/core/measure";
|
|
5
|
-
import { setTimeout } from "@src/core/timeout";
|
|
6
|
-
import * as dimension from "@src/data/dimension";
|
|
7
|
-
import * as metric from "@src/data/metric";
|
|
8
|
-
import * as internal from "@src/diagnostic/internal";
|
|
9
|
-
import * as navigation from "@src/performance/navigation";
|
|
10
|
-
import * as interaction from "@src/performance/interaction";
|
|
11
|
-
|
|
12
|
-
let observer: PerformanceObserver;
|
|
13
|
-
const types: string[] = [Constant.Navigation, Constant.Resource, Constant.LongTask, Constant.FID, Constant.CLS, Constant.LCP, Constant.PerformanceEventTiming];
|
|
14
|
-
|
|
15
|
-
export function start(): void {
|
|
16
|
-
// Capture connection properties, if available
|
|
17
|
-
if (navigator && navigator["connection"]) {
|
|
18
|
-
dimension.log(Dimension.ConnectionType, navigator["connection"]["effectiveType"]);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Check the browser support performance observer as a pre-requisite for any performance measurement
|
|
22
|
-
if (window["PerformanceObserver"] && PerformanceObserver.supportedEntryTypes) {
|
|
23
|
-
// Start monitoring performance data after page has finished loading.
|
|
24
|
-
// If the document.readyState is not yet complete, we intentionally call observe using a setTimeout.
|
|
25
|
-
// This allows us to capture loadEventEnd on navigation timeline.
|
|
26
|
-
if (document.readyState !== "complete") {
|
|
27
|
-
bind(window, "load", setTimeout.bind(this, observe, 0));
|
|
28
|
-
} else { observe(); }
|
|
29
|
-
} else { internal.log(Code.PerformanceObserver, Severity.Info); }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function observe(): void {
|
|
33
|
-
// Some browsers will throw an error for unsupported entryType, e.g. "layout-shift"
|
|
34
|
-
// In those cases, we log it as a warning and continue with rest of the Clarity processing
|
|
35
|
-
try {
|
|
36
|
-
if (observer) { observer.disconnect(); }
|
|
37
|
-
observer = new PerformanceObserver(measure(handle) as PerformanceObserverCallback);
|
|
38
|
-
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver/observe
|
|
39
|
-
// "buffered" flag indicates whether buffered entries should be queued into the observer's buffer.
|
|
40
|
-
// It must only be used only with the "type" option, and cannot be used with entryTypes.
|
|
41
|
-
// This is why we need to individually "observe" each supported type
|
|
42
|
-
for (let x of types) {
|
|
43
|
-
if (PerformanceObserver.supportedEntryTypes.indexOf(x) >= 0) {
|
|
44
|
-
// Initialize CLS with a value of zero. It's possible (and recommended) for sites to not have any cumulative layout shift.
|
|
45
|
-
// In those cases, we want to still initialize the metric in Clarity
|
|
46
|
-
if (x === Constant.CLS) { metric.sum(Metric.CumulativeLayoutShift, 0); }
|
|
47
|
-
observer.observe({type: x, buffered: true});
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
} catch { internal.log(Code.PerformanceObserver, Severity.Warning); }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function handle(entries: PerformanceObserverEntryList): void {
|
|
54
|
-
process(entries.getEntries());
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function process(entries: PerformanceEntryList): void {
|
|
58
|
-
let visible = "visibilityState" in document ? document.visibilityState === "visible" : true;
|
|
59
|
-
for (let i = 0; i < entries.length; i++) {
|
|
60
|
-
let entry = entries[i];
|
|
61
|
-
switch (entry.entryType) {
|
|
62
|
-
case Constant.Navigation:
|
|
63
|
-
navigation.compute(entry as PerformanceNavigationTiming);
|
|
64
|
-
break;
|
|
65
|
-
case Constant.Resource:
|
|
66
|
-
let name = entry.name;
|
|
67
|
-
dimension.log(Dimension.NetworkHosts, host(name));
|
|
68
|
-
if (name === config.upload || name === config.fallback) { metric.max(Metric.UploadTime, entry.duration); }
|
|
69
|
-
break;
|
|
70
|
-
case Constant.LongTask:
|
|
71
|
-
metric.count(Metric.LongTaskCount);
|
|
72
|
-
break;
|
|
73
|
-
case Constant.FID:
|
|
74
|
-
if (visible) { metric.max(Metric.FirstInputDelay, entry["processingStart"] - entry.startTime); }
|
|
75
|
-
break;
|
|
76
|
-
case Constant.PerformanceEventTiming:
|
|
77
|
-
if (visible && 'PerformanceEventTiming' in window && 'interactionId' in PerformanceEventTiming.prototype)
|
|
78
|
-
{
|
|
79
|
-
interaction.processInteractionEntry(entry as PerformanceEventTiming);
|
|
80
|
-
// Logging it as dimension because we're always looking for the last value.
|
|
81
|
-
dimension.log(Dimension.InteractionNextPaint, interaction.estimateP98LongestInteraction().toString());
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
84
|
-
case Constant.CLS:
|
|
85
|
-
// Scale the value to avoid sending back floating point number
|
|
86
|
-
if (visible && !entry["hadRecentInput"]) { metric.sum(Metric.CumulativeLayoutShift, entry["value"] * 1000); }
|
|
87
|
-
break;
|
|
88
|
-
case Constant.LCP:
|
|
89
|
-
if (visible) { metric.max(Metric.LargestPaint, entry.startTime); }
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function stop(): void {
|
|
96
|
-
if (observer) { observer.disconnect(); }
|
|
97
|
-
observer = null;
|
|
98
|
-
interaction.resetInteractions();
|
|
99
|
-
anchorCache = null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Cached anchor element for optimal performance & memory management
|
|
103
|
-
let anchorCache: HTMLAnchorElement | null = null;
|
|
104
|
-
|
|
105
|
-
function host(url: string): string {
|
|
106
|
-
if (!anchorCache) {
|
|
107
|
-
anchorCache = document.createElement("a");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
anchorCache.href = url;
|
|
111
|
-
return anchorCache.host;
|
|
112
|
-
}
|
|
1
|
+
import { Code, Constant, Dimension, Metric, Severity, PerformanceEventTiming } from "@clarity-types/data";
|
|
2
|
+
import config from "@src/core/config";
|
|
3
|
+
import { bind } from "@src/core/event";
|
|
4
|
+
import measure from "@src/core/measure";
|
|
5
|
+
import { setTimeout } from "@src/core/timeout";
|
|
6
|
+
import * as dimension from "@src/data/dimension";
|
|
7
|
+
import * as metric from "@src/data/metric";
|
|
8
|
+
import * as internal from "@src/diagnostic/internal";
|
|
9
|
+
import * as navigation from "@src/performance/navigation";
|
|
10
|
+
import * as interaction from "@src/performance/interaction";
|
|
11
|
+
|
|
12
|
+
let observer: PerformanceObserver;
|
|
13
|
+
const types: string[] = [Constant.Navigation, Constant.Resource, Constant.LongTask, Constant.FID, Constant.CLS, Constant.LCP, Constant.PerformanceEventTiming];
|
|
14
|
+
|
|
15
|
+
export function start(): void {
|
|
16
|
+
// Capture connection properties, if available
|
|
17
|
+
if (navigator && navigator["connection"]) {
|
|
18
|
+
dimension.log(Dimension.ConnectionType, navigator["connection"]["effectiveType"]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Check the browser support performance observer as a pre-requisite for any performance measurement
|
|
22
|
+
if (window["PerformanceObserver"] && PerformanceObserver.supportedEntryTypes) {
|
|
23
|
+
// Start monitoring performance data after page has finished loading.
|
|
24
|
+
// If the document.readyState is not yet complete, we intentionally call observe using a setTimeout.
|
|
25
|
+
// This allows us to capture loadEventEnd on navigation timeline.
|
|
26
|
+
if (document.readyState !== "complete") {
|
|
27
|
+
bind(window, "load", setTimeout.bind(this, observe, 0));
|
|
28
|
+
} else { observe(); }
|
|
29
|
+
} else { internal.log(Code.PerformanceObserver, Severity.Info); }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function observe(): void {
|
|
33
|
+
// Some browsers will throw an error for unsupported entryType, e.g. "layout-shift"
|
|
34
|
+
// In those cases, we log it as a warning and continue with rest of the Clarity processing
|
|
35
|
+
try {
|
|
36
|
+
if (observer) { observer.disconnect(); }
|
|
37
|
+
observer = new PerformanceObserver(measure(handle) as PerformanceObserverCallback);
|
|
38
|
+
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver/observe
|
|
39
|
+
// "buffered" flag indicates whether buffered entries should be queued into the observer's buffer.
|
|
40
|
+
// It must only be used only with the "type" option, and cannot be used with entryTypes.
|
|
41
|
+
// This is why we need to individually "observe" each supported type
|
|
42
|
+
for (let x of types) {
|
|
43
|
+
if (PerformanceObserver.supportedEntryTypes.indexOf(x) >= 0) {
|
|
44
|
+
// Initialize CLS with a value of zero. It's possible (and recommended) for sites to not have any cumulative layout shift.
|
|
45
|
+
// In those cases, we want to still initialize the metric in Clarity
|
|
46
|
+
if (x === Constant.CLS) { metric.sum(Metric.CumulativeLayoutShift, 0); }
|
|
47
|
+
observer.observe({type: x, buffered: true});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch { internal.log(Code.PerformanceObserver, Severity.Warning); }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handle(entries: PerformanceObserverEntryList): void {
|
|
54
|
+
process(entries.getEntries());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function process(entries: PerformanceEntryList): void {
|
|
58
|
+
let visible = "visibilityState" in document ? document.visibilityState === "visible" : true;
|
|
59
|
+
for (let i = 0; i < entries.length; i++) {
|
|
60
|
+
let entry = entries[i];
|
|
61
|
+
switch (entry.entryType) {
|
|
62
|
+
case Constant.Navigation:
|
|
63
|
+
navigation.compute(entry as PerformanceNavigationTiming);
|
|
64
|
+
break;
|
|
65
|
+
case Constant.Resource:
|
|
66
|
+
let name = entry.name;
|
|
67
|
+
dimension.log(Dimension.NetworkHosts, host(name));
|
|
68
|
+
if (name === config.upload || name === config.fallback) { metric.max(Metric.UploadTime, entry.duration); }
|
|
69
|
+
break;
|
|
70
|
+
case Constant.LongTask:
|
|
71
|
+
metric.count(Metric.LongTaskCount);
|
|
72
|
+
break;
|
|
73
|
+
case Constant.FID:
|
|
74
|
+
if (visible) { metric.max(Metric.FirstInputDelay, entry["processingStart"] - entry.startTime); }
|
|
75
|
+
break;
|
|
76
|
+
case Constant.PerformanceEventTiming:
|
|
77
|
+
if (visible && 'PerformanceEventTiming' in window && 'interactionId' in PerformanceEventTiming.prototype)
|
|
78
|
+
{
|
|
79
|
+
interaction.processInteractionEntry(entry as PerformanceEventTiming);
|
|
80
|
+
// Logging it as dimension because we're always looking for the last value.
|
|
81
|
+
dimension.log(Dimension.InteractionNextPaint, interaction.estimateP98LongestInteraction().toString());
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case Constant.CLS:
|
|
85
|
+
// Scale the value to avoid sending back floating point number
|
|
86
|
+
if (visible && !entry["hadRecentInput"]) { metric.sum(Metric.CumulativeLayoutShift, entry["value"] * 1000); }
|
|
87
|
+
break;
|
|
88
|
+
case Constant.LCP:
|
|
89
|
+
if (visible) { metric.max(Metric.LargestPaint, entry.startTime); }
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function stop(): void {
|
|
96
|
+
if (observer) { observer.disconnect(); }
|
|
97
|
+
observer = null;
|
|
98
|
+
interaction.resetInteractions();
|
|
99
|
+
anchorCache = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Cached anchor element for optimal performance & memory management
|
|
103
|
+
let anchorCache: HTMLAnchorElement | null = null;
|
|
104
|
+
|
|
105
|
+
function host(url: string): string {
|
|
106
|
+
if (!anchorCache) {
|
|
107
|
+
anchorCache = document.createElement("a");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
anchorCache.href = url;
|
|
111
|
+
return anchorCache.host;
|
|
112
|
+
}
|
package/src/queue.ts
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { Constant } from "@clarity-types/data";
|
|
2
|
-
import * as clarity from "@src/clarity";
|
|
3
|
-
|
|
4
|
-
const w = window;
|
|
5
|
-
const c = Constant.Clarity;
|
|
6
|
-
|
|
7
|
-
export function setup() {
|
|
8
|
-
// Start queuing up calls while Clarity is inactive and we are in a browser enviornment
|
|
9
|
-
if (typeof w !== "undefined") {
|
|
10
|
-
w[c] = function() {
|
|
11
|
-
(w[c].q = w[c].q || []).push(arguments);
|
|
12
|
-
// if the start function was called, don't queue it and instead process the queue
|
|
13
|
-
arguments[0] === "start" && w[c].q.unshift(w[c].q.pop()) && process();
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function process() {
|
|
19
|
-
if (typeof w !== "undefined") {
|
|
20
|
-
// Do not execute or reset global "clarity" variable if a version of Clarity is already running on the page
|
|
21
|
-
if (w[c] && w[c].v) { return console.warn("Error CL001: Multiple Clarity tags detected."); }
|
|
22
|
-
|
|
23
|
-
// Expose clarity in a browser environment
|
|
24
|
-
// To be efficient about queuing up operations while Clarity is wiring up, we expose clarity.*(args) => clarity(*, args);
|
|
25
|
-
// This allows us to reprocess any calls that we missed once Clarity is available on the page
|
|
26
|
-
// Once Clarity script bundle is loaded on the page, we also initialize a "v" property that holds current version
|
|
27
|
-
// We use the presence or absence of "v" to determine if we are attempting to run a duplicate instance
|
|
28
|
-
let queue = w[c] ? (w[c].q || []) : [];
|
|
29
|
-
w[c] = function(method: string, ...args: any[]): void { return clarity[method](...args); }
|
|
30
|
-
w[c].v = clarity.version;
|
|
31
|
-
while (queue.length > 0) { w[c](...queue.shift()); }
|
|
32
|
-
}
|
|
33
|
-
}
|
|
1
|
+
import { Constant } from "@clarity-types/data";
|
|
2
|
+
import * as clarity from "@src/clarity";
|
|
3
|
+
|
|
4
|
+
const w = window;
|
|
5
|
+
const c = Constant.Clarity;
|
|
6
|
+
|
|
7
|
+
export function setup() {
|
|
8
|
+
// Start queuing up calls while Clarity is inactive and we are in a browser enviornment
|
|
9
|
+
if (typeof w !== "undefined") {
|
|
10
|
+
w[c] = function() {
|
|
11
|
+
(w[c].q = w[c].q || []).push(arguments);
|
|
12
|
+
// if the start function was called, don't queue it and instead process the queue
|
|
13
|
+
arguments[0] === "start" && w[c].q.unshift(w[c].q.pop()) && process();
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function process() {
|
|
19
|
+
if (typeof w !== "undefined") {
|
|
20
|
+
// Do not execute or reset global "clarity" variable if a version of Clarity is already running on the page
|
|
21
|
+
if (w[c] && w[c].v) { return console.warn("Error CL001: Multiple Clarity tags detected."); }
|
|
22
|
+
|
|
23
|
+
// Expose clarity in a browser environment
|
|
24
|
+
// To be efficient about queuing up operations while Clarity is wiring up, we expose clarity.*(args) => clarity(*, args);
|
|
25
|
+
// This allows us to reprocess any calls that we missed once Clarity is available on the page
|
|
26
|
+
// Once Clarity script bundle is loaded on the page, we also initialize a "v" property that holds current version
|
|
27
|
+
// We use the presence or absence of "v" to determine if we are attempting to run a duplicate instance
|
|
28
|
+
let queue = w[c] ? (w[c].q || []) : [];
|
|
29
|
+
w[c] = function(method: string, ...args: any[]): void { return clarity[method](...args); }
|
|
30
|
+
w[c].v = clarity.version;
|
|
31
|
+
while (queue.length > 0) { w[c](...queue.shift()); }
|
|
32
|
+
}
|
|
33
|
+
}
|