use nodeinfo whenever possible

This commit is contained in:
Matthieu 2021-02-04 20:44:31 +01:00
parent 360e40b7fa
commit f8b3e1627a
7 changed files with 99 additions and 59 deletions

View File

@ -10,22 +10,15 @@ import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.lifecycle.lifecycleScope
import com.h.pixeldroid.databinding.ActivityLoginBinding
import com.h.pixeldroid.databinding.ActivityPostCreationBinding
import com.h.pixeldroid.utils.BaseActivity
import com.h.pixeldroid.utils.*
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.*
import com.h.pixeldroid.utils.db.addUser
import com.h.pixeldroid.utils.db.storeInstance
import com.h.pixeldroid.utils.hasInternet
import com.h.pixeldroid.utils.normalizeDomain
import com.h.pixeldroid.utils.openUrl
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import okhttp3.HttpUrl
import kotlinx.coroutines.*
import retrofit2.HttpException
import java.io.IOException
import java.lang.IllegalArgumentException
/**
Overview of the flow of the login process: (boxes are requests done in parallel,
@ -125,11 +118,7 @@ class LoginActivity : BaseActivity() {
private fun registerAppToServer(normalizedDomain: String) {
try{
HttpUrl.Builder().host(normalizedDomain.replace("https://", "")).scheme("https").build()
} catch (e: IllegalArgumentException) {
return failedRegistration(getString(R.string.invalid_domain))
}
if(!validDomain(normalizedDomain)) failedRegistration(getString(R.string.invalid_domain))
hideKeyboard()
loadingAnimation(true)
@ -138,7 +127,6 @@ class LoginActivity : BaseActivity() {
lifecycleScope.launch {
try {
supervisorScope { }
val credentialsDeferred: Deferred<Application?> = async {
try {
pixelfedAPI.registerApplication(
@ -157,7 +145,6 @@ class LoginActivity : BaseActivity() {
val clientId = credentials?.client_id ?: return@launch failedRegistration()
preferences.edit()
.putString("domain", normalizedDomain)
.putString("clientID", clientId)
.putString("clientSecret", credentials.client_secret)
.apply()
@ -181,15 +168,39 @@ class LoginActivity : BaseActivity() {
normalizedDomain: String,
clientId: String,
nodeInfoSchemaUrl: String
) {
val nodeInfo = try {
) = coroutineScope {
val nodeInfo: NodeInfo = try {
pixelfedAPI.nodeInfoSchema(nodeInfoSchemaUrl)
} catch (exception: IOException) {
return failedRegistration(getString(R.string.instance_error))
return@coroutineScope failedRegistration(getString(R.string.instance_error))
} catch (exception: HttpException) {
return failedRegistration(getString(R.string.instance_error))
return@coroutineScope failedRegistration(getString(R.string.instance_error))
}
//TODO here check for api being activated, if not show dialog
val domain: String = try {
if (nodeInfo.hasInstanceEndpointInfo()) {
storeInstance(db, nodeInfo)
nodeInfo.metadata?.config?.site?.url!!
} else {
val instance: Instance = try {
pixelfedAPI.instance()
} catch (exception: IOException) {
return@coroutineScope failedRegistration(getString(R.string.instance_error))
} catch (exception: HttpException) {
return@coroutineScope failedRegistration(getString(R.string.instance_error))
}
storeInstance(db, nodeInfo = null, instance = instance)
instance.uri ?: return@coroutineScope failedRegistration(getString(R.string.instance_error))
}
} catch (e: IllegalArgumentException){
return@coroutineScope failedRegistration(getString(R.string.instance_error))
}
preferences.edit().putString("domain", domain).apply()
if (!nodeInfo.software?.name.orEmpty().contains("pixelfed")) {
val builder = AlertDialog.Builder(this@LoginActivity)
builder.apply {
@ -238,9 +249,6 @@ class LoginActivity : BaseActivity() {
lifecycleScope.launch {
try {
val instanceDeferred = async {
pixelfedAPI.instance()
}
val token = pixelfedAPI.obtainToken(
clientId, clientSecret, "$oauthScheme://$PACKAGE_ID", SCOPE, code,
"authorization_code"
@ -248,20 +256,12 @@ class LoginActivity : BaseActivity() {
if (token.access_token == null) {
return@launch failedRegistration(getString(R.string.token_error))
}
val instance = instanceDeferred.await()
if (instance.uri == null) {
return@launch failedRegistration(getString(R.string.instance_error))
}
storeInstance(db, instance)
storeUser(
token.access_token,
token.refresh_token,
clientId,
clientSecret,
instance.uri
domain
)
wipeSharedSettings()
} catch (exception: IOException) {

View File

@ -91,7 +91,7 @@ class PostCreationActivity : BaseActivity() {
instances.filter { instanceDatabaseEntity ->
instanceDatabaseEntity.uri.contains(user!!.instance_uri)
}
thisInstances.first().max_toot_chars
thisInstances.first().maxStatusChars
} else {
Instance.DEFAULT_MAX_TOOT_CHARS
}

View File

@ -14,6 +14,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.h.pixeldroid.R
import okhttp3.HttpUrl
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@ -22,6 +23,21 @@ fun hasInternet(context: Context): Boolean {
return cm.activeNetwork != null
}
/**
* Check if domain is valid or not
*/
fun validDomain(domain: String?): Boolean {
domain?.apply {
try {
HttpUrl.Builder().host(replace("https://", "")).scheme("https").build()
} catch (e: IllegalArgumentException) {
return false
}
} ?: return false
return true
}
fun normalizeDomain(domain: String): String {
return "https://" + domain
.replace("http://", "")

View File

@ -11,6 +11,8 @@ data class Instance (
val version: String?
) {
companion object {
// Default max number of chars for Mastodon: used when their is no other value supplied by
// either NodeInfo or the instance endpoint
const val DEFAULT_MAX_TOOT_CHARS = 500
}
}

View File

@ -1,5 +1,7 @@
package com.h.pixeldroid.utils.api.objects
import com.h.pixeldroid.utils.validDomain
/*
See https://nodeinfo.diaspora.software/schema.html and https://pixelfed.social/api/nodeinfo/2.0.json
A lot of attributes we don't need are omitted, if in the future they are needed we
@ -11,8 +13,21 @@ data class NodeInfo (
val software: Software?,
val protocols: List<String>?,
val openRegistrations: Boolean?,
val metadata: PixelfedMetadata?
val metadata: PixelfedMetadata?,
){
/**
* Check if this NodeInfo has the fields we need or if we also need to look into the
* /api/v1/instance endpoint
* This only checks for values that might be in the /api/v1/instance endpoint.
*/
fun hasInstanceEndpointInfo(): Boolean {
return validDomain(metadata?.config?.site?.url)
&& !metadata?.config?.site?.name.isNullOrBlank()
&& metadata?.config?.uploader?.max_caption_length?.toIntOrNull() != null
}
data class Software(
val name: String?,
val version: String?
@ -31,7 +46,8 @@ data class NodeInfo (
val open_registration: Boolean?,
val uploader: Uploader?,
val activitypub: ActivityPub?,
val features: Features?
val features: Features?,
val site: Site?
){
data class Uploader(
val max_photo_size: String?,
@ -55,6 +71,13 @@ data class NodeInfo (
val stories: Boolean?,
val video: Boolean?
)
data class Site(
val name: String?,
val domain: String?,
val url: String?,
val description: String?
)
}
}

View File

@ -4,23 +4,16 @@ import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import com.h.pixeldroid.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Instance
import com.h.pixeldroid.utils.api.objects.NodeInfo
import com.h.pixeldroid.utils.normalizeDomain
private fun normalizeOrNot(uri: String): String{
return if(uri.startsWith("http://localhost")){
uri
} else {
normalizeDomain(uri)
}
}
import java.lang.IllegalArgumentException
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
db.userDao().insertUser(
UserDatabaseEntity(
user_id = account.id!!,
//make sure not to normalize to https when localhost, to allow testing
instance_uri = normalizeOrNot(instance_uri),
instance_uri = normalizeDomain(instance_uri),
username = account.username!!,
display_name = account.getDisplayName(),
avatar_static = account.avatar_static.orEmpty(),
@ -33,14 +26,20 @@ fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser:
)
}
fun storeInstance(db: AppDatabase, instance: Instance) {
val maxTootChars = instance.max_toot_chars?.toInt() ?: Instance.DEFAULT_MAX_TOOT_CHARS
val dbInstance = InstanceDatabaseEntity(
//make sure not to normalize to https when localhost, to allow testing
uri = normalizeOrNot(instance.uri.orEmpty()),
title = instance.title.orEmpty(),
max_toot_chars = maxTootChars,
thumbnail = instance.thumbnail.orEmpty()
)
fun storeInstance(db: AppDatabase, nodeInfo: NodeInfo?, instance: Instance? = null) {
val dbInstance: InstanceDatabaseEntity = nodeInfo?.run {
InstanceDatabaseEntity(
uri = normalizeDomain(metadata?.config?.site?.url!!),
title = metadata.config.site.name!!,
maxStatusChars = metadata.config.uploader?.max_caption_length!!.toInt(),
)
} ?: instance?.run {
InstanceDatabaseEntity(
uri = normalizeDomain(uri.orEmpty()),
title = title.orEmpty(),
maxStatusChars = max_toot_chars?.toInt() ?: Instance.DEFAULT_MAX_TOOT_CHARS,
)
} ?: throw IllegalArgumentException("Cannot store instance where both are null")
db.instanceDao().insertInstance(dbInstance)
}

View File

@ -6,8 +6,8 @@ import com.h.pixeldroid.utils.api.objects.Instance
@Entity(tableName = "instances")
data class InstanceDatabaseEntity (
@PrimaryKey var uri: String,
var title: String = "",
var max_toot_chars: Int = Instance.DEFAULT_MAX_TOOT_CHARS,
var thumbnail: String = ""
@PrimaryKey var uri: String,
var title: String,
var maxStatusChars: Int = Instance.DEFAULT_MAX_TOOT_CHARS,
)