* Remove redundant string * Use placeholder avatar on home screen * Add initial home screen ui test * Fix settings screen PR remarks * Remove UIKit alert from home screen sign out * Remove UIKit alert from soft logout clear all data * Add reference screenshots for home screen UI tests * Formatting fixes * Add clearing room method to client proxy * Clear room proxies on screen dismiss * Fix retain cycle in room view model * Do not go into authentication state immediately * Define sizes for user and room avatars on different screens * Use defined avatar sizes everywhere * Disable image disk caching * Rename rounded corner shape * Fix text color of placeholder avatars * Fix PR reviews on formatted body text * Fix merge conflict * Remove shouldShowSenderDetails everywhere and just use it from inGroupState * Remove redundant linter disablings * Fix PR remarks * Rename media provider size parameter
154 lines
6.3 KiB
Swift
154 lines
6.3 KiB
Swift
//
|
|
// 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 Kingfisher
|
|
import MatrixRustSDK
|
|
|
|
class UserSessionStore: UserSessionStoreProtocol {
|
|
private let keychainController: KeychainControllerProtocol
|
|
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
|
|
|
/// Whether or not there are sessions in the store.
|
|
var hasSessions: Bool { !keychainController.restoreTokens().isEmpty }
|
|
|
|
/// The base directory where all session data is stored.
|
|
private(set) lazy var baseDirectory: URL = {
|
|
guard let appGroupContainerURL = FileManager.default.appGroupContainerURL else {
|
|
fatalError("Should always be able to retrieve the container directory")
|
|
}
|
|
|
|
let url = appGroupContainerURL
|
|
.appendingPathComponent("Library", isDirectory: true)
|
|
.appendingPathComponent("Caches", isDirectory: true)
|
|
.appendingPathComponent("Sessions", isDirectory: true)
|
|
|
|
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
|
|
|
|
MXLog.debug("Setup base directory at: \(url)")
|
|
|
|
return url
|
|
}()
|
|
|
|
init(bundleIdentifier: String, backgroundTaskService: BackgroundTaskServiceProtocol) {
|
|
keychainController = KeychainController(identifier: bundleIdentifier)
|
|
self.backgroundTaskService = backgroundTaskService
|
|
}
|
|
|
|
func restoreUserSession() async -> Result<UserSession, UserSessionStoreError> {
|
|
let availableCredentials = keychainController.restoreTokens()
|
|
|
|
guard let credentials = availableCredentials.first else {
|
|
return .failure(.missingCredentials)
|
|
}
|
|
|
|
switch await restorePreviousLogin(credentials) {
|
|
case .success(let clientProxy):
|
|
return .success(UserSession(clientProxy: clientProxy,
|
|
mediaProvider: MediaProvider(clientProxy: clientProxy,
|
|
imageCache: .onlyInMemory,
|
|
backgroundTaskService: backgroundTaskService)))
|
|
case .failure(let error):
|
|
MXLog.error("Failed restoring login with error: \(error)")
|
|
|
|
// On any restoration failure reset the token and restart
|
|
keychainController.removeAllRestoreTokens()
|
|
deleteSessionDirectory(for: credentials.userID)
|
|
|
|
return .failure(error)
|
|
}
|
|
}
|
|
|
|
func userSession(for client: Client) async -> Result<UserSession, UserSessionStoreError> {
|
|
switch await setupProxyForClient(client) {
|
|
case .success(let clientProxy):
|
|
return .success(UserSession(clientProxy: clientProxy,
|
|
mediaProvider: MediaProvider(clientProxy: clientProxy,
|
|
imageCache: .onlyInMemory,
|
|
backgroundTaskService: backgroundTaskService)))
|
|
case .failure(let error):
|
|
MXLog.error("Failed creating user session with error: \(error)")
|
|
return .failure(error)
|
|
}
|
|
}
|
|
|
|
func refreshRestoreToken(for userSession: UserSessionProtocol) -> Result<Void, UserSessionStoreError> {
|
|
guard let accessToken = userSession.clientProxy.restoreToken else {
|
|
return .failure(.failedRefreshingRestoreToken)
|
|
}
|
|
|
|
keychainController.setRestoreToken(accessToken, forUsername: userSession.clientProxy.userIdentifier)
|
|
|
|
return .success(())
|
|
}
|
|
|
|
func logout(userSession: UserSessionProtocol) {
|
|
let userID = userSession.clientProxy.userIdentifier
|
|
keychainController.removeRestoreTokenForUsername(userID)
|
|
deleteSessionDirectory(for: userID)
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func restorePreviousLogin(_ credentials: KeychainCredentials) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
|
|
Benchmark.startTrackingForIdentifier("Login", message: "Started restoring previous login")
|
|
|
|
let builder = ClientBuilder()
|
|
.basePath(path: baseDirectory.path)
|
|
.username(username: credentials.userID)
|
|
|
|
do {
|
|
let client: Client = try await Task.dispatch(on: .global()) {
|
|
let client = try builder.build()
|
|
try client.restoreLogin(restoreToken: credentials.restoreToken)
|
|
return client
|
|
}
|
|
return await setupProxyForClient(client)
|
|
} catch {
|
|
MXLog.error("Failed restoring login with error: \(error)")
|
|
return .failure(.failedRestoringLogin)
|
|
}
|
|
}
|
|
|
|
private func setupProxyForClient(_ client: Client) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
|
|
do {
|
|
let accessToken = try client.restoreToken()
|
|
let userId = try client.userId()
|
|
|
|
keychainController.setRestoreToken(accessToken, forUsername: userId)
|
|
} catch {
|
|
MXLog.error("Failed setting up user session with error: \(error)")
|
|
return .failure(.failedSettingUpSession)
|
|
}
|
|
|
|
let clientProxy = ClientProxy(client: client, backgroundTaskService: backgroundTaskService)
|
|
|
|
return .success(clientProxy)
|
|
}
|
|
|
|
private func deleteSessionDirectory(for userID: String) {
|
|
// Rust sanitises the user ID replacing invalid characters with an _
|
|
let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_")
|
|
let url = baseDirectory.appendingPathComponent(sanitisedUserID)
|
|
|
|
do {
|
|
try FileManager.default.removeItem(at: url)
|
|
} catch {
|
|
MXLog.failure("Failed deleting the session data: \(error)")
|
|
}
|
|
}
|
|
}
|