clarity-js 0.8.42 → 0.8.43

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.
Files changed (114) hide show
  1. package/README.md +26 -26
  2. package/build/clarity.extended.js +1 -1
  3. package/build/clarity.insight.js +1 -1
  4. package/build/clarity.js +6043 -6030
  5. package/build/clarity.min.js +1 -1
  6. package/build/clarity.module.js +6043 -6030
  7. package/build/clarity.performance.js +1 -1
  8. package/package.json +70 -70
  9. package/rollup.config.ts +161 -161
  10. package/src/clarity.ts +65 -65
  11. package/src/core/api.ts +8 -8
  12. package/src/core/config.ts +29 -29
  13. package/src/core/copy.ts +3 -3
  14. package/src/core/dynamic.ts +13 -7
  15. package/src/core/event.ts +53 -53
  16. package/src/core/hash.ts +19 -19
  17. package/src/core/history.ts +71 -71
  18. package/src/core/index.ts +81 -81
  19. package/src/core/measure.ts +19 -19
  20. package/src/core/report.ts +28 -28
  21. package/src/core/scrub.ts +204 -202
  22. package/src/core/task.ts +181 -181
  23. package/src/core/throttle.ts +46 -46
  24. package/src/core/time.ts +26 -26
  25. package/src/core/timeout.ts +10 -10
  26. package/src/core/version.ts +2 -2
  27. package/src/data/baseline.ts +162 -162
  28. package/src/data/compress.ts +31 -31
  29. package/src/data/consent.ts +77 -77
  30. package/src/data/custom.ts +23 -23
  31. package/src/data/dimension.ts +53 -53
  32. package/src/data/encode.ts +155 -155
  33. package/src/data/envelope.ts +53 -53
  34. package/src/data/extract.ts +211 -211
  35. package/src/data/index.ts +50 -50
  36. package/src/data/limit.ts +44 -44
  37. package/src/data/metadata.ts +411 -408
  38. package/src/data/metric.ts +51 -51
  39. package/src/data/ping.ts +36 -36
  40. package/src/data/signal.ts +30 -30
  41. package/src/data/summary.ts +34 -34
  42. package/src/data/token.ts +39 -39
  43. package/src/data/upgrade.ts +44 -44
  44. package/src/data/upload.ts +333 -333
  45. package/src/data/variable.ts +83 -83
  46. package/src/diagnostic/encode.ts +40 -40
  47. package/src/diagnostic/fraud.ts +36 -36
  48. package/src/diagnostic/index.ts +13 -13
  49. package/src/diagnostic/internal.ts +28 -28
  50. package/src/diagnostic/script.ts +35 -35
  51. package/src/dynamic/agent/blank.ts +2 -2
  52. package/src/dynamic/agent/crisp.ts +40 -40
  53. package/src/dynamic/agent/encode.ts +25 -25
  54. package/src/dynamic/agent/index.ts +8 -8
  55. package/src/dynamic/agent/livechat.ts +58 -58
  56. package/src/dynamic/agent/tidio.ts +44 -44
  57. package/src/global.ts +6 -6
  58. package/src/index.ts +9 -9
  59. package/src/insight/blank.ts +14 -14
  60. package/src/insight/encode.ts +60 -60
  61. package/src/insight/snapshot.ts +114 -114
  62. package/src/interaction/change.ts +38 -38
  63. package/src/interaction/click.ts +173 -173
  64. package/src/interaction/clipboard.ts +32 -32
  65. package/src/interaction/encode.ts +210 -210
  66. package/src/interaction/index.ts +60 -60
  67. package/src/interaction/input.ts +57 -57
  68. package/src/interaction/pointer.ts +137 -137
  69. package/src/interaction/resize.ts +50 -50
  70. package/src/interaction/scroll.ts +129 -129
  71. package/src/interaction/selection.ts +66 -66
  72. package/src/interaction/submit.ts +30 -30
  73. package/src/interaction/timeline.ts +69 -69
  74. package/src/interaction/unload.ts +26 -26
  75. package/src/interaction/visibility.ts +27 -27
  76. package/src/layout/animation.ts +133 -133
  77. package/src/layout/custom.ts +42 -42
  78. package/src/layout/discover.ts +31 -31
  79. package/src/layout/document.ts +46 -46
  80. package/src/layout/dom.ts +439 -439
  81. package/src/layout/encode.ts +154 -154
  82. package/src/layout/index.ts +42 -42
  83. package/src/layout/mutation.ts +411 -411
  84. package/src/layout/node.ts +294 -294
  85. package/src/layout/offset.ts +19 -19
  86. package/src/layout/region.ts +151 -151
  87. package/src/layout/schema.ts +63 -63
  88. package/src/layout/selector.ts +82 -82
  89. package/src/layout/style.ts +159 -159
  90. package/src/layout/target.ts +32 -32
  91. package/src/layout/traverse.ts +27 -27
  92. package/src/performance/blank.ts +9 -9
  93. package/src/performance/encode.ts +31 -31
  94. package/src/performance/index.ts +12 -12
  95. package/src/performance/interaction.ts +125 -125
  96. package/src/performance/navigation.ts +31 -31
  97. package/src/performance/observer.ts +112 -112
  98. package/src/queue.ts +33 -33
  99. package/test/core.test.ts +139 -139
  100. package/test/helper.ts +162 -162
  101. package/test/html/core.html +27 -27
  102. package/test/stub.test.ts +7 -7
  103. package/test/tsconfig.test.json +5 -5
  104. package/tsconfig.json +21 -21
  105. package/tslint.json +32 -32
  106. package/types/agent.d.ts +39 -39
  107. package/types/core.d.ts +150 -150
  108. package/types/data.d.ts +572 -571
  109. package/types/diagnostic.d.ts +24 -24
  110. package/types/global.d.ts +30 -30
  111. package/types/index.d.ts +40 -40
  112. package/types/interaction.d.ts +177 -177
  113. package/types/layout.d.ts +276 -276
  114. 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
+ }