mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
#53 - Refactored various components:
- updated the authentication coordinator to use async await - replaced the UserSession with a ClientProxy (similar to the RoomProxy) - renamed `completion` to `callback` on the coordinators and view models - renamed `viewModelResult` to `viewModelAction` - introduced back UserSession as a user specific service container
This commit is contained in:
committed by
Stefan Ceriu
parent
edf765b7cd
commit
1b4d3a650e
@@ -19,9 +19,9 @@ protocol AuthenticationCoordinatorDelegate: AnyObject {
|
||||
|
||||
func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
|
||||
func authenticationCoordinatorDidSetupUserSession(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
func authenticationCoordinatorDidSetupClientProxy(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
|
||||
func authenticationCoordinatorDidTearDownUserSession(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
func authenticationCoordinatorDidTearDownClientProxy(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
|
||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator,
|
||||
didFailWithError error: AuthenticationCoordinatorError)
|
||||
@@ -32,7 +32,7 @@ class AuthenticationCoordinator: Coordinator {
|
||||
private let keychainController: KeychainControllerProtocol
|
||||
private let navigationRouter: NavigationRouter
|
||||
|
||||
private(set) var userSession: UserSession?
|
||||
private(set) var clientProxy: ClientProxyProtocol?
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
weak var delegate: AuthenticationCoordinatorDelegate?
|
||||
@@ -45,13 +45,13 @@ class AuthenticationCoordinator: Coordinator {
|
||||
|
||||
func start() {
|
||||
|
||||
let availableRestoreTokens = keychainController.restoreTokens()
|
||||
let availableAccessTokens = keychainController.accessTokens()
|
||||
|
||||
guard let usernameTokenTuple = availableRestoreTokens.first else {
|
||||
guard let usernameTokenTuple = availableAccessTokens.first else {
|
||||
startNewLoginFlow { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.delegate?.authenticationCoordinatorDidSetupUserSession(self)
|
||||
self.delegate?.authenticationCoordinatorDidSetupClientProxy(self)
|
||||
case .failure(let error):
|
||||
self.delegate?.authenticationCoordinator(self, didFailWithError: error)
|
||||
MXLog.error("Failed logging in user with error: \(error)")
|
||||
@@ -60,33 +60,31 @@ class AuthenticationCoordinator: Coordinator {
|
||||
return
|
||||
}
|
||||
|
||||
restorePreviousLogin(usernameTokenTuple) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
Task {
|
||||
switch await restorePreviousLogin(usernameTokenTuple) {
|
||||
case .success:
|
||||
self.delegate?.authenticationCoordinatorDidSetupUserSession(self)
|
||||
self.delegate?.authenticationCoordinatorDidSetupClientProxy(self)
|
||||
case .failure(let error):
|
||||
self.delegate?.authenticationCoordinator(self, didFailWithError: error)
|
||||
MXLog.error("Failed restoring login with error: \(error)")
|
||||
|
||||
// On any restoration failure reset the token and restart
|
||||
self.keychainController.removeAllTokens()
|
||||
self.keychainController.removeAllAccessTokens()
|
||||
self.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logout() {
|
||||
keychainController.removeAllTokens()
|
||||
|
||||
if let userIdentifier = userSession?.userIdentifier {
|
||||
func logout() {
|
||||
keychainController.removeAllAccessTokens()
|
||||
|
||||
if let userIdentifier = clientProxy?.userIdentifier {
|
||||
deleteBaseDirectoryForUsername(userIdentifier)
|
||||
}
|
||||
|
||||
userSession = nil
|
||||
clientProxy = nil
|
||||
|
||||
delegate?.authenticationCoordinatorDidTearDownUserSession(self)
|
||||
delegate?.authenticationCoordinatorDidTearDownClientProxy(self)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -95,17 +93,15 @@ class AuthenticationCoordinator: Coordinator {
|
||||
let parameters = LoginScreenCoordinatorParameters()
|
||||
let coordinator = LoginScreenCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.completion = { [weak self, weak coordinator] result in
|
||||
coordinator.callback = { [weak self, weak coordinator] result in
|
||||
guard let self = self, let coordinator = coordinator else {
|
||||
return
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .login(let result):
|
||||
self.login(username: result.username, password: result.password) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
Task {
|
||||
switch await self.login(username: result.username, password: result.password) {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
self.remove(childCoordinator: coordinator)
|
||||
@@ -113,6 +109,7 @@ class AuthenticationCoordinator: Coordinator {
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,71 +120,63 @@ class AuthenticationCoordinator: Coordinator {
|
||||
coordinator.start()
|
||||
}
|
||||
|
||||
private func login(username: String, password: String, completion: @escaping (Result<Void, AuthenticationCoordinatorError>) -> Void) {
|
||||
private func login(username: String, password: String) async -> Result<Void, AuthenticationCoordinatorError> {
|
||||
Benchmark.startTrackingForIdentifier("Login", message: "Started new login")
|
||||
|
||||
delegate?.authenticationCoordinatorDidStartLoading(self)
|
||||
|
||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
do {
|
||||
self.setupUserSessionForClient(try loginNewClient(basePath: self.baseDirectoryPathForUsername(username),
|
||||
username: username,
|
||||
password: password))
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
} catch {
|
||||
MXLog.error("Failed logging in with error: \(error)")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(.failedLoggingIn))
|
||||
}
|
||||
}
|
||||
let basePath = baseDirectoryPathForUsername(username)
|
||||
let loginTask = Task.detached {
|
||||
try loginNewClient(basePath: basePath,
|
||||
username: username,
|
||||
password: password)
|
||||
}
|
||||
|
||||
switch await loginTask.result {
|
||||
case .success(let client):
|
||||
return await setupProxyForClient(client)
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed logging in with error: \(error)")
|
||||
return .failure(.failedLoggingIn)
|
||||
}
|
||||
}
|
||||
|
||||
private func restorePreviousLogin(_ usernameTokenTuple: (username: String, token: String), completion: @escaping (Result<Void, AuthenticationCoordinatorError>) -> Void) {
|
||||
private func restorePreviousLogin(_ usernameTokenTuple: (username: String, accessToken: String)) async -> Result<Void, AuthenticationCoordinatorError> {
|
||||
Benchmark.startTrackingForIdentifier("Login", message: "Started restoring previous login")
|
||||
|
||||
delegate?.authenticationCoordinatorDidStartLoading(self)
|
||||
|
||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
do {
|
||||
self.setupUserSessionForClient(try loginWithToken(basePath: self.baseDirectoryPathForUsername(usernameTokenTuple.username),
|
||||
restoreToken: usernameTokenTuple.token))
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
} catch {
|
||||
MXLog.error("Failed restoring login with error: \(error)")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(.failedRestoringLogin))
|
||||
}
|
||||
}
|
||||
let basePath = baseDirectoryPathForUsername(usernameTokenTuple.username)
|
||||
let loginTask = Task.detached {
|
||||
try loginWithToken(basePath: basePath,
|
||||
restoreToken: usernameTokenTuple.accessToken)
|
||||
}
|
||||
|
||||
switch await loginTask.result {
|
||||
case .success(let client):
|
||||
return await setupProxyForClient(client)
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed restoring login with error: \(error)")
|
||||
return .failure(.failedRestoringLogin)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupUserSessionForClient(_ client: Client) {
|
||||
private func setupProxyForClient(_ client: Client) async -> Result<Void, AuthenticationCoordinatorError> {
|
||||
Benchmark.endTrackingForIdentifier("Login", message: "Finished login")
|
||||
|
||||
do {
|
||||
let restoreToken = try client.restoreToken()
|
||||
let accessToken = try client.restoreToken()
|
||||
let userId = try client.userId()
|
||||
|
||||
keychainController.setRestoreToken(restoreToken, forUsername: userId)
|
||||
keychainController.setAccessToken(accessToken, forUsername: userId)
|
||||
} catch {
|
||||
delegate?.authenticationCoordinator(self, didFailWithError: .failedSettingUpSession)
|
||||
MXLog.error("Failed setting up user session with error: \(error)")
|
||||
return .failure(.failedSettingUpSession)
|
||||
}
|
||||
|
||||
userSession = UserSession(client: client)
|
||||
clientProxy = ClientProxy(client: client)
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
private func baseDirectoryPathForUsername(_ username: String) -> String {
|
||||
|
||||
@@ -9,26 +9,21 @@ import Foundation
|
||||
import KeychainAccess
|
||||
|
||||
class KeychainController: KeychainControllerProtocol {
|
||||
|
||||
struct Constants {
|
||||
static let restoreTokenGroupKey = "restoreTokens"
|
||||
}
|
||||
|
||||
private let keychain: Keychain
|
||||
|
||||
init(identifier: String) {
|
||||
keychain = Keychain(service: identifier)
|
||||
}
|
||||
|
||||
func setRestoreToken(_ token: String, forUsername username: String) {
|
||||
func setAccessToken(_ accessToken: String, forUsername username: String) {
|
||||
do {
|
||||
try keychain.set(token, key: username)
|
||||
try keychain.set(accessToken, key: username)
|
||||
} catch {
|
||||
MXLog.error("Failed storing user restore token with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func restoreTokenForUsername(_ username: String) -> String? {
|
||||
func accessTokenForUsername(_ username: String) -> String? {
|
||||
do {
|
||||
return try keychain.get(username)
|
||||
} catch {
|
||||
@@ -37,17 +32,17 @@ class KeychainController: KeychainControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func restoreTokens() -> [(username: String, token: String)] {
|
||||
func accessTokens() -> [(username: String, accessToken: String)] {
|
||||
keychain.allKeys().compactMap { username in
|
||||
guard let token = restoreTokenForUsername(username) else {
|
||||
guard let accessToken = accessTokenForUsername(username) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (username, token)
|
||||
return (username, accessToken)
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllTokens() {
|
||||
func removeAllAccessTokens() {
|
||||
do {
|
||||
try keychain.removeAll()
|
||||
} catch {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
import Foundation
|
||||
|
||||
protocol KeychainControllerProtocol {
|
||||
func setRestoreToken(_ token: String, forUsername username: String)
|
||||
func restoreTokenForUsername(_ username: String) -> String?
|
||||
func restoreTokens() -> [(username: String, token: String)]
|
||||
func removeAllTokens()
|
||||
func setAccessToken(_ accessToken: String, forUsername username: String)
|
||||
func accessTokenForUsername(_ username: String) -> String?
|
||||
func accessTokens() -> [(username: String, accessToken: String)]
|
||||
func removeAllAccessTokens()
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
//
|
||||
// UserSession.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 14.02.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
import Combine
|
||||
import UIKit
|
||||
import Kingfisher
|
||||
|
||||
enum UserSessionCallback {
|
||||
case updatedRoomsList
|
||||
}
|
||||
|
||||
enum UserSessionError: Error {
|
||||
case failedRetrievingAvatarURL
|
||||
case failedRetrievingDisplayName
|
||||
}
|
||||
|
||||
private class WeakUserSessionWrapper: ClientDelegate {
|
||||
private weak var userSession: UserSession?
|
||||
|
||||
init(userSession: UserSession) {
|
||||
self.userSession = userSession
|
||||
}
|
||||
|
||||
@MainActor func didReceiveSyncUpdate() {
|
||||
userSession?.didReceiveSyncUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class UserSession {
|
||||
|
||||
private let client: Client
|
||||
|
||||
private(set) var rooms: [RoomProxy] = [] {
|
||||
didSet {
|
||||
self.callbacks.send(.updatedRoomsList)
|
||||
}
|
||||
}
|
||||
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
|
||||
deinit {
|
||||
client.setDelegate(delegate: nil)
|
||||
}
|
||||
|
||||
let callbacks = PassthroughSubject<UserSessionCallback, Never>()
|
||||
|
||||
init(client: Client) {
|
||||
self.client = client
|
||||
self.mediaProvider = MediaProvider(client: client, imageCache: ImageCache.default)
|
||||
|
||||
client.setDelegate(delegate: WeakUserSessionWrapper(userSession: self))
|
||||
|
||||
Benchmark.startTrackingForIdentifier("ClientSync", message: "Started sync.")
|
||||
client.startSync()
|
||||
|
||||
Task { await updateRooms() }
|
||||
}
|
||||
|
||||
var userIdentifier: String {
|
||||
do {
|
||||
return try client.userId()
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving room info with error: \(error)")
|
||||
return "Unknown user identifier"
|
||||
}
|
||||
}
|
||||
|
||||
func loadUserDisplayName() async -> Result<String, UserSessionError> {
|
||||
await Task.detached { () -> Result<String, UserSessionError> in
|
||||
do {
|
||||
let displayName = try self.client.displayName()
|
||||
return .success(displayName)
|
||||
} catch {
|
||||
return .failure(.failedRetrievingDisplayName)
|
||||
}
|
||||
|
||||
}
|
||||
.value
|
||||
}
|
||||
|
||||
func loadUserAvatarURL() async -> Result<String, UserSessionError> {
|
||||
await Task.detached { () -> Result<String, UserSessionError> in
|
||||
do {
|
||||
let avatarURL = try self.client.avatarUrl()
|
||||
return .success(avatarURL)
|
||||
} catch {
|
||||
return .failure(.failedRetrievingDisplayName)
|
||||
}
|
||||
}
|
||||
.value
|
||||
}
|
||||
|
||||
// MARK: ClientDelegate
|
||||
|
||||
func didReceiveSyncUpdate() {
|
||||
Benchmark.logElapsedDurationForIdentifier("ClientSync", message: "Received sync update")
|
||||
|
||||
Task.detached {
|
||||
await self.updateRooms()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func updateRooms() async {
|
||||
var currentRooms = self.rooms
|
||||
Benchmark.startTrackingForIdentifier("ClientRooms", message: "Fetching available rooms")
|
||||
let sdkRooms = client.rooms()
|
||||
Benchmark.endTrackingForIdentifier("ClientRooms", message: "Retrieved \(sdkRooms.count) rooms")
|
||||
|
||||
Benchmark.startTrackingForIdentifier("ProcessingRooms", message: "Started processing \(sdkRooms.count) rooms")
|
||||
let diff = sdkRooms.map({ $0.id() }).difference(from: currentRooms.map(\.id))
|
||||
|
||||
for change in diff {
|
||||
switch change {
|
||||
case .insert(_, let id, _):
|
||||
guard let sdkRoom = sdkRooms.first(where: { $0.id() == id }) else {
|
||||
MXLog.error("Failed retrieving sdk room with id: \(id)")
|
||||
break
|
||||
}
|
||||
currentRooms.append(RoomProxy(room: sdkRoom, roomMessageFactory: RoomMessageFactory()))
|
||||
case .remove(_, let id, _):
|
||||
currentRooms.removeAll { $0.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark.endTrackingForIdentifier("ProcessingRooms", message: "Finished processing \(sdkRooms.count) rooms")
|
||||
|
||||
rooms = currentRooms
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user