1import { getChatProfile, isReviewSystems } from './chatProfiles.js'; 2import { installChatHome } from './chatHome.js'; 3 4export const WIDGET_URL = 'https://salesiq.zohopublic.com/widget?wc=siq2cb9730a126d35f0054ef754ae294816d344a10e62833785e43c0a7ae0a7241d'; 5 6/** An explicit release gate: installing assets does not activate an unverified bot. */ 7export function canStartChat(config, hostname) { 8 return typeof config?.product === 'string' && config.enabled === true && !isReviewSystems(hostname) && 9 Boolean(getChatProfile(config.product, config.surface)); 10} 11 12export async function startChat(doc, config) { 13 const win = doc.defaultView; 14 const excluded = () => (config.excludedPaths || []).some(path => win.location.pathname === path || win.location.pathname.startsWith(path + '/')); 15 if (excluded()) return; 16 if (config.consentKey) { 17 try { if (win.localStorage.getItem(config.consentKey) !== 'accepted') return; } catch { return; } 18 } 19 if (!canStartChat(config, win.location.hostname) || doc.querySelector('[data-ms-chat-runtime]')) return; 20 // Never replace another brand or take over an existing visitor session. 21 if (doc.querySelector('#zsiqscript') || win.$zoho?.salesiq) return; 22 const marker = doc.createElement('meta'); 23 marker.dataset.msChatRuntime = '1'; 24 doc.head.append(marker); 25 let stop; 26 let previousProduct; 27 let sessionEnded = false; 28 const endSession = () => { 29 sessionEnded = true; 30 win.$zoho?.salesiq?.reset?.(); 31 }; 32 win.addEventListener('multisystems:session-ended', endSession); 33 const profileForPage = () => { 34 const slug = config.product === 'multisystems' && win.location.pathname.startsWith('/products/') 35 ? win.location.pathname.split('/')[2] : config.product; 36 const hostProfile = getChatProfile(config.product, config.surface, '/chat/logos/'); 37 const topicProfile = getChatProfile(slug, config.surface, '/chat/logos/'); 38 return topicProfile ? { ...topicProfile, logo: hostProfile.logo } : hostProfile; 39 }; 40 const response = await win.fetch('/chat/welcome.css'); 41 const css = response.ok ? await response.text() : ''; 42 if (sessionEnded) return; 43 const launcherStyle = doc.createElement('style'); 44 launcherStyle.textContent = `#zs_fl_chat.siqico-chat{background:white var(--ms-chat-logo) center/32px 32px no-repeat;border-radius:50%}#zs_fl_chat.siqico-chat::before{visibility:hidden}@media(max-width:600px){#zsiq_chat_wrap{left:0!important;right:0!important;bottom:0!important;width:100vw!important;max-width:100vw!important;height:var(--ms-chat-height,100dvh)!important;max-height:var(--ms-chat-height,100dvh)!important}#siq_chatwindow{width:100%!important;height:100%!important}}`; 45 doc.head.append(launcherStyle); 46 const resize = () => doc.documentElement.style.setProperty('--ms-chat-height', `${win.visualViewport?.height ?? win.innerHeight}px`); 47 const update = () => { 48 if (sessionEnded) return; 49 if (excluded()) { 50 win.$zoho?.salesiq?.floatbutton?.visible?.('hide'); 51 win.$zoho?.salesiq?.floatwindow?.visible?.('hide'); 52 return; 53 } 54 win.$zoho?.salesiq?.floatbutton?.visible?.('show'); 55 const profile = profileForPage(); 56 win.$zoho?.salesiq?.visitor?.info?.({ ms_product: profile.product, ms_surface: profile.surface }); 57 if (previousProduct === profile.product) return; 58 previousProduct = profile.product; 59 doc.documentElement.style.setProperty('--ms-chat-logo', `url("${profile.logo}")`); 60 stop?.(); 61 if (css) stop = installChatHome(doc, css, profile); 62 }; 63 win.$zoho = win.$zoho || {}; 64 win.$zoho.salesiq = { ready: () => { 65 if (sessionEnded) { win.$zoho?.salesiq?.reset?.(); return; } 66 update(); 67 } }; 68 const script = doc.createElement('script'); 69 script.id = 'zsiqscript'; 70 script.src = WIDGET_URL; 71 script.defer = true; 72 doc.head.append(script); 73 resize(); update(); 74 win.visualViewport?.addEventListener('resize', resize); 75 win.addEventListener('resize', resize); 76 win.addEventListener('popstate', update); 77 // One adapter for Next, React Router, Astro and plain browser navigation. 78 for (const method of ['pushState', 'replaceState']) { 79 const original = win.history[method]; 80 win.history[method] = function (...args) { const result = original.apply(this, args); update(); return result; }; 81 } 82} 83 84const browserDocument = globalThis.document; 85if (browserDocument && !isReviewSystems(browserDocument.defaultView.location.hostname)) { 86 browserDocument.defaultView.fetch('/chat/site.json').then(response => response.ok ? response.json() : null) 87 .then(config => config && startChat(browserDocument, config)) 88 .catch(() => { /* The host page and its contact links remain usable. */ }); 89}
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.