Browse Source

Use SDK to get Element Wellknown content.

pull/3127/head
Benoit Marty 3 months ago
parent
commit
8ec283f2ca
  1. 6
      features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt
  2. 61
      features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ElementCallBaseUrlProvider.kt
  3. 5
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
  4. 11
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/call/ElementCallBaseUrlProvider.kt
  5. 6
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
  6. 52
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/call/DefaultElementCallBaseUrlProvider.kt
  7. 35
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/call/ElementWellKnownParser.kt
  8. 107
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/call/DefaultElementCallBaseUrlProviderTest.kt
  9. 28
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/call/FakeElementWellKnownParser.kt
  10. 8
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

6
features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt

@ -20,6 +20,7 @@ import com.squareup.anvil.annotations.ContributesBinding @@ -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( @@ -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(

61
features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ElementCallBaseUrlProvider.kt

@ -1,61 +0,0 @@ @@ -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
}
}
}

5
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt

@ -127,4 +127,9 @@ interface MatrixClient : Closeable { @@ -127,4 +127,9 @@ interface MatrixClient : Closeable {
* compute it manually.
*/
fun userIdServerName(): String
/**
* Execute generic GET requests through the SDKs internal HTTP client.
*/
suspend fun getUrl(url: String): Result<String>
}

11
features/call/impl/src/main/kotlin/io/element/android/features/call/impl/wellknown/CallWellknownAPI.kt → libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/call/ElementCallBaseUrlProvider.kt

@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
* 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
* https://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,
@ -14,11 +14,10 @@ @@ -14,11 +14,10 @@
* limitations under the License.
*/
package io.element.android.features.call.impl.wellknown
package io.element.android.libraries.matrix.api.call
import retrofit2.http.GET
import io.element.android.libraries.matrix.api.MatrixClient
internal interface CallWellknownAPI {
@GET(".well-known/element/call.json")
suspend fun getCallWellKnown(): CallWellKnown
interface ElementCallBaseUrlProvider {
suspend fun provides(matrixClient: MatrixClient): String?
}

6
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt

@ -291,6 +291,12 @@ class RustMatrixClient( @@ -291,6 +291,12 @@ class RustMatrixClient(
?: sessionId.value.substringAfter(":")
}
override suspend fun getUrl(url: String): Result<String> = withContext(sessionDispatcher) {
runCatching {
client.getUrl(url)
}
}
override suspend fun getRoom(roomId: RoomId): MatrixRoom? {
return roomFactory.create(roomId)
}

52
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/call/DefaultElementCallBaseUrlProvider.kt

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
/*
* 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
*
* https://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.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
}
}

35
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/call/ElementWellKnownParser.kt

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
/*
* 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
*
* https://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.libraries.matrix.impl.call
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import org.matrix.rustcomponents.sdk.ElementWellKnown
import org.matrix.rustcomponents.sdk.makeElementWellKnown
interface ElementWellKnownParser {
fun parse(str: String): Result<ElementWellKnown>
}
@ContributesBinding(AppScope::class)
class RustElementWellKnownParser : ElementWellKnownParser {
override fun parse(str: String): Result<ElementWellKnown> {
return runCatching {
makeElementWellKnown(str)
}
}
}

107
libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/call/DefaultElementCallBaseUrlProviderTest.kt

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
/*
* 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
*
* https://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.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
)
)
}
}

28
features/call/impl/src/main/kotlin/io/element/android/features/call/impl/wellknown/CallWellKnown.kt → libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/call/FakeElementWellKnownParser.kt

@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
* 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
* https://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,
@ -14,22 +14,14 @@ @@ -14,22 +14,14 @@
* limitations under the License.
*/
package io.element.android.features.call.impl.wellknown
package io.element.android.libraries.matrix.impl.call
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.matrix.rustcomponents.sdk.ElementWellKnown
/**
* Example:
* <pre>
* {
* "widget_url": "https://call.server.com"
* }
* </pre>
* .
*/
@Serializable
data class CallWellKnown(
@SerialName("widget_url")
val widgetUrl: String? = null,
)
class FakeElementWellKnownParser(
private val result: Result<ElementWellKnown>
) : ElementWellKnownParser {
override fun parse(str: String): Result<ElementWellKnown> {
return result
}
}

8
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

@ -82,6 +82,8 @@ class FakeMatrixClient( @@ -82,6 +82,8 @@ class FakeMatrixClient(
private val resolveRoomAliasResult: (RoomAlias) -> Result<ResolvedRoomAlias> = { Result.success(ResolvedRoomAlias(A_ROOM_ID, emptyList())) },
private val getRoomPreviewFromRoomIdResult: (RoomId, List<String>) -> Result<RoomPreview> = { _, _ -> Result.failure(AN_EXCEPTION) },
private val clearCacheLambda: () -> Unit = { lambdaError() },
private val userIdServerNameLambda: () -> String = { lambdaError() },
private val getUrlLambda: (String) -> Result<String> = { lambdaError() },
) : MatrixClient {
var setDisplayNameCalled: Boolean = false
private set
@ -315,6 +317,10 @@ class FakeMatrixClient( @@ -315,6 +317,10 @@ class FakeMatrixClient(
override fun sendQueueDisabledFlow(): Flow<RoomId> = sendQueueDisabledFlow
override fun userIdServerName(): String {
TODO("Not yet implemented")
return userIdServerNameLambda()
}
override suspend fun getUrl(url: String): Result<String> {
return getUrlLambda(url)
}
}

Loading…
Cancel
Save