Learn
Reconnect, replay, and Durable Object hibernation
When a Room DO hibernates, WebSockets drop and wake again. FluxyChat’s SDK handles the client experience: backoff, connectionState, and history replay via loadMore.
What happens on the server
Durable Objects can hibernate idle WebSocket connections to save resources. When clients reconnect, the DO accepts a new upgrade and resumes fan-out — but messages sent while a client was offline are not magically replayed over the socket alone.
That is why production chat pairs live WebSocket events with persisted history in D1.
What the SDK does for you
- connectionState.status — connected, connecting, reconnecting, disconnected.
- connectionState.nextRetryAt — show “Reconnecting in 3s…” in your UI.
- connectionState.transport — websocket, sse, or polling when WS is blocked.
- loadMore() — paginate older messages after refresh or reconnect.
- clientMessageId — idempotent retries for sends that failed mid-flight.
- deliveryStatus per message — pending, sent, failed with retryMessage().
JWT refresh before expiry (partysocket-style)
Long-lived sessions need the JWT refreshed before exp to avoid a 1008 close mid-session. The SDK passes the token on the WS URL — re-connecting with a fresh JWT keeps the user online without an explicit reconnect trigger.
- Use
decodeFluxyJwtPayload(token)andjwtRefreshDelayMs(payload)(from@fluxy-chat/sdk) to schedule a refresh ~60s beforeexp. - On
onAuthError(close 1008), mint a new JWT server-side and callclient.connectRoom(roomId, …)again with the new token. The previousFluxyChatRoomConnectioninstance is already in the disconnected state, so the new connect is a clean re-handshake. - The room DO does not store the JWT — every connect re-validates the token + room membership, so token rotation is safe.
React example (useChat)
import { FluxyChatClient } from "@fluxy-chat/sdk";
import { useChat } from "@fluxy-chat/react";
const client = new FluxyChatClient({ baseUrl, userId, token });
function Room({ roomId }: { roomId: string }) {
const { messages, sendMessage, connectionState, loadMore, hasMore, retryMessage } =
useChat({ roomId, client });
return (
<div>
<p>
{connectionState.status}
{connectionState.nextRetryAt
? ` · retry ${connectionState.nextRetryAt}`
: null}
</p>
{hasMore ? (
<button type="button" onClick={() => void loadMore()}>
Load older messages
</button>
) : null}
{messages.map((m) => (
<div key={m.id}>
{m.content}
{m.deliveryStatus === "failed" ? (
<button type="button" onClick={() => retryMessage(m.clientMessageId!)}>
Retry
</button>
) : null}
</div>
))}
</div>
);
}Vanilla / Vue / Solid (createFluxyRoomSession)
import { FluxyChatClient, createFluxyRoomSession } from "@fluxy-chat/sdk";
const client = new FluxyChatClient({ baseUrl, userId, token });
const { store, stop } = createFluxyRoomSession({ roomId: "support-1", client });
store.subscribe((state) => {
console.log(state.connectionState, state.messages.length);
});
// After reconnect: store.getState().loadMore()
// Cleanup: stop();Pusher Channels alternative for SaaS chat
Pusher's free tier works until it doesn't. If connection pricing is climbing faster than your app, room chat on Cloudflare with optional self-host is worth a look.
Session security — storage, refresh, and token lifecycle
Best practices for managing FluxyChat sessions: secure token storage, refresh strategies, and preventing session fixation.