diff --git a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/viewmodel/MainViewModel.kt b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/viewmodel/MainViewModel.kt index b4612ef5..15643434 100644 --- a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/viewmodel/MainViewModel.kt +++ b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/presentation/viewmodel/MainViewModel.kt @@ -45,6 +45,11 @@ class MainViewModel @Inject constructor( private val _hasEnteredSession = MutableStateFlow(false) val hasEnteredSession: StateFlow = _hasEnteredSession.asStateFlow() + // Synchronous flag to prevent participant_joined from adding duplicates after session_started + // This is set immediately (synchronously) when session_started is processed, ensuring + // any subsequent participant_joined events in the same callback queue will see the flag + private var sessionStartedForSession: String? = null + init { // Start initialization on app launch checkAllServices() @@ -339,6 +344,12 @@ class MainViewModel @Inject constructor( when (event.eventType) { "session_started" -> { + // CRITICAL: Set flag immediately (synchronously) to prevent subsequent + // participant_joined events from adding duplicates. This must be the + // first line before any async operations. + sessionStartedForSession = event.sessionId + android.util.Log.d("MainViewModel", "Session started flag set for: ${event.sessionId}") + // Check if this is for keygen initiator (CreateWallet) val currentSessionId = _currentSessionId.value if (currentSessionId != null && event.sessionId == currentSessionId) { @@ -396,11 +407,12 @@ class MainViewModel @Inject constructor( "party_joined", "participant_joined" -> { android.util.Log.d("MainViewModel", "Processing participant_joined event...") - // Don't add participants if keygen/sign has already started - // This prevents duplicate additions after session_started event - val currentStatus = repository.sessionStatus.value - if (currentStatus == SessionStatus.IN_PROGRESS || currentStatus == SessionStatus.COMPLETED) { - android.util.Log.d("MainViewModel", " Session already in progress/completed, ignoring participant_joined") + // CRITICAL: Check synchronous flag first - if session_started was already + // processed for this session, don't add more participants + // This is 100% reliable because the flag is set synchronously in session_started + // handler before any async operations, and callbacks are processed sequentially + if (sessionStartedForSession == event.sessionId) { + android.util.Log.d("MainViewModel", " Session already started for ${event.sessionId}, ignoring participant_joined") return@setSessionEventCallback } @@ -548,6 +560,8 @@ class MainViewModel @Inject constructor( _publicKey.value = null _createdInviteCode.value = null _hasEnteredSession.value = false + // Reset synchronous flag for fresh session + sessionStartedForSession = null // Reset session status to WAITING for fresh start repository.resetSessionStatus() } @@ -740,6 +754,8 @@ class MainViewModel @Inject constructor( pendingJoinToken = "" pendingPassword = "" pendingJoinKeygenInfo = null + // Reset synchronous flag for fresh session + sessionStartedForSession = null // Reset session status to WAITING for fresh start repository.resetSessionStatus() } @@ -925,6 +941,8 @@ class MainViewModel @Inject constructor( pendingCoSignInviteCode = "" pendingCoSignJoinToken = "" pendingJoinSignInfo = null + // Reset synchronous flag for fresh session + sessionStartedForSession = null // Reset session status to WAITING for fresh start repository.resetSessionStatus() } @@ -1671,6 +1689,8 @@ class MainViewModel @Inject constructor( _signature.value = null _txHash.value = null pendingSignInitiatorInfo = null + // Reset synchronous flag for fresh session + sessionStartedForSession = null // Reset session status to WAITING for fresh start repository.resetSessionStatus() }