/* * ⚠️ Messages are ESCAPED. This builds innerHTML, and callers pass server error strings straight * in — including ones that reflect user input verbatim, such as the OIDC issuer in * `not a URL: `. A review typed `` as an issuer and got script * execution in the admin's own session. A toast is a place text goes, never markup. */ function esc(v) { return String(v == null ? '' : v) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } export function showToast(message, type = 'info', duration = 4000) { const container = document.getElementById('toastContainer'); const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.setAttribute('role', type === 'error' ? 'alert' : 'status'); toast.setAttribute('aria-live', type === 'error' ? 'assertive' : 'polite'); toast.innerHTML = ` ${type === 'success' ? '' : type === 'error' ? '' : ''} ${esc(message)} `; container.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateX(100%)'; toast.style.transition = 'all 0.3s ease'; setTimeout(() => toast.remove(), 300); }, duration); }