UNPKG

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
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; }