From 1028c7b4de03d25966977ff2da051f403674fc53 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 27 Oct 2025 15:37:09 +0000 Subject: [PATCH 01/13] refactor: use the same reauthenticate method for providers with same logic --- .../Services/AccountService+Apple.swift | 15 ++------------- .../Services/AccountService+Email.swift | 1 - .../Sources/Services/AccountService.swift | 19 +++++++++++++++++++ .../Services/AccountService+Facebook.swift | 15 ++------------- .../Services/AccountService+Google.swift | 16 ++-------------- .../Services/AccountService+OAuth.swift | 15 ++------------- .../Services/AccountService+Twitter.swift | 15 ++------------- 7 files changed, 29 insertions(+), 67 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift b/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift index 6974a1d902..28f572112f 100644 --- a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift +++ b/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift @@ -9,23 +9,12 @@ import FirebaseAuthSwiftUI import Observation -protocol AppleOperationReauthentication { +protocol AppleOperationReauthentication: ProviderOperationReauthentication { var appleProvider: AppleProviderSwift { get } } extension AppleOperationReauthentication { - @MainActor func reauthenticate() async throws { - guard let user = Auth.auth().currentUser else { - throw AuthServiceError.reauthenticationRequired("No user currently signed-in") - } - - do { - let credential = try await appleProvider.createAuthCredential() - try await user.reauthenticate(with: credential) - } catch { - throw AuthServiceError.signInFailed(underlying: error) - } - } + var authProvider: AuthProviderSwift { appleProvider } } @MainActor diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift index 1e0db52283..d79866c152 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift @@ -20,7 +20,6 @@ protocol EmailPasswordOperationReauthentication { } extension EmailPasswordOperationReauthentication { - // TODO: - @MainActor because User is non-sendable. Might change this once User is sendable in firebase-ios-sdk @MainActor func reauthenticate() async throws { guard let user = Auth.auth().currentUser else { throw AuthServiceError.reauthenticationRequired("No user currently signed-in") diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift index 8ad6cbe342..4d33ecd401 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift @@ -45,3 +45,22 @@ public extension AuthenticatedOperation { } } } + +public protocol ProviderOperationReauthentication { + var authProvider: AuthProviderSwift { get } +} + +public extension ProviderOperationReauthentication { + @MainActor func reauthenticate() async throws { + guard let user = Auth.auth().currentUser else { + throw AuthServiceError.reauthenticationRequired("No user currently signed-in") + } + + do { + let credential = try await authProvider.createAuthCredential() + try await user.reauthenticate(with: credential) + } catch { + throw AuthServiceError.signInFailed(underlying: error) + } + } +} diff --git a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift index 2100d08467..ac6a56b246 100644 --- a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift +++ b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift @@ -23,23 +23,12 @@ import FirebaseAuthSwiftUI import Observation -protocol FacebookOperationReauthentication { +protocol FacebookOperationReauthentication: ProviderOperationReauthentication { var facebookProvider: FacebookProviderSwift { get } } extension FacebookOperationReauthentication { - @MainActor func reauthenticate() async throws { - guard let user = Auth.auth().currentUser else { - throw AuthServiceError.reauthenticationRequired("No user currently signed-in") - } - - do { - let credential = try await facebookProvider.createAuthCredential() - try await user.reauthenticate(with: credential) - } catch { - throw AuthServiceError.signInFailed(underlying: error) - } - } + var authProvider: AuthProviderSwift { facebookProvider } } @MainActor diff --git a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift index c651d66bab..86162b16df 100644 --- a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift +++ b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift @@ -30,24 +30,12 @@ import FirebaseAuthSwiftUI import Observation -protocol GoogleOperationReauthentication { +protocol GoogleOperationReauthentication: ProviderOperationReauthentication { var googleProvider: GoogleProviderSwift { get } } extension GoogleOperationReauthentication { - @MainActor func reauthenticate() async throws { - guard let user = Auth.auth().currentUser else { - throw AuthServiceError.reauthenticationRequired("No user currently signed-in") - } - - do { - let credential = try await googleProvider.createAuthCredential() - try await user.reauthenticate(with: credential) - - } catch { - throw AuthServiceError.signInFailed(underlying: error) - } - } + var authProvider: AuthProviderSwift { googleProvider } } @MainActor diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift index 44b66b4403..73792f2d44 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift @@ -9,23 +9,12 @@ import FirebaseAuthSwiftUI import Observation -protocol OAuthOperationReauthentication { +protocol OAuthOperationReauthentication: ProviderOperationReauthentication { var oauthProvider: OAuthProviderSwift { get } } extension OAuthOperationReauthentication { - @MainActor func reauthenticate() async throws { - guard let user = Auth.auth().currentUser else { - throw AuthServiceError.reauthenticationRequired("No user currently signed-in") - } - - do { - let credential = try await oauthProvider.createAuthCredential() - try await user.reauthenticate(with: credential) - } catch { - throw AuthServiceError.signInFailed(underlying: error) - } - } + var authProvider: AuthProviderSwift { oauthProvider } } @MainActor diff --git a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift b/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift index 89b35fec96..458dcc72d8 100644 --- a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift +++ b/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift @@ -9,23 +9,12 @@ import FirebaseAuthSwiftUI import Observation -protocol TwitterOperationReauthentication { +protocol TwitterOperationReauthentication: ProviderOperationReauthentication { var twitterProvider: TwitterProviderSwift { get } } extension TwitterOperationReauthentication { - @MainActor func reauthenticate() async throws { - guard let user = Auth.auth().currentUser else { - throw AuthServiceError.reauthenticationRequired("No user currently signed-in") - } - - do { - let credential = try await twitterProvider.createAuthCredential() - try await user.reauthenticate(with: credential) - } catch { - throw AuthServiceError.signInFailed(underlying: error) - } - } + var authProvider: AuthProviderSwift { twitterProvider } } @MainActor From 2376da1691cc7988cbcdd02273d4ea46cef21aff Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 27 Oct 2025 15:52:00 +0000 Subject: [PATCH 02/13] refactor: use the same protocol for account services --- .../Sources/Services/AccountService+Apple.swift | 13 ++++--------- .../Sources/Services/AccountService+Facebook.swift | 13 ++++--------- .../Sources/Services/AccountService+Google.swift | 13 ++++--------- .../Sources/Services/AccountService+OAuth.swift | 13 ++++--------- .../Sources/Services/AccountService+Twitter.swift | 13 ++++--------- 5 files changed, 20 insertions(+), 45 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift b/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift index 28f572112f..dbe8a89beb 100644 --- a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift +++ b/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift @@ -9,18 +9,13 @@ import FirebaseAuthSwiftUI import Observation -protocol AppleOperationReauthentication: ProviderOperationReauthentication { - var appleProvider: AppleProviderSwift { get } -} - -extension AppleOperationReauthentication { - var authProvider: AuthProviderSwift { appleProvider } -} - @MainActor class AppleDeleteUserOperation: AuthenticatedOperation, - @preconcurrency AppleOperationReauthentication { + @preconcurrency ProviderOperationReauthentication { let appleProvider: AppleProviderSwift + + var authProvider: AuthProviderSwift { appleProvider } + init(appleProvider: AppleProviderSwift) { self.appleProvider = appleProvider } diff --git a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift index ac6a56b246..2f511f4a8c 100644 --- a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift +++ b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift @@ -23,18 +23,13 @@ import FirebaseAuthSwiftUI import Observation -protocol FacebookOperationReauthentication: ProviderOperationReauthentication { - var facebookProvider: FacebookProviderSwift { get } -} - -extension FacebookOperationReauthentication { - var authProvider: AuthProviderSwift { facebookProvider } -} - @MainActor class FacebookDeleteUserOperation: AuthenticatedOperation, - @preconcurrency FacebookOperationReauthentication { + @preconcurrency ProviderOperationReauthentication { let facebookProvider: FacebookProviderSwift + + var authProvider: AuthProviderSwift { facebookProvider } + init(facebookProvider: FacebookProviderSwift) { self.facebookProvider = facebookProvider } diff --git a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift index 86162b16df..f449a09e71 100644 --- a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift +++ b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift @@ -30,18 +30,13 @@ import FirebaseAuthSwiftUI import Observation -protocol GoogleOperationReauthentication: ProviderOperationReauthentication { - var googleProvider: GoogleProviderSwift { get } -} - -extension GoogleOperationReauthentication { - var authProvider: AuthProviderSwift { googleProvider } -} - @MainActor class GoogleDeleteUserOperation: AuthenticatedOperation, - @preconcurrency GoogleOperationReauthentication { + @preconcurrency ProviderOperationReauthentication { let googleProvider: GoogleProviderSwift + + var authProvider: AuthProviderSwift { googleProvider } + init(googleProvider: GoogleProviderSwift) { self.googleProvider = googleProvider } diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift index 73792f2d44..daf028da21 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift @@ -9,18 +9,13 @@ import FirebaseAuthSwiftUI import Observation -protocol OAuthOperationReauthentication: ProviderOperationReauthentication { - var oauthProvider: OAuthProviderSwift { get } -} - -extension OAuthOperationReauthentication { - var authProvider: AuthProviderSwift { oauthProvider } -} - @MainActor class OAuthDeleteUserOperation: AuthenticatedOperation, - @preconcurrency OAuthOperationReauthentication { + @preconcurrency ProviderOperationReauthentication { let oauthProvider: OAuthProviderSwift + + var authProvider: AuthProviderSwift { oauthProvider } + init(oauthProvider: OAuthProviderSwift) { self.oauthProvider = oauthProvider } diff --git a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift b/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift index 458dcc72d8..111aee290c 100644 --- a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift +++ b/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift @@ -9,18 +9,13 @@ import FirebaseAuthSwiftUI import Observation -protocol TwitterOperationReauthentication: ProviderOperationReauthentication { - var twitterProvider: TwitterProviderSwift { get } -} - -extension TwitterOperationReauthentication { - var authProvider: AuthProviderSwift { twitterProvider } -} - @MainActor class TwitterDeleteUserOperation: AuthenticatedOperation, - @preconcurrency TwitterOperationReauthentication { + @preconcurrency ProviderOperationReauthentication { let twitterProvider: TwitterProviderSwift + + var authProvider: AuthProviderSwift { twitterProvider } + init(twitterProvider: TwitterProviderSwift) { self.twitterProvider = twitterProvider } From aaaf4879e0dfa1fedf79c876cda1644185c337c8 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 27 Oct 2025 16:05:47 +0000 Subject: [PATCH 03/13] refactor: attempt to use one class --- .../Services/AccountService+Apple.swift | 28 ----------- .../Services/AppleProviderAuthUI.swift | 2 +- .../Sources/Services/AccountService.swift | 18 +++++++ .../Services/AccountService+Facebook.swift | 42 ---------------- .../Services/FacebookProviderAuthUI.swift | 2 +- .../Services/AccountService+Google.swift | 49 ------------------- .../Services/GoogleProviderAuthUI.swift | 2 +- .../Services/AccountService+OAuth.swift | 28 ----------- .../Sources/Services/OAuthProviderSwift.swift | 2 +- .../Services/AccountService+Twitter.swift | 28 ----------- .../Services/TwitterProviderAuthUI.swift | 2 +- 11 files changed, 23 insertions(+), 180 deletions(-) delete mode 100644 FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift delete mode 100644 FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift delete mode 100644 FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift delete mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift delete mode 100644 FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift diff --git a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift b/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift deleted file mode 100644 index dbe8a89beb..0000000000 --- a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AccountService+Apple.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// AccountService+Apple.swift -// FirebaseUI -// -// Created by Russell Wheatley on 21/10/2025. -// - -@preconcurrency import FirebaseAuth -import FirebaseAuthSwiftUI -import Observation - -@MainActor -class AppleDeleteUserOperation: AuthenticatedOperation, - @preconcurrency ProviderOperationReauthentication { - let appleProvider: AppleProviderSwift - - var authProvider: AuthProviderSwift { appleProvider } - - init(appleProvider: AppleProviderSwift) { - self.appleProvider = appleProvider - } - - func callAsFunction(on user: User) async throws { - try await callAsFunction(on: user) { - try await user.delete() - } - } -} diff --git a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AppleProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AppleProviderAuthUI.swift index 76dacb5baa..4432be7abf 100644 --- a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AppleProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AppleProviderAuthUI.swift @@ -139,7 +139,7 @@ public class AppleProviderSwift: AuthProviderSwift, DeleteUserSwift { } public func deleteUser(user: User) async throws { - let operation = AppleDeleteUserOperation(appleProvider: self) + let operation = ProviderDeleteUserOperation(provider: self) try await operation(on: user) } } diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift index 4d33ecd401..3ff13864bd 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift @@ -64,3 +64,21 @@ public extension ProviderOperationReauthentication { } } } + +@MainActor +public class ProviderDeleteUserOperation: AuthenticatedOperation, + @preconcurrency ProviderOperationReauthentication { + let provider: Provider + + public var authProvider: AuthProviderSwift { provider } + + public init(provider: Provider) { + self.provider = provider + } + + public func callAsFunction(on user: User) async throws { + try await callAsFunction(on: user) { + try await user.delete() + } + } +} diff --git a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift deleted file mode 100644 index 2f511f4a8c..0000000000 --- a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AccountService+Facebook.swift +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// -// AccountService+Facebook.swift -// FirebaseUI -// -// Created by Russell Wheatley on 14/05/2025. -// - -@preconcurrency import FirebaseAuth -import FirebaseAuthSwiftUI -import Observation - -@MainActor -class FacebookDeleteUserOperation: AuthenticatedOperation, - @preconcurrency ProviderOperationReauthentication { - let facebookProvider: FacebookProviderSwift - - var authProvider: AuthProviderSwift { facebookProvider } - - init(facebookProvider: FacebookProviderSwift) { - self.facebookProvider = facebookProvider - } - - func callAsFunction(on user: User) async throws { - try await callAsFunction(on: user) { - try await user.delete() - } - } -} diff --git a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift index be4194e49e..51aaa80d4f 100644 --- a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift @@ -118,7 +118,7 @@ public class FacebookProviderSwift: AuthProviderSwift, DeleteUserSwift { } public func deleteUser(user: User) async throws { - let operation = FacebookDeleteUserOperation(facebookProvider: self) + let operation = ProviderDeleteUserOperation(provider: self) try await operation(on: user) } } diff --git a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift deleted file mode 100644 index f449a09e71..0000000000 --- a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AccountService+Google.swift +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// -// AccountService+Google.swift -// FirebaseUI -// -// Created by Russell Wheatley on 22/05/2025. -// - -// -// AccountService+Facebook.swift -// FirebaseUI -// -// Created by Russell Wheatley on 14/05/2025. -// - -@preconcurrency import FirebaseAuth -import FirebaseAuthSwiftUI -import Observation - -@MainActor -class GoogleDeleteUserOperation: AuthenticatedOperation, - @preconcurrency ProviderOperationReauthentication { - let googleProvider: GoogleProviderSwift - - var authProvider: AuthProviderSwift { googleProvider } - - init(googleProvider: GoogleProviderSwift) { - self.googleProvider = googleProvider - } - - func callAsFunction(on user: User) async throws { - try await callAsFunction(on: user) { - try await user.delete() - } - } -} diff --git a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift index 06ad5ca39f..04a5850602 100644 --- a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift @@ -70,7 +70,7 @@ public class GoogleProviderSwift: AuthProviderSwift, DeleteUserSwift { } public func deleteUser(user: User) async throws { - let operation = GoogleDeleteUserOperation(googleProvider: self) + let operation = ProviderDeleteUserOperation(provider: self) try await operation(on: user) } } diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift deleted file mode 100644 index daf028da21..0000000000 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// AccountService+OAuth.swift -// FirebaseUI -// -// Created by Russell Wheatley on 21/10/2025. -// - -@preconcurrency import FirebaseAuth -import FirebaseAuthSwiftUI -import Observation - -@MainActor -class OAuthDeleteUserOperation: AuthenticatedOperation, - @preconcurrency ProviderOperationReauthentication { - let oauthProvider: OAuthProviderSwift - - var authProvider: AuthProviderSwift { oauthProvider } - - init(oauthProvider: OAuthProviderSwift) { - self.oauthProvider = oauthProvider - } - - func callAsFunction(on user: User) async throws { - try await callAsFunction(on: user) { - try await user.delete() - } - } -} diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift index feb3f9062c..fd90906356 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift @@ -115,7 +115,7 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { } public func deleteUser(user: User) async throws { - let operation = OAuthDeleteUserOperation(oauthProvider: self) + let operation = ProviderDeleteUserOperation(provider: self) try await operation(on: user) } } diff --git a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift b/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift deleted file mode 100644 index 111aee290c..0000000000 --- a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/AccountService+Twitter.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// AccountService+Twitter.swift -// FirebaseUI -// -// Created by Russell Wheatley on 10/10/2025. -// - -@preconcurrency import FirebaseAuth -import FirebaseAuthSwiftUI -import Observation - -@MainActor -class TwitterDeleteUserOperation: AuthenticatedOperation, - @preconcurrency ProviderOperationReauthentication { - let twitterProvider: TwitterProviderSwift - - var authProvider: AuthProviderSwift { twitterProvider } - - init(twitterProvider: TwitterProviderSwift) { - self.twitterProvider = twitterProvider - } - - func callAsFunction(on user: User) async throws { - try await callAsFunction(on: user) { - try await user.delete() - } - } -} diff --git a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/TwitterProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/TwitterProviderAuthUI.swift index 627d8de1b9..c324934447 100644 --- a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/TwitterProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/TwitterProviderAuthUI.swift @@ -44,7 +44,7 @@ public class TwitterProviderSwift: AuthProviderSwift, DeleteUserSwift { } public func deleteUser(user: User) async throws { - let operation = TwitterDeleteUserOperation(twitterProvider: self) + let operation = ProviderDeleteUserOperation(provider: self) try await operation(on: user) } } From 7c0537f545b07933762a28fad17734d16cef4eac Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 27 Oct 2025 16:10:07 +0000 Subject: [PATCH 04/13] fx: make mainactor --- .../FirebaseAuthSwiftUI/Sources/Services/AccountService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift index 3ff13864bd..0b4aaab138 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift @@ -33,7 +33,7 @@ public protocol AuthenticatedOperation { public extension AuthenticatedOperation { func callAsFunction(on _: User, - _ performOperation: () async throws -> Void) async throws { + _ performOperation: @MainActor () async throws -> Void) async throws { do { try await performOperation() } catch let error as NSError where error.requiresReauthentication { From 1b143392ec54bc64bd32fe7058d8cc257b752c78 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 27 Oct 2025 16:19:48 +0000 Subject: [PATCH 05/13] refactor: add phone auth to deleteUser + put in same protocol --- .../Sources/Services/AppleProviderAuthUI.swift | 2 +- .../Sources/Services/AuthService.swift | 12 ++++-------- .../Sources/Services/FacebookProviderAuthUI.swift | 2 +- .../Sources/Services/GoogleProviderAuthUI.swift | 2 +- .../Sources/Services/OAuthProviderSwift.swift | 2 +- .../Sources/Services/PhoneAuthProviderAuthUI.swift | 5 +++++ .../Sources/Services/TwitterProviderAuthUI.swift | 2 +- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AppleProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AppleProviderAuthUI.swift index 4432be7abf..a3d829e532 100644 --- a/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AppleProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Services/AppleProviderAuthUI.swift @@ -114,7 +114,7 @@ extension AuthenticateWithAppleDialog: ASAuthorizationControllerDelegate { // MARK: - Apple Provider Swift -public class AppleProviderSwift: AuthProviderSwift, DeleteUserSwift { +public class AppleProviderSwift: AuthProviderSwift { public let scopes: [ASAuthorization.Scope] let providerId = "apple.com" diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift index 4c124a695c..10f2f517bd 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift @@ -17,6 +17,7 @@ import SwiftUI public protocol AuthProviderSwift { @MainActor func createAuthCredential() async throws -> AuthCredential + @MainActor func deleteUser(user: User) async throws } public protocol AuthProviderUI { @@ -25,10 +26,6 @@ public protocol AuthProviderUI { var provider: AuthProviderSwift { get } } -public protocol DeleteUserSwift { - @MainActor func deleteUser(user: User) async throws -} - public protocol PhoneAuthProviderSwift: AuthProviderSwift { @MainActor func verifyPhoneNumber(phoneNumber: String) async throws -> String } @@ -282,13 +279,12 @@ public extension AuthService { let operation = EmailPasswordDeleteUserOperation(passwordPrompt: passwordPrompt) try await operation(on: user) } else { - // Find provider by matching ID and ensure it can delete users - guard let matchingProvider = providers.first(where: { $0.id == providerId }), - let provider = matchingProvider.provider as? DeleteUserSwift else { + // Find provider by matching ID + guard let matchingProvider = providers.first(where: { $0.id == providerId }) else { throw AuthServiceError.providerNotFound("No provider found for \(providerId)") } - try await provider.deleteUser(user: user) + try await matchingProvider.provider.deleteUser(user: user) } } } catch { diff --git a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift index 51aaa80d4f..f123f04229 100644 --- a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift @@ -19,7 +19,7 @@ import FirebaseAuth import FirebaseAuthSwiftUI import SwiftUI -public class FacebookProviderSwift: AuthProviderSwift, DeleteUserSwift { +public class FacebookProviderSwift: AuthProviderSwift { let scopes: [String] let providerId = "facebook.com" private let loginManager = LoginManager() diff --git a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift index 04a5850602..c3edb91956 100644 --- a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift @@ -19,7 +19,7 @@ import GoogleSignIn import GoogleSignInSwift import SwiftUI -public class GoogleProviderSwift: AuthProviderSwift, DeleteUserSwift { +public class GoogleProviderSwift: AuthProviderSwift { let scopes: [String] let clientID: String let providerId = "google.com" diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift index fd90906356..0a39a5fd1d 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift @@ -18,7 +18,7 @@ import FirebaseCore import SwiftUI /// Configuration for a generic OAuth provider -public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { +public class OAuthProviderSwift: AuthProviderSwift { public let providerId: String public let scopes: [String] public let customParameters: [String: String] diff --git a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift index c1c3d89695..ca1204fac3 100644 --- a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift @@ -63,6 +63,11 @@ public class PhoneProviderSwift: PhoneAuthProviderSwift { presentingViewController.present(hostingController, animated: true) } } + + public func deleteUser(user: User) async throws { + let operation = ProviderDeleteUserOperation(provider: self) + try await operation(on: user) + } } public class PhoneAuthProviderAuthUI: AuthProviderUI { diff --git a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/TwitterProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/TwitterProviderAuthUI.swift index c324934447..693d72f0e6 100644 --- a/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/TwitterProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Services/TwitterProviderAuthUI.swift @@ -17,7 +17,7 @@ import FirebaseAuthSwiftUI import FirebaseCore import SwiftUI -public class TwitterProviderSwift: AuthProviderSwift, DeleteUserSwift { +public class TwitterProviderSwift: AuthProviderSwift { public let scopes: [String] let providerId = "twitter.com" From 894ff6712218c17352b077b5e1b6221798c48b24 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 27 Oct 2025 16:53:53 +0000 Subject: [PATCH 06/13] fix: providerId for phone --- .../Sources/Services/PhoneAuthProviderAuthUI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift index ca1204fac3..77b040b2d1 100644 --- a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift @@ -72,7 +72,7 @@ public class PhoneProviderSwift: PhoneAuthProviderSwift { public class PhoneAuthProviderAuthUI: AuthProviderUI { public var provider: AuthProviderSwift - public let id: String = "phone.com" + public let id: String = "phone" public init(provider: PhoneAuthProviderSwift? = nil) { self.provider = provider ?? PhoneProviderSwift() From e7ada7a811d5954acc98cc4f626a7f9ed9716d90 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 28 Oct 2025 11:31:49 +0000 Subject: [PATCH 07/13] fix: non sendable CI issue --- .../FirebaseAuthSwiftUI/Sources/Services/AccountService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift index 0b4aaab138..6179836532 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift @@ -58,7 +58,7 @@ public extension ProviderOperationReauthentication { do { let credential = try await authProvider.createAuthCredential() - try await user.reauthenticate(with: credential) + _ = try await user.reauthenticate(with: credential) } catch { throw AuthServiceError.signInFailed(underlying: error) } From d2521e20825c4d599f3c10dae1ea21cb38ef5204 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 28 Oct 2025 11:32:01 +0000 Subject: [PATCH 08/13] chore: use swift v6 for example app --- .../FirebaseSwiftUIExample.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample.xcodeproj/project.pbxproj b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample.xcodeproj/project.pbxproj index e0c6a0533d..abec964899 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample.xcodeproj/project.pbxproj +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample.xcodeproj/project.pbxproj @@ -481,7 +481,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebase.auth.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -514,7 +514,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebase.auth.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -532,7 +532,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.invertase.testing.FirebaseSwiftUIExampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseSwiftUIExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/FirebaseSwiftUIExample"; }; @@ -551,7 +551,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.invertase.testing.FirebaseSwiftUIExampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseSwiftUIExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/FirebaseSwiftUIExample"; }; @@ -568,7 +568,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.invertase.testing.FirebaseSwiftUIExampleUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = FirebaseSwiftUIExample; }; @@ -585,7 +585,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.invertase.testing.FirebaseSwiftUIExampleUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = FirebaseSwiftUIExample; }; From ed53347c641bdc0fb6b5373483512645f878f8f3 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 28 Oct 2025 12:03:13 +0000 Subject: [PATCH 09/13] fix: move mainactor --- .../Sources/Services/AccountService+Email.swift | 5 +++-- .../Sources/Services/AccountService.swift | 3 ++- .../FirebaseAuthSwiftUI/Sources/Services/AuthService.swift | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift index d79866c152..2d9848cdc7 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift @@ -15,12 +15,13 @@ @preconcurrency import FirebaseAuth import Observation +@MainActor protocol EmailPasswordOperationReauthentication { var passwordPrompt: PasswordPromptCoordinator { get } } extension EmailPasswordOperationReauthentication { - @MainActor func reauthenticate() async throws { + func reauthenticate() async throws { guard let user = Auth.auth().currentUser else { throw AuthServiceError.reauthenticationRequired("No user currently signed-in") } @@ -33,7 +34,7 @@ extension EmailPasswordOperationReauthentication { let password = try await passwordPrompt.confirmPassword() let credential = EmailAuthProvider.credential(withEmail: email, password: password) - try await Auth.auth().currentUser?.reauthenticate(with: credential) + _ = try await Auth.auth().currentUser?.reauthenticate(with: credential) } catch { throw AuthServiceError.signInFailed(underlying: error) } diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift index 6179836532..954eccf1b4 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift @@ -46,12 +46,13 @@ public extension AuthenticatedOperation { } } +@MainActor public protocol ProviderOperationReauthentication { var authProvider: AuthProviderSwift { get } } public extension ProviderOperationReauthentication { - @MainActor func reauthenticate() async throws { + func reauthenticate() async throws { guard let user = Auth.auth().currentUser else { throw AuthServiceError.reauthenticationRequired("No user currently signed-in") } diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift index 10f2f517bd..bc2e2fa626 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift @@ -719,10 +719,10 @@ public extension AuthService { } let password = try await passwordPrompt.confirmPassword() let credential = EmailAuthProvider.credential(withEmail: email, password: password) - try await user.reauthenticate(with: credential) + _ = try await user.reauthenticate(with: credential) } else if let matchingProvider = providers.first(where: { $0.id == providerId }) { let credential = try await matchingProvider.provider.createAuthCredential() - try await user.reauthenticate(with: credential) + _ = try await user.reauthenticate(with: credential) } else { throw AuthServiceError.providerNotFound("No provider found for \(providerId)") } From 8e9b294c6f49654ee2e10e5d68668ae40890612e Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 28 Oct 2025 12:17:07 +0000 Subject: [PATCH 10/13] chore: move to swift 6 --- Package.swift | 64 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/Package.swift b/Package.swift index 3ab09585ea..488b32e7d2 100644 --- a/Package.swift +++ b/Package.swift @@ -272,12 +272,18 @@ let package = Package( path: "FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources", resources: [ .process("Strings"), + ], + swiftSettings: [ + .swiftLanguageVersion(.v6), ] ), .testTarget( name: "FirebaseAuthSwiftUITests", dependencies: ["FirebaseAuthSwiftUI"], - path: "FirebaseSwiftUI/FirebaseAuthSwiftUI/Tests/" + path: "FirebaseSwiftUI/FirebaseAuthSwiftUI/Tests/", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .target( name: "FirebaseGoogleSwiftUI", @@ -286,12 +292,18 @@ let package = Package( .product(name: "GoogleSignIn", package: "GoogleSignIn-iOS"), .product(name: "GoogleSignInSwift", package: "GoogleSignIn-iOS"), ], - path: "FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources" + path: "FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .testTarget( name: "FirebaseGoogleSwiftUITests", dependencies: ["FirebaseGoogleSwiftUI"], - path: "FirebaseSwiftUI/FirebaseGoogleSwiftUI/Tests/" + path: "FirebaseSwiftUI/FirebaseGoogleSwiftUI/Tests/", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .target( name: "FirebaseFacebookSwiftUI", @@ -300,24 +312,36 @@ let package = Package( .product(name: "FacebookLogin", package: "facebook-ios-sdk"), .product(name: "FacebookCore", package: "facebook-ios-sdk"), ], - path: "FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources" + path: "FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .testTarget( name: "FirebaseFacebookSwiftUITests", dependencies: ["FirebaseFacebookSwiftUI"], - path: "FirebaseSwiftUI/FirebaseFacebookSwiftUI/Tests/" + path: "FirebaseSwiftUI/FirebaseFacebookSwiftUI/Tests/", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .target( name: "FirebasePhoneAuthSwiftUI", dependencies: [ "FirebaseAuthSwiftUI", ], - path: "FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources" + path: "FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .testTarget( name: "FirebasePhoneAuthSwiftUITests", dependencies: ["FirebasePhoneAuthSwiftUI"], - path: "FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Tests/" + path: "FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Tests/", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .target( name: "FirebaseTwitterSwiftUI", @@ -327,24 +351,36 @@ let package = Package( path: "FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources", resources: [ .process("Resources") + ], + swiftSettings: [ + .swiftLanguageVersion(.v6), ] ), .testTarget( name: "FirebaseTwitterSwiftUITests", dependencies: ["FirebaseTwitterSwiftUI"], - path: "FirebaseSwiftUI/FirebaseTwitterSwiftUI/Tests/" + path: "FirebaseSwiftUI/FirebaseTwitterSwiftUI/Tests/", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .target( name: "FirebaseAppleSwiftUI", dependencies: [ "FirebaseAuthSwiftUI", ], - path: "FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources" + path: "FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .testTarget( name: "FirebaseAppleSwiftUITests", dependencies: ["FirebaseAppleSwiftUI"], - path: "FirebaseSwiftUI/FirebaseAppleSwiftUI/Tests/" + path: "FirebaseSwiftUI/FirebaseAppleSwiftUI/Tests/", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), .target( name: "FirebaseOAuthSwiftUI", @@ -354,12 +390,18 @@ let package = Package( path: "FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources", resources: [ .process("Resources") + ], + swiftSettings: [ + .swiftLanguageVersion(.v6), ] ), .testTarget( name: "FirebaseOAuthSwiftUITests", dependencies: ["FirebaseOAuthSwiftUI"], - path: "FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/" + path: "FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/", + swiftSettings: [ + .swiftLanguageVersion(.v6), + ] ), ] ) From 48215618d9e6f56ffa881c8be066d802fc3dd5f4 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 28 Oct 2025 15:33:01 +0000 Subject: [PATCH 11/13] ci: update to use xcode 26 --- .github/workflows/swiftui-auth.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/swiftui-auth.yml b/.github/workflows/swiftui-auth.yml index 7faaacd95c..bf57dd7df2 100644 --- a/.github/workflows/swiftui-auth.yml +++ b/.github/workflows/swiftui-auth.yml @@ -25,7 +25,7 @@ jobs: # Package Unit Tests (standalone, no emulator needed) unit-tests: name: Package Unit Tests - runs-on: macos-15 + runs-on: macos-26 timeout-minutes: 15 steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 @@ -34,7 +34,7 @@ jobs: run: gem install xcpretty - name: Select Xcode version - run: sudo xcode-select -switch /Applications/Xcode_16.4.app/Contents/Developer + run: sudo xcode-select -switch /Applications/Xcode_26.0.1.app/Contents/Developer - name: Run FirebaseSwiftUI Package Unit Tests run: | @@ -62,7 +62,7 @@ jobs: # Integration Tests (requires emulator) integration-tests: name: Integration Tests - runs-on: macos-15 + runs-on: macos-26 timeout-minutes: 20 steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 @@ -90,7 +90,7 @@ jobs: run: gem install xcpretty - name: Select Xcode version - run: sudo xcode-select -switch /Applications/Xcode_16.4.app/Contents/Developer + run: sudo xcode-select -switch /Applications/Xcode_26.0.1.app/Contents/Developer - name: Build for Integration Tests run: | @@ -128,7 +128,7 @@ jobs: # UI Tests (requires emulator) ui-tests: name: UI Tests - runs-on: macos-15 + runs-on: macos-26 timeout-minutes: 40 steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 @@ -156,7 +156,7 @@ jobs: run: gem install xcpretty - name: Select Xcode version - run: sudo xcode-select -switch /Applications/Xcode_16.4.app/Contents/Developer + run: sudo xcode-select -switch /Applications/Xcode_26.0.1.app/Contents/Developer - name: Build for UI Tests run: | From 5d05bd35f3eabf58d9c935b28dcef5730534e0ea Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 28 Oct 2025 15:45:55 +0000 Subject: [PATCH 12/13] ci: update iphone device --- .github/workflows/swiftui-auth.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/swiftui-auth.yml b/.github/workflows/swiftui-auth.yml index bf57dd7df2..44f17a59c5 100644 --- a/.github/workflows/swiftui-auth.yml +++ b/.github/workflows/swiftui-auth.yml @@ -41,7 +41,7 @@ jobs: set -o pipefail xcodebuild test \ -scheme FirebaseUI-Package \ - -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + -destination 'platform=iOS Simulator,name=iPhone 17' \ -enableCodeCoverage YES \ -resultBundlePath FirebaseSwiftUIPackageTests.xcresult | tee FirebaseSwiftUIPackageTests.log | xcpretty --test --color --simple @@ -98,7 +98,7 @@ jobs: set -o pipefail xcodebuild build-for-testing \ -scheme FirebaseSwiftUIExampleTests \ - -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + -destination 'platform=iOS Simulator,name=iPhone 17' \ -enableCodeCoverage YES | xcpretty --color --simple - name: Run Integration Tests @@ -107,7 +107,7 @@ jobs: set -o pipefail xcodebuild test-without-building \ -scheme FirebaseSwiftUIExampleTests \ - -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + -destination 'platform=iOS Simulator,name=iPhone 17' \ -enableCodeCoverage YES \ -resultBundlePath FirebaseSwiftUIExampleTests.xcresult | tee FirebaseSwiftUIExampleTests.log | xcpretty --test --color --simple @@ -164,7 +164,7 @@ jobs: set -o pipefail xcodebuild build-for-testing \ -scheme FirebaseSwiftUIExampleUITests \ - -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + -destination 'platform=iOS Simulator,name=iPhone 17' \ -enableCodeCoverage YES | xcpretty --color --simple - name: Run UI Tests @@ -173,7 +173,7 @@ jobs: set -o pipefail xcodebuild test-without-building \ -scheme FirebaseSwiftUIExampleUITests \ - -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + -destination 'platform=iOS Simulator,name=iPhone 17' \ -parallel-testing-enabled YES \ -maximum-concurrent-test-simulator-destinations 2 \ -enableCodeCoverage YES \ From 598e9187056a3320ed427efebd43ef7da97b065e Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 29 Oct 2025 10:34:49 +0000 Subject: [PATCH 13/13] test: use main thread for test helper functions --- .../FirebaseSwiftUIExampleTests/TestHarness.swift | 2 ++ .../FirebaseSwiftUIExampleUITests/MFAResolutionUITests.swift | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests/TestHarness.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests/TestHarness.swift index 7c81662457..7645e56d03 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests/TestHarness.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests/TestHarness.swift @@ -15,6 +15,7 @@ func configureFirebaseIfNeeded() { } } +@MainActor private var hasCheckedEmulatorAvailability = false @MainActor @@ -62,6 +63,7 @@ func createEmail() -> String { return "\(before)@\(after).com" } +@MainActor func waitForStateChange(timeout: TimeInterval = 10.0, condition: @escaping () -> Bool) async throws { let startTime = Date() diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAResolutionUITests.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAResolutionUITests.swift index ef93e71984..93eff325ee 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAResolutionUITests.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAResolutionUITests.swift @@ -121,6 +121,7 @@ final class MFAResolutionUITests: XCTestCase { /// - phoneNumber: The phone number to enroll for SMS MFA (e.g., "+15551234567") /// - displayName: Optional display name for the MFA factor /// - Returns: True if MFA was successfully enabled, false otherwise + @MainActor private func enableSMSMFAViaEmulator( idToken: String, phoneNumber: String, @@ -261,6 +262,7 @@ final class MFAResolutionUITests: XCTestCase { /// - Parameters: /// - phoneNumber: The phone number to retrieve the code for /// - codeType: The type of code - "enrollment" for MFA enrollment, "verification" for phone verification during resolution + @MainActor private func getSMSVerificationCode(for phoneNumber: String, codeType: String = "enrollment") async -> String? { let emulatorUrl = "http://127.0.0.1:9099/emulator/v1/projects/flutterfire-e2e-tests/verificationCodes" @@ -336,6 +338,7 @@ final class MFAResolutionUITests: XCTestCase { /// - email: The user's email address /// - password: The user's password (defaults to "123456") /// - Returns: The user's ID token, or nil if the sign-in failed + @MainActor private func getIDTokenFromEmulator(email: String, password: String = "123456") async -> String? { let signInUrl = "http://127.0.0.1:9099/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=fake-api-key" @@ -379,6 +382,7 @@ final class MFAResolutionUITests: XCTestCase { } } + @MainActor private func signUpUser(email: String, password: String = "12345678") async throws { // Create user via Auth Emulator REST API let url = URL(string: "http://127.0.0.1:9099/identitytoolkit.googleapis.com/v1/accounts:signUp?key=fake-api-key")!