Use embedded version of Element Call (#4470)

* Use embedded version of Element Call: for in-app room calls, the app will use an embedded version of Element Call shipped with the app instead of using an external service.

* Remove `ElementCallBaseUrlProvider` so we don't use the Element well known file to get the base URL anymore

* Remove `ElementCallConfig.DEFAULT_BASE_URL` since it's not used anymore

* Restore the usage of the custom EC base URL in developer settings as the actual base URL, it present

* Add a way to customise the embedded EC analytic credentials

* Update CI to use the EC analytic credentials as secrets

* Improve the custom URL placeholder to include the `/room` suffix
This commit is contained in:
Jorge Martin Espinosa 2025-03-26 09:35:21 +01:00 committed by GitHub
parent 03f4122b3f
commit ba626fc173
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 177 additions and 288 deletions

View file

@ -189,12 +189,12 @@ class RustMatrixAuthenticationService @Inject constructor(
return withContext(coroutineDispatchers.io) {
runCatching {
val client = currentClient ?: error("You need to call `setHomeserver()` first")
val oAuthAuthenticationData = client.urlForOidc(
val oAuthAuthorizationData = client.urlForOidc(
oidcConfiguration = oidcConfigurationProvider.get(),
prompt = prompt.toRustPrompt(),
)
val url = oAuthAuthenticationData.loginUrl()
pendingOAuthAuthorizationData = oAuthAuthenticationData
val url = oAuthAuthorizationData.loginUrl()
pendingOAuthAuthorizationData = oAuthAuthorizationData
OidcDetails(url)
}.mapFailure { failure ->
failure.mapAuthenticationException()

View file

@ -1,43 +0,0 @@
/*
* Copyright 2024 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.matrix.impl.call
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.call.ElementCallBaseUrlProvider
import timber.log.Timber
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultElementCallBaseUrlProvider @Inject constructor(
private val elementWellKnownParser: ElementWellKnownParser,
) : ElementCallBaseUrlProvider {
override suspend fun provides(matrixClient: MatrixClient): String? {
val url = buildString {
append("https://")
append(matrixClient.userIdServerName())
append("/.well-known/element/element.json")
}
return matrixClient.getUrl(url)
.onFailure { failure ->
Timber.w(failure, "Failed to fetch well-known element.json")
}
.getOrNull()
?.let { wellKnownStr ->
elementWellKnownParser.parse(wellKnownStr)
.onFailure { failure ->
// Can be a HTML 404.
Timber.w(failure, "Failed to parse content")
}
.getOrNull()
}
?.call
?.widgetUrl
}
}

View file

@ -8,15 +8,13 @@
package io.element.android.libraries.matrix.impl.widget
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.RageshakeConfig
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.meta.BuildType
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.widget.CallAnalyticCredentialsProvider
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.services.analytics.api.store.AnalyticsStore
import io.element.android.services.analyticsproviders.posthog.PosthogEndpointConfigProvider
import io.element.android.services.analyticsproviders.sentry.SentryConfig
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.flow.first
import org.matrix.rustcomponents.sdk.EncryptionSystem
import org.matrix.rustcomponents.sdk.VirtualElementCallWidgetOptions
@ -27,12 +25,11 @@ import org.matrix.rustcomponents.sdk.Intent as CallIntent
@ContributesBinding(AppScope::class)
class DefaultCallWidgetSettingsProvider @Inject constructor(
private val buildMeta: BuildMeta,
private val posthogEndpointConfigProvider: PosthogEndpointConfigProvider,
private val analyticsStore: AnalyticsStore,
private val callAnalyticsCredentialsProvider: CallAnalyticCredentialsProvider,
private val analyticsService: AnalyticsService,
) : CallWidgetSettingsProvider {
override suspend fun provide(baseUrl: String, widgetId: String, encrypted: Boolean): MatrixWidgetSettings {
val analyticsEnabled = analyticsStore.userConsentFlow.first()
val posthogEndpointConfig = posthogEndpointConfigProvider.provide()
val isAnalyticsEnabled = analyticsService.getUserConsent().first()
val options = VirtualElementCallWidgetOptions(
elementCallUrl = baseUrl,
widgetId = widgetId,
@ -44,12 +41,12 @@ class DefaultCallWidgetSettingsProvider @Inject constructor(
encryption = if (encrypted) EncryptionSystem.PerParticipantKeys else EncryptionSystem.Unencrypted,
intent = CallIntent.START_CALL,
hideScreensharing = false,
posthogUserId = null,
posthogApiHost = posthogEndpointConfig.host.takeIf { analyticsEnabled },
posthogApiKey = posthogEndpointConfig.apiKey.takeIf { analyticsEnabled },
rageshakeSubmitUrl = RageshakeConfig.BUG_REPORT_URL,
sentryDsn = SentryConfig.DSN.takeIf { analyticsEnabled },
sentryEnvironment = if (buildMeta.buildType == BuildType.RELEASE) SentryConfig.ENV_RELEASE else SentryConfig.ENV_DEBUG,
posthogUserId = callAnalyticsCredentialsProvider.posthogUserId.takeIf { isAnalyticsEnabled },
posthogApiHost = callAnalyticsCredentialsProvider.posthogApiHost.takeIf { isAnalyticsEnabled },
posthogApiKey = callAnalyticsCredentialsProvider.posthogApiKey.takeIf { isAnalyticsEnabled },
rageshakeSubmitUrl = callAnalyticsCredentialsProvider.rageshakeSubmitUrl,
sentryDsn = callAnalyticsCredentialsProvider.sentryDsn.takeIf { isAnalyticsEnabled },
sentryEnvironment = if (buildMeta.buildType == BuildType.RELEASE) "RELEASE" else "DEBUG",
parentUrl = null,
hideHeader = true,
)

View file

@ -1,99 +0,0 @@
/*
* Copyright 2024 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.matrix.impl.call
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.matrix.rustcomponents.sdk.ElementCallWellKnown
import org.matrix.rustcomponents.sdk.ElementWellKnown
class DefaultElementCallBaseUrlProviderTest {
@Test
fun `provides returns null when getUrl returns an error`() = runTest {
val userIdServerNameLambda = lambdaRecorder<String> { "example.com" }
val getUrlLambda = lambdaRecorder<String, Result<String>> { _ ->
Result.failure(AN_EXCEPTION)
}
val sut = DefaultElementCallBaseUrlProvider(
FakeElementWellKnownParser(
Result.success(createElementWellKnown(""))
)
)
val matrixClient = FakeMatrixClient(
userIdServerNameLambda = userIdServerNameLambda,
getUrlLambda = getUrlLambda,
)
val result = sut.provides(matrixClient)
assertThat(result).isNull()
userIdServerNameLambda.assertions().isCalledOnce()
getUrlLambda.assertions().isCalledOnce()
.with(value("https://example.com/.well-known/element/element.json"))
}
@Test
fun `provides returns null when content parsing fails`() = runTest {
val userIdServerNameLambda = lambdaRecorder<String> { "example.com" }
val getUrlLambda = lambdaRecorder<String, Result<String>> { _ ->
Result.success("""{"call":{"widget_url":"https://example.com/call"}}""")
}
val sut = DefaultElementCallBaseUrlProvider(
createFakeElementWellKnownParser(
Result.failure(AN_EXCEPTION)
)
)
val matrixClient = FakeMatrixClient(
userIdServerNameLambda = userIdServerNameLambda,
getUrlLambda = getUrlLambda,
)
val result = sut.provides(matrixClient)
assertThat(result).isNull()
userIdServerNameLambda.assertions().isCalledOnce()
getUrlLambda.assertions().isCalledOnce()
.with(value("https://example.com/.well-known/element/element.json"))
}
@Test
fun `provides returns value when getUrl returns correct content`() = runTest {
val userIdServerNameLambda = lambdaRecorder<String> { "example.com" }
val getUrlLambda = lambdaRecorder<String, Result<String>> { _ ->
Result.success("""{"call":{"widget_url":"https://example.com/call"}}""")
}
val sut = DefaultElementCallBaseUrlProvider(
createFakeElementWellKnownParser(
Result.success(createElementWellKnown("aUrl"))
)
)
val matrixClient = FakeMatrixClient(
userIdServerNameLambda = userIdServerNameLambda,
getUrlLambda = getUrlLambda,
)
val result = sut.provides(matrixClient)
assertThat(result).isEqualTo("aUrl")
userIdServerNameLambda.assertions().isCalledOnce()
getUrlLambda.assertions().isCalledOnce()
.with(value("https://example.com/.well-known/element/element.json"))
}
private fun createFakeElementWellKnownParser(result: Result<ElementWellKnown>): FakeElementWellKnownParser {
return FakeElementWellKnownParser(result)
}
private fun createElementWellKnown(widgetUrl: String): ElementWellKnown {
return ElementWellKnown(
call = ElementCallWellKnown(
widgetUrl = widgetUrl,
),
registrationHelperUrl = null,
)
}
}