clarity-js 0.6.37 → 0.6.40

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.
@@ -111,7 +111,7 @@ function stop$B() {
111
111
  startTime = 0;
112
112
  }
113
113
 
114
- var version$1 = "0.6.37";
114
+ var version$1 = "0.6.40";
115
115
 
116
116
  // tslint:disable: no-bitwise
117
117
  function hash (input) {
@@ -138,9 +138,9 @@ var buffer = null;
138
138
  var update$2 = false;
139
139
  function start$E() {
140
140
  update$2 = false;
141
- reset$o();
141
+ reset$p();
142
142
  }
143
- function reset$o() {
143
+ function reset$p() {
144
144
  // Baseline state holds the previous values - if it is updated in the current payload,
145
145
  // reset the state to current value after sending the previous state
146
146
  if (update$2) {
@@ -208,14 +208,14 @@ function compute$c() {
208
208
  }
209
209
  }
210
210
  function stop$A() {
211
- reset$o();
211
+ reset$p();
212
212
  }
213
213
 
214
214
  var baseline = /*#__PURE__*/Object.freeze({
215
215
  __proto__: null,
216
216
  get state () { return state$9; },
217
217
  start: start$E,
218
- reset: reset$o,
218
+ reset: reset$p,
219
219
  track: track$7,
220
220
  activity: activity,
221
221
  visibility: visibility,
@@ -285,7 +285,7 @@ function max(metric, value) {
285
285
  function compute$b() {
286
286
  encode$1(0 /* Event.Metric */);
287
287
  }
288
- function reset$n() {
288
+ function reset$o() {
289
289
  updates$3 = {};
290
290
  }
291
291
 
@@ -304,7 +304,7 @@ function start$C() {
304
304
  interval = 60000 /* Setting.PingInterval */;
305
305
  last = 0;
306
306
  }
307
- function reset$m() {
307
+ function reset$n() {
308
308
  if (timeout$6) {
309
309
  clearTimeout(timeout$6);
310
310
  }
@@ -332,7 +332,7 @@ var ping$1 = /*#__PURE__*/Object.freeze({
332
332
  __proto__: null,
333
333
  get data () { return data$h; },
334
334
  start: start$C,
335
- reset: reset$m,
335
+ reset: reset$n,
336
336
  stop: stop$y
337
337
  });
338
338
 
@@ -363,7 +363,7 @@ function track$6(event, time) {
363
363
  function compute$a() {
364
364
  encode$1(36 /* Event.Summary */);
365
365
  }
366
- function reset$l() {
366
+ function reset$m() {
367
367
  data$g = {};
368
368
  }
369
369
 
@@ -374,7 +374,7 @@ var summary = /*#__PURE__*/Object.freeze({
374
374
  stop: stop$x,
375
375
  track: track$6,
376
376
  compute: compute$a,
377
- reset: reset$l
377
+ reset: reset$m
378
378
  });
379
379
 
380
380
  var data$f = null;
@@ -416,7 +416,7 @@ var upgrade$1 = /*#__PURE__*/Object.freeze({
416
416
 
417
417
  var data$e = null;
418
418
  function start$z() {
419
- reset$k();
419
+ reset$l();
420
420
  }
421
421
  function set(variable, value) {
422
422
  var values = typeof value === "string" /* Constant.String */ ? [value] : value;
@@ -447,11 +447,11 @@ function log$2(variable, value) {
447
447
  function compute$9() {
448
448
  encode$1(34 /* Event.Variable */);
449
449
  }
450
- function reset$k() {
450
+ function reset$l() {
451
451
  data$e = {};
452
452
  }
453
453
  function stop$v() {
454
- reset$k();
454
+ reset$l();
455
455
  }
456
456
 
457
457
  var variable = /*#__PURE__*/Object.freeze({
@@ -461,7 +461,7 @@ var variable = /*#__PURE__*/Object.freeze({
461
461
  set: set,
462
462
  identify: identify,
463
463
  compute: compute$9,
464
- reset: reset$k,
464
+ reset: reset$l,
465
465
  stop: stop$v
466
466
  });
467
467
 
@@ -600,6 +600,11 @@ function compute$8() {
600
600
  compute$4();
601
601
  }
602
602
 
603
+ var catchallRegex = /\S/gi;
604
+ var unicodeRegex = true;
605
+ var digitRegex = null;
606
+ var letterRegex = null;
607
+ var currencyRegex = null;
603
608
  function scrub (value, hint, privacy, mangle) {
604
609
  if (mangle === void 0) { mangle = false; }
605
610
  if (value) {
@@ -611,9 +616,9 @@ function scrub (value, hint, privacy, mangle) {
611
616
  case "*T" /* Layout.Constant.TextTag */:
612
617
  case "value":
613
618
  case "placeholder":
614
- return redact(value);
619
+ case "click":
615
620
  case "input":
616
- return mangleToken(value);
621
+ return redact(value);
617
622
  }
618
623
  return value;
619
624
  case 2 /* Privacy.Text */:
@@ -650,7 +655,7 @@ function mangleText(value) {
650
655
  return value;
651
656
  }
652
657
  function mask(value) {
653
- return value.replace(/\S/gi, "\u2022" /* Data.Constant.Mask */);
658
+ return value.replace(catchallRegex, "\u2022" /* Data.Constant.Mask */);
654
659
  }
655
660
  function mangleToken(value) {
656
661
  var length = ((Math.floor(value.length / 5 /* Data.Setting.WordLength */) + 1) * 5 /* Data.Setting.WordLength */);
@@ -662,10 +667,23 @@ function mangleToken(value) {
662
667
  }
663
668
  function redact(value) {
664
669
  var spaceIndex = -1;
670
+ var gap = 0;
665
671
  var hasDigit = false;
666
672
  var hasEmail = false;
667
673
  var hasWhitespace = false;
668
674
  var array = null;
675
+ // Initialize unicode regex, if supported by the browser
676
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
677
+ if (unicodeRegex && digitRegex === null) {
678
+ try {
679
+ digitRegex = new RegExp("\\p{N}", "gu");
680
+ letterRegex = new RegExp("\\p{L}", "gu");
681
+ currencyRegex = new RegExp("\\p{Sc}", "gu");
682
+ }
683
+ catch (_a) {
684
+ unicodeRegex = false;
685
+ }
686
+ }
669
687
  for (var i = 0; i < value.length; i++) {
670
688
  var c = value.charCodeAt(i);
671
689
  hasDigit = hasDigit || (c >= 48 /* Data.Character.Zero */ && c <= 57 /* Data.Character.Nine */); // Check for digits in the current word
@@ -678,7 +696,19 @@ function redact(value) {
678
696
  if (array === null) {
679
697
  array = value.split("" /* Data.Constant.Empty */);
680
698
  }
681
- mutate(array, spaceIndex, hasWhitespace ? i : i + 1);
699
+ // Work on a token at a time so we don't have to apply regex to a larger string
700
+ var token = value.substring(spaceIndex + 1, hasWhitespace ? i : i + 1);
701
+ // Check if unicode regex is supported, otherwise fallback to calling mask function on this token
702
+ if (unicodeRegex && currencyRegex !== null) {
703
+ // Do not redact information if the token contains a currency symbol
704
+ token = token.match(currencyRegex) ? token : token.replace(letterRegex, "\u2022" /* Data.Constant.Letter */).replace(digitRegex, "\u2022" /* Data.Constant.Digit */);
705
+ }
706
+ else {
707
+ token = mask(token);
708
+ }
709
+ // Merge token back into array at the right place
710
+ array.splice(spaceIndex + 1 - gap, token.length, token);
711
+ gap += token.length - 1;
682
712
  }
683
713
  // Reset digit and email flags after every word boundary, except the beginning of string
684
714
  if (hasWhitespace) {
@@ -689,11 +719,6 @@ function redact(value) {
689
719
  }
690
720
  }
691
721
  return array ? array.join("" /* Data.Constant.Empty */) : value;
692
- }
693
- function mutate(array, start, end) {
694
- for (var i = start + 1; i < end; i++) {
695
- array[i] = "\u2022" /* Data.Constant.Mask */;
696
- }
697
722
  }
698
723
 
699
724
  var history$5 = [];
@@ -714,12 +739,15 @@ function check$4(id, target, input) {
714
739
  }
715
740
  }
716
741
 
717
- var TAGS = ["DIV", "TR", "P", "LI", "UL", "A", "BUTTON"];
718
- function selector (input, beta) {
719
- if (beta === void 0) { beta = false; }
742
+ var excludeClassNames = "load,active,fixed,visible,focus,show,collaps,animat" /* Constant.ExcludeClassNames */.split("," /* Constant.Comma */);
743
+ var selectorMap = {};
744
+ function reset$k() {
745
+ selectorMap = {};
746
+ }
747
+ function get$1(input, type) {
720
748
  var a = input.attributes;
721
- var prefix = input.prefix ? input.prefix[beta ? 1 /* Selector.Beta */ : 0 /* Selector.Stable */] : null;
722
- var suffix = beta || ((a && !("class" /* Constant.Class */ in a)) || TAGS.indexOf(input.tag) >= 0) ? ":nth-of-type(".concat(input.position, ")") : "" /* Constant.Empty */;
749
+ var prefix = input.prefix ? input.prefix[type] : null;
750
+ var suffix = type === 0 /* Selector.Alpha */ ? "".concat("~" /* Constant.Tilde */).concat(input.position - 1) : ":nth-of-type(".concat(input.position, ")");
723
751
  switch (input.tag) {
724
752
  case "STYLE":
725
753
  case "TITLE":
@@ -734,23 +762,33 @@ function selector (input, beta) {
734
762
  if (prefix === null) {
735
763
  return "" /* Constant.Empty */;
736
764
  }
737
- prefix = "".concat(prefix, ">");
765
+ prefix = "".concat(prefix).concat(">" /* Constant.Separator */);
738
766
  input.tag = input.tag.indexOf("svg:" /* Constant.SvgPrefix */) === 0 ? input.tag.substr("svg:" /* Constant.SvgPrefix */.length) : input.tag;
739
767
  var selector = "".concat(prefix).concat(input.tag).concat(suffix);
740
- var classes = "class" /* Constant.Class */ in a && a["class" /* Constant.Class */].length > 0 ? a["class" /* Constant.Class */].trim().split(/\s+/) : null;
741
- if (beta) {
742
- // In beta mode, update selector to use "id" field when available. There are two exceptions:
743
- // (1) if "id" appears to be an auto generated string token, e.g. guid or a random id containing digits
744
- // (2) if "id" appears inside a shadow DOM, in which case we continue to prefix up to shadow DOM to prevent conflicts
745
- var id = "id" /* Constant.Id */ in a && a["id" /* Constant.Id */].length > 0 ? a["id" /* Constant.Id */] : null;
746
- classes = input.tag !== "BODY" /* Constant.BodyTag */ && classes ? classes.filter(function (c) { return !hasDigits(c); }) : [];
747
- selector = classes.length > 0 ? "".concat(prefix).concat(input.tag, ".").concat(classes.join(".")).concat(suffix) : selector;
748
- selector = id && hasDigits(id) === false ? "".concat(getDomPrefix(prefix), "#").concat(id) : selector;
749
- }
750
- else {
751
- // Otherwise, fallback to stable mode, where we include class names as part of the selector
752
- selector = classes ? "".concat(prefix).concat(input.tag, ".").concat(classes.join(".")).concat(suffix) : selector;
768
+ var id = "id" /* Constant.Id */ in a && a["id" /* Constant.Id */].length > 0 ? a["id" /* Constant.Id */] : null;
769
+ 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;
770
+ if (classes && classes.length > 0) {
771
+ if (type === 0 /* Selector.Alpha */) {
772
+ // In Alpha mode, update selector to use class names, with relative positioning within the parent id container.
773
+ // If the node has valid class name(s) then drop relative positioning within the parent path to keep things simple.
774
+ var key = "".concat(getDomPath(prefix)).concat(input.tag).concat("." /* Constant.Dot */).concat(classes);
775
+ if (!(key in selectorMap)) {
776
+ selectorMap[key] = [];
777
+ }
778
+ if (selectorMap[key].indexOf(input.id) < 0) {
779
+ selectorMap[key].push(input.id);
780
+ }
781
+ selector = "".concat(key).concat("~" /* Constant.Tilde */).concat(selectorMap[key].indexOf(input.id));
782
+ }
783
+ else {
784
+ // In Beta mode, we continue to look at query selectors in context of the full page
785
+ selector = "".concat(prefix).concat(input.tag, ".").concat(classes).concat(suffix);
786
+ }
753
787
  }
788
+ // Update selector to use "id" field when available. There are two exceptions:
789
+ // (1) if "id" appears to be an auto generated string token, e.g. guid or a random id containing digits
790
+ // (2) if "id" appears inside a shadow DOM, in which case we continue to prefix up to shadow DOM to prevent conflicts
791
+ selector = id && filter(id) ? "".concat(getDomPrefix(prefix)).concat("#" /* Constant.Hash */).concat(id) : selector;
754
792
  return selector;
755
793
  }
756
794
  }
@@ -759,22 +797,42 @@ function getDomPrefix(prefix) {
759
797
  var iframeDomStart = prefix.lastIndexOf("".concat("iframe:" /* Constant.IFramePrefix */).concat("HTML" /* Constant.HTML */));
760
798
  var domStart = Math.max(shadowDomStart, iframeDomStart);
761
799
  if (domStart < 0) {
762
- return "";
800
+ return "" /* Constant.Empty */;
801
+ }
802
+ return prefix.substring(0, prefix.indexOf(">" /* Constant.Separator */, domStart) + 1);
803
+ }
804
+ function getDomPath(input) {
805
+ var parts = input.split(">" /* Constant.Separator */);
806
+ for (var i = 0; i < parts.length; i++) {
807
+ var tIndex = parts[i].indexOf("~" /* Constant.Tilde */);
808
+ var dIndex = parts[i].indexOf("." /* Constant.Dot */);
809
+ parts[i] = parts[i].substring(0, dIndex > 0 ? dIndex : (tIndex > 0 ? tIndex : parts[i].length));
763
810
  }
764
- var domEnd = prefix.indexOf(">", domStart) + 1;
765
- return prefix.substr(0, domEnd);
811
+ return parts.join(">" /* Constant.Separator */);
766
812
  }
767
- // Check if the given input string has digits or not
768
- function hasDigits(value) {
813
+ // Check if the given input string has digits or excluded class names
814
+ function filter(value) {
815
+ if (!value) {
816
+ return false;
817
+ } // Do not process empty strings
818
+ if (excludeClassNames.some(function (x) { return value.toLowerCase().indexOf(x) >= 0; })) {
819
+ return false;
820
+ }
769
821
  for (var i = 0; i < value.length; i++) {
770
822
  var c = value.charCodeAt(i);
771
823
  if (c >= 48 /* Character.Zero */ && c <= 57 /* Character.Nine */) {
772
- return true;
824
+ return false;
773
825
  }
774
826
  }
775
- return false;
827
+ return true;
776
828
  }
777
829
 
830
+ var selector = /*#__PURE__*/Object.freeze({
831
+ __proto__: null,
832
+ reset: reset$k,
833
+ get: get$1
834
+ });
835
+
778
836
  // Track the start time to be able to compute duration at the end of the task
779
837
  var idleTimeout = 5000;
780
838
  var tracker = {};
@@ -1094,7 +1152,7 @@ function encode$4 (type, timer, ts) {
1094
1152
  }
1095
1153
  tokens.push(suspend ? "*M" /* Constant.SuspendMutationTag */ : data[key]);
1096
1154
  if (box && box.length === 2) {
1097
- tokens.push("".concat("#" /* Constant.Box */).concat(str$1(box[0]), ".").concat(str$1(box[1])));
1155
+ tokens.push("".concat("#" /* Constant.Hash */).concat(str$1(box[0]), ".").concat(str$1(box[1])));
1098
1156
  }
1099
1157
  break;
1100
1158
  case "attributes":
@@ -1385,7 +1443,7 @@ function observe$8(root) {
1385
1443
  bind(root, "mousedown", mouse.bind(this, 13 /* Event.MouseDown */, root), true);
1386
1444
  bind(root, "mouseup", mouse.bind(this, 14 /* Event.MouseUp */, root), true);
1387
1445
  bind(root, "mousemove", mouse.bind(this, 12 /* Event.MouseMove */, root), true);
1388
- bind(root, "mousewheel", mouse.bind(this, 15 /* Event.MouseWheel */, root), true);
1446
+ bind(root, "wheel", mouse.bind(this, 15 /* Event.MouseWheel */, root), true);
1389
1447
  bind(root, "dblclick", mouse.bind(this, 16 /* Event.DoubleClick */, root), true);
1390
1448
  bind(root, "touchstart", touch.bind(this, 17 /* Event.TouchStart */, root), true);
1391
1449
  bind(root, "touchend", touch.bind(this, 18 /* Event.TouchEnd */, root), true);
@@ -2319,6 +2377,7 @@ var override = [];
2319
2377
  var unmask = [];
2320
2378
  var updatedFragments = {};
2321
2379
  var maskText = [];
2380
+ var maskInput = [];
2322
2381
  var maskDisable = [];
2323
2382
  // The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced
2324
2383
  var idMap = null; // Maps node => id.
@@ -2340,12 +2399,14 @@ function reset$7() {
2340
2399
  hashMap = {};
2341
2400
  override = [];
2342
2401
  unmask = [];
2343
- maskText = "password,secret,pass,social,ssn,name,code,dob,cell,mob,contact,hidden,account,cvv,ccv,email,tel,phone,address,addr,card,zip" /* Mask.Text */.split("," /* Constant.Comma */);
2402
+ maskText = "address,password,contact" /* Mask.Text */.split("," /* Constant.Comma */);
2403
+ maskInput = "password,secret,pass,social,ssn,name,code,dob,cell,mob,contact,hidden,account,cvv,ccv,email,tel,phone,address,addr,card,zip" /* Mask.Input */.split("," /* Constant.Comma */);
2344
2404
  maskDisable = "radio,checkbox,range,button,reset,submit" /* Mask.Disable */.split("," /* Constant.Comma */);
2345
2405
  idMap = new WeakMap();
2346
2406
  iframeMap = new WeakMap();
2347
2407
  privacyMap = new WeakMap();
2348
2408
  fraudMap = new WeakMap();
2409
+ reset$k();
2349
2410
  }
2350
2411
  // We parse new root nodes for any regions or masked nodes in the beginning (document) and
2351
2412
  // later whenever there are new additions or modifications to DOM (mutations)
@@ -2522,36 +2583,36 @@ function privacy(node, value, parent) {
2522
2583
  metadata.privacy = 2 /* Privacy.Text */;
2523
2584
  break;
2524
2585
  case tag === "*T" /* Constant.TextTag */:
2525
- // If it's a text node belonging to a STYLE or TITLE tag or one of SCRUB_EXCEPTIONS, then capture content
2586
+ // If it's a text node belonging to a STYLE or TITLE tag or one of scrub exceptions, then capture content
2526
2587
  var pTag = parent && parent.data ? parent.data.tag : "" /* Constant.Empty */;
2527
- var pSelector_1 = parent && parent.selector ? parent.selector[0 /* Selector.Stable */] : "" /* Constant.Empty */;
2528
- metadata.privacy = pTag === "STYLE" /* Constant.StyleTag */ || pTag === "TITLE" /* Constant.TitleTag */ || override.some(function (x) { return pSelector_1.indexOf(x) >= 0; }) ? 0 /* Privacy.None */ : current;
2588
+ var pSelector_1 = parent && parent.selector ? parent.selector[1 /* Selector.Default */] : "" /* Constant.Empty */;
2589
+ var tags = ["STYLE" /* Constant.StyleTag */, "TITLE" /* Constant.TitleTag */, "svg:style" /* Constant.SvgStyle */];
2590
+ metadata.privacy = tags.includes(pTag) || override.some(function (x) { return pSelector_1.indexOf(x) >= 0; }) ? 0 /* Privacy.None */ : current;
2529
2591
  break;
2530
2592
  case "type" /* Constant.Type */ in attributes:
2531
2593
  // If this node has an explicit type assigned to it, go through masking rules to determine right privacy setting
2532
- metadata.privacy = inspect(attributes["type" /* Constant.Type */], metadata);
2594
+ metadata.privacy = inspect(attributes["type" /* Constant.Type */], maskInput, metadata);
2533
2595
  break;
2534
2596
  case tag === "INPUT" /* Constant.InputTag */ && current === 0 /* Privacy.None */:
2535
2597
  // If even default privacy setting is to not mask, we still scan through input fields for any sensitive information
2536
2598
  var field_1 = "" /* Constant.Empty */;
2537
2599
  Object.keys(attributes).forEach(function (x) { return field_1 += attributes[x].toLowerCase(); });
2538
- metadata.privacy = inspect(field_1, metadata);
2600
+ metadata.privacy = inspect(field_1, maskInput, metadata);
2539
2601
  break;
2540
2602
  case current === 1 /* Privacy.Sensitive */ && tag === "INPUT" /* Constant.InputTag */:
2603
+ // Look through class names to aggressively mask content
2604
+ metadata.privacy = inspect(attributes["class" /* Constant.Class */], maskText, metadata);
2541
2605
  // If it's a button or an input option, make an exception to disable masking
2542
2606
  metadata.privacy = maskDisable.indexOf(attributes["type" /* Constant.Type */]) >= 0 ? 0 /* Privacy.None */ : current;
2543
2607
  break;
2544
2608
  case current === 1 /* Privacy.Sensitive */:
2545
2609
  // In a mode where we mask sensitive information by default, look through class names to aggressively mask content
2546
- metadata.privacy = inspect(attributes["class" /* Constant.Class */], metadata);
2547
- break;
2548
- default:
2549
- metadata.privacy = parent ? parent.metadata.privacy : metadata.privacy;
2610
+ metadata.privacy = inspect(attributes["class" /* Constant.Class */], maskText, metadata);
2550
2611
  break;
2551
2612
  }
2552
2613
  }
2553
- function inspect(input, metadata) {
2554
- if (input && maskText.some(function (x) { return input.indexOf(x) >= 0; })) {
2614
+ function inspect(input, lookup, metadata) {
2615
+ if (input && lookup.some(function (x) { return input.indexOf(x) >= 0; })) {
2555
2616
  return 2 /* Privacy.Text */;
2556
2617
  }
2557
2618
  return metadata.privacy;
@@ -2589,10 +2650,11 @@ function updateSelector(value) {
2589
2650
  var prefix = parent ? parent.selector : null;
2590
2651
  var d = value.data;
2591
2652
  var p = position(parent, value);
2592
- var s = { tag: d.tag, prefix: prefix, position: p, attributes: d.attributes };
2593
- value.selector = [selector(s), selector(s, true)];
2653
+ var s = { id: value.id, tag: d.tag, prefix: prefix, position: p, attributes: d.attributes };
2654
+ value.selector = [get$1(s, 0 /* Selector.Alpha */), get$1(s, 1 /* Selector.Beta */)];
2594
2655
  value.hash = value.selector.map(function (x) { return x ? hash(x) : null; });
2595
2656
  value.hash.forEach(function (h) { return hashMap[h] = value.id; });
2657
+ // Match fragment configuration against both alpha and beta hash
2596
2658
  if (value.hash.some(function (h) { return fragments.indexOf(h) !== -1; })) {
2597
2659
  value.fragment = value.id;
2598
2660
  }
@@ -3136,7 +3198,7 @@ function queue(tokens, transmit) {
3136
3198
  // We enrich the data going out with the existing upload. In these cases, call to upload comes with 'transmit' set to false.
3137
3199
  if (transmit && timeout === null) {
3138
3200
  if (type !== 25 /* Event.Ping */) {
3139
- reset$m();
3201
+ reset$n();
3140
3202
  }
3141
3203
  timeout = setTimeout(upload, gap);
3142
3204
  queuedTime = now;
@@ -3591,7 +3653,7 @@ function encode$1 (event) {
3591
3653
  tokens.push(b.data.activityTime);
3592
3654
  queue(tokens, false);
3593
3655
  }
3594
- reset$o();
3656
+ reset$p();
3595
3657
  break;
3596
3658
  case 25 /* Event.Ping */:
3597
3659
  tokens.push(data$h.gap);
@@ -3624,7 +3686,7 @@ function encode$1 (event) {
3624
3686
  tokens.push(v);
3625
3687
  tokens.push(data$e[v]);
3626
3688
  }
3627
- reset$k();
3689
+ reset$l();
3628
3690
  queue(tokens, false);
3629
3691
  }
3630
3692
  break;
@@ -3639,7 +3701,7 @@ function encode$1 (event) {
3639
3701
  // However, for data over the wire, we round it off to milliseconds precision.
3640
3702
  tokens.push(Math.round(updates$3[m]));
3641
3703
  }
3642
- reset$n();
3704
+ reset$o();
3643
3705
  queue(tokens, false);
3644
3706
  }
3645
3707
  break;
@@ -3665,7 +3727,7 @@ function encode$1 (event) {
3665
3727
  tokens.push(key);
3666
3728
  tokens.push([].concat.apply([], data$g[e]));
3667
3729
  }
3668
- reset$l();
3730
+ reset$m();
3669
3731
  queue(tokens, false);
3670
3732
  }
3671
3733
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-js",
3
- "version": "0.6.37",
3
+ "version": "0.6.40",
4
4
  "description": "An analytics library that uses web page interactions to generate aggregated insights",
5
5
  "author": "Microsoft Corp.",
6
6
  "license": "MIT",
package/src/core/scrub.ts CHANGED
@@ -2,6 +2,12 @@ import { Privacy } from "@clarity-types/core";
2
2
  import * as Data from "@clarity-types/data";
3
3
  import * as Layout from "@clarity-types/layout";
4
4
 
5
+ const catchallRegex = /\S/gi;
6
+ let unicodeRegex = true;
7
+ let digitRegex = null;
8
+ let letterRegex = null;
9
+ let currencyRegex = null;
10
+
5
11
  export default function(value: string, hint: string, privacy: Privacy, mangle: boolean = false): string {
6
12
  if (value) {
7
13
  switch (privacy) {
@@ -12,9 +18,9 @@ export default function(value: string, hint: string, privacy: Privacy, mangle: b
12
18
  case Layout.Constant.TextTag:
13
19
  case "value":
14
20
  case "placeholder":
15
- return redact(value);
21
+ case "click":
16
22
  case "input":
17
- return mangleToken(value);
23
+ return redact(value);
18
24
  }
19
25
  return value;
20
26
  case Privacy.Text:
@@ -53,7 +59,7 @@ function mangleText(value: string): string {
53
59
  }
54
60
 
55
61
  function mask(value: string): string {
56
- return value.replace(/\S/gi, Data.Constant.Mask);
62
+ return value.replace(catchallRegex, Data.Constant.Mask);
57
63
  }
58
64
 
59
65
  function mangleToken(value: string): string {
@@ -67,10 +73,22 @@ function mangleToken(value: string): string {
67
73
 
68
74
  function redact(value: string): string {
69
75
  let spaceIndex = -1;
76
+ let gap = 0;
70
77
  let hasDigit = false;
71
78
  let hasEmail = false;
72
79
  let hasWhitespace = false;
73
80
  let array = null;
81
+
82
+ // Initialize unicode regex, if supported by the browser
83
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
84
+ if (unicodeRegex && digitRegex === null) {
85
+ try {
86
+ digitRegex = new RegExp("\\p{N}", "gu");
87
+ letterRegex = new RegExp("\\p{L}", "gu");
88
+ currencyRegex = new RegExp("\\p{Sc}", "gu");
89
+ } catch { unicodeRegex = false; }
90
+ }
91
+
74
92
  for (let i = 0; i < value.length; i++) {
75
93
  let c = value.charCodeAt(i);
76
94
  hasDigit = hasDigit || (c >= Data.Character.Zero && c <= Data.Character.Nine); // Check for digits in the current word
@@ -82,7 +100,18 @@ function redact(value: string): string {
82
100
  // Performance optimization: Lazy load string -> array conversion only when required
83
101
  if (hasDigit || hasEmail) {
84
102
  if (array === null) { array = value.split(Data.Constant.Empty); }
85
- mutate(array, spaceIndex, hasWhitespace ? i : i + 1);
103
+ // Work on a token at a time so we don't have to apply regex to a larger string
104
+ let token = value.substring(spaceIndex + 1, hasWhitespace ? i : i + 1);
105
+ // Check if unicode regex is supported, otherwise fallback to calling mask function on this token
106
+ if (unicodeRegex && currencyRegex !== null) {
107
+ // Do not redact information if the token contains a currency symbol
108
+ token = token.match(currencyRegex) ? token : token.replace(letterRegex, Data.Constant.Letter).replace(digitRegex, Data.Constant.Digit);
109
+ } else {
110
+ token = mask(token);
111
+ }
112
+ // Merge token back into array at the right place
113
+ array.splice(spaceIndex + 1 - gap, token.length, token);
114
+ gap += token.length - 1;
86
115
  }
87
116
  // Reset digit and email flags after every word boundary, except the beginning of string
88
117
  if (hasWhitespace) {
@@ -94,9 +123,3 @@ function redact(value: string): string {
94
123
  }
95
124
  return array ? array.join(Data.Constant.Empty) : value;
96
125
  }
97
-
98
- function mutate(array: string[], start: number, end: number): void {
99
- for (let i = start + 1; i < end; i++) {
100
- array[i] = Data.Constant.Mask;
101
- }
102
- }
@@ -1,2 +1,2 @@
1
- let version = "0.6.37";
1
+ let version = "0.6.40";
2
2
  export default version;
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 }
@@ -20,7 +20,7 @@ export function observe(root: Node): void {
20
20
  bind(root, "mousedown", mouse.bind(this, Event.MouseDown, root), true);
21
21
  bind(root, "mouseup", mouse.bind(this, Event.MouseUp, root), true);
22
22
  bind(root, "mousemove", mouse.bind(this, Event.MouseMove, root), true);
23
- bind(root, "mousewheel", mouse.bind(this, Event.MouseWheel, root), true);
23
+ bind(root, "wheel", mouse.bind(this, Event.MouseWheel, root), true);
24
24
  bind(root, "dblclick", mouse.bind(this, Event.DoubleClick, root), true);
25
25
  bind(root, "touchstart", touch.bind(this, Event.TouchStart, root), true);
26
26
  bind(root, "touchend", touch.bind(this, Event.TouchEnd, root), true);