Updated on 2026-08-14
This commit is contained in:
parent
7e6461b073
commit
79fefb8300
4 changed files with 57 additions and 6 deletions
|
|
@ -12,11 +12,13 @@ import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
|||
import com.tangem.lib.auth.AuthFeatureToggles
|
||||
import com.tangem.lib.auth.session.DeviceRegistrar
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
import com.tangem.tap.common.analytics.handlers.appsflyer.AppsFlyerClient
|
||||
import com.tangem.tap.common.log.TangemLoggingInitializer
|
||||
import com.tangem.tap.domain.walletregistration.WalletRegistrationLauncher
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
|
@ -58,4 +60,8 @@ interface ApplicationEntryPoint {
|
|||
fun getDeviceRegistrar(): DeviceRegistrar
|
||||
|
||||
fun getAuthFeatureToggles(): AuthFeatureToggles
|
||||
|
||||
fun getWalletRegistrationLauncher(): WalletRegistrationLauncher
|
||||
|
||||
fun getUserWalletsListRepository(): UserWalletsListRepository
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
|||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.lib.auth.AuthFeatureToggles
|
||||
import com.tangem.lib.auth.devicekey.DeviceKeyManager
|
||||
|
|
@ -34,6 +35,8 @@ import com.tangem.tap.common.analytics.handlers.customerio.CustomerIoAnalyticsHa
|
|||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
import com.tangem.tap.common.log.TangemLoggingInitializer
|
||||
import com.tangem.tap.domain.walletregistration.WalletRegistrationLauncher
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.EntryPoints
|
||||
|
|
@ -104,6 +107,12 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
private val authFeatureToggles: AuthFeatureToggles
|
||||
get() = entryPoint.getAuthFeatureToggles()
|
||||
|
||||
private val walletRegistrationLauncher: WalletRegistrationLauncher
|
||||
get() = entryPoint.getWalletRegistrationLauncher()
|
||||
|
||||
private val userWalletsListRepository: UserWalletsListRepository
|
||||
get() = entryPoint.getUserWalletsListRepository()
|
||||
|
||||
// endregion
|
||||
|
||||
private val appScope = MainScope()
|
||||
|
|
@ -152,6 +161,18 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
deviceKeyManager.generateIfMissing()
|
||||
deviceRegistrar.register()
|
||||
.onLeft { error -> TangemLogger.w("Device registration deferred: $error") }
|
||||
.onRight {
|
||||
// Safety net: register any MOBILE wallets that missed it (created before the
|
||||
// toggle was on, or after a transient failure). Requires the DPoP session from
|
||||
// device registration, so it runs only once that succeeded. Best-effort —
|
||||
// loading the wallet list (userWalletsSync) can throw, which must not crash
|
||||
// startup, so the whole retry is guarded.
|
||||
runSuspendCatching {
|
||||
walletRegistrationLauncher.retryMobileRegistrations(
|
||||
userWalletsListRepository.userWalletsSync(),
|
||||
)
|
||||
}.onFailure { error -> TangemLogger.e("Mobile wallet registration retry failed", error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
walletsRepository = entryPoint.getWalletsRepository()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import com.tangem.lib.auth.AuthFeatureToggles
|
||||
import com.tangem.lib.auth.session.WalletRegistrar
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
|
|
@ -21,7 +22,7 @@ import javax.inject.Inject
|
|||
* never blocks the user). MOBILE wallets register without UI; COLD wallets attest inside a live
|
||||
* card session (no extra tap) and POST after the session closes.
|
||||
*/
|
||||
internal class WalletRegistrationLauncher @Inject constructor(
|
||||
class WalletRegistrationLauncher @Inject internal constructor(
|
||||
private val walletRegistrar: WalletRegistrar,
|
||||
private val mobileSigner: MobileWalletRegistrationSigner,
|
||||
private val coldSigner: ColdWalletRegistrationSigner,
|
||||
|
|
@ -71,11 +72,21 @@ internal class WalletRegistrationLauncher @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/** Launch-time safety net: registers any not-yet-registered MOBILE wallets (no UI). */
|
||||
/**
|
||||
* Launch-time safety net: registers not-yet-registered MOBILE wallets without any UI.
|
||||
*
|
||||
* Only wallets that can sign **silently** are retried — i.e. [HotWalletId.AuthType.NoPassword].
|
||||
* Password/Biometry wallets would pop an unlock prompt (see `DefaultHotWalletAccessor`), which
|
||||
* must never happen at startup; those are left to register when a real unlock context exists
|
||||
* (e.g. on creation, or the next time the user unlocks them).
|
||||
*/
|
||||
suspend fun retryMobileRegistrations(userWallets: List<UserWallet>) {
|
||||
if (!authFeatureToggles.isBackendAuthenticationEnabled) return
|
||||
|
||||
userWallets.filterIsInstance<UserWallet.Hot>().forEach { registerMobile(it) }
|
||||
userWallets.asSequence()
|
||||
.filterIsInstance<UserWallet.Hot>()
|
||||
.filter { it.hotWalletId.authType == HotWalletId.AuthType.NoPassword }
|
||||
.forEach { registerMobile(it) }
|
||||
}
|
||||
|
||||
private fun UserWalletId.toBase64(): String = Base64.encodeToString(value, Base64.NO_WRAP)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import arrow.core.right
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import com.tangem.lib.auth.AuthFeatureToggles
|
||||
import com.tangem.lib.auth.session.WalletRegistrar
|
||||
import com.tangem.lib.auth.session.WalletSigner
|
||||
|
|
@ -75,17 +76,29 @@ internal class WalletRegistrationLauncherTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `retryMobileRegistrations registers only hot wallets`() = runTest {
|
||||
fun `retryMobileRegistrations registers only silently-signable hot wallets`() = runTest {
|
||||
every { authFeatureToggles.isBackendAuthenticationEnabled } returns true
|
||||
|
||||
launcher.retryMobileRegistrations(listOf(hotWallet(), mockk<UserWallet.Cold>()))
|
||||
launcher.retryMobileRegistrations(
|
||||
listOf(
|
||||
hotWallet(authType = HotWalletId.AuthType.NoPassword), // registered — signs silently
|
||||
hotWallet(authType = HotWalletId.AuthType.Password), // skipped — would prompt
|
||||
hotWallet(authType = HotWalletId.AuthType.Biometry), // skipped — would prompt
|
||||
mockk<UserWallet.Cold>(), // skipped — not a hot wallet
|
||||
),
|
||||
)
|
||||
|
||||
coVerify(exactly = 1) { walletRegistrar.register(any(), any()) }
|
||||
}
|
||||
|
||||
private fun hotWallet(walletIdValue: ByteArray = ByteArray(32) { 1 }): UserWallet.Hot {
|
||||
private fun hotWallet(
|
||||
walletIdValue: ByteArray = ByteArray(32) { 1 },
|
||||
authType: HotWalletId.AuthType = HotWalletId.AuthType.NoPassword,
|
||||
): UserWallet.Hot {
|
||||
val hotWalletId = mockk<HotWalletId> { every { this@mockk.authType } returns authType }
|
||||
val wallet = mockk<UserWallet.Hot>()
|
||||
every { wallet.walletId } returns UserWalletId(value = walletIdValue)
|
||||
every { wallet.hotWalletId } returns hotWalletId
|
||||
return wallet
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue