Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group Message Kind #217

Merged
merged 15 commits into from
Apr 9, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.xmtp.android.library.codecs.ContentTypeReply
import org.xmtp.android.library.codecs.ContentTypeText
import org.xmtp.android.library.codecs.Reply
import org.xmtp.android.library.messages.MessageKind
import org.xmtp.android.library.messages.PrivateKey
import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.walletAddress
import org.xmtp.proto.mls.message.contents.TranscriptMessages.GroupMembershipChangesOrBuilder
import uniffi.xmtpv3.org.xmtp.android.library.codecs.ContentTypeGroupMembershipChange
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChangeCodec
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChanges

Expand Down Expand Up @@ -69,6 +75,7 @@ class GroupMembershipChangeTest {
content?.membersAddedList?.map { it.accountAddress.lowercase() }?.sorted()
)
assert(content?.membersRemovedList.isNullOrEmpty())
assertEquals(messages.first().kind, MessageKind.MEMBERSHIP_CHANGE)
}

@Test
Expand Down Expand Up @@ -97,6 +104,35 @@ class GroupMembershipChangeTest {
content?.membersRemovedList?.map { it.accountAddress.lowercase() }?.sorted()
)
assert(content?.membersAddedList.isNullOrEmpty())
assertEquals(updatedMessages.first().kind, MessageKind.MEMBERSHIP_CHANGE)
}

@Test
fun testRemovesInvalidMessageKind() {
Client.register(codec = GroupMembershipChangeCodec())

val membershipChange = GroupMembershipChanges.newBuilder().build()

val group = runBlocking {
alixClient.conversations.newGroup(
listOf(
bo.walletAddress,
caro.walletAddress
)
)
}
val messages = group.messages()
assertEquals(messages.size, 1)
assertEquals(group.memberAddresses().size, 3)
runBlocking {
group.send(
content = membershipChange,
options = SendOptions(contentType = ContentTypeGroupMembershipChange),
)
group.sync()
}
val updatedMessages = group.messages()
assertEquals(updatedMessages.size, 1)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ import org.xmtp.android.library.codecs.Reaction
import org.xmtp.android.library.codecs.ReactionAction
import org.xmtp.android.library.codecs.ReactionCodec
import org.xmtp.android.library.codecs.ReactionSchema
import org.xmtp.android.library.messages.MessageKind
import org.xmtp.android.library.messages.PrivateKey
import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.walletAddress
import uniffi.xmtpv3.GroupPermissions
import uniffi.xmtpv3.org.xmtp.android.library.codecs.ContentTypeGroupMembershipChange
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChangeCodec
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChanges

@RunWith(AndroidJUnit4::class)
class GroupTest {
Expand Down Expand Up @@ -323,6 +327,7 @@ class GroupTest {
runBlocking { sameGroup.sync() }
assertEquals(sameGroup.messages().size, 2)
assertEquals(sameGroup.messages().first().body, "gm")
assertEquals(sameGroup.messages().first().kind, MessageKind.APPLICATION)
}

@Test
Expand Down Expand Up @@ -360,12 +365,19 @@ class GroupTest {

@Test
fun testCanStreamGroupMessages() = kotlinx.coroutines.test.runTest {
Client.register(codec = GroupMembershipChangeCodec())
val membershipChange = GroupMembershipChanges.newBuilder().build()

val group = boClient.conversations.newGroup(listOf(alix.walletAddress.lowercase()))
alixClient.conversations.syncGroups()
val alixGroup = alixClient.conversations.listGroups().first()
group.streamMessages().test {
alixGroup.send("hi")
assertEquals("hi", awaitItem().body)
alixGroup.send(
content = membershipChange,
options = SendOptions(contentType = ContentTypeGroupMembershipChange),
)
alixGroup.send("hi again")
assertEquals("hi again", awaitItem().body)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import android.util.Log
import com.google.protobuf.kotlin.toByteString
import kotlinx.coroutines.flow.Flow
import org.xmtp.android.library.codecs.EncodedContent
import org.xmtp.android.library.libxmtp.Message
import org.xmtp.android.library.libxmtp.MessageV3
import org.xmtp.android.library.messages.DecryptedMessage
import org.xmtp.android.library.messages.Envelope
import org.xmtp.android.library.messages.PagingInfoSortDirection
Expand Down Expand Up @@ -121,7 +121,7 @@ sealed class Conversation {
}
}

fun decode(envelope: Envelope, message: Message? = null): DecodedMessage {
fun decode(envelope: Envelope, message: MessageV3? = null): DecodedMessage {
return when (this) {
is V1 -> conversationV1.decode(envelope)
is V2 -> conversationV2.decodeEnvelope(envelope)
Expand Down Expand Up @@ -277,7 +277,7 @@ sealed class Conversation {

fun decrypt(
envelope: Envelope,
message: Message? = null,
message: MessageV3? = null,
): DecryptedMessage {
return when (this) {
is V1 -> conversationV1.decrypt(envelope)
Expand Down
14 changes: 11 additions & 3 deletions library/src/main/java/org/xmtp/android/library/Conversations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.merge
import org.xmtp.android.library.GRPCApiClient.Companion.makeQueryRequest
import org.xmtp.android.library.GRPCApiClient.Companion.makeSubscribeRequest
import org.xmtp.android.library.libxmtp.Message
import org.xmtp.android.library.libxmtp.MessageV3
import org.xmtp.android.library.messages.DecryptedMessage
import org.xmtp.android.library.messages.Envelope
import org.xmtp.android.library.messages.EnvelopeBuilder
import org.xmtp.android.library.messages.InvitationV1
import org.xmtp.android.library.messages.MessageKind
import org.xmtp.android.library.messages.MessageV1Builder
import org.xmtp.android.library.messages.Pagination
import org.xmtp.android.library.messages.SealedInvitation
Expand Down Expand Up @@ -46,6 +47,7 @@ import uniffi.xmtpv3.FfiListConversationsOptions
import uniffi.xmtpv3.FfiMessage
import uniffi.xmtpv3.FfiMessageCallback
import uniffi.xmtpv3.GroupPermissions
import uniffi.xmtpv3.org.xmtp.android.library.codecs.ContentTypeGroupMembershipChange
import java.util.Date
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.DurationUnit
Expand Down Expand Up @@ -576,7 +578,10 @@ data class Conversations(
fun streamAllGroupMessages(): Flow<DecodedMessage> = callbackFlow {
val messageCallback = object : FfiMessageCallback {
override fun onMessage(message: FfiMessage) {
trySend(Message(client, message).decode())
val decodedMessage = MessageV3(client, message).decode()
if (!(decodedMessage.encodedContent.type == ContentTypeGroupMembershipChange && decodedMessage.kind != MessageKind.MEMBERSHIP_CHANGE)) {
trySend(decodedMessage)
}
nplasterer marked this conversation as resolved.
Show resolved Hide resolved
}
}
val stream = libXMTPConversations?.streamAllMessages(messageCallback)
Expand All @@ -587,7 +592,10 @@ data class Conversations(
fun streamAllGroupDecryptedMessages(): Flow<DecryptedMessage> = callbackFlow {
val messageCallback = object : FfiMessageCallback {
override fun onMessage(message: FfiMessage) {
trySend(Message(client, message).decrypt())
val decryptedMessage = MessageV3(client, message).decrypt()
if (!(decryptedMessage.encodedContent.type == ContentTypeGroupMembershipChange && decryptedMessage.kind != MessageKind.MEMBERSHIP_CHANGE)) {
trySend(decryptedMessage)
}
}
}
val stream = libXMTPConversations?.streamAllMessages(messageCallback)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.xmtp.android.library

import org.xmtp.android.library.codecs.TextCodec
import org.xmtp.android.library.codecs.decoded
import org.xmtp.android.library.messages.MessageKind
import org.xmtp.proto.message.contents.Content
import java.util.Date

Expand All @@ -11,7 +12,8 @@ data class DecodedMessage(
var topic: String,
var encodedContent: Content.EncodedContent,
var senderAddress: String,
var sent: Date
var sent: Date,
var kind: MessageKind = MessageKind.APPLICATION
) {
companion object {
fun preview(client: Client, topic: String, body: String, senderAddress: String, sent: Date): DecodedMessage {
Expand Down
53 changes: 42 additions & 11 deletions library/src/main/java/org/xmtp/android/library/Group.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import kotlinx.coroutines.flow.callbackFlow
import org.xmtp.android.library.codecs.ContentCodec
import org.xmtp.android.library.codecs.EncodedContent
import org.xmtp.android.library.codecs.compress
import org.xmtp.android.library.libxmtp.Message
import org.xmtp.android.library.libxmtp.MessageV3
import org.xmtp.android.library.messages.DecryptedMessage
import org.xmtp.android.library.messages.MessageKind
import org.xmtp.android.library.messages.PagingInfoSortDirection
import org.xmtp.android.library.messages.Topic
import org.xmtp.proto.message.api.v1.MessageApiOuterClass
Expand All @@ -17,6 +18,7 @@ import uniffi.xmtpv3.FfiListMessagesOptions
import uniffi.xmtpv3.FfiMessage
import uniffi.xmtpv3.FfiMessageCallback
import uniffi.xmtpv3.GroupPermissions
import uniffi.xmtpv3.org.xmtp.android.library.codecs.ContentTypeGroupMembershipChange
import java.util.Date
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.DurationUnit
Expand Down Expand Up @@ -94,11 +96,16 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
limit = limit?.toLong()
)
).map {
Message(client, it).decode()
MessageV3(client, it).decode()
}

val filteredMessages = messages.filterNot {
it.encodedContent.type == ContentTypeGroupMembershipChange && it.kind != MessageKind.MEMBERSHIP_CHANGE
}

return when (direction) {
MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> messages
else -> messages.reversed()
MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> filteredMessages
else -> filteredMessages.reversed()
}
}

Expand All @@ -115,17 +122,35 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
limit = limit?.toLong()
)
).map {
Message(client, it).decrypt()
MessageV3(client, it).decrypt()
}

val filteredMessages = messages.filterNot {
it.encodedContent.type == ContentTypeGroupMembershipChange && it.kind != MessageKind.MEMBERSHIP_CHANGE
}

return when (direction) {
MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> messages
else -> messages.reversed()
MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> filteredMessages
else -> filteredMessages.reversed()
}
}

suspend fun processMessage(envelopeBytes: ByteArray): Message {
suspend fun processMessage(envelopeBytes: ByteArray): DecodedMessage {
val message = libXMTPGroup.processStreamedGroupMessage(envelopeBytes)
return Message(client, message)
val decodedMessage = MessageV3(client, message).decode()
if (decodedMessage.encodedContent.type == ContentTypeGroupMembershipChange && decodedMessage.kind != MessageKind.MEMBERSHIP_CHANGE) {
throw XMTPException("Invalid message")
}
return decodedMessage
}

suspend fun processMessageDecrypted(envelopeBytes: ByteArray): DecryptedMessage {
val message = libXMTPGroup.processStreamedGroupMessage(envelopeBytes)
val decryptedMessage = MessageV3(client, message).decrypt()
if (decryptedMessage.encodedContent.type == ContentTypeGroupMembershipChange && decryptedMessage.kind != MessageKind.MEMBERSHIP_CHANGE) {
throw XMTPException("Invalid message")
}
return decryptedMessage
}

fun isActive(): Boolean {
Expand Down Expand Up @@ -173,7 +198,10 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
fun streamMessages(): Flow<DecodedMessage> = callbackFlow {
val messageCallback = object : FfiMessageCallback {
override fun onMessage(message: FfiMessage) {
trySend(Message(client, message).decode())
val decodedMessage = MessageV3(client, message).decode()
if (!(decodedMessage.encodedContent.type == ContentTypeGroupMembershipChange && decodedMessage.kind != MessageKind.MEMBERSHIP_CHANGE)) {
trySend(decodedMessage)
}
}
}

Expand All @@ -184,7 +212,10 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
fun streamDecryptedMessages(): Flow<DecryptedMessage> = callbackFlow {
val messageCallback = object : FfiMessageCallback {
override fun onMessage(message: FfiMessage) {
trySend(Message(client, message).decrypt())
val decryptedMessage = MessageV3(client, message).decrypt()
if (!(decryptedMessage.encodedContent.type == ContentTypeGroupMembershipChange && decryptedMessage.kind != MessageKind.MEMBERSHIP_CHANGE)) {
trySend(decryptedMessage)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import org.xmtp.android.library.DecodedMessage
import org.xmtp.android.library.XMTPException
import org.xmtp.android.library.codecs.EncodedContent
import org.xmtp.android.library.messages.DecryptedMessage
import org.xmtp.android.library.messages.MessageKind
import org.xmtp.android.library.messages.Topic
import org.xmtp.android.library.toHex
import uniffi.xmtpv3.FfiGroupMessageKind
import uniffi.xmtpv3.FfiMessage
import java.util.Date

data class Message(val client: Client, private val libXMTPMessage: FfiMessage) {
data class MessageV3(val client: Client, private val libXMTPMessage: FfiMessage) {

val id: ByteArray
get() = libXMTPMessage.id

Expand All @@ -23,6 +26,12 @@ data class Message(val client: Client, private val libXMTPMessage: FfiMessage) {
val sentAt: Date
get() = Date(libXMTPMessage.sentAtNs / 1_000_000)

val kind: MessageKind
get() = when (libXMTPMessage.kind) {
FfiGroupMessageKind.APPLICATION -> MessageKind.APPLICATION
FfiGroupMessageKind.MEMBERSHIP_CHANGE -> MessageKind.MEMBERSHIP_CHANGE
}

fun decode(): DecodedMessage {
try {
return DecodedMessage(
Expand All @@ -31,7 +40,8 @@ data class Message(val client: Client, private val libXMTPMessage: FfiMessage) {
topic = Topic.groupMessage(convoId.toHex()).description,
encodedContent = EncodedContent.parseFrom(libXMTPMessage.content),
senderAddress = senderAddress,
sent = sentAt
sent = sentAt,
kind = kind
)
} catch (e: Exception) {
throw XMTPException("Error decoding message", e)
Expand All @@ -44,7 +54,8 @@ data class Message(val client: Client, private val libXMTPMessage: FfiMessage) {
topic = Topic.groupMessage(convoId.toHex()).description,
encodedContent = decode().encodedContent,
senderAddress = senderAddress,
sentAt = Date()
sentAt = Date(),
kind = kind
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ data class DecryptedMessage(
var encodedContent: EncodedContent,
var senderAddress: String,
var sentAt: Date,
var topic: String = ""
var topic: String = "",
var kind: MessageKind = MessageKind.APPLICATION
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ enum class MessageVersion(val rawValue: String) {
}
}

enum class MessageKind {
APPLICATION, MEMBERSHIP_CHANGE
}

class MessageBuilder {
companion object {
fun buildFromMessageV1(v1: MessageV1): Message {
Expand Down
Loading
Loading