PageSourceSearch

https://www.scribbr.co.uk/wp-content/themes/scribbr-2017/js/chat.zendesk.js

js scribbr.co.uk collected 2026-09-28 06:39:55 UTC 8,373 bytes, 318 lines download raw bytes

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.