> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bbrands.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Client Integration

> The shared usePusherChannel hook, the client environment contract and the polling fallback.

## The shared hook

Every BBrandsLab frontend subscribes through one framework-agnostic hook,
`usePusherChannel`, exported from `@bbrandslab/ui`.

<CardGroup cols={2}>
  <Card title="One socket per (key, cluster)" icon="plug">
    Clients are memoised at module level so several subscriptions on the same
    page reuse a single WebSocket.
  </Card>

  <Card title="Lazy pusher-js" icon="feather">
    Loaded with a dynamic import. `pusher-js` is a **peer dependency** of the UI
    package; each consuming app declares it as a direct dependency.
  </Card>

  <Card title="Bounded buffer" icon="layer-group">
    Events are batched per animation frame and capped at `maxEvents` (default
    250\), so high-throughput channels never freeze the UI.
  </Card>

  <Card title="Graceful disable" icon="power-off">
    An empty/undefined `pusherKey` yields `status: "disabled"` without opening a
    socket.
  </Card>
</CardGroup>

```tsx theme={null}
const { events, latest, status } = usePusherChannel({
  channel: machineId ? `totem-${machineId}` : null,
  cluster: env.NEXT_PUBLIC_PUSHER_CLUSTER,
  events: ["alert:created", "command:result"],
  pusherKey: env.NEXT_PUBLIC_PUSHER_KEY,
});

if (status === "disabled") return <ConfigureRealtimeBanner />;
```

When `events` is omitted the hook binds every event on the channel and filters
out Pusher system events.

## Client environment variables

| Variable                     | Purpose                                    |
| ---------------------------- | ------------------------------------------ |
| `NEXT_PUBLIC_PUSHER_KEY`     | Public app key. Empty → realtime disabled. |
| `NEXT_PUBLIC_PUSHER_CLUSTER` | Cluster, defaults to `us2`.                |

<Note>
  The key is public by design; only the server holds
  `INTEGRATION_PUSHER_SECRET`. Private channels are authorized through
  [`POST /api/v3/realtime/pusher/auth`](/realtime/channels-and-events).
</Note>

## Consuming apps

<CardGroup cols={2}>
  <Card title="portal-customer" icon="file-signature">
    Subscribes to the digital-contract channel and invalidates the contract
    query when a signature-stage event arrives.
  </Card>

  <Card title="horizon-enterprise" icon="tower-broadcast">
    Subscribes to NOC and Maihue Street totem channels; combines Pusher
    invalidation with a 60 s notifications poll.
  </Card>
</CardGroup>

## Polling fallback

Realtime is optional, so every consumer keeps a query-based fallback. When no
Pusher key is configured, the UI relies on React Query refetching alone. Design
every screen so it is fully usable without realtime.
