clarity-js 0.6.27 → 0.6.31
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/build/clarity.js +1928 -1861
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +1928 -1861
- package/package.json +1 -1
- package/src/core/measure.ts +2 -1
- package/src/core/report.ts +8 -7
- package/src/core/version.ts +1 -1
- package/src/data/limit.ts +0 -2
- package/src/data/metadata.ts +29 -3
- package/src/diagnostic/internal.ts +2 -1
- package/src/layout/dom.ts +28 -13
- package/src/layout/mutation.ts +4 -3
- package/src/layout/node.ts +1 -0
- package/src/layout/selector.ts +18 -4
- package/test/helper.ts +10 -2
- package/types/core.d.ts +2 -1
- package/types/data.d.ts +9 -3
- package/types/index.d.ts +1 -1
- package/types/layout.d.ts +1 -0
package/package.json
CHANGED
package/src/core/measure.ts
CHANGED
|
@@ -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) {
|
package/src/core/report.ts
CHANGED
|
@@ -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/
|
|
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(
|
|
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 = {
|
|
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
|
}
|
package/src/core/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let version = "0.6.
|
|
1
|
+
let version = "0.6.31";
|
|
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();
|
package/src/data/metadata.ts
CHANGED
|
@@ -38,10 +38,10 @@ 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);
|
|
44
43
|
metric.max(Metric.Automation, navigator.webdriver ? BooleanFlag.True : BooleanFlag.False);
|
|
44
|
+
userAgentData();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Metrics
|
|
@@ -64,13 +64,39 @@ export function start(): void {
|
|
|
64
64
|
track(u);
|
|
65
65
|
}
|
|
66
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
|
+
|
|
67
86
|
export function stop(): void {
|
|
68
87
|
callback = null;
|
|
69
88
|
rootDomain = null;
|
|
89
|
+
data = null;
|
|
70
90
|
}
|
|
71
91
|
|
|
72
|
-
export function metadata(cb: MetadataCallback): void {
|
|
73
|
-
|
|
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
|
+
}
|
|
74
100
|
}
|
|
75
101
|
|
|
76
102
|
export function id(): string {
|
|
@@ -28,7 +28,8 @@ export function log(code: Code, severity: Severity, name: string = null, message
|
|
|
28
28
|
|
|
29
29
|
function csp(e: SecurityPolicyViolationEvent): void {
|
|
30
30
|
let upload = config.upload as string;
|
|
31
|
-
|
|
31
|
+
// Look for first "/" starting after initial "https://" string
|
|
32
|
+
let parts = upload && typeof upload === Constant.String ? upload.substr(0, upload.indexOf("/", Constant.HTTPS.length)).split(Constant.Dot) : [];
|
|
32
33
|
let domain = parts.length >= 2 ? parts.splice(-2).join(Constant.Dot) : null;
|
|
33
34
|
// Capture content security policy violation only if disposition value is not explicitly set to "report"
|
|
34
35
|
if (domain && e.blockedURI && e.blockedURI.indexOf(domain) >= 0 && e["disposition"] !== Constant.Report) {
|
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, NodeInfo, NodeValue, SelectorInput, Source } from "@clarity-types/layout";
|
|
3
|
+
import { Constant, NodeInfo, NodeValue, Selector, SelectorInput, Source } from "@clarity-types/layout";
|
|
4
4
|
import config from "@src/core/config";
|
|
5
5
|
import hash from "@src/core/hash";
|
|
6
6
|
import * as internal from "@src/diagnostic/internal";
|
|
@@ -12,13 +12,15 @@ 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[] = [];
|
|
19
19
|
let values: NodeValue[] = [];
|
|
20
20
|
let updateMap: number[] = [];
|
|
21
21
|
let hashMap: { [hash: string]: number } = {};
|
|
22
|
+
let override = [];
|
|
23
|
+
let unmask = [];
|
|
22
24
|
|
|
23
25
|
// The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced
|
|
24
26
|
let idMap: WeakMap<Node, number> = null; // Maps node => id.
|
|
@@ -27,7 +29,7 @@ let privacyMap: WeakMap<Node, Privacy> = null; // Maps node => Privacy (enum)
|
|
|
27
29
|
|
|
28
30
|
export function start(): void {
|
|
29
31
|
reset();
|
|
30
|
-
parse(document);
|
|
32
|
+
parse(document, true);
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export function stop(): void {
|
|
@@ -40,6 +42,8 @@ function reset(): void {
|
|
|
40
42
|
values = [];
|
|
41
43
|
updateMap = [];
|
|
42
44
|
hashMap = {};
|
|
45
|
+
override = [];
|
|
46
|
+
unmask = [];
|
|
43
47
|
idMap = new WeakMap();
|
|
44
48
|
iframeMap = new WeakMap();
|
|
45
49
|
privacyMap = new WeakMap();
|
|
@@ -47,10 +51,13 @@ function reset(): void {
|
|
|
47
51
|
|
|
48
52
|
// We parse new root nodes for any regions or masked nodes in the beginning (document) and
|
|
49
53
|
// later whenever there are new additions or modifications to DOM (mutations)
|
|
50
|
-
export function parse(root: ParentNode): void {
|
|
54
|
+
export function parse(root: ParentNode, init: boolean = false): void {
|
|
51
55
|
// Wrap selectors in a try / catch block.
|
|
52
56
|
// It's possible for script to receive invalid selectors, e.g. "'#id'" with extra quotes, and cause the code below to fail
|
|
53
57
|
try {
|
|
58
|
+
// Parse unmask configuration into separate query selectors and override tokens as part of initialization
|
|
59
|
+
if (init) { config.unmask.forEach(x => x.indexOf(Constant.Bang) < 0 ? unmask.push(x) : override.push(x.substr(1))); }
|
|
60
|
+
|
|
54
61
|
// Since mutations may happen on leaf nodes too, e.g. text nodes, which may not support all selector APIs.
|
|
55
62
|
// We ensure that the root note supports querySelectorAll API before executing the code below to identify new regions.
|
|
56
63
|
if ("querySelectorAll" in root) {
|
|
@@ -58,7 +65,7 @@ export function parse(root: ParentNode): void {
|
|
|
58
65
|
extract.metrics(root, config.metrics);
|
|
59
66
|
extract.dimensions(root, config.dimensions);
|
|
60
67
|
config.mask.forEach(x => root.querySelectorAll(x).forEach(e => privacyMap.set(e, Privacy.TextImage))); // Masked Elements
|
|
61
|
-
|
|
68
|
+
unmask.forEach(x => root.querySelectorAll(x).forEach(e => privacyMap.set(e, Privacy.None))); // Unmasked Elements
|
|
62
69
|
}
|
|
63
70
|
} catch (e) { internal.log(Code.Selector, Severity.Warning, e ? e.name : null); }
|
|
64
71
|
}
|
|
@@ -80,19 +87,17 @@ export function add(node: Node, parent: Node, data: NodeInfo, source: Source): v
|
|
|
80
87
|
let previousId = getPreviousId(node);
|
|
81
88
|
let privacy = config.content ? Privacy.Sensitive : Privacy.Text;
|
|
82
89
|
let parentValue = null;
|
|
83
|
-
let parentTag = Constant.Empty;
|
|
84
90
|
let regionId = region.exists(node) ? id : null;
|
|
85
91
|
|
|
86
92
|
if (parentId >= 0 && values[parentId]) {
|
|
87
93
|
parentValue = values[parentId];
|
|
88
|
-
parentTag = parentValue.data.tag;
|
|
89
94
|
parentValue.children.push(id);
|
|
90
95
|
regionId = regionId === null ? parentValue.region : regionId;
|
|
91
96
|
privacy = parentValue.metadata.privacy;
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
// Check to see if this particular node should be masked or not
|
|
95
|
-
privacy = getPrivacy(node, data,
|
|
100
|
+
privacy = getPrivacy(node, data, parentValue, privacy);
|
|
96
101
|
|
|
97
102
|
// If there's an explicit region attribute set on the element, use it to mark a region on the page
|
|
98
103
|
if (data.attributes && Constant.RegionData in data.attributes) {
|
|
@@ -198,13 +203,27 @@ export function iframe(node: Node): HTMLIFrameElement {
|
|
|
198
203
|
return doc && iframeMap.has(doc) ? iframeMap.get(doc) : null;
|
|
199
204
|
}
|
|
200
205
|
|
|
201
|
-
function getPrivacy(node: Node, data: NodeInfo,
|
|
206
|
+
function getPrivacy(node: Node, data: NodeInfo, parent: NodeValue, privacy: Privacy): Privacy {
|
|
202
207
|
let attributes = data.attributes;
|
|
203
208
|
let tag = data.tag.toUpperCase();
|
|
204
209
|
|
|
205
210
|
// If this node was explicitly configured to contain sensitive content, use that information and return the value
|
|
206
211
|
if (privacyMap.has(node)) { return privacyMap.get(node); }
|
|
207
212
|
|
|
213
|
+
// If it's a text node belonging to a STYLE or TITLE tag;
|
|
214
|
+
// Or, the text node belongs to one of SCRUB_EXCEPTIONS
|
|
215
|
+
// then reset the privacy setting to ensure we capture the content
|
|
216
|
+
if (tag === Constant.TextTag && parent && parent.data) {
|
|
217
|
+
let path = parent.selector ? parent.selector[Selector.Stable] : Constant.Empty;
|
|
218
|
+
privacy = parent.data.tag === Constant.StyleTag || parent.data.tag === Constant.TitleTag ? Privacy.None : privacy;
|
|
219
|
+
for (let entry of override) {
|
|
220
|
+
if (path.indexOf(entry) >= 0) {
|
|
221
|
+
privacy = Privacy.None;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
208
227
|
// Do not proceed if attributes are missing for the node
|
|
209
228
|
if (attributes === null || attributes === undefined) { return privacy; }
|
|
210
229
|
|
|
@@ -243,10 +262,6 @@ function getPrivacy(node: Node, data: NodeInfo, parentTag: string, privacy: Priv
|
|
|
243
262
|
if (Constant.MaskData in attributes) { privacy = Privacy.TextImage; }
|
|
244
263
|
if (Constant.UnmaskData in attributes) { privacy = Privacy.None; }
|
|
245
264
|
|
|
246
|
-
// If it's a text node belonging to a STYLE or TITLE tag; then reset the privacy setting to ensure we capture the content
|
|
247
|
-
let cTag = tag === Constant.TextTag ? parentTag : tag;
|
|
248
|
-
if (cTag === Constant.StyleTag || cTag === Constant.TitleTag) { privacy = Privacy.None; }
|
|
249
|
-
|
|
250
265
|
return privacy;
|
|
251
266
|
}
|
|
252
267
|
|
package/src/layout/mutation.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
@@ -56,7 +56,7 @@ export function start(): void {
|
|
|
56
56
|
// In case we are unable to add a hook and browser throws an exception,
|
|
57
57
|
// reset attachShadow variable and resume processing like before
|
|
58
58
|
try {
|
|
59
|
-
|
|
59
|
+
Element.prototype.attachShadow = function (): ShadowRoot {
|
|
60
60
|
return schedule(attachShadow.apply(this, arguments)) as ShadowRoot;
|
|
61
61
|
}
|
|
62
62
|
} catch { attachShadow = null; }
|
|
@@ -107,7 +107,7 @@ export function stop(): void {
|
|
|
107
107
|
|
|
108
108
|
// Restoring original attachShadow
|
|
109
109
|
if (attachShadow != null) {
|
|
110
|
-
|
|
110
|
+
Element.prototype.attachShadow = attachShadow;
|
|
111
111
|
attachShadow = null;
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -145,6 +145,7 @@ async function process(): Promise<void> {
|
|
|
145
145
|
let target = mutation.target;
|
|
146
146
|
let type = track(mutation, timer);
|
|
147
147
|
if (type && target && target.ownerDocument) { dom.parse(target.ownerDocument); }
|
|
148
|
+
if (type && target && target.nodeType == Node.DOCUMENT_FRAGMENT_NODE && (target as ShadowRoot).host) { dom.parse(target as ShadowRoot); }
|
|
148
149
|
switch (type) {
|
|
149
150
|
case Constant.Attributes:
|
|
150
151
|
processNode(target, Source.Attributes);
|
package/src/layout/node.ts
CHANGED
|
@@ -46,6 +46,7 @@ export default function (node: Node, source: Source): Node {
|
|
|
46
46
|
case Node.DOCUMENT_FRAGMENT_NODE:
|
|
47
47
|
let shadowRoot = (node as ShadowRoot);
|
|
48
48
|
if (shadowRoot.host) {
|
|
49
|
+
dom.parse(shadowRoot);
|
|
49
50
|
let type = typeof (shadowRoot.constructor);
|
|
50
51
|
if (type === Constant.Function && shadowRoot.constructor.toString().indexOf(Constant.NativeCode) >= 0) {
|
|
51
52
|
observe(shadowRoot);
|
package/src/layout/selector.ts
CHANGED
|
@@ -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
|
-
//
|
|
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 ?
|
|
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
|
-
|
|
59
|
+
if (c >= Character.Zero && c <= Character.Nine) { return true };
|
|
46
60
|
}
|
|
47
61
|
return false;
|
|
48
62
|
}
|
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
|
|
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
|
|
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
|
@@ -101,12 +101,13 @@ export interface BrowserEvent {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export interface Report {
|
|
104
|
-
|
|
104
|
+
v: string; // Version
|
|
105
105
|
p: string; // Project Id
|
|
106
106
|
u: string; // User Id
|
|
107
107
|
s: string; // Session Id
|
|
108
108
|
n: number; // Page Number
|
|
109
109
|
m?: string; // Message, optional
|
|
110
|
+
e?: string; // Error Stack, optional
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
export interface Config {
|
package/types/data.d.ts
CHANGED
|
@@ -84,7 +84,8 @@ export const enum Metric {
|
|
|
84
84
|
CartTax = 23,
|
|
85
85
|
CartTotal = 24,
|
|
86
86
|
EventCount = 25,
|
|
87
|
-
Automation = 26
|
|
87
|
+
Automation = 26,
|
|
88
|
+
Mobile = 27
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
export const enum Dimension {
|
|
@@ -109,7 +110,11 @@ export const enum Dimension {
|
|
|
109
110
|
Headline = 18,
|
|
110
111
|
MetaType = 19,
|
|
111
112
|
MetaTitle = 20,
|
|
112
|
-
Generator = 21
|
|
113
|
+
Generator = 21,
|
|
114
|
+
Platform = 22,
|
|
115
|
+
PlatformVersion = 23,
|
|
116
|
+
Brand = 24,
|
|
117
|
+
Model = 25
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
export const enum Check {
|
|
@@ -238,7 +243,8 @@ export const enum Constant {
|
|
|
238
243
|
HTTPS = "https://",
|
|
239
244
|
CompressionStream = "CompressionStream",
|
|
240
245
|
Accept = "Accept",
|
|
241
|
-
ClarityGzip = "application/x-clarity-gzip"
|
|
246
|
+
ClarityGzip = "application/x-clarity-gzip",
|
|
247
|
+
Tilde = "~",
|
|
242
248
|
}
|
|
243
249
|
|
|
244
250
|
export const enum XMLReadyState {
|
package/types/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ interface Clarity {
|
|
|
15
15
|
event: (name: string, value: string) => void;
|
|
16
16
|
set: (variable: string, value: string | string[]) => void;
|
|
17
17
|
identify: (userId: string, sessionId?: string, pageId?: string) => void;
|
|
18
|
-
metadata: (callback: Data.MetadataCallback) => void;
|
|
18
|
+
metadata: (callback: Data.MetadataCallback, wait?: boolean) => void;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
interface Helper {
|