diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/analytics/AddressBookEvents.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/analytics/AddressBookEvents.kt index e11a6468fe..a5f37268f2 100644 --- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/analytics/AddressBookEvents.kt +++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/analytics/AddressBookEvents.kt @@ -3,7 +3,6 @@ package com.tangem.features.addressbook.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_TYPE import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE -import com.tangem.domain.models.wallet.UserWalletId private const val ADDRESS_BOOK_CATEGORY = "Address Book" @@ -24,85 +23,85 @@ sealed class AddressBookEvents( data object AddressScreenOpened : AddressBookEvents(event = "Address Screen Opened") class ContactListScreenOpened( - walletId: UserWalletId, + walletId: String, source: Source, contactsCount: Int, ) : AddressBookEvents( event = "Contact List Screen Opened", params = mapOf( - WALLET_ID to walletId.stringValue, + WALLET_ID to walletId, SOURCE to source.value, CONTACTS_COUNT to contactsCount.toString(), ), ) { enum class Source(val value: String) { - Settings("settings"), - SendFlow("send_flow"), + Settings("Settings"), + SendFlow("Send Flow"), } } class AddContactTapped( - walletId: UserWalletId, + walletId: String, source: Source, ) : AddressBookEvents( event = "Add Contact Tapped", params = mapOf( - WALLET_ID to walletId.stringValue, + WALLET_ID to walletId, SOURCE to source.value, ), ) { enum class Source(val value: String) { - Settings("settings"), - SendSuccess("send_success"), + Settings("Settings"), + SendSuccess("Send Flow"), } } class ContactSaved( - walletId: UserWalletId, + walletId: String, contactId: String, mode: Mode, ) : AddressBookEvents( event = "Contact Saved", params = mapOf( - WALLET_ID to walletId.stringValue, + WALLET_ID to walletId, CONTACT_ID to contactId, MODE to mode.value, ), ) { enum class Mode(val value: String) { - Create("create"), - Edit("edit"), + Create("Create"), + Edit("Edit"), } } class SaveErrorShown( - walletId: UserWalletId, + walletId: String, contactId: String?, errorType: ErrorType, ) : AddressBookEvents( event = "Save Error Shown", params = buildMap { - put(WALLET_ID, walletId.stringValue) + put(WALLET_ID, walletId) contactId?.let { put(CONTACT_ID, it) } put(ERROR_TYPE, errorType.value) }, ) { enum class ErrorType(val value: String) { - Network("network"), - Server("server"), - Signing("signing"), + Network("Network"), + Server("Server"), + Signing("Signing"), } } // endregion // region Contact editing class ContactScreenOpened( - walletId: UserWalletId, + walletId: String, contactId: String, ) : AddressBookEvents( event = "Contact Screen Opened", params = mapOf( - WALLET_ID to walletId.stringValue, + WALLET_ID to walletId, CONTACT_ID to contactId, ), ) @@ -110,30 +109,30 @@ sealed class AddressBookEvents( // region Send flow class SendFlowWidgetShown( - walletId: UserWalletId, + walletId: String, ) : AddressBookEvents( event = "Send Flow Widget Shown", - params = mapOf(WALLET_ID to walletId.stringValue), + params = mapOf(WALLET_ID to walletId), ) class ContactSelectedInSend( - walletId: UserWalletId, + walletId: String, contactId: String, ) : AddressBookEvents( event = "Contact Selected", params = mapOf( - WALLET_ID to walletId.stringValue, + WALLET_ID to walletId, CONTACT_ID to contactId, ), ) class AddressSubstitutedInSend( - walletId: UserWalletId, + walletId: String, contactId: String, ) : AddressBookEvents( event = "Address Substituted In Send", params = mapOf( - WALLET_ID to walletId.stringValue, + WALLET_ID to walletId, CONTACT_ID to contactId, ), ) @@ -141,23 +140,23 @@ sealed class AddressBookEvents( // region Input errors class AddressInvalid( - walletId: UserWalletId, + walletId: String, contactId: String?, ) : AddressBookEvents( event = "Address Invalid", params = buildMap { - put(WALLET_ID, walletId.stringValue) + put(WALLET_ID, walletId) put(CONTACT_ID, contactId.orEmpty()) }, ) class DuplicateNameErrorShown( - walletId: UserWalletId, + walletId: String, contactId: String?, ) : AddressBookEvents( event = "Duplicate Name Error Shown", params = buildMap { - put(WALLET_ID, walletId.stringValue) + put(WALLET_ID, walletId) contactId?.let { put(CONTACT_ID, it) } }, ) @@ -183,23 +182,23 @@ sealed class AddressBookEvents( // region Deletion class AddressRemoved( - walletId: UserWalletId, + walletId: String, contactId: String, ) : AddressBookEvents( event = "Address Removed", params = mapOf( - WALLET_ID to walletId.stringValue, + WALLET_ID to walletId, CONTACT_ID to contactId, ), ) class ContactDeleted( - walletId: UserWalletId, + walletId: String, contactId: String, ) : AddressBookEvents( event = "Contact Deleted", params = mapOf( - WALLET_ID to walletId.stringValue, + WALLET_ID to walletId, CONTACT_ID to contactId, ), ) diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSender.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSender.kt index e43c1a4813..759f0f70b3 100644 --- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSender.kt +++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSender.kt @@ -1,5 +1,7 @@ package com.tangem.features.addressbook.common +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.toHexString import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.addressbook.error.AddressBookSyncError import com.tangem.domain.addressbook.error.SaveContactError @@ -60,31 +62,31 @@ internal class AddressBookAnalyticsSender @Inject constructor( override fun onAddressSubstitutedInSend(walletId: UserWalletId, contactId: String) { analyticsEventHandler.send( - AddressBookEvents.AddressSubstitutedInSend(walletId = walletId, contactId = contactId), + AddressBookEvents.AddressSubstitutedInSend(walletId = walletId.hashForAnalytics(), contactId = contactId), ) } fun sendAddressInvalid(walletId: UserWalletId, contactId: String) { analyticsEventHandler.send( - AddressBookEvents.AddressInvalid(walletId = walletId, contactId = contactId), + AddressBookEvents.AddressInvalid(walletId = walletId.hashForAnalytics(), contactId = contactId), ) } fun sendDuplicateNameErrorShown(walletId: UserWalletId, contactId: String?) { analyticsEventHandler.send( - AddressBookEvents.DuplicateNameErrorShown(walletId = walletId, contactId = contactId), + AddressBookEvents.DuplicateNameErrorShown(walletId = walletId.hashForAnalytics(), contactId = contactId), ) } fun sendAddressRemoved(walletId: UserWalletId, contactId: String) { analyticsEventHandler.send( - AddressBookEvents.AddressRemoved(walletId = walletId, contactId = contactId), + AddressBookEvents.AddressRemoved(walletId = walletId.hashForAnalytics(), contactId = contactId), ) } fun sendContactDeleted(walletId: UserWalletId, contactId: String) { analyticsEventHandler.send( - AddressBookEvents.ContactDeleted(walletId = walletId, contactId = contactId), + AddressBookEvents.ContactDeleted(walletId = walletId.hashForAnalytics(), contactId = contactId), ) } @@ -114,7 +116,7 @@ internal class AddressBookAnalyticsSender @Inject constructor( fun sendContactSaved(walletId: UserWalletId, contactId: String, isEdit: Boolean) { analyticsEventHandler.send( AddressBookEvents.ContactSaved( - walletId = walletId, + walletId = walletId.hashForAnalytics(), contactId = contactId, mode = if (isEdit) { AddressBookEvents.ContactSaved.Mode.Edit @@ -133,7 +135,7 @@ internal class AddressBookAnalyticsSender @Inject constructor( val errorType = error.toErrorType() ?: return analyticsEventHandler.send( AddressBookEvents.SaveErrorShown( - walletId = walletId, + walletId = walletId.hashForAnalytics(), contactId = contactId, errorType = errorType, ), @@ -158,8 +160,12 @@ internal class AddressBookAnalyticsSender @Inject constructor( analyticsEventHandler.send(AddressBookEvents.AddressScreenOpened) } - private suspend fun selectedWalletId(): UserWalletId = userWalletsListRepository.selectedUserWallet + private suspend fun selectedWalletId(): String = userWalletsListRepository.selectedUserWallet .filterNotNull() .first() .walletId + .hashForAnalytics() + + /** Analytics must never receive a raw wallet id — send its SHA-256 (uppercase hex) instead. */ + private fun UserWalletId.hashForAnalytics(): String = value.calculateSha256().toHexString() } \ No newline at end of file diff --git a/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSenderTest.kt b/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSenderTest.kt index 1c2a4af1de..a272d95583 100644 --- a/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSenderTest.kt +++ b/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSenderTest.kt @@ -1,5 +1,7 @@ package com.tangem.features.addressbook.common +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.toHexString import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.addressbook.error.AddressBookSyncError import com.tangem.domain.addressbook.error.SaveContactError @@ -56,7 +58,7 @@ internal class AddressBookAnalyticsSenderTest { // Assert val expected = AddressBookEvents.ContactListScreenOpened( - walletId = EXPECTED_WALLET_ID, + walletId = EXPECTED_WALLET_ID_HASH, source = model.source, contactsCount = model.contactsCount, ) @@ -72,7 +74,7 @@ internal class AddressBookAnalyticsSenderTest { // Assert val expected = AddContactTapped( - walletId = EXPECTED_WALLET_ID, + walletId = EXPECTED_WALLET_ID_HASH, source = model.expectedSource, ) verify(exactly = 1) { analyticsEventHandler.send(expected) } @@ -86,7 +88,7 @@ internal class AddressBookAnalyticsSenderTest { // Assert val expected = ContactSaved( - walletId = EXPECTED_WALLET_ID, + walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID, mode = model.expectedMode, ) @@ -105,7 +107,7 @@ internal class AddressBookAnalyticsSenderTest { verify(exactly = 0) { analyticsEventHandler.send(any()) } } else { val expected = AddressBookEvents.SaveErrorShown( - walletId = EXPECTED_WALLET_ID, + walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID, errorType = expectedType, ) @@ -138,7 +140,7 @@ internal class AddressBookAnalyticsSenderTest { advanceUntilIdle() // Assert - val expected = AddressBookEvents.ContactScreenOpened(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID) + val expected = AddressBookEvents.ContactScreenOpened(walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID) verify(exactly = 1) { analyticsEventHandler.send(expected) } } @@ -149,7 +151,7 @@ internal class AddressBookAnalyticsSenderTest { advanceUntilIdle() // Assert - val expected = AddressBookEvents.SendFlowWidgetShown(walletId = EXPECTED_WALLET_ID) + val expected = AddressBookEvents.SendFlowWidgetShown(walletId = EXPECTED_WALLET_ID_HASH) verify(exactly = 1) { analyticsEventHandler.send(expected) } } @@ -160,7 +162,10 @@ internal class AddressBookAnalyticsSenderTest { advanceUntilIdle() // Assert - val expected = AddressBookEvents.ContactSelectedInSend(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID) + val expected = AddressBookEvents.ContactSelectedInSend( + walletId = EXPECTED_WALLET_ID_HASH, + contactId = CONTACT_ID, + ) verify(exactly = 1) { analyticsEventHandler.send(expected) } } @@ -170,7 +175,8 @@ internal class AddressBookAnalyticsSenderTest { sender.onAddressSubstitutedInSend(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID) // Assert - val expected = AddressBookEvents.AddressSubstitutedInSend(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID) + val expected = + AddressBookEvents.AddressSubstitutedInSend(walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID) verify(exactly = 1) { analyticsEventHandler.send(expected) } } @@ -181,7 +187,7 @@ internal class AddressBookAnalyticsSenderTest { sender.sendAddressInvalid(walletId = EXPECTED_WALLET_ID, contactId = contactId) // Assert - val expected = AddressBookEvents.AddressInvalid(walletId = EXPECTED_WALLET_ID, contactId = contactId) + val expected = AddressBookEvents.AddressInvalid(walletId = EXPECTED_WALLET_ID_HASH, contactId = contactId) verify(exactly = 1) { analyticsEventHandler.send(expected) } } @@ -192,7 +198,10 @@ internal class AddressBookAnalyticsSenderTest { sender.sendDuplicateNameErrorShown(walletId = EXPECTED_WALLET_ID, contactId = contactId) // Assert - val expected = AddressBookEvents.DuplicateNameErrorShown(walletId = EXPECTED_WALLET_ID, contactId = contactId) + val expected = AddressBookEvents.DuplicateNameErrorShown( + walletId = EXPECTED_WALLET_ID_HASH, + contactId = contactId, + ) verify(exactly = 1) { analyticsEventHandler.send(expected) } } @@ -202,7 +211,7 @@ internal class AddressBookAnalyticsSenderTest { sender.sendAddressRemoved(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID) // Assert - val expected = AddressBookEvents.AddressRemoved(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID) + val expected = AddressBookEvents.AddressRemoved(walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID) verify(exactly = 1) { analyticsEventHandler.send(expected) } } @@ -212,7 +221,7 @@ internal class AddressBookAnalyticsSenderTest { sender.sendContactDeleted(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID) // Assert - val expected = AddressBookEvents.ContactDeleted(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID) + val expected = AddressBookEvents.ContactDeleted(walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID) verify(exactly = 1) { analyticsEventHandler.send(expected) } } @@ -241,9 +250,15 @@ internal class AddressBookAnalyticsSenderTest { private fun provideSaveErrorModels() = listOf( SaveErrorModel(error = SaveContactError.Signing(mockk()), expectedType = ErrorType.Signing), - SaveErrorModel(error = SaveContactError.Backend(AddressBookSyncError.Network), expectedType = ErrorType.Network), + SaveErrorModel( + error = SaveContactError.Backend(AddressBookSyncError.Network), + expectedType = ErrorType.Network, + ), // 412 - SaveErrorModel(error = SaveContactError.Backend(AddressBookSyncError.Conflict), expectedType = ErrorType.Server), + SaveErrorModel( + error = SaveContactError.Backend(AddressBookSyncError.Conflict), + expectedType = ErrorType.Server, + ), // 5xx / unmapped SaveErrorModel(error = SaveContactError.Backend(AddressBookSyncError.Unknown), expectedType = ErrorType.Server), SaveErrorModel( @@ -262,6 +277,10 @@ internal class AddressBookAnalyticsSenderTest { private companion object { val EXPECTED_WALLET_ID = UserWalletId("0011223344") + + // Analytics receives the SHA-256 (uppercase hex) of the wallet id, never the raw value. + val EXPECTED_WALLET_ID_HASH = EXPECTED_WALLET_ID.value.calculateSha256().toHexString() + const val CONTACT_ID = "contact-42" } } \ No newline at end of file