(() => { let ws = null; let reconnectTimer = null; function normalizeToken(value) { const token = String(value || '').trim(); if (!token) return ''; if (token.toLowerCase().startsWith('bearer ')) { return token.slice(7).trim(); } return token; } function getCookie(name) { const cookie = document.cookie || ''; const parts = cookie.split(';').map(p => p.trim()); const match = parts.find(p => p.startsWith(`${name}=`)); if (!match) return ''; return decodeURIComponent(match.slice(name.length + 1)); } function getToken() { const fromLocal = normalizeToken(localStorage.getItem('access_token')); if (fromLocal) return fromLocal; const fromSession = normalizeToken(sessionStorage.getItem('access_token')); if (fromSession) return fromSession; const fromCookie = normalizeToken(getCookie('access_token')); return fromCookie; } function ensureContainer() { let container = document.getElementById('telefoni-toast-container'); if (!container) { container = document.createElement('div'); container.id = 'telefoni-toast-container'; container.setAttribute('aria-live', 'polite'); container.setAttribute('aria-atomic', 'true'); container.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 9999; width: 420px; max-width: 90%; `; document.body.appendChild(container); } return container; } function escapeHtml(str) { return String(str ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } function showIncomingCallToast(data) { const container = ensureContainer(); const contact = data.contact || null; const number = data.number || ''; const title = contact?.name ? contact.name : 'Ukendt nummer'; const company = contact?.company ? contact.company : ''; const recentCases = data.recent_cases || []; const lastCall = data.last_call; const callId = data.call_id; const toastEl = document.createElement('div'); toastEl.className = 'toast align-items-stretch'; toastEl.setAttribute('role', 'alert'); toastEl.setAttribute('aria-live', 'assertive'); toastEl.setAttribute('aria-atomic', 'true'); const openContactBtn = contact?.id ? `` : ''; // Build recent cases HTML let casesHtml = ''; if (recentCases.length > 0) { casesHtml = '
Åbne sager:'; recentCases.forEach(c => { casesHtml += `
${escapeHtml(c.titel)}
`; }); casesHtml += '
'; } // Build last call HTML let lastCallHtml = ''; if (lastCall) { // lastCall can be either a date string (legacy) or an object with started_at and bruger_navn const callDate = lastCall.started_at ? lastCall.started_at : lastCall; const lastCallDate = new Date(callDate); const now = new Date(); const diffMs = now - lastCallDate; const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); let timeAgo = ''; if (diffDays === 0) { timeAgo = 'I dag'; } else if (diffDays === 1) { timeAgo = 'I går'; } else if (diffDays < 7) { timeAgo = `${diffDays} dage siden`; } else { timeAgo = lastCallDate.toLocaleDateString('da-DK'); } const brugerInfo = lastCall.bruger_navn ? ` (${escapeHtml(lastCall.bruger_navn)})` : ''; // Format duration let durationInfo = ''; if (lastCall.duration_sec) { const mins = Math.floor(lastCall.duration_sec / 60); const secs = lastCall.duration_sec % 60; if (mins > 0) { durationInfo = ` - ${mins}m ${secs}s`; } else { durationInfo = ` - ${secs}s`; } } lastCallHtml = `
Sidst snakket: ${timeAgo}${brugerInfo}${durationInfo}
`; } toastEl.innerHTML = `
Opkald ${escapeHtml(data.direction === 'outbound' ? 'Udgående' : 'Indgående')}
${escapeHtml(number)}
${escapeHtml(title)}
${company ? `
${escapeHtml(company)}
` : ''} ${lastCallHtml} ${casesHtml}
${openContactBtn}
`; container.appendChild(toastEl); const toast = new bootstrap.Toast(toastEl, { autohide: false }); toast.show(); toastEl.addEventListener('hidden.bs.toast', () => toastEl.remove()); toastEl.addEventListener('click', (e) => { const btn = e.target.closest('button[data-action]'); if (!btn) return; const action = btn.getAttribute('data-action'); if (action === 'open-contact' && contact?.id) { window.location.href = `/contacts/${contact.id}`; } if (action === 'create-case') { const qs = new URLSearchParams(); if (contact?.id) qs.set('contact_id', String(contact.id)); qs.set('title', `Telefonsamtale – ${number}`); qs.set('telefoni_opkald_id', String(callId)); window.location.href = `/sag/new?${qs.toString()}`; } }); } function scheduleReconnect() { if (reconnectTimer) return; reconnectTimer = setTimeout(() => { reconnectTimer = null; connect(); }, 5000); } function connect() { if (ws && ws.readyState === WebSocket.OPEN) { return; } const token = getToken(); if (!token) { scheduleReconnect(); return; } const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'; const url = `${proto}://${window.location.host}/api/v1/telefoni/ws?token=${encodeURIComponent(token)}`; ws = new WebSocket(url); ws.onopen = () => console.log('📞 Telefoni WS connected'); ws.onclose = (evt) => { console.log('📞 Telefoni WS disconnected', evt.code, evt.reason || ''); scheduleReconnect(); }; ws.onerror = () => { // onclose handles reconnect }; ws.onmessage = (evt) => { try { const msg = JSON.parse(evt.data); if (msg?.event === 'incoming_call') { showIncomingCallToast(msg.data || {}); } } catch (e) { console.warn('Telefoni WS message parse failed', e); } }; } document.addEventListener('DOMContentLoaded', connect); window.addEventListener('focus', connect); window.addEventListener('storage', (evt) => { if (evt.key === 'access_token') connect(); }); })();