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:
parent
0eadfeaaca
commit
abaa765207
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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? {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -13,7 +13,7 @@ class MessagingModuleConfiguration(
|
|||
val device: Device,
|
||||
val messageDataProvider: MessageDataProvider,
|
||||
val getUserED25519KeyPair: () -> KeyPair?,
|
||||
val configFactory: ConfigFactoryProtocol
|
||||
val configFactory: ConfigFactoryProtocol,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue