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.tap.common.AndroidAssetReader
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.FeedbackManager
import com.tangem.tap.common.images.createCoilImageLoader
@ -100,7 +100,7 @@ class TapApplication : Application(), ImageLoaderFactory {
}
private fun initAnalytics(application: Application, config: Config) {
val globalAnalyticsHandler = AnalyticsEventHandlerBuilder(
val globalAnalyticsHandler = GlobalAnalyticsEventHandlerBuilder(
application = application,
config = config,
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.common.card.Card
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
interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
ShopifyOrderEventHandler,
ErrorEventHandler,
SdkErrorEventHandler
SdkErrorEventHandler,
ShopifyOrderEventHandler
class GlobalAnalyticsHandler(
private val analyticsHandlers: List<AnalyticsEventHandler>,
@ -28,15 +35,9 @@ class GlobalAnalyticsHandler(
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>) {
analyticsHandlers.filterIsInstance<ErrorEventHandler>().forEach {
it.handleErrorEvent(error, params)
analyticsHandlers.filterIsInstance<ErrorEventLogger>().forEach {
it.logErrorEvent(error, params)
}
}
@ -61,6 +62,12 @@ class GlobalAnalyticsHandler(
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) {

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.tangem.blockchain.common.BlockchainError
import com.tangem.common.card.Card
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
interface EventHandler {
/**
[REDACTED_AUTHOR]
*/
interface AnalyticsEventHandler {
fun handleEvent(
event: String,
params: Map<String, String> = emptyMap(),
)
}
interface AnalyticsEventHandler : EventHandler {
fun handleAnalyticsEvent(
event: AnalyticsEvent,
params: Map<String, String> = emptyMap(),
@ -25,17 +29,11 @@ interface AnalyticsEventHandler : EventHandler {
card: Card?,
blockchain: String? = null,
params: Map<String, String> = emptyMap(),
): Map<String, String> {
return mapOf(
AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue,
AnalyticsParam.BATCH_ID.param to card?.batchId,
AnalyticsParam.BLOCKCHAIN.param to blockchain,
).filterNotNull() + params
}
}
interface ShopifyOrderEventHandler {
fun handleShopifyOrderEvent(order: Storefront.Order)
): Map<String, String> = mapOf(
AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue,
AnalyticsParam.BATCH_ID.param to card?.batchId,
AnalyticsParam.BLOCKCHAIN.param to blockchain,
).filterNotNull() + params
}
interface ErrorEventHandler {
@ -63,4 +61,8 @@ interface BlockchainSdkErrorEventHandler {
params: Map<AnalyticsParam, String> = mapOf(),
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
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.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsEventHandler
import org.json.JSONObject
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
class AmplitudeAnalyticsHandler(
application: Application,
key: String,
private val client: AmplitudeAnalyticsClient,
) : AnalyticsEventHandler {
private val client: AmplitudeClient = Amplitude.getInstance()
init {
client.initialize(application, key)
client.enableForegroundTracking(application)
}
override fun handleEvent(event: String, params: Map<String, String>) {
client.logEvent(event, ParamsJSONObjectConverter().convert(params))
client.logEvent(event, params)
}
override fun handleAnalyticsEvent(
@ -33,11 +20,4 @@ class AmplitudeAnalyticsHandler(
) {
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
import android.content.Context
import com.appsflyer.AFInAppEventParameterName
import com.appsflyer.AFInAppEventType
import com.appsflyer.AppsFlyerLib
import com.shopify.buy3.Storefront
import com.tangem.common.Converter
import com.tangem.common.card.Card
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsEventHandler
import com.tangem.tap.common.analytics.ShopifyOrderEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
class AppsFlyerAnalyticsHandler(
private val context: Context,
key: String,
private val client: AppsFlyerAnalyticsClient,
) : 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>) {
appsFlyerLib.logEvent(context, event, params)
client.logEvent(event, params)
}
override fun handleAnalyticsEvent(
@ -43,25 +33,26 @@ class AppsFlyerAnalyticsHandler(
companion object {
const val ORDER_EVENT = AFInAppEventType.PURCHASE
}
}
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"
private class OrderToParamsConverter : Converter<Storefront.Order, Map<String, String>> {
val discountCode =
(value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
override fun convert(value: Storefront.Order): Map<String, String> {
val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
val discountParams = if (discountCode != null) {
mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode)
} else {
mapOf()
}
val discountCode =
(value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
return mapOf(
AFInAppEventParameterName.CONTENT_ID to sku,
AFInAppEventParameterName.REVENUE to value.totalPriceV2.amount,
AFInAppEventParameterName.CURRENCY to value.currencyCode.name,
) + discountParams
val discountParams = if (discountCode != null) {
mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode)
} else {
mapOf()
}
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
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.shopify.buy3.Storefront
import com.tangem.blockchain.common.BlockchainError
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.Converter
import com.tangem.common.card.Card
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.AnalyticsEventHandler
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.ErrorEventHandler
import com.tangem.tap.common.analytics.SdkErrorEventHandler
import com.tangem.tap.common.analytics.ShopifyOrderEventHandler
import com.tangem.tap.common.analytics.TangemSdk
import com.tangem.tap.features.demo.DemoTransactionSender
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.api.ErrorEventHandler
import com.tangem.tap.common.analytics.api.SdkErrorEventHandler
import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter
import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter
class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler, ErrorEventHandler,
SdkErrorEventHandler {
private val fbAnalytics = Firebase.analytics
private val fbCrashlytics = Firebase.crashlytics
class FirebaseAnalyticsHandler(
private val client: FirebaseAnalyticsClient,
) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler, ShopifyOrderEventHandler {
override fun handleEvent(event: String, params: Map<String, String>) {
fbAnalytics.logEvent(event, params.toBundle())
client.logEvent(event, params)
}
override fun handleAnalyticsEvent(
@ -38,16 +30,11 @@ class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler
card: Card?,
blockchain: String?,
) {
handleEvent(event.event, prepareParams(card, blockchain, params))
}
override fun handleShopifyOrderEvent(order: Storefront.Order) {
handleEvent(ORDER_EVENT, OrderToParamsConverter().convert(order))
handleEvent(event.name, prepareParams(card, blockchain, params))
}
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {
params.forEach { (key, value) -> fbCrashlytics.setCustomKey(key, value) }
fbCrashlytics.recordException(error)
client.logErrorEvent(error, params)
}
override fun handleCardSdkErrorEvent(
@ -56,18 +43,11 @@ class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler
params: Map<AnalyticsParam, String>,
card: Card?,
) {
if (error is TangemSdkError.UserCancelled) return
val mutableParams = params.toMutableMap()
if (card != null) mutableParams + prepareParams(card)
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))
val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card))
val converter = CardSdkErrorConverter()
converter.convert(model)?.let {
handleErrorEvent(it.throwable, it.params)
}
}
override fun handleBlockchainSdkErrorEvent(
@ -76,52 +56,36 @@ class FirebaseAnalyticsHandler : AnalyticsEventHandler, ShopifyOrderEventHandler
params: Map<AnalyticsParam, String>,
card: Card?,
) {
val blockchainSdkError = (error as? BlockchainSdkError) ?: return
if (blockchainSdkError is BlockchainSdkError.WrappedTangemError) {
val tangemSdkError = (blockchainSdkError.tangemError as? TangemSdkError) ?: return
handleCardSdkErrorEvent(tangemSdkError, action, params, card)
return
val model = BlockchainSdkErrorConverter.Model(error, action, params, prepareParams(card))
val converter = BlockchainSdkErrorConverter(CardSdkErrorConverter())
converter.convert(model)?.let {
handleErrorEvent(it.throwable, it.params)
}
}
override fun handleShopifyOrderEvent(order: Storefront.Order) {
handleEvent(FirebaseClient.ORDER_EVENT, OrderToParamsConverter().convert(order))
}
}
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 =
(value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
val discountParams = if (discountCode != null) {
mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode)
} else {
mapOf()
}
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 {
return bundleOf(*this.toList().toTypedArray())
}
companion object {
const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE
}
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 =
(value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
val discountParams = if (discountCode != null) {
mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode)
} else {
mapOf()
}
return mapOf(
FirebaseAnalytics.Param.ITEM_ID to sku,
FirebaseAnalytics.Param.VALUE to value.totalPriceV2.amount,
FirebaseAnalytics.Param.CURRENCY to value.currencyCode.name,
) + discountParams
}
return mapOf(
FirebaseAnalytics.Param.ITEM_ID to sku,
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)
}
}