Updated on 2026-08-14

This commit is contained in:
Tangem 2022-09-23 02:14:22 +03:00
parent d17d9e35bf
commit e97cb41fdb
18 changed files with 468 additions and 228 deletions

View file

@ -14,7 +14,7 @@ import com.tangem.domain.common.LogConfig
import com.tangem.network.common.MoshiConverter import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.AndroidAssetReader import com.tangem.tap.common.AndroidAssetReader
import com.tangem.tap.common.AssetReader import com.tangem.tap.common.AssetReader
import com.tangem.tap.common.analytics.AnalyticsEventHandlerBuilder import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandlerBuilder
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.images.createCoilImageLoader import com.tangem.tap.common.images.createCoilImageLoader
@ -100,7 +100,7 @@ class TapApplication : Application(), ImageLoaderFactory {
} }
private fun initAnalytics(application: Application, config: Config) { private fun initAnalytics(application: Application, config: Config) {
val globalAnalyticsHandler = AnalyticsEventHandlerBuilder( val globalAnalyticsHandler = GlobalAnalyticsEventHandlerBuilder(
application = application, application = application,
config = config, config = config,
isDebug = BuildConfig.DEBUG, isDebug = BuildConfig.DEBUG,

View file

@ -1,66 +0,0 @@
package com.tangem.tap.common.analytics
import android.app.Application
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.domain.common.AnalyticsHandlersLogConfig
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
import com.tangem.tap.domain.configurable.config.Config
/**
[REDACTED_AUTHOR]
*/
class AnalyticsEventHandlerBuilder(
private val application: Application,
private val config: Config,
private val isDebug: Boolean,
private val logConfig: AnalyticsHandlersLogConfig,
private val jsonConverter: MoshiJsonConverter,
) {
private val analyticHandlers = mutableListOf<AnalyticsEventHandler>()
fun build(): GlobalAnalyticsEventHandler {
return GlobalAnalyticsHandler(analyticHandlers.toList())
}
fun default(): GlobalAnalyticsEventHandler {
addFirebaseAnalytics()
addAppsFlyer()
addAmplitude()
return GlobalAnalyticsHandler(analyticHandlers.toList())
}
fun addFirebaseAnalytics(): AnalyticsEventHandlerBuilder {
if (isDebug) {
addEventLogger(logConfig.firebase, FirebaseAnalyticsHandler::class.java.simpleName)
} else {
analyticHandlers.add(FirebaseAnalyticsHandler())
}
return this
}
fun addAppsFlyer(): AnalyticsEventHandlerBuilder {
if (isDebug) {
addEventLogger(logConfig.appsFlyer, AppsFlyerAnalyticsHandler::class.java.simpleName)
} else {
analyticHandlers.add(AppsFlyerAnalyticsHandler(application, config.appsFlyerDevKey))
}
return this
}
fun addAmplitude(): AnalyticsEventHandlerBuilder {
if (isDebug) {
addEventLogger(logConfig.amplitude, AmplitudeAnalyticsHandler::class.java.simpleName)
} else {
analyticHandlers.add(AmplitudeAnalyticsHandler(application, config.amplitudeApiKey))
}
return this
}
private fun addEventLogger(logsEnabled: Boolean, name: String) {
// if (logsEnabled) analyticHandlers.add(AnalyticsLogsPrinter(name, jsonConverter))
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.tap.common.analytics
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.tap.common.analytics.api.ErrorEventLogger
import com.tangem.tap.common.analytics.api.EventLogger
import timber.log.Timber
class AnalyticsEventsLogger(
private val name: String,
private val jsonConverter: MoshiJsonConverter,
) : EventLogger, ErrorEventLogger {
override fun logEvent(event: String, params: Map<String, String>) {
Timber.d(jsonConverter.prettyPrint(PrintEventModel(name, event, params)))
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
Timber.e(error, jsonConverter.prettyPrint(PrintEventModel(name, "error", params)))
}
}
private data class PrintEventModel(
val client: String,
val event: String,
val params: Map<String, String>,
)

View file

@ -0,0 +1,3 @@
package com.tangem.tap.common.analytics

View file

@ -0,0 +1,79 @@
package com.tangem.tap.common.analytics
import android.app.Application
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.domain.common.AnalyticsHandlersLogConfig
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeClient
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeLogClient
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerClient
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerLogClient
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseClient
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseLogClient
import com.tangem.tap.domain.configurable.config.Config
/**
[REDACTED_AUTHOR]
*/
class GlobalAnalyticsEventHandlerBuilder(
val application: Application,
val config: Config,
val isDebug: Boolean,
val logConfig: AnalyticsHandlersLogConfig,
val jsonConverter: MoshiJsonConverter,
) {
private val analyticHandlers = mutableListOf<AnalyticsEventHandler>()
fun build(): GlobalAnalyticsEventHandler {
return GlobalAnalyticsHandler(analyticHandlers.toList())
}
fun default(): GlobalAnalyticsEventHandler {
addFirebaseAnalytics()
addAppsFlyer()
addAmplitude()
return GlobalAnalyticsHandler(analyticHandlers.toList())
}
fun addFirebaseAnalytics(): GlobalAnalyticsEventHandlerBuilder {
if (isDebug) {
if (logConfig.firebase) FirebaseLogClient(createLogger("FirebaseAnalyticsHandler")) else null
} else {
FirebaseClient()
}?.let {
analyticHandlers.add(FirebaseAnalyticsHandler(it))
}
return this
}
fun addAppsFlyer(): GlobalAnalyticsEventHandlerBuilder {
if (isDebug) {
if (logConfig.appsFlyer) AppsFlyerLogClient(createLogger("AppsFlyerAnalyticsHandler")) else null
} else {
AppsFlyerClient(application, config.appsFlyerDevKey)
}?.let {
analyticHandlers.add(AppsFlyerAnalyticsHandler(it))
}
return this
}
fun addAmplitude(): GlobalAnalyticsEventHandlerBuilder {
if (isDebug) {
if (logConfig.firebase) AmplitudeLogClient(createLogger("AmplitudeAnalyticsHandler")) else null
} else {
AmplitudeClient(application, config.amplitudeApiKey)
}?.let {
analyticHandlers.add(AmplitudeAnalyticsHandler(it))
}
return this
}
private fun createLogger(name: String): AnalyticsEventsLogger {
return AnalyticsEventsLogger(name, jsonConverter)
}
}

View file

@ -4,12 +4,19 @@ import com.shopify.buy3.Storefront
import com.tangem.blockchain.common.BlockchainError import com.tangem.blockchain.common.BlockchainError
import com.tangem.common.card.Card import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
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.SdkErrorEventHandler
import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.common.extensions.filterNotNull
interface GlobalAnalyticsEventHandler : AnalyticsEventHandler, interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
ShopifyOrderEventHandler,
ErrorEventHandler, ErrorEventHandler,
SdkErrorEventHandler SdkErrorEventHandler,
ShopifyOrderEventHandler
class GlobalAnalyticsHandler( class GlobalAnalyticsHandler(
private val analyticsHandlers: List<AnalyticsEventHandler>, private val analyticsHandlers: List<AnalyticsEventHandler>,
@ -28,15 +35,9 @@ class GlobalAnalyticsHandler(
analyticsHandlers.forEach { it.handleAnalyticsEvent(event, params, card, blockchain) } analyticsHandlers.forEach { it.handleAnalyticsEvent(event, params, card, blockchain) }
} }
override fun handleShopifyOrderEvent(order: Storefront.Order) {
analyticsHandlers.filterIsInstance<ShopifyOrderEventHandler>().forEach {
it.handleShopifyOrderEvent(order)
}
}
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) { override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {
analyticsHandlers.filterIsInstance<ErrorEventHandler>().forEach { analyticsHandlers.filterIsInstance<ErrorEventLogger>().forEach {
it.handleErrorEvent(error, params) it.logErrorEvent(error, params)
} }
} }
@ -61,6 +62,12 @@ class GlobalAnalyticsHandler(
it.handleBlockchainSdkErrorEvent(error, action, params, card) it.handleBlockchainSdkErrorEvent(error, action, params, card)
} }
} }
override fun handleShopifyOrderEvent(order: Storefront.Order) {
analyticsHandlers.filterIsInstance<ShopifyOrderEventHandler>().forEach {
it.handleShopifyOrderEvent(order)
}
}
} }
fun GlobalAnalyticsEventHandler.logWcEvent(event: Analytics.WcAnalyticsEvent) { fun GlobalAnalyticsEventHandler.logWcEvent(event: Analytics.WcAnalyticsEvent) {

View file

@ -0,0 +1,18 @@
package com.tangem.tap.common.analytics.api
/**
[REDACTED_AUTHOR]
*/
interface EventLogger {
fun logEvent(
event: String,
params: Map<String, String> = emptyMap(),
)
}
interface ErrorEventLogger {
fun logErrorEvent(
error: Throwable,
params: Map<String, String> = emptyMap(),
)
}

View file

@ -1,19 +1,23 @@
package com.tangem.tap.common.analytics package com.tangem.tap.common.analytics.api
import com.shopify.buy3.Storefront import com.shopify.buy3.Storefront
import com.tangem.blockchain.common.BlockchainError import com.tangem.blockchain.common.BlockchainError
import com.tangem.common.card.Card import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.common.extensions.filterNotNull
interface EventHandler { /**
[REDACTED_AUTHOR]
*/
interface AnalyticsEventHandler {
fun handleEvent( fun handleEvent(
event: String, event: String,
params: Map<String, String> = emptyMap(), params: Map<String, String> = emptyMap(),
) )
}
interface AnalyticsEventHandler : EventHandler {
fun handleAnalyticsEvent( fun handleAnalyticsEvent(
event: AnalyticsEvent, event: AnalyticsEvent,
params: Map<String, String> = emptyMap(), params: Map<String, String> = emptyMap(),
@ -25,17 +29,11 @@ interface AnalyticsEventHandler : EventHandler {
card: Card?, card: Card?,
blockchain: String? = null, blockchain: String? = null,
params: Map<String, String> = emptyMap(), params: Map<String, String> = emptyMap(),
): Map<String, String> { ): Map<String, String> = mapOf(
return mapOf( AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue,
AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue, AnalyticsParam.BATCH_ID.param to card?.batchId,
AnalyticsParam.BATCH_ID.param to card?.batchId, AnalyticsParam.BLOCKCHAIN.param to blockchain,
AnalyticsParam.BLOCKCHAIN.param to blockchain, ).filterNotNull() + params
).filterNotNull() + params
}
}
interface ShopifyOrderEventHandler {
fun handleShopifyOrderEvent(order: Storefront.Order)
} }
interface ErrorEventHandler { interface ErrorEventHandler {
@ -64,3 +62,7 @@ interface BlockchainSdkErrorEventHandler {
card: Card? = null, card: Card? = null,
) )
} }
interface ShopifyOrderEventHandler {
fun handleShopifyOrderEvent(order: Storefront.Order)
}

View file

@ -0,0 +1,88 @@
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.Analytics
import com.tangem.tap.common.analytics.AnalyticsParam
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[AnalyticsParam.ACTION.param] = value.action.key
mutableParams[AnalyticsParam.ERROR_CODE.param] = value.error.code.toString()
mutableParams[AnalyticsParam.ERROR_DESCRIPTION.param] = value.error.javaClass.simpleName
mutableParams[AnalyticsParam.ERROR_KEY.param] = "TangemSdkError"
return ErrorEvent(TangemSdk.map(value.error), mutableParams)
}
internal data class Model(
val error: TangemSdkError,
val action: Analytics.ActionToLog,
val params: Map<AnalyticsParam, 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[AnalyticsParam.ACTION.param] = value.action.key
mutableParams[AnalyticsParam.ERROR_CODE.param] = error.code.toString()
mutableParams[AnalyticsParam.ERROR_DESCRIPTION.param] = "${error.javaClass.simpleName}: ${error.customMessage}"
mutableParams[AnalyticsParam.ERROR_KEY.param] = "BlockchainSdkError"
return ErrorEvent(value.error, mutableParams)
}
data class Model(
val error: BlockchainError,
val action: Analytics.ActionToLog,
val params: Map<AnalyticsParam, String>,
val cardParams: Map<String, String>,
)
}
internal fun Map<AnalyticsParam, String>.convertToParamValue(): Map<String, String> {
return mutableMapOf<String, String>().also { newMap ->
this.forEach {
newMap[it.key.param] = it.value
}
}
}

View file

@ -1,28 +1,15 @@
package com.tangem.tap.common.analytics.handlers.amplitude package com.tangem.tap.common.analytics.handlers.amplitude
import android.app.Application
import com.amplitude.api.Amplitude
import com.amplitude.api.AmplitudeClient
import com.tangem.common.Converter
import com.tangem.common.card.Card import com.tangem.common.card.Card
import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import org.json.JSONObject
class AmplitudeAnalyticsHandler( class AmplitudeAnalyticsHandler(
application: Application, private val client: AmplitudeAnalyticsClient,
key: String,
) : AnalyticsEventHandler { ) : AnalyticsEventHandler {
private val client: AmplitudeClient = Amplitude.getInstance()
init {
client.initialize(application, key)
client.enableForegroundTracking(application)
}
override fun handleEvent(event: String, params: Map<String, String>) { override fun handleEvent(event: String, params: Map<String, String>) {
client.logEvent(event, ParamsJSONObjectConverter().convert(params)) client.logEvent(event, params)
} }
override fun handleAnalyticsEvent( override fun handleAnalyticsEvent(
@ -33,11 +20,4 @@ class AmplitudeAnalyticsHandler(
) { ) {
handleEvent(event.event, prepareParams(card, blockchain, params)) handleEvent(event.event, prepareParams(card, blockchain, params))
} }
class ParamsJSONObjectConverter : Converter<Map<String, String>, JSONObject> {
override fun convert(value: Map<String, String>): JSONObject = JSONObject().apply {
value.forEach { this.put(it.key, it.value) }
}
}
} }

View file

@ -0,0 +1,36 @@
package com.tangem.tap.common.analytics.handlers.amplitude
import android.app.Application
import com.amplitude.api.Amplitude
import com.amplitude.api.AmplitudeClient
import com.tangem.common.Converter
import com.tangem.tap.common.analytics.api.EventLogger
import org.json.JSONObject
/**
[REDACTED_AUTHOR]
*/
interface AmplitudeAnalyticsClient : EventLogger
internal class AmplitudeClient(
application: Application,
key: String,
) : AmplitudeAnalyticsClient {
private val client: AmplitudeClient = Amplitude.getInstance()
init {
client.initialize(application, key)
client.enableForegroundTracking(application)
}
override fun logEvent(event: String, params: Map<String, String>) {
client.logEvent(event, ParamsToJSONObjectConverter().convert(params))
}
}
private class ParamsToJSONObjectConverter : Converter<Map<String, String>, JSONObject> {
override fun convert(value: Map<String, String>): JSONObject = JSONObject().apply {
value.forEach { this.put(it.key, it.value) }
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.tap.common.analytics.handlers.amplitude
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
/**
[REDACTED_AUTHOR]
*/
internal class AmplitudeLogClient(
private val logger: AnalyticsEventsLogger,
) : AmplitudeAnalyticsClient {
override fun logEvent(event: String, params: Map<String, String>) {
logger.logEvent(event, params)
}
}

View file

@ -1,30 +1,20 @@
package com.tangem.tap.common.analytics.handlers.appsFlyer package com.tangem.tap.common.analytics.handlers.appsFlyer
import android.content.Context
import com.appsflyer.AFInAppEventParameterName import com.appsflyer.AFInAppEventParameterName
import com.appsflyer.AFInAppEventType import com.appsflyer.AFInAppEventType
import com.appsflyer.AppsFlyerLib
import com.shopify.buy3.Storefront import com.shopify.buy3.Storefront
import com.tangem.common.Converter import com.tangem.common.Converter
import com.tangem.common.card.Card import com.tangem.common.card.Card
import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.ShopifyOrderEventHandler import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
class AppsFlyerAnalyticsHandler( class AppsFlyerAnalyticsHandler(
private val context: Context, private val client: AppsFlyerAnalyticsClient,
key: String,
) : AnalyticsEventHandler, ShopifyOrderEventHandler { ) : AnalyticsEventHandler, ShopifyOrderEventHandler {
private val appsFlyerLib: AppsFlyerLib = AppsFlyerLib.getInstance()
init {
appsFlyerLib.init(key, null, context)
appsFlyerLib.start(context)
}
override fun handleEvent(event: String, params: Map<String, String>) { override fun handleEvent(event: String, params: Map<String, String>) {
appsFlyerLib.logEvent(context, event, params) client.logEvent(event, params)
} }
override fun handleAnalyticsEvent( override fun handleAnalyticsEvent(
@ -43,25 +33,26 @@ class AppsFlyerAnalyticsHandler(
companion object { companion object {
const val ORDER_EVENT = AFInAppEventType.PURCHASE const val ORDER_EVENT = AFInAppEventType.PURCHASE
} }
}
class OrderToParamsConverter : Converter<Storefront.Order, Map<String, String>> { private class OrderToParamsConverter : Converter<Storefront.Order, Map<String, String>> {
override fun convert(value: Storefront.Order): Map<String, String> {
val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
val discountCode = override fun convert(value: Storefront.Order): Map<String, String> {
(value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
val discountParams = if (discountCode != null) { val discountCode =
mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode) (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
} else {
mapOf()
}
return mapOf( val discountParams = if (discountCode != null) {
AFInAppEventParameterName.CONTENT_ID to sku, mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode)
AFInAppEventParameterName.REVENUE to value.totalPriceV2.amount, } else {
AFInAppEventParameterName.CURRENCY to value.currencyCode.name, mapOf()
) + discountParams
} }
return mapOf(
AFInAppEventParameterName.CONTENT_ID to sku,
AFInAppEventParameterName.REVENUE to value.totalPriceV2.amount,
AFInAppEventParameterName.CURRENCY to value.currencyCode.name,
) + discountParams
} }
} }

View file

@ -0,0 +1,27 @@
package com.tangem.tap.common.analytics.handlers.appsFlyer
import android.content.Context
import com.appsflyer.AppsFlyerLib
import com.tangem.tap.common.analytics.api.EventLogger
/**
[REDACTED_AUTHOR]
*/
interface AppsFlyerAnalyticsClient : EventLogger
internal class AppsFlyerClient(
private val context: Context,
key: String,
) : AppsFlyerAnalyticsClient {
private val appsFlyerLib: AppsFlyerLib = AppsFlyerLib.getInstance()
init {
appsFlyerLib.init(key, null, context)
appsFlyerLib.start(context)
}
override fun logEvent(event: String, params: Map<String, String>) {
appsFlyerLib.logEvent(context, event, params)
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.tap.common.analytics.handlers.appsFlyer
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
/**
[REDACTED_AUTHOR]
*/
internal class AppsFlyerLogClient(
private val logger: AnalyticsEventsLogger,
) : AppsFlyerAnalyticsClient {
override fun logEvent(event: String, params: Map<String, String>) {
logger.logEvent(event, params)
}
}

View file

@ -1,35 +1,27 @@
package com.tangem.tap.common.analytics.handlers.firebase package com.tangem.tap.common.analytics.handlers.firebase
import android.os.Bundle
import androidx.core.os.bundleOf
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.shopify.buy3.Storefront import com.shopify.buy3.Storefront
import com.tangem.blockchain.common.BlockchainError import com.tangem.blockchain.common.BlockchainError
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.Converter import com.tangem.common.Converter
import com.tangem.common.card.Card import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsEventHandler
import com.tangem.tap.common.analytics.AnalyticsParam import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.ErrorEventHandler import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.SdkErrorEventHandler import com.tangem.tap.common.analytics.api.ErrorEventHandler
import com.tangem.tap.common.analytics.ShopifyOrderEventHandler import com.tangem.tap.common.analytics.api.SdkErrorEventHandler
import com.tangem.tap.common.analytics.TangemSdk import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter
import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter
class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler, ErrorEventHandler, class FirebaseAnalyticsHandler(
SdkErrorEventHandler { private val client: FirebaseAnalyticsClient,
) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler, ShopifyOrderEventHandler {
private val fbAnalytics = Firebase.analytics
private val fbCrashlytics = Firebase.crashlytics
override fun handleEvent(event: String, params: Map<String, String>) { override fun handleEvent(event: String, params: Map<String, String>) {
fbAnalytics.logEvent(event, params.toBundle()) client.logEvent(event, params)
} }
override fun handleAnalyticsEvent( override fun handleAnalyticsEvent(
@ -38,16 +30,11 @@ class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler
card: Card?, card: Card?,
blockchain: String?, blockchain: String?,
) { ) {
handleEvent(event.event, prepareParams(card, blockchain, params)) handleEvent(event.name, prepareParams(card, blockchain, params))
}
override fun handleShopifyOrderEvent(order: Storefront.Order) {
handleEvent(ORDER_EVENT, OrderToParamsConverter().convert(order))
} }
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) { override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {
params.forEach { (key, value) -> fbCrashlytics.setCustomKey(key, value) } client.logErrorEvent(error, params)
fbCrashlytics.recordException(error)
} }
override fun handleCardSdkErrorEvent( override fun handleCardSdkErrorEvent(
@ -56,18 +43,11 @@ class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler
params: Map<AnalyticsParam, String>, params: Map<AnalyticsParam, String>,
card: Card?, card: Card?,
) { ) {
if (error is TangemSdkError.UserCancelled) return val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card))
val converter = CardSdkErrorConverter()
val mutableParams = params.toMutableMap() converter.convert(model)?.let {
if (card != null) mutableParams + prepareParams(card) handleErrorEvent(it.throwable, it.params)
}
mutableParams[AnalyticsParam.ACTION] = action.key
mutableParams[AnalyticsParam.ERROR_CODE] = error.code.toString()
mutableParams[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName
mutableParams[AnalyticsParam.ERROR_KEY] = "TangemSdkError"
mutableParams.forEach { fbCrashlytics.setCustomKey(it.key.param, it.value) }
fbCrashlytics.recordException(TangemSdk.map(error))
} }
override fun handleBlockchainSdkErrorEvent( override fun handleBlockchainSdkErrorEvent(
@ -76,52 +56,36 @@ class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler
params: Map<AnalyticsParam, String>, params: Map<AnalyticsParam, String>,
card: Card?, card: Card?,
) { ) {
val blockchainSdkError = (error as? BlockchainSdkError) ?: return
if (blockchainSdkError is BlockchainSdkError.WrappedTangemError) { val model = BlockchainSdkErrorConverter.Model(error, action, params, prepareParams(card))
val tangemSdkError = (blockchainSdkError.tangemError as? TangemSdkError) ?: return val converter = BlockchainSdkErrorConverter(CardSdkErrorConverter())
handleCardSdkErrorEvent(tangemSdkError, action, params, card) converter.convert(model)?.let {
return handleErrorEvent(it.throwable, it.params)
} }
if (blockchainSdkError.customMessage.contains(DemoTransactionSender.ID)) return
val mutableParams = params.toMutableMap()
mutableParams[AnalyticsParam.ACTION] = action.key
mutableParams[AnalyticsParam.ERROR_CODE] = error.code.toString()
mutableParams[AnalyticsParam.ERROR_DESCRIPTION] = "${error.javaClass.simpleName}: ${error.customMessage}"
mutableParams[AnalyticsParam.ERROR_KEY] = "BlockchainSdkError"
mutableParams.forEach { fbCrashlytics.setCustomKey(it.key.param, it.value) }
fbCrashlytics.recordException(error)
} }
private fun Map<String, String>.toBundle(): Bundle { override fun handleShopifyOrderEvent(order: Storefront.Order) {
return bundleOf(*this.toList().toTypedArray()) handleEvent(FirebaseClient.ORDER_EVENT, OrderToParamsConverter().convert(order))
} }
}
companion object {
const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE private class OrderToParamsConverter : Converter<Storefront.Order, Map<String, String>> {
} override fun convert(value: Storefront.Order): Map<String, String> {
val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
class OrderToParamsConverter : Converter<Storefront.Order, Map<String, String>> {
override fun convert(value: Storefront.Order): Map<String, String> { val discountCode =
val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown" (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
val discountCode = val discountParams = if (discountCode != null) {
(value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode)
} else {
val discountParams = if (discountCode != null) { mapOf()
mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode) }
} else {
mapOf() return mapOf(
} FirebaseAnalytics.Param.ITEM_ID to sku,
FirebaseAnalytics.Param.VALUE to value.totalPriceV2.amount,
return mapOf( FirebaseAnalytics.Param.CURRENCY to value.currencyCode.name,
FirebaseAnalytics.Param.ITEM_ID to sku, ) + discountParams
FirebaseAnalytics.Param.VALUE to value.totalPriceV2.amount,
FirebaseAnalytics.Param.CURRENCY to value.currencyCode.name,
) + discountParams
}
} }
} }

View file

@ -0,0 +1,36 @@
package com.tangem.tap.common.analytics.handlers.firebase
import android.os.Bundle
import androidx.core.os.bundleOf
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.tangem.tap.common.analytics.api.ErrorEventLogger
import com.tangem.tap.common.analytics.api.EventLogger
/**
[REDACTED_AUTHOR]
*/
interface FirebaseAnalyticsClient : EventLogger, ErrorEventLogger
internal class FirebaseClient : FirebaseAnalyticsClient {
private val fbAnalytics = Firebase.analytics
private val fbCrashlytics = Firebase.crashlytics
override fun logEvent(event: String, params: Map<String, String>) {
fbAnalytics.logEvent(event, params.toBundle())
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
params.forEach { fbCrashlytics.setCustomKey(it.key, it.value) }
fbCrashlytics.recordException(error)
}
private fun Map<String, String>.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray())
companion object {
const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.tap.common.analytics.handlers.firebase
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
/**
[REDACTED_AUTHOR]
*/
internal class FirebaseLogClient(
private val logger: AnalyticsEventsLogger,
) : FirebaseAnalyticsClient {
override fun logEvent(event: String, params: Map<String, String>) {
logger.logEvent(event, params)
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
logger.logErrorEvent(error, params)
}
}