From da59bca723f551afd70a7851e0c45822bbc8dbc3 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 25 Nov 2024 10:16:41 -0800 Subject: [PATCH] Consent Streaming (#333) * bump bindings * bump bindings * add test for consent streaming * add ability to stream consent * fix lint issue * Update library/src/main/java/org/xmtp/android/library/PrivatePreferences.kt Co-authored-by: Cameron Voell <1103838+cameronvoell@users.noreply.github.com> --------- Co-authored-by: Cameron Voell <1103838+cameronvoell@users.noreply.github.com> --- .../xmtp/android/library/ConversationsTest.kt | 72 ++++++++++++++++++- .../java/org/xmtp/android/library/DmTest.kt | 12 ++-- .../org/xmtp/android/library/GroupTest.kt | 30 ++++---- .../library/SmartContractWalletTest.kt | 24 +++---- .../java/org/xmtp/android/library/Client.kt | 4 -- .../android/library/PrivatePreferences.kt | 47 +++++++++--- 6 files changed, 141 insertions(+), 48 deletions(-) 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 6eaad4072..fabc31b1c 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt @@ -211,14 +211,14 @@ class ConversationsTest { boDm?.sync() alixClient.conversations.sync() alixClient2.conversations.sync() - alixClient2.syncConsent() + alixClient2.preferences.syncConsent() alixClient.conversations.syncAllConversations() Thread.sleep(2000) alixClient2.conversations.syncAllConversations() Thread.sleep(2000) val dm2 = alixClient2.findConversation(dm.id)!! assertEquals(ConsentState.DENIED, dm2.consentState()) - alixClient2.preferences.consentList.setConsentState( + alixClient2.preferences.setConsentState( listOf( ConsentListEntry( dm2.id, @@ -228,10 +228,76 @@ class ConversationsTest { ) ) assertEquals( - alixClient2.preferences.consentList.conversationState(dm2.id), + alixClient2.preferences.conversationState(dm2.id), ConsentState.ALLOWED ) assertEquals(dm2.consentState(), ConsentState.ALLOWED) } } + + @Test + fun testStreamConsent() { + 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 alixGroup = runBlocking { alixClient.conversations.newGroup(listOf(bo.walletAddress)) } + + val alixClient2 = runBlocking { + Client().create( + account = alixWallet, + options = ClientOptions( + ClientOptions.Api(XMTPEnvironment.LOCAL, false), + appContext = context, + dbEncryptionKey = key, + dbDirectory = context.filesDir.absolutePath.toString() + ) + ) + } + + runBlocking { + alixGroup.send("Hello") + alixClient2.conversations.sync() + alixClient.conversations.syncAllConversations() + alixClient2.conversations.syncAllConversations() + } + val alix2Group = alixClient2.findGroup(alixGroup.id)!! + val consent = mutableListOf() + val job = CoroutineScope(Dispatchers.IO).launch { + try { + alixClient.preferences.streamConsent() + .collect { entry -> + consent.add(entry) + } + } catch (e: Exception) { + } + } + + Thread.sleep(1000) + + runBlocking { + alix2Group.updateConsentState(ConsentState.DENIED) + val dm3 = alixClient2.conversations.newConversation(caro.walletAddress) + dm3.updateConsentState(ConsentState.DENIED) + alixClient.conversations.sync() + alixClient2.conversations.sync() + alixClient.conversations.syncAllConversations() + alixClient2.conversations.syncAllConversations() + } + + Thread.sleep(2000) + assertEquals(3, consent.size) + assertEquals(alixGroup.consentState(), ConsentState.DENIED) + job.cancel() + } } diff --git a/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt b/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt index 7945dc540..8a541ff1f 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt @@ -141,7 +141,7 @@ class DmTest { dm.send("gm") dm.sync() assertEquals( - boClient.preferences.consentList.conversationState(dm.id), + boClient.preferences.conversationState(dm.id), ConsentState.ALLOWED ) assertEquals(dm.consentState(), ConsentState.ALLOWED) @@ -330,13 +330,13 @@ class DmTest { val dm = boClient.conversations.findOrCreateDm(alix.walletAddress) assertEquals( - boClient.preferences.consentList.conversationState(dm.id), + boClient.preferences.conversationState(dm.id), ConsentState.ALLOWED ) assertEquals(dm.consentState(), ConsentState.ALLOWED) - boClient.preferences.consentList.setConsentState( + boClient.preferences.setConsentState( listOf( ConsentListEntry( dm.id, @@ -346,12 +346,12 @@ class DmTest { ) ) assertEquals( - boClient.preferences.consentList.conversationState(dm.id), + boClient.preferences.conversationState(dm.id), ConsentState.DENIED ) assertEquals(dm.consentState(), ConsentState.DENIED) - boClient.preferences.consentList.setConsentState( + boClient.preferences.setConsentState( listOf( ConsentListEntry( dm.id, @@ -361,7 +361,7 @@ class DmTest { ) ) assertEquals( - boClient.preferences.consentList.conversationState(dm.id), + boClient.preferences.conversationState(dm.id), ConsentState.ALLOWED ) assertEquals(dm.consentState(), ConsentState.ALLOWED) 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 1c89792cd..95ae9a273 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt @@ -112,11 +112,11 @@ class GroupTest { runBlocking { assertEquals( - boClient.preferences.consentList.conversationState(boGroup.id), + boClient.preferences.conversationState(boGroup.id), ConsentState.ALLOWED ) assertEquals( - alixClient.preferences.consentList.conversationState(alixGroup.id), + alixClient.preferences.conversationState(alixGroup.id), ConsentState.UNKNOWN ) } @@ -420,7 +420,7 @@ class GroupTest { group.sync() assertEquals(group.consentState(), ConsentState.ALLOWED) assertEquals( - boClient.preferences.consentList.conversationState(group.id), + boClient.preferences.conversationState(group.id), ConsentState.ALLOWED ) } @@ -731,12 +731,12 @@ class GroupTest { ) ) assertEquals( - boClient.preferences.consentList.conversationState(group.id), + boClient.preferences.conversationState(group.id), ConsentState.ALLOWED ) assertEquals(group.consentState(), ConsentState.ALLOWED) - boClient.preferences.consentList.setConsentState( + boClient.preferences.setConsentState( listOf( ConsentListEntry( group.id, @@ -746,14 +746,14 @@ class GroupTest { ) ) assertEquals( - boClient.preferences.consentList.conversationState(group.id), + boClient.preferences.conversationState(group.id), ConsentState.DENIED ) assertEquals(group.consentState(), ConsentState.DENIED) group.updateConsentState(ConsentState.ALLOWED) assertEquals( - boClient.preferences.consentList.conversationState(group.id), + boClient.preferences.conversationState(group.id), ConsentState.ALLOWED ) assertEquals(group.consentState(), ConsentState.ALLOWED) @@ -765,10 +765,10 @@ class GroupTest { runBlocking { val boGroup = boClient.conversations.newGroup(listOf(alix.walletAddress)) assertEquals( - boClient.preferences.consentList.inboxIdState(alixClient.inboxId), + boClient.preferences.inboxIdState(alixClient.inboxId), ConsentState.UNKNOWN ) - boClient.preferences.consentList.setConsentState( + boClient.preferences.setConsentState( listOf( ConsentListEntry( alixClient.inboxId, @@ -781,11 +781,11 @@ class GroupTest { assertEquals(alixMember!!.consentState, ConsentState.ALLOWED) assertEquals( - boClient.preferences.consentList.inboxIdState(alixClient.inboxId), + boClient.preferences.inboxIdState(alixClient.inboxId), ConsentState.ALLOWED ) - boClient.preferences.consentList.setConsentState( + boClient.preferences.setConsentState( listOf( ConsentListEntry( alixClient.inboxId, @@ -798,11 +798,11 @@ class GroupTest { assertEquals(alixMember!!.consentState, ConsentState.DENIED) assertEquals( - boClient.preferences.consentList.inboxIdState(alixClient.inboxId), + boClient.preferences.inboxIdState(alixClient.inboxId), ConsentState.DENIED ) - boClient.preferences.consentList.setConsentState( + boClient.preferences.setConsentState( listOf( ConsentListEntry( alixClient.address, @@ -814,11 +814,11 @@ class GroupTest { alixMember = boGroup.members().firstOrNull { it.inboxId == alixClient.inboxId } assertEquals(alixMember!!.consentState, ConsentState.ALLOWED) assertEquals( - boClient.preferences.consentList.inboxIdState(alixClient.inboxId), + boClient.preferences.inboxIdState(alixClient.inboxId), ConsentState.ALLOWED ) assertEquals( - boClient.preferences.consentList.addressState(alixClient.address), + boClient.preferences.addressState(alixClient.address), ConsentState.ALLOWED ) } diff --git a/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt b/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt index 648d0d31c..b2bf9538c 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt @@ -212,12 +212,12 @@ class SmartContractWalletTest { ) } assertEquals( - davonSCWClient.preferences.consentList.conversationState(davonGroup.id), + davonSCWClient.preferences.conversationState(davonGroup.id), ConsentState.ALLOWED ) assertEquals(davonGroup.consentState(), ConsentState.ALLOWED) - davonSCWClient.preferences.consentList.setConsentState( + davonSCWClient.preferences.setConsentState( listOf( ConsentListEntry( davonGroup.id, @@ -227,14 +227,14 @@ class SmartContractWalletTest { ) ) assertEquals( - davonSCWClient.preferences.consentList.conversationState(davonGroup.id), + davonSCWClient.preferences.conversationState(davonGroup.id), ConsentState.DENIED ) assertEquals(davonGroup.consentState(), ConsentState.DENIED) davonGroup.updateConsentState(ConsentState.ALLOWED) assertEquals( - davonSCWClient.preferences.consentList.conversationState(davonGroup.id), + davonSCWClient.preferences.conversationState(davonGroup.id), ConsentState.ALLOWED ) assertEquals(davonGroup.consentState(), ConsentState.ALLOWED) @@ -253,10 +253,10 @@ class SmartContractWalletTest { ) } assertEquals( - davonSCWClient.preferences.consentList.inboxIdState(boEOAClient.inboxId), + davonSCWClient.preferences.inboxIdState(boEOAClient.inboxId), ConsentState.UNKNOWN ) - davonSCWClient.preferences.consentList.setConsentState( + davonSCWClient.preferences.setConsentState( listOf( ConsentListEntry( boEOAClient.inboxId, @@ -269,11 +269,11 @@ class SmartContractWalletTest { assertEquals(alixMember!!.consentState, ConsentState.ALLOWED) assertEquals( - davonSCWClient.preferences.consentList.inboxIdState(boEOAClient.inboxId), + davonSCWClient.preferences.inboxIdState(boEOAClient.inboxId), ConsentState.ALLOWED ) - davonSCWClient.preferences.consentList.setConsentState( + davonSCWClient.preferences.setConsentState( listOf( ConsentListEntry( boEOAClient.inboxId, @@ -286,11 +286,11 @@ class SmartContractWalletTest { assertEquals(alixMember!!.consentState, ConsentState.DENIED) assertEquals( - davonSCWClient.preferences.consentList.inboxIdState(boEOAClient.inboxId), + davonSCWClient.preferences.inboxIdState(boEOAClient.inboxId), ConsentState.DENIED ) - davonSCWClient.preferences.consentList.setConsentState( + davonSCWClient.preferences.setConsentState( listOf( ConsentListEntry( eriSCWClient.address, @@ -302,11 +302,11 @@ class SmartContractWalletTest { alixMember = davonGroup.members().firstOrNull { it.inboxId == eriSCWClient.inboxId } assertEquals(alixMember!!.consentState, ConsentState.ALLOWED) assertEquals( - davonSCWClient.preferences.consentList.inboxIdState(eriSCWClient.inboxId), + davonSCWClient.preferences.inboxIdState(eriSCWClient.inboxId), ConsentState.ALLOWED ) assertEquals( - davonSCWClient.preferences.consentList.addressState(eriSCWClient.address), + davonSCWClient.preferences.addressState(eriSCWClient.address), ConsentState.ALLOWED ) } 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 f426a5b5f..129b93f61 100644 --- a/library/src/main/java/org/xmtp/android/library/Client.kt +++ b/library/src/main/java/org/xmtp/android/library/Client.kt @@ -340,10 +340,6 @@ class Client() { ffiClient.sendSyncRequest(FfiDeviceSyncKind.MESSAGES) } - suspend fun syncConsent() { - ffiClient.sendSyncRequest(FfiDeviceSyncKind.CONSENT) - } - suspend fun inboxStatesForInboxIds( refreshFromNetwork: Boolean, inboxIds: List, diff --git a/library/src/main/java/org/xmtp/android/library/PrivatePreferences.kt b/library/src/main/java/org/xmtp/android/library/PrivatePreferences.kt index e345327de..ad01e0b09 100644 --- a/library/src/main/java/org/xmtp/android/library/PrivatePreferences.kt +++ b/library/src/main/java/org/xmtp/android/library/PrivatePreferences.kt @@ -1,8 +1,15 @@ package org.xmtp.android.library +import android.util.Log +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import uniffi.xmtpv3.FfiConsent +import uniffi.xmtpv3.FfiConsentCallback import uniffi.xmtpv3.FfiConsentEntityType import uniffi.xmtpv3.FfiConsentState +import uniffi.xmtpv3.FfiDeviceSyncKind +import uniffi.xmtpv3.FfiSubscribeException import uniffi.xmtpv3.FfiXmtpClient enum class ConsentState { @@ -85,10 +92,32 @@ data class ConsentListEntry( get() = "${entryType.name}-$value" } -class ConsentList( - val client: Client, +data class PrivatePreferences( + var client: Client, private val ffiClient: FfiXmtpClient, ) { + suspend fun syncConsent() { + ffiClient.sendSyncRequest(FfiDeviceSyncKind.CONSENT) + } + + suspend fun streamConsent(): Flow = callbackFlow { + val consentCallback = object : FfiConsentCallback { + override fun onConsentUpdate(consent: List) { + consent.iterator().forEach { + trySend(it.fromFfiConsent()) + } + } + + override fun onError(error: FfiSubscribeException) { + Log.e("XMTP consent stream", error.message.toString()) + } + } + + val stream = ffiClient.conversations().streamConsent(consentCallback) + + awaitClose { stream.end() } + } + suspend fun setConsentState(entries: List) { ffiClient.setConsentStates(entries.map { it.toFfiConsent() }) } @@ -101,6 +130,14 @@ class ConsentList( ) } + private fun FfiConsent.fromFfiConsent(): ConsentListEntry { + return ConsentListEntry( + entity, + EntryType.fromFfiConsentEntityType(entityType), + ConsentState.fromFfiConsentState(state), + ) + } + suspend fun addressState(address: String): ConsentState { return ConsentState.fromFfiConsentState( ffiClient.getConsentState( @@ -128,9 +165,3 @@ class ConsentList( ) } } - -data class PrivatePreferences( - var client: Client, - private val ffiClient: FfiXmtpClient, - var consentList: ConsentList = ConsentList(client, ffiClient), -)