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:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.FromChat"> android:theme="@style/Theme.FromChat"
android:usesCleartextTraffic="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
+2 -1
View File
@@ -3,7 +3,8 @@ package ru.fromchat
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.format.char 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 { val DATETIME_FORMAT = LocalDateTime.Format {
day() day()
@@ -16,10 +16,11 @@ import io.ktor.http.ContentType
import io.ktor.http.contentType import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import ru.fromchat.API_HOST
import ru.fromchat.utils.failOnError import ru.fromchat.utils.failOnError
object ApiClient { object ApiClient {
private val json = Json { val json = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
isLenient = true isLenient = true
encodeDefaults = true encodeDefaults = true
@@ -53,7 +54,7 @@ object ApiClient {
suspend fun login(request: LoginRequest) = suspend fun login(request: LoginRequest) =
http http
.post("https://fromchat.ru/api/login") { .post("$API_HOST/api/login") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(request.also { Log.d("ApiClient", "Login request: $it") }) setBody(request.also { Log.d("ApiClient", "Login request: $it") })
} }
@@ -66,7 +67,7 @@ object ApiClient {
suspend fun register(request: RegisterRequest) = suspend fun register(request: RegisterRequest) =
http http
.post("https://fromchat.ru/api/register") { .post("$API_HOST/api/register") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(request) setBody(request)
} }
@@ -74,7 +75,7 @@ object ApiClient {
suspend fun getMessages() = suspend fun getMessages() =
http http
.get("https://fromchat.ru/api/get_messages") { .get("$API_HOST/api/get_messages") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
} }
.failOnError() .failOnError()
@@ -82,7 +83,7 @@ object ApiClient {
suspend fun send(message: String) = suspend fun send(message: String) =
http http
.post("https://fromchat.ru/api/send_message") { .post("$API_HOST/api/send_message") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
bearerAuth(token!!) bearerAuth(token!!)
setBody(SendMessageRequest(message)) setBody(SendMessageRequest(message))
+2 -1
View File
@@ -61,7 +61,8 @@ data class Message(
@Serializable @Serializable
data class SendMessageRequest( data class SendMessageRequest(
val content: String val content: String,
val reply_to_id: Int? = null
) )
@Serializable @Serializable
@@ -3,9 +3,8 @@ package ru.fromchat.api
import android.util.Log import android.util.Log
import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
import io.ktor.client.plugins.websocket.webSocket import io.ktor.client.plugins.websocket.webSocket
import io.ktor.client.request.url
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.http.URLProtocol
import io.ktor.http.encodedPath
import io.ktor.websocket.Frame import io.ktor.websocket.Frame
import io.ktor.websocket.readText import io.ktor.websocket.readText
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -20,7 +19,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import ru.fromchat.API_HOST import ru.fromchat.WS_API_HOST
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
object WebSocketManager { object WebSocketManager {
@@ -55,16 +54,12 @@ object WebSocketManager {
try { try {
ApiClient.http.webSocket( ApiClient.http.webSocket(
method = HttpMethod.Get, method = HttpMethod.Get,
host = API_HOST,
request = { request = {
url { url("$WS_API_HOST/api/chat/ws")
protocol = URLProtocol.WSS
host = "fromchat.ru"
encodedPath = "/api/chat/ws"
}
} }
) { ) {
session = this session = this
connecting = false
Log.d("WebSocketManager", "WebSocket connected") Log.d("WebSocketManager", "WebSocket connected")
for (frame in incoming) { for (frame in incoming) {
@@ -82,17 +77,31 @@ object WebSocketManager {
} }
} catch (e: Throwable) { } catch (e: Throwable) {
Log.w("WebSocketManager", "An error occurred:", e) Log.w("WebSocketManager", "An error occurred:", e)
connecting = false
session = null
delay(3000) delay(3000)
} finally { } finally {
Log.w("WebSocketManager", "WebSocket disconnected") Log.w("WebSocketManager", "WebSocket disconnected")
session = null session = null
connecting = false
} }
} }
} }
} }
suspend fun send(message: WebSocketMessage) { 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) @OptIn(DelicateCoroutinesApi::class)
@@ -101,19 +110,31 @@ object WebSocketManager {
var handler: ((WebSocketMessage) -> Unit)? = null var handler: ((WebSocketMessage) -> Unit)? = null
return try { 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) send(message)
withTimeout(timeoutMs) { withTimeout(timeoutMs) {
suspendCoroutine { continuation -> suspendCoroutine { continuation ->
handler = { handler = { response ->
continuation.resumeWith(Result.success(it)) // Only process responses that match our request type
if (response.type == message.type) {
continuation.resumeWith(Result.success(response))
removeGlobalMessageHandler(handler!!) removeGlobalMessageHandler(handler!!)
} }
}
addGlobalMessageHandler(handler) addGlobalMessageHandler(handler)
} }
} }
} catch (_: TimeoutCancellationException) { } catch (_: TimeoutCancellationException) {
Log.w("WebSocketManager", "Request timed out") Log.w("WebSocketManager", "Request timed out")
null null
} catch (e: Exception) {
Log.e("WebSocketManager", "Request failed", e)
null
} finally { } finally {
handler?.let { removeGlobalMessageHandler(it) } handler?.let { removeGlobalMessageHandler(it) }
} }
@@ -67,7 +67,6 @@ import kotlinx.coroutines.launch
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.format import kotlinx.datetime.format
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.encodeToJsonElement
import ru.fromchat.DATETIME_FORMAT import ru.fromchat.DATETIME_FORMAT
@@ -75,7 +74,6 @@ import ru.fromchat.R
import ru.fromchat.api.ApiClient import ru.fromchat.api.ApiClient
import ru.fromchat.api.Message import ru.fromchat.api.Message
import ru.fromchat.api.SendMessageRequest import ru.fromchat.api.SendMessageRequest
import ru.fromchat.api.SendMessageResponse
import ru.fromchat.api.WebSocketCredentials import ru.fromchat.api.WebSocketCredentials
import ru.fromchat.api.WebSocketManager import ru.fromchat.api.WebSocketManager
import ru.fromchat.api.WebSocketMessage import ru.fromchat.api.WebSocketMessage
@@ -116,8 +114,12 @@ fun PublicChatScreen() {
scope.launch { scope.launch {
if (msg.type == "newMessage") { if (msg.type == "newMessage") {
try { try {
messages += Json.decodeFromJsonElement<Message>(msg.data!!) 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() scrollDown()
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e("PublicChatScreen", "Failed to process incoming message:", e) Log.e("PublicChatScreen", "Failed to process incoming message:", e)
} }
@@ -188,29 +190,36 @@ fun PublicChatScreen() {
IconButton( IconButton(
onClick = { onClick = {
scope.launch { scope.launch {
runCatching { val messageText = "${message.text.trim()}"
Json.decodeFromJsonElement<SendMessageResponse>( if (messageText.isEmpty()) return@launch
WebSocketManager.request(
try {
val response = WebSocketManager.request(
WebSocketMessage( WebSocketMessage(
type = "sendMessage", type = "sendMessage",
credentials = WebSocketCredentials( credentials = WebSocketCredentials(
scheme = "Bearer", scheme = "Bearer",
credentials = ApiClient.token!! credentials = ApiClient.token!!
), ),
data = Json.encodeToJsonElement( data = ApiClient.json.encodeToJsonElement(
SendMessageRequest( SendMessageRequest(
content = "${message.text}" content = messageText
) )
) )
) )
)!!.data!!
) )
}.getOrNull()?.let {
messages += it.message
scrollDown()
}
if (response != null && response.error == null) {
message.setTextAndPlaceCursorAtEnd("") 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
}
} }
} }
) { ) {