Skip to content

Commit

Permalink
fix: missing ServerConfig crashes after session expired / logout [WPB…
Browse files Browse the repository at this point in the history
…-5960] (#2570)
  • Loading branch information
saleniuk committed Jan 12, 2024
1 parent 15587ad commit 4a723c5
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 100 deletions.
25 changes: 25 additions & 0 deletions app/src/main/kotlin/com/wire/android/GlobalObserversManager.kt
Expand Up @@ -20,18 +20,25 @@

package com.wire.android

import com.wire.android.datastore.UserDataStoreProvider
import com.wire.android.di.KaliumCoreLogic
import com.wire.android.notification.NotificationChannelsManager
import com.wire.android.notification.WireNotificationManager
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.logout.LogoutReason
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.auth.LogoutCallback
import com.wire.kalium.logic.feature.user.webSocketStatus.ObservePersistentWebSocketConnectionStatusUseCase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton
Expand All @@ -46,6 +53,7 @@ class GlobalObserversManager @Inject constructor(
@KaliumCoreLogic private val coreLogic: CoreLogic,
private val notificationManager: WireNotificationManager,
private val notificationChannelsManager: NotificationChannelsManager,
private val userDataStoreProvider: UserDataStoreProvider,
) {
private val scope = CoroutineScope(SupervisorJob() + dispatcherProvider.io())

Expand All @@ -58,6 +66,7 @@ class GlobalObserversManager @Inject constructor(
}
}
}
scope.handleLogouts()
}

private suspend fun setUpNotifications() {
Expand Down Expand Up @@ -91,4 +100,20 @@ class GlobalObserversManager @Inject constructor(
// but we can't start PersistentWebSocketService here, to avoid ForegroundServiceStartNotAllowedException
}
}

private fun CoroutineScope.handleLogouts() {
callbackFlow<Unit> {
val callback: LogoutCallback = object : LogoutCallback {
override suspend fun invoke(userId: UserId, reason: LogoutReason) {
notificationManager.stopObservingOnLogout(userId)
notificationChannelsManager.deleteChannelGroup(userId)
if (reason != LogoutReason.SELF_SOFT_LOGOUT) {
userDataStoreProvider.getOrCreate(userId).clear()
}
}
}
coreLogic.getGlobalScope().logoutCallbackManager.register(callback)
awaitClose { coreLogic.getGlobalScope().logoutCallbackManager.unregister(callback) }
}.launchIn(this)
}
}
6 changes: 6 additions & 0 deletions app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt
Expand Up @@ -41,6 +41,7 @@ import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveTeamSettingsSel
import com.wire.kalium.logic.feature.selfDeletingMessages.PersistNewSelfDeletionTimerUseCase
import com.wire.kalium.logic.feature.server.ServerConfigForAccountUseCase
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.feature.session.DoesValidSessionExistUseCase
import com.wire.kalium.logic.feature.session.GetSessionsUseCase
import com.wire.kalium.logic.feature.session.UpdateCurrentSessionUseCase
import com.wire.kalium.logic.feature.user.MarkFileSharingChangeAsNotifiedUseCase
Expand Down Expand Up @@ -310,6 +311,11 @@ class UseCaseModule {
fun provideObserveValidAccountsUseCase(@KaliumCoreLogic coreLogic: CoreLogic): ObserveValidAccountsUseCase =
coreLogic.getGlobalScope().observeValidAccounts

@ViewModelScoped
@Provides
fun provideDoesValidSessionExistsUseCase(@KaliumCoreLogic coreLogic: CoreLogic): DoesValidSessionExistUseCase =
coreLogic.getGlobalScope().doesValidSessionExist

@ViewModelScoped
@Provides
fun observeSecurityClassificationLabelUseCase(
Expand Down
Expand Up @@ -20,6 +20,7 @@

package com.wire.android.feature

import com.wire.android.appLogger
import com.wire.android.di.ApplicationScope
import com.wire.android.di.AuthServerConfigProvider
import com.wire.android.navigation.BackStackMode
Expand All @@ -40,6 +41,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -64,11 +67,12 @@ class AccountSwitchUseCase @Inject constructor(
}

suspend operator fun invoke(params: SwitchAccountParam): SwitchAccountResult {
val current = currentAccount
val current = currentAccount.await()
appLogger.i("$TAG Switching account invoked: ${params.toLogString()}, current account: ${current?.userId?.toLogString() ?: "-"}")
return when (params) {
is SwitchAccountParam.SwitchToAccount -> switch(params.userId, current.await())
SwitchAccountParam.TryToSwitchToNextAccount -> getNextAccountIfPossibleAndSwitch(current.await())
SwitchAccountParam.Clear -> switch(null, current.await())
is SwitchAccountParam.SwitchToAccount -> switch(params.userId, current)
SwitchAccountParam.TryToSwitchToNextAccount -> getNextAccountIfPossibleAndSwitch(current)
SwitchAccountParam.Clear -> switch(null, current)
}
}

Expand All @@ -83,6 +87,8 @@ class AccountSwitchUseCase @Inject constructor(
}?.userId
}
}
if (nextSessionId == null) appLogger.i("$TAG No next account to switch to")
else appLogger.i("$TAG Switching to next account: ${nextSessionId.toLogString()}")
return switch(nextSessionId, current)
}

Expand All @@ -104,6 +110,7 @@ class AccountSwitchUseCase @Inject constructor(
}

private suspend fun updateAuthServer(current: UserId) {
appLogger.i("$TAG Updating auth server config for account: ${current.toLogString()}")
serverConfigForAccountUseCase(current).let {
when (it) {
is ServerConfigForAccountUseCase.Result.Success -> authServerConfigProvider.updateAuthServer(it.config)
Expand All @@ -126,6 +133,7 @@ class AccountSwitchUseCase @Inject constructor(
}

private suspend fun handleInvalidSession(invalidAccount: AccountInfo.Invalid) {
appLogger.i("$TAG Handling invalid account: ${invalidAccount.userId.toLogString()}")
when (invalidAccount.logoutReason) {
LogoutReason.SELF_SOFT_LOGOUT, LogoutReason.SELF_HARD_LOGOUT -> {
deleteSession(invalidAccount.userId)
Expand All @@ -137,14 +145,21 @@ class AccountSwitchUseCase @Inject constructor(
}

private companion object {
const val TAG = "AccountSwitch"
const val DELETE_USER_SESSION_TIMEOUT = 3000L
}
}

sealed class SwitchAccountParam {
object TryToSwitchToNextAccount : SwitchAccountParam()
data object TryToSwitchToNextAccount : SwitchAccountParam()
data class SwitchToAccount(val userId: UserId) : SwitchAccountParam()
data object Clear : SwitchAccountParam()
private fun toLogMap(): Map<String, String> = when (this) {
is Clear -> mutableMapOf("value" to "CLEAR")
is SwitchToAccount -> mutableMapOf("value" to "SWITCH_TO_ACCOUNT", "userId" to userId.toLogString())
is TryToSwitchToNextAccount -> mutableMapOf("value" to "TRY_TO_SWITCH_TO_NEXT_ACCOUNT")
}
fun toLogString(): String = Json.encodeToString(toLogMap())
}

sealed class SwitchAccountResult {
Expand Down
11 changes: 2 additions & 9 deletions app/src/main/kotlin/com/wire/android/ui/WireActivity.kt
Expand Up @@ -90,7 +90,6 @@ import com.wire.android.util.debug.FeatureVisibilityFlags
import com.wire.android.util.debug.LocalFeatureVisibilityFlags
import com.wire.android.util.deeplink.DeepLinkResult
import com.wire.android.util.ui.updateScreenSettings
import com.wire.kalium.logic.data.user.UserId
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
Expand Down Expand Up @@ -196,10 +195,7 @@ class WireActivity : AppCompatActivity() {
// and if any NavigationCommand is executed before the graph is fully built, it will cause a NullPointerException.
setUpNavigation(navigator.navController, onComplete)
handleScreenshotCensoring()
handleDialogs(
navigator::navigate,
viewModel.currentUserId.value
)
handleDialogs(navigator::navigate)
}
}
}
Expand Down Expand Up @@ -253,10 +249,7 @@ class WireActivity : AppCompatActivity() {
}

@Composable
private fun handleDialogs(navigate: (NavigationCommand) -> Unit, userId: UserId?) {
LaunchedEffect(userId) {
featureFlagNotificationViewModel.loadInitialSync()
}
private fun handleDialogs(navigate: (NavigationCommand) -> Unit) {
with(featureFlagNotificationViewModel.featureFlagState) {
if (shouldShowTeamAppLockDialog) {
TeamAppLockFeatureFlagDialog(
Expand Down
Expand Up @@ -26,6 +26,7 @@ import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.DialogProperties
import com.wire.android.BuildConfig
import com.wire.android.R
import com.wire.android.appLogger
Expand Down Expand Up @@ -296,6 +297,7 @@ private fun accountLoggedOutDialog(reason: CurrentSessionErrorState, navigateAwa
title = stringResource(id = title),
text = text,
onDismiss = remember { { } },
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false, usePlatformDefaultWidth = false),
optionButton1Properties = WireDialogButtonProperties(
text = stringResource(R.string.label_ok),
onClick = navigateAway,
Expand Down
21 changes: 15 additions & 6 deletions app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt
Expand Up @@ -21,7 +21,6 @@
package com.wire.android.ui

import android.content.Intent
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
Expand Down Expand Up @@ -66,6 +65,8 @@ import com.wire.kalium.logic.feature.server.GetServerConfigResult
import com.wire.kalium.logic.feature.server.GetServerConfigUseCase
import com.wire.kalium.logic.feature.session.CurrentSessionFlowUseCase
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.feature.session.DoesValidSessionExistResult
import com.wire.kalium.logic.feature.session.DoesValidSessionExistUseCase
import com.wire.kalium.logic.feature.session.GetAllSessionsResult
import com.wire.kalium.logic.feature.session.GetSessionsUseCase
import com.wire.kalium.logic.feature.user.screenshotCensoring.ObserveScreenshotCensoringConfigResult
Expand Down Expand Up @@ -96,6 +97,7 @@ class WireActivityViewModel @Inject constructor(
@KaliumCoreLogic private val coreLogic: CoreLogic,
private val dispatchers: DispatcherProvider,
private val currentSessionFlow: CurrentSessionFlowUseCase,
private val doesValidSessionExist: DoesValidSessionExistUseCase,
private val getServerConfigUseCase: GetServerConfigUseCase,
private val deepLinkProcessor: DeepLinkProcessor,
private val authServerConfigProvider: AuthServerConfigProvider,
Expand All @@ -116,12 +118,12 @@ class WireActivityViewModel @Inject constructor(
private set

private val observeUserId = currentSessionFlow()
.distinctUntilChanged()
.onEach {
if (it is CurrentSessionResult.Success) {
if (it.accountInfo.isValid().not()) {
handleInvalidSession((it.accountInfo as AccountInfo.Invalid).logoutReason)
}
currentUserId.value = it.accountInfo.userId
}
}
.map { result ->
Expand All @@ -134,13 +136,14 @@ class WireActivityViewModel @Inject constructor(
} else {
null
}
}.distinctUntilChanged().flowOn(dispatchers.io()).shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
}
.distinctUntilChanged()
.flowOn(dispatchers.io())
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)

private val _observeSyncFlowState: MutableStateFlow<SyncState?> = MutableStateFlow(null)
val observeSyncFlowState: StateFlow<SyncState?> = _observeSyncFlowState

val currentUserId: MutableState<UserId?> = mutableStateOf(null)

init {
observeSyncState()
observeUpdateAppState()
Expand Down Expand Up @@ -290,7 +293,13 @@ class WireActivityViewModel @Inject constructor(

fun dismissNewClientsDialog(userId: UserId) {
globalAppState = globalAppState.copy(newClientDialog = null)
viewModelScope.launch { clearNewClientsForUser(userId) }
viewModelScope.launch {
doesValidSessionExist(userId).let {
if (it is DoesValidSessionExistResult.Success && it.doesValidSessionExist) {
clearNewClientsForUser(userId)
}
}
}
}

fun switchAccount(userId: UserId, actions: SwitchAccountActions, onComplete: () -> Unit) {
Expand Down
Expand Up @@ -56,7 +56,7 @@ class SetLockScreenViewModel @Inject constructor(
observeAppLockConfig(),
observeIsAppLockEditable()
) { config, isEditable ->
SetLockCodeViewState(
state.copy(
timeout = config.timeout,
isEditable = isEditable
)
Expand Down

0 comments on commit 4a723c5

Please sign in to comment.