From b34ebb9fa3e1f57b082b1a09800040cae96db5fd Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 13 Jan 2025 12:12:13 -0800 Subject: [PATCH] Revoke installations (#362) * add static inbox state and revoke installation and clean up run blocking * add tests for it --- .../org/xmtp/android/library/ClientTest.kt | 77 ++++++++++++++++++- .../java/org/xmtp/android/library/Client.kt | 52 ++++++++++--- 2 files changed, 116 insertions(+), 13 deletions(-) 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 ff7ff142..c71caa27 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ClientTest.kt @@ -102,6 +102,27 @@ class ClientTest { } } + @Test + fun testStaticInboxIds() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val fixtures = fixtures() + val states = runBlocking { + Client.inboxStatesForInboxIds( + listOf(fixtures.boClient.inboxId, fixtures.caroClient.inboxId), + context, + ClientOptions.Api(XMTPEnvironment.LOCAL, false) + ) + } + assertEquals( + states.first().recoveryAddress.lowercase(), + fixtures.bo.walletAddress.lowercase() + ) + assertEquals( + states.last().recoveryAddress.lowercase(), + fixtures.caro.walletAddress.lowercase() + ) + } + @Test fun testCanDeleteDatabase() { val key = SecureRandom().generateSeed(32) @@ -302,6 +323,59 @@ class ClientTest { assertEquals(boClient.inboxId, boInboxId) } + @Test + fun testRevokesInstallations() { + val key = SecureRandom().generateSeed(32) + val context = InstrumentationRegistry.getInstrumentation().targetContext + val alixWallet = PrivateKeyBuilder() + + val alixClient = runBlocking { + Client().create( + account = alixWallet, + options = ClientOptions( + ClientOptions.Api(XMTPEnvironment.LOCAL, false), + appContext = context, + dbEncryptionKey = key + ) + ) + } + + val alixClient2 = runBlocking { + Client().create( + account = alixWallet, + options = ClientOptions( + ClientOptions.Api(XMTPEnvironment.LOCAL, false), + appContext = context, + dbEncryptionKey = key, + dbDirectory = context.filesDir.absolutePath.toString() + ) + ) + } + + val alixClient3 = runBlocking { + Client().create( + account = alixWallet, + options = ClientOptions( + ClientOptions.Api(XMTPEnvironment.LOCAL, false), + appContext = context, + dbEncryptionKey = key, + dbDirectory = File(context.filesDir.absolutePath, "xmtp_db3").toPath() + .toString() + ) + ) + } + + var state = runBlocking { alixClient3.inboxState(true) } + assertEquals(state.installations.size, 3) + + runBlocking { + alixClient3.revokeInstallations(alixWallet, listOf(alixClient2.installationId)) + } + + state = runBlocking { alixClient3.inboxState(true) } + assertEquals(state.installations.size, 2) + } + @Test fun testRevokesAllOtherInstallations() { val key = SecureRandom().generateSeed(32) @@ -335,7 +409,8 @@ class ClientTest { ClientOptions.Api(XMTPEnvironment.LOCAL, false), appContext = context, dbEncryptionKey = key, - dbDirectory = File(context.filesDir.absolutePath, "xmtp_db3").toPath().toString() + dbDirectory = File(context.filesDir.absolutePath, "xmtp_db3").toPath() + .toString() ) ) } 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 9e1d0b5f..e13c88ff 100644 --- a/library/src/main/java/org/xmtp/android/library/Client.kt +++ b/library/src/main/java/org/xmtp/android/library/Client.kt @@ -2,6 +2,8 @@ package org.xmtp.android.library import android.content.Context import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.xmtp.android.library.codecs.ContentCodec import org.xmtp.android.library.codecs.TextCodec import org.xmtp.android.library.libxmtp.InboxState @@ -60,15 +62,13 @@ class Client() { } private val apiClientCache = mutableMapOf() - private val cacheLock = Any() + private val cacheLock = Mutex() suspend fun connectToApiBackend(api: ClientOptions.Api): XmtpApiClient { val cacheKey = api.env.getUrl() - return synchronized(cacheLock) { + return cacheLock.withLock { apiClientCache.getOrPut(cacheKey) { - runBlocking { - connectToBackend(api.env.getUrl(), api.isSecure) - } + connectToBackend(api.env.getUrl(), api.isSecure) } } } @@ -91,11 +91,11 @@ class Client() { codecRegistry.register(codec = codec) } - suspend fun canMessage( - accountAddresses: List, + suspend fun withFfiClient( appContext: Context, api: ClientOptions.Api, - ): Map { + useClient: suspend (ffiClient: FfiXmtpClient) -> T, + ): T { val accountAddress = "0x0000000000000000000000000000000000000000" val inboxId = getOrCreateInboxId(api, accountAddress) val alias = "xmtp-${api.env}-$inboxId" @@ -115,11 +115,32 @@ class Client() { historySyncUrl = null ) - val result = ffiClient.canMessage(accountAddresses) - ffiClient.releaseDbConnection() - File(dbPath).delete() + return try { + useClient(ffiClient) + } finally { + ffiClient.releaseDbConnection() + File(dbPath).delete() + } + } + + suspend fun inboxStatesForInboxIds( + inboxIds: List, + appContext: Context, + api: ClientOptions.Api, + ): List { + return withFfiClient(appContext, api) { ffiClient -> + ffiClient.addressesFromInboxId(true, inboxIds).map { InboxState(it) } + } + } - return result + suspend fun canMessage( + accountAddresses: List, + appContext: Context, + api: ClientOptions.Api, + ): Map { + return withFfiClient(appContext, api) { ffiClient -> + ffiClient.canMessage(accountAddresses) + } } } @@ -237,6 +258,13 @@ class Client() { return Pair(ffiClient, dbPath) } + suspend fun revokeInstallations(signingKey: SigningKey, installationIds: List) { + val ids = installationIds.map { it.hexToByteArray() } + val signatureRequest = ffiClient.revokeInstallations(ids) + handleSignature(signatureRequest, signingKey) + ffiClient.applySignatureRequest(signatureRequest) + } + suspend fun revokeAllOtherInstallations(signingKey: SigningKey) { val signatureRequest = ffiClient.revokeAllOtherInstallations() handleSignature(signatureRequest, signingKey)