clarity-js 0.8.11 → 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 (90) hide show
  1. package/build/clarity.extended.js +1 -1
  2. package/build/clarity.insight.js +1 -1
  3. package/build/clarity.js +2964 -3181
  4. package/build/clarity.min.js +1 -1
  5. package/build/clarity.module.js +2964 -3181
  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/config.ts +2 -2
  11. package/src/core/event.ts +32 -36
  12. package/src/core/hash.ts +6 -5
  13. package/src/core/history.ts +11 -10
  14. package/src/core/index.ts +11 -21
  15. package/src/core/measure.ts +5 -9
  16. package/src/core/report.ts +3 -3
  17. package/src/core/scrub.ts +20 -29
  18. package/src/core/task.ts +45 -73
  19. package/src/core/time.ts +3 -3
  20. package/src/core/timeout.ts +2 -2
  21. package/src/core/version.ts +1 -1
  22. package/src/data/baseline.ts +55 -60
  23. package/src/data/consent.ts +2 -2
  24. package/src/data/custom.ts +13 -8
  25. package/src/data/dimension.ts +7 -11
  26. package/src/data/encode.ts +30 -36
  27. package/src/data/envelope.ts +38 -38
  28. package/src/data/extract.ts +77 -86
  29. package/src/data/index.ts +6 -10
  30. package/src/data/limit.ts +1 -1
  31. package/src/data/metadata.ts +266 -305
  32. package/src/data/metric.ts +8 -18
  33. package/src/data/ping.ts +4 -8
  34. package/src/data/signal.ts +18 -18
  35. package/src/data/summary.ts +4 -6
  36. package/src/data/token.ts +8 -10
  37. package/src/data/upgrade.ts +3 -7
  38. package/src/data/upload.ts +49 -100
  39. package/src/data/variable.ts +20 -27
  40. package/src/diagnostic/encode.ts +2 -2
  41. package/src/diagnostic/fraud.ts +4 -3
  42. package/src/diagnostic/internal.ts +5 -11
  43. package/src/diagnostic/script.ts +8 -12
  44. package/src/global.ts +1 -1
  45. package/src/insight/blank.ts +4 -4
  46. package/src/insight/encode.ts +17 -23
  47. package/src/insight/snapshot.ts +37 -57
  48. package/src/interaction/change.ts +6 -9
  49. package/src/interaction/click.ts +28 -34
  50. package/src/interaction/clipboard.ts +2 -2
  51. package/src/interaction/encode.ts +31 -35
  52. package/src/interaction/input.ts +9 -11
  53. package/src/interaction/pointer.ts +24 -35
  54. package/src/interaction/resize.ts +5 -5
  55. package/src/interaction/scroll.ts +11 -14
  56. package/src/interaction/selection.ts +8 -12
  57. package/src/interaction/submit.ts +2 -2
  58. package/src/interaction/timeline.ts +9 -13
  59. package/src/interaction/unload.ts +1 -1
  60. package/src/interaction/visibility.ts +2 -2
  61. package/src/layout/animation.ts +41 -47
  62. package/src/layout/discover.ts +5 -5
  63. package/src/layout/document.ts +19 -31
  64. package/src/layout/dom.ts +91 -141
  65. package/src/layout/encode.ts +37 -52
  66. package/src/layout/mutation.ts +318 -321
  67. package/src/layout/node.ts +81 -104
  68. package/src/layout/offset.ts +6 -7
  69. package/src/layout/region.ts +40 -60
  70. package/src/layout/schema.ts +8 -15
  71. package/src/layout/selector.ts +25 -47
  72. package/src/layout/style.ts +36 -44
  73. package/src/layout/target.ts +10 -14
  74. package/src/layout/traverse.ts +11 -17
  75. package/src/performance/blank.ts +1 -1
  76. package/src/performance/encode.ts +4 -4
  77. package/src/performance/interaction.ts +70 -58
  78. package/src/performance/navigation.ts +2 -2
  79. package/src/performance/observer.ts +26 -59
  80. package/src/queue.ts +9 -16
  81. package/tsconfig.json +1 -1
  82. package/tslint.json +32 -25
  83. package/types/core.d.ts +13 -13
  84. package/types/data.d.ts +29 -32
  85. package/types/diagnostic.d.ts +1 -1
  86. package/types/interaction.d.ts +4 -4
  87. package/types/layout.d.ts +21 -36
  88. package/types/performance.d.ts +5 -6
  89. package/.lintstagedrc.yml +0 -3
  90. package/biome.json +0 -43
@@ -1,47 +1,46 @@
1
- import { Code, Dimension, Severity } from "@clarity-types/data";
2
1
  import { Constant, Source } from "@clarity-types/layout";
2
+ import { Code, Dimension, Severity } from "@clarity-types/data";
3
+ import * as dom from "./dom";
3
4
  import * as event from "@src/core/event";
4
5
  import * as dimension from "@src/data/dimension";
5
- import { electron } from "@src/data/metadata";
6
6
  import * as internal from "@src/diagnostic/internal";
7
7
  import * as interaction from "@src/interaction";
8
8
  import * as mutation from "@src/layout/mutation";
9
9
  import * as schema from "@src/layout/schema";
10
10
  import { checkDocumentStyles } from "@src/layout/style";
11
- import * as dom from "./dom";
11
+ import { electron } from "@src/data/metadata";
12
12
 
13
13
  const IGNORE_ATTRIBUTES = ["title", "alt", "onload", "onfocus", "onerror", "data-drupal-form-submit-last", "aria-label"];
14
14
  const newlineRegex = /[\r\n]+/g;
15
15
 
16
- export default function (inputNode: Node, source: Source, timestamp: number): Node {
17
- let node = inputNode;
16
+ export default function (node: Node, source: Source, timestamp: number): Node {
18
17
  let child: Node = null;
19
18
 
20
19
  // Do not track this change if we are attempting to remove a node before discovering it
21
- if (source === Source.ChildListRemove && dom.has(node) === false) {
22
- return child;
23
- }
20
+ if (source === Source.ChildListRemove && dom.has(node) === false) { return child; }
24
21
 
25
22
  // Special handling for text nodes that belong to style nodes
26
- if (source !== Source.Discover && node.nodeType === Node.TEXT_NODE && node.parentElement && node.parentElement.tagName === "STYLE") {
23
+ if (source !== Source.Discover &&
24
+ node.nodeType === Node.TEXT_NODE &&
25
+ node.parentElement &&
26
+ node.parentElement.tagName === "STYLE") {
27
27
  node = node.parentNode;
28
28
  }
29
29
 
30
- const add = dom.has(node) === false;
31
- const call = add ? "add" : "update";
30
+ let add = dom.has(node) === false;
31
+ let call = add ? "add" : "update";
32
32
  let parent = node.parentElement ? node.parentElement : null;
33
- const insideFrame = node.ownerDocument !== document;
33
+ let insideFrame = node.ownerDocument !== document;
34
34
  switch (node.nodeType) {
35
- case Node.DOCUMENT_TYPE_NODE: {
35
+ case Node.DOCUMENT_TYPE_NODE:
36
36
  parent = insideFrame && node.parentNode ? dom.iframe(node.parentNode) : parent;
37
- const docTypePrefix = insideFrame ? Constant.IFramePrefix : Constant.Empty;
38
- const doctype = node as DocumentType;
39
- const docName = doctype.name ? doctype.name : Constant.HTML;
40
- const docAttributes = { name: docName, publicId: doctype.publicId, systemId: doctype.systemId };
41
- const docData = { tag: docTypePrefix + Constant.DocumentTag, attributes: docAttributes };
37
+ let docTypePrefix = insideFrame ? Constant.IFramePrefix : Constant.Empty;
38
+ let doctype = node as DocumentType;
39
+ let docName = doctype.name ? doctype.name : Constant.HTML;
40
+ let docAttributes = { name: docName, publicId: doctype.publicId, systemId: doctype.systemId };
41
+ let docData = { tag: docTypePrefix + Constant.DocumentTag, attributes: docAttributes };
42
42
  dom[call](node, parent, docData, source);
43
43
  break;
44
- }
45
44
  case Node.DOCUMENT_NODE:
46
45
  // We check for regions in the beginning when discovering document and
47
46
  // later whenever there are new additions or modifications to DOM (mutations)
@@ -51,20 +50,20 @@ export default function (inputNode: Node, source: Source, timestamp: number): No
51
50
  checkDocumentStyles(node as Document, timestamp);
52
51
  observe(node as Document);
53
52
  break;
54
- case Node.DOCUMENT_FRAGMENT_NODE: {
55
- const shadowRoot = node as ShadowRoot;
53
+ case Node.DOCUMENT_FRAGMENT_NODE:
54
+ let shadowRoot = (node as ShadowRoot);
56
55
  if (shadowRoot.host) {
57
56
  dom.parse(shadowRoot);
58
- const type = typeof shadowRoot.constructor;
57
+ let type = typeof (shadowRoot.constructor);
59
58
  if (type === Constant.Function && shadowRoot.constructor.toString().indexOf(Constant.NativeCode) >= 0) {
60
59
  observe(shadowRoot);
61
-
60
+
62
61
  // See: https://wicg.github.io/construct-stylesheets/ for more details on adoptedStyleSheets.
63
62
  // At the moment, we are only able to capture "open" shadow DOM nodes. If they are closed, they are not accessible.
64
63
  // In future we may decide to proxy "attachShadow" call to gain access, but at the moment, we don't want to
65
64
  // cause any unintended side effect to the page. We will re-evaluate after we gather more real world data on this.
66
- const style = Constant.Empty as string;
67
- const fragmentData = { tag: Constant.ShadowDomTag, attributes: { style } };
65
+ let style = Constant.Empty as string;
66
+ let fragmentData = { tag: Constant.ShadowDomTag, attributes: { style } };
68
67
  dom[call](node, shadowRoot.host, fragmentData, source);
69
68
  } else {
70
69
  // If the browser doesn't support shadow DOM natively, we detect that, and send appropriate tag back.
@@ -75,100 +74,91 @@ export default function (inputNode: Node, source: Source, timestamp: number): No
75
74
  checkDocumentStyles(node as Document, timestamp);
76
75
  }
77
76
  break;
78
- }
79
77
  case Node.TEXT_NODE:
80
78
  // In IE11 TEXT_NODE doesn't expose a valid parentElement property. Instead we need to lookup parentNode property.
81
- parent = parent ? parent : (node.parentNode as HTMLElement);
79
+ parent = parent ? parent : node.parentNode as HTMLElement;
82
80
  // Account for this text node only if we are tracking the parent node
83
81
  // We do not wish to track text nodes for ignored parent nodes, like script tags
84
82
  // Also, we do not track text nodes for STYLE tags
85
83
  // The only exception is when we receive a mutation to remove the text node, in that case
86
84
  // parent will be null, but we can still process the node by checking it's an update call.
87
85
  if (call === "update" || (parent && dom.has(parent) && parent.tagName !== "STYLE" && parent.tagName !== "NOSCRIPT")) {
88
- const textData = { tag: Constant.TextTag, value: node.nodeValue };
86
+ let textData = { tag: Constant.TextTag, value: node.nodeValue };
89
87
  dom[call](node, parent, textData, source);
90
88
  }
91
89
  break;
92
- case Node.ELEMENT_NODE: {
93
- const element = node as HTMLElement;
90
+ case Node.ELEMENT_NODE:
91
+ let element = (node as HTMLElement);
94
92
  let tag = element.tagName;
95
- const attributes = getAttributes(element);
93
+ let attributes = getAttributes(element);
96
94
  // In some cases, external libraries like vue-fragment, can modify parentNode property to not be in sync with the DOM
97
95
  // For correctness, we first look at parentElement and if it not present then fall back to using parentNode
98
- parent = node.parentElement ? node.parentElement : node.parentNode ? (node.parentNode as HTMLElement) : null;
96
+ parent = node.parentElement ? node.parentElement : (node.parentNode ? node.parentNode as HTMLElement : null);
99
97
  // If we encounter a node that is part of SVG namespace, prefix the tag with SVG_PREFIX
100
- if (element.namespaceURI === Constant.SvgNamespace) {
101
- tag = Constant.SvgPrefix + tag;
102
- }
98
+ if (element.namespaceURI === Constant.SvgNamespace) { tag = Constant.SvgPrefix + tag; }
103
99
 
104
100
  switch (tag) {
105
- case "HTML": {
101
+ case "HTML":
106
102
  parent = insideFrame && parent ? dom.iframe(parent) : parent;
107
- const htmlPrefix = insideFrame ? Constant.IFramePrefix : Constant.Empty;
108
- const htmlData = { tag: htmlPrefix + tag, attributes };
103
+ let htmlPrefix = insideFrame ? Constant.IFramePrefix : Constant.Empty;
104
+ let htmlData = { tag: htmlPrefix + tag, attributes };
109
105
  dom[call](node, parent, htmlData, source);
110
106
  break;
111
- }
112
107
  case "SCRIPT":
113
108
  if (Constant.Type in attributes && attributes[Constant.Type] === Constant.JsonLD) {
114
109
  try {
115
110
  schema.ld(JSON.parse((element as HTMLScriptElement).text.replace(newlineRegex, Constant.Empty)));
116
- } catch {
117
- /* do nothing */
118
- }
111
+ } catch { /* do nothing */ }
119
112
  }
120
113
  break;
121
- case "NOSCRIPT": {
114
+ case "NOSCRIPT":
122
115
  // keeping the noscript tag but ignoring its contents. Some HTML markup relies on having these tags
123
116
  // to maintain parity with the original css view, but we don't want to execute any noscript in Clarity
124
- const noscriptData = { tag, attributes: {}, value: "" };
117
+ let noscriptData = { tag, attributes: {}, value: '' };
125
118
  dom[call](node, parent, noscriptData, source);
126
119
  break;
127
- }
128
- case "META": {
129
- const key = Constant.Property in attributes ? Constant.Property : Constant.Name in attributes ? Constant.Name : null;
120
+ case "META":
121
+ var key = (Constant.Property in attributes ?
122
+ Constant.Property :
123
+ (Constant.Name in attributes ? Constant.Name : null));
130
124
  if (key && Constant.Content in attributes) {
131
- const content = attributes[Constant.Content];
132
- switch (attributes[key]) {
125
+ let content = attributes[Constant.Content]
126
+ switch(attributes[key]) {
133
127
  case Constant.ogTitle:
134
- dimension.log(Dimension.MetaTitle, content);
128
+ dimension.log(Dimension.MetaTitle, content)
135
129
  break;
136
130
  case Constant.ogType:
137
- dimension.log(Dimension.MetaType, content);
131
+ dimension.log(Dimension.MetaType, content)
138
132
  break;
139
133
  case Constant.Generator:
140
- dimension.log(Dimension.Generator, content);
134
+ dimension.log(Dimension.Generator, content)
141
135
  break;
142
136
  }
143
137
  }
144
138
  break;
145
- }
146
- case "HEAD": {
147
- const head = { tag, attributes };
148
- const l = insideFrame && node.ownerDocument?.location ? node.ownerDocument.location : location;
149
- head.attributes[Constant.Base] = `${l.protocol}//${l.host}${l.pathname}`;
139
+ case "HEAD":
140
+ let head = { tag, attributes };
141
+ let l = insideFrame && node.ownerDocument?.location ? node.ownerDocument.location : location;
142
+ head.attributes[Constant.Base] = l.protocol + "//" + l.host + l.pathname;
150
143
  dom[call](node, parent, head, source);
151
144
  break;
152
- }
153
- case "BASE": {
145
+ case "BASE":
154
146
  // Override the auto detected base path to explicit value specified in this tag
155
- const baseHead = dom.get(node.parentElement);
147
+ let baseHead = dom.get(node.parentElement);
156
148
  if (baseHead) {
157
149
  // We create "a" element so we can generate protocol and hostname for relative paths like "/path/"
158
- const a = document.createElement("a");
159
- a.href = attributes.href;
160
- baseHead.data.attributes[Constant.Base] = `${a.protocol}//${a.host}${a.pathname}`;
150
+ let a = document.createElement("a");
151
+ a.href = attributes["href"];
152
+ baseHead.data.attributes[Constant.Base] = a.protocol + "//" + a.host + a.pathname;
161
153
  }
162
154
  break;
163
- }
164
- case "STYLE": {
165
- const styleData = { tag, attributes, value: getStyleValue(element as HTMLStyleElement) };
155
+ case "STYLE":
156
+ let styleData = { tag, attributes, value: getStyleValue(element as HTMLStyleElement) };
166
157
  dom[call](node, parent, styleData, source);
167
158
  break;
168
- }
169
- case "IFRAME": {
170
- const iframe = node as HTMLIFrameElement;
171
- const frameData = { tag, attributes };
159
+ case "IFRAME":
160
+ let iframe = node as HTMLIFrameElement;
161
+ let frameData = { tag, attributes };
172
162
  if (dom.sameorigin(iframe)) {
173
163
  mutation.monitor(iframe);
174
164
  frameData.attributes[Constant.SameOrigin] = "true";
@@ -181,15 +171,14 @@ export default function (inputNode: Node, source: Source, timestamp: number): No
181
171
  }
182
172
  dom[call](node, parent, frameData, source);
183
173
  break;
184
- }
185
- case "LINK": {
174
+ case "LINK":
186
175
  // electron stylesheets reference the local file system - translating those
187
176
  // to inline styles so playback can work
188
- if (electron && attributes.rel === Constant.StyleSheet) {
189
- for (const styleSheetIndex in Object.keys(document.styleSheets)) {
190
- const currentStyleSheet = document.styleSheets[styleSheetIndex];
191
- if (currentStyleSheet.ownerNode === element) {
192
- const syntheticStyleData = { tag: "STYLE", attributes, value: getCssRules(currentStyleSheet) };
177
+ if (electron && attributes['rel'] === Constant.StyleSheet) {
178
+ for (var styleSheetIndex in Object.keys(document.styleSheets)) {
179
+ var currentStyleSheet = document.styleSheets[styleSheetIndex];
180
+ if (currentStyleSheet.ownerNode == element) {
181
+ let syntheticStyleData = { tag: "STYLE", attributes, value: getCssRules(currentStyleSheet) };
193
182
  dom[call](node, parent, syntheticStyleData, source);
194
183
  break;
195
184
  }
@@ -197,32 +186,26 @@ export default function (inputNode: Node, source: Source, timestamp: number): No
197
186
  break;
198
187
  }
199
188
  // for links that aren't electron style sheets we can process them normally
200
- const linkData = { tag, attributes };
189
+ let linkData = { tag, attributes };
201
190
  dom[call](node, parent, linkData, source);
202
191
  break;
203
- }
204
192
  case "VIDEO":
205
193
  case "AUDIO":
206
- case "SOURCE": {
194
+ case "SOURCE":
207
195
  // Ignoring any base64 src attribute for media elements to prevent big unused tokens to be sent and shock the network
208
196
  if (Constant.Src in attributes && attributes[Constant.Src].startsWith("data:")) {
209
197
  attributes[Constant.Src] = "";
210
198
  }
211
- const mediaTag = { tag, attributes };
199
+ let mediaTag = { tag, attributes };
212
200
  dom[call](node, parent, mediaTag, source);
213
201
  break;
214
- }
215
- default: {
216
- const data = { tag, attributes };
217
- if (element.shadowRoot) {
218
- child = element.shadowRoot;
219
- }
202
+ default:
203
+ let data = { tag, attributes };
204
+ if (element.shadowRoot) { child = element.shadowRoot; }
220
205
  dom[call](node, parent, data, source);
221
206
  break;
222
- }
223
207
  }
224
208
  break;
225
- }
226
209
  default:
227
210
  break;
228
211
  }
@@ -230,9 +213,7 @@ export default function (inputNode: Node, source: Source, timestamp: number): No
230
213
  }
231
214
 
232
215
  function observe(root: Document | ShadowRoot): void {
233
- if (dom.has(root) || event.has(root)) {
234
- return;
235
- }
216
+ if (dom.has(root) || event.has(root)) { return; }
236
217
  mutation.observe(root); // Observe mutations for this root node
237
218
  interaction.observe(root); // Observe interactions for this root node
238
219
  }
@@ -247,13 +228,13 @@ export function removeObserver(root: HTMLIFrameElement): void {
247
228
  // For iframes, scroll event is observed on content window and this needs to be removed as well
248
229
  event.unbind(win);
249
230
  }
250
-
231
+
251
232
  if (doc) {
252
233
  // When an iframe is removed, we should also remove all listeners attached to its document
253
234
  // to avoid memory leaks.
254
235
  event.unbind(doc);
255
236
  mutation.disconnect(doc);
256
-
237
+
257
238
  // Remove iframe and content document from maps tracking them
258
239
  dom.removeIFrame(root, doc);
259
240
  }
@@ -265,7 +246,7 @@ function getStyleValue(style: HTMLStyleElement): string {
265
246
  // Additionally, check if style node has an id - if so it's at a high risk to have experienced dynamic
266
247
  // style updates which would make the textContent out of date with its true style contribution.
267
248
  let value = style.textContent ? style.textContent.trim() : Constant.Empty;
268
- const dataset = style.dataset ? Object.keys(style.dataset).length : 0;
249
+ let dataset = style.dataset ? Object.keys(style.dataset).length : 0;
269
250
  if (value.length === 0 || dataset > 0 || style.id.length > 0) {
270
251
  value = getCssRules(style.sheet as CSSStyleSheet);
271
252
  }
@@ -276,13 +257,9 @@ export function getCssRules(sheet: CSSStyleSheet): string {
276
257
  let value = Constant.Empty as string;
277
258
  let cssRules = null;
278
259
  // Firefox throws a SecurityError when trying to access cssRules of a stylesheet from a different domain
279
- try {
280
- cssRules = sheet ? sheet.cssRules : [];
281
- } catch (e) {
260
+ try { cssRules = sheet ? sheet.cssRules : []; } catch (e) {
282
261
  internal.log(Code.CssRules, Severity.Warning, e ? e.name : null);
283
- if (e && e.name !== "SecurityError") {
284
- throw e;
285
- }
262
+ if (e && e.name !== "SecurityError") { throw e; }
286
263
  }
287
264
 
288
265
  if (cssRules !== null) {
@@ -295,11 +272,11 @@ export function getCssRules(sheet: CSSStyleSheet): string {
295
272
  }
296
273
 
297
274
  function getAttributes(element: HTMLElement): { [key: string]: string } {
298
- const output = {};
299
- const attributes = element.attributes;
275
+ let output = {};
276
+ let attributes = element.attributes;
300
277
  if (attributes && attributes.length > 0) {
301
278
  for (let i = 0; i < attributes.length; i++) {
302
- const name = attributes[i].name;
279
+ let name = attributes[i].name;
303
280
  if (IGNORE_ATTRIBUTES.indexOf(name) < 0) {
304
281
  output[name] = attributes[i].value;
305
282
  }
@@ -1,16 +1,15 @@
1
- import type { OffsetDistance } from "@clarity-types/core";
1
+ import { OffsetDistance } from "@clarity-types/core";
2
2
  import { iframe } from "@src/layout/dom";
3
3
 
4
- export function offset(inputElement: HTMLElement): OffsetDistance {
5
- let element = inputElement;
6
- const output: OffsetDistance = { x: 0, y: 0 };
4
+ export function offset(element: HTMLElement): OffsetDistance {
5
+ let output: OffsetDistance = { x: 0, y: 0 };
7
6
 
8
7
  // Walk up the chain to ensure we compute offset distance correctly
9
8
  // In case where we may have nested IFRAMEs, we keep walking up until we get to the top most parent page
10
- if (element?.offsetParent) {
9
+ if (element && element.offsetParent) {
11
10
  do {
12
- const parent = element.offsetParent as HTMLElement;
13
- const frame = parent === null ? iframe(element.ownerDocument) : null;
11
+ let parent = element.offsetParent as HTMLElement;
12
+ let frame = parent === null ? iframe(element.ownerDocument) : null;
14
13
  output.x += element.offsetLeft;
15
14
  output.y += element.offsetTop;
16
15
  element = frame ? frame : parent;
@@ -1,5 +1,5 @@
1
1
  import { Event, Setting } from "@clarity-types/data";
2
- import { InteractionState, type RegionData, type RegionQueue, type RegionState, RegionVisibility } from "@clarity-types/layout";
2
+ import { InteractionState, RegionData, RegionState, RegionQueue, RegionVisibility } from "@clarity-types/layout";
3
3
  import { FunctionNames } from "@clarity-types/performance";
4
4
  import { time } from "@src/core/time";
5
5
  import * as dom from "@src/layout/dom";
@@ -18,22 +18,20 @@ export function start(): void {
18
18
  regionMap = new WeakMap();
19
19
  regions = {};
20
20
  queue = [];
21
- watch = !!window.IntersectionObserver;
21
+ watch = window["IntersectionObserver"] ? true : false;
22
+
22
23
  }
23
24
 
24
25
  export function observe(node: Node, name: string): void {
25
26
  if (regionMap.has(node) === false) {
26
27
  regionMap.set(node, name);
27
- observer =
28
- observer === null && watch
29
- ? new IntersectionObserver(handler, {
30
- // Get notified as intersection continues to change
31
- // This allows us to process regions that get partially hidden during the lifetime of the page
32
- // See: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#creating_an_intersection_observer
33
- // By default, intersection observers only fire an event when even a single pixel is visible and not thereafter.
34
- threshold: [0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
35
- })
36
- : observer;
28
+ observer = observer === null && watch ? new IntersectionObserver(handler, {
29
+ // Get notified as intersection continues to change
30
+ // This allows us to process regions that get partially hidden during the lifetime of the page
31
+ // See: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#creating_an_intersection_observer
32
+ // By default, intersection observers only fire an event when even a single pixel is visible and not thereafter.
33
+ threshold: [0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
34
+ }) : observer;
37
35
  if (observer && node && node.nodeType === Node.ELEMENT_NODE) {
38
36
  observer.observe(node as Element);
39
37
  }
@@ -44,25 +42,18 @@ export function exists(node: Node): boolean {
44
42
  // Check if regionMap is not null before looking up a node
45
43
  // Since, dom module stops after region module, it's possible that we may set regionMap to be null
46
44
  // and still attempt to call exists on a late coming DOM mutation (or addition), effectively causing a script error
47
- return regionMap?.has(node);
45
+ return regionMap && regionMap.has(node);
48
46
  }
49
47
 
50
48
  export function track(id: number, event: Event): void {
51
- const node = dom.getNode(id);
52
- const data =
53
- id in regions
54
- ? regions[id]
55
- : { id, visibility: RegionVisibility.Rendered, interaction: InteractionState.None, name: regionMap.get(node) };
56
-
49
+ let node = dom.getNode(id);
50
+ let data = id in regions ? regions[id] : { id, visibility: RegionVisibility.Rendered, interaction: InteractionState.None, name: regionMap.get(node) };
51
+
57
52
  // Determine the interaction state based on incoming event
58
53
  let interaction = InteractionState.None;
59
54
  switch (event) {
60
- case Event.Click:
61
- interaction = InteractionState.Clicked;
62
- break;
63
- case Event.Input:
64
- interaction = InteractionState.Input;
65
- break;
55
+ case Event.Click: interaction = InteractionState.Clicked; break;
56
+ case Event.Input: interaction = InteractionState.Input; break;
66
57
  }
67
58
  // Process updates to this region, if applicable
68
59
  process(node, data, interaction, data.visibility);
@@ -73,31 +64,27 @@ export function compute(): void {
73
64
  // Process any regions where we couldn't resolve an "id" for at the time of last intersection observer event
74
65
  // This could happen in cases where elements are not yet processed by Clarity's virtual DOM but browser reports a change, regardless.
75
66
  // For those cases we add them to the queue and re-process them below
76
- const q = [];
77
- for (const r of queue) {
78
- const id = dom.getId(r.node);
67
+ let q = [];
68
+ for (let r of queue) {
69
+ let id = dom.getId(r.node);
79
70
  if (id) {
80
71
  r.state.data.id = id;
81
72
  regions[id] = r.state.data;
82
73
  state.push(r.state);
83
- } else {
84
- q.push(r);
85
- }
74
+ } else { q.push(r); }
86
75
  }
87
76
  queue = q;
88
77
 
89
78
  // Schedule encode only when we have at least one valid data entry
90
- if (state.length > 0) {
91
- encode(Event.Region);
92
- }
79
+ if (state.length > 0) { encode(Event.Region); }
93
80
  }
94
81
 
95
82
  function handler(entries: IntersectionObserverEntry[]): void {
96
- for (const entry of entries) {
97
- const target = entry.target;
98
- const rect = entry.boundingClientRect;
99
- const overlap = entry.intersectionRect;
100
- const viewport = entry.rootBounds;
83
+ for (let entry of entries) {
84
+ let target = entry.target;
85
+ let rect = entry.boundingClientRect;
86
+ let overlap = entry.intersectionRect;
87
+ let viewport = entry.rootBounds;
101
88
  // Only capture regions that have non-zero width or height to avoid tracking and sending regions
102
89
  // that cannot ever be seen by the user. In some cases, websites will have a multiple copy of the same region
103
90
  // like search box - one for desktop, and another for mobile. In those cases, CSS media queries determine which one should be visible.
@@ -111,35 +98,28 @@ function handler(entries: IntersectionObserverEntry[]): void {
111
98
 
112
99
  // For regions that have relatively smaller area, we look at intersection ratio and see the overlap relative to element's area
113
100
  // However, for larger regions, area of regions could be bigger than viewport and therefore comparison is relative to visible area
114
- const viewportRatio = overlap ? (overlap.width * overlap.height * 1.0) / (viewport.width * viewport.height) : 0;
115
- const visible = viewportRatio > Setting.ViewportIntersectionRatio || entry.intersectionRatio > Setting.IntersectionRatio;
101
+ let viewportRatio = overlap ? (overlap.width * overlap.height * 1.0) / (viewport.width * viewport.height) : 0;
102
+ let visible = viewportRatio > Setting.ViewportIntersectionRatio || entry.intersectionRatio > Setting.IntersectionRatio;
116
103
  // If an element is either visible or was visible and has been scrolled to the end
117
- // i.e. Scrolled to end is determined by if the starting position of the element + the window height is more than the total element height.
104
+ // i.e. Scrolled to end is determined by if the starting position of the element + the window height is more than the total element height.
118
105
  // starting position is relative to the viewport - so Intersection observer returns a negative value for rect.top to indicate that the element top is above the viewport
119
- const scrolledToEnd =
120
- (visible || data.visibility === RegionVisibility.Visible) && Math.abs(rect.top) + viewport.height > rect.height;
106
+ let scrolledToEnd = (visible || data.visibility == RegionVisibility.Visible) && Math.abs(rect.top) + viewport.height > rect.height;
121
107
  // Process updates to this region, if applicable
122
- process(
123
- target,
124
- data,
125
- data.interaction,
126
- scrolledToEnd ? RegionVisibility.ScrolledToEnd : visible ? RegionVisibility.Visible : RegionVisibility.Rendered,
127
- );
108
+ process(target, data, data.interaction,
109
+ (scrolledToEnd ?
110
+ RegionVisibility.ScrolledToEnd :
111
+ (visible ? RegionVisibility.Visible : RegionVisibility.Rendered)));
128
112
 
129
113
  // Stop observing this element now that we have already received scrolled signal
130
- if (data.visibility >= RegionVisibility.ScrolledToEnd && observer) {
131
- observer.unobserve(target);
132
- }
114
+ if (data.visibility >= RegionVisibility.ScrolledToEnd && observer) { observer.unobserve(target); }
133
115
  }
134
116
  }
135
- if (state.length > 0) {
136
- encode(Event.Region);
137
- }
117
+ if (state.length > 0) { encode(Event.Region); }
138
118
  }
139
119
 
140
120
  function process(n: Node, d: RegionData, s: InteractionState, v: RegionVisibility): void {
141
121
  // Check if received a state that supersedes existing state
142
- const updated = s > d.interaction || v > d.visibility;
122
+ let updated = s > d.interaction || v > d.visibility;
143
123
  d.interaction = s > d.interaction ? s : d.interaction;
144
124
  d.visibility = v > d.visibility ? v : d.visibility;
145
125
  // If the corresponding node is already discovered, update the internal state
@@ -151,16 +131,16 @@ function process(n: Node, d: RegionData, s: InteractionState, v: RegionVisibilit
151
131
  }
152
132
  } else {
153
133
  // Get the time before adding to queue to ensure accurate event time
154
- queue.push({ node: n, state: clone(d) });
134
+ queue.push({node: n, state: clone(d)});
155
135
  }
156
136
  }
157
137
 
158
138
  function clone(r: RegionData): RegionState {
159
- return { time: time(), data: { id: r.id, interaction: r.interaction, visibility: r.visibility, name: r.name } };
139
+ return { time: time(), data: { id: r.id, interaction: r.interaction, visibility: r.visibility, name: r.name }};
160
140
  }
161
141
 
162
142
  export function reset(): void {
163
- state = [];
143
+ state = [];
164
144
  }
165
145
 
166
146
  export function stop(): void {
@@ -6,9 +6,8 @@ import * as metric from "@src/data/metric";
6
6
  const digitsRegex = /[^0-9\.]/g;
7
7
 
8
8
  /* JSON+LD (Linked Data) Recursive Parser */
9
- // biome-ignore lint/suspicious/noExplicitAny: specifically parsing json with any type
10
9
  export function ld(json: any): void {
11
- for (const key of Object.keys(json)) {
10
+ for (let key of Object.keys(json)) {
12
11
  let value = json[key];
13
12
  if (key === JsonLD.Type && typeof value === "string") {
14
13
  value = value.toLowerCase();
@@ -18,16 +17,14 @@ export function ld(json: any): void {
18
17
  case JsonLD.Article:
19
18
  case JsonLD.Recipe:
20
19
  dimension.log(Dimension.SchemaType, json[key]);
21
- dimension.log(Dimension.AuthorName, json[JsonLD.Creator]);
22
- dimension.log(Dimension.Headline, json[JsonLD.Headline]);
20
+ dimension.log(Dimension.AuthorName, json[JsonLD.Creator]);
21
+ dimension.log(Dimension.Headline, json[JsonLD.Headline]);
23
22
  break;
24
23
  case JsonLD.Product:
25
24
  dimension.log(Dimension.SchemaType, json[key]);
26
25
  dimension.log(Dimension.ProductName, json[JsonLD.Name]);
27
26
  dimension.log(Dimension.ProductSku, json[JsonLD.Sku]);
28
- if (json[JsonLD.Brand]) {
29
- dimension.log(Dimension.ProductBrand, json[JsonLD.Brand][JsonLD.Name]);
30
- }
27
+ if (json[JsonLD.Brand]) { dimension.log(Dimension.ProductBrand, json[JsonLD.Brand][JsonLD.Name]); }
31
28
  break;
32
29
  case JsonLD.AggregateRating:
33
30
  if (json[JsonLD.RatingValue]) {
@@ -51,19 +48,15 @@ export function ld(json: any): void {
51
48
  }
52
49
  }
53
50
  // Continue parsing nested objects
54
- if (value !== null && typeof value === "object") {
55
- ld(value);
56
- }
51
+ if (value !== null && typeof(value) === Constant.Object) { ld(value); }
57
52
  }
58
53
  }
59
54
 
60
- function num(input: string | number, scale = 1): number {
55
+ function num(input: string | number, scale: number = 1): number {
61
56
  if (input !== null) {
62
57
  switch (typeof input) {
63
- case Constant.Number:
64
- return Math.round((input as number) * scale);
65
- case Constant.String:
66
- return Math.round(Number.parseFloat((input as string).replace(digitsRegex, Constant.Empty)) * scale);
58
+ case Constant.Number: return Math.round((input as number) * scale);
59
+ case Constant.String: return Math.round(parseFloat((input as string).replace(digitsRegex, Constant.Empty)) * scale);
67
60
  }
68
61
  }
69
62
  return null;