clarity-js 0.6.28 → 0.6.32
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 +108 -78
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +108 -78
- package/package.json +1 -1
- package/src/core/api.ts +8 -0
- package/src/core/event.ts +4 -3
- package/src/core/version.ts +1 -1
- package/src/data/metadata.ts +1 -1
- package/src/data/upload.ts +11 -8
- package/src/diagnostic/internal.ts +1 -14
- package/src/layout/dom.ts +28 -13
- package/src/layout/mutation.ts +14 -12
- package/src/layout/node.ts +1 -0
- package/src/layout/selector.ts +14 -3
- package/test/helper.ts +10 -2
- package/types/core.d.ts +7 -0
- package/types/data.d.ts +3 -0
- package/types/layout.d.ts +1 -2
package/build/clarity.module.js
CHANGED
|
@@ -63,6 +63,13 @@ var config$1 = {
|
|
|
63
63
|
upgrade: null
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
function api(method) {
|
|
67
|
+
// Zone.js, a popular package for Angular, overrides native browser APIs which can lead to inconsistent state for single page applications.
|
|
68
|
+
// Example issue: https://github.com/angular/angular/issues/31712
|
|
69
|
+
// As a work around, we ensuring Clarity access APIs outside of Zone (and use native implementation instead)
|
|
70
|
+
return window["Zone" /* Zone */] && "__symbol__" /* Symbol */ in window["Zone" /* Zone */] ? window["Zone" /* Zone */]["__symbol__" /* Symbol */](method) : method;
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
var startTime = 0;
|
|
67
74
|
function start$E() {
|
|
68
75
|
startTime = performance.now();
|
|
@@ -76,7 +83,7 @@ function stop$B() {
|
|
|
76
83
|
startTime = 0;
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
var version$1 = "0.6.
|
|
86
|
+
var version$1 = "0.6.32";
|
|
80
87
|
|
|
81
88
|
// tslint:disable: no-bitwise
|
|
82
89
|
function hash (input) {
|
|
@@ -610,7 +617,7 @@ function mangleText(value) {
|
|
|
610
617
|
var index = value.indexOf(first);
|
|
611
618
|
var prefix = value.substr(0, index);
|
|
612
619
|
var suffix = value.substr(index + trimmed.length);
|
|
613
|
-
return ""
|
|
620
|
+
return "".concat(prefix).concat(trimmed.length.toString(36)).concat(suffix);
|
|
614
621
|
}
|
|
615
622
|
return value;
|
|
616
623
|
}
|
|
@@ -803,7 +810,7 @@ function suspend$1(timer) {
|
|
|
803
810
|
});
|
|
804
811
|
}
|
|
805
812
|
function key(timer) {
|
|
806
|
-
return timer.id
|
|
813
|
+
return "".concat(timer.id, ".").concat(timer.cost);
|
|
807
814
|
}
|
|
808
815
|
function wait() {
|
|
809
816
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -1028,7 +1035,7 @@ function encode$4 (type, timer, ts) {
|
|
|
1028
1035
|
}
|
|
1029
1036
|
tokens.push(suspend ? "*M" /* SuspendMutationTag */ : data[key]);
|
|
1030
1037
|
if (size && size.length === 2) {
|
|
1031
|
-
tokens.push(""
|
|
1038
|
+
tokens.push("".concat("#" /* Box */).concat(str$1(size[0]), ".").concat(str$1(size[1])));
|
|
1032
1039
|
}
|
|
1033
1040
|
break;
|
|
1034
1041
|
case "attributes":
|
|
@@ -1068,7 +1075,7 @@ function str$1(input) {
|
|
|
1068
1075
|
return input.toString(36);
|
|
1069
1076
|
}
|
|
1070
1077
|
function attribute(key, value, privacy) {
|
|
1071
|
-
return key
|
|
1078
|
+
return "".concat(key, "=").concat(scrub(value, key, privacy));
|
|
1072
1079
|
}
|
|
1073
1080
|
|
|
1074
1081
|
var data$b = [];
|
|
@@ -1221,13 +1228,12 @@ var history$3 = {};
|
|
|
1221
1228
|
var data$9;
|
|
1222
1229
|
function start$s() {
|
|
1223
1230
|
history$3 = {};
|
|
1224
|
-
bind(document, "securitypolicyviolation", csp);
|
|
1225
1231
|
}
|
|
1226
1232
|
function log$1(code, severity, name, message, stack) {
|
|
1227
1233
|
if (name === void 0) { name = null; }
|
|
1228
1234
|
if (message === void 0) { message = null; }
|
|
1229
1235
|
if (stack === void 0) { stack = null; }
|
|
1230
|
-
var key = name ? name
|
|
1236
|
+
var key = name ? "".concat(name, "|").concat(message) : "";
|
|
1231
1237
|
// While rare, it's possible for code to fail repeatedly during the lifetime of the same page
|
|
1232
1238
|
// In those cases, we only want to log the failure once and not spam logs with redundant information.
|
|
1233
1239
|
if (code in history$3 && history$3[code].indexOf(key) >= 0) {
|
|
@@ -1243,15 +1249,6 @@ function log$1(code, severity, name, message, stack) {
|
|
|
1243
1249
|
}
|
|
1244
1250
|
encode$3(33 /* Log */);
|
|
1245
1251
|
}
|
|
1246
|
-
function csp(e) {
|
|
1247
|
-
var upload = config$1.upload;
|
|
1248
|
-
var parts = upload ? upload.substr(0, upload.indexOf("/", "https://" /* HTTPS */.length)).split("." /* Dot */) : []; // Look for first "/" starting after initial "https://" string
|
|
1249
|
-
var domain = parts.length >= 2 ? parts.splice(-2).join("." /* Dot */) : null;
|
|
1250
|
-
// Capture content security policy violation only if disposition value is not explicitly set to "report"
|
|
1251
|
-
if (domain && e.blockedURI && e.blockedURI.indexOf(domain) >= 0 && e["disposition"] !== "report" /* Report */) {
|
|
1252
|
-
log$1(7 /* ContentSecurityPolicy */, 1 /* Warning */, e.blockedURI);
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
1252
|
function stop$r() {
|
|
1256
1253
|
history$3 = {};
|
|
1257
1254
|
}
|
|
@@ -1290,7 +1287,7 @@ function metrics(root, value) {
|
|
|
1290
1287
|
root.querySelectorAll(match).forEach(function (e) { max(metricId, num$2(e.innerText, scale)); });
|
|
1291
1288
|
break;
|
|
1292
1289
|
case 2 /* Attribute */:
|
|
1293
|
-
root.querySelectorAll("["
|
|
1290
|
+
root.querySelectorAll("[".concat(match, "]")).forEach(function (e) { max(metricId, num$2(e.getAttribute(match), scale, false)); });
|
|
1294
1291
|
break;
|
|
1295
1292
|
case 1 /* Javascript */:
|
|
1296
1293
|
max(metricId, evaluate(match, "number" /* Number */));
|
|
@@ -1312,7 +1309,7 @@ function dimensions(root, value) {
|
|
|
1312
1309
|
root.querySelectorAll(match).forEach(function (e) { log(dimensionId, str(e.innerText)); });
|
|
1313
1310
|
break;
|
|
1314
1311
|
case 2 /* Attribute */:
|
|
1315
|
-
root.querySelectorAll("["
|
|
1312
|
+
root.querySelectorAll("[".concat(match, "]")).forEach(function (e) { log(dimensionId, str(e.getAttribute(match))); });
|
|
1316
1313
|
break;
|
|
1317
1314
|
case 1 /* Javascript */:
|
|
1318
1315
|
log(dimensionId, str(evaluate(match, "string" /* String */)));
|
|
@@ -1379,7 +1376,7 @@ function selector (input, beta) {
|
|
|
1379
1376
|
if (beta === void 0) { beta = false; }
|
|
1380
1377
|
var a = input.attributes;
|
|
1381
1378
|
var prefix = input.prefix ? input.prefix[beta ? 1 /* Beta */ : 0 /* Stable */] : null;
|
|
1382
|
-
var suffix = beta || ((a && !("class" /* Class */ in a)) || TAGS.indexOf(input.tag) >= 0) ? ":nth-of-type("
|
|
1379
|
+
var suffix = beta || ((a && !("class" /* Class */ in a)) || TAGS.indexOf(input.tag) >= 0) ? ":nth-of-type(".concat(input.position, ")") : "" /* Empty */;
|
|
1383
1380
|
switch (input.tag) {
|
|
1384
1381
|
case "STYLE":
|
|
1385
1382
|
case "TITLE":
|
|
@@ -1394,28 +1391,36 @@ function selector (input, beta) {
|
|
|
1394
1391
|
if (prefix === null) {
|
|
1395
1392
|
return "" /* Empty */;
|
|
1396
1393
|
}
|
|
1397
|
-
prefix = prefix
|
|
1394
|
+
prefix = "".concat(prefix, ">");
|
|
1398
1395
|
input.tag = input.tag.indexOf("svg:" /* SvgPrefix */) === 0 ? input.tag.substr("svg:" /* SvgPrefix */.length) : input.tag;
|
|
1399
|
-
var selector = ""
|
|
1396
|
+
var selector = "".concat(prefix).concat(input.tag).concat(suffix);
|
|
1400
1397
|
var classes = "class" /* Class */ in a && a["class" /* Class */].length > 0 ? a["class" /* Class */].trim().split(/\s+/) : null;
|
|
1401
1398
|
if (beta) {
|
|
1402
1399
|
// In beta mode, update selector to use "id" field when available. There are two exceptions:
|
|
1403
1400
|
// (1) if "id" appears to be an auto generated string token, e.g. guid or a random id containing digits
|
|
1404
1401
|
// (2) if "id" appears inside a shadow DOM, in which case we continue to prefix up to shadow DOM to prevent conflicts
|
|
1405
|
-
var shadowStart = prefix.lastIndexOf("*S" /* ShadowDomTag */);
|
|
1406
|
-
var shadowEnd = prefix.indexOf(">", shadowStart) + 1;
|
|
1407
1402
|
var id = "id" /* Id */ in a && a["id" /* Id */].length > 0 ? a["id" /* Id */] : null;
|
|
1408
1403
|
classes = input.tag !== "BODY" /* BodyTag */ && classes ? classes.filter(function (c) { return !hasDigits(c); }) : [];
|
|
1409
|
-
selector = classes.length > 0 ? ""
|
|
1410
|
-
selector = id && hasDigits(id) === false ? (
|
|
1404
|
+
selector = classes.length > 0 ? "".concat(prefix).concat(input.tag, ".").concat(classes.join(".")).concat(suffix) : selector;
|
|
1405
|
+
selector = id && hasDigits(id) === false ? "".concat(getDomPrefix(prefix), "#").concat(id) : selector;
|
|
1411
1406
|
}
|
|
1412
1407
|
else {
|
|
1413
1408
|
// Otherwise, fallback to stable mode, where we include class names as part of the selector
|
|
1414
|
-
selector = classes ? ""
|
|
1409
|
+
selector = classes ? "".concat(prefix).concat(input.tag, ".").concat(classes.join(".")).concat(suffix) : selector;
|
|
1415
1410
|
}
|
|
1416
1411
|
return selector;
|
|
1417
1412
|
}
|
|
1418
1413
|
}
|
|
1414
|
+
function getDomPrefix(prefix) {
|
|
1415
|
+
var shadowDomStart = prefix.lastIndexOf("*S" /* ShadowDomTag */);
|
|
1416
|
+
var iframeDomStart = prefix.lastIndexOf("".concat("iframe:" /* IFramePrefix */).concat("HTML" /* HTML */));
|
|
1417
|
+
var domStart = Math.max(shadowDomStart, iframeDomStart);
|
|
1418
|
+
if (domStart < 0) {
|
|
1419
|
+
return "";
|
|
1420
|
+
}
|
|
1421
|
+
var domEnd = prefix.indexOf(">", domStart) + 1;
|
|
1422
|
+
return prefix.substr(0, domEnd);
|
|
1423
|
+
}
|
|
1419
1424
|
// Check if the given input string has digits or not
|
|
1420
1425
|
function hasDigits(value) {
|
|
1421
1426
|
for (var i = 0; i < value.length; i++) {
|
|
@@ -1430,19 +1435,21 @@ function hasDigits(value) {
|
|
|
1430
1435
|
var index = 1;
|
|
1431
1436
|
// Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#%3Cinput%3E_types
|
|
1432
1437
|
var DISALLOWED_TYPES = ["password", "hidden", "email", "tel"];
|
|
1433
|
-
var DISALLOWED_NAMES = ["addr", "cell", "code", "dob", "email", "mob", "name", "phone", "secret", "social", "ssn", "tel", "zip", "pass"];
|
|
1438
|
+
var DISALLOWED_NAMES = ["addr", "cell", "code", "dob", "email", "mob", "name", "phone", "secret", "social", "ssn", "tel", "zip", "pass", "card", "account", "cvv", "ccv"];
|
|
1434
1439
|
var DISALLOWED_MATCH = ["address", "password", "contact"];
|
|
1435
1440
|
var nodes = [];
|
|
1436
1441
|
var values = [];
|
|
1437
1442
|
var updateMap = [];
|
|
1438
1443
|
var hashMap = {};
|
|
1444
|
+
var override = [];
|
|
1445
|
+
var unmask = [];
|
|
1439
1446
|
// The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced
|
|
1440
1447
|
var idMap = null; // Maps node => id.
|
|
1441
1448
|
var iframeMap = null; // Maps iframe's contentDocument => parent iframe element
|
|
1442
1449
|
var privacyMap = null; // Maps node => Privacy (enum)
|
|
1443
1450
|
function start$r() {
|
|
1444
1451
|
reset$g();
|
|
1445
|
-
parse(document);
|
|
1452
|
+
parse(document, true);
|
|
1446
1453
|
}
|
|
1447
1454
|
function stop$q() {
|
|
1448
1455
|
reset$g();
|
|
@@ -1453,16 +1460,23 @@ function reset$g() {
|
|
|
1453
1460
|
values = [];
|
|
1454
1461
|
updateMap = [];
|
|
1455
1462
|
hashMap = {};
|
|
1463
|
+
override = [];
|
|
1464
|
+
unmask = [];
|
|
1456
1465
|
idMap = new WeakMap();
|
|
1457
1466
|
iframeMap = new WeakMap();
|
|
1458
1467
|
privacyMap = new WeakMap();
|
|
1459
1468
|
}
|
|
1460
1469
|
// We parse new root nodes for any regions or masked nodes in the beginning (document) and
|
|
1461
1470
|
// later whenever there are new additions or modifications to DOM (mutations)
|
|
1462
|
-
function parse(root) {
|
|
1471
|
+
function parse(root, init) {
|
|
1472
|
+
if (init === void 0) { init = false; }
|
|
1463
1473
|
// Wrap selectors in a try / catch block.
|
|
1464
1474
|
// It's possible for script to receive invalid selectors, e.g. "'#id'" with extra quotes, and cause the code below to fail
|
|
1465
1475
|
try {
|
|
1476
|
+
// Parse unmask configuration into separate query selectors and override tokens as part of initialization
|
|
1477
|
+
if (init) {
|
|
1478
|
+
config$1.unmask.forEach(function (x) { return x.indexOf("!" /* Bang */) < 0 ? unmask.push(x) : override.push(x.substr(1)); });
|
|
1479
|
+
}
|
|
1466
1480
|
// Since mutations may happen on leaf nodes too, e.g. text nodes, which may not support all selector APIs.
|
|
1467
1481
|
// We ensure that the root note supports querySelectorAll API before executing the code below to identify new regions.
|
|
1468
1482
|
if ("querySelectorAll" in root) {
|
|
@@ -1470,7 +1484,7 @@ function parse(root) {
|
|
|
1470
1484
|
metrics(root, config$1.metrics);
|
|
1471
1485
|
dimensions(root, config$1.dimensions);
|
|
1472
1486
|
config$1.mask.forEach(function (x) { return root.querySelectorAll(x).forEach(function (e) { return privacyMap.set(e, 3 /* TextImage */); }); }); // Masked Elements
|
|
1473
|
-
|
|
1487
|
+
unmask.forEach(function (x) { return root.querySelectorAll(x).forEach(function (e) { return privacyMap.set(e, 0 /* None */); }); }); // Unmasked Elements
|
|
1474
1488
|
}
|
|
1475
1489
|
}
|
|
1476
1490
|
catch (e) {
|
|
@@ -1495,17 +1509,15 @@ function add(node, parent, data, source) {
|
|
|
1495
1509
|
var previousId = getPreviousId(node);
|
|
1496
1510
|
var privacy = config$1.content ? 1 /* Sensitive */ : 2 /* Text */;
|
|
1497
1511
|
var parentValue = null;
|
|
1498
|
-
var parentTag = "" /* Empty */;
|
|
1499
1512
|
var regionId = exists(node) ? id : null;
|
|
1500
1513
|
if (parentId >= 0 && values[parentId]) {
|
|
1501
1514
|
parentValue = values[parentId];
|
|
1502
|
-
parentTag = parentValue.data.tag;
|
|
1503
1515
|
parentValue.children.push(id);
|
|
1504
1516
|
regionId = regionId === null ? parentValue.region : regionId;
|
|
1505
1517
|
privacy = parentValue.metadata.privacy;
|
|
1506
1518
|
}
|
|
1507
1519
|
// Check to see if this particular node should be masked or not
|
|
1508
|
-
privacy = getPrivacy(node, data,
|
|
1520
|
+
privacy = getPrivacy(node, data, parentValue, privacy);
|
|
1509
1521
|
// If there's an explicit region attribute set on the element, use it to mark a region on the page
|
|
1510
1522
|
if (data.attributes && "data-clarity-region" /* RegionData */ in data.attributes) {
|
|
1511
1523
|
observe$b(node, data.attributes["data-clarity-region" /* RegionData */]);
|
|
@@ -1600,21 +1612,35 @@ function iframe(node) {
|
|
|
1600
1612
|
var doc = node.nodeType === Node.DOCUMENT_NODE ? node : null;
|
|
1601
1613
|
return doc && iframeMap.has(doc) ? iframeMap.get(doc) : null;
|
|
1602
1614
|
}
|
|
1603
|
-
function getPrivacy(node, data,
|
|
1615
|
+
function getPrivacy(node, data, parent, privacy) {
|
|
1604
1616
|
var attributes = data.attributes;
|
|
1605
1617
|
var tag = data.tag.toUpperCase();
|
|
1606
1618
|
// If this node was explicitly configured to contain sensitive content, use that information and return the value
|
|
1607
1619
|
if (privacyMap.has(node)) {
|
|
1608
1620
|
return privacyMap.get(node);
|
|
1609
1621
|
}
|
|
1622
|
+
// If it's a text node belonging to a STYLE or TITLE tag;
|
|
1623
|
+
// Or, the text node belongs to one of SCRUB_EXCEPTIONS
|
|
1624
|
+
// then reset the privacy setting to ensure we capture the content
|
|
1625
|
+
if (tag === "*T" /* TextTag */ && parent && parent.data) {
|
|
1626
|
+
var path = parent.selector ? parent.selector[0 /* Stable */] : "" /* Empty */;
|
|
1627
|
+
privacy = parent.data.tag === "STYLE" /* StyleTag */ || parent.data.tag === "TITLE" /* TitleTag */ ? 0 /* None */ : privacy;
|
|
1628
|
+
for (var _i = 0, override_1 = override; _i < override_1.length; _i++) {
|
|
1629
|
+
var entry = override_1[_i];
|
|
1630
|
+
if (path.indexOf(entry) >= 0) {
|
|
1631
|
+
privacy = 0 /* None */;
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1610
1636
|
// Do not proceed if attributes are missing for the node
|
|
1611
1637
|
if (attributes === null || attributes === undefined) {
|
|
1612
1638
|
return privacy;
|
|
1613
1639
|
}
|
|
1614
1640
|
// Look up for sensitive fields
|
|
1615
1641
|
if ("class" /* Class */ in attributes && privacy === 1 /* Sensitive */) {
|
|
1616
|
-
for (var
|
|
1617
|
-
var match = DISALLOWED_MATCH_1[
|
|
1642
|
+
for (var _a = 0, DISALLOWED_MATCH_1 = DISALLOWED_MATCH; _a < DISALLOWED_MATCH_1.length; _a++) {
|
|
1643
|
+
var match = DISALLOWED_MATCH_1[_a];
|
|
1618
1644
|
if (attributes["class" /* Class */].indexOf(match) >= 0) {
|
|
1619
1645
|
privacy = 2 /* Text */;
|
|
1620
1646
|
break;
|
|
@@ -1626,12 +1652,12 @@ function getPrivacy(node, data, parentTag, privacy) {
|
|
|
1626
1652
|
if (privacy === 0 /* None */) {
|
|
1627
1653
|
var field = "" /* Empty */;
|
|
1628
1654
|
// Be aggressive in looking up any attribute (id, class, name, etc.) for disallowed names
|
|
1629
|
-
for (var
|
|
1630
|
-
var attribute = _b
|
|
1655
|
+
for (var _b = 0, _c = Object.keys(attributes); _b < _c.length; _b++) {
|
|
1656
|
+
var attribute = _c[_b];
|
|
1631
1657
|
field += attributes[attribute].toLowerCase();
|
|
1632
1658
|
}
|
|
1633
|
-
for (var
|
|
1634
|
-
var name_1 = DISALLOWED_NAMES_1[
|
|
1659
|
+
for (var _d = 0, DISALLOWED_NAMES_1 = DISALLOWED_NAMES; _d < DISALLOWED_NAMES_1.length; _d++) {
|
|
1660
|
+
var name_1 = DISALLOWED_NAMES_1[_d];
|
|
1635
1661
|
if (field.indexOf(name_1) >= 0) {
|
|
1636
1662
|
privacy = 2 /* Text */;
|
|
1637
1663
|
break;
|
|
@@ -1654,11 +1680,6 @@ function getPrivacy(node, data, parentTag, privacy) {
|
|
|
1654
1680
|
if ("data-clarity-unmask" /* UnmaskData */ in attributes) {
|
|
1655
1681
|
privacy = 0 /* None */;
|
|
1656
1682
|
}
|
|
1657
|
-
// If it's a text node belonging to a STYLE or TITLE tag; then reset the privacy setting to ensure we capture the content
|
|
1658
|
-
var cTag = tag === "*T" /* TextTag */ ? parentTag : tag;
|
|
1659
|
-
if (cTag === "STYLE" /* StyleTag */ || cTag === "TITLE" /* TitleTag */) {
|
|
1660
|
-
privacy = 0 /* None */;
|
|
1661
|
-
}
|
|
1662
1683
|
return privacy;
|
|
1663
1684
|
}
|
|
1664
1685
|
function diff(a, b, field) {
|
|
@@ -2569,6 +2590,7 @@ function processNode (node, source) {
|
|
|
2569
2590
|
case Node.DOCUMENT_FRAGMENT_NODE:
|
|
2570
2591
|
var shadowRoot = node;
|
|
2571
2592
|
if (shadowRoot.host) {
|
|
2593
|
+
parse(shadowRoot);
|
|
2572
2594
|
var type = typeof (shadowRoot.constructor);
|
|
2573
2595
|
if (type === "function" /* Function */ && shadowRoot.constructor.toString().indexOf("[native code]" /* NativeCode */) >= 0) {
|
|
2574
2596
|
observe$2(shadowRoot);
|
|
@@ -2802,7 +2824,7 @@ function start$e() {
|
|
|
2802
2824
|
deleteRule = CSSStyleSheet.prototype.deleteRule;
|
|
2803
2825
|
}
|
|
2804
2826
|
if (attachShadow === null) {
|
|
2805
|
-
attachShadow =
|
|
2827
|
+
attachShadow = Element.prototype.attachShadow;
|
|
2806
2828
|
}
|
|
2807
2829
|
// Some popular open source libraries, like styled-components, optimize performance
|
|
2808
2830
|
// by injecting CSS using insertRule API vs. appending text node. A side effect of
|
|
@@ -2820,7 +2842,7 @@ function start$e() {
|
|
|
2820
2842
|
// In case we are unable to add a hook and browser throws an exception,
|
|
2821
2843
|
// reset attachShadow variable and resume processing like before
|
|
2822
2844
|
try {
|
|
2823
|
-
|
|
2845
|
+
Element.prototype.attachShadow = function () {
|
|
2824
2846
|
return schedule(attachShadow.apply(this, arguments));
|
|
2825
2847
|
};
|
|
2826
2848
|
}
|
|
@@ -2834,11 +2856,8 @@ function observe$1(node) {
|
|
|
2834
2856
|
// For this reason, we need to wire up mutations every time we see a new shadow dom.
|
|
2835
2857
|
// Also, wrap it inside a try / catch. In certain browsers (e.g. legacy Edge), observer on shadow dom can throw errors
|
|
2836
2858
|
try {
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
// As a temporary work around, ensuring Clarity can invoke MutationObserver outside of Zone (and use native implementation instead)
|
|
2840
|
-
var api = window["Zone" /* Zone */] && "__symbol__" /* Symbol */ in window["Zone" /* Zone */] ? window["Zone" /* Zone */]["__symbol__" /* Symbol */]("MutationObserver" /* MutationObserver */) : "MutationObserver" /* MutationObserver */;
|
|
2841
|
-
var observer = api in window ? new window[api](measure(handle$1)) : null;
|
|
2859
|
+
var m = api("MutationObserver" /* MutationObserver */);
|
|
2860
|
+
var observer = m in window ? new window[m](measure(handle$1)) : null;
|
|
2842
2861
|
if (observer) {
|
|
2843
2862
|
observer.observe(node, { attributes: true, childList: true, characterData: true, subtree: true });
|
|
2844
2863
|
observers.push(observer);
|
|
@@ -2876,7 +2895,7 @@ function stop$d() {
|
|
|
2876
2895
|
}
|
|
2877
2896
|
// Restoring original attachShadow
|
|
2878
2897
|
if (attachShadow != null) {
|
|
2879
|
-
|
|
2898
|
+
Element.prototype.attachShadow = attachShadow;
|
|
2880
2899
|
attachShadow = null;
|
|
2881
2900
|
}
|
|
2882
2901
|
history$2 = {};
|
|
@@ -2930,6 +2949,9 @@ function process$1() {
|
|
|
2930
2949
|
if (type && target && target.ownerDocument) {
|
|
2931
2950
|
parse(target.ownerDocument);
|
|
2932
2951
|
}
|
|
2952
|
+
if (type && target && target.nodeType == Node.DOCUMENT_FRAGMENT_NODE && target.host) {
|
|
2953
|
+
parse(target);
|
|
2954
|
+
}
|
|
2933
2955
|
switch (type) {
|
|
2934
2956
|
case "attributes" /* Attributes */:
|
|
2935
2957
|
processNode(target, 3 /* Attributes */);
|
|
@@ -3056,12 +3078,15 @@ function schedule(node) {
|
|
|
3056
3078
|
function trigger$1() {
|
|
3057
3079
|
for (var _i = 0, queue_1 = queue$1; _i < queue_1.length; _i++) {
|
|
3058
3080
|
var node = queue_1[_i];
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3081
|
+
// Generate a mutation for this node only if it still exists
|
|
3082
|
+
if (node) {
|
|
3083
|
+
var shadowRoot = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
|
|
3084
|
+
// Skip re-processing shadowRoot if it was already discovered
|
|
3085
|
+
if (shadowRoot && has(node)) {
|
|
3086
|
+
continue;
|
|
3087
|
+
}
|
|
3088
|
+
generate(node, shadowRoot ? "childList" /* ChildList */ : "characterData" /* CharacterData */);
|
|
3063
3089
|
}
|
|
3064
|
-
generate(node, shadowRoot ? "childList" /* ChildList */ : "characterData" /* CharacterData */);
|
|
3065
3090
|
}
|
|
3066
3091
|
queue$1 = [];
|
|
3067
3092
|
}
|
|
@@ -3413,8 +3438,8 @@ function upload(final) {
|
|
|
3413
3438
|
compute$8();
|
|
3414
3439
|
last = final === true;
|
|
3415
3440
|
e = JSON.stringify(envelope(last));
|
|
3416
|
-
a = "["
|
|
3417
|
-
p = sendPlaybackBytes ? "["
|
|
3441
|
+
a = "[".concat(analysis.join(), "]");
|
|
3442
|
+
p = sendPlaybackBytes ? "[".concat(playback.join(), "]") : "" /* Empty */;
|
|
3418
3443
|
encoded = { e: e, a: a, p: p };
|
|
3419
3444
|
payload = stringify(encoded);
|
|
3420
3445
|
if (!last) return [3 /*break*/, 1];
|
|
@@ -3441,7 +3466,7 @@ function upload(final) {
|
|
|
3441
3466
|
});
|
|
3442
3467
|
}
|
|
3443
3468
|
function stringify(encoded) {
|
|
3444
|
-
return encoded.p.length > 0 ? "{\"e\":"
|
|
3469
|
+
return encoded.p.length > 0 ? "{\"e\":".concat(encoded.e, ",\"a\":").concat(encoded.a, ",\"p\":").concat(encoded.p, "}") : "{\"e\":".concat(encoded.e, ",\"a\":").concat(encoded.a, "}");
|
|
3445
3470
|
}
|
|
3446
3471
|
function send(payload, zipped, sequence, beacon) {
|
|
3447
3472
|
if (beacon === void 0) { beacon = false; }
|
|
@@ -3454,10 +3479,14 @@ function send(payload, zipped, sequence, beacon) {
|
|
|
3454
3479
|
// However, we don't want to rely on it for every payload, since we have no ability to retry if the upload failed.
|
|
3455
3480
|
// Also, in case of sendBeacon, we do not have a way to alter HTTP headers and therefore can't send compressed payload
|
|
3456
3481
|
if (beacon && "sendBeacon" in navigator) {
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3482
|
+
try {
|
|
3483
|
+
// Navigator needs to be bound to sendBeacon before it is used to avoid errors in some browsers
|
|
3484
|
+
dispatched = navigator.sendBeacon.bind(navigator)(url, payload);
|
|
3485
|
+
if (dispatched) {
|
|
3486
|
+
done(sequence);
|
|
3487
|
+
}
|
|
3460
3488
|
}
|
|
3489
|
+
catch ( /* do nothing - and we will automatically fallback to XHR below */_a) { /* do nothing - and we will automatically fallback to XHR below */ }
|
|
3461
3490
|
}
|
|
3462
3491
|
// Before initiating XHR upload, we check if the data has already been uploaded using sendBeacon
|
|
3463
3492
|
// There are two cases when dispatched could still be false:
|
|
@@ -3501,7 +3530,7 @@ function check$3(xhr, sequence) {
|
|
|
3501
3530
|
if (xhr && xhr.readyState === 4 /* Done */ && transitData) {
|
|
3502
3531
|
// Attempt send payload again (as configured in settings) if we do not receive a success (2XX) response code back from the server
|
|
3503
3532
|
if ((xhr.status < 200 || xhr.status > 208) && transitData.attempts <= 1 /* RetryLimit */) {
|
|
3504
|
-
// We re-attempt in all cases except when server explicitly rejects our request with 4XX error
|
|
3533
|
+
// We re-attempt in all cases except when server explicitly rejects our request with 4XX error
|
|
3505
3534
|
if (xhr.status >= 400 && xhr.status < 500) {
|
|
3506
3535
|
// In case of a 4XX response from the server, we bail out instead of trying again
|
|
3507
3536
|
trigger(6 /* Server */);
|
|
@@ -3555,7 +3584,7 @@ function done(sequence) {
|
|
|
3555
3584
|
}
|
|
3556
3585
|
function delay() {
|
|
3557
3586
|
// Progressively increase delay as we continue to send more payloads from the client to the server
|
|
3558
|
-
// If we are not uploading data to a server, and instead invoking UploadCallback, in that case keep returning configured value
|
|
3587
|
+
// If we are not uploading data to a server, and instead invoking UploadCallback, in that case keep returning configured value
|
|
3559
3588
|
var gap = config$1.lean === false && discoverBytes > 0 ? 100 /* MinUploadDelay */ : data$1.sequence * config$1.delay;
|
|
3560
3589
|
return typeof config$1.upload === "string" /* String */ ? Math.max(Math.min(gap, 30000 /* MaxUploadDelay */), 100 /* MinUploadDelay */) : config$1.delay;
|
|
3561
3590
|
}
|
|
@@ -3717,7 +3746,7 @@ function log(dimension, value) {
|
|
|
3717
3746
|
// Check valid value before moving ahead
|
|
3718
3747
|
if (value) {
|
|
3719
3748
|
// Ensure received value is casted into a string if it wasn't a string to begin with
|
|
3720
|
-
value = ""
|
|
3749
|
+
value = "".concat(value);
|
|
3721
3750
|
if (!(dimension in data$3)) {
|
|
3722
3751
|
data$3[dimension] = [];
|
|
3723
3752
|
}
|
|
@@ -3802,9 +3831,10 @@ function userAgentData() {
|
|
|
3802
3831
|
"platformVersion",
|
|
3803
3832
|
"uaFullVersion"])
|
|
3804
3833
|
.then(function (ua) {
|
|
3834
|
+
var _a;
|
|
3805
3835
|
log(22 /* Platform */, ua.platform);
|
|
3806
3836
|
log(23 /* PlatformVersion */, ua.platformVersion);
|
|
3807
|
-
ua.brands.forEach(function (brand) {
|
|
3837
|
+
(_a = ua.brands) === null || _a === void 0 ? void 0 : _a.forEach(function (brand) {
|
|
3808
3838
|
log(24 /* Brand */, brand.name + "~" /* Tilde */ + brand.version);
|
|
3809
3839
|
});
|
|
3810
3840
|
log(25 /* Model */, ua.model);
|
|
@@ -3897,7 +3927,7 @@ function session() {
|
|
|
3897
3927
|
output.session = parts[0];
|
|
3898
3928
|
output.count = num(parts[2]) + 1;
|
|
3899
3929
|
output.upgrade = num(parts[3]);
|
|
3900
|
-
output.upload = parts.length >= 6 ? ""
|
|
3930
|
+
output.upload = parts.length >= 6 ? "".concat("https://" /* HTTPS */).concat(parts[5], "/").concat(parts[4]) : "".concat("https://" /* HTTPS */).concat(parts[4]);
|
|
3901
3931
|
}
|
|
3902
3932
|
}
|
|
3903
3933
|
return output;
|
|
@@ -3922,11 +3952,11 @@ function user() {
|
|
|
3922
3952
|
// Check if we either got version-less cookie value or saw multiple copies of the user cookie crumbs
|
|
3923
3953
|
// In both these cases, we go ahead and delete the existing cookie set on current domain
|
|
3924
3954
|
if (parts.length === 1 || count > 1) {
|
|
3925
|
-
var deleted = ""
|
|
3955
|
+
var deleted = "".concat(";" /* Semicolon */).concat("expires=" /* Expires */).concat((new Date(0)).toUTCString()).concat(";path=/" /* Path */);
|
|
3926
3956
|
// First, delete current user cookie which might be set on current sub-domain vs. root domain
|
|
3927
|
-
document.cookie = "_clck" /* CookieKey
|
|
3957
|
+
document.cookie = "".concat("_clck" /* CookieKey */, "=").concat(deleted);
|
|
3928
3958
|
// Second, same thing for current session cookie so it can be re-written later with the root domain
|
|
3929
|
-
document.cookie = "_clsk" /* SessionKey
|
|
3959
|
+
document.cookie = "".concat("_clsk" /* SessionKey */, "=").concat(deleted);
|
|
3930
3960
|
}
|
|
3931
3961
|
// End code for backward compatibility
|
|
3932
3962
|
// Read version information and timestamp from cookie, if available
|
|
@@ -3963,19 +3993,19 @@ function setCookie(key, value, time) {
|
|
|
3963
3993
|
var expiry = new Date();
|
|
3964
3994
|
expiry.setDate(expiry.getDate() + time);
|
|
3965
3995
|
var expires = expiry ? "expires=" /* Expires */ + expiry.toUTCString() : "" /* Empty */;
|
|
3966
|
-
var cookie = key
|
|
3996
|
+
var cookie = "".concat(key, "=").concat(value).concat(";" /* Semicolon */).concat(expires).concat(";path=/" /* Path */);
|
|
3967
3997
|
try {
|
|
3968
3998
|
// Attempt to get the root domain only once and fall back to writing cookie on the current domain.
|
|
3969
3999
|
if (rootDomain === null) {
|
|
3970
4000
|
var hostname = location.hostname ? location.hostname.split("." /* Dot */) : [];
|
|
3971
4001
|
// Walk backwards on a domain and attempt to set a cookie, until successful
|
|
3972
4002
|
for (var i = hostname.length - 1; i >= 0; i--) {
|
|
3973
|
-
rootDomain = "."
|
|
4003
|
+
rootDomain = ".".concat(hostname[i]).concat(rootDomain ? rootDomain : "" /* Empty */);
|
|
3974
4004
|
// We do not wish to attempt writing a cookie on the absolute last part of the domain, e.g. .com or .net.
|
|
3975
4005
|
// So we start attempting after second-last part, e.g. .domain.com (PASS) or .co.uk (FAIL)
|
|
3976
4006
|
if (i < hostname.length - 1) {
|
|
3977
4007
|
// Write the cookie on the current computed top level domain
|
|
3978
|
-
document.cookie = ""
|
|
4008
|
+
document.cookie = "".concat(cookie).concat(";" /* Semicolon */).concat("domain=" /* Domain */).concat(rootDomain);
|
|
3979
4009
|
// Once written, check if the cookie exists and its value matches exactly with what we intended to set
|
|
3980
4010
|
// Checking for exact value match helps us eliminate a corner case where the cookie may already be present with a different value
|
|
3981
4011
|
// If the check is successful, no more action is required and we can return from the function since rootDomain cookie is already set
|
|
@@ -3993,7 +4023,7 @@ function setCookie(key, value, time) {
|
|
|
3993
4023
|
catch (_a) {
|
|
3994
4024
|
rootDomain = "" /* Empty */;
|
|
3995
4025
|
}
|
|
3996
|
-
document.cookie = rootDomain ? ""
|
|
4026
|
+
document.cookie = rootDomain ? "".concat(cookie).concat(";" /* Semicolon */).concat("domain=" /* Domain */).concat(rootDomain) : cookie;
|
|
3997
4027
|
}
|
|
3998
4028
|
}
|
|
3999
4029
|
|
|
@@ -4089,7 +4119,7 @@ function bind(target, event, listener, capture) {
|
|
|
4089
4119
|
// Wrapping following lines inside try / catch to cover edge cases where we might try to access an inaccessible element.
|
|
4090
4120
|
// E.g. Iframe may start off as same-origin but later turn into cross-origin, and the following lines will throw an exception.
|
|
4091
4121
|
try {
|
|
4092
|
-
target
|
|
4122
|
+
target[api("addEventListener" /* AddEventListener */)](event, listener, capture);
|
|
4093
4123
|
bindings.push({ event: event, target: target, listener: listener, capture: capture });
|
|
4094
4124
|
}
|
|
4095
4125
|
catch ( /* do nothing */_a) { /* do nothing */ }
|
|
@@ -4100,7 +4130,7 @@ function reset$1() {
|
|
|
4100
4130
|
var binding = bindings_1[_i];
|
|
4101
4131
|
// Wrapping inside try / catch to avoid situations where the element may be destroyed before we get a chance to unbind
|
|
4102
4132
|
try {
|
|
4103
|
-
binding.target
|
|
4133
|
+
binding.target[api("removeEventListener" /* RemoveEventListener */)](binding.event, binding.listener, binding.capture);
|
|
4104
4134
|
}
|
|
4105
4135
|
catch ( /* do nothing */_a) { /* do nothing */ }
|
|
4106
4136
|
}
|
package/package.json
CHANGED
package/src/core/api.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Constant } from "@clarity-types/core";
|
|
2
|
+
|
|
3
|
+
export default function api(method: string): string {
|
|
4
|
+
// Zone.js, a popular package for Angular, overrides native browser APIs which can lead to inconsistent state for single page applications.
|
|
5
|
+
// Example issue: https://github.com/angular/angular/issues/31712
|
|
6
|
+
// As a work around, we ensuring Clarity access APIs outside of Zone (and use native implementation instead)
|
|
7
|
+
return window[Constant.Zone] && Constant.Symbol in window[Constant.Zone] ? window[Constant.Zone][Constant.Symbol](method) : method;
|
|
8
|
+
}
|
package/src/core/event.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { BrowserEvent } from "@clarity-types/core";
|
|
1
|
+
import { BrowserEvent, Constant } from "@clarity-types/core";
|
|
2
|
+
import api from "./api";
|
|
2
3
|
import measure from "./measure";
|
|
3
4
|
|
|
4
5
|
let bindings: BrowserEvent[] = [];
|
|
@@ -8,7 +9,7 @@ export function bind(target: EventTarget, event: string, listener: EventListener
|
|
|
8
9
|
// Wrapping following lines inside try / catch to cover edge cases where we might try to access an inaccessible element.
|
|
9
10
|
// E.g. Iframe may start off as same-origin but later turn into cross-origin, and the following lines will throw an exception.
|
|
10
11
|
try {
|
|
11
|
-
target.
|
|
12
|
+
target[api(Constant.AddEventListener)](event, listener, capture);
|
|
12
13
|
bindings.push({ event, target, listener, capture });
|
|
13
14
|
} catch { /* do nothing */ }
|
|
14
15
|
}
|
|
@@ -18,7 +19,7 @@ export function reset(): void {
|
|
|
18
19
|
for (let binding of bindings) {
|
|
19
20
|
// Wrapping inside try / catch to avoid situations where the element may be destroyed before we get a chance to unbind
|
|
20
21
|
try {
|
|
21
|
-
binding.target.
|
|
22
|
+
binding.target[api(Constant.RemoveEventListener)](binding.event, binding.listener, binding.capture);
|
|
22
23
|
} catch { /* do nothing */ }
|
|
23
24
|
}
|
|
24
25
|
bindings = [];
|
package/src/core/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let version = "0.6.
|
|
1
|
+
let version = "0.6.32";
|
|
2
2
|
export default version;
|
package/src/data/metadata.ts
CHANGED
|
@@ -74,7 +74,7 @@ export function userAgentData(): void {
|
|
|
74
74
|
.then(ua => {
|
|
75
75
|
dimension.log(Dimension.Platform, ua.platform);
|
|
76
76
|
dimension.log(Dimension.PlatformVersion, ua.platformVersion);
|
|
77
|
-
ua.brands
|
|
77
|
+
ua.brands?.forEach(brand => {
|
|
78
78
|
dimension.log(Dimension.Brand, brand.name + Constant.Tilde + brand.version);
|
|
79
79
|
});
|
|
80
80
|
dimension.log(Dimension.Model, ua.model);
|