overcentric
Version:
Overcentric watches your website, product, and users - and tells you what matters and what to do about it.
401 lines (400 loc) • 15.8 kB
JavaScript
var _a, _b;
// Compute normalized hostname once when the module loads
const normalizedHostname = typeof window !== "undefined"
? window.location.hostname.replace(/^www\./, "")
: "";
let dockIframe = null;
let dockOrigin = "https://app.overcentric.com";
let dockButton = null;
let secondaryColor = "#5c5c5c";
let dockContainer = null;
let dockStyle = null;
let dockCallout = null;
let calloutTimeout = null;
let messageListener = null;
let clickListener = null;
try {
if (typeof window !== "undefined" &&
typeof document !== "undefined" &&
(document === null || document === void 0 ? void 0 : document.readyState) !== undefined) {
dockOrigin = ((_b = (_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.origin) === null || _b === void 0 ? void 0 : _b.includes("localhost"))
? "http://localhost:4000"
: "https://app.overcentric.com";
}
}
catch (error) { }
const getClosedIcon = (color) => `<svg width="90%" height="90%" viewBox="0 0 24 24" fill="${color}" xmlns="http://www.w3.org/2000/svg">
<path d="M3 7.8C3 6.11984 3 5.27976 3.32698 4.63803C3.6146 4.07354 4.07354 3.6146 4.63803 3.32698C5.27976 3 6.11984 3 7.8 3H16.2C17.8802 3 18.7202 3 19.362 3.32698C19.9265 3.6146 20.3854 4.07354 20.673 4.63803C21 5.27976 21 6.11984 21 7.8V13.2C21 14.8802 21 15.7202 20.673 16.362C20.3854 16.9265 19.9265 17.3854 19.362 17.673C18.7202 18 17.8802 18 16.2 18H9.68375C9.0597 18 8.74767 18 8.44921 18.0613C8.18443 18.1156 7.9282 18.2055 7.68749 18.3285C7.41617 18.4671 7.17252 18.662 6.68521 19.0518L4.29976 20.9602C3.88367 21.2931 3.67563 21.4595 3.50054 21.4597C3.34827 21.4599 3.20422 21.3906 3.10923 21.2716C3 21.1348 3 20.8684 3 20.3355V7.8Z" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`;
const getOpenIcon = (color) => `<svg width="90%" height="90%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`;
/**
* Create and show a callout above the dock button
* @param text - The text to display in the callout
*/
function createCallout(text) {
if (!dockContainer || !dockButton)
return;
// Create callout div
dockCallout = document.createElement("div");
dockCallout.className = "oc-dock-callout";
dockCallout.textContent = text;
// Style the callout
dockCallout.style.position = "absolute";
dockCallout.style.bottom = "60px"; // 10px above the 50px button
dockCallout.style.right = "0";
dockCallout.style.width = "max-content";
dockCallout.style.backgroundColor = "white";
dockCallout.style.color = "#333";
dockCallout.style.padding = "10px 14px";
dockCallout.style.borderRadius = "12px";
dockCallout.style.boxShadow = "0 2px 10px rgba(0, 0, 0, 0.1)";
dockCallout.style.fontSize = "13px";
dockCallout.style.fontFamily = "system-ui, -apple-system, sans-serif";
dockCallout.style.maxWidth = window.innerWidth <= 480 ? "85vw" : "320px";
dockCallout.style.whiteSpace = "normal";
dockCallout.style.overflowWrap = "break-word";
dockCallout.style.lineHeight = "1.4";
dockCallout.style.opacity = "0";
dockCallout.style.transform = "translateY(5px)";
dockCallout.style.transition = "opacity 0.3s ease, transform 0.3s ease";
dockCallout.style.cursor = "pointer";
dockCallout.style.zIndex = "10000";
// Create close button
const closeButton = document.createElement("button");
closeButton.innerHTML = "×";
closeButton.className = "oc-dock-callout-close";
// Style the close button
closeButton.style.position = "absolute";
closeButton.style.top = "0";
closeButton.style.right = "0";
closeButton.style.width = "20px";
closeButton.style.height = "20px";
closeButton.style.backgroundColor = "rgba(232, 232, 232, 1)";
closeButton.style.border = "1px solid #e0e0e0";
closeButton.style.borderRadius = "50%";
closeButton.style.cursor = "pointer";
closeButton.style.fontSize = "12px";
closeButton.style.color = "#ddd";
closeButton.style.display = "flex";
closeButton.style.alignItems = "center";
closeButton.style.justifyContent = "center";
closeButton.style.opacity = "0";
closeButton.style.transition = "opacity 0.2s ease";
closeButton.style.transform = "translate(50%, -50%)";
closeButton.style.zIndex = "10001";
// Add hover effects for the callout and close button
dockCallout.addEventListener("mouseenter", () => {
closeButton.style.opacity = "1";
});
dockCallout.addEventListener("mouseleave", () => {
closeButton.style.opacity = "0";
});
// Add hover effects for the close button itself
closeButton.addEventListener("mouseenter", () => {
closeButton.style.backgroundColor = "#f5f5f5";
});
closeButton.addEventListener("mouseleave", () => {
closeButton.style.backgroundColor = "white";
});
// Close button click handler
closeButton.addEventListener("click", (event) => {
event.stopPropagation();
hideCallout();
});
// Add close button to callout
dockCallout.appendChild(closeButton);
// Add callout to container
dockContainer.appendChild(dockCallout);
// Show callout after 3 second delay
calloutTimeout = setTimeout(() => {
if (dockCallout) {
dockCallout.style.opacity = "1";
dockCallout.style.transform = "translateY(0)";
}
}, 3000);
dockCallout.addEventListener("click", (event) => {
event.stopPropagation();
openDock();
});
}
/**
* Hide and remove the callout
*/
function hideCallout() {
if (calloutTimeout) {
clearTimeout(calloutTimeout);
calloutTimeout = null;
}
if (dockCallout) {
// Only set dismissed flag if there's actually a callout being hidden
localStorage.setItem("overcentric_dock_callout_dismissed", "true");
dockCallout.style.opacity = "0";
dockCallout.style.transform = "translateY(5px)";
setTimeout(() => {
if (dockCallout && dockCallout.parentNode) {
dockCallout.parentNode.removeChild(dockCallout);
dockCallout = null;
}
}, 500);
}
}
/**
* Initialize the dock with a floating button and iframe.
* @param options - The options for the dock.
*/
export function initDock(options) {
// Clean up any existing dock first
cleanupDock();
// Create container for the dock
dockContainer = document.createElement("div");
dockContainer.className = "oc-dock-container";
dockContainer.style.position = "fixed";
dockContainer.style.zIndex = "9999";
// Position the dock based on config
if (options.position === "bottom-right") {
dockContainer.style.bottom = "20px";
dockContainer.style.right = "20px";
}
else if (options.position === "bottom-left") {
dockContainer.style.bottom = "20px";
dockContainer.style.left = "20px";
}
secondaryColor = options.dockColorSecondary || "#5c5c5c";
// Create the floating button
dockButton = document.createElement("button");
dockButton.innerHTML = getClosedIcon(secondaryColor);
dockButton.style.backgroundColor =
options.dockColor || "rgba(255, 255, 255, 0.95)";
dockButton.style.backdropFilter = options.dockColor ? "none" : "blur(5px)";
dockButton.style.color = secondaryColor;
dockButton.style.border = "none";
dockButton.style.borderRadius = "12px";
dockButton.style.width = "50px";
dockButton.style.height = "50px";
dockButton.style.cursor = "pointer";
dockButton.style.transition = "background-color 0.3s ease";
dockButton.style.outline = "none";
dockButton.style.boxShadow = "0 4px 30px rgba(0, 0, 0, 0.3)";
dockButton.style.padding = "10px";
dockButton.style.display = "flex";
dockButton.style.opacity = "0";
dockButton.style.alignItems = "center";
dockButton.style.justifyContent = "center";
dockButton.style.transition = "opacity 0.6s ease";
// Add a unique class to the button for styling
const buttonClass = "oc-dock-launcher";
dockButton.classList.add(buttonClass);
// Create notification dot (hidden by default)
const notificationDot = document.createElement("div");
notificationDot.className = "oc-dock-notification-dot";
notificationDot.style.position = "absolute";
notificationDot.style.top = "-2px";
notificationDot.style.right = "-4px";
notificationDot.style.width = "16px";
notificationDot.style.height = "16px";
notificationDot.style.backgroundColor = "#f25028";
notificationDot.style.borderRadius = "50%";
notificationDot.style.border = "2px solid white";
notificationDot.style.display = "none";
// Create the iframe for the dock
dockIframe = document.createElement("iframe");
dockIframe.style.position = "fixed";
dockIframe.style.bottom = "80px";
dockIframe.style.right = "20px";
dockIframe.style.zIndex = "1000";
dockIframe.style.display = "none";
dockIframe.style.opacity = "0";
dockIframe.style.transition = "opacity 0.3s ease";
// Make the dock responsive based on screen size
const isMobile = window.innerWidth <= 768;
if (isMobile) {
dockIframe.style.width = "100%";
dockIframe.style.height = `calc(100% - 80px)`;
dockIframe.style.left = "0";
dockIframe.style.right = "0";
dockIframe.style.bottom = "80px";
}
else {
dockIframe.style.width = "360px";
dockIframe.style.height = "540px";
}
dockIframe.style.border = "none";
dockIframe.style.borderRadius = "12px";
dockIframe.style.backgroundColor = "rgba(255, 255, 255, 0.98)";
dockIframe.style.backdropFilter = "blur(50px)";
dockIframe.style.boxShadow = "rgb(0 0 0 / 10%) 0px 2px 15px 5px";
dockIframe.src = `${dockOrigin}/dock`;
dockContainer.appendChild(dockButton);
setTimeout(() => {
if (dockButton) {
dockButton.style.opacity = "1";
}
}, 300);
dockContainer.appendChild(dockIframe);
dockContainer.appendChild(notificationDot);
document.body.appendChild(dockContainer);
// Create and show callout if text is provided
const hasDismissedCallout = localStorage.getItem("overcentric_dock_callout_dismissed") === "true";
if (!hasDismissedCallout && options.dockCalloutText) {
createCallout(options.dockCalloutText);
}
// Create and append style element with hover effect
dockStyle = document.createElement("style");
dockStyle.textContent = `
.${buttonClass}:hover {
opacity: 0.8 !important;
}
`;
document.head.appendChild(dockStyle);
// Toggle iframe visibility on button click
clickListener = () => {
if ((dockIframe === null || dockIframe === void 0 ? void 0 : dockIframe.style.display) === "none") {
openDock();
}
else {
closeDock();
}
};
dockContainer.addEventListener("click", clickListener);
messageListener = (event) => {
// Verify origin for security
if (event.origin !== dockOrigin)
return;
if (event.data && event.data.type === "DOCK_READY") {
// Iframe is ready, now send the data
sendDataToIframe(dockIframe, {
context: options.context,
position: options.position,
isChatEnabled: options.isChatEnabled,
isKBEnabled: options.isKBEnabled,
dockColor: options.dockColor,
dockColorSecondary: options.dockColorSecondary
});
}
else if (event.data.type === "DOCK_NOTIFICATION") {
// Handle notification status update
if (event.data.hasUnread) {
console.log("Has unread notifications");
notificationDot.style.display = "block";
}
else {
console.log("No unread notifications");
notificationDot.style.display = "none";
}
}
};
window.addEventListener("message", messageListener);
}
// Function to send data to iframe
function sendDataToIframe(iframe, options) {
var _a;
if (!iframe.contentWindow)
return;
const dataToSend = {
type: "DOCK_PARENT_INFO",
project_id: window.overcentricProjectId,
device_id: window.overcentricDeviceId,
identity: window.overcentricUserIdentity,
session_id: window.overcentricSessionId,
url: window.location.href,
hostname: normalizedHostname,
context: options.context,
position: options.position,
isChatEnabled: options.isChatEnabled,
isKBEnabled: options.isKBEnabled,
color: options.dockColor,
secondaryColor: options.dockColorSecondary
};
// Send message to iframe with specific target origin for security
(_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage(dataToSend, dockOrigin);
}
/**
* Update the dock iframe with the identity ID.
* @param identity - The identity to be added to the iframe URL.
*/
export function updateDockIdentity(identity) {
if (dockIframe) {
const currentSrc = new URL(dockIframe.src);
currentSrc.searchParams.set("identityId", identity.uniqueIdentifier);
dockIframe.src = currentSrc.toString();
}
}
export function setDockUrl(urlSuffix) {
if (dockIframe) {
dockIframe.src = `${dockOrigin}/dock/${urlSuffix}`;
}
}
export function openDock() {
hideCallout();
if (dockIframe) {
dockIframe.style.display = "block";
setTimeout(() => {
var _a;
if (dockIframe) {
dockIframe.style.opacity = "1";
(_a = dockIframe.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage({ type: "DOCK_OPEN" }, dockOrigin);
}
if (dockButton) {
dockButton.innerHTML = getOpenIcon(secondaryColor);
}
}, 100);
}
}
export function closeDock() {
if (dockIframe) {
dockIframe.style.opacity = "0";
setTimeout(() => {
var _a;
if (dockIframe) {
dockIframe.style.display = "none";
(_a = dockIframe.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage({ type: "DOCK_CLOSE" }, dockOrigin);
}
if (dockButton) {
dockButton.innerHTML = getClosedIcon(secondaryColor);
}
}, 200);
}
}
/**
* Clean up all dock resources, event listeners, and DOM elements.
*/
export function cleanupDock() {
// Clean up callout
hideCallout();
// Remove event listeners
if (messageListener) {
try {
window.removeEventListener("message", messageListener);
}
catch (e) {
// Ignore cross-origin errors
}
messageListener = null;
}
if (clickListener && dockContainer) {
try {
dockContainer.removeEventListener("click", clickListener);
}
catch (e) {
// Ignore cross-origin errors
}
clickListener = null;
}
// Remove DOM elements
if (dockContainer && dockContainer.parentNode) {
dockContainer.parentNode.removeChild(dockContainer);
}
if (dockStyle && dockStyle.parentNode) {
dockStyle.parentNode.removeChild(dockStyle);
}
// Reset references
dockContainer = null;
dockIframe = null;
dockButton = null;
dockStyle = null;
dockCallout = null;
}