Server-Sent Events
Wrapper sobre EventSource com reconnect exponencial, heartbeat opt-in e parsing JSON. Caso de uso original: stream de notificações (NEW-ALO, PAYMENT-SUCCESS, etc.) do alofans-frontend.
SSE vs WebSocket — qual escolher?
SSE é unidirecional (servidor → cliente), roda sobre HTTP comum, reconecta sozinho e autentica por cookie sem cerimônia. Se o cliente não precisa enviar mensagens, SSE é mais simples e barato. Pra tráfego bidirecional (chat, colaboração), use WebSocket.
Quando usar
- Push unilateral servidor → cliente, sem necessidade de envio do cliente.
- Reconexão automática barata.
- Autenticação por cookie (
withCredentials: true).
API imperativa — createEventStream
Use fora de React (services, inicialização) quando você gerencia o ciclo de vida na mão:
import { createEventStream } from "tempest-react-sdk";
interface StreamEvent {
type: "NOTIFY" | "PAYMENT-SUCCESS";
message: string;
}
const stream = createEventStream<StreamEvent>(
`${import.meta.env.VITE_API_URL}/notifications/stream`,
{
withCredentials: true,
namedEvents: ["notification", "payment"],
heartbeatEvents: ["ping"],
maxRetries: 10,
onOpen: () => console.log("SSE aberto"),
onMessage: ({ event, data, id }) => {
if (event === "payment") handlePayment(data);
else addNotification(data);
},
onStatusChange: (status) => console.log("SSE", status),
onError: (err) => console.error(err),
},
);
// Mais tarde, ao desmontar / deslogar:
stream.close();
// Forçar reconexão imediata (zera o contador de tentativas):
stream.reconnect();
// Ler o status atual:
console.log(stream.status);
Cada onMessage recebe { event, data, id, raw }: event é o nome do evento SSE (default "message"), data já vem JSON-parsed (com fallback pra string crua), id é o lastEventId do servidor e raw é o MessageEvent original.
Reconexão e heartbeat
Backoff: 1s → 2s → 4s → 8s → ... (limitado em 30s), até maxRetries (default 10)
- A cada erro o stream fecha, agenda reconexão com backoff exponencial e emite status
"closed". Ao reabrir com sucesso, o contador zera. - Esgotadas as
maxRetries, o status vira"error"e o stream para de tentar. - Eventos listados em
heartbeatEvents(default["ping"]) não disparamonMessage— só mantêm o socket vivo.
Configure os heartbeatEvents conforme seu backend
Se o servidor envia keep-alives sob outro nome de evento (ex.: "keepalive"), liste-o em heartbeatEvents pra não poluir o onMessage com pings.
Hook — useEventStream
Dentro de componentes, o hook amarra o ciclo de vida do stream ao componente — abre na montagem, fecha no unmount:
import { useEventStream } from "tempest-react-sdk";
interface Notification {
id: string;
message: string;
}
export function NotificationListener({ user }: { user: { id: string } | null }) {
const { status, lastMessage, reconnect } = useEventStream<Notification>(
`${import.meta.env.VITE_API_URL}/notifications/stream`,
{
enabled: !!user, // só conecta quando o usuário existe
withCredentials: true,
onMessage: ({ data }) => addToInbox(data),
},
);
return (
<div>
<span>Stream: {status}</span>
{status === "error" && <button onClick={reconnect}>Reconectar</button>}
{lastMessage && <p>Última: {lastMessage.data.message}</p>}
</div>
);
}
enabled: falsedesconecta o stream (use enquanto espera ousercarregar).- Mudar a URL re-abre a conexão; mudar o
onMessagenão (callback via ref interna — sem reconexões à toa). lastMessageguarda a última mensagem recebida (heartbeats não contam).- Cleanup automático no unmount.
error significa que esgotou as tentativas
Quando o status chega em "error", o stream desistiu sozinho. Ofereça um botão chamando reconnect() (que zera o contador) em vez de esperar uma reconexão automática que não vem mais.
Status
"idle" | "connecting" | "open" | "closed" | "error":
idle— ainda não conectou (ouenabled: false).connecting— handshake em andamento.open— conectado e recebendo.closed— fechado, possivelmente aguardando reconexão.error— esgotoumaxRetries, não tenta mais.
Recap
createEventStream(url, options)abre um SSE com reconnect exponencial; o controller expõeclose,reconnectestatus.useEventStream(url, options)é o wrapper React: amarra o ciclo de vida ao componente, expõestatus/lastMessage/reconnecte respeitaenabled.- Heartbeats (default
["ping"]) mantêm o socket vivo sem dispararonMessage. - Status
"error"= esgotou tentativas; ofereçareconnect()ao usuário.