mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-26 11:56:44 +00:00
Release/1.18.5 (#1535)
* fix: Authenticate all Open Group API calls * Use unblinded authentication when we have `capabilities` data for the open group server we are sending the request to but don't have the `blind` capability * Use blinded authentication when we haven't gotten any `capabilities` for an open group server, or if we have `capabilities` and the server has the `blind` capability * Hide send button when message contains only whitespace * Fix bug displaying user ID when quoting own message * Fix notification update for incoming unsend request * Improve check if author is own user when quoting messages * Fixed video call auto rotate, when auto rotate is disabled * refactor: simplify comparison * Stop playing message if deleted * Accidental change * Accidental change * Comments * Feedback * Comments * Import * Fix delete message for everyone doesn't stop the audio playing * Correct the usage of flowOn * Import * Optimise XML * Remove unused file * Remove view pools * Remove the use of executor in ThreadUtils * Using trim and empty to capture semantic concept of nothing being in there * Remove config checks (PR 1294) Refactor: remove checks for whether new config is enabled throughout config factory generation. First commit from PR 1294. * [SES-2162] - Remove wrapping of config message (#1517) * Remove wrapping of config message * Addresses feedback * Merged in ThreadUtils fix * JDK installation * Revert JDK change --------- Co-authored-by: fanchao <git@fanchao.dev> * Update libsession * [SES-337] Add rounded corners to thumbnail in QuoteView (#1285) * Add rounded corners to thumbnail in QuoteView * Simplify ThumbnailView * Cleanup ThumbnailView * Removed include custom attributes The custom attributes are not passed to the view. I added the radius programatically instead. * Clipping whole thumbnail view instead of just the image requests --------- Co-authored-by: AL-Session <160798022+AL-Session@users.noreply.github.com> Co-authored-by: ThomasSession <thomas.r@getsession.org> * Highlight @You mentions (#985) * Highlight @You mentions * fix: resolve merge conflicts * Setting the proper design rules for mentions * New RoundedBackgroundSpan, applied to "you" mentions The rounded background highlighter can take padding, so there is no need to add those extra spaces at the start and end. * Better mention highlight logic Some mention highlight should only format the text and not apply any styling. Also making sure we cater for all cases properly * Updated the text color logic based on design rules * Fine tuning the color rules * Removing usage of Resources.getSystem() Only making the db call if there actually is a mention * Moving color definition outside the loop to avoid repetitions --------- Co-authored-by: charles <charles@oxen.io> Co-authored-by: 0x330a <92654767+0x330a@users.noreply.github.com> Co-authored-by: ThomasSession <thomas.r@getsession.org> * [SES-2018] Refactor mention (#1510) * Refactor mention * Fixes robolectric test problem * Fixes tests * Naming and comments * Naming * Dispatcher --------- Co-authored-by: fanchao <git@fanchao.dev> * [SES-1966] Attachment batch download and tidy-up (#1507) * Attachment batch download * Addressed feedback and test issues * Feedback fixes * timedWindow for flow * Feedback * Dispatchers * Remove `flowOn` * New implementation of timedBuffer * Organise import * Feedback * Fix test * Tidied up logic around `eligibleForDownload` * Updated comment --------- Co-authored-by: fanchao <git@fanchao.dev> * Fix issue with span being the full length (#1528) * Proper display of unresolved names in mentions (#1530) * Fix issue with span being the full length * Making sure a mention with a username without a resolved name still displayed with the appropriate style with the truncated is * Testnet build (#1532) Co-authored-by: fanchao <git@fanchao.dev> * Allow "public.loki.foundation" to be accessed by http (#1534) Co-authored-by: fanchao <git@fanchao.dev> * Bumping the version code and name * Reverting temporary change --------- Co-authored-by: charles <charles@oxen.io> Co-authored-by: andrew <andrewgallasch@gmail.com> Co-authored-by: aaronkerckhoff <aaronkerckhoff@gmail.com> Co-authored-by: Rugved Darwhekar <darwhekarrugved@gmail.com> Co-authored-by: 0x330a <92654767+0x330a@users.noreply.github.com> Co-authored-by: fanchao <git@fanchao.dev> Co-authored-by: Fanchao Liu <273191+simophin@users.noreply.github.com> Co-authored-by: AL-Session <160798022+AL-Session@users.noreply.github.com> Co-authored-by: ceokot <ceokot@users.noreply.github.com>
This commit is contained in:
@@ -53,7 +53,7 @@ interface StorageProtocol {
|
||||
fun persistJob(job: Job)
|
||||
fun markJobAsSucceeded(jobId: String)
|
||||
fun markJobAsFailedPermanently(jobId: String)
|
||||
fun getAllPendingJobs(type: String): Map<String,Job?>
|
||||
fun getAllPendingJobs(vararg types: String): Map<String,Job?>
|
||||
fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob?
|
||||
fun getMessageSendJob(messageSendJobID: String): MessageSendJob?
|
||||
fun getMessageReceiveJob(messageReceiveJobID: String): Job?
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import org.session.libsession.database.MessageDataProvider
|
||||
import org.session.libsession.database.StorageProtocol
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
||||
@@ -40,6 +42,36 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
|
||||
// Keys used for database storage
|
||||
private val ATTACHMENT_ID_KEY = "attachment_id"
|
||||
private val TS_INCOMING_MESSAGE_ID_KEY = "tsIncoming_message_id"
|
||||
|
||||
/**
|
||||
* Check if the attachment in the given message is eligible for download.
|
||||
*
|
||||
* Note that this function only checks for the eligibility of the attachment in the sense
|
||||
* of whether the download is allowed, it does not check if the download has already taken
|
||||
* place.
|
||||
*/
|
||||
fun eligibleForDownload(threadID: Long,
|
||||
storage: StorageProtocol,
|
||||
messageDataProvider: MessageDataProvider,
|
||||
databaseMessageID: Long): Boolean {
|
||||
val threadRecipient = storage.getRecipientForThread(threadID) ?: return false
|
||||
|
||||
// if we are the sender we are always eligible
|
||||
val selfSend = messageDataProvider.isMmsOutgoing(databaseMessageID)
|
||||
if (selfSend) {
|
||||
return true
|
||||
}
|
||||
|
||||
// you can't be eligible without a sender
|
||||
val sender = messageDataProvider.getIndividualRecipientForMms(databaseMessageID)?.address?.serialize()
|
||||
?: return false
|
||||
|
||||
// you can't be eligible without a contact entry
|
||||
val contact = storage.getContactWithSessionID(sender) ?: return false
|
||||
|
||||
// we are eligible if we are receiving a group message or the contact is trusted
|
||||
return threadRecipient.isGroupRecipient || contact.isTrusted
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execute(dispatcherName: String) {
|
||||
@@ -88,21 +120,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
|
||||
return
|
||||
}
|
||||
|
||||
val threadRecipient = storage.getRecipientForThread(threadID)
|
||||
val selfSend = messageDataProvider.isMmsOutgoing(databaseMessageID)
|
||||
val sender = if (selfSend) {
|
||||
storage.getUserPublicKey()
|
||||
} else {
|
||||
messageDataProvider.getIndividualRecipientForMms(databaseMessageID)?.address?.serialize()
|
||||
}
|
||||
val contact = sender?.let { storage.getContactWithSessionID(it) }
|
||||
if (threadRecipient == null || sender == null || (contact == null && !selfSend)) {
|
||||
handleFailure(Error.NoSender, null)
|
||||
return
|
||||
}
|
||||
if (!threadRecipient.isGroupRecipient && contact?.isTrusted != true && storage.getUserPublicKey() != sender) {
|
||||
// if we aren't receiving a group message, a message from ourselves (self-send) and the contact sending is not trusted:
|
||||
// do not continue, but do not fail
|
||||
if (!eligibleForDownload(threadID, storage, messageDataProvider, databaseMessageID)) {
|
||||
handleFailure(Error.NoSender, null)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ class BackgroundGroupAddJob(val joinUrl: String): Job {
|
||||
delegate?.handleJobFailed(this, dispatcherName, DuplicateGroupException())
|
||||
return
|
||||
}
|
||||
|
||||
storage.addOpenGroup(openGroup.joinUrl())
|
||||
storage.onOpenGroupAdded(openGroup.server, openGroup.room)
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import network.loki.messenger.libsession_util.ConfigBase
|
||||
import network.loki.messenger.libsession_util.ConfigBase.Companion.protoKindFor
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
@@ -10,7 +9,6 @@ import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.utilities.Data
|
||||
import org.session.libsession.snode.RawResponse
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.utilities.Log
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@@ -26,14 +24,10 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
|
||||
|
||||
override suspend fun execute(dispatcherName: String) {
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val forcedConfig = TextSecurePreferences.hasForcedNewConfig(MessagingModuleConfiguration.shared.context)
|
||||
val currentTime = SnodeAPI.nowWithOffset
|
||||
val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()
|
||||
val userPublicKey = storage.getUserPublicKey()
|
||||
val delegate = delegate
|
||||
if (destination is Destination.ClosedGroup // TODO: closed group configs will be handled in closed group feature
|
||||
// if we haven't enabled the new configs don't run
|
||||
|| !ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)
|
||||
if (destination is Destination.ClosedGroup
|
||||
// if we don't have a user ed key pair for signing updates
|
||||
|| userEdKeyPair == null
|
||||
// this will be useful to not handle null delegate cases
|
||||
@@ -67,7 +61,7 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
|
||||
SharedConfigurationMessage(config.protoKindFor(), data, seqNo) to config
|
||||
}.map { (message, config) ->
|
||||
// return a list of batch request objects
|
||||
val snodeMessage = MessageSender.buildWrappedMessageToSnode(destination, message, true)
|
||||
val snodeMessage = MessageSender.buildConfigMessageToSnode(destination.destinationPublicKey(), message)
|
||||
val authenticated = SnodeAPI.buildAuthenticatedStoreBatchInfo(
|
||||
destination.destinationPublicKey(),
|
||||
config.configNamespace(),
|
||||
|
||||
@@ -102,7 +102,7 @@ class JobQueue : JobDelegate {
|
||||
execute(dispatcherName)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
Log.d(dispatcherName, "unhandledJobException: ${javaClass.simpleName} (id: $id)")
|
||||
Log.d(dispatcherName, "unhandledJobException: ${javaClass.simpleName} (id: $id)", e)
|
||||
this@JobQueue.handleJobFailed(this, dispatcherName, e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ data class GroupMember(
|
||||
val role: GroupMemberRole
|
||||
)
|
||||
|
||||
enum class GroupMemberRole {
|
||||
STANDARD, ZOOMBIE, MODERATOR, ADMIN, HIDDEN_MODERATOR, HIDDEN_ADMIN
|
||||
enum class GroupMemberRole(val isModerator: Boolean = false) {
|
||||
STANDARD,
|
||||
ZOOMBIE,
|
||||
MODERATOR(true),
|
||||
ADMIN(true),
|
||||
HIDDEN_MODERATOR(true),
|
||||
HIDDEN_ADMIN(true),
|
||||
}
|
||||
|
||||
@@ -273,7 +273,6 @@ object OpenGroupApi {
|
||||
val queryParameters: Map<String, String> = mapOf(),
|
||||
val parameters: Any? = null,
|
||||
val headers: Map<String, String> = mapOf(),
|
||||
val isAuthRequired: Boolean = true,
|
||||
val body: ByteArray? = null,
|
||||
/**
|
||||
* Always `true` under normal circumstances. You might want to disable
|
||||
@@ -319,73 +318,72 @@ object OpenGroupApi {
|
||||
?: return Promise.ofFail(Error.NoEd25519KeyPair)
|
||||
val urlRequest = urlBuilder.toString()
|
||||
val headers = request.headers.toMutableMap()
|
||||
if (request.isAuthRequired) {
|
||||
val nonce = sodium.nonce(16)
|
||||
val timestamp = TimeUnit.MILLISECONDS.toSeconds(SnodeAPI.nowWithOffset)
|
||||
var pubKey = ""
|
||||
var signature = ByteArray(Sign.BYTES)
|
||||
var bodyHash = ByteArray(0)
|
||||
if (request.parameters != null) {
|
||||
val parameterBytes = JsonUtil.toJson(request.parameters).toByteArray()
|
||||
val parameterHash = ByteArray(GenericHash.BYTES_MAX)
|
||||
if (sodium.cryptoGenericHash(
|
||||
parameterHash,
|
||||
parameterHash.size,
|
||||
parameterBytes,
|
||||
parameterBytes.size.toLong()
|
||||
)
|
||||
) {
|
||||
bodyHash = parameterHash
|
||||
}
|
||||
} else if (request.body != null) {
|
||||
val byteHash = ByteArray(GenericHash.BYTES_MAX)
|
||||
if (sodium.cryptoGenericHash(
|
||||
byteHash,
|
||||
byteHash.size,
|
||||
request.body,
|
||||
request.body.size.toLong()
|
||||
)
|
||||
) {
|
||||
bodyHash = byteHash
|
||||
}
|
||||
}
|
||||
val messageBytes = Hex.fromStringCondensed(publicKey)
|
||||
.plus(nonce)
|
||||
.plus("$timestamp".toByteArray(Charsets.US_ASCII))
|
||||
.plus(request.verb.rawValue.toByteArray())
|
||||
.plus("/${request.endpoint.value}".toByteArray())
|
||||
.plus(bodyHash)
|
||||
if (serverCapabilities.isEmpty() || serverCapabilities.contains(Capability.BLIND.name.lowercase())) {
|
||||
SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair ->
|
||||
pubKey = SessionId(
|
||||
IdPrefix.BLINDED,
|
||||
keyPair.publicKey.asBytes
|
||||
).hexString
|
||||
|
||||
signature = SodiumUtilities.sogsSignature(
|
||||
messageBytes,
|
||||
ed25519KeyPair.secretKey.asBytes,
|
||||
keyPair.secretKey.asBytes,
|
||||
keyPair.publicKey.asBytes
|
||||
) ?: return Promise.ofFail(Error.SigningFailed)
|
||||
} ?: return Promise.ofFail(Error.SigningFailed)
|
||||
} else {
|
||||
pubKey = SessionId(
|
||||
IdPrefix.UN_BLINDED,
|
||||
ed25519KeyPair.publicKey.asBytes
|
||||
).hexString
|
||||
sodium.cryptoSignDetached(
|
||||
signature,
|
||||
messageBytes,
|
||||
messageBytes.size.toLong(),
|
||||
ed25519KeyPair.secretKey.asBytes
|
||||
val nonce = sodium.nonce(16)
|
||||
val timestamp = TimeUnit.MILLISECONDS.toSeconds(SnodeAPI.nowWithOffset)
|
||||
var pubKey = ""
|
||||
var signature = ByteArray(Sign.BYTES)
|
||||
var bodyHash = ByteArray(0)
|
||||
if (request.parameters != null) {
|
||||
val parameterBytes = JsonUtil.toJson(request.parameters).toByteArray()
|
||||
val parameterHash = ByteArray(GenericHash.BYTES_MAX)
|
||||
if (sodium.cryptoGenericHash(
|
||||
parameterHash,
|
||||
parameterHash.size,
|
||||
parameterBytes,
|
||||
parameterBytes.size.toLong()
|
||||
)
|
||||
) {
|
||||
bodyHash = parameterHash
|
||||
}
|
||||
} else if (request.body != null) {
|
||||
val byteHash = ByteArray(GenericHash.BYTES_MAX)
|
||||
if (sodium.cryptoGenericHash(
|
||||
byteHash,
|
||||
byteHash.size,
|
||||
request.body,
|
||||
request.body.size.toLong()
|
||||
)
|
||||
) {
|
||||
bodyHash = byteHash
|
||||
}
|
||||
headers["X-SOGS-Nonce"] = encodeBytes(nonce)
|
||||
headers["X-SOGS-Timestamp"] = "$timestamp"
|
||||
headers["X-SOGS-Pubkey"] = pubKey
|
||||
headers["X-SOGS-Signature"] = encodeBytes(signature)
|
||||
}
|
||||
val messageBytes = Hex.fromStringCondensed(publicKey)
|
||||
.plus(nonce)
|
||||
.plus("$timestamp".toByteArray(Charsets.US_ASCII))
|
||||
.plus(request.verb.rawValue.toByteArray())
|
||||
.plus("/${request.endpoint.value}".toByteArray())
|
||||
.plus(bodyHash)
|
||||
if (serverCapabilities.isEmpty() || serverCapabilities.contains(Capability.BLIND.name.lowercase())) {
|
||||
SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair ->
|
||||
pubKey = SessionId(
|
||||
IdPrefix.BLINDED,
|
||||
keyPair.publicKey.asBytes
|
||||
).hexString
|
||||
|
||||
signature = SodiumUtilities.sogsSignature(
|
||||
messageBytes,
|
||||
ed25519KeyPair.secretKey.asBytes,
|
||||
keyPair.secretKey.asBytes,
|
||||
keyPair.publicKey.asBytes
|
||||
) ?: return Promise.ofFail(Error.SigningFailed)
|
||||
} ?: return Promise.ofFail(Error.SigningFailed)
|
||||
} else {
|
||||
pubKey = SessionId(
|
||||
IdPrefix.UN_BLINDED,
|
||||
ed25519KeyPair.publicKey.asBytes
|
||||
).hexString
|
||||
sodium.cryptoSignDetached(
|
||||
signature,
|
||||
messageBytes,
|
||||
messageBytes.size.toLong(),
|
||||
ed25519KeyPair.secretKey.asBytes
|
||||
)
|
||||
}
|
||||
headers["X-SOGS-Nonce"] = encodeBytes(nonce)
|
||||
headers["X-SOGS-Timestamp"] = "$timestamp"
|
||||
headers["X-SOGS-Pubkey"] = pubKey
|
||||
headers["X-SOGS-Signature"] = encodeBytes(signature)
|
||||
|
||||
val requestBuilder = okhttp3.Request.Builder()
|
||||
.url(urlRequest)
|
||||
@@ -927,7 +925,7 @@ object OpenGroupApi {
|
||||
}
|
||||
|
||||
fun getCapabilities(server: String): Promise<Capabilities, Exception> {
|
||||
val request = Request(verb = GET, room = null, server = server, endpoint = Endpoint.Capabilities, isAuthRequired = false)
|
||||
val request = Request(verb = GET, room = null, server = server, endpoint = Endpoint.Capabilities)
|
||||
return getResponseBody(request).map { response ->
|
||||
JsonUtil.fromJson(response, Capabilities::class.java)
|
||||
}
|
||||
|
||||
@@ -81,6 +81,15 @@ object MessageSender {
|
||||
}
|
||||
}
|
||||
|
||||
fun buildConfigMessageToSnode(destinationPubKey: String, message: SharedConfigurationMessage): SnodeMessage {
|
||||
return SnodeMessage(
|
||||
destinationPubKey,
|
||||
Base64.encodeBytes(message.data),
|
||||
ttl = message.ttl,
|
||||
SnodeAPI.nowWithOffset
|
||||
)
|
||||
}
|
||||
|
||||
// One-on-One Chats & Closed Groups
|
||||
@Throws(Exception::class)
|
||||
fun buildWrappedMessageToSnode(destination: Destination, message: Message, isSyncMessage: Boolean): SnodeMessage {
|
||||
|
||||
@@ -203,12 +203,10 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) {
|
||||
|
||||
TextSecurePreferences.setConfigurationMessageSynced(context, true)
|
||||
TextSecurePreferences.setLastProfileUpdateTime(context, message.sentTimestamp!!)
|
||||
val isForceSync = TextSecurePreferences.hasForcedNewConfig(context)
|
||||
val currentTime = SnodeAPI.nowWithOffset
|
||||
if (ConfigBase.isNewConfigEnabled(isForceSync, currentTime)) {
|
||||
TextSecurePreferences.setHasLegacyConfig(context, true)
|
||||
if (!firstTimeSync) return
|
||||
}
|
||||
|
||||
TextSecurePreferences.setHasLegacyConfig(context, true)
|
||||
if (!firstTimeSync) return
|
||||
|
||||
val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys()
|
||||
for (closedGroup in message.closedGroups) {
|
||||
if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) {
|
||||
@@ -260,7 +258,7 @@ fun MessageReceiver.handleUnsendRequest(message: UnsendRequest): Long? {
|
||||
SnodeAPI.deleteMessage(author, listOf(serverHash))
|
||||
}
|
||||
val deletedMessageId = messageDataProvider.updateMessageAsDeleted(timestamp, author)
|
||||
if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) {
|
||||
if (!messageDataProvider.isOutgoingMessage(timestamp)) {
|
||||
SSKEnvironment.shared.notificationManager.updateNotification(context)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.session.libsession.snode.RawResponse
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.snode.SnodeModule
|
||||
import org.session.libsession.utilities.ConfigFactoryProtocol
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.Namespace
|
||||
import org.session.libsignal.utilities.Snode
|
||||
@@ -126,37 +127,26 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
|
||||
private fun processConfig(snode: Snode, rawMessages: RawResponse, namespace: Int, forConfigObject: ConfigBase?) {
|
||||
if (forConfigObject == null) return
|
||||
|
||||
val messages = SnodeAPI.parseRawMessagesResponse(
|
||||
rawMessages,
|
||||
snode,
|
||||
userPublicKey,
|
||||
namespace,
|
||||
updateLatestHash = true,
|
||||
updateStoredHashes = true,
|
||||
)
|
||||
val messages = rawMessages["messages"] as? List<*>
|
||||
val processed = if (!messages.isNullOrEmpty()) {
|
||||
SnodeAPI.updateLastMessageHashValueIfPossible(snode, userPublicKey, messages, namespace)
|
||||
SnodeAPI.removeDuplicates(userPublicKey, messages, namespace, true).mapNotNull { messageBody ->
|
||||
val rawMessageAsJSON = messageBody as? Map<*, *> ?: return@mapNotNull null
|
||||
val hashValue = rawMessageAsJSON["hash"] as? String ?: return@mapNotNull null
|
||||
val b64EncodedBody = rawMessageAsJSON["data"] as? String ?: return@mapNotNull null
|
||||
val timestamp = rawMessageAsJSON["t"] as? Long ?: SnodeAPI.nowWithOffset
|
||||
val body = Base64.decode(b64EncodedBody)
|
||||
Triple(body, hashValue, timestamp)
|
||||
}
|
||||
} else emptyList()
|
||||
|
||||
if (messages.isEmpty()) {
|
||||
// no new messages to process
|
||||
return
|
||||
}
|
||||
if (processed.isEmpty()) return
|
||||
|
||||
var latestMessageTimestamp: Long? = null
|
||||
messages.forEach { (envelope, hash) ->
|
||||
processed.forEach { (body, hash, timestamp) ->
|
||||
try {
|
||||
val (message, _) = MessageReceiver.parse(data = envelope.toByteArray(),
|
||||
// assume no groups in personal poller messages
|
||||
openGroupServerID = null, currentClosedGroups = emptySet()
|
||||
)
|
||||
// sanity checks
|
||||
if (message !is SharedConfigurationMessage) {
|
||||
Log.w("Loki", "shared config message handled in configs wasn't SharedConfigurationMessage but was ${message.javaClass.simpleName}")
|
||||
return@forEach
|
||||
}
|
||||
val merged = forConfigObject.merge(hash!! to message.data).firstOrNull { it == hash }
|
||||
if (merged != null) {
|
||||
// We successfully merged the hash, we can now update the timestamp
|
||||
latestMessageTimestamp = if ((message.sentTimestamp ?: 0L) > (latestMessageTimestamp ?: 0L)) { message.sentTimestamp } else { latestMessageTimestamp }
|
||||
}
|
||||
forConfigObject.merge(hash to body)
|
||||
latestMessageTimestamp = if (timestamp > (latestMessageTimestamp ?: 0L)) { timestamp } else { latestMessageTimestamp }
|
||||
} catch (e: Exception) {
|
||||
Log.e("Loki", e)
|
||||
}
|
||||
|
||||
@@ -829,7 +829,7 @@ object SnodeAPI {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLastMessageHashValueIfPossible(snode: Snode, publicKey: String, rawMessages: List<*>, namespace: Int) {
|
||||
fun updateLastMessageHashValueIfPossible(snode: Snode, publicKey: String, rawMessages: List<*>, namespace: Int) {
|
||||
val lastMessageAsJSON = rawMessages.lastOrNull() as? Map<*, *>
|
||||
val hashValue = lastMessageAsJSON?.get("hash") as? String
|
||||
if (hashValue != null) {
|
||||
@@ -839,7 +839,7 @@ object SnodeAPI {
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeDuplicates(publicKey: String, rawMessages: List<*>, namespace: Int, updateStoredHashes: Boolean): List<*> {
|
||||
fun removeDuplicates(publicKey: String, rawMessages: List<*>, namespace: Int, updateStoredHashes: Boolean): List<*> {
|
||||
val originalMessageHashValues = database.getReceivedMessageHashValues(publicKey, namespace)?.toMutableSet() ?: mutableSetOf()
|
||||
val receivedMessageHashValues = originalMessageHashValues.toMutableSet()
|
||||
val result = rawMessages.filter { rawMessage ->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
fun truncateIdForDisplay(id: String): String =
|
||||
id.takeIf { it.length > 8 }?.apply{ "${take(4)}…${takeLast(4)}" } ?: id
|
||||
id.takeIf { it.length > 8 }?.run{ "${take(4)}…${takeLast(4)}" } ?: id
|
||||
|
||||
Reference in New Issue
Block a user