Merge pull request #1131 from mastodon/move-credentials-to-keychain
Use Keychain for credentials
This commit is contained in:
commit
8381a44b71
@ -331,7 +331,6 @@
|
||||
DB63F777279A9A2A00455B82 /* NotificationView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F776279A9A2A00455B82 /* NotificationView+Configuration.swift */; };
|
||||
DB63F779279ABF9C00455B82 /* DataSourceFacade+Reblog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F778279ABF9C00455B82 /* DataSourceFacade+Reblog.swift */; };
|
||||
DB63F77B279ACAE500455B82 /* DataSourceFacade+Favorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F77A279ACAE500455B82 /* DataSourceFacade+Favorite.swift */; };
|
||||
DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */; };
|
||||
DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */; };
|
||||
DB65C63727A2AF6C008BAC2E /* ReportItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB65C63627A2AF6C008BAC2E /* ReportItem.swift */; };
|
||||
DB6746EB278ED8B0008A6B94 /* PollOptionView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6746EA278ED8B0008A6B94 /* PollOptionView+Configuration.swift */; };
|
||||
@ -1037,7 +1036,6 @@
|
||||
DB63F776279A9A2A00455B82 /* NotificationView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationView+Configuration.swift"; sourceTree = "<group>"; };
|
||||
DB63F778279ABF9C00455B82 /* DataSourceFacade+Reblog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Reblog.swift"; sourceTree = "<group>"; };
|
||||
DB63F77A279ACAE500455B82 /* DataSourceFacade+Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Favorite.swift"; sourceTree = "<group>"; };
|
||||
DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonAuthentication+Fetch.swift"; sourceTree = "<group>"; };
|
||||
DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Fetch.swift"; sourceTree = "<group>"; };
|
||||
DB65C63627A2AF6C008BAC2E /* ReportItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportItem.swift; sourceTree = "<group>"; };
|
||||
DB6746EA278ED8B0008A6B94 /* PollOptionView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PollOptionView+Configuration.swift"; sourceTree = "<group>"; };
|
||||
@ -2404,7 +2402,6 @@
|
||||
DB64BA462851F23300ADF1B7 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */,
|
||||
DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */,
|
||||
);
|
||||
path = Model;
|
||||
@ -4057,7 +4054,6 @@
|
||||
DBB8AB4626AECDE200F6D281 /* SendPostIntentHandler.swift in Sources */,
|
||||
2AB5011D299243FB00346092 /* WidgetExtension.intentdefinition in Sources */,
|
||||
2AE202AC297FE19100F66E55 /* FollowersCountIntentHandler.swift in Sources */,
|
||||
DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */,
|
||||
DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */,
|
||||
D8F0372C29D232730027DE2E /* HashtagIntentHandler.swift in Sources */,
|
||||
2A86A14629892944007F1062 /* MultiFollowersCountIntentHandler.swift in Sources */,
|
||||
|
@ -57,12 +57,8 @@ final public class SceneCoordinator {
|
||||
return
|
||||
} else {
|
||||
// switch to notification's account
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken)
|
||||
request.returnsObjectsAsFaults = false
|
||||
request.fetchLimit = 1
|
||||
do {
|
||||
guard let authentication = try appContext.managedObjectContext.fetch(request).first else {
|
||||
guard let authentication = AuthenticationServiceProvider.shared.authentications.first(where: { $0.userAccessToken == accessToken }) else {
|
||||
return
|
||||
}
|
||||
let domain = authentication.domain
|
||||
@ -226,8 +222,7 @@ extension SceneCoordinator {
|
||||
let rootViewController: UIViewController
|
||||
|
||||
do {
|
||||
let request = MastodonAuthentication.activeSortedFetchRequest // use active order
|
||||
let _authentication = try appContext.managedObjectContext.fetch(request).first
|
||||
let _authentication = AuthenticationServiceProvider.shared.authenticationSortedByActivation().first
|
||||
let _authContext = _authentication.flatMap { AuthContext(authentication: $0) }
|
||||
self.authContext = _authContext
|
||||
|
||||
@ -538,7 +533,7 @@ private extension SceneCoordinator {
|
||||
viewController = activityViewController
|
||||
case .settings(let setting):
|
||||
guard let presentedOn = sender,
|
||||
let accountName = authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: appContext.managedObjectContext)?.username,
|
||||
let accountName = authContext?.mastodonAuthenticationBox.authentication.username,
|
||||
let authContext
|
||||
else { return nil }
|
||||
|
||||
|
@ -74,7 +74,7 @@ extension DiscoverySection {
|
||||
cell.profileCardView.viewModel.familiarFollowers = nil
|
||||
}
|
||||
// bind me
|
||||
cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
}
|
||||
return cell
|
||||
case .bottomLoader:
|
||||
|
@ -80,7 +80,7 @@ extension UserSection {
|
||||
configuration: Configuration
|
||||
) {
|
||||
cell.configure(
|
||||
me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user,
|
||||
me: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext),
|
||||
tableView: tableView,
|
||||
viewModel: viewModel,
|
||||
delegate: configuration.userTableViewCellDelegate
|
||||
|
@ -12,17 +12,13 @@ import MastodonSDK
|
||||
|
||||
extension AppContext {
|
||||
func nextAccount(in authContext: AuthContext) -> MastodonAuthentication? {
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
guard
|
||||
let accounts = try? managedObjectContext.fetch(request),
|
||||
accounts.count > 1
|
||||
else { return nil }
|
||||
let accounts = AuthenticationServiceProvider.shared.authentications
|
||||
guard accounts.count > 1 else { return nil }
|
||||
|
||||
let nextSelectedAccountIndex: Int? = {
|
||||
for (index, account) in accounts.enumerated() {
|
||||
guard account == authContext.mastodonAuthenticationBox
|
||||
.authenticationRecord
|
||||
.object(in: managedObjectContext)
|
||||
.authentication
|
||||
else { continue }
|
||||
|
||||
let nextAccountIndex = index + 1
|
||||
|
@ -24,7 +24,7 @@ extension DataSourceFacade {
|
||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
guard let user = record.object(in: managedObjectContext) else { return }
|
||||
_ = Persistence.SearchHistory.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
@ -42,7 +42,7 @@ extension DataSourceFacade {
|
||||
switch tag {
|
||||
case .entity(let entity):
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
|
||||
let now = Date()
|
||||
|
||||
@ -68,7 +68,7 @@ extension DataSourceFacade {
|
||||
case .record(let record):
|
||||
try? await managedObjectContext.performChanges {
|
||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
guard let tag = record.object(in: managedObjectContext) else { return }
|
||||
|
||||
let now = Date()
|
||||
@ -99,7 +99,7 @@ extension DataSourceFacade {
|
||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let _ = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let _ = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
let request = SearchHistory.sortedFetchRequest
|
||||
request.predicate = SearchHistory.predicate(
|
||||
domain: authenticationBox.domain,
|
||||
|
@ -21,10 +21,8 @@ final class AccountListViewModel: NSObject {
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let mastodonAuthenticationFetchedResultsController: NSFetchedResultsController<MastodonAuthentication>
|
||||
|
||||
// output
|
||||
@Published var authentications: [ManagedObjectRecord<MastodonAuthentication>] = []
|
||||
@Published var items: [Item] = []
|
||||
|
||||
let dataSourceDidUpdate = PassthroughSubject<Void, Never>()
|
||||
@ -33,30 +31,11 @@ final class AccountListViewModel: NSObject {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.mastodonAuthenticationFetchedResultsController = {
|
||||
let fetchRequest = MastodonAuthentication.sortedFetchRequest
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
fetchRequest.fetchBatchSize = 20
|
||||
let controller = NSFetchedResultsController(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
return controller
|
||||
}()
|
||||
|
||||
super.init()
|
||||
// end init
|
||||
|
||||
mastodonAuthenticationFetchedResultsController.delegate = self
|
||||
do {
|
||||
try mastodonAuthenticationFetchedResultsController.performFetch()
|
||||
authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecord } ?? []
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
|
||||
$authentications
|
||||
AuthenticationServiceProvider.shared.$authentications
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] authentications in
|
||||
guard let self = self else { return }
|
||||
@ -85,7 +64,7 @@ extension AccountListViewModel {
|
||||
}
|
||||
|
||||
enum Item: Hashable {
|
||||
case authentication(record: ManagedObjectRecord<MastodonAuthentication>)
|
||||
case authentication(record: MastodonAuthentication)
|
||||
case addAccount
|
||||
}
|
||||
|
||||
@ -97,12 +76,12 @@ extension AccountListViewModel {
|
||||
switch item {
|
||||
case .authentication(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AccountListTableViewCell.self), for: indexPath) as! AccountListTableViewCell
|
||||
if let authentication = record.object(in: managedObjectContext),
|
||||
let activeAuthentication = self.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)
|
||||
if let activeAuthentication = AuthenticationServiceProvider.shared.authenticationSortedByActivation().first
|
||||
{
|
||||
AccountListViewModel.configure(
|
||||
in: managedObjectContext,
|
||||
cell: cell,
|
||||
authentication: authentication,
|
||||
authentication: record,
|
||||
activeAuthentication: activeAuthentication
|
||||
)
|
||||
}
|
||||
@ -119,11 +98,12 @@ extension AccountListViewModel {
|
||||
}
|
||||
|
||||
static func configure(
|
||||
in context: NSManagedObjectContext,
|
||||
cell: AccountListTableViewCell,
|
||||
authentication: MastodonAuthentication,
|
||||
activeAuthentication: MastodonAuthentication
|
||||
) {
|
||||
let user = authentication.user
|
||||
guard let user = authentication.user(in: context) else { return }
|
||||
|
||||
// avatar
|
||||
cell.avatarButton.avatarImageView.configure(
|
||||
@ -168,16 +148,3 @@ extension AccountListViewModel {
|
||||
.joined(separator: ", ")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
extension AccountListViewModel: NSFetchedResultsControllerDelegate {
|
||||
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
guard controller === mastodonAuthenticationFetchedResultsController else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecord } ?? []
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -66,8 +66,7 @@ extension AccountListViewController: PanModalPresentable {
|
||||
return .contentHeight(CGFloat(height))
|
||||
}
|
||||
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
let authenticationCount = (try? context.managedObjectContext.count(for: request)) ?? 0
|
||||
let authenticationCount = AuthenticationServiceProvider.shared.authentications.count
|
||||
|
||||
let count = authenticationCount + 1
|
||||
let height = calculateHeight(of: count)
|
||||
@ -165,9 +164,8 @@ extension AccountListViewController: UITableViewDelegate {
|
||||
switch item {
|
||||
case .authentication(let record):
|
||||
assert(Thread.isMainThread)
|
||||
guard let authentication = record.object(in: context.managedObjectContext) else { return }
|
||||
Task { @MainActor in
|
||||
let isActive = try await context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID)
|
||||
let isActive = try await context.authenticationService.activeMastodonUser(domain: record.domain, userID: record.userID)
|
||||
guard isActive else { return }
|
||||
self.coordinator.setup()
|
||||
} // end Task
|
||||
|
@ -211,7 +211,8 @@ extension HomeTimelineViewController {
|
||||
|
||||
let userDoesntFollowPeople: Bool
|
||||
if let managedObjectContext = self?.context.managedObjectContext,
|
||||
let me = self?.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user {
|
||||
let authContext = self?.authContext,
|
||||
let me = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext){
|
||||
userDoesntFollowPeople = me.followersCount == 0
|
||||
} else {
|
||||
userDoesntFollowPeople = true
|
||||
|
@ -293,9 +293,8 @@ extension MastodonLoginViewController: MastodonLoginViewModelDelegate {
|
||||
snapshot.appendSections([MastodonLoginViewSection.servers])
|
||||
snapshot.appendItems(viewModel.filteredServers)
|
||||
|
||||
dataSource?.apply(snapshot, animatingDifferences: false)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.dataSource?.apply(snapshot, animatingDifferences: false)
|
||||
let numberOfResults = viewModel.filteredServers.count
|
||||
self.contentView.updateCorners(numberOfResults: numberOfResults)
|
||||
}
|
||||
|
@ -193,41 +193,26 @@ extension AuthenticationViewModel {
|
||||
domain: info.domain,
|
||||
authorization: authorization
|
||||
)
|
||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
||||
.tryMap { response -> Mastodon.Response.Content<Mastodon.Entity.Account> in
|
||||
let account = response.value
|
||||
let mastodonUserRequest = MastodonUser.sortedFetchRequest
|
||||
mastodonUserRequest.predicate = MastodonUser.predicate(domain: info.domain, id: account.id)
|
||||
mastodonUserRequest.fetchLimit = 1
|
||||
guard let mastodonUser = try? managedObjectContext.fetch(mastodonUserRequest).first else {
|
||||
return Fail(error: AuthenticationError.badCredentials).eraseToAnyPublisher()
|
||||
throw AuthenticationError.badCredentials
|
||||
}
|
||||
|
||||
let property = MastodonAuthentication.Property(
|
||||
domain: info.domain,
|
||||
AuthenticationServiceProvider.shared
|
||||
.authentications
|
||||
.insert(MastodonAuthentication.createFrom(domain: info.domain,
|
||||
userID: mastodonUser.id,
|
||||
username: mastodonUser.username,
|
||||
appAccessToken: userToken.accessToken, // TODO: swap app token
|
||||
userAccessToken: userToken.accessToken,
|
||||
clientID: info.clientID,
|
||||
clientSecret: info.clientSecret
|
||||
)
|
||||
return managedObjectContext.performChanges {
|
||||
_ = APIService.CoreData.createOrMergeMastodonAuthentication(
|
||||
into: managedObjectContext,
|
||||
for: mastodonUser,
|
||||
in: info.domain,
|
||||
property: property,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result in
|
||||
switch result {
|
||||
case .failure(let error): throw error
|
||||
case .success: return response
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
clientSecret: info.clientSecret), at: 0)
|
||||
|
||||
return response
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ final class FollowedTagsViewModel: NSObject {
|
||||
self.fetchedResultsController = FollowedTagsFetchedResultController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
user: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)!.user
|
||||
user: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)! // fixme:
|
||||
)
|
||||
|
||||
super.init()
|
||||
|
@ -98,6 +98,7 @@ extension ProfileHeaderView.ViewModel {
|
||||
// follows you
|
||||
$relationshipActionOptionSet
|
||||
.map { $0.contains(.followingBy) && !$0.contains(.isMyself) }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { isFollowingBy in
|
||||
view.followsYouBlurEffectView.isHidden = !isFollowingBy
|
||||
}
|
||||
@ -182,16 +183,19 @@ extension ProfileHeaderView.ViewModel {
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
$relationshipActionOptionSet
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { optionSet in
|
||||
let isBlocking = optionSet.contains(.blocking)
|
||||
let isBlockedBy = optionSet.contains(.blockingBy)
|
||||
let isSuspended = optionSet.contains(.suspended)
|
||||
let isNeedsHidden = isBlocking || isBlockedBy || isSuspended
|
||||
|
||||
view.bioMetaText.textView.isHidden = isNeedsHidden
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
// dashboard
|
||||
$isMyself
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { isMyself in
|
||||
if isMyself {
|
||||
view.statusDashboardView.postDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myPosts
|
||||
@ -246,6 +250,7 @@ extension ProfileHeaderView.ViewModel {
|
||||
$isEditing,
|
||||
$isUpdating
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { relationshipActionOptionSet, isEditing, isUpdating in
|
||||
if relationshipActionOptionSet.contains(.edit) {
|
||||
// check .edit state and set .editing when isEditing
|
||||
|
@ -15,7 +15,7 @@ import MastodonSDK
|
||||
final class MeProfileViewModel: ProfileViewModel {
|
||||
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
let user = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
let user = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
super.init(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
@ -30,4 +30,26 @@ final class MeProfileViewModel: ProfileViewModel {
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
Task {
|
||||
do {
|
||||
|
||||
_ = try await context.apiService.authenticatedUserInfo(authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||
|
||||
try await context.managedObjectContext.performChanges {
|
||||
guard let me = self.authContext.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
self.me = me
|
||||
}
|
||||
} catch {
|
||||
// do nothing?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -273,6 +273,8 @@ extension ProfileViewController {
|
||||
bindTitleView()
|
||||
bindMoreBarButtonItem()
|
||||
bindPager()
|
||||
|
||||
viewModel.viewDidLoad()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
@ -935,11 +937,6 @@ extension ProfileViewController: PagerTabStripNavigateable {
|
||||
|
||||
private extension ProfileViewController {
|
||||
var currentInstance: Instance? {
|
||||
guard let authenticationRecord = authContext.mastodonAuthenticationBox
|
||||
.authenticationRecord
|
||||
.object(in: context.managedObjectContext)
|
||||
else { return nil }
|
||||
|
||||
return authenticationRecord.instance
|
||||
authContext.mastodonAuthenticationBox.authentication.instance(in: context.managedObjectContext)
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ class ProfileViewModel: NSObject {
|
||||
super.init()
|
||||
|
||||
// bind me
|
||||
self.me = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
self.me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
$me
|
||||
.assign(to: \.me, on: relationshipViewModel)
|
||||
.store(in: &disposeBag)
|
||||
@ -171,9 +171,10 @@ class ProfileViewModel: NSObject {
|
||||
.assign(to: &$isPagingEnabled)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ProfileViewModel {
|
||||
func viewDidLoad() {
|
||||
|
||||
}
|
||||
|
||||
// fetch profile info before edit
|
||||
func fetchEditProfileInfo() -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||
|
@ -59,7 +59,7 @@ class ReportResultViewModel: ObservableObject {
|
||||
|
||||
Task { @MainActor in
|
||||
guard let user = user.object(in: context.managedObjectContext) else { return }
|
||||
guard let me = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user else { return }
|
||||
guard let me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else { return }
|
||||
self.relationshipViewModel.user = user
|
||||
self.relationshipViewModel.me = me
|
||||
|
||||
|
@ -258,7 +258,11 @@ extension MainTabBarController {
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
if let user = authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user {
|
||||
NotificationCenter.default.publisher(for: .userFetched)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
if let user = self.authContext?.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) {
|
||||
self.avatarURLObserver = user.publisher(for: \.avatar)
|
||||
.sink { [weak self, weak user] _ in
|
||||
guard let self = self else { return }
|
||||
@ -272,14 +276,16 @@ extension MainTabBarController {
|
||||
guard let profileTabItem = _profileTabItem else { return }
|
||||
profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(user.displayNameWithFallback)
|
||||
|
||||
context.authenticationService.updateActiveUserAccountPublisher
|
||||
self.context.authenticationService.updateActiveUserAccountPublisher
|
||||
.sink { [weak self] in
|
||||
self?.updateUserAccount()
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
.store(in: &self.disposeBag)
|
||||
} else {
|
||||
self.avatarURLObserver = nil
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer()
|
||||
tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:)))
|
||||
@ -451,9 +457,9 @@ extension MainTabBarController {
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
if let user = authContext.mastodonAuthenticationBox.authenticationRecord.object(
|
||||
if let user = authContext.mastodonAuthenticationBox.authentication.user(
|
||||
in: context.managedObjectContext
|
||||
)?.user {
|
||||
) {
|
||||
user.update(
|
||||
property: .init(
|
||||
entity: profileResponse.value,
|
||||
|
@ -75,7 +75,7 @@ extension SidebarViewModel {
|
||||
let imageURL: URL? = {
|
||||
switch item {
|
||||
case .me:
|
||||
let user = self.authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user
|
||||
let user = self.authContext?.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext)
|
||||
return user?.avatarImageURL()
|
||||
default:
|
||||
return nil
|
||||
@ -132,7 +132,7 @@ extension SidebarViewModel {
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
case .me:
|
||||
guard let user = self.authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user else { return }
|
||||
guard let user = self.authContext?.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else { return }
|
||||
let currentUserDisplayName = user.displayNameWithFallback
|
||||
cell.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName)
|
||||
default:
|
||||
|
@ -79,7 +79,7 @@ extension SearchResultOverviewCoordinator: SearchResultsOverviewTableViewControl
|
||||
return Persistence.Status.fetch(in: managedObjectContext, context: Persistence.Status.PersistContext(
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
entity: status,
|
||||
me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user,
|
||||
me: authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext),
|
||||
statusCache: nil,
|
||||
userCache: nil,
|
||||
networkDate: Date()))
|
||||
|
@ -126,7 +126,7 @@ extension SearchResultSection {
|
||||
configuration: Configuration
|
||||
) {
|
||||
cell.configure(
|
||||
me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user,
|
||||
me: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext),
|
||||
tableView: tableView,
|
||||
viewModel: viewModel,
|
||||
delegate: configuration.userTableViewCellDelegate
|
||||
|
@ -17,6 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
let appContext = AppContext()
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
AuthenticationServiceProvider.shared.restore()
|
||||
|
||||
AppSecret.default.register()
|
||||
|
||||
|
@ -185,11 +185,8 @@ extension SceneDelegate {
|
||||
assertionFailure()
|
||||
return false
|
||||
}
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken)
|
||||
request.fetchLimit = 1
|
||||
|
||||
guard let authentication = try? coordinator.appContext.managedObjectContext.fetch(request).first else {
|
||||
guard let authentication = AuthenticationServiceProvider.shared.getAuthentication(matching: accessToken) else {
|
||||
assertionFailure()
|
||||
return false
|
||||
}
|
||||
|
@ -50,9 +50,8 @@ extension SendPostIntentHandler: SendPostIntentHandling {
|
||||
let mastodonAuthentications: [MastodonAuthentication]
|
||||
let accounts = intent.accounts ?? []
|
||||
if accounts.isEmpty {
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
let authentications = try managedObjectContext.fetch(request)
|
||||
let _authentication = authentications.sorted(by: { $0.activedAt > $1.activedAt }).first
|
||||
// fixme: refactor this and implemented method on AuthenticationServiceProvider
|
||||
let _authentication = AuthenticationServiceProvider.shared.authentications.sorted(by: { $0.activedAt > $1.activedAt }).first
|
||||
|
||||
guard let authentication = _authentication else {
|
||||
let failureReason = APIService.APIError.implicit(.authenticationMissing).errorDescription ?? "Fail to Send Post"
|
||||
@ -65,12 +64,12 @@ extension SendPostIntentHandler: SendPostIntentHandling {
|
||||
|
||||
let authenticationBoxes = mastodonAuthentications.map { authentication in
|
||||
MastodonAuthenticationBox(
|
||||
authenticationRecord: .init(objectID: authentication.objectID),
|
||||
authentication: authentication,
|
||||
domain: authentication.domain,
|
||||
userID: authentication.userID,
|
||||
appAuthorization: .init(accessToken: authentication.appAccessToken),
|
||||
userAuthorization: .init(accessToken: authentication.userAccessToken),
|
||||
inMemoryCache: .sharedCache(for: authentication.objectID.description)
|
||||
inMemoryCache: .sharedCache(for: authentication.userAccessToken)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,11 @@ extension Account {
|
||||
static func fetch(in managedObjectContext: NSManagedObjectContext) async throws -> [Account] {
|
||||
// get accounts
|
||||
let accounts: [Account] = try await managedObjectContext.perform {
|
||||
let results = try MastodonAuthentication.fetch(in: managedObjectContext)
|
||||
let results = AuthenticationServiceProvider.shared.authentications
|
||||
let accounts = results.compactMap { mastodonAuthentication -> Account? in
|
||||
let user = mastodonAuthentication.user
|
||||
guard let user = mastodonAuthentication.user(in: managedObjectContext) else {
|
||||
return nil
|
||||
}
|
||||
let account = Account(
|
||||
identifier: mastodonAuthentication.identifier.uuidString,
|
||||
display: user.displayNameWithFallback,
|
||||
@ -43,9 +45,7 @@ extension Array where Element == Account {
|
||||
let identifiers = self
|
||||
.compactMap { $0.identifier }
|
||||
.compactMap { UUID(uuidString: $0) }
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
request.predicate = MastodonAuthentication.predicate(identifiers: identifiers)
|
||||
let results = try managedObjectContext.fetch(request)
|
||||
let results = AuthenticationServiceProvider.shared.authentications.filter({ identifiers.contains($0.identifier) })
|
||||
return results
|
||||
}
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// MastodonAuthentication.swift
|
||||
// MastodonIntent
|
||||
//
|
||||
// Created by MainasuK on 2022-6-9.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
|
||||
extension MastodonAuthentication {
|
||||
|
||||
static func fetch(in managedObjectContext: NSManagedObjectContext) throws -> [MastodonAuthentication] {
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
let results = try managedObjectContext.fetch(request)
|
||||
return results
|
||||
}
|
||||
|
||||
}
|
@ -65,7 +65,7 @@
|
||||
<attribute name="version" optional="YES" attributeType="String"/>
|
||||
<relationship name="authentications" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonAuthentication" inverseName="instance" inverseEntity="MastodonAuthentication"/>
|
||||
</entity>
|
||||
<entity name="MastodonAuthentication" representedClassName="CoreDataStack.MastodonAuthentication" syncable="YES">
|
||||
<entity name="MastodonAuthentication" representedClassName="CoreDataStack.MastodonAuthenticationLegacy" syncable="YES">
|
||||
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="appAccessToken" attributeType="String"/>
|
||||
<attribute name="clientID" attributeType="String"/>
|
||||
|
@ -65,7 +65,7 @@
|
||||
<attribute name="version" optional="YES" attributeType="String"/>
|
||||
<relationship name="authentications" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonAuthentication" inverseName="instance" inverseEntity="MastodonAuthentication"/>
|
||||
</entity>
|
||||
<entity name="MastodonAuthentication" representedClassName="CoreDataStack.MastodonAuthentication" syncable="YES">
|
||||
<entity name="MastodonAuthentication" representedClassName="CoreDataStack.MastodonAuthenticationLegacy" syncable="YES">
|
||||
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="appAccessToken" attributeType="String"/>
|
||||
<attribute name="clientID" attributeType="String"/>
|
||||
|
@ -20,9 +20,14 @@ public final class CoreDataStack {
|
||||
self.storeDescriptions = storeDescriptions
|
||||
}
|
||||
|
||||
public convenience init(databaseName: String = "shared") {
|
||||
public convenience init(databaseName: String = "shared", isInMemory: Bool = false) {
|
||||
let storeURL = URL.storeURL(for: AppName.groupID, databaseName: databaseName)
|
||||
let storeDescription = NSPersistentStoreDescription(url: storeURL)
|
||||
let storeDescription: NSPersistentStoreDescription
|
||||
if isInMemory {
|
||||
storeDescription = NSPersistentStoreDescription(url: URL(string: "file:///dev/null")!) /// in-memory store with all features in favor of NSInMemoryStoreType
|
||||
} else {
|
||||
storeDescription = NSPersistentStoreDescription(url: storeURL)
|
||||
}
|
||||
self.init(persistentStoreDescriptions: [storeDescription])
|
||||
}
|
||||
|
||||
@ -115,16 +120,18 @@ extension CoreDataStack {
|
||||
}
|
||||
}
|
||||
|
||||
extension CoreDataStack {
|
||||
|
||||
public func rebuild() {
|
||||
public extension CoreDataStack {
|
||||
func tearDown() {
|
||||
let oldStoreURL = persistentContainer.persistentStoreCoordinator.url(for: persistentContainer.persistentStoreCoordinator.persistentStores.first!)
|
||||
try! persistentContainer.persistentStoreCoordinator.destroyPersistentStore(at: oldStoreURL, ofType: NSSQLiteStoreType, options: nil)
|
||||
}
|
||||
|
||||
func rebuild() {
|
||||
tearDown()
|
||||
|
||||
CoreDataStack.load(persistentContainer: persistentContainer) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.didFinishLoad.value = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ public final class Instance: NSManagedObject {
|
||||
@NSManaged public private(set) var configurationV2Raw: Data?
|
||||
|
||||
// MARK: one-to-many relationships
|
||||
@NSManaged public var authentications: Set<MastodonAuthentication>
|
||||
@NSManaged public var authentications: Set<MastodonAuthenticationLegacy>
|
||||
}
|
||||
|
||||
extension Instance {
|
||||
|
@ -8,7 +8,8 @@
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
final public class MastodonAuthentication: NSManagedObject {
|
||||
@objc(MastodonAuthentication)
|
||||
final public class MastodonAuthenticationLegacy: NSManagedObject {
|
||||
|
||||
public typealias ID = UUID
|
||||
|
||||
@ -35,16 +36,16 @@ final public class MastodonAuthentication: NSManagedObject {
|
||||
|
||||
}
|
||||
|
||||
extension MastodonAuthentication {
|
||||
extension MastodonAuthenticationLegacy {
|
||||
|
||||
public override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
|
||||
setPrimitiveValue(UUID(), forKey: #keyPath(MastodonAuthentication.identifier))
|
||||
setPrimitiveValue(UUID(), forKey: #keyPath(MastodonAuthenticationLegacy.identifier))
|
||||
let now = Date()
|
||||
setPrimitiveValue(now, forKey: #keyPath(MastodonAuthentication.createdAt))
|
||||
setPrimitiveValue(now, forKey: #keyPath(MastodonAuthentication.updatedAt))
|
||||
setPrimitiveValue(now, forKey: #keyPath(MastodonAuthentication.activedAt))
|
||||
setPrimitiveValue(now, forKey: #keyPath(MastodonAuthenticationLegacy.createdAt))
|
||||
setPrimitiveValue(now, forKey: #keyPath(MastodonAuthenticationLegacy.updatedAt))
|
||||
setPrimitiveValue(now, forKey: #keyPath(MastodonAuthenticationLegacy.activedAt))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@ -52,8 +53,8 @@ extension MastodonAuthentication {
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
user: MastodonUser
|
||||
) -> MastodonAuthentication {
|
||||
let authentication: MastodonAuthentication = context.insertObject()
|
||||
) -> MastodonAuthenticationLegacy {
|
||||
let authentication: MastodonAuthenticationLegacy = context.insertObject()
|
||||
|
||||
authentication.domain = property.domain
|
||||
authentication.userID = property.userID
|
||||
@ -112,7 +113,7 @@ extension MastodonAuthentication {
|
||||
|
||||
}
|
||||
|
||||
extension MastodonAuthentication {
|
||||
extension MastodonAuthenticationLegacy {
|
||||
public struct Property {
|
||||
|
||||
public let domain: String
|
||||
@ -144,51 +145,51 @@ extension MastodonAuthentication {
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonAuthentication: Managed {
|
||||
extension MastodonAuthenticationLegacy: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \MastodonAuthentication.createdAt, ascending: false)]
|
||||
return [NSSortDescriptor(keyPath: \MastodonAuthenticationLegacy.createdAt, ascending: false)]
|
||||
}
|
||||
|
||||
public static var activeSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \MastodonAuthentication.activedAt, ascending: false)]
|
||||
return [NSSortDescriptor(keyPath: \MastodonAuthenticationLegacy.activedAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonAuthentication {
|
||||
public static var activeSortedFetchRequest: NSFetchRequest<MastodonAuthentication> {
|
||||
let request = NSFetchRequest<MastodonAuthentication>(entityName: entityName)
|
||||
extension MastodonAuthenticationLegacy {
|
||||
public static var activeSortedFetchRequest: NSFetchRequest<MastodonAuthenticationLegacy> {
|
||||
let request = NSFetchRequest<MastodonAuthenticationLegacy>(entityName: entityName)
|
||||
request.sortDescriptors = activeSortDescriptors
|
||||
return request
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonAuthentication {
|
||||
extension MastodonAuthenticationLegacy {
|
||||
|
||||
public static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.domain), domain)
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthenticationLegacy.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(userID: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.userID), userID)
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthenticationLegacy.userID), userID)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, userID: String) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
MastodonAuthentication.predicate(domain: domain),
|
||||
MastodonAuthentication.predicate(userID: userID)
|
||||
MastodonAuthenticationLegacy.predicate(domain: domain),
|
||||
MastodonAuthenticationLegacy.predicate(userID: userID)
|
||||
])
|
||||
}
|
||||
|
||||
public static func predicate(userAccessToken: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.userAccessToken), userAccessToken)
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthenticationLegacy.userAccessToken), userAccessToken)
|
||||
}
|
||||
|
||||
public static func predicate(identifier: UUID) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.identifier), identifier as NSUUID)
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthenticationLegacy.identifier), identifier as NSUUID)
|
||||
}
|
||||
|
||||
public static func predicate(identifiers: [UUID]) -> NSPredicate {
|
||||
return NSPredicate(format: "%K IN %@", #keyPath(MastodonAuthentication.identifier), identifiers as [NSUUID])
|
||||
return NSPredicate(format: "%K IN %@", #keyPath(MastodonAuthenticationLegacy.identifier), identifiers as [NSUUID])
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ final public class MastodonUser: NSManagedObject {
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var pinnedStatus: Status?
|
||||
@NSManaged public private(set) var mastodonAuthentication: MastodonAuthentication?
|
||||
@NSManaged public private(set) var mastodonAuthentication: MastodonAuthenticationLegacy?
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var statuses: Set<Status>
|
||||
|
@ -46,9 +46,18 @@ public class AppContext: ObservableObject {
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
public init() {
|
||||
|
||||
let authProvider = AuthenticationServiceProvider.shared
|
||||
let _coreDataStack = CoreDataStack()
|
||||
if authProvider.authenticationMigrationRequired {
|
||||
authProvider.migrateLegacyAuthentications(
|
||||
in: _coreDataStack.persistentContainer.viewContext
|
||||
)
|
||||
}
|
||||
|
||||
let _managedObjectContext = _coreDataStack.persistentContainer.viewContext
|
||||
let _backgroundManagedObjectContext = _coreDataStack.persistentContainer.newBackgroundContext()
|
||||
|
||||
coreDataStack = _coreDataStack
|
||||
managedObjectContext = _managedObjectContext
|
||||
backgroundManagedObjectContext = _backgroundManagedObjectContext
|
||||
|
@ -24,31 +24,12 @@ public class AuthContext {
|
||||
private init(mastodonAuthenticationBox: MastodonAuthenticationBox) {
|
||||
self.mastodonAuthenticationBox = mastodonAuthenticationBox
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AuthContext {
|
||||
|
||||
public convenience init?(authentication: MastodonAuthentication) {
|
||||
self.init(mastodonAuthenticationBox: MastodonAuthenticationBox(authentication: authentication))
|
||||
|
||||
ManagedObjectObserver.observe(object: authentication)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { _ in
|
||||
} receiveValue: { [weak self] change in
|
||||
guard let self = self else { return }
|
||||
switch change.changeType {
|
||||
case .update(let object):
|
||||
guard let authentication = object as? MastodonAuthentication else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
self.mastodonAuthenticationBox = .init(authentication: authentication)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
public struct MastodonAuthenticationBox: UserIdentifier {
|
||||
public let authenticationRecord: ManagedObjectRecord<MastodonAuthentication>
|
||||
public let authentication: MastodonAuthentication
|
||||
public let domain: String
|
||||
public let userID: MastodonUser.ID
|
||||
public let appAuthorization: Mastodon.API.OAuth.Authorization
|
||||
@ -18,14 +18,14 @@ public struct MastodonAuthenticationBox: UserIdentifier {
|
||||
public let inMemoryCache: MastodonAccountInMemoryCache
|
||||
|
||||
public init(
|
||||
authenticationRecord: ManagedObjectRecord<MastodonAuthentication>,
|
||||
authentication: MastodonAuthentication,
|
||||
domain: String,
|
||||
userID: MastodonUser.ID,
|
||||
appAuthorization: Mastodon.API.OAuth.Authorization,
|
||||
userAuthorization: Mastodon.API.OAuth.Authorization,
|
||||
inMemoryCache: MastodonAccountInMemoryCache
|
||||
) {
|
||||
self.authenticationRecord = authenticationRecord
|
||||
self.authentication = authentication
|
||||
self.domain = domain
|
||||
self.userID = userID
|
||||
self.appAuthorization = appAuthorization
|
||||
@ -38,12 +38,12 @@ extension MastodonAuthenticationBox {
|
||||
|
||||
init(authentication: MastodonAuthentication) {
|
||||
self = MastodonAuthenticationBox(
|
||||
authenticationRecord: .init(objectID: authentication.objectID),
|
||||
authentication: authentication,
|
||||
domain: authentication.domain,
|
||||
userID: authentication.userID,
|
||||
appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.appAccessToken),
|
||||
userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken),
|
||||
inMemoryCache: .sharedCache(for: authentication.objectID.description)
|
||||
inMemoryCache: .sharedCache(for: authentication.userID) // todo: make sure this is really unique
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,116 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import KeychainAccess
|
||||
import MastodonCommon
|
||||
import os.log
|
||||
|
||||
public class AuthenticationServiceProvider: ObservableObject {
|
||||
private let logger = Logger(subsystem: "AuthenticationServiceProvider", category: "Authentication")
|
||||
|
||||
public static let shared = AuthenticationServiceProvider()
|
||||
private static let keychain = Keychain(service: "org.joinmastodon.app.authentications", accessGroup: AppName.groupID)
|
||||
private let userDefaults: UserDefaults = .shared
|
||||
|
||||
private init() {}
|
||||
|
||||
@Published public var authentications: [MastodonAuthentication] = [] {
|
||||
didSet {
|
||||
persist() // todo: Is this too heavy and too often here???
|
||||
}
|
||||
}
|
||||
|
||||
func update(instance: Instance, where domain: String) {
|
||||
authentications = authentications.map { authentication in
|
||||
guard authentication.domain == domain else { return authentication }
|
||||
return authentication.updating(instance: instance)
|
||||
}
|
||||
}
|
||||
|
||||
func delete(authentication: MastodonAuthentication) {
|
||||
authentications.removeAll(where: { $0 == authentication })
|
||||
}
|
||||
|
||||
func activateAuthentication(in domain: String, for userID: String) {
|
||||
authentications = authentications.map { authentication in
|
||||
guard authentication.domain == domain, authentication.userID == userID else {
|
||||
return authentication
|
||||
}
|
||||
return authentication.updating(activatedAt: Date())
|
||||
}
|
||||
}
|
||||
|
||||
func getAuthentication(in domain: String, for userID: String) -> MastodonAuthentication? {
|
||||
authentications.first(where: { $0.domain == domain && $0.userID == userID })
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
public extension AuthenticationServiceProvider {
|
||||
func getAuthentication(matching userAccessToken: String) -> MastodonAuthentication? {
|
||||
authentications.first(where: { $0.userAccessToken == userAccessToken })
|
||||
}
|
||||
|
||||
func authenticationSortedByActivation() -> [MastodonAuthentication] { // fixme: why do we need this?
|
||||
return authentications.sorted(by: { $0.activedAt > $1.activedAt })
|
||||
}
|
||||
|
||||
func restore() {
|
||||
authentications = Self.keychain.allKeys().compactMap {
|
||||
guard
|
||||
let encoded = Self.keychain[$0],
|
||||
let data = Data(base64Encoded: encoded)
|
||||
else { return nil }
|
||||
return try? JSONDecoder().decode(MastodonAuthentication.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
func migrateLegacyAuthentications(in context: NSManagedObjectContext) {
|
||||
do {
|
||||
let legacyAuthentications = try context.fetch(MastodonAuthenticationLegacy.sortedFetchRequest)
|
||||
let migratedAuthentications = legacyAuthentications.compactMap { auth -> MastodonAuthentication? in
|
||||
return MastodonAuthentication(
|
||||
identifier: auth.identifier,
|
||||
domain: auth.domain,
|
||||
username: auth.username,
|
||||
appAccessToken: auth.appAccessToken,
|
||||
userAccessToken: auth.userAccessToken,
|
||||
clientID: auth.clientID,
|
||||
clientSecret: auth.clientSecret,
|
||||
createdAt: auth.createdAt,
|
||||
updatedAt: auth.updatedAt,
|
||||
activedAt: auth.activedAt,
|
||||
userID: auth.userID
|
||||
)
|
||||
}
|
||||
|
||||
if migratedAuthentications.count != legacyAuthentications.count {
|
||||
logger.log(level: .default, "Not all account authentications could be migrated.")
|
||||
} else {
|
||||
logger.log(level: .default, "All account authentications were successful.")
|
||||
}
|
||||
|
||||
self.authentications = migratedAuthentications
|
||||
userDefaults.didMigrateAuthentications = true
|
||||
} catch {
|
||||
userDefaults.didMigrateAuthentications = false
|
||||
logger.log(level: .error, "Could not migrate legacy authentications")
|
||||
}
|
||||
}
|
||||
|
||||
var authenticationMigrationRequired: Bool {
|
||||
userDefaults.didMigrateAuthentications == false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
private extension AuthenticationServiceProvider {
|
||||
func persist() {
|
||||
for authentication in authentications {
|
||||
Self.keychain[authentication.persistenceIdentifier] = try? JSONEncoder().encode(authentication).base64EncodedString()
|
||||
}
|
||||
}
|
||||
}
|
107
MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift
Normal file
107
MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
public struct MastodonAuthentication: Codable, Hashable {
|
||||
public typealias ID = UUID
|
||||
|
||||
public private(set) var identifier: ID
|
||||
public private(set) var domain: String
|
||||
public private(set) var username: String
|
||||
|
||||
public private(set) var appAccessToken: String
|
||||
public private(set) var userAccessToken: String
|
||||
public private(set) var clientID: String
|
||||
public private(set) var clientSecret: String
|
||||
|
||||
public private(set) var createdAt: Date
|
||||
public private(set) var updatedAt: Date
|
||||
public private(set) var activedAt: Date
|
||||
|
||||
public private(set) var userID: String
|
||||
public private(set) var instanceObjectIdURI: URL?
|
||||
|
||||
internal var persistenceIdentifier: String {
|
||||
"\(username)@\(domain)"
|
||||
}
|
||||
|
||||
public static func createFrom(
|
||||
domain: String,
|
||||
userID: String,
|
||||
username: String,
|
||||
appAccessToken: String,
|
||||
userAccessToken: String,
|
||||
clientID: String,
|
||||
clientSecret: String
|
||||
) -> Self {
|
||||
let now = Date()
|
||||
return MastodonAuthentication(
|
||||
identifier: .init(),
|
||||
domain: domain,
|
||||
username: username,
|
||||
appAccessToken: appAccessToken,
|
||||
userAccessToken: userAccessToken,
|
||||
clientID: clientID,
|
||||
clientSecret: clientSecret,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
activedAt: now,
|
||||
userID: userID,
|
||||
instanceObjectIdURI: nil
|
||||
)
|
||||
}
|
||||
|
||||
func copy(
|
||||
identifier: ID? = nil,
|
||||
domain: String? = nil,
|
||||
username: String? = nil,
|
||||
appAccessToken: String? = nil,
|
||||
userAccessToken: String? = nil,
|
||||
clientID: String? = nil,
|
||||
clientSecret: String? = nil,
|
||||
createdAt: Date? = nil,
|
||||
updatedAt: Date? = nil,
|
||||
activedAt: Date? = nil,
|
||||
userID: String? = nil,
|
||||
instanceObjectIdURI: URL? = nil
|
||||
) -> Self {
|
||||
MastodonAuthentication(
|
||||
identifier: identifier ?? self.identifier,
|
||||
domain: domain ?? self.domain,
|
||||
username: username ?? self.username,
|
||||
appAccessToken: appAccessToken ?? self.appAccessToken,
|
||||
userAccessToken: userAccessToken ?? self.userAccessToken,
|
||||
clientID: clientID ?? self.clientID,
|
||||
clientSecret: clientSecret ?? self.clientSecret,
|
||||
createdAt: createdAt ?? self.createdAt,
|
||||
updatedAt: updatedAt ?? self.updatedAt,
|
||||
activedAt: activedAt ?? self.activedAt,
|
||||
userID: userID ?? self.userID,
|
||||
instanceObjectIdURI: instanceObjectIdURI ?? self.instanceObjectIdURI
|
||||
)
|
||||
}
|
||||
|
||||
public func instance(in context: NSManagedObjectContext) -> Instance? {
|
||||
guard
|
||||
let instanceObjectIdURI = instanceObjectIdURI,
|
||||
let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: instanceObjectIdURI)
|
||||
else { return nil }
|
||||
|
||||
return try? context.existingObject(with: objectID) as? Instance
|
||||
}
|
||||
|
||||
public func user(in context: NSManagedObjectContext) -> MastodonUser? {
|
||||
let userPredicate = MastodonUser.predicate(domain: domain, id: userID)
|
||||
return MastodonUser.findOrFetch(in: context, matching: userPredicate)
|
||||
}
|
||||
|
||||
func updating(instance: Instance) -> Self {
|
||||
copy(instanceObjectIdURI: instance.objectID.uriRepresentation())
|
||||
}
|
||||
|
||||
func updating(activatedAt: Date) -> Self {
|
||||
copy(activedAt: activatedAt)
|
||||
}
|
||||
}
|
@ -167,7 +167,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
|
||||
for entity in response.value {
|
||||
_ = Persistence.Tag.createOrMerge(
|
||||
|
@ -67,12 +67,15 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
let blockContext: MastodonBlockContext = try await managedObjectContext.performChanges {
|
||||
guard let user = user.object(in: managedObjectContext),
|
||||
let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext)
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let user = user.object(in: managedObjectContext),
|
||||
let me = authentication.user(in: managedObjectContext)
|
||||
else {
|
||||
throw APIError.implicit(.badRequest)
|
||||
}
|
||||
let me = authentication.user
|
||||
|
||||
let isBlocking = user.blockingBy.contains(me)
|
||||
let isFollowing = user.followingBy.contains(me)
|
||||
// toggle block state
|
||||
@ -116,10 +119,13 @@ extension APIService {
|
||||
}
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let user = user.object(in: managedObjectContext),
|
||||
let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext)
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let user = user.object(in: managedObjectContext),
|
||||
let me = authentication.user(in: managedObjectContext)
|
||||
else { return }
|
||||
let me = authentication.user
|
||||
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
|
@ -27,12 +27,15 @@ extension APIService {
|
||||
|
||||
// update bookmark state and retrieve bookmark context
|
||||
let bookmarkContext: MastodonBookmarkContext = try await managedObjectContext.performChanges {
|
||||
guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext),
|
||||
let _status = record.object(in: managedObjectContext)
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let _status = record.object(in: managedObjectContext),
|
||||
let me = authentication.user(in: managedObjectContext)
|
||||
else {
|
||||
throw APIError.implicit(.badRequest)
|
||||
}
|
||||
let me = authentication.user
|
||||
|
||||
let status = _status.reblog ?? _status
|
||||
let isBookmarked = status.bookmarkedBy.contains(me)
|
||||
status.update(bookmarked: !isBookmarked, by: me)
|
||||
@ -60,10 +63,13 @@ extension APIService {
|
||||
|
||||
// update bookmark state
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext),
|
||||
let _status = record.object(in: managedObjectContext)
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let _status = record.object(in: managedObjectContext),
|
||||
let me = authentication.user(in: managedObjectContext)
|
||||
else { return }
|
||||
let me = authentication.user
|
||||
|
||||
let status = _status.reblog ?? _status
|
||||
|
||||
switch result {
|
||||
@ -108,7 +114,10 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else {
|
||||
|
||||
guard
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
@ -28,12 +28,15 @@ extension APIService {
|
||||
|
||||
// update like state and retrieve like context
|
||||
let favoriteContext: MastodonFavoriteContext = try await managedObjectContext.performChanges {
|
||||
guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext),
|
||||
let _status = record.object(in: managedObjectContext)
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let _status = record.object(in: managedObjectContext),
|
||||
let me = authentication.user(in: managedObjectContext)
|
||||
else {
|
||||
throw APIError.implicit(.badRequest)
|
||||
}
|
||||
let me = authentication.user
|
||||
|
||||
let status = _status.reblog ?? _status
|
||||
let isFavorited = status.favouritedBy.contains(me)
|
||||
let favoritedCount = status.favouritesCount
|
||||
@ -65,10 +68,13 @@ extension APIService {
|
||||
|
||||
// update like state
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext),
|
||||
let _status = record.object(in: managedObjectContext)
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let _status = record.object(in: managedObjectContext),
|
||||
let me = authentication.user(in: managedObjectContext)
|
||||
else { return }
|
||||
let me = authentication.user
|
||||
|
||||
let status = _status.reblog ?? _status
|
||||
|
||||
switch result {
|
||||
@ -117,7 +123,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
let _followContext: MastodonFollowContext? = try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return nil }
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return nil }
|
||||
guard let user = user.object(in: managedObjectContext) else { return nil }
|
||||
|
||||
let isFollowing = user.followingBy.contains(me)
|
||||
@ -88,7 +88,7 @@ extension APIService {
|
||||
|
||||
// update friendship state
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user,
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext),
|
||||
let user = user.object(in: managedObjectContext)
|
||||
else { return }
|
||||
|
||||
@ -120,10 +120,9 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
guard let user = user.object(in: managedObjectContext),
|
||||
let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext)
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
else { throw APIError.implicit(.badRequest) }
|
||||
|
||||
let me = authentication.user
|
||||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||||
|
||||
let oldShowReblogs = me.showingReblogsBy.contains(user)
|
||||
@ -144,7 +143,7 @@ extension APIService {
|
||||
}
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
|
@ -35,7 +35,7 @@ extension APIService {
|
||||
)
|
||||
request.fetchLimit = 1
|
||||
guard let user = managedObjectContext.safeFetch(request).first else { return }
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
|
||||
Persistence.MastodonUser.update(
|
||||
mastodonUser: user,
|
||||
|
@ -35,7 +35,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
|
||||
for entity in response.value {
|
||||
let result = Persistence.MastodonUser.createOrMerge(
|
||||
|
@ -36,7 +36,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
|
||||
for entity in response.value {
|
||||
let result = Persistence.MastodonUser.createOrMerge(
|
||||
|
@ -44,7 +44,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
|
||||
for entity in response.value {
|
||||
_ = Persistence.Status.createOrMerge(
|
||||
|
@ -11,6 +11,10 @@ import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
public extension Foundation.Notification.Name {
|
||||
static let userFetched = Notification.Name(rawValue: "org.joinmastodon.app.user-fetched")
|
||||
}
|
||||
|
||||
extension APIService {
|
||||
|
||||
public func homeTimeline(
|
||||
@ -38,8 +42,20 @@ extension APIService {
|
||||
).singleOutput()
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
|
||||
// FIXME: This is a dirty hack to make the performance-stuff work.
|
||||
// Problem is, that we don't persist the user on disk anymore. So we have to fetch
|
||||
// it when we need it to display on the home timeline.
|
||||
for authentication in AuthenticationServiceProvider.shared.authentications {
|
||||
_ = try await accountInfo(domain: authentication.domain,
|
||||
userID: authentication.userID,
|
||||
authorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken)).value
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: .userFetched, object: nil)
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
@ -66,13 +66,15 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
let muteContext: MastodonMuteContext = try await managedObjectContext.performChanges {
|
||||
guard let user = user.object(in: managedObjectContext),
|
||||
let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext)
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let user = user.object(in: managedObjectContext),
|
||||
let me = authentication.user(in: managedObjectContext)
|
||||
else {
|
||||
throw APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
let me = authentication.user
|
||||
let isMuting = user.mutingBy.contains(me)
|
||||
|
||||
// toggle mute state
|
||||
@ -112,9 +114,8 @@ extension APIService {
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let user = user.object(in: managedObjectContext),
|
||||
let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext)
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
else { return }
|
||||
let me = authentication.user
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
|
@ -88,7 +88,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
@ -176,7 +176,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
_ = Persistence.Notification.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.Notification.PersistContext(
|
||||
|
@ -35,7 +35,7 @@ extension APIService {
|
||||
).singleOutput()
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
_ = Persistence.Poll.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.Poll.PersistContext(
|
||||
@ -78,7 +78,7 @@ extension APIService {
|
||||
).singleOutput()
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
_ = Persistence.Poll.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.Poll.PersistContext(
|
||||
|
@ -29,7 +29,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
for entity in response.value {
|
||||
_ = Persistence.Status.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
|
@ -27,11 +27,13 @@ extension APIService {
|
||||
|
||||
// update repost state and retrieve repost context
|
||||
let _reblogContext: MastodonReblogContext? = try await managedObjectContext.performChanges {
|
||||
guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext),
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let me = authentication.user(in: managedObjectContext),
|
||||
let _status = record.object(in: managedObjectContext)
|
||||
else { return nil }
|
||||
|
||||
let me = authentication.user
|
||||
let status = _status.reblog ?? _status
|
||||
let isReblogged = status.rebloggedBy.contains(me)
|
||||
let rebloggedCount = status.reblogsCount
|
||||
@ -66,10 +68,13 @@ extension APIService {
|
||||
|
||||
// update repost state
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext),
|
||||
let authentication = authenticationBox.authentication
|
||||
|
||||
guard
|
||||
let me = authentication.user(in: managedObjectContext),
|
||||
let _status = record.object(in: managedObjectContext)
|
||||
else { return }
|
||||
let me = authentication.user
|
||||
|
||||
let status = _status.reblog ?? _status
|
||||
|
||||
switch result {
|
||||
|
@ -41,7 +41,7 @@ extension APIService {
|
||||
).singleOutput()
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else {
|
||||
// assertionFailure()
|
||||
return
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
|
||||
// user
|
||||
for entity in response.value.accounts {
|
||||
|
@ -76,7 +76,7 @@ extension APIService {
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
let status = Persistence.Status.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.Status.PersistContext(
|
||||
|
@ -33,7 +33,7 @@ extension APIService {
|
||||
#if !APP_EXTENSION
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
_ = Persistence.Status.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.Status.PersistContext(
|
||||
|
@ -29,7 +29,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
_ = Persistence.Status.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.Status.PersistContext(
|
||||
|
@ -73,7 +73,7 @@ fileprivate extension APIService {
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Tag> {
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
|
||||
_ = Persistence.Tag.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
|
@ -29,7 +29,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
let value = response.value.ancestors + response.value.descendants
|
||||
|
||||
for entity in value {
|
||||
|
@ -45,7 +45,7 @@ extension APIService {
|
||||
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user
|
||||
let me = authenticationBox.authentication.user(in: managedObjectContext)
|
||||
for entity in response.value {
|
||||
_ = Persistence.Status.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
|
@ -1,75 +0,0 @@
|
||||
//
|
||||
// APIService+CoreData+MastodonAuthentication.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/2/3.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService.CoreData {
|
||||
|
||||
public static func createOrMergeMastodonAuthentication(
|
||||
into managedObjectContext: NSManagedObjectContext,
|
||||
for authenticateMastodonUser: MastodonUser,
|
||||
in domain: String,
|
||||
property: MastodonAuthentication.Property,
|
||||
networkDate: Date
|
||||
) -> (mastodonAuthentication: MastodonAuthentication, isCreated: Bool) {
|
||||
// fetch old mastodon authentication
|
||||
let oldMastodonAuthentication: MastodonAuthentication? = {
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
request.predicate = MastodonAuthentication.predicate(domain: domain, userID: property.userID)
|
||||
request.fetchLimit = 1
|
||||
request.returnsObjectsAsFaults = false
|
||||
do {
|
||||
return try managedObjectContext.fetch(request).first
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
if let oldMastodonAuthentication = oldMastodonAuthentication {
|
||||
// merge old mastodon authentication
|
||||
APIService.CoreData.mergeMastodonAuthentication(
|
||||
for: authenticateMastodonUser,
|
||||
old: oldMastodonAuthentication,
|
||||
in: domain,
|
||||
property: property,
|
||||
networkDate: networkDate
|
||||
)
|
||||
return (oldMastodonAuthentication, false)
|
||||
} else {
|
||||
let mastodonAuthentication = MastodonAuthentication.insert(
|
||||
into: managedObjectContext,
|
||||
property: property,
|
||||
user: authenticateMastodonUser
|
||||
)
|
||||
return (mastodonAuthentication, true)
|
||||
}
|
||||
}
|
||||
|
||||
static func mergeMastodonAuthentication(
|
||||
for authenticateMastodonUser: MastodonUser,
|
||||
old authentication: MastodonAuthentication,
|
||||
in domain: String,
|
||||
property: MastodonAuthentication.Property,
|
||||
networkDate: Date
|
||||
) {
|
||||
guard networkDate > authentication.updatedAt else { return }
|
||||
|
||||
|
||||
authentication.update(username: property.username)
|
||||
authentication.update(appAccessToken: property.appAccessToken)
|
||||
authentication.update(userAccessToken: property.userAccessToken)
|
||||
authentication.update(clientID: property.clientID)
|
||||
authentication.update(clientSecret: property.clientSecret)
|
||||
|
||||
authentication.didUpdate(at: networkDate)
|
||||
}
|
||||
|
||||
}
|
@ -21,10 +21,9 @@ public final class AuthenticationService: NSObject {
|
||||
weak var apiService: APIService?
|
||||
let managedObjectContext: NSManagedObjectContext // read-only
|
||||
let backgroundManagedObjectContext: NSManagedObjectContext
|
||||
let mastodonAuthenticationFetchedResultsController: NSFetchedResultsController<MastodonAuthentication>
|
||||
let authenticationServiceProvider = AuthenticationServiceProvider.shared
|
||||
|
||||
// output
|
||||
@Published public var mastodonAuthentications: [ManagedObjectRecord<MastodonAuthentication>] = []
|
||||
@Published public var mastodonAuthenticationBoxes: [MastodonAuthenticationBox] = []
|
||||
|
||||
private func fetchFollowedBlockedUserIds(
|
||||
@ -92,21 +91,8 @@ public final class AuthenticationService: NSObject {
|
||||
self.managedObjectContext = managedObjectContext
|
||||
self.backgroundManagedObjectContext = backgroundManagedObjectContext
|
||||
self.apiService = apiService
|
||||
self.mastodonAuthenticationFetchedResultsController = {
|
||||
let fetchRequest = MastodonAuthentication.sortedFetchRequest
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
fetchRequest.fetchBatchSize = 20
|
||||
let controller = NSFetchedResultsController(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: managedObjectContext,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
return controller
|
||||
}()
|
||||
super.init()
|
||||
|
||||
mastodonAuthenticationFetchedResultsController.delegate = self
|
||||
super.init()
|
||||
|
||||
$mastodonAuthenticationBoxes
|
||||
.sink { [weak self] boxes in
|
||||
@ -122,10 +108,9 @@ public final class AuthenticationService: NSObject {
|
||||
|
||||
// TODO: verify credentials for active authentication
|
||||
|
||||
$mastodonAuthentications
|
||||
authenticationServiceProvider.$authentications
|
||||
.map { authentications -> [MastodonAuthenticationBox] in
|
||||
return authentications
|
||||
.compactMap { $0.object(in: managedObjectContext) }
|
||||
.sorted(by: { $0.activedAt > $1.activedAt })
|
||||
.compactMap { authentication -> MastodonAuthenticationBox? in
|
||||
return MastodonAuthenticationBox(authentication: authentication)
|
||||
@ -133,14 +118,7 @@ public final class AuthenticationService: NSObject {
|
||||
}
|
||||
.assign(to: &$mastodonAuthenticationBoxes)
|
||||
|
||||
do {
|
||||
try mastodonAuthenticationFetchedResultsController.performFetch()
|
||||
mastodonAuthentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?
|
||||
.sorted(by: { $0.activedAt > $1.activedAt })
|
||||
.compactMap { $0.asRecord } ?? []
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
AuthenticationServiceProvider.shared.authentications = AuthenticationServiceProvider.shared.authenticationSortedByActivation()
|
||||
}
|
||||
|
||||
}
|
||||
@ -150,18 +128,9 @@ extension AuthenticationService {
|
||||
public func activeMastodonUser(domain: String, userID: MastodonUser.ID) async throws -> Bool {
|
||||
var isActive = false
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
AuthenticationServiceProvider.shared.activateAuthentication(in: domain, for: userID)
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
request.predicate = MastodonAuthentication.predicate(domain: domain, userID: userID)
|
||||
request.fetchLimit = 1
|
||||
guard let mastodonAuthentication = try? managedObjectContext.fetch(request).first else {
|
||||
return
|
||||
}
|
||||
mastodonAuthentication.update(activedAt: Date())
|
||||
isActive = true
|
||||
}
|
||||
|
||||
return isActive
|
||||
}
|
||||
@ -182,12 +151,7 @@ extension AuthenticationService {
|
||||
managedObjectContext.delete(feed)
|
||||
}
|
||||
|
||||
guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext) else {
|
||||
assertionFailure()
|
||||
throw APIService.APIError.implicit(.authenticationMissing)
|
||||
}
|
||||
|
||||
managedObjectContext.delete(authentication)
|
||||
AuthenticationServiceProvider.shared.delete(authentication: authenticationBox.authentication)
|
||||
}
|
||||
|
||||
// cancel push notification subscription
|
||||
@ -202,19 +166,3 @@ extension AuthenticationService {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
extension AuthenticationService: NSFetchedResultsControllerDelegate {
|
||||
|
||||
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
guard controller === mastodonAuthenticationFetchedResultsController else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
mastodonAuthentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?
|
||||
.sorted(by: { $0.activedAt > $1.activedAt })
|
||||
.compactMap { $0.asRecord } ?? []
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -78,18 +78,8 @@ extension InstanceService {
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
|
||||
// update relationship
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
request.predicate = MastodonAuthentication.predicate(domain: domain)
|
||||
request.returnsObjectsAsFaults = false
|
||||
do {
|
||||
let authentications = try managedObjectContext.fetch(request)
|
||||
for authentication in authentications {
|
||||
authentication.update(instance: instance)
|
||||
}
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
// update instance
|
||||
AuthenticationServiceProvider.shared.update(instance: instance, where: domain)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result in
|
||||
@ -116,18 +106,8 @@ extension InstanceService {
|
||||
)
|
||||
)
|
||||
|
||||
// update relationship
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
request.predicate = MastodonAuthentication.predicate(domain: domain)
|
||||
request.returnsObjectsAsFaults = false
|
||||
do {
|
||||
let authentications = try managedObjectContext.fetch(request)
|
||||
for authentication in authentications {
|
||||
authentication.update(instance: instance)
|
||||
}
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
// update instance
|
||||
AuthenticationServiceProvider.shared.update(instance: instance, where: domain)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result in
|
||||
|
@ -41,7 +41,7 @@ public final class NotificationService {
|
||||
self.apiService = apiService
|
||||
self.authenticationService = authenticationService
|
||||
|
||||
authenticationService.$mastodonAuthentications
|
||||
AuthenticationServiceProvider.shared.$authentications
|
||||
.sink(receiveValue: { [weak self] mastodonAuthentications in
|
||||
guard let self = self else { return }
|
||||
|
||||
@ -100,13 +100,13 @@ extension NotificationService {
|
||||
let managedObjectContext = authenticationService.managedObjectContext
|
||||
return try await managedObjectContext.perform {
|
||||
var items: [UIApplicationShortcutItem] = []
|
||||
for object in authenticationService.mastodonAuthentications {
|
||||
guard let authentication = managedObjectContext.object(with: object.objectID) as? MastodonAuthentication else { continue }
|
||||
for authentication in AuthenticationServiceProvider.shared.authentications {
|
||||
guard let user = authentication.user(in: managedObjectContext) else { continue }
|
||||
let accessToken = authentication.userAccessToken
|
||||
let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken)
|
||||
guard count > 0 else { continue }
|
||||
|
||||
let title = "@\(authentication.user.acctWithDomain)"
|
||||
let title = "@\(user.acctWithDomain)"
|
||||
let subtitle = L10n.A11y.Plural.Count.Unread.notification(count)
|
||||
|
||||
let item = UIApplicationShortcutItem(
|
||||
@ -201,9 +201,8 @@ extension NotificationService {
|
||||
|
||||
let needsCancelSubscription: Bool = try await managedObjectContext.perform {
|
||||
// check authentication exists
|
||||
let authenticationRequest = MastodonAuthentication.sortedFetchRequest
|
||||
authenticationRequest.predicate = MastodonAuthentication.predicate(userAccessToken: userAccessToken)
|
||||
return managedObjectContext.safeFetch(authenticationRequest).first == nil
|
||||
let results = AuthenticationServiceProvider.shared.authentications.filter { $0.userAccessToken == userAccessToken }
|
||||
return results.first == nil
|
||||
}
|
||||
|
||||
guard needsCancelSubscription else {
|
||||
@ -240,23 +239,18 @@ extension NotificationService {
|
||||
|
||||
private func authenticationBox(for pushNotification: MastodonPushNotification) async throws -> MastodonAuthenticationBox? {
|
||||
guard let authenticationService = self.authenticationService else { return nil }
|
||||
let managedObjectContext = authenticationService.managedObjectContext
|
||||
return try await managedObjectContext.perform {
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
request.predicate = MastodonAuthentication.predicate(userAccessToken: pushNotification.accessToken)
|
||||
request.fetchLimit = 1
|
||||
guard let authentication = managedObjectContext.safeFetch(request).first else { return nil }
|
||||
let results = AuthenticationServiceProvider.shared.authentications.filter { $0.userAccessToken == pushNotification.accessToken }
|
||||
guard let authentication = results.first else { return nil }
|
||||
|
||||
return MastodonAuthenticationBox(
|
||||
authenticationRecord: .init(objectID: authentication.objectID),
|
||||
authentication: authentication,
|
||||
domain: authentication.domain,
|
||||
userID: authentication.userID,
|
||||
appAuthorization: .init(accessToken: authentication.appAccessToken),
|
||||
userAuthorization: .init(accessToken: authentication.userAccessToken),
|
||||
inMemoryCache: .sharedCache(for: authentication.objectID.description)
|
||||
inMemoryCache: .sharedCache(for: authentication.userAccessToken)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension UserDefaults {
|
||||
|
||||
enum Keys {
|
||||
static let didMigrateAuthenticationsKey = "didMigrateAuthentications"
|
||||
}
|
||||
|
||||
@objc dynamic var didMigrateAuthentications: Bool {
|
||||
get {
|
||||
return bool(forKey: Keys.didMigrateAuthenticationsKey)
|
||||
}
|
||||
|
||||
set {
|
||||
set(newValue, forKey: Keys.didMigrateAuthenticationsKey)
|
||||
}
|
||||
}
|
||||
}
|
@ -156,7 +156,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||
self.visibility = {
|
||||
// default private when user locked
|
||||
var visibility: Mastodon.Entity.Status.Visibility = {
|
||||
guard let author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user else {
|
||||
guard let author = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else {
|
||||
return .public
|
||||
}
|
||||
return author.locked ? .private : .public
|
||||
@ -224,7 +224,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
let author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
let author = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
|
||||
var mentionAccts: [String] = []
|
||||
if author?.id != status.author.id {
|
||||
@ -259,9 +259,8 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||
let _configuration: Mastodon.Entity.Instance.Configuration? = {
|
||||
var configuration: Mastodon.Entity.Instance.Configuration? = nil
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)
|
||||
else { return }
|
||||
configuration = authentication.instance?.configuration
|
||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
||||
configuration = authentication.instance(in: context.managedObjectContext)?.configuration
|
||||
}
|
||||
return configuration
|
||||
}()
|
||||
@ -319,7 +318,7 @@ extension ComposeContentViewModel {
|
||||
$authContext
|
||||
.sink { [weak self] authContext in
|
||||
guard let self = self else { return }
|
||||
guard let user = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user else { return }
|
||||
guard let user = authContext.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else { return }
|
||||
self.avatarURL = user.avatarImageURL()
|
||||
self.name = user.nameMetaContent ?? PlaintextMetaContent(string: user.displayNameWithFallback)
|
||||
self.username = user.acctWithDomain
|
||||
@ -565,7 +564,7 @@ extension ComposeContentViewModel {
|
||||
let managedObjectContext = self.context.managedObjectContext
|
||||
var _author: ManagedObjectRecord<MastodonUser>?
|
||||
managedObjectContext.performAndWait {
|
||||
_author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user.asRecord
|
||||
_author = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext)?.asRecord
|
||||
}
|
||||
guard let author = _author else {
|
||||
throw AppError.badAuthentication
|
||||
@ -621,7 +620,7 @@ extension ComposeContentViewModel {
|
||||
let managedObjectContext = self.context.managedObjectContext
|
||||
var _author: ManagedObjectRecord<MastodonUser>?
|
||||
managedObjectContext.performAndWait {
|
||||
_author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user.asRecord
|
||||
_author = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext)?.asRecord
|
||||
}
|
||||
guard let author = _author else {
|
||||
throw AppError.badAuthentication
|
||||
|
@ -237,9 +237,8 @@ extension NotificationView.ViewModel {
|
||||
|
||||
var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)
|
||||
else { return }
|
||||
configuration = authentication.instance?.configurationV2
|
||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
||||
configuration = authentication.instance(in: context.managedObjectContext)?.configurationV2
|
||||
}
|
||||
return configuration
|
||||
}()
|
||||
|
@ -686,9 +686,8 @@ extension StatusView.ViewModel {
|
||||
|
||||
var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)
|
||||
else { return }
|
||||
configuration = authentication.instance?.configurationV2
|
||||
let authentication = authContext.mastodonAuthenticationBox.authentication
|
||||
configuration = authentication.instance(in: context.managedObjectContext)?.configurationV2
|
||||
}
|
||||
return configuration
|
||||
}()
|
||||
|
@ -126,6 +126,7 @@ public final class RelationshipViewModel {
|
||||
$me,
|
||||
relationshipUpdatePublisher
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] user, me, _ in
|
||||
guard let self = self else { return }
|
||||
self.update(user: user, me: me)
|
||||
|
@ -160,8 +160,7 @@ extension ShareViewController {
|
||||
|
||||
extension ShareViewController {
|
||||
private func setupAuthContext() throws -> AuthContext? {
|
||||
let request = MastodonAuthentication.activeSortedFetchRequest // use active order
|
||||
let _authentication = try context.managedObjectContext.fetch(request).first
|
||||
let _authentication = AuthenticationServiceProvider.shared.authenticationSortedByActivation().first
|
||||
let _authContext = _authentication.flatMap { AuthContext(authentication: $0) }
|
||||
return _authContext
|
||||
}
|
||||
|
@ -83,9 +83,9 @@ private extension FollowersCountWidgetProvider {
|
||||
}
|
||||
|
||||
guard
|
||||
let desiredAccount = configuration.account ?? authBox.authenticationRecord.object(
|
||||
let desiredAccount = configuration.account ?? authBox.authentication.user(
|
||||
in: WidgetExtension.appContext.managedObjectContext
|
||||
)?.user.acctWithDomain
|
||||
)?.acctWithDomain
|
||||
else {
|
||||
return completion(.unconfigured)
|
||||
}
|
||||
|
@ -86,9 +86,9 @@ private extension MultiFollowersCountWidgetProvider {
|
||||
|
||||
if let configuredAccounts = configuration.accounts?.compactMap({ $0 }) {
|
||||
desiredAccounts = configuredAccounts
|
||||
} else if let currentlyLoggedInAccount = authBox.authenticationRecord.object(
|
||||
} else if let currentlyLoggedInAccount = authBox.authentication.user(
|
||||
in: WidgetExtension.appContext.managedObjectContext
|
||||
)?.user.acctWithDomain {
|
||||
)?.acctWithDomain {
|
||||
desiredAccounts = [currentlyLoggedInAccount]
|
||||
} else {
|
||||
return completion(.unconfigured)
|
||||
|
Loading…
x
Reference in New Issue
Block a user