forked from shadowfacts/Tusker
171 lines
6.1 KiB
Swift
171 lines
6.1 KiB
Swift
//
|
|
// UserAccountsManager.swift
|
|
// UserAccounts
|
|
//
|
|
// Created by Shadowfacts on 3/5/23.
|
|
//
|
|
|
|
import Foundation
|
|
import Combine
|
|
|
|
// Sendability: UserDefaults is not marked Sendable, but is documented as being thread safe
|
|
public final class UserAccountsManager: ObservableObject, @unchecked Sendable {
|
|
|
|
public static let shared = UserAccountsManager()
|
|
|
|
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")
|
|
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: []
|
|
)
|
|
]
|
|
}
|
|
} else {
|
|
defaults = UserDefaults(suiteName: "group.space.vaccor.Tusker")!
|
|
}
|
|
|
|
migrateAccountIDsIfNecessary()
|
|
}
|
|
|
|
private let accountsKey = "accounts"
|
|
public private(set) var accounts: [UserAccountInfo] {
|
|
get {
|
|
if let array = defaults.array(forKey: accountsKey) as? [[String: Any]] {
|
|
return array.compactMap(UserAccountInfo.init(userDefaultsDict:))
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
set {
|
|
objectWillChange.send()
|
|
let array = newValue.map(\.userDefaultsDict)
|
|
defaults.set(array, forKey: accountsKey)
|
|
}
|
|
}
|
|
|
|
private let mostRecentAccountKey = "mostRecentAccount"
|
|
public private(set) var mostRecentAccountID: String? {
|
|
get {
|
|
return defaults.string(forKey: mostRecentAccountKey)
|
|
}
|
|
set {
|
|
objectWillChange.send()
|
|
defaults.set(newValue, forKey: mostRecentAccountKey)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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)
|
|
}
|
|
let info = UserAccountInfo(instanceURL: url, clientID: clientID, clientSecret: secret, username: username, accessToken: accessToken, scopes: scopes)
|
|
accounts.append(info)
|
|
self.accounts = accounts
|
|
return info
|
|
}
|
|
|
|
public func removeAccount(_ info: UserAccountInfo) {
|
|
accounts.removeAll(where: { $0.id == info.id })
|
|
}
|
|
|
|
public func getAccount(id: String) -> UserAccountInfo? {
|
|
return accounts.first(where: { $0.id == id })
|
|
}
|
|
|
|
public func getMostRecentAccount() -> UserAccountInfo? {
|
|
guard onboardingComplete else { return nil }
|
|
let mostRecent: UserAccountInfo?
|
|
if let id = mostRecentAccountID {
|
|
mostRecent = accounts.first { $0.id == id }
|
|
} else {
|
|
mostRecent = nil
|
|
}
|
|
return mostRecent ?? accounts.first!
|
|
}
|
|
|
|
public func setMostRecentAccount(_ account: UserAccountInfo?) {
|
|
mostRecentAccountID = account?.id
|
|
}
|
|
|
|
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 {
|
|
static let userLoggedOut = Notification.Name("Tusker.userLoggedOut")
|
|
static let addAccount = Notification.Name("Tusker.addAccount")
|
|
static let activateAccount = Notification.Name("Tusker.activateAccount")
|
|
}
|