PageSourceSearch

https://easyverein.com/static/homepage/js/frontChat.js?version=9bb5d2

js easyverein.com collected 2026-09-24 11:16:09 UTC 11,976 bytes, 436 lines download raw bytes

1/**
2 * Sets up the chat and knowledge base
3 *
4 * @param {boolean} isLitePage - Whether the page is a lite page or not
5 */
6function initializeFront(isLitePage = false) {
7	const button = document.querySelector('#openSupportChat')
8	button?.addEventListener('click', async (e) => {
9		startLoadingAnimation(button, isLitePage)
10		if (button.dataset.initialized !== 'true') {
11			await initFrontChat(button, true)
12		} else {
13			await showFrontChat()
14		}
15		setHasMessages(false)
16		stopLoadingAnimation(button, isLitePage)
17		setCookie('openFrontChat', true)
18	})
19	document.querySelectorAll('.chatBotLink, .chatLink').forEach((element) => {
20		element.addEventListener('click', (event) => {
21			event.preventDefault()
22			document.querySelector('.fc-button').click()
23		})
24	})
25	document.querySelectorAll('.chatBotLink').forEach((element) => {
26		element.addEventListener('click', (event) => {
27			setTimeout(() => {
28				sendMessageInFrontChat('Starte den Bot')
29			}, 50)
30		})
31	})
32
33	if (getCookie('openFrontChat')) {
34		initFrontChat(button, false)
35	}
36}
37
38/**
39 * Initializes the chat window
40 *
41 * @param {HTMLElement} initButton - The button that initializes the chat
42 * @param {boolean} show - Whether to also show the chat window or not
43 */
44async function initFrontChat(initButton, show = false) {
45	const globals = getGlobals()
46	let chatId = globals.frontChatId
47	let frontIdentity = globals.frontIdentity
48	if (frontIdentity.length === 0) {
49		chatId = globals.frontSalesChatId
50		frontIdentity = {}
51	}
52	frontIdentity.chatId = chatId
53	frontIdentity.useDefaultLauncher = false
54
55	window.FrontChat('init', frontIdentity)
56
57	// Front has no real callback for when the chat is ready, so we have to poll
58	while (!isFrontChatInitialized()) {
59		await sleep(200)
60	}
61	if (show) {
62		await showFrontChat()
63	}
64	initButton.dataset.initialized = 'true'
65	window.FrontChat('onUnreadChange', (data) => {
66		if (data.unread_count && !isFrontChatOpen()) {
67			setHasMessages(true)
68		}
69	})
70}
71
72/**
73 * Opens the chat window
74 */
75async function showFrontChat() {
76	while (!isFrontChatOpen()) {
77		window.FrontChat('show')
78		await sleep(200)
79	}
80	const supportUnavailableMessage = getContextData('supportUnavailable')
81	if (supportUnavailableMessage && supportUnavailableMessage.length) {
82		const chatFrame = document.getElementById('front-chat-iframe')
83		if (chatFrame) {
84			const chatDocument = chatFrame.contentWindow.document
85			if (!chatDocument.querySelector('.supportUnavailableMessage')) {
86				const html = `<div class="supportUnavailableMessage" style="margin: 1rem; padding: .5rem; border: 1px solid #23985D; background-color: #E2F8ED; font-size: smaller; border-radius: 1rem;">${supportUnavailableMessage}</div>`
87				chatDocument
88					?.querySelector('textarea')
89					?.parentNode?.parentNode?.parentNode?.previousElementSibling?.insertAdjacentHTML(
90						'beforeend',
91						html
92					)
93			}
94		}
95	}
96}
97
98/**
99 * Checks if the chat window is initialized
100 *
101 * @returns {boolean} Whether the chat window is initialized or not
102 */
103function isFrontChatInitialized() {
104	const chatFrame = document.getElementById('front-chat-iframe')
105	return (
106		chatFrame && chatFrame.contentWindow && chatFrame.contentWindow.FrontChatApp
107	)
108}
109
110/**
111 * Checks if the chat window is open
112 *
113 * @returns {boolean} Whether the chat window is open or not
114 */
115function isFrontChatOpen() {
116	const chatFrame = document.getElementById('front-chat-iframe')
117	return isFrontChatInitialized() && chatFrame.clientHeight
118}
119
120/**
121 * Toggles the visibility of the unread messages display
122 * Calls the API to remove the unread status
123 *
124 * @param {boolean} hasMessages - whether the user has messages or not
125 */
126function setHasMessages(hasMessages) {
127	setVisibility('.has-chats-display', hasMessages)
128	setVisibility('.has-no-chats-display', !hasMessages)
129
130	if (!hasMessages && getGlobals().frontIdentity.email !== undefined) {
131		fetch('/webhook/front/read/', { method: 'POST', headers: headers() })
132	}
133}
134
135/**
136 * Sleeps for a given amount of time
137 *
138 * @param {number} ms - The time to sleep in milliseconds
139 * @returns {Promise<void>} A promise that resolves after the given time
140 */
141function sleep(ms) {
142	return new Promise((resolve) => setTimeout(resolve, ms))
143}
144
145/**
146 * Starts the loading animation for the button
147 *
148 * @param {HTMLElement} button - the button for the animation
149 * @param {boolean} isLitePage - whether the page is a lite page or not
150 */
151function startLoadingAnimation(button, isLitePage) {
152	if (isLitePage) {
153		setLoadingCircle(button.querySelector('.fc-button'), true, false, false)
154	} else {
155		setLoadingCircle(button, false, true)
156	}
157}
158
159/**
160 * Stops the loading animation for the button
161 *
162 * @param {HTMLElement} button - the button for the animation
163 * @param {boolean} isLitePage - whether the page is a lite page or not
164 */
165function stopLoadingAnimation(button, isLitePage) {
166	button.querySelector('.loading')?.remove()
167	if (isLitePage) {
168		button
169			.querySelector('.fc-button')
170			.insertAdjacentHTML('beforeend', '<i class="far fa-comment-lines"></i>')
171	}
172}
173
174/**
175 * Returns the CSRF token
176 *
177 * @throws If no CSRF Token (`{% csrf_token %}`) is on the page
178 * @returns {string} CSRF middleware token
179 */
180function getCSRF() {
181	const csrfInput = document.querySelector('[name=csrfmiddlewaretoken]')
182	if (csrfInput === undefined) {
183		throw new Error(
184			"Couldn't find a CSRF-Token input on the page. Please fix the markup."
185		)
186	}
187	return csrfInput.value
188}
189
190/**
191 * Gives correct headers for API requests
192 *
193 * @returns {object} request header object
194 * @throws if no CSRF-Token has been included in the HTMl template
195 */
196function headers() {
197	const csrf = getCSRF()
198	return {
199		Accept: 'application/json, text/plain, */*',
200		'Content-Type': 'application/json',
201		'X-CSRFToken': csrf,
202	}
203}
204
205/**
206 * Set the loading circle spinner to the given element if it is not already set
207 *
208 * @param {Element} element - A html element to show the spinner circle inside
209 * @param {boolean} centered - (optional) Set the spinner in the center of the element
210 * @param {boolean} keepContent - (optional) Don't reset the content of the element
211 * @param {boolean} spinnerSmall - (optional) size of the spinner
212 */
213function setLoadingCircle(
214	element,
215	centered = true,
216	keepContent = false,
217	spinnerSmall = true
218) {
219	setLoadingCircleText(element, undefined, centered, keepContent, spinnerSmall)
220}
221
222/**
223 * Set the loading circle spinner to the given element if it is not already set
224 *
225 * @param {Element} element - A html element to show the spinner circle inside
226 * @param {string} text - Text to show below the spinner. If falsy don't show the text.
227 * @param {boolean} centered - (optional) Set the spinner in the center of the element
228 * @param {boolean} keepContent - (optional) Dont reset the content of the element
229 * @param {boolean} spinnerSmall - (optional) size of the spinner
230 */
231function setLoadingCircleText(
232	element,
233	text,
234	centered = true,
235	keepContent = false,
236	spinnerSmall = true
237) {
238	if (!element.innerHTML.includes('spinner-border')) {
239		let textSpan = '<span class="visually-hidden">Laden…</span>'
240		if (text) {
241			textSpan = `<span>${text}</span>`
242		}
243		const loader = `
244			<span class="loading loading-circle ${
245				centered
246					? 'd-flex flex-column align-items-center justify-content-center'
247					: ''
248			}">
249			<span class="spinner-border ${
250				spinnerSmall ? 'spinner-border-sm' : ''
251			}" role="status" aria-hidden="true"></span>
252			${textSpan}
253			</span>
254		`
255		element.innerHTML = keepContent ? element.innerHTML + ` ${loader}` : loader
256	}
257}
258
259/**
260 * Stores a key value pair as a cookie with optional time to live.
261 *
262 * @param {string} name - name of the cookie
263 * @param {string} value - cookie value
264 * @param {number} [daysTillExpired] - days until cookie expires, can be `null`
265 */
266function setCookie(name, value, daysTillExpired = null) {
267	if (daysTillExpired !== null) {
268		const d = new Date()
269		d.setTime(d.getTime() + daysTillExpired * 24 * 60 * 60 * 1000)
270		const expires = 'expires=' + d.toUTCString()
271		document.cookie =
272			name + '=' + value + ';' + expires + ';path=/;SameSite=Lax'
273	} else {
274		document.cookie = name + '=' + value + ';path=/;SameSite=Lax'
275	}
276}
277
278/**
279 * Retrieves a cookie
280 *
281 * @param {string} name - name of the cookie to retrieve
282 * @returns {string} retrieved cookie or `''`
283 */
284function getCookie(name) {
285	name = name + '='
286
287	// this is needed cause "usedEmails" cookie could contain "%", but "%" crashes in decodeURIComponent, see #13279
288	let decodedCookie = ''
289	try {
290		decodedCookie = decodeURIComponent(document.cookie)
291	} catch (error) {
292		decodedCookie = document.cookie
293		console.error(error)
294	}
295
296	const ca = decodedCookie.split(';')
297	for (let i = 0; i < ca.length; i++) {
298		let c = ca[i]
299		while (c.charAt(0) === ' ') {
300			c = c.substring(1)
301		}
302		if (c.indexOf(name) === 0) {
303			return c.substring(name.length, c.length)
304		}
305	}
306	return ''
307}
308
309/**
310 * Show a HTML element by removing the .hidden class
311 *
312 * @param {object} element - HTML node
313 */
314function showElement(element) {
315	element.classList.remove('hidden')
316}
317
318/**
319 * Hide a HTML element by adding the .hidden class
320 *
321 * @param {object} element - HTML node
322 */
323function hideElement(element) {
324	element.classList.add('hidden')
325}
326
327/**
328 * Sets the visibility of the element based on `visible`.
329 *
330 * @param {HTMLElement} element - HTML element
331 * @param {boolean} visible - if true shows the element, else hides it
332 */
333function setElementVisibility(element, visible) {
334	if (visible) {
335		showElement(element)
336	} else {
337		hideElement(element)
338	}
339}
340
341/**
342 * Set the visibility of all HTML elements matching selector.
343 *
344 * @param {string} selector - string of the selector; what you'd add in querySelector
345 * @param {boolean} visible - if true shows the element, else hides it
346 */
347function setVisibility(selector, visible) {
348	document.querySelectorAll(selector).forEach((element) => {
349		setElementVisibility(element, visible)
350	})
351}
352
353/**
354 * Retrieve and parse data from a Django json_script template tag
355 *
356 * @param {string} id - name of the json_script (`json_script:"id"`)
357 * @returns {Array | object} parsed JSON object, or empty array
358 */
359function getContextData(id) {
360	if (!id) {
361		return []
362	}
363	const node = document.querySelector(`#${id}`)
364	if (node !== undefined) {
365		try {
366			return JSON.parse(node.innerText)
367		} catch (error) {
368			return []
369		}
370	}
371	return []
372}
373
374/**
375 * Returns global Django template constants.
376 *
377 * @returns {object} object of template constants
378 */
379function getGlobals() {
380	return {
381		languageCode: getContextData('language_code'),
382		userID: getContextData('user_id'),
383		isDebug: getContextData('is_debug'),
384		frontChatId: getContextData('frontChatId'),
385		frontSalesChatId: getContextData('frontSalesChatId'),
386		frontIdentity: getContextData('frontIdentity'),
387	}
388}
389
390document.addEventListener('DOMContentLoaded', () => {
391	const supportUnavailableMessage = getContextData('supportUnavailable')
392	if (!supportUnavailableMessage.length || !supportUnavailableMessage) {
393		initializeFront(true)
394	}
395})
396
397/**
398 * Sends a message in the FrontChat
399 *
400 * @param {string} message - The message to send
401 */
402async function sendMessageInFrontChat(message) {
403	if (!isFrontChatInitialized()) {
404		console.error('FrontChat is not initialized.')
405		return
406	}
407
408	const chatFrame = document.getElementById('front-chat-iframe')
409	const chatDocument = chatFrame.contentWindow.document
410	const textArea = chatDocument.querySelector('textarea')
411	let botButton
412	let sendButton
413	chatDocument.querySelectorAll('button').forEach((button) => {
414		if (button.innerText === 'Ich möchte mit einem KI Bot sprechen') {
415			botButton = button
416		}
417		if (button.parentNode.dataset.testid === 'message-input-submit-button') {
418			sendButton = button
419		}
420	})
421
422	if (textArea) {
423		textArea.value = message
424		const event = new Event('input', { bubbles: true })
425		textArea.dispatchEvent(event)
426
427		if (sendButton) {
428			await sleep(100)
429			sendButton.click()
430		} else {
431			console.error('Send button not found.')
432		}
433	} else {
434		botButton.click()
435	}
436}

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.