Skip to content

Commit bded2b7

Browse files
nplastererKoleokcameronvoellneekolas
authored
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 <kylechamberlain@proton.me> Co-authored-by: cameronvoell <cameronvoell@users.noreply.github.com> Co-authored-by: Nicholas Molnar <65710+neekolas@users.noreply.github.com>
1 parent a39b6c2 commit bded2b7

File tree

5 files changed

+72
-11
lines changed

5 files changed

+72
-11
lines changed

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

+40
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,46 @@ class GroupTest {
7575
runBlocking { Client().createV3(account = davonV3Wallet, options = options) }
7676
}
7777

78+
@Test
79+
fun testsCanDualSendConversations() {
80+
val v2Convo = runBlocking { alixClient.conversations.newConversation(bo.walletAddress) }
81+
runBlocking {
82+
alixClient.conversations.syncConversations()
83+
boClient.conversations.syncConversations()
84+
}
85+
val alixDm = runBlocking { alixClient.findDm(bo.walletAddress) }
86+
val boDm = runBlocking { boClient.findDm(alix.walletAddress) }
87+
88+
assertEquals(alixDm?.id, boDm?.id)
89+
assertEquals(runBlocking { alixClient.conversations.list().size }, 1)
90+
assertEquals(runBlocking { alixClient.conversations.listDms().size }, 1)
91+
assertEquals(runBlocking { boClient.conversations.listDms().size }, 1)
92+
assertEquals(runBlocking { boClient.conversations.list().size }, 1)
93+
assertEquals(v2Convo.topic, runBlocking { boClient.conversations.list().first().topic })
94+
}
95+
96+
@Test
97+
fun testsCanDualSendMessages() {
98+
val alixV2Convo = runBlocking { alixClient.conversations.newConversation(bo.walletAddress) }
99+
val boV2Convo = runBlocking { boClient.conversations.list().first() }
100+
runBlocking { boClient.conversations.syncConversations() }
101+
val alixDm = runBlocking { alixClient.findDm(bo.walletAddress) }
102+
val boDm = runBlocking { boClient.findDm(alix.walletAddress) }
103+
104+
runBlocking { alixV2Convo.send("first") }
105+
runBlocking { boV2Convo.send("second") }
106+
107+
runBlocking {
108+
alixDm?.sync()
109+
boDm?.sync()
110+
}
111+
112+
assertEquals(runBlocking { alixV2Convo.messages().size }, 2)
113+
assertEquals(runBlocking { alixV2Convo.messages().size }, runBlocking { boV2Convo.messages().size })
114+
assertEquals(boDm?.messages()?.size, 2)
115+
assertEquals(alixDm?.messages()?.size, 3) // We send the group membership update to the dm
116+
}
117+
78118
@Test
79119
fun testCanCreateAGroupWithDefaultPermissions() {
80120
val boGroup = runBlocking {

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,16 @@ data class ConversationV1(
195195
}
196196

197197
suspend fun send(prepared: PreparedMessage): String {
198+
if (client.v3Client != null) {
199+
try {
200+
val dm = client.conversations.findOrCreateDm(peerAddress)
201+
prepared.encodedContent?.let {
202+
dm.send(it)
203+
}
204+
} catch (e: Exception) {
205+
Log.e("ConversationV1 send", e.message.toString())
206+
}
207+
}
198208
client.publish(envelopes = prepared.envelopes)
199209
if (client.contacts.consentList.state(address = peerAddress) == ConsentState.UNKNOWN) {
200210
client.contacts.allow(addresses = listOf(peerAddress))
@@ -269,7 +279,7 @@ data class ConversationV1(
269279
)
270280
client.contacts.hasIntroduced[peerAddress] = true
271281
}
272-
return PreparedMessage(envelopes)
282+
return PreparedMessage(envelopes, encodedContent)
273283
}
274284

275285
private fun generateId(envelope: Envelope): String =

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,16 @@ data class ConversationV2(
200200
}
201201

202202
suspend fun send(prepared: PreparedMessage): String {
203+
if (client.v3Client != null) {
204+
try {
205+
val dm = client.conversations.findOrCreateDm(peerAddress)
206+
prepared.encodedContent?.let {
207+
dm.send(it)
208+
}
209+
} catch (e: Exception) {
210+
Log.e("ConversationV1 send", e.message.toString())
211+
}
212+
}
203213
client.publish(envelopes = prepared.envelopes)
204214
if (client.contacts.consentList.state(address = peerAddress) == ConsentState.UNKNOWN) {
205215
client.contacts.allow(addresses = listOf(peerAddress))
@@ -270,7 +280,7 @@ data class ConversationV2(
270280
timestamp = Date(),
271281
message = MessageBuilder.buildFromMessageV2(v2 = message.messageV2).toByteArray(),
272282
)
273-
return PreparedMessage(listOf(envelope))
283+
return PreparedMessage(listOf(envelope), encodedContent)
274284
}
275285

276286
private fun generateId(envelope: Envelope): String =

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

+7-8
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ data class Conversations(
193193
}
194194

195195
suspend fun findOrCreateDm(peerAddress: String): Dm {
196-
if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.")
197196
if (peerAddress.lowercase() == client.address.lowercase()) {
198197
throw XMTPException("Recipient is sender")
199198
}
@@ -300,6 +299,13 @@ data class Conversations(
300299
client.contacts.allow(addresses = listOf(peerAddress))
301300
val conversation = Conversation.V2(conversationV2)
302301
conversationsByTopic[conversation.topic] = conversation
302+
if (client.v3Client != null) {
303+
try {
304+
client.conversations.findOrCreateDm(peerAddress)
305+
} catch (e: Exception) {
306+
Log.e("newConversation", e.message.toString())
307+
}
308+
}
303309
return conversation
304310
}
305311

@@ -326,7 +332,6 @@ data class Conversations(
326332
before: Date? = null,
327333
limit: Int? = null,
328334
): List<Dm> {
329-
if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.")
330335
val ffiDms = libXMTPConversations?.listDms(
331336
opts = FfiListConversationsOptions(
332337
after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
@@ -347,9 +352,6 @@ data class Conversations(
347352
order: ConversationOrder = ConversationOrder.CREATED_AT,
348353
consentState: ConsentState? = null,
349354
): List<Conversation> {
350-
if (client.hasV2Client)
351-
throw XMTPException("Only supported for V3 only clients.")
352-
353355
val ffiConversations = libXMTPConversations?.list(
354356
FfiListConversationsOptions(
355357
after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
@@ -553,7 +555,6 @@ data class Conversations(
553555
}
554556

555557
fun streamConversations(): Flow<Conversation> = callbackFlow {
556-
if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.")
557558
val conversationCallback = object : FfiConversationCallback {
558559
override fun onConversation(conversation: FfiConversation) {
559560
if (conversation.groupMetadata().conversationType() == "dm") {
@@ -656,7 +657,6 @@ data class Conversations(
656657
}
657658

658659
fun streamAllConversationMessages(): Flow<DecodedMessage> = callbackFlow {
659-
if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.")
660660
val messageCallback = object : FfiMessageCallback {
661661
override fun onMessage(message: FfiMessage) {
662662
val conversation = client.findConversation(message.convoId.toHex())
@@ -684,7 +684,6 @@ data class Conversations(
684684
}
685685

686686
fun streamAllConversationDecryptedMessages(): Flow<DecryptedMessage> = callbackFlow {
687-
if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.")
688687
val messageCallback = object : FfiMessageCallback {
689688
override fun onMessage(message: FfiMessage) {
690689
val conversation = client.findConversation(message.convoId.toHex())

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.xmtp.android.library
33
import org.web3j.crypto.Hash
44
import org.xmtp.android.library.messages.Envelope
55
import org.xmtp.proto.message.api.v1.MessageApiOuterClass.PublishRequest
6+
import org.xmtp.proto.message.contents.Content.EncodedContent
67

78
// This houses a fully prepared message that can be published
89
// as soon as the API client has connectivity.
@@ -14,7 +15,8 @@ data class PreparedMessage(
1415
// The first envelope should send the message to the conversation itself.
1516
// Any more are for required intros/invites etc.
1617
// A client can just publish these when it has connectivity.
17-
val envelopes: List<Envelope>
18+
val envelopes: List<Envelope>,
19+
val encodedContent: EncodedContent? = null
1820
) {
1921
companion object {
2022
fun fromSerializedData(data: ByteArray): PreparedMessage {

0 commit comments

Comments
 (0)