Operations
Reverb & Echo
Externa uses Reverb + Laravel Echo (laravel-echo + pusher-js) for admin realtime notifications and presence. Locally, prefer Herd Pro Reverb on :8080 (already running as a shared service). AI chat SSE is unchanged.
Upstream
Reverb · Broadcasting / presence · Fallback: Minimal vs full
What websockets cover
| Covered by WebSockets | Not covered (other transport) |
|---|---|
| Private user notifications (file zip / duplication outcomes) | AI chat — browser SSE (POST /ai/chat), not Echo |
Presence channel online (green dots on Users list) | Activity log flood — Spatie rows stay poll/Inertia; not broadcast per row |
Connection status indicator (avatar ConnectionDot) | Public CMS / GraphQL / API keys — no realtime fan-out |
Auth via /broadcasting/auth (session cookie) | Unread count when BROADCAST_CONNECTION is log/null — 60s poll only then |
When realtime is on, the notifications bell does not poll every 60s. Poll is the fallback for the minimal stack only.
Events (notification classes that broadcast)
All four use trait App\Notifications\Concerns\BroadcastsWithDatabase:
- Always
database - Plus
broadcastwhenBROADCAST_CONNECTIONis notlog/null/ empty
| Class | data.type | Emitted from |
|---|---|---|
FileZipReadyNotification | file_zip_ready | PrepareFilesZipJob |
FileZipFailedNotification | file_zip_failed | PrepareFilesZipJob |
FileDuplicationCompletedNotification | file_duplication_completed | DuplicateFilesJob |
FileDuplicationFailedNotification | file_duplication_failed | DuplicateFilesJob |
toBroadcast() wraps the same toArray() payload as the database notification (BroadcastMessage).
Channels
Name in routes/channels.php | Wire / Echo name | Auth |
|---|---|---|
App.Models.User.{id} | Private App.Models.User.{id} | Owner only ((int) $user->id === (int) $id) |
online | Presence presence-online (Echo join('online')) | Active User; returns { id, name } |
Echo listen points (UI)
| Location | Behavior |
|---|---|
resources/js/lib/echo.ts | ensureEcho() — boots Reverb client when Inertia realtime.enabled is true |
resources/js/app.tsx | Eager ensureEcho(true) on boot when enabled |
NotificationsBell | echo.private('App.Models.User.{id}').notification(...) — bumps unread + NOTIFICATIONS_UPDATED_EVENT |
useOnlineUsers | echo.join('online') — .here / .joining / .leaving → Users list green dots |
useEchoConnection + ConnectionDot | Tracks Pusher connection state; rendered on sidebar avatar via UserInfo (nav-user.tsx → showConnectionStatus) |
Connection dot tones: connected (green), connecting (amber pulse), disabled (muted), else destructive.
Herd Reverb (recommended local)
Herd Pro runs a shared Reverb on port 8080. Match credentials from Herd → Services → Reverb (defaults in externa-core/.env.example):
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=1001
REVERB_APP_KEY=laravel-herd
REVERB_APP_SECRET=secret
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
- Host for Echo must be
127.0.0.1orlocalhost(not0.0.0.0). - After changing
VITE_REVERB_*, restartnpm run dev(or rebuild). - Do not also run
php artisan reverb:startwhile Herd Reverb owns:8080— port conflict. - Auth endpoint:
/broadcasting/auth(web middleware / session cookie).
Standalone Reverb (no Herd service)
php84 artisan reverb:start
Use your own REVERB_APP_* values on both the app and the Reverb process. composer run dev:full starts reverb:start for that path.
Feature detect
HandleInertiaRequests shares:
realtime: {
enabled: boolean
broadcaster: string
}
enabled is false when BROADCAST_CONNECTION is log or null — UI skips Echo; bell keeps the 60s unread poll.