clarity-js 0.8.12 → 0.8.14

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