1const ZENDESK_WIDGET_KEY = '176ae8cf-d251-46a2-a822-6b06f03dab03'; 2const ZENDESK_SNIPPET_ID = 'ze-snippet'; 3const ZENDESK_SHOW_CHAT_WEBHOOK_PATH = '/webhook/show-chat-zendesk'; 4const ZENDESK_CONVERSATION_FIELDS_PATH = '/api/v1/zendesk/conversation-fields'; 5 6const zendeskState = window.__scribbrZendeskState || { 7 ready: false, 8 openOnReady: false, 9 openCallbacks: [], 10 openHandlerRegistered: false, 11 snippetLoadStarted: false, 12 loginStarted: false, 13}; 14 15window.__scribbrZendeskState = zendeskState; 16 17const getZendeskSnippetSrc = () => { 18 return `https://static.zdassets.com/ekr/snippet.js?key=${ZENDESK_WIDGET_KEY}`; 19}; 20 21const getCookie = function (name) { 22 let value = '; ' + document.cookie; 23 let parts = value.split('; ' + name + '='); 24 if (parts.length === 2) { 25 return parts.pop().split(';').shift(); 26 } 27}; 28 29const getToken = function () { 30 let token = getCookie('token'); 31 if (isProd()) { 32 token = getCookie('token_app'); 33 } 34 return token; 35}; 36 37const isProd = () => { 38 return window.location.host.startsWith('www.scribbr') 39 || window.location.host.startsWith('app.scribbr') 40 || window.location.host.startsWith('editor.scribbr') 41 || window.location.host.startsWith('techademia.scribbr'); 42}; 43 44const isLocalDebugHost = () => { 45 return window.location.hostname === 'localhost' || window.location.hostname.endsWith('.local'); 46}; 47 48const getHoustonBaseUrl = () => { 49 if (isLocalDebugHost()) { 50 return 'http://scribbr.local'; 51 } 52 53 if (isProd()) { 54 return 'https://app.scribbr.com'; 55 } 56 57 return 'https://staging.scribbr.com'; 58}; 59 60const sendShowChatWebhook = () => { 61 const token = getToken(); 62 63 if (!token) { 64 return; 65 } 66 67 try { 68 const xmlHttp = new XMLHttpRequest(); 69 xmlHttp.open('POST', `${getHoustonBaseUrl()}${ZENDESK_SHOW_CHAT_WEBHOOK_PATH}`, true); 70 xmlHttp.setRequestHeader('Authorization', 'Bearer ' + token); 71 xmlHttp.send(null); 72 } catch (error) { 73 console.error(error); 74 } 75}; 76 77const fetchZendeskMessagingJwt = () => { 78 const token = getToken(); 79 80 if (!token) { 81 return Promise.reject(new Error('Missing Scribbr auth token')); 82 } 83 84 return fetch(`${getHoustonBaseUrl()}/api/v1/zendesk/messaging-jwt`, { 85 method: 'POST', 86 headers: { 87 Authorization: 'Bearer ' + token, 88 }, 89 credentials: 'omit', 90 }).then(function (response) { 91 if (!response.ok) { 92 throw new Error('Failed to fetch Zendesk messaging JWT'); 93 } 94 95 return response.json(); 96 }).then(function (data) { 97 if (!data || !data.token) { 98 throw new Error('Missing Zendesk messaging JWT in response'); 99 } 100 101 return data.token; 102 }); 103}; 104 105/** 106 * Conversation field payloads for Zendesk Messaging (custom ticket fields). 107 * @returns {Promise<Array<{id: string, value: string|number|boolean}>>} 108 */ 109const fetchZendeskConversationFields = () => { 110 const token = getToken(); 111 112 if (!token) { 113 return Promise.reject(new Error('Missing Scribbr auth token')); 114 } 115 116 return fetch(`${getHoustonBaseUrl()}${ZENDESK_CONVERSATION_FIELDS_PATH}`, { 117 method: 'POST', 118 headers: { 119 Authorization: 'Bearer ' + token, 120 }, 121 credentials: 'omit', 122 }).then(function (response) { 123 if (!response.ok) { 124 throw new Error('Failed to fetch Zendesk conversation fields'); 125 } 126 127 return response.json(); 128 }).then(function (data) { 129 if (!data || !Array.isArray(data.conversationFields)) { 130 return []; 131 } 132 133 return data.conversationFields; 134 }); 135}; 136 137/** 138 * Applies fields to the widget so they attach to the next conversation / message (after login). 139 * @param {() => void} [onComplete] 140 */ 141const syncZendeskConversationFields = (onComplete) => { 142 const done = typeof onComplete === 'function' ? onComplete : function () {}; 143 144 if (typeof window.zE !== 'function') { 145 done(); 146 return; 147 } 148 149 fetchZendeskConversationFields() 150 .then(function (fields) { 151 if (!fields.length) { 152 done(); 153 return; 154 } 155 156 window.zE('messenger:set', 'conversationFields', fields, done); 157 }) 158 .catch(function (error) { 159 console.error('Zendesk conversation fields sync failed', error); 160 done(); 161 }); 162}; 163 164const authenticateZendeskMessagingUser = () => { 165 const token = getToken(); 166 167 if (typeof window.zE !== 'function' || !token || zendeskState.loginStarted) { 168 return; 169 } 170 171 zendeskState.loginStarted = true; 172 window.zE('messenger', 'loginUser', function (callback) { 173 fetchZendeskMessagingJwt() 174 .then(function (zendeskJwt) { 175 callback(zendeskJwt); 176 }) 177 .catch(function (error) { 178 zendeskState.loginStarted = false;
179 console.error(error); 180 }); 181 }, function (error) { 182 if (error) { 183 zendeskState.loginStarted = false; 184 console.error('Zendesk messaging login failed', error); 185 return; 186 } 187 188 syncZendeskConversationFields(); 189 }); 190}; 191 192const openZendeskWidget = () => { 193 if (!zendeskState.ready || typeof window.zE !== 'function') { 194 zendeskState.openOnReady = true; 195 return; 196 } 197 198 const token = getToken(); 199 200 if (!token) { 201 window.zE('messenger', 'open'); 202 return; 203 } 204 205 syncZendeskConversationFields(function () { 206 window.zE('messenger', 'open'); 207 }); 208}; 209 210const flushOpenCallbacks = () => { 211 zendeskState.openCallbacks.forEach((callback) => { 212 try { 213 callback(); 214 } catch (error) { 215 console.error(error); 216 } 217 }); 218}; 219 220const registerZendeskOpenHandler = () => { 221 if (zendeskState.openHandlerRegistered || typeof window.zE !== 'function') { 222 return; 223 } 224 225 zendeskState.openHandlerRegistered = true; 226 window.zE('messenger:on', 'open', function () { 227 sendShowChatWebhook(); 228 flushOpenCallbacks(); 229 }); 230}; 231 232const onZendeskReady = () => { 233 if (typeof window.zE !== 'function') { 234 return; 235 } 236 237 window.zE(function () { 238 zendeskState.ready = true; 239 registerZendeskOpenHandler(); 240 authenticateZendeskMessagingUser(); 241 242 if (zendeskState.openOnReady) { 243 zendeskState.openOnReady = false; 244 openZendeskWidget(); 245 } 246 }); 247}; 248 249const ensureZendeskSnippet = () => { 250 if (typeof window.zE === 'function' && zendeskState.ready) { 251 authenticateZendeskMessagingUser(); 252 return; 253 } 254 255 const existingScript = document.getElementById(ZENDESK_SNIPPET_ID); 256 257 if (existingScript) { 258 onZendeskReady(); 259 return; 260 } 261 262 if (zendeskState.snippetLoadStarted) { 263 return; 264 } 265 266 zendeskState.snippetLoadStarted = true; 267 268 const script = document.createElement('script'); 269 script.id = ZENDESK_SNIPPET_ID; 270 script.src = getZendeskSnippetSrc(); 271 script.async = true; 272 script.onload = onZendeskReady; 273 document.body.appendChild(script); 274}; 275 276/** 277 * Minimal messenger API (show / auth refresh) for legacy call sites and the chat launcher. 278 */ 279const installScribbrChatShim = () => { 280 window.scribbrChat = function (action, payload) { 281 switch (action) { 282 case 'show': 283 openZendeskWidget(); 284 break; 285 case 'onShow': 286 if (typeof payload === 'function') { 287 zendeskState.openCallbacks.push(payload); 288 } 289 break; 290 case 'shutdown': 291 if (typeof window.zE === 'function') { 292 window.zE('messenger', 'logoutUser'); 293 } 294 zendeskState.loginStarted = false; 295 break; 296 case 'boot': 297 case 'update': 298 authenticateZendeskMessagingUser(); 299 break; 300 case 'reattach_activator': 301 break; 302 default: 303 break; 304 } 305 }; 306}; 307 308const loadZendesk = function () { 309 installScribbrChatShim(); 310 ensureZendeskSnippet(); 311}; 312 313loadZendesk(); 314 315if (new URLSearchParams(window.location.href.split('?')[1] || '').has('logged-out')) { 316 window.scribbrChat('shutdown'); 317 window.scribbrChat('boot'); 318}
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.