Updated on 2026-08-14
This commit is contained in:
commit
531eb86ac5
1243 changed files with 49302 additions and 12422 deletions
|
|
@ -3,6 +3,8 @@ package com.tangem.core.analytics.models
|
|||
import com.tangem.core.analytics.models.AnalyticsParam.Key.REFERRAL
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.REFERRAL_ID
|
||||
|
||||
const val IS_NOT_HTTP_ERROR = "Is not http error"
|
||||
|
||||
sealed class AnalyticsParam {
|
||||
|
||||
sealed class CardBalanceState(val value: String) {
|
||||
|
|
@ -80,7 +82,9 @@ sealed class AnalyticsParam {
|
|||
data object Backup : ScreensSources("Backup")
|
||||
data object Onboarding : ScreensSources("Onboarding")
|
||||
data object LongTap : ScreensSources("Long Tap")
|
||||
data object Market : ScreensSources("Market")
|
||||
data object Markets : ScreensSources("Markets")
|
||||
data object MarketPulse : ScreensSources("Market Pulse")
|
||||
data object TangemPay : ScreensSources("Tangem Pay")
|
||||
data object WalletSettings : ScreensSources("Wallet Settings")
|
||||
data object Upgrade : ScreensSources("Upgrade")
|
||||
|
|
@ -90,6 +94,9 @@ sealed class AnalyticsParam {
|
|||
data object AddNewWallet : ScreensSources("Add New Wallet")
|
||||
data object AddNew : ScreensSources("Add New")
|
||||
data object CreateWallet : ScreensSources("Create Wallet")
|
||||
data object NewsList : ScreensSources("News List")
|
||||
data object NewsLink : ScreensSources("News Link")
|
||||
data object NewsPage : ScreensSources("News Page")
|
||||
}
|
||||
|
||||
sealed class TxSentFrom(val value: String) {
|
||||
|
|
@ -97,24 +104,28 @@ sealed class AnalyticsParam {
|
|||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
override val feeToken: String,
|
||||
) : TxSentFrom("Send"), TxData
|
||||
|
||||
data class Swap(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
override val feeToken: String,
|
||||
) : TxSentFrom("Swap"), TxData
|
||||
|
||||
data class Staking(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
override val feeToken: String,
|
||||
) : TxSentFrom("Staking"), TxData
|
||||
|
||||
data class Approve(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
override val feeToken: String,
|
||||
val permissionType: String,
|
||||
) : TxSentFrom("Approve"), TxData
|
||||
|
||||
|
|
@ -122,6 +133,7 @@ sealed class AnalyticsParam {
|
|||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType?,
|
||||
override val feeToken: String,
|
||||
) : TxSentFrom("WalletConnect"), TxData
|
||||
|
||||
data object Sell : TxSentFrom("Sell")
|
||||
|
|
@ -130,24 +142,28 @@ sealed class AnalyticsParam {
|
|||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
override val feeToken: String,
|
||||
) : TxSentFrom("NFT"), TxData
|
||||
|
||||
data class SendWithSwap(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
override val feeToken: String,
|
||||
) : TxSentFrom("Send&Swap"), TxData
|
||||
|
||||
data class Earning(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
override val feeToken: String,
|
||||
) : TxSentFrom("Earning"), TxData
|
||||
}
|
||||
|
||||
sealed interface TxData {
|
||||
val blockchain: String
|
||||
val token: String
|
||||
val feeToken: String
|
||||
val feeType: FeeType?
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +287,9 @@ sealed class AnalyticsParam {
|
|||
const val ENS = "ENS"
|
||||
const val ENS_ADDRESS = "ENS Address"
|
||||
const val ACCOUNT_DERIVATION_FROM = "Account Derivation (from)"
|
||||
const val ACCOUNT_DERIVATION_TO = "Account Derivation (to)"
|
||||
const val FEE_TOKEN = "Fee Token"
|
||||
const val ACCOUNT_DERIVATION = "Account Derivation"
|
||||
const val REFERRAL = "Referral"
|
||||
const val REFERRAL_ID = "Referral_ID"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ sealed class Basic(
|
|||
sentFrom.feeType?.value?.let {
|
||||
this[AnalyticsParam.Key.FEE_TYPE] = it
|
||||
}
|
||||
this[AnalyticsParam.Key.FEE_TOKEN] = sentFrom.feeToken
|
||||
}
|
||||
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
|
||||
this[AnalyticsParam.Key.PERMISSION_TYPE] = sentFrom.permissionType
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.core.analytics.models
|
||||
|
||||
/**
|
||||
* Marker interface for analytics events that can be throttled by session and/or time.
|
||||
*
|
||||
* Events implementing this interface will be tracked by their [oneTimeEventId].
|
||||
*
|
||||
* Behavior depends on [throttleSeconds]:
|
||||
* - **null** (default): One time per session — event is sent only once during the application session.
|
||||
* - **non-null**: Time-based throttling — event is not sent if less than [throttleSeconds] seconds
|
||||
* have passed since the last send for this [oneTimeEventId].
|
||||
*
|
||||
* @see Analytics.send
|
||||
*/
|
||||
interface OneTimePerSessionEvent {
|
||||
/**
|
||||
* Unique identifier for the throttled event.
|
||||
*
|
||||
* This ID is used to track whether and when the event was last sent.
|
||||
* Events with the same [oneTimeEventId] share the same throttling state.
|
||||
*/
|
||||
val oneTimeEventId: String
|
||||
|
||||
/**
|
||||
* Minimum interval in seconds between sends for this event.
|
||||
*
|
||||
* - **null**: One time per session only. Event is sent at most once per session.
|
||||
* - **non-null**: Don't send if less than this many seconds have passed since the last send.
|
||||
*/
|
||||
val throttleSeconds: Long?
|
||||
get() = null
|
||||
}
|
||||
|
|
@ -6,12 +6,14 @@ 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
|
||||
import com.tangem.core.analytics.models.OneTimePerSessionEvent
|
||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -31,6 +33,7 @@ object Analytics : GlobalAnalyticsEventHandler {
|
|||
|
||||
private val handlers = mutableMapOf<String, AnalyticsHandler>()
|
||||
private val paramsInterceptors = ConcurrentHashMap<String, ParamsInterceptor>()
|
||||
private val throttledEventsState = ConcurrentHashMap<String, Long>()
|
||||
private val analyticsFilters = mutableSetOf<AnalyticsEventFilter>()
|
||||
private val analyticsMutex = Mutex()
|
||||
|
||||
|
|
@ -85,6 +88,9 @@ object Analytics : GlobalAnalyticsEventHandler {
|
|||
|
||||
override fun send(event: AnalyticsEvent) {
|
||||
analyticsScope.launch {
|
||||
if (event is OneTimePerSessionEvent && !shouldSendThrottledEvent(event)) {
|
||||
return@launch
|
||||
}
|
||||
event.params = applyParamsInterceptors(event)
|
||||
val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) }
|
||||
|
||||
|
|
@ -130,6 +136,21 @@ object Analytics : GlobalAnalyticsEventHandler {
|
|||
return interceptedParams
|
||||
}
|
||||
|
||||
private fun shouldSendThrottledEvent(event: OneTimePerSessionEvent): Boolean {
|
||||
val now = System.currentTimeMillis()
|
||||
val id = event.oneTimeEventId
|
||||
return when (val throttleMs = event.throttleSeconds?.let(TimeUnit.SECONDS::toMillis)) {
|
||||
null -> throttledEventsState.putIfAbsent(id, now) == null
|
||||
else -> throttledEventsState.compute(id) { _, lastSendTime ->
|
||||
when {
|
||||
lastSendTime == null -> now
|
||||
now - lastSendTime >= throttleMs -> now
|
||||
else -> lastSendTime
|
||||
}
|
||||
} == now
|
||||
}
|
||||
}
|
||||
|
||||
private fun createScope(): CoroutineScope {
|
||||
val name = "Analytics"
|
||||
val dispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.core.analytics.api
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class ResettableOneTimeEventSender(private val analyticsEventHandler: AnalyticsEventHandler) {
|
||||
|
||||
private val sentEvents = ConcurrentHashMap<String, AnalyticsEvent>()
|
||||
|
||||
fun sendEventOnce(key: String, event: AnalyticsEvent) {
|
||||
if (sentEvents.putIfAbsent(key, event) == null) {
|
||||
analyticsEventHandler.send(event)
|
||||
}
|
||||
}
|
||||
|
||||
fun reset(key: String) {
|
||||
sentEvents.remove(key)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue