Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-09 06:08:40 +05:00
parent fec0f14952
commit 5030c571c5
32 changed files with 298 additions and 658 deletions

View file

@ -1,22 +1,14 @@
package com.tangem.tap.common.analytics
import com.tangem.blockchain.common.BlockchainError
import com.tangem.common.card.Card
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.AnalyticsHandler
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
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@ -28,33 +20,26 @@ 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)
}
ParamsInterceptorHolder
object Analytics : GlobalAnalyticsEventHandler {
private val analyticsScope: CoroutineScope by lazy { createScope() }
private val handlers = mutableMapOf<String, AnalyticsEventHandler>()
private val analyticsFilters = mutableSetOf<AnalyticsEventFilter>()
private val handlers = mutableMapOf<String, AnalyticsHandler>()
private val paramsInterceptors = mutableMapOf<String, ParamsInterceptor>()
private val analyticsFilters = mutableSetOf<AnalyticsEventFilter>()
private val analyticsHandlers: List<AnalyticsEventHandler>
private val analyticsHandlers: List<AnalyticsHandler>
get() = handlers.values.toList()
override fun id(): String = analyticsHandlers.joinToString(", ") { it.id() }
override fun addHandler(name: String, handler: AnalyticsEventHandler) {
override fun addHandler(name: String, handler: AnalyticsHandler) {
handlers[name] = handler
}
override fun removeHandler(name: String): AnalyticsEventHandler? {
override fun removeHandler(name: String): AnalyticsHandler? {
return handlers.remove(name)
}
@ -74,84 +59,29 @@ object Analytics : GlobalAnalyticsEventHandler {
return paramsInterceptors.remove(interceptor.id())
}
override fun send(event: String, params: Map<String, String>) {
override fun send(event: AnalyticsEvent) {
analyticsScope.launch {
analyticsHandlers.forEach { it.send(event, params.interceptParams()) }
}
}
override fun send(event: AnalyticsEvent, card: Card?, blockchain: String?) {
analyticsScope.launch {
val eventString = prepareEventString(event.category, event.event)
val eventParams = prepareParams(card, blockchain, event.interceptParams())
event.params = applyParamsInterceptors(event)
val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) }
when {
eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(eventString, eventParams) }
eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(event) }
eventFilter.canBeSent(event) -> {
analyticsHandlers
.filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) }
.forEach { handler -> handler.send(eventString, eventParams) }
.forEach { handler -> handler.send(event) }
}
}
}
}
override fun handleAnalyticsEvent(
event: AnalyticsEventAnOld,
params: Map<String, String>,
card: Card?,
blockchain: String?,
) {
analyticsScope.launch {
analyticsHandlers.forEach {
it.handleAnalyticsEvent(event, params.interceptParams(), card, blockchain)
}
}
}
private fun applyParamsInterceptors(event: AnalyticsEvent): MutableMap<String, String> {
val interceptedParams = event.params.toMutableMap()
paramsInterceptors.values
.filter { it.canBeAppliedTo(event) }
.forEach { it.intercept(interceptedParams) }
override fun send(error: Throwable, params: Map<String, String>) {
analyticsScope.launch {
analyticsHandlers.filterIsInstance<ErrorEventLogger>().forEach {
it.logErrorEvent(error, params)
}
}
}
override fun send(
error: TangemSdkError,
action: AnalyticsAnOld.ActionToLog,
params: Map<AnalyticsParamAnOld, String>,
card: Card?,
) {
analyticsScope.launch {
analyticsHandlers.filterIsInstance<CardSdkErrorEventHandler>().forEach {
it.send(error, action, params, card)
}
}
}
override fun send(
error: BlockchainError,
action: AnalyticsAnOld.ActionToLog,
params: Map<AnalyticsParamAnOld, String>,
card: Card?,
) {
analyticsScope.launch {
analyticsHandlers.filterIsInstance<BlockchainSdkErrorEventHandler>().forEach {
it.send(error, action, params, card)
}
}
}
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) }
return interceptedParams
}
private fun createScope(): CoroutineScope {
@ -160,43 +90,4 @@ object Analytics : GlobalAnalyticsEventHandler {
val exHandler = FeatureCoroutineExceptionHandler.create(name)
return CoroutineScope(Job() + dispatcher + CoroutineName(name) + exHandler)
}
}
fun GlobalAnalyticsEventHandler.logWcEvent(event: AnalyticsAnOld.WcAnalyticsEvent) {
when (event) {
is AnalyticsAnOld.WcAnalyticsEvent.Action -> {
handleAnalyticsEvent(
event = AnalyticsEventAnOld.WC_SUCCESS_RESPONSE,
params = mapOf(
AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action.name,
),
)
}
is AnalyticsAnOld.WcAnalyticsEvent.Error -> {
val params = mapOf(
AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action?.name,
AnalyticsParamAnOld.ERROR_DESCRIPTION.param to event.error.message,
).filterNotNull()
send(event.error, params)
}
is AnalyticsAnOld.WcAnalyticsEvent.InvalidRequest ->
handleAnalyticsEvent(
event = AnalyticsEventAnOld.WC_INVALID_REQUEST,
params = mapOf(
AnalyticsParamAnOld.WALLET_CONNECT_REQUEST.param to event.json,
).filterNotNull(),
)
is AnalyticsAnOld.WcAnalyticsEvent.Session -> {
val analyticsEvent = when (event.event) {
AnalyticsAnOld.WcSessionEvent.Disconnect -> AnalyticsEventAnOld.WC_SESSION_DISCONNECTED
AnalyticsAnOld.WcSessionEvent.Connect -> AnalyticsEventAnOld.WC_NEW_SESSION
}
handleAnalyticsEvent(
event = analyticsEvent,
params = mapOf(
AnalyticsParamAnOld.WALLET_CONNECT_DAPP_URL.param to event.url,
).filterNotNull(),
)
}
}
}

View file

@ -1,71 +0,0 @@
package com.tangem.tap.common.analytics
enum class AnalyticsEventAnOld(val event: String) {
CARD_IS_SCANNED("card_is_scanned"),
TRANSACTION_IS_SENT("transaction_is_sent"),
READY_TO_SCAN("ready_to_scan"),
DEMO_MODE_ACTIVATED("demo_mode_activated"),
APP_RATING_DISPLAYED("rate_app_warning_displayed"),
APP_RATING_DISMISS("dismiss_rate_app_warning"),
APP_RATING_NEGATIVE("negative_rate_app_feedback"),
APP_RATING_POSITIVE("positive_rate_app_feedback"),
WC_SUCCESS_RESPONSE("wallet_connect_success_response"),
WC_INVALID_REQUEST("wallet_connect_invalid_request"),
WC_NEW_SESSION("wallet_connect_new_session"),
WC_SESSION_DISCONNECTED("wallet_connect_session_disconnected"),
GET_CARD("get_card"),
}
enum class AnalyticsParamAnOld(val param: String) {
BLOCKCHAIN("blockchain"),
CARD_ID("cardId"),
BATCH_ID("batch_id"),
FIRMWARE("firmware"),
ACTION("action"),
ERROR_DESCRIPTION("error_description"),
ERROR_CODE("error_code"),
NEW_SECURITY_OPTION("new_security_option"),
ERROR_KEY("Tangem SDK error key"),
WALLET_CONNECT_ACTION("wallet_connect_action"),
WALLET_CONNECT_REQUEST("wallet_connect_request"),
WALLET_CONNECT_DAPP_URL("wallet_connect_dapp_url"),
SOURCE("source"),
}
enum class GetCardSourceParamsAnOld(val param: String) {
WELCOME("welcome"),
ONBOARDING_BUY_MORE("wallet_onboaring_buy_more_cards"),
ONBOARDING("wallet_onboarding")
}
object AnalyticsAnOld {
enum class ActionToLog(val key: String) {
Scan("tap_scan_task"),
SendTransaction("send_transaction"),
WalletConnectSign("wallet_connect_personal_sign"),
WalletConnectTransaction("wallet_connect_tx_sign"),
ReadPinSettings("read_pin_settings"),
ChangeSecOptions("change_sec_options"),
CreateWallet("create_wallet"),
PurgeWallet("purge_wallet"),
WriteIssuerData("write_issuer_data"),
}
enum class WcSessionEvent { Disconnect, Connect }
enum class WcAction { PersonalSign, SignTransaction, SendTransaction }
sealed class WcAnalyticsEvent {
data class Error(val error: Throwable, val action: WcAction?) :
WcAnalyticsEvent()
data class Session(val event: WcSessionEvent, val url: String?) : WcAnalyticsEvent()
data class Action(val action: WcAction) : WcAnalyticsEvent()
data class InvalidRequest(val json: String?) : WcAnalyticsEvent()
}
}

View file

@ -0,0 +1,37 @@
package com.tangem.tap.common.analytics
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.api.ParamsInterceptor
/**
[REDACTED_AUTHOR]
*/
class AnalyticsFactory {
private val builders = mutableListOf<AnalyticsHandlerBuilder>()
private val filters = mutableListOf<AnalyticsEventFilter>()
private val interceptors = mutableListOf<ParamsInterceptor>()
fun addHandlerBuilder(builder: AnalyticsHandlerBuilder) {
builders.add(builder)
}
fun addFilter(filter: AnalyticsEventFilter) {
filters.add(filter)
}
fun addParamsInterceptor(interceptor: ParamsInterceptor) {
interceptors.add(interceptor)
}
fun build(analytics: Analytics, data: AnalyticsHandlerBuilder.Data) {
builders.mapNotNull { it.build(data) }.forEach { analytics.addHandler(it.id(), it) }
filters.forEach { analytics.addFilter(it) }
interceptors.forEach { analytics.addParamsInterceptor(it) }
builders.clear()
filters.clear()
interceptors.clear()
}
}

View file

@ -1,23 +0,0 @@
package com.tangem.tap.common.analytics
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
/**
[REDACTED_AUTHOR]
*/
class AnalyticsHandlersFactory {
private val builders = mutableListOf<AnalyticsHandlerBuilder>()
fun addBuilder(builder: AnalyticsHandlerBuilder) {
builders.add(builder)
}
fun build(data: AnalyticsHandlerBuilder.Data): List<AnalyticsEventHandler> {
val handlers = builders.mapNotNull { it.build(data) }
builders.clear()
return handlers
}
}

View file

@ -1,37 +0,0 @@
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

@ -21,7 +21,7 @@ interface AnalyticsEventFilter {
/**
* Performs a check to see if the event can be dispatched by a specific handler
*/
fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean
fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean
}
interface AnalyticsFilterHolder {

View file

@ -1,53 +1,44 @@
package com.tangem.tap.common.analytics.api
import android.app.Application
import com.tangem.common.card.Card
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.domain.common.AnalyticsHandlersLogConfig
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.common.analytics.events.AnalyticsEvent
import com.tangem.tap.domain.configurable.config.Config
/**
[REDACTED_AUTHOR]
*/
interface AnalyticsEventHandler {
fun send(event: AnalyticsEvent)
}
interface AnalyticsHandler : AnalyticsEventHandler {
fun id(): String
fun send(event: String, params: Map<String, String> = emptyMap())
@Deprecated("Migrate to AnalyticsEvent")
fun handleAnalyticsEvent(
event: AnalyticsEventAnOld,
params: Map<String, String> = emptyMap(),
card: Card? = null,
blockchain: String? = null,
) {
send(event.event, prepareParams(card, blockchain, params))
override fun send(event: AnalyticsEvent) {
send(prepareEventString(event), event.params)
}
fun prepareParams(
card: Card? = null,
blockchain: String? = null,
params: Map<String, String> = emptyMap(),
): Map<String, String> = mapOf(
AnalyticsParam.Firmware to card?.firmwareVersion?.stringValue,
AnalyticsParam.Blockchain to blockchain,
).filterNotNull() + params
fun prepareEventString(event: AnalyticsEvent): String = "[${event.category}] ${event.event}"
}
fun prepareEventString(category: String, event: String): String {
return "[$category] $event"
}
interface ErrorEventHandler {
fun send(
error: Throwable,
params: Map<String, String> = emptyMap(),
)
}
interface AnalyticsHandlerHolder {
fun addHandler(name: String, handler: AnalyticsEventHandler)
fun removeHandler(name: String): AnalyticsEventHandler?
fun addHandler(name: String, handler: AnalyticsHandler)
fun removeHandler(name: String): AnalyticsHandler?
}
interface AnalyticsHandlerBuilder {
fun build(data: Data): AnalyticsEventHandler?
fun build(data: Data): AnalyticsHandler?
data class Data(
val application: Application,

View file

@ -0,0 +1,70 @@
package com.tangem.tap.common.analytics.converters
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.Converter
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.features.demo.DemoTransactionSender
/**
[REDACTED_AUTHOR]
*/
class AnalyticsErrorConverter : Converter<Throwable, Map<String, String>> {
fun canBeHandled(error: Throwable): Boolean {
return when (error) {
is TangemSdkError.UserCancelled -> false
is BlockchainSdkError -> !error.customMessage.contains(DemoTransactionSender.ID)
else -> true
}
}
override fun convert(value: Throwable): Map<String, String> {
return when (value) {
is BlockchainSdkError -> BlockchainSdkErrorConverter(CardSdkErrorConverter()).convert(value)
is TangemSdkError -> CardSdkErrorConverter().convert(value)
else -> ThrowableErrorConverter().convert(value)
}
}
}
private class ThrowableErrorConverter : Converter<Throwable, Map<String, String>> {
override fun convert(value: Throwable): Map<String, String> {
val unknown = "unknown"
return mapOf(
AnalyticsParam.ErrorKey to AnalyticsParam.Error.App.value,
AnalyticsParam.ErrorDescription to (value.message ?: value.cause?.message ?: unknown),
)
}
}
class CardSdkErrorConverter : Converter<TangemSdkError, Map<String, String>> {
override fun convert(value: TangemSdkError): Map<String, String> {
if (value is TangemSdkError.UserCancelled) return emptyMap()
return mapOf(
AnalyticsParam.ErrorKey to AnalyticsParam.Error.CardSdk.value,
AnalyticsParam.ErrorCode to value.code.toString(),
AnalyticsParam.ErrorDescription to value.toString(),
)
}
}
private class BlockchainSdkErrorConverter(
private val cardSdkErrorConverter: CardSdkErrorConverter,
) : Converter<BlockchainSdkError, Map<String, String>> {
override fun convert(value: BlockchainSdkError): Map<String, String> {
if (value.customMessage.contains(DemoTransactionSender.ID)) return emptyMap()
if (value is BlockchainSdkError.WrappedTangemError) {
return (value.tangemError as? TangemSdkError)?.let { cardSdkErrorConverter.convert(it) } ?: emptyMap()
}
return mapOf(
AnalyticsParam.ErrorKey to AnalyticsParam.Error.BlockchainSdk.value,
AnalyticsParam.ErrorCode to value.code.toString(),
AnalyticsParam.ErrorDescription to "${value.javaClass.simpleName}: ${value.customMessage}",
)
}
}

View file

@ -1,88 +0,0 @@
package com.tangem.tap.common.analytics.converters
import com.tangem.blockchain.common.BlockchainError
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.Converter
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.AnalyticsAnOld
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
import com.tangem.tap.common.analytics.TangemSdk
import com.tangem.tap.features.demo.DemoTransactionSender
/**
[REDACTED_AUTHOR]
*/
internal data class ErrorEvent(
val throwable: Throwable,
val params: Map<String, String>,
)
internal class CardSdkErrorConverter : Converter<CardSdkErrorConverter.Model, ErrorEvent?> {
override fun convert(value: Model): ErrorEvent? {
if (value.error is TangemSdkError.UserCancelled) return null
val mutableParams = value.params.convertToParamValue().toMutableMap().apply {
putAll(value.cardParams)
}
mutableParams[AnalyticsParamAnOld.ACTION.param] = value.action.key
mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = value.error.code.toString()
mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = value.error.javaClass.simpleName
mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "TangemSdkError"
return ErrorEvent(TangemSdk.map(value.error), mutableParams)
}
internal data class Model(
val error: TangemSdkError,
val action: AnalyticsAnOld.ActionToLog,
val params: Map<AnalyticsParamAnOld, String>,
val cardParams: Map<String, String>,
)
}
internal class BlockchainSdkErrorConverter(
private val cardSdkErrorConverter: CardSdkErrorConverter,
) : Converter<BlockchainSdkErrorConverter.Model, ErrorEvent?> {
override fun convert(value: Model): ErrorEvent? {
val error = (value.error as? BlockchainSdkError) ?: return null
if (error is BlockchainSdkError.WrappedTangemError) {
val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return null
val cardSdkModel = CardSdkErrorConverter.Model(
error = tangemSdkError,
action = value.action,
params = value.params,
cardParams = value.cardParams,
)
return cardSdkErrorConverter.convert(cardSdkModel)
}
if (error.customMessage.contains(DemoTransactionSender.ID)) return null
val mutableParams = value.params.convertToParamValue().toMutableMap()
mutableParams[AnalyticsParamAnOld.ACTION.param] = value.action.key
mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = error.code.toString()
mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = "${error.javaClass.simpleName}: ${error.customMessage}"
mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "BlockchainSdkError"
return ErrorEvent(value.error, mutableParams)
}
data class Model(
val error: BlockchainError,
val action: AnalyticsAnOld.ActionToLog,
val params: Map<AnalyticsParamAnOld, String>,
val cardParams: Map<String, String>,
)
}
internal fun Map<AnalyticsParamAnOld, String>.convertToParamValue(): Map<String, String> {
return mutableMapOf<String, String>().also { newMap ->
this.forEach {
newMap[it.key.param] = it.value
}
}
}

View file

@ -6,6 +6,7 @@ package com.tangem.tap.common.analytics.events
sealed class AnalyticsEvent(
val category: String,
val event: String,
val params: Map<String, String> = mapOf(),
var params: Map<String, String> = mapOf(),
val error: Throwable? = null,
var filterData: Any? = null,
)

View file

@ -54,11 +54,16 @@ sealed class AnalyticsParam {
}
}
sealed class Error(val value: String) {
object App : Error("App Error")
object CardSdk : Error("Card Sdk Error")
object BlockchainSdk : Error("Blockchain Sdk Error")
}
companion object Key {
const val BatchId = "Batch"
// old keys
const val Blockchain = "blockchain"
const val Firmware = "firmware"
const val ErrorDescription = "Error Description"
const val ErrorCode = "Error Code"
const val ErrorKey = "Error Key"
}
}

View file

@ -6,7 +6,8 @@ package com.tangem.tap.common.analytics.events
sealed class Basic(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Basic", event, params) {
error: Throwable? = null,
) : AnalyticsEvent("Basic", event, params, error) {
class SignedIn(
state: AnalyticsParam.CardBalanceState,
@ -25,4 +26,9 @@ sealed class Basic(
event = "Topped up",
params = mapOf("Currency" to currency.value),
)
class ScanError(error: Throwable) : Basic(
event = "Scan",
error = error,
)
}

View file

@ -9,7 +9,8 @@ sealed class Settings(
category: String = "Settings",
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category, event, params) {
error: Throwable? = null,
) : AnalyticsEvent(category, event, params, error) {
class ScreenOpened : Settings(event = "Settings Screen Opened")
class ButtonChat : Settings(event = "Button - Chat")
@ -28,10 +29,15 @@ sealed class Settings(
sealed class CardSettings(
event: String,
params: Map<String, String> = mapOf(),
) : Settings("Settings / Card Settings", event, params) {
error: Throwable? = null,
) : Settings("Settings / Card Settings", event, params, error) {
class ButtonFactoryReset : CardSettings("Button - Factory Reset")
class FactoryResetFinished : CardSettings("Factory Reset Finished")
class FactoryResetFinished(error: Throwable? = null) : CardSettings(
event = "Factory Reset Finished",
error = error,
)
class UserCodeChanged : CardSettings("User Code Changed")
class ButtonChangeSecurityMode : CardSettings("Button - Change Security Mode")
@ -40,9 +46,10 @@ sealed class Settings(
params = mapOf("Type" to type.value),
)
class SecurityModeChanged(mode: AnalyticsParam.SecurityMode) : CardSettings(
class SecurityModeChanged(mode: AnalyticsParam.SecurityMode, error: Throwable? = null) : CardSettings(
event = "Security Mode Changed",
params = mapOf("Mode" to mode.value),
error = error,
)
}

View file

@ -9,7 +9,8 @@ sealed class Token(
category: String,
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category, event, params) {
error: Throwable? = null,
) : AnalyticsEvent(category, event, params, error) {
class Refreshed : Token("Token", "Refreshed")
class ButtonExplore : Token("Token", "Button - Explore")
@ -57,16 +58,18 @@ sealed class Token(
sealed class Send(
event: String,
params: Map<String, String> = mapOf(),
) : Token("Token / Send", event, params) {
error: Throwable? = null,
) : Token("Token / Send", event, params, error) {
class ScreenOpened : Send("Send Screen Opened")
class ButtonPaste : Send("Button - Paste")
class ButtonQRCode : Send("Button - QR Code")
class ButtonSwapCurrency : Send("Button - Swap Currency")
class TransactionSent(type: CurrencyType) : Send(
class TransactionSent(type: CurrencyType, error: Throwable? = null) : Send(
event = "Transaction Sent",
params = mapOf("Token" to type.value),
error = error,
)
}

View file

@ -6,9 +6,13 @@ package com.tangem.tap.common.analytics.events
sealed class WalletConnect(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Wallet Connect", event, params) {
error: Throwable? = null,
) : AnalyticsEvent("Wallet Connect", event, params, error) {
class NewSessionEstablished : WalletConnect("New Session Established")
class SessionDisconnected : WalletConnect("Session Disconnected")
class RequestSigned : WalletConnect("Request Signed")
class SignError(error: Throwable) : WalletConnect("Sign", error = error)
class TransactionError(error: Throwable) : WalletConnect("Transaction", error = error)
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.common.analytics.filters
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandler
import com.tangem.tap.common.analytics.events.AnalyticsEvent
import com.tangem.tap.common.analytics.events.Basic
@ -23,5 +23,5 @@ class BasicSignInFilter : AnalyticsEventFilter {
return canBeSent
}
override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean = true
}

View file

@ -2,7 +2,7 @@ package com.tangem.tap.common.analytics.filters
import com.tangem.common.extensions.guard
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandler
import com.tangem.tap.common.analytics.events.AnalyticsEvent
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
@ -39,7 +39,7 @@ class BasicTopUpFilter(
}
}
override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean = true
data class Data(
val walletId: String,

View file

@ -1,7 +1,7 @@
package com.tangem.tap.common.analytics.filters
import com.tangem.tap.common.analytics.api.AnalyticsEventFilter
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandler
import com.tangem.tap.common.analytics.events.AnalyticsEvent
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
@ -16,7 +16,7 @@ class ShopPurchasedEventFilter : AnalyticsEventFilter {
override fun canBeSent(event: AnalyticsEvent): Boolean = true
override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean {
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean {
return when (handler) {
is AppsFlyerAnalyticsHandler, is FirebaseAnalyticsHandler -> true
else -> false

View file

@ -1,11 +1,11 @@
package com.tangem.tap.common.analytics.handlers.amplitude
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
class AmplitudeAnalyticsHandler(
private val client: AmplitudeAnalyticsClient,
) : AnalyticsEventHandler {
) : AnalyticsHandler {
override fun id(): String = ID
@ -18,7 +18,7 @@ class AmplitudeAnalyticsHandler(
}
class Builder : AnalyticsHandlerBuilder {
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when {
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
!data.isDebug -> AmplitudeClient(data.application, data.config.amplitudeApiKey)
data.isDebug && data.logConfig.amplitude -> AmplitudeLogClient(data.jsonConverter)
else -> null

View file

@ -1,11 +1,11 @@
package com.tangem.tap.common.analytics.handlers.appsFlyer
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
class AppsFlyerAnalyticsHandler(
private val client: AppsFlyerAnalyticsClient,
) : AnalyticsEventHandler {
) : AnalyticsHandler {
override fun id(): String = ID
@ -18,7 +18,7 @@ class AppsFlyerAnalyticsHandler(
}
class Builder : AnalyticsHandlerBuilder {
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when {
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
!data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerDevKey)
data.isDebug && data.logConfig.appsFlyer -> AppsFlyerLogClient(data.jsonConverter)
else -> null

View file

@ -1,20 +1,14 @@
package com.tangem.tap.common.analytics.handlers.firebase
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
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.api.ErrorEventHandler
import com.tangem.tap.common.analytics.api.SdkErrorEventHandler
import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter
import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter
import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter
import com.tangem.tap.common.analytics.events.AnalyticsEvent
class FirebaseAnalyticsHandler(
private val client: FirebaseAnalyticsClient,
) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler {
) : AnalyticsHandler, ErrorEventHandler {
override fun id(): String = ID
@ -22,43 +16,32 @@ class FirebaseAnalyticsHandler(
client.logEvent(event, params)
}
override fun send(event: AnalyticsEvent) {
when (event.error) {
null -> super.send(event)
else -> {
val errorConverter = AnalyticsErrorConverter()
if (!errorConverter.canBeHandled(event.error)) return
val errorParams = errorConverter.convert(event.error).toMutableMap()
errorParams["Category"] = event.category
errorParams["Event"] = event.event
errorParams.putAll(event.params)
send(event.error, errorParams)
}
}
}
override fun send(error: Throwable, params: Map<String, String>) {
client.logErrorEvent(error, params)
}
override fun send(
error: TangemSdkError,
action: AnalyticsAnOld.ActionToLog,
params: Map<AnalyticsParamAnOld, String>,
card: Card?,
) {
val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card))
val converter = CardSdkErrorConverter()
converter.convert(model)?.let {
send(it.throwable, it.params)
}
}
override fun send(
error: BlockchainError,
action: AnalyticsAnOld.ActionToLog,
params: Map<AnalyticsParamAnOld, String>,
card: Card?,
) {
val model = BlockchainSdkErrorConverter.Model(error, action, params, prepareParams(card))
val converter = BlockchainSdkErrorConverter(CardSdkErrorConverter())
converter.convert(model)?.let {
send(it.throwable, it.params)
}
}
companion object {
const val ID = "Firebase"
}
class Builder : AnalyticsHandlerBuilder {
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when {
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
!data.isDebug -> FirebaseClient()
data.isDebug && data.logConfig.firebase -> FirebaseLogClient(data.jsonConverter)
else -> null