feat: invite contact uses multiple contacts now, fix compile issues and write to members based on success / failure state and pending

This commit is contained in:
0x330a 2023-11-06 17:59:05 +11:00
parent 0eadfeaaca
commit abaa765207
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
8 changed files with 162 additions and 101 deletions

View File

@ -222,7 +222,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
messageDataProvider,
()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this),
configFactory
);
);
callMessageProcessor = new CallMessageProcessor(this, textSecurePreferences, ProcessLifecycleOwner.get().getLifecycle(), storage);
Log.i(TAG, "onCreate()");
startKovenant();

View File

@ -241,7 +241,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private val viewModel: ConversationViewModel by viewModels {
var threadId = intent.getLongExtra(THREAD_ID, -1L)
if (threadId == -1L) {
intent.getParcelableExtra(ADDRESS, Address::class.java)?.let { it ->
intent.getParcelableExtra<Address>(ADDRESS)?.let { it ->
threadId = threadDb.getThreadIdIfExistsFor(it.serialize())
if (threadId == -1L) {
val sessionId = SessionId(it.serialize())
@ -423,6 +423,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
updatePlaceholder()
setUpBlockedBanner()
binding!!.searchBottomBar.setEventListener(this)
binding!!.toolbarContent.profilePictureView.setOnClickListener(this)
updateSendAfterApprovalText()
showOrHideInputIfNeeded()
setUpMessageRequestsBar()

View File

@ -7,7 +7,7 @@ import org.session.libsession.messaging.jobs.AttachmentDownloadJob
import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
import org.session.libsession.messaging.jobs.InviteContactJob
import org.session.libsession.messaging.jobs.InviteContactsJob
import org.session.libsession.messaging.jobs.Job
import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.jobs.MessageSendJob
@ -74,11 +74,11 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
return result.firstOrNull { job -> job.attachmentID == attachmentID }
}
fun getGroupInviteJob(groupSessionId: String, memberSessionId: String): InviteContactJob? {
fun getGroupInviteJob(groupSessionId: String, memberSessionId: String): InviteContactsJob? {
val database = databaseHelper.readableDatabase
return database.getAll(sessionJobTable, "$jobType = ?", arrayOf(InviteContactJob.KEY)) { cursor ->
jobFromCursor(cursor) as? InviteContactJob
}.firstOrNull { it != null && it.groupSessionId == groupSessionId && it.memberSessionId == memberSessionId }
return database.getAll(sessionJobTable, "$jobType = ?", arrayOf(InviteContactsJob.KEY)) { cursor ->
jobFromCursor(cursor) as? InviteContactsJob
}.firstOrNull { it != null && it.groupSessionId == groupSessionId && it.memberSessionIds.contains(memberSessionId) }
}
fun getMessageSendJob(messageSendJobID: String): MessageSendJob? {

View File

@ -27,7 +27,7 @@ import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
import org.session.libsession.messaging.jobs.ConfigurationSyncJob
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
import org.session.libsession.messaging.jobs.InviteContactJob
import org.session.libsession.messaging.jobs.InviteContactsJob
import org.session.libsession.messaging.jobs.Job
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveJob
@ -1026,10 +1026,10 @@ open class Storage(
setRecipientApprovedMe(groupRecipient, true)
setRecipientApproved(groupRecipient, true)
pollerFactory.updatePollers()
members.forEach { contact ->
val job = InviteContactJob(group.groupSessionId.hexString(), contact.sessionID)
JobQueue.shared.add(job)
}
val memberArray = members.map(Contact::sessionID).toTypedArray()
val job = InviteContactsJob(group.groupSessionId.hexString(), memberArray)
JobQueue.shared.add(job)
return Optional.of(groupRecipient)
} catch (e: Exception) {
Log.e("Group Config", e)

View File

@ -13,7 +13,7 @@ class MessagingModuleConfiguration(
val device: Device,
val messageDataProvider: MessageDataProvider,
val getUserED25519KeyPair: () -> KeyPair?,
val configFactory: ConfigFactoryProtocol
val configFactory: ConfigFactoryProtocol,
) {
companion object {

View File

@ -1,86 +0,0 @@
package org.session.libsession.messaging.jobs
import com.google.protobuf.ByteString
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.GroupUpdated
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.snode.SnodeAPI
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateInviteMessage
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.SessionId
class InviteContactJob(val groupSessionId: String, val memberSessionId: String): Job {
sealed class InviteError(message: String): Exception(message) {
object NO_GROUP_KEYS: InviteError("No group keys config for this group")
}
companion object {
const val KEY = "InviteContactJob"
private const val GROUP = "group"
private const val MEMBER = "member"
}
override var delegate: JobDelegate? = null
override var id: String? = null
override var failureCount: Int = 0
override val maxFailureCount: Int = 1
override suspend fun execute(dispatcherName: String) {
val delegate = delegate ?: return
val configs = MessagingModuleConfiguration.shared.configFactory
val storage = MessagingModuleConfiguration.shared.storage
val adminKey = configs.userGroups?.getClosedGroup(groupSessionId)?.adminKey
?: return delegate.handleJobFailedPermanently(this, dispatcherName, NullPointerException("No admin key"))
val subAccount = configs.getGroupKeysConfig(SessionId.from(groupSessionId))?.use { keys ->
keys.makeSubAccount(SessionId.from(memberSessionId))
} ?: return delegate.handleJobFailedPermanently(this, dispatcherName, InviteError.NO_GROUP_KEYS)
val timestamp = SnodeAPI.nowWithOffset
val messageToSign = "INVITE$memberSessionId$timestamp"
val signature = SodiumUtilities.sign(messageToSign.toByteArray(), adminKey)
val userProfile = storage.getUserProfile()
val lokiProfile = LokiProfile.newBuilder()
.setDisplayName(userProfile.displayName)
if (userProfile.profilePictureURL?.isNotEmpty() == true) {
lokiProfile.profilePicture = userProfile.profilePictureURL
}
val groupInvite = GroupUpdateInviteMessage.newBuilder()
.setGroupSessionId(groupSessionId)
.setMemberAuthData(ByteString.copyFrom(subAccount))
.setAdminSignature(ByteString.copyFrom(signature))
.setName(userProfile.displayName)
.setProfile(lokiProfile.build())
if (userProfile.profileKey?.isNotEmpty() == true) {
groupInvite.profileKey = ByteString.copyFrom(userProfile.profileKey)
}
val message = GroupUpdateMessage.newBuilder()
.setInviteMessage(groupInvite)
.build()
val update = GroupUpdated(message).apply {
sentTimestamp = timestamp
}
try {
MessageSender.send(update, Destination.Contact(memberSessionId), false).get()
Log.d("InviteContactJob", "Sent invite message successfully")
delegate.handleJobSucceeded(this, dispatcherName)
} catch (e: Exception) {
Log.e("InviteContactJob", e)
delegate.handleJobFailed(this, dispatcherName, e)
}
}
override fun serialize(): Data =
Data.Builder()
.putString(GROUP, groupSessionId)
.putString(MEMBER, memberSessionId)
.build()
override fun getFactoryKey(): String = KEY
}

View File

@ -0,0 +1,146 @@
package org.session.libsession.messaging.jobs
import com.google.protobuf.ByteString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.GroupUpdated
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.snode.SnodeAPI
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateInviteMessage
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile
import org.session.libsignal.utilities.SessionId
import org.session.libsignal.utilities.prettifiedDescription
class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<String>) : Job {
companion object {
const val KEY = "InviteContactJob"
private const val GROUP = "group"
private const val MEMBER = "member"
}
override var delegate: JobDelegate? = null
override var id: String? = null
override var failureCount: Int = 0
override val maxFailureCount: Int = 1
override suspend fun execute(dispatcherName: String) {
val delegate = delegate ?: return
val configs = MessagingModuleConfiguration.shared.configFactory
val storage = MessagingModuleConfiguration.shared.storage
val adminKey = configs.userGroups?.getClosedGroup(groupSessionId)?.adminKey
?: return delegate.handleJobFailedPermanently(
this,
dispatcherName,
NullPointerException("No admin key")
)
withContext(Dispatchers.IO) {
val sessionId = SessionId.from(groupSessionId)
val members = configs.getGroupMemberConfig(sessionId)
val info = configs.getGroupInfoConfig(sessionId)
val keys = configs.getGroupKeysConfig(sessionId, info, members, free = false)
if (members == null || info == null || keys == null) {
return@withContext delegate.handleJobFailedPermanently(
this@InviteContactsJob,
dispatcherName,
NullPointerException("One of the group configs was null")
)
}
val requests = memberSessionIds.map { memberSessionId ->
async {
// Make the request for this member
val member = members.get(memberSessionId) ?: return@async run {
InviteResult.failure(
memberSessionId,
NullPointerException("No group member ${memberSessionId.prettifiedDescription()} in members config")
)
}
members.set(member.copy(invitePending = true, inviteFailed = false))
val subAccount = keys.makeSubAccount(SessionId.from(memberSessionId))
val timestamp = SnodeAPI.nowWithOffset
val messageToSign = "INVITE$memberSessionId$timestamp"
val signature = SodiumUtilities.sign(messageToSign.toByteArray(), adminKey)
val userProfile = storage.getUserProfile()
val lokiProfile = LokiProfile.newBuilder()
.setDisplayName(userProfile.displayName)
if (userProfile.profilePictureURL?.isNotEmpty() == true) {
lokiProfile.profilePicture = userProfile.profilePictureURL
}
val groupInvite = GroupUpdateInviteMessage.newBuilder()
.setGroupSessionId(groupSessionId)
.setMemberAuthData(ByteString.copyFrom(subAccount))
.setAdminSignature(ByteString.copyFrom(signature))
.setName(userProfile.displayName)
.setProfile(lokiProfile.build())
if (userProfile.profileKey?.isNotEmpty() == true) {
groupInvite.profileKey = ByteString.copyFrom(userProfile.profileKey)
}
val message = GroupUpdateMessage.newBuilder()
.setInviteMessage(groupInvite)
.build()
val update = GroupUpdated(message).apply {
sentTimestamp = timestamp
}
try {
MessageSender.send(update, Destination.Contact(memberSessionId), false)
.get()
InviteResult.success(memberSessionId)
} catch (e: Exception) {
InviteResult.failure(memberSessionId, e)
}
}
}
val results = requests.awaitAll()
results.forEach { result ->
if (result.success) {
// update invite pending / invite failed
val toSet = members.get(result.memberSessionId)!!.copy(
inviteFailed = false,
invitePending = false
)
members.set(toSet)
} else {
// update invite failed
val toSet = members.get(result.memberSessionId)?.copy(
inviteFailed = true,
invitePending = false
) ?: return@forEach
members.set(toSet)
}
}
}
}
@Suppress("DataClassPrivateConstructor")
data class InviteResult private constructor(
val memberSessionId: String,
val success: Boolean,
val error: Exception? = null
) {
companion object {
fun success(memberSessionId: String) = InviteResult(memberSessionId, success = true)
fun failure(memberSessionId: String, error: Exception) =
InviteResult(memberSessionId, success = false, error)
}
}
override fun serialize(): Data =
Data.Builder()
.putString(GROUP, groupSessionId)
.putStringArray(MEMBER, memberSessionIds)
.build()
override fun getFactoryKey(): String = KEY
}

View File

@ -126,7 +126,7 @@ class JobQueue : JobDelegate {
is AttachmentUploadJob,
is MessageSendJob,
is ConfigurationSyncJob,
is InviteContactJob -> {
is InviteContactsJob -> {
txQueue.send(job)
}
is RetrieveProfileAvatarJob,
@ -231,7 +231,7 @@ class JobQueue : JobDelegate {
OpenGroupDeleteJob.KEY,
RetrieveProfileAvatarJob.KEY,
ConfigurationSyncJob.KEY,
InviteContactJob.KEY
InviteContactsJob.KEY
)
allJobTypes.forEach { type ->
resumePendingJobs(type)