Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-14 13:37:27 +03:00
parent 62d0a771d9
commit 1d7416c58f
11 changed files with 126 additions and 81 deletions

View file

@ -1,7 +1,12 @@
package com.tangem.datasource.api.common.config
import com.tangem.datasource.utils.RequestHeader
import com.tangem.utils.version.AppVersionProvider
/** TangemTech [ApiConfig] */
internal class TangemTech : ApiConfig() {
internal class TangemTech(
private val appVersionProvider: AppVersionProvider,
) : ApiConfig() {
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
@ -13,12 +18,16 @@ internal class TangemTech : ApiConfig() {
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD,
baseUrl = "https://api.tangem-tech.com/v1/",
headers = emptyMap(), // TODO: [REDACTED_JIRA]
headers = createHeaders(),
)
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.DEV,
baseUrl = "https://devapi.tangem-tech.com/v1/",
headers = emptyMap(), // TODO: [REDACTED_JIRA]
headers = createHeaders(),
)
private fun createHeaders() = buildMap {
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider).values)
}
}

View file

@ -3,7 +3,10 @@ package com.tangem.datasource.api.tangemTech
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
import com.tangem.datasource.api.tangemTech.models.*
import com.tangem.datasource.api.utils.ReadTimeout
import com.tangem.datasource.config.models.ProviderModel
import retrofit2.http.*
import java.util.concurrent.TimeUnit
/**
* Interface of Tangem Tech API
@ -15,6 +18,7 @@ interface TangemTechApi {
@GET("coins")
suspend fun getCoins(
@Header("Cache-Control") cacheControl: String = "max-age=600",
@Query("contractAddress") contractAddress: String? = null,
@Query("exchangeable") exchangeable: Boolean? = null,
@Query("networkIds") networkIds: String? = null,
@ -29,7 +33,9 @@ interface TangemTechApi {
suspend fun getRates(@Query("currencyId") currencyId: String, @Query("coinIds") coinIds: String): RatesResponse
@GET("currencies")
suspend fun getCurrencyList(): ApiResponse<CurrenciesResponse>
suspend fun getCurrencyList(
@Header("Cache-Control") cacheControl: String = "max-age=600",
): ApiResponse<CurrenciesResponse>
@GET("geo")
suspend fun getUserCountryCode(): GeoResponse
@ -59,15 +65,6 @@ interface TangemTechApi {
@Body startReferralBody: StartReferralBody,
): ReferralResponse
@GET("shops")
suspend fun getShopInfo(@Query(value = "name") name: String): ShopResponse
@GET("sales")
suspend fun getSalesInfo(
@Query(value = "locale") locale: String,
@Query(value = "shops") shops: String,
): SalesResponse
@GET("quotes")
suspend fun getQuotes(
@Query("currencyId") currencyId: String,
@ -76,7 +73,10 @@ interface TangemTechApi {
): ApiResponse<QuotesResponse>
@GET("promotion")
suspend fun getPromotionInfo(@Query("programName") name: String): ApiResponse<PromotionInfoResponse>
suspend fun getPromotionInfo(
@Query("programName") name: String,
@Header("Cache-Control") cacheControl: String = "max-age=600",
): ApiResponse<PromotionInfoResponse>
@GET("settings/{wallet_id}")
suspend fun getUserTokensSettings(
@ -131,4 +131,8 @@ interface TangemTechApi {
@GET("features")
suspend fun getFeatures(): ApiResponse<FeaturesResponse>
@ReadTimeout(duration = 5, unit = TimeUnit.SECONDS)
@GET("networks/providers")
suspend fun getBlockchainProviders(): Map<String, List<ProviderModel>>
}

View file

@ -1,15 +0,0 @@
package com.tangem.datasource.api.tangemTech
import com.tangem.datasource.config.models.ProviderModel
import retrofit2.http.GET
/**
* Tangem Tech API for app services
*
[REDACTED_AUTHOR]
*/
interface TangemTechServiceApi {
@GET("networks/providers")
suspend fun getBlockchainProviders(): Map<String, List<ProviderModel>>
}

View file

@ -0,0 +1,39 @@
package com.tangem.datasource.api.utils
import java.util.concurrent.TimeUnit
/**
* Set connect timeout of request
*
* @property duration duration
* @property unit unit
*
* @see "HttpClientExt.applyTimeoutAnnotations"
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
internal annotation class ConnectTimeout(val duration: Int, val unit: TimeUnit)
/**
* Set read timeout of request
*
* @property duration duration
* @property unit unit
*
* @see "HttpClientExt.applyTimeoutAnnotations"
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
internal annotation class ReadTimeout(val duration: Int, val unit: TimeUnit)
/**
* Set write timeout of request
*
* @property duration duration
* @property unit unit
*
* @see "HttpClientExt.applyTimeoutAnnotations"
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
internal annotation class WriteTimeout(val duration: Int, val unit: TimeUnit)

View file

@ -28,5 +28,5 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
fun provideTangemTechConfig(): ApiConfig = TangemTech()
fun provideTangemTechConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemTech(appVersionProvider)
}

View file

@ -14,13 +14,10 @@ import com.tangem.datasource.api.markets.TangemTechMarketsApi
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.TangemTechApiV2
import com.tangem.datasource.api.tangemTech.TangemTechServiceApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.utils.RequestHeader
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.datasource.utils.*
import com.tangem.datasource.utils.RequestHeader.AppVersionPlatformHeaders
import com.tangem.datasource.utils.RequestHeader.StakeKit
import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.version.AppVersionProvider
@ -103,16 +100,24 @@ class NetworkModule {
fun provideTangemTechApi(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
appVersionProvider: AppVersionProvider,
apiConfigsManager: ApiConfigsManager,
): TangemTechApi {
return provideTangemTechApiInternal(
moshi = moshi,
context = context,
appVersionProvider = appVersionProvider,
apiConfigsManager = apiConfigsManager,
baseUrl = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.TangemTech).baseUrl,
)
val environmentConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.TangemTech)
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
.baseUrl(environmentConfig.baseUrl)
.client(
OkHttpClient.Builder()
.addEnvironmentSwitcher(id = ApiConfig.ID.TangemTech, apiConfigsManager = apiConfigsManager)
.addHeaders(environmentConfig.headers)
.applyTimeoutAnnotations()
.addLoggers(context)
.build(),
)
.build()
.create(TangemTechApi::class.java)
}
@Provides
@ -150,27 +155,6 @@ class NetworkModule {
)
}
@Provides
@Singleton
fun provideTangemTechServiceApi(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
appVersionProvider: AppVersionProvider,
apiConfigsManager: ApiConfigsManager,
): TangemTechServiceApi {
return provideTangemTechApiInternal(
moshi = moshi,
context = context,
appVersionProvider = appVersionProvider,
apiConfigsManager = apiConfigsManager,
baseUrl = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.TangemTech).baseUrl,
timeouts = Timeouts(
callTimeoutSeconds = TANGEM_TECH_SERVICE_TIMEOUT_SECONDS,
),
requestHeaders = listOf(AppVersionPlatformHeaders(appVersionProvider)),
)
}
@Provides
@DevTangemApi
@Singleton
@ -202,10 +186,11 @@ class NetworkModule {
apiConfigsManager: ApiConfigsManager,
baseUrl: String,
timeouts: Timeouts = Timeouts(),
requestHeaders: List<RequestHeader> = listOf(CacheControlHeader, AppVersionPlatformHeaders(appVersionProvider)),
requestHeaders: List<RequestHeader> = listOf(AppVersionPlatformHeaders(appVersionProvider)),
): T {
val client = OkHttpClient.Builder()
.addEnvironmentSwitcher(id = ApiConfig.ID.TangemTech, apiConfigsManager = apiConfigsManager)
.applyTimeoutAnnotations()
.let { builder ->
var b = builder
if (timeouts.callTimeoutSeconds != null) {
@ -253,7 +238,6 @@ class NetworkModule {
const val PROD_V2_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v2/"
const val TANGEM_TECH_SERVICE_TIMEOUT_SECONDS = 5L
const val TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS = 60L
}
}

View file

@ -7,9 +7,13 @@ 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.datasource.api.utils.ConnectTimeout
import com.tangem.datasource.api.utils.ReadTimeout
import com.tangem.datasource.api.utils.WriteTimeout
import com.tangem.utils.Provider
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Invocation
/** Extension for adding headers [requestHeaders] to every [OkHttpClient] request */
internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeader): OkHttpClient.Builder {
@ -26,6 +30,34 @@ internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeade
)
}
/**
* Apply timeout annotations [Interceptor].
* Add this [Interceptor] to [OkHttpClient] if use timeout annotations for retrofit requests.
*/
internal fun OkHttpClient.Builder.applyTimeoutAnnotations(): OkHttpClient.Builder {
return addInterceptor(
Interceptor { chain ->
val request = chain.request()
val tag = request.tag(Invocation::class.java)
val connectionTimeout = tag?.method()?.getAnnotation(ConnectTimeout::class.java)
val readTimeout = tag?.method()?.getAnnotation(ReadTimeout::class.java)
val writeTimeout = tag?.method()?.getAnnotation(WriteTimeout::class.java)
chain
.apply {
connectionTimeout?.let { withConnectTimeout(timeout = it.duration, unit = it.unit) }
}
.apply {
readTimeout?.let { withReadTimeout(timeout = it.duration, unit = it.unit) }
}
.apply {
writeTimeout?.let { withWriteTimeout(timeout = it.duration, unit = it.unit) }
}
.proceed(request)
},
)
}
/** Extension for adding headers [requestHeaders] to every [OkHttpClient] request */
internal fun OkHttpClient.Builder.addHeaders(requestHeaders: Map<String, Provider<String>>): OkHttpClient.Builder {
return addInterceptor(

View file

@ -36,7 +36,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
val API_CONFIGS = setOf(
Express(mockk(), mockk(), mockk()),
TangemTech(),
TangemTech(mockk()),
// Don't forget to add new config
)

View file

@ -6,21 +6,13 @@ import com.tangem.core.featuretoggle.storage.FeatureToggle
import com.tangem.core.featuretoggle.storage.FeatureTogglesStorage
import com.tangem.core.featuretoggle.utils.associateToggles
import com.tangem.core.featuretoggle.version.VersionProvider
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerifyOrder
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.verifyAll
import kotlinx.coroutines.ExperimentalCoroutinesApi
import io.mockk.*
import kotlinx.coroutines.test.runTest
import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal class ProdFeatureTogglesManagerTest {
private val localFeatureTogglesStorage = mockk<FeatureTogglesStorage>()

View file

@ -4,7 +4,7 @@ import androidx.core.util.PatternsCompat
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchainsdk.BlockchainProvidersResponse
import com.tangem.blockchainsdk.utils.createPrivateProviderType
import com.tangem.datasource.api.tangemTech.TangemTechServiceApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.config.models.ProviderModel
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -24,7 +24,7 @@ import javax.inject.Singleton
*/
@Singleton
internal class BlockchainProvidersResponseLoader @Inject constructor(
private val tangemTechServiceApi: TangemTechServiceApi,
private val tangemTechServiceApi: TangemTechApi,
private val assetLoader: AssetLoader,
private val dispatchers: CoroutineDispatcherProvider,
) {

View file

@ -7,7 +7,7 @@ import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import com.tangem.blockchainsdk.BlockchainProvidersResponse
import com.tangem.datasource.api.tangemTech.TangemTechServiceApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.asset.reader.AssetReader
import com.tangem.datasource.config.models.ProviderModel
@ -24,7 +24,7 @@ import org.junit.Test
@OptIn(ExperimentalStdlibApi::class)
internal class BlockchainProvidersResponseLoaderTest {
private val tangemTechServiceApi = mockk<TangemTechServiceApi>()
private val tangemTechServiceApi = mockk<TangemTechApi>()
private val assetReader = mockk<AssetReader>()
private val moshi = mockk<Moshi>()
private val jsonAdapter = mockk<JsonAdapter<BlockchainProvidersResponse>>()