Use deterministic ids for accounts

This commit is contained in:
Shadowfacts 2022-03-06 15:34:30 -05:00
parent dec7a6e57f
commit 7f5006c629
5 changed files with 44 additions and 21 deletions

View File

@ -7,10 +7,11 @@
import Foundation
public struct Token: Decodable {
public struct Token: Codable, Sendable {
public let accessToken: String
public let expiresIn: Int?
public let refreshToken: String?
public let owner: String?
public init(from decoder: Decoder) throws {
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.expiresIn = try container.decodeIfPresent(Int.self, forKey: .expiresIn)
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 {
case accessToken = "access_token"
case expiresIn = "expires_in"
case refreshToken = "refresh_token"
case owner
}
}

View File

@ -29,7 +29,9 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentContainer")
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
if let error = error {

View File

@ -22,7 +22,7 @@ actor FervorController {
nonisolated let account: LocalData.Account?
private(set) var clientID: String?
private(set) var clientSecret: String?
private(set) var accessToken: String?
private(set) var token: Token?
nonisolated let persistentContainer: PersistentContainer!
@ -32,7 +32,7 @@ actor FervorController {
init(instanceURL: URL, account: LocalData.Account?) async {
self.instanceURL = instanceURL
self.client = FervorClient(instanceURL: instanceURL, accessToken: account?.accessToken)
self.client = FervorClient(instanceURL: instanceURL, accessToken: account?.token.accessToken)
self.account = account
self.clientID = account?.clientID
self.clientSecret = account?.clientSecret
@ -63,8 +63,7 @@ actor FervorController {
}
func getToken(authCode: String) async throws {
let token = try await client.token(authCode: authCode, redirectURI: FervorController.oauthRedirectURI, clientID: clientID!, clientSecret: clientSecret!)
accessToken = token.accessToken
token = try await client.token(authCode: authCode, redirectURI: FervorController.oauthRedirectURI, clientID: clientID!, clientSecret: clientSecret!)
}
func syncAll() async throws {
@ -93,6 +92,7 @@ actor FervorController {
await ExcerptGenerator.generateAll(self)
}
@MainActor
func syncReadToServer() async {
var count = 0

View File

@ -6,6 +6,8 @@
//
import Foundation
import Fervor
import CryptoKit
struct LocalData {
@ -28,15 +30,12 @@ struct LocalData {
}
}
static var mostRecentAccountID: UUID? {
static var mostRecentAccountID: Data? {
get {
guard let str = UserDefaults.standard.string(forKey: "mostRecentAccountID") else {
return nil
}
return UUID(uuidString: str)
return UserDefaults.standard.data(forKey: "mostRecentAccountID")
}
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 {
return nil
}
return account(with: id)
}
static func account(with id: Data) -> Account? {
return accounts.first(where: { $0.id == id })
}
struct Account: Codable {
let id: UUID
let id: Data
let instanceURL: URL
let clientID: String
let clientSecret: String
let accessToken: String
// todo: refresh tokens
let token: Token
init(instanceURL: URL, clientID: String, clientSecret: String, accessToken: String) {
self.id = UUID()
init(instanceURL: URL, clientID: String, clientSecret: String, token: Token) {
// 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.clientID = clientID
self.clientSecret = clientSecret
self.accessToken = accessToken
self.token = token
}
}

View File

@ -5,7 +5,7 @@
// Created by Shadowfacts on 10/29/21.
//
@preconcurrency import Foundation
import Foundation
import UIKit
import OSLog
@ -33,7 +33,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
loginVC.delegate = self
window!.rootViewController = loginVC
} 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
fervorController = await FervorController(account: account)
syncFromServer()
@ -144,7 +146,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
extension SceneDelegate: LoginViewControllerDelegate {
func didLogin(with controller: FervorController) {
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.mostRecentAccountID = account.id
fervorController = await FervorController(account: account)