From 33bdf968c5c664275043921fcb4cb3ce139f76fa Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:10:00 +0300 Subject: [PATCH] Expose global account data get and set on MatrixClient MatrixClient had no way to read or write global account data although the underlying client supports it. Add getAccountData and setAccountData to the interface and implement them in RustMatrixClient using the same withContext/runCatchingExceptions idiom as the neighbouring methods. The content is the raw JSON string the SDK returns and accepts: event types are open ended, so a typed wrapper belongs to whichever caller needs one. Scope is limited to global account data; the room level variant and the change notification APIs can follow when a feature needs them. --- .../libraries/matrix/api/MatrixClient.kt | 10 +++++++ .../libraries/matrix/impl/RustMatrixClient.kt | 12 +++++++++ .../matrix/impl/RustMatrixClientTest.kt | 27 +++++++++++++++++++ .../impl/fixtures/fakes/FakeFfiClient.kt | 10 +++++++ .../libraries/matrix/test/FakeMatrixClient.kt | 10 +++++++ 5 files changed, 69 insertions(+) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 0dd8821a4f..44351e5198 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -210,6 +210,16 @@ interface MatrixClient { */ suspend fun addRecentEmoji(emoji: String): Result + /** + * Returns the raw JSON content of the global account data event of type [eventType], or null if it is not set. + */ + suspend fun getAccountData(eventType: String): Result + + /** + * Sets the global account data event of type [eventType] to the raw JSON [content]. + */ + suspend fun setAccountData(eventType: String, content: String): Result + /** * Marks the room with the provided [roomId] as read, sending a fully read receipt for [eventId]. * diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 88b1f05909..e0c1ce7662 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -806,6 +806,18 @@ class RustMatrixClient( } } + override suspend fun getAccountData(eventType: String): Result = withContext(sessionDispatcher) { + runCatchingExceptions { + innerClient.accountData(eventType) + } + } + + override suspend fun setAccountData(eventType: String, content: String): Result = withContext(sessionDispatcher) { + runCatchingExceptions { + innerClient.setAccountData(eventType, content) + } + } + override suspend fun canLinkNewDevice(): Result = withContext(sessionDispatcher) { runCatchingExceptions { innerClient.isLoginWithQrCodeSupported() diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt index 4ea4bb64e3..a4264407df 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt @@ -44,6 +44,9 @@ import org.matrix.rustcomponents.sdk.StoreSizes import org.matrix.rustcomponents.sdk.UserProfile import java.io.File +private const val AN_ACCOUNT_DATA_EVENT_TYPE = "org.example.custom" +private const val AN_ACCOUNT_DATA_CONTENT = """{"key":"value"}""" + class RustMatrixClientTest { @Test fun `ensure that sessionId and deviceId can be retrieved from the client`() = runTest { @@ -138,6 +141,30 @@ class RustMatrixClientTest { assertThat(createParameters?.historyVisibilityOverride).isEqualTo(RoomHistoryVisibility.Invited) } + @Test + fun `getAccountData returns the raw content provided by the client`() = runTest { + val accountDataResult = lambdaRecorder { AN_ACCOUNT_DATA_CONTENT } + val client = createRustMatrixClient( + client = FakeFfiClient(accountDataResult = accountDataResult) + ) + + assertThat(client.getAccountData(AN_ACCOUNT_DATA_EVENT_TYPE).getOrThrow()).isEqualTo(AN_ACCOUNT_DATA_CONTENT) + accountDataResult.assertions().isCalledOnce().with(value(AN_ACCOUNT_DATA_EVENT_TYPE)) + client.destroy() + } + + @Test + fun `setAccountData forwards the event type and the raw content to the client`() = runTest { + val setAccountDataResult = lambdaRecorder { _, _ -> } + val client = createRustMatrixClient( + client = FakeFfiClient(setAccountDataResult = setAccountDataResult) + ) + + assertThat(client.setAccountData(AN_ACCOUNT_DATA_EVENT_TYPE, AN_ACCOUNT_DATA_CONTENT).isSuccess).isTrue() + setAccountDataResult.assertions().isCalledOnce().with(value(AN_ACCOUNT_DATA_EVENT_TYPE), value(AN_ACCOUNT_DATA_CONTENT)) + client.destroy() + } + private fun TestScope.createRustMatrixClient( client: Client = FakeFfiClient(), sessionStore: SessionStore = InMemorySessionStore( diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt index efae379415..e8dde253b7 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt @@ -55,6 +55,8 @@ class FakeFfiClient( private val getStoreSizesResult: () -> StoreSizes = { lambdaError() }, private val createRoomResult: (CreateRoomParameters) -> String = { lambdaError() }, private val homeserverCapabilities: HomeserverCapabilities = FakeFfiHomeserverCapabilities(), + private val accountDataResult: (String) -> String? = { lambdaError() }, + private val setAccountDataResult: (String, String) -> Unit = { _, _ -> lambdaError() }, private val closeResult: () -> Unit = {}, ) : Client(NoHandle) { override fun userId(): String = userId @@ -114,5 +116,13 @@ class FakeFfiClient( return homeserverCapabilities } + override suspend fun accountData(eventType: String): String? = simulateLongTask { + accountDataResult(eventType) + } + + override suspend fun setAccountData(eventType: String, content: String) = simulateLongTask { + setAccountDataResult(eventType, content) + } + override fun close() = closeResult() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index cdf7cc85cf..3679bb8c99 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -124,6 +124,8 @@ class FakeMatrixClient( private val getJoinedRoomIdsResult: () -> Result> = { Result.success(emptySet()) }, private val getRecentEmojisLambda: () -> Result> = { Result.success(emptyList()) }, private val addRecentEmojiLambda: (String) -> Result = { Result.success(Unit) }, + private val getAccountDataLambda: (String) -> Result = { lambdaError() }, + private val setAccountDataLambda: (String, String) -> Result = { _, _ -> lambdaError() }, private val markRoomAsFullyReadResult: (RoomId, EventId) -> Result = { _, _ -> lambdaError() }, private val markAllRoomsAsReadResult: () -> Result = { Result.success(Unit) }, private val performDatabaseVacuumLambda: () -> Result = { lambdaError() }, @@ -407,6 +409,14 @@ class FakeMatrixClient( return getRecentEmojisLambda() } + override suspend fun getAccountData(eventType: String): Result { + return getAccountDataLambda(eventType) + } + + override suspend fun setAccountData(eventType: String, content: String): Result { + return setAccountDataLambda(eventType, content) + } + override suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result { return markRoomAsFullyReadResult(roomId, eventId) }