mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:02:10 +00:00
#40: Add UserSessionStoreProtocol.
Only log out of the specific account. Add tests for the keychain controller. Expand test coverage. PR comments
This commit is contained in:
@@ -18,9 +18,9 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
|
||||
private let navigationRouter: NavigationRouter
|
||||
|
||||
private let userSessionStore: UserSessionStore
|
||||
private let userSessionStore: UserSessionStoreProtocol
|
||||
|
||||
private var userSession: UserSession!
|
||||
private var userSession: UserSessionProtocol!
|
||||
|
||||
private let memberDetailProviderManager: MemberDetailProviderManager
|
||||
|
||||
@@ -88,7 +88,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
stateMachine.processEvent(.attemptedSignIn)
|
||||
}
|
||||
|
||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didLoginWithSession userSession: UserSession) {
|
||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didLoginWithSession userSession: UserSessionProtocol) {
|
||||
self.userSession = userSession
|
||||
remove(childCoordinator: authenticationCoordinator)
|
||||
stateMachine.processEvent(.succeededSigningIn)
|
||||
@@ -100,14 +100,14 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
// swiftlint:disable cyclomatic_complexity
|
||||
// swiftlint:disable cyclomatic_complexity function_body_length
|
||||
private func setupStateMachine() {
|
||||
stateMachine.addTransitionHandler { [weak self] context in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch (context.fromState, context.event, context.toState) {
|
||||
case (.initial, .startWithAuthentication, .signedOut):
|
||||
self.showAuthentication()
|
||||
self.startAuthentication()
|
||||
case (.signedOut, .attemptedSignIn, .signingIn):
|
||||
self.showLoadingIndicator()
|
||||
case (.signingIn, .failedSigningIn, .signedOut):
|
||||
@@ -151,7 +151,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
fatalError("Failed transition with context: \(context)")
|
||||
}
|
||||
}
|
||||
// swiftlint:enable cyclomatic_complexity
|
||||
// swiftlint:enable cyclomatic_complexity function_body_length
|
||||
|
||||
private func restoreUserSession() {
|
||||
Task {
|
||||
@@ -166,7 +166,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
}
|
||||
}
|
||||
|
||||
private func showAuthentication() {
|
||||
private func startAuthentication() {
|
||||
let coordinator = AuthenticationCoordinator(userSessionStore: userSessionStore,
|
||||
navigationRouter: navigationRouter)
|
||||
coordinator.delegate = self
|
||||
@@ -184,7 +184,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
|
||||
mainNavigationController.setViewControllers([splashViewController], animated: false)
|
||||
|
||||
showAuthentication()
|
||||
startAuthentication()
|
||||
}
|
||||
|
||||
private func presentHomeScreen() {
|
||||
|
||||
@@ -19,7 +19,7 @@ protocol AuthenticationCoordinatorDelegate: AnyObject {
|
||||
func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
|
||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator,
|
||||
didLoginWithSession userSession: UserSession)
|
||||
didLoginWithSession userSession: UserSessionProtocol)
|
||||
|
||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator,
|
||||
didFailWithError error: AuthenticationCoordinatorError)
|
||||
@@ -27,7 +27,7 @@ protocol AuthenticationCoordinatorDelegate: AnyObject {
|
||||
|
||||
class AuthenticationCoordinator: Coordinator {
|
||||
|
||||
private let userSessionStore: UserSessionStore
|
||||
private let userSessionStore: UserSessionStoreProtocol
|
||||
private let navigationRouter: NavigationRouter
|
||||
|
||||
private(set) var clientProxy: ClientProxyProtocol?
|
||||
@@ -35,7 +35,7 @@ class AuthenticationCoordinator: Coordinator {
|
||||
|
||||
weak var delegate: AuthenticationCoordinatorDelegate?
|
||||
|
||||
init(userSessionStore: UserSessionStore,
|
||||
init(userSessionStore: UserSessionStoreProtocol,
|
||||
navigationRouter: NavigationRouter) {
|
||||
self.userSessionStore = userSessionStore
|
||||
self.navigationRouter = navigationRouter
|
||||
|
||||
+2
-4
@@ -16,13 +16,11 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// Metrics used across the entire onboarding flow.
|
||||
struct AuthenticationMetrics {
|
||||
/// Standard constants used across the app's UI.
|
||||
struct UIConstants {
|
||||
static let maxContentWidth: CGFloat = 600
|
||||
static let maxContentHeight: CGFloat = 750
|
||||
|
||||
/// The padding used between the top of the main content and the navigation bar.
|
||||
static let topPaddingToNavigationBar: CGFloat = 16
|
||||
/// The width/height used for the main icon shown in most of the screens.
|
||||
static let iconSize: CGFloat = 90
|
||||
}
|
||||
@@ -16,11 +16,7 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
protocol SplashScreenCoordinatorProtocol: Coordinator, Presentable {
|
||||
var callback: ((SplashScreenCoordinatorAction) -> Void)? { get set }
|
||||
}
|
||||
|
||||
final class SplashScreenCoordinator: SplashScreenCoordinatorProtocol {
|
||||
final class SplashScreenCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ enum SplashScreenViewModelAction {
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct SplashScreenViewState: BindableState, CustomDebugStringConvertible {
|
||||
struct SplashScreenViewState: BindableState {
|
||||
private enum Constants {
|
||||
static let gradientColors = [
|
||||
Color(red: 0.95, green: 0.98, blue: 0.96),
|
||||
@@ -55,11 +55,6 @@ struct SplashScreenViewState: BindableState, CustomDebugStringConvertible {
|
||||
let content: [SplashScreenPageContent]
|
||||
var bindings: SplashScreenBindings
|
||||
|
||||
/// Custom debug description to reduce noise in the logs.
|
||||
var debugDescription: String {
|
||||
"SplashScreenViewState at page \(bindings.pageIndex)."
|
||||
}
|
||||
|
||||
init() {
|
||||
// The pun doesn't translate, so we only use it for English.
|
||||
let locale = Locale.current
|
||||
|
||||
@@ -51,6 +51,7 @@ struct SplashScreen: View {
|
||||
overlayHeight: overlayFrame.height + geometry.safeAreaInsets.bottom)
|
||||
.frame(width: geometry.size.width)
|
||||
.tag(-1)
|
||||
.accessibilityIdentifier("hiddenPage")
|
||||
|
||||
ForEach(0..<pageCount, id: \.self) { index in
|
||||
SplashScreenPage(content: viewModel.viewState.content[index],
|
||||
@@ -90,7 +91,7 @@ struct SplashScreen: View {
|
||||
|
||||
buttons
|
||||
.padding(.horizontal, 16)
|
||||
.frame(maxWidth: AuthenticationMetrics.maxContentWidth)
|
||||
.frame(maxWidth: UIConstants.maxContentWidth)
|
||||
Spacer()
|
||||
}
|
||||
.background(ViewFrameReader(frame: $overlayFrame))
|
||||
|
||||
@@ -68,8 +68,8 @@ struct SplashScreenPage: View {
|
||||
Spacer().frame(maxHeight: overlayHeight)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.frame(maxWidth: AuthenticationMetrics.maxContentWidth,
|
||||
maxHeight: AuthenticationMetrics.maxContentHeight)
|
||||
.frame(maxWidth: UIConstants.maxContentWidth,
|
||||
maxHeight: UIConstants.maxContentHeight)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(backgroundGradient.ignoresSafeArea())
|
||||
|
||||
+10
-2
@@ -20,7 +20,7 @@ class KeychainController: KeychainControllerProtocol {
|
||||
do {
|
||||
try keychain.set(accessToken, key: username)
|
||||
} catch {
|
||||
MXLog.error("Failed storing user restore token with error: \(error)")
|
||||
MXLog.error("Failed storing user access token with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class KeychainController: KeychainControllerProtocol {
|
||||
do {
|
||||
return try keychain.get(username)
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving user restore token")
|
||||
MXLog.error("Failed retrieving user access token")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,14 @@ class KeychainController: KeychainControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func removeAccessTokenForUsername(_ username: String) {
|
||||
do {
|
||||
try keychain.remove(username)
|
||||
} catch {
|
||||
MXLog.error("Failed removing access token with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllAccessTokens() {
|
||||
do {
|
||||
try keychain.removeAll()
|
||||
+1
@@ -12,5 +12,6 @@ protocol KeychainControllerProtocol {
|
||||
func setAccessToken(_ accessToken: String, forUsername username: String)
|
||||
func accessTokenForUsername(_ username: String) -> String?
|
||||
func accessTokens() -> [(username: String, accessToken: String)]
|
||||
func removeAccessTokenForUsername(_ username: String)
|
||||
func removeAllAccessTokens()
|
||||
}
|
||||
+4
-10
@@ -18,14 +18,7 @@ import Foundation
|
||||
import MatrixRustSDK
|
||||
import Kingfisher
|
||||
|
||||
enum UserSessionStoreError: Error {
|
||||
case missingCredentials
|
||||
case failedRestoringLogin
|
||||
case failedSettingUpSession
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class UserSessionStore {
|
||||
class UserSessionStore: UserSessionStoreProtocol {
|
||||
|
||||
private let keychainController: KeychainControllerProtocol
|
||||
|
||||
@@ -70,8 +63,9 @@ class UserSessionStore {
|
||||
}
|
||||
|
||||
func logout(userSession: UserSessionProtocol) {
|
||||
keychainController.removeAllAccessTokens()
|
||||
deleteBaseDirectory(for: userSession.clientProxy.userIdentifier)
|
||||
let username = userSession.clientProxy.userIdentifier
|
||||
keychainController.removeAccessTokenForUsername(username)
|
||||
deleteBaseDirectory(for: username)
|
||||
}
|
||||
|
||||
private func restorePreviousLogin(_ usernameTokenTuple: (username: String, accessToken: String)) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright 2022 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.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
enum UserSessionStoreError: Error {
|
||||
case missingCredentials
|
||||
case failedRestoringLogin
|
||||
case failedSettingUpSession
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol UserSessionStoreProtocol {
|
||||
/// Whether or not there are sessions in the store.
|
||||
var hasSessions: Bool { get }
|
||||
|
||||
/// Restores an existing user session.
|
||||
func restoreUserSession() async -> Result<UserSession, UserSessionStoreError>
|
||||
|
||||
/// Creates a user session for a new client from the SDK.
|
||||
func userSession(for client: Client) async -> Result<UserSession, UserSessionStoreError>
|
||||
|
||||
/// Logs out of the specified session.
|
||||
func logout(userSession: UserSessionProtocol)
|
||||
|
||||
/// Returns the location to store user data for a particular username.
|
||||
func baseDirectoryPath(for username: String) -> String
|
||||
}
|
||||
@@ -22,6 +22,8 @@ class UITestsAppCoordinator: Coordinator {
|
||||
window.tintColor = .element.accent
|
||||
|
||||
let screens = mockScreens()
|
||||
screens.forEach { $0.coordinator.start() }
|
||||
|
||||
let rootView = UITestsRootView(mockScreens: screens) { id in
|
||||
guard let screen = screens.first(where: { $0.id == id }) else {
|
||||
fatalError()
|
||||
@@ -52,5 +54,5 @@ class UITestsAppCoordinator: Coordinator {
|
||||
|
||||
struct MockScreen: Identifiable {
|
||||
let id: String
|
||||
let coordinator: Presentable
|
||||
let coordinator: Coordinator & Presentable
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user