clarity-js 0.6.26 → 0.6.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-js",
3
- "version": "0.6.26",
3
+ "version": "0.6.30",
4
4
  "description": "An analytics library that uses web page interactions to generate aggregated insights",
5
5
  "author": "Microsoft Corp.",
6
6
  "license": "MIT",
@@ -10,6 +10,7 @@ let config: Config = {
10
10
  unmask: [],
11
11
  regions: [],
12
12
  metrics: [],
13
+ dimensions: [],
13
14
  cookies: [],
14
15
  report: null,
15
16
  upload: null,
@@ -1,12 +1,13 @@
1
1
  import { Setting } from "@clarity-types/core";
2
2
  import { Metric } from "@clarity-types/data";
3
+ import { report } from "@src/core/report";
3
4
  import * as metric from "@src/data/metric";
4
5
 
5
6
  // tslint:disable-next-line: ban-types
6
7
  export default function (method: Function): Function {
7
8
  return function (): void {
8
9
  let start = performance.now();
9
- method.apply(this, arguments);
10
+ try { method.apply(this, arguments); } catch (ex) { throw report(ex); }
10
11
  let duration = performance.now() - start;
11
12
  metric.sum(Metric.TotalCost, duration);
12
13
  if (duration > Setting.LongTask) {
@@ -1,7 +1,6 @@
1
1
  import { Report } from "@clarity-types/core";
2
- import { Check } from "@clarity-types/data";
3
2
  import config from "@src/core/config";
4
- import { data } from "@src/data/metadata";
3
+ import { data } from "@src/data/envelope";
5
4
 
6
5
  let history: string[];
7
6
 
@@ -9,19 +8,21 @@ export function reset(): void {
9
8
  history = [];
10
9
  }
11
10
 
12
- export function report(check: Check, message: string = null): void {
11
+ export function report(e: Error): Error {
13
12
  // Do not report the same message twice for the same page
14
- if (history && history.indexOf(message) === -1) {
13
+ if (history && history.indexOf(e.message) === -1) {
15
14
  const url = config.report;
16
15
  if (url && url.length > 0) {
17
- let payload: Report = {c: check, p: data.projectId, u: data.userId, s: data.sessionId, n: data.pageNum };
18
- if (message) payload.m = message;
16
+ let payload: Report = {v: data.version, p: data.projectId, u: data.userId, s: data.sessionId, n: data.pageNum};
17
+ if (e.message) { payload.m = e.message; }
18
+ if (e.stack) { payload.e = e.stack; }
19
19
  // Using POST request instead of a GET request (img-src) to not violate existing CSP rules
20
20
  // Since, Clarity already uses XHR to upload data, we stick with similar POST mechanism for reporting too
21
21
  let xhr = new XMLHttpRequest();
22
22
  xhr.open("POST", url);
23
23
  xhr.send(JSON.stringify(payload));
24
- history.push(message);
24
+ history.push(e.message);
25
25
  }
26
26
  }
27
+ return e;
27
28
  }
@@ -1,2 +1,2 @@
1
- let version = "0.6.26";
1
+ let version = "0.6.30";
2
2
  export default version;
package/src/data/limit.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Check, Event, LimitData, Setting } from "@clarity-types/data";
2
2
  import * as clarity from "@src/clarity";
3
- import { report } from "@src/core/report";
4
3
  import { time } from "@src/core/time";
5
4
  import * as envelope from "@src/data/envelope";
6
5
  import * as metadata from "@src/data/metadata";
@@ -25,7 +24,6 @@ export function check(bytes: number): void {
25
24
  }
26
25
 
27
26
  export function trigger(reason: Check): void {
28
- report(reason);
29
27
  data.check = reason;
30
28
  metadata.clear();
31
29
  clarity.stop();
@@ -38,14 +38,16 @@ export function start(): void {
38
38
  dimension.log(Dimension.TabId, tab());
39
39
  dimension.log(Dimension.PageLanguage, document.documentElement.lang);
40
40
  dimension.log(Dimension.DocumentDirection, document.dir);
41
-
42
41
  if (navigator) {
43
42
  dimension.log(Dimension.Language, (<any>navigator).userLanguage || navigator.language);
43
+ metric.max(Metric.Automation, navigator.webdriver ? BooleanFlag.True : BooleanFlag.False);
44
+ userAgentData();
44
45
  }
45
46
 
46
47
  // Metrics
47
48
  metric.max(Metric.ClientTimestamp, s.ts);
48
49
  metric.max(Metric.Playback, BooleanFlag.False);
50
+
49
51
  if (screen) {
50
52
  metric.max(Metric.ScreenWidth, Math.round(screen.width));
51
53
  metric.max(Metric.ScreenHeight, Math.round(screen.height));
@@ -62,13 +64,39 @@ export function start(): void {
62
64
  track(u);
63
65
  }
64
66
 
67
+ export function userAgentData(): void {
68
+ if (navigator["userAgentData"] && navigator["userAgentData"].getHighEntropyValues) {
69
+ navigator["userAgentData"].getHighEntropyValues(
70
+ ["model",
71
+ "platform",
72
+ "platformVersion",
73
+ "uaFullVersion"])
74
+ .then(ua => {
75
+ dimension.log(Dimension.Platform, ua.platform);
76
+ dimension.log(Dimension.PlatformVersion, ua.platformVersion);
77
+ ua.brands?.forEach(brand => {
78
+ dimension.log(Dimension.Brand, brand.name + Constant.Tilde + brand.version);
79
+ });
80
+ dimension.log(Dimension.Model, ua.model);
81
+ metric.max(Metric.Mobile, ua.mobile ? BooleanFlag.True : BooleanFlag.False);
82
+ });
83
+ }
84
+ }
85
+
65
86
  export function stop(): void {
66
87
  callback = null;
67
88
  rootDomain = null;
89
+ data = null;
68
90
  }
69
91
 
70
- export function metadata(cb: MetadataCallback): void {
71
- callback = cb;
92
+ export function metadata(cb: MetadataCallback, wait: boolean = true): void {
93
+ if (data && wait === false) {
94
+ // Immediately invoke the callback if the caller explicitly doesn't want to wait for the upgrade confirmation
95
+ cb(data, !config.lean);
96
+ } else {
97
+ // Save the callback for future reference; so we can inform the caller when page gets upgraded and we have a valid playback flag
98
+ callback = cb;
99
+ }
72
100
  }
73
101
 
74
102
  export function id(): string {
@@ -56,6 +56,9 @@ export function queue(tokens: Token[], transmit: boolean = true): void {
56
56
  break;
57
57
  }
58
58
 
59
+ // Increment event count metric
60
+ metric.count(Metric.EventCount);
61
+
59
62
  // Following two checks are precautionary and act as a fail safe mechanism to get out of unexpected situations.
60
63
  // Check 1: If for any reason the upload hasn't happened after waiting for 2x the config.delay time,
61
64
  // reset the timer. This allows Clarity to attempt an upload again.
@@ -1,10 +1,8 @@
1
1
  import { Event, Token } from "@clarity-types/data";
2
2
  import { time } from "@src/core/time";
3
3
  import { queue } from "@src/data/upload";
4
- import * as image from "@src/diagnostic/image";
5
4
  import * as internal from "@src/diagnostic/internal";
6
5
  import * as script from "@src/diagnostic/script";
7
- import { metadata } from "@src/layout/target";
8
6
 
9
7
  export default async function (type: Event): Promise<void> {
10
8
  let tokens: Token[] = [time(), type];
@@ -18,14 +16,6 @@ export default async function (type: Event): Promise<void> {
18
16
  tokens.push(script.data.source);
19
17
  queue(tokens);
20
18
  break;
21
- case Event.ImageError:
22
- if (image.data) {
23
- let imageTarget = metadata(image.data.target as Node, type);
24
- tokens.push(image.data.source);
25
- tokens.push(imageTarget.id);
26
- queue(tokens);
27
- }
28
- break;
29
19
  case Event.Log:
30
20
  if (internal.data) {
31
21
  tokens.push(internal.data.code);
@@ -1,14 +1,11 @@
1
- import * as image from "./image";
2
1
  import * as internal from "./internal";
3
2
  import * as script from "./script";
4
3
 
5
4
  export function start(): void {
6
5
  script.start();
7
- image.start();
8
6
  internal.start();
9
7
  }
10
8
 
11
9
  export function stop(): void {
12
- image.stop();
13
10
  internal.stop();
14
11
  }
@@ -0,0 +1,32 @@
1
+ import { Event } from "@clarity-types/data";
2
+ import { Clipboard, ClipboardState } from "@clarity-types/interaction";
3
+ import { bind } from "@src/core/event";
4
+ import { schedule } from "@src/core/task";
5
+ import { time } from "@src/core/time";
6
+ import encode from "./encode";
7
+ import { target } from "@src/layout/target";
8
+
9
+ export let state: ClipboardState[] = [];
10
+
11
+ export function start(): void {
12
+ reset();
13
+ }
14
+
15
+ export function observe(root: Node): void {
16
+ bind(root, "cut", recompute.bind(this, Clipboard.Cut), true);
17
+ bind(root, "copy", recompute.bind(this, Clipboard.Copy), true);
18
+ bind(root, "paste", recompute.bind(this, Clipboard.Paste), true);
19
+ }
20
+
21
+ function recompute(action: Clipboard, evt: UIEvent): void {
22
+ state.push({ time: time(), event: Event.Clipboard, data: { target: target(evt), action } });
23
+ schedule(encode.bind(this, Event.Clipboard));
24
+ }
25
+
26
+ export function reset(): void {
27
+ state = [];
28
+ }
29
+
30
+ export function stop(): void {
31
+ reset();
32
+ }
@@ -5,11 +5,13 @@ import * as baseline from "@src/data/baseline";
5
5
  import { queue } from "@src/data/upload";
6
6
  import { metadata } from "@src/layout/target";
7
7
  import * as click from "./click";
8
+ import * as clipboard from "./clipboard";
8
9
  import * as input from "./input";
9
10
  import * as pointer from "./pointer";
10
11
  import * as resize from "./resize";
11
12
  import * as scroll from "./scroll";
12
13
  import * as selection from "./selection";
14
+ import * as submit from "./submit";
13
15
  import * as timeline from "./timeline";
14
16
  import * as unload from "./unload";
15
17
  import * as visibility from "./visibility";
@@ -27,8 +29,7 @@ export default async function (type: Event): Promise<void> {
27
29
  case Event.TouchEnd:
28
30
  case Event.TouchMove:
29
31
  case Event.TouchCancel:
30
- for (let i = 0; i < pointer.state.length; i++) {
31
- let entry = pointer.state[i];
32
+ for (let entry of pointer.state) {
32
33
  let pTarget = metadata(entry.data.target as Node, entry.event);
33
34
  if (pTarget.id > 0) {
34
35
  tokens = [entry.time, entry.event];
@@ -42,8 +43,7 @@ export default async function (type: Event): Promise<void> {
42
43
  pointer.reset();
43
44
  break;
44
45
  case Event.Click:
45
- for (let i = 0; i < click.state.length; i++) {
46
- let entry = click.state[i];
46
+ for (let entry of click.state) {
47
47
  let cTarget = metadata(entry.data.target as Node, entry.event);
48
48
  tokens = [entry.time, entry.event];
49
49
  let cHash = cTarget.hash.join(Constant.Dot);
@@ -63,6 +63,18 @@ export default async function (type: Event): Promise<void> {
63
63
  }
64
64
  click.reset();
65
65
  break;
66
+ case Event.Clipboard:
67
+ for (let entry of clipboard.state) {
68
+ tokens = [entry.time, entry.event];
69
+ let target = metadata(entry.data.target as Node, entry.event);
70
+ if (target.id > 0) {
71
+ tokens.push(target.id);
72
+ tokens.push(entry.data.action);
73
+ queue(tokens);
74
+ }
75
+ }
76
+ clipboard.reset();
77
+ break;
66
78
  case Event.Resize:
67
79
  let r = resize.data;
68
80
  tokens.push(r.width);
@@ -78,8 +90,7 @@ export default async function (type: Event): Promise<void> {
78
90
  queue(tokens);
79
91
  break;
80
92
  case Event.Input:
81
- for (let i = 0; i < input.state.length; i++) {
82
- let entry = input.state[i];
93
+ for (let entry of input.state) {
83
94
  let iTarget = metadata(entry.data.target as Node, entry.event);
84
95
  tokens = [entry.time, entry.event];
85
96
  tokens.push(iTarget.id);
@@ -102,8 +113,7 @@ export default async function (type: Event): Promise<void> {
102
113
  }
103
114
  break;
104
115
  case Event.Scroll:
105
- for (let i = 0; i < scroll.state.length; i++) {
106
- let entry = scroll.state[i];
116
+ for (let entry of scroll.state) {
107
117
  let sTarget = metadata(entry.data.target as Node, entry.event);
108
118
  if (sTarget.id > 0) {
109
119
  tokens = [entry.time, entry.event];
@@ -116,9 +126,19 @@ export default async function (type: Event): Promise<void> {
116
126
  }
117
127
  scroll.reset();
118
128
  break;
129
+ case Event.Submit:
130
+ for (let entry of submit.state) {
131
+ tokens = [entry.time, entry.event];
132
+ let target = metadata(entry.data.target as Node, entry.event);
133
+ if (target.id > 0) {
134
+ tokens.push(target.id);
135
+ queue(tokens);
136
+ }
137
+ }
138
+ submit.reset();
139
+ break;
119
140
  case Event.Timeline:
120
- for (let i = 0; i < timeline.updates.length; i++) {
121
- let entry = timeline.updates[i];
141
+ for (let entry of timeline.updates) {
122
142
  tokens = [entry.time, entry.event];
123
143
  tokens.push(entry.data.type);
124
144
  tokens.push(entry.data.hash);
@@ -1,9 +1,11 @@
1
1
  import * as click from "@src/interaction/click";
2
+ import * as clipboard from "@src/interaction/clipboard";
2
3
  import * as input from "@src/interaction/input";
3
4
  import * as pointer from "@src/interaction/pointer";
4
5
  import * as resize from "@src/interaction/resize";
5
6
  import * as scroll from "@src/interaction/scroll";
6
7
  import * as selection from "@src/interaction/selection";
8
+ import * as submit from "@src/interaction/submit";
7
9
  import * as timeline from "@src/interaction/timeline";
8
10
  import * as unload from "@src/interaction/unload";
9
11
  import * as visibility from "@src/interaction/visibility";
@@ -11,24 +13,28 @@ import * as visibility from "@src/interaction/visibility";
11
13
  export function start(): void {
12
14
  timeline.start();
13
15
  click.start();
16
+ clipboard.start();
14
17
  pointer.start();
15
18
  input.start();
16
19
  resize.start();
17
20
  visibility.start();
18
21
  scroll.start();
19
22
  selection.start();
23
+ submit.start();
20
24
  unload.start();
21
25
  }
22
26
 
23
27
  export function stop(): void {
24
28
  timeline.stop();
25
29
  click.stop();
30
+ clipboard.stop();
26
31
  pointer.stop();
27
32
  input.stop();
28
33
  resize.stop();
29
34
  visibility.stop();
30
35
  scroll.stop();
31
36
  selection.stop();
37
+ submit.stop();
32
38
  unload.stop()
33
39
  }
34
40
 
@@ -38,8 +44,10 @@ export function observe(root: Node): void {
38
44
  // In case of shadow DOM, following events automatically bubble up to the parent document.
39
45
  if (root.nodeType === Node.DOCUMENT_NODE) {
40
46
  click.observe(root);
47
+ clipboard.observe(root);
41
48
  pointer.observe(root);
42
49
  input.observe(root);
43
50
  selection.observe(root);
51
+ submit.observe(root);
44
52
  }
45
- }
53
+ }
@@ -0,0 +1,30 @@
1
+ import { Event } from "@clarity-types/data";
2
+ import { SubmitState } from "@clarity-types/interaction";
3
+ import { bind } from "@src/core/event";
4
+ import { schedule } from "@src/core/task";
5
+ import { time } from "@src/core/time";
6
+ import encode from "./encode";
7
+ import { target } from "@src/layout/target";
8
+
9
+ export let state: SubmitState[] = [];
10
+
11
+ export function start(): void {
12
+ reset();
13
+ }
14
+
15
+ export function observe(root: Node): void {
16
+ bind(root, "submit", recompute, true);
17
+ }
18
+
19
+ function recompute(evt: UIEvent): void {
20
+ state.push({ time: time(), event: Event.Submit, data: { target: target(evt) } });
21
+ schedule(encode.bind(this, Event.Submit));
22
+ }
23
+
24
+ export function reset(): void {
25
+ state = [];
26
+ }
27
+
28
+ export function stop(): void {
29
+ reset();
30
+ }
package/src/layout/dom.ts CHANGED
@@ -12,7 +12,7 @@ let index: number = 1;
12
12
 
13
13
  // Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#%3Cinput%3E_types
14
14
  const DISALLOWED_TYPES = ["password", "hidden", "email", "tel"];
15
- const DISALLOWED_NAMES = ["addr", "cell", "code", "dob", "email", "mob", "name", "phone", "secret", "social", "ssn", "tel", "zip", "pass"];
15
+ const DISALLOWED_NAMES = ["addr", "cell", "code", "dob", "email", "mob", "name", "phone", "secret", "social", "ssn", "tel", "zip", "pass", "card", "account", "cvv", "ccv"];
16
16
  const DISALLOWED_MATCH = ["address", "password", "contact"];
17
17
 
18
18
  let nodes: Node[] = [];
@@ -56,6 +56,7 @@ export function parse(root: ParentNode): void {
56
56
  if ("querySelectorAll" in root) {
57
57
  extract.regions(root, config.regions);
58
58
  extract.metrics(root, config.metrics);
59
+ extract.dimensions(root, config.dimensions);
59
60
  config.mask.forEach(x => root.querySelectorAll(x).forEach(e => privacyMap.set(e, Privacy.TextImage))); // Masked Elements
60
61
  config.unmask.forEach(x => root.querySelectorAll(x).forEach(e => privacyMap.set(e, Privacy.None))); // Unmasked Elements
61
62
  }
@@ -1,5 +1,6 @@
1
- import { Extract, Metric, Region, RegionFilter } from "@clarity-types/core";
2
- import { Constant } from "@clarity-types/data";
1
+ import { Dimension, Extract, Metric, Region, RegionFilter } from "@clarity-types/core";
2
+ import { Constant, Setting } from "@clarity-types/data";
3
+ import * as dimension from "@src/data/dimension";
3
4
  import * as metric from "@src/data/metric";
4
5
  import * as region from "@src/layout/region";
5
6
 
@@ -33,6 +34,19 @@ export function metrics(root: ParentNode, value: Metric[]): void {
33
34
  }
34
35
  }
35
36
 
37
+ export function dimensions(root: ParentNode, value: Dimension[]): void {
38
+ for (let v of value) {
39
+ const [dimensionId, source, match] = v;
40
+ if (match) {
41
+ switch (source) {
42
+ case Extract.Text: root.querySelectorAll(match).forEach(e => { dimension.log(dimensionId, str((e as HTMLElement).innerText)); }); break;
43
+ case Extract.Attribute: root.querySelectorAll(`[${match}]`).forEach(e => { dimension.log(dimensionId, str(e.getAttribute(match))); }); break;
44
+ case Extract.Javascript: dimension.log(dimensionId, str(evaluate(match, Constant.String))); break;
45
+ }
46
+ }
47
+ }
48
+ }
49
+
36
50
  function regex(match: string): RegExp {
37
51
  regexCache[match] = match in regexCache ? regexCache[match] : new RegExp(match);
38
52
  return regexCache[match];
@@ -52,6 +66,11 @@ function evaluate(variable: string, type: string = null, base: Object = window):
52
66
  return null;
53
67
  }
54
68
 
69
+ function str(input: string): string {
70
+ // Automatically trim string to max of Setting.DimensionLimit to avoid fetching long strings
71
+ return input ? input.substr(0, Setting.DimensionLimit) : input;
72
+ }
73
+
55
74
  function num(text: string, scale: number, localize: boolean = true): number {
56
75
  try {
57
76
  scale = scale || 1;
@@ -36,7 +36,7 @@ export function start(): void {
36
36
 
37
37
  if (insertRule === null) { insertRule = CSSStyleSheet.prototype.insertRule; }
38
38
  if (deleteRule === null) { deleteRule = CSSStyleSheet.prototype.deleteRule; }
39
- if (attachShadow === null) { attachShadow = HTMLElement.prototype.attachShadow; }
39
+ if (attachShadow === null) { attachShadow = Element.prototype.attachShadow; }
40
40
 
41
41
  // Some popular open source libraries, like styled-components, optimize performance
42
42
  // by injecting CSS using insertRule API vs. appending text node. A side effect of
@@ -52,10 +52,14 @@ export function start(): void {
52
52
  return deleteRule.apply(this, arguments);
53
53
  };
54
54
 
55
- // Listening to attachShadow API
56
- HTMLElement.prototype.attachShadow = function (): ShadowRoot {
57
- return schedule(attachShadow.apply(this, arguments)) as ShadowRoot;
58
- }
55
+ // Add a hook to attachShadow API calls
56
+ // In case we are unable to add a hook and browser throws an exception,
57
+ // reset attachShadow variable and resume processing like before
58
+ try {
59
+ Element.prototype.attachShadow = function (): ShadowRoot {
60
+ return schedule(attachShadow.apply(this, arguments)) as ShadowRoot;
61
+ }
62
+ } catch { attachShadow = null; }
59
63
  }
60
64
 
61
65
  export function observe(node: Node): void {
@@ -103,7 +107,7 @@ export function stop(): void {
103
107
 
104
108
  // Restoring original attachShadow
105
109
  if (attachShadow != null) {
106
- HTMLElement.prototype.attachShadow = attachShadow;
110
+ Element.prototype.attachShadow = attachShadow;
107
111
  attachShadow = null;
108
112
  }
109
113
 
@@ -24,12 +24,13 @@ export default function(input: SelectorInput, beta: boolean = false): string {
24
24
  let selector = `${prefix}${input.tag}${suffix}`;
25
25
  let classes = Constant.Class in a && a[Constant.Class].length > 0 ? a[Constant.Class].trim().split(/\s+/) : null;
26
26
  if (beta) {
27
- // In beta mode, update selector to use "id" field when available
28
- // The only exception is if "id" appears to be an auto generated string token, e.g. guid or a random id
27
+ // In beta mode, update selector to use "id" field when available. There are two exceptions:
28
+ // (1) if "id" appears to be an auto generated string token, e.g. guid or a random id containing digits
29
+ // (2) if "id" appears inside a shadow DOM, in which case we continue to prefix up to shadow DOM to prevent conflicts
29
30
  let id = Constant.Id in a && a[Constant.Id].length > 0 ? a[Constant.Id] : null;
30
31
  classes = input.tag !== Constant.BodyTag && classes ? classes.filter(c => !hasDigits(c)) : [];
31
32
  selector = classes.length > 0 ? `${prefix}${input.tag}.${classes.join(".")}${suffix}` : selector;
32
- selector = id && hasDigits(id) === false ? `#${id}` : selector;
33
+ selector = id && hasDigits(id) === false ? `${getDomPrefix(prefix)}#${id}` : selector;
33
34
  } else {
34
35
  // Otherwise, fallback to stable mode, where we include class names as part of the selector
35
36
  selector = classes ? `${prefix}${input.tag}.${classes.join(".")}${suffix}` : selector;
@@ -38,11 +39,24 @@ export default function(input: SelectorInput, beta: boolean = false): string {
38
39
  }
39
40
  }
40
41
 
42
+ function getDomPrefix(prefix: string): string {
43
+ const shadowDomStart = prefix.lastIndexOf(Constant.ShadowDomTag);
44
+ const iframeDomStart = prefix.lastIndexOf(`${Constant.IFramePrefix}${Constant.HTML}`);
45
+ const domStart = Math.max(shadowDomStart, iframeDomStart);
46
+
47
+ if (domStart < 0) {
48
+ return "";
49
+ }
50
+
51
+ const domEnd = prefix.indexOf(">", domStart) + 1;
52
+ return prefix.substr(0, domEnd);
53
+ }
54
+
41
55
  // Check if the given input string has digits or not
42
56
  function hasDigits(value: string): boolean {
43
57
  for (let i = 0; i < value.length; i++) {
44
58
  let c = value.charCodeAt(i);
45
- return c >= Character.Zero && c <= Character.Nine;
59
+ if (c >= Character.Zero && c <= Character.Nine) { return true };
46
60
  }
47
61
  return false;
48
62
  }
@@ -1,21 +1,12 @@
1
1
  import {Event, Token} from "@clarity-types/data";
2
2
  import { time } from "@src/core/time";
3
3
  import { queue } from "@src/data/upload";
4
- import * as connection from "@src/performance/connection";
5
4
  import * as navigation from "@src/performance/navigation";
6
5
 
7
6
  export default async function(type: Event): Promise<void> {
8
7
  let t = time();
9
8
  let tokens: Token[] = [t, type];
10
9
  switch (type) {
11
- case Event.Connection:
12
- tokens.push(connection.data.downlink);
13
- tokens.push(connection.data.rtt);
14
- tokens.push(connection.data.saveData);
15
- tokens.push(connection.data.type);
16
- connection.reset();
17
- queue(tokens, false);
18
- break;
19
10
  case Event.Navigation:
20
11
  tokens.push(navigation.data.fetchStart);
21
12
  tokens.push(navigation.data.connectStart);
@@ -1,15 +1,12 @@
1
- import * as connection from "@src/performance/connection";
2
1
  import * as navigation from "@src/performance/navigation";
3
2
  import * as observer from "@src/performance/observer";
4
3
 
5
4
  export function start(): void {
6
5
  navigation.reset();
7
- connection.start();
8
6
  observer.start();
9
7
  }
10
8
 
11
9
  export function stop(): void {
12
10
  observer.stop();
13
- connection.stop();
14
11
  navigation.reset();
15
12
  }
package/test/helper.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Core, Data, Layout } from "clarity-decode";
2
2
  import * as fs from 'fs';
3
+ import * as url from 'url';
3
4
  import * as path from 'path';
4
5
  import { Browser, Page, chromium } from 'playwright';
5
6
 
@@ -8,7 +9,13 @@ export async function launch(): Promise<Browser> {
8
9
  }
9
10
 
10
11
  export async function markup(page: Page, file: string, override: Core.Config = null): Promise<string[]> {
11
- const html = fs.readFileSync(path.resolve(__dirname, `./html/${file}`), 'utf8');
12
+ const htmlPath = path.resolve(__dirname, `./html/${file}`);
13
+ const htmlFileUrl = url.pathToFileURL(htmlPath).toString();
14
+ const html = fs.readFileSync(htmlPath, 'utf8');
15
+ await Promise.all([
16
+ page.goto(htmlFileUrl),
17
+ page.waitForNavigation()
18
+ ]);
12
19
  await page.setContent(html.replace("</body>", `
13
20
  <script>
14
21
  window.payloads = [];
@@ -17,6 +24,7 @@ export async function markup(page: Page, file: string, override: Core.Config = n
17
24
  </script>
18
25
  </body>
19
26
  `));
27
+ await page.hover("#two");
20
28
  await page.waitForFunction("payloads && payloads.length > 1");
21
29
  return await page.evaluate('payloads');
22
30
  }
@@ -34,7 +42,7 @@ export function node(decoded: Data.DecodedPayload[], key: string, value: string
34
42
  }
35
43
 
36
44
  // Walking over the decoded payload to find the right match
37
- for (let i = decoded.length - 1; i > 0; i--) {
45
+ for (let i = decoded.length - 1; i >= 0; i--) {
38
46
  if (decoded[i].dom) {
39
47
  for (let j = 0; j < decoded[i].dom.length; j++) {
40
48
  if (decoded[i].dom[j].data) {
package/types/core.d.ts CHANGED
@@ -5,6 +5,7 @@ type TaskResolve = () => void;
5
5
  type UploadCallback = (data: string) => void;
6
6
  type Region = [number /* RegionId */, string /* Query Selector */, RegionFilter? /* Region Filter */, string? /* Filter Text */];
7
7
  type Metric = [Data.Metric /* MetricId */, Extract /* Extract Filter */, string /* Match Value */, number? /* Scale Factor */];
8
+ type Dimension = [Data.Dimension /* DimensionId */, Extract /* Extract Filter */, string /* Match Value */];
8
9
 
9
10
  /* Enum */
10
11
 
@@ -100,12 +101,13 @@ export interface BrowserEvent {
100
101
  }
101
102
 
102
103
  export interface Report {
103
- c: Data.Check; // Reporting code
104
+ v: string; // Version
104
105
  p: string; // Project Id
105
106
  u: string; // User Id
106
107
  s: string; // Session Id
107
108
  n: number; // Page Number
108
109
  m?: string; // Message, optional
110
+ e?: string; // Error Stack, optional
109
111
  }
110
112
 
111
113
  export interface Config {
@@ -118,6 +120,7 @@ export interface Config {
118
120
  unmask?: string[];
119
121
  regions?: Region[];
120
122
  metrics?: Metric[];
123
+ dimensions?: Dimension[];
121
124
  cookies?: string[];
122
125
  report?: string;
123
126
  upload?: string | UploadCallback;