Updated on 2026-08-14
This commit is contained in:
parent
38f5c42a94
commit
c6d333dbeb
14 changed files with 149 additions and 59 deletions
|
|
@ -2,21 +2,23 @@ package com.tangem.datasource.api.common
|
|||
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.utils.Provider
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okio.IOException
|
||||
|
||||
/**
|
||||
* Switch base url [Interceptor]
|
||||
* Switch api environment [Interceptor]
|
||||
*
|
||||
* @property id api config id [ApiConfig.ID]
|
||||
* @property apiConfigsManager api configs manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SwitchBaseUrlInterceptor(
|
||||
internal class SwitchEnvironmentInterceptor(
|
||||
private val id: ApiConfig.ID,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
) : Interceptor {
|
||||
|
|
@ -26,18 +28,27 @@ internal class SwitchBaseUrlInterceptor(
|
|||
var request = chain.request()
|
||||
val builder = request.newBuilder()
|
||||
|
||||
val environmentConfig = apiConfigsManager.getEnvironmentConfig(id)
|
||||
|
||||
request = builder
|
||||
.url(url = request.url.adjustBaseUrl())
|
||||
.url(url = request.url.adjustBaseUrl(environmentConfig.baseUrl))
|
||||
.addHeaders(headers = environmentConfig.headers)
|
||||
.build()
|
||||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
private fun HttpUrl.adjustBaseUrl(): HttpUrl {
|
||||
val host = apiConfigsManager.getBaseUrl(id).toHttpUrl().host
|
||||
|
||||
private fun HttpUrl.adjustBaseUrl(url: String): HttpUrl {
|
||||
return this.newBuilder()
|
||||
.host(host)
|
||||
.host(host = url.toHttpUrl().host)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun Request.Builder.addHeaders(headers: Map<String, Provider<String>>): Request.Builder {
|
||||
headers.forEach { (name, valueProvider) ->
|
||||
addHeader(name = name, value = valueProvider())
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
@ -14,8 +14,8 @@ sealed class ApiConfig {
|
|||
/** Default environment */
|
||||
abstract val defaultEnvironment: ApiEnvironment
|
||||
|
||||
/** Available environments with base url */
|
||||
abstract val environments: Map<ApiEnvironment, String>
|
||||
/** Available environments */
|
||||
abstract val environmentConfigs: List<ApiEnvironmentConfig>
|
||||
|
||||
/** Unique id */
|
||||
val id: ID = initializeId()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.utils.Provider
|
||||
|
||||
/**
|
||||
* Api environment config
|
||||
*
|
||||
* @property environment environment
|
||||
* @property baseUrl base url
|
||||
* @property headers headers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class ApiEnvironmentConfig(
|
||||
val environment: ApiEnvironment,
|
||||
val baseUrl: String,
|
||||
val headers: Map<String, Provider<String>> = emptyMap(),
|
||||
)
|
||||
|
|
@ -1,18 +1,50 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
|
||||
/** Express [ApiConfig] */
|
||||
internal class Express : ApiConfig() {
|
||||
internal class Express(
|
||||
private val expressAuthProvider: ExpressAuthProvider,
|
||||
private val appVersionProvider: AppVersionProvider,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
|
||||
|
||||
override val environments: Map<ApiEnvironment, String> = mapOf(
|
||||
ApiEnvironment.DEV to "[REDACTED_ENV_URL]",
|
||||
ApiEnvironment.STAGE to "[REDACTED_ENV_URL]",
|
||||
ApiEnvironment.PROD to "https://express.tangem.com/v1/",
|
||||
override val environmentConfigs: List<ApiEnvironmentConfig> = listOf(
|
||||
createDevEnvironment(),
|
||||
createStageEnvironment(),
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.STAGE,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://express.tangem.com/v1/",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createHeaders() = buildMap {
|
||||
put(key = "api-key", value = Provider(expressAuthProvider::getApiKey))
|
||||
put(key = "user-id", value = Provider(expressAuthProvider::getUserId))
|
||||
put(key = "session-id", value = Provider(expressAuthProvider::getSessionId))
|
||||
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider).values)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
fun getInitialEnvironment(): ApiEnvironment {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,20 @@ internal class TangemTech : ApiConfig() {
|
|||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||
|
||||
override val environments: Map<ApiEnvironment, String> = mapOf(
|
||||
ApiEnvironment.DEV to "https://devapi.tangem-tech.com/v1/",
|
||||
ApiEnvironment.PROD to "https://api.tangem-tech.com/v1/",
|
||||
override val environmentConfigs = listOf(
|
||||
createDevEnvironment(),
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.tangem-tech.com/v1/",
|
||||
headers = emptyMap(), // TODO: [REDACTED_JIRA]
|
||||
)
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "https://devapi.tangem-tech.com/v1/",
|
||||
headers = emptyMap(), // TODO: [REDACTED_JIRA]
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.api.common.config.managers
|
||||
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||
|
||||
/**
|
||||
* Api configs manager
|
||||
|
|
@ -12,6 +13,6 @@ interface ApiConfigsManager {
|
|||
/** Initialize resources */
|
||||
suspend fun initialize() {}
|
||||
|
||||
/** Get base url of api by [id] */
|
||||
fun getBaseUrl(id: ApiConfig.ID): String
|
||||
/** Get environment config by [id] */
|
||||
fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.datasource.api.common.config.managers
|
|||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectMap
|
||||
|
|
@ -44,7 +45,7 @@ internal class DevApiConfigsManager(
|
|||
.launchIn(CoroutineScope(dispatchers.main))
|
||||
}
|
||||
|
||||
override fun getBaseUrl(id: ApiConfig.ID): String {
|
||||
override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig {
|
||||
val apiConfigs = _apiConfigs.value
|
||||
|
||||
val config = apiConfigs.map { it }.firstOrNull { it.key.id == id }?.key
|
||||
|
|
@ -53,7 +54,7 @@ internal class DevApiConfigsManager(
|
|||
val currentEnvironment = apiConfigs[config]
|
||||
?: error("Current environment of api config with id [$id] not found")
|
||||
|
||||
return config.environments[currentEnvironment]
|
||||
return config.environmentConfigs.firstOrNull { it.environment == currentEnvironment }
|
||||
?: error("Api config with id [$id] doesn't contain environment [$currentEnvironment]")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.datasource.api.common.config.managers
|
|||
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||
|
||||
/**
|
||||
* Implementation of [ApiConfigsManager] in PROD environment
|
||||
|
|
@ -12,14 +13,14 @@ internal class ProdApiConfigsManager(
|
|||
private val apiConfigs: ApiConfigs,
|
||||
) : ApiConfigsManager {
|
||||
|
||||
override fun getBaseUrl(id: ApiConfig.ID): String {
|
||||
override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig {
|
||||
val config = apiConfigs.firstOrNull { it.id == id }
|
||||
?: error("Api config with id [$id] not found. Check ApiConfig implementations")
|
||||
?: error("Api config with id [$id] not found. Check that ApiConfig with id [$id] was provided into DI")
|
||||
|
||||
return config.environments[config.defaultEnvironment]
|
||||
return config.environmentConfigs.firstOrNull { it.environment == config.defaultEnvironment }
|
||||
?: error(
|
||||
"Api config with id [$id] doesn't contain " +
|
||||
"environment [${config.defaultEnvironment}]. Check ApiConfig implementations",
|
||||
"Api config with id [$id] doesn't contain environment [${config.defaultEnvironment}]. " +
|
||||
"Check ApiConfig's environments is included default environment",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ package com.tangem.datasource.di
|
|||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.Express
|
||||
import com.tangem.datasource.api.common.config.TangemTech
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -15,7 +17,12 @@ internal object ApiConfigsModule {
|
|||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideExpressConfig(): ApiConfig = Express()
|
||||
fun provideExpressConfig(
|
||||
expressAuthProvider: ExpressAuthProvider,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
): ApiConfig {
|
||||
return Express(expressAuthProvider, appVersionProvider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.datasource.utils.RequestHeader.*
|
|||
import com.tangem.datasource.utils.addEnvironmentSwitcher
|
||||
import com.tangem.datasource.utils.addHeaders
|
||||
import com.tangem.datasource.utils.addLoggers
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
|
|
@ -59,19 +58,18 @@ class NetworkModule {
|
|||
fun provideExpressApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
expressAuthProvider: ExpressAuthProvider,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
): TangemExpressApi {
|
||||
val environmentConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.Express)
|
||||
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
|
||||
.baseUrl(apiConfigsManager.getBaseUrl(id = ApiConfig.ID.Express))
|
||||
.baseUrl(environmentConfig.baseUrl)
|
||||
.client(
|
||||
OkHttpClient.Builder()
|
||||
.addEnvironmentSwitcher(ApiConfig.ID.Express, apiConfigsManager)
|
||||
.addHeaders(Express(expressAuthProvider))
|
||||
.addHeaders(AppVersionPlatformHeaders(appVersionProvider))
|
||||
.addHeaders(environmentConfig.headers)
|
||||
.addLoggers(context)
|
||||
.build(),
|
||||
)
|
||||
|
|
@ -113,7 +111,7 @@ class NetworkModule {
|
|||
context = context,
|
||||
appVersionProvider = appVersionProvider,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
baseUrl = apiConfigsManager.getBaseUrl(id = ApiConfig.ID.TangemTech),
|
||||
baseUrl = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.TangemTech).baseUrl,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +163,7 @@ class NetworkModule {
|
|||
context = context,
|
||||
appVersionProvider = appVersionProvider,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
baseUrl = apiConfigsManager.getBaseUrl(id = ApiConfig.ID.TangemTech),
|
||||
baseUrl = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.TangemTech).baseUrl,
|
||||
timeouts = Timeouts(
|
||||
callTimeoutSeconds = TANGEM_TECH_SERVICE_TIMEOUT_SECONDS,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ package com.tangem.datasource.utils
|
|||
import android.content.Context
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.SwitchBaseUrlInterceptor
|
||||
import com.tangem.datasource.api.common.SwitchEnvironmentInterceptor
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||
import com.tangem.utils.Provider
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeade
|
|||
Interceptor { chain ->
|
||||
val request = chain.request().newBuilder().apply {
|
||||
requestHeaders
|
||||
.flatMap(RequestHeader::values)
|
||||
.flatMap { it.values.toList() }
|
||||
.forEach { addHeader(it.first, it.second.invoke()) }
|
||||
}.build()
|
||||
|
||||
|
|
@ -25,6 +26,19 @@ internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeade
|
|||
)
|
||||
}
|
||||
|
||||
/** Extension for adding headers [requestHeaders] to every [OkHttpClient] request */
|
||||
internal fun OkHttpClient.Builder.addHeaders(requestHeaders: Map<String, Provider<String>>): OkHttpClient.Builder {
|
||||
return addInterceptor(
|
||||
Interceptor { chain ->
|
||||
val request = chain.request().newBuilder().apply {
|
||||
requestHeaders.forEach { addHeader(it.key, it.value.invoke()) }
|
||||
}.build()
|
||||
|
||||
chain.proceed(request)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension for logging each [OkHttpClient] request
|
||||
*
|
||||
|
|
@ -53,7 +67,7 @@ internal fun OkHttpClient.Builder.addEnvironmentSwitcher(
|
|||
): OkHttpClient.Builder {
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
addInterceptor(
|
||||
interceptor = SwitchBaseUrlInterceptor(
|
||||
interceptor = SwitchEnvironmentInterceptor(
|
||||
id = id,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.datasource.utils
|
||||
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
|
||||
/**
|
||||
|
|
@ -10,31 +10,25 @@ import com.tangem.utils.version.AppVersionProvider
|
|||
*
|
||||
* @param pairs header name and header value pairs
|
||||
*/
|
||||
sealed class RequestHeader(vararg pairs: Pair<String, () -> String>) {
|
||||
sealed class RequestHeader(vararg pairs: Pair<String, Provider<String>>) {
|
||||
|
||||
/** Header list */
|
||||
val values: List<Pair<String, () -> String>> = pairs.toList()
|
||||
val values: Map<String, Provider<String>> = pairs.toMap()
|
||||
|
||||
data object CacheControlHeader : RequestHeader("Cache-Control" to { "max-age=600" })
|
||||
data object CacheControlHeader : RequestHeader("Cache-Control" to Provider { "max-age=600" })
|
||||
|
||||
class AuthenticationHeader(authProvider: AuthProvider) : RequestHeader(
|
||||
"card_id" to { authProvider.getCardId() },
|
||||
"card_public_key" to { authProvider.getCardPublicKey() },
|
||||
)
|
||||
|
||||
class Express(expressAuthProvider: ExpressAuthProvider) : RequestHeader(
|
||||
"api-key" to { expressAuthProvider.getApiKey() },
|
||||
"user-id" to { expressAuthProvider.getUserId() },
|
||||
"session-id" to { expressAuthProvider.getSessionId() },
|
||||
"card_id" to Provider(authProvider::getCardId),
|
||||
"card_public_key" to Provider(authProvider::getCardPublicKey),
|
||||
)
|
||||
|
||||
class AppVersionPlatformHeaders(appVersionProvider: AppVersionProvider) : RequestHeader(
|
||||
"version" to { appVersionProvider.versionName },
|
||||
"platform" to { "android" },
|
||||
"version" to Provider(appVersionProvider::versionName),
|
||||
"platform" to Provider { "android" },
|
||||
)
|
||||
|
||||
class StakeKit(stakeKitAuthProvider: StakeKitAuthProvider) : RequestHeader(
|
||||
"X-API-KEY" to { stakeKitAuthProvider.getApiKey() },
|
||||
"accept" to { "application/json" },
|
||||
"X-API-KEY" to Provider(stakeKitAuthProvider::getApiKey),
|
||||
"accept" to Provider { "application/json" },
|
||||
)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_
|
|||
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 io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
|
@ -24,7 +25,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
|
||||
@Test
|
||||
fun test_getBaseUrl() {
|
||||
val actual = manager.getBaseUrl(id = model.id)
|
||||
val actual = manager.getEnvironmentConfig(id = model.id).baseUrl
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
|
@ -34,7 +35,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
private companion object {
|
||||
|
||||
val API_CONFIGS = setOf(
|
||||
Express(),
|
||||
Express(mockk(), mockk()),
|
||||
TangemTech(),
|
||||
|
||||
// Don't forget to add new config
|
||||
|
|
|
|||
|
|
@ -69,10 +69,10 @@ internal class EnvironmentsTogglesViewModel @Inject constructor(
|
|||
EnvironmentTogglesScreenUM.ApiInfoUM(
|
||||
name = config.id.name,
|
||||
select = currentEnvironment.name,
|
||||
url = config.environments[currentEnvironment]
|
||||
url = config.environmentConfigs.firstOrNull { it.environment == currentEnvironment }?.baseUrl
|
||||
?: error("Current environment's url isn't found"),
|
||||
environments = config.environments
|
||||
.map { environment -> environment.key.name }
|
||||
environments = config.environmentConfigs
|
||||
.map { environmentConfig -> environmentConfig.environment.name }
|
||||
.toImmutableSet(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue