import BitwardenKit import BitwardenResources import BitwardenSdk import Combine import Foundation // swiftlint:disable file_length // MARK: - StateService /// A protocol for a `StateService` which manages the state of the accounts in the app. /// protocol StateService: AnyObject { /// The language option currently selected for the app. var appLanguage: LanguageOption { get set } /// The organization identifier being remembered on the single-sign on screen. var rememberedOrgIdentifier: String? { get set } /// Adds a new account to the app's state after a successful login. /// /// - Parameter account: The `Account` to add. /// func addAccount(_ account: Account) async /// Clears the pins stored on device and in memory. /// func clearPins() async throws /// Deletes the current active account. /// func deleteAccount() async throws /// Returns whether the active account was switched in the extension. This compares the current /// active account in memory with what's stored on disk to determine if the account was switched. /// /// - Returns: Whether the active was switched in the extension. /// func didAccountSwitchInExtension() async throws -> Bool /// Returns whether the active user account has access to premium features. /// /// - Returns: Whether the active account has access to premium features. /// func doesActiveAccountHavePremium() async -> Bool /// Gets the access token's expiration date for an account. /// /// - Parameter userId: The user ID associated with the access token expiration date. /// - Returns: The user's access token expiration date. /// func getAccessTokenExpirationDate(userId: String) async -> Date? /// Gets the account for an id. /// /// - Parameter userId: The id for an account. If nil, the active account will be returned. /// - Returns: The account for the id. /// func getAccount(userId: String?) async throws -> Account /// Gets the account encryptions keys for an account. /// /// - Parameter userId: The user ID of the account. Defaults to the active account if `nil`. /// - Returns: The account encryption keys. /// func getAccountEncryptionKeys(userId: String?) async throws -> AccountEncryptionKeys /// Gets whether the user has unlocked their account in the current session interactively. /// - Parameter userId: The user ID of the account. Defaults to the active account if `nil`. func getAccountHasBeenUnlockedInteractively(userId: String?) async throws -> Bool /// Gets the user's progress for setting up autofill. /// /// - Parameter userId: The user ID associated with the autofill setup progress. /// - Returns: The user's autofill setup progress. /// func getAccountSetupAutofill(userId: String) async -> AccountSetupProgress? /// Gets the user's progress for importing logins. /// /// - Parameter userId: The user ID associated with the import logins setup progress. /// - Returns: The user's import logins setup progress. /// func getAccountSetupImportLogins(userId: String) async -> AccountSetupProgress? /// Gets the user's progress for setting up vault unlock. /// /// - Parameter userId: The user ID associated with the vault unlock setup progress. /// - Returns: The user's vault unlock setup progress. /// func getAccountSetupVaultUnlock(userId: String) async -> AccountSetupProgress? /// Gets all accounts. /// /// - Returns: The known user accounts. /// func getAccounts() async throws -> [Account] /// Gets the account id or the active account id for a possible id. /// /// - Parameter userId: The possible user Id of an account. /// - Returns: The user account id or the active id. /// func getAccountIdOrActiveId(userId: String?) async throws -> String /// Gets the active account. /// /// - Returns: The active user account. /// func getActiveAccount() async throws -> Account /// Gets the active account id. /// /// - Returns: The active user account id. /// func getActiveAccountId() async throws -> String /// Gets whether the autofill info prompt has been shown. /// /// - Returns: Whether the autofill info prompt has been shown. /// func getAddSitePromptShown() async -> Bool /// Gets the allow sync on refresh value for an account. /// /// - Parameter userId: The user ID of the account. Defaults to the active account if `nil`. /// - Returns: The allow sync on refresh value. /// func getAllowSyncOnRefresh(userId: String?) async throws -> Bool /// Gets the Universal Clipboard setting for a user account. /// /// - Parameter userId: The user ID of the account. Defaults to the active account if `nil`. /// - Returns: A Boolean value indicating whether Universal Clipboard is allowed. /// func getAllowUniversalClipboard(userId: String?) async throws -> Bool /// Gets the app rehydration state. /// - Parameter userId: The user ID associated with this state. /// - Returns: The rehydration state. func getAppRehydrationState(userId: String?) async throws -> AppRehydrationState? /// Get the app theme. /// /// - Returns: The app theme. /// func getAppTheme() async -> AppTheme /// Get the active user's Biometric Authentication Preference. /// /// - Returns: A `Bool` indicating the user's preference for using biometric authentication. /// If `true`, the device should attempt biometric authentication for authorization events. /// If `false`, the device should not attempt biometric authentication for authorization events. /// func getBiometricAuthenticationEnabled() async throws -> Bool /// Gets the clear clipboard value for an account. /// /// - Parameter userId: The user ID associated with the clear clipboard value. Defaults to the active /// account if `nil` /// - Returns: The time after which the clipboard should clear. /// func getClearClipboardValue(userId: String?) async throws -> ClearClipboardValue /// Gets the connect to watch value for an account. /// /// - Parameter userId: The user ID associated with the connect to watch value. Defaults to the active /// account if `nil` /// - Returns: Whether to connect to the watch app. /// func getConnectToWatch(userId: String?) async throws -> Bool /// Gets the default URI match type value for an account. /// /// - Parameter userId: The user ID of the account. Defaults to the active account if `nil`. /// - Returns: The default URI match type value. /// func getDefaultUriMatchType(userId: String?) async -> UriMatchType /// Gets the disable auto-copy TOTP value for an account. /// /// - Parameter userId: The user ID of the account. Defaults to the active account if `nil`. /// - Returns: The disable auto-copy TOTP value. /// func getDisableAutoTotpCopy(userId: String?) async throws -> Bool /// The user's pin protected by their user key. /// /// - Parameter userId: The user ID associated with the encrypted pin. /// - Returns: The user's pin protected by their user key. /// func getEncryptedPin(userId: String?) async throws -> String? /// Gets the environment URLs for a user ID. /// /// - Parameter userId: The user ID associated with the environment URLs. /// - Returns: The user's environment URLs. /// func getEnvironmentURLs(userId: String?) async throws -> EnvironmentURLData? /// Gets the events stored to disk to be uploaded in the future. /// /// - Parameters: /// - userId: The user ID associated with the events. /// - Returns: The events for the user /// func getEvents(userId: String?) async throws -> [EventData] /// Gets the data for the flight recorder. /// /// - Returns: The flight recorder data. /// func getFlightRecorderData() async -> FlightRecorderData? /// Gets whether a sync has been done successfully after login. This is particular useful to trigger logic that /// needs to be executed right after login in and after the first successful sync. /// /// - Parameter userId: The user ID associated with the sync after login. /// - Returns: `true` if sync has already been done after login, `false` otherwise. /// func getHasPerformedSyncAfterLogin(userId: String?) async throws -> Bool /// Gets whether the intro carousel screen has been shown. /// /// - Returns: Whether the intro carousel screen has been shown. /// func getIntroCarouselShown() async -> Bool /// Gets the user's last active time within the app. /// This value is set when the app is backgrounded. /// /// - Parameter userId: The user ID associated with the last active time within the app. /// - Returns: The date of the last active time. /// func getLastActiveTime(userId: String?) async throws -> Date? /// Gets the time of the last sync for a user. /// /// - Parameter userId: The user ID associated with the last sync time. /// - Returns: The user's last sync time. /// func getLastSyncTime(userId: String?) async throws -> Date? /// The last value of the connect to watch setting, ignoring the user id. Used for /// sending the status to the watch if the user is logged out. /// /// - Returns: The last known value of the `connectToWatch` setting. /// func getLastUserShouldConnectToWatch() async -> Bool /// Gets the status of Learn Generator Action Card. /// /// - Returns: The status of Learn generator Action Card. /// func getLearnGeneratorActionCardStatus() async -> AccountSetupProgress? /// Get any pending login request data. /// /// - Returns: The pending login request data from a push notification. /// func getLoginRequest() async -> LoginRequestNotification? /// Gets whether the account belonging to the user Id has been manually locked. /// - Parameter userId: The user ID associated with the account. /// - Returns: `true` if manually locked, `false` otherwise. func getManuallyLockedAccount(userId: String?) async throws -> Bool /// Gets the master password hash for a user ID. /// /// - Parameter userId: The user ID associated with the master password hash. /// - Returns: The user's master password hash. /// func getMasterPasswordHash(userId: String?) async throws -> String? /// Gets the last notifications registration date for a user ID. /// /// - Parameter userId: The user ID of the account. Defaults to the active account if `nil`. /// - Returns: The last notifications registration date. /// func getNotificationsLastRegistrationDate(userId: String?) async throws -> Date? /// Gets the password generation options for a user ID. /// /// - Parameter userId: The user ID associated with the password generation options. /// - Returns: The password generation options for the user ID. /// func getPasswordGenerationOptions(userId: String?) async throws -> PasswordGenerationOptions? /// Gets the pending actions from `AppIntent`s. /// - Returns: The pending actions to execute. func getPendingAppIntentActions() async -> [PendingAppIntentAction]? /// Gets the environment URLs used by the app prior to the user authenticating. /// /// - Returns: The environment URLs used prior to user authentication. /// func getPreAuthEnvironmentURLs() async -> EnvironmentURLData? /// Gets the environment URLs for a given email during account creation. /// /// - Parameter email: The email used to start the account creation. /// - Returns: The environment URLs used prior to start the account creation. /// func getAccountCreationEnvironmentURLs(email: String) async -> EnvironmentURLData? /// Gets the App Review Prompt data. /// /// - Returns: The App Review Prompt data. /// func getReviewPromptData() async -> ReviewPromptData? /// Get whether the device should be trusted. /// /// - Returns: Whether to trust the device. /// func getShouldTrustDevice(userId: String) async -> Bool? /// Gets the status of Learn New Login Action Card. /// /// - Returns: The status of Learn New Login Action Card. /// func getLearnNewLoginActionCardStatus() async -> AccountSetupProgress? /// Get whether to show the website icons. /// /// - Returns: Whether to show the website icons. /// func getShowWebIcons() async -> Bool /// Gets whether Siri & Shortcuts access is enabled. /// - Parameter userId: The user ID. /// - Returns: Whether Siri & Shortcuts access is enabled. func getSiriAndShortcutsAccess(userId: String?) async throws -> Bool /// Gets the sync to Authenticator value for an account. /// /// - Parameter userId: The user ID associated with the sync to Authenticator value. Defaults to the active /// account if `nil` /// - Returns: Whether to sync TOPT codes to the Authenticator app. /// func getSyncToAuthenticator(userId: String?) async throws -> Bool /// Gets the session timeout action. /// /// - Parameter userId: The user ID for the account. /// - Returns: The action to perform when a session timeout occurs. /// func getTimeoutAction(userId: String?) async throws -> SessionTimeoutAction /// Get the two-factor token (non-nil if the user selected the "remember me" option). /// /// - Parameter email: The user's email address. /// - Returns: The two-factor token. /// func getTwoFactorToken(email: String) async -> String? /// Gets the number of unsuccessful attempts to unlock the vault for a user ID. /// /// - Parameter userId: The optional user ID associated with the unsuccessful unlock attempts, /// if `nil` defaults to currently active user. /// - Returns: The number of unsuccessful attempts to unlock the vault. /// func getUnsuccessfulUnlockAttempts(userId: String?) async throws -> Int /// Gets whether a user has a master password. /// /// - Parameter userId: The user ID of the user to determine whether they have a master password. /// - Returns: Whether the user has a master password. /// func getUserHasMasterPassword(userId: String?) async throws -> Bool /// Gets the user ID of any accounts with the specified email. /// /// - Parameter email: The email of the account. /// - Returns: A list of user IDs for the accounts with a matching email. /// func getUserIds(email: String) async -> [String] /// Gets the username generation options for a user ID. /// /// - Parameter userId: The user ID associated with the username generation options. /// - Returns: The username generation options for the user ID. /// func getUsernameGenerationOptions(userId: String?) async throws -> UsernameGenerationOptions? /// Gets whether the user uses key connector. /// /// - Parameter userId: The user ID to check if they use key connector. /// - Returns: Whether the user uses key connector. /// func getUsesKeyConnector(userId: String?) async throws -> Bool /// Gets the session timeout value. /// /// - Parameter userId: The user ID for the account. /// - Returns: The session timeout value. /// func getVaultTimeout(userId: String?) async throws -> SessionTimeoutValue /// Whether the user is authenticated. /// /// - Parameter userId: The user ID to check if they are authenticated. /// - Returns: Whether the user is authenticated. /// func isAuthenticated(userId: String?) async throws -> Bool /// Logs the user out of an account. /// /// - Parameters: /// - userId: The user ID of the account to log out of. Defaults to the active account if `nil`. /// - userInitiated: Whether the logout was user initiated or a result of a logout timeout action. /// func logoutAccount(userId: String?, userInitiated: Bool) async throws /// The pin protected user key. /// /// - Note: This is being replaced by ``pinProtectedUserKeyEnvelope(userId:)``. /// /// - Parameter userId: The user ID associated with the pin protected user key. /// - Returns: The user's pin protected user key. /// func pinProtectedUserKey(userId: String?) async throws -> String? /// The pin protected user key envelope. /// /// - Parameter userId: The user ID associated with the pin protected user key envelope. /// - Returns: The user's pin protected user key envelope. /// func pinProtectedUserKeyEnvelope(userId: String?) async throws -> String? /// Whether pin unlock requires the user to enter their master password or use biometrics after /// an app restart. /// /// - Returns: Whether pin unlock the user to enter their master password or use biometrics /// after an app restart. /// func pinUnlockRequiresPasswordAfterRestart() async throws -> Bool /// Sets the access token's expiration date for an account. /// /// - Parameters: /// - expirationDate: The user's access token expiration date. /// - userId: The user ID associated with the access token expiration date. /// func setAccessTokenExpirationDate(_ expirationDate: Date?, userId: String) async /// Sets the account encryption keys for an account. /// /// - Parameters: /// - encryptionKeys: The account encryption keys. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setAccountEncryptionKeys(_ encryptionKeys: AccountEncryptionKeys, userId: String?) async throws /// Sets whether the user has unlocked their account in the current session interactively. /// - Parameters: /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// - value: Whether the user has unlocked their account in the current session. func setAccountHasBeenUnlockedInteractively(userId: String?, value: Bool) async throws /// Sets the KDF config for an account. /// /// - Parameters: /// - kdfConfig: The account's KDF config. /// - userId: The user ID of the account to set the KDF config for. /// func setAccountKdf(_ kdfConfig: KdfConfig, userId: String) async throws /// Sets the master password unlock data for an account. /// /// - Parameters: /// - masterPasswordUnlock: The account master password unlock data. /// - userId: The user ID of the account to associate with the master password unlock data. /// func setAccountMasterPasswordUnlock( _ masterPasswordUnlock: MasterPasswordUnlockResponseModel, userId: String, ) async /// Sets the user's progress for setting up autofill. /// /// - Parameters: /// - autofillSetup: The user's autofill setup progress. /// - userId: The user ID associated with the autofill setup progress. /// func setAccountSetupAutofill(_ autofillSetup: AccountSetupProgress?, userId: String?) async throws /// Sets the user's progress for setting up import logins. /// /// - Parameters: /// - importLogins: The user's import logins setup progress. /// - userId: The user ID associated with the import logins setup progress. /// func setAccountSetupImportLogins(_ importLogins: AccountSetupProgress?, userId: String?) async throws /// Sets the user's progress for setting up vault unlock. /// /// - Parameters: /// - autofillSetup: The user's vault unlock setup progress. /// - userId: The user ID associated with the vault unlock setup progress. /// func setAccountSetupVaultUnlock(_ vaultUnlockSetup: AccountSetupProgress?, userId: String?) async throws /// Sets the active account. /// /// - Parameter userId: The user Id of the account to set as active. /// func setActiveAccount(userId: String) async throws /// Sets whether the autofill info prompt has been shown. /// /// - Parameter shown: Whether the autofill info prompt has been shown. /// func setAddSitePromptShown(_ shown: Bool) async /// Sets the allow sync on refresh value for an account. /// /// - Parameters: /// - allowSyncOnRefresh: Whether to allow sync on refresh. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setAllowSyncOnRefresh(_ allowSyncOnRefresh: Bool, userId: String?) async throws /// Sets the Universal Clipboard setting for a user account. /// /// - Parameters: /// - allowUniversalClipboard: A Boolean value indicating whether Universal Clipboard should be allowed. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setAllowUniversalClipboard(_ allowUniversalClipboard: Bool, userId: String?) async throws /// Sets the app theme. /// /// - Parameter appTheme: The new app theme. /// func setAppTheme(_ appTheme: AppTheme) async /// Sets the user's Biometric Authentication Preference. /// /// - Parameter isEnabled: A `Bool` indicating the user's preference for using biometric authentication. /// If `true`, the device should attempt biometric authentication for authorization events. /// If `false`, the device should not attempt biometric authentication for authorization events. /// func setBiometricAuthenticationEnabled(_ isEnabled: Bool?) async throws /// Sets the clear clipboard value for an account. /// /// - Parameters: /// - clearClipboardValue: The time after which to clear the clipboard. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setClearClipboardValue(_ clearClipboardValue: ClearClipboardValue?, userId: String?) async throws /// Sets the connect to watch value for an account. /// /// - Parameters: /// - connectToWatch: Whether to connect to the watch app. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setConnectToWatch(_ connectToWatch: Bool, userId: String?) async throws /// Sets the default URI match type value for an account. /// /// - Parameters: /// - defaultUriMatchType: The default URI match type. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setDefaultUriMatchType(_ defaultUriMatchType: UriMatchType?, userId: String?) async throws /// Sets the disable auto-copy TOTP value for an account. /// /// - Parameters: /// - disableAutoTotpCopy: Whether the TOTP for a cipher should be auto-copied. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setDisableAutoTotpCopy(_ disableAutoTotpCopy: Bool, userId: String?) async throws /// Sets the events saved to disk for future upload. /// /// - Parameters: /// - events: The events to save. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setEvents(_ events: [EventData], userId: String?) async throws /// Sets the force password reset reason for an account. /// /// - Parameters: /// - reason: The reason why a password reset is required. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setForcePasswordResetReason(_ reason: ForcePasswordResetReason?, userId: String?) async throws /// Sets whether a sync has been done successfully after login. This is particular useful to trigger logic that /// needs to be executed right after login in and after the first successful sync. /// /// - Parameters: /// - hasBeenPerformed: Whether a sync has been performed after login. /// - userId: The user ID associated with the sync after login. func setHasPerformedSyncAfterLogin(_ hasBeenPerformed: Bool, userId: String?) async throws /// Sets the data for the flight recorder. /// func setFlightRecorderData(_ data: FlightRecorderData?) async /// Sets whether the intro carousel screen has been shown. /// /// - Parameter shown: Whether the intro carousel screen has been shown. /// func setIntroCarouselShown(_ shown: Bool) async /// Sets the status of Learn generator Action Card. /// /// - Parameter status: The status of Learn generator Action Card. /// func setLearnGeneratorActionCardStatus(_ status: AccountSetupProgress) async /// Sets the status of Learn New Login Action Card. /// /// - Parameter status: The status of Learn New Login Action Card. /// func setLearnNewLoginActionCardStatus(_ status: AccountSetupProgress) async /// Sets the last active time within the app. /// /// - Parameters: /// - date: The current time. /// - userId: The user ID associated with the last active time within the app. /// func setLastActiveTime(_ date: Date?, userId: String?) async throws /// Sets the time of the last sync for a user ID. /// /// - Parameters: /// - date: The time of the last sync. /// - userId: The user ID associated with the last sync time. /// func setLastSyncTime(_ date: Date?, userId: String?) async throws /// Set pending login request data from a push notification. /// /// - Parameter loginRequest: The pending login request data. /// func setLoginRequest(_ loginRequest: LoginRequestNotification?) async /// Sets whether the account belonging to the user Id has been manually locked. /// - Parameters /// - isLocked: Whether the account has been locked manually. /// - userId: The user ID associated with the account. /// - Returns: `true` if manually locked, `false` otherwise. func setManuallyLockedAccount(_ isLocked: Bool, userId: String?) async throws /// Sets the master password hash for a user ID. /// /// - Parameters: /// - hash: The user's master password hash. /// - userId: The user ID associated with the master password hash. /// func setMasterPasswordHash(_ hash: String?, userId: String?) async throws /// Sets the last notifications registration date for a user ID. /// /// - Parameters: /// - date: The last notifications registration date. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setNotificationsLastRegistrationDate(_ date: Date?, userId: String?) async throws /// Sets the password generation options for a user ID. /// /// - Parameters: /// - options: The user's password generation options. /// - userId: The user ID associated with the password generation options. /// func setPasswordGenerationOptions(_ options: PasswordGenerationOptions?, userId: String?) async throws /// Sets the pending actions from `AppIntent`s. /// - Parameter actions: Actions pending to be executed. func setPendingAppIntentActions(actions: [PendingAppIntentAction]?) async /// Set's the pin keys. /// /// - Parameters: /// - enrollPinResponse: The user's pin keys from enrolling a pin. /// - requirePasswordAfterRestart: Whether to require password after app restart. /// func setPinKeys( enrollPinResponse: EnrollPinResponse, requirePasswordAfterRestart: Bool, ) async throws /// Sets the pin protected user key to memory. /// /// - Parameter pin: The user's pin. /// func setPinProtectedUserKeyToMemory(_ pin: String) async throws /// Sets the environment URLs used prior to user authentication. /// /// - Parameter urls: The environment URLs used prior to user authentication. /// func setPreAuthEnvironmentURLs(_ urls: EnvironmentURLData) async /// Sets the environment URLs for a given email during account creation. /// - Parameters: /// - urls: The environment urls used to start the account creation. /// - email: The email used to start the account creation. /// func setAccountCreationEnvironmentURLs(urls: EnvironmentURLData, email: String) async /// Sets the app rehydration state for the active account. /// - Parameters: /// - rehydrationState: The app rehydration state. /// - userId: The user ID of the rehydration state. func setAppRehydrationState(_ rehydrationState: AppRehydrationState?, userId: String?) async throws /// Sets the App Review Prompt data. /// /// - Parameter data: The App Review Prompt data. /// func setReviewPromptData(_ data: ReviewPromptData) async /// Set whether to trust the device. /// /// - Parameter shouldTrustDevice: Whether to trust the device. /// func setShouldTrustDevice(_ shouldTrustDevice: Bool?, userId: String) async /// Set whether to show the website icons. /// /// - Parameter showWebIcons: Whether to show the website icons. /// func setShowWebIcons(_ showWebIcons: Bool) async /// Set whether to allow access to Siri & Shortcuts using `AppIntent`. /// /// - Parameters: /// - siriAndShortcutsAccess: Whether access is enabled. /// - userId: The user ID of the account. Defaults to the active account if `nil`. func setSiriAndShortcutsAccess(_ siriAndShortcutsAccess: Bool, userId: String?) async throws /// Sets the sync to authenticator value for an account. /// /// - Parameters: /// - syncToAuthenticator: Whether to sync TOTP codes to the Authenticator app. /// - userId: The user ID of the account. Defaults to the active account if `nil`. /// func setSyncToAuthenticator(_ syncToAuthenticator: Bool, userId: String?) async throws /// Sets the session timeout action. /// /// - Parameters: /// - action: The action to take when the user's session times out. /// - userId: The user ID associated with the timeout action. /// func setTimeoutAction(action: SessionTimeoutAction, userId: String?) async throws /// Sets the user's two-factor token. /// /// - Parameters: /// - token: The two-factor token. /// - email: The user's email address. /// func setTwoFactorToken(_ token: String?, email: String) async /// Sets the number of unsuccessful attempts to unlock the vault for a user ID. /// /// - Parameter userId: The user ID associated with the unsuccessful unlock attempts. /// if `nil` defaults to currently active user. /// func setUnsuccessfulUnlockAttempts(_ attempts: Int, userId: String?) async throws /// Sets whether the user has a master password. /// /// - Parameter hasMasterPassword: Whether the user has a master password. /// func setUserHasMasterPassword(_ hasMasterPassword: Bool) async throws /// Sets the username generation options for a user ID. /// /// - Parameters: /// - options: The user's username generation options. /// - userId: The user ID associated with the username generation options. /// func setUsernameGenerationOptions(_ options: UsernameGenerationOptions?, userId: String?) async throws /// Sets whether the user uses key connector. /// /// - Parameters: /// - usesKeyConnector: Whether the user uses key connector. /// - userId: The user ID to set whether they use key connector. /// func setUsesKeyConnector(_ usesKeyConnector: Bool, userId: String?) async throws /// Sets the session timeout value. /// /// - Parameters: /// - value: The value that dictates how many seconds in the future a timeout should occur. /// - userId: The user ID associated with the timeout value. /// func setVaultTimeout(value: SessionTimeoutValue, userId: String?) async throws /// Updates the profile information for a user. /// /// - Parameters: /// - response: The profile response information to use while updating. /// - userId: The id of the user this updated information belongs to. /// func updateProfile(from response: ProfileResponseModel, userId: String) async // MARK: Publishers /// A publisher for the active account id /// /// - Returns: The userId `String` of the active account /// func activeAccountIdPublisher() async -> AnyPublisher /// A publisher for the app theme. /// /// - Returns: A publisher for the app theme. /// func appThemePublisher() async -> AnyPublisher /// A publisher for the connect to watch value. /// /// - Returns: A publisher for the connect to watch value. /// func connectToWatchPublisher() async -> AnyPublisher<(String?, Bool), Never> /// A publisher for the last sync time for the active account. /// /// - Returns: A publisher for the last sync time. /// func lastSyncTimePublisher() async throws -> AnyPublisher /// A publisher for the pending App Intent actions. /// /// - Returns: A publisher for the pending App Intent actions. /// func pendingAppIntentActionsPublisher() async -> AnyPublisher<[PendingAppIntentAction]?, Never> /// A publisher for showing badges in the settings tab. /// /// - Returns: A publisher for showing badges in the settings tab. /// func settingsBadgePublisher() async throws -> AnyPublisher /// A publisher for whether or not to show the web icons. /// /// - Returns: A publisher for whether or not to show the web icons. /// func showWebIconsPublisher() async -> AnyPublisher /// A publisher for the sync to authenticator value. /// /// - Returns: A publisher for the sync to authenticator value. /// func syncToAuthenticatorPublisher() async -> AnyPublisher<(String?, Bool), Never> } extension StateService { /// Appends the `action` to the current pending `AppIntent` actions. func addPendingAppIntentAction(_ action: PendingAppIntentAction) async { var actions = await getPendingAppIntentActions() ?? [] guard !actions.contains(action) else { return } actions.append(action) await setPendingAppIntentActions(actions: actions) } /// Gets the access token's expiration date for the active account. /// /// - Returns: The user's access token expiration date. /// func getAccessTokenExpirationDate() async throws -> Date? { try await getAccessTokenExpirationDate(userId: getActiveAccountId()) } /// Gets the account encryptions keys for the active account. /// /// - Returns: The account encryption keys. /// func getAccountEncryptionKeys() async throws -> AccountEncryptionKeys { try await getAccountEncryptionKeys(userId: nil) } /// Gets whether the user has unlocked their account in the current session interactively. func getAccountHasBeenUnlockedInteractively() async throws -> Bool { try await getAccountHasBeenUnlockedInteractively(userId: nil) } /// Gets either a valid account id or the active account id. /// /// - Parameter userId: The possible user id. /// If `nil`, this method will attempt to return the active account id. /// If non-nil, this method will validate the user id. /// - Returns: A valid user id. /// func getAccountIdOrActiveId(userId: String?) async throws -> String { try await getAccount(userId: userId).profile.userId } /// Gets the active user's progress for setting up autofill. /// /// - Returns: The user's autofill setup progress. /// func getAccountSetupAutofill() async throws -> AccountSetupProgress? { try await getAccountSetupAutofill(userId: getActiveAccountId()) } /// Gets the active user's progress for importing logins. /// /// - Returns: The user's import logins setup progress. /// func getAccountSetupImportLogins() async throws -> AccountSetupProgress? { try await getAccountSetupImportLogins(userId: getActiveAccountId()) } /// Gets the active user's progress for setting up vault unlock. /// /// - Returns: The user's vault unlock setup progress. /// func getAccountSetupVaultUnlock() async throws -> AccountSetupProgress? { try await getAccountSetupVaultUnlock(userId: getActiveAccountId()) } /// Gets the active account id. /// /// - Returns: The active user id. /// func getActiveAccountId() async throws -> String { try await getActiveAccount().profile.userId } /// Gets the active account. /// /// - Returns: The active user account. /// func getActiveAccount() async throws -> Account { do { return try await getAccount(userId: nil) } catch { throw StateServiceError.noActiveAccount } } /// Gets the allow sync on refresh value for the active account. /// /// - Returns: The allow sync on refresh value. /// func getAllowSyncOnRefresh() async throws -> Bool { try await getAllowSyncOnRefresh(userId: nil) } /// Gets the Universal Clipboard setting for the active account. /// /// - Returns: A Boolean value indicating whether Universal Clipboard is allowed. /// func getAllowUniversalClipboard() async throws -> Bool { try await getAllowUniversalClipboard(userId: nil) } /// Gets the app rehydration state for the active account. /// - Returns: The rehydration state. func getAppRehydrationState() async throws -> AppRehydrationState? { try await getAppRehydrationState(userId: nil) } /// Gets the clear clipboard value for the active account. /// /// - Returns: The clear clipboard value. /// func getClearClipboardValue() async throws -> ClearClipboardValue { try await getClearClipboardValue(userId: nil) } /// Gets the connect to watch value for the active account. /// /// - Returns: Whether to connect to the watch app. /// func getConnectToWatch() async throws -> Bool { try await getConnectToWatch(userId: nil) } /// Gets the default URI match type value for the active account. /// /// - Returns: The default URI match type value. /// func getDefaultUriMatchType() async -> UriMatchType { await getDefaultUriMatchType(userId: nil) } /// Gets the disable auto-copy TOTP value for the active account. /// /// - Returns: The disable auto-copy TOTP value. /// func getDisableAutoTotpCopy() async throws -> Bool { try await getDisableAutoTotpCopy(userId: nil) } /// The user's pin protected by their user key. /// /// - Returns: The user's pin protected by their user key. /// func getEncryptedPin() async throws -> String? { try await getEncryptedPin(userId: nil) } /// Gets the environment URLs for the active account. /// /// - Returns: The environment URLs for the active account. /// func getEnvironmentURLs() async throws -> EnvironmentURLData? { try await getEnvironmentURLs(userId: nil) } /// Gets whether a sync has been done successfully after login for the current user. /// This is particular useful to trigger logic that needs to be executed right after login in /// and after the first successful sync. /// /// - Returns: `true` if sync has already been done after login, `false` otherwise. /// func getHasPerformedSyncAfterLogin() async throws -> Bool { try await getHasPerformedSyncAfterLogin(userId: nil) } /// Gets the user's last active time within the app. /// This value is set when the app is backgrounded. /// /// - Returns: The date of the last active time. /// func getLastActiveTime() async throws -> Date? { try await getLastActiveTime(userId: nil) } /// Gets the time of the last sync for a user. /// /// - Parameter userId: The user ID associated with the last sync time. /// - Returns: The user's last sync time. /// func getLastSyncTime() async throws -> Date? { try await getLastSyncTime(userId: nil) } /// Gets the master password hash for the active account. /// /// - Returns: The user's master password hash. /// func getMasterPasswordHash() async throws -> String? { try await getMasterPasswordHash(userId: nil) } /// Gets the last notifications registration date for the active account. /// /// - Returns: The last notifications registration date for the active account. /// func getNotificationsLastRegistrationDate() async throws -> Date? { try await getNotificationsLastRegistrationDate(userId: nil) } /// Gets the password generation options for the active account. /// /// - Returns: The password generation options for the user ID. /// func getPasswordGenerationOptions() async throws -> PasswordGenerationOptions? { try await getPasswordGenerationOptions(userId: nil) } /// Gets whether Siri & Shortcuts access is enabled for the active account. /// - Returns: Whether Siri & Shortcuts access is enabled. func getSiriAndShortcutsAccess() async throws -> Bool { try await getSiriAndShortcutsAccess(userId: nil) } /// Gets the sync to authenticator value for the active account. /// /// - Returns: Whether to sync TOTP codes to the Authenticator app. /// func getSyncToAuthenticator() async throws -> Bool { try await getSyncToAuthenticator(userId: nil) } /// Gets the session timeout action. /// /// - Returns: The action to perform when a session timeout occurs. /// func getTimeoutAction() async throws -> SessionTimeoutAction { try await getTimeoutAction(userId: nil) } /// Sets the number of unsuccessful attempts to unlock the vault for the active account. /// /// - Returns: The number of unsuccessful unlock attempts for the active account. /// func getUnsuccessfulUnlockAttempts() async -> Int { if let attempts = try? await getUnsuccessfulUnlockAttempts(userId: nil) { return attempts } return 0 } /// Gets whether a user has a master password. /// /// - Returns: Whether the user has a master password. /// func getUserHasMasterPassword() async throws -> Bool { try await getUserHasMasterPassword(userId: nil) } /// Gets the username generation options for the active account. /// /// - Returns: The username generation options for the user ID. /// func getUsernameGenerationOptions() async throws -> UsernameGenerationOptions? { try await getUsernameGenerationOptions(userId: nil) } /// Gets whether the user uses key connector. /// /// - Returns: Whether the user uses key connector. /// func getUsesKeyConnector() async throws -> Bool { try await getUsesKeyConnector(userId: nil) } /// Gets the session timeout value. /// /// - Returns: The session timeout value. /// func getVaultTimeout() async throws -> SessionTimeoutValue { try await getVaultTimeout(userId: nil) } /// Whether the active user account is authenticated. /// /// - Returns: Whether the user is authenticated. /// func isAuthenticated() async throws -> Bool { try await isAuthenticated(userId: nil) } /// Logs the user out of the active account. /// /// - Parameters userInitiated: Whether the logout was user initiated or a result of a logout /// timeout action. /// func logoutAccount(userInitiated: Bool) async throws { try await logoutAccount(userId: nil, userInitiated: userInitiated) } /// The pin protected user key. /// /// - Returns: The pin protected user key. /// func pinProtectedUserKey() async throws -> String? { try await pinProtectedUserKey(userId: nil) } /// The pin protected user key envelope. /// /// - Returns: The pin protected user key envelope. /// func pinProtectedUserKeyEnvelope() async throws -> String? { try await pinProtectedUserKeyEnvelope(userId: nil) } /// Sets the access token's expiration date for the active account. /// /// - Parameter expirationDate: The user's access token expiration date. /// func setAccessTokenExpirationDate(_ expirationDate: Date?) async throws { try await setAccessTokenExpirationDate(expirationDate, userId: getActiveAccountId()) } /// Sets the account encryption keys for the active account. /// /// - Parameter encryptionKeys: The account encryption keys. /// func setAccountEncryptionKeys(_ encryptionKeys: AccountEncryptionKeys) async throws { try await setAccountEncryptionKeys(encryptionKeys, userId: nil) } /// Sets whether the user has unlocked their account in the current session interactively. /// - Parameter value: Whether the user has unlocked their account in the current session func setAccountHasBeenUnlockedInteractively(value: Bool) async throws { try await setAccountHasBeenUnlockedInteractively(userId: nil, value: value) } /// Sets the KDF config for the active account. /// /// - Parameter kdfConfig: The account's KDF config. /// func setAccountKdf(_ kdfConfig: KdfConfig) async throws { try await setAccountKdf(kdfConfig, userId: getActiveAccountId()) } /// Sets the master password unlock data for the active account. /// /// - Parameter masterPasswordUnlock: The account master password unlock data. /// func setAccountMasterPasswordUnlock(_ masterPasswordUnlock: MasterPasswordUnlockResponseModel) async throws { let userId = try await getActiveAccountId() await setAccountMasterPasswordUnlock(masterPasswordUnlock, userId: userId) } /// Sets the active user's progress for setting up autofill. /// /// - Parameter autofillSetup: The user's autofill setup progress. /// func setAccountSetupAutofill(_ autofillSetup: AccountSetupProgress?) async throws { try await setAccountSetupAutofill(autofillSetup, userId: nil) } /// Sets the active user's progress for importing logins. /// /// - Parameter importLogins: The user's import logins progress. /// func setAccountSetupImportLogins(_ importLogins: AccountSetupProgress?) async throws { try await setAccountSetupImportLogins(importLogins, userId: nil) } /// Sets the active user's progress for setting up vault unlock. /// /// - Parameter vaultUnlockSetup: The user's vault unlock setup progress. /// func setAccountSetupVaultUnlock(_ vaultUnlockSetup: AccountSetupProgress?) async throws { try await setAccountSetupVaultUnlock(vaultUnlockSetup, userId: nil) } /// Sets the allow sync on refresh value for the active account. /// /// - Parameter allowSyncOnRefresh: The allow sync on refresh value. /// func setAllowSyncOnRefresh(_ allowSyncOnRefresh: Bool) async throws { try await setAllowSyncOnRefresh(allowSyncOnRefresh, userId: nil) } /// Sets the Universal Clipboard setting for the active account. /// /// - Parameter allowUniversalClipboard: A Boolean value indicating whether Universal Clipboard should be allowed. /// func setAllowUniversalClipboard(_ allowUniversalClipboard: Bool) async throws { try await setAllowUniversalClipboard(allowUniversalClipboard, userId: nil) } /// Sets the clear clipboard value for the active account. /// /// - Parameter clearClipboardValue: The time after which to clear the clipboard. /// func setClearClipboardValue(_ clearClipboardValue: ClearClipboardValue?) async throws { try await setClearClipboardValue(clearClipboardValue, userId: nil) } /// Sets the connect to watch value for the active account. /// /// - Parameter connectToWatch: Whether to connect to the watch app. /// func setConnectToWatch(_ connectToWatch: Bool) async throws { try await setConnectToWatch(connectToWatch, userId: nil) } /// Sets the default URI match type value the active account. /// /// - Parameter defaultUriMatchType: The default URI match type. /// func setDefaultUriMatchType(_ defaultUriMatchType: UriMatchType?) async throws { try await setDefaultUriMatchType(defaultUriMatchType, userId: nil) } /// Sets the disable auto-copy TOTP value for an account. /// /// - Parameter disableAutoTotpCopy: Whether the TOTP for a cipher should be auto-copied. /// func setDisableAutoTotpCopy(_ disableAutoTotpCopy: Bool) async throws { try await setDisableAutoTotpCopy(disableAutoTotpCopy, userId: nil) } /// Sets the force password reset reason for the active account. /// /// - Parameter reason: The reason why a password reset is required. /// func setForcePasswordResetReason(_ reason: ForcePasswordResetReason?) async throws { try await setForcePasswordResetReason(reason, userId: nil) } /// Sets whether a sync has been done successfully after login for the current user. /// This is particular useful to trigger logic that needs to be executed right after login in /// and after the first successful sync. /// /// - Parameters: /// - hasBeenPerformed: Whether a sync has been performed after login. func setHasPerformedSyncAfterLogin(_ hasBeenPerformed: Bool) async throws { try await setHasPerformedSyncAfterLogin(hasBeenPerformed, userId: nil) } /// Sets the last active time within the app. /// /// - Parameter date: The current time. /// func setLastActiveTime(_ date: Date?) async throws { try await setLastActiveTime(date, userId: nil) } /// Sets the time of the last sync for a user ID. /// /// - Parameter date: The time of the last sync (as the number of seconds since the Unix epoch).] /// func setLastSyncTime(_ date: Date?) async throws { try await setLastSyncTime(date, userId: nil) } /// Sets the master password hash for the active account. /// /// - Parameter hash: The user's master password hash. /// func setMasterPasswordHash(_ hash: String?) async throws { try await setMasterPasswordHash(hash, userId: nil) } /// Sets the last notifications registration date for the active account. /// /// - Parameter date: The last notifications registration date. /// func setNotificationsLastRegistrationDate(_ date: Date?) async throws { try await setNotificationsLastRegistrationDate(date, userId: nil) } /// Sets the password generation options for the active account. /// /// - Parameter options: The user's password generation options. /// func setPasswordGenerationOptions(_ options: PasswordGenerationOptions?) async throws { try await setPasswordGenerationOptions(options, userId: nil) } /// Sets the app rehydration state for the active account. /// /// - Parameter rehydrationState: The app rehydration state. /// func setAppRehydrationState(_ rehydrationState: AppRehydrationState?) async throws { try await setAppRehydrationState(rehydrationState, userId: nil) } /// Set whether to allow access to Siri & Shortcuts using `AppIntent` for the active account. /// /// - Parameters: /// - siriAndShortcutsAccess: Whether access is enabled. func setSiriAndShortcutsAccess(_ siriAndShortcutsAccess: Bool) async throws { try await setSiriAndShortcutsAccess(siriAndShortcutsAccess, userId: nil) } /// Sets the sync to authenticator value for the active account. /// /// - Parameter syncToAuthenticator: Whether to sync TOTP codes to the Authenticator app. /// func setSyncToAuthenticator(_ syncToAuthenticator: Bool) async throws { try await setSyncToAuthenticator(syncToAuthenticator, userId: nil) } /// Sets the session timeout action. /// /// - Parameter action: The action to take when the user's session times out. /// func setTimeoutAction(action: SessionTimeoutAction) async throws { try await setTimeoutAction(action: action, userId: nil) } /// Sets the number of unsuccessful attempts to unlock the vault for the active account. /// /// - Parameter attempts: The number of unsuccessful unlock attempts. /// func setUnsuccessfulUnlockAttempts(_ attempts: Int) async { try? await setUnsuccessfulUnlockAttempts(attempts, userId: nil) } /// Sets the username generation options for the active account. /// /// - Parameter options: The user's username generation options. /// func setUsernameGenerationOptions(_ options: UsernameGenerationOptions?) async throws { try await setUsernameGenerationOptions(options, userId: nil) } /// Sets whether the user uses key connector. /// /// - Parameter usesKeyConnector: Whether the user uses key connector. /// func setUsesKeyConnector(_ usesKeyConnector: Bool) async throws { try await setUsesKeyConnector(usesKeyConnector, userId: nil) } /// Sets the session timeout value. /// /// - Parameter value: The value that dictates how many seconds in the future a timeout should occur. /// func setVaultTimeout(value: SessionTimeoutValue) async throws { try await setVaultTimeout(value: value, userId: nil) } } // MARK: - StateServiceError /// The errors thrown from a `StateService`. /// enum StateServiceError: LocalizedError { /// There are no known accounts. case noAccounts /// There isn't an account with the specified user ID. case noAccountForUserId /// There isn't an active account. case noActiveAccount /// The user has no private key. case noEncryptedPrivateKey /// The user has no pin protected user key. case noPinProtectedUserKey /// The user has no user key. case noEncUserKey var errorDescription: String? { switch self { case .noActiveAccount: Localizations.noAccountFoundPleaseLogInAgainIfYouContinueToSeeThisError default: nil } } } // MARK: - DefaultStateService /// A default implementation of `StateService`. /// actor DefaultStateService: StateService, ConfigStateService { // swiftlint:disable:this type_body_length // MARK: Properties /// The language option currently selected for the app. nonisolated var appLanguage: LanguageOption { get { LanguageOption(appSettingsStore.appLocale) } set { appSettingsStore.appLocale = newValue.value } } /// The organization identifier being remembered on the single-sign on screen. nonisolated var rememberedOrgIdentifier: String? { get { appSettingsStore.rememberedOrgIdentifier } set { appSettingsStore.rememberedOrgIdentifier = newValue } } // MARK: Private Properties /// The data stored in memory. var accountVolatileData: [String: AccountVolatileData] = [:] /// The service that persists app settings. let appSettingsStore: AppSettingsStore /// A subject containing the app theme. private var appThemeSubject: CurrentValueSubject /// A subject containing the connect to watch value. private var connectToWatchByUserIdSubject = CurrentValueSubject<[String: Bool], Never>([:]) /// The data store that handles performing data requests. private let dataStore: DataStore /// The service used by the application to report non-fatal errors. private let errorReporter: ErrorReporter /// A subject containing the last sync time mapped to user ID. private var lastSyncTimeByUserIdSubject = CurrentValueSubject<[String: Date], Never>([:]) /// A service used to access data in the keychain. private let keychainRepository: KeychainRepository /// A subject containing the pending App Intent actions. private var pendingAppIntentActionsSubject = CurrentValueSubject<[PendingAppIntentAction]?, Never>(nil) /// A subject containing the settings badge value mapped to user ID. private let settingsBadgeByUserIdSubject = CurrentValueSubject<[String: SettingsBadgeState], Never>([:]) /// A subject containing whether to show the website icons. private var showWebIconsSubject: CurrentValueSubject /// A subject containing the sync to authenticator value. private var syncToAuthenticatorByUserIdSubject = CurrentValueSubject<[String: Bool], Never>([:]) // MARK: Initialization /// Initialize a `DefaultStateService`. /// /// - Parameters: /// - appSettingsStore: The service that persists app settings. /// - dataStore: The data store that handles performing data requests. /// - errorReporter: The service used by the application to report non-fatal errors. /// - keychainRepository: A service used to access data in the keychain. /// init( appSettingsStore: AppSettingsStore, dataStore: DataStore, errorReporter: ErrorReporter, keychainRepository: KeychainRepository, ) { self.appSettingsStore = appSettingsStore self.dataStore = dataStore self.errorReporter = errorReporter self.keychainRepository = keychainRepository appThemeSubject = CurrentValueSubject(AppTheme(appSettingsStore.appTheme)) showWebIconsSubject = CurrentValueSubject(!appSettingsStore.disableWebIcons) Task { for await activeUserId in await self.appSettingsStore.activeAccountIdPublisher().values { errorReporter.setUserId(activeUserId) } } } // MARK: Methods func addAccount(_ account: Account) async { var state = appSettingsStore.state ?? State() defer { appSettingsStore.state = state } state.accounts[account.profile.userId] = account state.activeUserId = account.profile.userId } func clearPins() async throws { let userId = try getActiveAccountUserId() accountVolatileData[userId]?.pinProtectedUserKey = nil appSettingsStore.setEncryptedPin(nil, userId: userId) appSettingsStore.setPinProtectedUserKey(key: nil, userId: userId) appSettingsStore.setPinProtectedUserKeyEnvelope(key: nil, userId: userId) } func deleteAccount() async throws { try await logoutAccount(userInitiated: true) } func didAccountSwitchInExtension() async throws -> Bool { do { return try getActiveAccountUserId() != appSettingsStore.cachedActiveUserId } catch StateServiceError.noActiveAccount { let cachedActiveUserId = appSettingsStore.cachedActiveUserId // If the user was logged out in the extension, but there's a cached active user, // reset the state to update the cached active user. appSettingsStore.state = appSettingsStore.state return cachedActiveUserId != nil } } func doesActiveAccountHavePremium() async -> Bool { do { let account = try await getActiveAccount() let hasPremiumPersonally = account.profile.hasPremiumPersonally ?? false guard !hasPremiumPersonally else { return true } let organizations = try await dataStore .fetchAllOrganizations(userId: account.profile.userId) .filter { $0.enabled && $0.usersGetPremium } return !organizations.isEmpty } catch { errorReporter.log(error: error) return false } } func getAccessTokenExpirationDate(userId: String) -> Date? { appSettingsStore.accessTokenExpirationDate(userId: userId) } func getAccount(userId: String?) throws -> Account { guard let accounts = appSettingsStore.state?.accounts else { throw StateServiceError.noAccounts } let userId = try userId ?? getActiveAccountUserId() guard let account = accounts .first(where: { $0.value.profile.userId == userId })?.value else { throw StateServiceError.noAccounts } return account } func getAccountEncryptionKeys(userId: String?) async throws -> AccountEncryptionKeys { let userId = try userId ?? getActiveAccountUserId() guard let encryptedPrivateKey = appSettingsStore.encryptedPrivateKey(userId: userId) else { throw StateServiceError.noEncryptedPrivateKey } return AccountEncryptionKeys( accountKeys: appSettingsStore.accountKeys(userId: userId), encryptedPrivateKey: encryptedPrivateKey, encryptedUserKey: appSettingsStore.encryptedUserKey(userId: userId), ) } func getAccountHasBeenUnlockedInteractively(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return accountVolatileData[userId]?.hasBeenUnlockedInteractively == true } func getAccountSetupAutofill(userId: String) async -> AccountSetupProgress? { appSettingsStore.accountSetupAutofill(userId: userId) } func getAccountSetupImportLogins(userId: String) async -> AccountSetupProgress? { appSettingsStore.accountSetupImportLogins(userId: userId) } func getAccountSetupVaultUnlock(userId: String) async -> AccountSetupProgress? { appSettingsStore.accountSetupVaultUnlock(userId: userId) } func getAccounts() throws -> [Account] { guard let accounts = appSettingsStore.state?.accounts else { throw StateServiceError.noAccounts } return Array(accounts.values) } func getActiveAccount() throws -> Account { guard let activeAccount = appSettingsStore.state?.activeAccount else { throw StateServiceError.noActiveAccount } return activeAccount } func getAddSitePromptShown() async -> Bool { appSettingsStore.addSitePromptShown } func getAllowSyncOnRefresh(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.allowSyncOnRefresh(userId: userId) } func getAllowUniversalClipboard(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.allowUniversalClipboard(userId: userId) } func getAppRehydrationState(userId: String?) async throws -> AppRehydrationState? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.appRehydrationState(userId: userId) } func getAppTheme() async -> AppTheme { AppTheme(appSettingsStore.appTheme) } func getClearClipboardValue(userId: String?) async throws -> ClearClipboardValue { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.clearClipboardValue(userId: userId) } func getConnectToWatch(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.connectToWatch(userId: userId) } func getDefaultUriMatchType(userId: String?) async -> UriMatchType { do { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.defaultUriMatchType(userId: userId) ?? .domain } catch { errorReporter.log(error: error) return .domain } } func getDisableAutoTotpCopy(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.disableAutoTotpCopy(userId: userId) } func getEncryptedPin(userId: String?) async throws -> String? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.encryptedPin(userId: userId) } func getEnvironmentURLs(userId: String?) async throws -> EnvironmentURLData? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.state?.accounts[userId]?.settings.environmentUrls } func getEvents(userId: String?) async throws -> [EventData] { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.events(userId: userId) } func getFlightRecorderData() async -> FlightRecorderData? { appSettingsStore.flightRecorderData } func getHasPerformedSyncAfterLogin(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.hasPerformedSyncAfterLogin(userId: userId) } func getIntroCarouselShown() async -> Bool { appSettingsStore.introCarouselShown } func getLastActiveTime(userId: String?) async throws -> Date? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.lastActiveTime(userId: userId) } func getLastSyncTime(userId: String?) async throws -> Date? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.lastSyncTime(userId: userId) } func getLastUserShouldConnectToWatch() async -> Bool { appSettingsStore.lastUserShouldConnectToWatch } func getLoginRequest() async -> LoginRequestNotification? { appSettingsStore.loginRequest } func getManuallyLockedAccount(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.manuallyLockedAccount(userId: userId) } func getMasterPasswordHash(userId: String?) async throws -> String? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.masterPasswordHash(userId: userId) } func getNotificationsLastRegistrationDate(userId: String?) async throws -> Date? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.notificationsLastRegistrationDate(userId: userId) } func getPasswordGenerationOptions(userId: String?) async throws -> PasswordGenerationOptions? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.passwordGenerationOptions(userId: userId) } func getPendingAppIntentActions() async -> [PendingAppIntentAction]? { appSettingsStore.pendingAppIntentActions } func getPreAuthEnvironmentURLs() async -> EnvironmentURLData? { appSettingsStore.preAuthEnvironmentURLs } func getAccountCreationEnvironmentURLs(email: String) async -> EnvironmentURLData? { appSettingsStore.accountCreationEnvironmentURLs(email: email) } func getLearnGeneratorActionCardStatus() async -> AccountSetupProgress? { appSettingsStore.learnGeneratorActionCardStatus } func getPreAuthServerConfig() async -> ServerConfig? { appSettingsStore.preAuthServerConfig } func getReviewPromptData() async -> ReviewPromptData? { appSettingsStore.reviewPromptData } func getServerConfig(userId: String?) async throws -> ServerConfig? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.serverConfig(userId: userId) } func getShouldTrustDevice(userId: String) async -> Bool? { appSettingsStore.shouldTrustDevice(userId: userId) } func getLearnNewLoginActionCardStatus() async -> AccountSetupProgress? { appSettingsStore.learnNewLoginActionCardStatus } func getShowWebIcons() async -> Bool { !appSettingsStore.disableWebIcons } func getSiriAndShortcutsAccess(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.siriAndShortcutsAccess(userId: userId) } func getSyncToAuthenticator(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.syncToAuthenticator(userId: userId) } func getTimeoutAction(userId: String?) async throws -> SessionTimeoutAction { let userId = try userId ?? getActiveAccountUserId() guard let rawValue = appSettingsStore.timeoutAction(userId: userId), let timeoutAction = SessionTimeoutAction(rawValue: rawValue) else { return .lock } return timeoutAction } func getTwoFactorToken(email: String) async -> String? { appSettingsStore.twoFactorToken(email: email) } func getUnsuccessfulUnlockAttempts(userId: String?) async throws -> Int { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.unsuccessfulUnlockAttempts(userId: userId) } func getUserHasMasterPassword(userId: String?) async throws -> Bool { try getAccount(userId: userId).profile.userDecryptionOptions?.hasMasterPassword ?? true } func getUserIds(email: String) async -> [String] { guard let state = appSettingsStore.state else { return [] } let userIds = state.accounts.values.filter { $0.profile.email == email }.map(\.profile.userId) return userIds } func getUsernameGenerationOptions(userId: String?) async throws -> UsernameGenerationOptions? { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.usernameGenerationOptions(userId: userId) } func getUsesKeyConnector(userId: String?) async throws -> Bool { let userId = try userId ?? getActiveAccountUserId() return appSettingsStore.usesKeyConnector(userId: userId) } func getVaultTimeout(userId: String?) async throws -> SessionTimeoutValue { let userId = try getAccount(userId: userId).profile.userId let userAuthKey = try? await keychainRepository.getUserAuthKeyValue(for: .neverLock(userId: userId)) guard let rawValue = appSettingsStore.vaultTimeout(userId: userId) else { // If there isn't a stored value, it may be because MAUI stored `nil` for never timeout. // So if the never lock key exists, set the timeout to never, otherwise to default. return userAuthKey != nil ? .never : .fifteenMinutes } let timeoutValue = SessionTimeoutValue(rawValue: rawValue) if timeoutValue == .never, userAuthKey == nil { // If never lock but no key (possibly due to logging out), return the default timeout. return .fifteenMinutes } return timeoutValue } func isAuthenticated(userId: String?) async throws -> Bool { do { let userId = try getAccount(userId: userId).profile.userId _ = try await keychainRepository.getAccessToken(userId: userId) return true } catch StateServiceError.noActiveAccount { return false } catch StateServiceError.noAccounts { return false } catch KeychainServiceError.osStatusError(errSecItemNotFound) { return false } } func logoutAccount(userId: String?, userInitiated: Bool) async throws { guard var state = appSettingsStore.state else { return } defer { appSettingsStore.state = state } let knownUserId: String = try userId ?? getActiveAccountUserId() if userInitiated { state.accounts.removeValue(forKey: knownUserId) } if state.activeUserId == knownUserId, userInitiated { // Find the next account to make the active account. state.activeUserId = state.accounts.first?.key } appSettingsStore.setAccessTokenExpirationDate(nil, userId: knownUserId) appSettingsStore.setBiometricAuthenticationEnabled(nil, for: knownUserId) appSettingsStore.setDefaultUriMatchType(nil, userId: knownUserId) appSettingsStore.setDisableAutoTotpCopy(nil, userId: knownUserId) appSettingsStore.setAccountKeys(nil, userId: knownUserId) appSettingsStore.setEncryptedPrivateKey(key: nil, userId: knownUserId) appSettingsStore.setEncryptedUserKey(key: nil, userId: knownUserId) appSettingsStore.setHasPerformedSyncAfterLogin(nil, userId: knownUserId) appSettingsStore.setLastSyncTime(nil, userId: knownUserId) appSettingsStore.setMasterPasswordHash(nil, userId: knownUserId) appSettingsStore.setPasswordGenerationOptions(nil, userId: knownUserId) try await dataStore.deleteDataForUser(userId: knownUserId) } func pinProtectedUserKey(userId: String?) async throws -> String? { let userId = try userId ?? getActiveAccountUserId() return accountVolatileData[userId]?.pinProtectedUserKey ?? appSettingsStore.pinProtectedUserKey(userId: userId) } func pinProtectedUserKeyEnvelope(userId: String?) async throws -> String? { let userId = try userId ?? getActiveAccountUserId() let key = accountVolatileData[userId]?.pinProtectedUserKey ?? appSettingsStore.pinProtectedUserKeyEnvelope(userId: userId) return key } func pinUnlockRequiresPasswordAfterRestart() async throws -> Bool { let userId = try getActiveAccountUserId() return appSettingsStore.pinProtectedUserKeyEnvelope(userId: userId) == nil && appSettingsStore.pinProtectedUserKey(userId: userId) == nil } func setAccessTokenExpirationDate(_ expirationDate: Date?, userId: String) async { appSettingsStore.setAccessTokenExpirationDate(expirationDate, userId: userId) } func setAccountKdf(_ kdfConfig: KdfConfig, userId: String) async throws { try updateAccountProfile(userId: userId) { profile in profile.kdfType = kdfConfig.kdfType profile.kdfIterations = kdfConfig.iterations profile.kdfMemory = kdfConfig.memory profile.kdfParallelism = kdfConfig.parallelism } } func setAccountEncryptionKeys(_ encryptionKeys: AccountEncryptionKeys, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setAccountKeys(encryptionKeys.accountKeys, userId: userId) appSettingsStore.setEncryptedPrivateKey(key: encryptionKeys.encryptedPrivateKey, userId: userId) appSettingsStore.setEncryptedUserKey(key: encryptionKeys.encryptedUserKey, userId: userId) } func setAccountHasBeenUnlockedInteractively(userId: String?, value: Bool) async throws { let userId = try userId ?? getActiveAccountUserId() accountVolatileData[ userId, default: AccountVolatileData(), ].hasBeenUnlockedInteractively = value } func setAccountMasterPasswordUnlock( _ masterPasswordUnlock: MasterPasswordUnlockResponseModel, userId: String, ) async { guard var state = appSettingsStore.state, var profile = state.accounts[userId]?.profile else { return } var userDecryptionOptions = profile.userDecryptionOptions ?? UserDecryptionOptions( hasMasterPassword: true, keyConnectorOption: nil, trustedDeviceOption: nil, ) userDecryptionOptions.masterPasswordUnlock = masterPasswordUnlock profile.userDecryptionOptions = userDecryptionOptions state.accounts[userId]?.profile = profile appSettingsStore.state = state } func setAccountSetupAutofill(_ autofillSetup: AccountSetupProgress?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setAccountSetupAutofill(autofillSetup, userId: userId) await updateSettingsBadgePublisher(userId: userId) } func setAccountSetupImportLogins(_ importLogins: AccountSetupProgress?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setAccountSetupImportLogins(importLogins, userId: userId) await updateSettingsBadgePublisher(userId: userId) } func setAccountSetupVaultUnlock(_ vaultUnlockSetup: AccountSetupProgress?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setAccountSetupVaultUnlock(vaultUnlockSetup, userId: userId) await updateSettingsBadgePublisher(userId: userId) } func setActiveAccount(userId: String) async throws { guard var state = appSettingsStore.state else { return } defer { appSettingsStore.state = state } guard state.accounts.contains(where: { $0.key == userId }) else { throw StateServiceError.noAccounts } state.activeUserId = userId } func setAddSitePromptShown(_ shown: Bool) async { appSettingsStore.addSitePromptShown = shown } func setAllowSyncOnRefresh(_ allowSyncOnRefresh: Bool, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setAllowSyncOnRefresh(allowSyncOnRefresh, userId: userId) } func setAllowUniversalClipboard(_ allowUniversalClipboard: Bool, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setAllowUniversalClipboard(allowUniversalClipboard, userId: userId) } func setAppTheme(_ appTheme: AppTheme) async { appSettingsStore.appTheme = appTheme.value appThemeSubject.send(appTheme) } func setClearClipboardValue(_ clearClipboardValue: ClearClipboardValue?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setClearClipboardValue(clearClipboardValue, userId: userId) } func setConnectToWatch(_ connectToWatch: Bool, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setConnectToWatch(connectToWatch, userId: userId) connectToWatchByUserIdSubject.value[userId] = connectToWatch // Save the value of the connect to watch setting independent of the user id, // in order to be able to send a status to the watch if the user logs out. appSettingsStore.lastUserShouldConnectToWatch = connectToWatch } func setDefaultUriMatchType(_ defaultUriMatchType: UriMatchType?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setDefaultUriMatchType(defaultUriMatchType, userId: userId) } func setDisableAutoTotpCopy(_ disableAutoTotpCopy: Bool, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setDisableAutoTotpCopy(disableAutoTotpCopy, userId: userId) } func setEvents(_ events: [EventData], userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setEvents(events, userId: userId) } func setFlightRecorderData(_ data: FlightRecorderData?) async { appSettingsStore.flightRecorderData = data } func setForcePasswordResetReason(_ reason: ForcePasswordResetReason?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() guard var state = appSettingsStore.state else { throw StateServiceError.noAccounts } defer { appSettingsStore.state = state } state.accounts[userId]?.profile.forcePasswordResetReason = reason } func setHasPerformedSyncAfterLogin(_ hasBeenPerformed: Bool, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setHasPerformedSyncAfterLogin(hasBeenPerformed, userId: userId) } func setIntroCarouselShown(_ shown: Bool) async { appSettingsStore.introCarouselShown = shown } func setLearnNewLoginActionCardStatus(_ status: AccountSetupProgress) async { appSettingsStore.learnNewLoginActionCardStatus = status } func setLastActiveTime(_ date: Date?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setLastActiveTime(date, userId: userId) } func setLastSyncTime(_ date: Date?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setLastSyncTime(date, userId: userId) lastSyncTimeByUserIdSubject.value[userId] = date } func setLearnGeneratorActionCardStatus(_ status: AccountSetupProgress) async { appSettingsStore.learnGeneratorActionCardStatus = status } func setLoginRequest(_ loginRequest: LoginRequestNotification?) async { appSettingsStore.loginRequest = loginRequest } func setManuallyLockedAccount(_ isLocked: Bool, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setManuallyLockedAccount(isLocked, userId: userId) } func setMasterPasswordHash(_ hash: String?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setMasterPasswordHash(hash, userId: userId) } func setNotificationsLastRegistrationDate(_ date: Date?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setNotificationsLastRegistrationDate(date, userId: userId) } func setPasswordGenerationOptions(_ options: PasswordGenerationOptions?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setPasswordGenerationOptions(options, userId: userId) } func setPendingAppIntentActions(actions: [PendingAppIntentAction]?) async { guard !actions.isEmptyOrNil else { appSettingsStore.pendingAppIntentActions = nil pendingAppIntentActionsSubject.send(nil) return } appSettingsStore.pendingAppIntentActions = actions pendingAppIntentActionsSubject.send(actions) } func setPinKeys( enrollPinResponse: EnrollPinResponse, requirePasswordAfterRestart: Bool, ) async throws { let userId = try getActiveAccountUserId() if requirePasswordAfterRestart { try await setPinProtectedUserKeyToMemory(enrollPinResponse.pinProtectedUserKeyEnvelope) } else { appSettingsStore.setPinProtectedUserKeyEnvelope( key: enrollPinResponse.pinProtectedUserKeyEnvelope, userId: userId, ) } appSettingsStore.setEncryptedPin(enrollPinResponse.userKeyEncryptedPin, userId: userId) // Remove any legacy pin protected user keys. appSettingsStore.setPinProtectedUserKey(key: nil, userId: userId) } func setPinProtectedUserKeyToMemory(_ pinProtectedUserKey: String) async throws { try accountVolatileData[ getActiveAccountUserId(), default: AccountVolatileData(), ].pinProtectedUserKey = pinProtectedUserKey } func setPreAuthEnvironmentURLs(_ urls: EnvironmentURLData) async { appSettingsStore.preAuthEnvironmentURLs = urls } func setAccountCreationEnvironmentURLs(urls: EnvironmentURLData, email: String) async { appSettingsStore.setAccountCreationEnvironmentURLs( environmentURLData: urls, email: email, ) } func setPreAuthServerConfig(config: ServerConfig) async { appSettingsStore.preAuthServerConfig = config } func setAppRehydrationState(_ rehydrationState: AppRehydrationState?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setAppRehydrationState(rehydrationState, userId: userId) } func setReviewPromptData(_ data: ReviewPromptData) async { appSettingsStore.reviewPromptData = data } func setServerConfig(_ config: ServerConfig?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setServerConfig(config, userId: userId) } func setShouldTrustDevice(_ shouldTrustDevice: Bool?, userId: String) { appSettingsStore.setShouldTrustDevice(shouldTrustDevice: shouldTrustDevice, userId: userId) } func setShowWebIcons(_ showWebIcons: Bool) async { appSettingsStore.disableWebIcons = !showWebIcons showWebIconsSubject.send(showWebIcons) } func setSiriAndShortcutsAccess(_ siriAndShortcutsAccess: Bool, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setSiriAndShortcutsAccess(siriAndShortcutsAccess, userId: userId) } func setSyncToAuthenticator(_ syncToAuthenticator: Bool, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setSyncToAuthenticator(syncToAuthenticator, userId: userId) syncToAuthenticatorByUserIdSubject.value[userId] = syncToAuthenticator } func setTimeoutAction(action: SessionTimeoutAction, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setTimeoutAction(key: action, userId: userId) } func setTwoFactorToken(_ token: String?, email: String) async { appSettingsStore.setTwoFactorToken(token, email: email) } func setUnsuccessfulUnlockAttempts(_ attempts: Int, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setUnsuccessfulUnlockAttempts(attempts, userId: userId) } func setUserHasMasterPassword(_ hasMasterPassword: Bool) async throws { let userId = try getActiveAccountUserId() var state = appSettingsStore.state ?? State() defer { appSettingsStore.state = state } guard var profile = state.accounts[userId]?.profile else { return } profile.userDecryptionOptions?.hasMasterPassword = hasMasterPassword state.accounts[userId]?.profile = profile } func setUsernameGenerationOptions(_ options: UsernameGenerationOptions?, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setUsernameGenerationOptions(options, userId: userId) } func setUsesKeyConnector(_ usesKeyConnector: Bool, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setUsesKeyConnector(usesKeyConnector, userId: userId) } func setVaultTimeout(value: SessionTimeoutValue, userId: String?) async throws { let userId = try userId ?? getActiveAccountUserId() appSettingsStore.setVaultTimeout(minutes: value.rawValue, userId: userId) } func updateProfile(from response: ProfileResponseModel, userId: String) async { var state = appSettingsStore.state ?? State() defer { appSettingsStore.state = state } guard var profile = state.accounts[userId]?.profile else { return } profile.hasPremiumPersonally = response.premium profile.avatarColor = response.avatarColor profile.creationDate = response.creationDate profile.email = response.email ?? profile.email profile.emailVerified = response.emailVerified profile.name = response.name profile.stamp = response.securityStamp profile.twoFactorEnabled = response.twoFactorEnabled state.accounts[userId]?.profile = profile } // MARK: Publishers func activeAccountIdPublisher() -> AnyPublisher { appSettingsStore.activeAccountIdPublisher() } func appThemePublisher() async -> AnyPublisher { appThemeSubject.eraseToAnyPublisher() } func connectToWatchPublisher() async -> AnyPublisher<(String?, Bool), Never> { activeAccountIdPublisher().flatMap { userId in self.connectToWatchByUserIdSubject.map { values in let userValue = if let userId { // Get the user's setting, if they're logged in. values[userId] ?? self.appSettingsStore.connectToWatch(userId: userId) } else { // Otherwise, use the last known value for the previous user. self.appSettingsStore.lastUserShouldConnectToWatch } return (userId, userValue) } } .eraseToAnyPublisher() } func lastSyncTimePublisher() async throws -> AnyPublisher { let userId = try getActiveAccountUserId() if lastSyncTimeByUserIdSubject.value[userId] == nil { lastSyncTimeByUserIdSubject.value[userId] = appSettingsStore.lastSyncTime(userId: userId) } return lastSyncTimeByUserIdSubject.map { $0[userId] }.eraseToAnyPublisher() } func pendingAppIntentActionsPublisher() async -> AnyPublisher<[PendingAppIntentAction]?, Never> { if pendingAppIntentActionsSubject.value == nil { pendingAppIntentActionsSubject.value = appSettingsStore.pendingAppIntentActions } return pendingAppIntentActionsSubject.eraseToAnyPublisher() } func settingsBadgePublisher() async throws -> AnyPublisher { let userId = try getActiveAccountUserId() await updateSettingsBadgePublisher(userId: userId) return settingsBadgeByUserIdSubject.compactMap { $0[userId] }.eraseToAnyPublisher() } func showWebIconsPublisher() async -> AnyPublisher { showWebIconsSubject.eraseToAnyPublisher() } func syncToAuthenticatorPublisher() async -> AnyPublisher<(String?, Bool), Never> { activeAccountIdPublisher().flatMap { userId in self.syncToAuthenticatorByUserIdSubject.map { values in guard let userId else { return (nil, false) } let userValue = values[userId] ?? self.appSettingsStore.syncToAuthenticator(userId: userId) return (userId, userValue) } } .eraseToAnyPublisher() } // MARK: Private /// Returns the user ID for the active account. /// /// - Returns: The user ID for the active account. /// private func getActiveAccountUserId() throws -> String { guard let activeUserId = appSettingsStore.state?.activeUserId else { throw StateServiceError.noActiveAccount } return activeUserId } /// Updates the account's profile. /// /// - Parameters: /// - userId: The user ID of the account to update. /// - updateProfile: A closure that allows making updates to the account's profile. Any /// updates made to the profile will be saved when the closure returns. /// private func updateAccountProfile(userId: String, updateProfile: (inout Account.AccountProfile) -> Void) throws { guard var state = appSettingsStore.state else { throw StateServiceError.noAccounts } guard var profile = state.accounts[userId]?.profile else { throw StateServiceError.noAccountForUserId } updateProfile(&profile) state.accounts[userId]?.profile = profile appSettingsStore.state = state } /// Updates the settings badge publisher by determining the settings badge count for the user. /// /// - Parameter userId: The user ID whose settings badge count should be updated. /// private func updateSettingsBadgePublisher(userId: String) async { let autofillSetupProgress = await getAccountSetupAutofill(userId: userId) let importLoginsSetupProgress = await getAccountSetupImportLogins(userId: userId) let vaultUnlockSetupProgress = await getAccountSetupVaultUnlock(userId: userId) var badgeCount = [autofillSetupProgress, vaultUnlockSetupProgress] .compactMap(\.self) .count(where: { $0 != .complete }) if importLoginsSetupProgress == .setUpLater { badgeCount += 1 } settingsBadgeByUserIdSubject.value[userId] = SettingsBadgeState( autofillSetupProgress: autofillSetupProgress, badgeValue: badgeCount > 0 ? String(badgeCount) : nil, importLoginsSetupProgress: importLoginsSetupProgress, vaultUnlockSetupProgress: vaultUnlockSetupProgress, ) } } // MARK: - AccountVolatileData /// The data stored in memory. /// struct AccountVolatileData { /// The pin protected user key. var pinProtectedUserKey: String? /// Whether the account has been unlocked with user interaction. var hasBeenUnlockedInteractively = false } // MARK: Biometrics extension DefaultStateService { func getBiometricAuthenticationEnabled() async throws -> Bool { let userId = try getActiveAccountUserId() return appSettingsStore.isBiometricAuthenticationEnabled(userId: userId) } func setBiometricAuthenticationEnabled(_ isEnabled: Bool?) async throws { let userId = try getActiveAccountUserId() appSettingsStore.setBiometricAuthenticationEnabled(isEnabled, for: userId) } }