Fix crash issue after first sign in.

This commit is contained in:
Marcin Czachursk 2023-03-29 17:06:41 +02:00
parent 68e40d2fd1
commit 2320a83ff5
7 changed files with 71 additions and 57 deletions

View File

@ -35,9 +35,9 @@ class ApplicationSettingsHandler {
} }
} }
func set(accountData: AccountData?) { func set(accountId: String?) {
let defaultSettings = self.get() let defaultSettings = self.get()
defaultSettings.currentAccount = accountData?.id defaultSettings.currentAccount = accountId
CoreDataHandler.shared.save() CoreDataHandler.shared.save()
} }

View File

@ -1308,7 +1308,7 @@
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88; CURRENT_PROJECT_VERSION = 89;
DEVELOPMENT_TEAM = B2U9FEKYP8; DEVELOPMENT_TEAM = B2U9FEKYP8;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VernissageWidget/Info.plist; INFOPLIST_FILE = VernissageWidget/Info.plist;
@ -1336,7 +1336,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88; CURRENT_PROJECT_VERSION = 89;
DEVELOPMENT_TEAM = B2U9FEKYP8; DEVELOPMENT_TEAM = B2U9FEKYP8;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VernissageWidget/Info.plist; INFOPLIST_FILE = VernissageWidget/Info.plist;
@ -1484,7 +1484,7 @@
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88; CURRENT_PROJECT_VERSION = 89;
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
DEVELOPMENT_TEAM = B2U9FEKYP8; DEVELOPMENT_TEAM = B2U9FEKYP8;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@ -1524,7 +1524,7 @@
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88; CURRENT_PROJECT_VERSION = 89;
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
DEVELOPMENT_TEAM = B2U9FEKYP8; DEVELOPMENT_TEAM = B2U9FEKYP8;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;

View File

@ -15,28 +15,28 @@ public class AuthorizationService {
private init() { } private init() { }
/// Access token verification. /// Access token verification.
public func verifyAccount(session: AuthorizationSession, currentAccount: AccountData?, _ result: @escaping (AccountData?) -> Void) async { public func verifyAccount(session: AuthorizationSession, accountModel: AccountModel, _ result: @escaping (AccountModel?) -> Void) async {
// When we dont have even one account stored in database then we have to ask user to enter server and sign in. // When we dont have even one account stored in database then we have to ask user to enter server and sign in.
guard let currentAccount, let accessToken = currentAccount.accessToken else { guard let accessToken = accountModel.accessToken else {
result(nil) result(nil)
return return
} }
// When we have at least one account then we have to verify access token. // When we have at least one account then we have to verify access token.
let client = PixelfedClient(baseURL: currentAccount.serverUrl).getAuthenticated(token: accessToken) let client = PixelfedClient(baseURL: accountModel.serverUrl).getAuthenticated(token: accessToken)
do { do {
let account = try await client.verifyCredentials() let account = try await client.verifyCredentials()
await self.update(accountId: currentAccount.id, let signedInAccountModel = await self.update(accountId: accountModel.id,
basedOn: account, basedOn: account,
accessToken: accessToken, accessToken: accessToken,
refreshToken: currentAccount.refreshToken) refreshToken: accountModel.refreshToken)
result(currentAccount) result(signedInAccountModel)
} catch { } catch {
do { do {
try await self.refreshCredentials(for: currentAccount, presentationContextProvider: session) let signedInAccountModel = try await self.refreshCredentials(for: accountModel, presentationContextProvider: session)
result(currentAccount) result(signedInAccountModel)
} catch { } catch {
ErrorService.shared.handle(error, message: "Issues during refreshing credentials.", showToastr: true) ErrorService.shared.handle(error, message: "Issues during refreshing credentials.", showToastr: true)
} }
@ -44,7 +44,7 @@ public class AuthorizationService {
} }
/// Sign in to the Pixelfed server. /// Sign in to the Pixelfed server.
public func sign(in serverAddress: String, session: AuthorizationSession, _ result: @escaping (AccountData) -> Void) async throws { public func sign(in serverAddress: String, session: AuthorizationSession, _ result: @escaping (AccountModel) -> Void) async throws {
guard let baseUrl = URL(string: serverAddress) else { guard let baseUrl = URL(string: serverAddress) else {
throw AuthorisationError.badServerUrl throw AuthorisationError.badServerUrl
@ -124,7 +124,8 @@ public class AuthorizationService {
CoreDataHandler.shared.save(viewContext: backgroundContext) CoreDataHandler.shared.save(viewContext: backgroundContext)
// Return account data. // Return account data.
result(accountData) let accountModel = AccountModel(accountData: accountData)
result(accountModel)
} }
public func refreshAccessTokens() async { public func refreshAccessTokens() async {
@ -134,7 +135,7 @@ public class AuthorizationService {
for account in accounts { for account in accounts {
group.addTask { group.addTask {
do { do {
try await self.refreshAccessToken(accountData: account) _ = try await self.refreshAccessToken(accountData: account)
#if DEBUG #if DEBUG
ToastrService.shared.showSuccess("New access tokens has been retrieved.", imageSystemName: "key.fill") ToastrService.shared.showSuccess("New access tokens has been retrieved.", imageSystemName: "key.fill")
@ -151,11 +152,11 @@ public class AuthorizationService {
} }
} }
private func refreshAccessToken(accountData: AccountData) async throws { private func refreshAccessToken(accountData: AccountData) async throws -> AccountModel? {
let client = PixelfedClient(baseURL: accountData.serverUrl) let client = PixelfedClient(baseURL: accountData.serverUrl)
guard let refreshToken = accountData.refreshToken else { guard let refreshToken = accountData.refreshToken else {
return return nil
} }
let oAuthSwiftCredential = try await client.refreshToken(clientId: accountData.clientId, let oAuthSwiftCredential = try await client.refreshToken(clientId: accountData.clientId,
@ -167,21 +168,22 @@ public class AuthorizationService {
// Get account information from server. // Get account information from server.
let account = try await authenticatedClient.verifyCredentials() let account = try await authenticatedClient.verifyCredentials()
await self.update(accountId: accountData.id,
basedOn: account, return await self.update(accountId: accountData.id,
accessToken: oAuthSwiftCredential.oauthToken, basedOn: account,
refreshToken: oAuthSwiftCredential.oauthRefreshToken) accessToken: oAuthSwiftCredential.oauthToken,
refreshToken: oAuthSwiftCredential.oauthRefreshToken)
} }
private func refreshCredentials(for accountData: AccountData, private func refreshCredentials(for accountModel: AccountModel,
presentationContextProvider: ASWebAuthenticationPresentationContextProviding presentationContextProvider: ASWebAuthenticationPresentationContextProviding
) async throws { ) async throws -> AccountModel? {
let client = PixelfedClient(baseURL: accountData.serverUrl) let client = PixelfedClient(baseURL: accountModel.serverUrl)
// Create application (we will get clientId and clientSecret). // Create application (we will get clientId and clientSecret).
let oAuthApp = Application(clientId: accountData.clientId, let oAuthApp = Application(clientId: accountModel.clientId,
clientSecret: accountData.clientSecret, clientSecret: accountModel.clientSecret,
redirectUri: AppConstants.oauthRedirectUri) redirectUri: AppConstants.oauthRedirectUri)
// Authorize a user (browser, we will get clientCode). // Authorize a user (browser, we will get clientCode).
@ -195,20 +197,21 @@ public class AuthorizationService {
// Get account information from server. // Get account information from server.
let account = try await authenticatedClient.verifyCredentials() let account = try await authenticatedClient.verifyCredentials()
await self.update(accountId: accountData.id,
basedOn: account, return await self.update(accountId: accountModel.id,
accessToken: oAuthSwiftCredential.oauthToken, basedOn: account,
refreshToken: oAuthSwiftCredential.oauthRefreshToken) accessToken: oAuthSwiftCredential.oauthToken,
refreshToken: oAuthSwiftCredential.oauthRefreshToken)
} }
private func update(accountId: String, private func update(accountId: String,
basedOn account: Account, basedOn account: Account,
accessToken: String, accessToken: String,
refreshToken: String? refreshToken: String?
) async { ) async -> AccountModel? {
let backgroundContext = CoreDataHandler.shared.newBackgroundContext() let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
guard let dbAccount = AccountDataHandler.shared.getAccountData(accountId: accountId, viewContext: backgroundContext) else { guard let dbAccount = AccountDataHandler.shared.getAccountData(accountId: accountId, viewContext: backgroundContext) else {
return return nil
} }
dbAccount.username = account.username dbAccount.username = account.username
@ -241,6 +244,8 @@ public class AuthorizationService {
// Save account data in database and in application state. // Save account data in database and in application state.
CoreDataHandler.shared.save(viewContext: backgroundContext) CoreDataHandler.shared.save(viewContext: backgroundContext)
return AccountModel(accountData: dbAccount)
} }
private func getAccountData(account: Account, backgroundContext: NSManagedObjectContext) -> AccountData { private func getAccountData(account: Account, backgroundContext: NSManagedObjectContext) -> AccountData {

View File

@ -34,8 +34,8 @@ struct VernissageApp: App {
.withAppRouteur() .withAppRouteur()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
case .signIn: case .signIn:
SignInView { accountData in SignInView { accountModel in
self.setApplicationState(accountData: accountData) self.setApplicationState(accountModel: accountModel)
} }
.withAppRouteur() .withAppRouteur()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
@ -91,6 +91,7 @@ struct VernissageApp: App {
} }
} }
@MainActor
private func onApplicationStart() async { private func onApplicationStart() async {
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.white.withAlphaComponent(0.7) UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.white.withAlphaComponent(0.7)
UIPageControl.appearance().pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.4) UIPageControl.appearance().pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.4)
@ -104,22 +105,29 @@ struct VernissageApp: App {
// Refresh other access tokens. // Refresh other access tokens.
await self.refreshAccessTokens() await self.refreshAccessTokens()
// When user doesn't exists then we have to open sign in view.
guard let currentAccount = AccountDataHandler.shared.getCurrentAccountData() else {
self.applicationViewMode = .signIn
return
}
// Create model based on core data entity.
let accountModel = AccountModel(accountData: currentAccount)
// Verify access token correctness. // Verify access token correctness.
let authorizationSession = AuthorizationSession() let authorizationSession = AuthorizationSession()
let currentAccount = AccountDataHandler.shared.getCurrentAccountData() await AuthorizationService.shared.verifyAccount(session: authorizationSession, accountModel: accountModel) { signedInAccountModel in
await AuthorizationService.shared.verifyAccount(session: authorizationSession, currentAccount: currentAccount) { accountData in guard let signedInAccountModel else {
guard let accountData = accountData else {
self.applicationViewMode = .signIn self.applicationViewMode = .signIn
return return
} }
self.setApplicationState(accountData: accountData, checkNewPhotos: true) self.setApplicationState(accountModel: signedInAccountModel, checkNewPhotos: true)
} }
} }
private func setApplicationState(accountData: AccountData, checkNewPhotos: Bool = false) { private func setApplicationState(accountModel: AccountModel, checkNewPhotos: Bool = false) {
Task { @MainActor in Task { @MainActor in
let accountModel = AccountModel(accountData: accountData)
let instance = try? await self.client.instances.instance(url: accountModel.serverUrl) let instance = try? await self.client.instances.instance(url: accountModel.serverUrl)
// Refresh client state. // Refresh client state.
@ -128,7 +136,7 @@ struct VernissageApp: App {
// Refresh application state. // Refresh application state.
self.applicationState.changeApplicationState(accountModel: accountModel, self.applicationState.changeApplicationState(accountModel: accountModel,
instance: instance, instance: instance,
lastSeenStatusId: accountData.lastSeenStatusId) lastSeenStatusId: accountModel.lastSeenStatusId)
// Change view displayed by application. // Change view displayed by application.
self.applicationViewMode = .mainView self.applicationViewMode = .mainView

View File

@ -323,26 +323,27 @@ struct MainView: View {
Task { Task {
// Verify access token correctness. // Verify access token correctness.
let authorizationSession = AuthorizationSession() let authorizationSession = AuthorizationSession()
await AuthorizationService.shared.verifyAccount(session: authorizationSession, currentAccount: account) { accountData in let accountModel = AccountModel(accountData: account)
guard let accountData = accountData else {
await AuthorizationService.shared.verifyAccount(session: authorizationSession, accountModel: accountModel) { signedInAccountModel in
guard let signedInAccountModel else {
ToastrService.shared.showError(subtitle: "mainview.error.switchAccounts") ToastrService.shared.showError(subtitle: "mainview.error.switchAccounts")
return return
} }
Task { @MainActor in Task { @MainActor in
let accountModel = AccountModel(accountData: accountData) let instance = try? await self.client.instances.instance(url: signedInAccountModel.serverUrl)
let instance = try? await self.client.instances.instance(url: accountModel.serverUrl)
// Refresh client state. // Refresh client state.
self.client.setAccount(account: accountModel) self.client.setAccount(account: signedInAccountModel)
// Refresh application state. // Refresh application state.
self.applicationState.changeApplicationState(accountModel: accountModel, self.applicationState.changeApplicationState(accountModel: signedInAccountModel,
instance: instance, instance: instance,
lastSeenStatusId: accountData.lastSeenStatusId) lastSeenStatusId: signedInAccountModel.lastSeenStatusId)
// Set account as default (application will open this account after restart). // Set account as default (application will open this account after restart).
ApplicationSettingsHandler.shared.set(accountData: accountData) ApplicationSettingsHandler.shared.set(accountId: signedInAccountModel.id)
} }
} }
} }

View File

@ -72,7 +72,7 @@ struct AccountsSectionView: View {
if shouldClearApplicationState { if shouldClearApplicationState {
// We have to do this after animation of deleting row is ended. // We have to do this after animation of deleting row is ended.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
ApplicationSettingsHandler.shared.set(accountData: nil) ApplicationSettingsHandler.shared.set(accountId: nil)
self.applicationState.clearApplicationState() self.applicationState.clearApplicationState()
self.client.clearAccount() self.client.clearAccount()
} }

View File

@ -19,7 +19,7 @@ struct SignInView: View {
@State private var instructionsUrlString:String? @State private var instructionsUrlString:String?
@State private var instances: [Instance] = [] @State private var instances: [Instance] = []
var onSignedIn: ((_ accountData: AccountData) -> Void)? var onSignedIn: ((_ accountModel: AccountModel) -> Void)?
var body: some View { var body: some View {
List { List {
@ -91,8 +91,8 @@ struct SignInView: View {
Task { Task {
do { do {
let authorizationSession = AuthorizationSession() let authorizationSession = AuthorizationSession()
try await AuthorizationService.shared.sign(in: baseAddress, session: authorizationSession) { accountData in try await AuthorizationService.shared.sign(in: baseAddress, session: authorizationSession) { accountModel in
onSignedIn?(accountData) onSignedIn?(accountModel)
DispatchQueue.main.sync { DispatchQueue.main.sync {
dismiss() dismiss()