Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up build client performance #354

Merged
merged 5 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
}
```

Expand Down Expand Up @@ -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}")
}
```

Expand Down
35 changes: 0 additions & 35 deletions dev/local/test/script.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
34 changes: 26 additions & 8 deletions library/src/main/java/org/xmtp/android/library/Client.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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(),
Expand All @@ -79,6 +86,7 @@ class Client() {
accountAddresses: List<String>,
appContext: Context,
api: ClientOptions.Api,
apiClient: XmtpApiClient? = null
): Map<String, Boolean> {
val accountAddress = "0x0000000000000000000000000000000000000000"
val inboxId = getOrCreateInboxId(api, accountAddress)
Expand All @@ -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(),
Expand All @@ -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)
Expand All @@ -124,23 +133,26 @@ class Client() {
this.installationId = installationId
this.inboxId = inboxId
this.environment = environment
this.apiClient = apiClient
}

private suspend fun initializeV3Client(
address: String,
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(
Expand All @@ -149,17 +161,19 @@ class Client() {
dbPath,
ffiClient.installationId().toHex(),
ffiClient.inboxId(),
clientOptions.api.env
clientOptions.api.env,
apiClient
)
}

// Function to create a V3 client with a signing key
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)
}
Expand All @@ -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)
}
Expand All @@ -184,7 +199,8 @@ class Client() {
options: ClientOptions,
signingKey: SigningKey?,
appContext: Context,
): Pair<FfiXmtpClient, String> {
apiClient: XmtpApiClient? = null,
): Triple<FfiXmtpClient, String, XmtpApiClient> {
val alias = "xmtp-${options.api.env}-$inboxId"

val mlsDbDirectory = options.dbDirectory
Expand All @@ -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(),
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading