clarity-js 0.8.12 → 0.8.13-beta

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 (91) hide show
  1. package/build/clarity.extended.js +1 -1
  2. package/build/clarity.insight.js +1 -1
  3. package/build/clarity.js +3952 -4179
  4. package/build/clarity.min.js +1 -1
  5. package/build/clarity.module.js +3952 -4179
  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 +28 -34
  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 +3 -3
  18. package/src/core/scrub.ts +20 -29
  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 +2 -2
  25. package/src/data/custom.ts +13 -8
  26. package/src/data/dimension.ts +7 -11
  27. package/src/data/encode.ts +30 -36
  28. package/src/data/envelope.ts +38 -38
  29. package/src/data/extract.ts +77 -86
  30. package/src/data/index.ts +6 -10
  31. package/src/data/limit.ts +1 -1
  32. package/src/data/metadata.ts +266 -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 +24 -35
  55. package/src/interaction/resize.ts +5 -5
  56. package/src/interaction/scroll.ts +11 -14
  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 +40 -60
  71. package/src/layout/schema.ts +8 -15
  72. package/src/layout/selector.ts +25 -47
  73. package/src/layout/style.ts +36 -44
  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 +29 -33
  86. package/types/diagnostic.d.ts +1 -1
  87. package/types/interaction.d.ts +4 -4
  88. package/types/layout.d.ts +21 -36
  89. package/types/performance.d.ts +5 -6
  90. package/.lintstagedrc.yml +0 -3
  91. package/biome.json +0 -43
package/src/layout/dom.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Privacy } from "@clarity-types/core";
2
2
  import { Code, Setting, Severity } from "@clarity-types/data";
3
- import { Constant, Mask, type NodeInfo, type NodeMeta, type NodeValue, Selector, type SelectorInput, Source } from "@clarity-types/layout";
3
+ import { Constant, Mask, NodeInfo, NodeMeta, NodeValue, Selector, SelectorInput, Source } from "@clarity-types/layout";
4
4
  import config from "@src/core/config";
5
5
  import { bind } from "@src/core/event";
6
6
  import hash from "@src/core/hash";
@@ -9,8 +9,8 @@ import * as internal from "@src/diagnostic/internal";
9
9
  import { removeObserver } from "@src/layout/node";
10
10
  import * as region from "@src/layout/region";
11
11
  import * as selector from "@src/layout/selector";
12
- let index = 1;
13
- let nodesMap: Map<number, Node> = null; // Maps id => node to retrieve further node details using id.
12
+ let index: number = 1;
13
+ let nodesMap: Map<Number, Node> = null; // Maps id => node to retrieve further node details using id.
14
14
  let values: NodeValue[] = [];
15
15
  let updateMap: number[] = [];
16
16
  let hashMap: { [hash: string]: number } = {};
@@ -24,7 +24,7 @@ let maskTags = [];
24
24
  // The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced
25
25
  let idMap: WeakMap<Node, number> = null; // Maps node => id.
26
26
  let iframeMap: WeakMap<Document, HTMLIFrameElement> = null; // Maps iframe's contentDocument => parent iframe element
27
- let iframeContentMap: WeakMap<HTMLIFrameElement, { doc: Document; win: Window }> = null; // Maps parent iframe element => iframe's contentDocument & contentWindow
27
+ let iframeContentMap: WeakMap<HTMLIFrameElement, { doc: Document, win: Window }> = null; // Maps parent iframe element => iframe's contentDocument & contentWindow
28
28
  let privacyMap: WeakMap<Node, Privacy> = null; // Maps node => Privacy (enum)
29
29
  let fraudMap: WeakMap<Node, number> = null; // Maps node => FraudId (number)
30
30
 
@@ -59,54 +59,26 @@ function reset(): void {
59
59
 
60
60
  // We parse new root nodes for any regions or masked nodes in the beginning (document) and
61
61
  // later whenever there are new additions or modifications to DOM (mutations)
62
- export function parse(root: ParentNode, init = false): void {
62
+ export function parse(root: ParentNode, init: boolean = false): void {
63
63
  // Wrap selectors in a try / catch block.
64
64
  // It's possible for script to receive invalid selectors, e.g. "'#id'" with extra quotes, and cause the code below to fail
65
65
  try {
66
- if (init) {
67
- // Parse unmask configuration into separate query selectors and override tokens as part of initialization
68
- for (const x of config.unmask) {
69
- if (x.indexOf(Constant.Bang) < 0) {
70
- unmask.push(x);
71
- } else {
72
- override.push(x.substr(1));
73
- }
74
- }
75
- }
66
+ // Parse unmask configuration into separate query selectors and override tokens as part of initialization
67
+ if (init) { config.unmask.forEach(x => x.indexOf(Constant.Bang) < 0 ? unmask.push(x) : override.push(x.substr(1))); }
76
68
 
77
69
  // Since mutations may happen on leaf nodes too, e.g. text nodes, which may not support all selector APIs.
78
70
  // We ensure that the root note supports querySelectorAll API before executing the code below to identify new regions.
79
71
  if ("querySelectorAll" in root) {
80
- for (const x of config.regions) {
81
- for (const e of Array.from(root.querySelectorAll(x[1]))) {
82
- region.observe(e, `${x[0]}`);
83
- }
84
- }
85
- for (const x of config.mask) {
86
- for (const e of Array.from(root.querySelectorAll(x))) {
87
- privacyMap.set(e, Privacy.TextImage);
88
- }
89
- }
90
- for (const x of config.checksum) {
91
- for (const e of Array.from(root.querySelectorAll(x[1]))) {
92
- fraudMap.set(e, x[0]);
93
- }
94
- }
95
- for (const x of unmask) {
96
- for (const e of Array.from(root.querySelectorAll(x))) {
97
- privacyMap.set(e, Privacy.None);
98
- }
99
- }
72
+ config.regions.forEach(x => root.querySelectorAll(x[1]).forEach(e => region.observe(e, `${x[0]}`))); // Regions
73
+ config.mask.forEach(x => root.querySelectorAll(x).forEach(e => privacyMap.set(e, Privacy.TextImage))); // Masked Elements
74
+ config.checksum.forEach(x => root.querySelectorAll(x[1]).forEach(e => fraudMap.set(e, x[0]))); // Fraud Checksum Check
75
+ unmask.forEach(x => root.querySelectorAll(x).forEach(e => privacyMap.set(e, Privacy.None))); // Unmasked Elements
100
76
  }
101
- } catch (e) {
102
- internal.log(Code.Selector, Severity.Warning, e ? e.name : null);
103
- }
77
+ } catch (e) { internal.log(Code.Selector, Severity.Warning, e ? e.name : null); }
104
78
  }
105
79
 
106
- export function getId(node: Node, autogen = false): number {
107
- if (node === null) {
108
- return null;
109
- }
80
+ export function getId(node: Node, autogen: boolean = false): number {
81
+ if (node === null) { return null; }
110
82
  let id = idMap.get(node);
111
83
  if (!id && autogen) {
112
84
  id = index++;
@@ -117,19 +89,19 @@ export function getId(node: Node, autogen = false): number {
117
89
  }
118
90
 
119
91
  export function add(node: Node, parent: Node, data: NodeInfo, source: Source): void {
120
- const parentId = parent ? getId(parent) : null;
92
+ let parentId = parent ? getId(parent) : null;
121
93
 
122
94
  // Do not add detached nodes
123
95
  if ((!parent || !parentId) && (node as ShadowRoot).host == null && node.nodeType !== Node.DOCUMENT_TYPE_NODE) {
124
96
  return;
125
97
  }
126
98
 
127
- const id = getId(node, true);
128
- const previousId = getPreviousId(node);
99
+ let id = getId(node, true);
100
+ let previousId = getPreviousId(node);
129
101
  let parentValue: NodeValue = null;
130
102
  let regionId = region.exists(node) ? id : null;
131
103
  let fraudId = fraudMap.has(node) ? fraudMap.get(node) : null;
132
- let privacyId = config.content ? Privacy.Sensitive : Privacy.TextImage;
104
+ let privacyId = config.content ? Privacy.Sensitive : Privacy.TextImage
133
105
  if (parentId >= 0 && values[parentId]) {
134
106
  parentValue = values[parentId];
135
107
  parentValue.children.push(id);
@@ -164,14 +136,14 @@ export function add(node: Node, parent: Node, data: NodeInfo, source: Source): v
164
136
  }
165
137
 
166
138
  export function update(node: Node, parent: Node, data: NodeInfo, source: Source): void {
167
- const id = getId(node);
168
- const parentId = parent ? getId(parent) : null;
169
- const previousId = getPreviousId(node);
139
+ let id = getId(node);
140
+ let parentId = parent ? getId(parent) : null;
141
+ let previousId = getPreviousId(node);
170
142
  let changed = false;
171
143
  let parentChanged = false;
172
144
 
173
145
  if (id in values) {
174
- const value = values[id];
146
+ let value = values[id];
175
147
  value.metadata.active = true;
176
148
 
177
149
  // Handle case where internal ordering may have changed
@@ -183,11 +155,11 @@ export function update(node: Node, parent: Node, data: NodeInfo, source: Source)
183
155
  // Handle case where parent might have been updated
184
156
  if (value.parent !== parentId) {
185
157
  changed = true;
186
- const oldParentId = value.parent;
158
+ let oldParentId = value.parent;
187
159
  value.parent = parentId;
188
160
  // Move this node to the right location under new parent
189
161
  if (parentId !== null && parentId >= 0) {
190
- const childIndex = previousId === null ? 0 : values[parentId].children.indexOf(previousId) + 1;
162
+ let childIndex = previousId === null ? 0 : values[parentId].children.indexOf(previousId) + 1;
191
163
  values[parentId].children.splice(childIndex, 0, id);
192
164
  // Update region after the move
193
165
  value.region = region.exists(node) ? id : values[parentId].region;
@@ -198,7 +170,7 @@ export function update(node: Node, parent: Node, data: NodeInfo, source: Source)
198
170
 
199
171
  // Remove reference to this node from the old parent
200
172
  if (oldParentId !== null && oldParentId >= 0) {
201
- const nodeIndex = values[oldParentId].children.indexOf(id);
173
+ let nodeIndex = values[oldParentId].children.indexOf(id);
202
174
  if (nodeIndex >= 0) {
203
175
  values[oldParentId].children.splice(nodeIndex, 1);
204
176
  }
@@ -207,10 +179,10 @@ export function update(node: Node, parent: Node, data: NodeInfo, source: Source)
207
179
  }
208
180
 
209
181
  // Update data
210
- for (const key in data) {
211
- if (diff(value.data, data, key)) {
182
+ for (let key in data) {
183
+ if (diff(value["data"], data, key)) {
212
184
  changed = true;
213
- value.data[key] = data[key];
185
+ value["data"][key] = data[key];
214
186
  }
215
187
  }
216
188
 
@@ -223,30 +195,28 @@ export function update(node: Node, parent: Node, data: NodeInfo, source: Source)
223
195
  export function sameorigin(node: Node): boolean {
224
196
  let output = false;
225
197
  if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).tagName === Constant.IFrameTag) {
226
- const frame = node as HTMLIFrameElement;
198
+ let frame = node as HTMLIFrameElement;
227
199
  // To determine if the iframe is same-origin or not, we try accessing it's contentDocument.
228
200
  // If the browser throws an exception, we assume it's cross-origin and move on.
229
201
  // However, if we do a get a valid document object back, we assume the contents are accessible and iframe is same-origin.
230
202
  try {
231
- const doc = frame.contentDocument;
203
+ let doc = frame.contentDocument;
232
204
  if (doc) {
233
205
  iframeMap.set(frame.contentDocument, frame);
234
206
  iframeContentMap.set(frame, { doc: frame.contentDocument, win: frame.contentWindow });
235
207
  output = true;
236
208
  }
237
- } catch {
238
- /* do nothing */
239
- }
209
+ } catch { /* do nothing */ }
240
210
  }
241
211
  return output;
242
212
  }
243
213
 
244
214
  export function iframe(node: Node): HTMLIFrameElement {
245
- const doc = node.nodeType === Node.DOCUMENT_NODE ? (node as Document) : null;
215
+ let doc = node.nodeType === Node.DOCUMENT_NODE ? node as Document : null;
246
216
  return doc && iframeMap.has(doc) ? iframeMap.get(doc) : null;
247
217
  }
248
218
 
249
- export function iframeContent(frame: HTMLIFrameElement): { doc: Document; win: Window } {
219
+ export function iframeContent(frame: HTMLIFrameElement): {doc: Document, win: Window } {
250
220
  if (iframeContentMap.has(frame)) {
251
221
  return iframeContentMap.get(frame);
252
222
  }
@@ -259,28 +229,26 @@ export function removeIFrame(frame: HTMLIFrameElement, doc: Document): void {
259
229
  }
260
230
 
261
231
  function privacy(node: Node, value: NodeValue, parent: NodeValue): void {
262
- const data = value.data;
263
- const metadata = value.metadata;
264
- const current = metadata.privacy;
265
- const attributes = data.attributes || {};
266
- const tag = data.tag.toUpperCase();
232
+ let data = value.data;
233
+ let metadata = value.metadata;
234
+ let current = metadata.privacy;
235
+ let attributes = data.attributes || {};
236
+ let tag = data.tag.toUpperCase();
267
237
 
268
238
  switch (true) {
269
- case maskTags.indexOf(tag) >= 0: {
270
- const type = attributes[Constant.Type];
239
+ case maskTags.indexOf(tag) >= 0:
240
+ let type = attributes[Constant.Type];
271
241
  let meta: string = Constant.Empty;
272
- const excludedPrivacyAttributes = [Constant.Class, Constant.Style];
273
- for (const x of Object.keys(attributes).filter((x) => !excludedPrivacyAttributes.includes(x as Constant))) {
274
- meta += attributes[x].toLowerCase();
275
- }
276
- const exclude = maskExclude.some((x) => meta.indexOf(x) >= 0);
242
+ const excludedPrivacyAttributes = [Constant.Class, Constant.Style]
243
+ Object.keys(attributes)
244
+ .filter((x) => !excludedPrivacyAttributes.includes(x as Constant))
245
+ .forEach((x) => (meta += attributes[x].toLowerCase()));
246
+ let exclude = maskExclude.some((x) => meta.indexOf(x) >= 0);
277
247
  // Regardless of privacy mode, always mask off user input from input boxes or drop downs with two exceptions:
278
248
  // (1) The node is detected to be one of the excluded fields, in which case we drop everything
279
249
  // (2) The node's type is one of the allowed types (like checkboxes)
280
- metadata.privacy =
281
- tag === Constant.InputTag && maskDisable.indexOf(type) >= 0 ? current : exclude ? Privacy.Exclude : Privacy.Text;
250
+ metadata.privacy = tag === Constant.InputTag && maskDisable.indexOf(type) >= 0 ? current : (exclude ? Privacy.Exclude : Privacy.Text);
282
251
  break;
283
- }
284
252
  case Constant.MaskData in attributes:
285
253
  metadata.privacy = Privacy.TextImage;
286
254
  break;
@@ -295,29 +263,28 @@ function privacy(node: Node, value: NodeValue, parent: NodeValue): void {
295
263
  // If this node was explicitly configured to be evaluated for fraud, then also mask content
296
264
  metadata.privacy = Privacy.Text;
297
265
  break;
298
- case tag === Constant.TextTag: {
266
+ case tag === Constant.TextTag:
299
267
  // If it's a text node belonging to a STYLE or TITLE tag or one of scrub exceptions, then capture content
300
- const pTag = parent?.data ? parent.data.tag : Constant.Empty;
301
- const pSelector = parent?.selector ? parent.selector[Selector.Default] : Constant.Empty;
302
- const tags: string[] = [Constant.StyleTag, Constant.TitleTag, Constant.SvgStyle];
303
- metadata.privacy = tags.includes(pTag) || override.some((x) => pSelector.indexOf(x) >= 0) ? Privacy.None : current;
268
+ let pTag = parent && parent.data ? parent.data.tag : Constant.Empty;
269
+ let pSelector = parent && parent.selector ? parent.selector[Selector.Default] : Constant.Empty;
270
+ let tags: string[] = [Constant.StyleTag, Constant.TitleTag, Constant.SvgStyle];
271
+ metadata.privacy = tags.includes(pTag) || override.some(x => pSelector.indexOf(x) >= 0) ? Privacy.None : current;
304
272
  break;
305
- }
306
273
  case current === Privacy.Sensitive:
307
274
  // In a mode where we mask sensitive information by default, look through class names to aggressively mask content
308
275
  metadata.privacy = inspect(attributes[Constant.Class], maskText, metadata);
309
276
  break;
310
277
  case tag === Constant.ImageTag:
311
278
  // Mask images with blob src as it is not publicly available anyway.
312
- if (attributes.src?.startsWith("blob:")) {
279
+ if(attributes.src?.startsWith('blob:')){
313
280
  metadata.privacy = Privacy.TextImage;
314
281
  }
315
- break;
282
+ break;
316
283
  }
317
284
  }
318
285
 
319
286
  function inspect(input: string, lookup: string[], metadata: NodeMeta): Privacy {
320
- if (input && lookup.some((x) => input.indexOf(x) >= 0)) {
287
+ if (input && lookup.some(x => input.indexOf(x) >= 0)) {
321
288
  return Privacy.Text;
322
289
  }
323
290
  return metadata.privacy;
@@ -325,16 +292,8 @@ function inspect(input: string, lookup: string[], metadata: NodeMeta): Privacy {
325
292
 
326
293
  function diff(a: NodeInfo, b: NodeInfo, field: string): boolean {
327
294
  if (typeof a[field] === "object" && typeof b[field] === "object") {
328
- for (const key in a[field]) {
329
- if (a[field][key] !== b[field][key]) {
330
- return true;
331
- }
332
- }
333
- for (const key in b[field]) {
334
- if (b[field][key] !== a[field][key]) {
335
- return true;
336
- }
337
- }
295
+ for (let key in a[field]) { if (a[field][key] !== b[field][key]) { return true; } }
296
+ for (let key in b[field]) { if (b[field][key] !== a[field][key]) { return true; } }
338
297
  return false;
339
298
  }
340
299
  return a[field] !== b[field];
@@ -344,7 +303,7 @@ function position(parent: NodeValue, child: NodeValue): number {
344
303
  child.metadata.position = 1;
345
304
  let idx = parent ? parent.children.indexOf(child.id) : -1;
346
305
  while (idx-- > 0) {
347
- const sibling = values[parent.children[idx]];
306
+ let sibling = values[parent.children[idx]];
348
307
  if (child.data.tag === sibling.data.tag) {
349
308
  child.metadata.position = sibling.metadata.position + 1;
350
309
  break;
@@ -354,22 +313,20 @@ function position(parent: NodeValue, child: NodeValue): number {
354
313
  }
355
314
 
356
315
  function updateSelector(value: NodeValue): void {
357
- const parent = value.parent && value.parent in values ? values[value.parent] : null;
358
- const prefix = parent ? parent.selector : null;
359
- const d = value.data;
360
- const p = position(parent, value);
361
- const s: SelectorInput = { id: value.id, tag: d.tag, prefix, position: p, attributes: d.attributes };
316
+ let parent = value.parent && value.parent in values ? values[value.parent] : null;
317
+ let prefix = parent ? parent.selector : null;
318
+ let d = value.data;
319
+ let p = position(parent, value);
320
+ let s: SelectorInput = { id: value.id, tag: d.tag, prefix, position: p, attributes: d.attributes };
362
321
  value.selector = [selector.get(s, Selector.Alpha), selector.get(s, Selector.Beta)];
363
- value.hash = value.selector.map((x) => (x ? hash(x) : null)) as [string, string];
364
- for (const h of value.hash) {
365
- hashMap[h] = value.id;
366
- }
322
+ value.hash = value.selector.map(x => x ? hash(x) : null) as [string, string];
323
+ value.hash.forEach(h => hashMap[h] = value.id);
367
324
  }
368
325
 
369
326
  export function hashText(hash: string): string {
370
- const id = lookup(hash);
371
- const node = getNode(id);
372
- return node !== null && node.textContent !== null ? node.textContent.substr(0, Setting.ClickText) : "";
327
+ let id = lookup(hash);
328
+ let node = getNode(id);
329
+ return node !== null && node.textContent !== null ? node.textContent.substr(0, Setting.ClickText) : '';
373
330
  }
374
331
 
375
332
  export function getNode(id: number): Node {
@@ -384,7 +341,7 @@ export function getValue(id: number): NodeValue {
384
341
  }
385
342
 
386
343
  export function get(node: Node): NodeValue {
387
- const id = getId(node);
344
+ let id = getId(node);
388
345
  return id in values ? values[id] : null;
389
346
  }
390
347
 
@@ -397,11 +354,9 @@ export function has(node: Node): boolean {
397
354
  }
398
355
 
399
356
  export function updates(): NodeValue[] {
400
- const output = [];
401
- for (const id of updateMap) {
402
- if (id in values) {
403
- output.push(values[id]);
404
- }
357
+ let output = [];
358
+ for (let id of updateMap) {
359
+ if (id in values) { output.push(values[id]); }
405
360
  }
406
361
  updateMap = [];
407
362
 
@@ -410,7 +365,7 @@ export function updates(): NodeValue[] {
410
365
 
411
366
  function remove(id: number, source: Source): void {
412
367
  if (id in values) {
413
- const value = values[id];
368
+ let value = values[id];
414
369
  value.metadata.active = false;
415
370
  value.parent = null;
416
371
  track(id, source);
@@ -422,22 +377,22 @@ function remove(id: number, source: Source): void {
422
377
 
423
378
  function removeNodeFromNodesMap(id: number) {
424
379
  const nodeToBeRemoved = nodesMap.get(id);
425
- // Shadow dom roots shouldn't be deleted,
380
+ // Shadow dom roots shouldn't be deleted,
426
381
  // we should keep listening to the mutations there even they're not rendered in the DOM.
427
- if (nodeToBeRemoved?.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
382
+ if(nodeToBeRemoved?.nodeType === Node.DOCUMENT_FRAGMENT_NODE){
428
383
  return;
429
384
  }
430
385
 
431
- if (nodeToBeRemoved?.nodeType === Node.ELEMENT_NODE && (nodeToBeRemoved as Element).tagName === "IFRAME") {
386
+ if (nodeToBeRemoved && nodeToBeRemoved?.nodeType === Node.ELEMENT_NODE && nodeToBeRemoved["tagName"] === "IFRAME") {
432
387
  const iframe = nodeToBeRemoved as HTMLIFrameElement;
433
388
  removeObserver(iframe);
434
389
  }
435
390
 
436
391
  nodesMap.delete(id);
437
392
 
438
- const value = id in values ? values[id] : null;
439
- if (value?.children) {
440
- for (const childId of value.children) {
393
+ let value = id in values ? values[id] : null;
394
+ if (value && value.children) {
395
+ for (let childId of value.children) {
441
396
  removeNodeFromNodesMap(childId);
442
397
  }
443
398
  }
@@ -445,22 +400,21 @@ function removeNodeFromNodesMap(id: number) {
445
400
 
446
401
  function updateImageSize(value: NodeValue): void {
447
402
  // If this element is a image node, and is masked, then track box model for the current element
448
- if (value.data.tag === Constant.ImageTag && value.metadata.privacy === Privacy.TextImage) {
449
- const img = getNode(value.id) as HTMLImageElement;
450
- // We will not capture the natural image dimensions until it loads.
451
- if (img && (!img.complete || img.naturalWidth === 0)) {
403
+ if (value.data.tag === Constant.ImageTag && value.metadata.privacy === Privacy.TextImage) {
404
+ let img = getNode(value.id) as HTMLImageElement;
405
+ // We will not capture the natural image dimensions until it loads.
406
+ if(img && (!img.complete || img.naturalWidth === 0)){
452
407
  // This will trigger mutation to update the original width and height after image loads.
453
- bind(img, "load", () => {
454
- img.setAttribute("data-clarity-loaded", `${shortid()}`);
455
- });
408
+ bind(img, 'load', () => {
409
+ img.setAttribute('data-clarity-loaded', `${shortid()}`);
410
+ })
456
411
  }
457
412
  value.metadata.size = [];
458
- }
413
+ }
459
414
  }
460
415
 
461
- function getPreviousId(inputNode: Node): number {
416
+ function getPreviousId(node: Node): number {
462
417
  let id = null;
463
- let node = inputNode;
464
418
 
465
419
  // Some nodes may not have an ID by design since Clarity skips over tags like SCRIPT, NOSCRIPT, META, COMMENTS, etc..
466
420
  // In that case, we keep going back and check for their sibling until we find a sibling with ID or no more sibling nodes are left.
@@ -471,19 +425,15 @@ function getPreviousId(inputNode: Node): number {
471
425
  return id;
472
426
  }
473
427
 
474
- function track(id: number, source: Source, changed = true, parentChanged = false): void {
475
- if (config.lean && config.lite) {
476
- return;
477
- }
428
+ function track(id: number, source: Source, changed: boolean = true, parentChanged: boolean = false): void {
429
+ if (config.lean && config.lite) { return; }
478
430
 
479
431
  // Keep track of the order in which mutations happened, they may not be sequential
480
432
  // Edge case: If an element is added later on, and pre-discovered element is moved as a child.
481
433
  // In that case, we need to reorder the pre-discovered element in the update list to keep visualization consistent.
482
- const uIndex = updateMap.indexOf(id);
434
+ let uIndex = updateMap.indexOf(id);
483
435
  if (uIndex >= 0 && source === Source.ChildListAdd && parentChanged) {
484
436
  updateMap.splice(uIndex, 1);
485
437
  updateMap.push(id);
486
- } else if (uIndex === -1 && changed) {
487
- updateMap.push(id);
488
- }
438
+ } else if (uIndex === -1 && changed) { updateMap.push(id); }
489
439
  }
@@ -1,34 +1,33 @@
1
- import { Privacy, Task, type Timer } from "@clarity-types/core";
2
- import { Event, Setting, type Token } from "@clarity-types/data";
3
- import { Constant, type NodeInfo, type NodeValue } from "@clarity-types/layout";
1
+ import { Privacy, Task, Timer } from "@clarity-types/core";
2
+ import { Event, Setting, Token } from "@clarity-types/data";
3
+ import { Constant, NodeInfo, NodeValue } from "@clarity-types/layout";
4
4
  import config from "@src/core/config";
5
5
  import * as scrub from "@src/core/scrub";
6
6
  import * as task from "@src/core/task";
7
7
  import { time } from "@src/core/time";
8
- import * as baseline from "@src/data/baseline";
9
8
  import tokenize from "@src/data/token";
9
+ import * as baseline from "@src/data/baseline";
10
10
  import { queue } from "@src/data/upload";
11
11
  import * as fraud from "@src/diagnostic/fraud";
12
- import * as animation from "@src/layout/animation";
13
12
  import * as doc from "@src/layout/document";
14
13
  import * as dom from "@src/layout/dom";
15
14
  import * as region from "@src/layout/region";
16
15
  import * as style from "@src/layout/style";
16
+ import * as animation from "@src/layout/animation";
17
17
 
18
18
  export default async function (type: Event, timer: Timer = null, ts: number = null): Promise<void> {
19
- const eventTime = ts || time();
19
+ let eventTime = ts || time()
20
20
  let tokens: Token[] = [eventTime, type];
21
21
  switch (type) {
22
- case Event.Document: {
23
- const d = doc.data;
22
+ case Event.Document:
23
+ let d = doc.data;
24
24
  tokens.push(d.width);
25
25
  tokens.push(d.height);
26
26
  baseline.track(type, d.width, d.height);
27
27
  queue(tokens);
28
28
  break;
29
- }
30
29
  case Event.Region:
31
- for (const r of region.state) {
30
+ for (let r of region.state) {
32
31
  tokens = [r.time, Event.Region];
33
32
  tokens.push(r.data.id);
34
33
  tokens.push(r.data.interaction);
@@ -40,14 +39,14 @@ export default async function (type: Event, timer: Timer = null, ts: number = nu
40
39
  break;
41
40
  case Event.StyleSheetAdoption:
42
41
  case Event.StyleSheetUpdate:
43
- for (const entry of style.sheetAdoptionState) {
42
+ for (let entry of style.sheetAdoptionState) {
44
43
  tokens = [entry.time, entry.event];
45
44
  tokens.push(entry.data.id);
46
45
  tokens.push(entry.data.operation);
47
46
  tokens.push(entry.data.newIds);
48
47
  queue(tokens);
49
48
  }
50
- for (const entry of style.sheetUpdateState) {
49
+ for (let entry of style.sheetUpdateState) {
51
50
  tokens = [entry.time, entry.event];
52
51
  tokens.push(entry.data.id);
53
52
  tokens.push(entry.data.operation);
@@ -57,7 +56,7 @@ export default async function (type: Event, timer: Timer = null, ts: number = nu
57
56
  style.reset();
58
57
  break;
59
58
  case Event.Animation:
60
- for (const entry of animation.state) {
59
+ for (let entry of animation.state) {
61
60
  tokens = [entry.time, entry.event];
62
61
  tokens.push(entry.data.id);
63
62
  tokens.push(entry.data.operation);
@@ -70,51 +69,40 @@ export default async function (type: Event, timer: Timer = null, ts: number = nu
70
69
  animation.reset();
71
70
  break;
72
71
  case Event.Discover:
73
- case Event.Mutation: {
72
+ case Event.Mutation:
74
73
  // Check if we are operating within the context of the current page
75
- if (task.state(timer) === Task.Stop) {
76
- break;
77
- }
78
- const values = dom.updates();
74
+ if (task.state(timer) === Task.Stop) { break; }
75
+ let values = dom.updates();
79
76
  // Only encode and queue DOM updates if we have valid updates to report back
80
77
  if (values.length > 0) {
81
- for (const value of values) {
78
+ for (let value of values) {
82
79
  let state = task.state(timer);
83
- if (state === Task.Wait) {
84
- state = await task.suspend(timer);
85
- }
86
- if (state === Task.Stop) {
87
- break;
88
- }
89
- const data: NodeInfo = value.data;
90
- const active = value.metadata.active;
91
- const suspend = value.metadata.suspend;
92
- const privacy = value.metadata.privacy;
93
- const mangle = shouldMangle(value);
94
- const keys = active ? ["tag", "attributes", "value"] : ["tag"];
95
- for (const key of keys) {
80
+ if (state === Task.Wait) { state = await task.suspend(timer); }
81
+ if (state === Task.Stop) { break; }
82
+ let data: NodeInfo = value.data;
83
+ let active = value.metadata.active;
84
+ let suspend = value.metadata.suspend;
85
+ let privacy = value.metadata.privacy;
86
+ let mangle = shouldMangle(value);
87
+ let keys = active ? ["tag", "attributes", "value"] : ["tag"];
88
+ for (let key of keys) {
96
89
  // we check for data[key] === '' because we want to encode empty strings as well, especially for value - which if skipped can cause our decoder to assume the final
97
90
  // attribute was the value for the node
98
- if (data[key] || data[key] === "") {
91
+ if (data[key] || data[key] === '') {
99
92
  switch (key) {
100
- case "tag": {
101
- const box = size(value);
102
- const factor = mangle ? -1 : 1;
93
+ case "tag":
94
+ let box = size(value);
95
+ let factor = mangle ? -1 : 1;
103
96
  tokens.push(value.id * factor);
104
- if (value.parent && active) {
105
- tokens.push(value.parent);
106
- if (value.previous) {
107
- tokens.push(value.previous);
108
- }
97
+ if (value.parent && active) {
98
+ tokens.push(value.parent);
99
+ if (value.previous) { tokens.push(value.previous); }
109
100
  }
110
101
  tokens.push(suspend ? Constant.SuspendMutationTag : data[key]);
111
- if (box && box.length === 2) {
112
- tokens.push(`${Constant.Hash}${str(box[0])}.${str(box[1])}`);
113
- }
102
+ if (box && box.length === 2) { tokens.push(`${Constant.Hash}${str(box[0])}.${str(box[1])}`); }
114
103
  break;
115
- }
116
104
  case "attributes":
117
- for (const attr in data[key]) {
105
+ for (let attr in data[key]) {
118
106
  if (data[key][attr] !== undefined) {
119
107
  tokens.push(attribute(attr, data[key][attr], privacy));
120
108
  }
@@ -128,24 +116,21 @@ export default async function (type: Event, timer: Timer = null, ts: number = nu
128
116
  }
129
117
  }
130
118
  }
131
- if (type === Event.Mutation) {
132
- baseline.activity(eventTime);
133
- }
119
+ if (type === Event.Mutation) { baseline.activity(eventTime); }
134
120
  queue(tokenize(tokens), !config.lean);
135
121
  }
136
122
  break;
137
- }
138
123
  }
139
124
  }
140
125
 
141
126
  function shouldMangle(value: NodeValue): boolean {
142
- const privacy = value.metadata.privacy;
127
+ let privacy = value.metadata.privacy;
143
128
  return value.data.tag === Constant.TextTag && !(privacy === Privacy.None || privacy === Privacy.Sensitive);
144
129
  }
145
130
 
146
131
  function size(value: NodeValue): number[] {
147
132
  if (value.metadata.size !== null && value.metadata.size.length === 0) {
148
- const img = dom.getNode(value.id) as HTMLImageElement;
133
+ let img = dom.getNode(value.id) as HTMLImageElement;
149
134
  if (img) {
150
135
  return [Math.floor(img.offsetWidth * Setting.BoxPrecision), Math.floor(img.offsetHeight * Setting.BoxPrecision)];
151
136
  }