clarity-js 0.6.39 → 0.6.41
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 +103 -62
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +103 -62
- package/package.json +1 -1
- package/src/core/config.ts +2 -1
- package/src/core/version.ts +1 -1
- package/src/data/upload.ts +6 -2
- package/src/index.ts +1 -1
- package/src/layout/dom.ts +14 -13
- package/src/layout/encode.ts +1 -1
- package/src/layout/selector.ts +47 -27
- package/test/core.test.ts +11 -5
- package/test/html/core.html +1 -0
- package/types/core.d.ts +1 -0
- package/types/data.d.ts +3 -2
- package/types/index.d.ts +6 -1
- package/types/layout.d.ts +11 -5
package/build/clarity.module.js
CHANGED
|
@@ -88,7 +88,8 @@ var config$1 = {
|
|
|
88
88
|
report: null,
|
|
89
89
|
upload: null,
|
|
90
90
|
fallback: null,
|
|
91
|
-
upgrade: null
|
|
91
|
+
upgrade: null,
|
|
92
|
+
action: null
|
|
92
93
|
};
|
|
93
94
|
|
|
94
95
|
function api(method) {
|
|
@@ -111,7 +112,7 @@ function stop$B() {
|
|
|
111
112
|
startTime = 0;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
var version$1 = "0.6.
|
|
115
|
+
var version$1 = "0.6.41";
|
|
115
116
|
|
|
116
117
|
// tslint:disable: no-bitwise
|
|
117
118
|
function hash (input) {
|
|
@@ -138,9 +139,9 @@ var buffer = null;
|
|
|
138
139
|
var update$2 = false;
|
|
139
140
|
function start$E() {
|
|
140
141
|
update$2 = false;
|
|
141
|
-
reset$
|
|
142
|
+
reset$p();
|
|
142
143
|
}
|
|
143
|
-
function reset$
|
|
144
|
+
function reset$p() {
|
|
144
145
|
// Baseline state holds the previous values - if it is updated in the current payload,
|
|
145
146
|
// reset the state to current value after sending the previous state
|
|
146
147
|
if (update$2) {
|
|
@@ -208,14 +209,14 @@ function compute$c() {
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
function stop$A() {
|
|
211
|
-
reset$
|
|
212
|
+
reset$p();
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
var baseline = /*#__PURE__*/Object.freeze({
|
|
215
216
|
__proto__: null,
|
|
216
217
|
get state () { return state$9; },
|
|
217
218
|
start: start$E,
|
|
218
|
-
reset: reset$
|
|
219
|
+
reset: reset$p,
|
|
219
220
|
track: track$7,
|
|
220
221
|
activity: activity,
|
|
221
222
|
visibility: visibility,
|
|
@@ -285,7 +286,7 @@ function max(metric, value) {
|
|
|
285
286
|
function compute$b() {
|
|
286
287
|
encode$1(0 /* Event.Metric */);
|
|
287
288
|
}
|
|
288
|
-
function reset$
|
|
289
|
+
function reset$o() {
|
|
289
290
|
updates$3 = {};
|
|
290
291
|
}
|
|
291
292
|
|
|
@@ -304,7 +305,7 @@ function start$C() {
|
|
|
304
305
|
interval = 60000 /* Setting.PingInterval */;
|
|
305
306
|
last = 0;
|
|
306
307
|
}
|
|
307
|
-
function reset$
|
|
308
|
+
function reset$n() {
|
|
308
309
|
if (timeout$6) {
|
|
309
310
|
clearTimeout(timeout$6);
|
|
310
311
|
}
|
|
@@ -332,7 +333,7 @@ var ping$1 = /*#__PURE__*/Object.freeze({
|
|
|
332
333
|
__proto__: null,
|
|
333
334
|
get data () { return data$h; },
|
|
334
335
|
start: start$C,
|
|
335
|
-
reset: reset$
|
|
336
|
+
reset: reset$n,
|
|
336
337
|
stop: stop$y
|
|
337
338
|
});
|
|
338
339
|
|
|
@@ -363,7 +364,7 @@ function track$6(event, time) {
|
|
|
363
364
|
function compute$a() {
|
|
364
365
|
encode$1(36 /* Event.Summary */);
|
|
365
366
|
}
|
|
366
|
-
function reset$
|
|
367
|
+
function reset$m() {
|
|
367
368
|
data$g = {};
|
|
368
369
|
}
|
|
369
370
|
|
|
@@ -374,7 +375,7 @@ var summary = /*#__PURE__*/Object.freeze({
|
|
|
374
375
|
stop: stop$x,
|
|
375
376
|
track: track$6,
|
|
376
377
|
compute: compute$a,
|
|
377
|
-
reset: reset$
|
|
378
|
+
reset: reset$m
|
|
378
379
|
});
|
|
379
380
|
|
|
380
381
|
var data$f = null;
|
|
@@ -416,7 +417,7 @@ var upgrade$1 = /*#__PURE__*/Object.freeze({
|
|
|
416
417
|
|
|
417
418
|
var data$e = null;
|
|
418
419
|
function start$z() {
|
|
419
|
-
reset$
|
|
420
|
+
reset$l();
|
|
420
421
|
}
|
|
421
422
|
function set(variable, value) {
|
|
422
423
|
var values = typeof value === "string" /* Constant.String */ ? [value] : value;
|
|
@@ -447,11 +448,11 @@ function log$2(variable, value) {
|
|
|
447
448
|
function compute$9() {
|
|
448
449
|
encode$1(34 /* Event.Variable */);
|
|
449
450
|
}
|
|
450
|
-
function reset$
|
|
451
|
+
function reset$l() {
|
|
451
452
|
data$e = {};
|
|
452
453
|
}
|
|
453
454
|
function stop$v() {
|
|
454
|
-
reset$
|
|
455
|
+
reset$l();
|
|
455
456
|
}
|
|
456
457
|
|
|
457
458
|
var variable = /*#__PURE__*/Object.freeze({
|
|
@@ -461,7 +462,7 @@ var variable = /*#__PURE__*/Object.freeze({
|
|
|
461
462
|
set: set,
|
|
462
463
|
identify: identify,
|
|
463
464
|
compute: compute$9,
|
|
464
|
-
reset: reset$
|
|
465
|
+
reset: reset$l,
|
|
465
466
|
stop: stop$v
|
|
466
467
|
});
|
|
467
468
|
|
|
@@ -701,7 +702,7 @@ function redact(value) {
|
|
|
701
702
|
// Check if unicode regex is supported, otherwise fallback to calling mask function on this token
|
|
702
703
|
if (unicodeRegex && currencyRegex !== null) {
|
|
703
704
|
// Do not redact information if the token contains a currency symbol
|
|
704
|
-
token = token.match(currencyRegex) ? token : token.replace(letterRegex, "\
|
|
705
|
+
token = token.match(currencyRegex) ? token : token.replace(letterRegex, "\u25AA" /* Data.Constant.Letter */).replace(digitRegex, "\u25AB" /* Data.Constant.Digit */);
|
|
705
706
|
}
|
|
706
707
|
else {
|
|
707
708
|
token = mask(token);
|
|
@@ -739,12 +740,15 @@ function check$4(id, target, input) {
|
|
|
739
740
|
}
|
|
740
741
|
}
|
|
741
742
|
|
|
742
|
-
var
|
|
743
|
-
|
|
744
|
-
|
|
743
|
+
var excludeClassNames = "load,active,fixed,visible,focus,show,collaps,animat" /* Constant.ExcludeClassNames */.split("," /* Constant.Comma */);
|
|
744
|
+
var selectorMap = {};
|
|
745
|
+
function reset$k() {
|
|
746
|
+
selectorMap = {};
|
|
747
|
+
}
|
|
748
|
+
function get$1(input, type) {
|
|
745
749
|
var a = input.attributes;
|
|
746
|
-
var prefix = input.prefix ? input.prefix[
|
|
747
|
-
var suffix =
|
|
750
|
+
var prefix = input.prefix ? input.prefix[type] : null;
|
|
751
|
+
var suffix = type === 0 /* Selector.Alpha */ ? "".concat("~" /* Constant.Tilde */).concat(input.position - 1) : ":nth-of-type(".concat(input.position, ")");
|
|
748
752
|
switch (input.tag) {
|
|
749
753
|
case "STYLE":
|
|
750
754
|
case "TITLE":
|
|
@@ -759,23 +763,33 @@ function selector (input, beta) {
|
|
|
759
763
|
if (prefix === null) {
|
|
760
764
|
return "" /* Constant.Empty */;
|
|
761
765
|
}
|
|
762
|
-
prefix = "".concat(prefix
|
|
766
|
+
prefix = "".concat(prefix).concat(">" /* Constant.Separator */);
|
|
763
767
|
input.tag = input.tag.indexOf("svg:" /* Constant.SvgPrefix */) === 0 ? input.tag.substr("svg:" /* Constant.SvgPrefix */.length) : input.tag;
|
|
764
768
|
var selector = "".concat(prefix).concat(input.tag).concat(suffix);
|
|
765
|
-
var
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
769
|
+
var id = "id" /* Constant.Id */ in a && a["id" /* Constant.Id */].length > 0 ? a["id" /* Constant.Id */] : null;
|
|
770
|
+
var classes = input.tag !== "BODY" /* Constant.BodyTag */ && "class" /* Constant.Class */ in a && a["class" /* Constant.Class */].length > 0 ? a["class" /* Constant.Class */].trim().split(/\s+/).filter(function (c) { return filter(c); }).join("." /* Constant.Period */) : null;
|
|
771
|
+
if (classes && classes.length > 0) {
|
|
772
|
+
if (type === 0 /* Selector.Alpha */) {
|
|
773
|
+
// In Alpha mode, update selector to use class names, with relative positioning within the parent id container.
|
|
774
|
+
// If the node has valid class name(s) then drop relative positioning within the parent path to keep things simple.
|
|
775
|
+
var key = "".concat(getDomPath(prefix)).concat(input.tag).concat("." /* Constant.Dot */).concat(classes);
|
|
776
|
+
if (!(key in selectorMap)) {
|
|
777
|
+
selectorMap[key] = [];
|
|
778
|
+
}
|
|
779
|
+
if (selectorMap[key].indexOf(input.id) < 0) {
|
|
780
|
+
selectorMap[key].push(input.id);
|
|
781
|
+
}
|
|
782
|
+
selector = "".concat(key).concat("~" /* Constant.Tilde */).concat(selectorMap[key].indexOf(input.id));
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
// In Beta mode, we continue to look at query selectors in context of the full page
|
|
786
|
+
selector = "".concat(prefix).concat(input.tag, ".").concat(classes).concat(suffix);
|
|
787
|
+
}
|
|
778
788
|
}
|
|
789
|
+
// Update selector to use "id" field when available. There are two exceptions:
|
|
790
|
+
// (1) if "id" appears to be an auto generated string token, e.g. guid or a random id containing digits
|
|
791
|
+
// (2) if "id" appears inside a shadow DOM, in which case we continue to prefix up to shadow DOM to prevent conflicts
|
|
792
|
+
selector = id && filter(id) ? "".concat(getDomPrefix(prefix)).concat("#" /* Constant.Hash */).concat(id) : selector;
|
|
779
793
|
return selector;
|
|
780
794
|
}
|
|
781
795
|
}
|
|
@@ -784,22 +798,42 @@ function getDomPrefix(prefix) {
|
|
|
784
798
|
var iframeDomStart = prefix.lastIndexOf("".concat("iframe:" /* Constant.IFramePrefix */).concat("HTML" /* Constant.HTML */));
|
|
785
799
|
var domStart = Math.max(shadowDomStart, iframeDomStart);
|
|
786
800
|
if (domStart < 0) {
|
|
787
|
-
return ""
|
|
801
|
+
return "" /* Constant.Empty */;
|
|
802
|
+
}
|
|
803
|
+
return prefix.substring(0, prefix.indexOf(">" /* Constant.Separator */, domStart) + 1);
|
|
804
|
+
}
|
|
805
|
+
function getDomPath(input) {
|
|
806
|
+
var parts = input.split(">" /* Constant.Separator */);
|
|
807
|
+
for (var i = 0; i < parts.length; i++) {
|
|
808
|
+
var tIndex = parts[i].indexOf("~" /* Constant.Tilde */);
|
|
809
|
+
var dIndex = parts[i].indexOf("." /* Constant.Dot */);
|
|
810
|
+
parts[i] = parts[i].substring(0, dIndex > 0 ? dIndex : (tIndex > 0 ? tIndex : parts[i].length));
|
|
788
811
|
}
|
|
789
|
-
|
|
790
|
-
return prefix.substr(0, domEnd);
|
|
812
|
+
return parts.join(">" /* Constant.Separator */);
|
|
791
813
|
}
|
|
792
|
-
// Check if the given input string has digits or
|
|
793
|
-
function
|
|
814
|
+
// Check if the given input string has digits or excluded class names
|
|
815
|
+
function filter(value) {
|
|
816
|
+
if (!value) {
|
|
817
|
+
return false;
|
|
818
|
+
} // Do not process empty strings
|
|
819
|
+
if (excludeClassNames.some(function (x) { return value.toLowerCase().indexOf(x) >= 0; })) {
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
794
822
|
for (var i = 0; i < value.length; i++) {
|
|
795
823
|
var c = value.charCodeAt(i);
|
|
796
824
|
if (c >= 48 /* Character.Zero */ && c <= 57 /* Character.Nine */) {
|
|
797
|
-
return
|
|
825
|
+
return false;
|
|
798
826
|
}
|
|
799
827
|
}
|
|
800
|
-
return
|
|
828
|
+
return true;
|
|
801
829
|
}
|
|
802
830
|
|
|
831
|
+
var selector = /*#__PURE__*/Object.freeze({
|
|
832
|
+
__proto__: null,
|
|
833
|
+
reset: reset$k,
|
|
834
|
+
get: get$1
|
|
835
|
+
});
|
|
836
|
+
|
|
803
837
|
// Track the start time to be able to compute duration at the end of the task
|
|
804
838
|
var idleTimeout = 5000;
|
|
805
839
|
var tracker = {};
|
|
@@ -1119,7 +1153,7 @@ function encode$4 (type, timer, ts) {
|
|
|
1119
1153
|
}
|
|
1120
1154
|
tokens.push(suspend ? "*M" /* Constant.SuspendMutationTag */ : data[key]);
|
|
1121
1155
|
if (box && box.length === 2) {
|
|
1122
|
-
tokens.push("".concat("#" /* Constant.
|
|
1156
|
+
tokens.push("".concat("#" /* Constant.Hash */).concat(str$1(box[0]), ".").concat(str$1(box[1])));
|
|
1123
1157
|
}
|
|
1124
1158
|
break;
|
|
1125
1159
|
case "attributes":
|
|
@@ -2373,6 +2407,7 @@ function reset$7() {
|
|
|
2373
2407
|
iframeMap = new WeakMap();
|
|
2374
2408
|
privacyMap = new WeakMap();
|
|
2375
2409
|
fraudMap = new WeakMap();
|
|
2410
|
+
reset$k();
|
|
2376
2411
|
}
|
|
2377
2412
|
// We parse new root nodes for any regions or masked nodes in the beginning (document) and
|
|
2378
2413
|
// later whenever there are new additions or modifications to DOM (mutations)
|
|
@@ -2549,14 +2584,11 @@ function privacy(node, value, parent) {
|
|
|
2549
2584
|
metadata.privacy = 2 /* Privacy.Text */;
|
|
2550
2585
|
break;
|
|
2551
2586
|
case tag === "*T" /* Constant.TextTag */:
|
|
2552
|
-
// If it's a text node belonging to a STYLE or TITLE tag or one of
|
|
2587
|
+
// If it's a text node belonging to a STYLE or TITLE tag or one of scrub exceptions, then capture content
|
|
2553
2588
|
var pTag = parent && parent.data ? parent.data.tag : "" /* Constant.Empty */;
|
|
2554
|
-
var pSelector_1 = parent && parent.selector ? parent.selector[
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
case "type" /* Constant.Type */ in attributes:
|
|
2558
|
-
// If this node has an explicit type assigned to it, go through masking rules to determine right privacy setting
|
|
2559
|
-
metadata.privacy = inspect(attributes["type" /* Constant.Type */], maskInput, metadata);
|
|
2589
|
+
var pSelector_1 = parent && parent.selector ? parent.selector[1 /* Selector.Default */] : "" /* Constant.Empty */;
|
|
2590
|
+
var tags = ["STYLE" /* Constant.StyleTag */, "TITLE" /* Constant.TitleTag */, "svg:style" /* Constant.SvgStyle */];
|
|
2591
|
+
metadata.privacy = tags.includes(pTag) || override.some(function (x) { return pSelector_1.indexOf(x) >= 0; }) ? 0 /* Privacy.None */ : current;
|
|
2560
2592
|
break;
|
|
2561
2593
|
case tag === "INPUT" /* Constant.InputTag */ && current === 0 /* Privacy.None */:
|
|
2562
2594
|
// If even default privacy setting is to not mask, we still scan through input fields for any sensitive information
|
|
@@ -2564,11 +2596,13 @@ function privacy(node, value, parent) {
|
|
|
2564
2596
|
Object.keys(attributes).forEach(function (x) { return field_1 += attributes[x].toLowerCase(); });
|
|
2565
2597
|
metadata.privacy = inspect(field_1, maskInput, metadata);
|
|
2566
2598
|
break;
|
|
2567
|
-
case
|
|
2599
|
+
case tag === "INPUT" /* Constant.InputTag */ && current === 1 /* Privacy.Sensitive */:
|
|
2568
2600
|
// Look through class names to aggressively mask content
|
|
2569
2601
|
metadata.privacy = inspect(attributes["class" /* Constant.Class */], maskText, metadata);
|
|
2570
|
-
// If
|
|
2571
|
-
metadata.privacy =
|
|
2602
|
+
// If this node has an explicit type assigned to it, go through masking rules to determine right privacy setting
|
|
2603
|
+
metadata.privacy = inspect(attributes["type" /* Constant.Type */], maskInput, metadata);
|
|
2604
|
+
// If it's a button or an input option, make an exception to disable masking in sensitive mode
|
|
2605
|
+
metadata.privacy = maskDisable.indexOf(attributes["type" /* Constant.Type */]) >= 0 ? 0 /* Privacy.None */ : metadata.privacy;
|
|
2572
2606
|
break;
|
|
2573
2607
|
case current === 1 /* Privacy.Sensitive */:
|
|
2574
2608
|
// In a mode where we mask sensitive information by default, look through class names to aggressively mask content
|
|
@@ -2615,10 +2649,11 @@ function updateSelector(value) {
|
|
|
2615
2649
|
var prefix = parent ? parent.selector : null;
|
|
2616
2650
|
var d = value.data;
|
|
2617
2651
|
var p = position(parent, value);
|
|
2618
|
-
var s = { tag: d.tag, prefix: prefix, position: p, attributes: d.attributes };
|
|
2619
|
-
value.selector = [
|
|
2652
|
+
var s = { id: value.id, tag: d.tag, prefix: prefix, position: p, attributes: d.attributes };
|
|
2653
|
+
value.selector = [get$1(s, 0 /* Selector.Alpha */), get$1(s, 1 /* Selector.Beta */)];
|
|
2620
2654
|
value.hash = value.selector.map(function (x) { return x ? hash(x) : null; });
|
|
2621
2655
|
value.hash.forEach(function (h) { return hashMap[h] = value.id; });
|
|
2656
|
+
// Match fragment configuration against both alpha and beta hash
|
|
2622
2657
|
if (value.hash.some(function (h) { return fragments.indexOf(h) !== -1; })) {
|
|
2623
2658
|
value.fragment = value.id;
|
|
2624
2659
|
}
|
|
@@ -3162,7 +3197,7 @@ function queue(tokens, transmit) {
|
|
|
3162
3197
|
// We enrich the data going out with the existing upload. In these cases, call to upload comes with 'transmit' set to false.
|
|
3163
3198
|
if (transmit && timeout === null) {
|
|
3164
3199
|
if (type !== 25 /* Event.Ping */) {
|
|
3165
|
-
reset$
|
|
3200
|
+
reset$n();
|
|
3166
3201
|
}
|
|
3167
3202
|
timeout = setTimeout(upload, gap);
|
|
3168
3203
|
queuedTime = now;
|
|
@@ -3352,8 +3387,8 @@ function delay() {
|
|
|
3352
3387
|
return typeof config$1.upload === "string" /* Constant.String */ ? Math.max(Math.min(gap, 30000 /* Setting.MaxUploadDelay */), 100 /* Setting.MinUploadDelay */) : config$1.delay;
|
|
3353
3388
|
}
|
|
3354
3389
|
function response(payload) {
|
|
3355
|
-
var
|
|
3356
|
-
switch (
|
|
3390
|
+
var parts = payload && payload.length > 0 ? payload.split(" ") : ["" /* Constant.Empty */];
|
|
3391
|
+
switch (parts[0]) {
|
|
3357
3392
|
case "END" /* Constant.End */:
|
|
3358
3393
|
// Clear out session storage and end the session so we can start fresh the next time
|
|
3359
3394
|
trigger(6 /* Check.Server */);
|
|
@@ -3362,6 +3397,12 @@ function response(payload) {
|
|
|
3362
3397
|
// Upgrade current session to send back playback information
|
|
3363
3398
|
upgrade("Auto" /* Constant.Auto */);
|
|
3364
3399
|
break;
|
|
3400
|
+
case "ACTION" /* Constant.Action */:
|
|
3401
|
+
// Invoke action callback, if configured and has a valid value
|
|
3402
|
+
if (config$1.action && parts.length > 1) {
|
|
3403
|
+
config$1.action(parts[1]);
|
|
3404
|
+
}
|
|
3405
|
+
break;
|
|
3365
3406
|
}
|
|
3366
3407
|
}
|
|
3367
3408
|
|
|
@@ -3617,7 +3658,7 @@ function encode$1 (event) {
|
|
|
3617
3658
|
tokens.push(b.data.activityTime);
|
|
3618
3659
|
queue(tokens, false);
|
|
3619
3660
|
}
|
|
3620
|
-
reset$
|
|
3661
|
+
reset$p();
|
|
3621
3662
|
break;
|
|
3622
3663
|
case 25 /* Event.Ping */:
|
|
3623
3664
|
tokens.push(data$h.gap);
|
|
@@ -3650,7 +3691,7 @@ function encode$1 (event) {
|
|
|
3650
3691
|
tokens.push(v);
|
|
3651
3692
|
tokens.push(data$e[v]);
|
|
3652
3693
|
}
|
|
3653
|
-
reset$
|
|
3694
|
+
reset$l();
|
|
3654
3695
|
queue(tokens, false);
|
|
3655
3696
|
}
|
|
3656
3697
|
break;
|
|
@@ -3665,7 +3706,7 @@ function encode$1 (event) {
|
|
|
3665
3706
|
// However, for data over the wire, we round it off to milliseconds precision.
|
|
3666
3707
|
tokens.push(Math.round(updates$3[m]));
|
|
3667
3708
|
}
|
|
3668
|
-
reset$
|
|
3709
|
+
reset$o();
|
|
3669
3710
|
queue(tokens, false);
|
|
3670
3711
|
}
|
|
3671
3712
|
break;
|
|
@@ -3691,7 +3732,7 @@ function encode$1 (event) {
|
|
|
3691
3732
|
tokens.push(key);
|
|
3692
3733
|
tokens.push([].concat.apply([], data$g[e]));
|
|
3693
3734
|
}
|
|
3694
|
-
reset$
|
|
3735
|
+
reset$m();
|
|
3695
3736
|
queue(tokens, false);
|
|
3696
3737
|
}
|
|
3697
3738
|
break;
|
package/package.json
CHANGED
package/src/core/config.ts
CHANGED
package/src/core/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let version = "0.6.
|
|
1
|
+
let version = "0.6.41";
|
|
2
2
|
export default version;
|
package/src/data/upload.ts
CHANGED
|
@@ -242,8 +242,8 @@ function delay(): number {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
function response(payload: string): void {
|
|
245
|
-
let
|
|
246
|
-
switch (
|
|
245
|
+
let parts = payload && payload.length > 0 ? payload.split(" ") : [Constant.Empty];
|
|
246
|
+
switch (parts[0]) {
|
|
247
247
|
case Constant.End:
|
|
248
248
|
// Clear out session storage and end the session so we can start fresh the next time
|
|
249
249
|
limit.trigger(Check.Server);
|
|
@@ -252,5 +252,9 @@ function response(payload: string): void {
|
|
|
252
252
|
// Upgrade current session to send back playback information
|
|
253
253
|
clarity.upgrade(Constant.Auto);
|
|
254
254
|
break;
|
|
255
|
+
case Constant.Action:
|
|
256
|
+
// Invoke action callback, if configured and has a valid value
|
|
257
|
+
if (config.action && parts.length > 1) { config.action(parts[1]); }
|
|
258
|
+
break;
|
|
255
259
|
}
|
|
256
260
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as clarity from "./clarity";
|
|
2
2
|
import hash from "./core/hash";
|
|
3
|
-
import selector from "./layout/selector";
|
|
3
|
+
import * as selector from "./layout/selector";
|
|
4
4
|
import { get, getNode, lookup } from "./layout/dom";
|
|
5
5
|
|
|
6
6
|
const helper = { hash, selector, get, getNode, lookup }
|
package/src/layout/dom.ts
CHANGED
|
@@ -5,7 +5,7 @@ import config from "@src/core/config";
|
|
|
5
5
|
import hash from "@src/core/hash";
|
|
6
6
|
import * as internal from "@src/diagnostic/internal";
|
|
7
7
|
import * as region from "@src/layout/region";
|
|
8
|
-
import selector from "@src/layout/selector";
|
|
8
|
+
import * as selector from "@src/layout/selector";
|
|
9
9
|
import * as mutation from "@src/layout/mutation";
|
|
10
10
|
import * as extract from "@src/data/extract";
|
|
11
11
|
let index: number = 1;
|
|
@@ -50,6 +50,7 @@ function reset(): void {
|
|
|
50
50
|
iframeMap = new WeakMap();
|
|
51
51
|
privacyMap = new WeakMap();
|
|
52
52
|
fraudMap = new WeakMap();
|
|
53
|
+
selector.reset();
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// We parse new root nodes for any regions or masked nodes in the beginning (document) and
|
|
@@ -236,14 +237,11 @@ function privacy(node: Node, value: NodeValue, parent: NodeValue): void {
|
|
|
236
237
|
metadata.privacy = Privacy.Text;
|
|
237
238
|
break;
|
|
238
239
|
case tag === Constant.TextTag:
|
|
239
|
-
// If it's a text node belonging to a STYLE or TITLE tag or one of
|
|
240
|
+
// If it's a text node belonging to a STYLE or TITLE tag or one of scrub exceptions, then capture content
|
|
240
241
|
let pTag = parent && parent.data ? parent.data.tag : Constant.Empty;
|
|
241
|
-
let pSelector = parent && parent.selector ? parent.selector[Selector.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
case Constant.Type in attributes:
|
|
245
|
-
// If this node has an explicit type assigned to it, go through masking rules to determine right privacy setting
|
|
246
|
-
metadata.privacy = inspect(attributes[Constant.Type], maskInput, metadata);
|
|
242
|
+
let pSelector = parent && parent.selector ? parent.selector[Selector.Default] : Constant.Empty;
|
|
243
|
+
let tags : string[] = [Constant.StyleTag, Constant.TitleTag, Constant.SvgStyle];
|
|
244
|
+
metadata.privacy = tags.includes(pTag) || override.some(x => pSelector.indexOf(x) >= 0) ? Privacy.None : current;
|
|
247
245
|
break;
|
|
248
246
|
case tag === Constant.InputTag && current === Privacy.None:
|
|
249
247
|
// If even default privacy setting is to not mask, we still scan through input fields for any sensitive information
|
|
@@ -251,11 +249,13 @@ function privacy(node: Node, value: NodeValue, parent: NodeValue): void {
|
|
|
251
249
|
Object.keys(attributes).forEach(x => field += attributes[x].toLowerCase());
|
|
252
250
|
metadata.privacy = inspect(field, maskInput, metadata);
|
|
253
251
|
break;
|
|
254
|
-
case
|
|
252
|
+
case tag === Constant.InputTag && current === Privacy.Sensitive:
|
|
255
253
|
// Look through class names to aggressively mask content
|
|
256
254
|
metadata.privacy = inspect(attributes[Constant.Class], maskText, metadata);
|
|
257
|
-
// If
|
|
258
|
-
metadata.privacy
|
|
255
|
+
// If this node has an explicit type assigned to it, go through masking rules to determine right privacy setting
|
|
256
|
+
metadata.privacy = inspect(attributes[Constant.Type], maskInput, metadata);
|
|
257
|
+
// If it's a button or an input option, make an exception to disable masking in sensitive mode
|
|
258
|
+
metadata.privacy = maskDisable.indexOf(attributes[Constant.Type]) >= 0 ? Privacy.None : metadata.privacy;
|
|
259
259
|
break;
|
|
260
260
|
case current === Privacy.Sensitive:
|
|
261
261
|
// In a mode where we mask sensitive information by default, look through class names to aggressively mask content
|
|
@@ -298,10 +298,11 @@ function updateSelector(value: NodeValue): void {
|
|
|
298
298
|
let prefix = parent ? parent.selector : null;
|
|
299
299
|
let d = value.data;
|
|
300
300
|
let p = position(parent, value);
|
|
301
|
-
let s: SelectorInput = { tag: d.tag, prefix, position: p, attributes: d.attributes };
|
|
302
|
-
value.selector = [selector(s), selector(s,
|
|
301
|
+
let s: SelectorInput = { id: value.id, tag: d.tag, prefix, position: p, attributes: d.attributes };
|
|
302
|
+
value.selector = [selector.get(s, Selector.Alpha), selector.get(s, Selector.Beta)];
|
|
303
303
|
value.hash = value.selector.map(x => x ? hash(x) : null) as [string, string];
|
|
304
304
|
value.hash.forEach(h => hashMap[h] = value.id);
|
|
305
|
+
// Match fragment configuration against both alpha and beta hash
|
|
305
306
|
if (value.hash.some(h => extract.fragments.indexOf(h) !== -1)) {
|
|
306
307
|
value.fragment = value.id;
|
|
307
308
|
}
|
package/src/layout/encode.ts
CHANGED
|
@@ -62,7 +62,7 @@ export default async function (type: Event, timer: Timer = null, ts: number = nu
|
|
|
62
62
|
if (value.parent && active) { tokens.push(value.parent); }
|
|
63
63
|
if (value.previous && active) { tokens.push(value.previous); }
|
|
64
64
|
tokens.push(suspend ? Constant.SuspendMutationTag : data[key]);
|
|
65
|
-
if (box && box.length === 2) { tokens.push(`${Constant.
|
|
65
|
+
if (box && box.length === 2) { tokens.push(`${Constant.Hash}${str(box[0])}.${str(box[1])}`); }
|
|
66
66
|
break;
|
|
67
67
|
case "attributes":
|
|
68
68
|
for (let attr in data[key]) {
|
package/src/layout/selector.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { Character } from "../../types/data";
|
|
2
2
|
import { Constant, Selector, SelectorInput } from "../../types/layout";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const excludeClassNames = Constant.ExcludeClassNames.split(Constant.Comma);
|
|
5
|
+
let selectorMap: { [selector: string]: number[] } = {};
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
+
export function reset(): void {
|
|
8
|
+
selectorMap = {};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function get(input: SelectorInput, type: Selector): string {
|
|
7
12
|
let a = input.attributes;
|
|
8
|
-
let prefix = input.prefix ? input.prefix[
|
|
9
|
-
let suffix =
|
|
13
|
+
let prefix = input.prefix ? input.prefix[type] : null;
|
|
14
|
+
let suffix = type === Selector.Alpha ? `${Constant.Tilde}${input.position-1}` : `:nth-of-type(${input.position})`;
|
|
10
15
|
switch (input.tag) {
|
|
11
16
|
case "STYLE":
|
|
12
17
|
case "TITLE":
|
|
@@ -19,22 +24,28 @@ export default function(input: SelectorInput, beta: boolean = false): string {
|
|
|
19
24
|
return Constant.HTML;
|
|
20
25
|
default:
|
|
21
26
|
if (prefix === null) { return Constant.Empty; }
|
|
22
|
-
prefix = `${prefix}
|
|
27
|
+
prefix = `${prefix}${Constant.Separator}`;
|
|
23
28
|
input.tag = input.tag.indexOf(Constant.SvgPrefix) === 0 ? input.tag.substr(Constant.SvgPrefix.length) : input.tag;
|
|
24
29
|
let selector = `${prefix}${input.tag}${suffix}`;
|
|
25
|
-
let
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
let id = Constant.Id in a && a[Constant.Id].length > 0 ? a[Constant.Id] : null;
|
|
31
|
+
let classes = input.tag !== Constant.BodyTag && Constant.Class in a && a[Constant.Class].length > 0 ? a[Constant.Class].trim().split(/\s+/).filter(c => filter(c)).join(Constant.Period) : null;
|
|
32
|
+
if (classes && classes.length > 0) {
|
|
33
|
+
if (type === Selector.Alpha) {
|
|
34
|
+
// In Alpha mode, update selector to use class names, with relative positioning within the parent id container.
|
|
35
|
+
// If the node has valid class name(s) then drop relative positioning within the parent path to keep things simple.
|
|
36
|
+
let key = `${getDomPath(prefix)}${input.tag}${Constant.Dot}${classes}`;
|
|
37
|
+
if (!(key in selectorMap)) { selectorMap[key] = []; }
|
|
38
|
+
if (selectorMap[key].indexOf(input.id) < 0) { selectorMap[key].push(input.id); }
|
|
39
|
+
selector = `${key}${Constant.Tilde}${selectorMap[key].indexOf(input.id)}`;
|
|
40
|
+
} else {
|
|
41
|
+
// In Beta mode, we continue to look at query selectors in context of the full page
|
|
42
|
+
selector = `${prefix}${input.tag}.${classes}${suffix}`
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Update selector to use "id" field when available. There are two exceptions:
|
|
46
|
+
// (1) if "id" appears to be an auto generated string token, e.g. guid or a random id containing digits
|
|
47
|
+
// (2) if "id" appears inside a shadow DOM, in which case we continue to prefix up to shadow DOM to prevent conflicts
|
|
48
|
+
selector = id && filter(id) ? `${getDomPrefix(prefix)}${Constant.Hash}${id}` : selector;
|
|
38
49
|
return selector;
|
|
39
50
|
}
|
|
40
51
|
}
|
|
@@ -44,19 +55,28 @@ function getDomPrefix(prefix: string): string {
|
|
|
44
55
|
const iframeDomStart = prefix.lastIndexOf(`${Constant.IFramePrefix}${Constant.HTML}`);
|
|
45
56
|
const domStart = Math.max(shadowDomStart, iframeDomStart);
|
|
46
57
|
|
|
47
|
-
if (domStart < 0) {
|
|
48
|
-
return "";
|
|
49
|
-
}
|
|
58
|
+
if (domStart < 0) { return Constant.Empty; }
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
return prefix.substring(0, prefix.indexOf(Constant.Separator, domStart) + 1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getDomPath(input: string): string {
|
|
64
|
+
let parts = input.split(Constant.Separator);
|
|
65
|
+
for (let i = 0; i < parts.length; i++) {
|
|
66
|
+
let tIndex = parts[i].indexOf(Constant.Tilde);
|
|
67
|
+
let dIndex = parts[i].indexOf(Constant.Dot);
|
|
68
|
+
parts[i] = parts[i].substring(0, dIndex > 0 ? dIndex : (tIndex > 0 ? tIndex : parts[i].length));
|
|
69
|
+
}
|
|
70
|
+
return parts.join(Constant.Separator);
|
|
53
71
|
}
|
|
54
72
|
|
|
55
|
-
// Check if the given input string has digits or
|
|
56
|
-
function
|
|
73
|
+
// Check if the given input string has digits or excluded class names
|
|
74
|
+
function filter(value: string): boolean {
|
|
75
|
+
if (!value) { return false; } // Do not process empty strings
|
|
76
|
+
if (excludeClassNames.some(x => value.toLowerCase().indexOf(x) >= 0)) { return false; }
|
|
57
77
|
for (let i = 0; i < value.length; i++) {
|
|
58
78
|
let c = value.charCodeAt(i);
|
|
59
|
-
if (c >= Character.Zero && c <= Character.Nine) { return
|
|
79
|
+
if (c >= Character.Zero && c <= Character.Nine) { return false };
|
|
60
80
|
}
|
|
61
|
-
return
|
|
81
|
+
return true;
|
|
62
82
|
}
|