@gemx-dev/clarity-js 0.8.46 → 0.8.48
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/build/clarity.extended.js +1 -1
- package/build/clarity.insight.js +1 -1
- package/build/clarity.js +591 -497
- package/build/clarity.min.js +6548 -1
- package/build/clarity.min.js.map +1 -0
- package/build/clarity.module.js +591 -497
- package/build/clarity.performance.js +1 -1
- package/package.json +2 -7
- package/rollup.config.ts +2 -2
- package/src/clarity.ts +26 -31
- package/src/core/report.ts +3 -10
- package/src/core/version.ts +1 -1
- package/src/custom/README.md +44 -0
- package/src/custom/dialog.ts +69 -0
- package/src/custom/index.ts +6 -0
- package/src/data/consent.ts +4 -3
- package/src/data/cookie.ts +90 -0
- package/src/data/index.ts +2 -1
- package/src/data/metadata.ts +23 -110
- package/src/data/util.ts +18 -0
- package/src/index.ts +1 -1
- package/src/insight/snapshot.ts +7 -7
- package/src/layout/node.ts +8 -0
- package/test/hash.test.ts +68 -0
- package/test/time.test.ts +42 -0
- package/tsconfig.json +3 -12
- package/types/data.d.ts +39 -30
- package/types/index.d.ts +3 -3
- package/test/core.test.ts +0 -139
- package/test/helper.ts +0 -167
- package/test/html/core.html +0 -34
- package/test/stub.test.ts +0 -7
- package/test/tsconfig.test.json +0 -6
package/src/data/metadata.ts
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
import { Time } from "@clarity-types/core";
|
|
2
|
-
import { BooleanFlag, Constant, Dimension, Metadata, MetadataCallback, MetadataCallbackOptions, Metric, Session,
|
|
1
|
+
import { Constant as CoreConstant, Time } from "@clarity-types/core";
|
|
2
|
+
import { BooleanFlag, ConsentData, ConsentSource, ConsentState, Constant, Dimension, Metadata, MetadataCallback, MetadataCallbackOptions, Metric, Session, Setting, User } from "@clarity-types/data";
|
|
3
3
|
import * as clarity from "@src/clarity";
|
|
4
4
|
import * as core from "@src/core";
|
|
5
5
|
import config from "@src/core/config";
|
|
6
6
|
import hash from "@src/core/hash";
|
|
7
7
|
import * as scrub from "@src/core/scrub";
|
|
8
|
+
import * as trackConsent from "@src/data/consent";
|
|
9
|
+
import { COOKIE_SEP, getCookie, setCookie } from "@src/data/cookie";
|
|
8
10
|
import * as dimension from "@src/data/dimension";
|
|
9
11
|
import * as metric from "@src/data/metric";
|
|
12
|
+
import { supported } from "@src/data/util";
|
|
10
13
|
import { set } from "@src/data/variable";
|
|
11
|
-
import * as trackConsent from "@src/data/consent";
|
|
12
|
-
import { Constant as CoreConstant } from "@clarity-types/core";
|
|
13
14
|
|
|
14
15
|
export let data: Metadata = null;
|
|
15
16
|
export let callbacks: MetadataCallbackOptions[] = [];
|
|
16
17
|
export let electron = BooleanFlag.False;
|
|
17
|
-
let rootDomain = null;
|
|
18
18
|
let consentStatus: ConsentState = null;
|
|
19
|
-
let defaultStatus: ConsentState = { source: ConsentSource.
|
|
19
|
+
let defaultStatus: ConsentState = { source: ConsentSource.Default, ad_Storage: Constant.Denied, analytics_Storage: Constant.Denied };
|
|
20
20
|
|
|
21
21
|
export function start(): void {
|
|
22
|
-
rootDomain = null;
|
|
23
22
|
const ua = navigator && "userAgent" in navigator ? navigator.userAgent : Constant.Empty;
|
|
24
23
|
const timezone = (typeof Intl !== 'undefined' && Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone) ?? '';
|
|
25
24
|
const timezoneOffset = new Date().getTimezoneOffset().toString();
|
|
@@ -89,7 +88,7 @@ export function start(): void {
|
|
|
89
88
|
// If consent status is not already set, initialize it based on project configuration. Otherwise, use the existing consent status.
|
|
90
89
|
if (consentStatus === null) {
|
|
91
90
|
consentStatus = {
|
|
92
|
-
source: ConsentSource.Implicit,
|
|
91
|
+
source: u.consent ? ConsentSource.Cookie : ConsentSource.Implicit,
|
|
93
92
|
ad_Storage: config.track ? Constant.Granted : Constant.Denied,
|
|
94
93
|
analytics_Storage: config.track ? Constant.Granted : Constant.Denied,
|
|
95
94
|
};
|
|
@@ -114,7 +113,6 @@ function userAgentData(): void {
|
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
export function stop(): void {
|
|
117
|
-
rootDomain = null;
|
|
118
116
|
data = null;
|
|
119
117
|
callbacks.forEach(cb => { cb.called = false; });
|
|
120
118
|
}
|
|
@@ -142,15 +140,15 @@ export function id(): string {
|
|
|
142
140
|
//TODO: Remove this function once consentv2 is fully released
|
|
143
141
|
export function consent(status = true): void {
|
|
144
142
|
if (!status) {
|
|
145
|
-
consentv2();
|
|
143
|
+
consentv2({ source: ConsentSource.APIv1, ad_Storage: Constant.Denied, analytics_Storage: Constant.Denied });
|
|
146
144
|
return;
|
|
147
145
|
}
|
|
148
146
|
|
|
149
|
-
consentv2({ ad_Storage: Constant.Granted, analytics_Storage: Constant.Granted });
|
|
147
|
+
consentv2({ source: ConsentSource.APIv1, ad_Storage: Constant.Granted, analytics_Storage: Constant.Granted });
|
|
150
148
|
trackConsent.consent();
|
|
151
149
|
}
|
|
152
150
|
|
|
153
|
-
export function consentv2(consentState: ConsentState = defaultStatus, source: number = ConsentSource.
|
|
151
|
+
export function consentv2(consentState: ConsentState = defaultStatus, source: number = ConsentSource.APIv2): void {
|
|
154
152
|
const updatedStatus = {
|
|
155
153
|
source: consentState.source ?? source,
|
|
156
154
|
ad_Storage: normalizeConsent(consentState.ad_Storage, consentStatus?.ad_Storage),
|
|
@@ -162,6 +160,9 @@ export function consentv2(consentState: ConsentState = defaultStatus, source: nu
|
|
|
162
160
|
updatedStatus.ad_Storage === consentStatus.ad_Storage &&
|
|
163
161
|
updatedStatus.analytics_Storage === consentStatus.analytics_Storage
|
|
164
162
|
) {
|
|
163
|
+
consentStatus.source = updatedStatus.source;
|
|
164
|
+
trackConsent.trackConsentv2(getConsentData(consentStatus));
|
|
165
|
+
trackConsent.consent();
|
|
165
166
|
return;
|
|
166
167
|
}
|
|
167
168
|
|
|
@@ -171,8 +172,7 @@ export function consentv2(consentState: ConsentState = defaultStatus, source: nu
|
|
|
171
172
|
|
|
172
173
|
if (!consentData.analytics_Storage && config.track) {
|
|
173
174
|
config.track = false;
|
|
174
|
-
|
|
175
|
-
setCookie(Constant.CookieKey, Constant.Empty, -Number.MAX_VALUE);
|
|
175
|
+
clear(true);
|
|
176
176
|
clarity.stop();
|
|
177
177
|
window.setTimeout(clarity.start, Setting.RestartDelay);
|
|
178
178
|
return;
|
|
@@ -202,9 +202,14 @@ function normalizeConsent(value: unknown, fallback: string = Constant.Denied): s
|
|
|
202
202
|
return typeof value === 'string' ? value.toLowerCase() : fallback;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
export function clear(): void {
|
|
205
|
+
export function clear(all: boolean = false): void {
|
|
206
206
|
// Clear any stored information in the cookie that tracks session information so we can restart fresh the next time
|
|
207
|
-
setCookie(Constant.SessionKey, Constant.Empty,
|
|
207
|
+
setCookie(Constant.SessionKey, Constant.Empty, -Number.MAX_VALUE);
|
|
208
|
+
|
|
209
|
+
// Clear user cookie as well if all flag is set
|
|
210
|
+
if (all) {
|
|
211
|
+
setCookie(Constant.CookieKey, Constant.Empty, -Number.MAX_VALUE);
|
|
212
|
+
}
|
|
208
213
|
}
|
|
209
214
|
|
|
210
215
|
function tab(): string {
|
|
@@ -227,7 +232,7 @@ export function save(): void {
|
|
|
227
232
|
let ts = Math.round(Date.now());
|
|
228
233
|
let upload = config.upload && typeof config.upload === Constant.String ? (config.upload as string).replace(Constant.HTTPS, Constant.Empty) : Constant.Empty;
|
|
229
234
|
let upgrade = config.lean ? BooleanFlag.False : BooleanFlag.True;
|
|
230
|
-
setCookie(Constant.SessionKey, [data.sessionId, ts, data.pageNum, upgrade, upload].join(
|
|
235
|
+
setCookie(Constant.SessionKey, [data.sessionId, ts, data.pageNum, upgrade, upload].join(COOKIE_SEP), Setting.SessionExpire);
|
|
231
236
|
}
|
|
232
237
|
|
|
233
238
|
function processCallback(upgrade: BooleanFlag, consentUpdate: boolean = false): void {
|
|
@@ -250,10 +255,6 @@ function processCallback(upgrade: BooleanFlag, consentUpdate: boolean = false):
|
|
|
250
255
|
}
|
|
251
256
|
}
|
|
252
257
|
|
|
253
|
-
function supported(target: Window | Document, api: string): boolean {
|
|
254
|
-
try { return !!target[api]; } catch { return false; }
|
|
255
|
-
}
|
|
256
|
-
|
|
257
258
|
function track(u: User, consent: BooleanFlag = null): void {
|
|
258
259
|
// If consent is not explicitly specified, infer it from the user object
|
|
259
260
|
consent = consent === null ? u.consent : consent;
|
|
@@ -266,7 +267,7 @@ function track(u: User, consent: BooleanFlag = null): void {
|
|
|
266
267
|
// To avoid cookie churn, write user id cookie only once every day
|
|
267
268
|
if (u.expiry === null || Math.abs(end - u.expiry) >= Setting.CookieInterval || u.consent !== consent || u.dob !== dob) {
|
|
268
269
|
let cookieParts = [data.userId, Setting.CookieVersion, end.toString(36), consent, dob];
|
|
269
|
-
setCookie(Constant.CookieKey, cookieParts.join(
|
|
270
|
+
setCookie(Constant.CookieKey, cookieParts.join(COOKIE_SEP), Setting.Expire);
|
|
270
271
|
}
|
|
271
272
|
}
|
|
272
273
|
|
|
@@ -321,91 +322,3 @@ function user(): User {
|
|
|
321
322
|
return output;
|
|
322
323
|
}
|
|
323
324
|
|
|
324
|
-
function getCookie(key: string, limit = false): string {
|
|
325
|
-
if (supported(document, Constant.Cookie)) {
|
|
326
|
-
let cookies: string[] = document.cookie.split(Constant.Semicolon);
|
|
327
|
-
if (cookies) {
|
|
328
|
-
for (let i = 0; i < cookies.length; i++) {
|
|
329
|
-
let pair: string[] = cookies[i].split(Constant.Equals);
|
|
330
|
-
if (pair.length > 1 && pair[0] && pair[0].trim() === key) {
|
|
331
|
-
// Some browsers automatically url encode cookie values if they are not url encoded.
|
|
332
|
-
// We therefore encode and decode cookie values ourselves.
|
|
333
|
-
// For backwards compatability we need to consider 3 cases:
|
|
334
|
-
// * Cookie was previously not encoded by Clarity and browser did not encode it
|
|
335
|
-
// * Cookie was previously not encoded by Clarity and browser encoded it once or more
|
|
336
|
-
// * Cookie was previously encoded by Clarity and browser did not encode it
|
|
337
|
-
let [isEncoded, decodedValue] = decodeCookieValue(pair[1]);
|
|
338
|
-
|
|
339
|
-
while (isEncoded) {
|
|
340
|
-
[isEncoded, decodedValue] = decodeCookieValue(decodedValue);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// If we are limiting cookies, check if the cookie value is limited
|
|
344
|
-
if (limit) {
|
|
345
|
-
return decodedValue.endsWith(`${Constant.Tilde}1`)
|
|
346
|
-
? decodedValue.substring(0, decodedValue.length - 2)
|
|
347
|
-
: null;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return decodedValue;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function decodeCookieValue(value: string): [boolean, string] {
|
|
359
|
-
try {
|
|
360
|
-
let decodedValue = decodeURIComponent(value);
|
|
361
|
-
return [decodedValue != value, decodedValue];
|
|
362
|
-
}
|
|
363
|
-
catch {
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return [false, value];
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function encodeCookieValue(value: string): string {
|
|
370
|
-
return encodeURIComponent(value);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function setCookie(key: string, value: string, time: number): void {
|
|
374
|
-
// only write cookies if we are currently in a cookie writing mode (and they are supported)
|
|
375
|
-
// OR if we are trying to write an empty cookie (i.e. clear the cookie value out)
|
|
376
|
-
if ((config.track || value == Constant.Empty) && ((navigator && navigator.cookieEnabled) || supported(document, Constant.Cookie))) {
|
|
377
|
-
// Some browsers automatically url encode cookie values if they are not url encoded.
|
|
378
|
-
// We therefore encode and decode cookie values ourselves.
|
|
379
|
-
let encodedValue = encodeCookieValue(value);
|
|
380
|
-
|
|
381
|
-
let expiry = new Date();
|
|
382
|
-
expiry.setDate(expiry.getDate() + time);
|
|
383
|
-
let expires = expiry ? Constant.Expires + expiry.toUTCString() : Constant.Empty;
|
|
384
|
-
let cookie = `${key}=${encodedValue}${Constant.Semicolon}${expires}${Constant.Path}`;
|
|
385
|
-
try {
|
|
386
|
-
// Attempt to get the root domain only once and fall back to writing cookie on the current domain.
|
|
387
|
-
if (rootDomain === null) {
|
|
388
|
-
let hostname = location.hostname ? location.hostname.split(Constant.Dot) : [];
|
|
389
|
-
// Walk backwards on a domain and attempt to set a cookie, until successful
|
|
390
|
-
for (let i = hostname.length - 1; i >= 0; i--) {
|
|
391
|
-
rootDomain = `.${hostname[i]}${rootDomain ? rootDomain : Constant.Empty}`;
|
|
392
|
-
// We do not wish to attempt writing a cookie on the absolute last part of the domain, e.g. .com or .net.
|
|
393
|
-
// So we start attempting after second-last part, e.g. .domain.com (PASS) or .co.uk (FAIL)
|
|
394
|
-
if (i < hostname.length - 1) {
|
|
395
|
-
// Write the cookie on the current computed top level domain
|
|
396
|
-
document.cookie = `${cookie}${Constant.Semicolon}${Constant.Domain}${rootDomain}`;
|
|
397
|
-
// Once written, check if the cookie exists and its value matches exactly with what we intended to set
|
|
398
|
-
// Checking for exact value match helps us eliminate a corner case where the cookie may already be present with a different value
|
|
399
|
-
// If the check is successful, no more action is required and we can return from the function since rootDomain cookie is already set
|
|
400
|
-
// If the check fails, continue with the for loop until we can successfully set and verify the cookie
|
|
401
|
-
if (getCookie(key) === value) { return; }
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// Finally, if we were not successful and gone through all the options, play it safe and reset rootDomain to be empty
|
|
405
|
-
// This forces our code to fall back to always writing cookie to the current domain
|
|
406
|
-
rootDomain = Constant.Empty;
|
|
407
|
-
}
|
|
408
|
-
} catch { rootDomain = Constant.Empty; }
|
|
409
|
-
document.cookie = rootDomain ? `${cookie}${Constant.Semicolon}${Constant.Domain}${rootDomain}` : cookie;
|
|
410
|
-
}
|
|
411
|
-
}
|
package/src/data/util.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function supported(target: Window | Document, api: string): boolean {
|
|
2
|
+
try { return !!target[api]; } catch { return false; }
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function encodeCookieValue(value: string): string {
|
|
6
|
+
return encodeURIComponent(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function decodeCookieValue(value: string): [boolean, string] {
|
|
10
|
+
try {
|
|
11
|
+
let decodedValue = decodeURIComponent(value);
|
|
12
|
+
return [decodedValue != value, decodedValue];
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return [false, value];
|
|
18
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import hash from "./core/hash";
|
|
|
3
3
|
import * as selector from "./layout/selector";
|
|
4
4
|
import { get, getNode, lookup } from "./layout/dom";
|
|
5
5
|
|
|
6
|
-
const helper = { hash, selector, get, getNode, lookup }
|
|
6
|
+
const helper = { hash, selector, get, getNode, lookup }
|
|
7
7
|
const version = clarity.version;
|
|
8
8
|
|
|
9
9
|
export { clarity, version, helper };
|
package/src/insight/snapshot.ts
CHANGED
|
@@ -38,7 +38,7 @@ export function metadata(node: Node): TargetMetadata {
|
|
|
38
38
|
return output;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export function snapshot(): void {
|
|
41
|
+
export function snapshot(): void {
|
|
42
42
|
values = [];
|
|
43
43
|
traverse(document);
|
|
44
44
|
encode(Event.Snapshot);
|
|
@@ -54,13 +54,13 @@ function traverse(root: Node): void {
|
|
|
54
54
|
let attributes = null, tag = null, value = null;
|
|
55
55
|
let node = queue.shift();
|
|
56
56
|
let next = node.firstChild;
|
|
57
|
-
let parent = node.parentElement ? node.parentElement : (node.parentNode ?
|
|
57
|
+
let parent = node.parentElement ? node.parentElement : (node.parentNode ? node.parentNode : null);
|
|
58
58
|
|
|
59
59
|
while (next) {
|
|
60
60
|
queue.push(next);
|
|
61
61
|
next = next.nextSibling;
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
// Process the node
|
|
65
65
|
let type = node.nodeType;
|
|
66
66
|
switch (type) {
|
|
@@ -69,7 +69,7 @@ function traverse(root: Node): void {
|
|
|
69
69
|
tag = Constant.DocumentTag;
|
|
70
70
|
attributes = { name: doctype.name, publicId: doctype.publicId, systemId: doctype.systemId }
|
|
71
71
|
break;
|
|
72
|
-
case Node.TEXT_NODE:
|
|
72
|
+
case Node.TEXT_NODE:
|
|
73
73
|
value = node.nodeValue;
|
|
74
74
|
tag = idMap.get(parent) ? Constant.TextTag : tag;
|
|
75
75
|
break;
|
|
@@ -79,7 +79,7 @@ function traverse(root: Node): void {
|
|
|
79
79
|
tag = ["NOSCRIPT", "SCRIPT", "STYLE"].indexOf(element.tagName) < 0 ? element.tagName : tag;
|
|
80
80
|
break;
|
|
81
81
|
}
|
|
82
|
-
add(node, parent, { tag, attributes, value });
|
|
82
|
+
add(node, parent, { tag, attributes, value });
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -88,7 +88,7 @@ function getAttributes(element: HTMLElement): { [key: string]: string } {
|
|
|
88
88
|
let output = {};
|
|
89
89
|
let attributes = element.attributes;
|
|
90
90
|
if (attributes && attributes.length > 0) {
|
|
91
|
-
for (let i = 0; i < attributes.length; i++) {
|
|
91
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
92
92
|
output[attributes[i].name] = attributes[i].value;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -112,4 +112,4 @@ function add(node: Node, parent: Node, data: NodeInfo): void {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
export function get(_node: Node): NodeValue {
|
|
115
|
+
export function get(_node: Node): NodeValue {return null;}
|
package/src/layout/node.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as interaction from "@src/interaction";
|
|
|
8
8
|
import * as mutation from "@src/layout/mutation";
|
|
9
9
|
import * as schema from "@src/layout/schema";
|
|
10
10
|
import * as custom from "@src/layout/custom";
|
|
11
|
+
import * as dialogCustom from "@src/custom/dialog";
|
|
11
12
|
import { checkDocumentStyles } from "@src/layout/style";
|
|
12
13
|
import { electron } from "@src/data/metadata";
|
|
13
14
|
|
|
@@ -200,6 +201,13 @@ export default function (node: Node, source: Source, timestamp: number): Node {
|
|
|
200
201
|
let mediaTag = { tag, attributes };
|
|
201
202
|
dom[call](node, parent, mediaTag, source);
|
|
202
203
|
break;
|
|
204
|
+
case "DIALOG":
|
|
205
|
+
// Use custom module for dialog tracking
|
|
206
|
+
let dialogElement = node as HTMLDialogElement;
|
|
207
|
+
let dialogTracking = dialogCustom.trackDialog(dialogElement, attributes);
|
|
208
|
+
let dialogData = dialogCustom.createDialogNodeData(tag, dialogTracking.attributes);
|
|
209
|
+
dom[call](node, parent, dialogData, source);
|
|
210
|
+
break;
|
|
203
211
|
default:
|
|
204
212
|
custom.check(element.localName);
|
|
205
213
|
let data = { tag, attributes };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import hash from "@src/core/hash";
|
|
3
|
+
|
|
4
|
+
test.describe("Core Utilities - Hash", () => {
|
|
5
|
+
test("hash function should generate consistent hash for same input", () => {
|
|
6
|
+
const input = "test-string";
|
|
7
|
+
const hash1 = hash(input);
|
|
8
|
+
const hash2 = hash(input);
|
|
9
|
+
|
|
10
|
+
expect(hash1).toBe(hash2);
|
|
11
|
+
expect(hash1).toBeTruthy();
|
|
12
|
+
expect(typeof hash1).toBe("string");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("hash function should generate different hashes for different inputs", () => {
|
|
16
|
+
const hash1 = hash("input1");
|
|
17
|
+
const hash2 = hash("input2");
|
|
18
|
+
const hash3 = hash("completely-different-input");
|
|
19
|
+
|
|
20
|
+
expect(hash1).not.toBe(hash2);
|
|
21
|
+
expect(hash2).not.toBe(hash3);
|
|
22
|
+
expect(hash1).not.toBe(hash3);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("hash function should respect precision parameter", () => {
|
|
26
|
+
const input = "test-precision";
|
|
27
|
+
const hashNoPrecision = hash(input);
|
|
28
|
+
const hash16 = hash(input, 16);
|
|
29
|
+
const hash8 = hash(input, 8);
|
|
30
|
+
|
|
31
|
+
expect(hashNoPrecision).toBeTruthy();
|
|
32
|
+
expect(hash16).toBeTruthy();
|
|
33
|
+
expect(hash8).toBeTruthy();
|
|
34
|
+
|
|
35
|
+
// Hash with precision should be different from no precision
|
|
36
|
+
expect(hashNoPrecision).not.toBe(hash16);
|
|
37
|
+
|
|
38
|
+
// Verify precision limits: 2^16 = 65536, 2^8 = 256
|
|
39
|
+
const hash16Num = parseInt(hash16, 36);
|
|
40
|
+
const hash8Num = parseInt(hash8, 36);
|
|
41
|
+
expect(hash16Num).toBeLessThan(65536);
|
|
42
|
+
expect(hash8Num).toBeLessThan(256);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("hash function should handle empty strings", () => {
|
|
46
|
+
const result = hash("");
|
|
47
|
+
expect(result).toBeTruthy();
|
|
48
|
+
expect(typeof result).toBe("string");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("hash function should handle special characters", () => {
|
|
52
|
+
const hash1 = hash("test@example.com");
|
|
53
|
+
const hash2 = hash("test#$%^&*()");
|
|
54
|
+
const hash3 = hash("🎉🎊✨");
|
|
55
|
+
|
|
56
|
+
expect(hash1).toBeTruthy();
|
|
57
|
+
expect(hash2).toBeTruthy();
|
|
58
|
+
expect(hash3).toBeTruthy();
|
|
59
|
+
expect(hash1).not.toBe(hash2);
|
|
60
|
+
expect(hash2).not.toBe(hash3);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("hash function should produce base36 output", () => {
|
|
64
|
+
const result = hash("test-base36");
|
|
65
|
+
// Base36 should only contain 0-9 and a-z
|
|
66
|
+
expect(/^[0-9a-z]+$/.test(result)).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { start, stop, time } from "@src/core/time";
|
|
3
|
+
|
|
4
|
+
test.describe("Time Utilities", () => {
|
|
5
|
+
test("time module should start and stop", () => {
|
|
6
|
+
start();
|
|
7
|
+
const time1 = time();
|
|
8
|
+
|
|
9
|
+
expect(time1).toBeGreaterThanOrEqual(0);
|
|
10
|
+
|
|
11
|
+
stop();
|
|
12
|
+
const time2 = time();
|
|
13
|
+
|
|
14
|
+
// After stop, time should still work but use different baseline
|
|
15
|
+
expect(time2).toBeGreaterThanOrEqual(0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("time function should track elapsed time", async () => {
|
|
19
|
+
start();
|
|
20
|
+
const time1 = time();
|
|
21
|
+
|
|
22
|
+
// Wait a bit
|
|
23
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
24
|
+
|
|
25
|
+
const time2 = time();
|
|
26
|
+
|
|
27
|
+
expect(time2).toBeGreaterThan(time1);
|
|
28
|
+
expect(time2 - time1).toBeGreaterThanOrEqual(50);
|
|
29
|
+
|
|
30
|
+
stop();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("time function should handle null event parameter", () => {
|
|
34
|
+
start();
|
|
35
|
+
const result = time(null);
|
|
36
|
+
|
|
37
|
+
expect(result).toBeGreaterThanOrEqual(0);
|
|
38
|
+
expect(typeof result).toBe("number");
|
|
39
|
+
|
|
40
|
+
stop();
|
|
41
|
+
});
|
|
42
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
2
3
|
"compilerOptions": {
|
|
3
|
-
"module": "esnext",
|
|
4
|
-
"target": "es5",
|
|
5
|
-
"lib": ["es6", "dom", "es2016", "es2017"],
|
|
6
|
-
"moduleResolution": "node",
|
|
7
|
-
"forceConsistentCasingInFileNames": true,
|
|
8
|
-
"noImplicitReturns": true,
|
|
9
|
-
"noUnusedLocals": true,
|
|
10
|
-
"noUnusedParameters": true,
|
|
11
|
-
"resolveJsonModule": true,
|
|
12
|
-
"esModuleInterop": true,
|
|
13
4
|
"baseUrl": ".",
|
|
14
5
|
"paths": {
|
|
15
6
|
"@src/*": ["src/*"],
|
|
16
7
|
"@clarity-types/*": ["types/*"]
|
|
17
8
|
}
|
|
18
9
|
},
|
|
19
|
-
"include":["src/**/*.ts","types/**/*.d.ts", "rollup.config.ts"],
|
|
20
|
-
"exclude": ["
|
|
10
|
+
"include":["src/**/*.ts", "types/**/*.d.ts", "test/**/*.ts", "rollup.config.ts"],
|
|
11
|
+
"exclude": ["node_modules", "build"]
|
|
21
12
|
}
|
package/types/data.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { Time } from "@clarity-types/core";
|
|
2
|
-
export type Target = number | Node;
|
|
3
|
-
export type Token = string | number | number[] | string[] | (string | number)[];
|
|
4
|
-
export type DecodedToken = any | any[];
|
|
2
|
+
export type Target = (number | Node);
|
|
3
|
+
export type Token = (string | number | number[] | string[] | (string | number)[]);
|
|
4
|
+
export type DecodedToken = (any | any[]);
|
|
5
5
|
|
|
6
6
|
export type MetadataCallback = (data: Metadata, playback: boolean, consentStatus?: ConsentState) => void;
|
|
7
7
|
export interface MetadataCallbackOptions {
|
|
8
|
-
callback: MetadataCallback
|
|
9
|
-
wait: boolean
|
|
10
|
-
recall: boolean
|
|
11
|
-
called: boolean
|
|
12
|
-
consentInfo: boolean
|
|
8
|
+
callback: MetadataCallback,
|
|
9
|
+
wait: boolean,
|
|
10
|
+
recall: boolean,
|
|
11
|
+
called: boolean,
|
|
12
|
+
consentInfo: boolean
|
|
13
13
|
}
|
|
14
|
-
export type SignalCallback = (data: ClaritySignal) => void
|
|
14
|
+
export type SignalCallback = (data: ClaritySignal) => void
|
|
15
15
|
|
|
16
16
|
/* Enum */
|
|
17
17
|
export const enum Event {
|
|
@@ -87,7 +87,7 @@ export const enum Event {
|
|
|
87
87
|
Keystrokes = 104,
|
|
88
88
|
BackGesture = 105,
|
|
89
89
|
WebViewStatus = 106,
|
|
90
|
-
AppInstallReferrer = 107
|
|
90
|
+
AppInstallReferrer = 107
|
|
91
91
|
// 200-300 reserved for internal use
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -182,7 +182,7 @@ export const enum Dimension {
|
|
|
182
182
|
Timezone = 34,
|
|
183
183
|
TimezoneOffset = 35,
|
|
184
184
|
Consent = 36,
|
|
185
|
-
InteractionNextPaint = 37
|
|
185
|
+
InteractionNextPaint = 37
|
|
186
186
|
// 200-300 reserved for internal use
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -194,7 +194,7 @@ export const enum Check {
|
|
|
194
194
|
Bytes = 4,
|
|
195
195
|
Collection = 5,
|
|
196
196
|
Server = 6,
|
|
197
|
-
Page = 7
|
|
197
|
+
Page = 7
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
export const enum Code {
|
|
@@ -218,29 +218,29 @@ export const enum Severity {
|
|
|
218
218
|
Info = 0,
|
|
219
219
|
Warning = 1,
|
|
220
220
|
Error = 2,
|
|
221
|
-
Fatal = 3
|
|
221
|
+
Fatal = 3
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
export const enum Upload {
|
|
225
225
|
Async = 0,
|
|
226
|
-
Beacon = 1
|
|
226
|
+
Beacon = 1
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
export const enum BooleanFlag {
|
|
230
230
|
False = 0,
|
|
231
|
-
True = 1
|
|
231
|
+
True = 1
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
export const enum GCMConsent {
|
|
235
235
|
Unknown = 0,
|
|
236
236
|
Granted = 1,
|
|
237
|
-
Denied = 2
|
|
237
|
+
Denied = 2
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
export const enum IframeStatus {
|
|
241
241
|
Unknown = 0,
|
|
242
242
|
TopFrame = 1,
|
|
243
|
-
Iframe = 2
|
|
243
|
+
Iframe = 2
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
export const enum Setting {
|
|
@@ -291,17 +291,17 @@ export const enum Character {
|
|
|
291
291
|
Blank = 32,
|
|
292
292
|
Tab = 9,
|
|
293
293
|
NewLine = 10,
|
|
294
|
-
Return = 13
|
|
294
|
+
Return = 13
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
export const enum ApplicationPlatform {
|
|
298
|
-
WebApp = 0
|
|
298
|
+
WebApp = 0
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
export const enum Constant {
|
|
302
302
|
Auto = "Auto",
|
|
303
303
|
Config = "Config",
|
|
304
|
-
Clarity = "
|
|
304
|
+
Clarity = "clarityGemX",
|
|
305
305
|
Restart = "restart",
|
|
306
306
|
Suspend = "suspend",
|
|
307
307
|
Pause = "pause",
|
|
@@ -380,27 +380,36 @@ export const enum XMLReadyState {
|
|
|
380
380
|
Opened = 1,
|
|
381
381
|
Headers_Recieved = 2,
|
|
382
382
|
Loading = 3,
|
|
383
|
-
Done = 4
|
|
383
|
+
Done = 4
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
export const enum ConsentSource {
|
|
387
387
|
Implicit = 0,
|
|
388
388
|
API = 1,
|
|
389
389
|
GCM = 2,
|
|
390
|
+
TCF = 3,
|
|
391
|
+
APIv1 = 4,
|
|
392
|
+
APIv2 = 5,
|
|
393
|
+
Cookie = 6,
|
|
394
|
+
Default = 7,
|
|
395
|
+
// 100-255 Reserved for CMP integration, both internal and external
|
|
396
|
+
ClarityShopifyPixel = 100,
|
|
397
|
+
ClarityShopifyApp = 101,
|
|
398
|
+
UET = 102
|
|
390
399
|
}
|
|
391
400
|
|
|
392
401
|
/* Helper Interfaces */
|
|
393
402
|
|
|
394
403
|
export interface Payload {
|
|
395
|
-
e: Token[] /* Envelope
|
|
396
|
-
a: Token[][] /* Events that are used for data analysis
|
|
397
|
-
p: Token[][] /* Events that are primarily used for session playback
|
|
404
|
+
e: Token[]; /* Envelope */
|
|
405
|
+
a: Token[][]; /* Events that are used for data analysis */
|
|
406
|
+
p: Token[][]; /* Events that are primarily used for session playback */
|
|
398
407
|
}
|
|
399
408
|
|
|
400
409
|
export interface EncodedPayload {
|
|
401
|
-
e: string /* Envelope
|
|
402
|
-
a: string /* Analytics Payload
|
|
403
|
-
p: string /* Playback Payload
|
|
410
|
+
e: string; /* Envelope */
|
|
411
|
+
a: string; /* Analytics Payload */
|
|
412
|
+
p: string; /* Playback Payload */
|
|
404
413
|
}
|
|
405
414
|
|
|
406
415
|
export interface Metadata {
|
|
@@ -534,8 +543,8 @@ export interface UploadData {
|
|
|
534
543
|
}
|
|
535
544
|
|
|
536
545
|
export interface ClaritySignal {
|
|
537
|
-
type: string
|
|
538
|
-
value?: number
|
|
546
|
+
type: string
|
|
547
|
+
value?: number
|
|
539
548
|
}
|
|
540
549
|
|
|
541
550
|
export interface PerformanceEventTiming extends PerformanceEntry {
|
|
@@ -557,7 +566,7 @@ export interface ConsentState {
|
|
|
557
566
|
export const enum ConsentType {
|
|
558
567
|
None = 0,
|
|
559
568
|
Implicit = 1,
|
|
560
|
-
General = 2
|
|
569
|
+
General = 2
|
|
561
570
|
}
|
|
562
571
|
|
|
563
572
|
export interface ConsentData {
|
package/types/index.d.ts
CHANGED
|
@@ -11,12 +11,12 @@ interface Clarity {
|
|
|
11
11
|
pause: () => void;
|
|
12
12
|
resume: () => void;
|
|
13
13
|
upgrade: (key: string) => void;
|
|
14
|
-
consent: () => void;
|
|
15
|
-
consentv2: () => void;
|
|
14
|
+
consent: (status?: boolean) => void;
|
|
15
|
+
consentv2: (consentState?: Data.ConsentState, source?: number) => void;
|
|
16
16
|
event: (name: string, value: string) => void;
|
|
17
17
|
set: (variable: string, value: string | string[]) => void;
|
|
18
18
|
identify: (userId: string, sessionId?: string, pageId?: string, userHint?: string) => void;
|
|
19
|
-
metadata: (callback: Data.MetadataCallback, wait?: boolean) => void;
|
|
19
|
+
metadata: (callback: Data.MetadataCallback, wait?: boolean, recall?: boolean, consentInfo?: boolean) => void;
|
|
20
20
|
signal: (callback: Data.SignalCallback) => void;
|
|
21
21
|
}
|
|
22
22
|
|