page-tracker-logger
Version:
A lightweight client-side JavaScript utility to track user behavior like clicks, tab switches, typing, idle time, and more.
176 lines (153 loc) • 4.93 kB
JavaScript
export default async function pageTracker(options = {}) {
const {
switchTabTrace = true,
clickTrace = true,
typingTrace = true,
externalPasteTrace = true,
localStorage: {
save = false,
key = "activity"
} = {},
fetch: {
senddata = false,
url = ""
} = {}
} = options;
let currRoute = window.location.href;
let visitPageTime = Date.now();
let RouteStatus = { url: "", visitPageTime: null, levePage: null, duration: null };
let clickLogs = [], switchTab = [], countswitchTab = 0, leaveTab = null;
let typingStatus = [], typing = false, typingStartTime = null, typingTimerId = null;
let copyStatusfromExtrenalSource = [], lastCopiedText = "";
let idleLogs = [], totalIdleTime = 0, idleStartTime = null, idleTimer = null;
let idleThreshold = 10000;
function toReadableTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins > 0 ? mins + " min " : ""}${secs} sec`;
}
if (clickTrace) {
document.addEventListener("click", (e) => {
let ele = e.target;
clickLogs.push({
tag: ele?.tagName,
innerText: ele?.innerText || "",
id: ele?.id || "",
className: ele.className || "",
timestamp: Date.now(),
});
});
}
if (switchTabTrace) {
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
countswitchTab++;
leaveTab = Date.now();
}
if (!document.hidden && leaveTab) {
let duration = (Date.now() - leaveTab) / 1000;
switchTab.push({ countswitchTab, duration: toReadableTime(duration) });
leaveTab = null;
}
});
}
if (typingTrace) {
document.addEventListener("keydown", (e) => {
if (e.key.length === 1 || e.key === "Backspace" || e.key === "Enter") {
if (!typing) {
typing = true;
typingStartTime = Date.now();
}
clearTimeout(typingTimerId);
typingTimerId = setTimeout(() => {
typing = false;
const duration = (Date.now() - typingStartTime) / 1000;
typingStatus.push({
timestampStartAt: typingStartTime,
timestampEndAt: Date.now(),
duration: toReadableTime(duration),
});
typingStartTime = null;
}, 3000);
}
});
}
if (externalPasteTrace) {
document.addEventListener("copy", () => {
lastCopiedText = window.getSelection().toString();
});
document.addEventListener("paste", (e) => {
const pastedText = e.clipboardData.getData("text");
const isFromExternal = pastedText !== lastCopiedText;
copyStatusfromExtrenalSource.push({
type: "paste",
content: pastedText,
fromExternal: isFromExternal,
timestamp: Date.now(),
});
});
}
function startIdleTimer() {
clearTimeout(idleTimer);
idleTimer = setTimeout(() => {
idleStartTime = Date.now();
}, idleThreshold);
}
function onUserActivity() {
if (idleStartTime !== null) {
const idleDuration = (Date.now() - idleStartTime) / 1000;
totalIdleTime += idleDuration;
idleLogs.push({
idleStart: idleStartTime,
idleEnd: Date.now(),
duration: toReadableTime(idleDuration),
});
idleStartTime = null;
}
startIdleTimer();
}
["mousemove", "keydown", "click", "scroll", "touchstart"].forEach((event) => {
document.addEventListener(event, onUserActivity);
});
startIdleTimer();
window.addEventListener("beforeunload", async () => {
if (idleStartTime !== null) {
const idleDuration = (Date.now() - idleStartTime) / 1000;
totalIdleTime += idleDuration;
idleLogs.push({
idleStart: idleStartTime,
idleEnd: Date.now(),
duration: toReadableTime(idleDuration),
});
}
RouteStatus.url = currRoute;
RouteStatus.visitPageTime = visitPageTime;
RouteStatus.levePage = Date.now();
RouteStatus.duration = toReadableTime((RouteStatus.levePage - visitPageTime) / 1000);
const finalData = {
RouteStatus,
clickLogs,
switchTab,
typingStatus,
copyStatusfromExtrenalSource,
idleLogs,
totalIdleTime: toReadableTime(totalIdleTime),
};
if (save && key) {
localStorage.setItem(key, JSON.stringify(finalData));
}
if (senddata && url) {
try {
await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(finalData),
});
} catch (err) {
console.error("Failed to send tracking data:", err);
}
}
});
}