From 690318cd8d3493ca96eb3e85f78288bae815325b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Jan 2025 10:33:05 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../common/SwitchEnvironmentInterceptor.kt | 13 +++-- .../api/common/config/ApiEnvironmentConfig.kt | 4 +- .../datasource/api/common/config/Express.kt | 10 ++-- .../datasource/api/common/config/StakeKit.kt | 6 +-- .../api/common/config/TangemVisa.kt | 9 +--- .../api/common/config/TangemVisaAuth.kt | 4 +- .../tangem/datasource/utils/HttpClientExt.kt | 18 ++++--- .../tangem/datasource/utils/RequestHeader.kt | 22 ++++----- .../managers/ProdApiConfigsManagerTest.kt | 49 ++++++++++--------- .../java/com/tangem/utils/ProviderSuspend.kt | 10 ++++ 10 files changed, 80 insertions(+), 65 deletions(-) create mode 100644 core/utils/src/main/java/com/tangem/utils/ProviderSuspend.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/SwitchEnvironmentInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/SwitchEnvironmentInterceptor.kt index e47f1f8494..1b2a53ec3c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/SwitchEnvironmentInterceptor.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/SwitchEnvironmentInterceptor.kt @@ -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>): Request.Builder { - headers.forEach { (name, valueProvider) -> - val value = valueProvider() + private fun Request.Builder.addHeaders(headers: Map>): 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 diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiEnvironmentConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiEnvironmentConfig.kt index 4ab1342caf..5c6bc0fc7e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiEnvironmentConfig.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiEnvironmentConfig.kt @@ -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> = emptyMap(), + val headers: Map> = emptyMap(), ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/Express.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/Express.kt index 2be7c447f4..a0c28a6880 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/Express.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/Express.kt @@ -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 { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/StakeKit.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/StakeKit.kt index 4370c06c92..0efb5068d1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/StakeKit.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/StakeKit.kt @@ -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" }, ), ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisa.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisa.kt index 4497034a6e..2f7ea8a41f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisa.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisa.kt @@ -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() }, ) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt index 102bf875d4..5a26524206 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt @@ -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>() + private fun createHeaders() = mapOf>() } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt index 250bbcbef2..a4dd795246 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt @@ -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>): OkHttpClient.Builder { +internal fun OkHttpClient.Builder.addHeaders( + requestHeaders: Map>, +): 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() diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt index 5b9de76ce7..a3ae68e61b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt @@ -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>) { +sealed class RequestHeader(vararg pairs: Pair>) { /** Header list */ - val values: Map> = pairs.toMap() + val values: Map> = 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() }, ) /** diff --git a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt index fc392118f3..5d92d7abd8 100644 --- a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt +++ b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt @@ -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 }, ), ), ) diff --git a/core/utils/src/main/java/com/tangem/utils/ProviderSuspend.kt b/core/utils/src/main/java/com/tangem/utils/ProviderSuspend.kt new file mode 100644 index 0000000000..47a25d101e --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/ProviderSuspend.kt @@ -0,0 +1,10 @@ +package com.tangem.utils + +/** + * Provider for suspend lazy initialization + * + * @param action initialization action + * +[REDACTED_AUTHOR] + */ +class ProviderSuspend(action: suspend () -> T) : suspend () -> T by action \ No newline at end of file