Tusker/Packages/UserAccounts/Sources/UserAccounts/UserAccountsManager.swift

171 lines
6.1 KiB
Swift
Raw Normal View History

2018-08-19 20:14:04 +00:00
//
// UserAccountsManager.swift
// UserAccounts
2018-08-19 20:14:04 +00:00
//
// Created by Shadowfacts on 3/5/23.
2018-08-19 20:14:04 +00:00
//
import Foundation
import Combine
2018-08-19 20:14:04 +00:00
// Sendability: UserDefaults is not marked Sendable, but is documented as being thread safe
public final class UserAccountsManager: ObservableObject, @unchecked Sendable {
2018-08-19 20:14:04 +00:00
public static let shared = UserAccountsManager()
2018-08-19 20:14:04 +00:00
2019-12-30 20:59:49 +00:00
let defaults: UserDefaults
private init() {
if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING") {
defaults = UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier!).uitesting")!
defaults.removePersistentDomain(forName: "\(Bundle.main.bundleIdentifier!).uitesting")
2019-12-31 16:40:56 +00:00
if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING_LOGIN") {
accounts = [
UserAccountInfo(
instanceURL: URL(string: "http://localhost:8080")!,
clientID: "client_id",
clientSecret: "client_secret",
username: "admin",
accessToken: "access_token",
scopes: []
)
]
2019-12-31 16:40:56 +00:00
}
2019-12-30 20:59:49 +00:00
} else {
2020-07-03 22:45:37 +00:00
defaults = UserDefaults(suiteName: "group.space.vaccor.Tusker")!
2019-12-30 20:59:49 +00:00
}
migrateAccountIDsIfNecessary()
2019-12-30 20:59:49 +00:00
}
2018-08-19 20:14:04 +00:00
private let accountsKey = "accounts"
public private(set) var accounts: [UserAccountInfo] {
2018-08-19 20:14:04 +00:00
get {
if let array = defaults.array(forKey: accountsKey) as? [[String: Any]] {
return array.compactMap(UserAccountInfo.init(userDefaultsDict:))
} else {
return []
}
2018-08-19 20:14:04 +00:00
}
set {
objectWillChange.send()
let array = newValue.map(\.userDefaultsDict)
defaults.set(array, forKey: accountsKey)
2018-08-19 20:14:04 +00:00
}
}
private let mostRecentAccountKey = "mostRecentAccount"
public private(set) var mostRecentAccountID: String? {
2018-08-19 20:14:04 +00:00
get {
return defaults.string(forKey: mostRecentAccountKey)
2018-08-19 20:14:04 +00:00
}
set {
objectWillChange.send()
defaults.set(newValue, forKey: mostRecentAccountKey)
2018-08-19 20:14:04 +00:00
}
}
private let usesAccountIDHashesKey = "usesAccountIDHashes"
private var usesAccountIDHashes: Bool {
get {
return defaults.bool(forKey: usesAccountIDHashesKey)
}
set {
return defaults.set(newValue, forKey: usesAccountIDHashesKey)
}
}
private func migrateAccountIDsIfNecessary() {
if usesAccountIDHashes {
return
}
if let mostRecentAccount = getMostRecentAccount() {
let hashedMostRecentID = UserAccountInfo.id(instanceURL: mostRecentAccount.instanceURL, username: mostRecentAccount.username)
mostRecentAccountID = hashedMostRecentID
}
if let array = defaults.array(forKey: accountsKey) as? [[String: String]] {
accounts = array.compactMap {
guard let urlString = $0["instanceURL"],
let url = URL(string: urlString),
let username = $0["username"] else {
return nil
}
var dict = $0
dict["id"] = UserAccountInfo.id(instanceURL: url, username: username)
return UserAccountInfo(userDefaultsDict: dict)
}
}
usesAccountIDHashes = true
}
// MARK: Account Management
public var onboardingComplete: Bool {
return !accounts.isEmpty
2018-08-19 20:14:04 +00:00
}
public func addAccount(instanceURL url: URL, clientID: String, clientSecret secret: String, username: String?, accessToken: String, scopes: [String]) -> UserAccountInfo {
var accounts = self.accounts
if let index = accounts.firstIndex(where: { $0.instanceURL == url && $0.username == username }) {
accounts.remove(at: index)
2018-08-19 20:14:04 +00:00
}
let info = UserAccountInfo(instanceURL: url, clientID: clientID, clientSecret: secret, username: username, accessToken: accessToken, scopes: scopes)
accounts.append(info)
self.accounts = accounts
return info
2018-08-19 20:14:04 +00:00
}
public func removeAccount(_ info: UserAccountInfo) {
2020-01-19 16:52:06 +00:00
accounts.removeAll(where: { $0.id == info.id })
}
public func getAccount(id: String) -> UserAccountInfo? {
return accounts.first(where: { $0.id == id })
}
public func getMostRecentAccount() -> UserAccountInfo? {
2020-01-19 16:52:06 +00:00
guard onboardingComplete else { return nil }
let mostRecent: UserAccountInfo?
2020-11-10 00:39:42 +00:00
if let id = mostRecentAccountID {
2020-01-19 16:52:06 +00:00
mostRecent = accounts.first { $0.id == id }
} else {
2020-01-19 16:52:06 +00:00
mostRecent = nil
2018-08-19 20:14:04 +00:00
}
2020-01-19 16:52:06 +00:00
return mostRecent ?? accounts.first!
}
public func setMostRecentAccount(_ account: UserAccountInfo?) {
2020-11-10 00:39:42 +00:00
mostRecentAccountID = account?.id
2018-08-19 20:14:04 +00:00
}
public func updateServerPreferences(_ account: UserAccountInfo, defaultLanguage: String?, defaultVisibility: String?, defaultFederation: Bool?) {
guard let index = accounts.firstIndex(where: { $0.id == account.id }) else {
return
}
var account = account
account.serverDefaultLanguage = defaultLanguage
account.serverDefaultVisibility = defaultVisibility
account.serverDefaultFederation = defaultFederation
accounts[index] = account
}
public func updateCredentials(_ account: UserAccountInfo, clientID: String, clientSecret: String, accessToken: String, scopes: [String]) {
guard let index = accounts.firstIndex(where: { $0.id == account.id }) else {
return
}
var account = account
account.clientID = clientID
account.clientSecret = clientSecret
account.accessToken = accessToken
account.scopes = scopes
accounts[index] = account
}
}
public extension Notification.Name {
2020-01-19 16:52:06 +00:00
static let userLoggedOut = Notification.Name("Tusker.userLoggedOut")
static let addAccount = Notification.Name("Tusker.addAccount")
static let activateAccount = Notification.Name("Tusker.activateAccount")
2019-09-16 17:12:23 +00:00
}