Merge remote-tracking branch 'refs/remotes/origin/master'
This commit is contained in:
69
FIX_WEBSOCKET_CONNECT.md
Normal file
69
FIX_WEBSOCKET_CONNECT.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Fix: WebSocket Connection After Registration
|
||||||
|
|
||||||
|
## Проблема
|
||||||
|
|
||||||
|
После первой регистрации аккаунта в Android приложении не работали запросы на сервер до перезапуска приложения.
|
||||||
|
|
||||||
|
## Причина
|
||||||
|
|
||||||
|
При регистрации или разблокировке аккаунта вызывался `ProtocolManager.authenticate()` до того, как WebSocket соединение было установлено.
|
||||||
|
|
||||||
|
Последовательность была следующая:
|
||||||
|
|
||||||
|
1. Создание/разблокировка аккаунта
|
||||||
|
2. Вызов `ProtocolManager.authenticate(publicKey, privateKeyHash)`
|
||||||
|
3. `authenticate()` вызывает `getProtocol().startHandshake()`
|
||||||
|
4. `startHandshake()` проверяет что не подключено и вызывает `connect()`, но затем сразу возвращается
|
||||||
|
5. Соединение устанавливается асинхронно, но handshake не происходит вовремя
|
||||||
|
6. Приложение переходит к экрану чатов, запросы не проходят
|
||||||
|
|
||||||
|
После перезапуска приложения `ProtocolManager.connect()` вызывался в `ChatsListScreen`, поэтому все работало нормально.
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Добавлен явный вызов `ProtocolManager.connect()` перед `authenticate()` в следующих местах:
|
||||||
|
|
||||||
|
### 1. AuthState.kt
|
||||||
|
|
||||||
|
- Метод `createAccount()` - после создания аккаунта
|
||||||
|
- Метод `unlock()` - после разблокировки
|
||||||
|
|
||||||
|
### 2. SetPasswordScreen.kt
|
||||||
|
|
||||||
|
- При создании нового аккаунта после установки пароля
|
||||||
|
|
||||||
|
### 3. UnlockScreen.kt
|
||||||
|
|
||||||
|
- При разблокировке существующего аккаунта
|
||||||
|
|
||||||
|
Добавлена задержка 500ms после `connect()` для того, чтобы WebSocket успел установить соединение до попытки аутентификации.
|
||||||
|
|
||||||
|
## Измененные файлы
|
||||||
|
|
||||||
|
- `app/src/main/java/com/rosetta/messenger/providers/AuthState.kt`
|
||||||
|
- `app/src/main/java/com/rosetta/messenger/ui/auth/SetPasswordScreen.kt`
|
||||||
|
- `app/src/main/java/com/rosetta/messenger/ui/auth/UnlockScreen.kt`
|
||||||
|
|
||||||
|
## Код изменений
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Было:
|
||||||
|
ProtocolManager.authenticate(publicKey, privateKeyHash)
|
||||||
|
|
||||||
|
// Стало:
|
||||||
|
ProtocolManager.connect()
|
||||||
|
kotlinx.coroutines.delay(500) // Даём время на установку соединения
|
||||||
|
ProtocolManager.authenticate(publicKey, privateKeyHash)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
После этого исправления:
|
||||||
|
|
||||||
|
1. Зарегистрируйте новый аккаунт
|
||||||
|
2. Сразу после регистрации попробуйте отправить сообщение или выполнить другой запрос
|
||||||
|
3. Запросы должны проходить без перезапуска приложения
|
||||||
|
|
||||||
|
## Дата
|
||||||
|
|
||||||
|
11 января 2026
|
||||||
@@ -69,6 +69,9 @@ class Protocol(
|
|||||||
private var lastPublicKey: String? = null
|
private var lastPublicKey: String? = null
|
||||||
private var lastPrivateHash: String? = null
|
private var lastPrivateHash: String? = null
|
||||||
|
|
||||||
|
// Heartbeat
|
||||||
|
private var heartbeatJob: Job? = null
|
||||||
|
|
||||||
// Supported packets
|
// Supported packets
|
||||||
private val supportedPackets = mapOf(
|
private val supportedPackets = mapOf(
|
||||||
0x00 to { PacketHandshake() },
|
0x00 to { PacketHandshake() },
|
||||||
@@ -92,6 +95,30 @@ class Protocol(
|
|||||||
handshakeComplete = true
|
handshakeComplete = true
|
||||||
_state.value = ProtocolState.AUTHENTICATED
|
_state.value = ProtocolState.AUTHENTICATED
|
||||||
flushPacketQueue()
|
flushPacketQueue()
|
||||||
|
|
||||||
|
// Start heartbeat with interval from server
|
||||||
|
startHeartbeat(packet.heartbeatInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start heartbeat to keep connection alive
|
||||||
|
*/
|
||||||
|
private fun startHeartbeat(intervalSeconds: Int) {
|
||||||
|
heartbeatJob?.cancel()
|
||||||
|
|
||||||
|
val intervalMs = (intervalSeconds * 1000L) / 2 // Send at half the interval
|
||||||
|
log("💓 Starting heartbeat with interval: ${intervalSeconds}s (sending every ${intervalMs}ms)")
|
||||||
|
|
||||||
|
heartbeatJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
delay(intervalMs)
|
||||||
|
if (webSocket?.send("heartbeat") == true) {
|
||||||
|
log("💓 Heartbeat sent")
|
||||||
|
} else {
|
||||||
|
log("💔 Heartbeat failed to send")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,13 +127,19 @@ class Protocol(
|
|||||||
* Initialize connection to server
|
* Initialize connection to server
|
||||||
*/
|
*/
|
||||||
fun connect() {
|
fun connect() {
|
||||||
if (_state.value == ProtocolState.CONNECTING || _state.value == ProtocolState.CONNECTED) {
|
if (_state.value == ProtocolState.CONNECTING) {
|
||||||
log("Already connecting or connected")
|
log("Already connecting, skipping...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow reconnection even if connected (for manual reconnect)
|
||||||
|
if (_state.value == ProtocolState.CONNECTED || _state.value == ProtocolState.AUTHENTICATED) {
|
||||||
|
log("Already connected/authenticated, skipping...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isManuallyClosed = false
|
isManuallyClosed = false
|
||||||
reconnectAttempts = 0 // Reset reconnect attempts on new connection
|
// Don't reset reconnectAttempts here - it's reset on successful connection in onOpen
|
||||||
_state.value = ProtocolState.CONNECTING
|
_state.value = ProtocolState.CONNECTING
|
||||||
_lastError.value = null
|
_lastError.value = null
|
||||||
|
|
||||||
@@ -266,6 +299,7 @@ class Protocol(
|
|||||||
_state.value = ProtocolState.DISCONNECTED
|
_state.value = ProtocolState.DISCONNECTED
|
||||||
handshakeComplete = false
|
handshakeComplete = false
|
||||||
handshakeJob?.cancel()
|
handshakeJob?.cancel()
|
||||||
|
heartbeatJob?.cancel()
|
||||||
|
|
||||||
if (!isManuallyClosed && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
if (!isManuallyClosed && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||||||
reconnectAttempts++
|
reconnectAttempts++
|
||||||
@@ -302,6 +336,7 @@ class Protocol(
|
|||||||
log("Disconnecting...")
|
log("Disconnecting...")
|
||||||
isManuallyClosed = true
|
isManuallyClosed = true
|
||||||
handshakeJob?.cancel()
|
handshakeJob?.cancel()
|
||||||
|
heartbeatJob?.cancel()
|
||||||
webSocket?.close(1000, "User disconnected")
|
webSocket?.close(1000, "User disconnected")
|
||||||
webSocket = null
|
webSocket = null
|
||||||
_state.value = ProtocolState.DISCONNECTED
|
_state.value = ProtocolState.DISCONNECTED
|
||||||
@@ -332,6 +367,7 @@ class Protocol(
|
|||||||
*/
|
*/
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
disconnect()
|
disconnect()
|
||||||
|
heartbeatJob?.cancel()
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,13 @@ class AuthStateManager(
|
|||||||
|
|
||||||
loadAccounts()
|
loadAccounts()
|
||||||
|
|
||||||
// Step 8: Authenticate with protocol
|
// Step 8: Connect and authenticate with protocol
|
||||||
|
Log.d(TAG, "🌐 Connecting to protocol server...")
|
||||||
|
ProtocolManager.connect()
|
||||||
|
|
||||||
|
// Give WebSocket time to connect before authenticating
|
||||||
|
kotlinx.coroutines.delay(500)
|
||||||
|
|
||||||
Log.d(TAG, "🌐 Authenticating with protocol server...")
|
Log.d(TAG, "🌐 Authenticating with protocol server...")
|
||||||
ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash)
|
ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash)
|
||||||
|
|
||||||
@@ -236,7 +242,13 @@ class AuthStateManager(
|
|||||||
status = AuthStatus.Authenticated(decryptedAccount)
|
status = AuthStatus.Authenticated(decryptedAccount)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
// Authenticate with protocol
|
// Connect and authenticate with protocol
|
||||||
|
Log.d(TAG, "🌐 Connecting to protocol server...")
|
||||||
|
ProtocolManager.connect()
|
||||||
|
|
||||||
|
// Give WebSocket time to connect before authenticating
|
||||||
|
kotlinx.coroutines.delay(500)
|
||||||
|
|
||||||
Log.d(TAG, "🌐 Authenticating with protocol server...")
|
Log.d(TAG, "🌐 Authenticating with protocol server...")
|
||||||
ProtocolManager.authenticate(decryptedAccount.publicKey, decryptedAccount.privateKeyHash)
|
ProtocolManager.authenticate(decryptedAccount.publicKey, decryptedAccount.privateKeyHash)
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ fun AuthFlow(
|
|||||||
currentScreen = AuthScreen.CONFIRM_SEED
|
currentScreen = AuthScreen.CONFIRM_SEED
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onAccountCreated = { onAuthComplete(null) }
|
onAccountCreated = { account -> onAuthComplete(account) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.rosetta.messenger.crypto.CryptoManager
|
import com.rosetta.messenger.crypto.CryptoManager
|
||||||
import com.rosetta.messenger.data.AccountManager
|
import com.rosetta.messenger.data.AccountManager
|
||||||
|
import com.rosetta.messenger.data.DecryptedAccount
|
||||||
import com.rosetta.messenger.data.EncryptedAccount
|
import com.rosetta.messenger.data.EncryptedAccount
|
||||||
import com.rosetta.messenger.network.ProtocolManager
|
import com.rosetta.messenger.network.ProtocolManager
|
||||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||||
@@ -38,7 +39,7 @@ fun SetPasswordScreen(
|
|||||||
seedPhrase: List<String>,
|
seedPhrase: List<String>,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onAccountCreated: () -> Unit
|
onAccountCreated: (DecryptedAccount) -> Unit
|
||||||
) {
|
) {
|
||||||
val themeAnimSpec = tween<Color>(durationMillis = 500, easing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f))
|
val themeAnimSpec = tween<Color>(durationMillis = 500, easing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f))
|
||||||
val backgroundColor by animateColorAsState(if (isDarkTheme) AuthBackground else AuthBackgroundLight, animationSpec = themeAnimSpec)
|
val backgroundColor by animateColorAsState(if (isDarkTheme) AuthBackground else AuthBackgroundLight, animationSpec = themeAnimSpec)
|
||||||
@@ -497,9 +498,21 @@ fun SetPasswordScreen(
|
|||||||
// 🔌 Connect to server and authenticate
|
// 🔌 Connect to server and authenticate
|
||||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)
|
val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)
|
||||||
Log.d("SetPasswordScreen", "🔌 Connecting to server...")
|
Log.d("SetPasswordScreen", "🔌 Connecting to server...")
|
||||||
|
ProtocolManager.connect()
|
||||||
|
// Give WebSocket time to connect before authenticating
|
||||||
|
kotlinx.coroutines.delay(500)
|
||||||
ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash)
|
ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash)
|
||||||
|
|
||||||
onAccountCreated()
|
// Create DecryptedAccount to pass to callback
|
||||||
|
val decryptedAccount = DecryptedAccount(
|
||||||
|
publicKey = keyPair.publicKey,
|
||||||
|
privateKey = keyPair.privateKey,
|
||||||
|
seedPhrase = seedPhrase,
|
||||||
|
privateKeyHash = privateKeyHash,
|
||||||
|
name = truncatedKey
|
||||||
|
)
|
||||||
|
|
||||||
|
onAccountCreated(decryptedAccount)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
error = "Failed to create account: ${e.message}"
|
error = "Failed to create account: ${e.message}"
|
||||||
isCreating = false
|
isCreating = false
|
||||||
|
|||||||
@@ -581,6 +581,9 @@ fun UnlockScreen(
|
|||||||
|
|
||||||
// Connect to server and authenticate
|
// Connect to server and authenticate
|
||||||
Log.d("UnlockScreen", "Connecting to server...")
|
Log.d("UnlockScreen", "Connecting to server...")
|
||||||
|
ProtocolManager.connect()
|
||||||
|
// Give WebSocket time to connect before authenticating
|
||||||
|
kotlinx.coroutines.delay(500)
|
||||||
ProtocolManager.authenticate(account.publicKey, privateKeyHash)
|
ProtocolManager.authenticate(account.publicKey, privateKeyHash)
|
||||||
|
|
||||||
accountManager.setCurrentAccount(account.publicKey)
|
accountManager.setCurrentAccount(account.publicKey)
|
||||||
|
|||||||
Reference in New Issue
Block a user