Skip to content

Commit 4ed2a74

Browse files
refactor: move facebook sign-in to provider
1 parent a797eec commit 4ed2a74

3 files changed

Lines changed: 136 additions & 119 deletions

File tree

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ public protocol GoogleProviderProtocol {
66
@MainActor func signInWithGoogle(clientID: String) async throws -> AuthCredential
77
}
88

9-
public protocol FacebookProviderProtocol {}
9+
public protocol FacebookProviderProtocol {
10+
@MainActor func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential
11+
}
1012

1113
public enum AuthenticationProvider {
1214
case email
@@ -66,14 +68,6 @@ final class AuthListenerManager {
6668
@MainActor
6769
@Observable
6870
public final class AuthService {
69-
@ObservationIgnored @AppStorage("email-link") public var emailLink: String?
70-
public let configuration: AuthConfiguration
71-
public let auth: Auth
72-
private var listenerManager: AuthListenerManager?
73-
private let googleProvider: GoogleProviderProtocol?
74-
private let facebookProvider: FacebookProviderProtocol?
75-
public let string: StringUtils
76-
7771
public init(configuration: AuthConfiguration = AuthConfiguration(), auth: Auth = Auth.auth(),
7872
googleProvider: GoogleProviderProtocol? = nil,
7973
facebookProvider: FacebookProviderProtocol? = nil) {
@@ -85,10 +79,19 @@ public final class AuthService {
8579
listenerManager = AuthListenerManager(auth: auth, authEnvironment: self)
8680
}
8781

82+
@ObservationIgnored @AppStorage("email-link") public var emailLink: String?
83+
public let configuration: AuthConfiguration
84+
public let auth: Auth
85+
86+
public let string: StringUtils
8887
public var currentUser: User?
8988
public var authenticationState: AuthenticationState = .unauthenticated
9089
public var authenticationFlow: AuthenticationFlow = .login
9190

91+
private var listenerManager: AuthListenerManager?
92+
private let googleProvider: GoogleProviderProtocol?
93+
private let facebookProvider: FacebookProviderProtocol?
94+
9295
private var safeGoogleProvider: GoogleProviderProtocol {
9396
get throws {
9497
guard let provider = googleProvider else {
@@ -233,3 +236,20 @@ public extension AuthService {
233236
}
234237
}
235238
}
239+
240+
// MARK: - Facebook Sign In
241+
242+
public extension AuthService {
243+
func signInWithFacebook(limitedLogin: Bool = true) async throws {
244+
authenticationState = .authenticating
245+
do {
246+
let credential = try await safeFacebookProvider
247+
.signInWithFacebook(isLimitedLogin: limitedLogin)
248+
try await signIn(with: credential)
249+
updateAuthenticationState()
250+
} catch {
251+
authenticationState = .unauthenticated
252+
throw error
253+
}
254+
}
255+
}
Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,115 @@
1+
import AppTrackingTransparency
12
import FacebookCore
23
import FacebookLogin
4+
import FirebaseAuth
35
import FirebaseAuthSwiftUI
46

57
let kFacebookEmailScope = "email"
68
let kFacebookProfileScope = "public_profile"
79
let kDefaultFacebookScopes = [kFacebookEmailScope, kFacebookProfileScope]
8-
// TODO: - need to think how to handle this
9-
let kFacebookProviderId = "facebook.com"
1010

1111
public enum FacebookLoginType {
1212
case classic
1313
case limitedLogin
1414
}
1515

16+
public enum FacebookProviderError: Error {
17+
case signInCancelled(String)
18+
case configurationInvalid(String)
19+
case accessToken(String)
20+
case authenticationToken(String)
21+
}
22+
1623
public class FacebookProviderSwift: FacebookProviderProtocol {
1724
let scopes: [String]
1825
let shortName = "Facebook"
1926
let providerId = "facebook.com"
27+
private let loginManager = LoginManager()
28+
private var rawNonce: String
29+
private var shaNonce: String
30+
2031
public init(scopes: [String]? = nil) {
2132
self.scopes = scopes ?? kDefaultFacebookScopes
33+
rawNonce = FacebookUtils.randomNonce()
34+
shaNonce = FacebookUtils.sha256Hash(of: rawNonce)
2235
}
2336

24-
public func authenticateWithClassic() {}
37+
@MainActor public func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential {
38+
let trackingStatus = ATTrackingManager.trackingAuthorizationStatus
39+
let tracking: LoginTracking = trackingStatus != .authorized ? .limited :
40+
(isLimitedLogin ? .limited : .enabled)
41+
42+
guard let configuration: LoginConfiguration = {
43+
if tracking == .limited {
44+
return LoginConfiguration(
45+
permissions: scopes,
46+
tracking: tracking,
47+
nonce: shaNonce
48+
)
49+
} else {
50+
return LoginConfiguration(
51+
permissions: scopes,
52+
tracking: tracking
53+
)
54+
}
55+
}() else {
56+
throw FacebookProviderError
57+
.configurationInvalid("Failed to create Facebook login configuration")
58+
}
59+
60+
let result = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
61+
Void,
62+
Error
63+
>) in
64+
loginManager.logIn(
65+
configuration: configuration
66+
) { result in
67+
switch result {
68+
case .cancelled:
69+
continuation
70+
.resume(throwing: FacebookProviderError.signInCancelled("User cancelled sign-in"))
71+
// showCanceledAlert = true
72+
case let .failed(error):
73+
continuation.resume(throwing: error)
74+
// errorMessage = authService.string.localizedErrorMessage(for: error)
75+
case .success:
76+
continuation.resume()
77+
}
78+
}
79+
}
80+
if isLimitedLogin {
81+
return try limitedLogin()
82+
} else {
83+
return try classicLogin()
84+
}
85+
}
86+
87+
private func classicLogin() throws -> AuthCredential {
88+
if let token = AccessToken.current,
89+
!token.isExpired {
90+
let credential = FacebookAuthProvider
91+
.credential(withAccessToken: token.tokenString)
92+
93+
return credential
94+
} else {
95+
throw FacebookProviderError
96+
.accessToken(
97+
"Access token has expired or not available. Please sign-in with Facebook before attempting to create a Facebook provider credential"
98+
)
99+
}
100+
}
101+
102+
private func limitedLogin() throws -> AuthCredential {
103+
if let idToken = AuthenticationToken.current {
104+
let credential = OAuthProvider.credential(withProviderID: providerId,
105+
idToken: idToken.tokenString,
106+
rawNonce: rawNonce)
107+
return credential
108+
} else {
109+
throw FacebookProviderError
110+
.authenticationToken(
111+
"Authentication is not available. Please sign-in with Facebook before attempting to create a Facebook provider credential"
112+
)
113+
}
114+
}
25115
}

FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Views/FacebookButtonView.swift

Lines changed: 14 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -5,119 +5,15 @@ import FirebaseAuth
55
import FirebaseAuthSwiftUI
66
import SwiftUI
77

8-
public enum FacebookError: Error {
9-
case accessToken(String)
10-
case authenticationToken(String)
11-
}
12-
138
@MainActor
149
public struct FacebookButtonView {
1510
@Environment(AuthService.self) private var authService
1611
@State private var errorMessage = ""
17-
@State private var limitedLogin: Bool = true
1812
@State private var showCanceledAlert = false
1913
@State private var showUserTrackingAlert = false
14+
@State private var limitedLogin = true
2015

21-
private var rawNonce: String
22-
private var shaNonce: String
23-
private let loginManager = LoginManager()
24-
25-
public init() {
26-
rawNonce = FacebookUtils.randomNonce()
27-
shaNonce = FacebookUtils.sha256Hash(of: rawNonce)
28-
}
29-
30-
private var trackingPreference: LoginTracking {
31-
// if not authorized, Facebook will default to limited login and classic login will fail
32-
let trackingStatus = ATTrackingManager.trackingAuthorizationStatus
33-
if trackingStatus != .authorized {
34-
return .limited
35-
}
36-
37-
return limitedLogin ? .limited : .enabled
38-
}
39-
40-
var configuration: LoginConfiguration? {
41-
if trackingPreference == .limited {
42-
return LoginConfiguration(
43-
permissions: [.publicProfile, .email],
44-
tracking: trackingPreference,
45-
nonce: shaNonce
46-
)
47-
}
48-
return LoginConfiguration(
49-
permissions: [.publicProfile, .email],
50-
tracking: trackingPreference
51-
)
52-
}
53-
54-
func invokeLoginMethod() {
55-
let validConfiguration = configuration
56-
if validConfiguration != nil {
57-
loginManager.logIn(
58-
configuration: validConfiguration
59-
) { result in
60-
switch result {
61-
case .cancelled:
62-
showCanceledAlert = true
63-
case let .failed(error):
64-
errorMessage = authService.string.localizedErrorMessage(for: error)
65-
case .success:
66-
if trackingPreference == .limited {
67-
Task {
68-
await limitedLogin()
69-
}
70-
} else {
71-
Task {
72-
await classicLogin()
73-
}
74-
}
75-
}
76-
}
77-
} else {
78-
errorMessage = "Facebook configuration is invalid."
79-
}
80-
}
81-
82-
private func classicLogin() async {
83-
do {
84-
if let token = AccessToken.current,
85-
!token.isExpired {
86-
let credential = FacebookAuthProvider
87-
.credential(withAccessToken: token.tokenString)
88-
try await authService.signIn(with: credential)
89-
} else {
90-
throw FacebookError
91-
.accessToken(
92-
"Access token has expired or not available. Please sign-in with Facebook before attempting to create a Facebook provider credential"
93-
)
94-
}
95-
} catch {
96-
errorMessage = authService.string.localizedErrorMessage(
97-
for: error
98-
)
99-
}
100-
}
101-
102-
private func limitedLogin() async {
103-
do {
104-
if let idToken = AuthenticationToken.current {
105-
let credential = OAuthProvider.credential(withProviderID: kFacebookProviderId,
106-
idToken: idToken.tokenString,
107-
rawNonce: rawNonce)
108-
try await authService.signIn(with: credential)
109-
} else {
110-
throw FacebookError
111-
.authenticationToken(
112-
"Authentication is not available. Please sign-in with Facebook before attempting to create a Facebook provider credential"
113-
)
114-
}
115-
} catch {
116-
errorMessage = authService.string.localizedErrorMessage(
117-
for: error
118-
)
119-
}
120-
}
16+
public init() {}
12117

12218
private var limitedLoginBinding: Binding<Bool> {
12319
Binding(
@@ -150,7 +46,18 @@ public struct FacebookButtonView {
15046
extension FacebookButtonView: View {
15147
public var body: some View {
15248
Button(action: {
153-
invokeLoginMethod()
49+
Task {
50+
do {
51+
try await authService.signInWithFacebook(limitedLogin: limitedLogin)
52+
} catch {
53+
switch error {
54+
case FacebookProviderError.signInCancelled:
55+
showCanceledAlert = true
56+
default:
57+
errorMessage = authService.string.localizedErrorMessage(for: error)
58+
}
59+
}
60+
}
15461
}) {
15562
HStack {
15663
Image(systemName: "f.circle.fill")

0 commit comments

Comments
 (0)