Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package cloud.mindbox.mobile_sdk.abtests

import cloud.mindbox.mobile_sdk.logger.mindboxLogW
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
import java.security.MessageDigest

internal class CustomerAbMixerImpl: CustomerAbMixer {
Expand All @@ -12,13 +10,6 @@ internal class CustomerAbMixerImpl: CustomerAbMixer {

@OptIn(ExperimentalUnsignedTypes::class)
override fun stringModulusHash(identifier: String, salt: String): Int {
MindboxPreferences.mixerFixedHash
.takeIf { it in 0..99 }
?.let {
this@CustomerAbMixerImpl.mindboxLogW("Mixer use fixed hash $it!")
return it
}

val saltedId = identifier.uppercase() + salt.uppercase()

val bytes = saltedId.toByteArray(Charsets.UTF_8)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import cloud.mindbox.mobile_sdk.inapp.data.repositories.InAppGeoRepositoryImpl
import cloud.mindbox.mobile_sdk.inapp.data.repositories.InAppRepositoryImpl
import cloud.mindbox.mobile_sdk.inapp.data.repositories.InAppSegmentationRepositoryImpl
import cloud.mindbox.mobile_sdk.inapp.data.repositories.MobileConfigRepositoryImpl
import cloud.mindbox.mobile_sdk.inapp.data.validators.InAppValidatorImpl
import cloud.mindbox.mobile_sdk.inapp.data.validators.OperationNameValidator
import cloud.mindbox.mobile_sdk.inapp.data.validators.OperationValidator
import cloud.mindbox.mobile_sdk.inapp.data.validators.*
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.GeoSerializationManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppSerializationManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.MobileConfigSerializationManager
Expand Down Expand Up @@ -42,6 +40,7 @@ internal fun DataModule(
inAppMapper = inAppMapper,
mobileConfigSerializationManager = mobileConfigSerializationManager,
inAppValidator = inAppValidator,
abTestValidator = abTestValidator,
monitoringValidator = monitoringValidator,
operationNameValidator = operationNameValidator,
operationValidator = operationValidator,
Expand Down Expand Up @@ -86,7 +85,11 @@ internal fun DataModule(

override val monitoringValidator: MonitoringValidator by lazy { MonitoringValidator() }

override val inAppValidator: InAppValidator by lazy { InAppValidatorImpl() }
override val inAppValidator: InAppValidator by lazy { InAppValidatorImpl(sdkVersionValidator) }

override val abTestValidator: ABTestValidator by lazy { ABTestValidator(sdkVersionValidator) }

override val sdkVersionValidator: SdkVersionValidator by lazy { SdkVersionValidator() }

override val operationNameValidator: OperationNameValidator
get() = OperationNameValidator()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cloud.mindbox.mobile_sdk.di.modules

import cloud.mindbox.mobile_sdk.abtests.CustomerAbMixer
import cloud.mindbox.mobile_sdk.abtests.CustomerAbMixerImpl
import cloud.mindbox.mobile_sdk.inapp.domain.InAppChoosingManagerImpl
import cloud.mindbox.mobile_sdk.inapp.domain.InAppEventManagerImpl
import cloud.mindbox.mobile_sdk.inapp.domain.InAppFilteringManagerImpl
Expand Down Expand Up @@ -32,7 +34,7 @@ internal fun DomainModule(
InAppChoosingManagerImpl(
inAppGeoRepository = inAppGeoRepository,
inAppSegmentationRepository = inAppSegmentationRepository,
inAppContentFetcher = inAppContentFetcher
inAppContentFetcher = inAppContentFetcher,
)
}

Expand All @@ -41,4 +43,7 @@ internal fun DomainModule(

override val inAppFilteringManager: InAppFilteringManager
get() = InAppFilteringManagerImpl(inAppRepository = inAppRepository)

override val customerAbMixer: CustomerAbMixer
get() = CustomerAbMixerImpl()
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package cloud.mindbox.mobile_sdk.di.modules

import android.app.Application
import cloud.mindbox.mobile_sdk.abtests.CustomerAbMixer
import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager
import cloud.mindbox.mobile_sdk.inapp.data.mapper.InAppMapper
import cloud.mindbox.mobile_sdk.inapp.data.validators.ABTestValidator
import cloud.mindbox.mobile_sdk.inapp.data.validators.OperationNameValidator
import cloud.mindbox.mobile_sdk.inapp.data.validators.OperationValidator
import cloud.mindbox.mobile_sdk.inapp.data.validators.SdkVersionValidator
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppContentFetcher
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppImageLoader
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.interactors.InAppInteractor
Expand Down Expand Up @@ -63,6 +66,8 @@ internal interface DataModule : MindboxModule {
val monitoringValidator: MonitoringValidator
val operationNameValidator: OperationNameValidator
val operationValidator: OperationValidator
val abTestValidator: ABTestValidator
val sdkVersionValidator: SdkVersionValidator
}

internal interface MonitoringModule : MindboxModule {
Expand All @@ -81,6 +86,7 @@ internal interface DomainModule : MindboxModule {
val inAppChoosingManager: InAppChoosingManager
val inAppEventManager: InAppEventManager
val inAppFilteringManager: InAppFilteringManager
val customerAbMixer: CustomerAbMixer
}

internal interface ApiModule : MindboxModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,24 @@ internal class InAppMapper {
} ?: emptyList(),
operations = inAppConfigResponse.settings?.map { (key, value) ->
key.enumValue<OperationName>() to OperationSystemName(value.systemName)
}?.toMap() ?: emptyMap()
}?.toMap() ?: emptyMap(),
abtests = inAppConfigResponse.abtests?.map { dto ->
ABTest(
id = dto.id,
minVersion = dto.sdkVersion?.minVersion,
maxVersion = dto.sdkVersion?.maxVersion,
salt = dto.salt!!,
variants = dto.variants?.map { variantDto ->
ABTest.Variant(
type = variantDto.objects!!.first().type!!,
kind = variantDto.objects.first().kind.enumValue(),
inapps = variantDto.objects.first().inapps ?: listOf(),
lower = variantDto.modulus!!.lower!!,
upper = variantDto.modulus.upper!!,
)
} ?: listOf()
)
} ?: listOf()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
package cloud.mindbox.mobile_sdk.inapp.data.repositories

import cloud.mindbox.mobile_sdk.inapp.data.mapper.InAppMapper
import cloud.mindbox.mobile_sdk.inapp.data.validators.ABTestValidator
import cloud.mindbox.mobile_sdk.inapp.data.validators.OperationNameValidator
import cloud.mindbox.mobile_sdk.inapp.data.validators.OperationValidator
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.MobileConfigSerializationManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfigRepository
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.validators.InAppValidator
import cloud.mindbox.mobile_sdk.inapp.domain.models.InApp
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppConfig
import cloud.mindbox.mobile_sdk.inapp.domain.models.OperationName
import cloud.mindbox.mobile_sdk.inapp.domain.models.OperationSystemName
import cloud.mindbox.mobile_sdk.inapp.domain.models.*
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
import cloud.mindbox.mobile_sdk.logger.mindboxLogE
import cloud.mindbox.mobile_sdk.managers.DbManager
import cloud.mindbox.mobile_sdk.managers.GatewayManager
import cloud.mindbox.mobile_sdk.models.operation.response.InAppConfigResponse
import cloud.mindbox.mobile_sdk.models.operation.response.OperationDto
import cloud.mindbox.mobile_sdk.models.operation.response.*
import cloud.mindbox.mobile_sdk.monitoring.data.validators.MonitoringValidator
import cloud.mindbox.mobile_sdk.monitoring.domain.models.LogRequest
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
Expand All @@ -29,6 +28,7 @@ internal class MobileConfigRepositoryImpl(
private val mobileConfigSerializationManager: MobileConfigSerializationManager,
private val inAppValidator: InAppValidator,
private val monitoringValidator: MonitoringValidator,
private val abTestValidator: ABTestValidator,
private val operationNameValidator: OperationNameValidator,
private val operationValidator: OperationValidator,
private val gatewayManager: GatewayManager,
Expand All @@ -38,6 +38,7 @@ internal class MobileConfigRepositoryImpl(

private var inApps: List<InApp>? = null
private var operations: Map<OperationName, OperationSystemName>? = null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Возможно стоит вынести в мобильный бэклог, перенос этих штук в sessionManager, выглядит, что в будущем это будет правильнее

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавил тикет на исправление. Из-за этих штук тригерится парсинг несколько раз

private var abtests: List<ABTest>? = null

override suspend fun fetchMobileConfig() {
val configuration = DbManager.listenConfigurations().first()
Expand All @@ -49,49 +50,17 @@ internal class MobileConfigRepositoryImpl(
private fun listenInAppConfig(): Flow<InAppConfig?> {
return MindboxPreferences.inAppConfigFlow.map { inAppConfigString ->
mutex.withLock {
MindboxLoggerImpl.d(
parent = this@MobileConfigRepositoryImpl,
this@MobileConfigRepositoryImpl.mindboxLogD(
message = "CachedConfig : $inAppConfigString"
)
val configBlank =
mobileConfigSerializationManager.deserializeToConfigDtoBlank(inAppConfigString)
val filteredInApps = configBlank?.inApps
?.filter { inAppDtoBlank ->
inAppValidator.validateInAppVersion(inAppDtoBlank)
}
?.map { inAppDtoBlank ->
inAppMapper.mapToInAppDto(
inAppDtoBlank = inAppDtoBlank,
formDto = mobileConfigSerializationManager.deserializeToInAppFormDto(
inAppDtoBlank.form
),
targetingDto = mobileConfigSerializationManager.deserializeToInAppTargetingDto(
inAppDtoBlank.targeting
)
)
}?.filter { inAppDto ->
inAppValidator.validateInApp(inAppDto)
}
val filteredMonitoring =
configBlank?.monitoring?.logs?.filter { logRequestDtoBlank ->
monitoringValidator.validateLogRequestDtoBlank(logRequestDtoBlank)
}?.map { logRequestDtoBlank ->
inAppMapper.mapToLogRequestDto(logRequestDtoBlank)
}

val filteredSettings = configBlank?.settings?.operations
?.filter { (name, operation) ->
operationNameValidator.isValid(name)
&& operationValidator.isValid(operation)
}?.map { (name, operation) ->
name!! to OperationDto(operation!!.systemName!!)
}?.toMap()
?: emptyMap()

val filteredConfig = InAppConfigResponse(
inApps = filteredInApps,
monitoring = filteredMonitoring,
settings = filteredSettings,
inApps = getInApps(configBlank),
monitoring = getMonitoring(configBlank),
settings = getSettings(configBlank),
abtests = getABTests(configBlank),
)

return@map inAppMapper.mapToInAppConfig(filteredConfig)
Expand All @@ -105,6 +74,56 @@ internal class MobileConfigRepositoryImpl(
}
}

private fun getInApps(configBlank: InAppConfigResponseBlank?): List<InAppDto>? =
configBlank?.inApps
?.filter { inAppDtoBlank ->
inAppValidator.validateInAppVersion(inAppDtoBlank)
}
?.map { inAppDtoBlank ->
inAppMapper.mapToInAppDto(
inAppDtoBlank = inAppDtoBlank,
formDto = mobileConfigSerializationManager.deserializeToInAppFormDto(
inAppDtoBlank.form
),
targetingDto = mobileConfigSerializationManager.deserializeToInAppTargetingDto(
inAppDtoBlank.targeting
)
)
}?.filter { inAppDto ->
inAppValidator.validateInApp(inAppDto)
}

private fun getMonitoring(configBlank: InAppConfigResponseBlank?): List<LogRequestDto>? =
configBlank?.monitoring?.logs?.filter { logRequestDtoBlank ->
monitoringValidator.validateLogRequestDtoBlank(logRequestDtoBlank)
}?.map { logRequestDtoBlank ->
inAppMapper.mapToLogRequestDto(logRequestDtoBlank)
}

private fun getSettings(configBlank: InAppConfigResponseBlank?): Map<String, OperationDto> =
configBlank?.settings?.operations
?.filter { (name, operation) ->
operationNameValidator.isValid(name)
&& operationValidator.isValid(operation)
}?.map { (name, operation) ->
name!! to OperationDto(operation!!.systemName!!)
}?.toMap()
?: emptyMap()


private fun getABTests(configBlank: InAppConfigResponseBlank?): List<ABTestDto> {
return try {
if (configBlank?.abtests == null) return listOf()

return configBlank.abtests.takeIf { abtests ->
abtests.all { abTestValidator.isValid(it) }
} ?: listOf()
} catch (e: Exception) {
mindboxLogE("Error parse abtests", e)
listOf()
}
}

override fun listenMonitoringSection(): Flow<List<LogRequest>?> {
return listenInAppConfig().map { inAppConfig ->
inAppConfig?.monitoring
Expand All @@ -131,4 +150,14 @@ internal class MobileConfigRepositoryImpl(
inAppList
}
}

override suspend fun getABTests(): List<ABTest> {
return abtests ?: run {
val list: List<ABTest> = listenInAppConfig().map { inAppConfig ->
inAppConfig?.abtests
}.first() ?: listOf()
abtests = list
list
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cloud.mindbox.mobile_sdk.inapp.data.validators

import cloud.mindbox.mobile_sdk.logger.mindboxLogW
import cloud.mindbox.mobile_sdk.models.operation.response.ABTestDto

internal class ABTestValidator(private val sdkVersionValidator: SdkVersionValidator) :
Validator<ABTestDto?> {

private val variantsValidator by lazy { VariantValidator() }

override fun isValid(item: ABTestDto?): Boolean {
if (item == null) {
mindboxLogW("The element in abtests block cannot be null. All abtests will not be used.")
return false
}

if (item.id.isBlank()) {
mindboxLogW("The field 'id' in abtests block cannot be null. All abtests will not be used.")
return false
}

if (item.sdkVersion == null || !sdkVersionValidator.isValid(item.sdkVersion)) {
mindboxLogW("In abtest ${item.id} 'sdkVersion' field is invalid. All abtests will not be used.")
return false
}

if (item.salt.isNullOrBlank()) {
mindboxLogW("In abtest ${item.id} 'salt' field is invalid. All abtests will not be used.")
return false
}

if (item.variants == null ||
item.variants.size < 2
) {
mindboxLogW("In abtest ${item.id} 'variants' field must have at least two items. All abtests will not be used.")
return false
}

if (item.variants.any { !variantsValidator.isValid(it) }) {
mindboxLogW("In abtest ${item.id} 'variants' field is invalid. All abtests will not be used.")
return false
}

var start = 0
item.variants.sortedBy { it.modulus!!.lower }
.onEach { abtest ->
if (abtest.modulus?.lower == start) {
start = abtest.modulus.upper!!
} else {
mindboxLogW("In abtest ${item.id} 'variants' field not have full cover. All abtests will not be used.")
return false
}
}

if (start !in 99..100) {
mindboxLogW("In abtest ${item.id} 'variants' field not have full cover. All abtests will not be used.")
return false
}

return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import cloud.mindbox.mobile_sdk.models.TreeTargetingDto
import cloud.mindbox.mobile_sdk.models.operation.response.InAppConfigResponseBlank
import cloud.mindbox.mobile_sdk.models.operation.response.InAppDto
import cloud.mindbox.mobile_sdk.models.operation.response.PayloadDto
import cloud.mindbox.mobile_sdk.utils.Constants

internal class InAppValidatorImpl : InAppValidator {
internal class InAppValidatorImpl(private val sdkVersionValidator: SdkVersionValidator) : InAppValidator {

private fun validateInAppTargeting(id: String, targeting: TreeTargetingDto?): Boolean {
return when (targeting) {
Expand Down Expand Up @@ -155,14 +154,7 @@ internal class InAppValidatorImpl : InAppValidator {
}

override fun validateInAppVersion(inAppDto: InAppConfigResponseBlank.InAppDtoBlank): Boolean {
val sdkVersion = inAppDto.sdkVersion ?: return false
val minVersionValid = sdkVersion.minVersion?.let { min ->
min <= Constants.SDK_VERSION_NUMERIC
} ?: true
val maxVersionValid = sdkVersion.maxVersion?.let { max ->
max >= Constants.SDK_VERSION_NUMERIC
} ?: true
return minVersionValid && maxVersionValid
return sdkVersionValidator.isValid(inAppDto.sdkVersion)
}

override fun validateInApp(inApp: InAppDto): Boolean {
Expand Down
Loading