Updated on 2026-08-14
This commit is contained in:
parent
7d9c12f1bc
commit
690318cd8d
10 changed files with 80 additions and 65 deletions
|
|
@ -2,7 +2,8 @@ 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 com.tangem.utils.ProviderSuspend
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
|
|
@ -44,11 +45,13 @@ internal class SwitchEnvironmentInterceptor(
|
|||
.build()
|
||||
}
|
||||
|
||||
private fun Request.Builder.addHeaders(headers: Map<String, Provider<String>>): Request.Builder {
|
||||
headers.forEach { (name, valueProvider) ->
|
||||
val value = valueProvider()
|
||||
private fun Request.Builder.addHeaders(headers: Map<String, ProviderSuspend<String>>): Request.Builder {
|
||||
runBlocking {
|
||||
headers.forEach { (name, valueProvider) ->
|
||||
val value = valueProvider()
|
||||
|
||||
if (value.isNotBlank()) addHeader(name = name, value = value)
|
||||
if (value.isNotBlank()) addHeader(name = name, value = value)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
/**
|
||||
* Api environment config
|
||||
|
|
@ -14,5 +14,5 @@ import com.tangem.utils.Provider
|
|||
data class ApiEnvironmentConfig(
|
||||
val environment: ApiEnvironment,
|
||||
val baseUrl: String,
|
||||
val headers: Map<String, Provider<String>> = emptyMap(),
|
||||
val headers: Map<String, ProviderSuspend<String>> = emptyMap(),
|
||||
)
|
||||
|
|
@ -4,7 +4,7 @@ import com.tangem.datasource.BuildConfig
|
|||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
|
||||
/**
|
||||
|
|
@ -47,11 +47,11 @@ internal class Express(
|
|||
)
|
||||
|
||||
private fun createHeaders(isProd: Boolean) = buildMap {
|
||||
put(key = "api-key", value = Provider { getApiKey(isProd) })
|
||||
put(key = "user-id", value = Provider(expressAuthProvider::getUserId))
|
||||
put(key = "session-id", value = Provider(expressAuthProvider::getSessionId))
|
||||
put(key = "api-key", value = ProviderSuspend { getApiKey(isProd) })
|
||||
put(key = "user-id", value = ProviderSuspend(expressAuthProvider::getUserId))
|
||||
put(key = "session-id", value = ProviderSuspend(expressAuthProvider::getSessionId))
|
||||
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider).values)
|
||||
put(key = "refcode", value = Provider(expressAuthProvider::getRefCode))
|
||||
put(key = "refcode", value = ProviderSuspend(expressAuthProvider::getRefCode))
|
||||
}
|
||||
|
||||
private fun getApiKey(isProd: Boolean): String {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
/**
|
||||
* StakeKit [ApiConfig]
|
||||
|
|
@ -25,8 +25,8 @@ internal class StakeKit(
|
|||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.stakek.it/v1/",
|
||||
headers = mapOf(
|
||||
"X-API-KEY" to Provider(stakeKitAuthProvider::getApiKey),
|
||||
"accept" to Provider { "application/json" },
|
||||
"X-API-KEY" to ProviderSuspend(stakeKitAuthProvider::getApiKey),
|
||||
"accept" to ProviderSuspend { "application/json" },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
internal class TangemVisa(
|
||||
private val authProvider: TangemVisaAuthProvider,
|
||||
|
|
@ -21,10 +20,6 @@ internal class TangemVisa(
|
|||
)
|
||||
|
||||
private fun createHeaders() = mapOf(
|
||||
"Authorization" to Provider {
|
||||
// This is safe because it's used by interceptor which runs on the IO thread
|
||||
// (maybe change to ProviderSuspend implementation)
|
||||
runBlocking { authProvider.getAuthHeader() }
|
||||
},
|
||||
"Authorization" to ProviderSuspend { authProvider.getAuthHeader() },
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
internal class TangemVisaAuth : ApiConfig() {
|
||||
|
||||
|
|
@ -16,5 +16,5 @@ internal class TangemVisaAuth : ApiConfig() {
|
|||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createHeaders() = mapOf<String, Provider<String>>()
|
||||
private fun createHeaders() = mapOf<String, ProviderSuspend<String>>()
|
||||
}
|
||||
|
|
@ -10,7 +10,8 @@ import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
|||
import com.tangem.datasource.api.utils.ConnectTimeout
|
||||
import com.tangem.datasource.api.utils.ReadTimeout
|
||||
import com.tangem.datasource.api.utils.WriteTimeout
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Invocation
|
||||
|
|
@ -22,7 +23,7 @@ internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeade
|
|||
val request = chain.request().newBuilder().apply {
|
||||
requestHeaders
|
||||
.flatMap { it.values.toList() }
|
||||
.forEach { addHeader(it.first, it.second.invoke()) }
|
||||
.forEach { addHeader(it.first, runBlocking { it.second.invoke() }) }
|
||||
}.build()
|
||||
|
||||
chain.proceed(request)
|
||||
|
|
@ -59,14 +60,17 @@ internal fun OkHttpClient.Builder.applyTimeoutAnnotations(): OkHttpClient.Builde
|
|||
}
|
||||
|
||||
/** Extension for adding headers [requestHeaders] to every [OkHttpClient] request */
|
||||
internal fun OkHttpClient.Builder.addHeaders(requestHeaders: Map<String, Provider<String>>): OkHttpClient.Builder {
|
||||
internal fun OkHttpClient.Builder.addHeaders(
|
||||
requestHeaders: Map<String, ProviderSuspend<String>>,
|
||||
): OkHttpClient.Builder {
|
||||
return addInterceptor(
|
||||
Interceptor { chain ->
|
||||
val request = chain.request().newBuilder().apply {
|
||||
requestHeaders.forEach {
|
||||
val value = it.value.invoke()
|
||||
|
||||
if (value.isNotBlank()) addHeader(name = it.key, value = value)
|
||||
runBlocking {
|
||||
requestHeaders.forEach {
|
||||
val value = it.value.invoke()
|
||||
if (value.isNotBlank()) addHeader(name = it.key, value = value)
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.datasource.utils
|
|||
import android.os.Build
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.utils.RequestHeader.CacheControlHeader.checkHeaderValueOrEmpty
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
|
@ -13,26 +13,26 @@ import java.util.TimeZone
|
|||
*
|
||||
* @param pairs header name and header value pairs
|
||||
*/
|
||||
sealed class RequestHeader(vararg pairs: Pair<String, Provider<String>>) {
|
||||
sealed class RequestHeader(vararg pairs: Pair<String, ProviderSuspend<String>>) {
|
||||
|
||||
/** Header list */
|
||||
val values: Map<String, Provider<String>> = pairs.toMap()
|
||||
val values: Map<String, ProviderSuspend<String>> = pairs.toMap()
|
||||
|
||||
data object CacheControlHeader : RequestHeader("Cache-Control" to Provider { "max-age=600" })
|
||||
data object CacheControlHeader : RequestHeader("Cache-Control" to ProviderSuspend { "max-age=600" })
|
||||
|
||||
class AuthenticationHeader(authProvider: AuthProvider) : RequestHeader(
|
||||
"card_id" to Provider(authProvider::getCardId),
|
||||
"card_public_key" to Provider(authProvider::getCardPublicKey),
|
||||
"card_id" to ProviderSuspend(authProvider::getCardId),
|
||||
"card_public_key" to ProviderSuspend(authProvider::getCardPublicKey),
|
||||
)
|
||||
|
||||
class AppVersionPlatformHeaders(appVersionProvider: AppVersionProvider) : RequestHeader(
|
||||
"version" to Provider(appVersionProvider::versionName),
|
||||
"platform" to Provider { "android" },
|
||||
"language" to Provider { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to Provider {
|
||||
"version" to ProviderSuspend { appVersionProvider.versionName },
|
||||
"platform" to ProviderSuspend { "android" },
|
||||
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to ProviderSuspend {
|
||||
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
|
||||
},
|
||||
"device" to Provider { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
"device" to ProviderSuspend { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD
|
|||
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -37,6 +38,8 @@ private val API_CONFIGS = setOf(
|
|||
Express(configManager, expressAuthProvider, appVersionProvider),
|
||||
TangemTech(appVersionProvider, appAuthProvider),
|
||||
StakeKit(stakeKitAuthProvider),
|
||||
TangemVisaAuth(),
|
||||
TangemVisa(visaAuthProvider),
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -56,7 +59,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY
|
||||
every { appAuthProvider.getCardId() } returns APP_CARD_ID
|
||||
every { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY
|
||||
every { runBlocking { visaAuthProvider.getAuthHeader() } } returns VISA_AUTH_HEADER // TODO
|
||||
coEvery { visaAuthProvider.getAuthHeader() } returns VISA_AUTH_HEADER
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -66,8 +69,8 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
Truth.assertThat(actual.environment).isEqualTo(model.expected.environment)
|
||||
Truth.assertThat(actual.baseUrl).isEqualTo(model.expected.baseUrl)
|
||||
|
||||
Truth.assertThat(actual.headers.mapValues { it.value() })
|
||||
.isEqualTo(model.expected.headers.mapValues { it.value() })
|
||||
Truth.assertThat(actual.headers.mapValues { runBlocking { it.value() } })
|
||||
.isEqualTo(model.expected.headers.mapValues { runBlocking { it.value() } })
|
||||
}
|
||||
|
||||
data class Model(val id: ApiConfig.ID, val expected: ApiEnvironmentConfig)
|
||||
|
|
@ -122,23 +125,23 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
},
|
||||
headers = mapOf(
|
||||
"api-key" to Provider {
|
||||
"api-key" to ProviderSuspend {
|
||||
if (environment == ApiEnvironment.PROD) {
|
||||
MockEnvironmentConfigStorage.EXPRESS_API_KEY
|
||||
} else {
|
||||
MockEnvironmentConfigStorage.EXPRESS_DEV_API_KEY
|
||||
}
|
||||
},
|
||||
"user-id" to Provider { EXPRESS_USER_ID },
|
||||
"session-id" to Provider { EXPRESS_SESSION_ID },
|
||||
"refcode" to Provider { EXPRESS_REF_CODE },
|
||||
"version" to Provider { VERSION_NAME },
|
||||
"platform" to Provider { "android" },
|
||||
"language" to Provider { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to Provider {
|
||||
"user-id" to ProviderSuspend { EXPRESS_USER_ID },
|
||||
"session-id" to ProviderSuspend { EXPRESS_SESSION_ID },
|
||||
"refcode" to ProviderSuspend { EXPRESS_REF_CODE },
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "android" },
|
||||
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to ProviderSuspend {
|
||||
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
|
||||
},
|
||||
"device" to Provider { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
"device" to ProviderSuspend { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -151,15 +154,15 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.tangem-tech.com/v1/",
|
||||
headers = mapOf(
|
||||
"card_id" to Provider { APP_CARD_ID },
|
||||
"card_public_key" to Provider { APP_CARD_PUBLIC_KEY },
|
||||
"version" to Provider { VERSION_NAME },
|
||||
"platform" to Provider { "android" },
|
||||
"language" to Provider { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to Provider {
|
||||
"card_id" to ProviderSuspend { APP_CARD_ID },
|
||||
"card_public_key" to ProviderSuspend { APP_CARD_PUBLIC_KEY },
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "android" },
|
||||
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to ProviderSuspend {
|
||||
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
|
||||
},
|
||||
"device" to Provider { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
"device" to ProviderSuspend { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -172,8 +175,8 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.stakek.it/v1/",
|
||||
headers = mapOf(
|
||||
"X-API-KEY" to Provider { STAKE_KIT_API_KEY },
|
||||
"accept" to Provider { "application/json" },
|
||||
"X-API-KEY" to ProviderSuspend { STAKE_KIT_API_KEY },
|
||||
"accept" to ProviderSuspend { "application/json" },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -196,7 +199,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://bff.tangem.com/",
|
||||
headers = mapOf(
|
||||
"Authorization" to Provider { VISA_AUTH_HEADER },
|
||||
"Authorization" to ProviderSuspend { VISA_AUTH_HEADER },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
10
core/utils/src/main/java/com/tangem/utils/ProviderSuspend.kt
Normal file
10
core/utils/src/main/java/com/tangem/utils/ProviderSuspend.kt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.utils
|
||||
|
||||
/**
|
||||
* Provider for suspend lazy initialization
|
||||
*
|
||||
* @param action initialization action
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ProviderSuspend<T>(action: suspend () -> T) : suspend () -> T by action
|
||||
Loading…
Add table
Add a link
Reference in a new issue