Исправлен race инициализации аккаунта после device verification
This commit is contained in:
@@ -225,7 +225,27 @@ class MainActivity : FragmentActivity() {
|
||||
LaunchedEffect(Unit) {
|
||||
val accounts = accountManager.getAllAccounts()
|
||||
hasExistingAccount = accounts.isNotEmpty()
|
||||
accountInfoList = accounts.map { it.toAccountInfo() }
|
||||
val infos = accounts.map { it.toAccountInfo() }
|
||||
accountInfoList = infos
|
||||
|
||||
// Reconcile process-cached account name with persisted profile data.
|
||||
currentAccount?.let { cached ->
|
||||
val persisted = infos.firstOrNull {
|
||||
it.publicKey.equals(cached.publicKey, ignoreCase = true)
|
||||
}
|
||||
val persistedUsername = persisted?.username?.trim().orEmpty().ifBlank { null }
|
||||
val normalizedCachedName =
|
||||
resolveAccountDisplayName(
|
||||
cached.publicKey,
|
||||
persisted?.name ?: cached.name,
|
||||
persistedUsername
|
||||
)
|
||||
if (normalizedCachedName != cached.name) {
|
||||
val updated = cached.copy(name = normalizedCachedName)
|
||||
currentAccount = updated
|
||||
cacheSessionAccount(updated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for initial load
|
||||
@@ -305,15 +325,29 @@ class MainActivity : FragmentActivity() {
|
||||
onAuthComplete = { account ->
|
||||
startCreateAccountFlow = false
|
||||
val normalizedAccount =
|
||||
account?.let {
|
||||
account?.let { decrypted ->
|
||||
val persisted =
|
||||
accountInfoList.firstOrNull {
|
||||
it.publicKey.equals(
|
||||
decrypted.publicKey,
|
||||
ignoreCase = true
|
||||
)
|
||||
}
|
||||
val persistedUsername =
|
||||
persisted?.username
|
||||
?.trim()
|
||||
.orEmpty()
|
||||
.ifBlank { null }
|
||||
val normalizedName =
|
||||
resolveAccountDisplayName(
|
||||
it.publicKey,
|
||||
it.name,
|
||||
null
|
||||
decrypted.publicKey,
|
||||
persisted?.name
|
||||
?: decrypted.name,
|
||||
persistedUsername
|
||||
)
|
||||
if (it.name == normalizedName) it
|
||||
else it.copy(name = normalizedName)
|
||||
if (decrypted.name == normalizedName)
|
||||
decrypted
|
||||
else decrypted.copy(name = normalizedName)
|
||||
}
|
||||
currentAccount = normalizedAccount
|
||||
cacheSessionAccount(normalizedAccount)
|
||||
@@ -321,6 +355,14 @@ class MainActivity : FragmentActivity() {
|
||||
// Save as last logged account
|
||||
normalizedAccount?.let {
|
||||
accountManager.setLastLoggedPublicKey(it.publicKey)
|
||||
// Initialize protocol/message account context
|
||||
// immediately after auth completion to avoid
|
||||
// packet processing race before MainScreen
|
||||
// composition.
|
||||
ProtocolManager.initializeAccount(
|
||||
it.publicKey,
|
||||
it.privateKey
|
||||
)
|
||||
}
|
||||
|
||||
// Первый запуск после регистрации:
|
||||
@@ -354,6 +396,27 @@ class MainActivity : FragmentActivity() {
|
||||
runCatching {
|
||||
accountManager.setCurrentAccount(it.publicKey)
|
||||
}
|
||||
|
||||
// Force-refresh account title from persisted
|
||||
// profile (name/username) to avoid temporary
|
||||
// public-key alias in UI after login.
|
||||
val persisted = accountManager.getAccount(it.publicKey)
|
||||
val persistedUsername =
|
||||
persisted?.username
|
||||
?.trim()
|
||||
.orEmpty()
|
||||
.ifBlank { null }
|
||||
val refreshedName =
|
||||
resolveAccountDisplayName(
|
||||
it.publicKey,
|
||||
persisted?.name ?: it.name,
|
||||
persistedUsername
|
||||
)
|
||||
if (refreshedName != it.name) {
|
||||
val updated = it.copy(name = refreshedName)
|
||||
currentAccount = updated
|
||||
cacheSessionAccount(updated)
|
||||
}
|
||||
}
|
||||
val accounts = accountManager.getAllAccounts()
|
||||
accountInfoList = accounts.map { it.toAccountInfo() }
|
||||
@@ -367,9 +430,9 @@ class MainActivity : FragmentActivity() {
|
||||
// lag
|
||||
currentAccount = null
|
||||
clearCachedSessionAccount()
|
||||
scope.launch {
|
||||
com.rosetta.messenger.network.ProtocolManager
|
||||
.disconnect()
|
||||
scope.launch {
|
||||
accountManager.logout()
|
||||
}
|
||||
}
|
||||
@@ -416,9 +479,9 @@ class MainActivity : FragmentActivity() {
|
||||
// lag
|
||||
currentAccount = null
|
||||
clearCachedSessionAccount()
|
||||
scope.launch {
|
||||
com.rosetta.messenger.network.ProtocolManager
|
||||
.disconnect()
|
||||
scope.launch {
|
||||
accountManager.logout()
|
||||
}
|
||||
},
|
||||
@@ -509,8 +572,8 @@ class MainActivity : FragmentActivity() {
|
||||
// Switch to another account: logout current, then show unlock.
|
||||
currentAccount = null
|
||||
clearCachedSessionAccount()
|
||||
scope.launch {
|
||||
com.rosetta.messenger.network.ProtocolManager.disconnect()
|
||||
scope.launch {
|
||||
accountManager.logout()
|
||||
}
|
||||
},
|
||||
@@ -520,8 +583,8 @@ class MainActivity : FragmentActivity() {
|
||||
preservedMainNavAccountKey = ""
|
||||
currentAccount = null
|
||||
clearCachedSessionAccount()
|
||||
scope.launch {
|
||||
com.rosetta.messenger.network.ProtocolManager.disconnect()
|
||||
scope.launch {
|
||||
accountManager.logout()
|
||||
}
|
||||
}
|
||||
@@ -535,8 +598,8 @@ class MainActivity : FragmentActivity() {
|
||||
preservedMainNavAccountKey = ""
|
||||
currentAccount = null
|
||||
clearCachedSessionAccount()
|
||||
scope.launch {
|
||||
ProtocolManager.disconnect()
|
||||
scope.launch {
|
||||
accountManager.logout()
|
||||
}
|
||||
}
|
||||
@@ -941,6 +1004,15 @@ fun MainScreen(
|
||||
CallManager.bindAccount(accountPublicKey)
|
||||
}
|
||||
|
||||
// Global account binding for protocol/message repository.
|
||||
// Keeps init independent from ChatsList composition timing.
|
||||
LaunchedEffect(accountPublicKey, accountPrivateKey) {
|
||||
val normalizedPublicKey = accountPublicKey.trim()
|
||||
val normalizedPrivateKey = accountPrivateKey.trim()
|
||||
if (normalizedPublicKey.isBlank() || normalizedPrivateKey.isBlank()) return@LaunchedEffect
|
||||
ProtocolManager.initializeAccount(normalizedPublicKey, normalizedPrivateKey)
|
||||
}
|
||||
|
||||
LaunchedEffect(callUiState.isVisible) {
|
||||
if (callUiState.isVisible) {
|
||||
isCallOverlayExpanded = true
|
||||
|
||||
@@ -301,6 +301,32 @@ class Protocol(
|
||||
startHeartbeat(packet.heartbeatInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// Device verification resolution from primary device.
|
||||
// Desktop typically continues after next handshake response; here we also
|
||||
// add a safety re-handshake trigger on ACCEPT to avoid being stuck in
|
||||
// DEVICE_VERIFICATION_REQUIRED if server doesn't immediately push 0x00.
|
||||
waitPacket(0x18) { packet ->
|
||||
val resolve = packet as? PacketDeviceResolve ?: return@waitPacket
|
||||
when (resolve.solution) {
|
||||
DeviceResolveSolution.ACCEPT -> {
|
||||
log("✅ DEVICE VERIFICATION ACCEPTED (deviceId=${shortKey(resolve.deviceId, 12)})")
|
||||
if (_state.value == ProtocolState.DEVICE_VERIFICATION_REQUIRED) {
|
||||
setState(ProtocolState.CONNECTED, "Device verification accepted")
|
||||
val publicKey = lastPublicKey
|
||||
val privateHash = lastPrivateHash
|
||||
if (!publicKey.isNullOrBlank() && !privateHash.isNullOrBlank()) {
|
||||
startHandshake(publicKey, privateHash, lastDevice)
|
||||
} else {
|
||||
log("⚠️ ACCEPT received but credentials are missing, waiting for reconnect")
|
||||
}
|
||||
}
|
||||
}
|
||||
DeviceResolveSolution.DECLINE -> {
|
||||
log("⛔ DEVICE VERIFICATION DECLINED (deviceId=${shortKey(resolve.deviceId, 12)})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -847,6 +873,11 @@ class Protocol(
|
||||
"⚡ FAST RECONNECT CHECK: state=$currentState, hasCredentials=$hasCredentials, isConnecting=$isConnecting, reason=$reason"
|
||||
)
|
||||
|
||||
if (isManuallyClosed) {
|
||||
log("⚡ FAST RECONNECT SKIP: manually closed, reason=$reason")
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasCredentials) return
|
||||
|
||||
if (currentState == ProtocolState.CONNECTING && isConnecting) {
|
||||
|
||||
@@ -293,11 +293,28 @@ object ProtocolManager {
|
||||
* Должен вызываться после авторизации пользователя
|
||||
*/
|
||||
fun initializeAccount(publicKey: String, privateKey: String) {
|
||||
val normalizedPublicKey = publicKey.trim()
|
||||
val normalizedPrivateKey = privateKey.trim()
|
||||
if (normalizedPublicKey.isBlank() || normalizedPrivateKey.isBlank()) {
|
||||
addLog("⚠️ initializeAccount skipped: missing account credentials")
|
||||
return
|
||||
}
|
||||
|
||||
addLog(
|
||||
"🔐 initializeAccount pk=${shortKeyForLog(normalizedPublicKey)} keyLen=${normalizedPrivateKey.length} state=${getProtocol().state.value}"
|
||||
)
|
||||
setSyncInProgress(false)
|
||||
clearTypingState()
|
||||
messageRepository?.initialize(publicKey, privateKey)
|
||||
if (resyncRequiredAfterAccountInit || protocol?.isAuthenticated() == true) {
|
||||
messageRepository?.initialize(normalizedPublicKey, normalizedPrivateKey)
|
||||
|
||||
val shouldResync = resyncRequiredAfterAccountInit || protocol?.isAuthenticated() == true
|
||||
if (shouldResync) {
|
||||
// Late account init may happen while an old sync request flag is still set.
|
||||
// Force a fresh synchronize request to recover dropped inbound packets.
|
||||
resyncRequiredAfterAccountInit = false
|
||||
syncRequestInFlight = false
|
||||
clearSyncRequestTimeout()
|
||||
addLog("🔄 Account initialized (${shortKeyForLog(normalizedPublicKey)}) -> force sync")
|
||||
requestSynchronize()
|
||||
}
|
||||
// Send "Rosetta Updates" message on version change (like desktop useUpdateMessage)
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.rosetta.messenger.crypto.CryptoManager
|
||||
import com.rosetta.messenger.data.AccountManager
|
||||
import com.rosetta.messenger.data.DecryptedAccount
|
||||
import com.rosetta.messenger.data.EncryptedAccount
|
||||
import com.rosetta.messenger.network.ProtocolManager
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -308,6 +309,9 @@ fun SetPasswordScreen(
|
||||
)
|
||||
accountManager.saveAccount(account)
|
||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)
|
||||
// Initialize repository/account context before handshake completes to avoid
|
||||
// "Sync postponed until account is initialized" race on first login.
|
||||
ProtocolManager.initializeAccount(keyPair.publicKey, keyPair.privateKey)
|
||||
startAuthHandshakeFast(keyPair.publicKey, privateKeyHash)
|
||||
accountManager.setCurrentAccount(keyPair.publicKey)
|
||||
val decryptedAccount = DecryptedAccount(
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.rosetta.messenger.data.DecryptedAccount
|
||||
import com.rosetta.messenger.data.EncryptedAccount
|
||||
import com.rosetta.messenger.data.resolveAccountDisplayName
|
||||
import com.rosetta.messenger.database.RosettaDatabase
|
||||
import com.rosetta.messenger.network.ProtocolManager
|
||||
import com.rosetta.messenger.repository.AvatarRepository
|
||||
import com.rosetta.messenger.ui.components.AvatarImage
|
||||
import com.rosetta.messenger.ui.chats.getAvatarColor
|
||||
@@ -116,6 +117,9 @@ val decryptedPrivateKey = CryptoManager.decryptWithPassword(
|
||||
name = selectedAccount.name
|
||||
)
|
||||
|
||||
// Initialize repository/account context before handshake completes to avoid
|
||||
// "Sync postponed until account is initialized" race.
|
||||
ProtocolManager.initializeAccount(account.publicKey, decryptedPrivateKey)
|
||||
startAuthHandshakeFast(account.publicKey, privateKeyHash)
|
||||
|
||||
accountManager.setCurrentAccount(account.publicKey)
|
||||
|
||||
Reference in New Issue
Block a user