clarity-js 0.7.7 → 0.7.9

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.
@@ -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
  }
@@ -149,9 +153,13 @@ function track(u: User, consent: BooleanFlag = null): void {
149
153
  // Convert time precision into days to reduce number of bytes we have to write in a cookie
150
154
  // E.g. Math.ceil(1628735962643 / (24*60*60*1000)) => 18852 (days) => ejo in base36 (13 bytes => 3 bytes)
151
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
+
152
159
  // To avoid cookie churn, write user id cookie only once every day
153
- if (u.expiry === null || Math.abs(end - u.expiry) >= Setting.CookieInterval || u.consent !== consent) {
154
- 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);
155
163
  }
156
164
  }
157
165
 
@@ -185,7 +193,7 @@ function num(string: string, base: number = 10): number {
185
193
  }
186
194
 
187
195
  function user(): User {
188
- 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 };
189
197
  let cookie = getCookie(Constant.CookieKey);
190
198
  if(cookie && cookie.length > 0) {
191
199
  // Splitting and looking up first part for forward compatibility, in case we wish to store additional information in a cookie
@@ -205,9 +213,11 @@ function user(): User {
205
213
  }
206
214
  // End code for backward compatibility
207
215
  // Read version information and timestamp from cookie, if available
216
+ if (parts.length > 1) { output.version = num(parts[1]); }
208
217
  if (parts.length > 2) { output.expiry = num(parts[2], 36); }
209
218
  // Check if we have explicit consent to track this user
210
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]); }
211
221
  // Set track configuration to true for this user if we have explicit consent, regardless of project setting
212
222
  config.track = config.track || output.consent === BooleanFlag.True;
213
223
  // Get user id from cookie only if we tracking is enabled, otherwise fallback to a random id
@@ -246,7 +256,7 @@ function setCookie(key: string, value: string, time: number): void {
246
256
  rootDomain = `.${hostname[i]}${rootDomain ? rootDomain : Constant.Empty}`;
247
257
  // We do not wish to attempt writing a cookie on the absolute last part of the domain, e.g. .com or .net.
248
258
  // So we start attempting after second-last part, e.g. .domain.com (PASS) or .co.uk (FAIL)
249
- if (i < hostname.length - 1) {
259
+ if (i < hostname.length - 1) {
250
260
  // Write the cookie on the current computed top level domain
251
261
  document.cookie = `${cookie}${Constant.Semicolon}${Constant.Domain}${rootDomain}`;
252
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 {}
@@ -25,7 +25,7 @@ export default async function(type: Event): Promise<void> {
25
25
  tokens.push(navigation.data.encodedSize);
26
26
  tokens.push(navigation.data.decodedSize);
27
27
  navigation.reset();
28
- queue(tokens, false);
28
+ queue(tokens);
29
29
  break;
30
30
  }
31
31
  }
package/types/core.d.ts CHANGED
@@ -39,7 +39,7 @@ export const enum ExtractSource {
39
39
  }
40
40
 
41
41
  export const enum Type {
42
- Array = 1,
42
+ Array = 1,
43
43
  Object = 2,
44
44
  Simple = 3
45
45
  }
@@ -136,6 +136,7 @@ export interface Config {
136
136
  fallback?: string;
137
137
  upgrade?: (key: string) => void;
138
138
  action?: (key: string) => void;
139
+ dob?: number;
139
140
  }
140
141
 
141
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",
@@ -290,7 +300,9 @@ export const enum Constant {
290
300
  ConditionEnd = "}",
291
301
  Seperator = "<SEP>",
292
302
  Timeout = "Timeout",
293
- Bang = "!"
303
+ Bang = "!",
304
+ SHA256 = "SHA-256",
305
+ Electron = "Electron"
294
306
  }
295
307
 
296
308
  export const enum XMLReadyState {
@@ -332,8 +344,10 @@ export interface Session {
332
344
 
333
345
  export interface User {
334
346
  id: string;
347
+ version: number;
335
348
  expiry: number;
336
349
  consent: BooleanFlag;
350
+ dob: number;
337
351
  }
338
352
 
339
353
  export interface Envelope extends Metadata {
@@ -372,6 +386,13 @@ export interface BaselineData {
372
386
  activityTime: number;
373
387
  }
374
388
 
389
+ export interface IdentityData {
390
+ userId: string;
391
+ userHint: string;
392
+ sessionId?: string;
393
+ pageId?: string;
394
+ }
395
+
375
396
  export interface DimensionData {
376
397
  [key: number]: string[];
377
398
  }
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