Skip to content

Commit 6f8209f

Browse files
authored
Improve V3 Client Create (#183)
* add dates to example and a test around removing members * allow passing of encryption key and database path * feat: fix up the tests * fix linter
1 parent 78c26eb commit 6f8209f

File tree

3 files changed

+54
-42
lines changed

3 files changed

+54
-42
lines changed

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

+9-13
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package org.xmtp.android.library
33
import androidx.test.ext.junit.runners.AndroidJUnit4
44
import androidx.test.platform.app.InstrumentationRegistry
55
import org.junit.Assert.assertEquals
6-
import org.junit.Assert.assertNull
76
import org.junit.Assert.fail
87
import org.junit.Ignore
98
import org.junit.Test
@@ -91,10 +90,7 @@ class ClientTest {
9190
)
9291
val client =
9392
Client().create(account = fakeWallet, options = options)
94-
assertEquals(
95-
client.address.lowercase(),
96-
client.libXMTPClient?.accountAddress()?.lowercase()
97-
)
93+
assert(client.canMessageV3(listOf(client.address)))
9894

9995
val bundle = client.privateKeyBundle
10096
val clientFromV1Bundle = Client().buildFromBundle(bundle, account = fakeWallet, options = options)
@@ -103,9 +99,12 @@ class ClientTest {
10399
client.privateKeyBundleV1.identityKey,
104100
clientFromV1Bundle.privateKeyBundleV1.identityKey,
105101
)
102+
103+
assert(clientFromV1Bundle.canMessageV3(listOf(client.address)))
104+
106105
assertEquals(
107-
client.libXMTPClient?.accountAddress(),
108-
clientFromV1Bundle.libXMTPClient?.accountAddress()
106+
client.address,
107+
clientFromV1Bundle.address
109108
)
110109
}
111110

@@ -122,8 +121,7 @@ class ClientTest {
122121
appContext = context
123122
)
124123
)
125-
val v3Client = client.libXMTPClient
126-
assertEquals(client.address.lowercase(), v3Client?.accountAddress()?.lowercase())
124+
assert(client.canMessageV3(listOf(client.address)))
127125
}
128126

129127
@Test
@@ -139,16 +137,14 @@ class ClientTest {
139137
appContext = context
140138
)
141139
)
142-
val v3Client = client.libXMTPClient
143-
assertEquals(client.address.lowercase(), v3Client?.accountAddress()?.lowercase())
140+
assert(client.canMessageV3(listOf(client.address)))
144141
}
145142

146143
@Test
147144
fun testDoesNotCreateAV3Client() {
148145
val fakeWallet = PrivateKeyBuilder()
149146
val client = Client().create(account = fakeWallet)
150-
val v3Client = client.libXMTPClient
151-
assertNull(v3Client)
147+
assert(!client.canMessageV3(listOf(client.address)))
152148
}
153149

154150
@Test

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

+44-28
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ data class ClientOptions(
6666
val preEnableIdentityCallback: PreEventCallback? = null,
6767
val appContext: Context? = null,
6868
val enableAlphaMls: Boolean = false,
69+
val dbPath: String? = null,
70+
val dbEncryptionKey: ByteArray? = null,
6971
) {
7072
data class Api(
7173
val env: XMTPEnvironment = XMTPEnvironment.DEV,
@@ -81,8 +83,8 @@ class Client() {
8183
lateinit var contacts: Contacts
8284
lateinit var conversations: Conversations
8385
var logger: XMTPLogger = XMTPLogger()
84-
var libXMTPClient: FfiXmtpClient? = null
8586
val libXMTPVersion: String = getVersionInfo()
87+
private var libXMTPClient: FfiXmtpClient? = null
8688

8789
companion object {
8890
private const val TAG = "Client"
@@ -296,40 +298,52 @@ class Client() {
296298
if (isAlphaMlsEnabled(options)) {
297299
val alias = "xmtp-${options!!.api.env}-${accountAddress.lowercase()}"
298300

299-
val dbDir = File(appContext?.filesDir?.absolutePath, "xmtp_db")
300-
dbDir.mkdir()
301-
val dbPath: String = dbDir.absolutePath + "/$alias.db3"
302-
303-
val keyStore = KeyStore.getInstance("AndroidKeyStore")
304-
withContext(Dispatchers.IO) {
305-
keyStore.load(null)
301+
val dbPath = if (options.dbPath == null) {
302+
val dbDir = File(appContext?.filesDir?.absolutePath, "xmtp_db")
303+
dbDir.mkdir()
304+
dbDir.absolutePath + "/$alias.db3"
305+
} else {
306+
options.dbPath
306307
}
307308

308-
val entry = keyStore.getEntry(alias, null)
309-
310-
val retrievedKey: SecretKey = if (entry is KeyStore.SecretKeyEntry) {
311-
entry.secretKey
309+
val encryptionKey = if (options.dbEncryptionKey == null) {
310+
val keyStore = KeyStore.getInstance("AndroidKeyStore")
311+
withContext(Dispatchers.IO) {
312+
keyStore.load(null)
313+
}
314+
315+
val entry = keyStore.getEntry(alias, null)
316+
317+
val retrievedKey: SecretKey = if (entry is KeyStore.SecretKeyEntry) {
318+
entry.secretKey
319+
} else {
320+
val keyGenerator =
321+
KeyGenerator.getInstance(
322+
KeyProperties.KEY_ALGORITHM_AES,
323+
"AndroidKeyStore"
324+
)
325+
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
326+
alias,
327+
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
328+
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
329+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
330+
.setKeySize(256)
331+
.build()
332+
333+
keyGenerator.init(keyGenParameterSpec)
334+
keyGenerator.generateKey()
335+
}
336+
retrievedKey.encoded
312337
} else {
313-
val keyGenerator =
314-
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
315-
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
316-
alias,
317-
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
318-
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
319-
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
320-
.setKeySize(256)
321-
.build()
322-
323-
keyGenerator.init(keyGenParameterSpec)
324-
keyGenerator.generateKey()
338+
options.dbEncryptionKey
325339
}
326340

327341
createClient(
328342
logger = logger,
329343
host = if (options.api.env == XMTPEnvironment.LOCAL) "http://${options.api.env.getValue()}:5556" else "https://${options.api.env.getValue()}:443",
330344
isSecure = options.api.isSecure,
331345
db = dbPath,
332-
encryptionKey = retrievedKey.encoded,
346+
encryptionKey = encryptionKey,
333347
accountAddress = accountAddress,
334348
legacyIdentitySource = legacyIdentitySource,
335349
legacySignedPrivateKeyProto = privateKeyBundleV1.toV2().identityKey.toByteArray()
@@ -564,10 +578,12 @@ class Client() {
564578
return runBlocking { query(Topic.contact(peerAddress)).envelopesList.size > 0 }
565579
}
566580

567-
fun canMessage(addresses: List<String>): Boolean {
568-
return runBlocking {
569-
libXMTPClient != null && !libXMTPClient!!.canMessage(addresses).contains(false)
581+
fun canMessageV3(addresses: List<String>): Boolean {
582+
if (libXMTPClient == null) return false
583+
val statuses = runBlocking {
584+
libXMTPClient!!.canMessage(addresses)
570585
}
586+
return !statuses.contains(false)
571587
}
572588

573589
val privateKeyBundle: PrivateKeyBundle

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ data class Conversations(
104104
) {
105105
throw XMTPException("Recipient is sender")
106106
}
107-
if (!client.canMessage(accountAddresses)) {
107+
if (!client.canMessageV3(accountAddresses)) {
108108
throw XMTPException("Recipient not on network")
109109
}
110110

0 commit comments

Comments
 (0)