Skip to content

Commit 855b96d

Browse files
refactor: implementations as described in API doc
1 parent a863e58 commit 855b96d

File tree

10 files changed

+77
-98
lines changed

10 files changed

+77
-98
lines changed

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public enum AuthServiceError: LocalizedError {
3636
case invalidCredentials(String)
3737
case signInFailed(underlying: Error)
3838
case accountMergeConflict(context: AccountMergeConflictContext)
39+
case providerNotFound(String)
3940

4041
public var errorDescription: String? {
4142
switch self {
@@ -55,6 +56,8 @@ public enum AuthServiceError: LocalizedError {
5556
return "Failed to sign in: \(error.localizedDescription)"
5657
case let .accountMergeConflict(context):
5758
return context.errorDescription
59+
case let .providerNotFound(description):
60+
return description
5861
}
5962
}
6063
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 31 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,21 @@
1515
@preconcurrency import FirebaseAuth
1616
import SwiftUI
1717

18-
public protocol ExternalAuthProvider {
19-
var id: String { get }
20-
@MainActor func authButton() -> AnyView
18+
public protocol AuthProviderSwift {
19+
@MainActor func createAuthCredential() async throws -> AuthCredential
2120
}
2221

23-
public protocol GoogleProviderAuthUIProtocol: ExternalAuthProvider {
24-
@MainActor func signInWithGoogle(clientID: String) async throws -> AuthCredential
25-
@MainActor func deleteUser(user: User) async throws
22+
public protocol AuthProviderUI {
23+
var id: String { get }
24+
@MainActor func authButton() -> AnyView
25+
var provider: AuthProviderSwift { get }
2626
}
2727

28-
public protocol FacebookProviderAuthUIProtocol: ExternalAuthProvider {
29-
@MainActor func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential
28+
public protocol DeleteUserSwift {
3029
@MainActor func deleteUser(user: User) async throws
3130
}
3231

33-
public protocol PhoneAuthProviderAuthUIProtocol: ExternalAuthProvider {
32+
public protocol PhoneAuthProviderAuthUIProtocol: AuthProviderSwift {
3433
@MainActor func verifyPhoneNumber(phoneNumber: String) async throws -> String
3534
}
3635

@@ -137,30 +136,14 @@ public final class AuthService {
137136

138137
// MARK: - Provider APIs
139138

140-
private var unsafeGoogleProvider: (any GoogleProviderAuthUIProtocol)?
141-
private var unsafeFacebookProvider: (any FacebookProviderAuthUIProtocol)?
142-
private var unsafePhoneAuthProvider: (any PhoneAuthProviderAuthUIProtocol)?
143-
144139
private var listenerManager: AuthListenerManager?
145140
public var signedInCredential: AuthCredential?
146141

147142
var emailSignInEnabled = false
148143

149-
private var providers: [ExternalAuthProvider] = []
150-
public func register(provider: ExternalAuthProvider) {
151-
switch provider {
152-
case let google as GoogleProviderAuthUIProtocol:
153-
unsafeGoogleProvider = google
154-
providers.append(provider)
155-
case let facebook as FacebookProviderAuthUIProtocol:
156-
unsafeFacebookProvider = facebook
157-
providers.append(provider)
158-
case let phone as PhoneAuthProviderAuthUIProtocol:
159-
unsafePhoneAuthProvider = phone
160-
providers.append(provider)
161-
default:
162-
break
163-
}
144+
private var providers: [AuthProviderUI] = []
145+
public func registerProvider(provider: AuthProviderUI) {
146+
providers.append(provider)
164147
}
165148

166149
public func renderButtons(spacing: CGFloat = 16) -> AnyView {
@@ -173,31 +156,9 @@ public final class AuthService {
173156
)
174157
}
175158

176-
private var googleProvider: any GoogleProviderAuthUIProtocol {
177-
get throws {
178-
guard let provider = unsafeGoogleProvider else {
179-
fatalError("`GoogleProviderAuthUI` has not been configured")
180-
}
181-
return provider
182-
}
183-
}
184-
185-
private var facebookProvider: any FacebookProviderAuthUIProtocol {
186-
get throws {
187-
guard let provider = unsafeFacebookProvider else {
188-
fatalError("`FacebookProviderAuthUI` has not been configured")
189-
}
190-
return provider
191-
}
192-
}
193-
194-
private var phoneAuthProvider: any PhoneAuthProviderAuthUIProtocol {
195-
get throws {
196-
guard let provider = unsafePhoneAuthProvider else {
197-
fatalError("`PhoneAuthProviderAuthUI` has not been configured")
198-
}
199-
return provider
200-
}
159+
public func signInWithProvider(_ provider: AuthProviderSwift) async throws {
160+
let credential = try await provider.createAuthCredential()
161+
try await signIn(credentials: credential)
201162
}
202163

203164
// MARK: - End Provider APIs
@@ -327,13 +288,16 @@ public extension AuthService {
327288
if providerId == EmailAuthProviderID {
328289
let operation = EmailPasswordDeleteUserOperation(passwordPrompt: passwordPrompt)
329290
try await operation(on: user)
330-
} else if providerId == FacebookAuthProviderID {
331-
try await facebookProvider.deleteUser(user: user)
332-
} else if providerId == GoogleAuthProviderID {
333-
try await googleProvider.deleteUser(user: user)
291+
} else {
292+
// Find provider by matching ID and ensure it can delete users
293+
guard let matchingProvider = providers.first(where: { $0.id == providerId }),
294+
let provider = matchingProvider.provider as? DeleteUserSwift else {
295+
throw AuthServiceError.providerNotFound("No provider found for \(providerId)")
296+
}
297+
298+
try await provider.deleteUser(user: user)
334299
}
335300
}
336-
337301
} catch {
338302
errorMessage = string.localizedErrorMessage(
339303
for: error
@@ -488,43 +452,20 @@ public extension AuthService {
488452
}
489453
}
490454

491-
// MARK: - Google Sign In
492-
493-
public extension AuthService {
494-
func signInWithGoogle() async throws {
495-
guard let clientID = auth.app?.options.clientID else {
496-
throw AuthServiceError
497-
.clientIdNotFound(
498-
"OAuth client ID not found. Please make sure Google Sign-In is enabled in the Firebase console. You may have to download a new GoogleService-Info.plist file after enabling Google Sign-In."
499-
)
500-
}
501-
let credential = try await googleProvider.signInWithGoogle(clientID: clientID)
502-
503-
try await signIn(credentials: credential)
504-
}
505-
}
506-
507-
// MARK: - Facebook Sign In
508-
509-
public extension AuthService {
510-
func signInWithFacebook(limitedLogin: Bool = true) async throws {
511-
let credential = try await facebookProvider
512-
.signInWithFacebook(isLimitedLogin: limitedLogin)
513-
try await signIn(credentials: credential)
514-
}
515-
}
516455

517456
// MARK: - Phone Auth Sign In
518457

519458
public extension AuthService {
520459
func verifyPhoneNumber(phoneNumber: String) async throws -> String {
521-
do {
522-
return try await phoneAuthProvider.verifyPhoneNumber(phoneNumber: phoneNumber)
523-
} catch {
524-
errorMessage = string.localizedErrorMessage(
525-
for: error
526-
)
527-
throw error
460+
return try await withCheckedThrowingContinuation { continuation in
461+
PhoneAuthProvider.provider()
462+
.verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in
463+
if let error = error {
464+
continuation.resume(throwing: error)
465+
return
466+
}
467+
continuation.resume(returning: verificationID!)
468+
}
528469
}
529470
}
530471

FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AuthService+Facebook.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public extension AuthService {
2525
@discardableResult
2626
func withFacebookSignIn(scopes scopes: [String]? = nil) -> AuthService {
2727
FacebookProviderAuthUI.configureProvider(scopes: scopes)
28-
register(provider: FacebookProviderAuthUI.shared)
28+
registerProvider(provider: FacebookProviderAuthUI.shared)
2929
return self
3030
}
3131
}

FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public enum FacebookProviderError: Error {
3131
case authenticationToken(String)
3232
}
3333

34-
public class FacebookProviderAuthUI: FacebookProviderAuthUIProtocol {
34+
public class FacebookProviderAuthUI: AuthProviderSwift, AuthProviderUI, DeleteUserSwift {
3535
public let id: String = "facebook"
3636
let scopes: [String]
3737
let shortName = "Facebook"
@@ -41,6 +41,8 @@ public class FacebookProviderAuthUI: FacebookProviderAuthUIProtocol {
4141
private var shaNonce: String?
4242
// Needed for reauthentication
4343
var isLimitedLogin: Bool = true
44+
45+
public var provider: AuthProviderSwift { self }
4446

4547
@MainActor private static var _shared: FacebookProviderAuthUI =
4648
.init(scopes: kDefaultFacebookScopes)
@@ -66,6 +68,10 @@ public class FacebookProviderAuthUI: FacebookProviderAuthUIProtocol {
6668
try await operation(on: user)
6769
}
6870

71+
@MainActor public func createAuthCredential() async throws -> AuthCredential {
72+
return try await signInWithFacebook(isLimitedLogin: true)
73+
}
74+
6975
@MainActor public func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential {
7076
let loginType: LoginTracking = isLimitedLogin ? .limited : .enabled
7177
self.isLimitedLogin = isLimitedLogin

FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Views/SignInWithFacebookButton.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ extension SignInWithFacebookButton: View {
6565
Button(action: {
6666
Task {
6767
do {
68-
try await authService.signInWithFacebook(limitedLogin: limitedLogin)
68+
let credential = try await FacebookProviderAuthUI.shared.signInWithFacebook(isLimitedLogin: limitedLogin)
69+
try await authService.signIn(credentials: credential)
6970
} catch {
7071
switch error {
7172
case FacebookProviderError.signInCancelled:

FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AuthService+Google.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public extension AuthService {
2525
@discardableResult
2626
func withGoogleSignIn(scopes scopes: [String]? = nil) -> AuthService {
2727
let clientID = auth.app?.options.clientID ?? ""
28-
register(provider: GoogleProviderAuthUI(scopes: scopes, clientID: clientID))
28+
registerProvider(provider: GoogleProviderAuthUI(scopes: scopes, clientID: clientID))
2929
return self
3030
}
3131
}

FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ public enum GoogleProviderError: Error {
2929
case user(String)
3030
}
3131

32-
public class GoogleProviderAuthUI: @preconcurrency GoogleProviderAuthUIProtocol {
32+
public class GoogleProviderAuthUI: AuthProviderSwift, AuthProviderUI, DeleteUserSwift {
3333
public let id: String = "google"
3434
let scopes: [String]
3535
let shortName = "Google"
3636
let providerId = "google.com"
3737
public let clientID: String
38+
39+
public var provider: AuthProviderSwift { self }
40+
3841
public init(scopes: [String]? = nil, clientID: String = FirebaseApp.app()!.options.clientID!) {
3942
self.scopes = scopes ?? kDefaultScopes
4043
self.clientID = clientID
@@ -50,6 +53,10 @@ public class GoogleProviderAuthUI: @preconcurrency GoogleProviderAuthUIProtocol
5053
try await operation(on: user)
5154
}
5255

56+
@MainActor public func createAuthCredential() async throws -> AuthCredential {
57+
return try await signInWithGoogle(clientID: clientID)
58+
}
59+
5360
@MainActor public func signInWithGoogle(clientID: String) async throws -> AuthCredential {
5461
guard let presentingViewController = await (UIApplication.shared.connectedScenes
5562
.first as? UIWindowScene)?.windows.first?.rootViewController else {

FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Views/SignInWithGoogleButton.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ extension SignInWithGoogleButton: View {
3838
public var body: some View {
3939
GoogleSignInButton(viewModel: customViewModel) {
4040
Task {
41-
try await authService.signInWithGoogle()
41+
guard let clientID = authService.auth.app?.options.clientID else {
42+
return
43+
}
44+
let provider = GoogleProviderAuthUI(clientID: clientID)
45+
try await authService.signInWithProvider(provider)
4246
}
4347
}
4448
}

FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/AuthService+Phone.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import FirebaseAuthSwiftUI
2424
public extension AuthService {
2525
@discardableResult
2626
func withPhoneSignIn() -> AuthService {
27-
register(provider: PhoneAuthProviderAuthUI())
27+
registerProvider(provider: PhoneAuthProviderAuthUI())
2828
return self
2929
}
3030
}

FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,29 @@ import SwiftUI
1818

1919
public typealias VerificationID = String
2020

21-
public class PhoneAuthProviderAuthUI: @preconcurrency PhoneAuthProviderAuthUIProtocol {
21+
public class PhoneAuthProviderAuthUI: @preconcurrency PhoneAuthProviderAuthUIProtocol, AuthProviderUI {
2222
public let id: String = "phone"
23+
24+
public var provider: AuthProviderSwift { self }
25+
26+
// Store verification details for the signIn method
27+
private var storedVerificationID: String?
28+
private var storedVerificationCode: String?
2329

2430
@MainActor public func authButton() -> AnyView {
2531
AnyView(PhoneAuthButtonView())
2632
}
33+
34+
@MainActor public func createAuthCredential() async throws -> AuthCredential {
35+
guard let verificationID = storedVerificationID,
36+
let verificationCode = storedVerificationCode else {
37+
fatalError("Phone authentication requires verifyPhoneNumber to be called first")
38+
}
39+
return PhoneAuthProvider.provider().credential(
40+
withVerificationID: verificationID,
41+
verificationCode: verificationCode
42+
)
43+
}
2744

2845
@MainActor public func verifyPhoneNumber(phoneNumber: String) async throws -> VerificationID {
2946
return try await withCheckedThrowingContinuation { continuation in

0 commit comments

Comments
 (0)