Updated on 2026-08-14

This commit is contained in:
Tangem 2021-06-24 14:19:23 +03:00
parent 2e5ce52b3a
commit b18e293e4f
11 changed files with 316 additions and 17 deletions

View file

@ -1,8 +1,16 @@
package com.tangem.tap.common.analytics
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.card.Card
interface AnalyticsHandler {
fun triggerEvent(event: AnalyticsEvent, card: Card? = null)
fun triggerEvent(event: AnalyticsEvent, card: Card? = null, blockchain: Blockchain? = null)
fun logCardSdkError(
error: TangemSdkError,
actionToLog: FirebaseAnalyticsHandler.ActionToLog,
parameters: Map<FirebaseAnalyticsHandler.AnalyticsParam, String>? = null,
card: Card? = null
)
}

View file

@ -3,34 +3,84 @@ package com.tangem.tap.common.analytics
import android.os.Bundle
import androidx.core.os.bundleOf
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.card.Card
import com.tangem.tap.common.extensions.filterNotNull
object FirebaseAnalyticsHandler : AnalyticsHandler {
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
Firebase.analytics.logEvent(event.event, setCardData(card))
override fun triggerEvent(event: AnalyticsEvent, card: Card?, blockchain: Blockchain?) {
Firebase.analytics.logEvent(event.event, setData(card, blockchain))
}
fun logException(name: String, throwable: Throwable) {
Firebase.analytics.logEvent(name, bundleOf(
"message" to (throwable.message ?: "none"),
"cause_message" to (throwable.cause?.message ?: "none"),
"stack_trace" to throwable.stackTraceToString()
"message" to (throwable.message ?: "none"),
"cause_message" to (throwable.cause?.message ?: "none"),
"stack_trace" to throwable.stackTraceToString()
))
}
private fun setCardData(card: Card?): Bundle {
override fun logCardSdkError(
error: TangemSdkError,
actionToLog: ActionToLog,
parameters: Map<AnalyticsParam, String>?,
card: Card?,
) {
if (error is TangemSdkError.UserCancelled) return
val params = parameters?.toMutableMap() ?: mutableMapOf()
if (card != null) params + getParamsFromCard(card)
params[AnalyticsParam.ACTION] = actionToLog.key
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
params[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName
params[AnalyticsParam.ERROR_KEY] = "TangemSdkError"
params.forEach { (key, value) ->
FirebaseCrashlytics.getInstance().setCustomKey(key.param, value)
}
val cardError = TangemSdk.map(error)
FirebaseCrashlytics.getInstance().recordException(cardError)
}
private fun getParamsFromCard(card: Card): Map<AnalyticsParam, String> {
return mapOf(
AnalyticsParam.FIRMWARE to card.firmwareVersion.version,
AnalyticsParam.BATCH_ID to card.cardData?.batchId
).filterNotNull()
}
private fun setData(card: Card?, blockchain: Blockchain?): Bundle {
if (card == null) return bundleOf()
return bundleOf(
AnalyticsParam.BLOCKCHAIN.param to card.cardData?.blockchainName,
AnalyticsParam.BATCH_ID.param to card.cardData?.batchId,
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version
AnalyticsParam.BLOCKCHAIN.param to (blockchain?.currency ?: card.cardData?.blockchainName),
AnalyticsParam.BATCH_ID.param to card.cardData?.batchId,
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version
)
}
private enum class AnalyticsParam(val param: String) {
enum class AnalyticsParam(val param: String) {
BLOCKCHAIN("blockchain"),
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"),
}
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"),
}
}

View file

@ -0,0 +1,77 @@
package com.tangem.tap.common.analytics
import com.tangem.TangemSdkError
object TangemSdk {
// This mapping is performed to group errors in FirebaseCrashlytics.
// At the moment, the errors in Crashlytics can only be grouped by their place of creation (class and line).
fun map(error: TangemSdkError): TangemSdkError {
return when (error) {
is TangemSdkError.TagLost -> TangemSdkError.TagLost()
is TangemSdkError.ExtendedLengthNotSupported -> TangemSdkError.ExtendedLengthNotSupported()
is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError()
is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed()
is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch(
error.customMessage)
is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage)
is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag(
error.customMessage)
is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch(
error.customMessage)
is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage)
is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse()
is TangemSdkError.UnknownStatus -> TangemSdkError.UnknownStatus(error.statusWord)
is TangemSdkError.ErrorProcessingCommand -> TangemSdkError.ErrorProcessingCommand()
is TangemSdkError.InvalidState -> TangemSdkError.InvalidState()
is TangemSdkError.InsNotSupported -> TangemSdkError.InsNotSupported()
is TangemSdkError.InvalidParams -> TangemSdkError.InvalidParams()
is TangemSdkError.NeedEncryption -> TangemSdkError.NeedEncryption()
is TangemSdkError.FileNotFound -> TangemSdkError.FileNotFound()
is TangemSdkError.WalletNotFound -> TangemSdkError.WalletNotFound()
is TangemSdkError.AlreadyPersonalized -> TangemSdkError.AlreadyPersonalized()
is TangemSdkError.CannotBeDepersonalized -> TangemSdkError.CannotBeDepersonalized()
is TangemSdkError.Pin1Required -> TangemSdkError.Pin1Required()
is TangemSdkError.CardReadWrongWallet -> TangemSdkError.CardReadWrongWallet()
is TangemSdkError.CardWithMaxZeroWallets -> TangemSdkError.CardWithMaxZeroWallets()
is TangemSdkError.AlreadyCreated -> TangemSdkError.AlreadyCreated()
is TangemSdkError.WalletIndexExceedsMaxValue -> TangemSdkError.WalletIndexExceedsMaxValue()
is TangemSdkError.MaxNumberOfWalletsCreated -> TangemSdkError.MaxNumberOfWalletsCreated()
is TangemSdkError.WalletIndexNotCorrect -> TangemSdkError.WalletIndexNotCorrect()
is TangemSdkError.PurgeWalletProhibited -> TangemSdkError.PurgeWalletProhibited()
is TangemSdkError.Pin1CannotBeChanged -> TangemSdkError.Pin1CannotBeChanged()
is TangemSdkError.Pin2CannotBeChanged -> TangemSdkError.Pin2CannotBeChanged()
is TangemSdkError.Pin1CannotBeDefault -> TangemSdkError.Pin1CannotBeDefault()
is TangemSdkError.NoRemainingSignatures -> TangemSdkError.NoRemainingSignatures()
is TangemSdkError.EmptyHashes -> TangemSdkError.EmptyHashes()
is TangemSdkError.HashSizeMustBeEqual -> TangemSdkError.HashSizeMustBeEqual()
is TangemSdkError.WalletIsNotCreated -> TangemSdkError.WalletIsNotCreated()
is TangemSdkError.SignHashesNotAvailable -> TangemSdkError.SignHashesNotAvailable()
is TangemSdkError.TooManyHashesInOneTransaction -> TangemSdkError.TooManyHashesInOneTransaction()
is TangemSdkError.ExtendedDataSizeTooLarge -> TangemSdkError.ExtendedDataSizeTooLarge()
is TangemSdkError.NotPersonalized -> TangemSdkError.NotPersonalized()
is TangemSdkError.NotActivated -> TangemSdkError.NotActivated()
is TangemSdkError.WalletIsPurged -> TangemSdkError.WalletIsPurged()
is TangemSdkError.Pin2OrCvcRequired -> TangemSdkError.Pin2OrCvcRequired()
is TangemSdkError.VerificationFailed -> TangemSdkError.VerificationFailed()
is TangemSdkError.DataSizeTooLarge -> TangemSdkError.DataSizeTooLarge()
is TangemSdkError.MissingCounter -> TangemSdkError.MissingCounter()
is TangemSdkError.OverwritingDataIsProhibited -> TangemSdkError.OverwritingDataIsProhibited()
is TangemSdkError.DataCannotBeWritten -> TangemSdkError.DataCannotBeWritten()
is TangemSdkError.MissingIssuerPubicKey -> TangemSdkError.MissingIssuerPubicKey()
is TangemSdkError.CardVerificationFailed -> TangemSdkError.CardVerificationFailed()
is TangemSdkError.WrongPin1 -> TangemSdkError.WrongPin1()
is TangemSdkError.WrongPin2 -> TangemSdkError.WrongPin2()
is TangemSdkError.UnknownError -> TangemSdkError.UnknownError()
is TangemSdkError.UserCancelled -> TangemSdkError.UserCancelled()
is TangemSdkError.Busy -> TangemSdkError.Busy()
is TangemSdkError.MissingPreflightRead -> TangemSdkError.MissingPreflightRead()
is TangemSdkError.WrongCardNumber -> TangemSdkError.WrongCardNumber()
is TangemSdkError.WrongCardType -> TangemSdkError.WrongCardType()
is TangemSdkError.CardError -> TangemSdkError.CardError()
is TangemSdkError.FirmwareNotSupported -> TangemSdkError.FirmwareNotSupported()
is TangemSdkError.WalletError -> TangemSdkError.WalletError()
}
}
}

View file

@ -0,0 +1,4 @@
package com.tangem.tap.common.extensions
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> =
filter { it.key != null && it.value != null } as Map<K, V>

View file

@ -16,6 +16,7 @@ import com.tangem.common.extensions.calculateSha256
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.domain.extensions.getDefaultWalletIndex
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.ScanNoteResponse
@ -37,10 +38,16 @@ class TangemSdkManager(val activity: ComponentActivity) {
analyticsHandler: AnalyticsHandler, messageRes: Int? = null
): CompletionResult<ScanNoteResponse> {
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
return runTaskAsyncReturnOnMain(ScanNoteTask(),
val result = runTaskAsyncReturnOnMain(ScanNoteTask(),
initialMessage = Message(
activity.getString(messageRes ?: R.string.initial_message_scan_header)
))
if (result is CompletionResult.Failure) {
(result.error as? TangemSdkError)?.let { error ->
analyticsHandler.logCardSdkError(error, FirebaseAnalyticsHandler.ActionToLog.Scan)
}
}
return result
}
suspend fun createWallet(cardId: String?): CompletionResult<Card> {

View file

@ -2,12 +2,14 @@ package com.tangem.tap.domain.twins
import com.tangem.KeyPair
import com.tangem.Message
import com.tangem.TangemSdkError
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.tangemSdkManager
@ -28,7 +30,16 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
currentCardPublicKey = response.data.walletPublicKey.toHexString()
return SimpleResult.Success
}
is CompletionResult.Failure -> return SimpleResult.failure(response.error)
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
card = scanNoteResponse.card
)
}
return SimpleResult.failure(response.error)
}
}
}
@ -48,7 +59,16 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
secondCardPublicKey = response.data.walletPublicKey.toHexString()
return SimpleResult.Success
}
is CompletionResult.Failure -> return SimpleResult.failure(response.error)
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
card = scanNoteResponse.card
)
}
return SimpleResult.failure(response.error)
}
}
}
@ -60,7 +80,16 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
)
return when (response) {
is CompletionResult.Success -> Result.Success(response.data)
is CompletionResult.Failure -> Result.failure(response.error)
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WriteIssuerData,
card = scanNoteResponse.card
)
}
Result.failure(response.error)
}
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.domain.walletconnect
import com.tangem.Message
import com.tangem.TangemSdkError
import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
@ -14,6 +15,7 @@ import com.tangem.commands.SignCommand
import com.tangem.commands.wallet.WalletIndex
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.hexToBytes
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
@ -128,6 +130,12 @@ class WalletConnectSdkHelper {
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
}
is SimpleResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectTransaction,
)
}
Timber.e(result.error)
null
}
@ -152,6 +160,12 @@ class WalletConnectSdkHelper {
HEX_PREFIX + result.data
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
)
}
Timber.e(result.error.customMessage)
null
}
@ -208,6 +222,12 @@ class WalletConnectSdkHelper {
)
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
)
}
Timber.e(result.error.customMessage)
null
}

View file

@ -1,7 +1,9 @@
package com.tangem.tap.features.details.redux
import com.tangem.TangemSdkError
import com.tangem.commands.common.network.Result
import com.tangem.common.CompletionResult
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@ -90,6 +92,15 @@ class DetailsMiddleware {
is CompletionResult.Success -> {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.PurgeWallet,
card = store.state.detailsState.card
)
}
}
}
}
}
@ -148,8 +159,20 @@ class DetailsMiddleware {
}
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success)
}
is CompletionResult.Failure, null ->
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error = error,
actionToLog = FirebaseAnalyticsHandler.ActionToLog.ChangeSecOptions,
parameters = mapOf(
FirebaseAnalyticsHandler.AnalyticsParam.NEW_SECURITY_OPTION to
(selectedOption?.name ?: "")
),
card = store.state.detailsState.card
)
}
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
}
}
}
}

View file

@ -0,0 +1,56 @@
package com.tangem.tap.features.details.ui.walletconnect.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
class WcDialog {
companion object {
fun create(dialogData: DialogData, context: Context): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(dialogData.title)
setMessage(dialogData.message)
setPositiveButton(dialogData.positiveButton.title) { _, _ ->
dialogData.positiveButton.action()
}
setNegativeButton(dialogData.negativeButton.title) { _, _ ->
dialogData.negativeButton.action()
}
setOnDismissListener { dialogData.onDismissAction() }
}.create()
}
}
}
data class DialogData(
val title: String,
val message: String,
val positiveButton: ButtonData,
val negativeButton: ButtonData,
val onDismissAction: () -> Unit,
)
data class ButtonData(
val title: String,
val action: () -> Unit,
)
//class WcDialogMessageBuilder(val messageRes: Int, val data: DialogMessageData? = null) {
// fun build(context: Context): String {
// when (data) {
// null -> context.getString(messageRes)
// }
// }
//}
interface DialogMessageData
data class WcTransactionDialogMessageData(
val cardId: String,
val dAppName: String,
val dAppUrl: String,
val amount: String,
val gasAmount: String,
val totalAmount: String,
val balance: String,
val isEnoughFundsToSend: Boolean,
)

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.send.redux.middlewares
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.TangemSdkError
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
import com.tangem.blockchain.common.*
@ -127,7 +128,11 @@ private fun sendTransaction(
when (result) {
is SimpleResult.Success -> {
tangemSdk.config.linkedTerminal = isLinkedTerminal
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.TRANSACTION_IS_SENT, card)
FirebaseAnalyticsHandler.triggerEvent(
event = AnalyticsEvent.TRANSACTION_IS_SENT,
card = card,
blockchain = walletManager.wallet.blockchain
)
dispatch(SendAction.SendSuccess)
dispatch(NavigationAction.PopBackTo())
scope.launch(Dispatchers.IO) {
@ -178,6 +183,16 @@ private fun sendTransaction(
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
else -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.SendTransaction,
mapOf(
FirebaseAnalyticsHandler.AnalyticsParam.BLOCKCHAIN
to walletManager.wallet.blockchain.currency),
card = card,
)
}
Timber.e(throwable)
FirebaseCrashlytics.getInstance().recordException(throwable)
dispatch(SendAction.SendError(TapError.CustomError(message)))

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux.middlewares
import android.content.Intent
import android.net.Uri
import androidx.core.content.ContextCompat
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.*
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.isZero
@ -111,6 +112,15 @@ class WalletMiddleware {
globalState.tapWalletManager.onCardScanned(scanNoteResponse)
}
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
card = store.state.detailsState.card
)
}
}
}
}
}