1(function () { 2 // Session storage key - use same key as ChatbotFrame.js 3 var SESSION_STORAGE_KEY = 'widgetSessionId'; 4 5 // Helper functions for session storage 6 function getStoredSession() { 7 try { 8 var stored = localStorage.getItem(SESSION_STORAGE_KEY); 9 return stored; // Return the sessionId directly, not wrapped in object 10 } catch (e) { 11 return null; 12 } 13 } 14 15 function setStoredSession(sessionId) { 16 try { 17 if (sessionId) { 18 localStorage.setItem(SESSION_STORAGE_KEY, sessionId); 19 } else { 20 localStorage.removeItem(SESSION_STORAGE_KEY); 21 } 22 } catch (e) { 23 // Ignore storage errors 24 } 25 } 26 27 // Function to initialize the widget 28 function initWidget() { 29 // Get config from global or query params 30 var config = window.slickbotsConfig || {}; 31 if (!config.agent) { 32 // Try to get from query string 33 var params = new URLSearchParams(window.location.search); 34 config.agent = params.get('agent'); 35 } 36 if (!config.tenant) { 37 var params = new URLSearchParams(window.location.search); 38 config.tenant = params.get('tenant'); 39 } 40 if (!config.widgetUrl) { 41 var params = new URLSearchParams(window.location.search); 42 config.widgetUrl = params.get('widgetUrl'); 43 } 44 if (!config.ctread) { 45 var params = new URLSearchParams(window.location.search); 46 config.ctread = params.get('ctread'); 47 } 48 49 var embedContainerSelector = config.embedContainer || null; 50 var embedLayout = config.embedLayout; 51 if (embedContainerSelector) { 52 if (embedLayout === 'floating') { 53 console.warn( 54 'Chattitude widget: embedContainer with embedLayout "floating" is not supported; falling back to default floating widget on body.' 55 ); 56 embedContainerSelector = null; 57 embedLayout = null; 58 } else if (!embedLayout || embedLayout === 'full') { 59 embedLayout = 'full'; 60 } 61 } 62 var isEmbedFull = Boolean(embedContainerSelector && embedLayout === 'full'); 63 64 // Build iframe src with agent as query param, even if widgetUrl already has query params/hash 65 var baseUrl = config.widgetUrl + '/index.html?mode=chat'; 66 var hasHash = baseUrl.indexOf('#') !== -1; 67 var query = (baseUrl.indexOf('?') === -1 ? '?' : '&') + 'agent=' + encodeURIComponent(config.agent); 68 69 if (config.tenant) query += '&tenant=' + encodeURIComponent(config.tenant); 70 if (config.ctread) query += '&ctread=' + encodeURIComponent(config.ctread); 71 if (isEmbedFull) query += '&embed=full'; 72 var transparentBg = 73 isEmbedFull && 74 (config.transparentBackground === true || config.transparentBackground === 'true'); 75 if (transparentBg) query += '&transparent=true'; 76 // Pass parent window's location.href as pageUrl 77 query += '&pageUrl=' + encodeURIComponent(window.location.href); 78 var src; 79 if (hasHash) { 80 // If widgetUrl uses hash routing, append query after the hash 81 var parts = baseUrl.split('#'); 82 src = parts[0] + '#' + parts[1].replace(/\/?$/, '') + (parts[1].indexOf('?') === -1 ? '?' : '&') + query; 83 } else { 84 src = baseUrl + query; 85 } 86 87 var mountEl = document.body; 88 if (embedContainerSelector) { 89 mountEl = document.querySelector(embedContainerSelector); 90 if (!mountEl) { 91 console.error('Chattitude widget: embed container not found:', embedContainerSelector); 92 return; 93 } 94 var pos = window.getComputedStyle(mountEl).position; 95 if (pos === 'static' || !pos) { 96 mountEl.style.position = 'relative'; 97 } 98 } 99 100 // Create the iframe 101 var iframe = document.createElement('iframe'); 102 iframe.src = src; 103 // Detect mobile device (full embed fills host container â avoid launcher-sized iframe) 104 var uaMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent); 105 var isMobile = uaMobile && !isEmbedFull; 106 var originalOverflow = document.body.style.overflow; 107 108 // Base iframe styles 109 iframe.style.border = 'none'; 110 iframe.style.overflow = 'hidden'; 111 iframe.style.boxSizing = 'border-box'; 112
113 if (isEmbedFull) { 114 iframe.style.position = 'absolute'; 115 iframe.style.top = '0'; 116 iframe.style.left = '0'; 117 iframe.style.right = '0'; 118 iframe.style.bottom = '0'; 119 iframe.style.width = '100%'; 120 iframe.style.height = '100%'; 121 iframe.style.maxWidth = '100%'; 122 iframe.style.maxHeight = '100%'; 123 iframe.style.margin = '0'; 124 iframe.style.borderRadius = 125 typeof config.embedBorderRadius === 'string' ? config.embedBorderRadius : '12px'; 126 iframe.style.zIndex = typeof config.embedZIndex === 'number' ? String(config.embedZIndex) : '1'; 127 } else { 128 iframe.style.position = 'fixed'; 129 iframe.style.zIndex = '999999'; 130 iframe.style.borderRadius = '16px'; 131 iframe.style.marginBottom = '10px'; 132 iframe.style.marginRight = '10px'; 133 134 if (isMobile) { 135 // Mobile: Start minimized (100x100 with 20px padding around 60px icon) to avoid blocking clicks 136 iframe.style.bottom = '5px'; 137 iframe.style.right = '5px'; 138 iframe.style.top = 'auto'; 139 iframe.style.left = 'auto'; 140 iframe.style.width = '100px'; 141 iframe.style.height = '100px'; 142 iframe.style.maxWidth = '100px'; 143 iframe.style.maxHeight = '100px'; 144 iframe.style.borderRadius = '50%'; 145 } else { 146 // Desktop: Fixed bottom-right position 147 iframe.style.bottom = '0'; 148 iframe.style.right = '0'; 149 iframe.style.top = 'auto'; 150 iframe.style.left = 'auto'; 151 iframe.style.width = '370px'; 152 iframe.style.height = '520px'; 153 iframe.style.maxWidth = '370px'; 154 iframe.style.maxHeight = '520px'; 155 iframe.style.borderRadius = '16px'; 156 } 157 } 158 159 iframe.allow = 'clipboard-write; microphone;'; 160 iframe.setAttribute( 161 'sandbox', 162 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox' 163 ); 164 iframe.title = 'Chattitude Agent'; 165 166 // Extract expected origin from iframe src for security verification 167 var expectedOrigin = null; 168 try { 169 var iframeUrl = new URL(iframe.src); 170 expectedOrigin = iframeUrl.origin; 171 } catch (e) { 172 // If URL parsing fails, we'll verify against the current origin 173 expectedOrigin = window.location.origin; 174 } 175 176 // Listen for messages from the widget - register BEFORE appending iframe 177 window.addEventListener('message', function (event) { 178 // Security: Verify message origin matches the iframe origin 179 // This prevents malicious sites from sending fake resize messages 180 if (expectedOrigin && event.origin !== expectedOrigin) { 181 console.warn('â ï¸ Ignoring postMessage from unexpected origin:', event.origin, 'Expected:', expectedOrigin); 182 return; 183 } 184 185 // Handle session request from iframe 186 if (event.data && event.data.type === 'slickbots-request-session') { 187 var session = getStoredSession(); 188 iframe.contentWindow.postMessage( 189 { 190 type: 'slickbots-session-response', 191 sessionId: session 192 }, 193 '*' 194 ); 195 return; 196 } 197 198 // Handle session change from iframe 199 if (event.data && event.data.type === 'slickbots-session-changed') { 200 setStoredSession(event.data.sessionId); 201 return; 202 } 203 204 if (event.data && event.data.type === 'slickbots-resize') { 205 if (isEmbedFull) { 206 // Host container + CSS control size; iframe stays fill 207 return; 208 } 209 // Re-detect mobile in message handler for reliability 210 // Use both user agent and viewport width as fallback 211 var isMobileDevice = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent); 212 var isSmallViewport = window.innerWidth <= 768; // Common mobile breakpoint 213 var isMobileHandler = isMobileDevice || isSmallViewport; 214 215 if (isMobileHandler) { 216 // Mobile: Update size based on widget state
217 // Widget sends 60x60 when minimized, fullscreen size when maximized 218 var width = event.data.chatWidth || 60; 219 var height = event.data.chatHeight || 60; 220 221 // Determine if minimized (small icon with padding, possibly with bubble) or maximized (fullscreen) 222 // Height can be up to ~220px when bubble is visible, width up to ~280px 223 if (width <= 300 && height <= 250) { 224 // Minimized: small icon with padding at bottom-right 225 // Clamp to reasonable values for minimized state 226 width = Math.max(40, Math.min(width, 300)); 227 height = Math.max(40, Math.min(height, 250)); 228 229 iframe.style.width = width + 'px'; 230 iframe.style.maxWidth = width + 'px'; 231 iframe.style.height = height + 'px'; 232 iframe.style.maxHeight = height + 'px'; 233 iframe.style.bottom = '5px'; 234 iframe.style.right = '5px'; 235 iframe.style.top = 'auto'; 236 iframe.style.left = 'auto'; 237 // Use rounded corners instead of full circle when bubble is visible (larger size) 238 if (width > 150 || height > 150) { 239 iframe.style.borderRadius = '20px'; // Rounded rectangle for bubble 240 } else { 241 iframe.style.borderRadius = '50%'; // Full circle for icon only 242 } 243 document.body.style.overflow = originalOverflow; 244 } else { 245 // Maximized: fullscreen - use 100% width and height 246 iframe.style.width = '100%'; 247 iframe.style.maxWidth = '100%'; 248 iframe.style.height = '100%'; 249 iframe.style.maxHeight = '100%'; 250 iframe.style.bottom = '0'; 251 iframe.style.right = '0'; 252 iframe.style.left = '0'; 253 iframe.style.top = '0'; 254 iframe.style.borderRadius = '0'; 255 document.body.style.overflow = 'hidden'; 256 } 257 } else { 258 // Desktop: Update size but maintain bottom-right positioning 259 // Always update both width and height, even if only one is provided 260 var width = event.data.chatWidth 261 ? Math.min(event.data.chatWidth, window.innerWidth || 1200) 262 : parseInt(iframe.style.width, 10) || 350; 263 var height = event.data.chatHeight 264 ? Math.min(event.data.chatHeight, window.innerHeight || 1200) 265 : parseInt(iframe.style.height, 10) || 520; 266 267 iframe.style.width = width + 'px'; 268 iframe.style.maxWidth = width + 'px'; 269 iframe.style.height = height + 'px'; 270 iframe.style.maxHeight = height + 'px'; 271 272 // Ensure positioning stays bottom-right 273 iframe.style.bottom = '0'; 274 iframe.style.right = '0'; 275 iframe.style.top = 'auto'; 276 iframe.style.left = 'auto'; 277 iframe.style.transform = 'none'; 278 279 if (width > 300 || height > 500) { 280 iframe.style.boxShadow = '0 12px 48px rgba(0, 0, 0, 0.35), 0 4px 16px rgba(0, 0, 0, 0.25)'; 281 } else { 282 iframe.style.boxShadow = 'none'; 283 } 284 } 285 } else if (event.data && event.data.type === 'closeSlickbot') { 286 // Handle close message from widget 287 iframe.style.display = 'none'; 288 // Optionally remove the iframe entirely 289 // iframe.parentNode.removeChild(iframe); 290 } 291 }); 292 293 // Insert iframe into page AFTER message listener is registered 294 mountEl.appendChild(iframe); 295 } 296 297 function run() { 298 if (!document.body) { 299 return; 300 } 301 initWidget(); 302 } 303 304 if (document.readyState === 'loading') { 305 document.addEventListener('DOMContentLoaded', run); 306 } else { 307 run(); 308 } 309})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.