Updated on 2026-08-14
This commit is contained in:
parent
d5fdc0d921
commit
b73b6c3fe7
6 changed files with 128 additions and 18 deletions
|
|
@ -55,6 +55,7 @@ internal class DefaultWcInitializeUseCase(
|
|||
onSuccess = {
|
||||
val walletDelegate = defineWalletDelegate()
|
||||
WalletKit.setWalletDelegate(walletDelegate)
|
||||
wcSdkObservers.forEach { it.onWcSdkInit() }
|
||||
},
|
||||
onError = { error ->
|
||||
Timber.e("Error while initializing Web3Wallet: $error")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.data.walletconnect.sessions
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
|
|
@ -13,10 +16,14 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal class DefaultWcSessionsManager constructor(
|
||||
private val store: WalletConnectStore,
|
||||
|
|
@ -26,19 +33,50 @@ internal class DefaultWcSessionsManager constructor(
|
|||
private val scope: CoroutineScope,
|
||||
) : WcSessionsManager, WcSdkObserver {
|
||||
|
||||
private val onSessionDelete = Channel<Wallet.Model.SessionDelete>(capacity = Channel.BUFFERED)
|
||||
private val oneTimeMigration = MutableStateFlow(true)
|
||||
|
||||
override val sessions: Flow<Map<UserWalletId, List<WcSession>>>
|
||||
get() = store.sessions
|
||||
.onEach(::migrateLegacyStore)
|
||||
.map(::associateWithSdk)
|
||||
.distinctUntilChanged()
|
||||
.transform { inStore ->
|
||||
if (oneTimeMigration.value) {
|
||||
oneTimeMigration.value = false
|
||||
val someMigrated = migrateLegacyStore(inStore)
|
||||
if (someMigrated) return@transform // ignore emit, wait next one
|
||||
}
|
||||
val inSdk: List<Wallet.Model.Session> = WalletKit.getListOfActiveSessions()
|
||||
val associatedSessions: List<WcSession> = associateWithSdk(inSdk, inStore)
|
||||
val someRemove = removeUnknownSessions(inStore, associatedSessions)
|
||||
if (someRemove) return@transform // ignore emit, wait next one
|
||||
emit(associatedSessions.groupBy { it.userWalletId })
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
|
||||
override fun onWcSdkInit() {
|
||||
oneTimeMigration.value = true
|
||||
listenOnSessionDelete()
|
||||
}
|
||||
|
||||
override suspend fun saveSession(userWalletId: UserWalletId, session: WcSession) {
|
||||
store.saveSession(WcSessionDTO(session.sdkModel.topic, session.userWalletId))
|
||||
}
|
||||
|
||||
override suspend fun removeSession(userWalletId: UserWalletId, session: WcSession) {
|
||||
store.removeSession(WcSessionDTO(session.sdkModel.topic, session.userWalletId))
|
||||
override suspend fun removeSession(userWalletId: UserWalletId, session: WcSession): Either<Throwable, Unit> {
|
||||
val topic = session.sdkModel.topic
|
||||
val sdkCall = sdkDisconnectSession(topic)
|
||||
sdkCall.onLeft { return it.left() }
|
||||
suspend fun waitSdkCallback() = onSessionDelete.receiveAsFlow().first {
|
||||
val isSomeError = it is Wallet.Model.SessionDelete.Error
|
||||
val isDeleted = it is Wallet.Model.SessionDelete.Success && it.topic == topic
|
||||
isSomeError || isDeleted
|
||||
}
|
||||
|
||||
val waitSdkCallback = runCatching { withTimeout(10.seconds) { waitSdkCallback() } }
|
||||
val sdkCallback = waitSdkCallback.getOrElse { return it.left() }
|
||||
return when (sdkCallback) {
|
||||
is Wallet.Model.SessionDelete.Error -> sdkCallback.error.left()
|
||||
is Wallet.Model.SessionDelete.Success -> Unit.right()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findSessionByTopic(topic: String): WcSession? = withContext(dispatchers.io) {
|
||||
|
|
@ -48,45 +86,66 @@ internal class DefaultWcSessionsManager constructor(
|
|||
}
|
||||
|
||||
override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) {
|
||||
if (sessionDelete !is Wallet.Model.SessionDelete.Success) return
|
||||
Timber.i("onSessionDelete: $sessionDelete")
|
||||
scope.launch {
|
||||
val storedSessions = store.findSessionByTopic(sessionDelete.topic) ?: return@launch
|
||||
store.removeSession(storedSessions)
|
||||
}
|
||||
onSessionDelete.trySend(sessionDelete)
|
||||
}
|
||||
|
||||
private suspend fun migrateLegacyStore(inNewStoreSessions: Set<WcSessionDTO>) {
|
||||
private suspend fun migrateLegacyStore(inNewStoreSessions: Set<WcSessionDTO>): Boolean {
|
||||
val walletIds = getWallets.invokeSync().mapTo(mutableSetOf()) { it.walletId }
|
||||
val inLegacyStoreSessions = walletIds
|
||||
.map { walletId ->
|
||||
flow { emit(legacyStore.loadSessions(walletId.stringValue).map { WcSessionDTO(it.topic, walletId) }) }
|
||||
}
|
||||
.merge()
|
||||
.first()
|
||||
.reduce { accumulator, value -> accumulator.plus(value) }
|
||||
|
||||
val mustSaveInNewStore = inLegacyStoreSessions.subtract(inNewStoreSessions)
|
||||
if (mustSaveInNewStore.isNotEmpty()) store.saveSessions(mustSaveInNewStore)
|
||||
return mustSaveInNewStore.isNotEmpty()
|
||||
}
|
||||
|
||||
private suspend fun associateWithSdk(storeSessions: Set<WcSessionDTO>): Map<UserWalletId, List<WcSession>> {
|
||||
val sdkSessions = WalletKit.getListOfActiveSessions()
|
||||
private fun associateWithSdk(
|
||||
sdkSessions: List<Wallet.Model.Session>,
|
||||
storeSessions: Set<WcSessionDTO>,
|
||||
): List<WcSession> {
|
||||
val wcSessions = sdkSessions.mapNotNull { sdkSession ->
|
||||
val storedSessions = storeSessions.find { it.topic == sdkSession.topic }
|
||||
?: return@mapNotNull null
|
||||
WcSession(userWalletId = storedSessions.walletId, sdkModel = WcSdkSessionConverter.convert(sdkSession))
|
||||
}
|
||||
return wcSessions
|
||||
}
|
||||
|
||||
private suspend fun removeUnknownSessions(storeSessions: Set<WcSessionDTO>, wcSessions: List<WcSession>): Boolean {
|
||||
val unknownStoredSessions = storeSessions
|
||||
.filterNot { dto -> wcSessions.any { it.sdkModel.topic == dto.topic } }
|
||||
val haveSomeUnknown = unknownStoredSessions.isNotEmpty()
|
||||
|
||||
if (unknownStoredSessions.isNotEmpty()) {
|
||||
if (haveSomeUnknown) {
|
||||
unknownStoredSessions.forEach { unknown ->
|
||||
legacyStore.removeSession(unknown.walletId.stringValue, unknown.topic)
|
||||
}
|
||||
store.removeSessions(unknownStoredSessions.toSet())
|
||||
}
|
||||
return haveSomeUnknown
|
||||
}
|
||||
|
||||
return wcSessions.groupBy { it.userWalletId }
|
||||
private suspend fun sdkDisconnectSession(topic: String): Either<Throwable, Unit> {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
WalletKit.disconnectSession(
|
||||
params = Wallet.Params.SessionDisconnect(topic),
|
||||
onSuccess = { continuation.resume(Unit.right()) },
|
||||
onError = { continuation.resume(it.throwable.left()) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun listenOnSessionDelete() {
|
||||
onSessionDelete.receiveAsFlow()
|
||||
.filterIsInstance<Wallet.Model.SessionDelete.Success>()
|
||||
.mapNotNull { sessionDelete -> store.findSessionByTopic(sessionDelete.topic) }
|
||||
.onEach { sessionDto -> store.removeSession(sessionDto) }
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ internal interface WcSdkObserver : WalletKit.WalletDelegate {
|
|||
override val onSessionAuthenticate: ((Wallet.Model.SessionAuthenticate, Wallet.Model.VerifyContext) -> Unit)?
|
||||
get() = super.onSessionAuthenticate
|
||||
|
||||
fun onWcSdkInit() {}
|
||||
|
||||
override fun onConnectionStateChange(state: Wallet.Model.ConnectionState) {}
|
||||
|
||||
override fun onError(error: Wallet.Model.Error) {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue