From d611a241cd46b5d844be7271a6e11cba8dba9b7f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Feb 2025 15:11:12 +0100 Subject: [PATCH 1/3] Preload account management URL. It will populate the SDK in-memory cache. --- .../element/android/appnav/loggedin/LoggedInPresenter.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index 2539f9e1d54..6d01abd1530 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -26,6 +26,7 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState +import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion import io.element.android.libraries.matrix.api.sync.SyncService @@ -35,6 +36,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatu import io.element.android.libraries.push.api.PushService import io.element.android.libraries.pushproviders.api.RegistrationFailure import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -60,6 +62,7 @@ class LoggedInPresenter @Inject constructor( pushService.ignoreRegistrationError(matrixClient.sessionId) }.collectAsState(initial = false) val pusherRegistrationState = remember>> { mutableStateOf(AsyncData.Uninitialized) } + LaunchedEffect(Unit) { preloadAccountManagementUrl() } LaunchedEffect(Unit) { sessionVerificationService.sessionVerifiedStatus .onEach { sessionVerifiedStatus -> @@ -202,4 +205,9 @@ class LoggedInPresenter @Inject constructor( analyticsService.capture(CryptoSessionStateChange(changeRecoveryState, changeVerificationState)) } } + + private fun CoroutineScope.preloadAccountManagementUrl() = launch { + matrixClient.getAccountManagementUrl(AccountManagementAction.Profile).getOrNull() + matrixClient.getAccountManagementUrl(AccountManagementAction.SessionsList).getOrNull() + } } From 941dfa7d380c42eb75be77d80c1aa87cf3191770 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Feb 2025 15:45:06 +0100 Subject: [PATCH 2/3] Add tests. --- .../appnav/loggedin/LoggedInPresenterTest.kt | 155 +++++++++--------- .../impl/root/PreferencesRootPresenterTest.kt | 70 +++++--- .../libraries/matrix/test/FakeMatrixClient.kt | 10 +- 3 files changed, 129 insertions(+), 106 deletions(-) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index bfd416eaf3a..aac40e000ce 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -5,12 +5,11 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package io.element.android.appnav.loggedin -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow import app.cash.turbine.ReceiveTurbine -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.CryptoSessionStateChange import im.vector.app.features.analytics.plan.UserProperties @@ -19,6 +18,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState +import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion import io.element.android.libraries.matrix.api.sync.SyncState @@ -45,8 +45,8 @@ import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -58,10 +58,7 @@ class LoggedInPresenterTest { @Test fun `present - initial state`() = runTest { - val presenter = createLoggedInPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + createLoggedInPresenter().test { val initialState = awaitItem() assertThat(initialState.showSyncSpinner).isFalse() assertThat(initialState.pusherRegistrationState.isUninitialized()).isTrue() @@ -69,13 +66,32 @@ class LoggedInPresenterTest { } } + @Test + fun `present - ensure that account urls are preloaded`() = runTest { + val accountManagementUrlResult = lambdaRecorder> { Result.success("aUrl") } + val matrixClient = FakeMatrixClient( + accountManagementUrlResult = accountManagementUrlResult, + ) + createLoggedInPresenter( + matrixClient = matrixClient, + ).test { + awaitItem() + advanceUntilIdle() + accountManagementUrlResult.assertions().isCalledExactly(2) + .withSequence( + listOf(value(AccountManagementAction.Profile)), + listOf(value(AccountManagementAction.SessionsList)), + ) + } + } + @Test fun `present - show sync spinner`() = runTest { val roomListService = FakeRoomListService() - val presenter = createLoggedInPresenter(roomListService, SyncState.Running) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + createLoggedInPresenter( + syncState = SyncState.Running, + matrixClient = FakeMatrixClient(roomListService = roomListService), + ).test { val initialState = awaitItem() assertThat(initialState.showSyncSpinner).isFalse() roomListService.postSyncIndicator(RoomListService.SyncIndicator.Show) @@ -92,18 +108,18 @@ class LoggedInPresenterTest { val verificationService = FakeSessionVerificationService() val encryptionService = FakeEncryptionService() val buildMeta = aBuildMeta() - val presenter = LoggedInPresenter( - matrixClient = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService), + LoggedInPresenter( + matrixClient = FakeMatrixClient( + roomListService = roomListService, + encryptionService = encryptionService, + ), syncService = FakeSyncService(initialSyncState = SyncState.Running), pushService = FakePushService(), sessionVerificationService = verificationService, analyticsService = analyticsService, encryptionService = encryptionService, buildMeta = buildMeta, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + ).test { encryptionService.emitRecoveryState(RecoveryState.UNKNOWN) encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE) verificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified) @@ -129,13 +145,10 @@ class LoggedInPresenterTest { val verificationService = FakeSessionVerificationService( initialSessionVerifiedStatus = SessionVerifiedStatus.NotVerified ) - val presenter = createLoggedInPresenter( + createLoggedInPresenter( pushService = pushService, sessionVerificationService = verificationService, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + ).test { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.errorOrNull()) .isInstanceOf(PusherRegistrationFailure.AccountNotVerified::class.java) @@ -155,13 +168,13 @@ class LoggedInPresenterTest { val pushService = createFakePushService( registerWithLambda = lambda, ) - val presenter = createLoggedInPresenter( + createLoggedInPresenter( pushService = pushService, sessionVerificationService = sessionVerificationService, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + matrixClient = FakeMatrixClient( + accountManagementUrlResult = { Result.success(null) }, + ), + ).test { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue() lambda.assertions() @@ -188,13 +201,13 @@ class LoggedInPresenterTest { val pushService = createFakePushService( registerWithLambda = lambda, ) - val presenter = createLoggedInPresenter( + createLoggedInPresenter( pushService = pushService, sessionVerificationService = sessionVerificationService, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + matrixClient = FakeMatrixClient( + accountManagementUrlResult = { Result.success(null) }, + ), + ).test { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.isFailure()).isTrue() lambda.assertions() @@ -233,13 +246,13 @@ class LoggedInPresenterTest { currentPushProvider = { pushProvider }, registerWithLambda = lambda, ) - val presenter = createLoggedInPresenter( + createLoggedInPresenter( pushService = pushService, sessionVerificationService = sessionVerificationService, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + matrixClient = FakeMatrixClient( + accountManagementUrlResult = { Result.success(null) }, + ), + ).test { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue() lambda.assertions() @@ -277,13 +290,13 @@ class LoggedInPresenterTest { currentPushProvider = { pushProvider }, registerWithLambda = lambda, ) - val presenter = createLoggedInPresenter( + createLoggedInPresenter( pushService = pushService, sessionVerificationService = sessionVerificationService, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + matrixClient = FakeMatrixClient( + accountManagementUrlResult = { Result.success(null) }, + ), + ).test { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue() lambda.assertions() @@ -317,13 +330,10 @@ class LoggedInPresenterTest { currentPushProvider = { pushProvider }, registerWithLambda = lambda, ) - val presenter = createLoggedInPresenter( + createLoggedInPresenter( pushService = pushService, sessionVerificationService = sessionVerificationService, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + ).test { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.errorOrNull()) .isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java) @@ -345,13 +355,10 @@ class LoggedInPresenterTest { registerWithLambda = lambda, setIgnoreRegistrationErrorLambda = setIgnoreRegistrationErrorLambda, ) - val presenter = createLoggedInPresenter( + createLoggedInPresenter( pushService = pushService, sessionVerificationService = sessionVerificationService, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + ).test { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.errorOrNull()) .isInstanceOf(PusherRegistrationFailure.NoProvidersAvailable::class.java) @@ -394,13 +401,10 @@ class LoggedInPresenterTest { registerWithLambda = lambda, selectPushProviderLambda = selectPushProviderLambda, ) - val presenter = createLoggedInPresenter( + createLoggedInPresenter( pushService = pushService, sessionVerificationService = sessionVerificationService, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + ).test { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.errorOrNull()) .isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java) @@ -445,13 +449,13 @@ class LoggedInPresenterTest { pushProvider1 = pushProvider1, registerWithLambda = lambda, ) - val presenter = createLoggedInPresenter( + createLoggedInPresenter( pushService = pushService, sessionVerificationService = sessionVerificationService, - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + matrixClient = FakeMatrixClient( + accountManagementUrlResult = { Result.success(null) }, + ), + ).test { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue() lambda.assertions().isCalledOnce() @@ -505,10 +509,9 @@ class LoggedInPresenterTest { currentSlidingSyncVersionLambda = { Result.success(SlidingSyncVersion.Proxy) }, availableSlidingSyncVersionsLambda = { Result.success(listOf(SlidingSyncVersion.Native)) }, ) - val presenter = createLoggedInPresenter(matrixClient = matrixClient) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + createLoggedInPresenter( + matrixClient = matrixClient, + ).test { val initialState = awaitItem() assertThat(initialState.forceNativeSlidingSyncMigration).isFalse() @@ -526,13 +529,14 @@ class LoggedInPresenterTest { assertThat(ignoreSdkError).isTrue() null } - val matrixClient = FakeMatrixClient().apply { + val matrixClient = FakeMatrixClient( + accountManagementUrlResult = { Result.success(null) }, + ).apply { this.logoutLambda = logoutLambda } - val presenter = createLoggedInPresenter(matrixClient = matrixClient) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + createLoggedInPresenter( + matrixClient = matrixClient, + ).test { val initialState = awaitItem() initialState.eventSink(LoggedInEvents.LogoutAndMigrateToNativeSlidingSync) @@ -548,14 +552,15 @@ class LoggedInPresenterTest { return awaitItem() } - private fun TestScope.createLoggedInPresenter( - roomListService: RoomListService = FakeRoomListService(), + private fun createLoggedInPresenter( syncState: SyncState = SyncState.Running, analyticsService: AnalyticsService = FakeAnalyticsService(), sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(), encryptionService: EncryptionService = FakeEncryptionService(), pushService: PushService = FakePushService(), - matrixClient: MatrixClient = FakeMatrixClient(roomListService = roomListService), + matrixClient: MatrixClient = FakeMatrixClient( + accountManagementUrlResult = { Result.success(null) }, + ), buildMeta: BuildMeta = aBuildMeta(), ): LoggedInPresenter { return LoggedInPresenter( diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index 6f85c436210..72ae8bdb10e 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -5,12 +5,11 @@ * Please see LICENSE files in the repository root for full details. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package io.element.android.features.preferences.impl.root -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow import app.cash.turbine.ReceiveTurbine -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider @@ -18,6 +17,7 @@ import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.indicator.impl.DefaultIndicatorService +import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_USER_NAME @@ -27,6 +27,10 @@ import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -37,11 +41,16 @@ class PreferencesRootPresenterTest { @Test fun `present - initial state`() = runTest { - val matrixClient = FakeMatrixClient(canDeactivateAccountResult = { true }) - val presenter = createPresenter(matrixClient = matrixClient) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val accountManagementUrlResult = lambdaRecorder> { action -> + Result.success("$action url") + } + val matrixClient = FakeMatrixClient( + canDeactivateAccountResult = { true }, + accountManagementUrlResult = accountManagementUrlResult, + ) + createPresenter( + matrixClient = matrixClient, + ).test { val initialState = awaitItem() assertThat(initialState.myUser).isEqualTo( MatrixUser( @@ -71,19 +80,26 @@ class PreferencesRootPresenterTest { assertThat(loadedState.canDeactivateAccount).isTrue() assertThat(loadedState.directLogoutState).isEqualTo(aDirectLogoutState()) assertThat(loadedState.snackbarMessage).isNull() + skipItems(1) + val finalState = awaitItem() + accountManagementUrlResult.assertions().isCalledExactly(2) + .withSequence( + listOf(value(AccountManagementAction.Profile)), + listOf(value(AccountManagementAction.SessionsList)), + ) + assertThat(finalState.accountManagementUrl).isEqualTo("Profile url") + assertThat(finalState.devicesManagementUrl).isEqualTo("SessionsList url") } } @Test fun `present - can deactivate account is false if the Matrix client say so`() = runTest { - val presenter = createPresenter( + createPresenter( matrixClient = FakeMatrixClient( - canDeactivateAccountResult = { false } - ) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + canDeactivateAccountResult = { false }, + accountManagementUrlResult = { Result.success(null) }, + ), + ).test { val loadedState = awaitFirstItem() assertThat(loadedState.canDeactivateAccount).isFalse() } @@ -91,12 +107,13 @@ class PreferencesRootPresenterTest { @Test fun `present - developer settings is hidden by default in release builds`() = runTest { - val presenter = createPresenter( + createPresenter( + matrixClient = FakeMatrixClient( + canDeactivateAccountResult = { true }, + accountManagementUrlResult = { Result.success(null) }, + ), showDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.RELEASE)) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + ).test { val loadedState = awaitFirstItem() assertThat(loadedState.showDeveloperSettings).isFalse() } @@ -104,12 +121,13 @@ class PreferencesRootPresenterTest { @Test fun `present - developer settings can be enabled in release builds`() = runTest { - val presenter = createPresenter( + createPresenter( + matrixClient = FakeMatrixClient( + canDeactivateAccountResult = { true }, + accountManagementUrlResult = { Result.success(null) }, + ), showDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.RELEASE)) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + ).test { val loadedState = awaitFirstItem() repeat(times = ShowDeveloperSettingsProvider.DEVELOPER_SETTINGS_COUNTER) { assertThat(loadedState.showDeveloperSettings).isFalse() @@ -125,7 +143,7 @@ class PreferencesRootPresenterTest { } private fun createPresenter( - matrixClient: FakeMatrixClient = FakeMatrixClient(canDeactivateAccountResult = { true }), + matrixClient: FakeMatrixClient = FakeMatrixClient(), sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(), showDeveloperSettingsProvider: ShowDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.DEBUG)), ) = PreferencesRootPresenter( diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 93c2d4c075a..030e8db7131 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -72,11 +72,11 @@ class FakeMatrixClient( private val syncService: FakeSyncService = FakeSyncService(), private val encryptionService: FakeEncryptionService = FakeEncryptionService(), private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(), - private val accountManagementUrlString: Result = Result.success(null), + private val accountManagementUrlResult: (AccountManagementAction?) -> Result = { lambdaError() }, private val resolveRoomAliasResult: (RoomAlias) -> Result> = { Result.success( - Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList())) - ) + Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList())) + ) }, private val getRoomPreviewResult: (RoomIdOrAlias, List) -> Result = { _, _ -> Result.failure(AN_EXCEPTION) }, private val clearCacheLambda: () -> Unit = { lambdaError() }, @@ -192,8 +192,8 @@ class FakeMatrixClient( return Result.success(result) } - override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result { - return accountManagementUrlString + override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result = simulateLongTask { + accountManagementUrlResult(action) } override suspend fun uploadMedia( From da198353a0e63947856c8cbe2f0754a0ee025050 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Feb 2025 15:47:53 +0100 Subject: [PATCH 3/3] No need to invoke getOrNull. --- .../io/element/android/appnav/loggedin/LoggedInPresenter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index 6d01abd1530..fbecfeb60b8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -207,7 +207,7 @@ class LoggedInPresenter @Inject constructor( } private fun CoroutineScope.preloadAccountManagementUrl() = launch { - matrixClient.getAccountManagementUrl(AccountManagementAction.Profile).getOrNull() - matrixClient.getAccountManagementUrl(AccountManagementAction.SessionsList).getOrNull() + matrixClient.getAccountManagementUrl(AccountManagementAction.Profile) + matrixClient.getAccountManagementUrl(AccountManagementAction.SessionsList) } }