createHttpClient

fun createHttpClient(engine: HttpClientEngine = httpClientEngine(), onUnauthorized: suspend () -> Unit = {}, logger: Logger = Logger.DEFAULT, expectSuccess: Boolean = true, retry: Boolean = true, requestTimeoutMillis: Long?): HttpClient

The shared client: content negotiation, INFO logging, a bounded exponential-backoff retry on transient (5xx / IO) failures, and a request timeout. Base URL and auth are applied per-call by the consumer's typed API layer (they change at runtime, so they can't live in a static defaultRequest).

onUnauthorized fires once per 401 response on a NON-auth route — the auth endpoints under /api/auth/ are skipped so a bad-credentials login 401 doesn't trigger a token clear / re-login loop. Wired lazily by networkModule to the bound UnauthorizedHandler.

logger routes Ktor's request/response log lines — defaults to Logger.DEFAULT (platform println/Logcat), same as before this parameter existed. Pass a consumer's own facade (e.g. an AppLog-backed Logger) to fold HTTP logging into the app's existing log pipeline instead.

expectSuccess/retry/requestTimeoutMillis default to the original hardcoded behavior (throw-on-non-2xx, bounded retry, 30s timeout) so existing callers are unaffected. A long-lived WebSocket or manual-status-handling client (Kursi's RoomApi) should pass expectSuccess = false, retry = false, requestTimeoutMillis = null — expectSuccess=true would throw on every non-2xx instead of letting the caller inspect the status, retry doesn't make sense for a socket upgrade, and a 30s requestTimeoutMillis would kill a long-lived connection.