Исправлен race инициализации аккаунта после device verification
This commit is contained in:
@@ -225,7 +225,27 @@ class MainActivity : FragmentActivity() {
|
|||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
val accounts = accountManager.getAllAccounts()
|
val accounts = accountManager.getAllAccounts()
|
||||||
hasExistingAccount = accounts.isNotEmpty()
|
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
|
// Wait for initial load
|
||||||
@@ -305,15 +325,29 @@ class MainActivity : FragmentActivity() {
|
|||||||
onAuthComplete = { account ->
|
onAuthComplete = { account ->
|
||||||
startCreateAccountFlow = false
|
startCreateAccountFlow = false
|
||||||
val normalizedAccount =
|
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 =
|
val normalizedName =
|
||||||
resolveAccountDisplayName(
|
resolveAccountDisplayName(
|
||||||
it.publicKey,
|
decrypted.publicKey,
|
||||||
it.name,
|
persisted?.name
|
||||||
null
|
?: decrypted.name,
|
||||||
|
persistedUsername
|
||||||
)
|
)
|
||||||
if (it.name == normalizedName) it
|
if (decrypted.name == normalizedName)
|
||||||
else it.copy(name = normalizedName)
|
decrypted
|
||||||
|
else decrypted.copy(name = normalizedName)
|
||||||
}
|
}
|
||||||
currentAccount = normalizedAccount
|
currentAccount = normalizedAccount
|
||||||
cacheSessionAccount(normalizedAccount)
|
cacheSessionAccount(normalizedAccount)
|
||||||
@@ -321,6 +355,14 @@ class MainActivity : FragmentActivity() {
|
|||||||
// Save as last logged account
|
// Save as last logged account
|
||||||
normalizedAccount?.let {
|
normalizedAccount?.let {
|
||||||
accountManager.setLastLoggedPublicKey(it.publicKey)
|
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 {
|
runCatching {
|
||||||
accountManager.setCurrentAccount(it.publicKey)
|
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()
|
val accounts = accountManager.getAllAccounts()
|
||||||
accountInfoList = accounts.map { it.toAccountInfo() }
|
accountInfoList = accounts.map { it.toAccountInfo() }
|
||||||
@@ -367,9 +430,9 @@ class MainActivity : FragmentActivity() {
|
|||||||
// lag
|
// lag
|
||||||
currentAccount = null
|
currentAccount = null
|
||||||
clearCachedSessionAccount()
|
clearCachedSessionAccount()
|
||||||
scope.launch {
|
|
||||||
com.rosetta.messenger.network.ProtocolManager
|
com.rosetta.messenger.network.ProtocolManager
|
||||||
.disconnect()
|
.disconnect()
|
||||||
|
scope.launch {
|
||||||
accountManager.logout()
|
accountManager.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,9 +479,9 @@ class MainActivity : FragmentActivity() {
|
|||||||
// lag
|
// lag
|
||||||
currentAccount = null
|
currentAccount = null
|
||||||
clearCachedSessionAccount()
|
clearCachedSessionAccount()
|
||||||
scope.launch {
|
|
||||||
com.rosetta.messenger.network.ProtocolManager
|
com.rosetta.messenger.network.ProtocolManager
|
||||||
.disconnect()
|
.disconnect()
|
||||||
|
scope.launch {
|
||||||
accountManager.logout()
|
accountManager.logout()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -509,8 +572,8 @@ class MainActivity : FragmentActivity() {
|
|||||||
// Switch to another account: logout current, then show unlock.
|
// Switch to another account: logout current, then show unlock.
|
||||||
currentAccount = null
|
currentAccount = null
|
||||||
clearCachedSessionAccount()
|
clearCachedSessionAccount()
|
||||||
scope.launch {
|
|
||||||
com.rosetta.messenger.network.ProtocolManager.disconnect()
|
com.rosetta.messenger.network.ProtocolManager.disconnect()
|
||||||
|
scope.launch {
|
||||||
accountManager.logout()
|
accountManager.logout()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -520,8 +583,8 @@ class MainActivity : FragmentActivity() {
|
|||||||
preservedMainNavAccountKey = ""
|
preservedMainNavAccountKey = ""
|
||||||
currentAccount = null
|
currentAccount = null
|
||||||
clearCachedSessionAccount()
|
clearCachedSessionAccount()
|
||||||
scope.launch {
|
|
||||||
com.rosetta.messenger.network.ProtocolManager.disconnect()
|
com.rosetta.messenger.network.ProtocolManager.disconnect()
|
||||||
|
scope.launch {
|
||||||
accountManager.logout()
|
accountManager.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,8 +598,8 @@ class MainActivity : FragmentActivity() {
|
|||||||
preservedMainNavAccountKey = ""
|
preservedMainNavAccountKey = ""
|
||||||
currentAccount = null
|
currentAccount = null
|
||||||
clearCachedSessionAccount()
|
clearCachedSessionAccount()
|
||||||
scope.launch {
|
|
||||||
ProtocolManager.disconnect()
|
ProtocolManager.disconnect()
|
||||||
|
scope.launch {
|
||||||
accountManager.logout()
|
accountManager.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -941,6 +1004,15 @@ fun MainScreen(
|
|||||||
CallManager.bindAccount(accountPublicKey)
|
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) {
|
LaunchedEffect(callUiState.isVisible) {
|
||||||
if (callUiState.isVisible) {
|
if (callUiState.isVisible) {
|
||||||
isCallOverlayExpanded = true
|
isCallOverlayExpanded = true
|
||||||
|
|||||||
@@ -301,6 +301,32 @@ class Protocol(
|
|||||||
startHeartbeat(packet.heartbeatInterval)
|
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"
|
"⚡ 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 (!hasCredentials) return
|
||||||
|
|
||||||
if (currentState == ProtocolState.CONNECTING && isConnecting) {
|
if (currentState == ProtocolState.CONNECTING && isConnecting) {
|
||||||
|
|||||||
@@ -293,11 +293,28 @@ object ProtocolManager {
|
|||||||
* Должен вызываться после авторизации пользователя
|
* Должен вызываться после авторизации пользователя
|
||||||
*/
|
*/
|
||||||
fun initializeAccount(publicKey: String, privateKey: String) {
|
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)
|
setSyncInProgress(false)
|
||||||
clearTypingState()
|
clearTypingState()
|
||||||
messageRepository?.initialize(publicKey, privateKey)
|
messageRepository?.initialize(normalizedPublicKey, normalizedPrivateKey)
|
||||||
if (resyncRequiredAfterAccountInit || protocol?.isAuthenticated() == true) {
|
|
||||||
|
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
|
resyncRequiredAfterAccountInit = false
|
||||||
|
syncRequestInFlight = false
|
||||||
|
clearSyncRequestTimeout()
|
||||||
|
addLog("🔄 Account initialized (${shortKeyForLog(normalizedPublicKey)}) -> force sync")
|
||||||
requestSynchronize()
|
requestSynchronize()
|
||||||
}
|
}
|
||||||
// Send "Rosetta Updates" message on version change (like desktop useUpdateMessage)
|
// 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.AccountManager
|
||||||
import com.rosetta.messenger.data.DecryptedAccount
|
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.ui.onboarding.PrimaryBlue
|
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -308,6 +309,9 @@ fun SetPasswordScreen(
|
|||||||
)
|
)
|
||||||
accountManager.saveAccount(account)
|
accountManager.saveAccount(account)
|
||||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)
|
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)
|
startAuthHandshakeFast(keyPair.publicKey, privateKeyHash)
|
||||||
accountManager.setCurrentAccount(keyPair.publicKey)
|
accountManager.setCurrentAccount(keyPair.publicKey)
|
||||||
val decryptedAccount = DecryptedAccount(
|
val decryptedAccount = DecryptedAccount(
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import com.rosetta.messenger.data.DecryptedAccount
|
|||||||
import com.rosetta.messenger.data.EncryptedAccount
|
import com.rosetta.messenger.data.EncryptedAccount
|
||||||
import com.rosetta.messenger.data.resolveAccountDisplayName
|
import com.rosetta.messenger.data.resolveAccountDisplayName
|
||||||
import com.rosetta.messenger.database.RosettaDatabase
|
import com.rosetta.messenger.database.RosettaDatabase
|
||||||
|
import com.rosetta.messenger.network.ProtocolManager
|
||||||
import com.rosetta.messenger.repository.AvatarRepository
|
import com.rosetta.messenger.repository.AvatarRepository
|
||||||
import com.rosetta.messenger.ui.components.AvatarImage
|
import com.rosetta.messenger.ui.components.AvatarImage
|
||||||
import com.rosetta.messenger.ui.chats.getAvatarColor
|
import com.rosetta.messenger.ui.chats.getAvatarColor
|
||||||
@@ -116,6 +117,9 @@ val decryptedPrivateKey = CryptoManager.decryptWithPassword(
|
|||||||
name = selectedAccount.name
|
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)
|
startAuthHandshakeFast(account.publicKey, privateKeyHash)
|
||||||
|
|
||||||
accountManager.setCurrentAccount(account.publicKey)
|
accountManager.setCurrentAccount(account.publicKey)
|
||||||
|
|||||||
Reference in New Issue
Block a user