Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-15 13:23:49 +03:00
parent 1b33e6b02e
commit 66efd406cc
13 changed files with 111 additions and 9 deletions

View file

@ -104,7 +104,6 @@ sealed class AnalyticsParam {
const val PERMISSION_TYPE = "Permission Type"
const val PRODUCT_TYPE = "Product Type"
const val FIRMWARE = "Firmware"
const val USER_WALLET_ID = "User Wallet ID"
const val CURRENCY = "Currency"
const val ERROR_DESCRIPTION = "Error Description"
const val ERROR_CODE = "Error Code"

View file

@ -1,14 +1,23 @@
package com.tangem.tap.common.analytics.handlers.amplitude
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.api.AnalyticsUserIdHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
class AmplitudeAnalyticsHandler(
private val client: AmplitudeAnalyticsClient,
) : AnalyticsHandler {
) : AnalyticsHandler, AnalyticsUserIdHandler {
override fun id(): String = ID
override fun setUserId(userId: String) {
client.setUserId(userId)
}
override fun clearUserId() {
client.clearUserId()
}
override fun send(eventId: String, params: Map<String, String>) {
client.logEvent(eventId, params)
}

View file

@ -4,13 +4,14 @@ import android.app.Application
import com.amplitude.api.Amplitude
import com.amplitude.api.AmplitudeClient
import com.tangem.core.analytics.api.EventLogger
import com.tangem.core.analytics.api.UserIdHolder
import com.tangem.utils.converter.Converter
import org.json.JSONObject
/**
[REDACTED_AUTHOR]
*/
interface AmplitudeAnalyticsClient : EventLogger
interface AmplitudeAnalyticsClient : EventLogger, UserIdHolder
internal class AmplitudeClient(
application: Application,
@ -24,6 +25,14 @@ internal class AmplitudeClient(
client.enableForegroundTracking(application)
}
override fun setUserId(userId: String) {
client.setUserId(userId)
}
override fun clearUserId() {
client.setUserId(null)
}
override fun logEvent(event: String, params: Map<String, String>) {
client.logEvent(event, ParamsToJSONObjectConverter().convert(params))
}

View file

@ -12,6 +12,16 @@ internal class AmplitudeLogClient(
private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(AmplitudeAnalyticsHandler.ID, jsonConverter)
private var userId: String? = null
override fun setUserId(userId: String) {
this.userId = userId
}
override fun clearUserId() {
this.userId = null
}
override fun logEvent(event: String, params: Map<String, String>) {
logger.logEvent(event, params)
}

View file

@ -3,6 +3,7 @@ package com.tangem.tap.common.analytics.handlers.firebase
import com.tangem.core.analytics.api.AnalyticsErrorHandler
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.api.AnalyticsUserIdHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
@ -10,12 +11,20 @@ import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter
class FirebaseAnalyticsHandler(
private val client: FirebaseAnalyticsClient,
) : AnalyticsHandler, AnalyticsErrorHandler, AnalyticsExceptionHandler {
) : AnalyticsHandler, AnalyticsErrorHandler, AnalyticsExceptionHandler, AnalyticsUserIdHandler {
private val errorConverter = AnalyticsErrorConverter()
override fun id(): String = ID
override fun setUserId(userId: String) {
client.setUserId(userId)
}
override fun clearUserId() {
client.clearUserId()
}
override fun send(eventId: String, params: Map<String, String>) {
client.logEvent(eventId, params)
}

View file

@ -8,11 +8,12 @@ import com.google.firebase.crashlytics.recordException
import com.google.firebase.ktx.Firebase
import com.tangem.core.analytics.api.ExceptionLogger
import com.tangem.core.analytics.api.EventLogger
import com.tangem.core.analytics.api.UserIdHolder
/**
[REDACTED_AUTHOR]
*/
interface FirebaseAnalyticsClient : EventLogger, ExceptionLogger
interface FirebaseAnalyticsClient : EventLogger, ExceptionLogger, UserIdHolder
internal class FirebaseClient : FirebaseAnalyticsClient {
@ -21,6 +22,14 @@ internal class FirebaseClient : FirebaseAnalyticsClient {
private val eventConverter = FirebaseAnalyticsEventConverter()
override fun setUserId(userId: String) {
Firebase.analytics.setUserId(userId)
}
override fun clearUserId() {
Firebase.analytics.setUserId(null)
}
override fun logEvent(event: String, params: Map<String, String>) {
fbAnalytics.logEvent(
eventConverter.convertEventName(event),

View file

@ -11,6 +11,15 @@ internal class FirebaseLogClient(
) : FirebaseAnalyticsClient {
private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(FirebaseAnalyticsHandler.ID, jsonConverter)
private var userId: String? = null
override fun setUserId(userId: String) {
this.userId = userId
}
override fun clearUserId() {
this.userId = null
}
override fun logEvent(event: String, params: Map<String, String>) {
logger.logEvent(event, params)

View file

@ -40,9 +40,6 @@ class CardContextInterceptor(
params[AnalyticsParam.BATCH] = card.batchId
params[AnalyticsParam.PRODUCT_TYPE] = getProductType()
params[AnalyticsParam.FIRMWARE] = card.firmwareVersion.stringValue
if (userWalletId != null) {
params[AnalyticsParam.USER_WALLET_ID] = userWalletId.stringValue
}
ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)?.let {
params[AnalyticsParam.CURRENCY] = it.value

View file

@ -2,6 +2,7 @@ package com.tangem.tap.common.extensions
import com.tangem.core.analytics.Analytics
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
/**
@ -12,6 +13,11 @@ import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterc
* Sets the new context
*/
fun Analytics.setContext(scanResponse: ScanResponse) {
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
if (userWalletId != null) {
setUserId(userWalletId.stringValue)
}
addParamsInterceptor(LinkedCardContextInterceptor(scanResponse))
}
@ -19,6 +25,7 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
* Erases the context
*/
fun Analytics.eraseContext() {
clearUserId()
removeParamsInterceptor(LinkedCardContextInterceptor.id())
}
@ -26,6 +33,11 @@ fun Analytics.eraseContext() {
* Adds a new context and keeps a previous context as the parent of the new one
*/
fun Analytics.addContext(scanResponse: ScanResponse) {
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
if (userWalletId != null) {
setUserId(userWalletId.stringValue)
}
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)

View file

@ -22,4 +22,7 @@ dependencies {
/** Core shouldn't depend on core, but in case with utils and logging its necessary */
implementation(projects.core.utils)
/** For calculating user id hash */
implementation(tangemDeps.card.core)
}

View file

@ -1,5 +1,7 @@
package com.tangem.core.analytics
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.api.*
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
@ -19,7 +21,8 @@ interface GlobalAnalyticsEventHandler :
AnalyticsFilterHolder,
ParamsInterceptorHolder,
AnalyticsErrorHandler,
AnalyticsExceptionHandler
AnalyticsExceptionHandler,
AnalyticsUserIdHandler
object Analytics : GlobalAnalyticsEventHandler {
@ -57,6 +60,26 @@ object Analytics : GlobalAnalyticsEventHandler {
return paramsInterceptors.remove(interceptorId)
}
override fun setUserId(userId: String) {
analyticsScope.launch {
val userIdHash = userId.calculateSha256().toHexString()
analyticsMutex.withLock {
analyticsHandlers.filterIsInstance<AnalyticsUserIdHandler>()
.forEach { handler -> handler.setUserId(userIdHash) }
}
}
}
override fun clearUserId() {
analyticsScope.launch {
analyticsMutex.withLock {
analyticsHandlers.filterIsInstance<AnalyticsUserIdHandler>()
.forEach { handler -> handler.clearUserId() }
}
}
}
override fun send(event: AnalyticsEvent) {
analyticsScope.launch {
event.params = applyParamsInterceptors(event)

View file

@ -18,6 +18,11 @@ interface AnalyticsExceptionHandler {
fun sendException(event: ExceptionAnalyticsEvent)
}
interface AnalyticsUserIdHandler {
fun setUserId(userId: String)
fun clearUserId()
}
interface AnalyticsHandler : AnalyticsEventHandler {
fun id(): String

View file

@ -0,0 +1,8 @@
package com.tangem.core.analytics.api
interface UserIdHolder {
fun setUserId(userId: String)
fun clearUserId()
}