Connection, offline and replay
WebSocket and SSE transports, automatic fallback, heartbeat, backoff, the outbound queue and sequence replay.
Transports
| Transport | How | When |
|---|---|---|
KletsoWebSocketTransport | wss://api.kletso.ai/v1/realtime, subprotocol kletso.v1, auth as the first frame, resolves on ready | Default primary |
KletsoSseTransport | GET /v1/conversations/:id/events?after=N&stream=1 with Accept: text/event-stream; outbound frames become REST calls | Networks that block WebSockets |
KletsoAutoTransport | WebSocket first; after 2 non-auth failures it latches to SSE for the life of the client | KletsoConfig.transport: auto (default) |
Both transports refuse frames above 256 KB and enforce the connectTimeout handshake budget.
Heartbeat and reconnect
- A
pingframe everyheartbeatInterval(25 s); nopongwithinheartbeatTimeout(10 s) closes with code 4000 and reconnects. - Backoff uses full jitter between 0 and
min(backoffCap, backoffBase × 2^attempt), reset onready. - Close code 4401 means the session is invalid: the SDK stops and surfaces
KletsoAuthException. 4403 means the token expired: the SDK refreshes the session (and calls youronTokenExpiredfor host JWTs) and reconnects.
Replay and dedupe
Every server event has a seq. On reconnect the SDK sends the last seq it saw in the auth (or switch) frame and the runtime replays everything after it before going live. Duplicates and out-of-order events are dropped by seq and event id, so a flaky network never duplicates a bubble.
Offline queue
Frames sent while disconnected (messages, actions, context, track, screen) are queued and flushed after ready with their original clientId, so the runtime deduplicates retries. Beyond outboundQueueLimit (100) the oldest frame is dropped and a KletsoQueueOverflowException is emitted on the client’s error stream.
Observing the connection
ValueListenableBuilder(
valueListenable: Kletso.instance.connection.asFlutter(),
builder: (_, state, __) => Text(state.name), // closed, connecting, open, reconnecting
)
KletsoChat already shows a connection banner and a “new messages” pill; you only need this for your own UI.