const ORIGIN = window.location.hostname === "localhost" ? "http://localhost:3000" : "https://getmegadesk.com"; (function () { // Prevent multiple loads of the embed script if (window.__helpdesk_embed_loaded) { return; } window.__helpdesk_embed_loaded = true; const pub = document.currentScript.dataset.pub let identity let frame let buttonFrame let floatingButtonsFrame let popupsFrame let popupsReady = false let popupsQueue = [] let currentPopupId const popupShowTimers = new Map() const popupHideTimers = new Map() let widgetAllowedRoutes = [] let widgetVisible = false const displayFrame = () => { frame = document.createElement("iframe") frame.src = `${ORIGIN}/embed/${pub}` frame.style.all = "unset" frame.style.position = "fixed" frame.style.zIndex = "1000000" frame.style.right = "0" frame.style.width = "100%" frame.style.maxWidth = "480px" frame.style.display = "none" // Responsive adjustments for mobile devices const setResponsiveStyles = () => { if (window.matchMedia("(max-width: 640px)").matches) { frame.style.bottom = "66px" frame.style.height = "calc(100dvh - 66px)" frame.style.maxHeight = "unset" } else { frame.style.bottom = "74px" frame.style.height = "840px" frame.style.maxHeight = "calc(100dvh - 80px)" } } setResponsiveStyles() window.addEventListener("resize", setResponsiveStyles) document.body.appendChild(frame) } const displayButtonFrame = () => { buttonFrame = document.createElement("iframe") buttonFrame.src = `${ORIGIN}/embed/${pub}/button` buttonFrame.style.all = "unset" buttonFrame.style.position = "fixed" buttonFrame.style.zIndex = "1000000" buttonFrame.style.bottom = "8px" buttonFrame.style.right = "12px" buttonFrame.style.width = "74px" buttonFrame.style.height = "74px" document.body.appendChild(buttonFrame) } const displayPopupsFrame = () => { popupsFrame = document.createElement("iframe") popupsFrame.src = `${ORIGIN}/embed/${pub}/popups` popupsFrame.style.all = "unset" popupsFrame.style.position = "fixed" popupsFrame.style.zIndex = "1000000" popupsFrame.style.bottom = "12px" popupsFrame.style.right = "calc(8px + 64px)" popupsFrame.style.width = "0px" popupsFrame.style.height = "0px" popupsFrame.style.display = "none" document.body.appendChild(popupsFrame) popupsFrame.addEventListener("load", () => { popupsReady = true popupsQueue.forEach(payload => { popupsFrame.contentWindow.postMessage(payload, "*") }) popupsQueue = [] }) } const storageKey = (ruleId) => `mg_popup_${pub}_${ruleId}`; const dismissKey = (ruleId) => `mg_popup_dismissed_${pub}_${ruleId}`; const STORAGE_COOLDOWN = 60 * 60 * 1000; const shouldShowByFrequency = (ruleId, frequency) => { const dismissedAt = localStorage.getItem(dismissKey(ruleId)); if (dismissedAt) { const ts = parseInt(dismissedAt, 10); if (!Number.isNaN(ts) && Date.now() - ts < STORAGE_COOLDOWN) return false; } const shownAt = localStorage.getItem(storageKey(ruleId)); if (shownAt) { const ts = parseInt(shownAt, 10); if (!Number.isNaN(ts) && Date.now() - ts < STORAGE_COOLDOWN) return false; } return true; } const markShown = (ruleId) => { localStorage.setItem(storageKey(ruleId), Date.now().toString()); } const markDismissed = (ruleId) => { localStorage.setItem(dismissKey(ruleId), Date.now().toString()); } const DEFAULT_TIME_UNTIL_SHOW = 2000; const TIME_UNTIL_HIDE = 5000; // Route matching with wildcard support const matchRoute = (pattern, path) => { // Normalize paths pattern = pattern.replace(/\/+$/, "") || "/" path = path.replace(/\/+$/, "") || "/" if (pattern === path) return true // Handle ** (match any depth) if (pattern.endsWith("/**")) { const prefix = pattern.slice(0, -3) return path === prefix || path.startsWith(prefix + "/") } // Handle * (match single segment) if (pattern.endsWith("/*")) { const prefix = pattern.slice(0, -2) const remaining = path.slice(prefix.length) return path.startsWith(prefix) && remaining.split("/").filter(Boolean).length <= 1 } return false } const shouldShowOnRoute = (allowedRoutes) => { if (!Array.isArray(allowedRoutes) || allowedRoutes.length === 0) { return true // Empty = show on all routes } const currentPath = window.location.pathname return allowedRoutes.some(pattern => matchRoute(pattern, currentPath)) } const updateWidgetVisibility = () => { const shouldShow = shouldShowOnRoute(widgetAllowedRoutes) if (shouldShow === widgetVisible) return widgetVisible = shouldShow if (buttonFrame) { buttonFrame.style.display = shouldShow ? "block" : "none" } if (frame && !shouldShow) { frame.style.display = "none" } if (popupsFrame && !shouldShow) { popupsFrame.style.display = "none" } } // Måste ha popstate för att kunna kolla om användaren byter route. const setupNavigationListeners = () => { // Handle back/forward navigation window.addEventListener("popstate", updateWidgetVisibility) const originalPushState = history.pushState const originalReplaceState = history.replaceState history.pushState = function (...args) { originalPushState.apply(this, args) updateWidgetVisibility() } history.replaceState = function (...args) { originalReplaceState.apply(this, args) updateWidgetVisibility() } } const popupDelayMs = (rule) => { const ms = Number(rule?.delayMs); if (Number.isFinite(ms) && ms >= 0) return ms; return DEFAULT_TIME_UNTIL_SHOW; } const setupPopupObserver = (rule) => { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { // om den är i vy if (entry.isIntersecting) { const existingHide = popupHideTimers.get(rule.id) if (existingHide) { clearTimeout(existingHide) popupHideTimers.delete(rule.id) } if (!popupShowTimers.has(rule.id)) { const timer = setTimeout(() => { popupShowTimers.delete(rule.id) // OM DEN REDAN VISAT ETC. if (!shouldShowByFrequency(rule.id, rule.frequency)) return; // OM POPUPSFRAME FINNS if (popupsFrame) { popupsFrame.style.display = "block"; const payload = { action: "showPopup", rule } currentPopupId = rule.id if (popupsReady && popupsFrame.contentWindow) { popupsFrame.contentWindow.postMessage(payload, "*") } else { popupsQueue.push(payload) } } }, popupDelayMs(rule)); popupShowTimers.set(rule.id, timer); } } else { const showTimer = popupShowTimers.get(rule.id); if (showTimer) clearTimeout(showTimer); popupShowTimers.delete(rule.id); const hideTimer = setTimeout(() => { popupHideTimers.delete(rule.id) if (currentPopupId === rule.id && popupsFrame?.contentWindow) { popupsFrame.contentWindow.postMessage({ action: "hidePopup" }, "*") currentPopupId = null } }, TIME_UNTIL_HIDE) popupHideTimers.set(rule.id, hideTimer) } }); }, { threshold: 0.3 }); const target = document.querySelector(rule.selector); if (target) { observer.observe(target); } else { let retries = 0; const retryInterval = setInterval(() => { const el = document.querySelector(rule.selector); if (el) { observer.observe(el); clearInterval(retryInterval); } else if (retries > 10) { clearInterval(retryInterval); } retries += 1; }, 500); } } async function main() { try { const settingsResponse = await fetch(`${ORIGIN}/api/pub-config/${pub}`) if (settingsResponse.ok) { const { identityEndpoint, popups, widgetAllowedRoutes: routes } = await settingsResponse.json() widgetAllowedRoutes = routes || [] if (identityEndpoint && identityEndpoint != "" && identityEndpoint != "/") { const identityResponse = await fetch(identityEndpoint) if (identityResponse.ok) { identity = (await identityResponse.json()).identity } } if (Array.isArray(popups)) { popups.forEach((rule) => { if (!rule?.id || !rule.selector || !rule.displayText || !rule.botMessage) return; setupPopupObserver(rule); }) } } } catch (e) { console.error("! Helpdesk error:", e) } // Always create iframes, but control visibility based on route displayButtonFrame() displayFrame() displayPopupsFrame() // Set initial visibility and listen for route changes widgetVisible = shouldShowOnRoute(widgetAllowedRoutes) buttonFrame.style.display = widgetVisible ? "block" : "none" setupNavigationListeners() window.addEventListener("message", async (event) => { if (event.data.action == "load") { frame.contentWindow.postMessage({ action: "identity", identity }, "*") } else if (event.data.action === "toggle") { frame.contentWindow.postMessage({ action: "toggle" }, "*") } else if (event.data.action === "floatingButton") { frame.contentWindow.postMessage({ action: "toggle" }, "*") frame.contentWindow.postMessage({ action: "floatingButton", text: event.data.text }, "*") } else if (event.data.action === "reportState") { const { showWindow } = event.data frame.style.display = showWindow ? "block" : "none" if(popupsFrame){ popupsFrame.style.display = "none" }else{ popupsFrame.style.display = "block" } } else if (event.data.action === "contentSize") { const { width, height } = event.data // if (floatingButtonsFrame) { // floatingButtonsFrame.style.width = `${width + 2}px` // floatingButtonsFrame.style.height = `${height + 2}px` // } if (popupsFrame) { popupsFrame.style.width = `${Math.ceil(width)}px` popupsFrame.style.height = `${Math.ceil(height)}px` } } else if (event.data.action === "popupClicked") { const { botMessage, ruleId } = event.data if (shouldShowByFrequency(ruleId, "once_per_session")) { markShown(ruleId) } if (window.megadeskSendText && typeof window.megadeskSendText === "function") { window.megadeskSendText(botMessage || "") } if (popupsFrame) { popupsFrame.contentWindow.postMessage({ action: "hidePopup" }, "*") } } else if (event.data.action === "popupDismissed") { const { ruleId } = event.data markShown(ruleId) markDismissed(ruleId) } }) } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); } window.megadeskSendText = (text) => { frame.contentWindow.postMessage({ action: "floatingButton", text: text }, "*") } })();