Connecting and Lifecycle
Fetch credentials, connect a channel, observe recovery, and release resources.
Before you start: complete the Quickstart. These snippets belong in its web/app.js; replace the corresponding setup or handler rather than creating a second client and channel.
1. Fetch credentials
The SDK has a built-in production endpoint. Supply a credential provider that calls your application's credential endpoint:
import { createClient } from "@useceleris/client";
const client = createClient({
credentialProvider: async (request) => {
const response = await fetch("/api/realtime-credentials", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
channelReference: request.channelReference,
replayLookbackMs: request.replayLookbackMs,
}),
signal: request.signal,
});
if (!response.ok) throw new Error("Credential request failed.");
return response.json();
},
});
const channel = client.channel("demo-room");
The provider receives a channel reference, a reason ("initial" or "reconnect"), and an abort signal. On recovery it also receives disconnectedAt, a Unix-millisecond number, and replayLookbackMs, a duration in milliseconds as a number. These optional fields can be sent directly in JSON; they are not bigint timestamps. The initial request omits them.
Return { payload, signature } exactly as your backend signed it. Forward the abort signal and check the HTTP result. Do not generate, cache, or inspect the signed strings.
Your backend must validate the requested channel and decide replay policy; see Authentication. The provider runs on every connection attempt, including recovery. A provider failure becomes a safe SDK error; its original details are not forwarded.
Constructing a client, channel, or segment does not open a connection. Use baseUrl only for local or self-hosted Celeris, not your application's credential endpoint. See the Client API for options.
2. Observe state, then connect
const events = channel.events();
const stopState = events.onStateChange((state) => {
console.log("Connection:", state);
});
const stopErrors = events.onError((error) => {
console.error(error.name, error.message);
});
try {
await channel.connect();
} catch (error) {
console.error("Initial connection failed:", error.code);
}
Expected: connecting, then connected. A failed initial attempt rejects the call and sets failed, without also reporting that failure to onError. It is not retried automatically.
Once connected, the channel is automatically a member of default. Use channel.defaultSegment() to access that stream; do not open another connection or call subscribe() just to join it. Register its message listener before connecting, and make sure your credentials grant read/write access to default. The Quickstart instead grants access to chat; see default-segment messaging.
| State | What your app should do |
|---|---|
idle | Offer Connect |
connecting | Wait; avoid another connect call |
connected | Enable connected operations |
reconnecting | Disable sending and show connection status |
failed | Explain the failure and offer an explicit retry |
closing | Wait for cleanup |
closed | Create a new channel if the user needs another connection |
A Retry button can call channel.connect() when the state is failed. Calling it while connected, connecting, or reconnecting rejects with OperationInProgress.
To cancel a connection attempt, pass an AbortSignal:
// Alternative connect call, not a second simultaneous attempt:
const controller = new AbortController();
const connecting = channel.connect({ signal: controller.signal });
controller.abort();
try {
await connecting;
} catch (error) {
console.log(error.code); // Cancelled
}
3. Respond to recovery
Register a recovery listener once when setting up the channel:
const stopRecovery = events.onRecovery((recovery) => {
console.log("Recovered:", recovery.retryIndex);
console.log("Gaps possible:", recovery.possibleGaps);
console.log("Duplicates possible:", recovery.possibleDuplicates);
// Trigger your application's snapshot reload here.
});
retryIndex is a number equal to the failed reconnect attempts already consumed in the current budget. It is 0 when recovery succeeds without an earlier failed attempt in that budget; it is not a count of socket drops. possibleGaps and possibleDuplicates are always true. For an order dashboard, reload the order from your own API and reconcile live updates using an application-owned version. The SDK does not supply that application-specific endpoint.
Listeners survive reconnects; do not register them again on every connected event. Also handle errors inside any asynchronous work you start in a listener: synchronous listener isolation is not a substitute for catching rejected promises.
See Reconnection and Recovery for triggers, timing, restored interests, replay permissions, and retry exhaustion. The event is not a replay-complete signal.
4. Release resources
Each event registration returns a disposer. Subscription handles have cancel(). The channel owns the socket.
await channel.close();
stopRecovery();
stopErrors();
stopState();
Also release your message and presence listeners and subscription handles when their UI component no longer needs them. Close the channel only if that component owns the shared connection.
close() is idempotent and permanent. It cancels recovery and closes the segment handles too. Do not reuse a closed channel; obtain a new one from the client.
Next: Messaging.