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 |
Presence channel chat.{uuid} (team chat messages / typing) | Public CMS / GraphQL / API keys — no realtime fan-out |
Connection status indicator (avatar ConnectionDot) | Unread count when BROADCAST_CONNECTION is log/null — 60s poll only then |
Auth via /broadcasting/auth (session cookie) | |
Private user events ChatUnreadUpdated / ThreadUpserted |
When realtime is on, the notifications bell does not poll every 60s. Poll is the fallback for the minimal stack only.
Team chat product docs: Chat (hub & items).
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).
Docker Compose
compose.yaml runs a dedicated reverb service. Host port defaults to 8081 (container 8080) so it does not clash with Herd Pro on :8080. Use .env.docker.example (REVERB_PORT / VITE_REVERB_PORT=8081). Inside the Compose network the app talks to REVERB_HOST=reverb on port 8080; browsers use the published host port.
Production: proxy WebSocket upgrades to the Reverb service — Deployment — Docker.
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.