Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-08 18:29:44 +03:00
parent c500f45307
commit a25cbc8d24
5 changed files with 209 additions and 0 deletions

View file

@ -0,0 +1,43 @@
package com.tangem.datasource.api.common
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Response
import okio.IOException
/**
* Switch base url [Interceptor]
*
* @property id api config id [ApiConfig.ID]
* @property apiConfigsManager api configs manager
*
[REDACTED_AUTHOR]
*/
internal class SwitchBaseUrlInterceptor(
private val id: ApiConfig.ID,
private val apiConfigsManager: ApiConfigsManager,
) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val builder = request.newBuilder()
request = builder
.url(url = request.url.adjustBaseUrl())
.build()
return chain.proceed(request)
}
private fun HttpUrl.adjustBaseUrl(): HttpUrl {
val host = apiConfigsManager.getBaseUrl(id).toHttpUrl().host
return this.newBuilder()
.host(host)
.build()
}
}

View file

@ -0,0 +1,69 @@
package com.tangem.datasource.api.common.config.managers
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
/**
* Implementation of [ApiConfigsManager] in DEV environment
*
* @property appPreferencesStore app preferences store
* @property dispatchers coroutine dispatcher provider
*/
internal class DevApiConfigsManager(
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : MutableApiConfigsManager {
override val configs: Flow<List<ApiConfig>> get() = _apiConfigs
private val _apiConfigs = MutableStateFlow(value = ApiConfig.values())
override suspend fun initialize() {
// We can't use appPreferencesStore.getObjectMap as base flow,
// because we should keep possibility to work with configs synchronous.
// See [getBaseUrl]
appPreferencesStore.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
.onEach { savedEnvironments ->
_apiConfigs.value = _apiConfigs.value.map { config ->
val savedEnvironment = savedEnvironments[config.id.name]
if (savedEnvironment != null) {
config.copySealed(currentEnvironment = savedEnvironment)
} else {
config
}
}
}
.launchIn(CoroutineScope(dispatchers.main))
}
override fun getBaseUrl(id: ApiConfig.ID): String {
val config = _apiConfigs.value.firstOrNull { it.id == id }
?: error("Api config with id [$id] not found. Check ApiConfig implementations")
return config.environments[config.currentEnvironment]
?: error(
"Api config with id [$id] doesn't contain environment [${config.currentEnvironment}]. " +
"Check ApiConfig implementations",
)
}
override suspend fun changeEnvironment(id: String, environment: ApiEnvironment) {
appPreferencesStore.editData { mutablePreferences ->
val updatedMap = mutablePreferences.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
.toMutableMap()
.apply { put(id, environment) }
mutablePreferences.setObjectMap(PreferencesKeys.apiConfigsEnvironmentKey, updatedMap)
}
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.datasource.api.common.config.managers
import com.tangem.datasource.api.common.config.ApiConfig
/** Implementation of [ApiConfigsManager] in PROD environment */
internal class ProdApiConfigsManager : ApiConfigsManager {
override fun getBaseUrl(id: ApiConfig.ID): String {
val config = ApiConfig.values().firstOrNull { it.id == id }
?: error("Api config with id [$id] not found. Check ApiConfig implementations")
return config.environments[config.currentEnvironment]
?: error(
"Api config with id [$id] doesn't contain " +
"environment [${config.currentEnvironment}]. Check ApiConfig implementations",
)
}
}

View file

@ -95,6 +95,8 @@ object PreferencesKeys {
booleanPreferencesKey(name = "isTokenSwapPromoOkxShown")
}
val apiConfigsEnvironmentKey by lazy { stringPreferencesKey(name = "apiConfigsEnvironment") }
// region Permission
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")

View file

@ -0,0 +1,77 @@
package com.tangem.datasource.api.common.config.managers
import com.google.common.truth.Truth
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiConfig.Companion.DEBUG_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfig.Companion.EXTERNAL_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfig.Companion.INTERNAL_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE
import com.tangem.datasource.api.common.config.Express
import com.tangem.datasource.api.common.config.TangemTech
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
/**
[REDACTED_AUTHOR]
*/
@RunWith(Parameterized::class)
internal class ProdApiConfigsManagerTest(private val model: Model) {
private val manager = ProdApiConfigsManager()
@Test
fun test_getBaseUrl() {
val actual = manager.getBaseUrl(id = model.id)
Truth.assertThat(actual).isEqualTo(model.expected)
}
data class Model(val id: ApiConfig.ID, val expected: String)
private companion object {
@JvmStatic
@Parameterized.Parameters
fun data(): Collection<Model> = ApiConfig.values().map {
when (it) {
is Express -> createExpressModel()
is TangemTech -> createTangemTechModel()
}
}
private fun createExpressModel(): Model {
return Model(
id = ApiConfig.ID.Express,
expected = when (BuildConfig.BUILD_TYPE) {
DEBUG_BUILD_TYPE -> "[REDACTED_ENV_URL]"
INTERNAL_BUILD_TYPE,
MOCKED_BUILD_TYPE,
-> "[REDACTED_ENV_URL]"
EXTERNAL_BUILD_TYPE,
RELEASE_BUILD_TYPE,
-> "https://express.tangem.com/v1/"
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
},
)
}
private fun createTangemTechModel(): Model {
return Model(
id = ApiConfig.ID.TangemTech,
expected = when (BuildConfig.BUILD_TYPE) {
DEBUG_BUILD_TYPE,
INTERNAL_BUILD_TYPE,
-> "https://devapi.tangem-tech.com/v1/"
MOCKED_BUILD_TYPE,
EXTERNAL_BUILD_TYPE,
RELEASE_BUILD_TYPE,
-> "https://api.tangem-tech.com/v1/"
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
},
)
}
}
}