Files
app/composeApp/src/commonMain/kotlin/ru/fromchat/api/ApiClient.kt
T

206 lines
6.5 KiB
Kotlin

package ru.fromchat.api
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.logging.SIMPLE
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.request.bearerAuth
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
import ru.fromchat.core.config.Config
import ru.fromchat.utils.failOnError
import kotlin.concurrent.Volatile
/**
* Creates a platform-specific HTTP client that supports WebSockets
* The config block is applied to configure plugins like WebSockets, JSON, etc.
*/
expect fun createPlatformHttpClient(
block: io.ktor.client.HttpClientConfig<*>.() -> Unit = {}
): HttpClient
object ApiClient {
val json = Json {
ignoreUnknownKeys = true
isLenient = true
encodeDefaults = true
}
val http = createPlatformHttpClient {
install(ContentNegotiation) {
json(json)
}
install(Logging) {
logger = Logger.SIMPLE
level = LogLevel.INFO
}
install(WebSockets)
}
@Volatile
var token: String? = null
@Volatile
var user: User? = null
suspend fun login(request: LoginRequest) =
http
.post("${Config.getApiBaseUrl()}/login") {
contentType(ContentType.Application.Json)
setBody(request.also { ru.fromchat.core.Logger.d("ApiClient", "Login request: $it") })
}
.failOnError()
.body<LoginResponse>()
.also {
token = it.token
user = it.user
}
suspend fun register(request: RegisterRequest) =
http
.post("${Config.getApiBaseUrl()}/register") {
contentType(ContentType.Application.Json)
setBody(request)
}
.failOnError()
suspend fun getMessages(limit: Int = 50, beforeId: Int? = null) =
http
.get("${Config.getApiBaseUrl()}/get_messages") {
contentType(ContentType.Application.Json)
bearerAuth(token ?: throw IllegalStateException("Not authenticated"))
parameter("limit", limit)
beforeId?.let { parameter("before_id", it) }
}
.failOnError()
.body<MessagesResponse>()
suspend fun send(message: String) =
http
.post("${Config.getApiBaseUrl()}/send_message") {
contentType(ContentType.Application.Json)
bearerAuth(token!!)
setBody(SendMessageRequest(message))
}
.failOnError()
.body<SendMessageResponse>()
suspend fun logout(authToken: String) {
// Don't throw on logout errors, just try to logout
try {
http.get("${Config.getApiBaseUrl()}/logout") {
bearerAuth(authToken)
}
} catch (e: Exception) {
// Ignore logout errors
}
}
// WebSocket send helpers
suspend fun sendMessage(content: String, replyToId: Int? = null, clientMessageId: String? = null) {
val token = token ?: throw IllegalStateException("Not authenticated")
WebSocketManager.send(
WebSocketMessage(
type = "sendMessage",
credentials = WebSocketCredentials(
scheme = "Bearer",
credentials = token
),
data = json.encodeToJsonElement(
WebSocketSendMessageRequest(
content = content,
reply_to_id = replyToId,
client_message_id = clientMessageId
)
)
)
)
}
suspend fun editMessage(messageId: Int, content: String) {
val token = token ?: throw IllegalStateException("Not authenticated")
WebSocketManager.send(
WebSocketMessage(
type = "editMessage",
credentials = WebSocketCredentials(
scheme = "Bearer",
credentials = token
),
data = json.encodeToJsonElement(
WebSocketEditMessageRequest(
message_id = messageId,
content = content
)
)
)
)
}
suspend fun deleteMessage(messageId: Int) {
val token = token ?: throw IllegalStateException("Not authenticated")
WebSocketManager.send(
WebSocketMessage(
type = "deleteMessage",
credentials = WebSocketCredentials(
scheme = "Bearer",
credentials = token
),
data = json.encodeToJsonElement(
WebSocketDeleteMessageRequest(
message_id = messageId
)
)
)
)
}
suspend fun sendTyping() {
val token = token ?: throw IllegalStateException("Not authenticated")
try {
WebSocketManager.send(
WebSocketMessage(
type = "typing",
credentials = WebSocketCredentials(
scheme = "Bearer",
credentials = token
)
)
)
} catch (e: Exception) {
// Silently ignore if WebSocket is not connected yet
// Typing indicators are not critical
}
}
suspend fun sendStopTyping() {
val token = token ?: throw IllegalStateException("Not authenticated")
try {
WebSocketManager.send(
WebSocketMessage(
type = "stopTyping",
credentials = WebSocketCredentials(
scheme = "Bearer",
credentials = token
)
)
)
} catch (e: Exception) {
// Silently ignore if WebSocket is not connected yet
// Typing indicators are not critical
}
}
}