* 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:
ismailgulek
2022-09-15 12:41:37 +03:00
committed by GitHub
parent 8967891e65
commit bf3c7e900f
37 changed files with 1237 additions and 63 deletions
@@ -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)