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.
Files changed (90) hide show
  1. package/.lintstagedrc.yml +3 -0
  2. package/biome.json +43 -0
  3. package/build/clarity.extended.js +1 -1
  4. package/build/clarity.insight.js +1 -1
  5. package/build/clarity.js +3095 -2876
  6. package/build/clarity.min.js +1 -1
  7. package/build/clarity.module.js +3095 -2876
  8. package/build/clarity.performance.js +1 -1
  9. package/package.json +17 -10
  10. package/rollup.config.ts +84 -88
  11. package/src/clarity.ts +34 -28
  12. package/src/core/config.ts +2 -2
  13. package/src/core/event.ts +36 -32
  14. package/src/core/hash.ts +5 -6
  15. package/src/core/history.ts +10 -11
  16. package/src/core/index.ts +21 -11
  17. package/src/core/measure.ts +9 -5
  18. package/src/core/report.ts +9 -5
  19. package/src/core/scrub.ts +29 -20
  20. package/src/core/task.ts +73 -45
  21. package/src/core/time.ts +3 -3
  22. package/src/core/timeout.ts +2 -2
  23. package/src/core/version.ts +1 -1
  24. package/src/data/baseline.ts +60 -55
  25. package/src/data/consent.ts +2 -2
  26. package/src/data/custom.ts +8 -13
  27. package/src/data/dimension.ts +11 -7
  28. package/src/data/encode.ts +36 -30
  29. package/src/data/envelope.ts +38 -38
  30. package/src/data/extract.ts +86 -77
  31. package/src/data/index.ts +10 -6
  32. package/src/data/limit.ts +1 -1
  33. package/src/data/metadata.ts +305 -266
  34. package/src/data/metric.ts +18 -8
  35. package/src/data/ping.ts +8 -4
  36. package/src/data/signal.ts +18 -18
  37. package/src/data/summary.ts +6 -4
  38. package/src/data/token.ts +10 -8
  39. package/src/data/upgrade.ts +7 -3
  40. package/src/data/upload.ts +100 -49
  41. package/src/data/variable.ts +27 -20
  42. package/src/diagnostic/encode.ts +2 -2
  43. package/src/diagnostic/fraud.ts +3 -4
  44. package/src/diagnostic/internal.ts +11 -5
  45. package/src/diagnostic/script.ts +12 -8
  46. package/src/global.ts +1 -1
  47. package/src/insight/blank.ts +4 -4
  48. package/src/insight/encode.ts +23 -17
  49. package/src/insight/snapshot.ts +57 -37
  50. package/src/interaction/change.ts +9 -6
  51. package/src/interaction/click.ts +34 -28
  52. package/src/interaction/clipboard.ts +2 -2
  53. package/src/interaction/encode.ts +35 -31
  54. package/src/interaction/input.ts +11 -9
  55. package/src/interaction/pointer.ts +41 -30
  56. package/src/interaction/resize.ts +5 -5
  57. package/src/interaction/scroll.ts +20 -17
  58. package/src/interaction/selection.ts +12 -8
  59. package/src/interaction/submit.ts +2 -2
  60. package/src/interaction/timeline.ts +13 -9
  61. package/src/interaction/unload.ts +1 -1
  62. package/src/interaction/visibility.ts +2 -2
  63. package/src/layout/animation.ts +47 -41
  64. package/src/layout/discover.ts +5 -5
  65. package/src/layout/document.ts +31 -19
  66. package/src/layout/dom.ts +141 -91
  67. package/src/layout/encode.ts +52 -37
  68. package/src/layout/mutation.ts +321 -318
  69. package/src/layout/node.ts +104 -81
  70. package/src/layout/offset.ts +7 -6
  71. package/src/layout/region.ts +66 -43
  72. package/src/layout/schema.ts +15 -8
  73. package/src/layout/selector.ts +47 -25
  74. package/src/layout/style.ts +45 -37
  75. package/src/layout/target.ts +14 -10
  76. package/src/layout/traverse.ts +17 -11
  77. package/src/performance/blank.ts +1 -1
  78. package/src/performance/encode.ts +4 -4
  79. package/src/performance/interaction.ts +58 -70
  80. package/src/performance/navigation.ts +2 -2
  81. package/src/performance/observer.ts +59 -26
  82. package/src/queue.ts +16 -9
  83. package/tsconfig.json +1 -1
  84. package/tslint.json +25 -32
  85. package/types/core.d.ts +13 -13
  86. package/types/data.d.ts +32 -29
  87. package/types/diagnostic.d.ts +1 -1
  88. package/types/interaction.d.ts +5 -4
  89. package/types/layout.d.ts +36 -21
  90. package/types/performance.d.ts +6 -5
@@ -1,4 +1,4 @@
1
- import { Event, Metric, MetricData } from "@clarity-types/data";
1
+ import { Event, Metric, type MetricData } from "@clarity-types/data";
2
2
  import encode from "./encode";
3
3
 
4
4
  export let data: MetricData = null;
@@ -16,16 +16,24 @@ export function stop(): void {
16
16
  }
17
17
 
18
18
  export function count(metric: Metric): void {
19
- if (!(metric in data)) { data[metric] = 0; }
20
- if (!(metric in updates)) { updates[metric] = 0; }
19
+ if (!(metric in data)) {
20
+ data[metric] = 0;
21
+ }
22
+ if (!(metric in updates)) {
23
+ updates[metric] = 0;
24
+ }
21
25
  data[metric]++;
22
26
  updates[metric]++;
23
27
  }
24
28
 
25
29
  export function sum(metric: Metric, value: number): void {
26
- if (value !== null) {
27
- if (!(metric in data)) { data[metric] = 0; }
28
- if (!(metric in updates)) { updates[metric] = 0; }
30
+ if (value !== null) {
31
+ if (!(metric in data)) {
32
+ data[metric] = 0;
33
+ }
34
+ if (!(metric in updates)) {
35
+ updates[metric] = 0;
36
+ }
29
37
  data[metric] += value;
30
38
  updates[metric] += value;
31
39
  }
@@ -33,8 +41,10 @@ export function sum(metric: Metric, value: number): void {
33
41
 
34
42
  export function max(metric: Metric, value: number): void {
35
43
  // Ensure that we do not process null or NaN values
36
- if (value !== null && isNaN(value) === false) {
37
- if (!(metric in data)) { data[metric] = 0; }
44
+ if (value !== null && Number.isNaN(value) === false) {
45
+ if (!(metric in data)) {
46
+ data[metric] = 0;
47
+ }
38
48
  if (value > data[metric] || data[metric] === 0) {
39
49
  updates[metric] = value;
40
50
  data[metric] = value;
package/src/data/ping.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Event, PingData, Setting } from "@clarity-types/data";
1
+ import { Event, type PingData, Setting } from "@clarity-types/data";
2
2
  import { suspend } from "@src/core";
3
3
  import { time } from "@src/core/time";
4
4
  import { clearTimeout, setTimeout } from "@src/core/timeout";
@@ -15,18 +15,22 @@ export function start(): void {
15
15
  }
16
16
 
17
17
  export function reset(): void {
18
- if (timeout) { clearTimeout(timeout); }
18
+ if (timeout) {
19
+ clearTimeout(timeout);
20
+ }
19
21
  timeout = setTimeout(ping, interval);
20
22
  last = time();
21
23
  }
22
24
 
23
25
  function ping(): void {
24
- let now = time();
26
+ const now = time();
25
27
  data = { gap: now - last };
26
28
  encode(Event.Ping);
27
29
  if (data.gap < Setting.PingTimeout) {
28
30
  timeout = setTimeout(ping, interval);
29
- } else { suspend(); }
31
+ } else {
32
+ suspend();
33
+ }
30
34
  }
31
35
 
32
36
  export function stop(): void {
@@ -1,30 +1,30 @@
1
- import { ClaritySignal, SignalCallback } from '@clarity-types/data';
1
+ import type { ClaritySignal, SignalCallback } from "@clarity-types/data";
2
2
 
3
3
  export let signalCallback: SignalCallback = null;
4
4
 
5
5
  export function signal(cb: SignalCallback): void {
6
- signalCallback = cb;
6
+ signalCallback = cb;
7
7
  }
8
8
 
9
9
  function parseSignals(signalsPayload: string): ClaritySignal[] {
10
- try{
11
- const parsedSignals: ClaritySignal[] = JSON.parse(signalsPayload);
12
- return parsedSignals;
13
- }catch{
14
- return []
15
- }
10
+ try {
11
+ const parsedSignals: ClaritySignal[] = JSON.parse(signalsPayload);
12
+ return parsedSignals;
13
+ } catch {
14
+ return [];
15
+ }
16
16
  }
17
17
 
18
18
  export function signalsEvent(signalsPayload: string) {
19
- try {
20
- if (!signalCallback) {
21
- return;
19
+ try {
20
+ if (!signalCallback) {
21
+ return;
22
+ }
23
+ const signals = parseSignals(signalsPayload);
24
+ for (const signal of signals) {
25
+ signalCallback(signal);
26
+ }
27
+ } catch {
28
+ //do nothing
22
29
  }
23
- const signals = parseSignals(signalsPayload);
24
- signals.forEach((signal) => {
25
- signalCallback(signal);
26
- });
27
- } catch {
28
- //do nothing
29
- }
30
30
  }
@@ -1,4 +1,4 @@
1
- import { Event, SummaryData, Setting } from "@clarity-types/data";
1
+ import { Event, Setting, type SummaryData } from "@clarity-types/data";
2
2
  import encode from "./encode";
3
3
 
4
4
  export let data: SummaryData = null;
@@ -15,13 +15,15 @@ export function track(event: Event, time: number): void {
15
15
  if (!(event in data)) {
16
16
  data[event] = [[time, 0]];
17
17
  } else {
18
- let e = data[event];
19
- let last = e[e.length - 1];
18
+ const e = data[event];
19
+ const last = e[e.length - 1];
20
20
  // Add a new entry only if the new event occurs after configured interval
21
21
  // Otherwise, extend the duration of the previous entry
22
22
  if (time - last[0] > Setting.SummaryInterval) {
23
23
  data[event].push([time, 0]);
24
- } else { last[1] = time - last[0]; }
24
+ } else {
25
+ last[1] = time - last[0];
26
+ }
25
27
  }
26
28
  }
27
29
 
package/src/data/token.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {Constant, Token} from "@clarity-types/data";
1
+ import type { Token } from "@clarity-types/data";
2
2
 
3
3
  // Following code takes an array of tokens and transforms it to optimize for repeating tokens and make it efficient to send over the wire
4
4
  // The way it works is that it iterate over all tokens and checks if the current token was already seen in the tokens array so far
@@ -6,18 +6,20 @@ import {Constant, Token} from "@clarity-types/data";
6
6
  // E.g. If tokens array is: ["hello", "world", "coding", "language", "world", "language", "example"]
7
7
  // Then the resulting tokens array after following code execution would be: ["hello", "world", "coding", "language", [1, 3], "example"]
8
8
  // Where [1,3] points to tokens[1] => "world" and tokens[3] => "language"
9
- export default function(tokens: Token[]): Token[] {
10
- let output: Token[] = [];
11
- let lookup: {[key: string]: number} = {};
9
+ export default function (tokens: Token[]): Token[] {
10
+ const output: Token[] = [];
11
+ const lookup: { [key: string]: number } = {};
12
12
  let pointer = 0;
13
13
  let reference = null;
14
14
  for (let i = 0; i < tokens.length; i++) {
15
15
  // Only optimize for string values
16
- if (typeof tokens[i] === Constant.String) {
17
- let token = tokens[i] as string;
18
- let index = lookup[token] || -1;
16
+ if (typeof tokens[i] === "string") {
17
+ const token = tokens[i] as string;
18
+ const index = lookup[token] || -1;
19
19
  if (index >= 0) {
20
- if (reference) { reference.push(index); } else {
20
+ if (reference) {
21
+ reference.push(index);
22
+ } else {
21
23
  reference = [index];
22
24
  output.push(reference);
23
25
  pointer++;
@@ -1,4 +1,4 @@
1
- import { Constant, Event, UpgradeData } from "@clarity-types/data";
1
+ import { Constant, Event, type UpgradeData } from "@clarity-types/data";
2
2
  import * as core from "@src/core";
3
3
  import config from "@src/core/config";
4
4
  import encode from "@src/data/encode";
@@ -9,7 +9,9 @@ import * as style from "@src/layout/style";
9
9
  export let data: UpgradeData = null;
10
10
 
11
11
  export function start(): void {
12
- if (!config.lean && config.upgrade) { config.upgrade(Constant.Config); }
12
+ if (!config.lean && config.upgrade) {
13
+ config.upgrade(Constant.Config);
14
+ }
13
15
  data = null;
14
16
  }
15
17
 
@@ -28,7 +30,9 @@ export function upgrade(key: string): void {
28
30
  metadata.save();
29
31
 
30
32
  // Callback upgrade handler, if configured
31
- if (config.upgrade) { config.upgrade(key); }
33
+ if (config.upgrade) {
34
+ config.upgrade(key);
35
+ }
32
36
 
33
37
  encode(Event.Upgrade);
34
38
 
@@ -1,34 +1,48 @@
1
- import { UploadCallback } from "@clarity-types/core";
2
- import { BooleanFlag, Check, Code, Constant, EncodedPayload, Event, Metric, Setting, Severity, Token, Transit, UploadData, XMLReadyState } from "@clarity-types/data";
1
+ import type { UploadCallback } from "@clarity-types/core";
2
+ import {
3
+ BooleanFlag,
4
+ Check,
5
+ Code,
6
+ Constant,
7
+ type EncodedPayload,
8
+ Event,
9
+ Metric,
10
+ Setting,
11
+ Severity,
12
+ type Token,
13
+ type Transit,
14
+ type UploadData,
15
+ XMLReadyState,
16
+ } from "@clarity-types/data";
3
17
  import * as clarity from "@src/clarity";
4
18
  import config from "@src/core/config";
5
19
  import measure from "@src/core/measure";
20
+ import { report } from "@src/core/report";
6
21
  import { time } from "@src/core/time";
7
22
  import { clearTimeout, setTimeout } from "@src/core/timeout";
8
23
  import compress from "@src/data/compress";
9
24
  import encode from "@src/data/encode";
10
25
  import * as envelope from "@src/data/envelope";
26
+ import * as extract from "@src/data/extract";
11
27
  import * as data from "@src/data/index";
12
28
  import * as limit from "@src/data/limit";
13
29
  import * as metadata from "@src/data/metadata";
14
30
  import * as metric from "@src/data/metric";
15
31
  import * as ping from "@src/data/ping";
32
+ import { signalsEvent } from "@src/data/signal";
16
33
  import * as internal from "@src/diagnostic/internal";
17
34
  import * as timeline from "@src/interaction/timeline";
18
35
  import * as region from "@src/layout/region";
19
- import * as extract from "@src/data/extract";
20
36
  import * as style from "@src/layout/style";
21
- import { report } from "@src/core/report";
22
- import { signalsEvent } from "@src/data/signal";
23
37
 
24
- let discoverBytes: number = 0;
25
- let playbackBytes: number = 0;
38
+ let discoverBytes = 0;
39
+ let playbackBytes = 0;
26
40
  let playback: string[];
27
41
  let analysis: string[];
28
42
  let timeout: number = null;
29
43
  let transit: Transit;
30
44
  let active: boolean;
31
- let queuedTime: number = 0;
45
+ let queuedTime = 0;
32
46
  let leanLimit = false;
33
47
  export let track: UploadData;
34
48
 
@@ -44,11 +58,11 @@ export function start(): void {
44
58
  track = null;
45
59
  }
46
60
 
47
- export function queue(tokens: Token[], transmit: boolean = true): void {
61
+ export function queue(tokens: Token[], transmit = true): void {
48
62
  if (active) {
49
- let now = time();
50
- let type = tokens.length > 1 ? tokens[1] : null;
51
- let event = JSON.stringify(tokens);
63
+ const now = time();
64
+ const type = tokens.length > 1 ? tokens[1] : null;
65
+ const event = JSON.stringify(tokens);
52
66
 
53
67
  if (!config.lean) {
54
68
  leanLimit = false;
@@ -58,15 +72,20 @@ export function queue(tokens: Token[], transmit: boolean = true): void {
58
72
  }
59
73
 
60
74
  switch (type) {
75
+ // biome-ignore lint/suspicious/noFallthroughSwitchClause: we want discover bytes to also count as playback bytes
61
76
  case Event.Discover:
62
- if (leanLimit) { break; }
77
+ if (leanLimit) {
78
+ break;
79
+ }
63
80
  discoverBytes += event.length;
64
81
  case Event.Box:
65
82
  case Event.Mutation:
66
83
  case Event.Snapshot:
67
84
  case Event.StyleSheetAdoption:
68
85
  case Event.StyleSheetUpdate:
69
- if (leanLimit) { break; }
86
+ if (leanLimit) {
87
+ break;
88
+ }
70
89
  playbackBytes += event.length;
71
90
  playback.push(event);
72
91
  break;
@@ -81,8 +100,8 @@ export function queue(tokens: Token[], transmit: boolean = true): void {
81
100
  // Following two checks are precautionary and act as a fail safe mechanism to get out of unexpected situations.
82
101
  // Check 1: If for any reason the upload hasn't happened after waiting for 2x the config.delay time,
83
102
  // reset the timer. This allows Clarity to attempt an upload again.
84
- let gap = delay();
85
- if (now - queuedTime > (gap * 2)) {
103
+ const gap = delay();
104
+ if (now - queuedTime > gap * 2) {
86
105
  clearTimeout(timeout);
87
106
  timeout = null;
88
107
  }
@@ -91,7 +110,9 @@ export function queue(tokens: Token[], transmit: boolean = true): void {
91
110
  // However, in certain scenarios - like metric calculation - which are triggered as part of an existing upload
92
111
  // We enrich the data going out with the existing upload. In these cases, call to upload comes with 'transmit' set to false.
93
112
  if (transmit && timeout === null) {
94
- if (type !== Event.Ping) { ping.reset(); }
113
+ if (type !== Event.Ping) {
114
+ ping.reset();
115
+ }
95
116
  timeout = setTimeout(upload, gap);
96
117
  queuedTime = now;
97
118
  limit.check(playbackBytes);
@@ -113,14 +134,17 @@ export function stop(): void {
113
134
  active = false;
114
135
  }
115
136
 
116
- async function upload(final: boolean = false): Promise<void> {
137
+ async function upload(final = false): Promise<void> {
117
138
  timeout = null;
118
139
 
119
140
  // Check if we can send playback bytes over the wire or not
120
141
  // For better instrumentation coverage, we send playback bytes from second sequence onwards
121
142
  // And, we only send playback metric when we are able to send the playback bytes back to server
122
- let sendPlaybackBytes = config.lean === false && playbackBytes > 0 && (playbackBytes < Setting.MaxFirstPayloadBytes || envelope.data.sequence > 0);
123
- if (sendPlaybackBytes) { metric.max(Metric.Playback, BooleanFlag.True); }
143
+ const sendPlaybackBytes =
144
+ config.lean === false && playbackBytes > 0 && (playbackBytes < Setting.MaxFirstPayloadBytes || envelope.data.sequence > 0);
145
+ if (sendPlaybackBytes) {
146
+ metric.max(Metric.Playback, BooleanFlag.True);
147
+ }
124
148
 
125
149
  // CAUTION: Ensure "transmit" is set to false in the queue function for following events
126
150
  // Otherwise you run a risk of infinite loop.
@@ -133,18 +157,18 @@ async function upload(final: boolean = false): Promise<void> {
133
157
  // In real world tests, we noticed that certain third party scripts (e.g. https://www.npmjs.com/package/raven-js)
134
158
  // could inject function arguments for internal tracking (likely stack traces for script errors).
135
159
  // For these edge cases, we want to ensure that an injected object (e.g. {"key": "value"}) isn't mistaken to be true.
136
- let last = final === true;
137
- let e = JSON.stringify(envelope.envelope(last));
138
- let a = `[${analysis.join()}]`;
160
+ const last = final === true;
161
+ const e = JSON.stringify(envelope.envelope(last));
162
+ const a = `[${analysis.join()}]`;
139
163
 
140
- let p = sendPlaybackBytes ? `[${playback.join()}]` : Constant.Empty;
141
- let encoded: EncodedPayload = {e, a, p};
164
+ const p = sendPlaybackBytes ? `[${playback.join()}]` : Constant.Empty;
165
+ const encoded: EncodedPayload = { e, a, p };
142
166
 
143
167
  // Get the payload ready for sending over the wire
144
168
  // We also attempt to compress the payload if it is not the last payload and the browser supports it
145
169
  // In all other cases, we continue to send back string value
146
- let payload = stringify(encoded);
147
- let zipped = last ? null : await compress(payload)
170
+ const payload = stringify(encoded);
171
+ const zipped = last ? null : await compress(payload);
148
172
  metric.sum(Metric.TotalBytes, zipped ? zipped.length : payload.length);
149
173
  send(payload, zipped, envelope.data.sequence, last);
150
174
 
@@ -162,9 +186,9 @@ function stringify(encoded: EncodedPayload): string {
162
186
  return encoded.p.length > 0 ? `{"e":${encoded.e},"a":${encoded.a},"p":${encoded.p}}` : `{"e":${encoded.e},"a":${encoded.a}}`;
163
187
  }
164
188
 
165
- function send(payload: string, zipped: Uint8Array, sequence: number, beacon: boolean = false): void {
189
+ function send(payload: string, zipped: Uint8Array, sequence: number, beacon = false): void {
166
190
  // Upload data if a valid URL is defined in the config
167
- if (typeof config.upload === Constant.String) {
191
+ if (typeof config.upload === "string") {
168
192
  const url = config.upload as string;
169
193
  let dispatched = false;
170
194
 
@@ -176,8 +200,12 @@ function send(payload: string, zipped: Uint8Array, sequence: number, beacon: boo
176
200
  try {
177
201
  // Navigator needs to be bound to sendBeacon before it is used to avoid errors in some browsers
178
202
  dispatched = navigator.sendBeacon.bind(navigator)(url, payload);
179
- if (dispatched) { done(sequence); }
180
- } catch { /* do nothing - and we will automatically fallback to XHR below */ }
203
+ if (dispatched) {
204
+ done(sequence);
205
+ }
206
+ } catch {
207
+ /* do nothing - and we will automatically fallback to XHR below */
208
+ }
181
209
  }
182
210
 
183
211
  // Before initiating XHR upload, we check if the data has already been uploaded using sendBeacon
@@ -188,12 +216,22 @@ function send(payload: string, zipped: Uint8Array, sequence: number, beacon: boo
188
216
  if (dispatched === false) {
189
217
  // While tracking payload for retry, we only track string value of the payload to err on the safe side
190
218
  // Not all browsers support compression API and the support for it in supported browsers is still experimental
191
- if (sequence in transit) { transit[sequence].attempts++; } else { transit[sequence] = { data: payload, attempts: 1 }; }
192
- let xhr = new XMLHttpRequest();
219
+ if (sequence in transit) {
220
+ transit[sequence].attempts++;
221
+ } else {
222
+ transit[sequence] = { data: payload, attempts: 1 };
223
+ }
224
+ const xhr = new XMLHttpRequest();
193
225
  xhr.open("POST", url, true);
194
226
  xhr.timeout = Setting.UploadTimeout;
195
- xhr.ontimeout = () => { report(new Error(`${Constant.Timeout} : ${url}`)) };
196
- if (sequence !== null) { xhr.onreadystatechange = (): void => { measure(check)(xhr, sequence); }; }
227
+ xhr.ontimeout = () => {
228
+ report(new Error(`${Constant.Timeout} : ${url}`));
229
+ };
230
+ if (sequence !== null) {
231
+ xhr.onreadystatechange = (): void => {
232
+ measure(check)(xhr, sequence);
233
+ };
234
+ }
197
235
  xhr.withCredentials = true;
198
236
  if (zipped) {
199
237
  // If we do have valid compressed array, send it with appropriate HTTP headers so server can decode it appropriately
@@ -212,7 +250,7 @@ function send(payload: string, zipped: Uint8Array, sequence: number, beacon: boo
212
250
  }
213
251
 
214
252
  function check(xhr: XMLHttpRequest, sequence: number): void {
215
- var transitData = transit[sequence];
253
+ const transitData = transit[sequence];
216
254
  if (xhr && xhr.readyState === XMLReadyState.Done && transitData) {
217
255
  // Attempt send payload again (as configured in settings) if we do not receive a success (2XX) response code back from the server
218
256
  if ((xhr.status < 200 || xhr.status > 208) && transitData.attempts <= Setting.RetryLimit) {
@@ -226,7 +264,9 @@ function check(xhr: XMLHttpRequest, sequence: number): void {
226
264
  // 1: Browsers block upload because of content security policy violation
227
265
  // 2: Safari will terminate pending XHR requests with status code 0 if the user navigates away from the page
228
266
  // In any case, we switch the upload URL to fallback configuration (if available) before re-trying one more time
229
- if (xhr.status === 0) { config.upload = config.fallback ? config.fallback : config.upload; }
267
+ if (xhr.status === 0) {
268
+ config.upload = config.fallback ? config.fallback : config.upload;
269
+ }
230
270
  // In all other cases, re-attempt sending the same data
231
271
  // For retry we always fallback to string payload, even though we may have attempted
232
272
  // sending zipped payload earlier
@@ -235,9 +275,13 @@ function check(xhr: XMLHttpRequest, sequence: number): void {
235
275
  } else {
236
276
  track = { sequence, attempts: transitData.attempts, status: xhr.status };
237
277
  // Send back an event only if we were not successful in our first attempt
238
- if (transitData.attempts > 1) { encode(Event.Upload); }
278
+ if (transitData.attempts > 1) {
279
+ encode(Event.Upload);
280
+ }
239
281
  // Handle response if it was a 200 response with a valid body
240
- if (xhr.status === 200 && xhr.responseText) { response(xhr.responseText); }
282
+ if (xhr.status === 200 && xhr.responseText) {
283
+ response(xhr.responseText);
284
+ }
241
285
  // If we exhausted our retries then trigger Clarity's shutdown for this page since the data will be incomplete
242
286
  if (xhr.status === 0) {
243
287
  // And, right before we terminate the session, we will attempt one last time to see if we can use
@@ -246,7 +290,9 @@ function check(xhr: XMLHttpRequest, sequence: number): void {
246
290
  limit.trigger(Check.Retry);
247
291
  }
248
292
  // Signal that this request completed successfully
249
- if (xhr.status >= 200 && xhr.status <= 208) { done(sequence); }
293
+ if (xhr.status >= 200 && xhr.status <= 208) {
294
+ done(sequence);
295
+ }
250
296
  // Stop tracking this payload now that it's all done
251
297
  delete transit[sequence];
252
298
  }
@@ -264,15 +310,14 @@ function done(sequence: number): void {
264
310
  function delay(): number {
265
311
  // Progressively increase delay as we continue to send more payloads from the client to the server
266
312
  // If we are not uploading data to a server, and instead invoking UploadCallback, in that case keep returning configured value
267
- let gap = config.lean === false && discoverBytes > 0 ? Setting.MinUploadDelay : envelope.data.sequence * config.delay;
268
- return typeof config.upload === Constant.String ? Math.max(Math.min(gap, Setting.MaxUploadDelay), Setting.MinUploadDelay) : config.delay;
313
+ const gap = config.lean === false && discoverBytes > 0 ? Setting.MinUploadDelay : envelope.data.sequence * config.delay;
314
+ return typeof config.upload === "string" ? Math.max(Math.min(gap, Setting.MaxUploadDelay), Setting.MinUploadDelay) : config.delay;
269
315
  }
270
316
 
271
317
  function response(payload: string): void {
272
- let lines = payload && payload.length > 0 ? payload.split("\n") : [];
273
- for (var line of lines)
274
- {
275
- let parts = line && line.length > 0 ? line.split(/ (.*)/) : [Constant.Empty];
318
+ const lines = payload && payload.length > 0 ? payload.split("\n") : [];
319
+ for (const line of lines) {
320
+ const parts = line && line.length > 0 ? line.split(/ (.*)/) : [Constant.Empty];
276
321
  switch (parts[0]) {
277
322
  case Constant.End:
278
323
  // Clear out session storage and end the session so we can start fresh the next time
@@ -284,13 +329,19 @@ function response(payload: string): void {
284
329
  break;
285
330
  case Constant.Action:
286
331
  // Invoke action callback, if configured and has a valid value
287
- if (config.action && parts.length > 1) { config.action(parts[1]); }
332
+ if (config.action && parts.length > 1) {
333
+ config.action(parts[1]);
334
+ }
288
335
  break;
289
336
  case Constant.Extract:
290
- if (parts.length > 1) { extract.trigger(parts[1]); }
337
+ if (parts.length > 1) {
338
+ extract.trigger(parts[1]);
339
+ }
291
340
  break;
292
341
  case Constant.Signal:
293
- if (parts.length > 1) { signalsEvent(parts[1]); }
342
+ if (parts.length > 1) {
343
+ signalsEvent(parts[1]);
344
+ }
294
345
  break;
295
346
  }
296
347
  }
@@ -1,4 +1,4 @@
1
- import { Constant, Event, IdentityData, Setting, VariableData } from "@clarity-types/data";
1
+ import { Constant, Event, type IdentityData, Setting, type VariableData } from "@clarity-types/data";
2
2
  import * as core from "@src/core";
3
3
  import { scrub } from "@src/core/scrub";
4
4
  import encode from "./encode";
@@ -10,23 +10,28 @@ export function start(): void {
10
10
  }
11
11
 
12
12
  export function set(variable: string, value: string | string[]): void {
13
- let values = typeof value === Constant.String ? [value as string] : value as string[];
13
+ const values = typeof value === "string" ? [value as string] : (value as string[]);
14
14
  log(variable, values);
15
15
  }
16
16
 
17
- export async function identify(userId: string, sessionId: string = null, pageId: string = null, userHint: string = null): Promise<IdentityData> {
18
- let output: IdentityData = { userId: await sha256(userId), userHint: userHint || redact(userId) };
17
+ export async function identify(
18
+ userId: string,
19
+ sessionId: string = null,
20
+ pageId: string = null,
21
+ userHint: string = null,
22
+ ): Promise<IdentityData> {
23
+ const output: IdentityData = { userId: await sha256(userId), userHint: userHint || redact(userId) };
19
24
 
20
25
  // By default, hash custom userId using SHA256 algorithm on the client to preserve privacy
21
26
  log(Constant.UserId, [output.userId]);
22
-
27
+
23
28
  // Optional non-identifying name for the user
24
29
  // If name is not explicitly provided, we automatically generate a redacted version of the userId
25
30
  log(Constant.UserHint, [output.userHint]);
26
- log(Constant.UserType, [detect(userId)]);
31
+ log(Constant.UserType, [detect(userId)]);
27
32
 
28
33
  // Log sessionId and pageId if provided
29
- if (sessionId) {
34
+ if (sessionId) {
30
35
  log(Constant.SessionId, [sessionId]);
31
36
  output.sessionId = sessionId;
32
37
  }
@@ -39,14 +44,12 @@ export async function identify(userId: string, sessionId: string = null, pageId:
39
44
  }
40
45
 
41
46
  function log(variable: string, value: string[]): void {
42
- if (core.active() &&
43
- variable &&
44
- value &&
45
- typeof variable === Constant.String &&
46
- variable.length < 255) {
47
- let validValues = variable in data ? data[variable] : [];
47
+ if (core.active() && variable && value && typeof variable === "string" && variable.length < 255) {
48
+ const validValues = variable in data ? data[variable] : [];
48
49
  for (let i = 0; i < value.length; i++) {
49
- if (typeof value[i] === Constant.String && value[i].length < 255) { validValues.push(value[i]); }
50
+ if (typeof value[i] === "string" && value[i].length < 255) {
51
+ validValues.push(value[i]);
52
+ }
50
53
  }
51
54
  data[variable] = validValues;
52
55
  }
@@ -65,8 +68,9 @@ export function stop(): void {
65
68
  }
66
69
 
67
70
  function redact(input: string): string {
68
- return input && input.length >= Setting.WordLength ?
69
- `${input.substring(0,2)}${scrub(input.substring(2), Constant.Asterix, Constant.Asterix)}` : scrub(input, Constant.Asterix, Constant.Asterix);
71
+ return input && input.length >= Setting.WordLength
72
+ ? `${input.substring(0, 2)}${scrub(input.substring(2), Constant.Asterix, Constant.Asterix)}`
73
+ : scrub(input, Constant.Asterix, Constant.Asterix);
70
74
  }
71
75
 
72
76
  async function sha256(input: string): Promise<string> {
@@ -74,11 +78,14 @@ async function sha256(input: string): Promise<string> {
74
78
  if (crypto && input) {
75
79
  // Reference: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string
76
80
  const buffer = await crypto.subtle.digest(Constant.SHA256, new TextEncoder().encode(input));
77
- return Array.prototype.map.call(new Uint8Array(buffer), (x: any) =>(('00'+x.toString(16)).slice(-2))).join('');
78
- } else { return Constant.Empty; }
79
- } catch { return Constant.Empty; }
81
+ return Array.prototype.map.call(new Uint8Array(buffer), (x) => `00${x.toString(16)}`.slice(-2)).join("");
82
+ }
83
+ return Constant.Empty;
84
+ } catch {
85
+ return Constant.Empty;
86
+ }
80
87
  }
81
88
 
82
89
  function detect(input: string): string {
83
90
  return input && input.indexOf(Constant.At) > 0 ? Constant.Email : Constant.String;
84
- }
91
+ }
@@ -1,4 +1,4 @@
1
- import { Event, Token } from "@clarity-types/data";
1
+ import { Event, type Token } from "@clarity-types/data";
2
2
  import * as scrub from "@src/core/scrub";
3
3
  import { time } from "@src/core/time";
4
4
  import { queue } from "@src/data/upload";
@@ -7,7 +7,7 @@ import * as internal from "@src/diagnostic/internal";
7
7
  import * as script from "@src/diagnostic/script";
8
8
 
9
9
  export default async function (type: Event): Promise<void> {
10
- let tokens: Token[] = [time(), type];
10
+ const tokens: Token[] = [time(), type];
11
11
 
12
12
  switch (type) {
13
13
  case Event.ScriptError:
@@ -1,5 +1,5 @@
1
1
  import { BooleanFlag, Event, IframeStatus, Metric, Setting } from "@clarity-types/data";
2
- import { FraudData } from "@clarity-types/diagnostic";
2
+ import type { FraudData } from "@clarity-types/diagnostic";
3
3
  import config from "@src/core/config";
4
4
  import hash from "@src/core/hash";
5
5
  import * as metric from "@src/data/metric";
@@ -13,11 +13,10 @@ export function start(): void {
13
13
  metric.max(Metric.Automation, navigator.webdriver ? BooleanFlag.True : BooleanFlag.False);
14
14
  try {
15
15
  // some sites (unintentionally) overwrite the window.self property, so we also check for the main window object
16
- metric.max(Metric.Iframed, window.top == window.self || window.top == window ? IframeStatus.TopFrame : IframeStatus.Iframe);
16
+ metric.max(Metric.Iframed, window.top === window.self || window.top === window ? IframeStatus.TopFrame : IframeStatus.Iframe);
17
17
  } catch (ex) {
18
18
  metric.max(Metric.Iframed, IframeStatus.Unknown);
19
19
  }
20
-
21
20
  }
22
21
 
23
22
  export function check(id: number, target: number, input: string): void {
@@ -34,4 +33,4 @@ export function check(id: number, target: number, input: string): void {
34
33
 
35
34
  export function stop(): void {
36
35
  history = [];
37
- }
36
+ }