From 48861633ee4a384dd24bfeb4008985bb6a752ef2 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Wed, 25 Feb 2026 23:03:28 +0500 Subject: [PATCH] feat: Implement deduplication for FCM token subscription and update related logic --- app/build.gradle.kts | 7 ++ .../com/rosetta/messenger/MainActivity.kt | 73 +------------------ .../messenger/network/ProtocolManager.kt | 34 +++++++-- .../push/RosettaFirebaseMessagingService.kt | 12 +-- 4 files changed, 41 insertions(+), 85 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8d47ff2..25d6094 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -84,6 +84,13 @@ android { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } jniLibs { useLegacyPackaging = true } } + + applicationVariants.all { + outputs.all { + val apkOut = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl + apkOut.outputFileName = "Rosetta-${versionName}.apk" + } + } } dependencies { diff --git a/app/src/main/java/com/rosetta/messenger/MainActivity.kt b/app/src/main/java/com/rosetta/messenger/MainActivity.kt index 07db142..1037fc4 100644 --- a/app/src/main/java/com/rosetta/messenger/MainActivity.kt +++ b/app/src/main/java/com/rosetta/messenger/MainActivity.kt @@ -32,10 +32,8 @@ import com.rosetta.messenger.crypto.CryptoManager import com.rosetta.messenger.data.RecentSearchesManager import com.rosetta.messenger.data.resolveAccountDisplayName import com.rosetta.messenger.database.RosettaDatabase -import com.rosetta.messenger.network.PacketPushNotification import com.rosetta.messenger.network.ProtocolManager import com.rosetta.messenger.network.ProtocolState -import com.rosetta.messenger.network.PushNotificationAction import com.rosetta.messenger.network.SearchUser import com.rosetta.messenger.repository.AvatarRepository import com.rosetta.messenger.ui.auth.AccountInfo @@ -62,9 +60,7 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull class MainActivity : FragmentActivity() { private lateinit var preferencesManager: PreferencesManager @@ -177,15 +173,6 @@ class MainActivity : FragmentActivity() { return@setContent } - // Ensure push token subscription is sent whenever protocol reaches AUTHENTICATED. - // This recovers token binding after reconnects and delayed handshakes. - LaunchedEffect(protocolState, currentAccount?.publicKey) { - currentAccount ?: return@LaunchedEffect - if (protocolState == ProtocolState.AUTHENTICATED) { - sendFcmTokenToServer() - } - } - RosettaAndroidTheme(darkTheme = isDarkTheme, animated = true) { Surface( modifier = Modifier.fillMaxSize(), @@ -254,10 +241,6 @@ class MainActivity : FragmentActivity() { accountManager.setLastLoggedPublicKey(it.publicKey) } - // 📤 Отправляем FCM токен на сервер после успешной - // аутентификации - account?.let { sendFcmTokenToServer() } - // Reload accounts list scope.launch { val accounts = accountManager.getAllAccounts() @@ -456,16 +439,14 @@ class MainActivity : FragmentActivity() { saveFcmToken(token) addFcmLog("💾 Токен сохранен локально") - if (ProtocolManager.state.value == ProtocolState.AUTHENTICATED) { - addFcmLog("🔁 Протокол уже AUTHENTICATED, отправляем токен сразу") - sendFcmTokenToServer() - } + // Token will be sent by ProtocolManager.onAuthenticated() + // when protocol reaches AUTHENTICATED state } else { addFcmLog("⚠️ Токен пустой") } - // Токен будет отправлен на сервер после успешной аутентификации - // (см. вызов sendFcmTokenToServer в onAccountLogin) + // Токен будет отправлен через ProtocolManager.subscribePushTokenIfAvailable() + // при достижении состояния AUTHENTICATED } } catch (e: Exception) { addFcmLog("❌ Ошибка Firebase: ${e.message}") @@ -479,52 +460,6 @@ class MainActivity : FragmentActivity() { prefs.edit().putString("fcm_token", token).apply() } - /** - * Отправить FCM токен на сервер Вызывается после успешной аутентификации, когда аккаунт уже - * расшифрован - */ - private fun sendFcmTokenToServer() { - lifecycleScope.launch { - try { - val prefs = getSharedPreferences("rosetta_prefs", MODE_PRIVATE) - val token = prefs.getString("fcm_token", null) - - if (token == null) { - addFcmLog("⚠️ Нет сохраненного токена для отправки") - return@launch - } - - val shortToken = "${token.take(12)}...${token.takeLast(8)}" - addFcmLog("📤 Подготовка к отправке токена на сервер") - addFcmLog("⏳ Ожидание аутентификации...") - - // 🔥 КРИТИЧНО: Ждем пока протокол станет AUTHENTICATED - val authenticated = withTimeoutOrNull(5000) { - ProtocolManager.state.first { it == ProtocolState.AUTHENTICATED } - } - - if (authenticated == null) { - addFcmLog("❌ Таймаут аутентификации (5000ms)") - return@launch - } - - addFcmLog("✅ Аутентификация успешна") - addFcmLog("📨 Отправка токена: $shortToken") - - val packet = - PacketPushNotification().apply { - this.notificationsToken = token - this.action = PushNotificationAction.SUBSCRIBE - } - - ProtocolManager.send(packet) - addFcmLog("✅ Пакет отправлен на сервер (ID: 0x10)") - addFcmLog("🎉 FCM токен успешно зарегистрирован!") - } catch (e: Exception) { - addFcmLog("❌ Ошибка отправки: ${e.message}") - } - } - } } private fun buildInitials(displayName: String): String = diff --git a/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt b/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt index 6e70758..b0cab28 100644 --- a/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt +++ b/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt @@ -33,6 +33,10 @@ object ProtocolManager { private var messageRepository: MessageRepository? = null private var appContext: Context? = null private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + // Guard: prevent duplicate FCM token subscribe within a single session + @Volatile + private var lastSubscribedToken: String? = null // Debug logs for dev console - 🚀 ОТКЛЮЧЕНО для производительности // Логи только в Logcat, не в StateFlow (это вызывало ANR!) @@ -393,20 +397,37 @@ object ProtocolManager { subscribePushTokenIfAvailable() } - private fun subscribePushTokenIfAvailable() { + /** + * Send FCM push token to server (SUBSCRIBE). + * Deduplicates: won't re-send the same token within one connection session. + * Called internally on AUTHENTICATED and can be called from + * [com.rosetta.messenger.push.RosettaFirebaseMessagingService.onNewToken] + * when Firebase rotates the token mid-session. + * + * @param forceToken if non-null, use this token instead of reading SharedPreferences + * (used by onNewToken which already has the fresh token). + */ + fun subscribePushTokenIfAvailable(forceToken: String? = null) { val context = appContext ?: return - val token = - context.getSharedPreferences("rosetta_prefs", Context.MODE_PRIVATE) - .getString("fcm_token", null) - ?.trim() - .orEmpty() + val token = (forceToken + ?: context.getSharedPreferences("rosetta_prefs", Context.MODE_PRIVATE) + .getString("fcm_token", null)) + ?.trim() + .orEmpty() if (token.isEmpty()) return + // Dedup: don't send the same token twice in one connection session + if (token == lastSubscribedToken) { + addLog("🔔 Push token already subscribed this session — skipped") + return + } + val packet = PacketPushNotification().apply { notificationsToken = token action = PushNotificationAction.SUBSCRIBE } send(packet) + lastSubscribedToken = token addLog("🔔 Push token subscribe requested on AUTHENTICATED") } @@ -803,6 +824,7 @@ object ProtocolManager { _devices.value = emptyList() _pendingDeviceVerification.value = null setSyncInProgress(false) + lastSubscribedToken = null // reset so token is re-sent on next connect } /** diff --git a/app/src/main/java/com/rosetta/messenger/push/RosettaFirebaseMessagingService.kt b/app/src/main/java/com/rosetta/messenger/push/RosettaFirebaseMessagingService.kt index 9ff7969..b1165d1 100644 --- a/app/src/main/java/com/rosetta/messenger/push/RosettaFirebaseMessagingService.kt +++ b/app/src/main/java/com/rosetta/messenger/push/RosettaFirebaseMessagingService.kt @@ -13,9 +13,7 @@ import com.rosetta.messenger.MainActivity import com.rosetta.messenger.R import com.rosetta.messenger.data.AccountManager import com.rosetta.messenger.data.PreferencesManager -import com.rosetta.messenger.network.PacketPushNotification import com.rosetta.messenger.network.ProtocolManager -import com.rosetta.messenger.network.PushNotificationAction import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -70,15 +68,9 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() { saveFcmToken(token) // Best-effort: если соединение уже авторизовано — сразу обновляем подписку на push. + // Используем единую точку отправки в ProtocolManager (с дедупликацией). if (ProtocolManager.isAuthenticated()) { - runCatching { - ProtocolManager.send( - PacketPushNotification().apply { - notificationsToken = token - action = PushNotificationAction.SUBSCRIBE - } - ) - } + runCatching { ProtocolManager.subscribePushTokenIfAvailable(forceToken = token) } } }