mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
Log out (#174)
* Expose logout method from SDK * Use logout from SDK, refactor logging out states * Add some strings * Introduce `initialDisplayName` on `UIDevice` * Implement soft logout screen * Add `softLogout` test screen identifier * Add new fields into the client proxy and implement new delegate methods * Add new fields into the user session and observe new client callbacks * Implement updated login method * Add remote logout state and event into the state machine * Implement refreshing restore token on `UserSessionStore` * Update app coordinator with new states and display soft logout screen when appropriate * Add reference screenshots for soft logout screen * Disable auto requesting photos access on screenshot detection * Make initial device name optional, generate project file * Add changelog * Use logout from SDK, refactor logging out states * Implement soft logout screen * Implement updated login method * Make initial device name optional, generate project file * Fix renamed event * Fix logout race * Remove redundant strings * Reuse existing strings * Confirm clear all data * Expose logout method from SDK * Use logout from SDK, refactor logging out states * Add some strings * Introduce `initialDisplayName` on `UIDevice` * Implement soft logout screen * Add `softLogout` test screen identifier * Add new fields into the client proxy and implement new delegate methods * Add new fields into the user session and observe new client callbacks * Implement updated login method * Add remote logout state and event into the state machine * Implement refreshing restore token on `UserSessionStore` * Update app coordinator with new states and display soft logout screen when appropriate * Add reference screenshots for soft logout screen * Disable auto requesting photos access on screenshot detection * Make initial device name optional, generate project file * Add changelog * Use logout from SDK, refactor logging out states * Implement soft logout screen * Implement updated login method * Make initial device name optional, generate project file * Fix renamed event * Fix logout race * Remove redundant strings * Reuse existing strings * Confirm clear all data * Comment out new apis for now * Make the PR compile
This commit is contained in:
@@ -104,11 +104,17 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func login(username: String, password: String) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||
func login(username: String, password: String, initialDeviceName: String?, deviceId: String?) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||
Benchmark.startTrackingForIdentifier("Login", message: "Started new login")
|
||||
|
||||
let loginTask: Task<Client, Error> = Task.detached {
|
||||
try self.authenticationService.login(username: username, password: password)
|
||||
#warning("Use new api on next SDK release.")
|
||||
return try self.authenticationService.login(username: username,
|
||||
password: password)
|
||||
// try self.authenticationService.login(username: username,
|
||||
// password: password,
|
||||
// initialDeviceName: initialDeviceName,
|
||||
// deviceId: deviceId)
|
||||
}
|
||||
|
||||
switch await loginTask.result {
|
||||
|
||||
@@ -36,5 +36,5 @@ protocol AuthenticationServiceProxyProtocol {
|
||||
/// Performs login using OIDC for the current homeserver.
|
||||
func loginWithOIDC(userAgent: OIDExternalUserAgentIOS) async -> Result<UserSessionProtocol, AuthenticationServiceError>
|
||||
/// Performs a password login using the current homeserver.
|
||||
func login(username: String, password: String) async -> Result<UserSessionProtocol, AuthenticationServiceError>
|
||||
func login(username: String, password: String, initialDeviceName: String?, deviceId: String?) async -> Result<UserSessionProtocol, AuthenticationServiceError>
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ class MockAuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
|
||||
func loginWithOIDC(userAgent: OIDExternalUserAgentIOS) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||
.failure(.oidcError(.notSupported))
|
||||
}
|
||||
|
||||
func login(username: String, password: String) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||
|
||||
func login(username: String, password: String, initialDeviceName: String?, deviceId: String?) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||
// Login only succeeds if the username and password match the valid credentials property
|
||||
guard username == validCredentials.username, password == validCredentials.password else {
|
||||
return .failure(.invalidCredentials)
|
||||
|
||||
@@ -28,7 +28,7 @@ class ScreenshotDetector {
|
||||
var callback: (@MainActor (UIImage?, Error?) -> Void)?
|
||||
|
||||
/// Flag to whether ask for photos authorization by default if needed.
|
||||
var autoRequestPHAuthorization = true
|
||||
var autoRequestPHAuthorization = false
|
||||
|
||||
init() {
|
||||
startObservingScreenshots()
|
||||
|
||||
@@ -29,6 +29,14 @@ private class WeakClientProxyWrapper: ClientDelegate {
|
||||
func didReceiveSyncUpdate() {
|
||||
clientProxy?.didReceiveSyncUpdate()
|
||||
}
|
||||
|
||||
func didReceiveAuthError(isSoftLogout: Bool) {
|
||||
clientProxy?.didReceiveAuthError(isSoftLogout: isSoftLogout)
|
||||
}
|
||||
|
||||
func didUpdateRestoreToken() {
|
||||
clientProxy?.didUpdateRestoreToken()
|
||||
}
|
||||
}
|
||||
|
||||
class ClientProxy: ClientProxyProtocol {
|
||||
@@ -57,11 +65,18 @@ class ClientProxy: ClientProxyProtocol {
|
||||
self.backgroundTaskService = backgroundTaskService
|
||||
|
||||
client.setDelegate(delegate: WeakClientProxyWrapper(clientProxy: self))
|
||||
|
||||
|
||||
#warning("Use isSoftLogout() api on next SDK release.")
|
||||
Benchmark.startTrackingForIdentifier("ClientSync", message: "Started sync.")
|
||||
client.startSync(timelineLimit: ClientProxy.syncLimit)
|
||||
|
||||
|
||||
Task { await updateRooms() }
|
||||
// if !client.isSoftLogout() {
|
||||
// Benchmark.startTrackingForIdentifier("ClientSync", message: "Started sync.")
|
||||
// client.startSync(timelineLimit: ClientProxy.syncLimit)
|
||||
//
|
||||
// Task { await updateRooms() }
|
||||
// }
|
||||
}
|
||||
|
||||
var userIdentifier: String {
|
||||
@@ -72,6 +87,34 @@ class ClientProxy: ClientProxyProtocol {
|
||||
return "Unknown user identifier"
|
||||
}
|
||||
}
|
||||
|
||||
var isSoftLogout: Bool {
|
||||
#warning("Use isSoftLogout() api on next SDK release.")
|
||||
return false
|
||||
// client.isSoftLogout()
|
||||
}
|
||||
|
||||
var deviceId: String? {
|
||||
do {
|
||||
return try client.deviceId()
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving device id with error: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var homeserver: String {
|
||||
client.homeserver()
|
||||
}
|
||||
|
||||
var restoreToken: String? {
|
||||
do {
|
||||
return try client.restoreToken()
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving restore token with error: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
|
||||
await Task.detached {
|
||||
@@ -91,7 +134,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
let avatarURL = try self.client.avatarUrl()
|
||||
return .success(avatarURL)
|
||||
} catch {
|
||||
return .failure(.failedRetrievingDisplayName)
|
||||
return .failure(.failedRetrievingAvatarURL)
|
||||
}
|
||||
}
|
||||
.value
|
||||
@@ -125,6 +168,15 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
.value
|
||||
}
|
||||
|
||||
func logout() async {
|
||||
do {
|
||||
#warning("Use logout() api on next SDK release.")
|
||||
// try client.logout()
|
||||
} catch {
|
||||
MXLog.error("Failed logging out with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@@ -137,6 +189,14 @@ class ClientProxy: ClientProxyProtocol {
|
||||
await self.updateRooms()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func didReceiveAuthError(isSoftLogout: Bool) {
|
||||
callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout))
|
||||
}
|
||||
|
||||
fileprivate func didUpdateRestoreToken() {
|
||||
callbacks.send(.updatedRestoreToken)
|
||||
}
|
||||
|
||||
private func updateRooms() async {
|
||||
var currentRooms = rooms
|
||||
|
||||
@@ -21,6 +21,8 @@ import MatrixRustSDK
|
||||
enum ClientProxyCallback {
|
||||
case updatedRoomsList
|
||||
case receivedSyncUpdate
|
||||
case receivedAuthError(isSoftLogout: Bool)
|
||||
case updatedRestoreToken
|
||||
}
|
||||
|
||||
enum ClientProxyError: Error {
|
||||
@@ -36,6 +38,14 @@ protocol ClientProxyProtocol {
|
||||
var callbacks: PassthroughSubject<ClientProxyCallback, Never> { get }
|
||||
|
||||
var userIdentifier: String { get }
|
||||
|
||||
var isSoftLogout: Bool { get }
|
||||
|
||||
var deviceId: String? { get }
|
||||
|
||||
var homeserver: String { get }
|
||||
|
||||
var restoreToken: String? { get }
|
||||
|
||||
var rooms: [RoomProxy] { get }
|
||||
|
||||
@@ -52,4 +62,6 @@ protocol ClientProxyProtocol {
|
||||
func loadMediaContentForSource(_ source: MatrixRustSDK.MediaSource) throws -> Data
|
||||
|
||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError>
|
||||
|
||||
func logout() async
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ struct MockClientProxy: ClientProxyProtocol {
|
||||
let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
|
||||
|
||||
let userIdentifier: String
|
||||
let isSoftLogout = false
|
||||
let deviceId: String? = nil
|
||||
let homeserver = ""
|
||||
let restoreToken: String? = nil
|
||||
|
||||
let rooms = [RoomProxy]()
|
||||
|
||||
@@ -51,4 +55,8 @@ struct MockClientProxy: ClientProxyProtocol {
|
||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
|
||||
.failure(.failedRetrievingSessionVerificationController)
|
||||
}
|
||||
|
||||
func logout() async {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ struct MockUserSession: UserSessionProtocol {
|
||||
let sessionVerificationController: SessionVerificationControllerProxyProtocol? = nil
|
||||
|
||||
var userID: String { clientProxy.userIdentifier }
|
||||
var isSoftLogout: Bool { clientProxy.isSoftLogout }
|
||||
var deviceId: String? { clientProxy.deviceId }
|
||||
var homeserver: String { clientProxy.homeserver }
|
||||
let clientProxy: ClientProxyProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
}
|
||||
|
||||
@@ -20,9 +20,14 @@ import Foundation
|
||||
class UserSession: UserSessionProtocol {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var checkForSessionVerificationControllerCancellable: AnyCancellable?
|
||||
private var authErrorCancellable: AnyCancellable?
|
||||
private var restoreTokenUpdateCancellable: AnyCancellable?
|
||||
|
||||
var userID: String { clientProxy.userIdentifier }
|
||||
|
||||
var isSoftLogout: Bool { clientProxy.isSoftLogout }
|
||||
var deviceId: String? { clientProxy.deviceId }
|
||||
var homeserver: String { clientProxy.homeserver }
|
||||
|
||||
let clientProxy: ClientProxyProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let callbacks = PassthroughSubject<UserSessionCallback, Never>()
|
||||
@@ -33,6 +38,8 @@ class UserSession: UserSessionProtocol {
|
||||
self.mediaProvider = mediaProvider
|
||||
|
||||
setupSessionVerificationWatchdog()
|
||||
setupAuthErrorWatchdog()
|
||||
setupRestoreTokenUpdateWatchdog()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -77,4 +84,38 @@ class UserSession: UserSessionProtocol {
|
||||
private func tearDownSessionVerificationControllerWatchdog() {
|
||||
checkForSessionVerificationControllerCancellable = nil
|
||||
}
|
||||
|
||||
// MARK: Auth Error Watchdog
|
||||
|
||||
private func setupAuthErrorWatchdog() {
|
||||
authErrorCancellable = clientProxy.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
if case .receivedAuthError(let isSoftLogout) = callback {
|
||||
self?.callbacks.send(.didReceiveAuthError(isSoftLogout: isSoftLogout))
|
||||
self?.tearDownAuthErrorWatchdog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func tearDownAuthErrorWatchdog() {
|
||||
authErrorCancellable = nil
|
||||
}
|
||||
|
||||
// MARK: Restore Token Update Watchdog
|
||||
|
||||
private func setupRestoreTokenUpdateWatchdog() {
|
||||
restoreTokenUpdateCancellable = clientProxy.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
if case .updatedRestoreToken = callback {
|
||||
self?.callbacks.send(.updateRestoreTokenNeeded)
|
||||
self?.tearDownRestoreTokenUpdateWatchdog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func tearDownRestoreTokenUpdateWatchdog() {
|
||||
restoreTokenUpdateCancellable = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,15 @@ import Foundation
|
||||
enum UserSessionCallback {
|
||||
case sessionVerificationNeeded
|
||||
case didVerifySession
|
||||
case didReceiveAuthError(isSoftLogout: Bool)
|
||||
case updateRestoreTokenNeeded
|
||||
}
|
||||
|
||||
protocol UserSessionProtocol {
|
||||
var userID: String { get }
|
||||
var isSoftLogout: Bool { get }
|
||||
var deviceId: String? { get }
|
||||
var homeserver: String { get }
|
||||
|
||||
var clientProxy: ClientProxyProtocol { get }
|
||||
var mediaProvider: MediaProviderProtocol { get }
|
||||
|
||||
@@ -69,6 +69,16 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
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
|
||||
|
||||
@@ -21,6 +21,7 @@ enum UserSessionStoreError: Error {
|
||||
case missingCredentials
|
||||
case failedRestoringLogin
|
||||
case failedSettingUpSession
|
||||
case failedRefreshingRestoreToken
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@@ -36,6 +37,9 @@ protocol UserSessionStoreProtocol {
|
||||
|
||||
/// Creates a user session for a new client from the SDK.
|
||||
func userSession(for client: Client) async -> Result<UserSession, UserSessionStoreError>
|
||||
|
||||
/// Refresh the restore token of the client for a given session.
|
||||
func refreshRestoreToken(for userSession: UserSessionProtocol) -> Result<Void, UserSessionStoreError>
|
||||
|
||||
/// Logs out of the specified session.
|
||||
func logout(userSession: UserSessionProtocol)
|
||||
|
||||
Reference in New Issue
Block a user