Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-03 14:59:47 +03:00
parent c9fca04205
commit 80ca0095f1
11 changed files with 83 additions and 46 deletions

View file

@ -15,6 +15,7 @@ import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.crypto.bip39.Wordlist
import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
@ -28,6 +29,7 @@ import com.tangem.sdk.nfc.AndroidNfcAvailabilityProvider
import com.tangem.sdk.nfc.NfcManager
import com.tangem.sdk.storage.create
import com.tangem.tap.foregroundActivityObserver
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
@ -48,6 +50,7 @@ internal class DefaultCardSdkProvider @Inject constructor(
private val apiConfigsManager: ApiConfigsManager,
appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
authProvider: AuthProvider,
) : CardSdkProvider, CardSdkOwner {
private val observer = Observer()
@ -68,13 +71,16 @@ internal class DefaultCardSdkProvider @Inject constructor(
},
)
val apiEnvironment = Provider {
apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.TangemTech).environment
}
val platformHeaders = RequestHeader.AppVersionPlatformHeaders(
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
)
val apiKeyHeader = RequestHeader.TangemApiKeyHeader(authProvider, apiEnvironment)
TangemApiServiceSettings.addInterceptors(
AddHeadersInterceptor(
requestHeader = RequestHeader.AppVersionPlatformHeaders(
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
),
),
AddHeadersInterceptor(platformHeaders.values + apiKeyHeader.values),
)
}

View file

@ -2,14 +2,19 @@ package com.tangem.tap.network.auth
import com.tangem.common.extensions.toHexString
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.utils.Provider
import com.tangem.utils.ProviderSuspend
internal class DefaultAuthProvider(
private val userWalletsListManager: UserWalletsListManager,
private val userWalletsListRepository: UserWalletsListRepository,
private val shouldUseNewListRepository: Boolean = false,
private val environmentConfigStorage: EnvironmentConfigStorage,
) : AuthProvider {
override suspend fun getCardPublicKey(): String {
@ -38,6 +43,20 @@ internal class DefaultAuthProvider(
}
}
override fun getApiKey(apiEnvironment: Provider<ApiEnvironment>): ProviderSuspend<String> {
return ProviderSuspend {
when (apiEnvironment.invoke()) {
ApiEnvironment.MOCK,
ApiEnvironment.DEV,
ApiEnvironment.DEV_2,
ApiEnvironment.DEV_3,
-> environmentConfigStorage.getConfigSync().tangemApiKeyDev
ApiEnvironment.STAGE -> environmentConfigStorage.getConfigSync().tangemApiKeyStage
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().tangemApiKey
} ?: error("No tangem tech api config provided")
}
}
private suspend fun getWallets(): List<UserWallet> {
return if (shouldUseNewListRepository) {
userWalletsListRepository.userWalletsSync()

View file

@ -28,11 +28,13 @@ internal class AuthModule {
userWalletsListManager: UserWalletsListManager,
userWalletsListRepository: UserWalletsListRepository,
hotWalletFeatureToggles: HotWalletFeatureToggles,
environmentConfigStorage: EnvironmentConfigStorage,
): AuthProvider {
return DefaultAuthProvider(
userWalletsListManager = userWalletsListManager,
userWalletsListRepository = userWalletsListRepository,
shouldUseNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled,
environmentConfigStorage = environmentConfigStorage,
)
}

View file

@ -1,5 +1,9 @@
package com.tangem.datasource.api.common
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.utils.Provider
import com.tangem.utils.ProviderSuspend
/**
* Provides auth for tangemTech API
*/
@ -12,6 +16,8 @@ interface AuthProvider {
suspend fun getCardId(): String
fun getApiKey(apiEnvironment: Provider<ApiEnvironment>): ProviderSuspend<String>
/**
* Returns map where keys(cardId) associated with cardPublicKey
*/

View file

@ -2,15 +2,13 @@ package com.tangem.datasource.api.common.config
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.utils.RequestHeader
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.Provider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
/** TangemTech [ApiConfig] */
internal class TangemTech(
private val environmentConfigStorage: EnvironmentConfigStorage,
private val appVersionProvider: AppVersionProvider,
private val authProvider: AuthProvider,
private val appInfoProvider: AppInfoProvider,
@ -63,20 +61,8 @@ internal class TangemTech(
)
private fun createHeaders(apiEnvironment: ApiEnvironment) = buildMap {
put(key = "api-key", value = ProviderSuspend { getApiKey(apiEnvironment) })
putAll(from = RequestHeader.TangemApiKeyHeader(authProvider, Provider { apiEnvironment }).values)
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
putAll(from = RequestHeader.AuthenticationHeader(authProvider).values)
}
private fun getApiKey(apiEnvironment: ApiEnvironment): String {
return when (apiEnvironment) {
ApiEnvironment.MOCK,
ApiEnvironment.DEV,
ApiEnvironment.DEV_2,
ApiEnvironment.DEV_3,
-> environmentConfigStorage.getConfigSync().tangemApiKeyDev
ApiEnvironment.STAGE -> environmentConfigStorage.getConfigSync().tangemApiKeyStage
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().tangemApiKey
} ?: error("No tangem tech api config provided")
}
}

View file

@ -42,12 +42,10 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
fun provideTangemTechConfig(
environmentConfigStorage: EnvironmentConfigStorage,
appVersionProvider: AppVersionProvider,
authProvider: AuthProvider,
appInfoProvider: AppInfoProvider,
): ApiConfig = TangemTech(
environmentConfigStorage = environmentConfigStorage,
appVersionProvider = appVersionProvider,
authProvider = authProvider,
appInfoProvider = appInfoProvider,

View file

@ -1,5 +1,6 @@
package com.tangem.datasource.utils
import com.tangem.utils.ProviderSuspend
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Response
@ -12,21 +13,17 @@ import okhttp3.Response
[REDACTED_AUTHOR]
*/
class AddHeadersInterceptor(
private val requestHeaders: Set<RequestHeader>,
private val requestHeaders: Map<String, ProviderSuspend<String>>,
) : Interceptor {
constructor(requestHeader: RequestHeader) : this(requestHeaders = setOf(requestHeader))
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
requestHeaders
.flatMap { it.values.toList() }
.forEach { (name, valueProvider) ->
val value = runBlocking { valueProvider() }
requestHeaders.forEach { (name, valueProvider) ->
val value = runBlocking { valueProvider() }
requestBuilder.addHeader(name = name, value = value)
}
requestBuilder.addHeader(name = name, value = value)
}
val request = requestBuilder.build()

View file

@ -5,13 +5,6 @@ import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.OkHttpClient
/** Extension for adding headers [requestHeaders] to every [OkHttpClient] request */
internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeader): OkHttpClient.Builder {
return addInterceptor(
interceptor = AddHeadersInterceptor(requestHeaders = requestHeaders.toSet()),
)
}
/** Extension for adding headers [requestHeaders] to every [OkHttpClient] request */
internal fun OkHttpClient.Builder.addHeaders(
requestHeaders: Map<String, ProviderSuspend<String>>,

View file

@ -2,7 +2,9 @@ package com.tangem.datasource.utils
import android.os.Build
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.utils.RequestHeader.CacheControlHeader.checkHeaderValueOrEmpty
import com.tangem.utils.Provider
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
@ -40,6 +42,13 @@ sealed class RequestHeader(vararg pairs: Pair<String, ProviderSuspend<String>>)
"device" to ProviderSuspend { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
)
/**
* Use ONLY for tangemApi (not express or yields)
*/
class TangemApiKeyHeader(authProvider: AuthProvider, apiEnvironment: Provider<ApiEnvironment>) : RequestHeader(
"api-key" to authProvider.getApiKey(apiEnvironment),
)
/**
* Use it to avoid crash in okhttp headers
*/

View file

@ -1,7 +1,12 @@
package com.tangem.datasource.api.common.config
import com.google.common.truth.Truth
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.utils.ProviderSuspend
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import timber.log.Timber
@ -12,6 +17,17 @@ import timber.log.Timber
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ApiConfigTest {
private val appAuthProvider = mockk<AuthProvider>()
private val apiKeyProvider = mockk<ProviderSuspend<String>>()
@BeforeEach
fun setup() {
clearMocks(
appAuthProvider,
)
every { appAuthProvider.getApiKey(any()) } returns apiKeyProvider
}
@Test
fun `all baseUrls ends with slash`() {
// Arrange
@ -41,15 +57,14 @@ class ApiConfigTest {
YieldSupply(
environmentConfigStorage = mockk(),
appVersionProvider = mockk(),
authProvider = mockk(),
authProvider = appAuthProvider,
appInfoProvider = mockk(),
)
}
ApiConfig.ID.TangemTech -> {
TangemTech(
environmentConfigStorage = mockk(),
appVersionProvider = mockk(),
authProvider = mockk(),
authProvider = appAuthProvider,
appInfoProvider = mockk(),
)
}

View file

@ -12,6 +12,7 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.INTERNAL_BUIL
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.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_API_KEY
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_API_KEY
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.utils.ProviderSuspend
@ -40,8 +41,9 @@ internal class ProdApiConfigsManagerTest {
private val stakeKitAuthProvider = mockk<StakeKitAuthProvider>()
private val appAuthProvider = mockk<AuthProvider>()
private val appInfoProvider = mockk<AppInfoProvider>()
private val tangemApiKeyProvider = mockk<ProviderSuspend<String>>()
private val manager = ProdApiConfigsManager(apiConfigs = createApiConfigs())
private lateinit var manager: ProdApiConfigsManager
@BeforeEach
fun setup() {
@ -56,9 +58,14 @@ internal class ProdApiConfigsManagerTest {
every { appVersionProvider.versionName } returns VERSION_NAME
every { expressAuthProvider.getSessionId() } returns EXPRESS_SESSION_ID
every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY
every { appAuthProvider.getApiKey(any()) } returns tangemApiKeyProvider
coEvery { tangemApiKeyProvider.invoke() } returns TANGEM_API_KEY
coEvery { appAuthProvider.getCardId() } returns APP_CARD_ID
coEvery { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY
every { appInfoProvider.osVersion } returns "Android 16"
manager = ProdApiConfigsManager(apiConfigs = createApiConfigs())
}
@ParameterizedTest
@ -94,7 +101,6 @@ internal class ProdApiConfigsManagerTest {
}
ApiConfig.ID.TangemTech -> {
TangemTech(
environmentConfigStorage = environmentConfigStorage,
appVersionProvider = appVersionProvider,
authProvider = appAuthProvider,
appInfoProvider = appInfoProvider,
@ -175,7 +181,7 @@ internal class ProdApiConfigsManagerTest {
environment = ApiEnvironment.PROD,
baseUrl = "https://api.tangem.org/",
headers = mapOf(
"api-key" to ProviderSuspend { MockEnvironmentConfigStorage.TANGEM_API_KEY },
"api-key" to ProviderSuspend { TANGEM_API_KEY },
"card_id" to ProviderSuspend { APP_CARD_ID },
"card_public_key" to ProviderSuspend { APP_CARD_PUBLIC_KEY },
"version" to ProviderSuspend { VERSION_NAME },