Bubble/Threaded/Data/Accounts/AccountManager.swift

171 lines
4.6 KiB
Swift

//Made by Lumaa
import Foundation
import KeychainSwift
@Observable
public class AccountManager: ObservableObject {
private var client: Client?
private var account: Account?
public static let shared: AccountManager = AccountManager()
init(client: Client? = nil, account: Account? = nil) {
self.client = client
self.account = account
}
public func clear() {
self.client = nil
self.account = nil
}
public func setClient(_ client: Client) {
self.client = client
}
public func getClient() -> Client? {
return client
}
public func setAccount(_ account: Account) {
self.account = account
}
public func getAccount() -> Account? {
return account
}
public func forceClient() -> Client {
guard client != nil else { fatalError("Client is not existant in that context") }
return client!
}
public func forceAccount() -> Account {
guard account != nil else { fatalError("Account is not existant in that context") }
return account!
}
public func fetchAccount() async -> Account? {
guard client != nil else { fatalError("Client is not existant in that context") }
account = try? await client!.get(endpoint: Accounts.verifyCredentials)
return account
}
}
public struct AppAccount: Codable, Identifiable, Hashable {
public let server: String
public var accountName: String?
public let oauthToken: OauthToken?
private static let saveKey: String = "threaded-appaccount.current"
private static var keychain: KeychainSwift {
let kc = KeychainSwift()
// synchronise later
return kc
}
public var key: String {
if let oauthToken {
"\(server):\(oauthToken.createdAt)"
} else {
"\(server):anonymous"
}
}
public var id: String {
key
}
public init(server: String, accountName: String?, oauthToken: OauthToken? = nil) {
self.server = server
self.accountName = accountName
self.oauthToken = oauthToken
}
public static func clear() {
Self.keychain.delete(Self.saveKey)
}
public func clear() {
Self.clear()
}
public func saveAsCurrent(_ appAccount: AppAccount? = nil) {
let encoder = JSONEncoder()
if let data = try? encoder.encode(appAccount ?? self) {
Self.keychain.set(data, forKey: Self.saveKey, withAccess: .accessibleWhenUnlocked)
} else {
fatalError("Couldn't encode AppAccount correctly to save")
}
}
public static func loadAsCurrent(_ data: Data? = nil) -> Self? {
let decoder = JSONDecoder()
if let newData = data ?? keychain.getData(Self.saveKey) {
if let decoded = try? decoder.decode(Self.self, from: newData) {
return decoded
} else {
return nil
}
} else {
return nil
}
}
}
extension AppAccount: Sendable {}
public enum Oauth: Endpoint {
case authorize(clientId: String)
case token(code: String, clientId: String, clientSecret: String)
public func path() -> String {
switch self {
case .authorize:
"oauth/authorize"
case .token:
"oauth/token"
}
}
public var jsonValue: Encodable? {
switch self {
case let .token(code, clientId, clientSecret):
TokenData(clientId: clientId, clientSecret: clientSecret, code: code)
default:
nil
}
}
public struct TokenData: Encodable {
public let grantType = "authorization_code"
public let clientId: String
public let clientSecret: String
public let redirectUri = AppInfo.scheme
public let code: String
public let scope = AppInfo.scopes
}
public func queryItems() -> [URLQueryItem]? {
switch self {
case let .authorize(clientId):
return [
.init(name: "response_type", value: "code"),
.init(name: "client_id", value: clientId),
.init(name: "redirect_uri", value: AppInfo.scheme),
.init(name: "scope", value: AppInfo.scopes),
]
default:
return nil
}
}
}
public struct OauthToken: Codable, Hashable, Sendable {
public let accessToken: String
public let tokenType: String
public let scope: String
public let createdAt: Double
}