mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Allow adding extra elements to the developer settings from external modules (#7230)
* Add `ExtraDeveloperOptionsRenderer` to display extra UI components in the developer settings screen. It defaults to not displaying anything, using `NoopExtraDeveloperOptionsRenderer` * Add `EnterpriseService.overriddenElementWellKnown` method to fetch the current overridden element well known value, if any * Modify the well known retrievers to use the overridden value if it's there. Also extract the JSON parsing to its own `ElementWellKnownParser` component. * Bump enterprise repo reference.
This commit is contained in:
committed by
GitHub
parent
10161a686f
commit
925376b6af
+12
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.wellknown.api
|
||||
|
||||
fun interface ElementWellKnownParser {
|
||||
fun parse(json: String): Result<ElementWellKnown>
|
||||
}
|
||||
@@ -34,6 +34,7 @@ dependencies {
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.cachestore.api)
|
||||
implementation(projects.features.enterprise.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
@@ -41,5 +42,6 @@ dependencies {
|
||||
testImplementation(projects.libraries.cachestore.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.wellknown.test)
|
||||
testImplementation(projects.features.enterprise.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultElementWellKnownParser(
|
||||
private val jsonProvider: JsonProvider,
|
||||
) : ElementWellKnownParser {
|
||||
override fun parse(json: String): Result<ElementWellKnown> {
|
||||
return runCatchingExceptions {
|
||||
val decoder = jsonProvider()
|
||||
decoder.decodeFromString<InternalElementWellKnown>(json).map()
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-3
@@ -9,13 +9,14 @@
|
||||
package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||
import io.element.android.libraries.wellknown.api.ElementWellknownStore
|
||||
import io.element.android.libraries.wellknown.api.SessionWellknownRetriever
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||
@@ -26,14 +27,20 @@ import timber.log.Timber
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultSessionWellknownRetriever(
|
||||
private val matrixClient: MatrixClient,
|
||||
private val json: JsonProvider,
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val elementWellknownStore: ElementWellknownStore,
|
||||
private val elementWellKnownParser: ElementWellKnownParser,
|
||||
private val enterpriseService: EnterpriseService,
|
||||
) : SessionWellknownRetriever {
|
||||
private val domain by lazy { matrixClient.userIdServerName() }
|
||||
|
||||
override suspend fun getElementWellKnown(): WellknownRetrieverResult<ElementWellKnown> {
|
||||
val overriddenElementWellKnown = enterpriseService.overriddenElementWellKnown()
|
||||
if (overriddenElementWellKnown != null) {
|
||||
return WellknownRetrieverResult.Success(overriddenElementWellKnown)
|
||||
}
|
||||
|
||||
val cacheData = elementWellknownStore.get(domain)
|
||||
return when (cacheData) {
|
||||
is WellknownRetrieverResult.Success -> {
|
||||
@@ -69,7 +76,7 @@ class DefaultSessionWellknownRetriever(
|
||||
.getUrl(url)
|
||||
.mapCatchingExceptions {
|
||||
val data = String(it)
|
||||
val parsed = json().decodeFromString<InternalElementWellKnown>(data).map()
|
||||
val parsed = elementWellKnownParser.parse(data).getOrThrow()
|
||||
// Also store in cache, if valid
|
||||
elementWellknownStore.update(domain, data)
|
||||
.onFailure { exception ->
|
||||
|
||||
+3
-3
@@ -9,11 +9,11 @@ package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||
import io.element.android.libraries.cachestore.api.CacheData
|
||||
import io.element.android.libraries.cachestore.api.CacheStore
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||
import io.element.android.libraries.wellknown.api.ElementWellknownStore
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
@@ -21,14 +21,14 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultWellKnownStore(
|
||||
private val cacheStore: CacheStore,
|
||||
private val json: JsonProvider,
|
||||
private val elementWellKnownParser: ElementWellKnownParser,
|
||||
private val systemClock: SystemClock,
|
||||
) : ElementWellknownStore {
|
||||
override suspend fun get(domain: String): WellknownRetrieverResult<ElementWellKnown> {
|
||||
return runCatchingExceptions {
|
||||
val cachedData = cacheStore.getData(key(domain))
|
||||
if (cachedData != null) {
|
||||
val data = json().decodeFromString<InternalElementWellKnown>(cachedData.value).map()
|
||||
val data = elementWellKnownParser.parse(cachedData.value).getOrThrow()
|
||||
if (systemClock.epochMillis() > cachedData.updatedAt + CACHE_VALIDITY_MILLIS) {
|
||||
WellknownRetrieverResult.Outdated(data)
|
||||
} else {
|
||||
|
||||
+7
@@ -10,6 +10,7 @@ package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
@@ -28,8 +29,14 @@ class DefaultWellknownRetriever(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val elementWellknownStore: ElementWellknownStore,
|
||||
private val jsonProvider: JsonProvider,
|
||||
private val enterpriseService: EnterpriseService,
|
||||
) : WellknownRetriever {
|
||||
override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult<ElementWellKnown> {
|
||||
val overriddenElementWellKnown = enterpriseService.overriddenElementWellKnown()
|
||||
if (overriddenElementWellKnown != null) {
|
||||
return WellknownRetrieverResult.Success(overriddenElementWellKnown)
|
||||
}
|
||||
|
||||
val domain = URL(baseUrl).host
|
||||
return buildWellknownApi(baseUrl)
|
||||
.map { wellknownApi ->
|
||||
|
||||
+28
-4
@@ -11,15 +11,16 @@
|
||||
package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.wellknown.test.FakeElementWellknownStore
|
||||
import io.element.android.features.wellknown.test.anElementWellKnown
|
||||
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
@@ -276,7 +277,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
Result.success("{}".toByteArray())
|
||||
},
|
||||
cacheStore = cacheStore,
|
||||
jsonProvider = JsonProvider { error("Failed to parse JSON") }
|
||||
elementWellKnownParser = { Result.failure(IllegalStateException("Failed to parse JSON")) }
|
||||
)
|
||||
assertThat(sut.getElementWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java)
|
||||
// Ensure that the cache is deleted after the failure to parse it
|
||||
@@ -320,20 +321,43 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get element wellknown was overridden`() = runTest {
|
||||
val getLambda = lambdaRecorder<String, Result<ByteArray>> { Result.failure(IllegalStateException("BOOM")) }
|
||||
val wellKnown = anElementWellKnown()
|
||||
|
||||
val sut = createDefaultSessionWellknownRetriever(
|
||||
getUrlLambda = getLambda,
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
overrideWellKnownResult = { wellKnown }
|
||||
)
|
||||
)
|
||||
|
||||
// The overridden value is returned
|
||||
assertThat(sut.getElementWellKnown()).isEqualTo(
|
||||
WellknownRetrieverResult.Success(wellKnown)
|
||||
)
|
||||
|
||||
// And the endpoint is never hit
|
||||
getLambda.assertions().isNeverCalled()
|
||||
}
|
||||
|
||||
private fun parsedWellKnownContent() = DefaultJsonProvider().invoke().decodeFromString<InternalElementWellKnown>(WELLKNOWN_CONTENT).map()
|
||||
|
||||
private fun TestScope.createDefaultSessionWellknownRetriever(
|
||||
cacheStore: FakeElementWellknownStore = FakeElementWellknownStore(),
|
||||
getUrlLambda: (String) -> Result<ByteArray>,
|
||||
jsonProvider: JsonProvider = DefaultJsonProvider(),
|
||||
elementWellKnownParser: ElementWellKnownParser = DefaultElementWellKnownParser(DefaultJsonProvider()),
|
||||
enterpriseService: FakeEnterpriseService = FakeEnterpriseService(overrideWellKnownResult = { null }),
|
||||
) = DefaultSessionWellknownRetriever(
|
||||
matrixClient = FakeMatrixClient(
|
||||
userIdServerNameLambda = { "user.domain.org" },
|
||||
getUrlLambda = getUrlLambda,
|
||||
),
|
||||
json = jsonProvider,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
elementWellknownStore = cacheStore,
|
||||
elementWellKnownParser = elementWellKnownParser,
|
||||
enterpriseService = enterpriseService,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
+3
-1
@@ -13,6 +13,7 @@ import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||
import io.element.android.libraries.cachestore.api.CacheData
|
||||
import io.element.android.libraries.sessionstorage.test.InMemoryCacheStore
|
||||
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnownParser
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
@@ -150,9 +151,10 @@ class DefaultWellKnownStoreTest {
|
||||
private fun createDefaultWellKnownStore(
|
||||
cacheStore: InMemoryCacheStore = InMemoryCacheStore(),
|
||||
systemClock: FakeSystemClock = FakeSystemClock(),
|
||||
elementWellKnownParser: ElementWellKnownParser = DefaultElementWellKnownParser(jsonProvider),
|
||||
) = DefaultWellKnownStore(
|
||||
cacheStore = cacheStore,
|
||||
json = jsonProvider,
|
||||
systemClock = systemClock,
|
||||
elementWellKnownParser = elementWellKnownParser,
|
||||
)
|
||||
}
|
||||
|
||||
+25
@@ -11,6 +11,7 @@
|
||||
package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.wellknown.test.FakeElementWellknownStore
|
||||
import io.element.android.features.wellknown.test.anElementWellKnown
|
||||
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||
@@ -19,6 +20,7 @@ import io.element.android.libraries.network.RetrofitFactory
|
||||
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
@@ -236,6 +238,27 @@ class DefaultWellknownRetrieverTest {
|
||||
assertThat(cacheStore.get(WELLKNOWN_URL)).isEqualTo(WellknownRetrieverResult.NotFound)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get element wellknown was overridden`() = runTest {
|
||||
val getLambda = lambdaRecorder<Request, Call> { mockCall() }
|
||||
val wellKnown = anElementWellKnown()
|
||||
|
||||
val sut = createDefaultWellknownRetriever(
|
||||
callFactory = getLambda,
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
overrideWellKnownResult = { wellKnown }
|
||||
)
|
||||
)
|
||||
|
||||
// The overridden value is returned
|
||||
assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo(
|
||||
WellknownRetrieverResult.Success(wellKnown)
|
||||
)
|
||||
|
||||
// And the endpoint is never hit
|
||||
getLambda.assertions().isNeverCalled()
|
||||
}
|
||||
|
||||
private fun defaultResponse(
|
||||
body: String = WELLKNOWN_CONTENT,
|
||||
status: Int = 200,
|
||||
@@ -251,6 +274,7 @@ class DefaultWellknownRetrieverTest {
|
||||
callFactory: Call.Factory = Call.Factory { _: Request -> mockCall(defaultResponse()) },
|
||||
cacheStore: FakeElementWellknownStore = FakeElementWellknownStore(),
|
||||
jsonProvider: JsonProvider = DefaultJsonProvider(),
|
||||
enterpriseService: FakeEnterpriseService = FakeEnterpriseService(overrideWellKnownResult = { null }),
|
||||
) = DefaultWellknownRetriever(
|
||||
retrofitFactory = RetrofitFactory(
|
||||
callFactory = { callFactory },
|
||||
@@ -258,6 +282,7 @@ class DefaultWellknownRetrieverTest {
|
||||
),
|
||||
jsonProvider = jsonProvider,
|
||||
elementWellknownStore = cacheStore,
|
||||
enterpriseService = enterpriseService,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
Reference in New Issue
Block a user