2023-12-29 11:17:37 +01:00
|
|
|
//Made by Lumaa
|
|
|
|
|
|
|
|
import Foundation
|
2024-02-04 08:43:56 +01:00
|
|
|
import KeychainSwift
|
2024-01-31 16:47:29 +01:00
|
|
|
|
2024-01-02 14:23:36 +01:00
|
|
|
@Observable
|
2024-02-11 17:00:29 +01:00
|
|
|
public class AccountManager: ObservableObject {
|
2024-01-02 14:23:36 +01:00
|
|
|
private var client: Client?
|
|
|
|
private var account: Account?
|
|
|
|
|
2024-02-11 17:00:29 +01:00
|
|
|
public static let shared: AccountManager = AccountManager()
|
|
|
|
|
2024-01-02 14:23:36 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-02-11 17:00:29 +01:00
|
|
|
public func setAccount(_ account: Account) {
|
|
|
|
self.account = account
|
|
|
|
}
|
|
|
|
|
2024-01-02 14:23:36 +01:00
|
|
|
public func getAccount() -> Account? {
|
|
|
|
return account
|
|
|
|
}
|
|
|
|
|
|
|
|
public func forceClient() -> Client {
|
2024-01-05 12:54:57 +01:00
|
|
|
guard client != nil else { fatalError("Client is not existant in that context") }
|
|
|
|
return client!
|
2024-01-02 14:23:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public func forceAccount() -> Account {
|
2024-01-05 12:54:57 +01:00
|
|
|
guard account != nil else { fatalError("Account is not existant in that context") }
|
|
|
|
return account!
|
2024-01-02 14:23:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-29 11:17:37 +01:00
|
|
|
public struct AppAccount: Codable, Identifiable, Hashable {
|
|
|
|
public let server: String
|
|
|
|
public var accountName: String?
|
|
|
|
public let oauthToken: OauthToken?
|
2024-02-04 08:43:56 +01:00
|
|
|
|
2024-01-02 14:23:36 +01:00
|
|
|
private static let saveKey: String = "threaded-appaccount.current"
|
2024-02-04 08:43:56 +01:00
|
|
|
private static var keychain: KeychainSwift {
|
|
|
|
let kc = KeychainSwift()
|
|
|
|
// synchronise later
|
|
|
|
return kc
|
|
|
|
}
|
2023-12-29 11:17:37 +01:00
|
|
|
|
|
|
|
public var key: String {
|
|
|
|
if let oauthToken {
|
|
|
|
"\(server):\(oauthToken.createdAt)"
|
|
|
|
} else {
|
|
|
|
"\(server):anonymous"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public var id: String {
|
|
|
|
key
|
|
|
|
}
|
|
|
|
|
2024-02-04 08:43:56 +01:00
|
|
|
public init(server: String, accountName: String?, oauthToken: OauthToken? = nil) {
|
2023-12-29 11:17:37 +01:00
|
|
|
self.server = server
|
|
|
|
self.accountName = accountName
|
|
|
|
self.oauthToken = oauthToken
|
|
|
|
}
|
|
|
|
|
2024-02-04 08:43:56 +01:00
|
|
|
public static func clear() {
|
|
|
|
Self.keychain.delete(Self.saveKey)
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
|
2024-02-04 08:43:56 +01:00
|
|
|
public func clear() {
|
|
|
|
Self.clear()
|
|
|
|
}
|
|
|
|
|
2024-02-22 23:16:08 +01:00
|
|
|
/// This function only works with the given `AppAccount`
|
2024-02-04 08:43:56 +01:00
|
|
|
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")
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
}
|
2024-01-02 14:23:36 +01:00
|
|
|
|
2024-02-22 23:16:08 +01:00
|
|
|
/// This function only works with the last saved `AppAccount` or with the given `Data`
|
2024-02-04 08:43:56 +01:00
|
|
|
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
|
|
|
|
}
|
2024-01-02 14:23:36 +01:00
|
|
|
}
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|