mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-11 23:33:36 -06:00
217 lines
7.4 KiB
Swift
217 lines
7.4 KiB
Swift
import Combine
|
|
import Foundation
|
|
import UIKit
|
|
|
|
/// The `AppProcessor` processes actions received at the application level and contains the logic
|
|
/// to control the top-level flow through the app.
|
|
///
|
|
@MainActor
|
|
public class AppProcessor {
|
|
// MARK: Properties
|
|
|
|
/// The root module to use to create sub-coordinators.
|
|
let appModule: AppModule
|
|
|
|
/// The root coordinator of the app.
|
|
var coordinator: AnyCoordinator<AppRoute, AppEvent>?
|
|
|
|
/// The services used by the app.
|
|
let services: ServiceContainer
|
|
|
|
// MARK: Initialization
|
|
|
|
/// Initializes an `AppProcessor`.
|
|
///
|
|
/// - Parameters:
|
|
/// - appModule: The root module to use to create sub-coordinators.
|
|
/// - services: The services used by the app.
|
|
///
|
|
public init(
|
|
appModule: AppModule,
|
|
services: ServiceContainer
|
|
) {
|
|
self.appModule = appModule
|
|
self.services = services
|
|
|
|
self.services.notificationService.setDelegate(self)
|
|
self.services.syncService.delegate = self
|
|
|
|
UI.initialLanguageCode = services.appSettingsStore.appLocale ?? Locale.current.languageCode
|
|
UI.applyDefaultAppearances()
|
|
|
|
Task {
|
|
for await _ in services.notificationCenterService.willEnterForegroundPublisher() {
|
|
let userId = try await self.services.stateService.getActiveAccountId()
|
|
let shouldTimeout = try await services.vaultTimeoutService.hasPassedSessionTimeout(userId: userId)
|
|
if shouldTimeout {
|
|
// Allow the AuthCoordinator to handle the timeout.
|
|
await coordinator?.handleEvent(.didTimeout(userId: userId))
|
|
}
|
|
}
|
|
}
|
|
|
|
Task {
|
|
for await _ in services.notificationCenterService.didEnterBackgroundPublisher() {
|
|
let userId = try await self.services.stateService.getActiveAccountId()
|
|
try await services.vaultTimeoutService.setLastActiveTime(userId: userId)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Methods
|
|
|
|
/// Starts the application flow by navigating the user to the first flow.
|
|
///
|
|
/// - Parameters:
|
|
/// - appContext: The context that the app is running within.
|
|
/// - initialRoute: The initial route to navigate to. If `nil` this, will navigate to the
|
|
/// unlock or landing auth route based on if there's an active account. Defaults to `nil`.
|
|
/// - navigator: The object that will be used to navigate between routes.
|
|
/// - window: The window to use to set the app's theme.
|
|
///
|
|
public func start(
|
|
appContext: AppContext,
|
|
initialRoute: AppRoute? = nil,
|
|
navigator: RootNavigator,
|
|
window: UIWindow?
|
|
) async {
|
|
let coordinator = appModule.makeAppCoordinator(appContext: appContext, navigator: navigator)
|
|
coordinator.start()
|
|
self.coordinator = coordinator
|
|
|
|
Task {
|
|
for await appTheme in await services.stateService.appThemePublisher().values {
|
|
navigator.appTheme = appTheme
|
|
window?.overrideUserInterfaceStyle = appTheme.userInterfaceStyle
|
|
}
|
|
}
|
|
|
|
await loadFlags()
|
|
await services.migrationService.performMigrations()
|
|
await services.environmentService.loadURLsForActiveAccount()
|
|
|
|
services.application?.registerForRemoteNotifications()
|
|
|
|
if let initialRoute {
|
|
coordinator.navigate(to: initialRoute)
|
|
} else {
|
|
await coordinator.handleEvent(.didStart)
|
|
}
|
|
}
|
|
|
|
// MARK: Notification Methods
|
|
|
|
/// Called when the app has registered for push notifications.
|
|
///
|
|
/// - Parameter tokenData: The device token for push notifications.
|
|
///
|
|
public func didRegister(withToken tokenData: Data) {
|
|
Task {
|
|
await services.notificationService.didRegister(withToken: tokenData)
|
|
}
|
|
}
|
|
|
|
/// Called when the app failed to register for push notifications.
|
|
///
|
|
/// - Parameter error: The error received.
|
|
///
|
|
public func failedToRegister(_ error: Error) {
|
|
services.errorReporter.log(error: error)
|
|
}
|
|
|
|
/// Called when the app has received data from a push notification.
|
|
///
|
|
/// - Parameters:
|
|
/// - message: The content of the push notification.
|
|
/// - notificationDismissed: `true` if a notification banner has been dismissed.
|
|
/// - notificationTapped: `true` if a notification banner has been tapped.
|
|
///
|
|
public func messageReceived(
|
|
_ message: [AnyHashable: Any],
|
|
notificationDismissed: Bool? = nil,
|
|
notificationTapped: Bool? = nil
|
|
) async {
|
|
await services.notificationService.messageReceived(
|
|
message,
|
|
notificationDismissed: notificationDismissed,
|
|
notificationTapped: notificationTapped
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - NotificationServiceDelegate
|
|
|
|
extension AppProcessor: NotificationServiceDelegate {
|
|
/// Users are logged out, route to landing page.
|
|
///
|
|
func routeToLanding() async {
|
|
coordinator?.navigate(to: .auth(.landing))
|
|
}
|
|
|
|
/// Show the login request.
|
|
///
|
|
/// - Parameter loginRequest: The login request.
|
|
///
|
|
func showLoginRequest(_ loginRequest: LoginRequest) {
|
|
coordinator?.navigate(to: .loginRequest(loginRequest))
|
|
}
|
|
|
|
/// Switch the active account in order to show the login request, prompting the user if necessary.
|
|
///
|
|
/// - Parameters:
|
|
/// - account: The account associated with the login request.
|
|
/// - loginRequest: The login request to show.
|
|
/// - showAlert: Whether to show the alert or simply switch the account.
|
|
///
|
|
func switchAccounts(to account: Account, for loginRequest: LoginRequest, showAlert: Bool) {
|
|
DispatchQueue.main.async {
|
|
if showAlert {
|
|
self.coordinator?.showAlert(.confirmation(
|
|
title: Localizations.logInRequested,
|
|
message: Localizations.loginAttemptFromXDoYouWantToSwitchToThisAccount(account.profile.email)
|
|
) {
|
|
self.switchAccounts(to: account.profile.userId, for: loginRequest)
|
|
})
|
|
} else {
|
|
self.switchAccounts(to: account.profile.userId, for: loginRequest)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Switch to the specified account and show the login request.
|
|
///
|
|
/// - Parameters:
|
|
/// - userId: The userId of the account to switch to.
|
|
/// - loginRequest: The login request to show.
|
|
///
|
|
private func switchAccounts(to userId: String, for loginRequest: LoginRequest) {
|
|
(coordinator as? VaultCoordinatorDelegate)?.didTapAccount(userId: userId)
|
|
coordinator?.navigate(to: .loginRequest(loginRequest))
|
|
}
|
|
}
|
|
|
|
// MARK: - SyncServiceDelegate
|
|
|
|
extension AppProcessor: SyncServiceDelegate {
|
|
func securityStampChanged(userId: String) async {
|
|
// Log the user out if their security stamp changes.
|
|
coordinator?.hideLoadingOverlay()
|
|
try? await services.authRepository.logout(userId: userId)
|
|
await coordinator?.handleEvent(.didLogout(userId: userId, userInitiated: false))
|
|
}
|
|
}
|
|
|
|
// MARK: - Feature flags
|
|
|
|
extension AppProcessor {
|
|
/// Loads feature flags.
|
|
///
|
|
func loadFlags() async {
|
|
do {
|
|
try await services.clientPlatform.loadFlags(flags: [FeatureFlagsConstants.enableCipherKeyEncryption: true])
|
|
} catch {
|
|
services.errorReporter.log(error: error)
|
|
}
|
|
}
|
|
}
|