clarity-js 0.7.6 → 0.7.8

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/src/core/scrub.ts CHANGED
@@ -64,7 +64,7 @@ export function text(value: string, hint: string, privacy: Privacy, mangle: bool
64
64
  switch (hint) {
65
65
  case Layout.Constant.TextTag:
66
66
  case Layout.Constant.DataAttribute:
67
- return scrub(value);
67
+ return scrub(value, Data.Constant.Letter, Data.Constant.Digit);
68
68
  case "value":
69
69
  case "input":
70
70
  case "click":
@@ -83,7 +83,10 @@ export function text(value: string, hint: string, privacy: Privacy, mangle: bool
83
83
  return value;
84
84
  }
85
85
 
86
- export function url(input: string): string {
86
+ export function url(input: string, electron: boolean = false): string {
87
+ // Replace the URL for Electron apps so we don't send back file:/// URL
88
+ if (electron) { return `${Data.Constant.HTTPS}${Data.Constant.Electron}`; }
89
+
87
90
  let drop = config.drop;
88
91
  if (drop && drop.length > 0 && input && input.indexOf("?") > 0) {
89
92
  let [path, query] = input.split("?");
@@ -109,9 +112,9 @@ function mask(value: string): string {
109
112
  return value.replace(catchallRegex, Data.Constant.Mask);
110
113
  }
111
114
 
112
- function scrub(value: string): string {
115
+ export function scrub(value: string, letter: string, digit: string): string {
113
116
  regex(); // Initialize regular expressions
114
- return value.replace(letterRegex, Data.Constant.Letter).replace(digitRegex, Data.Constant.Digit);
117
+ return value ? value.replace(letterRegex, letter).replace(digitRegex, digit) : value;
115
118
  }
116
119
 
117
120
  function mangleToken(value: string): string {
@@ -161,7 +164,7 @@ function redact(value: string): string {
161
164
  // Check if unicode regex is supported, otherwise fallback to calling mask function on this token
162
165
  if (unicodeRegex && currencyRegex !== null) {
163
166
  // Do not redact information if the token contains a currency symbol
164
- token = token.match(currencyRegex) ? token : token.replace(letterRegex, Data.Constant.Letter).replace(digitRegex, Data.Constant.Digit);
167
+ token = token.match(currencyRegex) ? token : scrub(token, Data.Constant.Letter, Data.Constant.Digit);
165
168
  } else {
166
169
  token = mask(token);
167
170
  }
@@ -1,2 +1,2 @@
1
- let version = "0.7.6";
1
+ let version = "0.7.8";
2
2
  export default version;
@@ -108,10 +108,17 @@ export default function(event: Event): void {
108
108
  break;
109
109
  case Event.Extract:
110
110
  let extractKeys = extract.keys;
111
- for (let e of extractKeys) {
111
+ extractKeys.forEach((e => {
112
112
  tokens.push(e);
113
- tokens.push([].concat(...extract.data[e]));
114
- }
113
+ let token = []
114
+ for (let d in extract.data[e]) {
115
+ let key = parseInt(d, 10);
116
+ token.push(key);
117
+ token.push(extract.data[e][d]);
118
+ }
119
+ tokens.push(token);
120
+ }));
121
+
115
122
  extract.reset();
116
123
  queue(tokens, false);
117
124
  }
@@ -3,16 +3,23 @@ import { Event, Setting, ExtractData } from "@clarity-types/data";
3
3
  import encode from "./encode";
4
4
  import * as internal from "@src/diagnostic/internal";
5
5
  import { Code, Constant, Severity } from "@clarity-types/data";
6
+ import { hashText } from "@src/clarity";
6
7
 
7
8
  export let data: ExtractData = {};
8
- export let keys: number[] = [];
9
+ export let keys: Set<number> = new Set();
9
10
 
10
11
  let variables : { [key: number]: { [key: number]: Syntax[] }} = {};
11
12
  let selectors : { [key: number]: { [key: number]: string }} = {};
13
+ let hashes : { [key: number]: { [key: number]: string }} = {};
12
14
  export function start(): void {
13
15
  reset();
14
16
  }
15
17
 
18
+ // Input string is of the following form:
19
+ // EXTRACT 101 { "1": ".class1", "2": "~window.a.b", "3": "!abc"}
20
+ // Which will set up event 101 to grab the contents of the class1 selector into component 1,
21
+ // the javascript evaluated contents of window.a.b into component 2,
22
+ // and the contents of Clarity's hash abc into component 3
16
23
  export function trigger(input: string): void {
17
24
  try {
18
25
  var parts = input && input.length > 0 ? input.split(/ (.*)/) : [Constant.Empty];
@@ -20,10 +27,18 @@ export function trigger(input: string): void {
20
27
  var values = parts.length > 1 ? JSON.parse(parts[1]) : {};
21
28
  variables[key] = {};
22
29
  selectors[key] = {};
30
+ hashes[key] = {};
23
31
  for (var v in values) {
32
+ // values is a set of strings for proper JSON parsing, but it's more efficient
33
+ // to interact with them as numbers
24
34
  let id = parseInt(v);
25
35
  let value = values[v] as string;
26
- let source = value.startsWith(Constant.Tilde) ? ExtractSource.Javascript : ExtractSource.Text;
36
+ let source = ExtractSource.Text;
37
+ if (value.startsWith(Constant.Tilde)) {
38
+ source = ExtractSource.Javascript
39
+ } else if (value.startsWith(Constant.Bang)) {
40
+ source = ExtractSource.Hash
41
+ }
27
42
  switch (source) {
28
43
  case ExtractSource.Javascript:
29
44
  let variable = value.substring(1, value.length);
@@ -32,6 +47,10 @@ export function trigger(input: string): void {
32
47
  case ExtractSource.Text:
33
48
  selectors[key][id] = value;
34
49
  break;
50
+ case ExtractSource.Hash:
51
+ let hash = value.substring(1, value.length);
52
+ hashes[key][id] = hash;
53
+ break;
35
54
  }
36
55
  }
37
56
  }
@@ -48,44 +67,63 @@ export function compute(): void {
48
67
  try {
49
68
  for (let v in variables) {
50
69
  let key = parseInt(v);
51
- if (!(key in keys)) {
52
- let variableData = variables[key];
53
- for (let v in variableData) {
54
- let variableKey = parseInt(v);
55
- let value = str(evaluate(clone(variableData[variableKey])));
56
- if (value) { update(key, variableKey, value); }
70
+ let variableData = variables[key];
71
+ for (let v in variableData) {
72
+ let variableKey = parseInt(v);
73
+ let value = str(evaluate(clone(variableData[variableKey])));
74
+ if (value) {
75
+ update(key, variableKey, value);
57
76
  }
77
+ }
58
78
 
59
- let selectorData = selectors[key];
60
- for (let s in selectorData) {
61
- let selectorKey = parseInt(s);
62
- let nodes = document.querySelectorAll(selectorData[selectorKey]) as NodeListOf<HTMLElement>;
63
- if (nodes) {
64
- let text = Array.from(nodes).map(e => e.innerText)
65
- update(key, selectorKey, text.join(Constant.Seperator).substring(0, Setting.ExtractLimit));
66
- }
79
+ let selectorData = selectors[key];
80
+ for (let s in selectorData) {
81
+ let selectorKey = parseInt(s);
82
+ let nodes = document.querySelectorAll(selectorData[selectorKey]) as NodeListOf<HTMLElement>;
83
+ if (nodes) {
84
+ let text = Array.from(nodes).map(e => e.textContent)
85
+ update(key, selectorKey, text.join(Constant.Seperator).substring(0, Setting.ExtractLimit));
67
86
  }
68
87
  }
88
+
89
+ let hashData = hashes[key];
90
+ for (let h in hashData) {
91
+ let hashKey = parseInt(h);
92
+ let content = hashText(hashData[hashKey]).trim().substring(0, Setting.ExtractLimit);
93
+ update(key, hashKey, content);
94
+ }
95
+ }
96
+
97
+ if (keys.size > 0) {
98
+ encode(Event.Extract);
69
99
  }
70
100
  }
71
101
  catch (e) { internal.log(Code.Selector, Severity.Warning, e ? e.name : null); }
72
-
73
- encode(Event.Extract);
74
102
  }
75
103
 
76
104
  export function reset(): void {
77
- data = {};
78
- keys = [];
79
- variables = {};
80
- selectors = {};
105
+ keys.clear();
81
106
  }
82
107
 
83
- export function update(key: number, subkey: number, value: string): void {
108
+ export function update(key: number, subkey: number, value: string): void {
109
+ var update = false;
84
110
  if (!(key in data)) {
85
- data[key] = []
86
- keys.push(key);
111
+ data[key] = {};
112
+ update = true;
87
113
  }
88
- data[key].push([subkey, value]);
114
+
115
+ if (!isEmpty(hashes[key])
116
+ && (!(subkey in data[key]) || data[key][subkey] != value))
117
+ {
118
+ update = true;
119
+ }
120
+
121
+ data[key][subkey] = value;
122
+ if (update) {
123
+ keys.add(key);
124
+ }
125
+
126
+ return;
89
127
  }
90
128
 
91
129
  export function stop(): void {
@@ -151,4 +189,8 @@ function match(base: Object, condition: string): boolean {
151
189
  }
152
190
 
153
191
  return true;
154
- }
192
+ }
193
+
194
+ function isEmpty(obj: Object): boolean {
195
+ return Object.keys(obj).length == 0;
196
+ }
package/src/data/limit.ts CHANGED
@@ -15,6 +15,7 @@ export function check(bytes: number): void {
15
15
  if (data.check === Check.None) {
16
16
  let reason = data.check;
17
17
  reason = envelope.data.sequence >= Setting.PayloadLimit ? Check.Payload : reason;
18
+ reason = envelope.data.pageNum >= Setting.PageLimit ? Check.Page : reason;
18
19
  reason = time() > Setting.ShutdownLimit ? Check.Shutdown : reason;
19
20
  reason = bytes > Setting.PlaybackBytesLimit ? Check.Shutdown : reason;
20
21
  if (reason !== data.check) {
@@ -16,6 +16,7 @@ export function start(): void {
16
16
  rootDomain = null;
17
17
  const ua = navigator && "userAgent" in navigator ? navigator.userAgent : Constant.Empty;
18
18
  const title = document && document.title ? document.title : Constant.Empty;
19
+ const electron = ua.indexOf(Constant.Electron) > 0 ? BooleanFlag.True : BooleanFlag.False;
19
20
 
20
21
  // Populate ids for this page
21
22
  let s = session();
@@ -26,20 +27,23 @@ export function start(): void {
26
27
  // Override configuration based on what's in the session storage, unless it is blank (e.g. using upload callback, like in devtools)
27
28
  config.lean = config.track && s.upgrade !== null ? s.upgrade === BooleanFlag.False : config.lean;
28
29
  config.upload = config.track && typeof config.upload === Constant.String && s.upload && s.upload.length > Constant.HTTPS.length ? s.upload : config.upload;
29
-
30
+
30
31
  // Log page metadata as dimensions
31
32
  dimension.log(Dimension.UserAgent, ua);
32
33
  dimension.log(Dimension.PageTitle, title);
33
- dimension.log(Dimension.Url, scrub.url(location.href));
34
+ dimension.log(Dimension.Url, scrub.url(location.href, !!electron));
34
35
  dimension.log(Dimension.Referrer, document.referrer);
35
36
  dimension.log(Dimension.TabId, tab());
36
37
  dimension.log(Dimension.PageLanguage, document.documentElement.lang);
37
38
  dimension.log(Dimension.DocumentDirection, document.dir);
38
39
  dimension.log(Dimension.DevicePixelRatio, `${window.devicePixelRatio}`);
39
-
40
+ dimension.log(Dimension.Dob, u.dob.toString());
41
+ dimension.log(Dimension.CookieVersion, u.version.toString());
42
+
40
43
  // Capture additional metadata as metrics
41
44
  metric.max(Metric.ClientTimestamp, s.ts);
42
- metric.max(Metric.Playback, BooleanFlag.False);
45
+ metric.max(Metric.Playback, BooleanFlag.False);
46
+ metric.max(Metric.Electron, electron);
43
47
 
44
48
  // Capture navigator specific dimensions
45
49
  if (navigator) {
@@ -48,7 +52,7 @@ export function start(): void {
48
52
  metric.max(Metric.MaxTouchPoints, navigator.maxTouchPoints);
49
53
  metric.max(Metric.DeviceMemory, Math.round((<any>navigator).deviceMemory));
50
54
  userAgentData();
51
- }
55
+ }
52
56
 
53
57
  if (screen) {
54
58
  metric.max(Metric.ScreenWidth, Math.round(screen.width));
@@ -69,12 +73,12 @@ export function start(): void {
69
73
  function userAgentData(): void {
70
74
  let uaData = navigator["userAgentData"];
71
75
  if (uaData && uaData.getHighEntropyValues) {
72
- uaData.getHighEntropyValues(["model","platform","platformVersion","uaFullVersion"]).then(ua => {
73
- dimension.log(Dimension.Platform, ua.platform);
74
- dimension.log(Dimension.PlatformVersion, ua.platformVersion);
76
+ uaData.getHighEntropyValues(["model","platform","platformVersion","uaFullVersion"]).then(ua => {
77
+ dimension.log(Dimension.Platform, ua.platform);
78
+ dimension.log(Dimension.PlatformVersion, ua.platformVersion);
75
79
  ua.brands?.forEach(brand => { dimension.log(Dimension.Brand, brand.name + Constant.Tilde + brand.version); });
76
- dimension.log(Dimension.Model, ua.model);
77
- metric.max(Metric.Mobile, ua.mobile ? BooleanFlag.True : BooleanFlag.False);
80
+ dimension.log(Dimension.Model, ua.model);
81
+ metric.max(Metric.Mobile, ua.mobile ? BooleanFlag.True : BooleanFlag.False);
78
82
  });
79
83
  } else { dimension.log(Dimension.Platform, navigator.platform); }
80
84
  }
@@ -85,11 +89,16 @@ export function stop(): void {
85
89
  }
86
90
 
87
91
  export function metadata(cb: MetadataCallback, wait: boolean = true): void {
88
- if (data && wait === false) {
92
+ let upgraded = config.lean ? BooleanFlag.False : BooleanFlag.True;
93
+ // if caller hasn't specified that they want to skip waiting for upgrade but we've already upgraded, we need to
94
+ // directly execute the callback rather than adding to our list as we only process callbacks at the moment
95
+ // we go through the upgrading flow.
96
+ if (data && (upgraded || wait === false)) {
89
97
  // Immediately invoke the callback if the caller explicitly doesn't want to wait for the upgrade confirmation
90
98
  cb(data, !config.lean);
99
+ } else {
100
+ callbacks.push({callback: cb, wait: wait });
91
101
  }
92
- callbacks.push({callback: cb, wait: wait });
93
102
  }
94
103
 
95
104
  export function id(): string {
@@ -144,9 +153,13 @@ function track(u: User, consent: BooleanFlag = null): void {
144
153
  // Convert time precision into days to reduce number of bytes we have to write in a cookie
145
154
  // E.g. Math.ceil(1628735962643 / (24*60*60*1000)) => 18852 (days) => ejo in base36 (13 bytes => 3 bytes)
146
155
  let end = Math.ceil((Date.now() + (Setting.Expire * Time.Day))/Time.Day);
156
+ // If DOB is not set in the user object, use the date set in the config as a DOB
157
+ let dob = u.dob === 0 ? (config.dob === null ? 0 : config.dob) : u.dob;
158
+
147
159
  // To avoid cookie churn, write user id cookie only once every day
148
- if (u.expiry === null || Math.abs(end - u.expiry) >= Setting.CookieInterval || u.consent !== consent) {
149
- setCookie(Constant.CookieKey, [data.userId, Setting.CookieVersion, end.toString(36), consent].join(Constant.Pipe), Setting.Expire);
160
+ if (u.expiry === null || Math.abs(end - u.expiry) >= Setting.CookieInterval || u.consent !== consent || u.dob !== dob) {
161
+ let cookieParts = [data.userId, Setting.CookieVersion, end.toString(36), consent, dob];
162
+ setCookie(Constant.CookieKey, cookieParts.join(Constant.Pipe), Setting.Expire);
150
163
  }
151
164
  }
152
165
 
@@ -180,7 +193,7 @@ function num(string: string, base: number = 10): number {
180
193
  }
181
194
 
182
195
  function user(): User {
183
- let output: User = { id: shortid(), expiry: null, consent: BooleanFlag.False };
196
+ let output: User = { id: shortid(), version: 0, expiry: null, consent: BooleanFlag.False, dob: 0 };
184
197
  let cookie = getCookie(Constant.CookieKey);
185
198
  if(cookie && cookie.length > 0) {
186
199
  // Splitting and looking up first part for forward compatibility, in case we wish to store additional information in a cookie
@@ -200,9 +213,11 @@ function user(): User {
200
213
  }
201
214
  // End code for backward compatibility
202
215
  // Read version information and timestamp from cookie, if available
216
+ if (parts.length > 1) { output.version = num(parts[1]); }
203
217
  if (parts.length > 2) { output.expiry = num(parts[2], 36); }
204
218
  // Check if we have explicit consent to track this user
205
219
  if (parts.length > 3 && num(parts[3]) === 1) { output.consent = BooleanFlag.True; }
220
+ if (parts.length > 4 && num(parts[1]) > 1) { output.dob = num(parts[4]); }
206
221
  // Set track configuration to true for this user if we have explicit consent, regardless of project setting
207
222
  config.track = config.track || output.consent === BooleanFlag.True;
208
223
  // Get user id from cookie only if we tracking is enabled, otherwise fallback to a random id
@@ -241,7 +256,7 @@ function setCookie(key: string, value: string, time: number): void {
241
256
  rootDomain = `.${hostname[i]}${rootDomain ? rootDomain : Constant.Empty}`;
242
257
  // We do not wish to attempt writing a cookie on the absolute last part of the domain, e.g. .com or .net.
243
258
  // So we start attempting after second-last part, e.g. .domain.com (PASS) or .co.uk (FAIL)
244
- if (i < hostname.length - 1) {
259
+ if (i < hostname.length - 1) {
245
260
  // Write the cookie on the current computed top level domain
246
261
  document.cookie = `${cookie}${Constant.Semicolon}${Constant.Domain}${rootDomain}`;
247
262
  // Once written, check if the cookie exists and its value matches exactly with what we intended to set
@@ -1,5 +1,6 @@
1
- import { Constant, Event, VariableData } from "@clarity-types/data";
1
+ import { Constant, Event, IdentityData, Setting, VariableData } from "@clarity-types/data";
2
2
  import * as core from "@src/core";
3
+ import { scrub } from "@src/core/scrub";
3
4
  import encode from "./encode";
4
5
 
5
6
  export let data: VariableData = null;
@@ -13,10 +14,28 @@ export function set(variable: string, value: string | string[]): void {
13
14
  log(variable, values);
14
15
  }
15
16
 
16
- export function identify(userId: string, sessionId: string = null, pageId: string = null): void {
17
- log(Constant.UserId, [userId]);
18
- log(Constant.SessionId, [sessionId]);
19
- log(Constant.PageId, [pageId]);
17
+ export async function identify(userId: string, sessionId: string = null, pageId: string = null, userHint: string = null): Promise<IdentityData> {
18
+ let output: IdentityData = { userId: await sha256(userId), userHint: userHint || redact(userId) };
19
+
20
+ // By default, hash custom userId using SHA256 algorithm on the client to preserve privacy
21
+ log(Constant.UserId, [output.userId]);
22
+
23
+ // Optional non-identifying name for the user
24
+ // If name is not explicitly provided, we automatically generate a redacted version of the userId
25
+ log(Constant.UserHint, [output.userHint]);
26
+ log(Constant.UserType, [detect(userId)]);
27
+
28
+ // Log sessionId and pageId if provided
29
+ if (sessionId) {
30
+ log(Constant.SessionId, [sessionId]);
31
+ output.sessionId = sessionId;
32
+ }
33
+ if (pageId) {
34
+ log(Constant.PageId, [pageId]);
35
+ output.pageId = pageId;
36
+ }
37
+
38
+ return output;
20
39
  }
21
40
 
22
41
  function log(variable: string, value: string[]): void {
@@ -44,3 +63,22 @@ export function reset(): void {
44
63
  export function stop(): void {
45
64
  reset();
46
65
  }
66
+
67
+ function redact(input: string): string {
68
+ return input && input.length >= Setting.WordLength ?
69
+ `${input.substring(0,2)}${scrub(input.substring(2), Constant.Asterix, Constant.Asterix)}` : scrub(input, Constant.Asterix, Constant.Asterix);
70
+ }
71
+
72
+ async function sha256(input: string): Promise<string> {
73
+ try {
74
+ if (crypto && input) {
75
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string
76
+ const buffer = await crypto.subtle.digest(Constant.SHA256, new TextEncoder().encode(input));
77
+ return Array.prototype.map.call(new Uint8Array(buffer), (x: any) =>(('00'+x.toString(16)).slice(-2))).join('');
78
+ } else { return Constant.Empty; }
79
+ } catch { return Constant.Empty; }
80
+ }
81
+
82
+ function detect(input: string): string {
83
+ return input && input.indexOf(Constant.At) > 0 ? Constant.Email : Constant.String;
84
+ }
@@ -92,10 +92,10 @@ function text(element: Node): string {
92
92
  // Grab text using "textContent" for most HTMLElements, however, use "value" for HTMLInputElements and "alt" for HTMLImageElement.
93
93
  let t = element.textContent || (element as HTMLInputElement).value || (element as HTMLImageElement).alt;
94
94
  if (t) {
95
- // Trim any spaces at the beginning or at the end of string
96
- // Also, replace multiple occurrence of space characters with a single white space
95
+ // Replace multiple occurrence of space characters with a single white space
96
+ // Also, trim any spaces at the beginning or at the end of string
97
97
  // Finally, send only first few characters as specified by the Setting
98
- output = t.trim().replace(/\s+/g, Constant.Space).substr(0, Setting.ClickText);
98
+ output = t.replace(/\s+/g, Constant.Space).trim().substr(0, Setting.ClickText);
99
99
  }
100
100
  }
101
101
  return output;
@@ -0,0 +1,7 @@
1
+ export * from "@src/insight/blank";
2
+
3
+ export let keys = [];
4
+
5
+ /* Intentionally blank module with empty code */
6
+ export function hashText(): void {}
7
+ export function trigger(): void {}
package/src/queue.ts CHANGED
@@ -9,6 +9,8 @@ export function setup() {
9
9
  if (typeof w !== "undefined") {
10
10
  w[c] = function() {
11
11
  (w[c].q = w[c].q || []).push(arguments);
12
+ // if the start function was called, don't queue it and instead process the queue
13
+ arguments[0] === "start" && w[c].q.unshift(w[c].q.pop()) && process();
12
14
  };
13
15
  }
14
16
  }
package/types/core.d.ts CHANGED
@@ -34,11 +34,12 @@ export const enum ExtractSource {
34
34
  Javascript = 0,
35
35
  Cookie = 1,
36
36
  Text = 2,
37
- Fragment = 3
37
+ Fragment = 3,
38
+ Hash = 4
38
39
  }
39
40
 
40
41
  export const enum Type {
41
- Array = 1,
42
+ Array = 1,
42
43
  Object = 2,
43
44
  Simple = 3
44
45
  }
@@ -135,6 +136,7 @@ export interface Config {
135
136
  fallback?: string;
136
137
  upgrade?: (key: string) => void;
137
138
  action?: (key: string) => void;
139
+ dob?: number;
138
140
  }
139
141
 
140
142
  export const enum Constant {
package/types/data.d.ts CHANGED
@@ -5,7 +5,7 @@ export type DecodedToken = (any | any[]);
5
5
 
6
6
  export type MetadataCallback = (data: Metadata, playback: boolean) => void;
7
7
  export interface MetadataCallbackOptions {
8
- callback: MetadataCallback,
8
+ callback: MetadataCallback,
9
9
  wait: boolean
10
10
  }
11
11
 
@@ -105,7 +105,8 @@ export const enum Metric {
105
105
  Iframed = 31,
106
106
  MaxTouchPoints = 32,
107
107
  HardwareConcurrency = 33,
108
- DeviceMemory = 34
108
+ DeviceMemory = 34,
109
+ Electron = 35
109
110
  }
110
111
 
111
112
  export const enum Dimension {
@@ -136,7 +137,9 @@ export const enum Dimension {
136
137
  Brand = 24,
137
138
  Model = 25,
138
139
  DevicePixelRatio = 26,
139
- ConnectionType = 27
140
+ ConnectionType = 27,
141
+ Dob = 28,
142
+ CookieVersion = 29
140
143
  }
141
144
 
142
145
  export const enum Check {
@@ -146,7 +149,8 @@ export const enum Check {
146
149
  Retry = 3,
147
150
  Bytes = 4,
148
151
  Collection = 5,
149
- Server = 6
152
+ Server = 6,
153
+ Page = 7
150
154
  }
151
155
 
152
156
  export const enum Code {
@@ -190,7 +194,7 @@ export const enum IframeStatus {
190
194
  export const enum Setting {
191
195
  Expire = 365, // 1 Year
192
196
  SessionExpire = 1, // 1 Day
193
- CookieVersion = 1, // Increment this version every time there's a cookie schema change
197
+ CookieVersion = 2, // Increment this version every time there's a cookie schema change
194
198
  SessionTimeout = 30 * Time.Minute, // 30 minutes
195
199
  CookieInterval = 1, // 1 Day
196
200
  PingInterval = 1 * Time.Minute, // 1 Minute
@@ -198,6 +202,7 @@ export const enum Setting {
198
202
  SummaryInterval = 100, // Same events within 100ms will be collapsed into single summary
199
203
  ClickText = 25, // Maximum number of characters to send as part of Click event's text field
200
204
  PayloadLimit = 128, // Do not allow more than specified payloads per page
205
+ PageLimit = 128, // Do not allow more than 128 pages in a session
201
206
  ShutdownLimit = 2 * Time.Hour, // Shutdown instrumentation after specified time
202
207
  RetryLimit = 1, // Maximum number of attempts to upload a payload before giving up
203
208
  PlaybackBytesLimit = 10 * 1024 * 1024, // 10MB
@@ -218,7 +223,7 @@ export const enum Setting {
218
223
  MinUploadDelay = 100, // Minimum time before we are ready to flush events to the server
219
224
  MaxUploadDelay = 30 * Time.Second, // Do flush out payload once every 30s,
220
225
  ExtractLimit = 10000, // Do not extract more than 10000 characters
221
- ChecksumPrecision = 24, // n-bit integer to represent token hash
226
+ ChecksumPrecision = 24, // n-bit integer to represent token hash
222
227
  UploadTimeout = 15000 // Timeout in ms for XHR requests
223
228
  }
224
229
 
@@ -249,6 +254,8 @@ export const enum Constant {
249
254
  Dropped = "*na*",
250
255
  Comma = ",",
251
256
  Dot = ".",
257
+ At = "@",
258
+ Asterix = "*",
252
259
  Semicolon = ";",
253
260
  Equals = "=",
254
261
  Path = ";path=/",
@@ -258,6 +265,7 @@ export const enum Constant {
258
265
  Top = "_top",
259
266
  String = "string",
260
267
  Number = "number",
268
+ Email = "email",
261
269
  CookieKey = "_clck", // Clarity Cookie Key
262
270
  SessionKey = "_clsk", // Clarity Session Key
263
271
  TabKey = "_cltk", // Clarity Tab Key
@@ -266,6 +274,8 @@ export const enum Constant {
266
274
  Upgrade = "UPGRADE",
267
275
  Action = "ACTION",
268
276
  Extract = "EXTRACT",
277
+ UserHint = "userHint",
278
+ UserType = "userType",
269
279
  UserId = "userId",
270
280
  SessionId = "sessionId",
271
281
  PageId = "pageId",
@@ -289,7 +299,10 @@ export const enum Constant {
289
299
  ConditionStart = "{",
290
300
  ConditionEnd = "}",
291
301
  Seperator = "<SEP>",
292
- Timeout = "Timeout"
302
+ Timeout = "Timeout",
303
+ Bang = "!",
304
+ SHA256 = "SHA-256",
305
+ Electron = "Electron"
293
306
  }
294
307
 
295
308
  export const enum XMLReadyState {
@@ -331,8 +344,10 @@ export interface Session {
331
344
 
332
345
  export interface User {
333
346
  id: string;
347
+ version: number;
334
348
  expiry: number;
335
349
  consent: BooleanFlag;
350
+ dob: number;
336
351
  }
337
352
 
338
353
  export interface Envelope extends Metadata {
@@ -371,6 +386,13 @@ export interface BaselineData {
371
386
  activityTime: number;
372
387
  }
373
388
 
389
+ export interface IdentityData {
390
+ userId: string;
391
+ userHint: string;
392
+ sessionId?: string;
393
+ pageId?: string;
394
+ }
395
+
374
396
  export interface DimensionData {
375
397
  [key: number]: string[];
376
398
  }
@@ -409,7 +431,7 @@ export interface UpgradeData {
409
431
  }
410
432
 
411
433
  export interface ExtractData {
412
- [key: number]: [number, string][]; // Array of [id, value] for every extracted data
434
+ [key: number]: { [subkey : number]: string }; // Array of { subkey: number } representing the extracted data
413
435
  }
414
436
 
415
437
  export interface UploadData {
package/types/index.d.ts CHANGED
@@ -14,7 +14,7 @@ interface Clarity {
14
14
  consent: () => void;
15
15
  event: (name: string, value: string) => void;
16
16
  set: (variable: string, value: string | string[]) => void;
17
- identify: (userId: string, sessionId?: string, pageId?: string) => void;
17
+ identify: (userId: string, sessionId?: string, pageId?: string, userHint?: string) => void;
18
18
  metadata: (callback: Data.MetadataCallback, wait?: boolean) => void;
19
19
  }
20
20