crunchit
Version:
Autotrack the events from your users
276 lines (259 loc) • 9.49 kB
JavaScript
const getIntent = require("./lib/intent");
const identify = require("./lib/identify");
const BatchProcessor = require("./lib/BatchProcessor");
const getUserPreferences = require("./lib/user_preferences");
const generateFingerprint = require("./lib/fingerprint");
const getElementXPath = require("./lib/xpathGenerator");
const handleCustomLabeling = require("./lib/handleCustomLabeling");
const { throttle, handleScroll } = require("./lib/scrollHandler");
const { v4: uuidv4 } = require("uuid");
const sendMessageToIframe = require("./lib/utils/sendMessageToIframe");
const { isAlreadySelected } = require("./lib/activateHighlighter");
var batchProcessor,
browserDetails,
ipapi,
distinctId,
sessionId,
isSelectorMode,
previousURL;
let clickHistory = {};
function isLabelerElement(targetElement) {
while (targetElement) {
if (
targetElement.id === "CRUNCH_IFRAME_CONTAINER" ||
targetElement.id === "CRUNCH_NAVBAR_CONTAINER"
) {
return true;
}
targetElement = targetElement.parentNode;
}
return false;
}
function autocapture() {
return function (event) {
let isLocating = window.localStorage.getItem("CRUNCH_LOCATING");
if (isLocating) {
event.preventDefault();
}
// ! ! Getting the stuff from event
let { intent: userIntent, allVariables } = getIntent(event);
let timestamp = new String(new Date() * 1);
let className = event.target.className.toString();
let id = event.target.id.toString();
let xpath = getElementXPath(event.target);
let dataAttributes = Object.assign({}, event.target.dataset);
// ! If we don't have a history for this element, create one
if (!clickHistory[xpath]) {
clickHistory[xpath] = [];
}
// ! Add the current click to the history
clickHistory[xpath].push(timestamp);
// ! Filter the history to only include clicks in the last second (1000ms)
let recentClicks = clickHistory[xpath].filter((t) => timestamp - t <= 1000);
// ! If there are more than 4 clicks in the last second, it's a rage click
if (recentClicks.length > 4) {
// console.log(`Rage click detected on element: ${xpath}`);
// ! Handle the rage click event here...
// ! Perhaps by sending an event to your batch processor:
let rageClickEvent = {
eventType: "RAGE_CLICK",
userIntent,
timestamp,
className,
cssId: id,
currentHref: window.location.href,
browserDetails,
ipDetails: ipapi,
dataAttributes,
userIntentVariables: allVariables,
};
batchProcessor.addAutoCaptureActivity(xpath, rageClickEvent);
}
// ! Replace the history for this element with only the recent clicks
clickHistory[xpath] = recentClicks;
// ! Captured details
const capturedDetails = {
eventType: "CLICK",
userIntent, // ! long string
timestamp, // ! unix
className, // ! string
cssId: id, // ! string
currentHref: window.location.href, // ! string
browserDetails,
ipDetails: ipapi, // ! object
dataAttributes,
userIntentVariables: allVariables,
};
if (isLocating) {
console.log("Reached here 1");
// ! locating mode - capture the click and send the details to iframe
let detailsToSend = {
userIntent, // ! long string
xpath,
className, // ! string
cssId: id, // ! string
currentHref: window.location.href, // ! string
dataAttributes,
userIntentVariables: allVariables,
};
let isSelectedElement = isAlreadySelected(xpath);
if (isSelectedElement)
detailsToSend["isSelectedElement"] = isSelectedElement;
// ! Send to the iframe
sendMessageToIframe({
type: "LOCATED_ELEMENT",
message: JSON.stringify(detailsToSend),
});
console.log("Reached here 2");
// ! Remove the locating tip
let crunchLocatingTip = window.document.getElementById(
"CRUNCH_IFRAME_CONTAINER_TIP"
);
crunchLocatingTip.style.display = "none";
window.crunchSelectedElement = event.target;
// ! remove localstorage data
window.localStorage.removeItem("CRUNCH_LOCATING");
// ! Remove event listeners
document.removeEventListener("mouseover");
window.document.onmouseover = null;
document.addEventListener("click", autocapture(), true); // ! The third argument set to true means that the event is captured, not bubbled
} else {
// ! Add to batch
batchProcessor.addAutoCaptureActivity(xpath, capturedDetails);
}
};
}
function track(eventName, eventProperties) {
let timestamp = new Date() * 1;
// ! Captured details
const defaultDetails = {
eventType: "CLICK",
eventName,
timestamp, // ! unix
currentHref: window.location.href, // ! string
browserDetails,
ipDetails: ipapi, // ! object
};
const capturedDetails = { ...defaultDetails, ...eventProperties };
batchProcessor.addManualCaptureActivity(capturedDetails);
}
function init(APP_ID) {
// ! Use APP_ID here if necessary
console.log("Ready to crunch");
// ! Set up a click event listener in the capture phase
document.addEventListener("click", autocapture(), true); // ! The third argument set to true means that the event is captured, not bubbled
// ! Set up a scroll event listener
window.addEventListener(
"scroll",
() => throttle(handleScroll, 4000, batchProcessor, browserDetails, ipapi),
true
);
// ! Listen for URL changes
// window.addEventListener("hashchange", (event) => {
// console.log(`Hash changed to ${event.newURL}`);
// });
window.history.pushState = new Proxy(window.history.pushState, {
apply: (target, thisArg, argArray) => {
// trigger here what you need
let URL = argArray[argArray.length - 1];
// console.log("history push", URL);
batchProcessor.addAutoCaptureActivity(URL, {
eventType: "PAGE_VIEW",
currentHref: URL,
timestamp: new String(new Date() * 1),
userIntent: `User visited ${URL}`,
browserDetails,
ipDetails: ipapi, // ! object
});
return target.apply(thisArg, argArray);
},
});
// ! Resetting the crunch locating tip
window.localStorage.removeItem("CRUNCH_LOCATING_TIP");
}
function letscrunch(app_id, config = { sessionTimeout: 1800000 }) {
// ! You can use app_id here if needed
window.localStorage.setItem("CRUNCH_APP_ID", app_id);
// ! Attach wrapOnClick to the global object
if (typeof window !== "undefined") {
window.localStorage.removeItem("CRUNCH_LOCATING");
// ! Client-side
window.crunchit = { autocapture, identify, track };
// ! Generate session_id [will be on the backend]
sessionId = window.sessionStorage.getItem("CRUNCH_SESSION_ID");
if (!sessionId) {
sessionId = uuidv4();
window.sessionStorage.setItem("CRUNCH_SESSION_ID", sessionId);
}
// ! Generate distinct_id [will be on the backend]
distinctId = window.localStorage.getItem("DISTINCT_ID");
if (!distinctId) {
distinctId = uuidv4();
window.localStorage.setItem("DISTINCT_ID", distinctId);
}
// ! Initializing batchProcessor
batchProcessor = new BatchProcessor(
10,
5000,
distinctId,
app_id,
sessionId,
config.sessionTimeout
);
// ! If the user closes the browser, save the current batch
window.addEventListener("beforeunload", () => {
// console.log("Leaving page", window.location.href);
batchProcessor.handleBrowserClose();
handleScroll(batchProcessor, browserDetails, ipapi);
});
// ! Setting the browser details
browserDetails = {
userAgent: window.navigator?.userAgent,
vendor: window.navigator?.vendor,
languages: window.navigator?.languages,
networkSpeed: window.navigator?.connection?.downlink,
preferences: getUserPreferences(),
fingerprint: generateFingerprint(distinctId),
};
// ! Setting the selector mode status
const urlParams = new URLSearchParams(window.location.search);
isSelectorMode = urlParams.has("selectorMode");
let selectorId = urlParams.get("selectorId");
let redirectUrl = urlParams.get("redirectUrl");
window.localStorage.setItem("CRUNCH_REDIRECT_URL", redirectUrl);
let xpath = urlParams.get("xpath");
if (isSelectorMode) {
if (selectorId) handleCustomLabeling(selectorId);
if (xpath) handleCustomLabeling(false, xpath);
else handleCustomLabeling(false, false);
}
// ! IPAPI
ipapi = window.localStorage.getItem("IPAPI_RESPONSE");
if (!ipapi)
fetch("https://ipapi.co/json/")
.then((res) => {
return res.json();
})
.then((data) => {
// console.log("ipapi res", data);
window.localStorage.setItem("IPAPI_RESPONSE", JSON.stringify(data));
ipapi = data;
})
.catch((err) => {
// console.log("ipapi err", err);
});
else ipapi = JSON.parse(ipapi);
// ! Set the previous page url
// previousURL = document.location.pathname;
// console.log("previousURL", previousURL);
// ! Initialise the app
init(app_id);
} else {
// ! Server-side
global.crunchit = { autocapture, identify, track };
}
}
if (typeof window !== "undefined") {
window.letscrunch = letscrunch;
}
module.exports = { letscrunch, identify, track };