// v1.0.0 // Inline embed loader (no iframe). Mounts chatbot React UI directly in host page DOM. console.log("Loading chatbot inline embed..."); const EMBED_BUNDLE_PATH = "static/js/chatbot-embed.js"; const EMBED_STYLESHEET_PATH = "static/css/chatbot-embed.css"; const WRAPPER_ID = "chatbot-embed-root"; const WRAPPER_CLASS = "chatbot-embed-wrapper"; const WRAPPER_LEGACY_CLASS = "chatbot-iframe-wrapper"; const STATUS_MESSAGE_PREFIX = "chatStatus:"; let embedBundlePromise = null; let embedStylesPromise = null; let statusListenerAttached = false; const scriptOriginAtLoad = (() => { if (document.currentScript && document.currentScript.src) { try { return new URL(document.currentScript.src).origin; } catch (error) { return null; } } return null; })(); const getMetaContent = (name) => { const meta = document.querySelector(`meta[name="${name}"]`); return meta ? meta.getAttribute("content") : null; }; const getScriptOrigin = () => { return scriptOriginAtLoad; }; const normalizeBase = (value) => { if (!value) return ""; return value.endsWith("/") ? value : `${value}/`; }; const ensureWrapper = () => { let wrapper = document.getElementById(WRAPPER_ID); if (wrapper) { return wrapper; } wrapper = document.createElement("div"); wrapper.id = WRAPPER_ID; wrapper.className = `${WRAPPER_CLASS} ${WRAPPER_LEGACY_CLASS} closed`; wrapper.dataset.chatbotEmbed = "true"; document.body.appendChild(wrapper); return wrapper; }; const applyStatusToWrapper = (wrapper, status) => { if (!wrapper) return; if (status === "chatOpen") { wrapper.classList.remove("closed"); } else if (status === "chatClosed") { wrapper.classList.add("closed"); } else if (status === "popupOpen") { wrapper.classList.add("popup-open"); } else if (status === "popupClosed") { wrapper.classList.remove("popup-open"); } }; const attachStatusListener = (wrapper) => { if (statusListenerAttached) { return; } window.addEventListener("message", (event) => { if (typeof event.data !== "string" || !event.data.startsWith(STATUS_MESSAGE_PREFIX)) { return; } const status = event.data.slice(STATUS_MESSAGE_PREFIX.length); applyStatusToWrapper(wrapper, status); }); statusListenerAttached = true; }; const ensureFonts = () => { const fontLinks = [ { id: "chatbot-embed-font-cairo", href: "https://fonts.googleapis.com/css2?family=Cairo:wght@200..1000&display=swap", }, { id: "chatbot-embed-font-inter", href: "https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap", }, ]; fontLinks.forEach(({ id, href }) => { if (document.getElementById(id)) return; const link = document.createElement("link"); link.id = id; link.rel = "stylesheet"; link.href = href; document.head.appendChild(link); }); }; const loadEmbedStyles = (href) => { if (embedStylesPromise) { return embedStylesPromise; } embedStylesPromise = new Promise((resolve) => { const existing = document.querySelector("link[data-chatbot-embed-style]"); if (existing) { resolve(); return; } const link = document.createElement("link"); link.rel = "stylesheet"; link.href = href; link.setAttribute("data-chatbot-embed-style", "true"); link.onload = () => resolve(); link.onerror = () => { console.error(`Chatbot embed: failed to load stylesheet ${href}`); resolve(); }; document.head.appendChild(link); }); return embedStylesPromise; }; const loadEmbedBundle = (src) => { if (window.ChatbotEmbed && typeof window.ChatbotEmbed.mount === "function") { return Promise.resolve(); } if (embedBundlePromise) { return embedBundlePromise; } embedBundlePromise = new Promise((resolve, reject) => { const existing = document.querySelector("script[data-chatbot-embed]"); if (existing) { existing.addEventListener("load", () => resolve()); existing.addEventListener("error", () => reject(new Error("Failed to load chatbot embed bundle"))); return; } const script = document.createElement("script"); script.src = src; script.async = true; script.setAttribute("data-chatbot-embed", "true"); script.onload = () => resolve(); script.onerror = () => reject(new Error("Failed to load chatbot embed bundle")); document.head.appendChild(script); }); return embedBundlePromise; }; const mountChatbot = () => { const botId = getMetaContent("chatbot-botid"); if (!botId) { console.error("Chatbot embed: missing meta tag chatbot-botid"); return; } const domain = getMetaContent("chatbot-domain") || getScriptOrigin() || window.location.origin; if (!domain) { console.error("Chatbot embed: missing meta tag chatbot-domain and script origin"); return; } const wrapper = ensureWrapper(); ensureFonts(); const assetBase = normalizeBase(domain); window.__CHATBOT_EMBED_ASSET_BASE__ = assetBase; const bundleSrc = `${assetBase}${EMBED_BUNDLE_PATH}`; const stylesheetSrc = `${assetBase}${EMBED_STYLESHEET_PATH}`; Promise.all([loadEmbedStyles(stylesheetSrc), loadEmbedBundle(bundleSrc)]) .then(() => { if (!window.ChatbotEmbed || typeof window.ChatbotEmbed.mount !== "function") { console.error("Chatbot embed: mount API not available"); return; } window.ChatbotEmbed.mount({ botId, container: wrapper, }); attachStatusListener(wrapper); window.postMessage(`currentUrl:${window.location.href}`, "*"); }) .catch((error) => { console.error(error); }); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", mountChatbot); } else { mountChatbot(); }