> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Kotlin SDK - SSE Live Updates

> Stream connection, reconnect, quarantine, and rate-limit handling for the Kotlin SDK.

The SDK can maintain a persistent Server-Sent Events (SSE) connection to `GET /v1/stream`. When the Nexus platform publishes a change (config edit, secret rotation, flag toggle), the server sends an event and the SDK immediately calls `sync()` to refresh its local snapshot.

## Starting the stream

```kotlin theme={null}
val client = NexusClient.create(config)
client.connectStream()
```

`connectStream()` is non-blocking. It launches a coroutine on the SDK's internal `Dispatchers.IO` scope and returns immediately. Your application thread is not blocked.

## Observing stream state

```kotlin theme={null}
import kotlinx.coroutines.flow.collect

// Collect in a coroutine
launch {
    client.streamStatus.collect { status ->
        println("Stream: $status")
    }
}
```

`streamStatus` is a `StateFlow<StreamStatus>`. Possible values:

| Value          | Meaning                                                                                                     |
| -------------- | ----------------------------------------------------------------------------------------------------------- |
| `CONNECTED`    | SSE connection established, receiving events                                                                |
| `DISCONNECTED` | Not connected (initial state, reconnecting, or stream stopped)                                              |
| `FALLBACK`     | SSE is sleeping the reconnect cooldown schedule after max consecutive transport errors; TTL sync still runs |

## Reconnect with exponential back-off

When the SSE connection drops due to a transient error (network blip, server restart), the SDK reconnects automatically.

* Back-off starts at 1 second.
* Each failure doubles the delay: 1 s, 2 s, 4 s, 8 s, ..., capped at 30 seconds.
* `observer.onDisconnected(cause)` fires on each failure.
* `observer.onReconnectAttempt(attempt)` fires before each reconnect attempt (1-indexed).
* After 3 consecutive failures, the SDK calls `observer.onFallback("max-errors")` and enters the reconnect cooldown schedule. TTL-based background sync continues during this period. Once each cooldown interval elapses, the SDK retries the SSE connection automatically.

```
failure 1 -> wait 1s  -> reconnect (attempt 1)
failure 2 -> wait 2s  -> reconnect (attempt 2)
failure 3 -> wait 4s  -> onFallback("max-errors") -> sleep cooldown[0]
                                                  -> retry SSE
                                                  -> if still failing: sleep cooldown[1], ...
```

The cooldown schedule is controlled by the `sseReconnectCooldown` config field (unit: minutes). The default schedule is `[5, 10, 20, 40, 60]`. The SDK stays at the last value once the schedule is exhausted. Set a single-element array for a fixed interval, or a multi-element array for a custom escalating schedule.

## Quarantine behavior (HTTP 429 with quarantine body)

A quarantine response is a server-driven cooperative pause. Unlike a regular failure, a quarantine does **not** increment the failure counter.

When the stream connection returns HTTP 429 with a JSON body `{"error":"quarantined","reason":"...","expires_at":"..."}`:

1. `observer.onQuarantined(reason, expiresAt)` is called.
2. The stream coroutine sleeps until `expiresAt` (minimum 5 seconds).
3. After the quarantine lifts, the stream reconnects as if no failure occurred.

```
connected -> quarantine 429 -> onQuarantined -> sleep until expiresAt
                                             -> reconnect (failure counter unchanged)
                                             -> connected again
```

## Rate-limit behavior (HTTP 429 without quarantine body)

A plain 429 (without the quarantine JSON body) means the server is temporarily rate-limiting this client.

1. The `Retry-After` header value (in seconds) is used as the delay. Falls back to 5 seconds if the header is absent.
2. `observer.onDisconnected(null)` is called.
3. The stream reconnects after the delay. This **does not** increment the failure counter either.

## `auth_expiring` event for WIF sessions

When WIF is active, the server sends an `auth_expiring` SSE event before the session token expires. The SDK intercepts this event and silently calls `refreshSession()` in the background - it does **not** trigger a `sync()` or call `onEvent`. This ensures zero-downtime token rotation for long-running WIF sessions.

## Disconnecting the stream

```kotlin theme={null}
client.disconnectStream()
```

Cancels all stream coroutines and sets `streamStatus` to `DISCONNECTED`. The HTTP client and TTL sync are not affected. You can call `connectStream()` again to restart.

## Shutting down completely

```kotlin theme={null}
client.close()
```

Cancels the entire coroutine scope (including stream), closes both HTTP clients, and deletes temp files. Call this once on application shutdown.

## Example: reacting to status changes

```kotlin theme={null}
import dev.westyx.nexus.StreamStatus
import kotlinx.coroutines.launch

launch {
    client.streamStatus.collect { status ->
        when (status) {
            StreamStatus.CONNECTED    -> metrics.gauge("nexus.stream", 1)
            StreamStatus.DISCONNECTED -> metrics.gauge("nexus.stream", 0)
            StreamStatus.FALLBACK     -> {
                metrics.gauge("nexus.stream", 0)
                alerting.send("Nexus stream sleeping reconnect cooldown - relying on TTL sync; will retry SSE automatically")
            }
        }
    }
}

client.connectStream()
```
