diff --git a/README.md b/README.md index 2009216..4ac93f9 100644 --- a/README.md +++ b/README.md @@ -54,19 +54,13 @@ let manager = SocialAuthenticationManager(authenticators: [AppleAuthenticator(), // signIn user with Apple let appleAuthenticator = manager.authenticator(for: AppleAuthenticator.self) -appleAuthenticator? +let result = try await appleAuthenticator? .signIn(from: , with: .random(length: 32)) - .finally { - // handle result - } // signIn user with Facebook let facebookAuthenticator = manager.authenticator(for: FacebookAuthenticator.self) -facebookAuthenticator? +let result = try await facebookAuthenticator? .signIn(from: , with: [.email]) - .finally { - // handle result - } // return currently authenticated authenticator let authenticated: Authenticator? = manager.authenticator @@ -123,11 +117,11 @@ You can easily add new authenticator that is not built-in with PovioKitAuth pack ```swift final class SnapchatAuthenticator: Authenticator { - public func signIn(from presentingViewController: UIViewController) -> Promise { - Promise { seal in + public func signIn(from presentingViewController: UIViewController) async throws -> Response { + try await withCheckedThrowingContinuation { continuation in SCSDKLoginClient.login(from: presentingViewController) { [weak self] success, error in guard success, error == nil else { - seal.reject(with: error) + continuation.resume(throwing: error) return } @@ -135,7 +129,7 @@ final class SnapchatAuthenticator: Authenticator { let variables = ["page": "bitmoji"] SCSDKLoginClient.fetchUserData(withQuery: query, variables: variables) { resources in ... - seal.resolve(with: response) + continuation.resume(returning: response) } } } diff --git a/Resources/Apple/README.md b/Resources/Apple/README.md index 74e9dde..c701d5a 100644 --- a/Resources/Apple/README.md +++ b/Resources/Apple/README.md @@ -20,30 +20,16 @@ Please read [official documentation](https://developer.apple.com/sign-in-with-ap let authenticator = AppleAuthenticator() // conforms to `AppleAuthProvidable` protocol // signIn user -authenticator +let result = try await authenticator .signIn(from: ) - .finally { - // handle result - } // signIn user with nonce -authenticator +let result = try await authenticator .signIn(from: , with: .random(length: 32)) - .finally { - // handle result - } // get authentication status let status = authenticator.isAuthenticated -// check authentication status -// we should check this when we need to explicitly query authenticator to check if authenticated -authenticator - .checkAuthentication - .finally { - // check result - } - // signOut user authenticator.signOut() // all provider data regarding the use auth is cleared at this point ``` diff --git a/Resources/Facebook/README.md b/Resources/Facebook/README.md index 3335fdf..3cb3062 100644 --- a/Resources/Facebook/README.md +++ b/Resources/Facebook/README.md @@ -12,18 +12,12 @@ Please read [official documentation](https://developers.facebook.com/docs/facebo let authenticator = FacebookAuthenticator() // signIn user with default permissions -authenticator +let result = try await authenticator .signIn(from: ) - .finally { - // handle result - } // signIn user with custom permissions -authenticator +let result = try await authenticator .signIn(from: , with: []) - .finally { - // handle result - } // get authentication status let state = authenticator.isAuthenticated diff --git a/Resources/Google/README.md b/Resources/Google/README.md index 725d2d6..5fa50ee 100644 --- a/Resources/Google/README.md +++ b/Resources/Google/README.md @@ -12,11 +12,8 @@ Please read [official documentation](https://developers.google.com/identity/sign let authenticator = GoogleAuthenticator() // signIn user -authenticator +let result = try await authenticator .signIn(from: ) - .finally { - // handle result - } // get authentication status let state = authenticator.isAuthenticated diff --git a/Sources/Apple/AppleAuthenticator.swift b/Sources/Apple/AppleAuthenticator.swift index a881de2..37901de 100644 --- a/Sources/Apple/AppleAuthenticator.swift +++ b/Sources/Apple/AppleAuthenticator.swift @@ -9,15 +9,14 @@ import AuthenticationServices import Foundation import PovioKitAuthCore -import PovioKitPromise public final class AppleAuthenticator: NSObject { private let storage: UserDefaults private let storageUserIdKey = "signIn.userId" private let storageAuthenticatedKey = "authenticated" private let provider: ASAuthorizationAppleIDProvider - private var processingPromise: Promise? - + private var continuation: CheckedContinuation? + public init(storage: UserDefaults? = nil) { self.provider = .init() self.storage = storage ?? .init(suiteName: "povioKit.auth.apple") ?? .standard @@ -34,49 +33,31 @@ public final class AppleAuthenticator: NSObject { extension AppleAuthenticator: Authenticator { /// SignIn user /// - /// Will return promise with the `Response` object on success or with `Error` on error. - public func signIn(from presentingViewController: UIViewController) -> Promise { - let promise = Promise() - processingPromise = promise - appleSignIn(on: presentingViewController, with: nil) - return promise + /// Will asynchronously return the `Response` object on success or `Error` on error. + public func signIn(from presentingViewController: UIViewController) async throws -> Response { + try await appleSignIn(on: presentingViewController, with: nil) } - + /// SignIn user with `nonce` value /// /// Nonce is usually needed when doing auth with an external auth provider (e.g. firebase). - /// Will return promise with the `Response` object on success or with `Error` on error. - public func signIn(from presentingViewController: UIViewController, with nonce: Nonce) -> Promise { - let promise = Promise() - processingPromise = promise - appleSignIn(on: presentingViewController, with: nonce) - return promise + /// Will asynchronously return the `Response` object on success or `Error` on error. + public func signIn(from presentingViewController: UIViewController, with nonce: Nonce) async throws -> Response { + try await appleSignIn(on: presentingViewController, with: nonce) } - + /// Clears the signIn footprint and logs out the user immediatelly. public func signOut() { storage.removeObject(forKey: storageUserIdKey) - rejectSignIn(with: .cancelled) + storage.setValue(false, forKey: storageAuthenticatedKey) + continuation = nil } /// Returns the current authentication state. public var isAuthenticated: Authenticated { storage.string(forKey: storageUserIdKey) != nil && storage.bool(forKey: storageAuthenticatedKey) } - - /// Checks the current auth state and returns the boolean value as promise. - public var checkAuthentication: Promise { - guard let userId = storage.string(forKey: storageUserIdKey) else { - return .value(false) - } - - return Promise { seal in - ASAuthorizationAppleIDProvider().getCredentialState(forUserID: userId) { credentialsState, _ in - seal.resolve(with: credentialsState == .authorized) - } - } - } - + /// Boolean if given `url` should be handled. /// /// Call this from UIApplicationDelegate’s `application:openURL:options:` method. @@ -137,7 +118,7 @@ extension AppleAuthenticator: ASAuthorizationControllerDelegate { email: email, expiresAt: expiresAt) - processingPromise?.resolve(with: response) + continuation?.resume(with: .success(response)) case _: rejectSignIn(with: .unhandledAuthorization) } @@ -155,15 +136,14 @@ extension AppleAuthenticator: ASAuthorizationControllerDelegate { // MARK: - Private Methods private extension AppleAuthenticator { - func appleSignIn(on presentingViewController: UIViewController, with nonce: Nonce?) { + func appleSignIn(on presentingViewController: UIViewController, with nonce: Nonce?) async throws -> Response { let request = provider.createRequest() request.requestedScopes = [.fullName, .email] - + switch nonce { case .random(let length): guard length > 0 else { - rejectSignIn(with: .invalidNonceLength) - return + throw Error.invalidNonceLength } request.nonce = generateNonceString(length: length).sha256 case .custom(let value): @@ -171,11 +151,14 @@ private extension AppleAuthenticator { case .none: break } - - let controller = ASAuthorizationController(authorizationRequests: [request]) - controller.delegate = self - controller.presentationContextProvider = presentingViewController - controller.performRequests() + + return try await withCheckedThrowingContinuation { continuation in + let controller = ASAuthorizationController(authorizationRequests: [request]) + controller.delegate = self + controller.presentationContextProvider = presentingViewController + self.continuation = continuation + controller.performRequests() + } } func setupCredentialsRevokeListener() { @@ -187,8 +170,8 @@ private extension AppleAuthenticator { func rejectSignIn(with error: Error) { storage.setValue(false, forKey: storageAuthenticatedKey) - processingPromise?.reject(with: error) - processingPromise = nil + continuation?.resume(throwing: error) + continuation = nil } } diff --git a/Sources/Facebook/FacebookAuthenticator.swift b/Sources/Facebook/FacebookAuthenticator.swift index bc115b2..d811bd4 100644 --- a/Sources/Facebook/FacebookAuthenticator.swift +++ b/Sources/Facebook/FacebookAuthenticator.swift @@ -10,7 +10,6 @@ import Foundation import FacebookLogin import PovioKitCore import PovioKitAuthCore -import PovioKitPromise public final class FacebookAuthenticator { private let provider: LoginManager @@ -25,15 +24,14 @@ extension FacebookAuthenticator: Authenticator { /// SignIn user. /// /// The `permissions` to use when doing a sign in. - /// Will return promise with the `Response` object on success or with `Error` on error. + /// Will asynchronously return the `Response` object on success or `Error` on error. public func signIn( from presentingViewController: UIViewController, - with permissions: [Permission] = [.email, .publicProfile]) -> Promise + with permissions: [Permission] = [.email, .publicProfile]) async throws -> Response { let permissions: [String] = permissions.map { $0.name } - - return signIn(with: permissions, on: presentingViewController) - .flatMap(with: fetchUserDetails) + let token = try await signIn(with: permissions, on: presentingViewController) + return try await fetchUserDetails(with: token) } /// Clears the signIn footprint and logs out the user immediatelly. @@ -69,38 +67,37 @@ public extension FacebookAuthenticator { // MARK: - Private Methods private extension FacebookAuthenticator { - func signIn(with permissions: [String], on presentingViewController: UIViewController) -> Promise { - Promise { seal in - provider - .logIn(permissions: permissions, from: presentingViewController) { result, error in - switch (result, error) { - case (let result?, nil): - if result.isCancelled { - seal.reject(with: Error.cancelled) - } else if let token = result.token { - seal.resolve(with: token) - } else { - seal.reject(with: Error.invalidIdentityToken) - } - case (nil, let error?): - seal.reject(with: Error.system(error)) - case _: - seal.reject(with: Error.system(NSError(domain: "com.povio.facebook.error", code: -1, userInfo: nil))) + func signIn(with permissions: [String], on presentingViewController: UIViewController) async throws -> AccessToken { + try await withCheckedThrowingContinuation { continuation in + provider.logIn(permissions: permissions, from: presentingViewController) { result, error in + switch (result, error) { + case (let result?, nil): + if result.isCancelled { + continuation.resume(throwing: Error.cancelled) + } else if let token = result.token { + continuation.resume(returning: token) + } else { + continuation.resume(throwing: Error.invalidIdentityToken) } + case (nil, let error?): + continuation.resume(throwing: Error.system(error)) + default: + continuation.resume(throwing: Error.system(NSError(domain: "com.povio.facebook.error", code: -1, userInfo: nil))) } + } } } - - func fetchUserDetails(with token: AccessToken) -> Promise { - let request = GraphRequest( - graphPath: "me", - parameters: ["fields": "id, email, first_name, last_name"], - tokenString: token.tokenString, - httpMethod: nil, - flags: .doNotInvalidateTokenOnError - ) - - return Promise { seal in + + func fetchUserDetails(with token: AccessToken) async throws -> Response { + try await withCheckedThrowingContinuation { continuation in + let request = GraphRequest( + graphPath: "me", + parameters: ["fields": "id, email, first_name, last_name"], + tokenString: token.tokenString, + httpMethod: nil, + flags: .doNotInvalidateTokenOnError + ) + request.start { _, result, error in switch result { case .some(let response): @@ -110,7 +107,7 @@ private extension FacebookAuthenticator { do { let data = try JSONSerialization.data(withJSONObject: response, options: []) let object = try data.decode(GraphResponse.self, with: decoder) - + let authResponse = Response( userId: object.id, token: token.tokenString, @@ -118,12 +115,12 @@ private extension FacebookAuthenticator { email: object.email, expiresAt: token.expirationDate ) - seal.resolve(with: authResponse) + continuation.resume(returning: authResponse) } catch { - seal.reject(with: Error.userDataDecode) + continuation.resume(throwing: Error.userDataDecode) } case .none: - seal.reject(with: Error.missingUserData) + continuation.resume(throwing: Error.missingUserData) } } } diff --git a/Sources/Google/GoogleAuthenticator.swift b/Sources/Google/GoogleAuthenticator.swift index 467c553..6e7f88d 100644 --- a/Sources/Google/GoogleAuthenticator.swift +++ b/Sources/Google/GoogleAuthenticator.swift @@ -9,7 +9,6 @@ import Foundation import GoogleSignIn import PovioKitAuthCore -import PovioKitPromise public final class GoogleAuthenticator { private let provider: GIDSignIn @@ -23,45 +22,15 @@ public final class GoogleAuthenticator { extension GoogleAuthenticator: Authenticator { /// SignIn user. /// - /// Will return promise with the `Response` object on success or with `Error` on error. + /// Will asynchronously return the `Response` object on success or `Error` on error. public func signIn(from presentingViewController: UIViewController, hint: String? = .none, - additionalScopes: [String]? = .none) -> Promise { + additionalScopes: [String]? = .none) async throws -> Response { guard !provider.hasPreviousSignIn() else { - // restore user - return Promise { seal in - provider.restorePreviousSignIn { result, error in - switch (result, error) { - case (let user?, _): - seal.resolve(with: user.authResponse) - case (_, let actualError?): - seal.reject(with: Error.system(actualError)) - case (.none, .none): - seal.reject(with: Error.unhandledAuthorization) - } - } - } + return try await restorePreviousSignIn() } - // sign in - return Promise { seal in - provider - .signIn(withPresenting: presentingViewController, hint: hint, additionalScopes: additionalScopes) { result, error in - switch (result, error) { - case (let signInResult?, _): - seal.resolve(with: signInResult.user.authResponse) - case (_, let actualError?): - let errorCode = (actualError as NSError).code - if errorCode == GIDSignInError.Code.canceled.rawValue { - seal.reject(with: Error.cancelled) - } else { - seal.reject(with: Error.system(actualError)) - } - case (.none, .none): - seal.reject(with: Error.unhandledAuthorization) - } - } - } + return try await signInUser(from: presentingViewController, hint: hint, additionalScopes: additionalScopes) } /// Clears the signIn footprint and logs out the user immediatelly. @@ -82,6 +51,44 @@ extension GoogleAuthenticator: Authenticator { } } +// MARK: - Private Methods +private extension GoogleAuthenticator { + func restorePreviousSignIn() async throws -> Response { + try await withCheckedThrowingContinuation { continuation in + provider.restorePreviousSignIn { user, error in + if let user = user { + continuation.resume(returning: user.authResponse) + } else if let error = error { + continuation.resume(throwing: Error.system(error)) + } else { + continuation.resume(throwing: Error.unhandledAuthorization) + } + } + } + } + + func signInUser(from presentingViewController: UIViewController, hint: String?, additionalScopes: [String]?) async throws -> Response { + try await withCheckedThrowingContinuation { continuation in + provider + .signIn(withPresenting: presentingViewController, hint: hint, additionalScopes: additionalScopes) { result, error in + switch (result, error) { + case (let signInResult?, _): + continuation.resume(returning: signInResult.user.authResponse) + case (_, let actualError?): + let errorCode = (actualError as NSError).code + if errorCode == GIDSignInError.Code.canceled.rawValue { + continuation.resume(throwing: Error.cancelled) + } else { + continuation.resume(throwing: Error.system(actualError)) + } + case (.none, .none): + continuation.resume(throwing: Error.unhandledAuthorization) + } + } + } + } +} + // MARK: - Error public extension GoogleAuthenticator { enum Error: Swift.Error { diff --git a/Sources/LinkedIn/LinkedInAuthenticator.swift b/Sources/LinkedIn/LinkedInAuthenticator.swift index 79155d3..bc91c4d 100644 --- a/Sources/LinkedIn/LinkedInAuthenticator.swift +++ b/Sources/LinkedIn/LinkedInAuthenticator.swift @@ -25,7 +25,7 @@ public final class LinkedInAuthenticator { extension LinkedInAuthenticator: Authenticator { /// SignIn user. /// - /// Will return promise with the `Response` object on success or with `Error` on error. + /// Will asynchronously return the `Response` object on success or `Error` on error. public func signIn(authCode: String, configuration: Configuration) async throws -> Response { let authRequest: LinkedInAPI.LinkedInAuthRequest = .init( code: authCode,