From de8949baedd41b79544ef1ff5671fae5e0b611a0 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Wed, 18 Dec 2024 08:09:21 -0800 Subject: [PATCH] Speed up build client performance (#354) * allow storing and passing the api client * speed up create by creating the api backend connection ahead of time * sender address needs to be senderInboxId * remove unused js file * fix up the lint --- README.md | 8 ++--- dev/local/test/script.js | 35 ------------------- .../conversation/ConversationViewHolder.kt | 2 +- .../example/message/MessageViewHolder.kt | 2 +- .../org/xmtp/android/library/ClientTest.kt | 21 +++++++++++ .../java/org/xmtp/android/library/Client.kt | 34 +++++++++++++----- .../xmtp/android/library/DecodedMessage.kt | 2 +- .../xmtp/android/library/libxmtp/Message.kt | 2 +- 8 files changed, 55 insertions(+), 51 deletions(-) delete mode 100644 dev/local/test/script.js diff --git a/README.md b/README.md index 9f12a0eec..996d52a7f 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ conversation.send(text = "gm") // Listen for new messages in the conversation conversation.streamMessages().collect { - print("${it.senderAddress}: ${it.body}") + print("${it.senderInboxId}: ${it.body}") } ``` @@ -199,17 +199,17 @@ val nextPage = conversation.messages(limit = 25, beforeNs = messages[0].sentNs) You can listen for any new messages (incoming or outgoing) in a conversation by calling `conversation.streamMessages()`. -A successfully received message (that makes it through the decoding and decryption without throwing) can be trusted to be authentic. Authentic means that it was sent by the owner of the `message.senderAddress` account and that it wasn't modified in transit. The `message.sent` timestamp can be trusted to have been set by the sender. +A successfully received message (that makes it through the decoding and decryption without throwing) can be trusted to be authentic. Authentic means that it was sent by the owner of the `message.senderInboxId` account and that it wasn't modified in transit. The `message.sent` timestamp can be trusted to have been set by the sender. The flow returned by the `stream` methods is an asynchronous data stream that sequentially emits values and completes normally or with an exception. ```kotlin conversation.streamMessages().collect { - if (it.senderAddress == client.address) { + if (it.senderInboxId == client.address) { // This message was sent from me } - print("New message from ${it.senderAddress}: ${it.body}") + print("New message from ${it.senderInboxId}: ${it.body}") } ``` diff --git a/dev/local/test/script.js b/dev/local/test/script.js deleted file mode 100644 index 164a446c5..000000000 --- a/dev/local/test/script.js +++ /dev/null @@ -1,35 +0,0 @@ -let Client = require("@xmtp/xmtp-js").Client; -let Wallet = require("ethers").Wallet; - -console.log("NODE VERSION", process.version); - -// 0xf4BF19Ed562651837bc11ff975472ABd239D35B5 -const keyBytes = [ - 80, 7, 53, 52, 122, 163, 75, 130, 199, 86, 216, 14, 29, 2, 255, 71, 121, 51, - 165, 3, 208, 178, 193, 207, 223, 217, 75, 247, 84, 78, 204, 3, -]; - -async function checkAll() { - const wallet = new Wallet(keyBytes); - const client = await Client.create(wallet, { - apiUrl: "http://wakunode:5555", - }); - - console.log("Listening…"); - - try { - for await (const message of await client.conversations.streamAllMessages()) { - if (message.senderAddress === wallet.address) { - continue; - } - - await message.conversation.send("HI " + message.senderAddress); - console.log(`Replied to ${message.senderAddress}`); - } - } catch (e) { - console.info(`Error:`, e); - await checkAll(); - } -} - -checkAll().then(() => console.log("Done")); \ No newline at end of file diff --git a/example/src/main/java/org/xmtp/android/example/conversation/ConversationViewHolder.kt b/example/src/main/java/org/xmtp/android/example/conversation/ConversationViewHolder.kt index fa76a042e..79ac7c567 100644 --- a/example/src/main/java/org/xmtp/android/example/conversation/ConversationViewHolder.kt +++ b/example/src/main/java/org/xmtp/android/example/conversation/ConversationViewHolder.kt @@ -39,7 +39,7 @@ class ConversationViewHolder( } else { "" } - val isMe = item.mostRecentMessage?.senderAddress == ClientManager.client.address + val isMe = item.mostRecentMessage?.senderInboxId == ClientManager.client.address if (messageBody.isNotBlank()) { binding.messageBody.text = if (isMe) binding.root.resources.getString( R.string.your_message_body, diff --git a/example/src/main/java/org/xmtp/android/example/message/MessageViewHolder.kt b/example/src/main/java/org/xmtp/android/example/message/MessageViewHolder.kt index f9814a4b3..58a67f086 100644 --- a/example/src/main/java/org/xmtp/android/example/message/MessageViewHolder.kt +++ b/example/src/main/java/org/xmtp/android/example/message/MessageViewHolder.kt @@ -28,7 +28,7 @@ class MessageViewHolder( @SuppressLint("SetTextI18n") fun bind(item: ConversationDetailViewModel.MessageListItem.Message) { val isFromMe = - ClientManager.client.address.lowercase() == item.message.senderAddress.lowercase() + ClientManager.client.address.lowercase() == item.message.senderInboxId.lowercase() val params = binding.messageContainer.layoutParams as ConstraintLayout.LayoutParams if (isFromMe) { params.rightToRight = PARENT_ID diff --git a/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt b/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt index c30f8cc80..c7da00079 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt @@ -557,10 +557,31 @@ class ClientTest { val time3 = end3.time - start3.time Log.d("PERF", "Built a client with inboxId in ${time3 / 1000.0}s") + val start4 = Date() + val buildClient3 = runBlocking { + Client().build( + fakeWallet.address, + options = ClientOptions( + ClientOptions.Api(XMTPEnvironment.DEV, true), + appContext = context, + dbEncryptionKey = key + ), + inboxId = client.inboxId, + apiClient = client.apiClient + ) + } + val end4 = Date() + val time4 = end4.time - start4.time + Log.d("PERF", "Built a client with inboxId and apiClient in ${time4 / 1000.0}s") + assert(time2 < time1) assert(time3 < time1) assert(time3 < time2) + assert(time4 < time1) + assert(time4 < time2) + assert(time4 < time3) assertEquals(client.inboxId, buildClient1.inboxId) assertEquals(client.inboxId, buildClient2.inboxId) + assertEquals(client.inboxId, buildClient3.inboxId) } } 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 334ee4bfb..ac392c563 100644 --- a/library/src/main/java/org/xmtp/android/library/Client.kt +++ b/library/src/main/java/org/xmtp/android/library/Client.kt @@ -1,6 +1,7 @@ package org.xmtp.android.library import android.content.Context +import com.google.protobuf.api import kotlinx.coroutines.runBlocking import org.xmtp.android.library.codecs.ContentCodec import org.xmtp.android.library.codecs.TextCodec @@ -10,6 +11,7 @@ import uniffi.xmtpv3.FfiConversationType import uniffi.xmtpv3.FfiDeviceSyncKind import uniffi.xmtpv3.FfiSignatureRequest import uniffi.xmtpv3.FfiXmtpClient +import uniffi.xmtpv3.XmtpApiClient import uniffi.xmtpv3.connectToBackend import uniffi.xmtpv3.createClient import uniffi.xmtpv3.generateInboxId @@ -47,6 +49,7 @@ class Client() { lateinit var conversations: Conversations lateinit var environment: XMTPEnvironment lateinit var dbPath: String + lateinit var apiClient: XmtpApiClient val libXMTPVersion: String = getVersionInfo() private lateinit var ffiClient: FfiXmtpClient @@ -59,6 +62,10 @@ class Client() { registry } + suspend fun connectToApiBackend(api: ClientOptions.Api): XmtpApiClient { + return connectToBackend(api.env.getUrl(), api.isSecure) + } + suspend fun getOrCreateInboxId(environment: ClientOptions.Api, address: String): String { var inboxId = getInboxIdForAddress( host = environment.env.getUrl(), @@ -79,6 +86,7 @@ class Client() { accountAddresses: List, appContext: Context, api: ClientOptions.Api, + apiClient: XmtpApiClient? = null ): Map { val accountAddress = "0x0000000000000000000000000000000000000000" val inboxId = getOrCreateInboxId(api, accountAddress) @@ -89,7 +97,7 @@ class Client() { val dbPath = directoryFile.absolutePath + "/$alias.db3" val ffiClient = createClient( - api = connectToBackend(api.env.getUrl(), api.isSecure), + api = apiClient ?: connectToApiBackend(api), db = dbPath, encryptionKey = null, accountAddress = accountAddress.lowercase(), @@ -114,6 +122,7 @@ class Client() { installationId: String, inboxId: String, environment: XMTPEnvironment, + apiClient: XmtpApiClient, ) : this() { this.address = address.lowercase() this.preferences = PrivatePreferences(client = this, ffiClient = libXMTPClient) @@ -124,6 +133,7 @@ class Client() { this.installationId = installationId this.inboxId = inboxId this.environment = environment + this.apiClient = apiClient } private suspend fun initializeV3Client( @@ -131,16 +141,18 @@ class Client() { clientOptions: ClientOptions, signingKey: SigningKey? = null, inboxId: String? = null, + apiClient: XmtpApiClient? = null, ): Client { val accountAddress = address.lowercase() val recoveredInboxId = inboxId ?: getOrCreateInboxId(clientOptions.api, accountAddress) - val (ffiClient, dbPath) = createFfiClient( + val (ffiClient, dbPath, apiClient) = createFfiClient( accountAddress, recoveredInboxId, clientOptions, signingKey, clientOptions.appContext, + apiClient, ) return Client( @@ -149,7 +161,8 @@ class Client() { dbPath, ffiClient.installationId().toHex(), ffiClient.inboxId(), - clientOptions.api.env + clientOptions.api.env, + apiClient ) } @@ -157,9 +170,10 @@ class Client() { suspend fun create( account: SigningKey, options: ClientOptions, + apiClient: XmtpApiClient? = null ): Client { return try { - initializeV3Client(account.address, options, account) + initializeV3Client(account.address, options, account, apiClient = apiClient) } catch (e: Exception) { throw XMTPException("Error creating V3 client: ${e.message}", e) } @@ -170,9 +184,10 @@ class Client() { address: String, options: ClientOptions, inboxId: String? = null, + apiClient: XmtpApiClient? = null, ): Client { return try { - initializeV3Client(address, options, inboxId = inboxId) + initializeV3Client(address, options, inboxId = inboxId, apiClient = apiClient) } catch (e: Exception) { throw XMTPException("Error creating V3 client: ${e.message}", e) } @@ -184,7 +199,8 @@ class Client() { options: ClientOptions, signingKey: SigningKey?, appContext: Context, - ): Pair { + apiClient: XmtpApiClient? = null, + ): Triple { val alias = "xmtp-${options.api.env}-$inboxId" val mlsDbDirectory = options.dbDirectory @@ -196,8 +212,10 @@ class Client() { directoryFile.mkdir() dbPath = directoryFile.absolutePath + "/$alias.db3" + val xmtpApiClient = + apiClient ?: connectToApiBackend(options.api) val ffiClient = createClient( - api = connectToBackend(options.api.env.getUrl(), options.api.isSecure), + api = xmtpApiClient, db = dbPath, encryptionKey = options.dbEncryptionKey, accountAddress = accountAddress.lowercase(), @@ -217,7 +235,7 @@ class Client() { ?: throw XMTPException("No signer passed but signer was required.") ffiClient.registerIdentity(signatureRequest) } - return Pair(ffiClient, dbPath) + return Triple(ffiClient, dbPath, xmtpApiClient) } suspend fun revokeAllOtherInstallations(signingKey: SigningKey) { diff --git a/library/src/main/java/org/xmtp/android/library/DecodedMessage.kt b/library/src/main/java/org/xmtp/android/library/DecodedMessage.kt index fa0358f48..bff236239 100644 --- a/library/src/main/java/org/xmtp/android/library/DecodedMessage.kt +++ b/library/src/main/java/org/xmtp/android/library/DecodedMessage.kt @@ -10,7 +10,7 @@ data class DecodedMessage( val client: Client, var topic: String, var encodedContent: Content.EncodedContent, - var senderAddress: String, + var senderInboxId: String, var sent: Date, var sentNs: Long, var deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.PUBLISHED diff --git a/library/src/main/java/org/xmtp/android/library/libxmtp/Message.kt b/library/src/main/java/org/xmtp/android/library/libxmtp/Message.kt index 6374a211c..1537c99dd 100644 --- a/library/src/main/java/org/xmtp/android/library/libxmtp/Message.kt +++ b/library/src/main/java/org/xmtp/android/library/libxmtp/Message.kt @@ -52,7 +52,7 @@ data class Message(val client: Client, private val libXMTPMessage: FfiMessage) { client = client, topic = Topic.groupMessage(convoId).description, encodedContent = EncodedContent.parseFrom(libXMTPMessage.content), - senderAddress = senderInboxId, + senderInboxId = senderInboxId, sent = sentAt, sentNs = sentAtNs, deliveryStatus = deliveryStatus