Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V alihashish/actions #446

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/clarity-devtools/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ export default function(): Core.Config {
],
fraud: true,
checksum: [],
liveSignalsActionConfigs: new Map<string, string>(),
liveSignalsActionType: null,
liveSignalsCustomAction: (): void => { },
};
}
5 changes: 4 additions & 1 deletion packages/clarity-devtools/src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ function wireup(settings: any): string {
unmask: "$__unmask__$",
content: "$__showText__$",
upload: (data: string): void => { window.postMessage({ action: "upload", payload: data }, "*"); },
projectId: "devtools"
projectId: "devtools",
liveSignalsActionConfigs: new Map<string, string>(),
liveSignalsActionType: 0,
liveSignalsCustomAction: () => {}
});
}).toString();
Object.keys(settings).forEach(s => code = code.replace(`"$__${s}__$"`, JSON.stringify(settings[s])));
Expand Down
2 changes: 1 addition & 1 deletion packages/clarity-js/src/clarity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as interaction from "@src/interaction";
import * as layout from "@src/layout";
import * as performance from "@src/performance";
export { version };
export { consent, event, identify, set, upgrade, metadata } from "@src/data";
export { consent, event, identify, set, upgrade, metadata, signal } from "@src/data";
export { hashText } from "@src/layout";

const modules: Module[] = [diagnostic, layout, interaction, performance];
Expand Down
124 changes: 124 additions & 0 deletions packages/clarity-js/src/core/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import config from "@src/core/config";
import { signal } from "@src/data/signal";
import { LiveSignalsActionType } from "@clarity-types/data";

export function determineAction(): void {
switch (config.liveSignalsActionType) {
case LiveSignalsActionType.NoAction:
// No action
signal(null);
break;
case LiveSignalsActionType.CustomAction:
// A custom action written by the client
signal(config.liveSignalsCustomAction);
break;
case LiveSignalsActionType.ContactUsDialogueBox:
signal(showDialog);
break;
case LiveSignalsActionType.ContactUsAlert:
signal(showAlert);
break;
default:
signal(null);
break;
}
}

function showAlert(): void {
// Shows a blocking warning
var alertMessage: string = "It seems you might be facing some troubles. Please contact us at: ";
if ("email" in config.liveSignalsActionConfigs)
{
// The client email for example
alertMessage += config.liveSignalsActionConfigs["email"];
}
alert(alertMessage);
}

function showDialog(): void {
// Shows a non-blocking dialog box
// This function may be further optimized and cleaned up
var dialogMessage: string = "It seems you might be facing some troubles. Please contact us at: ";
if ("email" in config.liveSignalsActionConfigs)
{
// The client email for example
dialogMessage += config.liveSignalsActionConfigs["email"];
}

var lightmode : boolean = false;
if ("lightmode" in config.liveSignalsActionConfigs)
{
if (typeof(config.liveSignalsActionConfigs["lightmode"]) == "string")
{
lightmode = (config.liveSignalsActionConfigs["lightmode"].toLowerCase() == "true")? true : false;
}
}

try {
// Tries to get the dialog element if it exists
const dialog = document.getElementById("zdialog") as HTMLDialogElement; // using a weird ID to avoid potential conflict with existing IDs
dialog.innerHTML = dialogMessage;
styleDialog(dialog, lightmode);
} catch (err) {
// If not, creates a new dialog element
const dialog = document.createElement("dialog") as HTMLDialogElement;
// dialog.innerHTML = "Non-Blocking test dialog 2";
dialog.innerHTML = dialogMessage;
styleDialog(dialog, lightmode);
}
}

function closeDialog(): void {
// No need to try catch, as this will only be called after
// showDialog, which means the dialog element exists
const dialog = document.getElementById("zdialog") as HTMLDialogElement;
dialog.style.display = "none";
}

function styleDialog(dialog: HTMLDialogElement, lightMode = false): void {
dialog.style.display = "block";
dialog.id = "zdialog"; // Choosing a weird name to avoid any potential conflicts with other elements

// Apply common styling
dialog.style.position = "fixed";
dialog.style.top = "50%";
dialog.style.left = "50%";
dialog.style.transform = "translate(-50%, -50%)";
dialog.style.margin = "0";
dialog.style.padding = "20px";
dialog.style.width = "80%";
dialog.style.textAlign = "center";
dialog.style.zIndex = "10000"; // Set the z-index to 10000 to make sure it is on top of everything

// Apply mode-specific styling
if (lightMode) {
dialog.style.backgroundColor = "rgba(39, 54, 223, 0.93)";
dialog.style.color = "rgb(245, 245, 245)";
dialog.style.border = "2px solid #ddd";
} else {
dialog.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
dialog.style.color = "#fff";
dialog.style.border = "2px solid #666";
}

// Create a close button
const close = document.createElement("button");
close.innerHTML = "OK";
close.onclick = closeDialog;
dialog.appendChild(close);

// Apply styling to the close button
close.style.backgroundColor = "#4CAF50";
close.style.color = "#fff";
close.style.padding = "10px 20px";
close.style.border = "none";
close.style.borderRadius = "4px";
close.style.fontSize = "16px";
close.style.cursor = "pointer";
close.style.textShadow = "1px 1px #000";
close.style.display = "block";
close.style.margin = "auto";
close.style.marginTop = "20px";

document.body.appendChild(dialog); // Append dialog to the body element
}
5 changes: 4 additions & 1 deletion packages/clarity-js/src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ let config: Config = {
fallback: null,
upgrade: null,
action: null,
dob: null
dob: null,
liveSignalsActionConfigs: new Map<string, string>(),
liveSignalsActionType: null,
liveSignalsCustomAction: null
};

export default config;
12 changes: 11 additions & 1 deletion packages/clarity-js/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,17 @@ export function config(override: Config): boolean {
// Process custom configuration overrides, if available
if (override === null || status) { return false; }
for (let key in override) {
if (key in configuration) { configuration[key] = override[key]; }
if (key in configuration) {
if (key === "liveSignalsCustomAction") {
// Check if there are some configs sent by the client
let liveSignalsConfigs: string = ('liveSignalsActionConfigs' in override)? "variables = override['liveSignalsActionConfigs'];" : "";
// This way, the client can use their parameters in their custom action through variables["keyName"]
let liveSignalsCustomCode: string = String(override[key]).replace(/\n/g, "\\n");;
configuration[key] = eval("() => {" + liveSignalsConfigs + liveSignalsCustomCode + "}");
continue;
}
configuration[key] = override[key];
}
}
return true;
}
Expand Down
34 changes: 34 additions & 0 deletions packages/clarity-js/src/core/signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ClaritySignal } from "@clarity-types/data";
import { signalCallback } from "@src/data/signal";
import { determineAction } from "@src/core/action";

function parseSignals(signalsString: string): ClaritySignal[] {
const signalsJson: ClaritySignal[] = JSON.parse(signalsString)
return signalsJson;
}


const LiveSignalType: Map<string, number> = new Map([
["BotSignal", 0],
["FrustrationScore", 1],
["PurchaseProbability", 2]
]);

export function signalEvent(signalsString: string) {
let botThreshold : number = 0.5;
let frustrationThreshold : number = 0.65;
let purchaseThreshold : number = 0.5;
const thresholds : number[] = [botThreshold, frustrationThreshold, purchaseThreshold];
try {
const signals = parseSignals(signalsString);

// Check whether user is frustrated or not
// if yes, execute an action based on configuration
if(!signalCallback) determineAction();
signals.forEach(signal => {
if (signal["value"] > thresholds[LiveSignalType.get(signal["type"])]) {
signalCallback(signal);
}
})
} catch (err) { }
}
1 change: 1 addition & 0 deletions packages/clarity-js/src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export { event } from "@src/data/custom";
export { consent, metadata } from "@src/data/metadata";
export { upgrade } from "@src/data/upgrade";
export { set, identify } from "@src/data/variable";
export { signal } from "@src/data/signal";

const modules: Module[] = [baseline, dimension, variable, limit, summary, metadata, envelope, upload, ping, upgrade, extract];

Expand Down
7 changes: 7 additions & 0 deletions packages/clarity-js/src/data/signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { SignalCallback } from "@clarity-types/data";

export let signalCallback: SignalCallback = null;

export function signal(cb: SignalCallback): void {
signalCallback = cb
}
4 changes: 4 additions & 0 deletions packages/clarity-js/src/data/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BooleanFlag, Check, Constant, EncodedPayload, Event, Metric, Setting, T
import * as clarity from "@src/clarity";
import config from "@src/core/config";
import measure from "@src/core/measure";
import { signalEvent } from "@src/core/signal";
import { time } from "@src/core/time";
import { clearTimeout, setTimeout } from "@src/core/timeout";
import compress from "@src/data/compress";
Expand Down Expand Up @@ -267,6 +268,9 @@ function response(payload: string): void {
case Constant.Extract:
if (parts.length > 1) { extract.trigger(parts[1]); }
break;
case Constant.Signal:
if (parts.length > 1) { signalEvent(parts[1]) }
break;
}
}
}
3 changes: 3 additions & 0 deletions packages/clarity-js/types/core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ export interface Config {
upgrade?: (key: string) => void;
action?: (key: string) => void;
dob?: number;
liveSignalsActionConfigs?: Map<string, string>;
liveSignalsActionType?: number;
liveSignalsCustomAction?: Data.SignalCallback;
}

export const enum Constant {
Expand Down
17 changes: 17 additions & 0 deletions packages/clarity-js/types/data.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface MetadataCallbackOptions {
callback: MetadataCallback,
wait: boolean
}
export type SignalCallback = (data: ClaritySignal) => void;

/* Enum */
export const enum Event {
Expand Down Expand Up @@ -273,6 +274,7 @@ export const enum Constant {
End = "END",
Upgrade = "UPGRADE",
Action = "ACTION",
Signal = "SIGNAL",
Extract = "EXTRACT",
UserHint = "userHint",
UserType = "userType",
Expand Down Expand Up @@ -313,6 +315,15 @@ export const enum XMLReadyState {
Done = 4
}

/* Live Signals */

export const enum LiveSignalsActionType {
NoAction = 0,
CustomAction = 1,
ContactUsDialogueBox = 2,
ContactUsAlert = 3
}

/* Helper Interfaces */

export interface Payload {
Expand Down Expand Up @@ -439,3 +450,9 @@ export interface UploadData {
attempts: number;
status: number;
}

export interface ClaritySignal {
type: string
timestamp?: number
value?: number
}
1 change: 1 addition & 0 deletions packages/clarity-js/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface Clarity {
set: (variable: string, value: string | string[]) => void;
identify: (userId: string, sessionId?: string, pageId?: string, userHint?: string) => void;
metadata: (callback: Data.MetadataCallback, wait?: boolean) => void;
signal: (callback: Data.SignalCallback) => void;
}

interface Selector {
Expand Down