Skip to content

Commit

Permalink
Dual Send 1to1 Conversations in V3 (#306)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: cameronvoell <[email protected]>
Co-authored-by: Nicholas Molnar <[email protected]>
  • Loading branch information
4 people authored Nov 4, 2024
1 parent a39b6c2 commit bded2b7
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 11 deletions.
40 changes: 40 additions & 0 deletions library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 11 additions & 1 deletion library/src/main/java/org/xmtp/android/library/ConversationV1.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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 =
Expand Down
12 changes: 11 additions & 1 deletion library/src/main/java/org/xmtp/android/library/ConversationV2.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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 =
Expand Down
15 changes: 7 additions & 8 deletions library/src/main/java/org/xmtp/android/library/Conversations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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
}

Expand All @@ -326,7 +332,6 @@ data class Conversations(
before: Date? = null,
limit: Int? = null,
): List<Dm> {
if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.")
val ffiDms = libXMTPConversations?.listDms(
opts = FfiListConversationsOptions(
after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
Expand All @@ -347,9 +352,6 @@ data class Conversations(
order: ConversationOrder = ConversationOrder.CREATED_AT,
consentState: ConsentState? = null,
): List<Conversation> {
if (client.hasV2Client)
throw XMTPException("Only supported for V3 only clients.")

val ffiConversations = libXMTPConversations?.list(
FfiListConversationsOptions(
after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
Expand Down Expand Up @@ -553,7 +555,6 @@ data class Conversations(
}

fun streamConversations(): Flow<Conversation> = 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") {
Expand Down Expand Up @@ -656,7 +657,6 @@ data class Conversations(
}

fun streamAllConversationMessages(): Flow<DecodedMessage> = 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())
Expand Down Expand Up @@ -684,7 +684,6 @@ data class Conversations(
}

fun streamAllConversationDecryptedMessages(): Flow<DecryptedMessage> = 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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<Envelope>
val envelopes: List<Envelope>,
val encodedContent: EncodedContent? = null
) {
companion object {
fun fromSerializedData(data: ByteArray): PreparedMessage {
Expand Down

0 comments on commit bded2b7

Please sign in to comment.