Start creating the widget url builder

This commit is contained in:
ganfra 2020-05-14 17:05:22 +02:00
parent 03389fc040
commit 8f5589d3e1
10 changed files with 226 additions and 15 deletions

View file

@ -24,6 +24,8 @@ import im.vector.matrix.android.internal.session.widgets.Widget
interface WidgetService { interface WidgetService {
fun getWidgetURLBuilder(): WidgetURLBuilder
fun getWidgetPostAPIMediator(): WidgetPostAPIMediator fun getWidgetPostAPIMediator(): WidgetPostAPIMediator
fun getRoomWidgets( fun getRoomWidgets(

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2020 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 im.vector.matrix.android.api.session.widgets
interface WidgetURLBuilder {
/**
* Takes care of fetching a scalar token if required and build the final url.
*/
suspend fun build(baseUrl: String, params: Map<String, String> = emptyMap(), forceFetchScalarToken: Boolean = false): String
}

View file

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.integrationmanager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LifecycleRegistry
import im.vector.matrix.android.R
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
@ -33,6 +34,7 @@ import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDat
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.StringProvider
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -50,8 +52,10 @@ import javax.inject.Inject
*/ */
@SessionScope @SessionScope
internal class IntegrationManager @Inject constructor(private val taskExecutor: TaskExecutor, internal class IntegrationManager @Inject constructor(private val taskExecutor: TaskExecutor,
private val stringProvider: StringProvider,
private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val accountDataDataSource: AccountDataDataSource) { private val accountDataDataSource: AccountDataDataSource,
private val configExtractor: IntegrationManagerConfigExtractor) {
interface Listener { interface Listener {
fun onIsEnabledChanged(enabled: Boolean) { fun onIsEnabledChanged(enabled: Boolean) {
@ -67,7 +71,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
} }
} }
private var currentConfig: IntegrationManagerConfig? = null private val currentConfigs = ArrayList<IntegrationManagerConfig>()
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry } private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner) private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
@ -75,6 +79,15 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
fun addListener(listener: Listener) = synchronized(listeners) { listeners.add(listener) } fun addListener(listener: Listener) = synchronized(listeners) { listeners.add(listener) }
fun removeListener(listener: Listener) = synchronized(listeners) { listeners.remove(listener) } fun removeListener(listener: Listener) = synchronized(listeners) { listeners.remove(listener) }
init {
val defaultConfig = IntegrationManagerConfig(
uiUrl = stringProvider.getString(R.string.integrations_ui_url),
apiUrl = stringProvider.getString(R.string.integrations_rest_url),
kind = IntegrationManagerConfig.Kind.DEFAULT
)
currentConfigs.add(defaultConfig)
}
fun start() { fun start() {
lifecycleRegistry.currentState = Lifecycle.State.STARTED lifecycleRegistry.currentState = Lifecycle.State.STARTED
accountDataDataSource accountDataDataSource
@ -98,8 +111,11 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
.observeNotNull(lifecycleOwner) { .observeNotNull(lifecycleOwner) {
val integrationManager = it.getOrNull()?.asIntegrationManagerWidgetContent() val integrationManager = it.getOrNull()?.asIntegrationManagerWidgetContent()
val config = integrationManager?.extractIntegrationManagerConfig() val config = integrationManager?.extractIntegrationManagerConfig()
if (config != null && config != currentConfig) { val accountConfig = currentConfigs.firstOrNull { currentConfig ->
currentConfig = config currentConfig.kind == IntegrationManagerConfig.Kind.ACCOUNT
}
if (config != null && accountConfig == null) {
currentConfigs.add(config)
notifyConfigurationChanged(config) notifyConfigurationChanged(config)
} }
} }
@ -109,6 +125,18 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
} }
fun hasConfig() = currentConfigs.isNotEmpty()
fun getOrderedConfigs(): List<IntegrationManagerConfig> {
return currentConfigs.sortedBy {
it.kind
}
}
fun getPreferredConfig(): IntegrationManagerConfig? {
return getOrderedConfigs().firstOrNull()
}
/** /**
* Returns false if the user as disabled integration manager feature * Returns false if the user as disabled integration manager feature
*/ */
@ -250,14 +278,8 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
//nop //nop
} }
} }
*/ */
fun getConfig(): IntegrationManagerConfig? {
val accountWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_WIDGETS) ?: return null
return accountWidgets.asIntegrationManagerWidgetContent()?.extractIntegrationManagerConfig()
}
private fun WidgetContent.extractIntegrationManagerConfig(): IntegrationManagerConfig? { private fun WidgetContent.extractIntegrationManagerConfig(): IntegrationManagerConfig? {
if (url.isNullOrBlank()) { if (url.isNullOrBlank()) {
return null return null
@ -265,7 +287,8 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
val integrationManagerData = data.toModel<IntegrationManagerWidgetData>() val integrationManagerData = data.toModel<IntegrationManagerWidgetData>()
return IntegrationManagerConfig( return IntegrationManagerConfig(
uiUrl = url, uiUrl = url,
apiUrl = integrationManagerData?.apiUrl ?: url apiUrl = integrationManagerData?.apiUrl ?: url,
kind = IntegrationManagerConfig.Kind.ACCOUNT
) )
} }

View file

@ -17,5 +17,14 @@ package im.vector.matrix.android.internal.session.integrationmanager
data class IntegrationManagerConfig( data class IntegrationManagerConfig(
val uiUrl: String, val uiUrl: String,
val apiUrl: String val apiUrl: String,
) val kind: Kind
) {
// Order matters, first is preferred
enum class Kind {
ACCOUNT,
HOMESERVER,
DEFAULT
}
}

View file

@ -17,8 +17,9 @@
package im.vector.matrix.android.internal.session.integrationmanager package im.vector.matrix.android.internal.session.integrationmanager
import im.vector.matrix.android.api.auth.data.WellKnown import im.vector.matrix.android.api.auth.data.WellKnown
import javax.inject.Inject
internal class IntegrationManagerConfigExtractor { internal class IntegrationManagerConfigExtractor @Inject constructor() {
fun extract(wellKnown: WellKnown): List<IntegrationManagerConfig> { fun extract(wellKnown: WellKnown): List<IntegrationManagerConfig> {
val managers = ArrayList<IntegrationManagerConfig>() val managers = ArrayList<IntegrationManagerConfig>()
@ -33,7 +34,8 @@ internal class IntegrationManagerConfigExtractor {
&& uiUrl!!.startsWith("https://")) { && uiUrl!!.startsWith("https://")) {
managers.add(IntegrationManagerConfig( managers.add(IntegrationManagerConfig(
apiUrl = apiUrl, apiUrl = apiUrl,
uiUrl = uiUrl uiUrl = uiUrl,
kind = IntegrationManagerConfig.Kind.HOMESERVER
)) ))
} }
} }

View file

@ -21,13 +21,19 @@ import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.widgets.WidgetPostAPIMediator import im.vector.matrix.android.api.session.widgets.WidgetPostAPIMediator
import im.vector.matrix.android.api.session.widgets.WidgetService import im.vector.matrix.android.api.session.widgets.WidgetService
import im.vector.matrix.android.api.session.widgets.WidgetURLBuilder
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager, internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager,
private val widgetURLBuilder: Provider<WidgetURLBuilder>,
private val widgetPostAPIMediator: Provider<WidgetPostAPIMediator>) : WidgetService { private val widgetPostAPIMediator: Provider<WidgetPostAPIMediator>) : WidgetService {
override fun getWidgetURLBuilder(): WidgetURLBuilder {
return widgetURLBuilder.get()
}
override fun getWidgetPostAPIMediator(): WidgetPostAPIMediator { override fun getWidgetPostAPIMediator(): WidgetPostAPIMediator {
return widgetPostAPIMediator.get() return widgetPostAPIMediator.get()
} }

View file

@ -21,6 +21,7 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import im.vector.matrix.android.api.session.widgets.WidgetPostAPIMediator import im.vector.matrix.android.api.session.widgets.WidgetPostAPIMediator
import im.vector.matrix.android.api.session.widgets.WidgetService import im.vector.matrix.android.api.session.widgets.WidgetService
import im.vector.matrix.android.api.session.widgets.WidgetURLBuilder
import im.vector.matrix.android.internal.session.widgets.token.DefaultGetScalarTokenTask import im.vector.matrix.android.internal.session.widgets.token.DefaultGetScalarTokenTask
import im.vector.matrix.android.internal.session.widgets.token.GetScalarTokenTask import im.vector.matrix.android.internal.session.widgets.token.GetScalarTokenTask
import retrofit2.Retrofit import retrofit2.Retrofit
@ -40,6 +41,9 @@ internal abstract class WidgetModule {
@Binds @Binds
abstract fun bindWidgetService(widgetService: DefaultWidgetService): WidgetService abstract fun bindWidgetService(widgetService: DefaultWidgetService): WidgetService
@Binds
abstract fun bindWidgetURLBuilder(widgetURLBuilder: DefaultWidgetURLBuilder): WidgetURLBuilder
@Binds @Binds
abstract fun bindWidgetPostAPIMediator(widgetPostMessageAPIProvider: DefaultWidgetPostAPIMediator): WidgetPostAPIMediator abstract fun bindWidgetPostAPIMediator(widgetPostMessageAPIProvider: DefaultWidgetPostAPIMediator): WidgetPostAPIMediator

View file

@ -0,0 +1,117 @@
/*
* Copyright (c) 2020 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 im.vector.matrix.android.internal.session.widgets
import im.vector.matrix.android.R
import im.vector.matrix.android.api.session.widgets.WidgetURLBuilder
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManagerConfig
import im.vector.matrix.android.internal.session.widgets.token.GetScalarTokenTask
import im.vector.matrix.android.internal.util.StringProvider
import java.net.URLEncoder
import javax.inject.Inject
@SessionScope
internal class DefaultWidgetURLBuilder @Inject constructor(private val integrationManager: IntegrationManager,
private val getScalarTokenTask: GetScalarTokenTask,
private val stringProvider: StringProvider
) : IntegrationManager.Listener, WidgetURLBuilder {
private var currentConfig: IntegrationManagerConfig? = null
private var whiteListedUrls: List<String> = emptyList()
fun start() {
setupWithConfiguration()
integrationManager.addListener(this)
}
fun stop() {
integrationManager.removeListener(this)
}
override fun onConfigurationChanged(config: IntegrationManagerConfig) {
setupWithConfiguration()
}
private fun setupWithConfiguration() {
val preferredConfig = integrationManager.getPreferredConfig()
if (currentConfig != preferredConfig) {
currentConfig = preferredConfig
val defaultWhiteList = stringProvider.getStringArray(R.array.integrations_widgets_urls).asList()
whiteListedUrls = when (preferredConfig?.kind) {
IntegrationManagerConfig.Kind.DEFAULT -> defaultWhiteList
IntegrationManagerConfig.Kind.ACCOUNT -> defaultWhiteList + preferredConfig.apiUrl
IntegrationManagerConfig.Kind.HOMESERVER -> listOf(preferredConfig.apiUrl)
else -> emptyList()
}
}
}
/**
* Takes care of fetching a scalar token if required and build the final url.
*/
override suspend fun build(baseUrl: String, params: Map<String, String>, forceFetchScalarToken: Boolean): String {
return if (isScalarUrl(baseUrl) || forceFetchScalarToken) {
val taskParams = GetScalarTokenTask.Params(baseUrl)
val scalarToken = getScalarTokenTask.execute(taskParams)
buildString {
append(baseUrl)
append("scalar_token", scalarToken)
appendParamsToUrl(params)
}
} else {
buildString {
append(baseUrl)
appendParamsToUrl(params)
}
}
}
private fun isScalarUrl(url: String): Boolean {
val allowed: List<String> = whiteListedUrls
for (allowedUrl in allowed) {
if (url.startsWith(allowedUrl)) {
return true
}
}
return false
}
private fun StringBuilder.appendParamsToUrl(params: Map<String, String>): StringBuilder {
params.forEach { (param, value) ->
appendParamToUrl(param, value)
}
return this
}
private fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder {
if (contains("?")) {
append("&")
} else {
append("?")
}
append(param)
append("=")
append(URLEncoder.encode(value, "utf-8"))
return this
}
}

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.util package im.vector.matrix.android.internal.util
import android.content.res.Resources import android.content.res.Resources
import androidx.annotation.ArrayRes
import androidx.annotation.NonNull import androidx.annotation.NonNull
import androidx.annotation.StringRes import androidx.annotation.StringRes
import dagger.Reusable import dagger.Reusable
@ -53,4 +54,9 @@ internal class StringProvider @Inject constructor(private val resources: Resourc
fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String { fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String {
return resources.getString(resId, *formatArgs) return resources.getString(resId, *formatArgs)
} }
@Throws(Resources.NotFoundException::class)
fun getStringArray(@ArrayRes id: Int): Array<String> {
return resources.getStringArray(id)
}
} }

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Widget urls -->
<string name="integrations_ui_url" translatable="false">"https://scalar.vector.im/"</string>
<string name="integrations_rest_url" translatable="false">"https://scalar.vector.im/api"</string>
<string name="integrations_jitsi_widget_url" translatable="false">"https://scalar.vector.im/api/widgets/jitsi.html"</string>
<string-array name="integrations_widgets_urls" translatable="false">
<item>https://scalar.vector.im/_matrix/integrations/v1</item>
<item>https://scalar.vector.im/api</item>
<item>https://scalar-staging.vector.im/_matrix/integrations/v1</item>
<item>https://scalar-staging.vector.im/api</item>
<item>https://scalar-staging.riot.im/scalar/api</item>
</string-array>
</resources>