forked from shadowfacts/Tusker
parent
bbfb3b0a7a
commit
35d21fb725
|
@ -8,6 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CryptoKit
|
||||
|
||||
class LocalData: ObservableObject {
|
||||
|
||||
|
@ -22,7 +23,6 @@ class LocalData: ObservableObject {
|
|||
if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING_LOGIN") {
|
||||
accounts = [
|
||||
UserAccountInfo(
|
||||
id: UUID().uuidString,
|
||||
instanceURL: URL(string: "http://localhost:8080")!,
|
||||
clientID: "client_id",
|
||||
clientSecret: "client_secret",
|
||||
|
@ -33,23 +33,15 @@ class LocalData: ObservableObject {
|
|||
} else {
|
||||
defaults = UserDefaults(suiteName: "group.space.vaccor.Tusker")!
|
||||
}
|
||||
|
||||
migrateAccountIDsIfNecessary()
|
||||
}
|
||||
|
||||
private let accountsKey = "accounts"
|
||||
var accounts: [UserAccountInfo] {
|
||||
private(set) var accounts: [UserAccountInfo] {
|
||||
get {
|
||||
if let array = defaults.array(forKey: accountsKey) as? [[String: String]] {
|
||||
return array.compactMap { (info) in
|
||||
guard let id = info["id"],
|
||||
let instanceURL = info["instanceURL"],
|
||||
let url = URL(string: instanceURL),
|
||||
let clientId = info["clientID"],
|
||||
let secret = info["clientSecret"],
|
||||
let accessToken = info["accessToken"] else {
|
||||
return nil
|
||||
}
|
||||
return UserAccountInfo(id: id, instanceURL: url, clientID: clientId, clientSecret: secret, username: info["username"], accessToken: accessToken)
|
||||
}
|
||||
return array.compactMap(UserAccountInfo.init(userDefaultsDict:))
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
@ -84,6 +76,41 @@ class LocalData: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
var onboardingComplete: Bool {
|
||||
return !accounts.isEmpty
|
||||
}
|
||||
|
@ -93,20 +120,12 @@ class LocalData: ObservableObject {
|
|||
if let index = accounts.firstIndex(where: { $0.instanceURL == url && $0.username == username }) {
|
||||
accounts.remove(at: index)
|
||||
}
|
||||
let id = UUID().uuidString
|
||||
let info = UserAccountInfo(id: id, instanceURL: url, clientID: clientID, clientSecret: secret, username: username, accessToken: accessToken)
|
||||
let info = UserAccountInfo(instanceURL: url, clientID: clientID, clientSecret: secret, username: username, accessToken: accessToken)
|
||||
accounts.append(info)
|
||||
self.accounts = accounts
|
||||
return info
|
||||
}
|
||||
|
||||
func setUsername(for info: UserAccountInfo, username: String) {
|
||||
var info = info
|
||||
info.username = username
|
||||
removeAccount(info)
|
||||
accounts.append(info)
|
||||
}
|
||||
|
||||
func removeAccount(_ info: UserAccountInfo) {
|
||||
accounts.removeAll(where: { $0.id == info.id })
|
||||
}
|
||||
|
@ -138,9 +157,57 @@ extension LocalData {
|
|||
let instanceURL: URL
|
||||
let clientID: String
|
||||
let clientSecret: String
|
||||
fileprivate(set) var username: String!
|
||||
private(set) var username: String!
|
||||
let accessToken: String
|
||||
|
||||
fileprivate static let tempAccountID = "temp"
|
||||
|
||||
fileprivate static func id(instanceURL: URL, username: String?) -> String {
|
||||
// We hash the instance host and username to form the account ID
|
||||
// so that account IDs will match across devices, allowing for data syncing and handoff.
|
||||
var hasher = SHA256()
|
||||
hasher.update(data: instanceURL.host!.data(using: .utf8)!)
|
||||
if let username {
|
||||
hasher.update(data: username.data(using: .utf8)!)
|
||||
}
|
||||
return Data(hasher.finalize()).base64EncodedString()
|
||||
}
|
||||
|
||||
/// Only to be used for temporary MastodonController needed to fetch own account info and create final UserAccountInfo with real username
|
||||
init(tempInstanceURL instanceURL: URL, clientID: String, clientSecret: String, accessToken: String) {
|
||||
self.id = UserAccountInfo.tempAccountID
|
||||
self.instanceURL = instanceURL
|
||||
self.clientID = clientID
|
||||
self.clientSecret = clientSecret
|
||||
self.accessToken = accessToken
|
||||
}
|
||||
|
||||
fileprivate init(instanceURL: URL, clientID: String, clientSecret: String, username: String? = nil, accessToken: String) {
|
||||
self.id = UserAccountInfo.id(instanceURL: instanceURL, username: username)
|
||||
self.instanceURL = instanceURL
|
||||
self.clientID = clientID
|
||||
self.clientSecret = clientSecret
|
||||
self.username = username
|
||||
self.accessToken = accessToken
|
||||
}
|
||||
|
||||
fileprivate init?(userDefaultsDict dict: [String: String]) {
|
||||
guard let id = dict["id"],
|
||||
let instanceURL = dict["instanceURL"],
|
||||
let url = URL(string: instanceURL),
|
||||
let clientID = dict["clientID"],
|
||||
let secret = dict["clientSecret"],
|
||||
let accessToken = dict["accessToken"] else {
|
||||
return nil
|
||||
}
|
||||
self.id = id
|
||||
self.instanceURL = url
|
||||
self.clientID = clientID
|
||||
self.clientSecret = secret
|
||||
self.username = dict["username"]
|
||||
self.accessToken = accessToken
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class OnboardingViewController: UINavigationController {
|
|||
}
|
||||
|
||||
// construct a temporary UserAccountInfo instance for the MastodonController to use to fetch its own account
|
||||
let tempAccountInfo = LocalData.UserAccountInfo(id: "temp", instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: nil, accessToken: accessToken)
|
||||
let tempAccountInfo = LocalData.UserAccountInfo(tempInstanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, accessToken: accessToken)
|
||||
mastodonController.accountInfo = tempAccountInfo
|
||||
|
||||
let ownAccount: Account
|
||||
|
|
|
@ -48,7 +48,7 @@ struct PreferencesView: View {
|
|||
indices.remove(index)
|
||||
}
|
||||
|
||||
localData.accounts.remove(atOffsets: indices)
|
||||
indices.forEach { localData.removeAccount(localData.accounts[$0]) }
|
||||
|
||||
if logoutFromCurrent {
|
||||
self.logoutPressed()
|
||||
|
|
Loading…
Reference in New Issue