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.
@@ -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.28";
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 "" + prefix + trimmed.length.toString(36) + suffix;
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 + "." + timer.cost;
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("" + "#" /* Box */ + str$1(size[0]) + "." + str$1(size[1]));
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 + "=" + scrub(value, key, privacy);
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 + "|" + message : "";
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("[" + match + "]").forEach(function (e) { max(metricId, num$2(e.getAttribute(match), scale, false)); });
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("[" + match + "]").forEach(function (e) { log(dimensionId, str(e.getAttribute(match))); });
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(" + input.position + ")" : "" /* Empty */;
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 = "" + prefix + input.tag + suffix;
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 ? "" + prefix + input.tag + "." + classes.join(".") + suffix : selector;
1410
- selector = id && hasDigits(id) === false ? (shadowStart >= 0 ? prefix.substr(0, shadowEnd) + "#" + id : "#" + id) : selector;
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 ? "" + prefix + input.tag + "." + classes.join(".") + suffix : selector;
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
- config$1.unmask.forEach(function (x) { return root.querySelectorAll(x).forEach(function (e) { return privacyMap.set(e, 0 /* None */); }); }); // Unmasked Elements
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, parentTag, privacy);
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, parentTag, privacy) {
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 _i = 0, DISALLOWED_MATCH_1 = DISALLOWED_MATCH; _i < DISALLOWED_MATCH_1.length; _i++) {
1617
- var match = DISALLOWED_MATCH_1[_i];
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 _a = 0, _b = Object.keys(attributes); _a < _b.length; _a++) {
1630
- var attribute = _b[_a];
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 _c = 0, DISALLOWED_NAMES_1 = DISALLOWED_NAMES; _c < DISALLOWED_NAMES_1.length; _c++) {
1634
- var name_1 = DISALLOWED_NAMES_1[_c];
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 = HTMLElement.prototype.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
- HTMLElement.prototype.attachShadow = function () {
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
- // In an edge case, it's possible to get stuck into infinite Mutation loop within Angular applications
2838
- // This appears to be an issue with Zone.js package, see: https://github.com/angular/angular/issues/31712
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
- HTMLElement.prototype.attachShadow = attachShadow;
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
- var shadowRoot = node && node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
3060
- // Skip re-processing shadowRoot if it was already discovered
3061
- if (shadowRoot && has(node)) {
3062
- continue;
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 = "[" + analysis.join() + "]";
3417
- p = sendPlaybackBytes ? "[" + playback.join() + "]" : "" /* Empty */;
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\":" + encoded.e + ",\"a\":" + encoded.a + ",\"p\":" + encoded.p + "}" : "{\"e\":" + encoded.e + ",\"a\":" + encoded.a + "}";
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
- dispatched = navigator.sendBeacon(url, payload);
3458
- if (dispatched) {
3459
- done(sequence);
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 = "" + 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 ? "" + "https://" /* HTTPS */ + parts[5] + "/" + parts[4] : "" + "https://" /* HTTPS */ + parts[4];
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 = "" + ";" /* Semicolon */ + "expires=" /* Expires */ + (new Date(0)).toUTCString() + ";path=/" /* Path */;
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 */ + "=" + deleted;
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 */ + "=" + deleted;
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 + "=" + value + ";" /* Semicolon */ + expires + ";path=/" /* Path */;
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 = "." + hostname[i] + (rootDomain ? rootDomain : "" /* Empty */);
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 = "" + cookie + ";" /* Semicolon */ + "domain=" /* Domain */ + rootDomain;
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 ? "" + cookie + ";" /* Semicolon */ + "domain=" /* Domain */ + rootDomain : cookie;
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.addEventListener(event, listener, capture);
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.removeEventListener(binding.event, binding.listener, binding.capture);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-js",
3
- "version": "0.6.28",
3
+ "version": "0.6.32",
4
4
  "description": "An analytics library that uses web page interactions to generate aggregated insights",
5
5
  "author": "Microsoft Corp.",
6
6
  "license": "MIT",
@@ -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.addEventListener(event, listener, capture);
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.removeEventListener(binding.event, binding.listener, binding.capture);
22
+ binding.target[api(Constant.RemoveEventListener)](binding.event, binding.listener, binding.capture);
22
23
  } catch { /* do nothing */ }
23
24
  }
24
25
  bindings = [];
@@ -1,2 +1,2 @@
1
- let version = "0.6.28";
1
+ let version = "0.6.32";
2
2
  export default version;
@@ -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.forEach(brand => {
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);