# Reverb & Echo

Source: https://docs.qiick.io/externa/reverb-and-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](https://laravel.com/docs/13.x/reverb) · [Broadcasting / presence](https://laravel.com/docs/13.x/broadcasting) · Fallback: [Minimal vs full](https://docs.qiick.io/externa/minimal-vs-full-stack)

## 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                                                           |
| Presence `collection-item.{collectionId}.{itemId}` (editors) | **CRDT field sync** — collab is presence + soft lock only ([Collaborative editing](https://docs.qiick.io/externa/collaborative-editing)) |
| 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)](https://docs.qiick.io/externa/chat).

## Events (notification classes that broadcast)

All four use trait `App\Notifications\Concerns\BroadcastsWithDatabase`:

- Always `database`
- Plus `broadcast` when `BROADCAST_CONNECTION` is **not** `log` / `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 }`                                |
| `collection-item.{collectionId}.{itemId}` | Presence `presence-collection-item.…`              | Active user + `can-show-collections` + item readable; `{ 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`):

```ini
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.1` or `localhost` (not `0.0.0.0`).
- After changing `VITE_REVERB_*`, restart `npm run dev` (or rebuild).
- **Do not** also run `php artisan reverb:start` while 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](https://docs.qiick.io/externa/deployment#docker-production).

### Standalone Reverb (no Herd service)

```bash
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:

```ts
realtime: {
  enabled: boolean
  broadcaster: string
}
```

`enabled` is false when `BROADCAST_CONNECTION` is `log` or `null` — UI skips Echo; bell keeps the **60s unread poll**.
