2022-02-14 18:05:21 +02:00
|
|
|
//
|
2022-08-11 08:54:24 +01:00
|
|
|
// Copyright 2022 New Vector Ltd
|
2022-02-14 18:05:21 +02:00
|
|
|
//
|
2022-08-11 08:54:24 +01:00
|
|
|
// 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.
|
2022-02-14 18:05:21 +02:00
|
|
|
//
|
|
|
|
|
2022-07-01 13:56:52 +03:00
|
|
|
import Combine
|
2022-07-27 16:06:40 +03:00
|
|
|
import MatrixRustSDK
|
2022-11-16 15:37:34 +02:00
|
|
|
import SwiftUI
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-09-12 21:34:53 +03:00
|
|
|
struct ServiceLocator {
|
|
|
|
fileprivate static var serviceLocator: ServiceLocator?
|
|
|
|
static var shared: ServiceLocator {
|
2022-10-17 09:56:17 +01:00
|
|
|
guard let serviceLocator else {
|
2022-09-12 21:34:53 +03:00
|
|
|
fatalError("The service locator should be setup at this point")
|
|
|
|
}
|
|
|
|
|
|
|
|
return serviceLocator
|
|
|
|
}
|
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
let userNotificationController: UserNotificationControllerProtocol
|
2022-09-12 21:34:53 +03:00
|
|
|
}
|
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
class AppCoordinator: CoordinatorProtocol {
|
2022-07-01 13:56:52 +03:00
|
|
|
private let stateMachine: AppCoordinatorStateMachine
|
2022-11-16 15:37:34 +02:00
|
|
|
private let navigationController: NavigationController
|
2022-06-15 10:10:22 +01:00
|
|
|
private let userSessionStore: UserSessionStoreProtocol
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
private var userSession: UserSessionProtocol! {
|
|
|
|
didSet {
|
|
|
|
deobserveUserSessionChanges()
|
2022-10-17 09:56:17 +01:00
|
|
|
if let userSession, !userSession.isSoftLogout {
|
2022-09-15 12:41:37 +03:00
|
|
|
observeUserSessionChanges()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-05-26 16:04:48 +03:00
|
|
|
|
2022-09-25 12:34:11 +03:00
|
|
|
private var userSessionFlowCoordinator: UserSessionFlowCoordinator?
|
2022-11-16 15:37:34 +02:00
|
|
|
private var authenticationCoordinator: AuthenticationCoordinator?
|
2022-09-25 12:34:11 +03:00
|
|
|
|
2022-06-06 12:38:07 +03:00
|
|
|
private let bugReportService: BugReportServiceProtocol
|
2022-06-29 13:03:28 +03:00
|
|
|
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
2022-06-06 12:38:07 +03:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
private var cancellables = Set<AnyCancellable>()
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2022-02-14 18:05:21 +02:00
|
|
|
init() {
|
2022-11-16 15:37:34 +02:00
|
|
|
navigationController = NavigationController()
|
2022-05-27 13:02:36 +03:00
|
|
|
stateMachine = AppCoordinatorStateMachine()
|
2022-09-12 21:34:53 +03:00
|
|
|
|
|
|
|
bugReportService = BugReportService(withBaseURL: BuildSettings.bugReportServiceBaseURL, sentryURL: BuildSettings.bugReportSentryURL)
|
2022-06-06 12:38:07 +03:00
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
navigationController.setRootCoordinator(SplashScreenCoordinator())
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
ServiceLocator.serviceLocator = ServiceLocator(userNotificationController: UserNotificationController(rootCoordinator: navigationController))
|
2022-05-11 16:27:30 +03:00
|
|
|
|
2022-02-14 18:05:21 +02:00
|
|
|
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
|
|
|
|
fatalError("Should have a valid bundle identifier at this point")
|
|
|
|
}
|
2022-06-29 13:03:28 +03:00
|
|
|
|
|
|
|
backgroundTaskService = UIKitBackgroundTaskService(withApplication: UIApplication.shared)
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-06-29 13:03:28 +03:00
|
|
|
userSessionStore = UserSessionStore(bundleIdentifier: bundleIdentifier,
|
|
|
|
backgroundTaskService: backgroundTaskService)
|
2022-04-05 10:01:16 +03:00
|
|
|
|
2022-05-27 13:02:36 +03:00
|
|
|
setupStateMachine()
|
|
|
|
|
2022-07-27 16:06:40 +03:00
|
|
|
setupLogging()
|
2022-04-05 10:01:16 +03:00
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
Bundle.elementFallbackLanguage = "en"
|
|
|
|
|
2022-04-05 10:01:16 +03:00
|
|
|
// Benchmark.trackingEnabled = true
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func start() {
|
2022-06-14 17:36:28 +01:00
|
|
|
stateMachine.processEvent(userSessionStore.hasSessions ? .startWithExistingSession : .startWithAuthentication)
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
|
2022-09-23 15:04:35 +03:00
|
|
|
func stop() {
|
|
|
|
hideLoadingIndicator()
|
|
|
|
}
|
2022-09-25 12:34:11 +03:00
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
func toPresentable() -> AnyView {
|
|
|
|
ServiceLocator.shared.userNotificationController.toPresentable()
|
|
|
|
}
|
|
|
|
|
2022-05-27 13:02:36 +03:00
|
|
|
// MARK: - Private
|
|
|
|
|
2022-07-27 16:06:40 +03:00
|
|
|
private func setupLogging() {
|
|
|
|
let loggerConfiguration = MXLogConfiguration()
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
// This exposes the full Rust side tracing subscriber filter for more flexibility.
|
|
|
|
// We can filter by level, crate and even file. See more details here:
|
|
|
|
// https://docs.rs/tracing-subscriber/0.2.7/tracing_subscriber/filter/struct.EnvFilter.html#examples
|
2022-11-16 14:34:37 +03:00
|
|
|
setupTracing(filter: "warn,hyper=warn,sled=warn,matrix_sdk_sled=warn")
|
2022-07-27 16:06:40 +03:00
|
|
|
|
|
|
|
loggerConfiguration.logLevel = .debug
|
|
|
|
#else
|
2022-11-16 14:34:37 +03:00
|
|
|
setupTracing(filter: "info,hyper=warn,sled=warn,matrix_sdk_sled=warn")
|
2022-07-27 16:06:40 +03:00
|
|
|
loggerConfiguration.logLevel = .info
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Avoid redirecting NSLogs to files if we are attached to a debugger.
|
|
|
|
if isatty(STDERR_FILENO) == 0 {
|
|
|
|
loggerConfiguration.redirectLogsToFiles = true
|
|
|
|
}
|
|
|
|
|
|
|
|
MXLog.configure(loggerConfiguration)
|
|
|
|
}
|
|
|
|
|
2022-09-23 12:21:41 +03:00
|
|
|
// swiftlint:disable:next cyclomatic_complexity
|
2022-05-27 13:02:36 +03:00
|
|
|
private func setupStateMachine() {
|
|
|
|
stateMachine.addTransitionHandler { [weak self] context in
|
2022-10-17 09:56:17 +01:00
|
|
|
guard let self else { return }
|
2022-06-14 17:36:28 +01:00
|
|
|
|
2022-05-27 13:02:36 +03:00
|
|
|
switch (context.fromState, context.event, context.toState) {
|
2022-06-14 17:36:28 +01:00
|
|
|
case (.initial, .startWithAuthentication, .signedOut):
|
2022-06-15 10:10:22 +01:00
|
|
|
self.startAuthentication()
|
2022-09-25 12:34:11 +03:00
|
|
|
case (.signedOut, .succeededSigningIn, .signedIn):
|
|
|
|
self.setupUserSession()
|
2022-06-14 17:36:28 +01:00
|
|
|
case (.initial, .startWithExistingSession, .restoringSession):
|
|
|
|
self.showLoadingIndicator()
|
|
|
|
self.restoreUserSession()
|
|
|
|
case (.restoringSession, .failedRestoringSession, .signedOut):
|
|
|
|
self.hideLoadingIndicator()
|
|
|
|
self.showLoginErrorToast()
|
2022-09-25 12:34:11 +03:00
|
|
|
case (.restoringSession, .succeededRestoringSession, .signedIn):
|
2022-05-27 13:02:36 +03:00
|
|
|
self.hideLoadingIndicator()
|
2022-09-25 12:34:11 +03:00
|
|
|
self.setupUserSession()
|
2022-09-15 12:41:37 +03:00
|
|
|
case (_, .signOut, .signingOut):
|
|
|
|
self.showLoadingIndicator()
|
2022-05-27 13:02:36 +03:00
|
|
|
self.tearDownUserSession()
|
2022-09-15 12:41:37 +03:00
|
|
|
case (.signingOut, .completedSigningOut, .signedOut):
|
|
|
|
self.presentSplashScreen()
|
|
|
|
self.hideLoadingIndicator()
|
|
|
|
case (_, .remoteSignOut(let isSoft), .remoteSigningOut):
|
|
|
|
self.showLoadingIndicator()
|
|
|
|
self.tearDownUserSession(isSoftLogout: isSoft)
|
|
|
|
case (.remoteSigningOut(let isSoft), .completedSigningOut, .signedOut):
|
|
|
|
self.presentSplashScreen(isSoftLogout: isSoft)
|
|
|
|
self.hideLoadingIndicator()
|
2022-05-27 13:02:36 +03:00
|
|
|
default:
|
|
|
|
fatalError("Unknown transition: \(context)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stateMachine.addErrorHandler { context in
|
|
|
|
fatalError("Failed transition with context: \(context)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-14 17:36:28 +01:00
|
|
|
private func restoreUserSession() {
|
|
|
|
Task {
|
|
|
|
switch await userSessionStore.restoreUserSession() {
|
|
|
|
case .success(let userSession):
|
|
|
|
self.userSession = userSession
|
2022-09-15 12:41:37 +03:00
|
|
|
if userSession.isSoftLogout {
|
|
|
|
stateMachine.processEvent(.remoteSignOut(isSoft: true))
|
|
|
|
} else {
|
|
|
|
stateMachine.processEvent(.succeededRestoringSession)
|
|
|
|
}
|
2022-06-14 17:36:28 +01:00
|
|
|
case .failure:
|
|
|
|
MXLog.error("Failed to restore an existing session.")
|
|
|
|
stateMachine.processEvent(.failedRestoringSession)
|
|
|
|
}
|
2022-05-26 16:04:48 +03:00
|
|
|
}
|
2022-06-14 17:36:28 +01:00
|
|
|
}
|
|
|
|
|
2022-06-15 10:10:22 +01:00
|
|
|
private func startAuthentication() {
|
2022-07-27 10:57:16 +01:00
|
|
|
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore)
|
2022-11-16 15:37:34 +02:00
|
|
|
authenticationCoordinator = AuthenticationCoordinator(authenticationService: authenticationService,
|
|
|
|
navigationController: navigationController)
|
|
|
|
authenticationCoordinator?.delegate = self
|
2022-05-26 16:04:48 +03:00
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
authenticationCoordinator?.start()
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
|
|
|
|
private func startAuthenticationSoftLogout() {
|
|
|
|
Task {
|
|
|
|
var displayName = ""
|
|
|
|
if case .success(let name) = await userSession.clientProxy.loadUserDisplayName() {
|
|
|
|
displayName = name
|
|
|
|
}
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
let credentials = SoftLogoutCredentials(userId: userSession.userID,
|
|
|
|
homeserverName: userSession.homeserver,
|
|
|
|
userDisplayName: displayName,
|
|
|
|
deviceId: userSession.deviceId)
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore)
|
|
|
|
_ = await authenticationService.configure(for: userSession.homeserver)
|
|
|
|
|
|
|
|
let parameters = SoftLogoutCoordinatorParameters(authenticationService: authenticationService,
|
|
|
|
credentials: credentials,
|
|
|
|
keyBackupNeeded: false)
|
|
|
|
let coordinator = SoftLogoutCoordinator(parameters: parameters)
|
|
|
|
coordinator.callback = { result in
|
|
|
|
switch result {
|
|
|
|
case .signedIn(let session):
|
|
|
|
self.userSession = session
|
|
|
|
self.stateMachine.processEvent(.succeededSigningIn)
|
|
|
|
case .clearAllData:
|
2022-09-23 12:21:41 +03:00
|
|
|
// clear user data
|
|
|
|
self.userSessionStore.logout(userSession: self.userSession)
|
|
|
|
self.userSession = nil
|
|
|
|
self.startAuthentication()
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
}
|
2022-11-16 15:37:34 +02:00
|
|
|
|
|
|
|
navigationController.setRootCoordinator(coordinator)
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-09-25 12:34:11 +03:00
|
|
|
private func setupUserSession() {
|
|
|
|
let userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession,
|
2022-11-16 15:37:34 +02:00
|
|
|
navigationController: navigationController,
|
2022-09-25 12:34:11 +03:00
|
|
|
bugReportService: bugReportService)
|
|
|
|
|
|
|
|
userSessionFlowCoordinator.callback = { [weak self] action in
|
|
|
|
switch action {
|
|
|
|
case .signOut:
|
|
|
|
self?.stateMachine.processEvent(.signOut)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
userSessionFlowCoordinator.start()
|
|
|
|
|
|
|
|
self.userSessionFlowCoordinator = userSessionFlowCoordinator
|
|
|
|
}
|
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
private func tearDownUserSession(isSoftLogout: Bool = false) {
|
2022-09-21 11:21:58 +03:00
|
|
|
userSession.clientProxy.stopSync()
|
2022-09-25 12:34:11 +03:00
|
|
|
userSessionFlowCoordinator?.stop()
|
2022-09-21 11:21:58 +03:00
|
|
|
|
|
|
|
deobserveUserSessionChanges()
|
|
|
|
|
|
|
|
if !isSoftLogout {
|
|
|
|
Task {
|
2022-09-15 12:41:37 +03:00
|
|
|
// first log out from the server
|
|
|
|
_ = await userSession.clientProxy.logout()
|
2022-09-21 11:21:58 +03:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
// regardless of the result, clear user data
|
|
|
|
userSessionStore.logout(userSession: userSession)
|
|
|
|
userSession = nil
|
|
|
|
}
|
|
|
|
}
|
2022-09-21 11:21:58 +03:00
|
|
|
|
|
|
|
// complete logging out
|
|
|
|
stateMachine.processEvent(.completedSigningOut)
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private func presentSplashScreen(isSoftLogout: Bool = false) {
|
2022-11-16 15:37:34 +02:00
|
|
|
navigationController.setRootCoordinator(SplashScreenCoordinator())
|
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
if isSoftLogout {
|
|
|
|
startAuthenticationSoftLogout()
|
|
|
|
} else {
|
|
|
|
startAuthentication()
|
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
private func observeUserSessionChanges() {
|
|
|
|
userSession.callbacks
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] callback in
|
2022-10-17 09:56:17 +01:00
|
|
|
guard let self else { return }
|
2022-09-15 12:41:37 +03:00
|
|
|
switch callback {
|
|
|
|
case .didReceiveAuthError(let isSoftLogout):
|
|
|
|
self.stateMachine.processEvent(.remoteSignOut(isSoft: isSoftLogout))
|
|
|
|
case .updateRestoreTokenNeeded:
|
|
|
|
if let userSession = self.userSession {
|
2022-11-04 13:24:53 +02:00
|
|
|
_ = self.userSessionStore.refreshRestorationToken(for: userSession)
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2022-09-23 13:21:54 +03:00
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private func deobserveUserSessionChanges() {
|
|
|
|
cancellables.forEach { $0.cancel() }
|
|
|
|
cancellables.removeAll()
|
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-07-01 13:56:52 +03:00
|
|
|
// MARK: Toasts and loading indicators
|
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
static let loadingIndicatorIdentifier = "AppCoordinatorLoading"
|
|
|
|
|
2022-07-01 13:56:52 +03:00
|
|
|
private func showLoadingIndicator() {
|
2022-11-16 15:37:34 +02:00
|
|
|
ServiceLocator.shared.userNotificationController.submitNotification(UserNotification(id: Self.loadingIndicatorIdentifier,
|
|
|
|
type: .modal,
|
|
|
|
title: ElementL10n.loading,
|
|
|
|
persistent: true))
|
2022-07-01 13:56:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private func hideLoadingIndicator() {
|
2022-11-16 15:37:34 +02:00
|
|
|
ServiceLocator.shared.userNotificationController.retractNotificationWithId(Self.loadingIndicatorIdentifier)
|
2022-07-01 13:56:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private func showLoginErrorToast() {
|
2022-11-16 15:37:34 +02:00
|
|
|
ServiceLocator.shared.userNotificationController.submitNotification(UserNotification(title: "Failed logging in"))
|
2022-07-01 13:56:52 +03:00
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - AuthenticationCoordinatorDelegate
|
|
|
|
|
|
|
|
extension AppCoordinator: AuthenticationCoordinatorDelegate {
|
|
|
|
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didLoginWithSession userSession: UserSessionProtocol) {
|
|
|
|
self.userSession = userSession
|
|
|
|
stateMachine.processEvent(.succeededSigningIn)
|
2022-07-01 13:56:52 +03:00
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|