Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-28 14:33:53 +04:00
parent 18af16b795
commit cd0b30eaec
40 changed files with 235 additions and 258 deletions

View file

@ -1,7 +1,7 @@
package com.tangem.datasource.api.common.config
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.config.ConfigManager
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
@ -10,12 +10,12 @@ import com.tangem.utils.version.AppVersionProvider
/**
* Express [ApiConfig]
*
* @property configManager config manager
* @property expressAuthProvider express auth provider
* @property appVersionProvider app version provider
* @property environmentConfigStorage environment config storage
* @property expressAuthProvider express auth provider
* @property appVersionProvider app version provider
*/
internal class Express(
private val configManager: ConfigManager,
private val environmentConfigStorage: EnvironmentConfigStorage,
private val expressAuthProvider: ExpressAuthProvider,
private val appVersionProvider: AppVersionProvider,
) : ApiConfig() {
@ -55,9 +55,9 @@ internal class Express(
private fun getApiKey(isProd: Boolean): String {
return if (isProd) {
configManager.getConfigSync().express
environmentConfigStorage.getConfigSync().express
} else {
configManager.getConfigSync().devExpress
environmentConfigStorage.getConfigSync().devExpress
}
?.apiKey
?: error("No express config provided")

View file

@ -4,7 +4,7 @@ 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 com.tangem.datasource.local.config.environment.models.ProviderModel
import retrofit2.http.*
import java.util.concurrent.TimeUnit

View file

@ -1,21 +0,0 @@
package com.tangem.datasource.config
import com.tangem.datasource.config.models.Config
import kotlinx.coroutines.flow.Flow
/**
* Config manager
*
[REDACTED_AUTHOR]
*/
interface ConfigManager {
/** Initialize and return [Config] */
suspend fun initialize(): Config
/** Get [Config] as [Flow] */
fun getConfig(): Flow<Config>
/** Get [Config] synchronously */
fun getConfigSync(): Config
}

View file

@ -1,42 +0,0 @@
package com.tangem.datasource.config
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.config.converter.EnvironmentConfigConverter
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.config.models.ConfigValueModel
import com.tangem.datasource.local.datastore.RuntimeStateStore
import kotlinx.coroutines.flow.Flow
import timber.log.Timber
/**
* Default config manager
*
* @property assetLoader asset loader
* @property configStore config store
*/
internal class DefaultConfigManager(
private val assetLoader: AssetLoader,
private val configStore: RuntimeStateStore<Config>,
) : ConfigManager {
override suspend fun initialize(): Config {
val configValueModel = assetLoader.load<ConfigValueModel>(fileName = CONFIG_FILE_NAME)
?: return configStore.get().value
val config = EnvironmentConfigConverter.convert(value = configValueModel)
configStore.store(value = config)
Timber.i("Config [$CONFIG_FILE_NAME] loaded successfully")
return config
}
override fun getConfig(): Flow<Config> = configStore.get()
override fun getConfigSync(): Config = configStore.get().value
private companion object {
const val CONFIG_FILE_NAME = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}"
}
}

View file

@ -1,12 +0,0 @@
package com.tangem.datasource.config.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ExpressModel(
@Json(name = "apiKey")
val apiKey: String,
@Json(name = "signVerifierPublicKey")
val signVerifierPublicKey: String,
)

View file

@ -1,12 +0,0 @@
package com.tangem.datasource.config.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class TonCenterKeys(
@Json(name = "mainnet")
val mainnet: String,
@Json(name = "testnet")
val testnet: String,
)

View file

@ -5,10 +5,10 @@ import com.tangem.crypto.CryptoUtils
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
internal class Sha256SignatureVerifier(
private val configManager: ConfigManager,
private val environmentConfigStorage: EnvironmentConfigStorage,
private val apiConfigsManager: ApiConfigsManager,
) : DataSignatureVerifier {
@ -24,8 +24,8 @@ internal class Sha256SignatureVerifier(
private fun getPubKey(): String? {
val expressConfig = apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.Express)
return when (expressConfig.environment) {
ApiEnvironment.PROD -> configManager.getConfigSync().express?.signVerifierPublicKey
else -> configManager.getConfigSync().devExpress?.signVerifierPublicKey
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().express?.signVerifierPublicKey
else -> environmentConfigStorage.getConfigSync().devExpress?.signVerifierPublicKey
}
}
}

View file

@ -4,7 +4,7 @@ import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.Express
import com.tangem.datasource.api.common.config.StakeKit
import com.tangem.datasource.api.common.config.TangemTech
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.utils.version.AppVersionProvider
@ -21,11 +21,11 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
fun provideExpressConfig(
configManager: ConfigManager,
environmentConfigStorage: EnvironmentConfigStorage,
expressAuthProvider: ExpressAuthProvider,
appVersionProvider: AppVersionProvider,
): ApiConfig {
return Express(configManager, expressAuthProvider, appVersionProvider)
return Express(environmentConfigStorage, expressAuthProvider, appVersionProvider)
}
@Provides

View file

@ -1,26 +0,0 @@
package com.tangem.datasource.di
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.config.DefaultConfigManager
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.local.datastore.RuntimeStateStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object ConfigModule {
@Provides
@Singleton
fun providesConfigManager(assetLoader: AssetLoader): ConfigManager {
return DefaultConfigManager(
assetLoader = assetLoader,
configStore = RuntimeStateStore(defaultValue = Config()),
)
}
}

View file

@ -4,11 +4,11 @@ import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.datasource.api.common.adapter.*
import com.tangem.datasource.api.common.adapter.BigDecimalAdapter
import com.tangem.datasource.api.common.adapter.DateTimeAdapter
import com.tangem.datasource.api.common.adapter.LocalDateAdapter
import com.tangem.datasource.config.models.ProviderModel
import com.tangem.datasource.api.common.adapter.addStakeKitEnumFallbackAdapters
import com.tangem.datasource.local.config.environment.models.ProviderModel
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

View file

@ -1,9 +1,9 @@
package com.tangem.datasource.di
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.crypto.Sha256SignatureVerifier
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -17,9 +17,9 @@ internal object SecurityModule {
@Provides
@Singleton
fun provideDataSignatureVerifier(
configManager: ConfigManager,
environmentConfigStorage: EnvironmentConfigStorage,
apiConfigsManager: ApiConfigsManager,
): DataSignatureVerifier {
return Sha256SignatureVerifier(configManager, apiConfigsManager)
return Sha256SignatureVerifier(environmentConfigStorage, apiConfigsManager)
}
}

View file

@ -1,24 +0,0 @@
package com.tangem.datasource.di
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.testnet.DefaultTestnetTokensStorage
import com.tangem.datasource.local.testnet.TestnetTokensStorage
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
[REDACTED_AUTHOR]
*/
@Module
@InstallIn(SingletonComponent::class)
internal object TestnetTokensStorageModule {
@Provides
@Singleton
fun providesTestnetTokensStorage(assetLoader: AssetLoader): TestnetTokensStorage {
return DefaultTestnetTokensStorage(assetLoader)
}
}

View file

@ -0,0 +1,34 @@
package com.tangem.datasource.di.local.config
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.config.environment.DefaultEnvironmentConfigStorage
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.testnet.DefaultTestnetTokensStorage
import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
import com.tangem.datasource.local.datastore.RuntimeStateStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object ConfigModule {
@Provides
@Singleton
fun provideEnvironmentConfigStorage(assetLoader: AssetLoader): EnvironmentConfigStorage {
return DefaultEnvironmentConfigStorage(
assetLoader = assetLoader,
environmentConfigStore = RuntimeStateStore(defaultValue = EnvironmentConfig()),
)
}
@Provides
@Singleton
fun providesTestnetTokensStorage(assetLoader: AssetLoader): TestnetTokensStorage {
return DefaultTestnetTokensStorage(assetLoader)
}
}

View file

@ -0,0 +1,41 @@
package com.tangem.datasource.local.config.environment
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.config.environment.converter.EnvironmentConfigConverter
import com.tangem.datasource.local.config.environment.models.EnvironmentConfigModel
import com.tangem.datasource.local.datastore.RuntimeStateStore
import kotlinx.coroutines.flow.Flow
import timber.log.Timber
/**
* Default implementation for storing [EnvironmentConfig]
*
* @property assetLoader asset loader
* @property environmentConfigStore config store
*/
internal class DefaultEnvironmentConfigStorage(
private val assetLoader: AssetLoader,
private val environmentConfigStore: RuntimeStateStore<EnvironmentConfig>,
) : EnvironmentConfigStorage {
override suspend fun initialize(): EnvironmentConfig {
val environmentConfigModel = assetLoader.load<EnvironmentConfigModel>(fileName = CONFIG_FILE_NAME)
?: return environmentConfigStore.get().value
val config = EnvironmentConfigConverter.convert(value = environmentConfigModel)
environmentConfigStore.store(value = config)
Timber.i("Config [$CONFIG_FILE_NAME] loaded successfully")
return config
}
override fun getConfig(): Flow<EnvironmentConfig> = environmentConfigStore.get()
override fun getConfigSync(): EnvironmentConfig = environmentConfigStore.get().value
private companion object {
const val CONFIG_FILE_NAME = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}"
}
}

View file

@ -1,8 +1,9 @@
package com.tangem.datasource.config.models
package com.tangem.datasource.local.config.environment
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.datasource.local.config.environment.models.ExpressModel
data class Config(
data class EnvironmentConfig(
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
val mercuryoWidgetId: String = "",

View file

@ -0,0 +1,20 @@
package com.tangem.datasource.local.config.environment
import kotlinx.coroutines.flow.Flow
/**
* Storage for [EnvironmentConfig]
*
[REDACTED_AUTHOR]
*/
interface EnvironmentConfigStorage {
/** Initialize and return [EnvironmentConfig] */
suspend fun initialize(): EnvironmentConfig
/** Get [EnvironmentConfig] as [Flow] */
fun getConfig(): Flow<EnvironmentConfig>
/** Get [EnvironmentConfig] synchronously */
fun getConfigSync(): EnvironmentConfig
}

View file

@ -1,17 +1,17 @@
package com.tangem.datasource.config.converter
package com.tangem.datasource.local.config.environment.converter
import com.tangem.blockchain.common.*
import com.tangem.datasource.config.models.ConfigValueModel
import com.tangem.datasource.local.config.environment.models.EnvironmentConfigModel
import com.tangem.utils.converter.Converter
/**
* Converts [ConfigValueModel] to [BlockchainSdkConfig]
* Converts [EnvironmentConfigModel] to [BlockchainSdkConfig]
*
[REDACTED_AUTHOR]
*/
internal object BlockchainSDKConfigConverter : Converter<ConfigValueModel, BlockchainSdkConfig> {
internal object BlockchainSDKConfigConverter : Converter<EnvironmentConfigModel, BlockchainSdkConfig> {
override fun convert(value: ConfigValueModel): BlockchainSdkConfig {
override fun convert(value: EnvironmentConfigModel): BlockchainSdkConfig {
return BlockchainSdkConfig(
blockchairCredentials = BlockchairCredentials(
apiKey = value.blockchairApiKeys,
@ -45,7 +45,7 @@ internal object BlockchainSDKConfigConverter : Converter<ConfigValueModel, Block
)
}
private fun createGetBlockCredentials(configValues: ConfigValueModel): GetBlockCredentials? {
private fun createGetBlockCredentials(configValues: EnvironmentConfigModel): GetBlockCredentials? {
return configValues.getBlockAccessTokens?.let { accessTokens ->
GetBlockCredentials(
xrp = GetBlockAccessToken(jsonRpc = accessTokens.xrp?.jsonRPC),

View file

@ -1,18 +1,18 @@
package com.tangem.datasource.config.converter
package com.tangem.datasource.local.config.environment.converter
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.config.models.ConfigValueModel
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.local.config.environment.models.EnvironmentConfigModel
import com.tangem.utils.converter.Converter
/**
* Converter from [ConfigValueModel] to [Config]
* Converter from [EnvironmentConfigModel] to [EnvironmentConfig]
*
[REDACTED_AUTHOR]
*/
internal object EnvironmentConfigConverter : Converter<ConfigValueModel, Config> {
internal object EnvironmentConfigConverter : Converter<EnvironmentConfigModel, EnvironmentConfig> {
override fun convert(value: ConfigValueModel): Config {
return Config(
override fun convert(value: EnvironmentConfigModel): EnvironmentConfig {
return EnvironmentConfig(
moonPayApiKey = value.moonPayApiKey,
moonPayApiSecretKey = value.moonPayApiSecretKey,
mercuryoWidgetId = value.mercuryoWidgetId,

View file

@ -1,11 +1,11 @@
package com.tangem.datasource.config.models
package com.tangem.datasource.local.config.environment.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@Suppress("LongParameterList")
@JsonClass(generateAdapter = true)
class ConfigValueModel(
class EnvironmentConfigModel(
@Json(name = "mercuryoWidgetId") val mercuryoWidgetId: String,
@Json(name = "mercuryoSecret") val mercuryoSecret: String,
@Json(name = "moonPayApiKey") val moonPayApiKey: String,
@ -68,10 +68,24 @@ data class GetBlockAccessTokens(
@Json(name = "filecoin") val filecoin: GetBlockToken?,
)
@JsonClass(generateAdapter = true)
data class TonCenterKeys(
@Json(name = "mainnet") val mainnet: String,
@Json(name = "testnet") val testnet: String,
)
@JsonClass(generateAdapter = true)
data class GetBlockToken(
@Json(name = "jsonRpc") val jsonRPC: String?,
@Json(name = "blockBookRest") val blockBookRest: String?,
@Json(name = "rest") val rest: String?,
@Json(name = "rosetta") val rosetta: String?,
)
@JsonClass(generateAdapter = true)
data class ExpressModel(
@Json(name = "apiKey")
val apiKey: String,
@Json(name = "signVerifierPublicKey")
val signVerifierPublicKey: String,
)

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.config.models
package com.tangem.datasource.local.config.environment.models
import com.squareup.moshi.Json

View file

@ -1,7 +1,7 @@
package com.tangem.datasource.local.testnet
package com.tangem.datasource.local.config.testnet
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.testnet.models.TestnetTokensConfig
import com.tangem.datasource.local.config.testnet.models.TestnetTokensConfig
/**
* Default implementation for storing testnet tokens data

View file

@ -1,6 +1,6 @@
package com.tangem.datasource.local.testnet
package com.tangem.datasource.local.config.testnet
import com.tangem.datasource.local.testnet.models.TestnetTokensConfig
import com.tangem.datasource.local.config.testnet.models.TestnetTokensConfig
/**
* Storage of testnet tokens data

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.local.testnet.models
package com.tangem.datasource.local.config.testnet.models
import com.squareup.moshi.Json
@ -7,6 +7,7 @@ import com.squareup.moshi.Json
*
* @property tokens testnet tokens list
*/
// TODO: use CoinsResponse
data class TestnetTokensConfig(
@Json(name = "coins") val tokens: List<Token>,
) {

View file

@ -1,28 +0,0 @@
package com.tangem.datasource.api.common.config.managers
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.config.models.ExpressModel
import kotlinx.coroutines.flow.flowOf
/**
* Mock [ConfigManager] implementation for [ProdApiConfigsManagerTest]
*
[REDACTED_AUTHOR]
*/
internal class MockConfigManager : ConfigManager {
private val config = Config(
express = ExpressModel(apiKey = EXPRESS_API_KEY, signVerifierPublicKey = "vocibus"),
devExpress = ExpressModel(apiKey = EXPRESS_DEV_API_KEY, signVerifierPublicKey = "pellentesque"),
)
override suspend fun initialize() = config
override fun getConfig() = flowOf(config)
override fun getConfigSync() = config
companion object {
const val EXPRESS_API_KEY = "express_api_key"
const val EXPRESS_DEV_API_KEY = "express_dev_api_key"
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.datasource.api.common.config.managers
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.environment.models.ExpressModel
import kotlinx.coroutines.flow.flowOf
/**
* Mock [EnvironmentConfigStorage] implementation for [ProdApiConfigsManagerTest]
*
[REDACTED_AUTHOR]
*/
internal class MockEnvironmentConfigStorage : EnvironmentConfigStorage {
private val environmentConfig = EnvironmentConfig(
express = ExpressModel(apiKey = EXPRESS_API_KEY, signVerifierPublicKey = "vocibus"),
devExpress = ExpressModel(apiKey = EXPRESS_DEV_API_KEY, signVerifierPublicKey = "pellentesque"),
)
override suspend fun initialize() = environmentConfig
override fun getConfig() = flowOf(environmentConfig)
override fun getConfigSync() = environmentConfig
companion object {
const val EXPRESS_API_KEY = "express_api_key"
const val EXPRESS_DEV_API_KEY = "express_dev_api_key"
}
}

View file

@ -19,7 +19,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
private val configManager = MockConfigManager()
private val configManager = MockEnvironmentConfigStorage()
private val appVersionProvider = mockk<AppVersionProvider>()
private val expressAuthProvider = mockk<ExpressAuthProvider>()
private val stakeKitAuthProvider = mockk<StakeKitAuthProvider>()
@ -106,9 +106,9 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
headers = mapOf(
"api-key" to Provider {
if (environment == ApiEnvironment.PROD) {
MockConfigManager.EXPRESS_API_KEY
MockEnvironmentConfigStorage.EXPRESS_API_KEY
} else {
MockConfigManager.EXPRESS_DEV_API_KEY
MockEnvironmentConfigStorage.EXPRESS_DEV_API_KEY
}
},
"user-id" to Provider { EXPRESS_USER_ID },