clarity-js 0.7.5 → 0.7.6

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.
@@ -143,11 +143,11 @@ function time(event) {
143
143
  var ts = event && event.timeStamp > 0 ? event.timeStamp : performance.now();
144
144
  return Math.max(Math.round(ts - startTime), 0);
145
145
  }
146
- function stop$C() {
146
+ function stop$D() {
147
147
  startTime = 0;
148
148
  }
149
149
 
150
- var version$1 = "0.7.5";
150
+ var version$1 = "0.7.6";
151
151
 
152
152
  // tslint:disable: no-bitwise
153
153
  function hash (input, precision) {
@@ -197,6 +197,7 @@ function text$1(value, hint, privacy, mangle) {
197
197
  case 3 /* Privacy.TextImage */:
198
198
  switch (hint) {
199
199
  case "*T" /* Layout.Constant.TextTag */:
200
+ case "data-" /* Layout.Constant.DataAttribute */:
200
201
  return mangle ? mangleText(value) : mask(value);
201
202
  case "src":
202
203
  case "srcset":
@@ -215,6 +216,7 @@ function text$1(value, hint, privacy, mangle) {
215
216
  case 4 /* Privacy.Exclude */:
216
217
  switch (hint) {
217
218
  case "*T" /* Layout.Constant.TextTag */:
219
+ case "data-" /* Layout.Constant.DataAttribute */:
218
220
  return mangle ? mangleText(value) : mask(value);
219
221
  case "value":
220
222
  case "input":
@@ -224,6 +226,25 @@ function text$1(value, hint, privacy, mangle) {
224
226
  case "checksum":
225
227
  return "" /* Data.Constant.Empty */;
226
228
  }
229
+ break;
230
+ case 5 /* Privacy.Snapshot */:
231
+ switch (hint) {
232
+ case "*T" /* Layout.Constant.TextTag */:
233
+ case "data-" /* Layout.Constant.DataAttribute */:
234
+ return scrub(value);
235
+ case "value":
236
+ case "input":
237
+ case "click":
238
+ case "change":
239
+ return Array(5 /* Data.Setting.WordLength */).join("\u2022" /* Data.Constant.Mask */);
240
+ case "checksum":
241
+ case "src":
242
+ case "srcset":
243
+ case "alt":
244
+ case "title":
245
+ return "" /* Data.Constant.Empty */;
246
+ }
247
+ break;
227
248
  }
228
249
  }
229
250
  return value;
@@ -251,6 +272,10 @@ function mangleText(value) {
251
272
  function mask(value) {
252
273
  return value.replace(catchallRegex, "\u2022" /* Data.Constant.Mask */);
253
274
  }
275
+ function scrub(value) {
276
+ regex(); // Initialize regular expressions
277
+ return value.replace(letterRegex, "\u25AA" /* Data.Constant.Letter */).replace(digitRegex, "\u25AB" /* Data.Constant.Digit */);
278
+ }
254
279
  function mangleToken(value) {
255
280
  var length = ((Math.floor(value.length / 5 /* Data.Setting.WordLength */) + 1) * 5 /* Data.Setting.WordLength */);
256
281
  var output = "" /* Layout.Constant.Empty */;
@@ -259,13 +284,7 @@ function mangleToken(value) {
259
284
  }
260
285
  return output;
261
286
  }
262
- function redact(value) {
263
- var spaceIndex = -1;
264
- var gap = 0;
265
- var hasDigit = false;
266
- var hasEmail = false;
267
- var hasWhitespace = false;
268
- var array = null;
287
+ function regex() {
269
288
  // Initialize unicode regex, if supported by the browser
270
289
  // Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
271
290
  if (unicodeRegex && digitRegex === null) {
@@ -278,6 +297,15 @@ function redact(value) {
278
297
  unicodeRegex = false;
279
298
  }
280
299
  }
300
+ }
301
+ function redact(value) {
302
+ var spaceIndex = -1;
303
+ var gap = 0;
304
+ var hasDigit = false;
305
+ var hasEmail = false;
306
+ var hasWhitespace = false;
307
+ var array = null;
308
+ regex(); // Initialize regular expressions
281
309
  for (var i = 0; i < value.length; i++) {
282
310
  var c = value.charCodeAt(i);
283
311
  hasDigit = hasDigit || (c >= 48 /* Data.Character.Zero */ && c <= 57 /* Data.Character.Nine */); // Check for digits in the current word
@@ -389,7 +417,7 @@ function compute$c() {
389
417
  encode$1(4 /* Event.Baseline */);
390
418
  }
391
419
  }
392
- function stop$B() {
420
+ function stop$C() {
393
421
  reset$q();
394
422
  }
395
423
 
@@ -400,7 +428,7 @@ var baseline = /*#__PURE__*/Object.freeze({
400
428
  reset: reset$q,
401
429
  start: start$F,
402
430
  get state () { return state$a; },
403
- stop: stop$B,
431
+ stop: stop$C,
404
432
  track: track$7,
405
433
  visibility: visibility
406
434
  });
@@ -426,7 +454,7 @@ function start$E() {
426
454
  updates$3 = {};
427
455
  count$1(5 /* Metric.InvokeCount */);
428
456
  }
429
- function stop$A() {
457
+ function stop$B() {
430
458
  data$i = {};
431
459
  updates$3 = {};
432
460
  }
@@ -504,7 +532,7 @@ function ping() {
504
532
  suspend();
505
533
  }
506
534
  }
507
- function stop$z() {
535
+ function stop$A() {
508
536
  clearTimeout(timeout$6);
509
537
  last = 0;
510
538
  interval = 0;
@@ -515,14 +543,14 @@ var ping$1 = /*#__PURE__*/Object.freeze({
515
543
  get data () { return data$h; },
516
544
  reset: reset$o,
517
545
  start: start$D,
518
- stop: stop$z
546
+ stop: stop$A
519
547
  });
520
548
 
521
549
  var data$g = null;
522
550
  function start$C() {
523
551
  data$g = {};
524
552
  }
525
- function stop$y() {
553
+ function stop$z() {
526
554
  data$g = {};
527
555
  }
528
556
  function track$6(event, time) {
@@ -555,7 +583,7 @@ var summary = /*#__PURE__*/Object.freeze({
555
583
  get data () { return data$g; },
556
584
  reset: reset$n,
557
585
  start: start$C,
558
- stop: stop$y,
586
+ stop: stop$z,
559
587
  track: track$6
560
588
  });
561
589
 
@@ -584,7 +612,7 @@ function upgrade(key) {
584
612
  encode$1(3 /* Event.Upgrade */);
585
613
  }
586
614
  }
587
- function stop$x() {
615
+ function stop$y() {
588
616
  data$f = null;
589
617
  }
590
618
 
@@ -592,7 +620,7 @@ var upgrade$1 = /*#__PURE__*/Object.freeze({
592
620
  __proto__: null,
593
621
  get data () { return data$f; },
594
622
  start: start$B,
595
- stop: stop$x,
623
+ stop: stop$y,
596
624
  upgrade: upgrade
597
625
  });
598
626
 
@@ -632,7 +660,7 @@ function compute$9() {
632
660
  function reset$m() {
633
661
  data$e = {};
634
662
  }
635
- function stop$w() {
663
+ function stop$x() {
636
664
  reset$m();
637
665
  }
638
666
 
@@ -644,7 +672,7 @@ var variable = /*#__PURE__*/Object.freeze({
644
672
  reset: reset$m,
645
673
  set: set,
646
674
  start: start$A,
647
- stop: stop$w
675
+ stop: stop$x
648
676
  });
649
677
 
650
678
  /******************************************************************************
@@ -764,13 +792,13 @@ function start$z() {
764
792
  start$E();
765
793
  modules$1.forEach(function (x) { return measure(x.start)(); });
766
794
  }
767
- function stop$v() {
795
+ function stop$w() {
768
796
  // Stop modules in the reverse order of their initialization
769
797
  // The ordering below should respect inter-module dependency.
770
798
  // E.g. if upgrade depends on upload, then upgrade needs to end before upload.
771
799
  // Similarly, if upload depends on metadata, upload needs to end before metadata.
772
800
  modules$1.slice().reverse().forEach(function (x) { return measure(x.stop)(); });
773
- stop$A();
801
+ stop$B();
774
802
  }
775
803
  function compute$8() {
776
804
  compute$9();
@@ -920,7 +948,7 @@ function start$x() {
920
948
  reset$k();
921
949
  parse$1(document, true);
922
950
  }
923
- function stop$u() {
951
+ function stop$v() {
924
952
  reset$k();
925
953
  }
926
954
  function reset$k() {
@@ -1267,7 +1295,7 @@ var dom = /*#__PURE__*/Object.freeze({
1267
1295
  parse: parse$1,
1268
1296
  sameorigin: sameorigin,
1269
1297
  start: start$x,
1270
- stop: stop$u,
1298
+ stop: stop$v,
1271
1299
  update: update$1,
1272
1300
  updates: updates$2
1273
1301
  });
@@ -1378,7 +1406,7 @@ function restart$2(timer) {
1378
1406
  tracker[id].yield = y;
1379
1407
  }
1380
1408
  }
1381
- function stop$t(timer) {
1409
+ function stop$u(timer) {
1382
1410
  var end = performance.now();
1383
1411
  var id = key(timer);
1384
1412
  var duration = end - tracker[id].start;
@@ -1398,7 +1426,7 @@ function suspend$1(timer) {
1398
1426
  case 0:
1399
1427
  id = key(timer);
1400
1428
  if (!(id in tracker)) return [3 /*break*/, 2];
1401
- stop$t(timer);
1429
+ stop$u(timer);
1402
1430
  _a = tracker[id];
1403
1431
  return [4 /*yield*/, wait()];
1404
1432
  case 1:
@@ -1541,7 +1569,7 @@ function compute$7() {
1541
1569
  encode$4(8 /* Event.Document */);
1542
1570
  }
1543
1571
  }
1544
- function end() {
1572
+ function stop$t() {
1545
1573
  reset$i();
1546
1574
  }
1547
1575
 
@@ -1676,7 +1704,7 @@ function str$1(input) {
1676
1704
  return input.toString(36);
1677
1705
  }
1678
1706
  function attribute(key, value, privacy) {
1679
- return "".concat(key, "=").concat(text$1(value, key, privacy));
1707
+ return "".concat(key, "=").concat(text$1(value, key.indexOf("data-" /* Constant.DataAttribute */) === 0 ? "data-" /* Constant.DataAttribute */ : key, privacy));
1680
1708
  }
1681
1709
 
1682
1710
  var state$8 = [];
@@ -1849,7 +1877,7 @@ function stop$r() {
1849
1877
  reset$g();
1850
1878
  }
1851
1879
 
1852
- function offset (element) {
1880
+ function offset(element) {
1853
1881
  var output = { x: 0, y: 0 };
1854
1882
  // Walk up the chain to ensure we compute offset distance correctly
1855
1883
  // In case where we may have nested IFRAMEs, we keep walking up until we get to the top most parent page
@@ -1922,6 +1950,18 @@ function handler$2(event, root, evt) {
1922
1950
  schedule$1(encode$3.bind(this, event));
1923
1951
  }
1924
1952
  }
1953
+ function link(node) {
1954
+ while (node && node !== document) {
1955
+ if (node.nodeType === Node.ELEMENT_NODE) {
1956
+ var element = node;
1957
+ if (element.tagName === "A") {
1958
+ return element;
1959
+ }
1960
+ }
1961
+ node = node.parentNode;
1962
+ }
1963
+ return null;
1964
+ }
1925
1965
  function text(element) {
1926
1966
  var output = null;
1927
1967
  if (element) {
@@ -2659,7 +2699,7 @@ function getAttributes(element) {
2659
2699
 
2660
2700
  function traverse (root, timer, source) {
2661
2701
  return __awaiter(this, void 0, void 0, function () {
2662
- var queue, node, next, state, subnode;
2702
+ var queue, entry, next, state, subnode;
2663
2703
  return __generator(this, function (_a) {
2664
2704
  switch (_a.label) {
2665
2705
  case 0:
@@ -2667,8 +2707,8 @@ function traverse (root, timer, source) {
2667
2707
  _a.label = 1;
2668
2708
  case 1:
2669
2709
  if (!(queue.length > 0)) return [3 /*break*/, 4];
2670
- node = queue.shift();
2671
- next = node.firstChild;
2710
+ entry = queue.shift();
2711
+ next = entry.firstChild;
2672
2712
  while (next) {
2673
2713
  queue.push(next);
2674
2714
  next = next.nextSibling;
@@ -2683,7 +2723,7 @@ function traverse (root, timer, source) {
2683
2723
  if (state === 2 /* Task.Stop */) {
2684
2724
  return [3 /*break*/, 4];
2685
2725
  }
2686
- subnode = processNode(node, source);
2726
+ subnode = processNode(entry, source);
2687
2727
  if (subnode) {
2688
2728
  queue.push(subnode);
2689
2729
  }
@@ -2866,7 +2906,7 @@ function process$1() {
2866
2906
  _b.sent();
2867
2907
  return [3 /*break*/, 1];
2868
2908
  case 8:
2869
- stop$t(timer);
2909
+ stop$u(timer);
2870
2910
  return [2 /*return*/];
2871
2911
  }
2872
2912
  });
@@ -2998,18 +3038,6 @@ function target(evt) {
2998
3038
  active$2(); // Mark active periods of time so mutations can continue uninterrupted
2999
3039
  return node.nodeType === Node.DOCUMENT_NODE ? node.documentElement : node;
3000
3040
  }
3001
- function link(node) {
3002
- while (node && node !== document) {
3003
- if (node.nodeType === Node.ELEMENT_NODE) {
3004
- var element = node;
3005
- if (element.tagName === "A") {
3006
- return element;
3007
- }
3008
- }
3009
- node = node.parentNode;
3010
- }
3011
- return null;
3012
- }
3013
3041
  function metadata$2(node, event, text) {
3014
3042
  if (text === void 0) { text = null; }
3015
3043
  // If the node is null, we return a reserved value for id: 0. Valid assignment of id begins from 1+.
@@ -3068,7 +3096,7 @@ function encode$3 (type, ts) {
3068
3096
  entry = _c[_b];
3069
3097
  cTarget = metadata$2(entry.data.target, entry.event, entry.data.text);
3070
3098
  tokens = [entry.time, entry.event];
3071
- cHash = cTarget.hash.join("." /* Constant.Dot */);
3099
+ cHash = cTarget.hash ? cTarget.hash.join("." /* Constant.Dot */) : "" /* Constant.Empty */;
3072
3100
  tokens.push(cTarget.id);
3073
3101
  tokens.push(entry.data.x);
3074
3102
  tokens.push(entry.data.y);
@@ -3288,6 +3316,7 @@ function queue(tokens, transmit) {
3288
3316
  discoverBytes += event_1.length;
3289
3317
  case 37 /* Event.Box */:
3290
3318
  case 6 /* Event.Mutation */:
3319
+ case 43 /* Event.Snapshot */:
3291
3320
  playbackBytes += event_1.length;
3292
3321
  playback.push(event_1);
3293
3322
  break;
@@ -4383,7 +4412,7 @@ function stop$5() {
4383
4412
  reset$2();
4384
4413
  reset$1();
4385
4414
  reset$j();
4386
- stop$C();
4415
+ stop$D();
4387
4416
  status = false;
4388
4417
  }
4389
4418
  function active() {
@@ -4472,7 +4501,7 @@ function discover() {
4472
4501
  return [4 /*yield*/, encode$4(5 /* Event.Discover */, timer, ts)];
4473
4502
  case 2:
4474
4503
  _a.sent();
4475
- stop$t(timer);
4504
+ stop$u(timer);
4476
4505
  return [2 /*return*/];
4477
4506
  }
4478
4507
  });
@@ -4490,9 +4519,9 @@ function start$3() {
4490
4519
  }
4491
4520
  function stop$3() {
4492
4521
  stop$s();
4493
- stop$u();
4522
+ stop$v();
4494
4523
  stop$f();
4495
- end();
4524
+ stop$t();
4496
4525
  }
4497
4526
 
4498
4527
  var layout = /*#__PURE__*/Object.freeze({
@@ -4717,7 +4746,7 @@ function stop() {
4717
4746
  if (active()) {
4718
4747
  // Stop modules in the reverse order of their initialization and start queuing up items again
4719
4748
  modules.slice().reverse().forEach(function (x) { return measure(x.stop)(); });
4720
- stop$v();
4749
+ stop$w();
4721
4750
  stop$5();
4722
4751
  setup();
4723
4752
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-js",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
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/rollup.config.ts CHANGED
@@ -46,11 +46,11 @@ export default [
46
46
  plugins: [
47
47
  alias({
48
48
  entries: [
49
- { find: '@src/interaction/change', replacement: '@src/core/blank' },
50
- { find: '@src/interaction/clipboard', replacement: '@src/core/blank' },
51
- { find: '@src/interaction/input', replacement: '@src/core/blank' },
52
- { find: '@src/interaction/pointer', replacement: '@src/core/blank' },
53
- { find: '@src/interaction/selection', replacement: '@src/core/blank' }
49
+ { find: '@src/layout/document', replacement: '@src/layout/document' },
50
+ { find: '@src/layout/encode', replacement: '@src/insight/encode' },
51
+ { find: /@src\/interaction\/(change|clipboard|input|pointer|selection)/, replacement: '@src/insight/blank' },
52
+ { find: /@src\/layout.*/, replacement: '@src/insight/snapshot' },
53
+ { find: /@src\/performance.*/, replacement: '@src/insight/blank' }
54
54
  ]
55
55
  }),
56
56
  resolve(),
package/src/core/scrub.ts CHANGED
@@ -30,6 +30,7 @@ export function text(value: string, hint: string, privacy: Privacy, mangle: bool
30
30
  case Privacy.TextImage:
31
31
  switch (hint) {
32
32
  case Layout.Constant.TextTag:
33
+ case Layout.Constant.DataAttribute:
33
34
  return mangle ? mangleText(value) : mask(value);
34
35
  case "src":
35
36
  case "srcset":
@@ -48,6 +49,7 @@ export function text(value: string, hint: string, privacy: Privacy, mangle: bool
48
49
  case Privacy.Exclude:
49
50
  switch (hint) {
50
51
  case Layout.Constant.TextTag:
52
+ case Layout.Constant.DataAttribute:
51
53
  return mangle ? mangleText(value) : mask(value);
52
54
  case "value":
53
55
  case "input":
@@ -57,6 +59,25 @@ export function text(value: string, hint: string, privacy: Privacy, mangle: bool
57
59
  case "checksum":
58
60
  return Data.Constant.Empty;
59
61
  }
62
+ break;
63
+ case Privacy.Snapshot:
64
+ switch (hint) {
65
+ case Layout.Constant.TextTag:
66
+ case Layout.Constant.DataAttribute:
67
+ return scrub(value);
68
+ case "value":
69
+ case "input":
70
+ case "click":
71
+ case "change":
72
+ return Array(Data.Setting.WordLength).join(Data.Constant.Mask);
73
+ case "checksum":
74
+ case "src":
75
+ case "srcset":
76
+ case "alt":
77
+ case "title":
78
+ return Data.Constant.Empty;
79
+ }
80
+ break;
60
81
  }
61
82
  }
62
83
  return value;
@@ -83,11 +104,16 @@ function mangleText(value: string): string {
83
104
  }
84
105
  return value;
85
106
  }
86
-
107
+
87
108
  function mask(value: string): string {
88
109
  return value.replace(catchallRegex, Data.Constant.Mask);
89
110
  }
90
111
 
112
+ function scrub(value: string): string {
113
+ regex(); // Initialize regular expressions
114
+ return value.replace(letterRegex, Data.Constant.Letter).replace(digitRegex, Data.Constant.Digit);
115
+ }
116
+
91
117
  function mangleToken(value: string): string {
92
118
  let length = ((Math.floor(value.length / Data.Setting.WordLength) + 1) * Data.Setting.WordLength);
93
119
  let output: string = Layout.Constant.Empty;
@@ -97,14 +123,7 @@ function mangleToken(value: string): string {
97
123
  return output;
98
124
  }
99
125
 
100
- function redact(value: string): string {
101
- let spaceIndex = -1;
102
- let gap = 0;
103
- let hasDigit = false;
104
- let hasEmail = false;
105
- let hasWhitespace = false;
106
- let array = null;
107
-
126
+ function regex(): void {
108
127
  // Initialize unicode regex, if supported by the browser
109
128
  // Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
110
129
  if (unicodeRegex && digitRegex === null) {
@@ -114,6 +133,17 @@ function redact(value: string): string {
114
133
  currencyRegex = new RegExp("\\p{Sc}", "gu");
115
134
  } catch { unicodeRegex = false; }
116
135
  }
136
+ }
137
+
138
+ function redact(value: string): string {
139
+ let spaceIndex = -1;
140
+ let gap = 0;
141
+ let hasDigit = false;
142
+ let hasEmail = false;
143
+ let hasWhitespace = false;
144
+ let array = null;
145
+
146
+ regex(); // Initialize regular expressions
117
147
 
118
148
  for (let i = 0; i < value.length; i++) {
119
149
  let c = value.charCodeAt(i);
@@ -1,2 +1,2 @@
1
- let version = "0.7.5";
1
+ let version = "0.7.6";
2
2
  export default version;
@@ -49,7 +49,8 @@ export function queue(tokens: Token[], transmit: boolean = true): void {
49
49
  case Event.Discover:
50
50
  discoverBytes += event.length;
51
51
  case Event.Box:
52
- case Event.Mutation:
52
+ case Event.Mutation:
53
+ case Event.Snapshot:
53
54
  playbackBytes += event.length;
54
55
  playback.push(event);
55
56
  break;
@@ -4,6 +4,8 @@ export let data = null;
4
4
  /* Intentionally blank module with empty code */
5
5
 
6
6
  export function start(): void {}
7
- export function observe(): void {}
8
7
  export function reset(): void {}
9
8
  export function stop(): void {}
9
+ export function log(): void {}
10
+ export function observe(): void {}
11
+ export function compute(): void {}
@@ -0,0 +1,61 @@
1
+ import { Privacy } from "@clarity-types/core";
2
+ import { Event, Token } from "@clarity-types/data";
3
+ import { Constant, NodeInfo } from "@clarity-types/layout";
4
+ import * as scrub from "@src/core/scrub";
5
+ import { time } from "@src/core/time";
6
+ import * as baseline from "@src/data/baseline";
7
+ import tokenize from "@src/data/token";
8
+ import { queue } from "@src/data/upload";
9
+ import * as snapshot from "@src/insight/snapshot";
10
+ import * as doc from "@src/layout/document";
11
+
12
+ export default async function (type: Event): Promise<void> {
13
+ let eventTime = time()
14
+ let tokens: Token[] = [eventTime, type];
15
+ switch (type) {
16
+ case Event.Document:
17
+ let d = doc.data;
18
+ tokens.push(d.width);
19
+ tokens.push(d.height);
20
+ baseline.track(type, d.width, d.height);
21
+ queue(tokens);
22
+ break;
23
+ case Event.Snapshot:
24
+ let values = snapshot.values;
25
+ // Only encode and queue DOM updates if we have valid updates to report back
26
+ if (values.length > 0) {
27
+ for (let value of values) {
28
+ let privacy = value.metadata.privacy;
29
+ let data: NodeInfo = value.data;
30
+ for (let key of ["tag", "attributes", "value"]) {
31
+ if (data[key]) {
32
+ switch (key) {
33
+ case "tag":
34
+ tokens.push(value.id);
35
+ if (value.parent) { tokens.push(value.parent); }
36
+ if (value.previous) { tokens.push(value.previous); }
37
+ tokens.push(data[key]);
38
+ break;
39
+ case "attributes":
40
+ for (let attr in data[key]) {
41
+ if (data[key][attr] !== undefined) {
42
+ tokens.push(attribute(attr, data[key][attr], privacy));
43
+ }
44
+ }
45
+ break;
46
+ case "value":
47
+ tokens.push(scrub.text(data[key], data.tag, privacy));
48
+ break;
49
+ }
50
+ }
51
+ }
52
+ }
53
+ queue(tokenize(tokens), true);
54
+ }
55
+ break;
56
+ }
57
+ }
58
+
59
+ function attribute(key: string, value: string, privacy: Privacy): string {
60
+ return `${key}=${scrub.text(value, key.indexOf(Constant.DataAttribute) === 0 ? Constant.DataAttribute : key, privacy)}`;
61
+ }