// // UserAccountInfo.swift // UserAccounts // // Created by Shadowfacts on 3/5/23. // import Foundation import CryptoKit public struct UserAccountInfo: Equatable, Hashable, Identifiable { public let id: String public let instanceURL: URL public let clientID: String public let clientSecret: String public private(set) var username: String! public let accessToken: String // Sort of hack to be able to access these from the share extension. public internal(set) var serverDefaultLanguage: String? public internal(set) var serverDefaultVisibility: String? public internal(set) var serverDefaultFederation: Bool? fileprivate static let tempAccountID = "temp" 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 public 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 } 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 } init?(userDefaultsDict dict: [String: Any]) { guard let id = dict["id"] as? String, let instanceURL = dict["instanceURL"] as? String, let url = URL(string: instanceURL), let clientID = dict["clientID"] as? String, let secret = dict["clientSecret"] as? String, let accessToken = dict["accessToken"] as? String else { return nil } self.id = id self.instanceURL = url self.clientID = clientID self.clientSecret = secret self.username = dict["username"] as? String self.accessToken = accessToken self.serverDefaultLanguage = dict["serverDefaultLanguage"] as? String self.serverDefaultVisibility = dict["serverDefaultVisibility"] as? String self.serverDefaultFederation = dict["serverDefaultFederation"] as? Bool } var userDefaultsDict: [String: Any] { var dict: [String: Any] = [ "id": id, "instanceURL": instanceURL.absoluteString, "clientID": clientID, "clientSecret": clientSecret, "accessToken": accessToken, ] if let username { dict["username"] = username } if let serverDefaultLanguage { dict["serverDefaultLanguage"] = serverDefaultLanguage } if let serverDefaultVisibility { dict["serverDefaultVisibility"] = serverDefaultVisibility } if let serverDefaultFederation { dict["serverDefaultFederation"] = serverDefaultFederation } return dict } /// A filename-safe string for this account public var persistenceKey: String { // slashes are not allowed in the persistent store coordinator name id.replacingOccurrences(of: "/", with: "_") } public func hash(into hasher: inout Hasher) { hasher.combine(id) } public static func ==(lhs: UserAccountInfo, rhs: UserAccountInfo) -> Bool { return lhs.id == rhs.id } }