Merge pull request #3127 from element-hq/feature/bma/elementWellKnown

Let the SDK retrieve and parse Element well known content
This commit is contained in:
Benoit Marty 2024-07-02 17:16:06 +02:00 committed by GitHub
commit 37eaa5e094
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 318 additions and 214 deletions

View file

@ -20,6 +20,7 @@ import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.ElementCallConfig
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.call.ElementCallBaseUrlProvider
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
@ -41,9 +42,10 @@ class DefaultCallWidgetProvider @Inject constructor(
languageTag: String?,
theme: String?,
): Result<CallWidgetProvider.GetWidgetResult> = runCatching {
val room = matrixClientsProvider.getOrRestore(sessionId).getOrThrow().getRoom(roomId) ?: error("Room not found")
val matrixClient = matrixClientsProvider.getOrRestore(sessionId).getOrThrow()
val room = matrixClient.getRoom(roomId) ?: error("Room not found")
val baseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull()
?: elementCallBaseUrlProvider.provides(sessionId)
?: elementCallBaseUrlProvider.provides(matrixClient)
?: ElementCallConfig.DEFAULT_BASE_URL
val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = room.isEncrypted)
val callUrl = room.generateWidgetWebViewUrl(

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.call.impl.utils
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.call.impl.wellknown.CallWellknownAPI
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.network.RetrofitFactory
import kotlinx.coroutines.withContext
import retrofit2.HttpException
import timber.log.Timber
import java.net.HttpURLConnection
import javax.inject.Inject
interface ElementCallBaseUrlProvider {
suspend fun provides(sessionId: SessionId): String?
}
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class DefaultElementCallBaseUrlProvider @Inject constructor(
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: CoroutineDispatchers,
) : ElementCallBaseUrlProvider {
private val apiCache = mutableMapOf<SessionId, CallWellknownAPI>()
override suspend fun provides(sessionId: SessionId): String? = withContext(coroutineDispatchers.io) {
val domain = sessionId.value.substringAfter(":")
val callWellknownAPI = apiCache.getOrPut(sessionId) {
retrofitFactory.create("https://$domain")
.create(CallWellknownAPI::class.java)
}
try {
callWellknownAPI.getCallWellKnown().widgetUrl
} catch (e: HttpException) {
// Ignore Http 404, but re-throws any other exceptions
if (e.code() != HttpURLConnection.HTTP_NOT_FOUND) {
throw e
}
Timber.w(e, "Failed to fetch wellknown data")
null
}
}
}

View file

@ -1,35 +0,0 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.call.impl.wellknown
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Example:
* <pre>
* {
* "widget_url": "https://call.server.com"
* }
* </pre>
* .
*/
@Serializable
data class CallWellKnown(
@SerialName("widget_url")
val widgetUrl: String? = null,
)

View file

@ -1,24 +0,0 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.call.impl.wellknown
import retrofit2.http.GET
internal interface CallWellknownAPI {
@GET(".well-known/element/call.json")
suspend fun getCallWellKnown(): CallWellKnown
}

View file

@ -18,9 +18,9 @@ package io.element.android.features.call.utils
import com.google.common.truth.Truth.assertThat
import io.element.android.features.call.impl.utils.DefaultCallWidgetProvider
import io.element.android.features.call.impl.utils.ElementCallBaseUrlProvider
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.call.ElementCallBaseUrlProvider
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
@ -116,9 +116,9 @@ class DefaultCallWidgetProviderTest {
@Test
fun `getWidget - will use a wellknown base url if it exists`() = runTest {
val aCustomUrl = "https://custom.element.io"
val providesLambda = lambdaRecorder<SessionId, String?> { _ -> aCustomUrl }
val elementCallBaseUrlProvider = FakeElementCallBaseUrlProvider { sessionId ->
providesLambda(sessionId)
val providesLambda = lambdaRecorder<MatrixClient, String?> { _ -> aCustomUrl }
val elementCallBaseUrlProvider = FakeElementCallBaseUrlProvider { matrixClient ->
providesLambda(matrixClient)
}
val room = FakeMatrixRoom().apply {
givenGenerateWidgetWebViewUrlResult(Result.success("url"))
@ -137,7 +137,7 @@ class DefaultCallWidgetProviderTest {
assertThat(settingsProvider.providedBaseUrls).containsExactly(aCustomUrl)
providesLambda.assertions()
.isCalledOnce()
.with(value(A_SESSION_ID))
.with(value(client))
}
private fun createProvider(

View file

@ -16,14 +16,14 @@
package io.element.android.features.call.utils
import io.element.android.features.call.impl.utils.ElementCallBaseUrlProvider
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.call.ElementCallBaseUrlProvider
import io.element.android.tests.testutils.lambda.lambdaError
class FakeElementCallBaseUrlProvider(
private val providesLambda: (SessionId) -> String? = { lambdaError() }
private val providesLambda: (MatrixClient) -> String? = { lambdaError() }
) : ElementCallBaseUrlProvider {
override suspend fun provides(sessionId: SessionId): String? {
return providesLambda(sessionId)
override suspend fun provides(matrixClient: MatrixClient): String? {
return providesLambda(matrixClient)
}
}