/* Kundenansicht (Mitarbeiter oeffnet das Portal eines Kunden aus dem Ticketsystem). Ein Browser haelt nur eine Portal-Sitzung: oeffnet jemand Kunde B, waehrend Kunde A noch in einem Tab offen ist, darf der Tab von A keine Daten von B laden. Deshalb traegt jede eigene /api-Anfrage die Kennung der Ansicht (X-Kundenansicht); der Server antwortet bei fremder Kennung mit 409 view_replaced, nach 2 Stunden mit 401 view_expired. Dann zeigt dieses Skript nur noch einen Hinweis. Klassisches Skript, vor app.js laden. */ (function () { 'use strict' const HEADER = 'X-Kundenansicht' let viewId = '' function isOwnApi(url) { try { const u = new URL(String(url), window.location.href) return u.origin === window.location.origin && u.pathname.startsWith('/api/') } catch { return false } } function overlay(text) { const existing = document.getElementById('kb-view-overlay-text') if (existing) { existing.textContent = text return } const box = document.createElement('div') box.id = 'kb-view-overlay' box.className = 'kb-view-overlay' box.setAttribute('role', 'alertdialog') box.setAttribute('aria-modal', 'true') const card = document.createElement('div') card.className = 'kb-view-overlay-card' const p = document.createElement('p') p.id = 'kb-view-overlay-text' p.textContent = text const button = document.createElement('button') button.type = 'button' button.textContent = 'Tab schließen' button.addEventListener('click', () => window.close()) card.appendChild(p) card.appendChild(button) box.appendChild(card) document.body.appendChild(box) } function showReplaced(kunde) { overlay(`Diese Ansicht wurde durch die Kundenansicht von ${kunde || 'einem anderen Kunden'} ersetzt.`) } function showExpired() { overlay('Die Kundenansicht ist abgelaufen. Bitte im Ticketsystem erneut öffnen.') } function inspect(status, body) { if (status === 409 && body && body.code === 'view_replaced') showReplaced(body.kunde) else if (status === 401 && body && body.code === 'view_expired') showExpired() } const originalFetch = window.fetch.bind(window) window.fetch = async function (input, init) { const url = typeof input === 'string' ? input : (input && input.url) || String(input) const own = isOwnApi(url) let options = init || {} if (viewId && own) { const headers = new Headers(options.headers || (typeof input !== 'string' && input && input.headers) || {}) headers.set(HEADER, viewId) options = { ...options, headers } } const response = await originalFetch(input, options) if (own && (response.status === 409 || response.status === 401)) { response.clone().json().then((body) => inspect(response.status, body)).catch(() => {}) } return response } if (window.XMLHttpRequest) { const proto = window.XMLHttpRequest.prototype const open = proto.open const send = proto.send proto.open = function (method, url, ...rest) { this.kbOwnApi = isOwnApi(url) return open.call(this, method, url, ...rest) } proto.send = function (...args) { if (this.kbOwnApi) { if (viewId) this.setRequestHeader(HEADER, viewId) this.addEventListener('load', () => { if (this.status !== 409 && this.status !== 401) return try { inspect(this.status, JSON.parse(this.responseText)) } catch { /* keine JSON-Antwort */ } }) } return send.apply(this, args) } } window.KbView = { setId(id) { viewId = String(id || '') }, id: () => viewId, showReplaced, showExpired, } })()