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 248d0bad83
commit 48a95c6e01
11 changed files with 103 additions and 56 deletions

View file

@ -21,4 +21,6 @@ data class AccountProvider constructor(
val subtitle: String? = null, val subtitle: String? = null,
val isPublic: Boolean = false, val isPublic: Boolean = false,
val isMatrixOrg: 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( get() = sequenceOf(
anAccountProvider(), anAccountProvider(),
anAccountProvider().copy(subtitle = null), 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 // Add other state here
) )
} }
@ -33,4 +35,6 @@ fun anAccountProvider() = AccountProvider(
subtitle = "Matrix.org is an open network for secure, decentralized communication.", subtitle = "Matrix.org is an open network for secure, decentralized communication.",
isPublic = true, isPublic = true,
isMatrixOrg = true, isMatrixOrg = true,
isValid = true,
supportSlidingSync = true,
) )

View file

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

View file

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

View file

@ -160,13 +160,7 @@ fun ChangeAccountProviderFormView(
} }
is Async.Success -> { is Async.Success -> {
state.userInputResult.state.forEach { homeserverData -> state.userInputResult.state.forEach { homeserverData ->
val isMatrixOrg = homeserverData.homeserverUrl == "https://matrix.org" val item = homeserverData.toAccountProvider()
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,
)
AccountProviderView( AccountProviderView(
item = item, item = item,
onClick = { 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 @Preview
@Composable @Composable
fun ChangeAccountProviderFormViewLightPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) = fun ChangeAccountProviderFormViewLightPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) =

View file

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

View file

@ -17,10 +17,10 @@
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.changeaccountprovider.form
data class HomeserverData constructor( 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 // The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url
val homeserverUrl: String, val homeserverUrl: String,
// True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid // True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid
val isWellknownValid: Boolean, 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 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.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -28,6 +29,9 @@ import kotlinx.serialization.Serializable
* }, * },
* "m.identity_server": { * "m.identity_server": {
* "base_url": "https://vector.im" * "base_url": "https://vector.im"
* },
* "org.matrix.msc3575.proxy": {
* "url": "https://slidingsync.lab.matrix.org"
* } * }
* } * }
* </pre> * </pre>
@ -40,4 +44,15 @@ data class WellKnown(
@SerialName("m.identity_server") @SerialName("m.identity_server")
val identityServer: WellKnownBaseConfig? = null, 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 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 io.element.android.libraries.network.RetrofitFactory
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class WellknownRequest @Inject constructor( class WellknownRequest @Inject constructor(
private val retrofitFactory: RetrofitFactory, 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 * @param baseUrl for instance https://matrix.org
*/ */
suspend fun execute(baseUrl: String): Boolean { suspend fun execute(baseUrl: String): WellKnown {
val wellknownApi = retrofitFactory.create(baseUrl) val wellknownApi = retrofitFactory.create(baseUrl)
.create(WellknownAPI::class.java) .create(WellknownAPI::class.java)
return wellknownApi.getWellKnown()
return try {
val response = wellknownApi.getWellKnown()
response.isValid()
} catch (throwable: Throwable) {
Timber.e(throwable)
false
}
} }
} }
private fun WellKnown.isValid(): Boolean {
return homeServer?.baseURL?.isNotBlank().orFalse()
}

View file

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