feat: Implement deduplication for FCM token subscription and update related logic
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user