diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ecd5ed8e7b..f2c7d37baa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -259,6 +259,7 @@ dependencies { if (isEnterpriseBuild) { allEnterpriseImpl(project) implementation(projects.appicon.enterprise) + implementation(project(":enterprise:features:enterprise:shared")) } else { implementation(projects.features.enterprise.implFoss) implementation(projects.appicon.element) diff --git a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt index 7564db5d95..39c70e35d8 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt @@ -18,7 +18,7 @@ import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.Provides import dev.zacsweers.metro.SingleIn import io.element.android.appconfig.ApplicationConfig -import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.features.enterprise.api.IsEnterpriseBuild import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.meta.BuildMeta @@ -74,17 +74,18 @@ object AppModule { fun providesBuildMeta( @ApplicationContext context: Context, buildType: BuildType, - enterpriseService: EnterpriseService, + isEnterpriseBuild: IsEnterpriseBuild, ): BuildMeta { val applicationName = ApplicationConfig.APPLICATION_NAME.takeIf { it.isNotEmpty() } ?: context.getString(R.string.app_name) + val isEnterprise = isEnterpriseBuild() return BuildMeta( isDebuggable = BuildConfig.DEBUG, buildType = buildType, applicationName = applicationName, - productionApplicationName = if (enterpriseService.isEnterpriseBuild) applicationName else ApplicationConfig.PRODUCTION_APPLICATION_NAME, - desktopApplicationName = if (enterpriseService.isEnterpriseBuild) applicationName else ApplicationConfig.DESKTOP_APPLICATION_NAME, + productionApplicationName = if (isEnterprise) applicationName else ApplicationConfig.PRODUCTION_APPLICATION_NAME, + desktopApplicationName = if (isEnterprise) applicationName else ApplicationConfig.DESKTOP_APPLICATION_NAME, applicationId = BuildConfig.APPLICATION_ID, - isEnterpriseBuild = enterpriseService.isEnterpriseBuild, + isEnterpriseBuild = isEnterprise, // TODO EAx Config.LOW_PRIVACY_LOG_ENABLE, lowPrivacyLoggingEnabled = false, versionName = BuildConfig.VERSION_NAME, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index d7b768132b..cd071131d9 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -51,7 +51,7 @@ import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.SessionEnterpriseService -import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider +import io.element.android.features.enterprise.api.remoteconfig.CustomMapTilerConfigProvider import io.element.android.features.ftue.api.FtueEntryPoint import io.element.android.features.ftue.api.state.FtueService import io.element.android.features.ftue.api.state.FtueState @@ -157,7 +157,7 @@ class LoggedInFlowNode( private val analyticsRoomListStateWatcher: AnalyticsRoomListStateWatcher, private val createRoomEntryPoint: CreateRoomEntryPoint, private val activeLiveLocationShareManager: ActiveLiveLocationShareManager, - private val remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider, + private val customMapTilerConfigProvider: CustomMapTilerConfigProvider, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Placeholder, @@ -685,7 +685,7 @@ class LoggedInFlowNode( val currentMapTilerConfig = LocalMapTilerConfig.current val updatedMapTilerConfig by produceState(currentMapTilerConfig) { - value = remoteEnterpriseConfigProvider.get(matrixClient.sessionId).dataOrNull()?.mapTilerConfig ?: currentMapTilerConfig + value = customMapTilerConfigProvider.get().getOrNull() ?: currentMapTilerConfig } ElementThemeApp( diff --git a/enterprise b/enterprise index f5f910856c..3bf0df1f25 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit f5f910856cfd4f982d1dcce16a80f8fedfbcbd31 +Subproject commit 3bf0df1f25c4df8ee6cc226b00d9b6a675b37a09 diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt index 7d23ad0ea1..2f1cbe1187 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.graphics.Color import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.libraries.matrix.api.UrlContentFetcher import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.wellknown.api.ElementWellKnown import kotlinx.coroutines.flow.Flow /** @@ -21,9 +20,6 @@ import kotlinx.coroutines.flow.Flow * On a standard build this reports permissive defaults, so callers do not need to branch on the build type themselves. */ interface EnterpriseService { - /** Whether this build is an enterprise one; `false` for the public app. */ - val isEnterpriseBuild: Boolean - /** * Whether the given session belongs to an enterprise deployment, which can be true even on a standard build. * @@ -41,17 +37,26 @@ interface EnterpriseService { suspend fun tweakMasUrl(url: String, homeserver: String, urlContentFetcher: UrlContentFetcher): String /** - * The homeservers the user is allowed to sign in to; an empty list, or one containing [ANY_ACCOUNT_PROVIDER], means no restriction. + * Returns the list of homeservers the user is allowed to sign in to. + * + * If the list is empty or contains the special value [ANY_ACCOUNT_PROVIDER], the user is allowed to sign in to any homeserver. */ - fun defaultHomeserverList(): List + fun homeserverAllowList(): List /** - * Whether the user is allowed to sign in to a given homeserver, according to [defaultHomeserverList]. + * Whether the user is allowed to sign in to a given homeserver, according to [homeserverAllowList]. * * @param homeserverUrl the server the user is trying to use. */ suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean + /** + * Whether the given homeserver enforces the use of Element Pro or a derived app. + * + * @param homeserverUrl the homeserver to check. + */ + suspend fun isElementProEnforced(homeserverUrl: String): Boolean + /** * Override the brand color. * @param sessionId the session to override the brand color for, or null to set the brand color to use when there is no session. @@ -94,11 +99,6 @@ interface EnterpriseService { */ fun getNoisyNotificationChannelId(sessionId: SessionId): String? - /** - * Gets the overridden Element Well-Known data if it has been set, or null if not set. - */ - fun overriddenElementWellKnown(): ElementWellKnown? - /** * Gets the Element Server Suite (ESS) config endpoint URL for the given domain. * Returns `null` when this build does not read its configuration from an ESS deployment. @@ -113,7 +113,7 @@ interface EnterpriseService { } fun EnterpriseService.canConnectToAnyHomeserver(): Boolean { - return defaultHomeserverList().let { + return homeserverAllowList().let { it.isEmpty() || it.contains(EnterpriseService.ANY_ACCOUNT_PROVIDER) } } diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/IsEnterpriseBuild.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/IsEnterpriseBuild.kt new file mode 100644 index 0000000000..83591ea7bd --- /dev/null +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/IsEnterpriseBuild.kt @@ -0,0 +1,18 @@ +/* + * 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.features.enterprise.api + +/** + * A use case to check if this build of the app is an enterprise build. + */ +fun interface IsEnterpriseBuild { + /** + * Returns `true` if this build of the app is an enterprise build, `false` otherwise. + */ + operator fun invoke(): Boolean +} diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt index 84067ce56f..94f455f2ff 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt @@ -14,6 +14,9 @@ package io.element.android.features.enterprise.api * [init] must have run before the other members return meaningful answers. */ interface SessionEnterpriseService { + /** Loads the deployment configuration of this session; to be called once when the session starts. */ + suspend fun init() + /** Whether Element Call may be used on this session, which a deployment can turn off. */ suspend fun isElementCallAvailable(): Boolean @@ -26,7 +29,4 @@ interface SessionEnterpriseService { /** Whether the homeserver forbids creating encrypted rooms, in which case the app must not offer the option. */ suspend fun isEncryptionDisabledByHomeserver(): Boolean - - /** Loads the deployment configuration of this session; to be called once when the session starts. */ - suspend fun init() } diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/CustomMapTilerConfigProvider.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/CustomMapTilerConfigProvider.kt new file mode 100644 index 0000000000..9bfd3c940a --- /dev/null +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/CustomMapTilerConfigProvider.kt @@ -0,0 +1,15 @@ +/* + * 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.features.enterprise.api.remoteconfig + +/** + * A provider of a runtime-fetched MapTiler configuration, which can be used to override the default MapTiler configuration. + */ +fun interface CustomMapTilerConfigProvider { + suspend fun get(): Result +} diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/MapTilerConfig.kt similarity index 57% rename from libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt rename to features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/MapTilerConfig.kt index 88a89e7a49..3e8b8b14e1 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/MapTilerConfig.kt @@ -1,29 +1,18 @@ /* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector Ltd. + * 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 +package io.element.android.features.enterprise.api.remoteconfig -data class ElementWellKnown( - val registrationHelperUrl: String?, - val enforceElementPro: Boolean?, - val rageshakeUrl: String?, - val brandColor: String?, - val notificationSound: String?, - val identityProviderAppScheme: String?, - val customRecoveryPassphrase: CustomRecoveryPassphrase?, - val contentScannerUrl: String?, - val forceDisableE2EE: Boolean?, - val mapTilerConfig: MapTilerConfig?, -) +import androidx.compose.runtime.Immutable /** * A `MapTiler` configuration received from the server. This configuration is used to display maps in the app. */ +@Immutable data class MapTilerConfig( /** The API key to use with MapTiler. This is always not null, but can be empty/blank. */ val apiKey: String, diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/RemoteEnterpriseConfig.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/RemoteEnterpriseConfig.kt deleted file mode 100644 index c9adf21eec..0000000000 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/RemoteEnterpriseConfig.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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.features.enterprise.api.remoteconfig - -import io.element.android.libraries.wellknown.api.ElementWellKnown - -/** - * Typealias for the well-known configuration used for enterprise features. - * - * This will allow for easier refactoring in the future when we'll want to move as much of its logic as possible to this module. - */ -typealias RemoteEnterpriseConfig = ElementWellKnown diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/RemoteEnterpriseConfigProvider.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/RemoteEnterpriseConfigProvider.kt deleted file mode 100644 index 4507b1a72a..0000000000 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/RemoteEnterpriseConfigProvider.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.features.enterprise.api.remoteconfig - -import io.element.android.libraries.matrix.api.UrlContentFetcher -import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult - -/** - * Wrapper to fetch the remote enterprise configuration for a given [SessionId], either from a local cache or from a remote source. - */ -interface RemoteEnterpriseConfigProvider { - /** - * Returns the configuration of a session, served from the cache when possible. - * A stale entry is reported as outdated while a refresh runs in the background, so this rarely waits on the network. - * - * @param sessionId the session whose configuration is requested. - */ - suspend fun get(sessionId: SessionId): WellknownRetrieverResult - - /** - * Creates a provider, which needs a fetcher because the configuration is read over HTTP. - */ - interface Factory { - /** - * @param urlContentFetcher used to perform the request; a client that is not authenticated yet also works. - */ - fun create(urlContentFetcher: UrlContentFetcher): RemoteEnterpriseConfigProvider - } -} diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/RemoteEnterpriseConfigResult.kt similarity index 63% rename from libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt rename to features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/RemoteEnterpriseConfigResult.kt index 98af981a66..5f687ff052 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/remoteconfig/RemoteEnterpriseConfigResult.kt @@ -1,33 +1,32 @@ /* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2025 New Vector Ltd. + * 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 +package io.element.android.features.enterprise.api.remoteconfig -sealed interface WellknownRetrieverResult { +sealed interface RemoteEnterpriseConfigResult { /** * Well-known data has been successfully retrieved. */ - data class Success(val data: T) : WellknownRetrieverResult + data class Success(val data: T) : RemoteEnterpriseConfigResult /** * Well-known data has been retrieved from the local cache but is outdated and a manual refresh has been launched. */ - data class Outdated(val data: T) : WellknownRetrieverResult + data class Outdated(val data: T) : RemoteEnterpriseConfigResult /** * Well-known data is not found (file does not exist server side, we got a 404). */ - data object NotFound : WellknownRetrieverResult + data object NotFound : RemoteEnterpriseConfigResult /** * Any other error. */ - data class Error(val exception: Exception) : WellknownRetrieverResult + data class Error(val exception: Exception) : RemoteEnterpriseConfigResult fun dataOrNull(): T? = when (this) { is Success -> data diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt index c75a2cb527..d07c0db569 100644 --- a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt +++ b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt @@ -16,18 +16,16 @@ import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.matrix.api.UrlContentFetcher import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.wellknown.api.ElementWellKnown import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf @ContributesBinding(AppScope::class) class DefaultEnterpriseService : EnterpriseService { - override val isEnterpriseBuild = false - override suspend fun isEnterpriseUser(sessionId: SessionId) = false override suspend fun tweakMasUrl(url: String, homeserver: String, urlContentFetcher: UrlContentFetcher) = url - override fun defaultHomeserverList(): List = emptyList() + override fun homeserverAllowList(): List = emptyList() override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true + override suspend fun isElementProEnforced(homeserverUrl: String): Boolean = false override suspend fun overrideBrandColor(sessionId: SessionId?, brandColor: String?) = Unit @@ -48,7 +46,5 @@ class DefaultEnterpriseService : EnterpriseService { override fun getNoisyNotificationChannelId(sessionId: SessionId): String? = null - override fun overriddenElementWellKnown(): ElementWellKnown? = null - override fun essConfigEndpointUrl(domain: String): String? = null } diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultIsEnterpriseBuild.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultIsEnterpriseBuild.kt new file mode 100644 index 0000000000..73da4d5d03 --- /dev/null +++ b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultIsEnterpriseBuild.kt @@ -0,0 +1,17 @@ +/* + * 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.features.enterprise.impl + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.enterprise.api.IsEnterpriseBuild + +@ContributesBinding(AppScope::class) +class DefaultIsEnterpriseBuild : IsEnterpriseBuild { + override fun invoke(): Boolean = false +} diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/remoteconfig/DefaultCustomMapTilerConfigProvider.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/remoteconfig/DefaultCustomMapTilerConfigProvider.kt new file mode 100644 index 0000000000..2b136d3739 --- /dev/null +++ b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/remoteconfig/DefaultCustomMapTilerConfigProvider.kt @@ -0,0 +1,18 @@ +/* + * 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.features.enterprise.impl.remoteconfig + +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.enterprise.api.remoteconfig.CustomMapTilerConfigProvider +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig +import io.element.android.libraries.di.SessionScope + +@ContributesBinding(SessionScope::class) +class DefaultCustomMapTilerConfigProvider : CustomMapTilerConfigProvider { + override suspend fun get(): Result = Result.success(null) +} diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/remoteconfig/DefaultRemoteEnterpriseConfigProvider.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/remoteconfig/DefaultRemoteEnterpriseConfigProvider.kt deleted file mode 100644 index fbc5cd5827..0000000000 --- a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/remoteconfig/DefaultRemoteEnterpriseConfigProvider.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.features.enterprise.impl.remoteconfig - -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.ContributesBinding -import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfig -import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider -import io.element.android.libraries.matrix.api.UrlContentFetcher -import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult - -@ContributesBinding(AppScope::class) -class DefaultRemoteEnterpriseConfigProvider : RemoteEnterpriseConfigProvider { - override suspend fun get(sessionId: SessionId): WellknownRetrieverResult { - return WellknownRetrieverResult.NotFound - } - - @ContributesBinding(AppScope::class) - class Factory : RemoteEnterpriseConfigProvider.Factory { - override fun create(urlContentFetcher: UrlContentFetcher): RemoteEnterpriseConfigProvider { - return DefaultRemoteEnterpriseConfigProvider() - } - } -} diff --git a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt index 2bee3f8b4d..474990c6d9 100644 --- a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt +++ b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt @@ -19,15 +19,9 @@ import org.junit.Test class DefaultEnterpriseServiceTest { @Test - fun `isEnterpriseBuild is false`() { + fun homeserverWhitelist() { val defaultEnterpriseService = DefaultEnterpriseService() - assertThat(defaultEnterpriseService.isEnterpriseBuild).isFalse() - } - - @Test - fun `defaultHomeserverList should return empty list`() { - val defaultEnterpriseService = DefaultEnterpriseService() - assertThat(defaultEnterpriseService.defaultHomeserverList()).isEmpty() + assertThat(defaultEnterpriseService.homeserverAllowList()).isEmpty() } @Test diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt index 858c46e3bf..e346e5934a 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt @@ -14,7 +14,6 @@ import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.matrix.api.UrlContentFetcher import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.simulateLongTask import kotlinx.coroutines.flow.Flow @@ -22,7 +21,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow class FakeEnterpriseService( - override val isEnterpriseBuild: Boolean = false, private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() }, private val defaultHomeserverListResult: () -> List = { emptyList() }, private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() }, @@ -33,8 +31,8 @@ class FakeEnterpriseService( private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() }, private val getNoisyNotificationChannelIdResult: (SessionId?) -> String? = { lambdaError() }, private val tweakMasUrlResult: (String, String, UrlContentFetcher) -> String = { _, _, _ -> lambdaError() }, - private val overrideWellKnownResult: () -> ElementWellKnown? = { lambdaError() }, private val essConfigEndpointUrlResult: (String) -> String = { lambdaError() }, + private val isElementProEnforcedResult: (String) -> Boolean = { lambdaError() }, ) : EnterpriseService { private val brandColorState = MutableStateFlow(initialBrandColor) private val semanticColorsState = MutableStateFlow(initialSemanticColors) @@ -47,7 +45,7 @@ class FakeEnterpriseService( tweakMasUrlResult(url, homeserver, urlContentFetcher) } - override fun defaultHomeserverList(): List { + override fun homeserverAllowList(): List { return defaultHomeserverListResult() } @@ -55,6 +53,10 @@ class FakeEnterpriseService( isAllowedToConnectToHomeserverResult(homeserverUrl) } + override suspend fun isElementProEnforced(homeserverUrl: String): Boolean = simulateLongTask { + isElementProEnforcedResult(homeserverUrl) + } + override suspend fun overrideBrandColor(sessionId: SessionId?, brandColor: String?) = simulateLongTask { overrideBrandColorResult(sessionId, brandColor) } @@ -84,10 +86,6 @@ class FakeEnterpriseService( return getNoisyNotificationChannelIdResult(sessionId) } - override fun overriddenElementWellKnown(): ElementWellKnown? { - return overrideWellKnownResult() - } - override fun essConfigEndpointUrl(domain: String): String { return essConfigEndpointUrlResult(domain) } diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProvider.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProvider.kt deleted file mode 100644 index 2c57dbb530..0000000000 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProvider.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.features.enterprise.test.remoteconfig - -import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfig -import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider -import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult - -class FakeRemoteEnterpriseConfigProvider( - private val getResult: (SessionId) -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound } -) : RemoteEnterpriseConfigProvider { - override suspend fun get(sessionId: SessionId): WellknownRetrieverResult { - return getResult(sessionId) - } -} diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProviderFactory.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProviderFactory.kt deleted file mode 100644 index a49bbba279..0000000000 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProviderFactory.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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.features.enterprise.test.remoteconfig - -import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider -import io.element.android.libraries.matrix.api.UrlContentFetcher - -class FakeRemoteEnterpriseConfigProviderFactory( - private val remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(), -) : RemoteEnterpriseConfigProvider.Factory { - override fun create(urlContentFetcher: UrlContentFetcher): RemoteEnterpriseConfigProvider = remoteEnterpriseConfigProvider -} diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/Fixtures.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/Fixtures.kt new file mode 100644 index 0000000000..fd429eada5 --- /dev/null +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/Fixtures.kt @@ -0,0 +1,22 @@ +/* + * 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.features.enterprise.test.remoteconfig + +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig + +fun aMapTilerConfig( + apiKey: String = "test_api_key", + baseUrl: String? = null, + lightStyleId: String? = null, + darkStyleId: String? = null, +) = MapTilerConfig( + apiKey = apiKey, + baseUrl = baseUrl, + lightStyleId = lightStyleId, + darkStyleId = darkStyleId, +) diff --git a/features/location/api/build.gradle.kts b/features/location/api/build.gradle.kts index b3eeacf87f..8830b39a22 100644 --- a/features/location/api/build.gradle.kts +++ b/features/location/api/build.gradle.kts @@ -71,6 +71,7 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.uiStrings) implementation(projects.libraries.wellknown.api) + implementation(projects.features.enterprise.api) implementation(libs.coil.compose) implementation(libs.datetime) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/LocalMapTilerConfig.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/LocalMapTilerConfig.kt index a2212119f5..752dd2401c 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/LocalMapTilerConfig.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/LocalMapTilerConfig.kt @@ -8,7 +8,7 @@ package io.element.android.features.location.api import androidx.compose.runtime.staticCompositionLocalOf -import io.element.android.libraries.wellknown.api.MapTilerConfig +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig /** * Provides a [MapTilerConfig] for the MapLibre maps. diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt index 33e454722f..b3149964e4 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt @@ -37,6 +37,7 @@ import coil3.compose.rememberAsyncImagePainter import coil3.request.ImageRequest import coil3.request.SuccessResult import io.element.android.compound.theme.ElementTheme +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig import io.element.android.features.location.api.internal.StaticMapPlaceholder import io.element.android.features.location.api.internal.centerBottomEdge import io.element.android.features.location.api.internal.rememberStaticMapBuilder @@ -45,7 +46,6 @@ import io.element.android.libraries.designsystem.components.PinVariant import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.CommonDrawables -import io.element.android.libraries.wellknown.api.MapTilerConfig /** * Shows a static map image downloaded via a third party service's static maps API. diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt index 78eae2f633..cd339f67b0 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt @@ -8,8 +8,8 @@ package io.element.android.features.location.api.internal +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig import io.element.android.features.location.api.BuildConfig -import io.element.android.libraries.wellknown.api.MapTilerConfig import kotlin.math.roundToInt /** diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt index 10aef90116..d817a3307f 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt @@ -10,8 +10,8 @@ package io.element.android.features.location.api.internal +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig import io.element.android.features.location.api.BuildConfig -import io.element.android.libraries.wellknown.api.MapTilerConfig internal class MapTilerTileServerStyleUriBuilder( private val baseUrl: String, diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt index 212da08647..31992b1dd6 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt @@ -10,8 +10,7 @@ package io.element.android.features.location.api.internal import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import io.element.android.libraries.wellknown.api.MapTilerConfig - +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig /** * Builds an URL for a 3rd party service provider static maps API. */ diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt index 9676005f59..67936525e5 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt @@ -11,7 +11,7 @@ package io.element.android.features.location.api.internal import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import io.element.android.compound.theme.ElementTheme -import io.element.android.libraries.wellknown.api.MapTilerConfig +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig /** * Builds a style URI for a MapLibre compatible tile server. diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt index 09aae4f310..c153ab1ce4 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt @@ -9,7 +9,7 @@ package io.element.android.features.location.api.internal import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.wellknown.api.MapTilerConfig +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig import org.junit.Test class MapTilerTileServerStyleUriBuilderTest { diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/MapBottomSheetScaffold.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/MapBottomSheetScaffold.kt index eaa238bed0..84c4ab4fba 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/MapBottomSheetScaffold.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/MapBottomSheetScaffold.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig import io.element.android.features.location.api.internal.rememberTileStyleUrl import io.element.android.features.location.impl.common.MapDefaults import io.element.android.libraries.architecture.AsyncData @@ -50,7 +51,6 @@ import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.components.BottomSheetScaffold import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator -import io.element.android.libraries.wellknown.api.MapTilerConfig import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.map.MapOptions diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenter.kt index d388e0c5d2..55f1e088d2 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenter.kt @@ -22,7 +22,7 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.Composer -import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider +import io.element.android.features.enterprise.api.remoteconfig.CustomMapTilerConfigProvider import io.element.android.features.location.api.live.ActiveLiveLocationShareManager import io.element.android.features.location.impl.common.LocationConstraintsCheck import io.element.android.features.location.impl.common.MapDefaults @@ -73,7 +73,7 @@ class ShareLocationPresenter( private val liveLocationShareManager: ActiveLiveLocationShareManager, private val liveLocationStore: LiveLocationStore, private val userLocationStateFactory: UserLocationState.Factory, - private val remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider, + private val customMapTilerConfigProvider: CustomMapTilerConfigProvider, ) : Presenter { @AssistedFactory fun interface Factory { @@ -96,7 +96,7 @@ class ShareLocationPresenter( val currentUser by client.userProfile.collectAsState() val customMapConfig by produceState(AsyncData.Loading()) { // Ignore errors - value = AsyncData.Success(remoteEnterpriseConfigProvider.get(client.sessionId).dataOrNull()?.mapTilerConfig) + value = AsyncData.Success(customMapTilerConfigProvider.get().getOrNull()) } val sendLiveLocationPermissions by room.permissionsAsState(SendLiveLocationPermissions.DEFAULT) { perms -> perms.sendLiveLocationPermissions() diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationState.kt index d2cce94ef8..b29228f866 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationState.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationState.kt @@ -8,12 +8,12 @@ package io.element.android.features.location.impl.share +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState import io.element.android.features.location.impl.common.userlocation.UserLocationState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.wellknown.api.MapTilerConfig import kotlinx.collections.immutable.ImmutableList data class ShareLocationState( diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationStateProvider.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationStateProvider.kt index 6d7ff5e612..9214d5a017 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationStateProvider.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationStateProvider.kt @@ -9,13 +9,13 @@ package io.element.android.features.location.impl.share import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState import io.element.android.features.location.impl.common.userlocation.UserLocationState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.wellknown.api.MapTilerConfig import kotlinx.collections.immutable.persistentListOf import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt index 2d9a4900dd..bbec7b449f 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt @@ -22,7 +22,7 @@ import androidx.compose.runtime.setValue import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject -import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider +import io.element.android.features.enterprise.api.remoteconfig.CustomMapTilerConfigProvider import io.element.android.features.location.api.Location import io.element.android.features.location.api.ShowLocationMode import io.element.android.features.location.api.live.ActiveLiveLocationShareManager @@ -47,7 +47,6 @@ import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.getBestName import io.element.android.libraries.matrix.api.room.joinedRoomMembers @@ -58,18 +57,18 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch -@AssistedInject class ShowLocationPresenter( +@AssistedInject +class ShowLocationPresenter( @Assisted private val mode: ShowLocationMode, permissionsPresenterFactory: PermissionsPresenter.Factory, private val locationActions: LocationActions, private val buildMeta: BuildMeta, private val dateFormatter: DateFormatter, private val stringProvider: StringProvider, - private val sessionId: SessionId, private val joinedRoom: JoinedRoom, private val liveLocationShareManager: ActiveLiveLocationShareManager, private val userLocationStateFactory: UserLocationState.Factory, - private val remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider, + private val customMapTilerConfigProvider: CustomMapTilerConfigProvider, ) : Presenter { @AssistedFactory fun interface Factory { fun create(mode: ShowLocationMode): ShowLocationPresenter @@ -89,7 +88,7 @@ import kotlinx.coroutines.launch val customMapTilerConfig by produceState(AsyncData.Loading()) { // Ignore errors - value = AsyncData.Success(remoteEnterpriseConfigProvider.get(sessionId).dataOrNull()?.mapTilerConfig) + value = AsyncData.Success(customMapTilerConfigProvider.get().getOrNull()) } fun checkLocationConstraints() { diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt index 2ae76ef9e9..0472eb94e9 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt @@ -8,6 +8,7 @@ package io.element.android.features.location.impl.show +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig import io.element.android.features.location.api.Location import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState import io.element.android.features.location.impl.common.ui.LocationMarkerData @@ -17,7 +18,6 @@ import io.element.android.libraries.designsystem.components.PinVariant import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.location.AssetType -import io.element.android.libraries.wellknown.api.MapTilerConfig import kotlinx.collections.immutable.ImmutableList data class ShowLocationState( diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt index a1f349d644..bf582eb59a 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt @@ -9,6 +9,7 @@ package io.element.android.features.location.impl.show import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig import io.element.android.features.location.api.Location import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState import io.element.android.features.location.impl.common.userlocation.UserLocationState @@ -18,7 +19,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.location.AssetType -import io.element.android.libraries.wellknown.api.MapTilerConfig import kotlinx.collections.immutable.toImmutableList class ShowLocationStateProvider : PreviewParameterProvider { diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/DefaultShareLocationEntryPointTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/DefaultShareLocationEntryPointTest.kt index 9be168e5a2..a37011e1d9 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/DefaultShareLocationEntryPointTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/DefaultShareLocationEntryPointTest.kt @@ -11,7 +11,6 @@ package io.element.android.features.location.impl.share import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext import com.google.common.truth.Truth.assertThat -import io.element.android.features.enterprise.test.remoteconfig.FakeRemoteEnterpriseConfigProvider import io.element.android.features.location.impl.common.FakeUserLocationStateFactory import io.element.android.features.location.impl.common.actions.FakeLocationActions import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter @@ -59,7 +58,7 @@ class DefaultShareLocationEntryPointTest { sessionId = room.sessionId, ), userLocationStateFactory = FakeUserLocationStateFactory(), - remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(), + customMapTilerConfigProvider = { Result.success(null) }, ) }, analyticsService = FakeAnalyticsService(), diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenterTest.kt index 6748c5a675..d249fdaf69 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenterTest.kt @@ -19,8 +19,8 @@ import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.Composer -import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider -import io.element.android.features.enterprise.test.remoteconfig.FakeRemoteEnterpriseConfigProvider +import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig +import io.element.android.features.enterprise.test.remoteconfig.aMapTilerConfig import io.element.android.features.location.api.Location import io.element.android.features.location.impl.aPermissionsState import io.element.android.features.location.impl.common.FakeUserLocationStateFactory @@ -32,8 +32,6 @@ import io.element.android.features.location.impl.common.ui.LocationConstraintsDi import io.element.android.features.location.impl.live.LiveLocationStore import io.element.android.features.location.test.FakeActiveLiveLocationShareManager import io.element.android.features.messages.test.FakeMessageComposerContext -import io.element.android.features.wellknown.test.aMapTilerConfig -import io.element.android.features.wellknown.test.anElementWellKnown import io.element.android.libraries.dateformatter.test.FakeDurationFormatter import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -55,7 +53,6 @@ import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermiss import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import io.element.android.libraries.preferences.test.FakePreferenceDataStoreFactory -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.assert @@ -92,7 +89,7 @@ class ShareLocationPresenterTest { liveLocationShareManager: FakeActiveLiveLocationShareManager = FakeActiveLiveLocationShareManager(), liveLocationStore: LiveLocationStore = createLiveLocationStore(sessionId = joinedRoom.sessionId), client: FakeMatrixClient = fakeMatrixClient, - remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(), + customMapTilerConfigProvider: () -> Result = { Result.success(null) }, ): ShareLocationPresenter = ShareLocationPresenter( permissionsPresenterFactory = { fakePermissionsPresenter }, room = joinedRoom, @@ -106,7 +103,7 @@ class ShareLocationPresenterTest { liveLocationShareManager = liveLocationShareManager, liveLocationStore = liveLocationStore, userLocationStateFactory = FakeUserLocationStateFactory(), - remoteEnterpriseConfigProvider = remoteEnterpriseConfigProvider, + customMapTilerConfigProvider = customMapTilerConfigProvider, ) @Test @@ -134,9 +131,7 @@ class ShareLocationPresenterTest { client = FakeMatrixClient( sessionId = A_USER_ID, ), - remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider( - getResult = { WellknownRetrieverResult.Success(anElementWellKnown(mapTilerConfig = mapTilerConfig)) } - ) + customMapTilerConfigProvider = { Result.success(mapTilerConfig) } ) shareLocationPresenter.test { val state = awaitItem() diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPointTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPointTest.kt index 69451eedb7..db5c6b9f9f 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPointTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPointTest.kt @@ -11,7 +11,6 @@ package io.element.android.features.location.impl.show import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext import com.google.common.truth.Truth.assertThat -import io.element.android.features.enterprise.test.remoteconfig.FakeRemoteEnterpriseConfigProvider import io.element.android.features.location.api.Location import io.element.android.features.location.api.ShowLocationEntryPoint import io.element.android.features.location.api.ShowLocationMode @@ -21,7 +20,6 @@ import io.element.android.features.location.impl.common.permissions.FakePermissi import io.element.android.features.location.test.FakeActiveLiveLocationShareManager import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.services.analytics.test.FakeAnalyticsService @@ -51,10 +49,9 @@ class DefaultShowLocationEntryPointTest { dateFormatter = FakeDateFormatter(), stringProvider = FakeStringProvider(), joinedRoom = joinedRoom, - sessionId = A_SESSION_ID, liveLocationShareManager = FakeActiveLiveLocationShareManager(), userLocationStateFactory = FakeUserLocationStateFactory(), - remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(), + customMapTilerConfigProvider = { Result.success(null) }, ) }, analyticsService = FakeAnalyticsService(), diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt index 77b26eb66c..7d9b0d5e03 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt @@ -12,7 +12,8 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.enterprise.test.remoteconfig.FakeRemoteEnterpriseConfigProvider +import io.element.android.features.enterprise.api.remoteconfig.CustomMapTilerConfigProvider +import io.element.android.features.enterprise.test.remoteconfig.aMapTilerConfig import io.element.android.features.location.api.Location import io.element.android.features.location.api.ShowLocationMode import io.element.android.features.location.impl.aPermissionsState @@ -23,20 +24,15 @@ import io.element.android.features.location.impl.common.permissions.PermissionsE import io.element.android.features.location.impl.common.permissions.PermissionsState import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState import io.element.android.features.location.test.FakeActiveLiveLocationShareManager -import io.element.android.features.wellknown.test.aMapTilerConfig -import io.element.android.features.wellknown.test.anElementWellKnown import io.element.android.libraries.dateformatter.test.FakeDateFormatter -import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.location.LiveLocationShare -import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.location.aLiveLocationShare -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test @@ -69,9 +65,8 @@ class ShowLocationPresenterTest { ), locationActions: FakeLocationActions = fakeLocationActions, joinedRoom: JoinedRoom = FakeJoinedRoom(), - sessionId: SessionId = A_SESSION_ID, liveLocationShareManager: FakeActiveLiveLocationShareManager = FakeActiveLiveLocationShareManager(), - remoteEnterpriseConfigProvider: FakeRemoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(), + customMapTilerConfigProvider: CustomMapTilerConfigProvider = { Result.success(null) }, ) = ShowLocationPresenter( mode = mode, permissionsPresenterFactory = { fakePermissionsPresenter }, @@ -80,21 +75,15 @@ class ShowLocationPresenterTest { dateFormatter = fakeDateFormatter, stringProvider = FakeStringProvider(), joinedRoom = joinedRoom, - sessionId = sessionId, liveLocationShareManager = liveLocationShareManager, userLocationStateFactory = FakeUserLocationStateFactory(), - remoteEnterpriseConfigProvider = remoteEnterpriseConfigProvider, + customMapTilerConfigProvider = customMapTilerConfigProvider, ) @Test fun `present - non-null customMapStyleUrl`() = runTest { val mapTilerConfig = aMapTilerConfig(apiKey = "A KEY") - val shareLocationPresenter = createShowLocationPresenter( - sessionId = A_SESSION_ID, - remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider( - getResult = { WellknownRetrieverResult.Success(anElementWellKnown(mapTilerConfig = mapTilerConfig)) } - ) - ) + val shareLocationPresenter = createShowLocationPresenter(customMapTilerConfigProvider = { Result.success(mapTilerConfig) }) shareLocationPresenter.test { val state = awaitItem() assertThat(state.customMapTilerConfig.isLoading()).isTrue() @@ -131,12 +120,7 @@ class ShowLocationPresenterTest { fun `centers on user location`() = runTest { fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted)) - val presenter = createShowLocationPresenter( - sessionId = A_SESSION_ID, - remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider( - getResult = { WellknownRetrieverResult.Success(anElementWellKnown(mapTilerConfig = aMapTilerConfig())) } - ) - ) + val presenter = createShowLocationPresenter(customMapTilerConfigProvider = { Result.success(aMapTilerConfig()) }) presenter.test { val initialState = awaitItem() diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt index 119923c67d..a3a475df2f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt @@ -11,17 +11,14 @@ package io.element.android.features.login.impl.accesscontrol import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.features.enterprise.api.IsEnterpriseBuild import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl import io.element.android.features.login.impl.changeserver.AccountProviderAccessException -import io.element.android.libraries.matrix.api.TemporaryMatrixClientFactory -import io.element.android.libraries.wellknown.api.EnterpriseRemoteConfigSource -import io.element.android.libraries.wellknown.api.WellknownRetriever @ContributesBinding(AppScope::class) class DefaultAccountProviderAccessControl( + private val isEnterpriseBuild: IsEnterpriseBuild, private val enterpriseService: EnterpriseService, - private val wellknownRetrieverFactory: WellknownRetriever.Factory, - private val temporaryMatrixClientFactory: TemporaryMatrixClientFactory, ) : AccountProviderAccessControl { override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String) = try { assertIsAllowedToConnectToAccountProvider( @@ -38,29 +35,19 @@ class DefaultAccountProviderAccessControl( title: String, accountProviderUrl: String, ) { - if (enterpriseService.isEnterpriseBuild.not()) { + if (isEnterpriseBuild().not()) { // Ensure that Element Pro is not required for this account provider - val temporaryMatrixClient = temporaryMatrixClientFactory.create(accountProviderUrl).getOrThrow() - temporaryMatrixClient.use { - val wellknownRetriever = wellknownRetrieverFactory.create(temporaryMatrixClient) - val wellKnown = wellknownRetriever.getElementWellKnown( - host = accountProviderUrl, - // It's expected this is hardcoded to the well-known endpoint - source = EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT, - ).dataOrNull() - - if (wellKnown?.enforceElementPro == true) { - throw AccountProviderAccessException.NeedElementProException( - unauthorisedAccountProviderTitle = title, - applicationId = ELEMENT_PRO_APPLICATION_ID, - ) - } + if (enterpriseService.isElementProEnforced(accountProviderUrl)) { + throw AccountProviderAccessException.NeedElementProException( + unauthorisedAccountProviderTitle = title, + applicationId = ELEMENT_PRO_APPLICATION_ID, + ) } } if (enterpriseService.isAllowedToConnectToHomeserver(accountProviderUrl).not()) { throw AccountProviderAccessException.UnauthorizedAccountProviderException( unauthorisedAccountProviderTitle = title, - authorisedAccountProviderTitles = enterpriseService.defaultHomeserverList(), + authorisedAccountProviderTitles = enterpriseService.homeserverAllowList(), ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt index 931959d4ea..ad016dec26 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt @@ -23,7 +23,7 @@ class AccountProviderDataSource( enterpriseService: EnterpriseService, ) { private val defaultAccountProvider = createAccountProvider( - url = enterpriseService.defaultHomeserverList() + url = enterpriseService.homeserverAllowList() .firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER } ?: AuthenticationConfig.MATRIX_ORG_URL ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt index 41605352ef..826cca0c2b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -28,7 +28,7 @@ class ChangeAccountProviderPresenter( @Composable override fun present(): ChangeAccountProviderState { val staticAccountProviderList = remember { - enterpriseService.defaultHomeserverList() + enterpriseService.homeserverAllowList() .filter { it != EnterpriseService.ANY_ACCOUNT_PROVIDER } .map { it.ensureProtocol() } .ifEmpty { listOf(AuthenticationConfig.MATRIX_ORG_URL) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt index a9983a19d1..92c3ff0fff 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt @@ -60,7 +60,7 @@ class ChooseAccountProviderPresenter( val staticAccountProviderList = remember { // The list cannot contains ANY_ACCOUNT_PROVIDER ("*") and cannot be empty at this point - enterpriseService.defaultHomeserverList() + enterpriseService.homeserverAllowList() .map { it.ensureProtocol() } .map { url -> AccountProvider( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt index 29e4b1afe5..850e4332dd 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt @@ -62,13 +62,13 @@ class OnBoardingPresenter( val forcedAccountProvider = remember { // If defaultHomeserverList() returns a singleton list, this is the default account provider. // In this case, the user can sign in using this homeserver, or use QrCode login - enterpriseService.defaultHomeserverList().singleOrNull() + enterpriseService.homeserverAllowList().singleOrNull() } val canConnectToAnyHomeserver = remember { enterpriseService.canConnectToAnyHomeserver() } val mustChooseAccountProvider = remember { - !canConnectToAnyHomeserver && enterpriseService.defaultHomeserverList().size > 1 + !canConnectToAnyHomeserver && enterpriseService.homeserverAllowList().size > 1 } val linkAccountProvider by produceState(initialValue = null) { // Account provider from the link, if allowed by the enterprise service diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt index ee4cea0532..fbfae984fc 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt @@ -11,15 +11,9 @@ package io.element.android.features.login.impl.accesscontrol import com.google.common.truth.Truth.assertThat import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.changeserver.AccountProviderAccessException -import io.element.android.features.wellknown.test.FakeWellknownRetriever -import io.element.android.features.wellknown.test.FakeWellknownRetrieverFactory -import io.element.android.features.wellknown.test.anElementWellKnown import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2 import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL -import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClientFactory -import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Test @@ -30,9 +24,7 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, isAllowedToConnectToHomeserver = true, - elementWellKnown = anElementWellKnown( - enforceElementPro = true, - ), + enterpriseService = FakeEnterpriseService(isElementProEnforcedResult = { true }), ) accessControl.expectNeedElementProException() } @@ -43,9 +35,7 @@ class DefaultAccountProviderAccessControlTest { isEnterpriseBuild = false, // false here. isAllowedToConnectToHomeserver = false, - elementWellKnown = anElementWellKnown( - enforceElementPro = true, - ), + enterpriseService = FakeEnterpriseService(isElementProEnforcedResult = { true }), ) accessControl.expectNeedElementProException() } @@ -55,8 +45,9 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, isAllowedToConnectToHomeserver = true, - elementWellKnown = anElementWellKnown( - enforceElementPro = false, + enterpriseService = FakeEnterpriseService( + isAllowedToConnectToHomeserverResult = { true }, + isElementProEnforcedResult = { false } ), ) accessControl.expectAllowed() @@ -67,19 +58,15 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, isAllowedToConnectToHomeserver = true, - elementWellKnown = anElementWellKnown( - enforceElementPro = null, - ), ) accessControl.expectAllowed() } @Test - fun `foss build should allow using account provider twith missing wellknown`() = runTest { + fun `foss build should allow using account provider with missing wellknown`() = runTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = false, isAllowedToConnectToHomeserver = true, - elementWellKnown = null, ) accessControl.expectAllowed() } @@ -90,8 +77,10 @@ class DefaultAccountProviderAccessControlTest { isEnterpriseBuild = false, isAllowedToConnectToHomeserver = false, allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2), - elementWellKnown = anElementWellKnown( - enforceElementPro = false, + enterpriseService = FakeEnterpriseService( + isAllowedToConnectToHomeserverResult = { false }, + isElementProEnforcedResult = { false }, + defaultHomeserverListResult = { listOf(AN_ACCOUNT_PROVIDER_2) }, ), ) accessControl.expectUnauthorizedAccountProviderException() @@ -102,8 +91,9 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = true, isAllowedToConnectToHomeserver = true, - elementWellKnown = anElementWellKnown( - enforceElementPro = true, + enterpriseService = FakeEnterpriseService( + isAllowedToConnectToHomeserverResult = { true }, + isElementProEnforcedResult = { true }, ), ) accessControl.expectAllowed() @@ -114,8 +104,9 @@ class DefaultAccountProviderAccessControlTest { val accessControl = createDefaultAccountProviderAccessControl( isEnterpriseBuild = true, isAllowedToConnectToHomeserver = true, - elementWellKnown = anElementWellKnown( - enforceElementPro = false, + enterpriseService = FakeEnterpriseService( + isAllowedToConnectToHomeserverResult = { true }, + isElementProEnforcedResult = { false }, ), ) accessControl.expectAllowed() @@ -127,8 +118,10 @@ class DefaultAccountProviderAccessControlTest { isEnterpriseBuild = true, isAllowedToConnectToHomeserver = false, allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2), - elementWellKnown = anElementWellKnown( - enforceElementPro = true, + enterpriseService = FakeEnterpriseService( + isAllowedToConnectToHomeserverResult = { false }, + isElementProEnforcedResult = { true }, + defaultHomeserverListResult = { listOf(AN_ACCOUNT_PROVIDER_2) }, ), ) accessControl.expectUnauthorizedAccountProviderException() @@ -140,8 +133,10 @@ class DefaultAccountProviderAccessControlTest { isEnterpriseBuild = true, isAllowedToConnectToHomeserver = false, allowedAccountProviders = listOf(AN_ACCOUNT_PROVIDER_2), - elementWellKnown = anElementWellKnown( - enforceElementPro = false, + enterpriseService = FakeEnterpriseService( + isAllowedToConnectToHomeserverResult = { false }, + isElementProEnforcedResult = { false }, + defaultHomeserverListResult = { listOf(AN_ACCOUNT_PROVIDER_2) }, ), ) accessControl.expectUnauthorizedAccountProviderException() @@ -151,25 +146,14 @@ class DefaultAccountProviderAccessControlTest { isEnterpriseBuild: Boolean = false, isAllowedToConnectToHomeserver: Boolean = false, allowedAccountProviders: List = emptyList(), - elementWellKnown: ElementWellKnown? = null, - ) = DefaultAccountProviderAccessControl( - enterpriseService = FakeEnterpriseService( - isEnterpriseBuild = isEnterpriseBuild, + enterpriseService: FakeEnterpriseService = FakeEnterpriseService( isAllowedToConnectToHomeserverResult = { isAllowedToConnectToHomeserver }, defaultHomeserverListResult = { allowedAccountProviders }, - ), - wellknownRetrieverFactory = FakeWellknownRetrieverFactory( - wellknownRetriever = FakeWellknownRetriever( - getElementWellKnownResult = { _, _ -> - if (elementWellKnown == null) { - WellknownRetrieverResult.NotFound - } else { - WellknownRetrieverResult.Success(elementWellKnown) - } - }, - ), - ), - temporaryMatrixClientFactory = FakeTemporaryMatrixClientFactory(), + isElementProEnforcedResult = { false }, + ) + ) = DefaultAccountProviderAccessControl( + isEnterpriseBuild = { isEnterpriseBuild }, + enterpriseService = enterpriseService, ) private fun DefaultAccountProviderAccessControl.expectNeedElementProException() { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index c28fee77ad..71663c5e7f 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -16,22 +16,15 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate -import io.element.android.features.wellknown.test.FakeWellknownRetriever -import io.element.android.features.wellknown.test.FakeWellknownRetrieverFactory -import io.element.android.features.wellknown.test.anElementWellKnown import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_HOMESERVER_URL -import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClientFactory import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory -import io.element.android.libraries.wellknown.api.EnterpriseRemoteConfigSource -import io.element.android.libraries.wellknown.api.WellknownRetriever -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -64,6 +57,7 @@ class ChangeServerPresenterTest { authenticationService = authenticationService, enterpriseService = FakeEnterpriseService( isAllowedToConnectToHomeserverResult = { true }, + isElementProEnforcedResult = { false }, ), ).test { val initialState = awaitItem() @@ -86,6 +80,7 @@ class ChangeServerPresenterTest { createPresenter( enterpriseService = FakeEnterpriseService( isAllowedToConnectToHomeserverResult = { true }, + isElementProEnforcedResult = { false }, ), authenticationService = authenticationService, ).test { @@ -113,6 +108,7 @@ class ChangeServerPresenterTest { createPresenter( enterpriseService = FakeEnterpriseService( isAllowedToConnectToHomeserverResult = { true }, + isElementProEnforcedResult = { false }, ), authenticationService = authenticationService, ).test { @@ -136,6 +132,7 @@ class ChangeServerPresenterTest { enterpriseService = FakeEnterpriseService( isAllowedToConnectToHomeserverResult = isAllowedToConnectToHomeserverResult, defaultHomeserverListResult = { listOf("element.io") }, + isElementProEnforcedResult = { false }, ), ).test { val initialState = awaitItem() @@ -159,23 +156,19 @@ class ChangeServerPresenterTest { @Test fun `present - change server element pro required error`() = runTest { - val getElementWellKnownResult = lambdaRecorder { homeserver: String, _: EnterpriseRemoteConfigSource -> - assertThat(homeserver).isEqualTo(A_HOMESERVER_URL) - WellknownRetrieverResult.Success( - anElementWellKnown( - enforceElementPro = true, - ) - ) - } + val isEnterpriseBuildLambda = lambdaRecorder { false } createPresenter( - wellknownRetriever = FakeWellknownRetriever( - getElementWellKnownResult = getElementWellKnownResult, - ), + isEnterpriseBuild = isEnterpriseBuildLambda, + enterpriseService = FakeEnterpriseService( + isElementProEnforcedResult = { true }, + ) ).test { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) val anAccountProvider = AccountProvider(url = A_HOMESERVER_URL) initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(anAccountProvider)) + // Skip loading state + skipItems(1) val failureState = awaitItem() assertThat( (failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).unauthorisedAccountProviderTitle @@ -183,7 +176,7 @@ class ChangeServerPresenterTest { assertThat( (failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).applicationId ).isEqualTo("io.element.enterprise") - getElementWellKnownResult.assertions().isCalledOnce() + isEnterpriseBuildLambda.assertions().isCalledOnce() } } @@ -237,7 +230,7 @@ class ChangeServerPresenterTest { val permissionsPresenter = FakePermissionsPresenter() createPresenter( authenticationService = authenticationService, - enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }), + enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }, isElementProEnforcedResult = { false }), localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true), permissionsPresenter = permissionsPresenter, ).test { @@ -259,16 +252,15 @@ class ChangeServerPresenterTest { authenticationService: FakeMatrixAuthenticationService = FakeMatrixAuthenticationService(), accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()), enterpriseService: EnterpriseService = FakeEnterpriseService(), - wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), localNetworkPermissionAdvisor: FakeLocalNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(), permissionsPresenter: FakePermissionsPresenter = FakePermissionsPresenter(), + isEnterpriseBuild: () -> Boolean = { false }, ) = ChangeServerPresenter( authenticationService = authenticationService, accountProviderDataSource = accountProviderDataSource, defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl( enterpriseService = enterpriseService, - wellknownRetrieverFactory = FakeWellknownRetrieverFactory(wellknownRetriever = wellknownRetriever), - temporaryMatrixClientFactory = FakeTemporaryMatrixClientFactory(), + isEnterpriseBuild = isEnterpriseBuild, ), localNetworkPermissionGate = LocalNetworkPermissionGate( advisor = localNetworkPermissionAdvisor, diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt index fbee19f05e..df3e3d2200 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt @@ -12,13 +12,12 @@ import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.AuthenticationConfig import io.element.android.appconfig.OnBoardingConfig import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.features.enterprise.api.IsEnterpriseBuild import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate import io.element.android.features.login.impl.login.LoginModePresenter -import io.element.android.features.wellknown.test.FakeWellknownRetriever -import io.element.android.features.wellknown.test.FakeWellknownRetrieverFactory import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -29,7 +28,6 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2 import io.element.android.libraries.matrix.test.A_LOGIN_HINT -import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClientFactory import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.oauth.api.OAuthActionFlow @@ -41,7 +39,6 @@ import io.element.android.libraries.permissions.test.FakePermissionsPresenterFac import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.aSessionData -import io.element.android.libraries.wellknown.api.WellknownRetriever import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test import kotlinx.coroutines.flow.Flow @@ -186,6 +183,7 @@ class OnBoardingPresenterTest { enterpriseService = FakeEnterpriseService( defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) }, isAllowedToConnectToHomeserverResult = { true }, + isElementProEnforcedResult = { false }, ), ) presenter.test { @@ -259,6 +257,7 @@ class OnBoardingPresenterTest { ), enterpriseService = FakeEnterpriseService( isAllowedToConnectToHomeserverResult = { true }, + isElementProEnforcedResult = { false }, ), loginModePresenter = createLoginModePresenter( authenticationService = authenticationService, @@ -295,7 +294,7 @@ private fun createPresenter( ), buildMeta: BuildMeta = aBuildMeta(), enterpriseService: EnterpriseService = FakeEnterpriseService(), - wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), + isEnterpriseBuild: IsEnterpriseBuild = { false }, rageshakeFeatureAvailability: () -> Flow = { flowOf(true) }, loginModePresenter: LoginModePresenter = createLoginModePresenter(), onBoardingLogoResIdProvider: OnBoardingLogoResIdProvider = OnBoardingLogoResIdProvider { null }, @@ -307,8 +306,7 @@ private fun createPresenter( enterpriseService = enterpriseService, defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl( enterpriseService = enterpriseService, - wellknownRetrieverFactory = FakeWellknownRetrieverFactory(wellknownRetriever = wellknownRetriever), - temporaryMatrixClientFactory = FakeTemporaryMatrixClientFactory(), + isEnterpriseBuild = isEnterpriseBuild, ), rageshakeFeatureAvailability = rageshakeFeatureAvailability, loginModePresenter = loginModePresenter, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index ac1ba06cf6..c5a9b430ee 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.data.ByteUnit +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.SessionId @@ -50,6 +51,7 @@ class DeveloperSettingsPresenter( private val databaseSizesUseCase: GetDatabaseSizesUseCase, private val fileSizeFormatter: FileSizeFormatter, private val markAllRoomsAsRead: MarkAllRoomsAsRead, + private val buildMeta: BuildMeta, ) : Presenter { @Composable override fun present(): DeveloperSettingsState { @@ -117,7 +119,7 @@ class DeveloperSettingsPresenter( databaseSizes = databaseSizes.value, clearCacheAction = clearCacheAction.value, markAllRoomsAsReadAction = markAllRoomsAsReadAction.value, - isEnterpriseBuild = enterpriseService.isEnterpriseBuild, + isEnterpriseBuild = buildMeta.isEnterpriseBuild, showColorPicker = showColorPicker, deviceId = deviceId, eventSink = ::handleEvent, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt index bb688f498d..3b2645783f 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -23,12 +23,14 @@ import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.data.megaBytes +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.test.A_DEVICE_ID import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -108,10 +110,8 @@ class DeveloperSettingsPresenterTest { fun `present - enterprise build can change the brand color`() = runTest { val overrideBrandColorResult = lambdaRecorder { _, _ -> } val presenter = createDeveloperSettingsPresenter( - enterpriseService = FakeEnterpriseService( - isEnterpriseBuild = true, - overrideBrandColorResult = overrideBrandColorResult, - ) + enterpriseService = FakeEnterpriseService(overrideBrandColorResult = overrideBrandColorResult), + buildMeta = aBuildMeta(isEnterpriseBuild = true), ) presenter.test { skipItems(1) @@ -179,6 +179,7 @@ class DeveloperSettingsPresenterTest { vacuumStoresUseCase: VacuumStoresUseCase = VacuumStoresUseCase {}, databaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) }, markAllRoomsAsRead: FakeMarkAllRoomsAsRead = FakeMarkAllRoomsAsRead(), + buildMeta: BuildMeta = aBuildMeta(), ): DeveloperSettingsPresenter { return DeveloperSettingsPresenter( appDeveloperSettingsPresenter = { anAppDeveloperSettingsState() }, @@ -191,6 +192,7 @@ class DeveloperSettingsPresenterTest { databaseSizesUseCase = databaseSizesUseCase, fileSizeFormatter = FakeFileSizeFormatter(), markAllRoomsAsRead = markAllRoomsAsRead, + buildMeta = buildMeta, ) } } diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/CustomRecoveryPassphrase.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/CustomRecoveryPassphrase.kt deleted file mode 100644 index c7bc602dde..0000000000 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/CustomRecoveryPassphrase.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 - -/** - * Server-driven settings for a user-chosen recovery passphrase, advertised under the well-known - * `custom_recovery_passphrase` key. Today the only rule is a minimum character count; additional - * settings can be added here as the schema grows. - */ -data class CustomRecoveryPassphrase( - val minCharacterCount: Int, -) { - /** True when [input] meets every active rule. */ - fun isSatisfiedBy(input: String): Boolean = isSatisfiedBy(input.length) - - /** True when an input of [length] characters meets every active rule. */ - fun isSatisfiedBy(length: Int): Boolean = length >= minCharacterCount -} diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnownParser.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnownParser.kt deleted file mode 100644 index 1466a5b422..0000000000 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnownParser.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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 - -/** - * Parses the raw JSON of an Element `.well-known` document. - */ -fun interface ElementWellKnownParser { - /** - * Parses the document, failing when it is not valid JSON; unknown fields are ignored so a newer server does not break an older client. - * - * @param json the raw document as served by the server. - */ - fun parse(json: String): Result -} diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellknownStore.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellknownStore.kt deleted file mode 100644 index 93256fa6f1..0000000000 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellknownStore.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 - -/** - * On-disk cache of the Element `.well-known` documents already fetched, so the app has a configuration to work with while offline. - */ -interface ElementWellknownStore { - /** - * Reads the cached document of a domain. - * Entries older than a day are reported as outdated rather than missing, so the caller can use them while refreshing; - * a document that no longer parses is reported as an error. - * - * @param domain the server whose cached document is requested. - */ - suspend fun get(domain: String): WellknownRetrieverResult - - /** - * Stores the document of a domain and stamps it as fetched now, replacing any previous entry. - * - * @param domain the server the document belongs to. - * @param wellknown the raw document, stored as-is rather than parsed. - */ - suspend fun update(domain: String, wellknown: String): Result - - /** - * Removes the cached document of a domain, used when the stored value turns out to be unusable. - * - * @param domain the server whose entry is removed. - */ - suspend fun delete(domain: String): Result - - /** - * Creates a store, one per configuration source so that their entries never collide. - */ - fun interface Factory { - /** - * @param prefix namespaces the cache keys, or `null` for the default well-known endpoint. - */ - fun create(prefix: String?): ElementWellknownStore - } -} diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/EnterpriseRemoteConfigSource.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/EnterpriseRemoteConfigSource.kt deleted file mode 100644 index 147f31269d..0000000000 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/EnterpriseRemoteConfigSource.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 - -/** - * The source from which the enterprise remote configuration is retrieved. - */ -enum class EnterpriseRemoteConfigSource { - /** - * Retrieve the enterprise remote configuration from the Element Server Suite configuration (ESS_CONFIG) endpoint. - */ - ESS_CONFIG, - - /** - * Retrieve the enterprise remote configuration from the Element server's well-known endpoint. - */ - WELLKNOWN_ENDPOINT, -} diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt deleted file mode 100644 index 9e265bc213..0000000000 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2025 New Vector 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 - -import io.element.android.libraries.matrix.api.UrlContentFetcher - -/** - * Retrieves the Element specific `.well-known` configuration of a server, which is how a deployment advertises its own settings. - */ -interface WellknownRetriever { - /** - * Returns the configuration of a host, served from the local cache when possible. - * A cached value that has gone stale is returned as outdated while a refresh runs in the background, so this rarely waits on the network. - * An enterprise build with a hardcoded configuration always short-circuits and returns that instead. - * - * @param host the server to look up; a full URL is accepted and reduced to its host. - * @param source which endpoint to read the configuration from, which also selects the cache it is stored in. - */ - suspend fun getElementWellKnown( - host: String, - source: EnterpriseRemoteConfigSource - ): WellknownRetrieverResult - - /** - * Creates a retriever, which needs a fetcher because the configuration is read over HTTP. - */ - interface Factory { - /** - * @param urlContentFetcher used to perform the request; a client that is not authenticated yet also works. - */ - fun create(urlContentFetcher: UrlContentFetcher): WellknownRetriever - } -} diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultElementWellKnownParser.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultElementWellKnownParser.kt deleted file mode 100644 index 4cf72366e0..0000000000 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultElementWellKnownParser.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { - return runCatchingExceptions { - val decoder = jsonProvider() - decoder.decodeFromString(json).map() - } - } -} diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStore.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStore.kt deleted file mode 100644 index 4d58e70db4..0000000000 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStore.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.Assisted -import dev.zacsweers.metro.AssistedFactory -import dev.zacsweers.metro.AssistedInject -import dev.zacsweers.metro.ContributesBinding -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 - -@AssistedInject -class DefaultWellKnownStore( - @Assisted - private val prefix: String?, - private val cacheStore: CacheStore, - private val elementWellKnownParser: ElementWellKnownParser, - private val systemClock: SystemClock, -) : ElementWellknownStore { - @AssistedFactory - @ContributesBinding(AppScope::class) - fun interface Factory : ElementWellknownStore.Factory { - override fun create(prefix: String?): DefaultWellKnownStore - } - override suspend fun get(domain: String): WellknownRetrieverResult { - return runCatchingExceptions { - val cachedData = cacheStore.getData(key(domain)) - if (cachedData != null) { - val data = elementWellKnownParser.parse(cachedData.value).getOrThrow() - if (systemClock.epochMillis() > cachedData.updatedAt + CACHE_VALIDITY_MILLIS) { - WellknownRetrieverResult.Outdated(data) - } else { - WellknownRetrieverResult.Success(data) - } - } else { - WellknownRetrieverResult.NotFound - } - }.recover { - WellknownRetrieverResult.Error(it as Exception) - }.getOrThrow() - } - - override suspend fun update(domain: String, wellknown: String): Result { - return runCatchingExceptions { - cacheStore.storeData(key(domain), CacheData(wellknown, systemClock.epochMillis())) - } - } - - override suspend fun delete(domain: String): Result { - return runCatchingExceptions { - cacheStore.deleteData(key(domain)) - } - } - - private fun key(domain: String): String = if (prefix != null) { - "$prefix-$domain" - } else { - "https://$domain/.well-known/element/element.json" - } - - companion object { - // 1 day - private const val CACHE_VALIDITY_MILLIS = 1 * 24 * 60 * 60 * 1000L - } -} diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt deleted file mode 100644 index 60e8c621e2..0000000000 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2025 New Vector 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.Assisted -import dev.zacsweers.metro.AssistedFactory -import dev.zacsweers.metro.AssistedInject -import dev.zacsweers.metro.ContributesBinding -import io.element.android.features.enterprise.api.EnterpriseService -import io.element.android.libraries.androidutils.text.takeIfNotBlank -import io.element.android.libraries.core.extensions.mapCatchingExceptions -import io.element.android.libraries.core.extensions.runCatchingExceptions -import io.element.android.libraries.core.uri.ensureProtocol -import io.element.android.libraries.di.annotations.AppCoroutineScope -import io.element.android.libraries.matrix.api.UrlContentFetcher -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.EnterpriseRemoteConfigSource -import io.element.android.libraries.wellknown.api.WellknownRetriever -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import timber.log.Timber -import java.net.URL - -@AssistedInject -class DefaultWellknownRetriever( - private val elementWellknownStoreFactory: ElementWellknownStore.Factory, - private val enterpriseService: EnterpriseService, - private val elementWellKnownParser: ElementWellKnownParser, - @Assisted private val urlContentFetcher: UrlContentFetcher, - @AppCoroutineScope private val coroutineScope: CoroutineScope, -) : WellknownRetriever { - @ContributesBinding(AppScope::class) - @AssistedFactory - fun interface Factory : WellknownRetriever.Factory { - override fun create(urlContentFetcher: UrlContentFetcher): DefaultWellknownRetriever - } - - override suspend fun getElementWellKnown( - host: String, - source: EnterpriseRemoteConfigSource - ): WellknownRetrieverResult { - val overriddenElementWellKnown = enterpriseService.overriddenElementWellKnown() - if (overriddenElementWellKnown != null) { - return WellknownRetrieverResult.Success(overriddenElementWellKnown) - } - - // The passed `host` function may be a full URL, in that case we're only interested in the actual host - val checkedHost = runCatchingExceptions { URL(host.ensureProtocol()).host.takeIfNotBlank() } - .getOrElse { return WellknownRetrieverResult.Error(IllegalArgumentException("host parameter is not valid", it)) } - ?: host - - // We instantiate different stores for different sources, so that we can cache them separately. - // The ESS_CONFIG source is cached with a prefix to differentiate it from the WELLKNOWN_ENDPOINT. - val prefix = when (source) { - EnterpriseRemoteConfigSource.ESS_CONFIG -> "ess_config" - EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT -> null - } - val store = elementWellknownStoreFactory.create(prefix) - return when (val cacheData = store.get(checkedHost)) { - is WellknownRetrieverResult.Success -> { - Timber.d("Using cached well-known for domain $checkedHost") - cacheData - } - is WellknownRetrieverResult.Outdated -> { - // Return the outdated data but refresh in the background - // If the cache is missing or outdated, trigger a refresh in background but still return the cached value - Timber.d("Outdated cached well-known for domain $checkedHost, returning existing value and fetching new one from network") - coroutineScope.launch { fetchElementWellKnown(checkedHost, source, store) } - cacheData - } - is WellknownRetrieverResult.NotFound -> { - // Try to fetch from the server - Timber.d("No cached well-known for domain $checkedHost, fetching from network") - fetchElementWellKnown(checkedHost, source, store) - } - is WellknownRetrieverResult.Error -> { - // Return the error - Timber.e(cacheData.exception, "Error retrieving well-known for domain $checkedHost") - cacheData.exception.toWellknownRetrieverResult() - } - } - } - - private suspend fun fetchElementWellKnown( - host: String, - source: EnterpriseRemoteConfigSource, - store: ElementWellknownStore - ): WellknownRetrieverResult { - return urlContentFetcher - .getUrl(url(host, source)) - .mapCatchingExceptions { - val data = String(it) - val parsed = elementWellKnownParser.parse(data).getOrThrow() - // Also store in cache, if valid - store.update(host, data) - .onFailure { exception -> - Timber.e(exception, "Failed to parse cached Element .well-known data for $host, deleting cache") - store.delete(host) - } - parsed - } - .toWellknownRetrieverResult(host) - } - - private fun Result.toWellknownRetrieverResult(host: String): WellknownRetrieverResult = fold( - onSuccess = { - WellknownRetrieverResult.Success(it) - }, - onFailure = { - Timber.e(it, "Failed to retrieve Element .well-known from $host") - // This check on message value is not ideal but this is what we got from the SDK. - it.toWellknownRetrieverResult() - } - ) - - private fun Throwable.toWellknownRetrieverResult(): WellknownRetrieverResult { - return if ((this as? ClientException.Generic)?.message?.contains("404") == true) { - WellknownRetrieverResult.NotFound - } else { - WellknownRetrieverResult.Error(this as Exception) - } - } - - private fun url(host: String, source: EnterpriseRemoteConfigSource): String { - val elementWellKnownUrl = "https://$host/.well-known/element/element.json" - return when (source) { - EnterpriseRemoteConfigSource.ESS_CONFIG -> enterpriseService.essConfigEndpointUrl(host) ?: elementWellKnownUrl - EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT -> elementWellKnownUrl - } - } -} diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt deleted file mode 100644 index 317109a1c0..0000000000 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2025 New Vector 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 kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -/** - * Example: - *
- * {
- *     "registration_helper_url": "https://element.io",
- *     "enforce_element_pro": true,
- *     "rageshake_url": "https://example.org/rageshake",
- *     "brand_color": "#FF0000",
- *     "notification_sound": "ring.flac",
- *     "idp_app_scheme": "io.element.app",
- *     "custom_recovery_passphrase": {
- *         "min_character_count": 8
- *     }
- * }
- * 
- * . - */ -@Serializable -data class InternalElementWellKnown( - @SerialName("registration_helper_url") - val registrationHelperUrl: String? = null, - @SerialName("enforce_element_pro") - val enforceElementPro: Boolean? = null, - @SerialName("rageshake_url") - val rageshakeUrl: String? = null, - @SerialName("brand_color") - val brandColor: String? = null, - @SerialName("notification_sound") - val notificationSound: String? = null, - @SerialName("idp_app_scheme") - val identityProviderAppScheme: String? = null, - @SerialName("custom_recovery_passphrase") - val customRecoveryPassphrase: InternalCustomRecoveryPassphrase? = null, - @SerialName("content_scanner_url") - val contentScannerUrl: String? = null, - @SerialName("force_disable_e2ee") - val forceDisableE2EE: Boolean? = null, - @SerialName("map_tiler") - val mapTilerConfig: InternalMapTilerConfig? = null, -) - -@Serializable -data class InternalCustomRecoveryPassphrase( - @SerialName("min_character_count") - val minCharacterCount: Int? = null, -) - -@Serializable -data class InternalMapTilerConfig( - @SerialName("api_key") - val apiKey: String, - @SerialName("light_style_id") - val lightStyleId: String? = null, - @SerialName("dark_style_id") - val darkStyleId: String? = null, - @SerialName("base_url") - val baseUrl: String? = null, -) diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt deleted file mode 100644 index bf43356c45..0000000000 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector 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 kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -/** - * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery - *
- * {
- *     "m.homeserver": {
- *         "base_url": "https://matrix.org"
- *     },
- *     "m.identity_server": {
- *         "base_url": "https://vector.im"
- *     },
- * }
- * 
- * . - */ -@Serializable -data class InternalWellKnown( - @SerialName("m.homeserver") - val homeServer: InternalWellKnownBaseConfig? = null, - @SerialName("m.identity_server") - val identityServer: InternalWellKnownBaseConfig? = null, -) diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt deleted file mode 100644 index 342cd43395..0000000000 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector 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 kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -/** - * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery - *
- * {
- *     "base_url": "https://element.io"
- * }
- * 
- * . - */ -@Serializable -data class InternalWellKnownBaseConfig( - @SerialName("base_url") - val baseURL: String? = null -) diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt deleted file mode 100644 index 254036c5c3..0000000000 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2025 New Vector 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 io.element.android.libraries.core.log.logger.LoggerTag -import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase -import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.MapTilerConfig -import timber.log.Timber - -private val loggerTag = LoggerTag("Wellknown") - -/** - * Floor for the recovery passphrase minimum length. A value of 1 guarantees the derived passphrase is - * never empty (an empty passphrase derives a recoverable but effectively unprotected secret-storage - * key). Anything stronger than non-empty is left to the homeserver operator's configuration. - */ -private const val MINIMUM_PASSPHRASE_CHARACTER_COUNT = 1 - -fun InternalElementWellKnown.map() = ElementWellKnown( - registrationHelperUrl = registrationHelperUrl, - enforceElementPro = enforceElementPro, - rageshakeUrl = rageshakeUrl, - brandColor = brandColor, - notificationSound = notificationSound, - identityProviderAppScheme = identityProviderAppScheme, - customRecoveryPassphrase = customRecoveryPassphrase?.toPublic(), - contentScannerUrl = contentScannerUrl, - forceDisableE2EE = forceDisableE2EE, - mapTilerConfig = mapTilerConfig?.let { - MapTilerConfig( - apiKey = it.apiKey, - lightStyleId = it.lightStyleId, - darkStyleId = it.darkStyleId, - baseUrl = it.baseUrl, - ) - } -) - -private fun InternalCustomRecoveryPassphrase.toPublic(): CustomRecoveryPassphrase { - // Whenever the homeserver advertises the settings block we run the custom passphrase flow, flooring - // the minimum at 1 so the passphrase can never be empty even if the server omits min_character_count - // or advertises a non-positive value. The operator owns any stronger minimum. - val min = (minCharacterCount ?: MINIMUM_PASSPHRASE_CHARACTER_COUNT).coerceAtLeast(MINIMUM_PASSPHRASE_CHARACTER_COUNT) - if (min != minCharacterCount) { - Timber.tag(loggerTag.value).w( - "custom_recovery_passphrase.min_character_count was %s; flooring to %d", - minCharacterCount, - min, - ) - } - return CustomRecoveryPassphrase(minCharacterCount = min) -} diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/SessionWellknownModule.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/SessionWellknownModule.kt deleted file mode 100644 index 1587e95765..0000000000 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/SessionWellknownModule.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.BindingContainer -import dev.zacsweers.metro.ContributesTo -import dev.zacsweers.metro.Provides -import io.element.android.features.enterprise.api.EnterpriseService -import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.di.annotations.SessionCoroutineScope -import io.element.android.libraries.matrix.api.UrlContentFetcher -import io.element.android.libraries.wellknown.api.ElementWellKnownParser -import io.element.android.libraries.wellknown.api.ElementWellknownStore -import io.element.android.libraries.wellknown.api.WellknownRetriever -import kotlinx.coroutines.CoroutineScope - -@BindingContainer -@ContributesTo(SessionScope::class) -object SessionWellknownModule { - @Provides - fun provideWellKnownRetriever( - elementWellknownStoreFactory: ElementWellknownStore.Factory, - enterpriseService: EnterpriseService, - elementWellKnownParser: ElementWellKnownParser, - urlContentFetcher: UrlContentFetcher, - @SessionCoroutineScope coroutineScope: CoroutineScope, - ): WellknownRetriever { - return DefaultWellknownRetriever( - elementWellknownStoreFactory = elementWellknownStoreFactory, - enterpriseService = enterpriseService, - elementWellKnownParser = elementWellKnownParser, - urlContentFetcher = urlContentFetcher, - coroutineScope = coroutineScope - ) - } -} diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt deleted file mode 100644 index 54528f7c42..0000000000 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector 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 retrofit2.http.GET - -internal interface WellknownAPI { - @GET(".well-known/matrix/client") - suspend fun getWellKnown(): InternalWellKnown - - @GET(".well-known/element/element.json") - suspend fun getElementWellKnown(): InternalElementWellKnown -} diff --git a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/api/CustomRecoveryPassphraseTest.kt b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/api/CustomRecoveryPassphraseTest.kt deleted file mode 100644 index d4e7ac112e..0000000000 --- a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/api/CustomRecoveryPassphraseTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 - -import com.google.common.truth.Truth.assertThat -import org.junit.Test - -class CustomRecoveryPassphraseTest { - @Test - fun `empty input never satisfies a positive minimum`() { - assertThat(CustomRecoveryPassphrase(minCharacterCount = 1).isSatisfiedBy("")).isFalse() - } - - @Test - fun `input shorter than minimum is not satisfied`() { - assertThat(CustomRecoveryPassphrase(minCharacterCount = 8).isSatisfiedBy("abc")).isFalse() - } - - @Test - fun `input exactly at minimum is satisfied`() { - assertThat(CustomRecoveryPassphrase(minCharacterCount = 8).isSatisfiedBy("abcdefgh")).isTrue() - } - - @Test - fun `input longer than minimum is satisfied`() { - assertThat(CustomRecoveryPassphrase(minCharacterCount = 4).isSatisfiedBy("abcdefgh")).isTrue() - } -} diff --git a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStoreTest.kt b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStoreTest.kt deleted file mode 100644 index dc101c41a0..0000000000 --- a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStoreTest.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat -import io.element.android.features.wellknown.test.anElementWellKnown -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 -import kotlinx.coroutines.test.runTest -import org.junit.Test - -private const val A_DOMAIN = "matrix.example.com" -private const val A_CACHE_KEY = "https://$A_DOMAIN/.well-known/element/element.json" - -// 1 day in millis -private const val CACHE_VALIDITY_MILLIS = 1 * 24 * 60 * 60 * 1000L - -class DefaultWellKnownStoreTest { - private val jsonProvider = DefaultJsonProvider() - - @Test - fun `get returns NotFound when cache is empty`() = runTest { - val sut = createDefaultWellKnownStore() - assertThat(sut.get(A_DOMAIN)).isEqualTo(WellknownRetrieverResult.NotFound) - } - - @Test - fun `get returns Success when cache has fresh data`() = runTest { - val wellKnown = InternalElementWellKnown(rageshakeUrl = "https://rageshake.example.com") - val jsonString = jsonProvider().encodeToString(wellKnown) - val clock = FakeSystemClock(epochMillisResult = A_FAKE_TIMESTAMP) - val cacheStore = InMemoryCacheStore( - initialData = mapOf(A_CACHE_KEY to CacheData(jsonString, A_FAKE_TIMESTAMP)) - ) - val sut = createDefaultWellKnownStore(cacheStore = cacheStore, systemClock = clock) - - val result = sut.get(A_DOMAIN) - - assertThat(result).isEqualTo(WellknownRetrieverResult.Success(wellKnown.map())) - } - - @Test - fun `get returns Outdated when cached data has expired`() = runTest { - val wellKnown = InternalElementWellKnown(rageshakeUrl = "https://rageshake.example.com") - val jsonString = jsonProvider().encodeToString(wellKnown) - // Store data at timestamp 0, but clock is past the validity window - val expiredTimestamp = 0L - val nowTimestamp = expiredTimestamp + CACHE_VALIDITY_MILLIS + 1 - val clock = FakeSystemClock(epochMillisResult = nowTimestamp) - val cacheStore = InMemoryCacheStore( - initialData = mapOf(A_CACHE_KEY to CacheData(jsonString, expiredTimestamp)) - ) - val sut = createDefaultWellKnownStore(cacheStore = cacheStore, systemClock = clock) - - val result = sut.get(A_DOMAIN) - - assertThat(result).isEqualTo(WellknownRetrieverResult.Outdated(wellKnown.map())) - } - - @Test - fun `get returns Error when cached data is invalid JSON`() = runTest { - val cacheStore = InMemoryCacheStore( - initialData = mapOf(A_CACHE_KEY to CacheData("not valid json at all", A_FAKE_TIMESTAMP)) - ) - val sut = createDefaultWellKnownStore(cacheStore = cacheStore) - - val result = sut.get(A_DOMAIN) - - assertThat(result).isInstanceOf(WellknownRetrieverResult.Error::class.java) - } - - @Test - fun `update stores data and get returns Success`() = runTest { - val wellKnown = """ - { - "registration_helper_url": "https://element.io", - "enforce_element_pro": true, - "rageshake_url": "https://example.org/rageshake", - "brand_color": "#FF0000", - "notification_sound": "ring.flac", - "idp_app_scheme": "io.element.app", - "custom_recovery_passphrase": { - "min_character_count": 8 - } - } - """.trimIndent() - val clock = FakeSystemClock(epochMillisResult = A_FAKE_TIMESTAMP) - val cacheStore = InMemoryCacheStore() - val sut = createDefaultWellKnownStore(cacheStore = cacheStore, systemClock = clock) - - val updateResult = sut.update(A_DOMAIN, wellKnown) - assertThat(updateResult.isSuccess).isTrue() - - val result = sut.get(A_DOMAIN) - assertThat(result).isEqualTo(WellknownRetrieverResult.Success(anElementWellKnown( - registrationHelperUrl = "https://element.io", - enforceElementPro = true, - rageshakeUrl = "https://example.org/rageshake", - brandColor = "#FF0000", - notificationSound = "ring.flac", - identityProviderAppScheme = "io.element.app", - customRecoveryPassphrase = CustomRecoveryPassphrase(8), - ))) - } - - @Test - fun `delete removes data and get returns NotFound`() = runTest { - val wellKnown = InternalElementWellKnown(rageshakeUrl = "https://rageshake.example.com") - val jsonString = jsonProvider().encodeToString(wellKnown) - val clock = FakeSystemClock(epochMillisResult = A_FAKE_TIMESTAMP) - val cacheStore = InMemoryCacheStore( - initialData = mapOf(A_CACHE_KEY to CacheData(jsonString, A_FAKE_TIMESTAMP)) - ) - val sut = createDefaultWellKnownStore(cacheStore = cacheStore, systemClock = clock) - - val deleteResult = sut.delete(A_DOMAIN) - assertThat(deleteResult.isSuccess).isTrue() - - assertThat(sut.get(A_DOMAIN)).isEqualTo(WellknownRetrieverResult.NotFound) - } - - @Test - fun `get returns data at the exact cache validity boundary as Success`() = runTest { - val wellKnown = InternalElementWellKnown(notificationSound = "ping.flac") - val jsonString = jsonProvider().encodeToString(wellKnown) - // Exactly at the boundary: epochMillis == updatedAt + CACHE_VALIDITY_MILLIS → not expired - val storedAt = 1000L - val nowAtBoundary = storedAt + CACHE_VALIDITY_MILLIS - val clock = FakeSystemClock(epochMillisResult = nowAtBoundary) - val cacheStore = InMemoryCacheStore( - initialData = mapOf(A_CACHE_KEY to CacheData(jsonString, storedAt)) - ) - val sut = createDefaultWellKnownStore(cacheStore = cacheStore, systemClock = clock) - - val result = sut.get(A_DOMAIN) - - assertThat(result).isEqualTo(WellknownRetrieverResult.Success(wellKnown.map())) - } - - @Test - fun `prefix is used`() = runTest { - val prefix = "test-prefix" - val clock = FakeSystemClock(epochMillisResult = A_FAKE_TIMESTAMP) - val cacheStore = InMemoryCacheStore( - initialData = mapOf("$prefix-$A_DOMAIN" to CacheData("{}", A_FAKE_TIMESTAMP)) - ) - val sut = createDefaultWellKnownStore(prefix = prefix, cacheStore = cacheStore, systemClock = clock) - - assertThat(sut.get(A_DOMAIN)).isInstanceOf(WellknownRetrieverResult.Success::class.java) - } - - private fun createDefaultWellKnownStore( - prefix: String? = null, - cacheStore: InMemoryCacheStore = InMemoryCacheStore(), - systemClock: FakeSystemClock = FakeSystemClock(), - elementWellKnownParser: ElementWellKnownParser = DefaultElementWellKnownParser(jsonProvider), - ) = DefaultWellKnownStore( - prefix = prefix, - cacheStore = cacheStore, - systemClock = systemClock, - elementWellKnownParser = elementWellKnownParser, - ) -} diff --git a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt deleted file mode 100644 index a58aa0a347..0000000000 --- a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -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.aMapTilerConfig -import io.element.android.features.wellknown.test.anElementWellKnown -import io.element.android.libraries.core.uri.ensureProtocol -import io.element.android.libraries.matrix.api.UrlContentFetcher -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.EnterpriseRemoteConfigSource -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult -import io.element.android.tests.testutils.lambda.lambdaRecorder -import io.element.android.tests.testutils.lambda.value -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Test -import java.net.URL - -class DefaultWellknownRetrieverTest { - @Test - fun `get element wellknown calls resolver and parser`() = runTest { - val parser = lambdaRecorder> { Result.success(anElementWellKnown()) } - val resolver = SpyUrlContentFetcher { Result.success(WELLKNOWN_CONTENT.toByteArray()) } - val sut = createDefaultWellknownRetriever( - elementWellKnownParser = parser, - urlResolver = resolver, - ) - - sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT) - - resolver.assertions().isCalledOnce().with(value(WELLKNOWN_URL)) - parser.assertions().isCalledOnce().with(value(WELLKNOWN_CONTENT)) - } - - @Test - fun `get element wellknown parser error`() = runTest { - val sut = createDefaultWellknownRetriever( - elementWellKnownParser = { Result.failure(IllegalStateException("Invalid JSON")) }, - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) - .isInstanceOf(WellknownRetrieverResult.Error::class.java) - } - - @Test - fun `get element wellknown network error`() = runTest { - val resolver = SpyUrlContentFetcher { Result.failure(IllegalStateException("Network error")) } - val sut = createDefaultWellknownRetriever(urlResolver = resolver) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) - .isInstanceOf(WellknownRetrieverResult.Error::class.java) - } - - @Test - fun `get element wellknown 404 http error counts as not found`() = runTest { - val resolver = SpyUrlContentFetcher { Result.failure(ClientException.Generic("Http error: 404", null)) } - val sut = createDefaultWellknownRetriever(urlResolver = resolver) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) - .isInstanceOf(WellknownRetrieverResult.NotFound::class.java) - } - - @Test - fun `get element wellknown will return an error if the provided host is not valid`() = runTest { - val sut = createDefaultWellknownRetriever() - assertThat(sut.getElementWellKnown("!not valid", EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) - .isInstanceOf(WellknownRetrieverResult.Error::class.java) - } - - @Test - fun `get element wellknown will return a cached value if present instead of fetching`() = runTest { - val host = URL(WELLKNOWN_URL).host.ensureProtocol() - val sut = createDefaultWellknownRetriever( - cacheStoreFactory = { FakeElementWellknownStore(initialData = mapOf(host to WellknownRetrieverResult.Success(expectedElementWellKnown))) } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) - .isInstanceOf(WellknownRetrieverResult.Success::class.java) - } - - @Test - fun `get element wellknown will return an outdated value first then fetch the current one`() = runTest { - val host = URL(WELLKNOWN_URL).host.ensureProtocol() - val resolver = SpyUrlContentFetcher { Result.success(WELLKNOWN_CONTENT.toByteArray()) } - val sut = createDefaultWellknownRetriever( - urlResolver = resolver, - cacheStoreFactory = { FakeElementWellknownStore(initialData = mapOf(host to WellknownRetrieverResult.Outdated(expectedElementWellKnown))) } - ) - // The first call returns the outdated value - assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) - .isInstanceOf(WellknownRetrieverResult.Outdated::class.java) - // The resolver is not called yet because the fetch is done in the background - resolver.assertions().isNeverCalled() - - // We give it some time to run the background fetch - runCurrent() - - // The resolver is called during the background fetch - resolver.assertions().isCalledOnce().with(value(WELLKNOWN_URL)) - } - - @Test - fun `get element wellknown will return an error if it happens when trying to retrieve the cached value`() = runTest { - val host = URL(WELLKNOWN_URL).host.ensureProtocol() - val sut = createDefaultWellknownRetriever( - cacheStoreFactory = { FakeElementWellknownStore(initialData = mapOf(host to WellknownRetrieverResult.Error(IllegalStateException("BOOM")))) } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) - .isInstanceOf(WellknownRetrieverResult.Error::class.java) - } - - @Test - fun `get element wellknown will return an error if it fails to serialize the retrieved value into the cache`() = runTest { - val sut = createDefaultWellknownRetriever( - elementWellKnownParser = { Result.failure(IllegalStateException("BOOM")) }, - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) - .isInstanceOf(WellknownRetrieverResult.Error::class.java) - } - - @Test - fun `WELLKNOWN_ENDPOINT source creates a store with no prefix`() = runTest { - val storeFactory = lambdaRecorder { FakeElementWellknownStore() } - val sut = createDefaultWellknownRetriever(cacheStoreFactory = storeFactory) - - sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT) - - storeFactory.assertions().isCalledOnce().with(value(null)) - } - - @Test - fun `ESS_CONFIG source creates a store with ess prefix and checks the ESS config endpoint`() = runTest { - val essConfigUrl = "https://ess-config.example.com" - val storeFactory = lambdaRecorder { FakeElementWellknownStore() } - val resolver = SpyUrlContentFetcher { Result.success(WELLKNOWN_CONTENT.toByteArray()) } - val sut = createDefaultWellknownRetriever( - cacheStoreFactory = storeFactory, - enterpriseService = FakeEnterpriseService( - overrideWellKnownResult = { null }, - essConfigEndpointUrlResult = { essConfigUrl }, - ), - urlResolver = resolver, - ) - - sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.ESS_CONFIG) - - storeFactory.assertions().isCalledOnce().with(value("ess_config")) - resolver.assertions().isCalledOnce().with(value(essConfigUrl)) - } - - @Test - fun `get element wellknown was overridden`() = runTest { - val wellKnown = anElementWellKnown() - - val resolver = SpyUrlContentFetcher { Result.success(WELLKNOWN_CONTENT.toByteArray()) } - - val sut = createDefaultWellknownRetriever( - enterpriseService = FakeEnterpriseService( - overrideWellKnownResult = { wellKnown }, - ), - urlResolver = resolver, - ) - - // The overridden value is returned - assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)).isEqualTo( - WellknownRetrieverResult.Success(wellKnown) - ) - - // And the endpoint is never hit - resolver.assertions().isNeverCalled() - } - - private fun TestScope.createDefaultWellknownRetriever( - cacheStoreFactory: ElementWellknownStore.Factory = { FakeElementWellknownStore() }, - elementWellKnownParser: ElementWellKnownParser = { Result.success(expectedElementWellKnown) }, - enterpriseService: FakeEnterpriseService = FakeEnterpriseService(overrideWellKnownResult = { null }), - urlResolver: UrlContentFetcher = SpyUrlContentFetcher { Result.success(WELLKNOWN_CONTENT.toByteArray()) }, - ) = DefaultWellknownRetriever( - elementWellknownStoreFactory = cacheStoreFactory, - enterpriseService = enterpriseService, - elementWellKnownParser = elementWellKnownParser, - urlContentFetcher = urlResolver, - coroutineScope = backgroundScope, - ) - - companion object { - private const val WELLKNOWN_URL = "https://user.domain.org/.well-known/element/element.json" - private const val WELLKNOWN_CONTENT = """{ - "registration_helper_url": "a_registration_url", - "enforce_element_pro": true, - "rageshake_url": "a_rageshake_url", - "brand_color": "#FF0000", - "notification_sound": "a_notification_sound.flac", - "idp_app_scheme": "an_app_scheme", - "content_scanner_url": "https://content-scanner.example.com", - "force_disable_e2ee": false, - "map_tiler": { - "api_key": "test_api_key", - "base_url": "https://tile-server.example.com" - } - }""" - - private val expectedElementWellKnown = ElementWellKnown( - registrationHelperUrl = "a_registration_url", - enforceElementPro = true, - rageshakeUrl = "a_rageshake_url", - brandColor = "#FF0000", - notificationSound = "a_notification_sound.flac", - identityProviderAppScheme = "an_app_scheme", - contentScannerUrl = "https://content-scanner.example.com", - customRecoveryPassphrase = null, - forceDisableE2EE = false, - mapTilerConfig = aMapTilerConfig( - apiKey = "test_api_key", - baseUrl = "https://tile-server.example.com", - ) - ) - } -} - -private class SpyUrlContentFetcher(lambda: (String) -> Result) : UrlContentFetcher { - private val spy = lambdaRecorder> { lambda(it) } - - override suspend fun getUrl(url: String): Result { - return spy(url) - } - - fun assertions() = spy.assertions() -} diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeElementWellknownStore.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeElementWellknownStore.kt deleted file mode 100644 index c5e2d2ae5b..0000000000 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeElementWellknownStore.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.features.wellknown.test - -import io.element.android.libraries.androidutils.json.DefaultJsonProvider -import io.element.android.libraries.androidutils.json.JsonProvider -import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.ElementWellknownStore -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult -import io.element.android.libraries.wellknown.impl.InternalElementWellKnown -import io.element.android.libraries.wellknown.impl.map -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.JsonDecodingException -import java.net.URL - -class FakeElementWellknownStore( - initialData: Map> = emptyMap(), - private val jsonProvider: JsonProvider = DefaultJsonProvider(), -) : ElementWellknownStore { - private val cachedData = mutableMapOf>() - - init { - initialData.forEach { (url, result) -> - cachedData[URL(url).host] = result - } - } - - override suspend fun get(domain: String): WellknownRetrieverResult { - return cachedData[domain] ?: WellknownRetrieverResult.NotFound - } - - @OptIn(ExperimentalSerializationApi::class) - override suspend fun update(domain: String, wellknown: String): Result { - val json = jsonProvider() - val mapped = try { - json.decodeFromString(wellknown).map() - } catch (e: JsonDecodingException) { - return Result.failure(e) - } - cachedData[domain] = WellknownRetrieverResult.Success(mapped) - return Result.success(Unit) - } - - override suspend fun delete(domain: String): Result { - cachedData.remove(domain) - return Result.success(Unit) - } -} diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt deleted file mode 100644 index e08969d0f9..0000000000 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2025 New Vector 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.features.wellknown.test - -import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.EnterpriseRemoteConfigSource -import io.element.android.libraries.wellknown.api.WellknownRetriever -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult - -class FakeWellknownRetriever( - private val getElementWellKnownResult: (String, EnterpriseRemoteConfigSource) -> WellknownRetrieverResult = { _, _ -> - WellknownRetrieverResult.NotFound - }, -) : WellknownRetriever { - override suspend fun getElementWellKnown(host: String, source: EnterpriseRemoteConfigSource): WellknownRetrieverResult { - return getElementWellKnownResult(host, source) - } -} diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetrieverFactory.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetrieverFactory.kt deleted file mode 100644 index 718649dd5d..0000000000 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetrieverFactory.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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.features.wellknown.test - -import io.element.android.libraries.matrix.api.UrlContentFetcher -import io.element.android.libraries.wellknown.api.WellknownRetriever - -class FakeWellknownRetrieverFactory( - private val wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), -) : WellknownRetriever.Factory { - override fun create(urlContentFetcher: UrlContentFetcher): WellknownRetriever = wellknownRetriever -} diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt deleted file mode 100644 index 36d9f7bc82..0000000000 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2025 New Vector 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.features.wellknown.test - -import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase -import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.MapTilerConfig - -fun anElementWellKnown( - registrationHelperUrl: String? = null, - enforceElementPro: Boolean? = null, - rageshakeUrl: String? = null, - brandColor: String? = null, - notificationSound: String? = null, - identityProviderAppScheme: String? = null, - customRecoveryPassphrase: CustomRecoveryPassphrase? = null, - contentScannerUrl: String? = null, - forceDisableE2EE: Boolean? = null, - mapTilerConfig: MapTilerConfig? = null, -) = ElementWellKnown( - registrationHelperUrl = registrationHelperUrl, - enforceElementPro = enforceElementPro, - rageshakeUrl = rageshakeUrl, - brandColor = brandColor, - notificationSound = notificationSound, - identityProviderAppScheme = identityProviderAppScheme, - customRecoveryPassphrase = customRecoveryPassphrase, - contentScannerUrl = contentScannerUrl, - forceDisableE2EE = forceDisableE2EE, - mapTilerConfig = mapTilerConfig, -) - -fun aCustomRecoveryPassphrase( - minCharacterCount: Int = 8, -) = CustomRecoveryPassphrase( - minCharacterCount = minCharacterCount, -) - -fun aMapTilerConfig( - apiKey: String = "test_api_key", - baseUrl: String? = null, - lightStyleId: String? = null, - darkStyleId: String? = null, -) = MapTilerConfig( - apiKey = apiKey, - baseUrl = baseUrl, - lightStyleId = lightStyleId, - darkStyleId = darkStyleId, -) diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt index eb59579d79..01f4329111 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt @@ -9,7 +9,6 @@ package io.element.android.services.analyticsproviders.posthog import dev.zacsweers.metro.Inject -import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.core.extensions.isElement import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType @@ -17,10 +16,9 @@ import io.element.android.libraries.core.meta.BuildType @Inject class PosthogEndpointConfigProvider( private val buildMeta: BuildMeta, - private val enterpriseService: EnterpriseService, ) { fun provide(): PosthogEndpointConfig? { - return if (enterpriseService.isEnterpriseBuild) { + return if (buildMeta.isEnterpriseBuild) { PosthogEndpointConfig( host = BuildConfig.POSTHOG_HOST, apiKey = BuildConfig.POSTHOG_APIKEY,