diff --git a/resources/js/app.js b/resources/js/app.js index 129251f..3b7ad81 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -4,25 +4,52 @@ import './webauthn'; // exposes window.clusevWebauthn (register/login ceremonies window.Pusher = Pusher; -// Reverb (Pusher protocol). The endpoint is injected at runtime by the layout +// Reverb (Pusher protocol). The endpoint is injected at RUNTIME by the app layout // (window.__clusev.reverb), derived from the effective panel domain — so changing the -// domain from the dashboard needs no JS rebuild. Falls back to Vite env for HMR/dev. -const rc = (typeof window !== 'undefined' && window.__clusev && window.__clusev.reverb) || {}; -const reverbScheme = rc.scheme ?? import.meta.env.VITE_REVERB_SCHEME ?? 'https'; -const reverbPort = Number(rc.port ?? import.meta.env.VITE_REVERB_PORT ?? (reverbScheme === 'https' ? 443 : 80)); -// Channels are PRIVATE: Echo POSTs to /broadcasting/auth (web group → PanelScheme + session) -// before subscribing, so we must send the CSRF token the layout exposes as . -const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content; -window.Echo = new Echo({ - broadcaster: 'reverb', - key: rc.key ?? import.meta.env.VITE_REVERB_APP_KEY, - wsHost: rc.host ?? import.meta.env.VITE_REVERB_HOST, - wsPort: reverbPort, - wssPort: reverbPort, - forceTLS: reverbScheme === 'https', - enabledTransports: ['ws', 'wss'], - auth: { headers: { 'X-CSRF-TOKEN': csrfToken } }, -}); +// domain from the dashboard needs no JS rebuild. The prod build deliberately bakes NO +// VITE_REVERB_* env (it would freeze the build-time domain); those env reads are only a +// dev/HMR convenience. +// +// CRITICAL: this MUST NOT throw at module top level. Auth pages (login / forced password +// change) use a layout that injects no __clusev, and the prod bundle has no VITE_REVERB_* +// fallback, so the config is legitimately absent there. A thrown `new Echo({key: undefined})` +// would abort this whole module BEFORE the alpine:init block below registers cmdk / +// modalTrigger / dualChart — leaving the command-palette's `x-data="cmdk(...)"` unresolved. +// Alpine then strips x-cloak but can't evaluate `x-show="open"`, so the palette renders as a +// full-screen, undismissable overlay. So: construct only when a real config is present, guard +// it, and (re)try on navigation since the config first appears on app-layout pages. +function ensureEcho() { + if (window.Echo) return; // singleton — construct once + const rc = (typeof window !== 'undefined' && window.__clusev && window.__clusev.reverb) || {}; + const key = rc.key ?? import.meta.env.VITE_REVERB_APP_KEY; + const host = rc.host ?? import.meta.env.VITE_REVERB_HOST; + if (! key || ! host) return; // no realtime config (e.g. auth pages) — skip, never throw + + const scheme = rc.scheme ?? import.meta.env.VITE_REVERB_SCHEME ?? 'https'; + const port = Number(rc.port ?? import.meta.env.VITE_REVERB_PORT ?? (scheme === 'https' ? 443 : 80)); + // Channels are PRIVATE: Echo POSTs to /broadcasting/auth (web group → PanelScheme + session) + // before subscribing, so we must send the CSRF token the layout exposes as . + const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content; + try { + window.Echo = new Echo({ + broadcaster: 'reverb', + key, + wsHost: host, + wsPort: port, + wssPort: port, + forceTLS: scheme === 'https', + enabledTransports: ['ws', 'wss'], + auth: { headers: { 'X-CSRF-TOKEN': csrfToken } }, + }); + } catch (e) { + // A realtime/websocket misconfig must never take down the rest of the UI. + console.error('[clusev] realtime (Echo/Reverb) init failed — continuing without live updates', e); + } +} +ensureEcho(); +// The reverb config first appears when an app-layout page mounts; on a SPA navigation INTO it +// (e.g. forced password change → dashboard) this module does not re-run, so try again then. +document.addEventListener('livewire:navigated', ensureEcho); // ── Modal-open detection ──────────────────────────────────────────────── // wire-elements/modal emits NO "opened" event; the only reliable, package-version-