watusertracking
Version:
User activity tracking package
273 lines (271 loc) • 12.2 kB
TypeScript
export function insitella() {
const apiBaseURL = "http://192.168.10.152";
const formatDate = (date) => date.toISOString().split("T")[0];
const generateUserName = (length) => {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return Array.from({ length }, () =>
chars.charAt(crypto.getRandomValues(new Uint32Array(1))[0] % chars.length)
).join("");
};
const saveUserNameInSession = (value) => sessionStorage.setItem("insitella_usernames", JSON.stringify(value));
const saveUserEventInSession = (value) => sessionStorage.setItem("insitella_userevents", JSON.stringify(value));
const getCookie = (cookieName) => {
return document.cookie
.split(";")
.map((c) => c.trim())
.find((c) => c.startsWith(`${cookieName}=`))
?.split("=")[1] || null;
};
const createCookie = (name, value, hours) => {
const exp = new Date(Date.now() + hours * 60 * 60 * 1000);
document.cookie = `${name}=${value};expires=${exp.toUTCString()};path=/`;
};
const detectDeviceType = () => {
const ua = navigator.userAgent.toLowerCase();
return /ipad|tablet|playbook|silk/i.test(ua)
? "tablet"
: /mobile|iphone|ipod|blackberry|opera mini|iemobile|windows phone|trident|opera mobi|mobilesafari|htc|nokia|symbian|samsung|lg|mot/i.test(ua)
? "mobile"
: "pc";
};
const determineCurrentScreen = () => window.location.href.split("/").pop();
const closeCookiePopup = () => document.getElementById("cookiePopup")?.remove();
const handleCookieOnAcceptance = async () => {
closeCookiePopup();
if (!navigator.geolocation) return;
const getPosition = () =>
new Promise((resolve, reject) =>
navigator.geolocation.getCurrentPosition(resolve, reject)
);
try {
const {
coords: { latitude, longitude },
} = await getPosition();
const url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=json`;
const response = await fetch(url);
const locationData = await response.json();
const userType = sessionStorage.getItem("insitella_usernames") ? "Authenticated" : "Anonymous";
const deviceType = getCookie("deviceType");
const userInfo = {
ip: ipAddress,
userName: generateUserName(5),
userType,
browserName,
formattedDate,
formattedTime,
clientName,
deviceType,
};
const locationInfo = {
clientName,
latitude: latitude.toString(),
longitude: longitude.toString(),
cityName: locationData.address?.city || locationData.address?.town || locationData.address?.village || "",
country: locationData.address?.country || ""
};
const deviceInfo = {
clientName,
DeviceName: deviceType,
};
const configRes = await fetch(`${apiBaseURL}/config`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userInfo }),
});
const { _id, serverUpdateTime } = await configRes.json();
createCookie("serverUpdateTime", serverUpdateTime, 30);
createCookie("userId", _id, 30);
locationInfo._id = _id;
deviceInfo._id = _id;
await fetch(`${apiBaseURL}/saveMapData`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(locationInfo),
});
await fetch(`${apiBaseURL}/saveDeviceData`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(deviceInfo),
});
createCookie("cookieAccepted", "true", 24);
} catch (err) { }
};
const htmlTemplate = `
<div id="cookiePopup" style="position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999;">
<div class="wrapper" style="background: #fff; position: fixed; bottom: 20px; left: 50px; max-width: 500px; border-radius: 15px; text-align: center; border: 1px solid #493179; padding: 25px; box-shadow: 0 0 18px rgba(0, 0, 0, 0.13);">
<img src="../../assets/img/cookie.png" alt="Cookie" style="max-width: 90px;">
<div class="content" style="margin-top: 10px;">
<h1 style="font-size: 25px; font-weight: 600;">GDPR Compliance Notice</h1>
<h5>What data do we collect?</h5>
<ul style="list-style-type: disc; text-align: left;">
<li>Personal information such as name, email, and location when you sign up or interact with our platform.</li>
<li>Usage patterns, preferences, and interactions to enhance your experience.</li>
</ul>
<div class="buttons" style="display: flex; justify-content: center;">
<button id="cookieCancelBtn" style="padding: 10px 20px; margin: 0 5px; border: none; font-size: 16px; font-weight: 500; border-radius: 5px; cursor: pointer; background: #eee; color: #333;">Cancel</button>
<button id="cookieAcceptBtn" style="padding: 10px 20px; margin: 0 5px; border: none; font-size: 16px; font-weight: 500; border-radius: 5px; cursor: pointer; background: #493179; color: #fff;">Accept</button>
</div>
</div>
</div>
</div>
`;
const currentDate = new Date();
const formattedDate = formatDate(currentDate);
const formattedTime = [currentDate.getHours(), currentDate.getMinutes(), currentDate.getSeconds()].map((n) => n.toLocaleString()).join(":");
const browserNameMap = {
Firefox: "Mozilla Firefox",
"Edg/": "Microsoft Edge",
Chrome: "Google Chrome",
Safari: "Apple Safari",
Opera: "Opera",
MSIE: "Internet Explorer",
"Trident/": "Internet Explorer",
};
const userAgent = navigator.userAgent;
const browserName = Object.keys(browserNameMap).find((key) => userAgent.includes(key)) || "Unknown Browser";
const clientName = document.querySelector("title")?.innerHTML || "";
let ipAddress = "";
let pageName = "";
let newPageName = "";
let isPageChanged = false;
if (!getCookie("deviceType")) createCookie("deviceType", detectDeviceType(), 24);
document.addEventListener("DOMContentLoaded", () => {
fetch("https://api.ipify.org?format=json")
.then((res) => res.json())
.then(({ ip }) => {
ipAddress = ip;
const deviceType = getCookie("deviceType");
const userDetail = {
userInfo: [{
ip,
userName: generateUserName(5),
browserName,
dates: formattedDate,
time: formattedTime,
deviceType,
clientName,
}],
};
const stored = JSON.parse(sessionStorage.getItem("insitella_usernames"));
if (stored?.userInfo?.[0]?.ip !== ip) saveUserNameInSession(userDetail);
})
.catch((err) => {});
if (!getCookie("cookieAccepted")) {
const container = document.createElement("div");
container.innerHTML = htmlTemplate.trim();
document.body.appendChild(container.firstChild);
document.getElementById("cookieAcceptBtn")?.addEventListener("click", handleCookieOnAcceptance);
document.getElementById("cookieCancelBtn")?.addEventListener("click", closeCookiePopup);
}
pageName = determineCurrentScreen();
new MutationObserver(() => {
const currentUrl = window.location.href;
newPageName = currentUrl.substring(currentUrl.lastIndexOf("/") + 1);
if (newPageName !== pageName) isPageChanged = true;
}).observe(document.body, { subtree: true, childList: true });
});
(function () {
let clickData = {};
let clickCounts = {};
let requestPayload = null;
let isResponseToDB = false;
let isSending = false;
const userDetail = {};
const dataKeysToClear = ["insitella_userevents", "insitella_usernames"];
const updateClickCount = (text, type) => {
const key = `${type}${text}`;
clickCounts[key] = (clickCounts[key] || 0) + 1;
const displayElement = document.getElementById(`${key}_click_count`);
if (displayElement) displayElement.textContent = clickCounts[key];
if (!clickData[pageName]) clickData[pageName] = {};
clickData[pageName][key] = clickCounts[key];
userDetail.userEvents = [{ ...clickData }];
clickData = {};
clickCounts = {};
const existingUserEventsInSession = sessionStorage.getItem("insitella_userevents");
const newUserEvents = JSON.parse(JSON.stringify(userDetail));
if (!existingUserEventsInSession) {
newUserEvents.userEvents[0].date = formattedDate;
saveUserEventInSession([newUserEvents]);
requestPayload = JSON.stringify(newUserEvents.userEvents);
isResponseToDB = true;
} else {
let stored = JSON.parse(existingUserEventsInSession);
const existingEvents = stored[0].userEvents;
newUserEvents.userEvents.forEach((event, index) => {
const targetEvent = existingEvents[index] || {};
const screen = Object.keys(event)[0];
if (!targetEvent[screen]) targetEvent[screen] = {};
for (const key in event[screen]) {
targetEvent[screen][key] = (targetEvent[screen][key] || 0) + event[screen][key];
}
existingEvents[index] = targetEvent;
});
existingEvents[0].date = formattedDate;
saveUserEventInSession([{ ...userDetail, userEvents: existingEvents }]);
requestPayload = JSON.stringify(existingEvents);
isResponseToDB = true;
}
};
const getParentContent = (el, selector) => {
let content = el.textContent.trim();
let parent = el.parentElement.closest(selector);
while (parent) {
content = parent.textContent.trim();
parent = parent.parentElement.closest(selector);
}
return content;
};
document.addEventListener("click", (event) => {
const target = event.target;
if (target.closest("button")) {
updateClickCount(getParentContent(target, "button"), "btn_");
if (isPageChanged) pageName = newPageName;
} else if (target.closest("a")) {
updateClickCount(getParentContent(target, "a"), "link_");
if (isPageChanged) pageName = newPageName;
}
});
const sendUserEventData = async () => {
const existingUserEventsInSession = sessionStorage.getItem("insitella_userevents");
if (isSending || !isResponseToDB || !requestPayload || !existingUserEventsInSession) return;
isSending = true;
const userId = getCookie("userId");
try {
dataKeysToClear.forEach((key) => sessionStorage.removeItem(key));
const response = await fetch(`${apiBaseURL}/updateUserEvents/${userId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: requestPayload,
});
if (!response.ok) throw new Error(`Failed to send event data: ${response.status}`);
const res = await response.json();
const existingUserEventsInSession = sessionStorage.getItem("insitella_userevents");
if (!existingUserEventsInSession) {
isResponseToDB = false;
requestPayload = null;
}
} catch (err) {
throw new Error(`Error sending user event data: ${err.message}`);
} finally {
isSending = false;
}
};
let bootstrapTimer = null;
let eventDataTimer = null;
const startSendingEventData = () => {
const interval = getCookie("serverUpdateTime");
if (interval) {
if (eventDataTimer) clearInterval(eventDataTimer);
eventDataTimer = setInterval(sendUserEventData, parseInt(interval));
if (bootstrapTimer) {
clearInterval(bootstrapTimer);
bootstrapTimer = null;
}
}
};
sendUserEventData();
bootstrapTimer = setInterval(startSendingEventData, 1000);
})();
}
module.exports = { insitella };