UNPKG

pulsex

Version:

A lightweight and powerful JavaScript library for tracking user activity on websites. Easily monitor user interactions, including page visits, clicks, time spent, and engagement patterns. Designed for flexibility and performance, PulseX integrates seamles

292 lines (291 loc) 11.9 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { sessionKey } from "./constants"; import { generateSessionId, saveToLocalStorage, loadQueueFromLocalStorage, deepCopy, } from "./utils"; export default class PulseX { constructor(config) { this.queue = []; this.engagementTrackingTasks = []; this.config = Object.assign({ maxQueueSize: 100 }, config); const storedSession = localStorage.getItem(sessionKey); if (storedSession) { this.sessionId = storedSession; } else { this.sessionId = generateSessionId(); localStorage.setItem(sessionKey, this.sessionId); } this.loadQueue(); } start() { this.startEngagementTracking(); this.setupVisibilityListener(); } /** * Tracks user engagement for a specific section of the webpage. * * @param {string} sectionId - The ID of the HTML element to track. * @param {number} [threshold] - The minimum time (in milliseconds) the user must view the section for it to be considered engaged. * * @remarks * - If the element with the given `sectionId` is not found, a warning will be logged. * - The section will be added to the tracking list, and engagement will be recorded only if the user views it for at least `threshold` milliseconds. * - If `threshold` is not provided, it may default to a predefined value in the tracking system. * * @example * ```ts * tracker.trackSectionEngagement("homepage", 3000); // Track "homepage" section with a 3-second threshold * ``` */ trackSectionEngagement(sectionId, threshold) { const element = document.getElementById(sectionId); if (!element) { console.warn(`PulseX: Element with ID ${sectionId} not found`); return; } const task = { element, threshold, }; // Add this section to the list of sections to track this.engagementTrackingTasks.push(task); } startEngagementTracking() { // Track user activity on those sections // like time of section on viewport, clicks on the section, hover on the section, etc. const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const sectionId = entry.target.id; const data = { sectionId, startTime: Date.now(), endTime: 0, totalDuration: 0, }; const payload = this.createBasePayload("section-engagement", data); this.queue.push(payload); } // stored the exiting time of the section after the user exits the section else { const sectionId = entry.target.id; const startedEvent = this.queue.find((event) => event.data.endTime === 0 && event.data.sectionId === sectionId); if (startedEvent) { startedEvent.data.endTime = Date.now(); startedEvent.data.totalDuration = startedEvent.data.endTime - startedEvent.data.startTime; } // get the task for the section to calculate the threshold const taskIndex = this.engagementTrackingTasks.findIndex((task) => task.element.id === sectionId); const task = this.engagementTrackingTasks[taskIndex]; if (task && startedEvent && (startedEvent === null || startedEvent === void 0 ? void 0 : startedEvent.data.totalDuration) >= (task.threshold || 0)) { // if the user has viewed the section for the minimum threshold time // then we can consider the user has engaged with the section this.saveQueueToLocalStorage(); } else if (startedEvent) { // if the user has not viewed the section for the minimum threshold time // then we can remove the event from the queue // this.queue.splice(this.queue.indexOf(startedEvent), 1); this.queue.splice(taskIndex, 1); } } }); }, { threshold: 0.5, }); this.engagementTrackingTasks.forEach((task) => { if (task) { observer.observe(task.element); } }); } createBasePayload(type, data) { return { _id: Math.random().toString(16).substring(2, 18), sessionId: this.sessionId, type, data, pageUrl: window.location.href, referrer: document.referrer, createdAt: new Date().toISOString(), }; } getQueue() { const queue = localStorage.getItem("pulsex_events"); return queue ? JSON.parse(queue) : []; } sendData() { return __awaiter(this, void 0, void 0, function* () { const queue = this.getQueue(); if (queue.length === 0) return; const eventsToSend = deepCopy(queue); this.clearQueue(); console.log("PulseX: Sending data...", eventsToSend); try { const response = yield fetch(this.config.apiEndpoint, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(eventsToSend), }); if (!response.ok) { throw new Error("Failed to send data"); } } catch (error) { console.error("PulseX: Error sending data:", error); this.queue.unshift(...eventsToSend); this.saveQueueToLocalStorage(); } }); } setupVisibilityListener() { // empty the queue when the tab is closed window.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") { this.sendData(); } }); } saveQueueToLocalStorage() { saveToLocalStorage(this.queue); } clearQueue() { this.queue = []; saveToLocalStorage([]); } loadQueue() { this.queue = loadQueueFromLocalStorage(); } // New Methods for Additional Event Tracking /** * Tracks click events on a specified element. * * @param {string} elementId - The ID of the element to track clicks on. * * @example * ```ts * tracker.trackClick("loginBtn"); // Tracks click events on element with ID 'loginBtn' * ``` */ trackClick(elementId) { const element = document.getElementById(elementId); if (!element) { console.warn(`PulseX: Element with ID ${elementId} not found`); return; } element.addEventListener("click", (event) => { const target = event.target; const clickData = { elementId: target.id, textContent: target.textContent || "", timestamp: Date.now(), x: event.clientX, y: event.clientY, button: event.button, }; const payload = this.createBasePayload("click", clickData); this.savePayloadToLocalStorage(payload); }); } /** * Tracks hover events on a specified element. * Records hover duration and, if a click occurs during the hover, includes the click data. * * @param {string} elementId - The ID of the element to track hover events on. * * @example * ```ts * tracker.trackHover("product-card"); // Tracks hover events on element with ID 'product-card' * ``` */ trackHover(elementId) { const element = document.getElementById(elementId); if (!element) { console.warn(`PulseX: Element with ID ${elementId} not found`); return; } let hoverStartTime = 0; let clickOccurred = false; let clickData = null; element.addEventListener("mouseover", () => { hoverStartTime = Date.now(); clickOccurred = false; clickData = null; }); element.addEventListener("click", (event) => { clickOccurred = true; const target = event.target; clickData = { elementId: target.id, textContent: target.textContent || "", timestamp: Date.now(), x: event.clientX, y: event.clientY, button: event.button, }; }); element.addEventListener("mouseout", () => { const hoverEndTime = Date.now(); const hoverDuration = hoverEndTime - hoverStartTime; const hoverData = { elementId, startTime: hoverStartTime, endTime: hoverEndTime, hoverDuration, clicked: clickOccurred, clickData: clickOccurred ? clickData : null, }; const payload = this.createBasePayload("hover", hoverData); this.savePayloadToLocalStorage(payload); }); } /** * Tracks form submission events on a specified form. * * @param {string} formId - The ID of the form to track submissions for. * * @example * ```ts * tracker.trackFormSubmission("login-form"); // Tracks form submissions on form with ID 'login-form' * ``` */ trackFormSubmission(formId) { const form = document.getElementById(formId); if (!form) { console.warn(`PulseX: Form with ID ${formId} not found`); return; } form.addEventListener("submit", (event) => { event.preventDefault(); const inputValues = {}; new FormData(form).forEach((value, key) => { inputValues[key] = value.toString(); }); const formData = { formId, timestamp: Date.now(), inputValues, }; const payload = this.createBasePayload("form-submission", formData); this.savePayloadToLocalStorage(payload); }); } savePayloadToLocalStorage(payload) { const currDataInLocalStorage = this.getQueue(); currDataInLocalStorage.push(payload); saveToLocalStorage(currDataInLocalStorage); } }