feat: Remove debug logging from message sending and user blocking/unblocking functions
This commit is contained in:
@@ -211,7 +211,6 @@ object CryptoManager {
|
||||
// Decompress (zlib inflate - совместимо с pako.inflate в JS)
|
||||
String(decompress(decrypted), Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("CryptoManager", "decryptWithPassword failed: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,20 +77,14 @@ object MessageCrypto {
|
||||
* XChaCha20-Poly1305 encrypt implementation
|
||||
*/
|
||||
private fun xchacha20Poly1305Encrypt(plaintext: ByteArray, key: ByteArray, nonce: ByteArray): ByteArray {
|
||||
android.util.Log.d("MessageCrypto", "🔐 XChaCha20-Poly1305 Encrypt:")
|
||||
android.util.Log.d("MessageCrypto", " Key hex: ${key.toHex()}")
|
||||
android.util.Log.d("MessageCrypto", " Nonce hex: ${nonce.toHex()}")
|
||||
android.util.Log.d("MessageCrypto", " Plaintext hex: ${plaintext.toHex()}")
|
||||
|
||||
// Step 1: Derive subkey using HChaCha20
|
||||
val subkey = hchacha20(key, nonce.copyOfRange(0, 16))
|
||||
android.util.Log.d("MessageCrypto", " Subkey hex: ${subkey.toHex()}")
|
||||
|
||||
// Step 2: Create ChaCha20 nonce (4 zeros + last 8 bytes of original nonce)
|
||||
val chacha20Nonce = ByteArray(12)
|
||||
// First 4 bytes are 0
|
||||
System.arraycopy(nonce, 16, chacha20Nonce, 4, 8)
|
||||
android.util.Log.d("MessageCrypto", " ChaCha20 nonce hex: ${chacha20Nonce.toHex()}")
|
||||
|
||||
// Step 3: Initialize ChaCha20 engine ONCE
|
||||
val engine = ChaCha7539Engine()
|
||||
@@ -99,14 +93,11 @@ object MessageCrypto {
|
||||
// Step 4: Generate Poly1305 key from first 64 bytes of keystream (counter=0)
|
||||
val poly1305KeyBlock = ByteArray(64)
|
||||
engine.processBytes(ByteArray(64), 0, 64, poly1305KeyBlock, 0)
|
||||
android.util.Log.d("MessageCrypto", " Poly1305 key hex: ${poly1305KeyBlock.copyOfRange(0, 32).toHex()}")
|
||||
|
||||
// Step 5: Encrypt plaintext (engine continues from counter=1 automatically)
|
||||
val ciphertext = ByteArray(plaintext.size)
|
||||
engine.processBytes(plaintext, 0, plaintext.size, ciphertext, 0)
|
||||
android.util.Log.d("MessageCrypto", " Ciphertext hex: ${ciphertext.toHex()}")
|
||||
|
||||
android.util.Log.d("MessageCrypto", " Ciphertext hex: ${ciphertext.toHex()}")
|
||||
|
||||
// Step 6: Generate Poly1305 tag
|
||||
val mac = Poly1305()
|
||||
@@ -309,15 +300,11 @@ object MessageCrypto {
|
||||
* КРИТИЧНО: ephemeralKey.getPrivate('hex') в JS может быть БЕЗ ведущих нулей!
|
||||
*/
|
||||
fun encryptKeyForRecipient(keyAndNonce: ByteArray, recipientPublicKeyHex: String): String {
|
||||
android.util.Log.d("MessageCrypto", "\n" + "━".repeat(80))
|
||||
android.util.Log.d("MessageCrypto", "🔐 ECDH ENCRYPT KEY FOR RECIPIENT")
|
||||
android.util.Log.d("MessageCrypto", "━".repeat(80))
|
||||
|
||||
val secureRandom = SecureRandom()
|
||||
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
|
||||
// Генерируем эфемерный приватный ключ (32 байта)
|
||||
android.util.Log.d("MessageCrypto", "📍 Step 1: Generate ephemeral key pair")
|
||||
val ephemeralPrivateKeyBytes = ByteArray(32)
|
||||
secureRandom.nextBytes(ephemeralPrivateKeyBytes)
|
||||
val ephemeralPrivateKey = BigInteger(1, ephemeralPrivateKeyBytes)
|
||||
@@ -328,29 +315,20 @@ object MessageCrypto {
|
||||
val ephemeralPrivateKeyHex = ephemeralPrivateKey.toString(16).let {
|
||||
// Но если нечётная длина, добавим ведущий 0 для правильного парсинга
|
||||
if (it.length % 2 != 0) {
|
||||
android.util.Log.d("MessageCrypto", "⚠️ Padded ephemeral private key (was odd length)")
|
||||
"0$it"
|
||||
} else it
|
||||
}
|
||||
|
||||
android.util.Log.d("MessageCrypto", " ✓ Ephemeral private key: $ephemeralPrivateKeyHex")
|
||||
android.util.Log.d("MessageCrypto", " ✓ Ephemeral private length: ${ephemeralPrivateKeyHex.length} hex chars")
|
||||
|
||||
// Получаем эфемерный публичный ключ
|
||||
val ephemeralPublicKey = ecSpec.g.multiply(ephemeralPrivateKey)
|
||||
val ephemeralPublicKeyHex = ephemeralPublicKey.getEncoded(false).toHex()
|
||||
android.util.Log.d("MessageCrypto", " ✓ Ephemeral public key: $ephemeralPublicKeyHex")
|
||||
|
||||
// Парсим публичный ключ получателя
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 2: Parse recipient public key")
|
||||
android.util.Log.d("MessageCrypto", " ✓ Recipient public key: $recipientPublicKeyHex")
|
||||
val recipientPublicKeyBytes = recipientPublicKeyHex.hexToBytes()
|
||||
android.util.Log.d("MessageCrypto", " ✓ Recipient key bytes: ${recipientPublicKeyBytes.size}")
|
||||
val recipientPublicKey = ecSpec.curve.decodePoint(recipientPublicKeyBytes)
|
||||
|
||||
// ECDH: ephemeralPrivate * recipientPublic = sharedSecret
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 3: Compute ECDH shared secret")
|
||||
android.util.Log.d("MessageCrypto", " • Computing: ephemeral_private × recipient_public")
|
||||
val sharedPoint = recipientPublicKey.multiply(ephemeralPrivateKey)
|
||||
|
||||
// ⚠️ КРИТИЧНО: Эмулируем JS поведение!
|
||||
@@ -358,48 +336,31 @@ object MessageCrypto {
|
||||
// crypto.enc.Hex.parse(hex) парсит как есть
|
||||
// Если X coordinate = 0x00abc..., JS получит "abc..." (меньше 32 байт)
|
||||
val xCoordBigInt = sharedPoint.normalize().xCoord.toBigInteger()
|
||||
android.util.Log.d("MessageCrypto", " • X coordinate (BigInt): $xCoordBigInt")
|
||||
var sharedSecretHex = xCoordBigInt.toString(16)
|
||||
android.util.Log.d("MessageCrypto", " • toString(16) result: $sharedSecretHex (${sharedSecretHex.length} chars)")
|
||||
|
||||
// JS: если hex нечётной длины, crypto.enc.Hex.parse добавит ведущий 0
|
||||
if (sharedSecretHex.length % 2 != 0) {
|
||||
android.util.Log.d("MessageCrypto", " ⚠️ Padding to even length for hex parsing")
|
||||
sharedSecretHex = "0$sharedSecretHex"
|
||||
}
|
||||
val sharedSecret = sharedSecretHex.hexToBytes()
|
||||
android.util.Log.d("MessageCrypto", " ✓ SHARED SECRET hex: $sharedSecretHex")
|
||||
android.util.Log.d("MessageCrypto", " ✓ Shared secret bytes: ${sharedSecret.size} bytes")
|
||||
|
||||
// Генерируем IV для AES (16 байт)
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 4: Generate AES IV")
|
||||
val iv = ByteArray(16)
|
||||
secureRandom.nextBytes(iv)
|
||||
val ivHex = iv.toHex()
|
||||
android.util.Log.d("MessageCrypto", " ✓ IV: $ivHex")
|
||||
|
||||
// ⚠️ КРИТИЧНО: Эмулируем поведение React Native + crypto-js!
|
||||
// React Native: Buffer.toString('binary') → строка с символами (включая > 127)
|
||||
// crypto-js: AES.encrypt(string, ...) → кодирует строку в UTF-8 перед шифрованием
|
||||
// Итого: байты > 127 превращаются в многобайтовые UTF-8 последовательности
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 5: Latin1 → UTF-8 (crypto-js compatibility)")
|
||||
android.util.Log.d("MessageCrypto", " • Input key+nonce bytes: ${keyAndNonce.size}")
|
||||
android.util.Log.d("MessageCrypto", " • Input hex: ${keyAndNonce.toHex()}")
|
||||
|
||||
// Шаг 1: Байты → Latin1 строка (как Buffer.toString('binary'))
|
||||
val latin1String = String(keyAndNonce, Charsets.ISO_8859_1)
|
||||
android.util.Log.d("MessageCrypto", " • Latin1 string length: ${latin1String.length} chars")
|
||||
|
||||
// Шаг 2: Latin1 строка → UTF-8 байты (как crypto-js делает внутри)
|
||||
val utf8Bytes = latin1String.toByteArray(Charsets.UTF_8)
|
||||
android.util.Log.d("MessageCrypto", " ✓ UTF-8 bytes length: ${utf8Bytes.size}")
|
||||
android.util.Log.d("MessageCrypto", " ✓ UTF-8 hex: ${utf8Bytes.toHex()}")
|
||||
|
||||
// AES шифрование
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 6: AES-CBC encryption")
|
||||
android.util.Log.d("MessageCrypto", " • AES key (shared secret): ${sharedSecret.size} bytes")
|
||||
android.util.Log.d("MessageCrypto", " • IV: ${iv.size} bytes")
|
||||
android.util.Log.d("MessageCrypto", " • Plaintext: ${utf8Bytes.size} bytes")
|
||||
|
||||
val aesKey = SecretKeySpec(sharedSecret, "AES")
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
@@ -407,19 +368,11 @@ object MessageCrypto {
|
||||
val encryptedKey = cipher.doFinal(utf8Bytes)
|
||||
val encryptedKeyHex = encryptedKey.toHex()
|
||||
|
||||
android.util.Log.d("MessageCrypto", " ✓ CIPHERTEXT: $encryptedKeyHex")
|
||||
android.util.Log.d("MessageCrypto", " ✓ Ciphertext length: ${encryptedKey.size} bytes")
|
||||
|
||||
// Формат как в RN: btoa(ivHex:encryptedHex:ephemeralPrivateHex)
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 7: Format as iv:ciphertext:ephemeralPrivate")
|
||||
val combined = "$ivHex:$encryptedKeyHex:$ephemeralPrivateKeyHex"
|
||||
android.util.Log.d("MessageCrypto", " • Combined string: ${combined.take(160)}...")
|
||||
android.util.Log.d("MessageCrypto", " • Combined length: ${combined.length} chars")
|
||||
|
||||
val result = Base64.encodeToString(combined.toByteArray(), Base64.NO_WRAP)
|
||||
android.util.Log.d("MessageCrypto", "\n ✅ FINAL ENCRYPTED KEY (Base64): $result")
|
||||
android.util.Log.d("MessageCrypto", " ✅ Base64 length: ${result.length} chars")
|
||||
android.util.Log.d("MessageCrypto", "━".repeat(80) + "\n")
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -430,111 +383,68 @@ object MessageCrypto {
|
||||
* КРИТИЧНО: ephemeralPrivateKeyHex может иметь нечётную длину!
|
||||
*/
|
||||
fun decryptKeyFromSender(encryptedKeyBase64: String, myPrivateKeyHex: String): ByteArray {
|
||||
android.util.Log.d("MessageCrypto", "\n" + "━".repeat(80))
|
||||
android.util.Log.d("MessageCrypto", "🔓 ECDH DECRYPT KEY FROM SENDER")
|
||||
android.util.Log.d("MessageCrypto", "━".repeat(80))
|
||||
android.util.Log.d("MessageCrypto", "📥 Encrypted key (Base64): $encryptedKeyBase64")
|
||||
android.util.Log.d("MessageCrypto", "📥 Base64 length: ${encryptedKeyBase64.length} chars")
|
||||
android.util.Log.d("MessageCrypto", "🔑 My private key: $myPrivateKeyHex")
|
||||
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 1: Decode Base64 and parse format")
|
||||
val combined = String(Base64.decode(encryptedKeyBase64, Base64.NO_WRAP))
|
||||
android.util.Log.d("MessageCrypto", " • Decoded string: ${combined.take(160)}...")
|
||||
android.util.Log.d("MessageCrypto", " • Decoded length: ${combined.length} chars")
|
||||
|
||||
val parts = combined.split(":")
|
||||
if (parts.size != 3) {
|
||||
android.util.Log.e("MessageCrypto", "❌ Invalid format: expected 3 parts, got ${parts.size}")
|
||||
throw IllegalArgumentException("Invalid encrypted key format: expected 3 parts, got ${parts.size}")
|
||||
}
|
||||
android.util.Log.d("MessageCrypto", " ✓ Parsed into 3 parts")
|
||||
|
||||
val ivHex = parts[0]
|
||||
val encryptedKeyHex = parts[1]
|
||||
var ephemeralPrivateKeyHex = parts[2]
|
||||
|
||||
android.util.Log.d("MessageCrypto", " • Part 1 (IV): $ivHex (${ivHex.length} chars)")
|
||||
android.util.Log.d("MessageCrypto", " • Part 2 (ciphertext): ${encryptedKeyHex.take(80)}... (${encryptedKeyHex.length} chars)")
|
||||
android.util.Log.d("MessageCrypto", " • Part 3 (ephemeral private): $ephemeralPrivateKeyHex")
|
||||
|
||||
// ⚠️ КРИТИЧНО: JS toString(16) может вернуть hex нечётной длины!
|
||||
// Добавляем ведущий 0 если нужно для правильного парсинга
|
||||
if (ephemeralPrivateKeyHex.length % 2 != 0) {
|
||||
android.util.Log.d("MessageCrypto", " ⚠️ Padding ephemeral key from ${ephemeralPrivateKeyHex.length} to ${ephemeralPrivateKeyHex.length + 1}")
|
||||
ephemeralPrivateKeyHex = "0$ephemeralPrivateKeyHex"
|
||||
}
|
||||
|
||||
val iv = ivHex.hexToBytes()
|
||||
val encryptedKey = encryptedKeyHex.hexToBytes()
|
||||
android.util.Log.d("MessageCrypto", " ✓ IV bytes: ${iv.size}")
|
||||
android.util.Log.d("MessageCrypto", " ✓ Ciphertext bytes: ${encryptedKey.size}")
|
||||
|
||||
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
|
||||
// Парсим эфемерный приватный ключ
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 2: Parse ephemeral key pair")
|
||||
val ephemeralPrivateKey = BigInteger(ephemeralPrivateKeyHex, 16)
|
||||
val ephemeralPublicKey = ecSpec.g.multiply(ephemeralPrivateKey)
|
||||
val ephemeralPublicKeyHex = ephemeralPublicKey.getEncoded(false).toHex()
|
||||
android.util.Log.d("MessageCrypto", " ✓ Ephemeral private: $ephemeralPrivateKeyHex")
|
||||
android.util.Log.d("MessageCrypto", " ✓ Ephemeral public: $ephemeralPublicKeyHex")
|
||||
|
||||
// Парсим мой приватный ключ
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 3: Parse my key pair")
|
||||
val myPrivateKey = BigInteger(myPrivateKeyHex, 16)
|
||||
val myPublicKey = ecSpec.g.multiply(myPrivateKey)
|
||||
val myPublicKeyHex = myPublicKey.getEncoded(false).toHex()
|
||||
android.util.Log.d("MessageCrypto", " ✓ My private: ${myPrivateKeyHex.take(32)}...")
|
||||
android.util.Log.d("MessageCrypto", " ✓ My public: $myPublicKeyHex")
|
||||
|
||||
// ECDH: ephemeralPrivate * myPublic = sharedSecret
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 4: Compute ECDH shared secret")
|
||||
android.util.Log.d("MessageCrypto", " • Computing: ephemeral_private × my_public")
|
||||
val sharedPoint = myPublicKey.multiply(ephemeralPrivateKey)
|
||||
|
||||
// ⚠️ КРИТИЧНО: Эмулируем JS поведение!
|
||||
// JS: BN.toString(16) НЕ добавляет ведущие нули
|
||||
val xCoordBigInt = sharedPoint.normalize().xCoord.toBigInteger()
|
||||
android.util.Log.d("MessageCrypto", " • X coordinate (BigInt): $xCoordBigInt")
|
||||
var sharedSecretHex = xCoordBigInt.toString(16)
|
||||
android.util.Log.d("MessageCrypto", " • toString(16) result: $sharedSecretHex (${sharedSecretHex.length} chars)")
|
||||
|
||||
if (sharedSecretHex.length % 2 != 0) {
|
||||
android.util.Log.d("MessageCrypto", " ⚠️ Padding to even length")
|
||||
sharedSecretHex = "0$sharedSecretHex"
|
||||
}
|
||||
val sharedSecret = sharedSecretHex.hexToBytes()
|
||||
android.util.Log.d("MessageCrypto", " ✓ SHARED SECRET hex: $sharedSecretHex")
|
||||
android.util.Log.d("MessageCrypto", " ✓ Shared secret bytes: ${sharedSecret.size} bytes")
|
||||
|
||||
// Расшифровываем используя sharedSecret как AES ключ
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 5: AES-CBC decryption")
|
||||
android.util.Log.d("MessageCrypto", " • AES key (shared secret): ${sharedSecret.size} bytes")
|
||||
android.util.Log.d("MessageCrypto", " • IV: ${iv.size} bytes")
|
||||
android.util.Log.d("MessageCrypto", " • Ciphertext: ${encryptedKey.size} bytes")
|
||||
|
||||
val aesKey = SecretKeySpec(sharedSecret, "AES")
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, aesKey, IvParameterSpec(iv))
|
||||
|
||||
val decryptedUtf8Bytes = cipher.doFinal(encryptedKey)
|
||||
android.util.Log.d("MessageCrypto", " ✓ Decrypted UTF-8 bytes: ${decryptedUtf8Bytes.size}")
|
||||
android.util.Log.d("MessageCrypto", " ✓ Decrypted hex: ${decryptedUtf8Bytes.toHex()}")
|
||||
|
||||
// ⚠️ КРИТИЧНО: Обратная конвертация UTF-8 → Latin1!
|
||||
// Desktop: decrypted.toString(crypto.enc.Utf8) → Buffer.from(str, 'binary')
|
||||
// Это декодирует UTF-8 в строку, потом берёт charCode каждого символа
|
||||
android.util.Log.d("MessageCrypto", "\n📍 Step 6: UTF-8 → Latin1 (crypto-js compatibility)")
|
||||
val utf8String = String(decryptedUtf8Bytes, Charsets.UTF_8)
|
||||
android.util.Log.d("MessageCrypto", " • UTF-8 string length: ${utf8String.length} chars")
|
||||
|
||||
val originalBytes = utf8String.toByteArray(Charsets.ISO_8859_1)
|
||||
android.util.Log.d("MessageCrypto", " ✓ RESULT bytes: ${originalBytes.size}")
|
||||
android.util.Log.d("MessageCrypto", " ✓ Result hex: ${originalBytes.toHex()}")
|
||||
|
||||
android.util.Log.d("MessageCrypto", "\n ✅ DECRYPTION COMPLETE")
|
||||
android.util.Log.d("MessageCrypto", " ✅ Returned ${originalBytes.size} bytes")
|
||||
android.util.Log.d("MessageCrypto", "━".repeat(80) + "\n")
|
||||
|
||||
return originalBytes
|
||||
}
|
||||
@@ -552,51 +462,16 @@ object MessageCrypto {
|
||||
* Полное шифрование сообщения для отправки
|
||||
*/
|
||||
fun encryptForSending(plaintext: String, recipientPublicKey: String): EncryptedForSending {
|
||||
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||
android.util.Log.d("MessageCrypto", "🚀🚀🚀 START ENCRYPTION FOR SENDING 🚀🚀🚀")
|
||||
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||
android.util.Log.d("MessageCrypto", "📝 PLAINTEXT: '$plaintext'")
|
||||
android.util.Log.d("MessageCrypto", "📝 Plaintext length: ${plaintext.length} chars")
|
||||
android.util.Log.d("MessageCrypto", "📝 Plaintext bytes: ${plaintext.toByteArray().size} bytes")
|
||||
android.util.Log.d("MessageCrypto", "📝 Plaintext hex (first 128): ${plaintext.toByteArray().take(64).toByteArray().toHex()}")
|
||||
android.util.Log.d("MessageCrypto", "🔑 RECIPIENT PUBLIC KEY: $recipientPublicKey")
|
||||
android.util.Log.d("MessageCrypto", "🔑 Public key length: ${recipientPublicKey.length} chars")
|
||||
|
||||
// 1. Шифруем текст
|
||||
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||
android.util.Log.d("MessageCrypto", "⚡ STEP 1: XCHACHA20-POLY1305 ENCRYPTION")
|
||||
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||
val encrypted = encryptMessage(plaintext)
|
||||
android.util.Log.d("MessageCrypto", "✅ CIPHERTEXT (hex): ${encrypted.ciphertext}")
|
||||
android.util.Log.d("MessageCrypto", "✅ Ciphertext length: ${encrypted.ciphertext.length} chars")
|
||||
android.util.Log.d("MessageCrypto", "✅ KEY (hex): ${encrypted.key}")
|
||||
android.util.Log.d("MessageCrypto", "✅ Key length: ${encrypted.key.length} chars (should be 64)")
|
||||
android.util.Log.d("MessageCrypto", "✅ NONCE (hex): ${encrypted.nonce}")
|
||||
android.util.Log.d("MessageCrypto", "✅ Nonce length: ${encrypted.nonce.length} chars (should be 48)")
|
||||
|
||||
// 2. Собираем key + nonce
|
||||
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||
android.util.Log.d("MessageCrypto", "⚡ STEP 2: COMBINE KEY + NONCE")
|
||||
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||
val keyAndNonce = encrypted.key.hexToBytes() + encrypted.nonce.hexToBytes()
|
||||
android.util.Log.d("MessageCrypto", "✅ KEY+NONCE combined: ${keyAndNonce.size} bytes (should be 56)")
|
||||
android.util.Log.d("MessageCrypto", "✅ Combined hex: ${keyAndNonce.toHex()}")
|
||||
|
||||
// 3. Шифруем ключ для получателя
|
||||
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||
android.util.Log.d("MessageCrypto", "⚡ STEP 3: ECDH + AES ENCRYPTION OF KEY")
|
||||
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||
val encryptedKey = encryptKeyForRecipient(keyAndNonce, recipientPublicKey)
|
||||
android.util.Log.d("MessageCrypto", "✅ ENCRYPTED KEY (Base64): $encryptedKey")
|
||||
android.util.Log.d("MessageCrypto", "✅ Encrypted key length: ${encryptedKey.length} chars")
|
||||
|
||||
android.util.Log.d("MessageCrypto", "\n" + "=".repeat(100))
|
||||
android.util.Log.d("MessageCrypto", "✅✅✅ ENCRYPTION COMPLETE ✅✅✅")
|
||||
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||
android.util.Log.d("MessageCrypto", "FINAL OUTPUTS:")
|
||||
android.util.Log.d("MessageCrypto", " • Ciphertext: ${encrypted.ciphertext.take(60)}... (${encrypted.ciphertext.length} chars)")
|
||||
android.util.Log.d("MessageCrypto", " • Encrypted key: ${encryptedKey.take(60)}... (${encryptedKey.length} chars)")
|
||||
android.util.Log.d("MessageCrypto", "=".repeat(100) + "\n")
|
||||
|
||||
return EncryptedForSending(encrypted.ciphertext, encryptedKey, keyAndNonce)
|
||||
}
|
||||
@@ -610,48 +485,17 @@ object MessageCrypto {
|
||||
encryptedKey: String,
|
||||
myPrivateKey: String
|
||||
): DecryptedIncoming {
|
||||
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||
android.util.Log.d("MessageCrypto", "🔓🔓🔓 START DECRYPTION OF INCOMING MESSAGE 🔓🔓🔓")
|
||||
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||
android.util.Log.d("MessageCrypto", "📩 CIPHERTEXT (hex): ${ciphertext.take(100)}...")
|
||||
android.util.Log.d("MessageCrypto", "📩 Ciphertext length: ${ciphertext.length} chars")
|
||||
android.util.Log.d("MessageCrypto", "🔐 ENCRYPTED KEY: ${encryptedKey.take(100)}...")
|
||||
android.util.Log.d("MessageCrypto", "🔐 Encrypted key length: ${encryptedKey.length} chars")
|
||||
android.util.Log.d("MessageCrypto", "🔑 MY PRIVATE KEY: ${myPrivateKey.take(32)}... (${myPrivateKey.length} chars)")
|
||||
|
||||
// 1. Расшифровываем ключ
|
||||
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||
android.util.Log.d("MessageCrypto", "⚡ STEP 1: ECDH + AES DECRYPTION OF KEY")
|
||||
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||
val keyAndNonce = decryptKeyFromSender(encryptedKey, myPrivateKey)
|
||||
android.util.Log.d("MessageCrypto", "✅ DECRYPTED KEY+NONCE: ${keyAndNonce.size} bytes (should be 56)")
|
||||
android.util.Log.d("MessageCrypto", "✅ Key+Nonce hex: ${keyAndNonce.toHex()}")
|
||||
|
||||
// 2. Разделяем key и nonce
|
||||
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||
android.util.Log.d("MessageCrypto", "⚡ STEP 2: SPLIT KEY AND NONCE")
|
||||
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||
val key = keyAndNonce.slice(0 until 32).toByteArray()
|
||||
val nonce = keyAndNonce.slice(32 until keyAndNonce.size).toByteArray()
|
||||
android.util.Log.d("MessageCrypto", "✅ KEY (32 bytes): ${key.toHex()}")
|
||||
android.util.Log.d("MessageCrypto", "✅ NONCE (24 bytes): ${nonce.toHex()}")
|
||||
android.util.Log.d("MessageCrypto", "✅ Key length: ${key.size} bytes")
|
||||
android.util.Log.d("MessageCrypto", "✅ Nonce length: ${nonce.size} bytes")
|
||||
|
||||
// 3. Расшифровываем сообщение
|
||||
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||
android.util.Log.d("MessageCrypto", "⚡ STEP 3: XCHACHA20-POLY1305 DECRYPTION")
|
||||
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||
val plaintext = decryptMessage(ciphertext, key.toHex(), nonce.toHex())
|
||||
android.util.Log.d("MessageCrypto", "✅ PLAINTEXT: '$plaintext'")
|
||||
android.util.Log.d("MessageCrypto", "✅ Plaintext length: ${plaintext.length} chars")
|
||||
android.util.Log.d("MessageCrypto", "✅ Plaintext bytes: ${plaintext.toByteArray().size} bytes")
|
||||
|
||||
android.util.Log.d("MessageCrypto", "\n" + "=".repeat(100))
|
||||
android.util.Log.d("MessageCrypto", "✅✅✅ DECRYPTION COMPLETE ✅✅✅")
|
||||
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||
android.util.Log.d("MessageCrypto", "FINAL OUTPUT: '$plaintext'")
|
||||
android.util.Log.d("MessageCrypto", "=".repeat(100) + "\n")
|
||||
|
||||
return DecryptedIncoming(plaintext, keyAndNonce)
|
||||
}
|
||||
@@ -690,7 +534,6 @@ object MessageCrypto {
|
||||
// 4. Расшифровываем AES-256-CBC
|
||||
decryptWithPBKDF2Key(encryptedData, pbkdf2Key)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("MessageCrypto", "❌ Failed to decrypt attachment blob", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -718,7 +561,6 @@ object MessageCrypto {
|
||||
return try {
|
||||
val parts = encryptedData.split(":")
|
||||
if (parts.size != 2) {
|
||||
android.util.Log.e("MessageCrypto", "Invalid encrypted data format")
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -745,7 +587,6 @@ object MessageCrypto {
|
||||
|
||||
String(outputStream.toByteArray(), Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("MessageCrypto", "❌ Failed to decrypt with PBKDF2 key", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -765,15 +606,10 @@ object MessageCrypto {
|
||||
*/
|
||||
fun encryptReplyBlob(replyJson: String, plainKeyAndNonce: ByteArray): String {
|
||||
return try {
|
||||
android.util.Log.d("MessageCrypto", "🔐 Encrypting reply blob...")
|
||||
android.util.Log.d("MessageCrypto", " - ReplyJson length: ${replyJson.length}")
|
||||
android.util.Log.d("MessageCrypto", " - PlainKeyAndNonce length: ${plainKeyAndNonce.size}")
|
||||
|
||||
// Convert keyAndNonce to string - simulate JS Buffer.toString('utf-8') behavior
|
||||
// which replaces invalid UTF-8 sequences with U+FFFD
|
||||
val password = bytesToJsUtf8String(plainKeyAndNonce)
|
||||
android.util.Log.d("MessageCrypto", " - Password length: ${password.length}")
|
||||
android.util.Log.d("MessageCrypto", " - Password char codes: ${password.take(5).map { it.code }}")
|
||||
|
||||
// Compress with pako (deflate)
|
||||
val deflater = java.util.zip.Deflater()
|
||||
@@ -783,7 +619,6 @@ object MessageCrypto {
|
||||
val compressedSize = deflater.deflate(compressedBuffer)
|
||||
deflater.end()
|
||||
val compressed = compressedBuffer.copyOf(compressedSize)
|
||||
android.util.Log.d("MessageCrypto", " - Compressed size: ${compressed.size}")
|
||||
|
||||
// PBKDF2 key derivation (matching RN: crypto.PBKDF2(password, 'rosetta', {keySize: 256/32, iterations: 1000}))
|
||||
val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
@@ -795,12 +630,10 @@ object MessageCrypto {
|
||||
)
|
||||
val secretKey = factory.generateSecret(spec)
|
||||
val keyBytes = secretKey.encoded
|
||||
android.util.Log.d("MessageCrypto", " - PBKDF2 key derived: ${keyBytes.toHex()}")
|
||||
|
||||
// Generate random IV (16 bytes)
|
||||
val iv = ByteArray(16)
|
||||
java.security.SecureRandom().nextBytes(iv)
|
||||
android.util.Log.d("MessageCrypto", " - IV generated: ${iv.size} bytes")
|
||||
|
||||
// AES-CBC encryption
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
@@ -808,17 +641,14 @@ object MessageCrypto {
|
||||
val ivSpec = IvParameterSpec(iv)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)
|
||||
val ciphertext = cipher.doFinal(compressed)
|
||||
android.util.Log.d("MessageCrypto", " - Ciphertext size: ${ciphertext.size}")
|
||||
|
||||
// Format: "ivBase64:ciphertextBase64" (same as RN new format)
|
||||
val ivBase64 = Base64.encodeToString(iv, Base64.NO_WRAP)
|
||||
val ctBase64 = Base64.encodeToString(ciphertext, Base64.NO_WRAP)
|
||||
val result = "$ivBase64:$ctBase64"
|
||||
android.util.Log.d("MessageCrypto", " ✅ Reply blob encrypted: ${result.take(50)}...")
|
||||
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("MessageCrypto", "❌ Failed to encrypt reply blob", e)
|
||||
// Fallback: return plaintext (for backwards compatibility)
|
||||
replyJson
|
||||
}
|
||||
@@ -849,31 +679,23 @@ object MessageCrypto {
|
||||
*/
|
||||
fun decryptReplyBlob(encryptedBlob: String, plainKeyAndNonce: ByteArray): String {
|
||||
return try {
|
||||
android.util.Log.d("MessageCrypto", "🔓 Decrypting reply blob...")
|
||||
android.util.Log.d("MessageCrypto", " - EncryptedBlob length: ${encryptedBlob.length}")
|
||||
android.util.Log.d("MessageCrypto", " - PlainKeyAndNonce length: ${plainKeyAndNonce.size}")
|
||||
|
||||
// Check if it's encrypted format (contains ':')
|
||||
if (!encryptedBlob.contains(':')) {
|
||||
android.util.Log.d("MessageCrypto", " - Plain JSON detected, returning as-is")
|
||||
return encryptedBlob
|
||||
}
|
||||
|
||||
// Parse ivBase64:ciphertextBase64
|
||||
val parts = encryptedBlob.split(':')
|
||||
if (parts.size != 2) {
|
||||
android.util.Log.e("MessageCrypto", " - Invalid format, expected iv:ciphertext")
|
||||
return encryptedBlob
|
||||
}
|
||||
|
||||
val iv = Base64.decode(parts[0], Base64.DEFAULT)
|
||||
val ciphertext = Base64.decode(parts[1], Base64.DEFAULT)
|
||||
android.util.Log.d("MessageCrypto", " - IV size: ${iv.size}, Ciphertext size: ${ciphertext.size}")
|
||||
|
||||
// Password from keyAndNonce - use same JS-like UTF-8 conversion
|
||||
val password = bytesToJsUtf8String(plainKeyAndNonce)
|
||||
android.util.Log.d("MessageCrypto", " - Password length: ${password.length}")
|
||||
android.util.Log.d("MessageCrypto", " - Password char codes: ${password.take(5).map { it.code }}")
|
||||
|
||||
// PBKDF2 key derivation
|
||||
val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
@@ -885,7 +707,6 @@ object MessageCrypto {
|
||||
)
|
||||
val secretKey = factory.generateSecret(spec)
|
||||
val keyBytes = secretKey.encoded
|
||||
android.util.Log.d("MessageCrypto", " - PBKDF2 key derived: ${keyBytes.toHex()}")
|
||||
|
||||
// AES-CBC decryption
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
@@ -902,10 +723,8 @@ object MessageCrypto {
|
||||
inflater.end()
|
||||
val plaintext = String(outputBuffer, 0, outputSize, Charsets.UTF_8)
|
||||
|
||||
android.util.Log.d("MessageCrypto", " ✅ Reply blob decrypted: ${plaintext.take(50)}...")
|
||||
plaintext
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("MessageCrypto", "❌ Failed to decrypt reply blob: ${e.message}", e)
|
||||
// Return as-is, might be plain JSON
|
||||
encryptedBlob
|
||||
}
|
||||
|
||||
@@ -41,19 +41,15 @@ class AccountManager(private val context: Context) {
|
||||
// Synchronous read from SharedPreferences - always up to date
|
||||
fun getLastLoggedPublicKey(): String? {
|
||||
val publicKey = sharedPrefs.getString(KEY_LAST_LOGGED, null)
|
||||
android.util.Log.d("AccountManager", "📖 getLastLoggedPublicKey: ${publicKey?.take(16) ?: "null"}")
|
||||
return publicKey
|
||||
}
|
||||
|
||||
// Synchronous write to SharedPreferences
|
||||
fun setLastLoggedPublicKey(publicKey: String) {
|
||||
android.util.Log.d("AccountManager", "💾 Saving last logged account: ${publicKey.take(16)}...")
|
||||
val success = sharedPrefs.edit().putString(KEY_LAST_LOGGED, publicKey).commit() // commit() is synchronous
|
||||
android.util.Log.d("AccountManager", "💾 Save result: $success")
|
||||
|
||||
// Verify immediately
|
||||
val saved = sharedPrefs.getString(KEY_LAST_LOGGED, null)
|
||||
android.util.Log.d("AccountManager", "💾 Verification read: ${saved?.take(16) ?: "null"}")
|
||||
}
|
||||
|
||||
suspend fun saveAccount(account: EncryptedAccount) {
|
||||
@@ -86,7 +82,6 @@ class AccountManager(private val context: Context) {
|
||||
}
|
||||
|
||||
suspend fun setCurrentAccount(publicKey: String) {
|
||||
android.util.Log.d("AccountManager", "🔐 setCurrentAccount called for: ${publicKey.take(16)}...")
|
||||
|
||||
// ⚡ ВАЖНО: Сначала сохраняем в SharedPreferences синхронно
|
||||
setLastLoggedPublicKey(publicKey)
|
||||
@@ -97,7 +92,6 @@ class AccountManager(private val context: Context) {
|
||||
preferences[IS_LOGGED_IN] = true
|
||||
}
|
||||
|
||||
android.util.Log.d("AccountManager", "✅ setCurrentAccount completed for: ${publicKey.take(16)}...")
|
||||
}
|
||||
|
||||
suspend fun logout() {
|
||||
|
||||
@@ -93,14 +93,12 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
* Инициализация с текущим аккаунтом
|
||||
*/
|
||||
fun initialize(publicKey: String, privateKey: String) {
|
||||
android.util.Log.d("MessageRepository", "🔐 initialize() called with publicKey: ${publicKey.take(16)}...")
|
||||
currentAccount = publicKey
|
||||
currentPrivateKey = privateKey
|
||||
|
||||
// Загрузка диалогов
|
||||
scope.launch {
|
||||
dialogDao.getDialogsFlow(publicKey).collect { entities ->
|
||||
android.util.Log.d("MessageRepository", "📋 MessageRepository dialogsFlow emitted: ${entities.size} dialogs")
|
||||
_dialogs.value = entities.map { it.toDialog() }
|
||||
|
||||
// 🔥 Запрашиваем информацию о пользователях, у которых нет имени
|
||||
@@ -238,23 +236,17 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
* Обработка входящего сообщения
|
||||
*/
|
||||
suspend fun handleIncomingMessage(packet: PacketMessage) {
|
||||
android.util.Log.d("MessageRepository", "═══════════════════════════════════════")
|
||||
android.util.Log.d("MessageRepository", "📩 handleIncomingMessage START")
|
||||
android.util.Log.d("MessageRepository", " from: ${packet.fromPublicKey.take(20)}...")
|
||||
|
||||
val account = currentAccount ?: run {
|
||||
android.util.Log.e("MessageRepository", "❌ ABORT: currentAccount is NULL!")
|
||||
return
|
||||
}
|
||||
val privateKey = currentPrivateKey ?: run {
|
||||
android.util.Log.e("MessageRepository", "❌ ABORT: currentPrivateKey is NULL!")
|
||||
return
|
||||
}
|
||||
|
||||
// 🔥 Проверяем, не заблокирован ли отправитель
|
||||
val isBlocked = database.blacklistDao().isUserBlocked(packet.fromPublicKey, account)
|
||||
if (isBlocked) {
|
||||
android.util.Log.d("MessageRepository", "🚫 BLOCKED: Ignoring message from blocked user ${packet.fromPublicKey.take(20)}...")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -264,20 +256,14 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
} else {
|
||||
packet.messageId
|
||||
}
|
||||
android.util.Log.d("MessageRepository", " messageId: $messageId (original: ${packet.messageId})")
|
||||
android.util.Log.d("MessageRepository", " currentAccount: ${account.take(20)}...")
|
||||
android.util.Log.d("MessageRepository", " currentPrivateKey: SET")
|
||||
|
||||
// Проверяем, не дубликат ли (используем сгенерированный messageId)
|
||||
val isDuplicate = messageDao.messageExists(account, messageId)
|
||||
android.util.Log.d("MessageRepository", " isDuplicate: $isDuplicate")
|
||||
if (isDuplicate) {
|
||||
android.util.Log.d("MessageRepository", "⚠️ Skipping duplicate message")
|
||||
return
|
||||
}
|
||||
|
||||
val dialogKey = getDialogKey(packet.fromPublicKey)
|
||||
android.util.Log.d("MessageRepository", " dialogKey: $dialogKey")
|
||||
|
||||
try {
|
||||
// Расшифровываем
|
||||
@@ -314,12 +300,9 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
dialogKey = dialogKey
|
||||
)
|
||||
messageDao.insertMessage(entity)
|
||||
android.util.Log.d("MessageRepository", "✅ Message saved to DB: ${packet.messageId.take(16)}...")
|
||||
|
||||
// Обновляем диалог
|
||||
android.util.Log.d("MessageRepository", "🔄 Calling updateDialog for ${packet.fromPublicKey.take(16)}...")
|
||||
updateDialog(packet.fromPublicKey, plainText, packet.timestamp, incrementUnread = true)
|
||||
android.util.Log.d("MessageRepository", "✅ updateDialog completed!")
|
||||
|
||||
// 🔥 Запрашиваем информацию о пользователе для отображения имени вместо ключа
|
||||
requestUserInfo(packet.fromPublicKey)
|
||||
@@ -329,7 +312,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
updateMessageCache(dialogKey, message)
|
||||
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("MessageRepository", "❌ Error handling incoming message", e)
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
@@ -382,7 +364,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
// чтобы unread_count обновился моментально
|
||||
dialogDao.updateDialogFromMessages(account, opponentKey)
|
||||
|
||||
android.util.Log.d("MessageRepository", "✅ Dialog marked as read and updated from messages")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -469,13 +450,10 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
val account = currentAccount ?: return
|
||||
val privateKey = currentPrivateKey ?: return
|
||||
|
||||
android.util.Log.d("MessageRepository", "📝 Updating dialog for ${opponentKey.take(16)}...")
|
||||
android.util.Log.d("MessageRepository", " lastMessage: ${lastMessage.take(50)}")
|
||||
|
||||
try {
|
||||
// 🔥 КРИТИЧНО: Сначала считаем реальное количество непрочитанных из messages
|
||||
val unreadCount = messageDao.getUnreadCountForDialog(account, opponentKey)
|
||||
android.util.Log.d("MessageRepository", " unreadCount from messages: $unreadCount")
|
||||
|
||||
// 🔒 Шифруем lastMessage
|
||||
val encryptedLastMessage = CryptoManager.encryptWithPassword(lastMessage, privateKey)
|
||||
@@ -485,12 +463,10 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
|
||||
if (existing != null) {
|
||||
// Обновляем существующий диалог
|
||||
android.util.Log.d("MessageRepository", " ✏️ Updating existing dialog...")
|
||||
dialogDao.updateLastMessage(account, opponentKey, encryptedLastMessage, timestamp)
|
||||
dialogDao.updateUnreadCount(account, opponentKey, unreadCount)
|
||||
} else {
|
||||
// Создаем новый диалог
|
||||
android.util.Log.d("MessageRepository", " ➕ Creating new dialog...")
|
||||
dialogDao.insertDialog(DialogEntity(
|
||||
account = account,
|
||||
opponentKey = opponentKey,
|
||||
@@ -500,9 +476,7 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
))
|
||||
}
|
||||
|
||||
android.util.Log.d("MessageRepository", " ✅ Dialog updated successfully!")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("MessageRepository", " ❌ Error updating dialog", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +494,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
lastSeen = if (!isOnline) System.currentTimeMillis() else 0
|
||||
)
|
||||
|
||||
android.util.Log.d("MessageRepository", "🟢 Updated online status for ${publicKey.take(16)}... isOnline=$isOnline")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,7 +507,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
val existing = dialogDao.getDialog(account, publicKey)
|
||||
if (existing != null) {
|
||||
dialogDao.updateOpponentInfo(account, publicKey, title, username, verified)
|
||||
android.util.Log.d("MessageRepository", "✅ Updated user info for ${publicKey.take(16)}... title=$title")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +523,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
this.search = publicKey
|
||||
}
|
||||
ProtocolManager.send(packet)
|
||||
android.util.Log.d("MessageRepository", "📤 Requested user info for ${publicKey.take(16)}...")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +534,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
try {
|
||||
CryptoManager.decryptWithPassword(plainMessage, privateKey) ?: plainMessage
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("MessageRepository", "Failed to decrypt plainMessage: ${e.message}")
|
||||
plainMessage // Fallback на зашифрованный текст если расшифровка не удалась
|
||||
}
|
||||
} else {
|
||||
@@ -649,7 +619,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
jsonObj.put("preview", decryptedBlob) // Для совместимости
|
||||
jsonObj.put("width", attachment.width)
|
||||
jsonObj.put("height", attachment.height)
|
||||
android.util.Log.d("MessageRepository", "✅ Decrypted MESSAGES blob: ${decryptedBlob.take(100)}")
|
||||
} else {
|
||||
// Fallback - сохраняем как есть
|
||||
jsonObj.put("id", attachment.id)
|
||||
@@ -660,7 +629,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
jsonObj.put("height", attachment.height)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("MessageRepository", "❌ Failed to decrypt MESSAGES blob", e)
|
||||
// Fallback - сохраняем как есть
|
||||
jsonObj.put("id", attachment.id)
|
||||
jsonObj.put("blob", attachment.blob)
|
||||
|
||||
@@ -911,13 +911,9 @@ fun ChatDetailScreen(
|
||||
}
|
||||
},
|
||||
onSend = {
|
||||
android.util.Log.d("ChatDetailScreen", "🔥🔥🔥 onSend callback CALLED 🔥🔥🔥")
|
||||
android.util.Log.d("ChatDetailScreen", "📝 inputText: '$inputText'")
|
||||
// Скрываем кнопку scroll на время отправки
|
||||
isSendingMessage = true
|
||||
android.util.Log.d("ChatDetailScreen", "➡️ Calling viewModel.sendMessage()")
|
||||
viewModel.sendMessage()
|
||||
android.util.Log.d("ChatDetailScreen", "✅ viewModel.sendMessage() called")
|
||||
// Скроллим к новому сообщению
|
||||
scope.launch {
|
||||
delay(100)
|
||||
@@ -1322,9 +1318,8 @@ fun ChatDetailScreen(
|
||||
account = currentUserPublicKey,
|
||||
opponentKey = user.publicKey
|
||||
)
|
||||
android.util.Log.d("ChatDetail", "✅ Chat deleted with: ${user.publicKey.take(10)}")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatDetail", "❌ Error deleting chat", e)
|
||||
// Error deleting chat
|
||||
}
|
||||
// Выходим ПОСЛЕ удаления
|
||||
onBack()
|
||||
@@ -1366,7 +1361,6 @@ fun ChatDetailScreen(
|
||||
showBlockConfirm = false
|
||||
scope.launch {
|
||||
try {
|
||||
android.util.Log.d("ChatDetail", "🚫 Blocking user: ${user.publicKey.take(10)}")
|
||||
// Добавляем пользователя в blacklist
|
||||
database.blacklistDao().blockUser(
|
||||
com.rosetta.messenger.database.BlacklistEntity(
|
||||
@@ -1375,9 +1369,8 @@ fun ChatDetailScreen(
|
||||
)
|
||||
)
|
||||
isBlocked = true
|
||||
android.util.Log.d("ChatDetail", "✅ User blocked: ${user.publicKey.take(10)}")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatDetail", "❌ Error blocking user", e)
|
||||
// Error blocking user
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1417,16 +1410,14 @@ fun ChatDetailScreen(
|
||||
showUnblockConfirm = false
|
||||
scope.launch {
|
||||
try {
|
||||
android.util.Log.d("ChatDetail", "✅ Unblocking user: ${user.publicKey.take(10)}")
|
||||
// Удаляем пользователя из blacklist
|
||||
database.blacklistDao().unblockUser(
|
||||
publicKey = user.publicKey,
|
||||
account = currentUserPublicKey
|
||||
)
|
||||
isBlocked = false
|
||||
android.util.Log.d("ChatDetail", "✅ User unblocked: ${user.publicKey.take(10)}")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatDetail", "❌ Error unblocking user", e)
|
||||
// Error unblocking user
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2087,10 +2078,8 @@ private fun MessageInputBar(
|
||||
|
||||
// Функция отправки - НЕ закрывает клавиатуру (UX правило #6)
|
||||
fun handleSend() {
|
||||
android.util.Log.d("MessageInputBar", "🚀 handleSend() called, value='$value', isNotBlank=${value.isNotBlank()}, hasReply=$hasReply")
|
||||
// Можно отправить если есть текст ИЛИ есть reply (как в React Native)
|
||||
if (value.isNotBlank() || hasReply) {
|
||||
android.util.Log.d("MessageInputBar", "✅ Calling onSend()")
|
||||
onSend()
|
||||
// Очищаем инпут, но клавиатура остаётся открытой
|
||||
}
|
||||
|
||||
@@ -839,7 +839,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* - Поддержка Reply/Forward через attachments (как в React Native)
|
||||
*/
|
||||
fun sendMessage() {
|
||||
android.util.Log.e("REPLY_DEBUG", "🚀🚀🚀 sendMessage() CALLED 🚀🚀🚀")
|
||||
Log.d(TAG, "🚀🚀🚀 sendMessage() CALLED 🚀🚀🚀")
|
||||
|
||||
val text = _inputText.value.trim()
|
||||
@@ -849,7 +848,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val replyMsgs = _replyMessages.value
|
||||
val isForward = _isForwardMode.value
|
||||
|
||||
android.util.Log.e("REPLY_DEBUG", "📝 Text: '$text', ReplyMsgs: ${replyMsgs.size}")
|
||||
Log.d(TAG, "📝 Text: '$text'")
|
||||
Log.d(TAG, "📧 Recipient: ${recipient?.take(16)}")
|
||||
Log.d(TAG, "👤 Sender: ${sender?.take(16)}")
|
||||
@@ -932,7 +930,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
var replyBlobPlaintext = "" // Сохраняем plaintext для БД
|
||||
|
||||
if (replyMsgsToSend.isNotEmpty()) {
|
||||
android.util.Log.e("REPLY_DEBUG", "🔥 FORMING REPLY ATTACHMENT, count=${replyMsgsToSend.size}")
|
||||
|
||||
// Формируем JSON массив с цитируемыми сообщениями (как в RN)
|
||||
val replyJsonArray = JSONArray()
|
||||
@@ -945,17 +942,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
put("attachments", JSONArray()) // Пустой массив вложений
|
||||
}
|
||||
replyJsonArray.put(replyJson)
|
||||
android.util.Log.e("REPLY_DEBUG", " - Reply msg: id=${msg.messageId}, text='${msg.text.take(30)}'")
|
||||
}
|
||||
|
||||
replyBlobPlaintext = replyJsonArray.toString() // 🔥 Сохраняем plaintext
|
||||
android.util.Log.e("REPLY_DEBUG", " - Reply blob plaintext length: ${replyBlobPlaintext.length}")
|
||||
android.util.Log.e("REPLY_DEBUG", " - Reply blob plaintext: ${replyBlobPlaintext.take(100)}")
|
||||
|
||||
// 🔥 Шифруем reply blob plainKeyAndNonce (как в React Native)
|
||||
val encryptedReplyBlob = MessageCrypto.encryptReplyBlob(replyBlobPlaintext, plainKeyAndNonce)
|
||||
android.util.Log.e("REPLY_DEBUG", " - Encrypted reply blob length: ${encryptedReplyBlob.length}")
|
||||
android.util.Log.e("REPLY_DEBUG", " - Encrypted reply blob: ${encryptedReplyBlob.take(60)}")
|
||||
|
||||
val replyAttachmentId = "reply_${timestamp}"
|
||||
messageAttachments.add(MessageAttachment(
|
||||
@@ -964,7 +956,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
type = AttachmentType.MESSAGES,
|
||||
preview = ""
|
||||
))
|
||||
android.util.Log.e("REPLY_DEBUG", " ✅ Reply attachment added, id=$replyAttachmentId")
|
||||
}
|
||||
|
||||
val packet = PacketMessage().apply {
|
||||
@@ -983,19 +974,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
Log.d(TAG, " - messageId: $messageId")
|
||||
Log.d(TAG, " - text: '$text'")
|
||||
Log.d(TAG, " - attachments count: ${packet.attachments.size}")
|
||||
android.util.Log.e("REPLY_DEBUG", "📦 PACKET READY TO SEND:")
|
||||
android.util.Log.e("REPLY_DEBUG", " - messageId: $messageId")
|
||||
android.util.Log.e("REPLY_DEBUG", " - text: '$text'")
|
||||
android.util.Log.e("REPLY_DEBUG", " - attachments count: ${packet.attachments.size}")
|
||||
packet.attachments.forEach { att ->
|
||||
Log.d(TAG, " - attachment: type=${att.type}, id=${att.id}, blob.length=${att.blob.length}")
|
||||
android.util.Log.e("REPLY_DEBUG", " - attachment: type=${att.type}, id=${att.id}, blob.length=${att.blob.length}")
|
||||
}
|
||||
|
||||
// Отправляем пакет
|
||||
ProtocolManager.send(packet)
|
||||
Log.d(TAG, "✅ PACKET SENT via ProtocolManager.send()")
|
||||
android.util.Log.e("REPLY_DEBUG", "✅ PACKET SENT via ProtocolManager.send()")
|
||||
|
||||
// 3. 🎯 UI обновление в Main потоке
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
@@ -55,19 +55,16 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
*/
|
||||
fun setAccount(publicKey: String, privateKey: String) {
|
||||
if (currentAccount == publicKey) {
|
||||
android.util.Log.d("ChatsListViewModel", "⚠️ setAccount called again for same account, skipping")
|
||||
return
|
||||
}
|
||||
currentAccount = publicKey
|
||||
currentPrivateKey = privateKey
|
||||
|
||||
android.util.Log.d("ChatsListViewModel", "✅ Setting up dialogs Flow for account: ${publicKey.take(16)}...")
|
||||
|
||||
viewModelScope.launch {
|
||||
dialogDao.getDialogsFlow(publicKey)
|
||||
.flowOn(Dispatchers.IO) // 🚀 Flow работает на IO
|
||||
.map { dialogsList ->
|
||||
android.util.Log.d("ChatsListViewModel", "📋 Dialogs Flow emitted: ${dialogsList.size} dialogs")
|
||||
// 🔓 Расшифровываем lastMessage на IO потоке (PBKDF2 - тяжелая операция!)
|
||||
dialogsList.map { dialog ->
|
||||
val decryptedLastMessage = try {
|
||||
@@ -99,7 +96,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
.flowOn(Dispatchers.Default) // 🚀 map выполняется на Default (CPU)
|
||||
.flowOn(Dispatchers.Main) // 🎯 КРИТИЧНО: Обновляем UI на главном потоке!
|
||||
.collect { decryptedDialogs ->
|
||||
android.util.Log.d("ChatsListViewModel", "✅ Updated UI with ${decryptedDialogs.size} decrypted dialogs")
|
||||
_dialogs.value = decryptedDialogs
|
||||
|
||||
// 🟢 Подписываемся на онлайн-статусы всех собеседников
|
||||
@@ -156,9 +152,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
dialogDao.updateOpponentInfo(currentAccount, opponentKey, opponentTitle, opponentUsername, verified)
|
||||
}
|
||||
|
||||
android.util.Log.d("ChatsListViewModel", "✅ Dialog upserted from messages table")
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatsListViewModel", "Error upserting dialog", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user