Get more info from WellKnown request.

This commit is contained in:
Benoit Marty 2023-06-08 14:32:58 +02:00 committed by Benoit Marty
parent 5ed3164faf
commit 4df03762a8
11 changed files with 103 additions and 56 deletions

View file

@ -21,4 +21,6 @@ data class AccountProvider constructor(
val subtitle: String? = null,
val isPublic: Boolean = false,
val isMatrixOrg: Boolean = false,
val isValid: Boolean = false,
val supportSlidingSync: Boolean = false,
)

View file

@ -23,7 +23,9 @@ open class AccountProviderProvider : PreviewParameterProvider<AccountProvider> {
get() = sequenceOf(
anAccountProvider(),
anAccountProvider().copy(subtitle = null),
anAccountProvider().copy(title = "Other", subtitle = null, isPublic = false, isMatrixOrg = false),
anAccountProvider().copy(subtitle = null, title = "no.sliding.sync", supportSlidingSync = false),
anAccountProvider().copy(subtitle = null, title = "invalid", isValid = false, supportSlidingSync = false),
anAccountProvider().copy(subtitle = null, title = "Other", isPublic = false, isMatrixOrg = false),
// Add other state here
)
}
@ -33,4 +35,6 @@ fun anAccountProvider() = AccountProvider(
subtitle = "Matrix.org is an open network for secure, decentralized communication.",
isPublic = true,
isMatrixOrg = true,
isValid = true,
supportSlidingSync = true,
)

View file

@ -34,6 +34,8 @@ class ChangeAccountProviderPresenter @Inject constructor(
subtitle = null,
isPublic = true,
isMatrixOrg = true,
isValid = true,
supportSlidingSync = true,
)
),
)

View file

@ -39,23 +39,20 @@ fun aChangeAccountProviderFormState(
fun aHomeserverDataList(): List<HomeserverData> {
return listOf(
HomeserverData(
userInput = "matrix",
homeserverUrl = "https://matrix.org",
isWellknownValid = true,
),
HomeserverData(
userInput = "matrix",
homeserverUrl = "https://matrix.io",
isWellknownValid = false,
)
aHomeserverData(isWellknownValid = true, supportSlidingSync = true),
aHomeserverData(homeserverUrl = "https://no.sliding.sync", isWellknownValid = true, supportSlidingSync = false),
aHomeserverData(homeserverUrl = "https://invalid", isWellknownValid = false, supportSlidingSync = false),
)
}
fun aHomeserverData(): HomeserverData {
fun aHomeserverData(
homeserverUrl: String = "https://matrix.org",
isWellknownValid: Boolean = true,
supportSlidingSync: Boolean = true,
): HomeserverData {
return HomeserverData(
userInput = "matrix",
homeserverUrl = "https://matrix.org",
isWellknownValid = true,
homeserverUrl = homeserverUrl,
isWellknownValid = isWellknownValid,
supportSlidingSync = supportSlidingSync,
)
}

View file

@ -160,13 +160,7 @@ fun ChangeAccountProviderFormView(
}
is Async.Success -> {
state.userInputResult.state.forEach { homeserverData ->
val isMatrixOrg = homeserverData.homeserverUrl == "https://matrix.org"
val item = AccountProvider(
title = homeserverData.homeserverUrl.removePrefix("http://").removePrefix("https://"),
subtitle = if (isMatrixOrg) stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle) else null,
isPublic = isMatrixOrg, // There is no need to know for other servers right now
isMatrixOrg = isMatrixOrg,
)
val item = homeserverData.toAccountProvider()
AccountProviderView(
item = item,
onClick = {
@ -183,6 +177,19 @@ fun ChangeAccountProviderFormView(
}
}
@Composable
private fun HomeserverData.toAccountProvider(): AccountProvider {
val isMatrixOrg = homeserverUrl == "https://matrix.org"
return AccountProvider(
title = homeserverUrl.removePrefix("http://").removePrefix("https://"),
subtitle = if (isMatrixOrg) stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle) else null,
isPublic = isMatrixOrg, // There is no need to know for other servers right now
isMatrixOrg = isMatrixOrg,
isValid = isWellknownValid,
supportSlidingSync = supportSlidingSync,
)
}
@Preview
@Composable
fun ChangeAccountProviderFormViewLightPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) =

View file

@ -43,7 +43,7 @@ import javax.inject.Inject
class DefaultHomeserverResolver @Inject constructor(
private val dispatchers: CoroutineDispatchers,
private val wellknownRequest: WellknownRequest,
): HomeserverResolver {
) : HomeserverResolver {
private val mutableFlow: MutableStateFlow<Async<List<HomeserverData>>> = MutableStateFlow(Async.Uninitialized)
override fun flow(): StateFlow<Async<List<HomeserverData>>> = mutableFlow
@ -52,14 +52,14 @@ class DefaultHomeserverResolver @Inject constructor(
override suspend fun accept(userInput: String) {
currentJob?.cancel()
val cleanedUpUserInput = userInput.trim()
val cleanedUpUserInput = userInput.trim().ensureProtocol().removeSuffix("/")
mutableFlow.tryEmit(Async.Uninitialized)
if (cleanedUpUserInput.length > 3) {
delay(300)
mutableFlow.tryEmit(Async.Loading())
withContext(dispatchers.io) {
val list = getUrlCandidate(cleanedUpUserInput)
currentJob = resolveList(userInput, list)
currentJob = resolveList(cleanedUpUserInput, list)
}
}
}
@ -69,10 +69,18 @@ class DefaultHomeserverResolver @Inject constructor(
return launch {
list.map {
async {
val isValid = tryOrNull { wellknownRequest.execute(it) }.orFalse()
val wellKnown = tryOrNull { wellknownRequest.execute(it) }
val isValid = wellKnown?.isValid().orFalse()
val supportSlidingSync = wellKnown?.supportSlidingSync().orFalse()
if (isValid) {
// Emit the list as soon as possible
currentList.add(HomeserverData(userInput, it, true))
currentList.add(
HomeserverData(
homeserverUrl = it,
isWellknownValid = true,
supportSlidingSync = supportSlidingSync
)
)
mutableFlow.tryEmit(Async.Success(currentList))
}
}
@ -85,9 +93,9 @@ class DefaultHomeserverResolver @Inject constructor(
Async.Success(
listOf(
HomeserverData(
userInput = userInput,
homeserverUrl = userInput,
isWellknownValid = false
isWellknownValid = false,
supportSlidingSync = false,
)
)
)
@ -102,18 +110,15 @@ class DefaultHomeserverResolver @Inject constructor(
private fun getUrlCandidate(data: String): List<String> {
return buildList {
val s = data.ensureProtocol()
.removeSuffix("/")
// Always try what the user has entered
add(s)
add(data)
if (s.contains(".")) {
if (data.contains(".")) {
// TLD detected?
} else {
add("${s}.org")
add("${s}.com")
add("${s}.io")
add("${data}.org")
add("${data}.com")
add("${data}.io")
}
}
}

View file

@ -17,10 +17,10 @@
package io.element.android.features.login.impl.changeaccountprovider.form
data class HomeserverData constructor(
// What the user has entered
val userInput: String,
// The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url
val homeserverUrl: String,
// True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid
val isWellknownValid: Boolean,
// True if a wellknown file has been found and is valid and is claiming a sliding sync Url
val supportSlidingSync: Boolean,
)

View file

@ -16,6 +16,7 @@
package io.element.android.features.login.impl.changeaccountprovider.form.network
import io.element.android.libraries.core.bool.orFalse
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -28,6 +29,9 @@ import kotlinx.serialization.Serializable
* },
* "m.identity_server": {
* "base_url": "https://vector.im"
* },
* "org.matrix.msc3575.proxy": {
* "url": "https://slidingsync.lab.matrix.org"
* }
* }
* </pre>
@ -40,4 +44,15 @@ data class WellKnown(
@SerialName("m.identity_server")
val identityServer: WellKnownBaseConfig? = null,
)
@SerialName("org.matrix.msc3575.proxy")
val slidingSyncProxy: WellKnownSlidingSyncConfig? = null,
) {
fun isValid(): Boolean {
return homeServer?.baseURL?.isNotBlank().orFalse()
}
fun supportSlidingSync(): Boolean {
return slidingSyncProxy?.url?.isNotBlank().orFalse()
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2023 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.login.impl.changeaccountprovider.form.network
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class WellKnownSlidingSyncConfig(
@SerialName("url")
val url: String? = null,
)

View file

@ -15,32 +15,19 @@
*/
package io.element.android.features.login.impl.changeaccountprovider.form.network
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.network.RetrofitFactory
import timber.log.Timber
import javax.inject.Inject
class WellknownRequest @Inject constructor(
private val retrofitFactory: RetrofitFactory,
) {
/**
* Return true if the wellknown can be retrieved and is valid.
* Return the WellKnown data, if found.
* @param baseUrl for instance https://matrix.org
*/
suspend fun execute(baseUrl: String): Boolean {
suspend fun execute(baseUrl: String): WellKnown {
val wellknownApi = retrofitFactory.create(baseUrl)
.create(WellknownAPI::class.java)
return try {
val response = wellknownApi.getWellKnown()
response.isValid()
} catch (throwable: Throwable) {
Timber.e(throwable)
false
}
return wellknownApi.getWellKnown()
}
}
private fun WellKnown.isValid(): Boolean {
return homeServer?.baseURL?.isNotBlank().orFalse()
}

View file

@ -40,6 +40,8 @@ class ChangeAccountProviderPresenterTest {
subtitle = null,
isPublic = true,
isMatrixOrg = true,
isValid = true,
supportSlidingSync = true,
)
)
)