Use deterministic ids for accounts
This commit is contained in:
parent
dec7a6e57f
commit
7f5006c629
|
@ -7,10 +7,11 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Token: Decodable {
|
public struct Token: Codable, Sendable {
|
||||||
public let accessToken: String
|
public let accessToken: String
|
||||||
public let expiresIn: Int?
|
public let expiresIn: Int?
|
||||||
public let refreshToken: String?
|
public let refreshToken: String?
|
||||||
|
public let owner: String?
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
@ -18,11 +19,21 @@ public struct Token: Decodable {
|
||||||
self.accessToken = try container.decode(String.self, forKey: .accessToken)
|
self.accessToken = try container.decode(String.self, forKey: .accessToken)
|
||||||
self.expiresIn = try container.decodeIfPresent(Int.self, forKey: .expiresIn)
|
self.expiresIn = try container.decodeIfPresent(Int.self, forKey: .expiresIn)
|
||||||
self.refreshToken = try container.decodeIfPresent(String.self, forKey: .refreshToken)
|
self.refreshToken = try container.decodeIfPresent(String.self, forKey: .refreshToken)
|
||||||
|
self.owner = try container.decodeIfPresent(String.self, forKey: .owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(accessToken, forKey: .accessToken)
|
||||||
|
try container.encodeIfPresent(expiresIn, forKey: .expiresIn)
|
||||||
|
try container.encodeIfPresent(refreshToken, forKey: .refreshToken)
|
||||||
|
try container.encodeIfPresent(owner, forKey: .owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case accessToken = "access_token"
|
case accessToken = "access_token"
|
||||||
case expiresIn = "expires_in"
|
case expiresIn = "expires_in"
|
||||||
case refreshToken = "refresh_token"
|
case refreshToken = "refresh_token"
|
||||||
|
case owner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,9 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentContainer")
|
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentContainer")
|
||||||
|
|
||||||
init(account: LocalData.Account) {
|
init(account: LocalData.Account) {
|
||||||
super.init(name: "\(account.id)", managedObjectModel: PersistentContainer.managedObjectModel)
|
// slashes the base64 string turn into subdirectories which we don't want
|
||||||
|
let name = account.id.base64EncodedString().replacingOccurrences(of: "/", with: "_")
|
||||||
|
super.init(name: name, managedObjectModel: PersistentContainer.managedObjectModel)
|
||||||
|
|
||||||
loadPersistentStores { description, error in
|
loadPersistentStores { description, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
|
|
|
@ -22,7 +22,7 @@ actor FervorController {
|
||||||
nonisolated let account: LocalData.Account?
|
nonisolated let account: LocalData.Account?
|
||||||
private(set) var clientID: String?
|
private(set) var clientID: String?
|
||||||
private(set) var clientSecret: String?
|
private(set) var clientSecret: String?
|
||||||
private(set) var accessToken: String?
|
private(set) var token: Token?
|
||||||
|
|
||||||
nonisolated let persistentContainer: PersistentContainer!
|
nonisolated let persistentContainer: PersistentContainer!
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ actor FervorController {
|
||||||
|
|
||||||
init(instanceURL: URL, account: LocalData.Account?) async {
|
init(instanceURL: URL, account: LocalData.Account?) async {
|
||||||
self.instanceURL = instanceURL
|
self.instanceURL = instanceURL
|
||||||
self.client = FervorClient(instanceURL: instanceURL, accessToken: account?.accessToken)
|
self.client = FervorClient(instanceURL: instanceURL, accessToken: account?.token.accessToken)
|
||||||
self.account = account
|
self.account = account
|
||||||
self.clientID = account?.clientID
|
self.clientID = account?.clientID
|
||||||
self.clientSecret = account?.clientSecret
|
self.clientSecret = account?.clientSecret
|
||||||
|
@ -63,8 +63,7 @@ actor FervorController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getToken(authCode: String) async throws {
|
func getToken(authCode: String) async throws {
|
||||||
let token = try await client.token(authCode: authCode, redirectURI: FervorController.oauthRedirectURI, clientID: clientID!, clientSecret: clientSecret!)
|
token = try await client.token(authCode: authCode, redirectURI: FervorController.oauthRedirectURI, clientID: clientID!, clientSecret: clientSecret!)
|
||||||
accessToken = token.accessToken
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncAll() async throws {
|
func syncAll() async throws {
|
||||||
|
@ -93,6 +92,7 @@ actor FervorController {
|
||||||
await ExcerptGenerator.generateAll(self)
|
await ExcerptGenerator.generateAll(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func syncReadToServer() async {
|
func syncReadToServer() async {
|
||||||
var count = 0
|
var count = 0
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Fervor
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
struct LocalData {
|
struct LocalData {
|
||||||
|
|
||||||
|
@ -28,15 +30,12 @@ struct LocalData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mostRecentAccountID: UUID? {
|
static var mostRecentAccountID: Data? {
|
||||||
get {
|
get {
|
||||||
guard let str = UserDefaults.standard.string(forKey: "mostRecentAccountID") else {
|
return UserDefaults.standard.data(forKey: "mostRecentAccountID")
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return UUID(uuidString: str)
|
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
UserDefaults.standard.set(newValue?.uuidString, forKey: "mostRecentAccountID")
|
UserDefaults.standard.set(newValue, forKey: "mostRecentAccountID")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,23 +43,32 @@ struct LocalData {
|
||||||
guard let id = mostRecentAccountID else {
|
guard let id = mostRecentAccountID else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return account(with: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func account(with id: Data) -> Account? {
|
||||||
return accounts.first(where: { $0.id == id })
|
return accounts.first(where: { $0.id == id })
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Account: Codable {
|
struct Account: Codable {
|
||||||
let id: UUID
|
let id: Data
|
||||||
let instanceURL: URL
|
let instanceURL: URL
|
||||||
let clientID: String
|
let clientID: String
|
||||||
let clientSecret: String
|
let clientSecret: String
|
||||||
let accessToken: String
|
let token: Token
|
||||||
// todo: refresh tokens
|
|
||||||
|
|
||||||
init(instanceURL: URL, clientID: String, clientSecret: String, accessToken: String) {
|
init(instanceURL: URL, clientID: String, clientSecret: String, token: Token) {
|
||||||
self.id = UUID()
|
// we use a hash of instance host and account id rather than random ids so that
|
||||||
|
// user activites can uniquely identify accounts across devices
|
||||||
|
var hasher = SHA256()
|
||||||
|
hasher.update(data: instanceURL.host!.data(using: .utf8)!)
|
||||||
|
hasher.update(data: token.owner!.data(using: .utf8)!)
|
||||||
|
self.id = Data(hasher.finalize())
|
||||||
|
|
||||||
self.instanceURL = instanceURL
|
self.instanceURL = instanceURL
|
||||||
self.clientID = clientID
|
self.clientID = clientID
|
||||||
self.clientSecret = clientSecret
|
self.clientSecret = clientSecret
|
||||||
self.accessToken = accessToken
|
self.token = token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 10/29/21.
|
// Created by Shadowfacts on 10/29/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
|
@ -33,7 +33,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
loginVC.delegate = self
|
loginVC.delegate = self
|
||||||
window!.rootViewController = loginVC
|
window!.rootViewController = loginVC
|
||||||
} else if activity?.activityType == NSUserActivity.activateAccountType,
|
} else if activity?.activityType == NSUserActivity.activateAccountType,
|
||||||
let account = LocalData.accounts.first(where: { $0.id.uuidString == activity!.userInfo?["accountID"] as? String }) {
|
let idStr = activity!.userInfo?["accountID"] as? String,
|
||||||
|
let id = Data(base64Encoded: idStr),
|
||||||
|
let account = LocalData.account(with: id) {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
fervorController = await FervorController(account: account)
|
fervorController = await FervorController(account: account)
|
||||||
syncFromServer()
|
syncFromServer()
|
||||||
|
@ -144,7 +146,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
extension SceneDelegate: LoginViewControllerDelegate {
|
extension SceneDelegate: LoginViewControllerDelegate {
|
||||||
func didLogin(with controller: FervorController) {
|
func didLogin(with controller: FervorController) {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
let account = LocalData.Account(instanceURL: controller.instanceURL, clientID: await controller.clientID!, clientSecret: await controller.clientSecret!, accessToken: await controller.accessToken!)
|
let account = LocalData.Account(instanceURL: controller.instanceURL, clientID: await controller.clientID!, clientSecret: await controller.clientSecret!, token: await controller.token!)
|
||||||
LocalData.accounts.append(account)
|
LocalData.accounts.append(account)
|
||||||
LocalData.mostRecentAccountID = account.id
|
LocalData.mostRecentAccountID = account.id
|
||||||
fervorController = await FervorController(account: account)
|
fervorController = await FervorController(account: account)
|
||||||
|
|
Loading…
Reference in New Issue