diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 4150bcaebc..089e956c61 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -40,12 +40,11 @@ import io.element.android.anvilannotations.ContributesNode import io.element.android.appnav.di.MatrixClientsHolder import io.element.android.appnav.intent.IntentResolver import io.element.android.appnav.intent.ResolvedIntent +import io.element.android.appnav.root.RootNavStateFlowFactory import io.element.android.appnav.root.RootPresenter import io.element.android.appnav.root.RootView -import io.element.android.features.login.api.LoginUserStory import io.element.android.features.login.api.oidc.OidcAction import io.element.android.features.login.api.oidc.OidcActionFlow -import io.element.android.features.preferences.api.CacheService import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -57,29 +56,22 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.flow.distinctUntilChanged - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart import kotlinx.parcelize.Parcelize import timber.log.Timber -import java.util.UUID @ContributesNode(AppScope::class) class RootFlowNode @AssistedInject constructor( @Assisted val buildContext: BuildContext, @Assisted plugins: List, private val authenticationService: MatrixAuthenticationService, - private val cacheService: CacheService, + private val navStateFlowFactory: RootNavStateFlowFactory, private val matrixClientsHolder: MatrixClientsHolder, private val presenter: RootPresenter, private val bugReportEntryPoint: BugReportEntryPoint, private val intentResolver: IntentResolver, private val oidcActionFlow: OidcActionFlow, - private val loginUserStory: LoginUserStory, ) : BackstackNode( backstack = BackStack( @@ -91,26 +83,25 @@ class RootFlowNode @AssistedInject constructor( ) { override fun onBuilt() { - matrixClientsHolder.restore(buildContext.savedStateMap) + matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap) super.onBuilt() - observeLoggedInState() + observeNavState() } override fun onSaveInstanceState(state: MutableSavedStateMap) { super.onSaveInstanceState(state) - matrixClientsHolder.save(state) + matrixClientsHolder.saveIntoSavedState(state) + navStateFlowFactory.saveIntoSavedState(state) } - private fun observeLoggedInState() { - combine( - cacheService.onClearedCacheEventFlow(), - isUserLoggedInFlow(), - ) { _, isLoggedIn -> isLoggedIn } - .onEach { isLoggedIn -> - Timber.v("isLoggedIn=$isLoggedIn") - if (isLoggedIn) { + private fun observeNavState() { + navStateFlowFactory.create(buildContext.savedStateMap) + .distinctUntilChanged() + .onEach { navState -> + Timber.v("navState=$navState") + if (navState.isLoggedIn) { tryToRestoreLatestSession( - onSuccess = { switchToLoggedInFlow(it) }, + onSuccess = { sessionId -> switchToLoggedInFlow(sessionId, navState.cacheIndex) }, onFailure = { switchToNotLoggedInFlow() } ) } else { @@ -120,19 +111,8 @@ class RootFlowNode @AssistedInject constructor( .launchIn(lifecycleScope) } - - private fun switchToLoggedInFlow(sessionId: SessionId) { - backstack.safeRoot(NavTarget.LoggedInFlow(sessionId)) - } - - private fun isUserLoggedInFlow(): Flow { - return combine( - authenticationService.isLoggedIn(), - loginUserStory.loginFlowIsDone - ) { isLoggedIn, loginFlowIsDone -> - isLoggedIn && loginFlowIsDone - } - .distinctUntilChanged() + private fun switchToLoggedInFlow(sessionId: SessionId, navId: Int) { + backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, navId)) } private fun switchToNotLoggedInFlow() { @@ -145,14 +125,8 @@ class RootFlowNode @AssistedInject constructor( onFailure: () -> Unit = {}, onSuccess: (SessionId) -> Unit = {}, ) { - // If the session is already known it'll be restored by the node hierarchy - if (matrixClientsHolder.knowSession(sessionId)) { - Timber.v("Session $sessionId already alive, no need to restore.") - return - } - authenticationService.restoreSession(sessionId) - .onSuccess { matrixClient -> - matrixClientsHolder.add(matrixClient) + matrixClientsHolder.getOrRestore(sessionId) + .onSuccess { Timber.v("Succeed to restore session $sessionId") onSuccess(sessionId) } @@ -204,7 +178,7 @@ class RootFlowNode @AssistedInject constructor( @Parcelize data class LoggedInFlow( val sessionId: SessionId, - val navId: UUID = UUID.randomUUID(), + val navId: Int ) : NavTarget @Parcelize @@ -278,11 +252,5 @@ class RootFlowNode @AssistedInject constructor( navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId } } - - private fun CacheService.onClearedCacheEventFlow(): Flow { - return clearedCacheEventFlow - .onEach { sessionId -> matrixClientsHolder.remove(sessionId) } - .map { } - .onStart { emit((Unit)) } - } } + diff --git a/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt index bbb14d4d29..3e36e7d692 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt @@ -18,23 +18,28 @@ package io.element.android.appnav.di import com.bumble.appyx.core.state.MutableSavedStateMap import com.bumble.appyx.core.state.SavedStateMap +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHolder.SaveInstanceKey" -class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) { +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) : MatrixClientProvider { private val sessionIdsToMatrixClient = ConcurrentHashMap() - - fun add(matrixClient: MatrixClient) { - sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient - } + private val restoreMutex = Mutex() fun removeAll() { sessionIdsToMatrixClient.clear() @@ -44,16 +49,21 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService: sessionIdsToMatrixClient.remove(sessionId) } - fun isEmpty(): Boolean = sessionIdsToMatrixClient.isEmpty() - - fun knowSession(sessionId: SessionId): Boolean = sessionIdsToMatrixClient.containsKey(sessionId) - fun getOrNull(sessionId: SessionId): MatrixClient? { return sessionIdsToMatrixClient[sessionId] } + override suspend fun getOrRestore(sessionId: SessionId): Result { + return restoreMutex.withLock { + when (val matrixClient = getOrNull(sessionId)) { + null -> restore(sessionId) + else -> Result.success(matrixClient) + } + } + } + @Suppress("UNCHECKED_CAST") - fun restore(state: SavedStateMap?) { + fun restoreWithSavedState(state: SavedStateMap?) { Timber.d("Restore state") if (state == null || sessionIdsToMatrixClient.isNotEmpty()) return Unit.also { Timber.w("Restore with non-empty map") @@ -64,21 +74,25 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService: // Not ideal but should only happens in case of process recreation. This ensure we restore all the active sessions before restoring the node graphs. runBlocking { sessionIds.forEach { sessionId -> - Timber.d("Restore matrix session: $sessionId") - authenticationService.restoreSession(sessionId) - .onSuccess { matrixClient -> - add(matrixClient) - } - .onFailure { - Timber.e("Fail to restore session") - } + restore(sessionId) } } } - fun save(state: MutableSavedStateMap) { + fun saveIntoSavedState(state: MutableSavedStateMap) { val sessionKeys = sessionIdsToMatrixClient.keys.toTypedArray() Timber.d("Save matrix session keys = ${sessionKeys.map { it.value }}") state[SAVE_INSTANCE_KEY] = sessionKeys } + + private suspend fun restore(sessionId: SessionId): Result { + Timber.d("Restore matrix session: $sessionId") + return authenticationService.restoreSession(sessionId) + .onSuccess { matrixClient -> + sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient + } + .onFailure { + Timber.e("Fail to restore session") + } + } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index a03b6d61a2..20ec9f48b4 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -32,11 +32,11 @@ import com.bumble.appyx.core.node.node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.newRoot import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.appnav.NodeLifecycleCallback -import io.element.android.appnav.safeRoot import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.libraries.architecture.BackstackNode @@ -92,9 +92,9 @@ class RoomFlowNode @AssistedInject constructor( .distinctUntilChanged() .onEach { isLoaded -> if (isLoaded) { - backstack.safeRoot(NavTarget.Loaded) + backstack.newRoot(NavTarget.Loaded) } else { - backstack.safeRoot(NavTarget.Loading) + backstack.newRoot(NavTarget.Loading) } }.launchIn(lifecycleScope) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavState.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavState.kt new file mode 100644 index 0000000000..ed3ac15972 --- /dev/null +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavState.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.appnav.root + +/** + * [RootNavState] produced by [RootNavStateFlowFactory]. + */ +data class RootNavState( + /** + * This value is incremented when a clear cache is done. + * Can be useful to track to force ui state to re-render + */ + val cacheIndex: Int, + /** + * true if we are currently loggedIn. + */ + val isLoggedIn: Boolean +) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt new file mode 100644 index 0000000000..0e8d93b0c9 --- /dev/null +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.appnav.root + +import com.bumble.appyx.core.state.MutableSavedStateMap +import com.bumble.appyx.core.state.SavedStateMap +import io.element.android.appnav.di.MatrixClientsHolder +import io.element.android.features.login.api.LoginUserStory +import io.element.android.features.preferences.api.CacheService +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +private const val SAVE_INSTANCE_KEY = "io.element.android.x.RootNavStateFlowFactory.SAVE_INSTANCE_KEY" + +/** + * This class is responsible for creating a flow of [RootNavState]. + * It gathers data from multiple datasource and creates a unique one. + */ +class RootNavStateFlowFactory @Inject constructor( + private val authenticationService: MatrixAuthenticationService, + private val cacheService: CacheService, + private val matrixClientsHolder: MatrixClientsHolder, + private val loginUserStory: LoginUserStory, +) { + + private var currentCacheIndex = 0 + + fun create(savedStateMap: SavedStateMap?): Flow { + return combine( + cacheIndexFlow(savedStateMap), + isUserLoggedInFlow(), + ) { cacheIndex, isLoggedIn -> + RootNavState(cacheIndex = cacheIndex, isLoggedIn = isLoggedIn) + } + } + + fun saveIntoSavedState(stateMap: MutableSavedStateMap) { + stateMap[SAVE_INSTANCE_KEY] = currentCacheIndex + } + + /** + * @return a flow of integer, where each time a clear cache is done, we have a new incremented value. + */ + private fun cacheIndexFlow(savedStateMap: SavedStateMap?): Flow { + val initialCacheIndex = savedStateMap.getCacheIndexOrDefault() + return cacheService.clearedCacheEventFlow + .onEach { sessionId -> + matrixClientsHolder.remove(sessionId) + } + .toIndexFlow(initialCacheIndex) + .onEach { cacheIndex -> + currentCacheIndex = cacheIndex + } + } + + private fun isUserLoggedInFlow(): Flow { + return combine( + authenticationService.isLoggedIn(), + loginUserStory.loginFlowIsDone + ) { isLoggedIn, loginFlowIsDone -> + isLoggedIn && loginFlowIsDone + } + .distinctUntilChanged() + } + + /** + * @return a flow of integer that increments the value by one each time a new element is emitted upstream. + */ + private fun Flow.toIndexFlow(initialValue: Int): Flow = flow { + var index = initialValue + emit(initialValue) + collect { + emit(++index) + } + } + + private fun SavedStateMap?.getCacheIndexOrDefault(): Int { + return this?.get(SAVE_INSTANCE_KEY) as? Int ?: 0 + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 722e83fe87..c03881144e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -247,6 +247,7 @@ koverMerged { target = kotlinx.kover.api.VerificationTarget.CLASS overrideClassFilter { includes += "^*State$" + excludes += "io.element.android.appnav.root.RootNavState*" excludes += "io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*" excludes += "io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*" excludes += "io.element.android.libraries.matrix.api.room.RoomMembershipState*" diff --git a/changelog.d/880.bugfix b/changelog.d/880.bugfix new file mode 100644 index 0000000000..b6d46820a3 --- /dev/null +++ b/changelog.d/880.bugfix @@ -0,0 +1 @@ +Fix sliding sync loop restarts due to expirations. diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt index 0ef0e02558..24c67a3824 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt @@ -27,7 +27,6 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient -import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import javax.inject.Inject diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClientProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClientProvider.kt new file mode 100644 index 0000000000..44d1a1d1a6 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClientProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api + +import io.element.android.libraries.matrix.api.core.SessionId + +interface MatrixClientProvider { + /** + * Can be used to get or restore a MatrixClient with the given [SessionId]. + * If a [MatrixClient] is already in memory, it'll return it. Otherwise it'll try to restore one. + * Most of the time you want to use injected constructor instead of retrieving a MatrixClient with this provider. + */ + suspend fun getOrRestore(sessionId: SessionId): Result +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ClientException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ClientException.kt new file mode 100644 index 0000000000..52dbd2eb12 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ClientException.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.exception + +sealed class ClientException(message: String) : Exception(message) { + class Generic(message: String) : ClientException(message) + class Other(message: String) : ClientException(message) +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index d7195ebf11..a467a161e0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -165,8 +165,10 @@ class RustMatrixClient constructor( val cachedRoomListItem = roomListService.roomOrNull(roomId.value) val fullRoom = cachedRoomListItem?.fullRoom() if (cachedRoomListItem == null || fullRoom == null) { + Timber.d("No room cached for $roomId") null } else { + Timber.d("Found room cached for $roomId") Pair(cachedRoomListItem, fullRoom) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt index c264a95f67..f0feb2857d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt @@ -19,7 +19,7 @@ package io.element.android.libraries.matrix.impl.auth import io.element.android.libraries.matrix.api.auth.AuthenticationException import org.matrix.rustcomponents.sdk.AuthenticationException as RustAuthenticationException -fun Throwable.mapAuthenticationException(): Throwable { +fun Throwable.mapAuthenticationException(): AuthenticationException { return when (this) { is RustAuthenticationException.ClientMissing -> AuthenticationException.ClientMissing(this.message!!) is RustAuthenticationException.Generic -> AuthenticationException.Generic(this.message!!) @@ -35,6 +35,6 @@ fun Throwable.mapAuthenticationException(): Throwable { is RustAuthenticationException.OidcNotSupported -> AuthenticationException.OidcError("OidcNotSupported", message!!) */ - else -> this + else -> AuthenticationException.Generic(this.message ?: "Unknown error") } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 0600e96f3b..a5314ce06e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.impl.RustMatrixClient +import io.element.android.libraries.matrix.impl.exception.mapClientException import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionStore @@ -95,7 +96,7 @@ class RustMatrixAuthenticationService @Inject constructor( throw IllegalStateException("No session to restore with id $sessionId") } }.mapFailure { failure -> - failure.mapAuthenticationException() + failure.mapClientException() } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ClientException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ClientException.kt new file mode 100644 index 0000000000..6efca88f9b --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ClientException.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.exception + +import io.element.android.libraries.matrix.api.exception.ClientException +import org.matrix.rustcomponents.sdk.ClientException as RustClientException + +fun Throwable.mapClientException(): ClientException { + return when (this) { + is RustClientException.Generic -> ClientException.Generic(msg) + else -> ClientException.Other(message ?: "Unknown error") + } +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt index 41d38f3ff5..9cd4956dca 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt @@ -23,12 +23,12 @@ import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.libraries.push.api.store.PushDataStore import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent @@ -57,7 +57,7 @@ class DefaultNotificationDrawerManager @Inject constructor( private val coroutineScope: CoroutineScope, private val dispatchers: CoroutineDispatchers, private val buildMeta: BuildMeta, - private val matrixAuthenticationService: MatrixAuthenticationService, + private val matrixClientProvider: MatrixClientProvider, ) : NotificationDrawerManager { /** * Lazily initializes the NotificationState as we rely on having a current session in order to fetch the persisted queue of events. @@ -251,11 +251,10 @@ class DefaultNotificationDrawerManager @Inject constructor( val currentUser = tryOrNull( onError = { Timber.e(it, "Unable to retrieve info for user ${sessionId.value}") }, operation = { - val client = matrixAuthenticationService.restoreSession(sessionId).getOrNull() - + val client = matrixClientProvider.getOrRestore(sessionId).getOrThrow() // myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash - val myUserDisplayName = client?.loadUserDisplayName()?.getOrNull() ?: sessionId.value - val userAvatarUrl = client?.loadUserAvatarURLString()?.getOrNull() + val myUserDisplayName = client.loadUserDisplayName().getOrNull() ?: sessionId.value + val userAvatarUrl = client.loadUserAvatarURLString().getOrNull() MatrixUser( userId = sessionId, displayName = myUserDisplayName, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index f0b70d8fac..dbdddf1ac9 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -18,7 +18,6 @@ package io.element.android.libraries.push.impl.notifications import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId @@ -35,6 +34,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessage import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType +import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.log.pushLoggerTag import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent @@ -59,25 +59,25 @@ class NotifiableEventResolver @Inject constructor( private val stringProvider: StringProvider, // private val noticeEventFormatter: NoticeEventFormatter, // private val displayableEventFormatter: DisplayableEventFormatter, - private val matrixAuthenticationService: MatrixAuthenticationService, private val buildMeta: BuildMeta, private val clock: SystemClock, + private val matrixClientProvider: MatrixClientProvider, ) { suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? { // Restore session - val session = matrixAuthenticationService.restoreSession(sessionId).getOrNull() ?: return null - val notificationService = session.notificationService() + val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null + val notificationService = client.notificationService() val notificationData = notificationService.getNotification( - userId = sessionId, - roomId = roomId, - eventId = eventId, + userId = sessionId, + roomId = roomId, + eventId = eventId, // FIXME should be true in the future, but right now it's broken // (https://github.com/vector-im/element-x-android/issues/640#issuecomment-1612913658) - filterByPushRules = false, - ).onFailure { - Timber.tag(loggerTag.value).e(it, "Unable to resolve event: $eventId.") - }.getOrNull() + filterByPushRules = false, + ).onFailure { + Timber.tag(loggerTag.value).e(it, "Unable to resolve event: $eventId.") + }.getOrNull() // TODO this notificationData is not always valid at the moment, sometimes the Rust SDK can't fetch the matching event return notificationData?.asNotifiableEvent(sessionId)