clarity-js 0.8.42 → 0.8.44
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/README.md +26 -26
- package/build/clarity.extended.js +1 -1
- package/build/clarity.insight.js +1 -1
- package/build/clarity.js +6101 -6071
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +6101 -6071
- package/build/clarity.performance.js +1 -1
- package/package.json +70 -70
- package/rollup.config.ts +161 -161
- package/src/clarity.ts +65 -65
- package/src/core/api.ts +8 -8
- package/src/core/config.ts +29 -29
- package/src/core/copy.ts +3 -3
- package/src/core/dynamic.ts +13 -7
- package/src/core/event.ts +53 -53
- package/src/core/hash.ts +19 -19
- package/src/core/history.ts +71 -71
- package/src/core/index.ts +81 -81
- package/src/core/measure.ts +19 -19
- package/src/core/report.ts +28 -28
- package/src/core/scrub.ts +204 -202
- package/src/core/task.ts +181 -181
- package/src/core/throttle.ts +46 -46
- package/src/core/time.ts +26 -26
- package/src/core/timeout.ts +10 -10
- package/src/core/version.ts +2 -2
- package/src/data/baseline.ts +162 -162
- package/src/data/compress.ts +31 -31
- package/src/data/consent.ts +77 -77
- package/src/data/cookie.ts +90 -0
- package/src/data/custom.ts +23 -23
- package/src/data/dimension.ts +53 -53
- package/src/data/encode.ts +155 -155
- package/src/data/envelope.ts +53 -53
- package/src/data/extract.ts +211 -211
- package/src/data/index.ts +51 -50
- package/src/data/limit.ts +44 -44
- package/src/data/metadata.ts +321 -408
- package/src/data/metric.ts +51 -51
- package/src/data/ping.ts +36 -36
- package/src/data/signal.ts +30 -30
- package/src/data/summary.ts +34 -34
- package/src/data/token.ts +39 -39
- package/src/data/upgrade.ts +44 -44
- package/src/data/upload.ts +333 -333
- package/src/data/util.ts +18 -0
- package/src/data/variable.ts +83 -83
- package/src/diagnostic/encode.ts +40 -40
- package/src/diagnostic/fraud.ts +36 -36
- package/src/diagnostic/index.ts +13 -13
- package/src/diagnostic/internal.ts +28 -28
- package/src/diagnostic/script.ts +35 -35
- package/src/dynamic/agent/blank.ts +2 -2
- package/src/dynamic/agent/crisp.ts +40 -40
- package/src/dynamic/agent/encode.ts +25 -25
- package/src/dynamic/agent/index.ts +8 -8
- package/src/dynamic/agent/livechat.ts +58 -58
- package/src/dynamic/agent/tidio.ts +44 -44
- package/src/global.ts +6 -6
- package/src/index.ts +9 -9
- package/src/insight/blank.ts +14 -14
- package/src/insight/encode.ts +60 -60
- package/src/insight/snapshot.ts +114 -114
- package/src/interaction/change.ts +38 -38
- package/src/interaction/click.ts +173 -173
- package/src/interaction/clipboard.ts +32 -32
- package/src/interaction/encode.ts +210 -210
- package/src/interaction/index.ts +60 -60
- package/src/interaction/input.ts +57 -57
- package/src/interaction/pointer.ts +137 -137
- package/src/interaction/resize.ts +50 -50
- package/src/interaction/scroll.ts +129 -129
- package/src/interaction/selection.ts +66 -66
- package/src/interaction/submit.ts +30 -30
- package/src/interaction/timeline.ts +69 -69
- package/src/interaction/unload.ts +26 -26
- package/src/interaction/visibility.ts +27 -27
- package/src/layout/animation.ts +133 -133
- package/src/layout/custom.ts +42 -42
- package/src/layout/discover.ts +31 -31
- package/src/layout/document.ts +46 -46
- package/src/layout/dom.ts +439 -439
- package/src/layout/encode.ts +154 -154
- package/src/layout/index.ts +42 -42
- package/src/layout/mutation.ts +411 -411
- package/src/layout/node.ts +294 -294
- package/src/layout/offset.ts +19 -19
- package/src/layout/region.ts +151 -151
- package/src/layout/schema.ts +63 -63
- package/src/layout/selector.ts +82 -82
- package/src/layout/style.ts +159 -159
- package/src/layout/target.ts +32 -32
- package/src/layout/traverse.ts +27 -27
- package/src/performance/blank.ts +9 -9
- package/src/performance/encode.ts +31 -31
- package/src/performance/index.ts +12 -12
- package/src/performance/interaction.ts +125 -125
- package/src/performance/navigation.ts +31 -31
- package/src/performance/observer.ts +112 -112
- package/src/queue.ts +33 -33
- package/test/core.test.ts +139 -139
- package/test/helper.ts +162 -162
- package/test/html/core.html +27 -27
- package/test/stub.test.ts +7 -7
- package/test/tsconfig.test.json +5 -5
- package/tsconfig.json +21 -21
- package/tslint.json +32 -32
- package/types/agent.d.ts +39 -39
- package/types/core.d.ts +150 -150
- package/types/data.d.ts +572 -571
- package/types/diagnostic.d.ts +24 -24
- package/types/global.d.ts +30 -30
- package/types/index.d.ts +40 -40
- package/types/interaction.d.ts +177 -177
- package/types/layout.d.ts +276 -276
- package/types/performance.d.ts +31 -31
package/test/core.test.ts
CHANGED
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
import { assert } from 'chai';
|
|
2
|
-
import { Browser, Page } from 'playwright';
|
|
3
|
-
import { changes, clicks, inputs, launch, markup, node, text } from './helper';
|
|
4
|
-
import { decode } from "clarity-decode";
|
|
5
|
-
|
|
6
|
-
let browser: Browser;
|
|
7
|
-
let page: Page;
|
|
8
|
-
|
|
9
|
-
describe('Core Tests', () => {
|
|
10
|
-
before(async () => {
|
|
11
|
-
browser = await launch();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
beforeEach(async () => {
|
|
15
|
-
page = await browser.newPage();
|
|
16
|
-
await page.goto('about:blank');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
await page.close();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
after(async () => {
|
|
24
|
-
await browser.close();
|
|
25
|
-
browser = null;
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should mask sensitive content by default', async () => {
|
|
29
|
-
let encoded: string[] = await markup(page, "core.html");
|
|
30
|
-
let decoded = encoded.map(x => decode(x));
|
|
31
|
-
let heading = text(decoded, "one");
|
|
32
|
-
let address = text(decoded, "two");
|
|
33
|
-
let email = node(decoded, "attributes.id", "eml");
|
|
34
|
-
let password = node(decoded, "attributes.id", "pwd");
|
|
35
|
-
let search = node(decoded, "attributes.id", "search");
|
|
36
|
-
let card = node(decoded, "attributes.id", "cardnum");
|
|
37
|
-
let option = text(decoded, "option1");
|
|
38
|
-
let textarea = text(decoded, "textarea");
|
|
39
|
-
let click = clicks(decoded)[0];
|
|
40
|
-
let input = inputs(decoded)[0];
|
|
41
|
-
let group = changes(decoded);
|
|
42
|
-
|
|
43
|
-
// Non-sensitive fields continue to pass through with sensitive bits masked off
|
|
44
|
-
assert.equal(heading, "Thanks for your order #▫▪▪▫▫▫▪▪");
|
|
45
|
-
|
|
46
|
-
// Sensitive fields, including input fields, are randomized and masked
|
|
47
|
-
assert.equal(address, "•••••• ••••• ••••• ••••• ••••• •••••");
|
|
48
|
-
assert.equal(email.attributes.value, "••••• •••• •••• ••••");
|
|
49
|
-
assert.equal(password.attributes.value, "••••");
|
|
50
|
-
assert.equal(search.attributes.value, "••••• •••• ••••");
|
|
51
|
-
assert.equal(card.attributes.value, "•••••");
|
|
52
|
-
assert.equal(textarea, "••••• •••••");
|
|
53
|
-
assert.equal(option, "• •••••");
|
|
54
|
-
|
|
55
|
-
// Clicked text and input value should be consistent with uber masking configuration
|
|
56
|
-
assert.equal(click.data.text, "Hello ▪▪▪▫▪");
|
|
57
|
-
assert.equal(input.data.value, "••••• •••• •••• ••••");
|
|
58
|
-
assert.equal(group.length, 2);
|
|
59
|
-
// Search change - we should captured mangled input and hash
|
|
60
|
-
assert.equal(group[0].data.type, "search");
|
|
61
|
-
assert.equal(group[0].data.value, "••••• •••• •••• ••••");
|
|
62
|
-
assert.equal(group[0].data.checksum, "4y7m6");
|
|
63
|
-
// Password change - we should capture placholder value and empty hash
|
|
64
|
-
assert.equal(group[1].data.type, "password");
|
|
65
|
-
assert.equal(group[1].data.value, "••••");
|
|
66
|
-
assert.equal(group[1].data.checksum, "");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should mask all text in strict mode', async () => {
|
|
70
|
-
let encoded: string[] = await markup(page, "core.html", { content: false });
|
|
71
|
-
let decoded = encoded.map(x => decode(x));
|
|
72
|
-
let heading = text(decoded, "one");
|
|
73
|
-
let address = text(decoded, "two");
|
|
74
|
-
let email = node(decoded, "attributes.id", "eml");
|
|
75
|
-
let password = node(decoded, "attributes.id", "pwd");
|
|
76
|
-
let search = node(decoded, "attributes.id", "search");
|
|
77
|
-
let card = node(decoded, "attributes.id", "cardnum");
|
|
78
|
-
let click = clicks(decoded)[0];
|
|
79
|
-
let input = inputs(decoded)[0];
|
|
80
|
-
let option = text(decoded, "option1");
|
|
81
|
-
|
|
82
|
-
// All fields are randomized and masked
|
|
83
|
-
assert.equal(heading, "• ••••• ••••• ••••• ••••• •••••");
|
|
84
|
-
assert.equal(address, "•••••• ••••• ••••• ••••• ••••• •••••");
|
|
85
|
-
assert.equal(email.attributes.value, "••••• •••• •••• ••••");
|
|
86
|
-
assert.equal(password.attributes.value, "••••");
|
|
87
|
-
assert.equal(search.attributes.value, "••••• •••• ••••");
|
|
88
|
-
assert.equal(card.attributes.value, "•••••");
|
|
89
|
-
assert.equal(option, "• •••••");
|
|
90
|
-
|
|
91
|
-
// Clicked text and input value should also be masked in strict mode
|
|
92
|
-
assert.equal(click.data.text, "••••• •••• ••••");
|
|
93
|
-
assert.equal(input.data.value, "••••• •••• •••• ••••");
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should unmask non-sensitive text in relaxed mode', async () => {
|
|
97
|
-
let encoded: string[] = await markup(page, "core.html", { unmask: ["body"] });
|
|
98
|
-
let decoded = encoded.map(x => decode(x));
|
|
99
|
-
let heading = text(decoded, "one");
|
|
100
|
-
let address = text(decoded, "two");
|
|
101
|
-
let email = node(decoded, "attributes.id", "eml");
|
|
102
|
-
let password = node(decoded, "attributes.id", "pwd");
|
|
103
|
-
let search = node(decoded, "attributes.id", "search");
|
|
104
|
-
let card = node(decoded, "attributes.id", "cardnum");
|
|
105
|
-
let click = clicks(decoded)[0];
|
|
106
|
-
let input = inputs(decoded)[0];
|
|
107
|
-
let option = text(decoded, "option1");
|
|
108
|
-
|
|
109
|
-
// Text flows through unmasked for non-sensitive fields, with exception of input fields
|
|
110
|
-
assert.equal(heading, "Thanks for your order #2AB700GH");
|
|
111
|
-
assert.equal(address, "1 Microsoft Way, Redmond, WA - 98052");
|
|
112
|
-
assert.equal(search.attributes.value, "••••• •••• ••••");
|
|
113
|
-
assert.equal(option, "• •••••");
|
|
114
|
-
|
|
115
|
-
// Sensitive fields are still masked
|
|
116
|
-
assert.equal(email.attributes.value, "••••• •••• •••• ••••");
|
|
117
|
-
assert.equal(password.attributes.value, "••••");
|
|
118
|
-
assert.equal(card.attributes.value, "•••••");
|
|
119
|
-
|
|
120
|
-
// Clicked text comes through unmasked in relaxed mode but input is still masked
|
|
121
|
-
assert.equal(click.data.text, "Hello Wor1d");
|
|
122
|
-
assert.equal(input.data.value, "••••• •••• •••• ••••");
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should respect mask config even in relaxed mode', async () => {
|
|
126
|
-
let encoded: string[] = await markup(page, "core.html", { mask: ["#mask"], unmask: ["body"] });
|
|
127
|
-
let decoded = encoded.map(x => decode(x));
|
|
128
|
-
let subtree = text(decoded, "child");
|
|
129
|
-
let click = clicks(decoded)[0];
|
|
130
|
-
let input = inputs(decoded)[0];
|
|
131
|
-
|
|
132
|
-
// Masked sub-trees continue to stay masked
|
|
133
|
-
assert.equal(subtree, "••••• •••••");
|
|
134
|
-
|
|
135
|
-
// Clicked text is masked due to masked configuration and input value is also masked
|
|
136
|
-
assert.equal(click.data.text, "••••• •••• ••••");
|
|
137
|
-
assert.equal(input.data.value, "••••• •••• •••• ••••");
|
|
138
|
-
});
|
|
139
|
-
});
|
|
1
|
+
import { assert } from 'chai';
|
|
2
|
+
import { Browser, Page } from 'playwright';
|
|
3
|
+
import { changes, clicks, inputs, launch, markup, node, text } from './helper';
|
|
4
|
+
import { decode } from "clarity-decode";
|
|
5
|
+
|
|
6
|
+
let browser: Browser;
|
|
7
|
+
let page: Page;
|
|
8
|
+
|
|
9
|
+
describe('Core Tests', () => {
|
|
10
|
+
before(async () => {
|
|
11
|
+
browser = await launch();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
page = await browser.newPage();
|
|
16
|
+
await page.goto('about:blank');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await page.close();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
after(async () => {
|
|
24
|
+
await browser.close();
|
|
25
|
+
browser = null;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should mask sensitive content by default', async () => {
|
|
29
|
+
let encoded: string[] = await markup(page, "core.html");
|
|
30
|
+
let decoded = encoded.map(x => decode(x));
|
|
31
|
+
let heading = text(decoded, "one");
|
|
32
|
+
let address = text(decoded, "two");
|
|
33
|
+
let email = node(decoded, "attributes.id", "eml");
|
|
34
|
+
let password = node(decoded, "attributes.id", "pwd");
|
|
35
|
+
let search = node(decoded, "attributes.id", "search");
|
|
36
|
+
let card = node(decoded, "attributes.id", "cardnum");
|
|
37
|
+
let option = text(decoded, "option1");
|
|
38
|
+
let textarea = text(decoded, "textarea");
|
|
39
|
+
let click = clicks(decoded)[0];
|
|
40
|
+
let input = inputs(decoded)[0];
|
|
41
|
+
let group = changes(decoded);
|
|
42
|
+
|
|
43
|
+
// Non-sensitive fields continue to pass through with sensitive bits masked off
|
|
44
|
+
assert.equal(heading, "Thanks for your order #▫▪▪▫▫▫▪▪");
|
|
45
|
+
|
|
46
|
+
// Sensitive fields, including input fields, are randomized and masked
|
|
47
|
+
assert.equal(address, "•••••• ••••• ••••• ••••• ••••• •••••");
|
|
48
|
+
assert.equal(email.attributes.value, "••••• •••• •••• ••••");
|
|
49
|
+
assert.equal(password.attributes.value, "••••");
|
|
50
|
+
assert.equal(search.attributes.value, "••••• •••• ••••");
|
|
51
|
+
assert.equal(card.attributes.value, "•••••");
|
|
52
|
+
assert.equal(textarea, "••••• •••••");
|
|
53
|
+
assert.equal(option, "• •••••");
|
|
54
|
+
|
|
55
|
+
// Clicked text and input value should be consistent with uber masking configuration
|
|
56
|
+
assert.equal(click.data.text, "Hello ▪▪▪▫▪");
|
|
57
|
+
assert.equal(input.data.value, "••••• •••• •••• ••••");
|
|
58
|
+
assert.equal(group.length, 2);
|
|
59
|
+
// Search change - we should captured mangled input and hash
|
|
60
|
+
assert.equal(group[0].data.type, "search");
|
|
61
|
+
assert.equal(group[0].data.value, "••••• •••• •••• ••••");
|
|
62
|
+
assert.equal(group[0].data.checksum, "4y7m6");
|
|
63
|
+
// Password change - we should capture placholder value and empty hash
|
|
64
|
+
assert.equal(group[1].data.type, "password");
|
|
65
|
+
assert.equal(group[1].data.value, "••••");
|
|
66
|
+
assert.equal(group[1].data.checksum, "");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should mask all text in strict mode', async () => {
|
|
70
|
+
let encoded: string[] = await markup(page, "core.html", { content: false });
|
|
71
|
+
let decoded = encoded.map(x => decode(x));
|
|
72
|
+
let heading = text(decoded, "one");
|
|
73
|
+
let address = text(decoded, "two");
|
|
74
|
+
let email = node(decoded, "attributes.id", "eml");
|
|
75
|
+
let password = node(decoded, "attributes.id", "pwd");
|
|
76
|
+
let search = node(decoded, "attributes.id", "search");
|
|
77
|
+
let card = node(decoded, "attributes.id", "cardnum");
|
|
78
|
+
let click = clicks(decoded)[0];
|
|
79
|
+
let input = inputs(decoded)[0];
|
|
80
|
+
let option = text(decoded, "option1");
|
|
81
|
+
|
|
82
|
+
// All fields are randomized and masked
|
|
83
|
+
assert.equal(heading, "• ••••• ••••• ••••• ••••• •••••");
|
|
84
|
+
assert.equal(address, "•••••• ••••• ••••• ••••• ••••• •••••");
|
|
85
|
+
assert.equal(email.attributes.value, "••••• •••• •••• ••••");
|
|
86
|
+
assert.equal(password.attributes.value, "••••");
|
|
87
|
+
assert.equal(search.attributes.value, "••••• •••• ••••");
|
|
88
|
+
assert.equal(card.attributes.value, "•••••");
|
|
89
|
+
assert.equal(option, "• •••••");
|
|
90
|
+
|
|
91
|
+
// Clicked text and input value should also be masked in strict mode
|
|
92
|
+
assert.equal(click.data.text, "••••• •••• ••••");
|
|
93
|
+
assert.equal(input.data.value, "••••• •••• •••• ••••");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should unmask non-sensitive text in relaxed mode', async () => {
|
|
97
|
+
let encoded: string[] = await markup(page, "core.html", { unmask: ["body"] });
|
|
98
|
+
let decoded = encoded.map(x => decode(x));
|
|
99
|
+
let heading = text(decoded, "one");
|
|
100
|
+
let address = text(decoded, "two");
|
|
101
|
+
let email = node(decoded, "attributes.id", "eml");
|
|
102
|
+
let password = node(decoded, "attributes.id", "pwd");
|
|
103
|
+
let search = node(decoded, "attributes.id", "search");
|
|
104
|
+
let card = node(decoded, "attributes.id", "cardnum");
|
|
105
|
+
let click = clicks(decoded)[0];
|
|
106
|
+
let input = inputs(decoded)[0];
|
|
107
|
+
let option = text(decoded, "option1");
|
|
108
|
+
|
|
109
|
+
// Text flows through unmasked for non-sensitive fields, with exception of input fields
|
|
110
|
+
assert.equal(heading, "Thanks for your order #2AB700GH");
|
|
111
|
+
assert.equal(address, "1 Microsoft Way, Redmond, WA - 98052");
|
|
112
|
+
assert.equal(search.attributes.value, "••••• •••• ••••");
|
|
113
|
+
assert.equal(option, "• •••••");
|
|
114
|
+
|
|
115
|
+
// Sensitive fields are still masked
|
|
116
|
+
assert.equal(email.attributes.value, "••••• •••• •••• ••••");
|
|
117
|
+
assert.equal(password.attributes.value, "••••");
|
|
118
|
+
assert.equal(card.attributes.value, "•••••");
|
|
119
|
+
|
|
120
|
+
// Clicked text comes through unmasked in relaxed mode but input is still masked
|
|
121
|
+
assert.equal(click.data.text, "Hello Wor1d");
|
|
122
|
+
assert.equal(input.data.value, "••••• •••• •••• ••••");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should respect mask config even in relaxed mode', async () => {
|
|
126
|
+
let encoded: string[] = await markup(page, "core.html", { mask: ["#mask"], unmask: ["body"] });
|
|
127
|
+
let decoded = encoded.map(x => decode(x));
|
|
128
|
+
let subtree = text(decoded, "child");
|
|
129
|
+
let click = clicks(decoded)[0];
|
|
130
|
+
let input = inputs(decoded)[0];
|
|
131
|
+
|
|
132
|
+
// Masked sub-trees continue to stay masked
|
|
133
|
+
assert.equal(subtree, "••••• •••••");
|
|
134
|
+
|
|
135
|
+
// Clicked text is masked due to masked configuration and input value is also masked
|
|
136
|
+
assert.equal(click.data.text, "••••• •••• ••••");
|
|
137
|
+
assert.equal(input.data.value, "••••• •••• •••• ••••");
|
|
138
|
+
});
|
|
139
|
+
});
|
package/test/helper.ts
CHANGED
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
import { Core, Data, decode, Interaction, Layout } from "clarity-decode";
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as url from 'url';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import { Browser, Page, chromium } from 'playwright';
|
|
6
|
-
|
|
7
|
-
export async function launch(): Promise<Browser> {
|
|
8
|
-
return chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function markup(page: Page, file: string, override: Core.Config = null): Promise<string[]> {
|
|
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
|
-
]);
|
|
19
|
-
await page.setContent(html.replace("</body>", `
|
|
20
|
-
<script>
|
|
21
|
-
window.payloads = [];
|
|
22
|
-
${fs.readFileSync(path.resolve(__dirname, `../build/clarity.min.js`), 'utf8')};
|
|
23
|
-
clarity("start", ${config(override)});
|
|
24
|
-
</script>
|
|
25
|
-
</body>
|
|
26
|
-
`));
|
|
27
|
-
await page.hover("#two");
|
|
28
|
-
await page.click("#child");
|
|
29
|
-
await page.locator('#search').fill('');
|
|
30
|
-
await page.locator('#search').type('query with numb3rs');
|
|
31
|
-
await page.locator('#pwd').type('p1ssw0rd');
|
|
32
|
-
await page.locator('#eml').fill('');
|
|
33
|
-
await page.locator('#eml').type('hello@world.com');
|
|
34
|
-
await page.waitForFunction("payloads && payloads.length > 2");
|
|
35
|
-
return await page.evaluate('payloads');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function clicks(decoded: Data.DecodedPayload[]): Interaction.ClickEvent[] {
|
|
39
|
-
let output: Interaction.ClickEvent[] = [];
|
|
40
|
-
for (let i = decoded.length - 1; i >= 0; i--) {
|
|
41
|
-
if (decoded[i].click) {
|
|
42
|
-
for (let j = 0; j < decoded[i].click.length;j++)
|
|
43
|
-
{
|
|
44
|
-
output.push(decoded[i].click[j]);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return output;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function inputs(decoded: Data.DecodedPayload[]): Interaction.InputEvent[] {
|
|
52
|
-
let output: Interaction.InputEvent[] = [];
|
|
53
|
-
for (let i = decoded.length - 1; i >= 0; i--) {
|
|
54
|
-
if (decoded[i].input) {
|
|
55
|
-
for (let j = 0; j < decoded[i].input.length;j++)
|
|
56
|
-
{
|
|
57
|
-
output.push(decoded[i].input[j]);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return output;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function changes(decoded: Data.DecodedPayload[]): Interaction.ChangeEvent[] {
|
|
65
|
-
let output: Interaction.ChangeEvent[] = [];
|
|
66
|
-
for (let i = decoded.length - 1; i >= 0; i--) {
|
|
67
|
-
if (decoded[i].change) {
|
|
68
|
-
for (let j = 0; j < decoded[i].change.length;j++)
|
|
69
|
-
{
|
|
70
|
-
output.push(decoded[i].change[j]);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return output;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function node(decoded: Data.DecodedPayload[], key: string, value: string | number, tag: string = null): Layout.DomData {
|
|
78
|
-
let sub = null;
|
|
79
|
-
|
|
80
|
-
// Exploding nested keys into key and sub key
|
|
81
|
-
if (key.indexOf(".") > 0) {
|
|
82
|
-
const parts = key.split(".");
|
|
83
|
-
if (parts.length === 2) {
|
|
84
|
-
key = parts[0];
|
|
85
|
-
sub = parts[1];
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Walking over the decoded payload to find the right match
|
|
90
|
-
for (let i = decoded.length - 1; i >= 0; i--) {
|
|
91
|
-
if (decoded[i].dom) {
|
|
92
|
-
for (let j = 0; j < decoded[i].dom.length; j++) {
|
|
93
|
-
if (decoded[i].dom[j].data) {
|
|
94
|
-
for (let k = 0; k < decoded[i].dom[j].data.length; k++) {
|
|
95
|
-
let d = decoded[i].dom[j].data[k];
|
|
96
|
-
if ((sub && d[key] && d[key][sub] === value) ||
|
|
97
|
-
(d[key] && d[key] === value)) {
|
|
98
|
-
if ((tag && d.tag === tag) || tag === null) {
|
|
99
|
-
return d;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function text(decoded: Data.DecodedPayload[], id: string): string {
|
|
111
|
-
let parent = node(decoded, "attributes.id", id);
|
|
112
|
-
if (parent) {
|
|
113
|
-
let child = node(decoded, "parent", parent.id, "*T");
|
|
114
|
-
if (child && child.value) {
|
|
115
|
-
return child.value;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
function config(override: Core.Config): string {
|
|
123
|
-
const settings = {
|
|
124
|
-
delay: 100,
|
|
125
|
-
content: true,
|
|
126
|
-
fraud: [],
|
|
127
|
-
regions: [],
|
|
128
|
-
mask: [],
|
|
129
|
-
unmask: [],
|
|
130
|
-
upload: payload => { window["payloads"].push(payload); window["clarity"]("upgrade", "test"); }
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Process overrides
|
|
134
|
-
if (override){
|
|
135
|
-
for (let key of Object.keys(override)) {
|
|
136
|
-
settings[key] = override[key];
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Serialize configuration
|
|
141
|
-
let output = "";
|
|
142
|
-
for (let key of Object.keys(settings)) {
|
|
143
|
-
switch (key) {
|
|
144
|
-
case "upload":
|
|
145
|
-
output += `${JSON.stringify(key)}: ${settings[key].toString()},`;
|
|
146
|
-
break;
|
|
147
|
-
case "projectId":
|
|
148
|
-
case "mask":
|
|
149
|
-
case "unmask":
|
|
150
|
-
case "regions":
|
|
151
|
-
case "cookies":
|
|
152
|
-
case "fraud":
|
|
153
|
-
output += `${JSON.stringify(key)}: ${JSON.stringify(settings[key])},`;
|
|
154
|
-
break;
|
|
155
|
-
default:
|
|
156
|
-
output += `${JSON.stringify(key)}: ${settings[key]},`;
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
output += `"projectId": "test"`;
|
|
161
|
-
return "{" + output + "}";
|
|
162
|
-
}
|
|
1
|
+
import { Core, Data, decode, Interaction, Layout } from "clarity-decode";
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as url from 'url';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { Browser, Page, chromium } from 'playwright';
|
|
6
|
+
|
|
7
|
+
export async function launch(): Promise<Browser> {
|
|
8
|
+
return chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function markup(page: Page, file: string, override: Core.Config = null): Promise<string[]> {
|
|
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
|
+
]);
|
|
19
|
+
await page.setContent(html.replace("</body>", `
|
|
20
|
+
<script>
|
|
21
|
+
window.payloads = [];
|
|
22
|
+
${fs.readFileSync(path.resolve(__dirname, `../build/clarity.min.js`), 'utf8')};
|
|
23
|
+
clarity("start", ${config(override)});
|
|
24
|
+
</script>
|
|
25
|
+
</body>
|
|
26
|
+
`));
|
|
27
|
+
await page.hover("#two");
|
|
28
|
+
await page.click("#child");
|
|
29
|
+
await page.locator('#search').fill('');
|
|
30
|
+
await page.locator('#search').type('query with numb3rs');
|
|
31
|
+
await page.locator('#pwd').type('p1ssw0rd');
|
|
32
|
+
await page.locator('#eml').fill('');
|
|
33
|
+
await page.locator('#eml').type('hello@world.com');
|
|
34
|
+
await page.waitForFunction("payloads && payloads.length > 2");
|
|
35
|
+
return await page.evaluate('payloads');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function clicks(decoded: Data.DecodedPayload[]): Interaction.ClickEvent[] {
|
|
39
|
+
let output: Interaction.ClickEvent[] = [];
|
|
40
|
+
for (let i = decoded.length - 1; i >= 0; i--) {
|
|
41
|
+
if (decoded[i].click) {
|
|
42
|
+
for (let j = 0; j < decoded[i].click.length;j++)
|
|
43
|
+
{
|
|
44
|
+
output.push(decoded[i].click[j]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return output;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function inputs(decoded: Data.DecodedPayload[]): Interaction.InputEvent[] {
|
|
52
|
+
let output: Interaction.InputEvent[] = [];
|
|
53
|
+
for (let i = decoded.length - 1; i >= 0; i--) {
|
|
54
|
+
if (decoded[i].input) {
|
|
55
|
+
for (let j = 0; j < decoded[i].input.length;j++)
|
|
56
|
+
{
|
|
57
|
+
output.push(decoded[i].input[j]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function changes(decoded: Data.DecodedPayload[]): Interaction.ChangeEvent[] {
|
|
65
|
+
let output: Interaction.ChangeEvent[] = [];
|
|
66
|
+
for (let i = decoded.length - 1; i >= 0; i--) {
|
|
67
|
+
if (decoded[i].change) {
|
|
68
|
+
for (let j = 0; j < decoded[i].change.length;j++)
|
|
69
|
+
{
|
|
70
|
+
output.push(decoded[i].change[j]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return output;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function node(decoded: Data.DecodedPayload[], key: string, value: string | number, tag: string = null): Layout.DomData {
|
|
78
|
+
let sub = null;
|
|
79
|
+
|
|
80
|
+
// Exploding nested keys into key and sub key
|
|
81
|
+
if (key.indexOf(".") > 0) {
|
|
82
|
+
const parts = key.split(".");
|
|
83
|
+
if (parts.length === 2) {
|
|
84
|
+
key = parts[0];
|
|
85
|
+
sub = parts[1];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Walking over the decoded payload to find the right match
|
|
90
|
+
for (let i = decoded.length - 1; i >= 0; i--) {
|
|
91
|
+
if (decoded[i].dom) {
|
|
92
|
+
for (let j = 0; j < decoded[i].dom.length; j++) {
|
|
93
|
+
if (decoded[i].dom[j].data) {
|
|
94
|
+
for (let k = 0; k < decoded[i].dom[j].data.length; k++) {
|
|
95
|
+
let d = decoded[i].dom[j].data[k];
|
|
96
|
+
if ((sub && d[key] && d[key][sub] === value) ||
|
|
97
|
+
(d[key] && d[key] === value)) {
|
|
98
|
+
if ((tag && d.tag === tag) || tag === null) {
|
|
99
|
+
return d;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function text(decoded: Data.DecodedPayload[], id: string): string {
|
|
111
|
+
let parent = node(decoded, "attributes.id", id);
|
|
112
|
+
if (parent) {
|
|
113
|
+
let child = node(decoded, "parent", parent.id, "*T");
|
|
114
|
+
if (child && child.value) {
|
|
115
|
+
return child.value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
function config(override: Core.Config): string {
|
|
123
|
+
const settings = {
|
|
124
|
+
delay: 100,
|
|
125
|
+
content: true,
|
|
126
|
+
fraud: [],
|
|
127
|
+
regions: [],
|
|
128
|
+
mask: [],
|
|
129
|
+
unmask: [],
|
|
130
|
+
upload: payload => { window["payloads"].push(payload); window["clarity"]("upgrade", "test"); }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Process overrides
|
|
134
|
+
if (override){
|
|
135
|
+
for (let key of Object.keys(override)) {
|
|
136
|
+
settings[key] = override[key];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Serialize configuration
|
|
141
|
+
let output = "";
|
|
142
|
+
for (let key of Object.keys(settings)) {
|
|
143
|
+
switch (key) {
|
|
144
|
+
case "upload":
|
|
145
|
+
output += `${JSON.stringify(key)}: ${settings[key].toString()},`;
|
|
146
|
+
break;
|
|
147
|
+
case "projectId":
|
|
148
|
+
case "mask":
|
|
149
|
+
case "unmask":
|
|
150
|
+
case "regions":
|
|
151
|
+
case "cookies":
|
|
152
|
+
case "fraud":
|
|
153
|
+
output += `${JSON.stringify(key)}: ${JSON.stringify(settings[key])},`;
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
output += `${JSON.stringify(key)}: ${settings[key]},`;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
output += `"projectId": "test"`;
|
|
161
|
+
return "{" + output + "}";
|
|
162
|
+
}
|