UNPKG

crunchit

Version:

Autotrack the events from your users

276 lines (259 loc) 9.49 kB
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 };