From 13fbc95d8c9ed5dd5aef20d8cb319226a1e29622 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 22 Jul 2024 17:20:14 -0600 Subject: [PATCH] Always set an inbox id for V2 (#275) * always set an inbox id even if V3 isnt enabled * fix the example and the tests * fix linter up' git st --- .../org/xmtp/android/example/ClientManager.kt | 18 ++- .../example/connect/ConnectWalletViewModel.kt | 4 +- .../org/xmtp/android/example/utils/KeyUtil.kt | 29 ++++ .../org/xmtp/android/library/ClientTest.kt | 63 +++++---- .../org/xmtp/android/library/CodecTest.kt | 4 +- .../xmtp/android/library/ConversationTest.kt | 10 +- .../xmtp/android/library/ConversationsTest.kt | 4 +- .../org/xmtp/android/library/GroupTest.kt | 2 +- .../xmtp/android/library/InvitationTest.kt | 2 +- .../android/library/LocalInstrumentedTest.kt | 74 +++++++---- .../org/xmtp/android/library/MessageTest.kt | 14 +- .../org/xmtp/android/library/TestHelpers.kt | 6 +- .../java/org/xmtp/android/library/Client.kt | 125 +++++++++--------- .../org/xmtp/android/library/TestHelpers.kt | 5 +- 14 files changed, 216 insertions(+), 144 deletions(-) diff --git a/example/src/main/java/org/xmtp/android/example/ClientManager.kt b/example/src/main/java/org/xmtp/android/example/ClientManager.kt index cbef06280..19820ef88 100644 --- a/example/src/main/java/org/xmtp/android/example/ClientManager.kt +++ b/example/src/main/java/org/xmtp/android/example/ClientManager.kt @@ -7,15 +7,25 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch +import org.xmtp.android.example.utils.KeyUtil import org.xmtp.android.library.Client import org.xmtp.android.library.ClientOptions import org.xmtp.android.library.XMTPEnvironment import org.xmtp.android.library.codecs.GroupUpdatedCodec import org.xmtp.android.library.messages.PrivateKeyBundleV1Builder +import org.xmtp.android.library.messages.walletAddress +import java.security.SecureRandom object ClientManager { - fun clientOptions(appContext: Context?): ClientOptions { + fun clientOptions(appContext: Context, address: String): ClientOptions { + val keyUtil = KeyUtil(appContext) + var encryptionKey = keyUtil.retrieveKey(address) + if (encryptionKey == null || encryptionKey.isEmpty()) { + encryptionKey = SecureRandom().generateSeed(32) + keyUtil.storeKey(address, encryptionKey) + } + return ClientOptions( api = ClientOptions.Api( XMTPEnvironment.DEV, @@ -23,7 +33,8 @@ object ClientManager { isSecure = true ), enableV3 = true, - appContext = appContext + appContext = appContext, + dbEncryptionKey = encryptionKey ) } @@ -46,7 +57,8 @@ object ClientManager { try { val v1Bundle = PrivateKeyBundleV1Builder.fromEncodedData(data = encodedPrivateKeyData) - _client = Client().buildFrom(v1Bundle, clientOptions(appContext)) + _client = + Client().buildFrom(v1Bundle, clientOptions(appContext, v1Bundle.walletAddress)) Client.register(codec = GroupUpdatedCodec()) _clientState.value = ClientState.Ready } catch (e: Exception) { diff --git a/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletViewModel.kt b/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletViewModel.kt index 467059ba6..56903b37a 100644 --- a/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletViewModel.kt +++ b/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletViewModel.kt @@ -86,7 +86,7 @@ class ConnectWalletViewModel(application: Application) : AndroidViewModel(applic _uiState.value = ConnectUiState.Loading try { val wallet = PrivateKeyBuilder() - val client = Client().create(wallet, ClientManager.clientOptions(getApplication())) + val client = Client().create(wallet, ClientManager.clientOptions(getApplication(), wallet.address)) Client.register(codec = GroupUpdatedCodec()) _uiState.value = ConnectUiState.Success( wallet.address, @@ -111,7 +111,7 @@ class ConnectWalletViewModel(application: Application) : AndroidViewModel(applic it.copy(showWallet = true, uri = uri) } } - val client = Client().create(wallet, ClientManager.clientOptions(getApplication())) + val client = Client().create(wallet, ClientManager.clientOptions(getApplication(), wallet.address)) Client.register(codec = GroupUpdatedCodec()) _uiState.value = ConnectUiState.Success( wallet.address, diff --git a/example/src/main/java/org/xmtp/android/example/utils/KeyUtil.kt b/example/src/main/java/org/xmtp/android/example/utils/KeyUtil.kt index 525376cde..88c065623 100644 --- a/example/src/main/java/org/xmtp/android/example/utils/KeyUtil.kt +++ b/example/src/main/java/org/xmtp/android/example/utils/KeyUtil.kt @@ -2,9 +2,19 @@ package org.xmtp.android.example.utils import android.accounts.AccountManager import android.content.Context +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64.NO_WRAP +import android.util.Base64.decode +import android.util.Base64.encodeToString import org.xmtp.android.example.R +import java.security.KeyStore +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey + class KeyUtil(val context: Context) { + private val PREFS_NAME = "EncryptionPref" fun loadKeys(): String? { val accountManager = AccountManager.get(context) val accounts = @@ -12,4 +22,23 @@ class KeyUtil(val context: Context) { val account = accounts.firstOrNull() ?: return null return accountManager.getPassword(account) } + + fun storeKey(address: String, key: ByteArray?) { + val alias = "xmtp-dev-${address.lowercase()}" + + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val editor = prefs.edit() + editor.putString(alias, encodeToString(key, NO_WRAP)) + editor.apply() + } + + fun retrieveKey(address: String): ByteArray? { + val alias = "xmtp-dev-${address.lowercase()}" + + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val keyString = prefs.getString(alias, null) + return if (keyString != null) { + decode(keyString, NO_WRAP) + } else null + } } \ No newline at end of file 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 a13825a01..6d89385c8 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt @@ -22,13 +22,13 @@ class ClientTest { @Test fun testTakesAWallet() { val fakeWallet = PrivateKeyBuilder() - Client().create(account = fakeWallet) + runBlocking { Client().create(account = fakeWallet) } } @Test fun testHasPrivateKeyBundleV1() { val fakeWallet = PrivateKeyBuilder() - val client = Client().create(account = fakeWallet) + val client = runBlocking { Client().create(account = fakeWallet) } assertEquals(1, client.privateKeyBundleV1.preKeysList?.size) val preKey = client.privateKeyBundleV1.preKeysList?.get(0) assert(preKey?.publicKey?.hasSignature() ?: false) @@ -41,7 +41,7 @@ class ClientTest { PrivateKeyOuterClass.PrivateKeyBundleV1.newBuilder().build().generate(wallet = wallet) val encodedData = PrivateKeyBundleV1Builder.encodeData(v1) val v1Copy = PrivateKeyBundleV1Builder.fromEncodedData(encodedData) - val client = Client().buildFrom(v1Copy) + val client = runBlocking { Client().buildFrom(v1Copy) } assertEquals( wallet.address, client.address, @@ -51,9 +51,9 @@ class ClientTest { @Test fun testCanBeCreatedWithBundle() { val fakeWallet = PrivateKeyBuilder() - val client = Client().create(account = fakeWallet) + val client = runBlocking { Client().create(account = fakeWallet) } val bundle = client.privateKeyBundle - val clientFromV1Bundle = Client().buildFromBundle(bundle) + val clientFromV1Bundle = runBlocking { Client().buildFromBundle(bundle) } assertEquals(client.address, clientFromV1Bundle.address) assertEquals( client.privateKeyBundleV1.identityKey, @@ -68,9 +68,9 @@ class ClientTest { @Test fun testCanBeCreatedWithV1Bundle() { val fakeWallet = PrivateKeyBuilder() - val client = Client().create(account = fakeWallet) + val client = runBlocking { Client().create(account = fakeWallet) } val bundleV1 = client.v1keys - val clientFromV1Bundle = Client().buildFromV1Bundle(bundleV1) + val clientFromV1Bundle = runBlocking { Client().buildFromV1Bundle(bundleV1) } assertEquals(client.address, clientFromV1Bundle.address) assertEquals( client.privateKeyBundleV1.identityKey, @@ -93,16 +93,18 @@ class ClientTest { appContext = context, dbEncryptionKey = key ) - val client = + val client = runBlocking { Client().create(account = fakeWallet, options = options) + } runBlocking { client.canMessageV3(listOf(client.address))[client.address]?.let { assert(it) } } val bundle = client.privateKeyBundle - val clientFromV1Bundle = + val clientFromV1Bundle = runBlocking { Client().buildFromBundle(bundle, options = options) + } assertEquals(client.address, clientFromV1Bundle.address) assertEquals( client.privateKeyBundleV1.identityKey, @@ -124,7 +126,7 @@ class ClientTest { val key = SecureRandom().generateSeed(32) val context = InstrumentationRegistry.getInstrumentation().targetContext val fakeWallet = PrivateKeyBuilder() - val client = + val client = runBlocking { Client().create( account = fakeWallet, options = ClientOptions( @@ -134,6 +136,7 @@ class ClientTest { dbEncryptionKey = key ) ) + } runBlocking { client.canMessageV3(listOf(client.address))[client.address]?.let { assert(it) } } @@ -146,7 +149,7 @@ class ClientTest { val context = InstrumentationRegistry.getInstrumentation().targetContext val fakeWallet = PrivateKeyBuilder() val fakeWallet2 = PrivateKeyBuilder() - var client = + var client = runBlocking { Client().create( account = fakeWallet, options = ClientOptions( @@ -156,7 +159,8 @@ class ClientTest { dbEncryptionKey = key ) ) - val client2 = + } + val client2 = runBlocking { Client().create( account = fakeWallet2, options = ClientOptions( @@ -166,6 +170,7 @@ class ClientTest { dbEncryptionKey = key ) ) + } runBlocking { client.conversations.newGroup(listOf(client2.address)) @@ -175,7 +180,7 @@ class ClientTest { client.deleteLocalDatabase() - client = + client = runBlocking { Client().create( account = fakeWallet, options = ClientOptions( @@ -185,7 +190,7 @@ class ClientTest { dbEncryptionKey = key ) ) - + } runBlocking { client.conversations.syncGroups() assertEquals(client.conversations.listGroups().size, 0) @@ -197,7 +202,7 @@ class ClientTest { val key = SecureRandom().generateSeed(32) val context = InstrumentationRegistry.getInstrumentation().targetContext val fakeWallet = PrivateKeyBuilder() - val client = + val client = runBlocking { Client().create( account = fakeWallet, options = ClientOptions( @@ -207,6 +212,7 @@ class ClientTest { dbEncryptionKey = key ) ) + } runBlocking { client.canMessageV3(listOf(client.address))[client.address]?.let { assert(it) } } @@ -217,7 +223,7 @@ class ClientTest { val key = SecureRandom().generateSeed(32) val context = InstrumentationRegistry.getInstrumentation().targetContext val fakeWallet = PrivateKeyBuilder() - val client = + val client = runBlocking { Client().create( account = fakeWallet, options = ClientOptions( @@ -227,6 +233,7 @@ class ClientTest { dbEncryptionKey = key ) ) + } runBlocking { client.canMessageV3(listOf(client.address))[client.address]?.let { assert(it) } } @@ -235,7 +242,7 @@ class ClientTest { @Test fun testDoesNotCreateAV3Client() { val fakeWallet = PrivateKeyBuilder() - val client = Client().create(account = fakeWallet) + val client = runBlocking { Client().create(account = fakeWallet) } assertThrows("Error no V3 client initialized", XMTPException::class.java) { runBlocking { client.canMessageV3(listOf(client.address))[client.address]?.let { assert(!it) } @@ -258,7 +265,9 @@ class ClientTest { val aliceWallet = PrivateKeyBuilder() val notOnNetwork = PrivateKeyBuilder() val opts = ClientOptions(ClientOptions.Api(XMTPEnvironment.LOCAL, false)) - val aliceClient = Client().create(aliceWallet, opts) + val aliceClient = runBlocking { + Client().create(aliceWallet, opts) + } runBlocking { aliceClient.ensureUserContactPublished() } val canMessage = runBlocking { Client.canMessage(aliceWallet.address, opts) } @@ -283,7 +292,9 @@ class ClientTest { ) try { - Client().create(account = fakeWallet, options = opts) + runBlocking { + Client().create(account = fakeWallet, options = opts) + } expectation.get(5, TimeUnit.SECONDS) } catch (e: Exception) { fail("Error: $e") @@ -305,7 +316,7 @@ class ClientTest { ) try { - Client().create(account = fakeWallet, options = opts) + runBlocking { Client().create(account = fakeWallet, options = opts) } expectation.get(5, TimeUnit.SECONDS) } catch (e: Exception) { fail("Error: $e") @@ -318,7 +329,7 @@ class ClientTest { val context = InstrumentationRegistry.getInstrumentation().targetContext val fakeWallet = PrivateKeyBuilder() val fakeWallet2 = PrivateKeyBuilder() - val boClient = + val boClient = runBlocking { Client().create( account = fakeWallet, options = ClientOptions( @@ -328,7 +339,8 @@ class ClientTest { dbEncryptionKey = key ) ) - val alixClient = + } + val alixClient = runBlocking { Client().create( account = fakeWallet2, options = ClientOptions( @@ -338,6 +350,7 @@ class ClientTest { dbEncryptionKey = key ) ) + } runBlocking { boClient.conversations.newGroup(listOf(alixClient.address)) @@ -368,7 +381,7 @@ class ClientTest { val context = InstrumentationRegistry.getInstrumentation().targetContext val alixWallet = PrivateKeyBuilder() val boWallet = PrivateKeyBuilder() - val alixClient = + val alixClient = runBlocking { Client().create( account = alixWallet, options = ClientOptions( @@ -378,7 +391,8 @@ class ClientTest { dbEncryptionKey = key ) ) - val boClient = + } + val boClient = runBlocking { Client().create( account = boWallet, options = ClientOptions( @@ -388,6 +402,7 @@ class ClientTest { dbEncryptionKey = key ) ) + } val boInboxId = runBlocking { alixClient.inboxIdFromAddress(boClient.address) } diff --git a/library/src/androidTest/java/org/xmtp/android/library/CodecTest.kt b/library/src/androidTest/java/org/xmtp/android/library/CodecTest.kt index 2a35d28f2..1b8ecdf30 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/CodecTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/CodecTest.kt @@ -112,11 +112,11 @@ class CodecTest { val alix = PrivateKeyBuilder() val clientOptions = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - val alixClient = Client().create(alix, clientOptions) + val alixClient = runBlocking { Client().create(alix, clientOptions) } val conversations = mutableListOf() repeat(5) { val account = PrivateKeyBuilder() - val client = Client().create(account, clientOptions) + val client = runBlocking { Client().create(account, clientOptions) } runBlocking { conversations.add( alixClient.conversations.newConversation( 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 6dcde9dd1..ae1fe40e2 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ConversationTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ConversationTest.kt @@ -69,7 +69,7 @@ class ConversationTest { @Test fun testDoesNotAllowConversationWithSelf() { - val client = Client().create(account = aliceWallet) + val client = runBlocking { Client().create(account = aliceWallet) } assertThrows("Recipient is sender", XMTPException::class.java) { runBlocking { client.conversations.newConversation(alice.walletAddress) } } @@ -328,10 +328,10 @@ class ConversationTest { @Test fun testEndToEndConversation() { val fakeContactWallet = PrivateKeyBuilder() - val fakeContactClient = Client().create(account = fakeContactWallet) + val fakeContactClient = runBlocking { Client().create(account = fakeContactWallet) } runBlocking { fakeContactClient.publishUserContact() } val fakeWallet = PrivateKeyBuilder() - val client = Client().create(account = fakeWallet) + val client = runBlocking { Client().create(account = fakeWallet) } val contact = client.getUserContact(peerAddress = fakeContactWallet.address)!! assertEquals(contact.walletAddress, fakeContactWallet.address) val created = Date() @@ -760,7 +760,7 @@ class ConversationTest { }.build() }.build() - val client = Client().create(account = PrivateKeyBuilder(key)) + val client = runBlocking { Client().create(account = PrivateKeyBuilder(key)) } runBlocking { val conversations = client.conversations.list() assertEquals(1, conversations.size) @@ -833,7 +833,7 @@ class ConversationTest { val isBobAllowed = aliceConversation.consentState() == ConsentState.ALLOWED assertTrue("Bob should be allowed from alice conversation", isBobAllowed) - val aliceClient2 = Client().create(aliceWallet) + val aliceClient2 = runBlocking { Client().create(aliceWallet) } val aliceConversation2 = runBlocking { aliceClient2.conversations.list()[0] } runBlocking { aliceClient2.contacts.refreshConsentList() } 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 205dfb87c..15f2bae4b 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt @@ -61,7 +61,7 @@ class ConversationsTest { fun testCanGetConversationFromIntroEnvelope() { val created = Date() val newWallet = PrivateKeyBuilder() - val newClient = Client().create(account = newWallet) + val newClient = runBlocking { Client().create(account = newWallet) } val message = MessageV1Builder.buildEncode( sender = newClient.privateKeyBundleV1, recipient = fixtures.aliceClient.v1keys.toPublicKeyBundle(), @@ -82,7 +82,7 @@ class ConversationsTest { fun testCanGetConversationFromInviteEnvelope() { val created = Date() val newWallet = PrivateKeyBuilder() - val newClient = Client().create(account = newWallet) + val newClient = runBlocking { Client().create(account = newWallet) } val invitation = InvitationV1.newBuilder().build().createDeterministic( sender = newClient.keys, recipient = alixClient.keys.getPublicKeyBundle() diff --git a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt index 736f5ff0b..d14b55b53 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt @@ -402,7 +402,7 @@ class GroupTest { fun testCannotSendMessageToGroupMemberNotOnV3() { val chuxAccount = PrivateKeyBuilder() val chux: PrivateKey = chuxAccount.getPrivateKey() - Client().create(account = chuxAccount) + runBlocking { Client().create(account = chuxAccount) } assertThrows("Recipient not on network", XMTPException::class.java) { runBlocking { boClient.conversations.newGroup(listOf(chux.walletAddress)) } 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 44e2572bc..104a42da1 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/InvitationTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/InvitationTest.kt @@ -50,7 +50,7 @@ class InvitationTest { }.build() }.build() - val client = Client().create(account = PrivateKeyBuilder(key)) + val client = runBlocking { Client().create(account = PrivateKeyBuilder(key)) } 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 325ccf6b0..e16937119 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/LocalInstrumentedTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/LocalInstrumentedTest.kt @@ -44,7 +44,7 @@ class LocalInstrumentedTest { appVersion = "XMTPTest/v1.0.0" ) ) - val client = Client().create(aliceWallet, clientOptions) + val client = runBlocking { Client().create(aliceWallet, clientOptions) } runBlocking { client.publishUserContact() } @@ -124,7 +124,7 @@ class LocalInstrumentedTest { // Done saving keys val clientOptions = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - val client = Client().create(account = aliceWallet, options = clientOptions) + val client = runBlocking { Client().create(account = aliceWallet, options = clientOptions) } val contact = client.getUserContact(peerAddress = aliceWallet.address) assertEquals( contact?.v2?.keyBundle?.identityKey?.secp256K1Uncompressed, @@ -140,9 +140,9 @@ class LocalInstrumentedTest { val alice = PrivateKeyBuilder() val clientOptions = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - val bobClient = Client().create(bob, clientOptions) + val bobClient = runBlocking { Client().create(bob, clientOptions) } // Publish alice's contact - Client().create(account = alice, clientOptions) + runBlocking { Client().create(account = alice, clientOptions) } val convo = runBlocking { bobClient.conversations.newConversation( alice.address, @@ -180,14 +180,28 @@ class LocalInstrumentedTest { @Test fun testListingConversations() { - val alice = Client().create( - PrivateKeyBuilder(), - ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - ) - val bob = Client().create( - PrivateKeyBuilder(), - ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - ) + val alice = runBlocking { + Client().create( + PrivateKeyBuilder(), + ClientOptions( + api = ClientOptions.Api( + env = XMTPEnvironment.LOCAL, + isSecure = false + ) + ) + ) + } + val bob = runBlocking { + Client().create( + PrivateKeyBuilder(), + ClientOptions( + api = ClientOptions.Api( + env = XMTPEnvironment.LOCAL, + isSecure = false + ) + ) + ) + } // First Bob starts a conversation with Alice val c1 = runBlocking { @@ -230,8 +244,8 @@ class LocalInstrumentedTest { @Test fun testUsingSavedCredentialsAndKeyMaterial() { val options = ClientOptions(ClientOptions.Api(XMTPEnvironment.LOCAL, isSecure = false)) - val alice = Client().create(PrivateKeyBuilder(), options) - val bob = Client().create(PrivateKeyBuilder(), options) + val alice = runBlocking { Client().create(PrivateKeyBuilder(), options) } + val bob = runBlocking { Client().create(PrivateKeyBuilder(), options) } // Alice starts a conversation with Bob val aliceConvo = runBlocking { @@ -257,10 +271,12 @@ class LocalInstrumentedTest { delayToPropagate() // When Alice's device wakes up, it uses her saved credentials - val alice2 = Client().buildFromBundle( - PrivateKeyBundle.parseFrom(keyBundle), - options - ) + val alice2 = runBlocking { + Client().buildFromBundle( + PrivateKeyBundle.parseFrom(keyBundle), + options + ) + } // And it uses the saved topic data for the conversation val aliceConvo2 = alice2.conversations.importTopicData( Keystore.TopicMap.TopicData.parseFrom(topicData) @@ -279,9 +295,9 @@ class LocalInstrumentedTest { val alice = PrivateKeyBuilder() val clientOptions = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - val bobClient = Client().create(bob, clientOptions) + val bobClient = runBlocking { Client().create(bob, clientOptions) } // Publish alice's contact - Client().create(account = alice, clientOptions) + runBlocking { Client().create(account = alice, clientOptions) } val convo = ConversationV1(client = bobClient, peerAddress = alice.address, sentAt = Date()) // Say this message is sent in the past runBlocking { convo.send(text = "10 seconds ago") } @@ -300,8 +316,8 @@ class LocalInstrumentedTest { val alice = PrivateKeyBuilder() val clientOptions = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - val bobClient = Client().create(bob, clientOptions) - val aliceClient = Client().create(alice, clientOptions) + val bobClient = runBlocking { Client().create(bob, clientOptions) } + val aliceClient = runBlocking { Client().create(alice, clientOptions) } aliceClient.conversations.streamAllMessages().mapLatest { assertEquals("hi", it.encodedContent.content.toStringUtf8()) } @@ -317,8 +333,8 @@ class LocalInstrumentedTest { val alice = PrivateKeyBuilder() val clientOptions = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - val bobClient = Client().create(bob, clientOptions) - val aliceClient = Client().create(alice, clientOptions) + val bobClient = runBlocking { Client().create(bob, clientOptions) } + val aliceClient = runBlocking { Client().create(alice, clientOptions) } // Overwrite contact as legacy publishLegacyContact(client = bobClient) @@ -756,7 +772,7 @@ class LocalInstrumentedTest { val options = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = true)) val keys = PrivateKeyBundleV1Builder.buildFromBundle(bytes) - Client().buildFrom(bundle = keys, options = options) + runBlocking { Client().buildFrom(bundle = keys, options = options) } } @Test @@ -806,8 +822,8 @@ class LocalInstrumentedTest { val alice = PrivateKeyBuilder() val clientOptions = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - val bobClient = Client().create(bob, clientOptions) - val aliceClient = Client().create(account = alice, options = clientOptions) + val bobClient = runBlocking { Client().create(bob, clientOptions) } + val aliceClient = runBlocking { Client().create(account = alice, options = clientOptions) } runBlocking { aliceClient.publishUserContact(legacy = true) bobClient.publishUserContact(legacy = true) @@ -827,8 +843,8 @@ class LocalInstrumentedTest { val alice = PrivateKeyBuilder() val clientOptions = ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) - val bobClient = Client().create(bob, clientOptions) - val aliceClient = Client().create(account = alice, options = clientOptions) + val bobClient = runBlocking { Client().create(bob, clientOptions) } + val aliceClient = runBlocking { Client().create(account = alice, options = clientOptions) } val aliceConversation = runBlocking { aliceClient.conversations.newConversation( bob.address, 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 341f83496..40a20c394 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/MessageTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/MessageTest.kt @@ -69,7 +69,7 @@ class MessageTest { val bobWallet = PrivateKeyBuilder() val alice = PrivateKeyBundleV1.newBuilder().build().generate(wallet = aliceWallet) val bob = PrivateKeyBundleV1.newBuilder().build().generate(wallet = bobWallet) - val client = Client().create(account = aliceWallet) + val client = runBlocking { Client().create(account = aliceWallet) } val invitationContext = Invitation.InvitationV1.Context.newBuilder().apply { conversationId = "https://example.com/1" }.build() @@ -160,7 +160,7 @@ class MessageTest { }.build() }.build() - val client = Client().create(account = PrivateKeyBuilder(key)) + val client = runBlocking { Client().create(account = PrivateKeyBuilder(key)) } runBlocking { val convo = client.conversations.list()[0] val message = convo.messages()[0] @@ -194,7 +194,7 @@ class MessageTest { }.build() }.build() - val client = Client().create(account = PrivateKeyBuilder(key)) + val client = runBlocking { Client().create(account = PrivateKeyBuilder(key)) } runBlocking { val convo = client.conversations.list()[0] convo.send( @@ -231,7 +231,7 @@ class MessageTest { }.build() }.build() }.build() - val client = Client().create(account = PrivateKeyBuilder(key)) + val client = runBlocking { Client().create(account = PrivateKeyBuilder(key)) } val conversations = runBlocking { client.conversations.list() } assertEquals(201, conversations.size) } @@ -239,7 +239,7 @@ class MessageTest { @Test fun canReceiveV1MessagesFromJS() { val wallet = FakeWallet.generate() - val client = Client().create(account = wallet) + val client = runBlocking { Client().create(account = wallet) } val convo = ConversationV1( client = client, peerAddress = "0xf4BF19Ed562651837bc11ff975472ABd239D35B5", @@ -255,7 +255,7 @@ class MessageTest { @Test fun canReceiveV2MessagesFromJS() { val wallet = PrivateKeyBuilder() - val client = Client().create(account = wallet) + val client = runBlocking { Client().create(account = wallet) } val convo = runBlocking { client.conversations.newConversation( "0xf4BF19Ed562651837bc11ff975472ABd239D35B5", @@ -302,7 +302,7 @@ class MessageTest { val keyBundleData = Numeric.hexStringToByteArray("0a86030ac001089387b882df3012220a204a393d6ac64c10770a2585def70329f10ca480517311f0b321a5cfbbae0119951a9201089387b882df3012440a420a4092f66532cf0266d146a17060fb64148e4a6adc673c14511e45f40ac66551234a336a8feb6ef3fabdf32ea259c2a3bca32b9550c3d34e004ea59e86b42f8001ac1a430a41041c919edda3399ab7f20f5e1a9339b1c2e666e80a164fb1c6d8bc1b7dbf2be158f87c837a6364c7fb667a40c2d234d198a7c2168a928d39409ad7d35d653d319912c00108a087b882df3012220a202ade2eefefa5f8855e557d685278e8717e3f57682b66c3d73aa87896766acddc1a920108a087b882df3012440a420a404f4a90ef10e1536e4588f12c2320229008d870d2abaecd1acfefe9ca91eb6f6d56b1380b1bdebdcf9c46fb19ceb3247d5d986a4dd2bce40a4bdf694c24b08fbb1a430a4104a51efe7833c46d2f683e2eb1c07811bb96ab5e4c2000a6f06124968e8842ff8be737ad7ca92b2dabb13550cdc561df15771c8494eca7b7ca5519f6da02f76489") val keyBundle = PrivateKeyOuterClass.PrivateKeyBundle.parseFrom(keyBundleData) - val client = Client().buildFrom(bundle = keyBundle.v1) + val client = runBlocking { Client().buildFrom(bundle = keyBundle.v1) } val conversationJSON = (""" {"version":"v2","topic":"/xmtp/0/m-2SkdN5Qa0ZmiFI5t3RFbfwIS-OLv5jusqndeenTLvNg/proto","keyMaterial":"ATA1L0O2aTxHmskmlGKCudqfGqwA1H+bad3W/GpGOr8=","peerAddress":"0x436D906d1339fC4E951769b1699051f020373D04","createdAt":"2023-01-26T22:58:45.068Z","context":{"conversationId":"pat/messageid","metadata":{}}} """).toByteArray( UTF_8, 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 e3b65ee96..9e5a770ff 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt @@ -50,11 +50,11 @@ data class Fixtures( ), ) { var alice: PrivateKey = aliceAccount.getPrivateKey() - var aliceClient: Client = Client().create(account = aliceAccount, options = clientOptions) + var aliceClient: Client = runBlocking { Client().create(account = aliceAccount, options = clientOptions) } var bob: PrivateKey = bobAccount.getPrivateKey() - var bobClient: Client = Client().create(account = bobAccount, options = clientOptions) + var bobClient: Client = runBlocking { Client().create(account = bobAccount, options = clientOptions) } var caro: PrivateKey = caroAccount.getPrivateKey() - var caroClient: Client = Client().create(account = caroAccount, options = clientOptions) + var caroClient: Client = runBlocking { Client().create(account = caroAccount, options = clientOptions) } constructor(clientOptions: ClientOptions?) : this( aliceAccount = PrivateKeyBuilder(), 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 133d3ba5e..f09d9cb37 100644 --- a/library/src/main/java/org/xmtp/android/library/Client.kt +++ b/library/src/main/java/org/xmtp/android/library/Client.kt @@ -5,7 +5,6 @@ import android.os.Build import android.util.Log import com.google.crypto.tink.subtle.Base64 import com.google.gson.GsonBuilder -import kotlinx.coroutines.runBlocking import org.web3j.crypto.Keys import org.web3j.crypto.Keys.toChecksumAddress import org.xmtp.android.library.codecs.ContentCodec @@ -86,7 +85,7 @@ class Client() { var installationId: String = "" private var v3Client: FfiXmtpClient? = null private var dbPath: String = "" - var inboxId: String = "" + lateinit var inboxId: String companion object { private const val TAG = "Client" @@ -165,7 +164,7 @@ class Client() { libXMTPClient: FfiXmtpClient? = null, dbPath: String = "", installationId: String = "", - inboxId: String = "", + inboxId: String, ) : this() { this.address = address this.privateKeyBundleV1 = privateKeyBundleV1 @@ -179,7 +178,7 @@ class Client() { this.inboxId = inboxId } - fun buildFrom( + suspend fun buildFrom( bundle: PrivateKeyBundleV1, options: ClientOptions? = null, account: SigningKey? = null, @@ -187,17 +186,16 @@ class Client() { return buildFromV1Bundle(bundle, options, account) } - fun create( + suspend fun create( account: SigningKey, options: ClientOptions? = null, ): Client { val clientOptions = options ?: ClientOptions() - val v2Client = runBlocking { + val v2Client = createV2Client( host = clientOptions.api.env.getUrl(), isSecure = clientOptions.api.isSecure ) - } clientOptions.api.appVersion?.let { v2Client.setAppVersion(it) } val apiClient = GRPCApiClient(environment = clientOptions.api.env, rustV2Client = v2Client) return create( @@ -207,79 +205,77 @@ class Client() { ) } - fun create( + suspend fun create( account: SigningKey, apiClient: ApiClient, options: ClientOptions? = null, ): Client { val clientOptions = options ?: ClientOptions() - return runBlocking { - try { - val privateKeyBundleV1 = loadOrCreateKeys( + try { + val privateKeyBundleV1 = loadOrCreateKeys( + account, + apiClient, + clientOptions + ) + val inboxId = getOrCreateInboxId(clientOptions, account.address) + val (libXMTPClient, dbPath) = + ffiXmtpClient( + clientOptions, account, - apiClient, - clientOptions + clientOptions.appContext, + privateKeyBundleV1, + account.address, + inboxId ) - val (libXMTPClient, dbPath) = - ffiXmtpClient( - options, - account, - clientOptions.appContext, - privateKeyBundleV1, - account.address - ) - - val client = - Client( - account.address, - privateKeyBundleV1, - apiClient, - libXMTPClient, - dbPath, - libXMTPClient?.installationId()?.toHex() ?: "", - libXMTPClient?.inboxId() ?: "" - ) - client.ensureUserContactPublished() - client - } catch (e: java.lang.Exception) { - throw XMTPException("Error creating client ${e.message}", e) - } + val client = + Client( + account.address, + privateKeyBundleV1, + apiClient, + libXMTPClient, + dbPath, + libXMTPClient?.installationId()?.toHex() ?: "", + libXMTPClient?.inboxId() ?: inboxId + ) + client.ensureUserContactPublished() + return client + } catch (e: java.lang.Exception) { + throw XMTPException("Error creating client ${e.message}", e) } } - fun buildFromBundle( + suspend fun buildFromBundle( bundle: PrivateKeyBundle, options: ClientOptions? = null, account: SigningKey? = null, ): Client = buildFromV1Bundle(v1Bundle = bundle.v1, account = account, options = options) - fun buildFromV1Bundle( + suspend fun buildFromV1Bundle( v1Bundle: PrivateKeyBundleV1, options: ClientOptions? = null, account: SigningKey? = null, ): Client { val address = v1Bundle.identityKey.publicKey.recoverWalletSignerPublicKey().walletAddress val newOptions = options ?: ClientOptions() - val v2Client = runBlocking { + val v2Client = createV2Client( host = newOptions.api.env.getUrl(), isSecure = newOptions.api.isSecure ) - } newOptions.api.appVersion?.let { v2Client.setAppVersion(it) } val apiClient = GRPCApiClient(environment = newOptions.api.env, rustV2Client = v2Client) + val inboxId = getOrCreateInboxId(newOptions, address) val (v3Client, dbPath) = if (isV3Enabled(options)) { - runBlocking { - ffiXmtpClient( - options, - account, - options?.appContext, - v1Bundle, - address - ) - } + ffiXmtpClient( + newOptions, + account, + options?.appContext, + v1Bundle, + address, + inboxId + ) } else Pair(null, "") return Client( @@ -289,7 +285,7 @@ class Client() { libXMTPClient = v3Client, dbPath = dbPath, installationId = v3Client?.installationId()?.toHex() ?: "", - inboxId = v3Client?.inboxId() ?: "" + inboxId = v3Client?.inboxId() ?: inboxId ) } @@ -298,27 +294,17 @@ class Client() { } private suspend fun ffiXmtpClient( - options: ClientOptions?, + options: ClientOptions, account: SigningKey?, appContext: Context?, privateKeyBundleV1: PrivateKeyBundleV1, address: String, + inboxId: String, ): Pair { var dbPath = "" val accountAddress = address.lowercase() val v3Client: FfiXmtpClient? = if (isV3Enabled(options)) { - var inboxId = getInboxIdForAddress( - logger = logger, - host = options!!.api.env.getUrl(), - isSecure = options.api.isSecure, - accountAddress = accountAddress - ) - - if (inboxId.isNullOrBlank()) { - inboxId = generateInboxId(accountAddress, 0.toULong()) - } - val alias = "xmtp-${options.api.env}-$inboxId" val mlsDbDirectory = options.dbDirectory @@ -634,6 +620,19 @@ class Client() { v3Client?.dbReconnect() ?: throw XMTPException("Error no V3 client initialized") } + suspend fun getOrCreateInboxId(options: ClientOptions, address: String): String { + var inboxId = getInboxIdForAddress( + logger = logger, + host = options.api.env.getUrl(), + isSecure = options.api.isSecure, + accountAddress = address + ) + if (inboxId.isNullOrBlank()) { + inboxId = generateInboxId(address, 0.toULong()) + } + return inboxId + } + suspend fun requestMessageHistorySync() { v3Client?.requestHistorySync() ?: throw XMTPException("Error no V3 client initialized") } diff --git a/library/src/test/java/org/xmtp/android/library/TestHelpers.kt b/library/src/test/java/org/xmtp/android/library/TestHelpers.kt index bab270e74..431cb9917 100644 --- a/library/src/test/java/org/xmtp/android/library/TestHelpers.kt +++ b/library/src/test/java/org/xmtp/android/library/TestHelpers.kt @@ -1,5 +1,6 @@ package org.xmtp.android.library +import kotlinx.coroutines.runBlocking import org.xmtp.android.library.codecs.Fetcher import org.xmtp.android.library.messages.PrivateKey import org.xmtp.android.library.messages.PrivateKeyBuilder @@ -19,10 +20,10 @@ data class Fixtures( ClientOptions.Api(XMTPEnvironment.LOCAL, isSecure = false) ), ) { - var aliceClient: Client = Client().create(account = aliceAccount, options = clientOptions) + var aliceClient: Client = runBlocking { Client().create(account = aliceAccount, options = clientOptions) } var alice: PrivateKey = aliceAccount.getPrivateKey() var bob: PrivateKey = bobAccount.getPrivateKey() - var bobClient: Client = Client().create(account = bobAccount, options = clientOptions) + var bobClient: Client = runBlocking { Client().create(account = bobAccount, options = clientOptions) } constructor(clientOptions: ClientOptions?) : this( aliceAccount = PrivateKeyBuilder(),