mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-20 07:48:26 +00:00
feat: syncing / joining / leaving working on open group v2
This commit is contained in:
parent
a4d79ea2d3
commit
6272856ef9
@ -353,6 +353,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||||
|
val openGroupV2 = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID)
|
||||||
//TODO Move open group related logic to OpenGroupUtilities / PublicChatManager / GroupManager
|
//TODO Move open group related logic to OpenGroupUtilities / PublicChatManager / GroupManager
|
||||||
if (publicChat != null) {
|
if (publicChat != null) {
|
||||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
@ -364,6 +365,13 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
|
|
||||||
ApplicationContext.getInstance(context).publicChatManager
|
ApplicationContext.getInstance(context).publicChatManager
|
||||||
.removeChat(publicChat.server, publicChat.channel)
|
.removeChat(publicChat.server, publicChat.channel)
|
||||||
|
} else if (openGroupV2 != null) {
|
||||||
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
|
apiDB.removeLastMessageServerID(openGroupV2.room, openGroupV2.server)
|
||||||
|
apiDB.removeLastDeletionServerID(openGroupV2.room, openGroupV2.server)
|
||||||
|
|
||||||
|
ApplicationContext.getInstance(context).publicChatManager
|
||||||
|
.removeChat(openGroupV2.server, openGroupV2.room)
|
||||||
} else {
|
} else {
|
||||||
threadDB.deleteConversation(threadID)
|
threadDB.deleteConversation(threadID)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.loki.activities
|
|||||||
|
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -23,9 +25,15 @@ import kotlinx.coroutines.withContext
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.DefaultGroup
|
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.DefaultGroup
|
||||||
|
import org.session.libsession.messaging.threads.Address
|
||||||
|
import org.session.libsession.messaging.threads.DistributionTypes
|
||||||
|
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||||
|
import org.session.libsession.utilities.GroupUtil
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
|
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
|
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
|
||||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
|
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||||
@ -86,14 +94,27 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
|
|||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
if (isV2OpenGroup) {
|
val (threadID, groupID) = if (isV2OpenGroup) {
|
||||||
val server = HttpUrl.Builder().scheme(httpUrl.scheme()).host(httpUrl.host()).build()
|
val server = HttpUrl.Builder().scheme(httpUrl.scheme()).host(httpUrl.host()).build()
|
||||||
OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, server.toString().removeSuffix("/"), room!!, publicKey!!)
|
val group = OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, server.toString().removeSuffix("/"), room!!, publicKey!!)
|
||||||
|
val threadID = GroupManager.getOpenGroupThreadID(group.id, this@JoinPublicChatActivity)
|
||||||
|
val groupID = GroupUtil.getEncodedOpenGroupID(group.id.toByteArray())
|
||||||
|
threadID to groupID
|
||||||
} else {
|
} else {
|
||||||
val channel: Long = 1
|
val channel: Long = 1
|
||||||
OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, url, channel)
|
val group = OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, url, channel)
|
||||||
|
val threadID = GroupManager.getOpenGroupThreadID(group.id, this@JoinPublicChatActivity)
|
||||||
|
val groupID = GroupUtil.getEncodedOpenGroupID(group.id.toByteArray())
|
||||||
|
threadID to groupID
|
||||||
}
|
}
|
||||||
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity)
|
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity)
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
// go to the new conversation and finish this one
|
||||||
|
openConversationActivity(this@JoinPublicChatActivity, threadID, Recipient.from(this@JoinPublicChatActivity, Address.fromSerialized(groupID), false))
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("JoinPublicChatActivity", "Fialed to join open group.", e)
|
Log.e("JoinPublicChatActivity", "Fialed to join open group.", e)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@ -102,10 +123,19 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
|
|||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) { finish() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
// region Convenience
|
||||||
|
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
||||||
|
val intent = Intent(context, ConversationActivity::class.java)
|
||||||
|
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId)
|
||||||
|
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
||||||
|
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Adapter
|
// region Adapter
|
||||||
@ -188,11 +218,13 @@ class EnterChatURLFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// region Convenience
|
||||||
private fun joinPublicChatIfPossible() {
|
private fun joinPublicChatIfPossible() {
|
||||||
val inputMethodManager = requireContext().getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
val inputMethodManager = requireContext().getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
inputMethodManager.hideSoftInputFromWindow(chatURLEditText.windowToken, 0)
|
inputMethodManager.hideSoftInputFromWindow(chatURLEditText.windowToken, 0)
|
||||||
val chatURL = chatURLEditText.text.trim().toString().toLowerCase()
|
val chatURL = chatURLEditText.text.trim().toString().toLowerCase()
|
||||||
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
|
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
@ -107,8 +107,8 @@ class PublicChatManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun addChat(server: String, room: String, info: OpenGroupAPIV2.Info): OpenGroupV2 {
|
fun addChat(server: String, room: String, info: OpenGroupAPIV2.Info, publicKey: String): OpenGroupV2 {
|
||||||
val chat = OpenGroupV2(server, room, info.id, info.name)
|
val chat = OpenGroupV2(server, room, info.name, publicKey)
|
||||||
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
|
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
|
||||||
var profilePicture: Bitmap? = null
|
var profilePicture: Bitmap? = null
|
||||||
if (threadID < 0) {
|
if (threadID < 0) {
|
||||||
@ -136,6 +136,16 @@ class PublicChatManager(private val context: Context) {
|
|||||||
Util.runOnMain { startPollersIfNeeded() }
|
Util.runOnMain { startPollersIfNeeded() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeChat(server: String, room: String) {
|
||||||
|
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
||||||
|
val groupId = "$server.$room"
|
||||||
|
val threadId = GroupManager.getOpenGroupThreadID(groupId, context)
|
||||||
|
val groupAddress = threadDB.getRecipientForThreadId(threadId)!!.address.serialize()
|
||||||
|
GroupManager.deleteGroup(groupAddress, context)
|
||||||
|
|
||||||
|
Util.runOnMain { startPollersIfNeeded() }
|
||||||
|
}
|
||||||
|
|
||||||
private fun refreshChatsAndPollers() {
|
private fun refreshChatsAndPollers() {
|
||||||
val storage = MessagingConfiguration.shared.storage
|
val storage = MessagingConfiguration.shared.storage
|
||||||
val chatsInDB = storage.getAllOpenGroups()
|
val chatsInDB = storage.getAllOpenGroups()
|
||||||
|
@ -34,7 +34,7 @@ object OpenGroupUtilities {
|
|||||||
val groupInfo = OpenGroupAPIV2.getInfo(room,server).get()
|
val groupInfo = OpenGroupAPIV2.getInfo(room,server).get()
|
||||||
val application = ApplicationContext.getInstance(context)
|
val application = ApplicationContext.getInstance(context)
|
||||||
|
|
||||||
val group = application.publicChatManager.addChat(server, room, groupInfo)
|
val group = application.publicChatManager.addChat(server, room, groupInfo, publicKey)
|
||||||
|
|
||||||
val storage = MessagingConfiguration.shared.storage
|
val storage = MessagingConfiguration.shared.storage
|
||||||
storage.removeLastDeletionServerId(room, server)
|
storage.removeLastDeletionServerId(room, server)
|
||||||
|
@ -6,6 +6,7 @@ import com.esotericsoftware.kryo.io.Output
|
|||||||
import org.session.libsession.messaging.MessagingConfiguration
|
import org.session.libsession.messaging.MessagingConfiguration
|
||||||
import org.session.libsession.messaging.fileserver.FileServerAPI
|
import org.session.libsession.messaging.fileserver.FileServerAPI
|
||||||
import org.session.libsession.messaging.messages.Message
|
import org.session.libsession.messaging.messages.Message
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||||
import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream
|
import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream
|
||||||
@ -52,10 +53,15 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
|||||||
var shouldEncrypt = true
|
var shouldEncrypt = true
|
||||||
val usePadding = false
|
val usePadding = false
|
||||||
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)
|
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)
|
||||||
|
val openGroupV2 = MessagingConfiguration.shared.storage.getV2OpenGroup(threadID)
|
||||||
openGroup?.let {
|
openGroup?.let {
|
||||||
server = it.server
|
server = it.server
|
||||||
shouldEncrypt = false
|
shouldEncrypt = false
|
||||||
}
|
}
|
||||||
|
openGroupV2?.let {
|
||||||
|
server = it.server
|
||||||
|
shouldEncrypt = false
|
||||||
|
}
|
||||||
|
|
||||||
val attachmentKey = Util.getSecretBytes(64)
|
val attachmentKey = Util.getSecretBytes(64)
|
||||||
val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length
|
val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length
|
||||||
@ -65,7 +71,11 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
|||||||
val outputStreamFactory = if (shouldEncrypt) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory()
|
val outputStreamFactory = if (shouldEncrypt) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory()
|
||||||
val attachmentData = PushAttachmentData(attachment.contentType, dataStream, ciphertextLength, outputStreamFactory, attachment.listener)
|
val attachmentData = PushAttachmentData(attachment.contentType, dataStream, ciphertextLength, outputStreamFactory, attachment.listener)
|
||||||
|
|
||||||
val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData)
|
val uploadResult = if (openGroupV2 == null) FileServerAPI.shared.uploadAttachment(server, attachmentData) else {
|
||||||
|
val dataBytes = attachmentData.data.readBytes()
|
||||||
|
val result = OpenGroupAPIV2.upload(dataBytes, openGroupV2.room, openGroupV2.server).get()
|
||||||
|
DotNetAPI.UploadResult(result, "${openGroupV2.server}/files/$result", byteArrayOf())
|
||||||
|
}
|
||||||
handleSuccess(attachment, attachmentKey, uploadResult)
|
handleSuccess(attachment, attachmentKey, uploadResult)
|
||||||
|
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
|
@ -5,6 +5,9 @@ import org.session.libsession.messaging.threads.Address
|
|||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsession.utilities.GroupUtil
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
import org.session.libsignal.service.loki.utilities.toHexString
|
||||||
|
|
||||||
|
typealias OpenGroupModel = org.session.libsession.messaging.opengroups.OpenGroup
|
||||||
|
typealias OpenGroupV2Model = org.session.libsession.messaging.opengroups.OpenGroupV2
|
||||||
|
|
||||||
sealed class Destination {
|
sealed class Destination {
|
||||||
|
|
||||||
class Contact(var publicKey: String) : Destination() {
|
class Contact(var publicKey: String) : Destination() {
|
||||||
@ -16,6 +19,9 @@ sealed class Destination {
|
|||||||
class OpenGroup(var channel: Long, var server: String) : Destination() {
|
class OpenGroup(var channel: Long, var server: String) : Destination() {
|
||||||
internal constructor(): this(0, "")
|
internal constructor(): this(0, "")
|
||||||
}
|
}
|
||||||
|
class OpenGroupV2(var room: String, var server: String): Destination() {
|
||||||
|
internal constructor(): this("", "")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(address: Address): Destination {
|
fun from(address: Address): Destination {
|
||||||
@ -29,9 +35,13 @@ sealed class Destination {
|
|||||||
ClosedGroup(groupPublicKey)
|
ClosedGroup(groupPublicKey)
|
||||||
}
|
}
|
||||||
address.isOpenGroup -> {
|
address.isOpenGroup -> {
|
||||||
val threadID = MessagingConfiguration.shared.storage.getThreadID(address.contactIdentifier())!!
|
val storage = MessagingConfiguration.shared.storage
|
||||||
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)!!
|
val threadID = storage.getThreadID(address.contactIdentifier())!!
|
||||||
OpenGroup(openGroup.channel, openGroup.server)
|
when (val openGroup = storage.getOpenGroup(threadID) ?: storage.getV2OpenGroup(threadID)) {
|
||||||
|
is OpenGroupModel -> OpenGroup(openGroup.channel, openGroup.server)
|
||||||
|
is OpenGroupV2Model -> OpenGroupV2(openGroup.room, openGroup.server)
|
||||||
|
else -> throw Exception("Invalid OpenGroup $openGroup")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
throw Exception("TODO: Handle legacy closed groups.")
|
throw Exception("TODO: Handle legacy closed groups.")
|
||||||
|
@ -21,6 +21,7 @@ import org.session.libsignal.service.loki.utilities.DownloadUtilities
|
|||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
import org.session.libsignal.service.loki.utilities.toHexString
|
||||||
import org.session.libsignal.utilities.Base64.*
|
import org.session.libsignal.utilities.Base64.*
|
||||||
|
import org.session.libsignal.utilities.Hex
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import org.session.libsignal.utilities.createContext
|
import org.session.libsignal.utilities.createContext
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
@ -58,6 +59,11 @@ object OpenGroupAPIV2 {
|
|||||||
val imageID: String?
|
val imageID: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class CompactPollResult(val messages: List<OpenGroupMessageV2>,
|
||||||
|
val deletions: List<Long>,
|
||||||
|
val moderators: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
data class Request(
|
data class Request(
|
||||||
val verb: HTTP.Verb,
|
val verb: HTTP.Verb,
|
||||||
val room: String?,
|
val room: String?,
|
||||||
@ -224,10 +230,10 @@ object OpenGroupAPIV2 {
|
|||||||
|
|
||||||
fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> {
|
fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> {
|
||||||
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED)
|
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED)
|
||||||
val json = signedMessage.toJSON()
|
val jsonMessage = signedMessage.toJSON()
|
||||||
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = json)
|
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = jsonMessage)
|
||||||
return send(request).map(sharedContext) {
|
return send(request).map(sharedContext) { json ->
|
||||||
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String, String>
|
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String, Any>
|
||||||
?: throw Error.PARSING_FAILED
|
?: throw Error.PARSING_FAILED
|
||||||
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED
|
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED
|
||||||
}
|
}
|
||||||
@ -249,21 +255,25 @@ object OpenGroupAPIV2 {
|
|||||||
|
|
||||||
var currentMax = lastMessageServerId
|
var currentMax = lastMessageServerId
|
||||||
val messages = rawMessages.mapNotNull { json ->
|
val messages = rawMessages.mapNotNull { json ->
|
||||||
val message = OpenGroupMessageV2.fromJSON(json) ?: return@mapNotNull null
|
try {
|
||||||
if (message.serverID == null || message.sender.isNullOrEmpty()) return@mapNotNull null
|
val message = OpenGroupMessageV2.fromJSON(json) ?: return@mapNotNull null
|
||||||
val sender = message.sender
|
if (message.serverID == null || message.sender.isNullOrEmpty()) return@mapNotNull null
|
||||||
val data = decode(message.base64EncodedData)
|
val sender = message.sender
|
||||||
val signature = decode(message.base64EncodedSignature)
|
val data = decode(message.base64EncodedData)
|
||||||
val publicKey = sender.removing05PrefixIfNeeded().encodeToByteArray()
|
val signature = decode(message.base64EncodedSignature)
|
||||||
val isValid = curve.verifySignature(publicKey, data, signature)
|
val publicKey = Hex.fromStringCondensed(sender.removing05PrefixIfNeeded())
|
||||||
if (!isValid) {
|
val isValid = curve.verifySignature(publicKey, data, signature)
|
||||||
Log.d("Loki", "Ignoring message with invalid signature")
|
if (!isValid) {
|
||||||
return@mapNotNull null
|
Log.d("Loki", "Ignoring message with invalid signature")
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
if (message.serverID > lastMessageServerId) {
|
||||||
|
currentMax = message.serverID
|
||||||
|
}
|
||||||
|
message
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
if (message.serverID > lastMessageServerId) {
|
|
||||||
currentMax = message.serverID
|
|
||||||
}
|
|
||||||
message
|
|
||||||
}
|
}
|
||||||
storage.setLastMessageServerId(room, server, currentMax)
|
storage.setLastMessageServerId(room, server, currentMax)
|
||||||
messages
|
messages
|
||||||
@ -332,6 +342,10 @@ object OpenGroupAPIV2 {
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region General
|
// region General
|
||||||
|
// fun getCompactPoll(): Promise<CompactPollResult, Exception> {
|
||||||
|
// val request = Request()
|
||||||
|
// }
|
||||||
|
|
||||||
fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> {
|
fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> {
|
||||||
val storage = MessagingConfiguration.shared.storage
|
val storage = MessagingConfiguration.shared.storage
|
||||||
storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY)
|
storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY)
|
||||||
|
@ -8,14 +8,14 @@ import org.session.libsignal.utilities.logging.Log
|
|||||||
import org.whispersystems.curve25519.Curve25519
|
import org.whispersystems.curve25519.Curve25519
|
||||||
|
|
||||||
data class OpenGroupMessageV2(
|
data class OpenGroupMessageV2(
|
||||||
val serverID: Long?,
|
val serverID: Long? = null,
|
||||||
val sender: String?,
|
val sender: String?,
|
||||||
val sentTimestamp: Long,
|
val sentTimestamp: Long,
|
||||||
// The serialized protobuf in base64 encoding
|
// The serialized protobuf in base64 encoding
|
||||||
val base64EncodedData: String,
|
val base64EncodedData: String,
|
||||||
// When sending a message, the sender signs the serialized protobuf with their private key so that
|
// When sending a message, the sender signs the serialized protobuf with their private key so that
|
||||||
// a receiving user can verify that the message wasn't tampered with.
|
// a receiving user can verify that the message wasn't tampered with.
|
||||||
val base64EncodedSignature: String?
|
val base64EncodedSignature: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -24,10 +24,10 @@ data class OpenGroupMessageV2(
|
|||||||
fun fromJSON(json: Map<String, Any>): OpenGroupMessageV2? {
|
fun fromJSON(json: Map<String, Any>): OpenGroupMessageV2? {
|
||||||
val base64EncodedData = json["data"] as? String ?: return null
|
val base64EncodedData = json["data"] as? String ?: return null
|
||||||
val sentTimestamp = json["timestamp"] as? Long ?: return null
|
val sentTimestamp = json["timestamp"] as? Long ?: return null
|
||||||
val serverID = json["server_id"] as? Long
|
val serverID = json["server_id"] as? Int
|
||||||
val sender = json["public_key"] as? String
|
val sender = json["public_key"] as? String
|
||||||
val base64EncodedSignature = json["signature"] as? String
|
val base64EncodedSignature = json["signature"] as? String
|
||||||
return OpenGroupMessageV2(serverID = serverID,
|
return OpenGroupMessageV2(serverID = serverID?.toLong(),
|
||||||
sender = sender,
|
sender = sender,
|
||||||
sentTimestamp = sentTimestamp,
|
sentTimestamp = sentTimestamp,
|
||||||
base64EncodedData = base64EncodedData,
|
base64EncodedData = base64EncodedData,
|
||||||
@ -44,7 +44,7 @@ data class OpenGroupMessageV2(
|
|||||||
if (sender != publicKey) return null // only sign our own messages?
|
if (sender != publicKey) return null // only sign our own messages?
|
||||||
|
|
||||||
val signature = try {
|
val signature = try {
|
||||||
curve.calculateSignature(privateKey, Base64.decode(base64EncodedData))
|
curve.calculateSignature(privateKey, decode(base64EncodedData))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Loki", "Couldn't sign OpenGroupV2Message", e)
|
Log.e("Loki", "Couldn't sign OpenGroupV2Message", e)
|
||||||
return null
|
return null
|
||||||
|
@ -13,7 +13,9 @@ import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
|||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||||
import org.session.libsession.messaging.messages.visible.*
|
import org.session.libsession.messaging.messages.visible.*
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroupMessage
|
import org.session.libsession.messaging.opengroups.OpenGroupMessage
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroupMessageV2
|
||||||
import org.session.libsession.messaging.threads.Address
|
import org.session.libsession.messaging.threads.Address
|
||||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||||
import org.session.libsession.snode.RawResponsePromise
|
import org.session.libsession.snode.RawResponsePromise
|
||||||
@ -63,7 +65,7 @@ object MessageSender {
|
|||||||
|
|
||||||
// Convenience
|
// Convenience
|
||||||
fun send(message: Message, destination: Destination): Promise<Unit, Exception> {
|
fun send(message: Message, destination: Destination): Promise<Unit, Exception> {
|
||||||
if (destination is Destination.OpenGroup) {
|
if (destination is Destination.OpenGroup || destination is Destination.OpenGroupV2) {
|
||||||
return sendToOpenGroupDestination(destination, message)
|
return sendToOpenGroupDestination(destination, message)
|
||||||
}
|
}
|
||||||
return sendToSnodeDestination(destination, message)
|
return sendToSnodeDestination(destination, message)
|
||||||
@ -91,7 +93,8 @@ object MessageSender {
|
|||||||
when (destination) {
|
when (destination) {
|
||||||
is Destination.Contact -> message.recipient = destination.publicKey
|
is Destination.Contact -> message.recipient = destination.publicKey
|
||||||
is Destination.ClosedGroup -> message.recipient = destination.groupPublicKey
|
is Destination.ClosedGroup -> message.recipient = destination.groupPublicKey
|
||||||
is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!")
|
is Destination.OpenGroup,
|
||||||
|
is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!")
|
||||||
}
|
}
|
||||||
// Validate the message
|
// Validate the message
|
||||||
if (!message.isValid()) { throw Error.InvalidMessage }
|
if (!message.isValid()) { throw Error.InvalidMessage }
|
||||||
@ -129,7 +132,8 @@ object MessageSender {
|
|||||||
val encryptionKeyPair = MessagingConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!!
|
val encryptionKeyPair = MessagingConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!!
|
||||||
ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, encryptionKeyPair.hexEncodedPublicKey)
|
ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, encryptionKeyPair.hexEncodedPublicKey)
|
||||||
}
|
}
|
||||||
is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!")
|
is Destination.OpenGroup,
|
||||||
|
is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!")
|
||||||
}
|
}
|
||||||
// Wrap the result
|
// Wrap the result
|
||||||
val kind: SignalServiceProtos.Envelope.Type
|
val kind: SignalServiceProtos.Envelope.Type
|
||||||
@ -143,7 +147,8 @@ object MessageSender {
|
|||||||
kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT
|
kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT
|
||||||
senderPublicKey = destination.groupPublicKey
|
senderPublicKey = destination.groupPublicKey
|
||||||
}
|
}
|
||||||
is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!")
|
is Destination.OpenGroup,
|
||||||
|
is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!")
|
||||||
}
|
}
|
||||||
val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext)
|
val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext)
|
||||||
// Calculate proof of work
|
// Calculate proof of work
|
||||||
@ -207,32 +212,59 @@ object MessageSender {
|
|||||||
deferred.reject(error)
|
deferred.reject(error)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val server: String
|
|
||||||
val channel: Long
|
|
||||||
when (destination) {
|
when (destination) {
|
||||||
is Destination.Contact -> throw Error.PreconditionFailure("Destination should not be contacts!")
|
is Destination.Contact -> throw Error.PreconditionFailure("Destination should not be contacts!")
|
||||||
is Destination.ClosedGroup -> throw Error.PreconditionFailure("Destination should not be closed groups!")
|
is Destination.ClosedGroup -> throw Error.PreconditionFailure("Destination should not be closed groups!")
|
||||||
is Destination.OpenGroup -> {
|
is Destination.OpenGroup -> {
|
||||||
message.recipient = "${destination.server}.${destination.channel}"
|
message.recipient = "${destination.server}.${destination.channel}"
|
||||||
server = destination.server
|
val server = destination.server
|
||||||
channel = destination.channel
|
val channel = destination.channel
|
||||||
|
|
||||||
|
// Validate the message
|
||||||
|
if (message !is VisibleMessage || !message.isValid()) {
|
||||||
|
throw Error.InvalidMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the message to an open group message
|
||||||
|
val openGroupMessage = OpenGroupMessage.from(message, server) ?: run {
|
||||||
|
throw Error.InvalidMessage
|
||||||
|
}
|
||||||
|
// Send the result
|
||||||
|
OpenGroupAPI.sendMessage(openGroupMessage, channel, server).success {
|
||||||
|
message.openGroupServerMessageID = it.serverID
|
||||||
|
handleSuccessfulMessageSend(message, destination)
|
||||||
|
deferred.resolve(Unit)
|
||||||
|
}.fail {
|
||||||
|
handleFailure(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Destination.OpenGroupV2 -> {
|
||||||
|
message.recipient = "${destination.server}.${destination.room}"
|
||||||
|
val server = destination.server
|
||||||
|
val room = destination.room
|
||||||
|
|
||||||
|
// Validate the message
|
||||||
|
if (message !is VisibleMessage || !message.isValid()) {
|
||||||
|
throw Error.InvalidMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
val proto = message.toProto()!!
|
||||||
|
|
||||||
|
val openGroupMessage = OpenGroupMessageV2(
|
||||||
|
sender = message.sender,
|
||||||
|
sentTimestamp = message.sentTimestamp!!,
|
||||||
|
base64EncodedData = Base64.encodeBytes(PushTransportDetails.getPaddedMessageBody(proto.dataMessage!!.toByteArray())),
|
||||||
|
)
|
||||||
|
|
||||||
|
OpenGroupAPIV2.send(openGroupMessage,room,server).success {
|
||||||
|
message.openGroupServerMessageID = it.serverID
|
||||||
|
handleSuccessfulMessageSend(message, destination)
|
||||||
|
deferred.resolve(Unit)
|
||||||
|
}.fail {
|
||||||
|
handleFailure(it)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Validate the message
|
|
||||||
if (message !is VisibleMessage || !message.isValid()) {
|
|
||||||
throw Error.InvalidMessage
|
|
||||||
}
|
|
||||||
// Convert the message to an open group message
|
|
||||||
val openGroupMessage = OpenGroupMessage.from(message, server) ?: kotlin.run {
|
|
||||||
throw Error.InvalidMessage
|
|
||||||
}
|
|
||||||
// Send the result
|
|
||||||
OpenGroupAPI.sendMessage(openGroupMessage, channel, server).success {
|
|
||||||
message.openGroupServerMessageID = it.serverID
|
|
||||||
handleSuccessfulMessageSend(message, destination)
|
|
||||||
deferred.resolve(Unit)
|
|
||||||
}.fail {
|
|
||||||
handleFailure(it)
|
|
||||||
}
|
}
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
handleFailure(exception)
|
handleFailure(exception)
|
||||||
|
@ -71,9 +71,6 @@ class ClosedGroupPoller {
|
|||||||
// ignore inactive group's messages
|
// ignore inactive group's messages
|
||||||
return@successBackground
|
return@successBackground
|
||||||
}
|
}
|
||||||
if (messages.isNotEmpty()) {
|
|
||||||
Log.d("Loki", "Received ${messages.count()} new message(s) in closed group with public key: $publicKey.")
|
|
||||||
}
|
|
||||||
messages.forEach { message ->
|
messages.forEach { message ->
|
||||||
val rawMessageAsJSON = message as? Map<*, *>
|
val rawMessageAsJSON = message as? Map<*, *>
|
||||||
val base64EncodedData = rawMessageAsJSON?.get("data") as? String
|
val base64EncodedData = rawMessageAsJSON?.get("data") as? String
|
||||||
|
@ -72,7 +72,6 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
|
|||||||
// Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below
|
// Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below
|
||||||
OpenGroupAPI.getMessages(openGroup.channel, openGroup.server).successBackground { messages ->
|
OpenGroupAPI.getMessages(openGroup.channel, openGroup.server).successBackground { messages ->
|
||||||
// Process messages in the background
|
// Process messages in the background
|
||||||
Log.d("Loki", "received ${messages.size} messages")
|
|
||||||
messages.forEach { message ->
|
messages.forEach { message ->
|
||||||
try {
|
try {
|
||||||
val senderPublicKey = message.senderPublicKey
|
val senderPublicKey = message.senderPublicKey
|
||||||
|
@ -62,7 +62,6 @@ class OpenGroupV2Poller(private val openGroup: OpenGroupV2, private val executor
|
|||||||
// Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below
|
// Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below
|
||||||
OpenGroupAPIV2.getMessages(openGroup.room, openGroup.server).successBackground { messages ->
|
OpenGroupAPIV2.getMessages(openGroup.room, openGroup.server).successBackground { messages ->
|
||||||
// Process messages in the background
|
// Process messages in the background
|
||||||
Log.d("Loki", "received ${messages.size} messages")
|
|
||||||
messages.forEach { message ->
|
messages.forEach { message ->
|
||||||
try {
|
try {
|
||||||
val senderPublicKey = message.sender!!
|
val senderPublicKey = message.sender!!
|
||||||
|
@ -5,14 +5,10 @@ import nl.komponents.kovenant.functional.bind
|
|||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import nl.komponents.kovenant.then
|
import nl.komponents.kovenant.then
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
|
|
||||||
import org.session.libsession.messaging.MessagingConfiguration
|
import org.session.libsession.messaging.MessagingConfiguration
|
||||||
|
import org.session.libsession.messaging.fileserver.FileServerAPI
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.messaging.fileserver.FileServerAPI
|
|
||||||
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
|
||||||
import org.session.libsignal.utilities.DiffieHellman
|
|
||||||
import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream
|
import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceAttachment
|
import org.session.libsignal.service.api.messages.SignalServiceAttachment
|
||||||
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException
|
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||||
@ -22,13 +18,12 @@ import org.session.libsignal.service.internal.push.ProfileAvatarData
|
|||||||
import org.session.libsignal.service.internal.push.PushAttachmentData
|
import org.session.libsignal.service.internal.push.PushAttachmentData
|
||||||
import org.session.libsignal.service.internal.push.http.DigestingRequestBody
|
import org.session.libsignal.service.internal.push.http.DigestingRequestBody
|
||||||
import org.session.libsignal.service.internal.push.http.ProfileCipherOutputStreamFactory
|
import org.session.libsignal.service.internal.push.http.ProfileCipherOutputStreamFactory
|
||||||
import org.session.libsignal.utilities.Hex
|
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
|
||||||
import org.session.libsignal.service.loki.api.utilities.HTTP
|
import org.session.libsignal.service.loki.api.utilities.HTTP
|
||||||
import org.session.libsignal.service.loki.utilities.*
|
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||||
|
import org.session.libsignal.service.loki.utilities.retryIfNeeded
|
||||||
import org.session.libsignal.utilities.*
|
import org.session.libsignal.utilities.*
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@ -270,13 +265,13 @@ open class DotNetAPI {
|
|||||||
return upload(server, request) { json -> // Retrying is handled by AttachmentUploadJob
|
return upload(server, request) { json -> // Retrying is handled by AttachmentUploadJob
|
||||||
val data = json["data"] as? Map<*, *>
|
val data = json["data"] as? Map<*, *>
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
Log.d("Loki", "Couldn't parse attachment from: $json.")
|
Log.e("Loki", "Couldn't parse attachment from: $json.")
|
||||||
throw Error.ParsingFailed
|
throw Error.ParsingFailed
|
||||||
}
|
}
|
||||||
val id = data["id"] as? Long ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as? String)?.toLong()
|
val id = data["id"] as? Long ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as? String)?.toLong()
|
||||||
val url = data["url"] as? String
|
val url = data["url"] as? String
|
||||||
if (id == null || url == null || url.isEmpty()) {
|
if (id == null || url == null || url.isEmpty()) {
|
||||||
Log.d("Loki", "Couldn't parse upload from: $json.")
|
Log.e("Loki", "Couldn't parse upload from: $json.")
|
||||||
throw Error.ParsingFailed
|
throw Error.ParsingFailed
|
||||||
}
|
}
|
||||||
UploadResult(id, url, file.transmittedDigest)
|
UploadResult(id, url, file.transmittedDigest)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user