diff --git a/library/src/androidTest/java/org/xmtp/android/library/ContactsTest.kt b/library/src/androidTest/java/org/xmtp/android/library/ContactsTest.kt index dcbc0d9c1..49266715c 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ContactsTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ContactsTest.kt @@ -27,19 +27,6 @@ class ContactsTest { assertEquals(contactBundle?.walletAddress, fixtures.bob.walletAddress) } - @Test - fun testCachesContacts() { - val fixtures = fixtures() - runBlocking { fixtures.bobClient.ensureUserContactPublished() } - // Look up the first time - fixtures.aliceClient.contacts.find(fixtures.bob.walletAddress) - fixtures.fakeApiClient.assertNoQuery { - val contactBundle = fixtures.aliceClient.contacts.find(fixtures.bob.walletAddress) - assertEquals(contactBundle?.walletAddress, fixtures.bob.walletAddress) - } - assert(fixtures.aliceClient.contacts.has(fixtures.bob.walletAddress)) - } - @Test fun testAllowAddress() { val fixtures = fixtures() 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 9ffa5784f..9f7a82067 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ConversationTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ConversationTest.kt @@ -49,7 +49,6 @@ import java.util.Date @RunWith(AndroidJUnit4::class) class ConversationTest { - lateinit var fakeApiClient: FakeApiClient lateinit var aliceWallet: PrivateKeyBuilder lateinit var bobWallet: PrivateKeyBuilder lateinit var alice: PrivateKey @@ -65,7 +64,6 @@ class ConversationTest { alice = fixtures.alice bobWallet = fixtures.bobAccount bob = fixtures.bob - fakeApiClient = fixtures.fakeApiClient aliceClient = fixtures.aliceClient bobClient = fixtures.bobClient } @@ -123,14 +121,7 @@ class ConversationTest { runBlocking { aliceClient.conversations.newConversation(bob.walletAddress) } assertEquals(conversation.peerAddress, bob.walletAddress) assertEquals(conversation.createdAt, someTimeAgo) - val existingMessages = fakeApiClient.published.size conversation = runBlocking { bobClient.conversations.newConversation(alice.walletAddress) } - - assertEquals( - "published more messages when we shouldn't have", - existingMessages, - fakeApiClient.published.size, - ) assertEquals(conversation.peerAddress, alice.walletAddress) assertEquals(conversation.createdAt, someTimeAgo) } @@ -144,14 +135,6 @@ class ConversationTest { ) } var conversation: Conversation? = null - fakeApiClient.assertNoPublish { - runBlocking { - conversation = bobClient.conversations.newConversation( - alice.walletAddress, - context = InvitationV1ContextBuilder.buildFromConversation("http://example.com/2"), - ) - } - } assertEquals( "made new conversation instead of using existing one", conversation!!.topic, @@ -408,15 +391,6 @@ class ConversationTest { } } - @Test - fun testCanUseCachedConversation() { - runBlocking { bobClient.conversations.newConversation(alice.walletAddress) } - - fakeApiClient.assertNoQuery { - runBlocking { bobClient.conversations.newConversation(alice.walletAddress) } - } - } - @Test @Ignore("Rust seems to be Flaky with V1") fun testCanPaginateV1Messages() { @@ -496,9 +470,18 @@ class ConversationTest { (topic.equals(steveConversation.topic) || topic.equals(bobConversation.topic)) } assertEquals(3, messages.size) - assertTrue("isSteveOrBobConversation message 0", isSteveOrBobConversation(messages[0].topic)) - assertTrue("isSteveOrBobConversation message 1", isSteveOrBobConversation(messages[1].topic)) - assertTrue("isSteveOrBobConversation message 2", isSteveOrBobConversation(messages[2].topic)) + assertTrue( + "isSteveOrBobConversation message 0", + isSteveOrBobConversation(messages[0].topic) + ) + assertTrue( + "isSteveOrBobConversation message 1", + isSteveOrBobConversation(messages[1].topic) + ) + assertTrue( + "isSteveOrBobConversation message 2", + isSteveOrBobConversation(messages[2].topic) + ) } @Test @@ -778,7 +761,6 @@ class ConversationTest { }.build() val client = Client().create(account = PrivateKeyBuilder(key)) - assertEquals(client.apiClient.environment, XMTPEnvironment.DEV) runBlocking { val conversations = client.conversations.list() assertEquals(1, conversations.size) @@ -827,7 +809,10 @@ class ConversationTest { val isAllowed = bobConversation.consentState() == ConsentState.ALLOWED // Conversations you start should start as allowed assertTrue("Bob convo should be allowed", isAllowed) - assertTrue("Bob contacts should be allowed", bobClient.contacts.isAllowed(alice.walletAddress)) + assertTrue( + "Bob contacts should be allowed", + bobClient.contacts.isAllowed(alice.walletAddress) + ) runBlocking { bobClient.contacts.deny(listOf(alice.walletAddress)) @@ -896,12 +881,24 @@ class ConversationTest { bobClient.contacts.refreshConsentList() Thread.sleep(1000) assertEquals(bobClient.contacts.consentList.entries.size, 2) - assertTrue("Bob convo should be allowed", bobConversation.consentState() == ConsentState.ALLOWED) - assertTrue("Caro convo should be allowed", caroConversation.consentState() == ConsentState.ALLOWED) + assertTrue( + "Bob convo should be allowed", + bobConversation.consentState() == ConsentState.ALLOWED + ) + assertTrue( + "Caro convo should be allowed", + caroConversation.consentState() == ConsentState.ALLOWED + ) bobClient.contacts.deny(listOf(alice.walletAddress, fixtures.caro.walletAddress)) assertEquals(bobClient.contacts.consentList.entries.size, 2) - assertTrue("Bob convo should be denied", bobConversation.consentState() == ConsentState.DENIED) - assertTrue("Caro convo should be denied", caroConversation.consentState() == ConsentState.DENIED) + assertTrue( + "Bob convo should be denied", + bobConversation.consentState() == ConsentState.DENIED + ) + assertTrue( + "Caro convo should be denied", + caroConversation.consentState() == ConsentState.DENIED + ) } } diff --git a/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt b/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt index 65cf3fb03..205dfb87c 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt @@ -36,7 +36,6 @@ import java.util.Date @RunWith(AndroidJUnit4::class) class ConversationsTest { - lateinit var fakeApiClient: FakeApiClient lateinit var alixWallet: PrivateKeyBuilder lateinit var boWallet: PrivateKeyBuilder lateinit var alix: PrivateKey @@ -53,7 +52,6 @@ class ConversationsTest { alix = fixtures.alice boWallet = fixtures.bobAccount bo = fixtures.bob - fakeApiClient = fixtures.fakeApiClient alixClient = fixtures.aliceClient boClient = fixtures.bobClient caroClient = fixtures.caroClient @@ -128,7 +126,7 @@ class ConversationsTest { runBlocking { boConversation.send(text = "Message $i") } sleep(1000) } - assertEquals(allMessages.size, 5) + assertEquals(5, allMessages.size) val caroConversation = runBlocking { caroClient.conversations.newConversation(alixClient.address) } @@ -139,7 +137,7 @@ class ConversationsTest { sleep(1000) } - assertEquals(allMessages.size, 10) + assertEquals(10, allMessages.size) job.cancel() @@ -158,7 +156,7 @@ class ConversationsTest { sleep(1000) } - assertEquals(allMessages.size, 15) + assertEquals(15, allMessages.size) } @Test diff --git a/library/src/androidTest/java/org/xmtp/android/library/InvitationTest.kt b/library/src/androidTest/java/org/xmtp/android/library/InvitationTest.kt index 9ab706b7a..c78d44cf1 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/InvitationTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/InvitationTest.kt @@ -52,7 +52,6 @@ class InvitationTest { }.build() val client = Client().create(account = PrivateKeyBuilder(key)) - Assert.assertEquals(client.apiClient.environment, XMTPEnvironment.DEV) val conversations = runBlocking { client.conversations.list() } assertEquals(1, conversations.size) val message = runBlocking { conversations[0].messages().firstOrNull() } 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 b6d392bb2..bb8a7412f 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/LocalInstrumentedTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/LocalInstrumentedTest.kt @@ -45,7 +45,6 @@ class LocalInstrumentedTest { ) ) val client = Client().create(aliceWallet, clientOptions) - assertEquals(XMTPEnvironment.LOCAL, client.apiClient.environment) runBlocking { client.publishUserContact() } @@ -71,7 +70,6 @@ class LocalInstrumentedTest { ) } val api = GRPCApiClient( - environment = XMTPEnvironment.LOCAL, rustV2Client = v2Client ) api.setAuthToken(authToken) @@ -107,7 +105,6 @@ class LocalInstrumentedTest { ) } val api = GRPCApiClient( - environment = XMTPEnvironment.LOCAL, rustV2Client = v2Client ) api.setAuthToken(authToken) @@ -126,7 +123,6 @@ class LocalInstrumentedTest { val clientOptions = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) val client = Client().create(account = aliceWallet, options = clientOptions) - assertEquals(XMTPEnvironment.LOCAL, client.apiClient.environment) val contact = client.getUserContact(peerAddress = aliceWallet.address) assertEquals( contact?.v2?.keyBundle?.identityKey?.secp256K1Uncompressed, @@ -774,7 +770,6 @@ class LocalInstrumentedTest { ) } val api = GRPCApiClient( - environment = XMTPEnvironment.LOCAL, rustV2Client = v2Client ) api.setAuthToken(authToken) diff --git a/library/src/androidTest/java/org/xmtp/android/library/MessageTest.kt b/library/src/androidTest/java/org/xmtp/android/library/MessageTest.kt index 0f1912669..341f83496 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/MessageTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/MessageTest.kt @@ -161,7 +161,6 @@ class MessageTest { }.build() val client = Client().create(account = PrivateKeyBuilder(key)) - assertEquals(client.apiClient.environment, XMTPEnvironment.DEV) runBlocking { val convo = client.conversations.list()[0] val message = convo.messages()[0] @@ -196,7 +195,6 @@ class MessageTest { }.build() val client = Client().create(account = PrivateKeyBuilder(key)) - assertEquals(client.apiClient.environment, XMTPEnvironment.DEV) runBlocking { val convo = client.conversations.list()[0] convo.send( @@ -234,7 +232,6 @@ class MessageTest { }.build() }.build() val client = Client().create(account = PrivateKeyBuilder(key)) - assertEquals(client.apiClient.environment, XMTPEnvironment.DEV) val conversations = runBlocking { client.conversations.list() } assertEquals(201, conversations.size) } diff --git a/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt b/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt index 5dc5b96a4..313deae33 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt @@ -17,6 +17,9 @@ import org.xmtp.android.library.messages.Topic import org.xmtp.android.library.messages.toPublicKeyBundle import org.xmtp.android.library.messages.walletAddress import org.xmtp.proto.message.api.v1.MessageApiOuterClass +import uniffi.xmtpv3.FfiV2Subscription +import uniffi.xmtpv3.FfiV2SubscriptionCallback +import uniffi.xmtpv3.NoPointer import java.io.File import java.net.URL import java.util.Date @@ -57,152 +60,6 @@ class FakeWallet : SigningKey { get() = privateKey.walletAddress } -class FakeStreamHolder { - private val flow = MutableSharedFlow() - suspend fun emit(value: Envelope) = flow.emit(value) - fun counts(): Flow = flow -} - -class FakeApiClient : ApiClient { - override val environment: XMTPEnvironment = XMTPEnvironment.LOCAL - private var authToken: String? = null - private val responses: MutableMap> = mutableMapOf() - val published: MutableList = mutableListOf() - var forbiddingQueries = false - private var stream = FakeStreamHolder() - - fun assertNoPublish(callback: () -> Unit) { - val oldCount = published.size - callback() - assertEquals(oldCount, published.size) - } - - fun assertNoQuery(callback: () -> Unit) { - forbiddingQueries = true - callback() - forbiddingQueries = false - } - - fun findPublishedEnvelope(topic: Topic): Envelope? = - findPublishedEnvelope(topic.description) - - fun findPublishedEnvelope(topic: String): Envelope? { - for (envelope in published.reversed()) { - if (envelope.contentTopic == topic) { - return envelope - } - } - return null - } - - override fun setAuthToken(token: String) { - authToken = token - } - - override suspend fun queryTopic( - topic: Topic, - pagination: Pagination?, - ): MessageApiOuterClass.QueryResponse { - return query(topic = topic.description, pagination) - } - - suspend fun send(envelope: Envelope) { - stream.emit(envelope) - } - - override suspend fun envelopes( - topic: String, - pagination: Pagination?, - ): List { - return query(topic = topic, pagination = pagination).envelopesList - } - - override suspend fun batchQuery(requests: List): MessageApiOuterClass.BatchQueryResponse { - val responses = requests.map { - query(it.getContentTopics(0), Pagination(after = Date(it.startTimeNs))) - } - - return MessageApiOuterClass.BatchQueryResponse.newBuilder().also { - it.addAllResponses(responses) - }.build() - } - - override suspend fun query( - topic: String, - pagination: Pagination?, - cursor: MessageApiOuterClass.Cursor?, - ): MessageApiOuterClass.QueryResponse { - var result: MutableList = mutableListOf() - val response = responses.toMutableMap().remove(topic) - if (response != null) { - result.addAll(response) - } - result.addAll( - published.filter { - it.contentTopic == topic - }.reversed() - ) - - val startAt = pagination?.before - if (startAt != null) { - result = result.filter { it.timestampNs < startAt.time } - .sortedBy { it.timestampNs }.toMutableList() - } - val endAt = pagination?.after - if (endAt != null) { - result = result.filter { - it.timestampNs > endAt.time - } - .sortedBy { it.timestampNs }.toMutableList() - } - val limit = pagination?.limit - if (limit != null) { - if (limit == 1) { - val first = result.firstOrNull() - if (first != null) { - result = mutableListOf(first) - } else { - result = mutableListOf() - } - } else { - result = result.take(limit - 1).toMutableList() - } - } - - val direction = pagination?.direction - if (direction != null) { - when (direction) { - MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> { - result = result.reversed().toMutableList() - } - - else -> Unit - } - } - - return QueryResponse.newBuilder().also { - it.addAllEnvelopes(result) - }.build() - } - - override suspend fun publish(envelopes: List) { - for (envelope in envelopes) { - send(envelope) - } - published.addAll(envelopes) - PublishResponse.newBuilder().build() - } - - override suspend fun subscribe(request: Flow): Flow { - val env = stream.counts().first() - - if (request.first().contentTopicsList.contains(env.contentTopic)) { - return flowOf(env) - } - return flowOf() - } -} - data class Fixtures( val aliceAccount: PrivateKeyBuilder, val bobAccount: PrivateKeyBuilder, @@ -211,7 +68,6 @@ data class Fixtures( ClientOptions.Api(XMTPEnvironment.LOCAL, isSecure = false) ), ) { - var fakeApiClient: FakeApiClient = FakeApiClient() var alice: PrivateKey = aliceAccount.getPrivateKey() var aliceClient: Client = Client().create(account = aliceAccount, options = clientOptions) var bob: PrivateKey = bobAccount.getPrivateKey() diff --git a/library/src/main/java/org/xmtp/android/library/ApiClient.kt b/library/src/main/java/org/xmtp/android/library/ApiClient.kt index f68ad94fd..058a04aa2 100644 --- a/library/src/main/java/org/xmtp/android/library/ApiClient.kt +++ b/library/src/main/java/org/xmtp/android/library/ApiClient.kt @@ -40,7 +40,7 @@ interface ApiClient { suspend fun envelopes(topic: String, pagination: Pagination? = null): List suspend fun publish(envelopes: List) suspend fun subscribe( - request: SubscribeRequest, + request: FfiV2SubscribeRequest, callback: FfiV2SubscriptionCallback, ): FfiV2Subscription } @@ -82,7 +82,7 @@ data class GRPCApiClient( fun makeSubscribeRequest( topics: List, - ): SubscribeRequest = SubscribeRequest.newBuilder().addAllContentTopics(topics).build() + ): FfiV2SubscribeRequest = FfiV2SubscribeRequest(topics) } private var authToken: String? = null @@ -146,22 +146,16 @@ data class GRPCApiClient( } override suspend fun subscribe( - request: SubscribeRequest, + request: FfiV2SubscribeRequest, callback: FfiV2SubscriptionCallback, ): FfiV2Subscription { - return rustV2Client.subscribe(subscribeRequestToFFi(request), callback) + return rustV2Client.subscribe(request, callback) } override fun close() { rustV2Client.close() } - private fun subscribeRequestToFFi(request: SubscribeRequest): FfiV2SubscribeRequest { - return FfiV2SubscribeRequest( - contentTopics = request.contentTopicsList, - ) - } - private fun envelopeToFFi(envelope: Envelope): FfiEnvelope { return FfiEnvelope( contentTopic = envelope.contentTopic, diff --git a/library/src/main/java/org/xmtp/android/library/Client.kt b/library/src/main/java/org/xmtp/android/library/Client.kt index c11be70a1..d6c132317 100644 --- a/library/src/main/java/org/xmtp/android/library/Client.kt +++ b/library/src/main/java/org/xmtp/android/library/Client.kt @@ -44,6 +44,7 @@ import org.xmtp.android.library.messages.walletAddress import org.xmtp.proto.message.api.v1.MessageApiOuterClass import org.xmtp.proto.message.api.v1.MessageApiOuterClass.BatchQueryResponse import org.xmtp.proto.message.api.v1.MessageApiOuterClass.QueryRequest +import uniffi.xmtpv3.FfiV2SubscribeRequest import uniffi.xmtpv3.FfiV2Subscription import uniffi.xmtpv3.FfiV2SubscriptionCallback import uniffi.xmtpv3.FfiXmtpClient @@ -517,7 +518,14 @@ class Client() { topics: List, callback: FfiV2SubscriptionCallback, ): FfiV2Subscription { - return apiClient.subscribe(makeSubscribeRequest(topics), callback) + return subscribe2(makeSubscribeRequest(topics), callback) + } + + suspend fun subscribe2( + request: FfiV2SubscribeRequest, + callback: FfiV2SubscriptionCallback, + ): FfiV2Subscription { + return apiClient.subscribe(request, callback) } 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 db6db2790..034f63743 100644 --- a/library/src/main/java/org/xmtp/android/library/Conversations.kt +++ b/library/src/main/java/org/xmtp/android/library/Conversations.kt @@ -49,6 +49,7 @@ import uniffi.xmtpv3.FfiGroup import uniffi.xmtpv3.FfiListConversationsOptions import uniffi.xmtpv3.FfiMessage import uniffi.xmtpv3.FfiMessageCallback +import uniffi.xmtpv3.FfiV2SubscribeRequest import uniffi.xmtpv3.FfiV2SubscriptionCallback import uniffi.xmtpv3.GroupPermissions import java.util.Date @@ -661,6 +662,8 @@ data class Conversations( topics.add(conversation.topic) } + val subscriptionRequest = FfiV2SubscribeRequest(topics) + val subscriptionCallback = object : FfiV2SubscriptionCallback { override fun onMessage(message: FfiEnvelope) { when { @@ -674,6 +677,7 @@ data class Conversations( val conversation = fromInvite(envelope = envelopeFromFFi(message)) conversationsByTopic[conversation.topic] = conversation topics.add(conversation.topic) + subscriptionRequest.contentTopics = topics } message.contentTopic.startsWith("/xmtp/0/intro-") -> { @@ -682,6 +686,7 @@ data class Conversations( val decoded = conversation.decode(envelopeFromFFi(message)) trySend(decoded) topics.add(conversation.topic) + subscriptionRequest.contentTopics = topics } else -> {} @@ -689,7 +694,8 @@ data class Conversations( } } - client.subscribe(topics, subscriptionCallback) + val stream = client.subscribe2(subscriptionRequest, subscriptionCallback) + awaitClose { runBlocking { stream.end() } } } fun streamAllMessages(includeGroups: Boolean = false): Flow { @@ -718,6 +724,8 @@ data class Conversations( topics.add(conversation.topic) } + val subscriptionRequest = FfiV2SubscribeRequest(topics) + val subscriptionCallback = object : FfiV2SubscriptionCallback { override fun onMessage(message: FfiEnvelope) { when { @@ -731,6 +739,7 @@ data class Conversations( val conversation = fromInvite(envelope = envelopeFromFFi(message)) conversationsByTopic[conversation.topic] = conversation topics.add(conversation.topic) + subscriptionRequest.contentTopics = topics } message.contentTopic.startsWith("/xmtp/0/intro-") -> { @@ -739,6 +748,7 @@ data class Conversations( val decrypted = conversation.decrypt(envelopeFromFFi(message)) trySend(decrypted) topics.add(conversation.topic) + subscriptionRequest.contentTopics = topics } else -> {} @@ -746,6 +756,7 @@ data class Conversations( } } - client.subscribe(topics, subscriptionCallback) + val stream = client.subscribe2(subscriptionRequest, subscriptionCallback) + awaitClose { runBlocking { stream.end() } } } }