Fix the payload format

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2025-10-07 13:26:04 +03:00
Unverified
parent 83eedb1eb8
commit a8a0986223
6 changed files with 78 additions and 44 deletions
+2 -1
View File
@@ -9,7 +9,8 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.FromChat">
android:theme="@style/Theme.FromChat"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
+2 -1
View File
@@ -3,7 +3,8 @@ package ru.fromchat
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.format.char
const val API_HOST = "fromchat.ru"
const val API_HOST = "http://10.0.2.2:8301"
const val WS_API_HOST = "ws://10.0.2.2:8301"
val DATETIME_FORMAT = LocalDateTime.Format {
day()
@@ -16,10 +16,11 @@ import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import ru.fromchat.API_HOST
import ru.fromchat.utils.failOnError
object ApiClient {
private val json = Json {
val json = Json {
ignoreUnknownKeys = true
isLenient = true
encodeDefaults = true
@@ -53,7 +54,7 @@ object ApiClient {
suspend fun login(request: LoginRequest) =
http
.post("https://fromchat.ru/api/login") {
.post("$API_HOST/api/login") {
contentType(ContentType.Application.Json)
setBody(request.also { Log.d("ApiClient", "Login request: $it") })
}
@@ -66,7 +67,7 @@ object ApiClient {
suspend fun register(request: RegisterRequest) =
http
.post("https://fromchat.ru/api/register") {
.post("$API_HOST/api/register") {
contentType(ContentType.Application.Json)
setBody(request)
}
@@ -74,7 +75,7 @@ object ApiClient {
suspend fun getMessages() =
http
.get("https://fromchat.ru/api/get_messages") {
.get("$API_HOST/api/get_messages") {
contentType(ContentType.Application.Json)
}
.failOnError()
@@ -82,7 +83,7 @@ object ApiClient {
suspend fun send(message: String) =
http
.post("https://fromchat.ru/api/send_message") {
.post("$API_HOST/api/send_message") {
contentType(ContentType.Application.Json)
bearerAuth(token!!)
setBody(SendMessageRequest(message))
+2 -1
View File
@@ -61,7 +61,8 @@ data class Message(
@Serializable
data class SendMessageRequest(
val content: String
val content: String,
val reply_to_id: Int? = null
)
@Serializable
@@ -3,9 +3,8 @@ package ru.fromchat.api
import android.util.Log
import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
import io.ktor.client.plugins.websocket.webSocket
import io.ktor.client.request.url
import io.ktor.http.HttpMethod
import io.ktor.http.URLProtocol
import io.ktor.http.encodedPath
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
import kotlinx.coroutines.CoroutineScope
@@ -20,7 +19,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import kotlinx.serialization.json.Json
import ru.fromchat.API_HOST
import ru.fromchat.WS_API_HOST
import kotlin.coroutines.suspendCoroutine
object WebSocketManager {
@@ -55,16 +54,12 @@ object WebSocketManager {
try {
ApiClient.http.webSocket(
method = HttpMethod.Get,
host = API_HOST,
request = {
url {
protocol = URLProtocol.WSS
host = "fromchat.ru"
encodedPath = "/api/chat/ws"
}
url("$WS_API_HOST/api/chat/ws")
}
) {
session = this
connecting = false
Log.d("WebSocketManager", "WebSocket connected")
for (frame in incoming) {
@@ -82,17 +77,31 @@ object WebSocketManager {
}
} catch (e: Throwable) {
Log.w("WebSocketManager", "An error occurred:", e)
connecting = false
session = null
delay(3000)
} finally {
Log.w("WebSocketManager", "WebSocket disconnected")
session = null
connecting = false
}
}
}
}
suspend fun send(message: WebSocketMessage) {
session?.send(Frame.Text(json.encodeToString(message)))
val session = session
if (session != null) {
try {
session.send(Frame.Text(json.encodeToString(message)))
} catch (e: Exception) {
Log.e("WebSocketManager", "Failed to send message", e)
throw e
}
} else {
Log.w("WebSocketManager", "Cannot send message: no active session")
throw IllegalStateException("No active WebSocket session")
}
}
@OptIn(DelicateCoroutinesApi::class)
@@ -101,12 +110,21 @@ object WebSocketManager {
var handler: ((WebSocketMessage) -> Unit)? = null
return try {
// Check if we have a valid session before sending
if (session == null) {
Log.w("WebSocketManager", "No active WebSocket session")
return null
}
send(message)
withTimeout(timeoutMs) {
suspendCoroutine { continuation ->
handler = {
continuation.resumeWith(Result.success(it))
removeGlobalMessageHandler(handler!!)
handler = { response ->
// Only process responses that match our request type
if (response.type == message.type) {
continuation.resumeWith(Result.success(response))
removeGlobalMessageHandler(handler!!)
}
}
addGlobalMessageHandler(handler)
}
@@ -114,6 +132,9 @@ object WebSocketManager {
} catch (_: TimeoutCancellationException) {
Log.w("WebSocketManager", "Request timed out")
null
} catch (e: Exception) {
Log.e("WebSocketManager", "Request failed", e)
null
} finally {
handler?.let { removeGlobalMessageHandler(it) }
}
@@ -67,7 +67,6 @@ import kotlinx.coroutines.launch
import kotlinx.datetime.TimeZone
import kotlinx.datetime.format
import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import ru.fromchat.DATETIME_FORMAT
@@ -75,7 +74,6 @@ import ru.fromchat.R
import ru.fromchat.api.ApiClient
import ru.fromchat.api.Message
import ru.fromchat.api.SendMessageRequest
import ru.fromchat.api.SendMessageResponse
import ru.fromchat.api.WebSocketCredentials
import ru.fromchat.api.WebSocketManager
import ru.fromchat.api.WebSocketMessage
@@ -116,8 +114,12 @@ fun PublicChatScreen() {
scope.launch {
if (msg.type == "newMessage") {
try {
messages += Json.decodeFromJsonElement<Message>(msg.data!!)
scrollDown()
val newMessage = ApiClient.json.decodeFromJsonElement<Message>(msg.data!!)
// Check if message already exists to prevent duplicates
if (messages.none { it.id == newMessage.id }) {
messages += newMessage
scrollDown()
}
} catch (e: Exception) {
Log.e("PublicChatScreen", "Failed to process incoming message:", e)
}
@@ -188,29 +190,36 @@ fun PublicChatScreen() {
IconButton(
onClick = {
scope.launch {
runCatching {
Json.decodeFromJsonElement<SendMessageResponse>(
WebSocketManager.request(
WebSocketMessage(
type = "sendMessage",
credentials = WebSocketCredentials(
scheme = "Bearer",
credentials = ApiClient.token!!
),
data = Json.encodeToJsonElement(
SendMessageRequest(
content = "${message.text}"
)
val messageText = "${message.text.trim()}"
if (messageText.isEmpty()) return@launch
try {
val response = WebSocketManager.request(
WebSocketMessage(
type = "sendMessage",
credentials = WebSocketCredentials(
scheme = "Bearer",
credentials = ApiClient.token!!
),
data = ApiClient.json.encodeToJsonElement(
SendMessageRequest(
content = messageText
)
)
)!!.data!!
)
)
}.getOrNull()?.let {
messages += it.message
scrollDown()
}
message.setTextAndPlaceCursorAtEnd("")
if (response != null && response.error == null) {
message.setTextAndPlaceCursorAtEnd("")
Log.d("PublicChatScreen", "Message sent successfully")
// Message will be added to the list via WebSocket "newMessage" event
} else {
Log.e("PublicChatScreen", "WebSocket error: ${response?.error}")
}
} catch (e: Exception) {
Log.e("PublicChatScreen", "Failed to send message", e)
// Could show a toast or error message to user here
}
}
}
) {