From bded2b73022822572d2cda3aafe976fbe3ea9e12 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 4 Nov 2024 13:30:06 -0800 Subject: [PATCH 1/2] Dual Send 1to1 Conversations in V3 (#306) * bump version * get on the latest bindings * add the defaults * bump version * Fix kt lint error that was blocking build * allow block number to be optional * bump version again * write a test for it * check versions * functions for mls dms * setup the logic for finding existing conversations * make a more useful interface for V3 * probably just want the bytes that were passed directly * start the testing file for it * bump * bump the lib as well * add real smart contract wallet test * add the binary files * need to fix the signature issue * dump the new schema * get on the latest version of libxmtp * make the signing key optional * write up the logic for new conversation * get all of the conversations functions inline git st * reorganize the file * dual send * add ordering to the list methods * fix up a few lint issues * do nothing if dual sending errors * clean up some log * remove extra check * test create or build * add involved tests * more tests * more better tests * really involved dm tests * get all dm tests passing * get the streaming working * a small test tweak * add some logic for only sending v3 streams if no associated v2 one exisits * getting closer on these streams * small tweaks * make the signing key optional * get on the latest version * new binaries * a few more tweaks to the sign functions * maybe getting closer * Fix failing SCW test * small log reminder * Fix anvil command * dump the bindings again * update the client to create and a seperate to build * get the tests cleaned up * remove the read me * dumpt he v * make optional * get all the tests working * fix the linter * rename * pull the pieces for just dms * get all the tests passing * add tests to make sure dms are not leaking * fix up the linter * add back the dual sending parts * add the logs * handle new conversation for hybrid clients * add tests * fix up the lint --------- Co-authored-by: koleok Co-authored-by: cameronvoell Co-authored-by: Nicholas Molnar <65710+neekolas@users.noreply.github.com> --- .../org/xmtp/android/library/GroupTest.kt | 40 +++++++++++++++++++ .../xmtp/android/library/ConversationV1.kt | 12 +++++- .../xmtp/android/library/ConversationV2.kt | 12 +++++- .../org/xmtp/android/library/Conversations.kt | 15 ++++--- .../xmtp/android/library/PreparedMessage.kt | 4 +- 5 files changed, 72 insertions(+), 11 deletions(-) diff --git a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt index 09679804b..f3169ffec 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt @@ -75,6 +75,46 @@ class GroupTest { runBlocking { Client().createV3(account = davonV3Wallet, options = options) } } + @Test + fun testsCanDualSendConversations() { + val v2Convo = runBlocking { alixClient.conversations.newConversation(bo.walletAddress) } + runBlocking { + alixClient.conversations.syncConversations() + boClient.conversations.syncConversations() + } + val alixDm = runBlocking { alixClient.findDm(bo.walletAddress) } + val boDm = runBlocking { boClient.findDm(alix.walletAddress) } + + assertEquals(alixDm?.id, boDm?.id) + assertEquals(runBlocking { alixClient.conversations.list().size }, 1) + assertEquals(runBlocking { alixClient.conversations.listDms().size }, 1) + assertEquals(runBlocking { boClient.conversations.listDms().size }, 1) + assertEquals(runBlocking { boClient.conversations.list().size }, 1) + assertEquals(v2Convo.topic, runBlocking { boClient.conversations.list().first().topic }) + } + + @Test + fun testsCanDualSendMessages() { + val alixV2Convo = runBlocking { alixClient.conversations.newConversation(bo.walletAddress) } + val boV2Convo = runBlocking { boClient.conversations.list().first() } + runBlocking { boClient.conversations.syncConversations() } + val alixDm = runBlocking { alixClient.findDm(bo.walletAddress) } + val boDm = runBlocking { boClient.findDm(alix.walletAddress) } + + runBlocking { alixV2Convo.send("first") } + runBlocking { boV2Convo.send("second") } + + runBlocking { + alixDm?.sync() + boDm?.sync() + } + + assertEquals(runBlocking { alixV2Convo.messages().size }, 2) + assertEquals(runBlocking { alixV2Convo.messages().size }, runBlocking { boV2Convo.messages().size }) + assertEquals(boDm?.messages()?.size, 2) + assertEquals(alixDm?.messages()?.size, 3) // We send the group membership update to the dm + } + @Test fun testCanCreateAGroupWithDefaultPermissions() { val boGroup = runBlocking { diff --git a/library/src/main/java/org/xmtp/android/library/ConversationV1.kt b/library/src/main/java/org/xmtp/android/library/ConversationV1.kt index 2d031b5fe..ef0cf66d2 100644 --- a/library/src/main/java/org/xmtp/android/library/ConversationV1.kt +++ b/library/src/main/java/org/xmtp/android/library/ConversationV1.kt @@ -195,6 +195,16 @@ data class ConversationV1( } suspend fun send(prepared: PreparedMessage): String { + if (client.v3Client != null) { + try { + val dm = client.conversations.findOrCreateDm(peerAddress) + prepared.encodedContent?.let { + dm.send(it) + } + } catch (e: Exception) { + Log.e("ConversationV1 send", e.message.toString()) + } + } client.publish(envelopes = prepared.envelopes) if (client.contacts.consentList.state(address = peerAddress) == ConsentState.UNKNOWN) { client.contacts.allow(addresses = listOf(peerAddress)) @@ -269,7 +279,7 @@ data class ConversationV1( ) client.contacts.hasIntroduced[peerAddress] = true } - return PreparedMessage(envelopes) + return PreparedMessage(envelopes, encodedContent) } private fun generateId(envelope: Envelope): String = diff --git a/library/src/main/java/org/xmtp/android/library/ConversationV2.kt b/library/src/main/java/org/xmtp/android/library/ConversationV2.kt index 981934d8f..90a1b80d0 100644 --- a/library/src/main/java/org/xmtp/android/library/ConversationV2.kt +++ b/library/src/main/java/org/xmtp/android/library/ConversationV2.kt @@ -200,6 +200,16 @@ data class ConversationV2( } suspend fun send(prepared: PreparedMessage): String { + if (client.v3Client != null) { + try { + val dm = client.conversations.findOrCreateDm(peerAddress) + prepared.encodedContent?.let { + dm.send(it) + } + } catch (e: Exception) { + Log.e("ConversationV1 send", e.message.toString()) + } + } client.publish(envelopes = prepared.envelopes) if (client.contacts.consentList.state(address = peerAddress) == ConsentState.UNKNOWN) { client.contacts.allow(addresses = listOf(peerAddress)) @@ -270,7 +280,7 @@ data class ConversationV2( timestamp = Date(), message = MessageBuilder.buildFromMessageV2(v2 = message.messageV2).toByteArray(), ) - return PreparedMessage(listOf(envelope)) + return PreparedMessage(listOf(envelope), encodedContent) } private fun generateId(envelope: Envelope): String = diff --git a/library/src/main/java/org/xmtp/android/library/Conversations.kt b/library/src/main/java/org/xmtp/android/library/Conversations.kt index a346cc710..8c9b65dcd 100644 --- a/library/src/main/java/org/xmtp/android/library/Conversations.kt +++ b/library/src/main/java/org/xmtp/android/library/Conversations.kt @@ -193,7 +193,6 @@ data class Conversations( } suspend fun findOrCreateDm(peerAddress: String): Dm { - if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") if (peerAddress.lowercase() == client.address.lowercase()) { throw XMTPException("Recipient is sender") } @@ -300,6 +299,13 @@ data class Conversations( client.contacts.allow(addresses = listOf(peerAddress)) val conversation = Conversation.V2(conversationV2) conversationsByTopic[conversation.topic] = conversation + if (client.v3Client != null) { + try { + client.conversations.findOrCreateDm(peerAddress) + } catch (e: Exception) { + Log.e("newConversation", e.message.toString()) + } + } return conversation } @@ -326,7 +332,6 @@ data class Conversations( before: Date? = null, limit: Int? = null, ): List { - if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") val ffiDms = libXMTPConversations?.listDms( opts = FfiListConversationsOptions( after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), @@ -347,9 +352,6 @@ data class Conversations( order: ConversationOrder = ConversationOrder.CREATED_AT, consentState: ConsentState? = null, ): List { - if (client.hasV2Client) - throw XMTPException("Only supported for V3 only clients.") - val ffiConversations = libXMTPConversations?.list( FfiListConversationsOptions( after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), @@ -553,7 +555,6 @@ data class Conversations( } fun streamConversations(): Flow = callbackFlow { - if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") val conversationCallback = object : FfiConversationCallback { override fun onConversation(conversation: FfiConversation) { if (conversation.groupMetadata().conversationType() == "dm") { @@ -656,7 +657,6 @@ data class Conversations( } fun streamAllConversationMessages(): Flow = callbackFlow { - if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") val messageCallback = object : FfiMessageCallback { override fun onMessage(message: FfiMessage) { val conversation = client.findConversation(message.convoId.toHex()) @@ -684,7 +684,6 @@ data class Conversations( } fun streamAllConversationDecryptedMessages(): Flow = callbackFlow { - if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") val messageCallback = object : FfiMessageCallback { override fun onMessage(message: FfiMessage) { val conversation = client.findConversation(message.convoId.toHex()) diff --git a/library/src/main/java/org/xmtp/android/library/PreparedMessage.kt b/library/src/main/java/org/xmtp/android/library/PreparedMessage.kt index 6bfc2cf48..e2f762dd1 100644 --- a/library/src/main/java/org/xmtp/android/library/PreparedMessage.kt +++ b/library/src/main/java/org/xmtp/android/library/PreparedMessage.kt @@ -3,6 +3,7 @@ package org.xmtp.android.library import org.web3j.crypto.Hash import org.xmtp.android.library.messages.Envelope import org.xmtp.proto.message.api.v1.MessageApiOuterClass.PublishRequest +import org.xmtp.proto.message.contents.Content.EncodedContent // This houses a fully prepared message that can be published // as soon as the API client has connectivity. @@ -14,7 +15,8 @@ data class PreparedMessage( // The first envelope should send the message to the conversation itself. // Any more are for required intros/invites etc. // A client can just publish these when it has connectivity. - val envelopes: List + val envelopes: List, + val encodedContent: EncodedContent? = null ) { companion object { fun fromSerializedData(data: ByteArray): PreparedMessage { From 3b17b2810ac30e330ed945e7f75a1171ded37199 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 4 Nov 2024 14:47:35 -0800 Subject: [PATCH 2/2] Fix pagination not being respected in group messages (#318) * add a test for it * update to nanoseconds * fix up linter issue --- .../xmtp/android/library/ConversationTest.kt | 9 +++- .../org/xmtp/android/library/GroupTest.kt | 24 +++++++++ .../android/library/LocalInstrumentedTest.kt | 15 +++++- .../org/xmtp/android/library/Conversation.kt | 51 +++++++++++++------ .../main/java/org/xmtp/android/library/Dm.kt | 18 +++---- .../java/org/xmtp/android/library/Group.kt | 18 +++---- .../xmtp/android/library/libxmtp/MessageV3.kt | 3 ++ 7 files changed, 100 insertions(+), 38 deletions(-) diff --git a/library/src/androidTest/java/org/xmtp/android/library/ConversationTest.kt b/library/src/androidTest/java/org/xmtp/android/library/ConversationTest.kt index c520796b7..c8b8a6afb 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ConversationTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ConversationTest.kt @@ -45,6 +45,8 @@ import org.xmtp.proto.message.contents.Invitation import org.xmtp.proto.message.contents.Invitation.InvitationV1.Context import java.nio.charset.StandardCharsets import java.util.Date +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.DurationUnit @RunWith(AndroidJUnit4::class) class ConversationTest { @@ -418,7 +420,12 @@ class ConversationTest { val messages = aliceConversation.messages(limit = 1) assertEquals(1, messages.size) assertEquals("hey alice 3", messages[0].body) - val messages2 = aliceConversation.messages(limit = 1, after = date) + val messages2 = aliceConversation.messages( + limit = 1, + afterNs = date.time.nanoseconds.toLong( + DurationUnit.NANOSECONDS + ) + ) assertEquals(1, messages2.size) assertEquals("hey alice 3", messages2[0].body) val messagesAsc = diff --git a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt index f3169ffec..3b3d23a1b 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt @@ -585,6 +585,30 @@ class GroupTest { assertEquals(sameGroup.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 2) } + @Test + fun testCanListGroupMessagesAfter() { + val group = runBlocking { boClient.conversations.newGroup(listOf(alix.walletAddress)) } + val messageId = runBlocking { + group.send("howdy") + group.send("gm") + } + val message = boClient.findMessage(messageId) + assertEquals(group.messages().size, 3) + assertEquals(group.messages(afterNs = message?.sentAtNs).size, 0) + runBlocking { + group.send("howdy") + group.send("gm") + } + assertEquals(group.messages().size, 5) + assertEquals(group.messages(afterNs = message?.sentAtNs).size, 2) + + runBlocking { alixClient.conversations.syncConversations() } + val sameGroup = runBlocking { alixClient.conversations.listGroups().last() } + runBlocking { sameGroup.sync() } + assertEquals(sameGroup.messages().size, 4) + assertEquals(sameGroup.messages(afterNs = message?.sentAtNs).size, 2) + } + @Test fun testCanSendContentTypesToGroup() { Client.register(codec = ReactionCodec()) diff --git a/library/src/androidTest/java/org/xmtp/android/library/LocalInstrumentedTest.kt b/library/src/androidTest/java/org/xmtp/android/library/LocalInstrumentedTest.kt index c636fde56..21c1aa060 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/LocalInstrumentedTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/LocalInstrumentedTest.kt @@ -29,6 +29,8 @@ import org.xmtp.proto.message.contents.PrivateKeyOuterClass import org.xmtp.proto.message.contents.PrivateKeyOuterClass.PrivateKeyBundle import uniffi.xmtpv3.createV2Client import java.util.Date +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.DurationUnit @RunWith(AndroidJUnit4::class) class LocalInstrumentedTest { @@ -163,10 +165,19 @@ class LocalInstrumentedTest { assertEquals(2, messagesLimit.size) val nowMessage = messages[0] assertEquals("now", nowMessage.body) - val messages2 = convo.messages(limit = 1, before = nowMessage.sent) + val messages2 = convo.messages( + limit = 1, + beforeNs = nowMessage.sent.time.nanoseconds.toLong( + DurationUnit.NANOSECONDS + ) + ) val tenSecondsAgoMessage = messages2[0] assertEquals("now first", tenSecondsAgoMessage.body) - val messages3 = convo.messages(after = tenSecondsAgoMessage.sent) + val messages3 = convo.messages( + afterNs = tenSecondsAgoMessage.sent.time.nanoseconds.toLong( + DurationUnit.NANOSECONDS + ) + ) val nowMessage2 = messages3[0] assertEquals("now", nowMessage2.body) val messagesAsc = diff --git a/library/src/main/java/org/xmtp/android/library/Conversation.kt b/library/src/main/java/org/xmtp/android/library/Conversation.kt index 5a701bb4f..fa48a04a3 100644 --- a/library/src/main/java/org/xmtp/android/library/Conversation.kt +++ b/library/src/main/java/org/xmtp/android/library/Conversation.kt @@ -169,50 +169,71 @@ sealed class Conversation { */ suspend fun messages( limit: Int? = null, - before: Date? = null, - after: Date? = null, + beforeNs: Long? = null, + afterNs: Long? = null, direction: PagingInfoSortDirection = MessageApiOuterClass.SortDirection.SORT_DIRECTION_DESCENDING, ): List { return when (this) { is V1 -> conversationV1.messages( limit = limit, - before = before, - after = after, + before = beforeNs?.let { Date(it / 1_000_000) }, + after = afterNs?.let { Date(it / 1_000_000) }, direction = direction, ) is V2 -> conversationV2.messages( limit = limit, - before = before, - after = after, + before = beforeNs?.let { Date(it / 1_000_000) }, + after = afterNs?.let { Date(it / 1_000_000) }, direction = direction, ) is Group -> { group.messages( limit = limit, - before = before, - after = after, + beforeNs = beforeNs, + afterNs = afterNs, direction = direction, ) } - is Dm -> dm.messages(limit, before, after, direction) + is Dm -> dm.messages(limit, beforeNs, afterNs, direction) } } suspend fun decryptedMessages( limit: Int? = null, - before: Date? = null, - after: Date? = null, + beforeNs: Long? = null, + afterNs: Long? = null, direction: PagingInfoSortDirection = MessageApiOuterClass.SortDirection.SORT_DIRECTION_DESCENDING, ): List { return when (this) { - is V1 -> conversationV1.decryptedMessages(limit, before, after, direction) - is V2 -> conversationV2.decryptedMessages(limit, before, after, direction) - is Group -> group.decryptedMessages(limit, before, after, direction) - is Dm -> dm.decryptedMessages(limit, before, after, direction) + is V1 -> conversationV1.decryptedMessages( + limit = limit, + before = beforeNs?.let { Date(it / 1_000_000) }, + after = afterNs?.let { Date(it / 1_000_000) }, + direction = direction, + ) + + is V2 -> + conversationV2.decryptedMessages( + limit = limit, + before = beforeNs?.let { Date(it / 1_000_000) }, + after = afterNs?.let { Date(it / 1_000_000) }, + direction = direction, + ) + + is Group -> { + group.decryptedMessages( + limit = limit, + beforeNs = beforeNs, + afterNs = afterNs, + direction = direction, + ) + } + + is Dm -> dm.decryptedMessages(limit, beforeNs, afterNs, direction) } } diff --git a/library/src/main/java/org/xmtp/android/library/Dm.kt b/library/src/main/java/org/xmtp/android/library/Dm.kt index 64ace0606..a1e3c785a 100644 --- a/library/src/main/java/org/xmtp/android/library/Dm.kt +++ b/library/src/main/java/org/xmtp/android/library/Dm.kt @@ -23,8 +23,6 @@ import uniffi.xmtpv3.FfiMessage import uniffi.xmtpv3.FfiMessageCallback import uniffi.xmtpv3.FfiSubscribeException import java.util.Date -import kotlin.time.Duration.Companion.nanoseconds -import kotlin.time.DurationUnit class Dm(val client: Client, private val libXMTPGroup: FfiConversation) { val id: String @@ -103,15 +101,15 @@ class Dm(val client: Client, private val libXMTPGroup: FfiConversation) { fun messages( limit: Int? = null, - before: Date? = null, - after: Date? = null, + beforeNs: Long? = null, + afterNs: Long? = null, direction: PagingInfoSortDirection = SortDirection.SORT_DIRECTION_DESCENDING, deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.ALL, ): List { return libXMTPGroup.findMessages( opts = FfiListMessagesOptions( - sentBeforeNs = before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), - sentAfterNs = after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + sentBeforeNs = beforeNs, + sentAfterNs = afterNs, limit = limit?.toLong(), deliveryStatus = when (deliveryStatus) { MessageDeliveryStatus.PUBLISHED -> FfiDeliveryStatus.PUBLISHED @@ -131,15 +129,15 @@ class Dm(val client: Client, private val libXMTPGroup: FfiConversation) { fun decryptedMessages( limit: Int? = null, - before: Date? = null, - after: Date? = null, + beforeNs: Long? = null, + afterNs: Long? = null, direction: PagingInfoSortDirection = SortDirection.SORT_DIRECTION_DESCENDING, deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.ALL, ): List { return libXMTPGroup.findMessages( opts = FfiListMessagesOptions( - sentBeforeNs = before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), - sentAfterNs = after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + sentBeforeNs = beforeNs, + sentAfterNs = afterNs, limit = limit?.toLong(), deliveryStatus = when (deliveryStatus) { MessageDeliveryStatus.PUBLISHED -> FfiDeliveryStatus.PUBLISHED diff --git a/library/src/main/java/org/xmtp/android/library/Group.kt b/library/src/main/java/org/xmtp/android/library/Group.kt index eca6771d9..3fbfbe190 100644 --- a/library/src/main/java/org/xmtp/android/library/Group.kt +++ b/library/src/main/java/org/xmtp/android/library/Group.kt @@ -29,8 +29,6 @@ import uniffi.xmtpv3.FfiSubscribeException import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.PermissionOption import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.PermissionPolicySet import java.util.Date -import kotlin.time.Duration.Companion.nanoseconds -import kotlin.time.DurationUnit class Group(val client: Client, private val libXMTPGroup: FfiConversation) { val id: String @@ -121,15 +119,15 @@ class Group(val client: Client, private val libXMTPGroup: FfiConversation) { fun messages( limit: Int? = null, - before: Date? = null, - after: Date? = null, + beforeNs: Long? = null, + afterNs: Long? = null, direction: PagingInfoSortDirection = SORT_DIRECTION_DESCENDING, deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.ALL, ): List { return libXMTPGroup.findMessages( opts = FfiListMessagesOptions( - sentBeforeNs = before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), - sentAfterNs = after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + sentBeforeNs = beforeNs, + sentAfterNs = afterNs, limit = limit?.toLong(), deliveryStatus = when (deliveryStatus) { MessageDeliveryStatus.PUBLISHED -> FfiDeliveryStatus.PUBLISHED @@ -149,15 +147,15 @@ class Group(val client: Client, private val libXMTPGroup: FfiConversation) { fun decryptedMessages( limit: Int? = null, - before: Date? = null, - after: Date? = null, + beforeNs: Long? = null, + afterNs: Long? = null, direction: PagingInfoSortDirection = SORT_DIRECTION_DESCENDING, deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.ALL, ): List { return libXMTPGroup.findMessages( opts = FfiListMessagesOptions( - sentBeforeNs = before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), - sentAfterNs = after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), + sentBeforeNs = beforeNs, + sentAfterNs = afterNs, limit = limit?.toLong(), deliveryStatus = when (deliveryStatus) { MessageDeliveryStatus.PUBLISHED -> FfiDeliveryStatus.PUBLISHED diff --git a/library/src/main/java/org/xmtp/android/library/libxmtp/MessageV3.kt b/library/src/main/java/org/xmtp/android/library/libxmtp/MessageV3.kt index 631fd2e5f..f26ae2fd5 100644 --- a/library/src/main/java/org/xmtp/android/library/libxmtp/MessageV3.kt +++ b/library/src/main/java/org/xmtp/android/library/libxmtp/MessageV3.kt @@ -29,6 +29,9 @@ data class MessageV3(val client: Client, private val libXMTPMessage: FfiMessage) val sentAt: Date get() = Date(libXMTPMessage.sentAtNs / 1_000_000) + val sentAtNs: Long + get() = libXMTPMessage.sentAtNs + val deliveryStatus: MessageDeliveryStatus get() = when (libXMTPMessage.deliveryStatus) { FfiDeliveryStatus.UNPUBLISHED -> MessageDeliveryStatus.UNPUBLISHED