useEventStream
Hook that subscribes to a Server-Sent Events endpoint of this origin, e.g. the messages of a conversation as an agent writes them.
const state = useEventStream<Message>("/api/conversations/42/events", { events: ["message", "done"], onEvent: (message, { type }) => setMessages((m) => [...m, message]),});Installation
Once per app, set up Aedis UI. The CLI verifies the signed release before writing anything and pins every dependency:
npx @aedis/cli@0.1.0 initImport the theme once in your global CSS, after Tailwind:
app/globals.css
@import "tailwindcss";@import "./styles/aedis-theme.css";Then add the component:
npx @aedis/cli@0.1.0 add use-event-stream# pnpm dlx @aedis/cli@0.1.0 add use-event-stream · yarn dlx @aedis/cli@0.1.0 add use-event-stream · bunx @aedis/cli@0.1.0 add use-event-streamProps
| Prop | Type | Default | Description |
|---|---|---|---|
| path | string | null | — | Same-origin path; null pauses. A token in the query string is refused. |
| onEvent | (data, { type, id }) => void | — | Called with the parsed JSON of each allowed event; malformed JSON is ignored. |
| events | string[] | ["message"] | Allow-list of event types. |
| enabled | boolean | true | Pause without changing the path. |
| lastEventId | string | — | Resume after this id (Last-Event-ID, 256 printable characters at most). |
| maxEventBytes / maxConnectionBytes | number | 64 KiB / 16 MiB | Bigger events are dropped; past the connection limit it reconnects from the last id. |
| heartbeatMs / maxRetries / retryMs / maxRetryMs | number | 15000 / 8 / 1000 / 30000 | Silence for 2x heartbeat reconnects; backoff with jitter honours the server's retry:. |
| returns | "connecting" | "open" | "retrying" | "closed" | "error" | — | Connection state. |
Security
- fetch with a streamed body, not EventSource: same-origin path, cookies same-origin only, redirects refused, X-Requested-With, no-store.
- Never a token in the query string: such paths throw. The session cookie (or a BFF, see bff-stream) authenticates the stream.
- Parser with limits per event and per connection; event types from an allow-list; malformed JSON ignored.
- Text is decoded in stream mode and stripInvisible runs on each assembled event, so a chunk boundary inside a surrogate pair cannot smuggle Unicode tags.
- 204 stops for good, other 4xx stop with an error state, 5xx and network errors retry with capped backoff; it aborts on unmount.