clarity-js 0.8.12 → 0.8.13-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/clarity.extended.js +1 -1
- package/build/clarity.insight.js +1 -1
- package/build/clarity.js +3952 -4179
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +3952 -4179
- package/build/clarity.performance.js +1 -1
- package/package.json +69 -76
- package/rollup.config.ts +88 -84
- package/src/clarity.ts +28 -34
- package/src/core/api.ts +1 -8
- package/src/core/config.ts +2 -2
- package/src/core/event.ts +32 -36
- package/src/core/hash.ts +6 -5
- package/src/core/history.ts +11 -10
- package/src/core/index.ts +11 -21
- package/src/core/measure.ts +5 -9
- package/src/core/report.ts +3 -3
- package/src/core/scrub.ts +20 -29
- package/src/core/task.ts +45 -73
- package/src/core/time.ts +3 -3
- package/src/core/timeout.ts +2 -2
- package/src/core/version.ts +1 -1
- package/src/data/baseline.ts +55 -60
- package/src/data/consent.ts +2 -2
- package/src/data/custom.ts +13 -8
- package/src/data/dimension.ts +7 -11
- package/src/data/encode.ts +30 -36
- package/src/data/envelope.ts +38 -38
- package/src/data/extract.ts +77 -86
- package/src/data/index.ts +6 -10
- package/src/data/limit.ts +1 -1
- package/src/data/metadata.ts +266 -305
- package/src/data/metric.ts +8 -18
- package/src/data/ping.ts +4 -8
- package/src/data/signal.ts +18 -18
- package/src/data/summary.ts +4 -6
- package/src/data/token.ts +8 -10
- package/src/data/upgrade.ts +3 -7
- package/src/data/upload.ts +49 -100
- package/src/data/variable.ts +20 -27
- package/src/diagnostic/encode.ts +2 -2
- package/src/diagnostic/fraud.ts +4 -3
- package/src/diagnostic/internal.ts +5 -11
- package/src/diagnostic/script.ts +8 -12
- package/src/global.ts +1 -1
- package/src/insight/blank.ts +4 -4
- package/src/insight/encode.ts +17 -23
- package/src/insight/snapshot.ts +37 -57
- package/src/interaction/change.ts +6 -9
- package/src/interaction/click.ts +28 -34
- package/src/interaction/clipboard.ts +2 -2
- package/src/interaction/encode.ts +31 -35
- package/src/interaction/input.ts +9 -11
- package/src/interaction/pointer.ts +24 -35
- package/src/interaction/resize.ts +5 -5
- package/src/interaction/scroll.ts +11 -14
- package/src/interaction/selection.ts +8 -12
- package/src/interaction/submit.ts +2 -2
- package/src/interaction/timeline.ts +9 -13
- package/src/interaction/unload.ts +1 -1
- package/src/interaction/visibility.ts +2 -2
- package/src/layout/animation.ts +41 -47
- package/src/layout/discover.ts +5 -5
- package/src/layout/document.ts +19 -31
- package/src/layout/dom.ts +91 -141
- package/src/layout/encode.ts +37 -52
- package/src/layout/mutation.ts +318 -321
- package/src/layout/node.ts +81 -104
- package/src/layout/offset.ts +6 -7
- package/src/layout/region.ts +40 -60
- package/src/layout/schema.ts +8 -15
- package/src/layout/selector.ts +25 -47
- package/src/layout/style.ts +36 -44
- package/src/layout/target.ts +10 -14
- package/src/layout/traverse.ts +11 -17
- package/src/performance/blank.ts +1 -1
- package/src/performance/encode.ts +4 -4
- package/src/performance/interaction.ts +70 -58
- package/src/performance/navigation.ts +2 -2
- package/src/performance/observer.ts +26 -59
- package/src/queue.ts +9 -16
- package/tsconfig.json +1 -1
- package/tslint.json +32 -25
- package/types/core.d.ts +13 -13
- package/types/data.d.ts +29 -33
- package/types/diagnostic.d.ts +1 -1
- package/types/interaction.d.ts +4 -4
- package/types/layout.d.ts +21 -36
- package/types/performance.d.ts +5 -6
- package/.lintstagedrc.yml +0 -3
- package/biome.json +0 -43
package/src/data/metric.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Event, Metric,
|
|
1
|
+
import { Event, Metric, MetricData } from "@clarity-types/data";
|
|
2
2
|
import encode from "./encode";
|
|
3
3
|
|
|
4
4
|
export let data: MetricData = null;
|
|
@@ -16,24 +16,16 @@ export function stop(): void {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function count(metric: Metric): void {
|
|
19
|
-
if (!(metric in data)) {
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
if (!(metric in updates)) {
|
|
23
|
-
updates[metric] = 0;
|
|
24
|
-
}
|
|
19
|
+
if (!(metric in data)) { data[metric] = 0; }
|
|
20
|
+
if (!(metric in updates)) { updates[metric] = 0; }
|
|
25
21
|
data[metric]++;
|
|
26
22
|
updates[metric]++;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
export function sum(metric: Metric, value: number): void {
|
|
30
|
-
if (value !== null) {
|
|
31
|
-
if (!(metric in data)) {
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
if (!(metric in updates)) {
|
|
35
|
-
updates[metric] = 0;
|
|
36
|
-
}
|
|
26
|
+
if (value !== null) {
|
|
27
|
+
if (!(metric in data)) { data[metric] = 0; }
|
|
28
|
+
if (!(metric in updates)) { updates[metric] = 0; }
|
|
37
29
|
data[metric] += value;
|
|
38
30
|
updates[metric] += value;
|
|
39
31
|
}
|
|
@@ -41,10 +33,8 @@ export function sum(metric: Metric, value: number): void {
|
|
|
41
33
|
|
|
42
34
|
export function max(metric: Metric, value: number): void {
|
|
43
35
|
// Ensure that we do not process null or NaN values
|
|
44
|
-
if (value !== null &&
|
|
45
|
-
if (!(metric in data)) {
|
|
46
|
-
data[metric] = 0;
|
|
47
|
-
}
|
|
36
|
+
if (value !== null && isNaN(value) === false) {
|
|
37
|
+
if (!(metric in data)) { data[metric] = 0; }
|
|
48
38
|
if (value > data[metric] || data[metric] === 0) {
|
|
49
39
|
updates[metric] = value;
|
|
50
40
|
data[metric] = value;
|
package/src/data/ping.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Event,
|
|
1
|
+
import { Event, PingData, Setting } from "@clarity-types/data";
|
|
2
2
|
import { suspend } from "@src/core";
|
|
3
3
|
import { time } from "@src/core/time";
|
|
4
4
|
import { clearTimeout, setTimeout } from "@src/core/timeout";
|
|
@@ -15,22 +15,18 @@ export function start(): void {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function reset(): void {
|
|
18
|
-
if (timeout) {
|
|
19
|
-
clearTimeout(timeout);
|
|
20
|
-
}
|
|
18
|
+
if (timeout) { clearTimeout(timeout); }
|
|
21
19
|
timeout = setTimeout(ping, interval);
|
|
22
20
|
last = time();
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
function ping(): void {
|
|
26
|
-
|
|
24
|
+
let now = time();
|
|
27
25
|
data = { gap: now - last };
|
|
28
26
|
encode(Event.Ping);
|
|
29
27
|
if (data.gap < Setting.PingTimeout) {
|
|
30
28
|
timeout = setTimeout(ping, interval);
|
|
31
|
-
} else {
|
|
32
|
-
suspend();
|
|
33
|
-
}
|
|
29
|
+
} else { suspend(); }
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
export function stop(): void {
|
package/src/data/signal.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ClaritySignal, SignalCallback } from '@clarity-types/data';
|
|
2
2
|
|
|
3
3
|
export let signalCallback: SignalCallback = null;
|
|
4
4
|
|
|
5
5
|
export function signal(cb: SignalCallback): void {
|
|
6
|
-
|
|
6
|
+
signalCallback = cb;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function parseSignals(signalsPayload: string): ClaritySignal[] {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
try{
|
|
11
|
+
const parsedSignals: ClaritySignal[] = JSON.parse(signalsPayload);
|
|
12
|
+
return parsedSignals;
|
|
13
|
+
}catch{
|
|
14
|
+
return []
|
|
15
|
+
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function signalsEvent(signalsPayload: string) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
const signals = parseSignals(signalsPayload);
|
|
24
|
-
for (const signal of signals) {
|
|
25
|
-
signalCallback(signal);
|
|
26
|
-
}
|
|
27
|
-
} catch {
|
|
28
|
-
//do nothing
|
|
19
|
+
try {
|
|
20
|
+
if (!signalCallback) {
|
|
21
|
+
return;
|
|
29
22
|
}
|
|
23
|
+
const signals = parseSignals(signalsPayload);
|
|
24
|
+
signals.forEach((signal) => {
|
|
25
|
+
signalCallback(signal);
|
|
26
|
+
});
|
|
27
|
+
} catch {
|
|
28
|
+
//do nothing
|
|
29
|
+
}
|
|
30
30
|
}
|
package/src/data/summary.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Event,
|
|
1
|
+
import { Event, SummaryData, Setting } from "@clarity-types/data";
|
|
2
2
|
import encode from "./encode";
|
|
3
3
|
|
|
4
4
|
export let data: SummaryData = null;
|
|
@@ -15,15 +15,13 @@ export function track(event: Event, time: number): void {
|
|
|
15
15
|
if (!(event in data)) {
|
|
16
16
|
data[event] = [[time, 0]];
|
|
17
17
|
} else {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
let e = data[event];
|
|
19
|
+
let last = e[e.length - 1];
|
|
20
20
|
// Add a new entry only if the new event occurs after configured interval
|
|
21
21
|
// Otherwise, extend the duration of the previous entry
|
|
22
22
|
if (time - last[0] > Setting.SummaryInterval) {
|
|
23
23
|
data[event].push([time, 0]);
|
|
24
|
-
} else {
|
|
25
|
-
last[1] = time - last[0];
|
|
26
|
-
}
|
|
24
|
+
} else { last[1] = time - last[0]; }
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
27
|
|
package/src/data/token.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {Constant, Token} from "@clarity-types/data";
|
|
2
2
|
|
|
3
3
|
// Following code takes an array of tokens and transforms it to optimize for repeating tokens and make it efficient to send over the wire
|
|
4
4
|
// The way it works is that it iterate over all tokens and checks if the current token was already seen in the tokens array so far
|
|
@@ -6,20 +6,18 @@ import type { Token } from "@clarity-types/data";
|
|
|
6
6
|
// E.g. If tokens array is: ["hello", "world", "coding", "language", "world", "language", "example"]
|
|
7
7
|
// Then the resulting tokens array after following code execution would be: ["hello", "world", "coding", "language", [1, 3], "example"]
|
|
8
8
|
// Where [1,3] points to tokens[1] => "world" and tokens[3] => "language"
|
|
9
|
-
export default function
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export default function(tokens: Token[]): Token[] {
|
|
10
|
+
let output: Token[] = [];
|
|
11
|
+
let lookup: {[key: string]: number} = {};
|
|
12
12
|
let pointer = 0;
|
|
13
13
|
let reference = null;
|
|
14
14
|
for (let i = 0; i < tokens.length; i++) {
|
|
15
15
|
// Only optimize for string values
|
|
16
|
-
if (typeof tokens[i] ===
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
if (typeof tokens[i] === Constant.String) {
|
|
17
|
+
let token = tokens[i] as string;
|
|
18
|
+
let index = lookup[token] || -1;
|
|
19
19
|
if (index >= 0) {
|
|
20
|
-
if (reference) {
|
|
21
|
-
reference.push(index);
|
|
22
|
-
} else {
|
|
20
|
+
if (reference) { reference.push(index); } else {
|
|
23
21
|
reference = [index];
|
|
24
22
|
output.push(reference);
|
|
25
23
|
pointer++;
|
package/src/data/upgrade.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Constant, Event,
|
|
1
|
+
import { Constant, Event, UpgradeData } from "@clarity-types/data";
|
|
2
2
|
import * as core from "@src/core";
|
|
3
3
|
import config from "@src/core/config";
|
|
4
4
|
import encode from "@src/data/encode";
|
|
@@ -9,9 +9,7 @@ import * as style from "@src/layout/style";
|
|
|
9
9
|
export let data: UpgradeData = null;
|
|
10
10
|
|
|
11
11
|
export function start(): void {
|
|
12
|
-
if (!config.lean && config.upgrade) {
|
|
13
|
-
config.upgrade(Constant.Config);
|
|
14
|
-
}
|
|
12
|
+
if (!config.lean && config.upgrade) { config.upgrade(Constant.Config); }
|
|
15
13
|
data = null;
|
|
16
14
|
}
|
|
17
15
|
|
|
@@ -30,9 +28,7 @@ export function upgrade(key: string): void {
|
|
|
30
28
|
metadata.save();
|
|
31
29
|
|
|
32
30
|
// Callback upgrade handler, if configured
|
|
33
|
-
if (config.upgrade) {
|
|
34
|
-
config.upgrade(key);
|
|
35
|
-
}
|
|
31
|
+
if (config.upgrade) { config.upgrade(key); }
|
|
36
32
|
|
|
37
33
|
encode(Event.Upgrade);
|
|
38
34
|
|
package/src/data/upload.ts
CHANGED
|
@@ -1,48 +1,34 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
BooleanFlag,
|
|
4
|
-
Check,
|
|
5
|
-
Code,
|
|
6
|
-
Constant,
|
|
7
|
-
type EncodedPayload,
|
|
8
|
-
Event,
|
|
9
|
-
Metric,
|
|
10
|
-
Setting,
|
|
11
|
-
Severity,
|
|
12
|
-
type Token,
|
|
13
|
-
type Transit,
|
|
14
|
-
type UploadData,
|
|
15
|
-
XMLReadyState,
|
|
16
|
-
} from "@clarity-types/data";
|
|
1
|
+
import { UploadCallback } from "@clarity-types/core";
|
|
2
|
+
import { BooleanFlag, Check, Code, Constant, EncodedPayload, Event, Metric, Setting, Severity, Token, Transit, UploadData, XMLReadyState } from "@clarity-types/data";
|
|
17
3
|
import * as clarity from "@src/clarity";
|
|
18
4
|
import config from "@src/core/config";
|
|
19
5
|
import measure from "@src/core/measure";
|
|
20
|
-
import { report } from "@src/core/report";
|
|
21
6
|
import { time } from "@src/core/time";
|
|
22
7
|
import { clearTimeout, setTimeout } from "@src/core/timeout";
|
|
23
8
|
import compress from "@src/data/compress";
|
|
24
9
|
import encode from "@src/data/encode";
|
|
25
10
|
import * as envelope from "@src/data/envelope";
|
|
26
|
-
import * as extract from "@src/data/extract";
|
|
27
11
|
import * as data from "@src/data/index";
|
|
28
12
|
import * as limit from "@src/data/limit";
|
|
29
13
|
import * as metadata from "@src/data/metadata";
|
|
30
14
|
import * as metric from "@src/data/metric";
|
|
31
15
|
import * as ping from "@src/data/ping";
|
|
32
|
-
import { signalsEvent } from "@src/data/signal";
|
|
33
16
|
import * as internal from "@src/diagnostic/internal";
|
|
34
17
|
import * as timeline from "@src/interaction/timeline";
|
|
35
18
|
import * as region from "@src/layout/region";
|
|
19
|
+
import * as extract from "@src/data/extract";
|
|
36
20
|
import * as style from "@src/layout/style";
|
|
21
|
+
import { report } from "@src/core/report";
|
|
22
|
+
import { signalsEvent } from "@src/data/signal";
|
|
37
23
|
|
|
38
|
-
let discoverBytes = 0;
|
|
39
|
-
let playbackBytes = 0;
|
|
24
|
+
let discoverBytes: number = 0;
|
|
25
|
+
let playbackBytes: number = 0;
|
|
40
26
|
let playback: string[];
|
|
41
27
|
let analysis: string[];
|
|
42
28
|
let timeout: number = null;
|
|
43
29
|
let transit: Transit;
|
|
44
30
|
let active: boolean;
|
|
45
|
-
let queuedTime = 0;
|
|
31
|
+
let queuedTime: number = 0;
|
|
46
32
|
let leanLimit = false;
|
|
47
33
|
export let track: UploadData;
|
|
48
34
|
|
|
@@ -58,11 +44,11 @@ export function start(): void {
|
|
|
58
44
|
track = null;
|
|
59
45
|
}
|
|
60
46
|
|
|
61
|
-
export function queue(tokens: Token[], transmit = true): void {
|
|
47
|
+
export function queue(tokens: Token[], transmit: boolean = true): void {
|
|
62
48
|
if (active) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
49
|
+
let now = time();
|
|
50
|
+
let type = tokens.length > 1 ? tokens[1] : null;
|
|
51
|
+
let event = JSON.stringify(tokens);
|
|
66
52
|
|
|
67
53
|
if (!config.lean) {
|
|
68
54
|
leanLimit = false;
|
|
@@ -72,20 +58,15 @@ export function queue(tokens: Token[], transmit = true): void {
|
|
|
72
58
|
}
|
|
73
59
|
|
|
74
60
|
switch (type) {
|
|
75
|
-
// biome-ignore lint/suspicious/noFallthroughSwitchClause: we want discover bytes to also count as playback bytes
|
|
76
61
|
case Event.Discover:
|
|
77
|
-
if (leanLimit) {
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
62
|
+
if (leanLimit) { break; }
|
|
80
63
|
discoverBytes += event.length;
|
|
81
64
|
case Event.Box:
|
|
82
65
|
case Event.Mutation:
|
|
83
66
|
case Event.Snapshot:
|
|
84
67
|
case Event.StyleSheetAdoption:
|
|
85
68
|
case Event.StyleSheetUpdate:
|
|
86
|
-
if (leanLimit) {
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
69
|
+
if (leanLimit) { break; }
|
|
89
70
|
playbackBytes += event.length;
|
|
90
71
|
playback.push(event);
|
|
91
72
|
break;
|
|
@@ -100,8 +81,8 @@ export function queue(tokens: Token[], transmit = true): void {
|
|
|
100
81
|
// Following two checks are precautionary and act as a fail safe mechanism to get out of unexpected situations.
|
|
101
82
|
// Check 1: If for any reason the upload hasn't happened after waiting for 2x the config.delay time,
|
|
102
83
|
// reset the timer. This allows Clarity to attempt an upload again.
|
|
103
|
-
|
|
104
|
-
if (now - queuedTime > gap * 2) {
|
|
84
|
+
let gap = delay();
|
|
85
|
+
if (now - queuedTime > (gap * 2)) {
|
|
105
86
|
clearTimeout(timeout);
|
|
106
87
|
timeout = null;
|
|
107
88
|
}
|
|
@@ -110,9 +91,7 @@ export function queue(tokens: Token[], transmit = true): void {
|
|
|
110
91
|
// However, in certain scenarios - like metric calculation - which are triggered as part of an existing upload
|
|
111
92
|
// We enrich the data going out with the existing upload. In these cases, call to upload comes with 'transmit' set to false.
|
|
112
93
|
if (transmit && timeout === null) {
|
|
113
|
-
if (type !== Event.Ping) {
|
|
114
|
-
ping.reset();
|
|
115
|
-
}
|
|
94
|
+
if (type !== Event.Ping) { ping.reset(); }
|
|
116
95
|
timeout = setTimeout(upload, gap);
|
|
117
96
|
queuedTime = now;
|
|
118
97
|
limit.check(playbackBytes);
|
|
@@ -134,17 +113,14 @@ export function stop(): void {
|
|
|
134
113
|
active = false;
|
|
135
114
|
}
|
|
136
115
|
|
|
137
|
-
async function upload(final = false): Promise<void> {
|
|
116
|
+
async function upload(final: boolean = false): Promise<void> {
|
|
138
117
|
timeout = null;
|
|
139
118
|
|
|
140
119
|
// Check if we can send playback bytes over the wire or not
|
|
141
120
|
// For better instrumentation coverage, we send playback bytes from second sequence onwards
|
|
142
121
|
// And, we only send playback metric when we are able to send the playback bytes back to server
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (sendPlaybackBytes) {
|
|
146
|
-
metric.max(Metric.Playback, BooleanFlag.True);
|
|
147
|
-
}
|
|
122
|
+
let sendPlaybackBytes = config.lean === false && playbackBytes > 0 && (playbackBytes < Setting.MaxFirstPayloadBytes || envelope.data.sequence > 0);
|
|
123
|
+
if (sendPlaybackBytes) { metric.max(Metric.Playback, BooleanFlag.True); }
|
|
148
124
|
|
|
149
125
|
// CAUTION: Ensure "transmit" is set to false in the queue function for following events
|
|
150
126
|
// Otherwise you run a risk of infinite loop.
|
|
@@ -157,18 +133,18 @@ async function upload(final = false): Promise<void> {
|
|
|
157
133
|
// In real world tests, we noticed that certain third party scripts (e.g. https://www.npmjs.com/package/raven-js)
|
|
158
134
|
// could inject function arguments for internal tracking (likely stack traces for script errors).
|
|
159
135
|
// For these edge cases, we want to ensure that an injected object (e.g. {"key": "value"}) isn't mistaken to be true.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
136
|
+
let last = final === true;
|
|
137
|
+
let e = JSON.stringify(envelope.envelope(last));
|
|
138
|
+
let a = `[${analysis.join()}]`;
|
|
163
139
|
|
|
164
|
-
|
|
165
|
-
|
|
140
|
+
let p = sendPlaybackBytes ? `[${playback.join()}]` : Constant.Empty;
|
|
141
|
+
let encoded: EncodedPayload = {e, a, p};
|
|
166
142
|
|
|
167
143
|
// Get the payload ready for sending over the wire
|
|
168
144
|
// We also attempt to compress the payload if it is not the last payload and the browser supports it
|
|
169
145
|
// In all other cases, we continue to send back string value
|
|
170
|
-
|
|
171
|
-
|
|
146
|
+
let payload = stringify(encoded);
|
|
147
|
+
let zipped = last ? null : await compress(payload)
|
|
172
148
|
metric.sum(Metric.TotalBytes, zipped ? zipped.length : payload.length);
|
|
173
149
|
send(payload, zipped, envelope.data.sequence, last);
|
|
174
150
|
|
|
@@ -186,9 +162,9 @@ function stringify(encoded: EncodedPayload): string {
|
|
|
186
162
|
return encoded.p.length > 0 ? `{"e":${encoded.e},"a":${encoded.a},"p":${encoded.p}}` : `{"e":${encoded.e},"a":${encoded.a}}`;
|
|
187
163
|
}
|
|
188
164
|
|
|
189
|
-
function send(payload: string, zipped: Uint8Array, sequence: number, beacon = false): void {
|
|
165
|
+
function send(payload: string, zipped: Uint8Array, sequence: number, beacon: boolean = false): void {
|
|
190
166
|
// Upload data if a valid URL is defined in the config
|
|
191
|
-
if (typeof config.upload ===
|
|
167
|
+
if (typeof config.upload === Constant.String) {
|
|
192
168
|
const url = config.upload as string;
|
|
193
169
|
let dispatched = false;
|
|
194
170
|
|
|
@@ -200,12 +176,8 @@ function send(payload: string, zipped: Uint8Array, sequence: number, beacon = fa
|
|
|
200
176
|
try {
|
|
201
177
|
// Navigator needs to be bound to sendBeacon before it is used to avoid errors in some browsers
|
|
202
178
|
dispatched = navigator.sendBeacon.bind(navigator)(url, payload);
|
|
203
|
-
if (dispatched) {
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
} catch {
|
|
207
|
-
/* do nothing - and we will automatically fallback to XHR below */
|
|
208
|
-
}
|
|
179
|
+
if (dispatched) { done(sequence); }
|
|
180
|
+
} catch { /* do nothing - and we will automatically fallback to XHR below */ }
|
|
209
181
|
}
|
|
210
182
|
|
|
211
183
|
// Before initiating XHR upload, we check if the data has already been uploaded using sendBeacon
|
|
@@ -216,22 +188,12 @@ function send(payload: string, zipped: Uint8Array, sequence: number, beacon = fa
|
|
|
216
188
|
if (dispatched === false) {
|
|
217
189
|
// While tracking payload for retry, we only track string value of the payload to err on the safe side
|
|
218
190
|
// Not all browsers support compression API and the support for it in supported browsers is still experimental
|
|
219
|
-
if (sequence in transit) {
|
|
220
|
-
|
|
221
|
-
} else {
|
|
222
|
-
transit[sequence] = { data: payload, attempts: 1 };
|
|
223
|
-
}
|
|
224
|
-
const xhr = new XMLHttpRequest();
|
|
191
|
+
if (sequence in transit) { transit[sequence].attempts++; } else { transit[sequence] = { data: payload, attempts: 1 }; }
|
|
192
|
+
let xhr = new XMLHttpRequest();
|
|
225
193
|
xhr.open("POST", url, true);
|
|
226
194
|
xhr.timeout = Setting.UploadTimeout;
|
|
227
|
-
xhr.ontimeout = () => {
|
|
228
|
-
|
|
229
|
-
};
|
|
230
|
-
if (sequence !== null) {
|
|
231
|
-
xhr.onreadystatechange = (): void => {
|
|
232
|
-
measure(check)(xhr, sequence);
|
|
233
|
-
};
|
|
234
|
-
}
|
|
195
|
+
xhr.ontimeout = () => { report(new Error(`${Constant.Timeout} : ${url}`)) };
|
|
196
|
+
if (sequence !== null) { xhr.onreadystatechange = (): void => { measure(check)(xhr, sequence); }; }
|
|
235
197
|
xhr.withCredentials = true;
|
|
236
198
|
if (zipped) {
|
|
237
199
|
// If we do have valid compressed array, send it with appropriate HTTP headers so server can decode it appropriately
|
|
@@ -250,7 +212,7 @@ function send(payload: string, zipped: Uint8Array, sequence: number, beacon = fa
|
|
|
250
212
|
}
|
|
251
213
|
|
|
252
214
|
function check(xhr: XMLHttpRequest, sequence: number): void {
|
|
253
|
-
|
|
215
|
+
var transitData = transit[sequence];
|
|
254
216
|
if (xhr && xhr.readyState === XMLReadyState.Done && transitData) {
|
|
255
217
|
// Attempt send payload again (as configured in settings) if we do not receive a success (2XX) response code back from the server
|
|
256
218
|
if ((xhr.status < 200 || xhr.status > 208) && transitData.attempts <= Setting.RetryLimit) {
|
|
@@ -264,9 +226,7 @@ function check(xhr: XMLHttpRequest, sequence: number): void {
|
|
|
264
226
|
// 1: Browsers block upload because of content security policy violation
|
|
265
227
|
// 2: Safari will terminate pending XHR requests with status code 0 if the user navigates away from the page
|
|
266
228
|
// In any case, we switch the upload URL to fallback configuration (if available) before re-trying one more time
|
|
267
|
-
if (xhr.status === 0) {
|
|
268
|
-
config.upload = config.fallback ? config.fallback : config.upload;
|
|
269
|
-
}
|
|
229
|
+
if (xhr.status === 0) { config.upload = config.fallback ? config.fallback : config.upload; }
|
|
270
230
|
// In all other cases, re-attempt sending the same data
|
|
271
231
|
// For retry we always fallback to string payload, even though we may have attempted
|
|
272
232
|
// sending zipped payload earlier
|
|
@@ -275,13 +235,9 @@ function check(xhr: XMLHttpRequest, sequence: number): void {
|
|
|
275
235
|
} else {
|
|
276
236
|
track = { sequence, attempts: transitData.attempts, status: xhr.status };
|
|
277
237
|
// Send back an event only if we were not successful in our first attempt
|
|
278
|
-
if (transitData.attempts > 1) {
|
|
279
|
-
encode(Event.Upload);
|
|
280
|
-
}
|
|
238
|
+
if (transitData.attempts > 1) { encode(Event.Upload); }
|
|
281
239
|
// Handle response if it was a 200 response with a valid body
|
|
282
|
-
if (xhr.status === 200 && xhr.responseText) {
|
|
283
|
-
response(xhr.responseText);
|
|
284
|
-
}
|
|
240
|
+
if (xhr.status === 200 && xhr.responseText) { response(xhr.responseText); }
|
|
285
241
|
// If we exhausted our retries then trigger Clarity's shutdown for this page since the data will be incomplete
|
|
286
242
|
if (xhr.status === 0) {
|
|
287
243
|
// And, right before we terminate the session, we will attempt one last time to see if we can use
|
|
@@ -290,9 +246,7 @@ function check(xhr: XMLHttpRequest, sequence: number): void {
|
|
|
290
246
|
limit.trigger(Check.Retry);
|
|
291
247
|
}
|
|
292
248
|
// Signal that this request completed successfully
|
|
293
|
-
if (xhr.status >= 200 && xhr.status <= 208) {
|
|
294
|
-
done(sequence);
|
|
295
|
-
}
|
|
249
|
+
if (xhr.status >= 200 && xhr.status <= 208) { done(sequence); }
|
|
296
250
|
// Stop tracking this payload now that it's all done
|
|
297
251
|
delete transit[sequence];
|
|
298
252
|
}
|
|
@@ -310,14 +264,15 @@ function done(sequence: number): void {
|
|
|
310
264
|
function delay(): number {
|
|
311
265
|
// Progressively increase delay as we continue to send more payloads from the client to the server
|
|
312
266
|
// If we are not uploading data to a server, and instead invoking UploadCallback, in that case keep returning configured value
|
|
313
|
-
|
|
314
|
-
return typeof config.upload ===
|
|
267
|
+
let gap = config.lean === false && discoverBytes > 0 ? Setting.MinUploadDelay : envelope.data.sequence * config.delay;
|
|
268
|
+
return typeof config.upload === Constant.String ? Math.max(Math.min(gap, Setting.MaxUploadDelay), Setting.MinUploadDelay) : config.delay;
|
|
315
269
|
}
|
|
316
270
|
|
|
317
271
|
function response(payload: string): void {
|
|
318
|
-
|
|
319
|
-
for (
|
|
320
|
-
|
|
272
|
+
let lines = payload && payload.length > 0 ? payload.split("\n") : [];
|
|
273
|
+
for (var line of lines)
|
|
274
|
+
{
|
|
275
|
+
let parts = line && line.length > 0 ? line.split(/ (.*)/) : [Constant.Empty];
|
|
321
276
|
switch (parts[0]) {
|
|
322
277
|
case Constant.End:
|
|
323
278
|
// Clear out session storage and end the session so we can start fresh the next time
|
|
@@ -329,19 +284,13 @@ function response(payload: string): void {
|
|
|
329
284
|
break;
|
|
330
285
|
case Constant.Action:
|
|
331
286
|
// Invoke action callback, if configured and has a valid value
|
|
332
|
-
if (config.action && parts.length > 1) {
|
|
333
|
-
config.action(parts[1]);
|
|
334
|
-
}
|
|
287
|
+
if (config.action && parts.length > 1) { config.action(parts[1]); }
|
|
335
288
|
break;
|
|
336
289
|
case Constant.Extract:
|
|
337
|
-
if (parts.length > 1) {
|
|
338
|
-
extract.trigger(parts[1]);
|
|
339
|
-
}
|
|
290
|
+
if (parts.length > 1) { extract.trigger(parts[1]); }
|
|
340
291
|
break;
|
|
341
292
|
case Constant.Signal:
|
|
342
|
-
if (parts.length > 1) {
|
|
343
|
-
signalsEvent(parts[1]);
|
|
344
|
-
}
|
|
293
|
+
if (parts.length > 1) { signalsEvent(parts[1]); }
|
|
345
294
|
break;
|
|
346
295
|
}
|
|
347
296
|
}
|
package/src/data/variable.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Constant, Event,
|
|
1
|
+
import { Constant, Event, IdentityData, Setting, VariableData } from "@clarity-types/data";
|
|
2
2
|
import * as core from "@src/core";
|
|
3
3
|
import { scrub } from "@src/core/scrub";
|
|
4
4
|
import encode from "./encode";
|
|
@@ -10,28 +10,23 @@ export function start(): void {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function set(variable: string, value: string | string[]): void {
|
|
13
|
-
|
|
13
|
+
let values = typeof value === Constant.String ? [value as string] : value as string[];
|
|
14
14
|
log(variable, values);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export async function identify(
|
|
18
|
-
userId:
|
|
19
|
-
sessionId: string = null,
|
|
20
|
-
pageId: string = null,
|
|
21
|
-
userHint: string = null,
|
|
22
|
-
): Promise<IdentityData> {
|
|
23
|
-
const output: IdentityData = { userId: await sha256(userId), userHint: userHint || redact(userId) };
|
|
17
|
+
export async function identify(userId: string, sessionId: string = null, pageId: string = null, userHint: string = null): Promise<IdentityData> {
|
|
18
|
+
let output: IdentityData = { userId: await sha256(userId), userHint: userHint || redact(userId) };
|
|
24
19
|
|
|
25
20
|
// By default, hash custom userId using SHA256 algorithm on the client to preserve privacy
|
|
26
21
|
log(Constant.UserId, [output.userId]);
|
|
27
|
-
|
|
22
|
+
|
|
28
23
|
// Optional non-identifying name for the user
|
|
29
24
|
// If name is not explicitly provided, we automatically generate a redacted version of the userId
|
|
30
25
|
log(Constant.UserHint, [output.userHint]);
|
|
31
|
-
log(Constant.UserType, [detect(userId)]);
|
|
26
|
+
log(Constant.UserType, [detect(userId)]);
|
|
32
27
|
|
|
33
28
|
// Log sessionId and pageId if provided
|
|
34
|
-
if (sessionId) {
|
|
29
|
+
if (sessionId) {
|
|
35
30
|
log(Constant.SessionId, [sessionId]);
|
|
36
31
|
output.sessionId = sessionId;
|
|
37
32
|
}
|
|
@@ -44,12 +39,14 @@ export async function identify(
|
|
|
44
39
|
}
|
|
45
40
|
|
|
46
41
|
function log(variable: string, value: string[]): void {
|
|
47
|
-
if (core.active() &&
|
|
48
|
-
|
|
42
|
+
if (core.active() &&
|
|
43
|
+
variable &&
|
|
44
|
+
value &&
|
|
45
|
+
typeof variable === Constant.String &&
|
|
46
|
+
variable.length < 255) {
|
|
47
|
+
let validValues = variable in data ? data[variable] : [];
|
|
49
48
|
for (let i = 0; i < value.length; i++) {
|
|
50
|
-
if (typeof value[i] ===
|
|
51
|
-
validValues.push(value[i]);
|
|
52
|
-
}
|
|
49
|
+
if (typeof value[i] === Constant.String && value[i].length < 255) { validValues.push(value[i]); }
|
|
53
50
|
}
|
|
54
51
|
data[variable] = validValues;
|
|
55
52
|
}
|
|
@@ -68,9 +65,8 @@ export function stop(): void {
|
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
function redact(input: string): string {
|
|
71
|
-
return input && input.length >= Setting.WordLength
|
|
72
|
-
|
|
73
|
-
: scrub(input, Constant.Asterix, Constant.Asterix);
|
|
68
|
+
return input && input.length >= Setting.WordLength ?
|
|
69
|
+
`${input.substring(0,2)}${scrub(input.substring(2), Constant.Asterix, Constant.Asterix)}` : scrub(input, Constant.Asterix, Constant.Asterix);
|
|
74
70
|
}
|
|
75
71
|
|
|
76
72
|
async function sha256(input: string): Promise<string> {
|
|
@@ -78,14 +74,11 @@ async function sha256(input: string): Promise<string> {
|
|
|
78
74
|
if (crypto && input) {
|
|
79
75
|
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string
|
|
80
76
|
const buffer = await crypto.subtle.digest(Constant.SHA256, new TextEncoder().encode(input));
|
|
81
|
-
return Array.prototype.map.call(new Uint8Array(buffer), (x) =>
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
} catch {
|
|
85
|
-
return Constant.Empty;
|
|
86
|
-
}
|
|
77
|
+
return Array.prototype.map.call(new Uint8Array(buffer), (x: any) =>(('00'+x.toString(16)).slice(-2))).join('');
|
|
78
|
+
} else { return Constant.Empty; }
|
|
79
|
+
} catch { return Constant.Empty; }
|
|
87
80
|
}
|
|
88
81
|
|
|
89
82
|
function detect(input: string): string {
|
|
90
83
|
return input && input.indexOf(Constant.At) > 0 ? Constant.Email : Constant.String;
|
|
91
|
-
}
|
|
84
|
+
}
|
package/src/diagnostic/encode.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Event,
|
|
1
|
+
import { Event, Token } from "@clarity-types/data";
|
|
2
2
|
import * as scrub from "@src/core/scrub";
|
|
3
3
|
import { time } from "@src/core/time";
|
|
4
4
|
import { queue } from "@src/data/upload";
|
|
@@ -7,7 +7,7 @@ import * as internal from "@src/diagnostic/internal";
|
|
|
7
7
|
import * as script from "@src/diagnostic/script";
|
|
8
8
|
|
|
9
9
|
export default async function (type: Event): Promise<void> {
|
|
10
|
-
|
|
10
|
+
let tokens: Token[] = [time(), type];
|
|
11
11
|
|
|
12
12
|
switch (type) {
|
|
13
13
|
case Event.ScriptError:
|
package/src/diagnostic/fraud.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BooleanFlag, Event, IframeStatus, Metric, Setting } from "@clarity-types/data";
|
|
2
|
-
import
|
|
2
|
+
import { FraudData } from "@clarity-types/diagnostic";
|
|
3
3
|
import config from "@src/core/config";
|
|
4
4
|
import hash from "@src/core/hash";
|
|
5
5
|
import * as metric from "@src/data/metric";
|
|
@@ -13,10 +13,11 @@ export function start(): void {
|
|
|
13
13
|
metric.max(Metric.Automation, navigator.webdriver ? BooleanFlag.True : BooleanFlag.False);
|
|
14
14
|
try {
|
|
15
15
|
// some sites (unintentionally) overwrite the window.self property, so we also check for the main window object
|
|
16
|
-
metric.max(Metric.Iframed, window.top
|
|
16
|
+
metric.max(Metric.Iframed, window.top == window.self || window.top == window ? IframeStatus.TopFrame : IframeStatus.Iframe);
|
|
17
17
|
} catch (ex) {
|
|
18
18
|
metric.max(Metric.Iframed, IframeStatus.Unknown);
|
|
19
19
|
}
|
|
20
|
+
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function check(id: number, target: number, input: string): void {
|
|
@@ -33,4 +34,4 @@ export function check(id: number, target: number, input: string): void {
|
|
|
33
34
|
|
|
34
35
|
export function stop(): void {
|
|
35
36
|
history = [];
|
|
36
|
-
}
|
|
37
|
+
}
|