From db1677e8a0d229d6983b0f8eb01f64835f3941c5 Mon Sep 17 00:00:00 2001 From: mdmathias Date: Mon, 1 Aug 2022 17:15:39 -0700 Subject: [PATCH 01/11] Use async await API in DaysUntilBirthday Sample App --- .../Shared/Services/BirthdayLoader.swift | 81 +++++----- .../Services/GoogleSignInAuthenticator.swift | 139 ++++++++---------- .../ViewModels/AuthenticationViewModel.swift | 58 +++++++- .../Shared/ViewModels/BirthdayViewModel.swift | 20 +-- .../Shared/Views/SignInView.swift | 2 +- .../iOS/UserProfileView.swift | 56 ++++--- .../macOS/UserProfileView.swift | 72 +++++---- 7 files changed, 231 insertions(+), 197 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift index 1dc4dea4..ed4fa229 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift @@ -17,8 +17,8 @@ import Combine import GoogleSignIn -/// An observable class to load the current user's birthday. -final class BirthdayLoader: ObservableObject { +/// A class to load the current user's birthday. +final class BirthdayLoader { /// The scope required to read a user's birthday. static let birthdayReadScope = "https://www.googleapis.com/auth/user.birthday.read" private let baseUrlString = "https://people.googleapis.com/v1/people/me" @@ -51,62 +51,49 @@ final class BirthdayLoader: ObservableObject { return URLSession(configuration: configuration) }() - private func sessionWithFreshToken(completion: @escaping (Result) -> Void) { - let authentication = GIDSignIn.sharedInstance.currentUser?.authentication - authentication?.do { auth, error in - guard let token = auth?.accessToken else { - completion(.failure(.couldNotCreateURLSession(error))) - return - } - let configuration = URLSessionConfiguration.default - configuration.httpAdditionalHeaders = [ - "Authorization": "Bearer \(token)" - ] - let session = URLSession(configuration: configuration) - completion(.success(session)) + private func sessionWithFreshToken() async throws -> URLSession { + guard let authentication = GIDSignIn.sharedInstance.currentUser?.authentication else { + throw Error.noCurrentUserForSessionWithFreshToken } + + let freshAuth = try await authentication.doWithFreshTokens() + let configuration = URLSessionConfiguration.default + configuration.httpAdditionalHeaders = [ + "Authorization": "Bearer \(freshAuth.accessToken)" + ] + let session = URLSession(configuration: configuration) + return session } - /// Creates a `Publisher` to fetch a user's `Birthday`. - /// - parameter completion: A closure passing back the `AnyPublisher` - /// upon success. - /// - note: The `AnyPublisher` passed back through the `completion` closure is created with a - /// fresh token. See `sessionWithFreshToken(completion:)` for more details. - func birthdayPublisher(completion: @escaping (AnyPublisher) -> Void) { - sessionWithFreshToken { [weak self] result in - switch result { - case .success(let authSession): - guard let request = self?.request else { - return completion(Fail(error: .couldNotCreateURLRequest).eraseToAnyPublisher()) + /// Fetches a `Birthday`. + /// - returns An instance of `Birthday`. + /// - throws: An instance of `BirthdayLoader.Error` arising while fetching a birthday. + func loadBirthday() async throws -> Birthday { + let session = try await sessionWithFreshToken() + guard let request = request else { + throw Error.couldNotCreateURLRequest + } + let birthdayData = try await withCheckedThrowingContinuation { + (continuation: CheckedContinuation) -> Void in + let task = session.dataTask(with: request) { data, response, error in + guard let data = data else { + return continuation.resume(throwing: error ?? Error.noBirthdayData) } - let bdayPublisher = authSession.dataTaskPublisher(for: request) - .tryMap { data, error -> Birthday in - let decoder = JSONDecoder() - let birthdayResponse = try decoder.decode(BirthdayResponse.self, from: data) - return birthdayResponse.firstBirthday - } - .mapError { error -> Error in - guard let loaderError = error as? Error else { - return Error.couldNotFetchBirthday(underlying: error) - } - return loaderError - } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - completion(bdayPublisher) - case .failure(let error): - completion(Fail(error: error).eraseToAnyPublisher()) + continuation.resume(returning: data) } + task.resume() } + let decoder = JSONDecoder() + let birthdayResponse = try decoder.decode(BirthdayResponse.self, from: birthdayData) + return birthdayResponse.firstBirthday } } extension BirthdayLoader { - /// An error representing what went wrong in fetching a user's number of day until their birthday. + /// An error for what went wrong in fetching a user's number of days until their birthday. enum Error: Swift.Error { - case couldNotCreateURLSession(Swift.Error?) + case noCurrentUserForSessionWithFreshToken case couldNotCreateURLRequest - case userHasNoBirthday - case couldNotFetchBirthday(underlying: Swift.Error) + case noBirthdayData } } diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 2a0e88fd..480d7fa3 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -16,9 +16,14 @@ import Foundation import GoogleSignIn +#if os(iOS) +import UIKit +#elseif os(macOS) +import AppKit +#endif /// An observable class for authenticating via Google. -final class GoogleSignInAuthenticator: ObservableObject { +final class GoogleSignInAuthenticator { // TODO: Replace this with your own ID. #if os(iOS) private let clientID = "687389107077-8qr6dh8fr4uaja89sdr5ieqb7mep04qv.apps.googleusercontent.com" @@ -38,40 +43,32 @@ final class GoogleSignInAuthenticator: ObservableObject { self.authViewModel = authViewModel } - /// Signs in the user based upon the selected account.' - /// - note: Successful calls to this will set the `authViewModel`'s `state` property. - func signIn() { -#if os(iOS) - guard let rootViewController = UIApplication.shared.windows.first?.rootViewController else { - print("There is no root view controller!") - return - } - - GIDSignIn.sharedInstance.signIn(with: configuration, - presenting: rootViewController) { user, error in - guard let user = user else { - print("Error! \(String(describing: error))") - return - } - self.authViewModel.state = .signedIn(user) - } -#elseif os(macOS) - guard let presentingWindow = NSApplication.shared.windows.first else { - print("There is no presenting window!") - return - } + #if os(iOS) + /// Signs in the user based upon the selected account. + /// - parameter rootViewController: The `UIViewController` to use during the sign in flow. + /// - returns: The signed in `GIDGoogleUser`. + /// - throws: Any error that may arise during the sign in process. + func signIn(with rootViewController: UIViewController) async throws -> GIDGoogleUser { + return try await GIDSignIn.sharedInstance.signIn( + with: configuration, + presenting: rootViewController + ) + } + #endif - GIDSignIn.sharedInstance.signIn(with: configuration, - presenting: presentingWindow) { user, error in - guard let user = user else { - print("Error! \(String(describing: error))") - return - } - self.authViewModel.state = .signedIn(user) - } -#endif + #if os(macOS) + /// Signs in the user based upon the selected account. + /// - parameter window: The `NSWindow` to use during the sign in flow. + /// - returns: The signed in `GIDGoogleUser`. + /// - throws: Any error that may arise during the sign in process. + func signIn(with window: NSWindow) async throws -> GIDGoogleUser { + return try await GIDSignIn.sharedInstance.signIn( + with: configuration, + presenting: window + ) } + #endif /// Signs out the current user. func signOut() { @@ -80,57 +77,41 @@ final class GoogleSignInAuthenticator: ObservableObject { } /// Disconnects the previously granted scope and signs the user out. - func disconnect() { - GIDSignIn.sharedInstance.disconnect { error in - if let error = error { - print("Encountered error disconnecting scope: \(error).") - } - self.signOut() - } + func disconnect() async throws { + try await GIDSignIn.sharedInstance.disconnect() } - // Confines birthday calucation to iOS for now. +#if os(iOS) /// Adds the birthday read scope for the current user. - /// - parameter completion: An escaping closure that is called upon successful completion of the - /// `addScopes(_:presenting:)` request. - /// - note: Successful requests will update the `authViewModel.state` with a new current user that - /// has the granted scope. - func addBirthdayReadScope(completion: @escaping () -> Void) { - #if os(iOS) - guard let rootViewController = UIApplication.shared.windows.first?.rootViewController else { - fatalError("No root view controller!") - } - - GIDSignIn.sharedInstance.addScopes([BirthdayLoader.birthdayReadScope], - presenting: rootViewController) { user, error in - if let error = error { - print("Found error while adding birthday read scope: \(error).") - return - } - - guard let currentUser = user else { return } - self.authViewModel.state = .signedIn(currentUser) - completion() - } - - #elseif os(macOS) - guard let presentingWindow = NSApplication.shared.windows.first else { - fatalError("No presenting window!") - } - - GIDSignIn.sharedInstance.addScopes([BirthdayLoader.birthdayReadScope], - presenting: presentingWindow) { user, error in - if let error = error { - print("Found error while adding birthday read scope: \(error).") - return - } - - guard let currentUser = user else { return } - self.authViewModel.state = .signedIn(currentUser) - completion() - } + /// - parameter viewController: The `UIViewController` to use while authorizing the scope. + /// - returns: The `GIDGoogleUser` with the authorized scope. + /// - throws: Any error that may arise while authorizing the scope. + func addBirthdayReadScope(viewController: UIViewController) async throws -> GIDGoogleUser { + return try await GIDSignIn.sharedInstance.addScopes( + [BirthdayLoader.birthdayReadScope], + presenting: viewController + ) + } +#endif - #endif +#if os(macOS) + /// Adds the birthday read scope for the current user. + /// - parameter window: The `NSWindow` to use while authorizing the scope. + /// - returns: The `GIDGoogleUser` with the authorized scope. + /// - throws: Any error that may arise while authorizing the scope. + func addBirthdayReadScope(window: NSWindow) async throws -> GIDGoogleUser { + return try await GIDSignIn.sharedInstance.addScopes( + [BirthdayLoader.birthdayReadScope], + presenting: window + ) } +#endif +} +extension GoogleSignInAuthenticator { + enum Error: Swift.Error { + case failedToSignIn + case failedToAddBirthdayReadScope(Swift.Error) + case userUnexpectedlyNilWhileAddingBirthdayReadScope + } } diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index 3ad14289..b5d1bbb3 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -47,7 +47,35 @@ final class AuthenticationViewModel: ObservableObject { /// Signs the user in. func signIn() { - authenticator.signIn() +#if os(iOS) + guard let rootViewController = UIApplication.shared.windows.first?.rootViewController else { + print("There is no root view controller!") + return + } + + Task { @MainActor in + do { + let user = try await authenticator.signIn(with: rootViewController) + self.state = .signedIn(user) + } catch { + print("Error signing in: \(error)") + } + } +#elseif os(macOS) + guard let presentingWindow = NSApplication.shared.windows.first else { + print("There is no presenting window!") + return + } + + Task { @MainActor in + do { + let user = try await authenticator.signIn(with: presentingWindow) + self.state = .signedIn(user) + } catch { + print("Error signing in: \(error)") + } + } +#endif } /// Signs the user out. @@ -57,19 +85,39 @@ final class AuthenticationViewModel: ObservableObject { /// Disconnects the previously granted scope and logs the user out. func disconnect() { - authenticator.disconnect() + Task { @MainActor in + do { + try await authenticator.disconnect() + authenticator.signOut() + } catch { + print("Error disconnecting: \(error)") + } + } } var hasBirthdayReadScope: Bool { return authorizedScopes.contains(BirthdayLoader.birthdayReadScope) } +#if os(iOS) /// Adds the requested birthday read scope. - /// - parameter completion: An escaping closure that is called upon successful completion. - func addBirthdayReadScope(completion: @escaping () -> Void) { - authenticator.addBirthdayReadScope(completion: completion) + /// - parameter viewController: A `UIViewController` to use while presenting the flow. + /// - returns: A `GIDGoogleUser` with the authorized scope. + /// - throws: Any error that may arise while adding the read birthday scope. + func addBirthdayReadScope(viewController: UIViewController) async throws -> GIDGoogleUser { + return try await authenticator.addBirthdayReadScope(viewController: viewController) } +#endif +#if os(macOS) + /// adds the requested birthday read scope. + /// - parameter window: An `NSWindow` to use while presenting the flow. + /// - returns: A `GIDGoogleUser` with the authorized scope. + /// - throws: Any error that may arise while adding the read birthday scope. + func addBirthdayReadScope(window: NSWindow) async throws -> GIDGoogleUser { + return try await authenticator.addBirthdayReadScope(window: window) + } +#endif } extension AuthenticationViewModel { diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/BirthdayViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/BirthdayViewModel.swift index 598ce1c4..db98b539 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/BirthdayViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/BirthdayViewModel.swift @@ -17,7 +17,8 @@ import Combine import Foundation -/// An observable class representing the current user's `Birthday` and the number of days until that date. +/// An observable class representing the current user's `Birthday` and the number of days until that +/// date. final class BirthdayViewModel: ObservableObject { /// The `Birthday` of the current user. /// - note: Changes to this property will be published to observers. @@ -40,17 +41,12 @@ final class BirthdayViewModel: ObservableObject { /// Fetches the birthday of the current user. func fetchBirthday() { - birthdayLoader.birthdayPublisher { publisher in - self.cancellable = publisher.sink { completion in - switch completion { - case .finished: - break - case .failure(let error): - self.birthday = Birthday.noBirthday - print("Error retrieving birthday: \(error)") - } - } receiveValue: { birthday in - self.birthday = birthday + Task { @MainActor in + do { + self.birthday = try await birthdayLoader.loadBirthday() + } catch { + print("Error retrieving birthday: \(error)") + self.birthday = .noBirthday } } } diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Views/SignInView.swift b/Samples/Swift/DaysUntilBirthday/Shared/Views/SignInView.swift index 5eecffcc..4adff36c 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Views/SignInView.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Views/SignInView.swift @@ -19,7 +19,7 @@ import GoogleSignInSwift struct SignInView: View { @EnvironmentObject var authViewModel: AuthenticationViewModel - @ObservedObject var vm = GoogleSignInButtonViewModel() + @StateObject var vm = GoogleSignInButtonViewModel() var body: some View { VStack { diff --git a/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift b/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift index 93366f47..c8339e1a 100644 --- a/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift +++ b/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift @@ -37,25 +37,45 @@ struct UserProfileView: View { Text(userProfile.email) } } - NavigationLink(NSLocalizedString("View Days Until Birthday", comment: "View birthday days"), - destination: BirthdayView(birthdayViewModel: birthdayViewModel).onAppear { - guard self.birthdayViewModel.birthday != nil else { - if !self.authViewModel.hasBirthdayReadScope { - self.authViewModel.addBirthdayReadScope { - self.birthdayViewModel.fetchBirthday() + NavigationLink( + NSLocalizedString("View Days Until Birthday", comment: "View birthday days"), + destination: BirthdayView(birthdayViewModel: birthdayViewModel) + .onAppear { + guard self.birthdayViewModel.birthday != nil else { + if !self.authViewModel.hasBirthdayReadScope { + guard let viewController = UIApplication.shared.windows.first?.rootViewController else { + print("There was no root view controller") + return + } + Task { @MainActor in + do { + let user = try await authViewModel.addBirthdayReadScope( + viewController: viewController + ) + self.authViewModel.state = .signedIn(user) + self.birthdayViewModel.fetchBirthday() + } catch { + print("Failed to fetch birthday: \(error)") + } + } + } else { + self.birthdayViewModel.fetchBirthday() + } + return } - } else { - self.birthdayViewModel.fetchBirthday() - } - return - } - }) + }) Spacer() } .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { - Button(NSLocalizedString("Disconnect", comment: "Disconnect button"), action: disconnect) - Button(NSLocalizedString("Sign Out", comment: "Sign out button"), action: signOut) + Button( + NSLocalizedString("Disconnect", comment: "Disconnect button"), + action: authViewModel.disconnect + ) + Button( + NSLocalizedString("Sign Out", comment: "Sign out button"), + action: authViewModel.signOut + ) } } } else { @@ -63,12 +83,4 @@ struct UserProfileView: View { } } } - - func disconnect() { - authViewModel.disconnect() - } - - func signOut() { - authViewModel.signOut() - } } diff --git a/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift b/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift index d7faad97..5faf1538 100644 --- a/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift +++ b/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift @@ -21,32 +21,50 @@ struct UserProfileView: View { Text(userProfile.email) } } - Button(NSLocalizedString("Sign Out", comment: "Sign out button"), action: signOut) - .background(Color.blue) - .foregroundColor(Color.white) - .cornerRadius(5) + Button( + NSLocalizedString("Sign Out", comment: "Sign out button"), + action: authViewModel.signOut + ) + .background(Color.blue) + .foregroundColor(Color.white) + .cornerRadius(5) - Button(NSLocalizedString("Disconnect", comment: "Disconnect button"), action: disconnect) - .background(Color.blue) - .foregroundColor(Color.white) - .cornerRadius(5) + Button( + NSLocalizedString("Disconnect", comment: "Disconnect button"), + action: authViewModel.disconnect + ) + .background(Color.blue) + .foregroundColor(Color.white) + .cornerRadius(5) Spacer() - NavigationLink(NSLocalizedString("View Days Until Birthday", comment: "View birthday days"), - destination: BirthdayView(birthdayViewModel: birthdayViewModel).onAppear { - guard self.birthdayViewModel.birthday != nil else { - if !self.authViewModel.hasBirthdayReadScope { - self.authViewModel.addBirthdayReadScope { - self.birthdayViewModel.fetchBirthday() + NavigationLink( + NSLocalizedString("View Days Until Birthday", comment: "View birthday days"), + destination: BirthdayView(birthdayViewModel: birthdayViewModel) + .onAppear { + guard self.birthdayViewModel.birthday != nil else { + if !self.authViewModel.hasBirthdayReadScope { + guard let window = NSApplication.shared.windows.first else { + print("There was no presenting window") + return + } + Task { @MainActor in + do { + let user = try await authViewModel.addBirthdayReadScope(window: window) + self.authViewModel.state = .signedIn(user) + self.birthdayViewModel.fetchBirthday() + } catch { + print("Failed to fetch birthday: \(error)") + } + } + } else { + self.birthdayViewModel.fetchBirthday() + } + return } - } else { - self.birthdayViewModel.fetchBirthday() - } - return - } - }) - .background(Color.blue) - .foregroundColor(Color.white) - .cornerRadius(5) + }) + .background(Color.blue) + .foregroundColor(Color.white) + .cornerRadius(5) Spacer() } } else { @@ -54,12 +72,4 @@ struct UserProfileView: View { } } } - - func disconnect() { - authViewModel.disconnect() - } - - func signOut() { - authViewModel.signOut() - } } From 7ac0d3d9be0ddacfb799128ee42e678b6110e054 Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Wed, 9 Nov 2022 11:16:58 -0800 Subject: [PATCH 02/11] Remove configuration references. --- .../Shared/Services/GoogleSignInAuthenticator.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index c9cf0a5b..42407562 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -39,7 +39,6 @@ final class GoogleSignInAuthenticator: ObservableObject { /// - throws: Any error that may arise during the sign in process. func signIn(with rootViewController: UIViewController) async throws -> GIDGoogleUser { return try await GIDSignIn.sharedInstance.signIn( - with: configuration, presenting: rootViewController ) } @@ -52,7 +51,6 @@ final class GoogleSignInAuthenticator: ObservableObject { /// - throws: Any error that may arise during the sign in process. func signIn(with window: NSWindow) async throws -> GIDGoogleUser { return try await GIDSignIn.sharedInstance.signIn( - with: configuration, presenting: window ) } From 0d1f63dae2b889e97a8d17f10002f1349060139d Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Wed, 9 Nov 2022 11:25:51 -0800 Subject: [PATCH 03/11] Use correct method signature. --- .../Shared/Services/GoogleSignInAuthenticator.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 42407562..70fcf074 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -38,9 +38,7 @@ final class GoogleSignInAuthenticator: ObservableObject { /// - returns: The signed in `GIDGoogleUser`. /// - throws: Any error that may arise during the sign in process. func signIn(with rootViewController: UIViewController) async throws -> GIDGoogleUser { - return try await GIDSignIn.sharedInstance.signIn( - presenting: rootViewController - ) + return try await GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) } #endif @@ -50,9 +48,7 @@ final class GoogleSignInAuthenticator: ObservableObject { /// - returns: The signed in `GIDGoogleUser`. /// - throws: Any error that may arise during the sign in process. func signIn(with window: NSWindow) async throws -> GIDGoogleUser { - return try await GIDSignIn.sharedInstance.signIn( - presenting: window - ) + return try await GIDSignIn.sharedInstance.signIn(withPresenting: window) } #endif From a75b5ab3ada092cf8b00a1204b2b0c19092c903a Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Thu, 8 Dec 2022 14:59:25 -0800 Subject: [PATCH 04/11] Cleanup. --- .../Shared/Services/BirthdayLoader.swift | 13 ------------ .../Services/GoogleSignInAuthenticator.swift | 16 +++++++------- .../ViewModels/AuthenticationViewModel.swift | 21 ++++++++----------- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift index ac136ca3..59bdee2e 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift @@ -38,19 +38,6 @@ final class BirthdayLoader { return URLRequest(url: url) }() - private lazy var session: URLSession? = { - guard let accessToken = GIDSignIn - .sharedInstance - .currentUser? - .accessToken - .tokenString else { return nil } - let configuration = URLSessionConfiguration.default - configuration.httpAdditionalHeaders = [ - "Authorization": "Bearer \(accessToken)" - ] - return URLSession(configuration: configuration) - }() - private func sessionWithFreshToken() async throws -> URLSession { guard let user = GIDSignIn.sharedInstance.currentUser else { throw Error.noCurrentUserForSessionWithFreshToken diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 931cf714..1657ce9a 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -33,25 +33,25 @@ final class GoogleSignInAuthenticator { } - #if os(iOS) +#if os(iOS) /// Signs in the user based upon the selected account. /// - parameter rootViewController: The `UIViewController` to use during the sign in flow. - /// - returns: The signed in `GIDGoogleUser`. + /// - returns: The resulting`GIDUserAuth`. /// - throws: Any error that may arise during the sign in process. func signIn(with rootViewController: UIViewController) async throws -> GIDUserAuth { return try await GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) } - #endif +#endif - #if os(macOS) +#if os(macOS) /// Signs in the user based upon the selected account. /// - parameter window: The `NSWindow` to use during the sign in flow. - /// - returns: The signed in `GIDGoogleUser`. + /// - returns: The resulting`GIDUserAuth`. /// - throws: Any error that may arise during the sign in process. func signIn(with window: NSWindow) async throws -> GIDUserAuth { return try await GIDSignIn.sharedInstance.signIn(withPresenting: window) } - #endif +#endif /// Signs out the current user. func signOut() { @@ -67,7 +67,7 @@ final class GoogleSignInAuthenticator { #if os(iOS) /// Adds the birthday read scope for the current user. /// - parameter viewController: The `UIViewController` to use while authorizing the scope. - /// - returns: The `GIDGoogleUser` with the authorized scope. + /// - returns: The resulting`GIDUserAuth`. /// - throws: Any error that may arise while authorizing the scope. func addBirthdayReadScope(viewController: UIViewController) async throws -> GIDUserAuth { guard let currentUser = GIDSignIn.sharedInstance.currentUser else { @@ -83,7 +83,7 @@ final class GoogleSignInAuthenticator { #if os(macOS) /// Adds the birthday read scope for the current user. /// - parameter window: The `NSWindow` to use while authorizing the scope. - /// - returns: The `GIDGoogleUser` with the authorized scope. + /// - returns: The resulting`GIDUserAuth`. /// - throws: Any error that may arise while authorizing the scope. func addBirthdayReadScope(window: NSWindow) async throws -> GIDUserAuth { guard let currentUser = GIDSignIn.sharedInstance.currentUser else { diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index 78a7c839..fc6962fa 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -52,30 +52,26 @@ final class AuthenticationViewModel: ObservableObject { print("There is no root view controller!") return } - - Task { @MainActor in - do { - let userAuth = try await authenticator.signIn(with: rootViewController) - self.state = .signedIn(userAuth.user) - } catch { - print("Error signing in: \(error)") - } - } #elseif os(macOS) guard let presentingWindow = NSApplication.shared.windows.first else { print("There is no presenting window!") return } +#endif Task { @MainActor in do { + +#if os(iOS) + let userAuth = try await authenticator.signIn(with: rootViewController) +#elseif os(macOS) let userAuth = try await authenticator.signIn(with: presentingWindow) +#endif self.state = .signedIn(userAuth.user) } catch { print("Error signing in: \(error)") } } -#endif } /// Signs the user out. @@ -102,17 +98,18 @@ final class AuthenticationViewModel: ObservableObject { #if os(iOS) /// Adds the requested birthday read scope. /// - parameter viewController: A `UIViewController` to use while presenting the flow. - /// - returns: A `GIDGoogleUser` with the authorized scope. + /// - returns: The resulting`GIDUserAuth`. /// - throws: Any error that may arise while adding the read birthday scope. func addBirthdayReadScope(viewController: UIViewController) async throws -> GIDUserAuth { return try await authenticator.addBirthdayReadScope(viewController: viewController) } #endif + #if os(macOS) /// adds the requested birthday read scope. /// - parameter window: An `NSWindow` to use while presenting the flow. - /// - returns: A `GIDGoogleUser` with the authorized scope. + /// - returns: The resulting `GIDUserAuth`. /// - throws: Any error that may arise while adding the read birthday scope. func addBirthdayReadScope(window: NSWindow) async throws -> GIDUserAuth { return try await authenticator.addBirthdayReadScope(window: window) From c1c4afeeb51d3a2b56751500743742287af67439 Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Thu, 8 Dec 2022 16:53:07 -0800 Subject: [PATCH 05/11] Update GIDUserAuth refs to GIDSignInResult. --- .../ViewModels/AuthenticationViewModel.swift | 14 +++++++------- .../DaysUntilBirthday/iOS/UserProfileView.swift | 4 ++-- .../DaysUntilBirthday/macOS/UserProfileView.swift | 5 +++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index fc6962fa..09de061b 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -63,11 +63,11 @@ final class AuthenticationViewModel: ObservableObject { do { #if os(iOS) - let userAuth = try await authenticator.signIn(with: rootViewController) + let signInResult = try await authenticator.signIn(with: rootViewController) #elseif os(macOS) - let userAuth = try await authenticator.signIn(with: presentingWindow) + let signInResult = try await authenticator.signIn(with: presentingWindow) #endif - self.state = .signedIn(userAuth.user) + self.state = .signedIn(signInResult.user) } catch { print("Error signing in: \(error)") } @@ -98,9 +98,9 @@ final class AuthenticationViewModel: ObservableObject { #if os(iOS) /// Adds the requested birthday read scope. /// - parameter viewController: A `UIViewController` to use while presenting the flow. - /// - returns: The resulting`GIDUserAuth`. + /// - returns: The `GIDSignInResult`. /// - throws: Any error that may arise while adding the read birthday scope. - func addBirthdayReadScope(viewController: UIViewController) async throws -> GIDUserAuth { + func addBirthdayReadScope(viewController: UIViewController) async throws -> GIDSignInResult { return try await authenticator.addBirthdayReadScope(viewController: viewController) } #endif @@ -109,9 +109,9 @@ final class AuthenticationViewModel: ObservableObject { #if os(macOS) /// adds the requested birthday read scope. /// - parameter window: An `NSWindow` to use while presenting the flow. - /// - returns: The resulting `GIDUserAuth`. + /// - returns: The `GIDSignInResult`. /// - throws: Any error that may arise while adding the read birthday scope. - func addBirthdayReadScope(window: NSWindow) async throws -> GIDUserAuth { + func addBirthdayReadScope(window: NSWindow) async throws -> GIDSignInResult { return try await authenticator.addBirthdayReadScope(window: window) } #endif diff --git a/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift b/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift index b3a627f1..d4d490cf 100644 --- a/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift +++ b/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift @@ -49,10 +49,10 @@ struct UserProfileView: View { } Task { @MainActor in do { - let userAuth = try await authViewModel.addBirthdayReadScope( + let signInResult = try await authViewModel.addBirthdayReadScope( viewController: viewController ) - self.authViewModel.state = .signedIn(userAuth.user) + self.authViewModel.state = .signedIn(signInResult.user) self.birthdayViewModel.fetchBirthday() } catch { print("Failed to fetch birthday: \(error)") diff --git a/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift b/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift index a9058a7b..6da47934 100644 --- a/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift +++ b/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift @@ -49,8 +49,9 @@ struct UserProfileView: View { } Task { @MainActor in do { - let userAuth = try await authViewModel.addBirthdayReadScope(window: window) - self.authViewModel.state = .signedIn(userAuth.user) + let signInResult = + try await authViewModel.addBirthdayReadScope(window: window) + self.authViewModel.state = .signedIn(signInResult.user) self.birthdayViewModel.fetchBirthday() } catch { print("Failed to fetch birthday: \(error)") From c57d715ff844bd463a6e40a6880bef6b3aff0ba4 Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Fri, 9 Dec 2022 10:43:19 -0800 Subject: [PATCH 06/11] Ensure calls to GSI are made on the main thread. --- .../Shared/Services/GoogleSignInAuthenticator.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 1518a224..1ede92ff 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -23,6 +23,7 @@ import AppKit #endif /// An observable class for authenticating via Google. +@MainActor final class GoogleSignInAuthenticator { private var authViewModel: AuthenticationViewModel From d0deb7377a1874be9fcbc477ad0378d04bb2dcc4 Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Fri, 9 Dec 2022 11:04:06 -0800 Subject: [PATCH 07/11] Fix build error by using MainActor on individual methods. --- .../Shared/Services/GoogleSignInAuthenticator.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 1ede92ff..319e9eb7 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -23,7 +23,6 @@ import AppKit #endif /// An observable class for authenticating via Google. -@MainActor final class GoogleSignInAuthenticator { private var authViewModel: AuthenticationViewModel @@ -38,6 +37,7 @@ final class GoogleSignInAuthenticator { /// - parameter rootViewController: The `UIViewController` to use during the sign in flow. /// - returns: The `GIDSignInResult`. /// - throws: Any error that may arise during the sign in process. + @MainActor func signIn(with rootViewController: UIViewController) async throws -> GIDSignInResult { return try await GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) } @@ -48,6 +48,7 @@ final class GoogleSignInAuthenticator { /// - parameter window: The `NSWindow` to use during the sign in flow. /// - returns: The `GIDSignInResult`. /// - throws: Any error that may arise during the sign in process. + @MainActor func signIn(with window: NSWindow) async throws -> GIDSignInResult { return try await GIDSignIn.sharedInstance.signIn(withPresenting: window) } @@ -60,6 +61,7 @@ final class GoogleSignInAuthenticator { } /// Disconnects the previously granted scope and signs the user out. + @MainActor func disconnect() async throws { try await GIDSignIn.sharedInstance.disconnect() } @@ -69,6 +71,7 @@ final class GoogleSignInAuthenticator { /// - parameter viewController: The `UIViewController` to use while authorizing the scope. /// - returns: The `GIDSignInResult`. /// - throws: Any error that may arise while authorizing the scope. + @MainActor func addBirthdayReadScope(viewController: UIViewController) async throws -> GIDSignInResult { guard let currentUser = GIDSignIn.sharedInstance.currentUser else { fatalError("No currentUser!") @@ -85,6 +88,7 @@ final class GoogleSignInAuthenticator { /// - parameter window: The `NSWindow` to use while authorizing the scope. /// - returns: The `GIDSignInResult`. /// - throws: Any error that may arise while authorizing the scope. + @MainActor func addBirthdayReadScope(window: NSWindow) async throws -> GIDSignInResult { guard let currentUser = GIDSignIn.sharedInstance.currentUser else { fatalError("No currentUser!") From 3cd4564ddc2556483046fe38c8e6e6f863876cfe Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Fri, 9 Dec 2022 15:17:34 -0800 Subject: [PATCH 08/11] Remove unnecessary MainActor annotations. --- .../Shared/ViewModels/AuthenticationViewModel.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index 09de061b..bce71aa2 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -59,9 +59,8 @@ final class AuthenticationViewModel: ObservableObject { } #endif - Task { @MainActor in + Task { do { - #if os(iOS) let signInResult = try await authenticator.signIn(with: rootViewController) #elseif os(macOS) @@ -81,7 +80,7 @@ final class AuthenticationViewModel: ObservableObject { /// Disconnects the previously granted scope and logs the user out. func disconnect() { - Task { @MainActor in + Task { do { try await authenticator.disconnect() authenticator.signOut() From 560554e46555759ade132cab19d5a129e60ce78f Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Fri, 9 Dec 2022 16:21:07 -0800 Subject: [PATCH 09/11] No need to signOut after disconnect. --- .../Shared/Services/GoogleSignInAuthenticator.swift | 1 + .../Shared/ViewModels/AuthenticationViewModel.swift | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 319e9eb7..9312683e 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -64,6 +64,7 @@ final class GoogleSignInAuthenticator { @MainActor func disconnect() async throws { try await GIDSignIn.sharedInstance.disconnect() + authViewModel.state = .signedOut } #if os(iOS) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index bce71aa2..36944cbb 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -78,12 +78,11 @@ final class AuthenticationViewModel: ObservableObject { authenticator.signOut() } - /// Disconnects the previously granted scope and logs the user out. + /// Disconnects the previously granted scope and signs the user out. func disconnect() { Task { do { try await authenticator.disconnect() - authenticator.signOut() } catch { print("Error disconnecting: \(error)") } From 707db4d5706c27bcc28725e007cb8e7b27e3cc77 Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Fri, 9 Dec 2022 16:27:34 -0800 Subject: [PATCH 10/11] GoogleSignInAuthenticator is no longer Observable. --- .../Shared/Services/GoogleSignInAuthenticator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 9312683e..8cd70cc2 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -22,7 +22,7 @@ import UIKit import AppKit #endif -/// An observable class for authenticating via Google. +/// A class for authenticating via Google. final class GoogleSignInAuthenticator { private var authViewModel: AuthenticationViewModel From a680352b087755bab82fc6eda6c50460dd5c80c1 Mon Sep 17 00:00:00 2001 From: Peter Andrews Date: Fri, 9 Dec 2022 16:44:56 -0800 Subject: [PATCH 11/11] Return MainActor annotations. --- .../Shared/ViewModels/AuthenticationViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index 36944cbb..9d16e0b2 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -59,7 +59,7 @@ final class AuthenticationViewModel: ObservableObject { } #endif - Task { + Task { @MainActor in do { #if os(iOS) let signInResult = try await authenticator.signIn(with: rootViewController) @@ -80,7 +80,7 @@ final class AuthenticationViewModel: ObservableObject { /// Disconnects the previously granted scope and signs the user out. func disconnect() { - Task { + Task { @MainActor in do { try await authenticator.disconnect() } catch {