Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-08 18:26:18 +05:00
parent d7db2c77f5
commit de93d0f350
14 changed files with 140 additions and 89 deletions

@ -1 +1 @@
Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354
Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6

View file

@ -6,9 +6,15 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.domain.common.FeatureCoroutineExceptionHandler
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsFilterHolder
import com.tangem.tap.common.analytics.api.AnalyticsHandlerHolder
import com.tangem.tap.common.analytics.api.BlockchainSdkErrorEventHandler
import com.tangem.tap.common.analytics.api.CardSdkErrorEventHandler
import com.tangem.tap.common.analytics.api.ErrorEventHandler
import com.tangem.tap.common.analytics.api.ErrorEventLogger
import com.tangem.tap.common.analytics.api.ParamsInterceptor
import com.tangem.tap.common.analytics.api.ParamsInterceptorHolder
import com.tangem.tap.common.analytics.api.SdkErrorEventHandler
import com.tangem.tap.common.analytics.events.AnalyticsEvent
import com.tangem.tap.common.extensions.filterNotNull
import kotlinx.coroutines.CoroutineName
@ -21,18 +27,27 @@ import java.util.concurrent.Executors
/**
[REDACTED_AUTHOR]
*/
interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
ErrorEventHandler,
SdkErrorEventHandler,
AnalyticsHandlerHolder,
AnalyticsFilterHolder,
ParamsInterceptorHolder {
fun send(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null)
}
object Analytics : GlobalAnalyticsEventHandler {
internal val analyticsScope: CoroutineScope by lazy { createScope() }
private val analyticsScope: CoroutineScope by lazy { createScope() }
private val eventFilters = mutableListOf<AnalyticsEventFilter>()
private val handlers = mutableMapOf<String, AnalyticsEventHandler>()
private val analyticsFilters = mutableSetOf<AnalyticsEventFilter>()
private val paramsInterceptors = mutableMapOf<String, ParamsInterceptor>()
private val analyticsHandlers: List<AnalyticsEventHandler>
get() = handlers.values.toList()
private val attachToAllEventsParams: MutableMap<String, String> = mutableMapOf()
override fun id(): String = analyticsHandlers.joinToString(", ") { it.id() }
override fun addHandler(name: String, handler: AnalyticsEventHandler) {
@ -44,33 +59,39 @@ object Analytics : GlobalAnalyticsEventHandler {
}
override fun addFilter(filter: AnalyticsEventFilter) {
eventFilters.add(filter)
analyticsFilters.add(filter)
}
override fun removeFilter(filter: AnalyticsEventFilter) {
eventFilters.remove(filter)
override fun removeFilter(filter: AnalyticsEventFilter): Boolean {
return analyticsFilters.remove(filter)
}
override fun attachToAllEvents(key: String, value: String) {
attachToAllEventsParams[key] = value
override fun addParamsInterceptor(interceptor: ParamsInterceptor) {
paramsInterceptors[interceptor.id()] = interceptor
}
override fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor? {
return paramsInterceptors.remove(interceptor.id())
}
override fun send(event: String, params: Map<String, String>) {
analyticsScope.launch {
analyticsHandlers.forEach { it.send(event, params) }
analyticsHandlers.forEach { it.send(event, params.interceptParams()) }
}
}
override fun send(event: AnalyticsEvent, card: Card?, blockchain: String?) {
analyticsScope.launch {
val eventFilter = eventFilters.firstOrNull { it.canBeAppliedTo(event) }
val eventString = prepareEventString(event.category, event.event)
val eventParams = prepareParams(card, blockchain, event.interceptParams())
val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) }
when {
eventFilter == null -> analyticsHandlers.forEach { it.send(event, card, blockchain) }
eventFilter == null -> analyticsHandlers.forEach { it.send(eventString, eventParams) }
eventFilter.canBeSent(event) -> {
analyticsHandlers
.filter { eventFilter.canBeConsumedBy(it, event) }
.forEach { it.send(event) }
.filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) }
.forEach { it.send(eventString, eventParams) }
}
}
}
@ -84,7 +105,7 @@ object Analytics : GlobalAnalyticsEventHandler {
) {
analyticsScope.launch {
analyticsHandlers.forEach {
it.handleAnalyticsEvent(event, params, card, blockchain)
it.handleAnalyticsEvent(event, params.interceptParams(), card, blockchain)
}
}
}
@ -123,17 +144,21 @@ object Analytics : GlobalAnalyticsEventHandler {
}
}
override fun prepareParams(card: Card?, blockchain: String?, params: Map<String, String>): Map<String, String> {
return super.prepareParams(card, blockchain, params).toMutableMap().apply {
putAll(attachToAllEventsParams)
}
private fun AnalyticsEvent.interceptParams(): Map<String, String> = params.interceptParams()
private fun Map<String, String>.interceptParams(): Map<String, String> {
return this.toMutableMap().apply { interceptParams() }
}
private fun MutableMap<String, String>.interceptParams() {
paramsInterceptors.values.forEach { it.intercept(this) }
}
private fun createScope(): CoroutineScope {
val name = "Analytics"
val dispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
val exceptionHandler = FeatureCoroutineExceptionHandler.create(name)
return CoroutineScope(Job() + dispatcher + CoroutineName(name) + exceptionHandler)
val exHandler = FeatureCoroutineExceptionHandler.create(name)
return CoroutineScope(Job() + dispatcher + CoroutineName(name) + exHandler)
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.tap.common.analytics
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.ErrorEventHandler
import com.tangem.tap.common.analytics.api.SdkErrorEventHandler
interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
ErrorEventHandler,
SdkErrorEventHandler {
fun addHandler(name: String, handler: AnalyticsEventHandler)
fun removeHandler(name: String): AnalyticsEventHandler?
fun addFilter(filter: AnalyticsEventFilter)
fun removeFilter(filter: AnalyticsEventFilter)
fun attachToAllEvents(key: String, value: String)
}

View file

@ -0,0 +1,37 @@
package com.tangem.tap.common.analytics.api
import com.tangem.blockchain.common.BlockchainError
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.AnalyticsAnOld
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
/**
[REDACTED_AUTHOR]
*/
interface ErrorEventHandler {
fun send(
error: Throwable,
params: Map<String, String> = emptyMap(),
)
}
interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler
interface CardSdkErrorEventHandler {
fun send(
error: TangemSdkError,
action: AnalyticsAnOld.ActionToLog,
params: Map<AnalyticsParamAnOld, String> = emptyMap(),
card: Card? = null,
)
}
interface BlockchainSdkErrorEventHandler {
fun send(
error: BlockchainError,
action: AnalyticsAnOld.ActionToLog,
params: Map<AnalyticsParamAnOld, String> = mapOf(),
card: Card? = null,
)
}

View file

@ -8,5 +8,10 @@ import com.tangem.tap.common.analytics.events.AnalyticsEvent
interface AnalyticsEventFilter {
fun canBeAppliedTo(event: AnalyticsEvent): Boolean
fun canBeSent(event: AnalyticsEvent): Boolean
fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean
fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean
}
interface AnalyticsFilterHolder {
fun addFilter(filter: AnalyticsEventFilter)
fun removeFilter(filter: AnalyticsEventFilter): Boolean
}

View file

@ -1,15 +1,10 @@
package com.tangem.tap.common.analytics.api
import android.app.Application
import com.tangem.blockchain.common.BlockchainError
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.domain.common.AnalyticsHandlersLogConfig
import com.tangem.tap.common.analytics.AnalyticsAnOld
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
import com.tangem.tap.common.analytics.events.AnalyticsEvent
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.domain.configurable.config.Config
@ -22,13 +17,6 @@ interface AnalyticsEventHandler {
fun send(event: String, params: Map<String, String> = emptyMap())
fun send(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null) {
send(
event = prepareEventString(event.category, event.event),
params = prepareParams(card, blockchain, event.params),
)
}
@Deprecated("Migrate to AnalyticsEvent")
fun handleAnalyticsEvent(
event: AnalyticsEventAnOld,
@ -45,7 +33,6 @@ interface AnalyticsEventHandler {
params: Map<String, String> = emptyMap(),
): Map<String, String> = mapOf(
AnalyticsParam.Firmware to card?.firmwareVersion?.stringValue,
AnalyticsParam.BatchId to card?.batchId,
AnalyticsParam.Blockchain to blockchain,
).filterNotNull() + params
@ -54,31 +41,9 @@ interface AnalyticsEventHandler {
}
}
interface ErrorEventHandler {
fun send(
error: Throwable,
params: Map<String, String> = emptyMap(),
)
}
interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler
interface CardSdkErrorEventHandler {
fun send(
error: TangemSdkError,
action: AnalyticsAnOld.ActionToLog,
params: Map<AnalyticsParamAnOld, String> = emptyMap(),
card: Card? = null,
)
}
interface BlockchainSdkErrorEventHandler {
fun send(
error: BlockchainError,
action: AnalyticsAnOld.ActionToLog,
params: Map<AnalyticsParamAnOld, String> = mapOf(),
card: Card? = null,
)
interface AnalyticsHandlerHolder {
fun addHandler(name: String, handler: AnalyticsEventHandler)
fun removeHandler(name: String): AnalyticsEventHandler?
}
interface AnalyticsHandlerBuilder {

View file

@ -0,0 +1,17 @@
package com.tangem.tap.common.analytics.api
import com.tangem.tap.common.analytics.events.AnalyticsEvent
/**
[REDACTED_AUTHOR]
*/
interface ParamsInterceptor {
fun id(): String
fun canBeAppliedTo(event: AnalyticsEvent): Boolean
fun intercept(params: MutableMap<String, String>)
}
interface ParamsInterceptorHolder {
fun addParamsInterceptor(interceptor: ParamsInterceptor)
fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor?
}

View file

@ -55,8 +55,10 @@ sealed class AnalyticsParam {
}
companion object Key {
const val BatchId = "Batch"
// old keys
const val Blockchain = "blockchain"
const val BatchId = "batch_id"
const val Firmware = "firmware"
}
}

View file

@ -17,7 +17,7 @@ sealed class Basic(
params = mapOf(
"State" to state.value,
"Currency" to currency.value,
"Batch" to batch,
AnalyticsParam.BatchId to batch,
),
)

View file

@ -23,5 +23,5 @@ class BasicSignInFilter : AnalyticsEventFilter {
return canBeSent
}
override fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true
override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true
}

View file

@ -39,7 +39,7 @@ class BasicTopUpFilter(
}
}
override fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true
override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true
data class Data(
val walletId: String,

View file

@ -16,7 +16,7 @@ class ShopPurchasedEventFilter : AnalyticsEventFilter {
override fun canBeSent(event: AnalyticsEvent): Boolean = true
override fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean {
override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean {
return when (handler) {
is AppsFlyerAnalyticsHandler, is FirebaseAnalyticsHandler -> true
else -> false

View file

@ -0,0 +1,21 @@
package com.tangem.tap.common.analytics.paramsInterceptor
import com.tangem.tap.common.analytics.api.ParamsInterceptor
import com.tangem.tap.common.analytics.events.AnalyticsEvent
import com.tangem.tap.common.analytics.events.AnalyticsParam
/**
[REDACTED_AUTHOR]
*/
class BatchIdParamsInterceptor(
val batchId: String,
) : ParamsInterceptor {
override fun id(): String = this::class.java.simpleName
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
override fun intercept(params: MutableMap<String, String>) {
params[AnalyticsParam.BatchId] = batchId
}
}

View file

@ -13,8 +13,8 @@ import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
import com.tangem.tap.common.analytics.GetCardSourceParamsAnOld
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOpenUrl
@ -165,7 +165,7 @@ private fun onScanSuccess(scanResponse: ScanResponse) {
val tapWalletManager = globalState.tapWalletManager
tapWalletManager.updateConfigManager(scanResponse)
Analytics.attachToAllEvents(AnalyticsParam.BatchId, scanResponse.card.batchId)
Analytics.addParamsInterceptor(BatchIdParamsInterceptor(scanResponse.card.batchId))
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))