feat: Bump version to 1.0.11, add ConnectionLogsScreen, and enhance message synchronization logic

This commit is contained in:
2026-02-27 02:43:30 +05:00
parent 829e19364a
commit 2da2c6ab36
9 changed files with 338 additions and 102 deletions

View File

@@ -98,6 +98,7 @@ class MessageRepository private constructor(private val context: Context) {
/** Desktop parity: MESSAGE_MAX_TIME_TO_DELEVERED_S = 80 (seconds) */
private const val MESSAGE_MAX_TIME_TO_DELIVERED_MS = 80_000L
private const val MAX_SYNC_FUTURE_DRIFT_MS = 86_400_000L // 24h
const val SYSTEM_SAFE_PUBLIC_KEY = "0x000000000000000000000000000000000000000002"
const val SYSTEM_SAFE_TITLE = "Safe"
@@ -366,16 +367,43 @@ class MessageRepository private constructor(private val context: Context) {
suspend fun getLastSyncTimestamp(): Long {
val account = currentAccount ?: return 0L
return syncTimeDao.getLastSync(account) ?: 0L
val stored = syncTimeDao.getLastSync(account) ?: 0L
val normalized = normalizeSyncTimestamp(stored)
if (normalized != stored) {
syncTimeDao.upsert(AccountSyncTimeEntity(account = account, lastSync = normalized))
if (stored > 0) {
android.util.Log.w(
"MessageRepository",
"⚠️ Normalized invalid last_sync for account=${account.take(10)}...: $stored -> $normalized"
)
ProtocolManager.addLog("⚠️ SYNC cursor normalized: $stored -> $normalized")
}
}
return normalized
}
suspend fun updateLastSyncTimestamp(timestamp: Long) {
if (timestamp <= 0) return
val account = currentAccount ?: return
val existing = syncTimeDao.getLastSync(account) ?: 0L
if (timestamp > existing) {
syncTimeDao.upsert(AccountSyncTimeEntity(account = account, lastSync = timestamp))
val normalized = normalizeSyncTimestamp(timestamp)
if (normalized <= 0) return
// Desktop parity: allow moving sync cursor backward if needed.
syncTimeDao.upsert(AccountSyncTimeEntity(account = account, lastSync = normalized))
}
private fun normalizeSyncTimestamp(rawTimestamp: Long): Long {
if (rawTimestamp <= 0) return 0L
val now = System.currentTimeMillis()
val maxAllowed = now + MAX_SYNC_FUTURE_DRIFT_MS
var normalized = rawTimestamp
// Heal common corruption where extra decimal places appear in timestamp.
while (normalized > maxAllowed) {
normalized /= 10L
if (normalized <= 0L) return 0L
}
return if (normalized > maxAllowed) 0L else normalized
}
/** Получить поток сообщений для диалога */
@@ -591,21 +619,21 @@ class MessageRepository private constructor(private val context: Context) {
return optimisticMessage
}
/** Обработка входящего сообщения */
suspend fun handleIncomingMessage(packet: PacketMessage) {
/** Обработка входящего сообщения. Возвращает true если пакет обработан безопасно. */
suspend fun handleIncomingMessage(packet: PacketMessage): Boolean {
val startTime = System.currentTimeMillis()
val account =
currentAccount
?: run {
MessageLogger.debug("📥 RECEIVE SKIP: account is null")
return
return false
}
val privateKey =
currentPrivateKey
?: run {
MessageLogger.debug("📥 RECEIVE SKIP: privateKey is null")
return
return false
}
// 📝 LOG: Начало обработки входящего сообщения
@@ -625,7 +653,7 @@ class MessageRepository private constructor(private val context: Context) {
val isBlocked = database.blacklistDao().isUserBlocked(packet.fromPublicKey, account)
if (isBlocked) {
MessageLogger.logBlockedSender(packet.fromPublicKey)
return
return true
}
}
@@ -650,14 +678,14 @@ class MessageRepository private constructor(private val context: Context) {
MessageLogger.debug(
"📥 SKIP (in-memory cache): Message $messageId already being processed"
)
return
return true
}
// 🔥 ВТОРОЙ УРОВЕНЬ ЗАЩИТЫ: Проверка в БД (для сообщений сохранённых в предыдущих сессиях)
val isDuplicate = messageDao.messageExists(account, messageId)
MessageLogger.logDuplicateCheck(messageId, isDuplicate)
if (isDuplicate) {
return
return true
}
val dialogOpponentKey = if (isOwnMessage) packet.toPublicKey else packet.fromPublicKey
@@ -794,6 +822,7 @@ class MessageRepository private constructor(private val context: Context) {
// 📝 LOG: Успешная обработка
MessageLogger.logReceiveSuccess(messageId, System.currentTimeMillis() - startTime)
return true
} catch (e: Exception) {
// 📝 LOG: Ошибка обработки
MessageLogger.logDecryptionError(messageId, e)
@@ -803,6 +832,7 @@ class MessageRepository private constructor(private val context: Context) {
// Разрешаем повторную обработку через re-sync, если пакет не удалось сохранить.
processedMessageIds.remove(messageId)
e.printStackTrace()
return false
}
}

View File

@@ -17,15 +17,15 @@ object ReleaseNotes {
val RELEASE_NOTICE = """
Update v$VERSION_PLACEHOLDER
Верификация аккаунта
- Бейдж верификации отображается в боковом меню рядом с именем
- Бейдж верификации отображается в экране профиля
- Статус загружается из кэша пользователей при старте
Синхронизация сообщений
- Исправлен недолет сообщений после оффлайна при массовой отправке (спам-тест)
- Исправлен сценарий, когда синхронизация останавливалась на первой пачке
- Нормализуется sync-cursor (last_sync), включая поврежденные timestamp
- Следующий sync-запрос отправляется с безопасным timestamp
Стабильность
- Фильтрация неподдерживаемых пакетов (группы, conversations)
- Добавлена фильтрация delivery-пакетов для неподдерживаемых диалогов
- Улучшена обработка ошибок в очереди входящих пакетов
Стабильность протокола
- Улучшена защита чтения строк из бинарного потока
- Ошибки внутри батча больше не клинят дальнейшую догрузку пакетов
""".trimIndent()
fun getNotice(version: String): String =