Skip to content

Commit 3c8a080

Browse files
authored
Group Message Kind (#217)
* add new xmtpv3 message kind * add the update jnilibs * do a rename to messageV3 and tweaks to add message kind to the message * add tests for it * move where message kind lives * filter them out of the message list * filter them out of the stream * filter the stream all as well * add tests * fix up the linter issues * fix example * make sure the tests run on local * add the libxmtp version * tweak this so it happens in the decode methods instea * fix up the linter
1 parent 23b2673 commit 3c8a080

File tree

15 files changed

+145
-24
lines changed

15 files changed

+145
-24
lines changed

library/src/androidTest/java/org/xmtp/android/library/GroupMembershipChangeTest.kt

+29
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.junit.runner.RunWith
1111
import org.xmtp.android.library.messages.PrivateKey
1212
import org.xmtp.android.library.messages.PrivateKeyBuilder
1313
import org.xmtp.android.library.messages.walletAddress
14+
import uniffi.xmtpv3.org.xmtp.android.library.codecs.ContentTypeGroupMembershipChange
1415
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChangeCodec
1516
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChanges
1617

@@ -99,6 +100,34 @@ class GroupMembershipChangeTest {
99100
assert(content?.membersAddedList.isNullOrEmpty())
100101
}
101102

103+
@Test
104+
fun testRemovesInvalidMessageKind() {
105+
Client.register(codec = GroupMembershipChangeCodec())
106+
107+
val membershipChange = GroupMembershipChanges.newBuilder().build()
108+
109+
val group = runBlocking {
110+
alixClient.conversations.newGroup(
111+
listOf(
112+
bo.walletAddress,
113+
caro.walletAddress
114+
)
115+
)
116+
}
117+
val messages = group.messages()
118+
assertEquals(messages.size, 1)
119+
assertEquals(group.memberAddresses().size, 3)
120+
runBlocking {
121+
group.send(
122+
content = membershipChange,
123+
options = SendOptions(contentType = ContentTypeGroupMembershipChange),
124+
)
125+
group.sync()
126+
}
127+
val updatedMessages = group.messages()
128+
assertEquals(updatedMessages.size, 1)
129+
}
130+
102131
@Test
103132
fun testIfNotRegisteredReturnsFallback() {
104133
val group = runBlocking {

library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt

+16
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import org.xmtp.android.library.messages.PrivateKey
2121
import org.xmtp.android.library.messages.PrivateKeyBuilder
2222
import org.xmtp.android.library.messages.walletAddress
2323
import uniffi.xmtpv3.GroupPermissions
24+
import uniffi.xmtpv3.org.xmtp.android.library.codecs.ContentTypeGroupMembershipChange
25+
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChangeCodec
26+
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChanges
2427

2528
@RunWith(AndroidJUnit4::class)
2629
class GroupTest {
@@ -360,12 +363,19 @@ class GroupTest {
360363

361364
@Test
362365
fun testCanStreamGroupMessages() = kotlinx.coroutines.test.runTest {
366+
Client.register(codec = GroupMembershipChangeCodec())
367+
val membershipChange = GroupMembershipChanges.newBuilder().build()
368+
363369
val group = boClient.conversations.newGroup(listOf(alix.walletAddress.lowercase()))
364370
alixClient.conversations.syncGroups()
365371
val alixGroup = alixClient.conversations.listGroups().first()
366372
group.streamMessages().test {
367373
alixGroup.send("hi")
368374
assertEquals("hi", awaitItem().body)
375+
alixGroup.send(
376+
content = membershipChange,
377+
options = SendOptions(contentType = ContentTypeGroupMembershipChange),
378+
)
369379
alixGroup.send("hi again")
370380
assertEquals("hi again", awaitItem().body)
371381
}
@@ -432,6 +442,8 @@ class GroupTest {
432442

433443
@Test
434444
fun testCanStreamAllDecryptedGroupMessages() = kotlinx.coroutines.test.runTest {
445+
Client.register(codec = GroupMembershipChangeCodec())
446+
val membershipChange = GroupMembershipChanges.newBuilder().build()
435447
val group = caroClient.conversations.newGroup(listOf(alix.walletAddress))
436448
alixClient.conversations.syncGroups()
437449

@@ -448,6 +460,10 @@ class GroupTest {
448460
}
449461

450462
group.send("hi 1")
463+
group.send(
464+
content = membershipChange,
465+
options = SendOptions(contentType = ContentTypeGroupMembershipChange),
466+
)
451467
group.send("hi 2")
452468

453469
job.join()

library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ class FakeApiClient : ApiClient {
175175
MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> {
176176
result = result.reversed().toMutableList()
177177
}
178+
178179
else -> Unit
179180
}
180181
}
@@ -191,6 +192,7 @@ class FakeApiClient : ApiClient {
191192
published.addAll(envelopes)
192193
return PublishResponse.newBuilder().build()
193194
}
195+
194196
override suspend fun subscribe(request: Flow<MessageApiOuterClass.SubscribeRequest>): Flow<MessageApiOuterClass.Envelope> {
195197
val env = stream.counts().first()
196198

@@ -205,7 +207,9 @@ data class Fixtures(
205207
val aliceAccount: PrivateKeyBuilder,
206208
val bobAccount: PrivateKeyBuilder,
207209
val caroAccount: PrivateKeyBuilder,
208-
val clientOptions: ClientOptions? = null
210+
val clientOptions: ClientOptions? = ClientOptions(
211+
ClientOptions.Api(XMTPEnvironment.LOCAL, isSecure = false)
212+
),
209213
) {
210214
var fakeApiClient: FakeApiClient = FakeApiClient()
211215
var alice: PrivateKey = aliceAccount.getPrivateKey()
@@ -214,6 +218,7 @@ data class Fixtures(
214218
var bobClient: Client = Client().create(account = bobAccount, options = clientOptions)
215219
var caro: PrivateKey = caroAccount.getPrivateKey()
216220
var caroClient: Client = Client().create(account = caroAccount, options = clientOptions)
221+
217222
constructor(clientOptions: ClientOptions?) : this(
218223
aliceAccount = PrivateKeyBuilder(),
219224
bobAccount = PrivateKeyBuilder(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Version: 4068715
2+
Branch: main
3+
Date: 2024-04-06 04:27:39 +0000

library/src/main/java/org/xmtp/android/library/Conversation.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import android.util.Log
44
import com.google.protobuf.kotlin.toByteString
55
import kotlinx.coroutines.flow.Flow
66
import org.xmtp.android.library.codecs.EncodedContent
7-
import org.xmtp.android.library.libxmtp.Message
7+
import org.xmtp.android.library.libxmtp.MessageV3
88
import org.xmtp.android.library.messages.DecryptedMessage
99
import org.xmtp.android.library.messages.Envelope
1010
import org.xmtp.android.library.messages.PagingInfoSortDirection
@@ -121,7 +121,7 @@ sealed class Conversation {
121121
}
122122
}
123123

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

278278
fun decrypt(
279279
envelope: Envelope,
280-
message: Message? = null,
280+
message: MessageV3? = null,
281281
): DecryptedMessage {
282282
return when (this) {
283283
is V1 -> conversationV1.decrypt(envelope)

library/src/main/java/org/xmtp/android/library/Conversations.kt

+9-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.flow
1212
import kotlinx.coroutines.flow.merge
1313
import org.xmtp.android.library.GRPCApiClient.Companion.makeQueryRequest
1414
import org.xmtp.android.library.GRPCApiClient.Companion.makeSubscribeRequest
15-
import org.xmtp.android.library.libxmtp.Message
15+
import org.xmtp.android.library.libxmtp.MessageV3
1616
import org.xmtp.android.library.messages.DecryptedMessage
1717
import org.xmtp.android.library.messages.Envelope
1818
import org.xmtp.android.library.messages.EnvelopeBuilder
@@ -576,7 +576,10 @@ data class Conversations(
576576
fun streamAllGroupMessages(): Flow<DecodedMessage> = callbackFlow {
577577
val messageCallback = object : FfiMessageCallback {
578578
override fun onMessage(message: FfiMessage) {
579-
trySend(Message(client, message).decode())
579+
val decodedMessage = MessageV3(client, message).decodeOrNull()
580+
decodedMessage?.let {
581+
trySend(it)
582+
}
580583
}
581584
}
582585
val stream = libXMTPConversations?.streamAllMessages(messageCallback)
@@ -587,7 +590,10 @@ data class Conversations(
587590
fun streamAllGroupDecryptedMessages(): Flow<DecryptedMessage> = callbackFlow {
588591
val messageCallback = object : FfiMessageCallback {
589592
override fun onMessage(message: FfiMessage) {
590-
trySend(Message(client, message).decrypt())
593+
val decryptedMessage = MessageV3(client, message).decryptOrNull()
594+
decryptedMessage?.let {
595+
trySend(it)
596+
}
591597
}
592598
}
593599
val stream = libXMTPConversations?.streamAllMessages(messageCallback)

library/src/main/java/org/xmtp/android/library/DecodedMessage.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ data class DecodedMessage(
1111
var topic: String,
1212
var encodedContent: Content.EncodedContent,
1313
var senderAddress: String,
14-
var sent: Date
14+
var sent: Date,
1515
) {
1616
companion object {
1717
fun preview(client: Client, topic: String, body: String, senderAddress: String, sent: Date): DecodedMessage {

library/src/main/java/org/xmtp/android/library/Group.kt

+17-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.callbackFlow
66
import org.xmtp.android.library.codecs.ContentCodec
77
import org.xmtp.android.library.codecs.EncodedContent
88
import org.xmtp.android.library.codecs.compress
9-
import org.xmtp.android.library.libxmtp.Message
9+
import org.xmtp.android.library.libxmtp.MessageV3
1010
import org.xmtp.android.library.messages.DecryptedMessage
1111
import org.xmtp.android.library.messages.PagingInfoSortDirection
1212
import org.xmtp.android.library.messages.Topic
@@ -93,9 +93,10 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
9393
sentAfterNs = after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
9494
limit = limit?.toLong()
9595
)
96-
).map {
97-
Message(client, it).decode()
96+
).mapNotNull {
97+
MessageV3(client, it).decodeOrNull()
9898
}
99+
99100
return when (direction) {
100101
MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> messages
101102
else -> messages.reversed()
@@ -114,18 +115,19 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
114115
sentAfterNs = after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
115116
limit = limit?.toLong()
116117
)
117-
).map {
118-
Message(client, it).decrypt()
118+
).mapNotNull {
119+
MessageV3(client, it).decryptOrNull()
119120
}
121+
120122
return when (direction) {
121123
MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> messages
122124
else -> messages.reversed()
123125
}
124126
}
125127

126-
suspend fun processMessage(envelopeBytes: ByteArray): Message {
128+
suspend fun processMessage(envelopeBytes: ByteArray): MessageV3 {
127129
val message = libXMTPGroup.processStreamedGroupMessage(envelopeBytes)
128-
return Message(client, message)
130+
return MessageV3(client, message)
129131
}
130132

131133
fun isActive(): Boolean {
@@ -173,7 +175,10 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
173175
fun streamMessages(): Flow<DecodedMessage> = callbackFlow {
174176
val messageCallback = object : FfiMessageCallback {
175177
override fun onMessage(message: FfiMessage) {
176-
trySend(Message(client, message).decode())
178+
val decodedMessage = MessageV3(client, message).decodeOrNull()
179+
decodedMessage?.let {
180+
trySend(it)
181+
}
177182
}
178183
}
179184

@@ -184,7 +189,10 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
184189
fun streamDecryptedMessages(): Flow<DecryptedMessage> = callbackFlow {
185190
val messageCallback = object : FfiMessageCallback {
186191
override fun onMessage(message: FfiMessage) {
187-
trySend(Message(client, message).decrypt())
192+
val decryptedMessage = MessageV3(client, message).decryptOrNull()
193+
decryptedMessage?.let {
194+
trySend(it)
195+
}
188196
}
189197
}
190198

library/src/main/java/org/xmtp/android/library/libxmtp/Message.kt library/src/main/java/org/xmtp/android/library/libxmtp/MessageV3.kt

+30-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package org.xmtp.android.library.libxmtp
22

3+
import android.util.Log
34
import org.xmtp.android.library.Client
45
import org.xmtp.android.library.DecodedMessage
56
import org.xmtp.android.library.XMTPException
67
import org.xmtp.android.library.codecs.EncodedContent
78
import org.xmtp.android.library.messages.DecryptedMessage
89
import org.xmtp.android.library.messages.Topic
910
import org.xmtp.android.library.toHex
11+
import uniffi.xmtpv3.FfiGroupMessageKind
1012
import uniffi.xmtpv3.FfiMessage
13+
import uniffi.xmtpv3.org.xmtp.android.library.codecs.ContentTypeGroupMembershipChange
1114
import java.util.Date
1215

13-
data class Message(val client: Client, private val libXMTPMessage: FfiMessage) {
16+
data class MessageV3(val client: Client, private val libXMTPMessage: FfiMessage) {
17+
1418
val id: ByteArray
1519
get() = libXMTPMessage.id
1620

@@ -25,26 +29,48 @@ data class Message(val client: Client, private val libXMTPMessage: FfiMessage) {
2529

2630
fun decode(): DecodedMessage {
2731
try {
28-
return DecodedMessage(
32+
val decodedMessage = DecodedMessage(
2933
id = id.toHex(),
3034
client = client,
3135
topic = Topic.groupMessage(convoId.toHex()).description,
3236
encodedContent = EncodedContent.parseFrom(libXMTPMessage.content),
3337
senderAddress = senderAddress,
34-
sent = sentAt
38+
sent = sentAt,
3539
)
40+
if (decodedMessage.encodedContent.type == ContentTypeGroupMembershipChange && libXMTPMessage.kind != FfiGroupMessageKind.MEMBERSHIP_CHANGE) {
41+
throw XMTPException("Error decoding group membership change")
42+
}
43+
return decodedMessage
3644
} catch (e: Exception) {
3745
throw XMTPException("Error decoding message", e)
3846
}
3947
}
4048

49+
fun decodeOrNull(): DecodedMessage? {
50+
return try {
51+
decode()
52+
} catch (e: Exception) {
53+
Log.d("MESSAGE_V3", "discarding message that failed to decode", e)
54+
null
55+
}
56+
}
57+
58+
fun decryptOrNull(): DecryptedMessage? {
59+
return try {
60+
decrypt()
61+
} catch (e: Exception) {
62+
Log.d("MESSAGE_V3", "discarding message that failed to decrypt", e)
63+
null
64+
}
65+
}
66+
4167
fun decrypt(): DecryptedMessage {
4268
return DecryptedMessage(
4369
id = id.toHex(),
4470
topic = Topic.groupMessage(convoId.toHex()).description,
4571
encodedContent = decode().encodedContent,
4672
senderAddress = senderAddress,
47-
sentAt = Date()
73+
sentAt = Date(),
4874
)
4975
}
5076
}

library/src/main/java/org/xmtp/android/library/messages/DecryptedMessage.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ data class DecryptedMessage(
88
var encodedContent: EncodedContent,
99
var senderAddress: String,
1010
var sentAt: Date,
11-
var topic: String = ""
11+
var topic: String = "",
1212
)

0 commit comments

Comments
 (0)