clarity-js
Version:
An analytics library that uses web page interactions to generate aggregated insights
163 lines (149 loc) • 5.28 kB
text/typescript
import { Core, Data, decode, Interaction, Layout } from "clarity-decode";
import * as fs from 'fs';
import * as url from 'url';
import * as path from 'path';
import { Browser, Page, chromium } from 'playwright';
export async function launch(): Promise<Browser> {
return chromium.launch({ headless: true, args: ['--no-sandbox'] });
}
export async function markup(page: Page, file: string, override: Core.Config = null): Promise<string[]> {
const htmlPath = path.resolve(__dirname, `./html/${file}`);
const htmlFileUrl = url.pathToFileURL(htmlPath).toString();
const html = fs.readFileSync(htmlPath, 'utf8');
await Promise.all([
page.goto(htmlFileUrl),
page.waitForNavigation()
]);
await page.setContent(html.replace("</body>", `
<script>
window.payloads = [];
${fs.readFileSync(path.resolve(__dirname, `../build/clarity.min.js`), 'utf8')};
clarity("start", ${config(override)});
</script>
</body>
`));
await page.hover("#two");
await page.click("#child");
await page.locator('#search').fill('');
await page.locator('#search').type('query with numb3rs');
await page.locator('#pwd').type('p1ssw0rd');
await page.locator('#eml').fill('');
await page.locator('#eml').type('hello@world.com');
await page.waitForFunction("payloads && payloads.length > 2");
return await page.evaluate('payloads');
}
export function clicks(decoded: Data.DecodedPayload[]): Interaction.ClickEvent[] {
let output: Interaction.ClickEvent[] = [];
for (let i = decoded.length - 1; i >= 0; i--) {
if (decoded[i].click) {
for (let j = 0; j < decoded[i].click.length;j++)
{
output.push(decoded[i].click[j]);
}
}
}
return output;
}
export function inputs(decoded: Data.DecodedPayload[]): Interaction.InputEvent[] {
let output: Interaction.InputEvent[] = [];
for (let i = decoded.length - 1; i >= 0; i--) {
if (decoded[i].input) {
for (let j = 0; j < decoded[i].input.length;j++)
{
output.push(decoded[i].input[j]);
}
}
}
return output;
}
export function changes(decoded: Data.DecodedPayload[]): Interaction.ChangeEvent[] {
let output: Interaction.ChangeEvent[] = [];
for (let i = decoded.length - 1; i >= 0; i--) {
if (decoded[i].change) {
for (let j = 0; j < decoded[i].change.length;j++)
{
output.push(decoded[i].change[j]);
}
}
}
return output;
}
export function node(decoded: Data.DecodedPayload[], key: string, value: string | number, tag: string = null): Layout.DomData {
let sub = null;
// Exploding nested keys into key and sub key
if (key.indexOf(".") > 0) {
const parts = key.split(".");
if (parts.length === 2) {
key = parts[0];
sub = parts[1];
}
}
// Walking over the decoded payload to find the right match
for (let i = decoded.length - 1; i >= 0; i--) {
if (decoded[i].dom) {
for (let j = 0; j < decoded[i].dom.length; j++) {
if (decoded[i].dom[j].data) {
for (let k = 0; k < decoded[i].dom[j].data.length; k++) {
let d = decoded[i].dom[j].data[k];
if ((sub && d[key] && d[key][sub] === value) ||
(d[key] && d[key] === value)) {
if ((tag && d.tag === tag) || tag === null) {
return d;
}
}
}
}
}
}
}
return null;
}
export function text(decoded: Data.DecodedPayload[], id: string): string {
let parent = node(decoded, "attributes.id", id);
if (parent) {
let child = node(decoded, "parent", parent.id, "*T");
if (child && child.value) {
return child.value;
}
}
return null;
}
function config(override: Core.Config): string {
const settings = {
delay: 100,
content: true,
fraud: [],
regions: [],
mask: [],
unmask: [],
upload: payload => { window["payloads"].push(payload); window["clarity"]("upgrade", "test"); }
}
// Process overrides
if (override){
for (let key of Object.keys(override)) {
settings[key] = override[key];
}
}
// Serialize configuration
let output = "";
for (let key of Object.keys(settings)) {
switch (key) {
case "upload":
output += `${JSON.stringify(key)}: ${settings[key].toString()},`;
break;
case "projectId":
case "mask":
case "unmask":
case "regions":
case "cookies":
case "fraud":
output += `${JSON.stringify(key)}: ${JSON.stringify(settings[key])},`;
break;
default:
output += `${JSON.stringify(key)}: ${settings[key]},`;
break;
}
}
output += `"projectId": "test"`;
return "{" + output + "}";
}