Tusker/Packages/UserAccounts/Sources/UserAccounts/UserAccountInfo.swift

112 lines
4.1 KiB
Swift

//
// UserAccountInfo.swift
// UserAccounts
//
// Created by Shadowfacts on 3/5/23.
//
import Foundation
import CryptoKit
public struct UserAccountInfo: Equatable, Hashable, Identifiable, Sendable {
public let id: String
public let instanceURL: URL
public internal(set) var clientID: String
public internal(set) var clientSecret: String
public let username: String!
public internal(set) var accessToken: String
public internal(set) var scopes: [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.username = nil
self.accessToken = accessToken
self.scopes = nil
}
init(instanceURL: URL, clientID: String, clientSecret: String, username: String? = nil, accessToken: String, scopes: [String]) {
self.id = UserAccountInfo.id(instanceURL: instanceURL, username: username)
self.instanceURL = instanceURL
self.clientID = clientID
self.clientSecret = clientSecret
self.username = username
self.accessToken = accessToken
self.scopes = scopes
}
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.scopes = dict["scopes"] as? [String]
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 scopes {
dict["scopes"] = scopes
}
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: "_")
}
}