clarity-js 0.8.9 → 0.8.10
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/.lintstagedrc.yml +3 -0
- package/biome.json +43 -0
- package/build/clarity.extended.js +1 -1
- package/build/clarity.insight.js +1 -1
- package/build/clarity.js +3095 -2876
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +3095 -2876
- package/build/clarity.performance.js +1 -1
- package/package.json +17 -10
- package/rollup.config.ts +84 -88
- package/src/clarity.ts +34 -28
- package/src/core/config.ts +2 -2
- package/src/core/event.ts +36 -32
- package/src/core/hash.ts +5 -6
- package/src/core/history.ts +10 -11
- package/src/core/index.ts +21 -11
- package/src/core/measure.ts +9 -5
- package/src/core/report.ts +9 -5
- package/src/core/scrub.ts +29 -20
- package/src/core/task.ts +73 -45
- package/src/core/time.ts +3 -3
- package/src/core/timeout.ts +2 -2
- package/src/core/version.ts +1 -1
- package/src/data/baseline.ts +60 -55
- package/src/data/consent.ts +2 -2
- package/src/data/custom.ts +8 -13
- package/src/data/dimension.ts +11 -7
- package/src/data/encode.ts +36 -30
- package/src/data/envelope.ts +38 -38
- package/src/data/extract.ts +86 -77
- package/src/data/index.ts +10 -6
- package/src/data/limit.ts +1 -1
- package/src/data/metadata.ts +305 -266
- package/src/data/metric.ts +18 -8
- package/src/data/ping.ts +8 -4
- package/src/data/signal.ts +18 -18
- package/src/data/summary.ts +6 -4
- package/src/data/token.ts +10 -8
- package/src/data/upgrade.ts +7 -3
- package/src/data/upload.ts +100 -49
- package/src/data/variable.ts +27 -20
- package/src/diagnostic/encode.ts +2 -2
- package/src/diagnostic/fraud.ts +3 -4
- package/src/diagnostic/internal.ts +11 -5
- package/src/diagnostic/script.ts +12 -8
- package/src/global.ts +1 -1
- package/src/insight/blank.ts +4 -4
- package/src/insight/encode.ts +23 -17
- package/src/insight/snapshot.ts +57 -37
- package/src/interaction/change.ts +9 -6
- package/src/interaction/click.ts +34 -28
- package/src/interaction/clipboard.ts +2 -2
- package/src/interaction/encode.ts +35 -31
- package/src/interaction/input.ts +11 -9
- package/src/interaction/pointer.ts +41 -30
- package/src/interaction/resize.ts +5 -5
- package/src/interaction/scroll.ts +20 -17
- package/src/interaction/selection.ts +12 -8
- package/src/interaction/submit.ts +2 -2
- package/src/interaction/timeline.ts +13 -9
- package/src/interaction/unload.ts +1 -1
- package/src/interaction/visibility.ts +2 -2
- package/src/layout/animation.ts +47 -41
- package/src/layout/discover.ts +5 -5
- package/src/layout/document.ts +31 -19
- package/src/layout/dom.ts +141 -91
- package/src/layout/encode.ts +52 -37
- package/src/layout/mutation.ts +321 -318
- package/src/layout/node.ts +104 -81
- package/src/layout/offset.ts +7 -6
- package/src/layout/region.ts +66 -43
- package/src/layout/schema.ts +15 -8
- package/src/layout/selector.ts +47 -25
- package/src/layout/style.ts +45 -37
- package/src/layout/target.ts +14 -10
- package/src/layout/traverse.ts +17 -11
- package/src/performance/blank.ts +1 -1
- package/src/performance/encode.ts +4 -4
- package/src/performance/interaction.ts +58 -70
- package/src/performance/navigation.ts +2 -2
- package/src/performance/observer.ts +59 -26
- package/src/queue.ts +16 -9
- package/tsconfig.json +1 -1
- package/tslint.json +25 -32
- package/types/core.d.ts +13 -13
- package/types/data.d.ts +32 -29
- package/types/diagnostic.d.ts +1 -1
- package/types/interaction.d.ts +5 -4
- package/types/layout.d.ts +36 -21
- package/types/performance.d.ts +6 -5
package/src/core/history.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { BooleanFlag, Code, Constant, Metric, Setting, Severity } from "@clarity-types/data";
|
|
2
2
|
import { FunctionNames } from "@clarity-types/performance";
|
|
3
3
|
import * as clarity from "@src/clarity";
|
|
4
|
-
import * as core from "@src/core"
|
|
4
|
+
import * as core from "@src/core";
|
|
5
5
|
import { bind } from "@src/core/event";
|
|
6
|
-
import * as internal from "@src/diagnostic/internal";
|
|
7
6
|
import * as metric from "@src/data/metric";
|
|
7
|
+
import * as internal from "@src/diagnostic/internal";
|
|
8
8
|
|
|
9
9
|
let pushState = null;
|
|
10
10
|
let replaceState = null;
|
|
@@ -17,10 +17,10 @@ export function start(): void {
|
|
|
17
17
|
bind(window, "popstate", compute);
|
|
18
18
|
|
|
19
19
|
// Add a proxy to history.pushState function
|
|
20
|
-
if (pushState === null) {
|
|
21
|
-
pushState = history.pushState;
|
|
22
|
-
history.pushState = function(): void {
|
|
23
|
-
pushState.apply(this,
|
|
20
|
+
if (pushState === null) {
|
|
21
|
+
pushState = history.pushState;
|
|
22
|
+
history.pushState = function (...args): void {
|
|
23
|
+
pushState.apply(this, args);
|
|
24
24
|
if (core.active() && check()) {
|
|
25
25
|
compute();
|
|
26
26
|
}
|
|
@@ -28,11 +28,10 @@ export function start(): void {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// Add a proxy to history.replaceState function
|
|
31
|
-
if (replaceState === null)
|
|
32
|
-
|
|
33
|
-
replaceState =
|
|
34
|
-
|
|
35
|
-
replaceState.apply(this, arguments);
|
|
31
|
+
if (replaceState === null) {
|
|
32
|
+
replaceState = history.replaceState;
|
|
33
|
+
history.replaceState = function (...args): void {
|
|
34
|
+
replaceState.apply(this, args);
|
|
36
35
|
if (core.active() && check()) {
|
|
37
36
|
compute();
|
|
38
37
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { Config } from "@clarity-types/core";
|
|
1
|
+
import type { Config } from "@clarity-types/core";
|
|
2
2
|
import { Constant } from "@clarity-types/data";
|
|
3
3
|
import { FunctionNames } from "@clarity-types/performance";
|
|
4
|
+
import * as clarity from "@src/clarity";
|
|
4
5
|
import configuration from "@src/core/config";
|
|
5
6
|
import * as event from "@src/core/event";
|
|
6
7
|
import * as history from "@src/core/history";
|
|
7
8
|
import * as report from "@src/core/report";
|
|
8
9
|
import * as task from "@src/core/task";
|
|
9
10
|
import * as time from "@src/core/time";
|
|
10
|
-
import * as clarity from "@src/clarity";
|
|
11
11
|
import * as custom from "@src/data/custom";
|
|
12
12
|
|
|
13
13
|
let status = false;
|
|
@@ -36,15 +36,17 @@ export function active(): boolean {
|
|
|
36
36
|
|
|
37
37
|
export function check(): boolean {
|
|
38
38
|
try {
|
|
39
|
-
|
|
40
|
-
return
|
|
39
|
+
const globalPrivacyControlSet = navigator && "globalPrivacyControl" in navigator && navigator.globalPrivacyControl === true;
|
|
40
|
+
return (
|
|
41
|
+
status === false &&
|
|
41
42
|
typeof Promise !== "undefined" &&
|
|
42
|
-
window
|
|
43
|
-
document
|
|
43
|
+
window.MutationObserver &&
|
|
44
|
+
document.createTreeWalker &&
|
|
44
45
|
"now" in Date &&
|
|
45
46
|
"now" in performance &&
|
|
46
47
|
typeof WeakMap !== "undefined" &&
|
|
47
48
|
!globalPrivacyControlSet
|
|
49
|
+
);
|
|
48
50
|
} catch (ex) {
|
|
49
51
|
return false;
|
|
50
52
|
}
|
|
@@ -52,9 +54,13 @@ export function check(): boolean {
|
|
|
52
54
|
|
|
53
55
|
export function config(override: Config): boolean {
|
|
54
56
|
// Process custom configuration overrides, if available
|
|
55
|
-
if (override === null || status) {
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
if (override === null || status) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
for (const key in override) {
|
|
61
|
+
if (key in configuration) {
|
|
62
|
+
configuration[key] = override[key];
|
|
63
|
+
}
|
|
58
64
|
}
|
|
59
65
|
return true;
|
|
60
66
|
}
|
|
@@ -71,8 +77,12 @@ export function suspend(): void {
|
|
|
71
77
|
if (status) {
|
|
72
78
|
custom.event(Constant.Clarity, Constant.Suspend);
|
|
73
79
|
clarity.stop();
|
|
74
|
-
["mousemove", "touchstart"]
|
|
75
|
-
|
|
80
|
+
for (const x of ["mousemove", "touchstart"]) {
|
|
81
|
+
event.bind(document, x, restart);
|
|
82
|
+
}
|
|
83
|
+
for (const x of ["resize", "scroll", "pageshow"]) {
|
|
84
|
+
event.bind(window, x, restart);
|
|
85
|
+
}
|
|
76
86
|
}
|
|
77
87
|
}
|
|
78
88
|
|
package/src/core/measure.ts
CHANGED
|
@@ -3,12 +3,16 @@ import { report } from "@src/core/report";
|
|
|
3
3
|
import * as metric from "@src/data/metric";
|
|
4
4
|
import * as internal from "@src/diagnostic/internal";
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// biome-ignore lint/complexity/noBannedTypes: specifically looking to instrument function calls
|
|
7
7
|
export default function (method: Function): Function {
|
|
8
|
-
return function (): void {
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
|
|
8
|
+
return function (...args): void {
|
|
9
|
+
const start = performance.now();
|
|
10
|
+
try {
|
|
11
|
+
method.apply(this, args);
|
|
12
|
+
} catch (ex) {
|
|
13
|
+
throw report(ex);
|
|
14
|
+
}
|
|
15
|
+
const duration = performance.now() - start;
|
|
12
16
|
metric.sum(Metric.TotalCost, duration);
|
|
13
17
|
if (duration > Setting.LongTask) {
|
|
14
18
|
metric.count(Metric.LongTaskCount);
|
package/src/core/report.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Report } from "@clarity-types/core";
|
|
1
|
+
import type { Report } from "@clarity-types/core";
|
|
2
2
|
import config from "@src/core/config";
|
|
3
3
|
import { data } from "@src/data/envelope";
|
|
4
4
|
|
|
@@ -13,12 +13,16 @@ export function report(e: Error): Error {
|
|
|
13
13
|
if (history && history.indexOf(e.message) === -1) {
|
|
14
14
|
const url = config.report;
|
|
15
15
|
if (url && url.length > 0) {
|
|
16
|
-
|
|
17
|
-
if (e.message) {
|
|
18
|
-
|
|
16
|
+
const payload: Report = { v: data.version, p: data.projectId, u: data.userId, s: data.sessionId, n: data.pageNum };
|
|
17
|
+
if (e.message) {
|
|
18
|
+
payload.m = e.message;
|
|
19
|
+
}
|
|
20
|
+
if (e.stack) {
|
|
21
|
+
payload.e = e.stack;
|
|
22
|
+
}
|
|
19
23
|
// Using POST request instead of a GET request (img-src) to not violate existing CSP rules
|
|
20
24
|
// Since, Clarity already uses XHR to upload data, we stick with similar POST mechanism for reporting too
|
|
21
|
-
|
|
25
|
+
const xhr = new XMLHttpRequest();
|
|
22
26
|
xhr.open("POST", url, true);
|
|
23
27
|
xhr.send(JSON.stringify(payload));
|
|
24
28
|
history.push(e.message);
|
package/src/core/scrub.ts
CHANGED
|
@@ -10,9 +10,9 @@ let digitRegex = null;
|
|
|
10
10
|
let letterRegex = null;
|
|
11
11
|
let currencyRegex = null;
|
|
12
12
|
|
|
13
|
-
export function text(value: string, hint: string, privacy: Privacy, mangle
|
|
13
|
+
export function text(value: string, hint: string, privacy: Privacy, mangle = false, type?: string): string {
|
|
14
14
|
if (value) {
|
|
15
|
-
if (hint
|
|
15
|
+
if (hint === "input" && (type === "checkbox" || type === "radio")) {
|
|
16
16
|
return value;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -88,17 +88,21 @@ export function text(value: string, hint: string, privacy: Privacy, mangle: bool
|
|
|
88
88
|
return value;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
export function url(input: string, electron
|
|
91
|
+
export function url(input: string, electron = false, truncate = false): string {
|
|
92
92
|
let result = input;
|
|
93
93
|
// Replace the URL for Electron apps so we don't send back file:/// URL
|
|
94
94
|
if (electron) {
|
|
95
95
|
result = `${Data.Constant.HTTPS}${Data.Constant.Electron}`;
|
|
96
96
|
} else {
|
|
97
|
-
|
|
97
|
+
const drop = config.drop;
|
|
98
98
|
if (drop && drop.length > 0 && input && input.indexOf("?") > 0) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
result =
|
|
99
|
+
const [path, query] = input.split("?");
|
|
100
|
+
const swap = Data.Constant.Dropped;
|
|
101
|
+
result =
|
|
102
|
+
`${path}?${query
|
|
103
|
+
.split("&")
|
|
104
|
+
.map((p) => (drop.some((x) => p.indexOf(`${x}=`) === 0) ? `${p.split("=")[0]}=${swap}` : p))
|
|
105
|
+
.join("&")}`;
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
|
|
@@ -109,12 +113,12 @@ export function url(input: string, electron: boolean = false, truncate: boolean
|
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
function mangleText(value: string): string {
|
|
112
|
-
|
|
116
|
+
const trimmed = value.trim();
|
|
113
117
|
if (trimmed.length > 0) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
const first = trimmed[0];
|
|
119
|
+
const index = value.indexOf(first);
|
|
120
|
+
const prefix = value.substr(0, index);
|
|
121
|
+
const suffix = value.substr(index + trimmed.length);
|
|
118
122
|
return `${prefix}${trimmed.length.toString(36)}${suffix}`;
|
|
119
123
|
}
|
|
120
124
|
return value;
|
|
@@ -130,7 +134,7 @@ export function scrub(value: string, letter: string, digit: string): string {
|
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
function mangleToken(value: string): string {
|
|
133
|
-
|
|
137
|
+
const length = (Math.floor(value.length / Data.Setting.WordLength) + 1) * Data.Setting.WordLength;
|
|
134
138
|
let output: string = Layout.Constant.Empty;
|
|
135
139
|
for (let i = 0; i < length; i++) {
|
|
136
140
|
output += i > 0 && i % Data.Setting.WordLength === 0 ? Data.Constant.Space : Data.Constant.Mask;
|
|
@@ -143,10 +147,12 @@ function regex(): void {
|
|
|
143
147
|
// Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
|
|
144
148
|
if (unicodeRegex && digitRegex === null) {
|
|
145
149
|
try {
|
|
146
|
-
digitRegex =
|
|
147
|
-
letterRegex =
|
|
148
|
-
currencyRegex =
|
|
149
|
-
} catch {
|
|
150
|
+
digitRegex = /\p{N}/gu;
|
|
151
|
+
letterRegex = /\p{L}/gu;
|
|
152
|
+
currencyRegex = /\p{Sc}/gu;
|
|
153
|
+
} catch {
|
|
154
|
+
unicodeRegex = false;
|
|
155
|
+
}
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
158
|
|
|
@@ -161,16 +167,19 @@ function redact(value: string): string {
|
|
|
161
167
|
regex(); // Initialize regular expressions
|
|
162
168
|
|
|
163
169
|
for (let i = 0; i < value.length; i++) {
|
|
164
|
-
|
|
170
|
+
const c = value.charCodeAt(i);
|
|
165
171
|
hasDigit = hasDigit || (c >= Data.Character.Zero && c <= Data.Character.Nine); // Check for digits in the current word
|
|
166
172
|
hasEmail = hasEmail || c === Data.Character.At; // Check for @ sign anywhere within the current word
|
|
167
|
-
hasWhitespace =
|
|
173
|
+
hasWhitespace =
|
|
174
|
+
c === Data.Character.Tab || c === Data.Character.NewLine || c === Data.Character.Return || c === Data.Character.Blank;
|
|
168
175
|
|
|
169
176
|
// Process each word as an individual token to redact any sensitive information
|
|
170
177
|
if (i === 0 || i === value.length - 1 || hasWhitespace) {
|
|
171
178
|
// Performance optimization: Lazy load string -> array conversion only when required
|
|
172
179
|
if (hasDigit || hasEmail) {
|
|
173
|
-
if (array === null) {
|
|
180
|
+
if (array === null) {
|
|
181
|
+
array = value.split(Data.Constant.Empty);
|
|
182
|
+
}
|
|
174
183
|
// Work on a token at a time so we don't have to apply regex to a larger string
|
|
175
184
|
let token = value.substring(spaceIndex + 1, hasWhitespace ? i : i + 1);
|
|
176
185
|
// Check if unicode regex is supported, otherwise fallback to calling mask function on this token
|
package/src/core/task.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
type AsyncTask,
|
|
3
|
+
Priority,
|
|
4
|
+
type RequestIdleCallbackDeadline,
|
|
5
|
+
type RequestIdleCallbackOptions,
|
|
6
|
+
Task,
|
|
7
|
+
type Timer,
|
|
8
|
+
} from "@clarity-types/core";
|
|
9
|
+
import type { TaskFunction, TaskResolve, Tasks } from "@clarity-types/core";
|
|
3
10
|
import { Code, Metric, Setting, Severity } from "@clarity-types/data";
|
|
4
11
|
import * as metadata from "@src/data/metadata";
|
|
5
12
|
import * as metric from "@src/data/metric";
|
|
@@ -25,7 +32,9 @@ export function resume(): void {
|
|
|
25
32
|
if (pauseTask) {
|
|
26
33
|
resumeResolve();
|
|
27
34
|
pauseTask = null;
|
|
28
|
-
if (activeTask === null) {
|
|
35
|
+
if (activeTask === null) {
|
|
36
|
+
run();
|
|
37
|
+
}
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
|
|
@@ -38,14 +47,14 @@ export function reset(): void {
|
|
|
38
47
|
|
|
39
48
|
export async function schedule(task: TaskFunction, priority: Priority = Priority.Normal): Promise<void> {
|
|
40
49
|
// If this task is already scheduled, skip it
|
|
41
|
-
for (
|
|
50
|
+
for (const q of queuedTasks) {
|
|
42
51
|
if (q.task === task) {
|
|
43
52
|
return;
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
const promise = new Promise<void>((resolve: TaskResolve): void => {
|
|
57
|
+
const insert = priority === Priority.High ? "unshift" : "push";
|
|
49
58
|
// Queue this task for asynchronous execution later
|
|
50
59
|
// We also store a unique page identifier (id) along with the task to ensure
|
|
51
60
|
// ensure that we do not accidentally execute this task in context of a different page
|
|
@@ -54,38 +63,49 @@ export async function schedule(task: TaskFunction, priority: Priority = Priority
|
|
|
54
63
|
|
|
55
64
|
// If there is no active task running, and Clarity is not in pause state,
|
|
56
65
|
// invoke the first task in the queue synchronously. This ensures that we don't yield the thread during unload event
|
|
57
|
-
if (activeTask === null && pauseTask === null) {
|
|
66
|
+
if (activeTask === null && pauseTask === null) {
|
|
67
|
+
run();
|
|
68
|
+
}
|
|
58
69
|
|
|
59
70
|
return promise;
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
function run(): void {
|
|
63
|
-
|
|
74
|
+
const entry = queuedTasks.shift();
|
|
64
75
|
if (entry) {
|
|
65
76
|
activeTask = entry;
|
|
66
|
-
entry
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
entry
|
|
78
|
+
.task()
|
|
79
|
+
.then((): void => {
|
|
80
|
+
// Bail out if the context in which this task was operating is different from the current page
|
|
81
|
+
// An example scenario where task could span across pages is Single Page Applications (SPA)
|
|
82
|
+
// A task that started on page #1, but completes on page #2
|
|
83
|
+
if (entry.id !== metadata.id()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
entry.resolve();
|
|
87
|
+
activeTask = null; // Reset active task back to null now that the promise is resolved
|
|
88
|
+
run();
|
|
89
|
+
})
|
|
90
|
+
.catch((error: Error): void => {
|
|
91
|
+
// If one of the scheduled tasks failed, log, recover and continue processing rest of the tasks
|
|
92
|
+
if (entry.id !== metadata.id()) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (error) {
|
|
96
|
+
internal.log(Code.RunTask, Severity.Warning, error.name, error.message, error.stack);
|
|
97
|
+
}
|
|
98
|
+
activeTask = null;
|
|
99
|
+
run();
|
|
100
|
+
});
|
|
81
101
|
}
|
|
82
102
|
}
|
|
83
103
|
|
|
84
104
|
export function state(timer: Timer): Task {
|
|
85
|
-
|
|
105
|
+
const id = key(timer);
|
|
86
106
|
if (id in tracker) {
|
|
87
|
-
|
|
88
|
-
return
|
|
107
|
+
const elapsed = performance.now() - tracker[id].start;
|
|
108
|
+
return elapsed > tracker[id].yield ? Task.Wait : Task.Run;
|
|
89
109
|
}
|
|
90
110
|
// If this task is no longer being tracked, send stop message to the caller
|
|
91
111
|
return Task.Stop;
|
|
@@ -96,10 +116,10 @@ export function start(timer: Timer): void {
|
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
function restart(timer: Timer): void {
|
|
99
|
-
|
|
100
|
-
if (tracker
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
const id = key(timer);
|
|
120
|
+
if (tracker?.[id]) {
|
|
121
|
+
const c = tracker[id].calls;
|
|
122
|
+
const y = tracker[id].yield;
|
|
103
123
|
start(timer);
|
|
104
124
|
tracker[id].calls = c + 1;
|
|
105
125
|
tracker[id].yield = y;
|
|
@@ -107,15 +127,17 @@ function restart(timer: Timer): void {
|
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
export function stop(timer: Timer): void {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
130
|
+
const end = performance.now();
|
|
131
|
+
const id = key(timer);
|
|
132
|
+
const duration = end - tracker[id].start;
|
|
113
133
|
metric.sum(timer.cost, duration);
|
|
114
134
|
metric.count(Metric.InvokeCount);
|
|
115
135
|
|
|
116
136
|
// For the first execution, which is synchronous, time is automatically counted towards TotalDuration.
|
|
117
137
|
// However, for subsequent asynchronous runs, we need to manually update TotalDuration metric.
|
|
118
|
-
if (tracker[id].calls > 0) {
|
|
138
|
+
if (tracker[id].calls > 0) {
|
|
139
|
+
metric.sum(Metric.TotalCost, duration);
|
|
140
|
+
}
|
|
119
141
|
}
|
|
120
142
|
|
|
121
143
|
export async function suspend(timer: Timer): Promise<Task> {
|
|
@@ -123,10 +145,10 @@ export async function suspend(timer: Timer): Promise<Task> {
|
|
|
123
145
|
// It's possible that Clarity is wrapping up instrumentation on a page and we are still in the middle of an async task.
|
|
124
146
|
// In that case, we do not wish to continue yielding thread.
|
|
125
147
|
// Instead, we will turn async task into a sync task and maximize our chances of getting some data back.
|
|
126
|
-
|
|
148
|
+
const id = key(timer);
|
|
127
149
|
if (id in tracker) {
|
|
128
150
|
stop(timer);
|
|
129
|
-
// some customer polyfills for requestIdleCallback return null
|
|
151
|
+
// some customer polyfills for requestIdleCallback return null
|
|
130
152
|
tracker[id].yield = (await wait())?.timeRemaining() || Setting.LongTask;
|
|
131
153
|
restart(timer);
|
|
132
154
|
}
|
|
@@ -140,7 +162,9 @@ function key(timer: Timer): string {
|
|
|
140
162
|
}
|
|
141
163
|
|
|
142
164
|
async function wait(): Promise<RequestIdleCallbackDeadline> {
|
|
143
|
-
if (pauseTask) {
|
|
165
|
+
if (pauseTask) {
|
|
166
|
+
await pauseTask;
|
|
167
|
+
}
|
|
144
168
|
return new Promise<RequestIdleCallbackDeadline>((resolve: (deadline: RequestIdleCallbackDeadline) => void): void => {
|
|
145
169
|
requestIdleCallback(resolve, { timeout: idleTimeout });
|
|
146
170
|
});
|
|
@@ -162,20 +186,24 @@ function requestIdleCallbackPolyfill(callback: (deadline: RequestIdleCallbackDea
|
|
|
162
186
|
const incoming = channel.port1;
|
|
163
187
|
const outgoing = channel.port2;
|
|
164
188
|
incoming.onmessage = (event: MessageEvent): void => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
189
|
+
const currentTime = performance.now();
|
|
190
|
+
const elapsed = currentTime - startTime;
|
|
191
|
+
const duration = currentTime - event.data;
|
|
168
192
|
if (duration > Setting.LongTask && elapsed < options.timeout) {
|
|
169
|
-
requestAnimationFrame((): void => {
|
|
193
|
+
requestAnimationFrame((): void => {
|
|
194
|
+
outgoing.postMessage(currentTime);
|
|
195
|
+
});
|
|
170
196
|
} else {
|
|
171
|
-
|
|
197
|
+
const didTimeout = elapsed > options.timeout;
|
|
172
198
|
callback({
|
|
173
199
|
didTimeout,
|
|
174
|
-
timeRemaining: (): number => didTimeout ? Setting.LongTask : Math.max(0, Setting.LongTask - duration)
|
|
200
|
+
timeRemaining: (): number => (didTimeout ? Setting.LongTask : Math.max(0, Setting.LongTask - duration)),
|
|
175
201
|
});
|
|
176
202
|
}
|
|
177
203
|
};
|
|
178
|
-
requestAnimationFrame((): void => {
|
|
204
|
+
requestAnimationFrame((): void => {
|
|
205
|
+
outgoing.postMessage(performance.now());
|
|
206
|
+
});
|
|
179
207
|
}
|
|
180
208
|
|
|
181
|
-
|
|
209
|
+
const requestIdleCallback = window.requestIdleCallback || requestIdleCallbackPolyfill;
|
package/src/core/time.ts
CHANGED
|
@@ -4,13 +4,13 @@ export function start(): void {
|
|
|
4
4
|
startTime = performance.now() + performance.timeOrigin;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
// event.timestamp is number of milliseconds elapsed since the document was loaded
|
|
7
|
+
// event.timestamp is number of milliseconds elapsed since the document was loaded
|
|
8
8
|
// since iframes can be loaded later the event timestamp is not the same as performance.now()
|
|
9
9
|
// converting everything to absolute time by adding timeorigin of the event view
|
|
10
10
|
// to synchronize times before calculating the difference with start time
|
|
11
11
|
export function time(event: UIEvent | PageTransitionEvent = null): number {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const ts = event && event.timeStamp > 0 ? event.timeStamp : performance.now();
|
|
13
|
+
const origin = event && (event as UIEvent).view ? (event as UIEvent).view.performance.timeOrigin : performance.timeOrigin;
|
|
14
14
|
return Math.max(Math.round(ts + origin - startTime), 0);
|
|
15
15
|
}
|
|
16
16
|
|
package/src/core/timeout.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Event } from "@clarity-types/data";
|
|
1
|
+
import type { Event } from "@clarity-types/data";
|
|
2
2
|
import measure from "./measure";
|
|
3
3
|
|
|
4
4
|
export function setTimeout(handler: (event?: Event | boolean) => void, timeout?: number, event?: Event): number {
|
|
@@ -6,5 +6,5 @@ export function setTimeout(handler: (event?: Event | boolean) => void, timeout?:
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function clearTimeout(handle: number): void {
|
|
9
|
-
|
|
9
|
+
window.clearTimeout(handle);
|
|
10
10
|
}
|
package/src/core/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
const version = "0.8.10";
|
|
2
2
|
export default version;
|
package/src/data/baseline.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BaselineData, BaselineState } from "@clarity-types/data";
|
|
1
|
+
import { BooleanFlag, Event } from "@clarity-types/data";
|
|
2
|
+
import type { BaselineData, BaselineState } from "@clarity-types/data";
|
|
3
3
|
import { time } from "@src/core/time";
|
|
4
4
|
import encode from "@src/data/encode";
|
|
5
5
|
|
|
6
6
|
export let state: BaselineState = null;
|
|
7
7
|
let buffer: BaselineData = null;
|
|
8
|
-
let update
|
|
8
|
+
let update = false;
|
|
9
9
|
|
|
10
10
|
export function start(): void {
|
|
11
11
|
update = false;
|
|
@@ -16,60 +16,65 @@ export function reset(): void {
|
|
|
16
16
|
// Baseline state holds the previous values - if it is updated in the current payload,
|
|
17
17
|
// reset the state to current value after sending the previous state
|
|
18
18
|
if (update) {
|
|
19
|
-
state = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
19
|
+
state = {
|
|
20
|
+
time: time(),
|
|
21
|
+
event: Event.Baseline,
|
|
22
|
+
data: {
|
|
23
|
+
visible: buffer.visible,
|
|
24
|
+
docWidth: buffer.docWidth,
|
|
25
|
+
docHeight: buffer.docHeight,
|
|
26
|
+
screenWidth: buffer.screenWidth,
|
|
27
|
+
screenHeight: buffer.screenHeight,
|
|
28
|
+
scrollX: buffer.scrollX,
|
|
29
|
+
scrollY: buffer.scrollY,
|
|
30
|
+
pointerX: buffer.pointerX,
|
|
31
|
+
pointerY: buffer.pointerY,
|
|
32
|
+
activityTime: buffer.activityTime,
|
|
33
|
+
scrollTime: buffer.scrollTime,
|
|
34
|
+
pointerTime: buffer.pointerTime,
|
|
35
|
+
moveX: buffer.moveX,
|
|
36
|
+
moveY: buffer.moveY,
|
|
37
|
+
moveTime: buffer.moveTime,
|
|
38
|
+
downX: buffer.downX,
|
|
39
|
+
downY: buffer.downY,
|
|
40
|
+
downTime: buffer.downTime,
|
|
41
|
+
upX: buffer.upX,
|
|
42
|
+
upY: buffer.upY,
|
|
43
|
+
upTime: buffer.upTime,
|
|
44
|
+
pointerPrevX: buffer.pointerPrevX,
|
|
45
|
+
pointerPrevY: buffer.pointerPrevY,
|
|
46
|
+
pointerPrevTime: buffer.pointerPrevTime,
|
|
47
|
+
},
|
|
45
48
|
};
|
|
46
49
|
}
|
|
47
|
-
buffer = buffer
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
50
|
+
buffer = buffer
|
|
51
|
+
? buffer
|
|
52
|
+
: {
|
|
53
|
+
visible: BooleanFlag.True,
|
|
54
|
+
docWidth: 0,
|
|
55
|
+
docHeight: 0,
|
|
56
|
+
screenWidth: 0,
|
|
57
|
+
screenHeight: 0,
|
|
58
|
+
scrollX: 0,
|
|
59
|
+
scrollY: 0,
|
|
60
|
+
pointerX: 0,
|
|
61
|
+
pointerY: 0,
|
|
62
|
+
activityTime: 0,
|
|
63
|
+
scrollTime: 0,
|
|
64
|
+
pointerTime: undefined,
|
|
65
|
+
moveX: undefined,
|
|
66
|
+
moveY: undefined,
|
|
67
|
+
moveTime: undefined,
|
|
68
|
+
downX: undefined,
|
|
69
|
+
downY: undefined,
|
|
70
|
+
downTime: undefined,
|
|
71
|
+
upX: undefined,
|
|
72
|
+
upY: undefined,
|
|
73
|
+
upTime: undefined,
|
|
74
|
+
pointerPrevX: undefined,
|
|
75
|
+
pointerPrevY: undefined,
|
|
76
|
+
pointerPrevTime: undefined,
|
|
77
|
+
};
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
export function track(event: Event, x: number, y: number, time?: number): void {
|