From 867e99b35bf4c3218719cbd27a0bcf04ef8a67b2 Mon Sep 17 00:00:00 2001 From: Ananya Gupta Date: Wed, 21 Jan 2026 11:01:10 +0530 Subject: [PATCH] feature/test/multi widget functionality --- public/chat-widget-local.js | 221 +++++++++++++++++++++++------------ public/chat-widget-style.css | 14 +-- 2 files changed, 153 insertions(+), 82 deletions(-) diff --git a/public/chat-widget-local.js b/public/chat-widget-local.js index 4f4222bd..5aaae6a6 100644 --- a/public/chat-widget-local.js +++ b/public/chat-widget-local.js @@ -1,11 +1,23 @@ /* eslint-disable */ // IIFE Scope ( for avoiding global scope pollution ) (function () { - if (window.__HELLO_WIDGET_LOADED__) { - console.warn('[Hello Widget] Script already loaded. Skipping second initialization.'); + const currentScript = document.currentScript || + Array.from(document.getElementsByTagName('script')).find(s => s.src && s.src.includes('chat-widget')); + if (!currentScript || !currentScript.src) { + console.error('[Hello Widget] Error: Could not detect chat-widget script. Make sure the script has a valid src attribute.'); return; } - window.__HELLO_WIDGET_LOADED__ = true; + const scriptUrl = new URL(currentScript.src); + + // If localhost, use port as subdomain identifier + // Otherwise, use actual subdomain from hostname + const subdomain = scriptUrl.hostname === 'localhost' + ? scriptUrl.port + : scriptUrl.hostname.split('.')[0]; + + console.log(`[Hello Widget] Detected subdomain: ${subdomain} from ${scriptUrl.href}`); + const scriptOrigin = scriptUrl.origin; + let block_chatbot = false; class CobrowseManager { @@ -19,6 +31,19 @@ console.log("[CoBrowse PARENT] No device ID provided, aborting script injection"); return; } + + // Check if CobrowseIO is already loaded (multi-widget scenario) + if (window.CobrowseIO || document.getElementById('CBParentScript')) { + console.log("[CoBrowse PARENT] CobrowseIO already loaded, skipping injection"); + this.scriptInjected = true; + // Update device ID if CobrowseIO is already available + if (window.CobrowseIO) { + window.CobrowseIO.customData = { + device_id: uuid + }; + } + return; + } this.scriptInjected = true; // Create and load the CobrowseIO script for parent window const script = document.createElement('script'); @@ -82,8 +107,9 @@ const CBManager = new CobrowseManager() class HelloChatbotEmbedManager { - constructor() { - this.prefix = 'hello-' + constructor(subdomain) { + this.subdomain = subdomain || '3001'; + this.prefix = `${this.subdomain}-hello-` this.elements = { chatbotIconContainer: `${this.prefix}chatbot-launcher-icon`, chatbotIconImage: `${this.prefix}chatbot-icon-image`, @@ -107,10 +133,11 @@ widthUnit: 'px', buttonName: '' }; + // chat-widget-local.js should only handle localhost this.urls = { - chatbotUrl: 'http://localhost:3001/chatbot', - styleSheet: 'http://localhost:3001/chat-widget-style.css', - urlMonitor: 'http://localhost:3001/urlMonitor.js' + chatbotUrl: `http://localhost:${this.subdomain}/chatbot`, + styleSheet: `http://localhost:${this.subdomain}/chat-widget-style.css`, + urlMonitor: `http://localhost:${this.subdomain}/urlMonitor.js` }; this.icons = { white: this.makeImageUrl('b1357e23-2fc6-4dc3-855a-7a213b1fa100'), @@ -218,9 +245,8 @@ window.addEventListener('message', (event) => { // Only process messages from trusted origins const trustedOrigins = [ - 'http://localhost:3001', - 'http://localhost:3000', - window.location.origin + scriptOrigin, // Origin where script was loaded from + window.location.origin // Origin of the parent page ]; if (trustedOrigins.includes(event.origin)) { @@ -231,6 +257,14 @@ handleIncomingMessages(event) { const { type, data } = event.data || {}; + // Get MY iframe's window object + const myIframe = document.getElementById(this.elements.chatbotIframeComponent); + const myIframeWindow = myIframe?.contentWindow; + // Check: Did MY iframe send this message? + if (event.source !== myIframeWindow) { + // Message is from a DIFFERENT iframe, ignore it + return; + } switch (type) { case 'MINIMIZE_CHATBOT': this.minimizeChatbot() @@ -781,7 +815,7 @@ const interfaceEmbed = document.getElementById(this.elements.chatbotIconContainer); if (interfaceEmbed) { interfaceEmbed.style.display = - (this.props.hide_launcher === true || this.props.hide_launcher === 'true' || this.hideHelloIcon || this.helloProps?.hide_launcher === true || this.helloProps?.hide_launcher === 'true' || helloChatbotManager.helloProps?.isMobileSDK) + (this.props.hide_launcher === true || this.props.hide_launcher === 'true' || this.hideHelloIcon || this.helloProps?.hide_launcher === true || this.helloProps?.hide_launcher === 'true' || this.helloProps?.isMobileSDK) ? 'none' : 'unset'; } @@ -826,11 +860,23 @@ } loadContent() { - if (this.state.bodyLoaded) return; + // Check if DOM already exists for this subdomain + const existingContainer = document.getElementById(this.elements.chatbotIframeContainer); + + if (existingContainer) { + console.log(`[Widget ${this.subdomain}] DOM already exists, reloading chatbot only`); + this.state.bodyLoaded = true; + // Set reference to existing parent container + this.parentContainer = existingContainer; + this.loadChatbotEmbed(); + return; + } + // Existing check for new instances + if (this.state.bodyLoaded) return; const { chatBotIcon } = this.createChatbotIcon(); document.body.appendChild(chatBotIcon); - document.head.appendChild(this.createStyleLink()); // load the External Css for script + document.head.appendChild(this.createStyleLink()); this.attachIconEvents(chatBotIcon); this.createIframeContainer(); @@ -895,6 +941,7 @@ parentContainer.style.height = isFunctionalHeight ? config.height : `${config.height}${config.heightUnit || ''}` || '70vh'; + // iframeParentContainer.style.height = `${config?.height}${config?.heightUnit || ''}` || '70vh'; parentContainer.style.width = `${config?.width}${config?.widthUnit || ''}` || '40vw'; // Reset parentId in props since container doesn't exist // this.updateProps({ parentId: null }); @@ -905,7 +952,9 @@ attachIconEvents(chatBotIcon) { const children = chatBotIcon.querySelectorAll('*'); // Select all descendant elements children.forEach(child => { - child.addEventListener('click', () => helloChatbotManager.openChatbot()); + child.addEventListener('click', () => { + this.openChatbot(); + }); }); } @@ -936,8 +985,8 @@ // Add event listeners for starter question text starterQuestionText.addEventListener('click', () => { - helloChatbotManager.openChatbot(); - helloChatbotManager.hideStarterQuestion(); + this.openChatbot(); + this.hideStarterQuestion(); }); } @@ -968,8 +1017,8 @@ e.stopPropagation(); // Send the new event for starter question option click sendMessageToChatbot({ type: 'STARTER_QUESTION_OPTION_CLICKED', data: { option: option } }); - helloChatbotManager.openChatbot(); - helloChatbotManager.hideStarterQuestion(); + this.openChatbot(); + this.hideStarterQuestion(); }); optionsContainer.appendChild(optionElement); @@ -982,7 +1031,7 @@ starterQuestionClose.addEventListener('click', (e) => { e.stopPropagation(); - helloChatbotManager.hideStarterQuestion(); + this.hideStarterQuestion(); }); } @@ -1120,19 +1169,19 @@ } if (this.state.interfaceLoaded && this.state.delayElapsed) { const interfaceEmbed = document.getElementById(this.elements.chatbotIconContainer); - if (!this.hideHelloIcon && (this.helloProps?.hide_launcher !== undefined && (this.helloProps?.hide_launcher === false || this.helloProps?.hide_launcher === 'false')) && !helloChatbotManager.helloProps?.isMobileSDK) { + if (!this.hideHelloIcon && (this.helloProps?.hide_launcher !== undefined && (this.helloProps?.hide_launcher === false || this.helloProps?.hide_launcher === 'false')) && !this.helloProps?.isMobileSDK) { if (interfaceEmbed) interfaceEmbed.style.display = 'block'; } - if (this.helloLaunchWidget) helloChatbotManager.openChatbot() - if (helloChatbotManager.helloProps?.icon_position === 'left') { + if (this.helloLaunchWidget) this.openChatbot() + if (this.helloProps?.icon_position === 'left') { interfaceEmbed.classList.add('left_all_child') document.getElementById(this.elements.chatbotIframeContainer).classList.add('left_all_child') } - if (helloChatbotManager.helloProps?.icon_position === 'right') { + if (this.helloProps?.icon_position === 'right') { interfaceEmbed.classList.add('right_all_child') document.getElementById(this.elements.chatbotIframeContainer).classList.add('right_all_child') } - const bottomMargin = helloChatbotManager.helloProps?.icon_bottom_margin + const bottomMargin = this.helloProps?.icon_bottom_margin if (bottomMargin) { interfaceEmbed.style.bottom = typeof bottomMargin === 'number' ? `${bottomMargin}px` @@ -1206,11 +1255,11 @@ } if ('hide_launcher' in data) { propsToUpdate.hide_launcher = data.hide_launcher || false; - helloChatbotManager.hideHelloIcon = data.hide_launcher || false; + this.hideHelloIcon = data.hide_launcher || false; } if ('launch_widget' in data) { propsToUpdate.launch_widget = data.launch_widget || false; - helloChatbotManager.helloLaunchWidget = data.launch_widget || false; + this.helloLaunchWidget = data.launch_widget || false; } if ('parentId' in data) { propsToUpdate.parentId = data.parentId || ''; @@ -1221,25 +1270,25 @@ if ('starter_question' in data) { if (data.starter_question === true || data.starter_question === 'true') { const questionText = data.starter_question_text || 'How can I help you today?'; - helloChatbotManager.createAndShowStarterQuestion(questionText); + this.createAndShowStarterQuestion(questionText); } else { - helloChatbotManager.hideStarterQuestion(); + this.hideStarterQuestion(); } } if ('starter_question_text' in data && data.starter_question_text) { - helloChatbotManager.updateStarterQuestionText(data.starter_question_text); + this.updateStarterQuestionText(data.starter_question_text); } // Update props in a single call if we have any if (Object.keys(propsToUpdate).length > 0) { - helloChatbotManager.updateProps(propsToUpdate); + this.updateProps(propsToUpdate); } // Send general data if (data) { - helloChatbotManager.state.tempDataToSend = { - ...helloChatbotManager.state.tempDataToSend, + this.state.tempDataToSend = { + ...this.state.tempDataToSend, ...data }; sendMessageToChatbot({ type: 'helloRunTimeData', data: data }); @@ -1252,12 +1301,11 @@ // Handle config updates if ('config' in data && data.config) { - const newConfig = { ...helloChatbotManager.config, ...data.config }; - helloChatbotManager.applyConfig(newConfig); - helloChatbotManager.updateProps({ config: newConfig }); + const newConfig = { ...this.config, ...data.config }; + this.applyConfig(newConfig); + this.updateProps({ config: newConfig }); } } - addUrlMonitor(data) { if (data.previewLinks?.length > 0) { if (this.state.urlMonitorAdded === false) { @@ -1275,9 +1323,13 @@ } } - const helloChatbotManager = new HelloChatbotEmbedManager(); + /// Create subdomain-specific manager instance + const helloChatbotManager = new HelloChatbotEmbedManager(subdomain); + // Store in subdomain-specific namespace + window[`${subdomain}_helloChatbotManager`] = helloChatbotManager; function SendDataToBot(dataToSend) { + const manager = window[`${subdomain}_helloChatbotManager`]; // Parse string data if needed if (typeof dataToSend === 'string') { try { @@ -1289,47 +1341,50 @@ } // Send to React Native if available - if (helloChatbotManager.helloProps?.isMobileSDK) { + if (manager.helloProps?.isMobileSDK) { sendDataToMobileSDK({ type: 'data', data: dataToSend }) } // Handle parent container changes if ('parentId' in dataToSend) { - helloChatbotManager.state.tempDataToSend = { - ...helloChatbotManager.state.tempDataToSend, + manager.state.tempDataToSend = { + ...manager.state.tempDataToSend, ...dataToSend }; - helloChatbotManager.helloProps = { - ...helloChatbotManager.helloProps, + manager.helloProps = { + ...manager.helloProps, ...dataToSend } - const previousParentId = helloChatbotManager.helloProps['parentId']; + const previousParentId = manager.helloProps['parentId']; const existingParent = document.getElementById(previousParentId); - if (existingParent?.contains(helloChatbotManager.parentContainer)) { + if (existingParent?.contains(manager.parentContainer)) { if (previousParentId !== dataToSend.parentId) { if (previousParentId) { - if (existingParent && helloChatbotManager.parentContainer && existingParent.contains(helloChatbotManager.parentContainer)) { - existingParent.removeChild(helloChatbotManager.parentContainer); + if (existingParent && manager.parentContainer && existingParent.contains(manager.parentContainer)) { + existingParent.removeChild(manager.parentContainer); } - } else if (helloChatbotManager.parentContainer && document.body.contains(helloChatbotManager.parentContainer)) { - document.body.removeChild(helloChatbotManager.parentContainer); + } else if (manager.parentContainer && document.body.contains(manager.parentContainer)) { + document.body.removeChild(manager.parentContainer); } - helloChatbotManager.updateProps({ parentId: dataToSend.parentId }); - helloChatbotManager.changeContainer(dataToSend.parentId || ''); + manager.updateProps({ parentId: dataToSend.parentId }); + manager.changeContainer(dataToSend.parentId || ''); + } else { + manager.changeContainer(dataToSend.parentId || ''); } } else { - helloChatbotManager.updateProps({ parentId: dataToSend.parentId }); - helloChatbotManager.changeContainer(dataToSend.parentId || ''); + manager.updateProps({ parentId: dataToSend.parentId }); + manager.changeContainer(dataToSend.parentId || ''); } } // Process other properties - helloChatbotManager.processDataProperties(dataToSend); + manager.processDataProperties(dataToSend); }; // Helper function to send messages to the iframe function sendMessageToChatbot(messageObj) { - const iframeComponent = document.getElementById(helloChatbotManager.elements.chatbotIframeComponent); + const manager = window[`${subdomain}_helloChatbotManager`]; + const iframeComponent = document.getElementById(manager.elements.chatbotIframeComponent); if (iframeComponent?.contentWindow) { iframeComponent?.contentWindow?.postMessage(messageObj, '*'); } @@ -1342,18 +1397,19 @@ } // Initialize the widget function - window.initChatWidget = (data, delay = 0) => { + const subdomainInitFunction = (data, delay = 0) => { + const manager = window[`${subdomain}_helloChatbotManager`]; if (block_chatbot) return; if (data.previewLinks) { - helloChatbotManager.addUrlMonitor(data); + manager.addUrlMonitor(data); } if (data) { - helloChatbotManager.helloProps = { ...data }; + manager.helloProps = { ...data }; if ('hide_launcher' in data) { - helloChatbotManager.hideHelloIcon = data.hide_launcher || false; + manager.hideHelloIcon = data.hide_launcher || false; } if ('launch_widget' in data) { - helloChatbotManager.helloLaunchWidget = data.launch_widget || false; + manager.helloLaunchWidget = data.launch_widget || false; } if ('variables' in data) { sendMessageToChatbot({ type: "SET_VARIABLES_FOR_BOT", data }); @@ -1364,13 +1420,21 @@ } } setTimeout(() => { - helloChatbotManager.state.delayElapsed = true; - helloChatbotManager.showIconIfReady(); // Check if both conditions are met + manager.state.delayElapsed = true; + manager.showIconIfReady(); }, delay); }; + // Store subdomain-specific init function + window[`${subdomain}_initChatWidget`] = subdomainInitFunction; + + // Generic initChatWidget that routes to subdomain-specific function + window.initChatWidget = (data, delay = 0) => { + window[`${subdomain}_initChatWidget`](data, delay); + }; + // Create chatWidget object with all widget control functions - window.chatWidget = { + const windowMethods = { SendDataToBot: (data) => { // Check if data has variables - send to iframe if (data && 'variables' in data) { @@ -1383,36 +1447,38 @@ addCustomData: (data) => sendMessageToChatbot({ type: "UPDATE_USER_DATA_SEGMENTO", data }), modifyCustomData: (data) => sendMessageToChatbot({ type: "UPDATE_USER_DATA_SEGMENTO", data }), addUserEvent: (data) => sendMessageToChatbot({ type: "ADD_USER_EVENT_SEGMENTO", data }), - open: (id = "") => helloChatbotManager.openChatbot(id), - close: () => helloChatbotManager.closeChatbot(), + open: (id = "") => window[`${subdomain}_helloChatbotManager`].openChatbot(id), + close: () => window[`${subdomain}_helloChatbotManager`].closeChatbot(), hide: () => { - helloChatbotManager.hideChatbotWithIcon(); + window[`${subdomain}_helloChatbotManager`].hideChatbotWithIcon(); }, show: () => { - helloChatbotManager.showChatbotIcon(); + window[`${subdomain}_helloChatbotManager`].showChatbotIcon(); }, toggleWidget: () => { - const iframeContainer = document.getElementById(helloChatbotManager.elements.chatbotIframeContainer); + const manager = window[`${subdomain}_helloChatbotManager`]; + const iframeContainer = document.getElementById(manager.elements.chatbotIframeContainer); if (iframeContainer?.style?.display === 'block') { - helloChatbotManager.closeChatbot(); + manager.closeChatbot(); } else { - helloChatbotManager.openChatbot(); + manager.openChatbot(); } }, handlePushNotification(data) { - helloChatbotManager.handlePushNotification(data); + window[`${subdomain}_helloChatbotManager`].handlePushNotification(data); }, showTicket: (id) => sendMessageToChatbot({ type: "SHOW_TICKET", data: { id } }), getTicketUnreadCount: (id) => { return new Promise((resolve, reject) => { + const manager = window[`${subdomain}_helloChatbotManager`]; // Store the resolver so handleIncomingMessages can call it - helloChatbotManager.state.unreadCountResolver = resolve; + manager.state.unreadCountResolver = resolve; // Set a timeout to clean up and resolve with 0 if no response setTimeout(() => { - if (helloChatbotManager.state.unreadCountResolver) { - helloChatbotManager.state.unreadCountResolver(0); - helloChatbotManager.state.unreadCountResolver = null; + if (manager.state.unreadCountResolver) { + manager.state.unreadCountResolver(0); + manager.state.unreadCountResolver = null; } }, 5000); @@ -1421,5 +1487,10 @@ }, }; + window[`chatWidget_${subdomain}`] = windowMethods; + if (subdomain == 'blacksea') { + window.chatWidget = windowMethods; + } + helloChatbotManager.initializeChatbot(); })(); \ No newline at end of file diff --git a/public/chat-widget-style.css b/public/chat-widget-style.css index 2ed0e30d..9ccd4f31 100644 --- a/public/chat-widget-style.css +++ b/public/chat-widget-style.css @@ -20,7 +20,7 @@ z-index: 99999 !important; } -#hello-chatbot-launcher-icon.show-bg-color { +[id$="-hello-chatbot-launcher-icon"].show-bg-color { background-color: #111111 !important; } @@ -63,7 +63,7 @@ bottom: 10px; right: 10px; background-color: #fff; - z-index: 9999; + z-index: 9999999999; display: none; box-sizing: border-box; border-radius: 12px; @@ -77,7 +77,7 @@ right: 10px; } -#hello-chatbot-icon-image { +[id$="-hello-chatbot-icon-image"] { background-color: none !important; object-fit: contain; height: 60px !important; @@ -89,11 +89,11 @@ z-index: 999999999 !important; } -#hello-iframe-parent-container { +[id$="-hello-iframe-parent-container"] { z-index: 99999; } -#hello-chatbot-launcher-icon { +[id$="-hello-chatbot-launcher-icon"] { /* background-color: transparent; */ object-fit: contain; /* cursor: pointer; */ @@ -243,7 +243,7 @@ transform: rotate(270deg); } -#hello-chatbot-iframe-component { +[id$="-hello-chatbot-iframe-component"] { width: 100%; height: 100%; border: 0px !important; @@ -257,7 +257,7 @@ object-fit: contain; } -#hello-close-button-interfaceEmbed, +[id$="-hello-close-button-interfaceEmbed"], .close-button-interfaceEmbed { cursor: pointer; position: absolute;