diff --git a/Firebase.podspec b/Firebase.podspec index e20903da021..c11b0b7021a 100644 --- a/Firebase.podspec +++ b/Firebase.podspec @@ -28,7 +28,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.cocoapods_version = '>= 1.12.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.default_subspec = 'Core' diff --git a/FirebaseABTesting.podspec b/FirebaseABTesting.podspec index 767da46ca85..612d9be3afc 100644 --- a/FirebaseABTesting.podspec +++ b/FirebaseABTesting.podspec @@ -35,7 +35,7 @@ Firebase Cloud Messaging and Firebase Remote Config in your app. s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false - s.swift_version = '5.9' + s.swift_version = '6.0' base_dir = "FirebaseABTesting/Sources/" s.source_files = [ diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index dcd6a9ee85b..907690f2442 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| } s.cocoapods_version = '>= 1.12.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = '12.0' s.osx.deployment_target = '10.15' diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index 3b22cf6f5c3..3e7569875bc 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -22,7 +22,7 @@ Pod::Spec.new do |s| tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseAppDistribution.podspec b/FirebaseAppDistribution.podspec index e9ca3a73ed0..f40e93920db 100644 --- a/FirebaseAppDistribution.podspec +++ b/FirebaseAppDistribution.podspec @@ -17,7 +17,7 @@ iOS SDK for App Distribution for Firebase. s.ios.deployment_target = '13.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index db99c757eba..122186f516f 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -24,7 +24,7 @@ supports email and password accounts, as well as several 3rd party authenticatio tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index 04ff2a7322f..5bddc4f16b4 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [fixed] Replaced unsafe uses of `os_unfair_lock` (#14548). + # 11.12.0 - [fixed] Fix a `fatalError` unenrolling from MFA. An invalid user token now throws an `invalidUserToken` error instead of crashing. (#14663) diff --git a/FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h b/FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h index a50669da433..53b0f745926 100644 --- a/FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h +++ b/FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h @@ -28,13 +28,12 @@ typedef void (^FIRTokenCallback)(NSString *_Nullable_result token, NSError *_Nul NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead."); /// Common methods for Auth interoperability. -NS_SWIFT_NAME(AuthInterop) -@protocol FIRAuthInterop +NS_SWIFT_NAME(AuthInterop) NS_SWIFT_SENDABLE @protocol FIRAuthInterop /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. - (void)getTokenForcingRefresh:(BOOL)forceRefresh - withCallback: - (void (^)(NSString *_Nullable_result token, NSError *_Nullable error))callback + withCallback:(void (^NS_SWIFT_UI_ACTOR)(NSString *_Nullable_result token, + NSError *_Nullable error))callback NS_SWIFT_NAME(getToken(forcingRefresh:completion:)); /// Get the current Auth user's UID. Returns nil if there is no user signed in. diff --git a/FirebaseAuth/Sources/Swift/Auth/Auth.swift b/FirebaseAuth/Sources/Swift/Auth/Auth.swift index 5d8050cc891..f88a1864990 100644 --- a/FirebaseAuth/Sources/Swift/Auth/Auth.swift +++ b/FirebaseAuth/Sources/Swift/Auth/Auth.swift @@ -18,6 +18,7 @@ import FirebaseAppCheckInterop import FirebaseAuthInterop import FirebaseCore import FirebaseCoreExtension +import FirebaseCoreInternal #if COCOAPODS internal import GoogleUtilities #else @@ -81,7 +82,8 @@ extension Auth: AuthInterop { /// This method is not for public use. It is for Firebase clients of AuthInterop. @objc(getTokenForcingRefresh:withCallback:) public func getToken(forcingRefresh forceRefresh: Bool, - completion callback: @escaping (String?, Error?) -> Void) { + completion callback: @escaping @MainActor @Sendable (String?, Error?) + -> Void) { kAuthGlobalWorkQueue.async { [weak self] in if let strongSelf = self { // Enable token auto-refresh if not already enabled. @@ -144,8 +146,7 @@ extension Auth: AuthInterop { /// /// This class is thread-safe. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@preconcurrency -@objc(FIRAuth) open class Auth: NSObject { +@objc(FIRAuth) open class Auth: NSObject, @unchecked Sendable /* TODO: sendable */ { /// Gets the auth object for the default Firebase app. /// /// The default Firebase app must have already been configured or an exception will be raised. @@ -226,14 +227,15 @@ extension Auth: AuthInterop { /// - user: The user object to be set as the current user of the calling Auth instance. /// - completion: Optionally; a block invoked after the user of the calling Auth instance has /// been updated or an error was encountered. - @objc open func updateCurrentUser(_ user: User?, completion: ((Error?) -> Void)? = nil) { + @objc open func updateCurrentUser(_ user: User?, + completion: (@MainActor (Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { guard let user else { let error = AuthErrorUtils.nullUserError(message: nil) Auth.wrapMainAsync(completion, error) return } - let updateUserBlock: (User) -> Void = { user in + let updateUserBlock: @Sendable (User) -> Void = { user in do { try self.updateCurrentUser(user, byForce: true, savingToDisk: true) Auth.wrapMainAsync(completion, nil) @@ -293,7 +295,7 @@ extension Auth: AuthInterop { ) #endif // !FIREBASE_CI @objc open func fetchSignInMethods(forEmail email: String, - completion: (([String]?, Error?) -> Void)? = nil) { + completion: (@MainActor ([String]?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { let request = CreateAuthURIRequest(identifier: email, continueURI: "http://www.google.com/", @@ -357,7 +359,7 @@ extension Auth: AuthInterop { /// or is canceled. Invoked asynchronously on the main thread in the future. @objc open func signIn(withEmail email: String, password: String, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion) Task { @@ -456,7 +458,7 @@ extension Auth: AuthInterop { /// or is canceled. Invoked asynchronously on the main thread in the future. @objc open func signIn(withEmail email: String, link: String, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@MainActor (AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion) let credential = EmailAuthCredential(withEmail: email, link: link) @@ -535,7 +537,7 @@ extension Auth: AuthInterop { @objc(signInWithProvider:UIDelegate:completion:) open func signIn(with provider: FederatedAuthProvider, uiDelegate: AuthUIDelegate?, - completion: ((AuthDataResult?, Error?) -> Void)?) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)?) { kAuthGlobalWorkQueue.async { Task { let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion) @@ -636,7 +638,7 @@ extension Auth: AuthInterop { /// or is canceled. Invoked asynchronously on the main thread in the future. @objc(signInWithCredential:completion:) open func signIn(with credential: AuthCredential, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@MainActor (AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion) Task { @@ -706,7 +708,8 @@ extension Auth: AuthInterop { /// not enabled. Enable them in the Auth section of the Firebase console. /// - Parameter completion: Optionally; a block which is invoked when the sign in finishes, or is /// canceled. Invoked asynchronously on the main thread in the future. - @objc open func signInAnonymously(completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + @objc open func signInAnonymously(completion: (@MainActor (AuthDataResult?, Error?) -> Void)? = + nil) { kAuthGlobalWorkQueue.async { let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion) if let currentUser = self._currentUser, currentUser.isAnonymous { @@ -773,7 +776,7 @@ extension Auth: AuthInterop { /// - Parameter completion: Optionally; a block which is invoked when the sign in finishes, or is /// canceled. Invoked asynchronously on the main thread in the future. @objc open func signIn(withCustomToken token: String, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@MainActor (AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion) let request = VerifyCustomTokenRequest(token: token, @@ -843,7 +846,7 @@ extension Auth: AuthInterop { /// or is canceled. Invoked asynchronously on the main thread in the future. @objc open func createUser(withEmail email: String, password: String, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)? = nil) { guard password.count > 0 else { if let completion { completion(nil, AuthErrorUtils.weakPasswordError(serverResponseReason: "Missing password")) @@ -884,7 +887,10 @@ extension Auth: AuthInterop { func internalCreateUserWithEmail(request: SignUpNewUserRequest, inResponse: SignUpNewUserResponse? = nil, - decoratedCallback: @escaping (Result) + decoratedCallback: @escaping @Sendable (Result< + AuthDataResult, + Error + >) -> Void) { Task { do { @@ -957,7 +963,7 @@ extension Auth: AuthInterop { /// - Parameter completion: Optionally; a block which is invoked when the request finishes. /// Invoked asynchronously on the main thread in the future. @objc open func confirmPasswordReset(withCode code: String, newPassword: String, - completion: @escaping (Error?) -> Void) { + completion: @MainActor @escaping (Error?) -> Void) { kAuthGlobalWorkQueue.async { let request = ResetPasswordRequest(oobCode: code, newPassword: newPassword, @@ -997,7 +1003,8 @@ extension Auth: AuthInterop { /// Invoked /// asynchronously on the main thread in the future. @objc open func checkActionCode(_ code: String, - completion: @escaping (ActionCodeInfo?, Error?) -> Void) { + completion: @MainActor @escaping (ActionCodeInfo?, Error?) + -> Void) { kAuthGlobalWorkQueue.async { let request = ResetPasswordRequest(oobCode: code, newPassword: nil, @@ -1042,7 +1049,8 @@ extension Auth: AuthInterop { /// - Parameter completion: Optionally; a block which is invoked when the request finishes. /// Invoked asynchronously on the main thread in the future. @objc open func verifyPasswordResetCode(_ code: String, - completion: @escaping (String?, Error?) -> Void) { + completion: @escaping @MainActor (String?, Error?) + -> Void) { checkActionCode(code) { info, error in if let error { completion(nil, error) @@ -1075,7 +1083,8 @@ extension Auth: AuthInterop { /// - Parameter code: The out of band code to be applied. /// - Parameter completion: Optionally; a block which is invoked when the request finishes. /// Invoked asynchronously on the main thread in the future. - @objc open func applyActionCode(_ code: String, completion: @escaping (Error?) -> Void) { + @objc open func applyActionCode(_ code: String, + completion: @escaping @MainActor (Error?) -> Void) { kAuthGlobalWorkQueue.async { let request = SetAccountInfoRequest(requestConfiguration: self.requestConfiguration) request.oobCode = code @@ -1120,7 +1129,7 @@ extension Auth: AuthInterop { /// Invoked /// asynchronously on the main thread in the future. @objc open func sendPasswordReset(withEmail email: String, - completion: ((Error?) -> Void)? = nil) { + completion: (@MainActor (Error?) -> Void)? = nil) { sendPasswordReset(withEmail: email, actionCodeSettings: nil, completion: completion) } @@ -1153,7 +1162,7 @@ extension Auth: AuthInterop { /// Invoked asynchronously on the main thread in the future. @objc open func sendPasswordReset(withEmail email: String, actionCodeSettings: ActionCodeSettings?, - completion: ((Error?) -> Void)? = nil) { + completion: (@MainActor (Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { let request = GetOOBConfirmationCodeRequest.passwordResetRequest( email: email, @@ -1222,7 +1231,7 @@ extension Auth: AuthInterop { /// Invoked asynchronously on the main thread in the future. @objc open func sendSignInLink(toEmail email: String, actionCodeSettings: ActionCodeSettings, - completion: ((Error?) -> Void)? = nil) { + completion: (@MainActor (Error?) -> Void)? = nil) { if !actionCodeSettings.handleCodeInApp { fatalError("The handleCodeInApp flag in ActionCodeSettings must be true for Email-link " + "Authentication.") @@ -1304,7 +1313,7 @@ extension Auth: AuthInterop { /// If you change the tenant ID of the `Auth` instance, the configuration will be /// reloaded. @objc(initializeRecaptchaConfigWithCompletion:) - open func initializeRecaptchaConfig(completion: ((Error?) -> Void)?) { + open func initializeRecaptchaConfig(completion: (@Sendable (Error?) -> Void)?) { Task { do { try await initializeRecaptchaConfig() @@ -1347,7 +1356,7 @@ extension Auth: AuthInterop { /// the main thread, even for it's initial invocation after having been added as a listener. /// - Returns: A handle useful for manually unregistering the block as a listener. @objc(addAuthStateDidChangeListener:) - open func addStateDidChangeListener(_ listener: @escaping (Auth, User?) -> Void) + open func addStateDidChangeListener(_ listener: @escaping @MainActor (Auth, User?) -> Void) -> NSObjectProtocol { var firstInvocation = true var previousUserID: String? @@ -1387,7 +1396,8 @@ extension Auth: AuthInterop { /// - Parameter listener: The block to be invoked. The block is always invoked asynchronously on /// the main thread, even for it's initial invocation after having been added as a listener. /// - Returns: A handle useful for manually unregistering the block as a listener. - @objc open func addIDTokenDidChangeListener(_ listener: @escaping (Auth, User?) -> Void) + @objc open func addIDTokenDidChangeListener(_ listener: @MainActor @escaping (Auth, User?) + -> Void) -> NSObjectProtocol { let handle = NotificationCenter.default.addObserver( forName: Auth.authStateDidChangeNotification, @@ -1395,7 +1405,10 @@ extension Auth: AuthInterop { queue: OperationQueue.main ) { notification in if let auth = notification.object as? Auth { - listener(auth, auth._currentUser) + // MainActor is guaranteed by the queue parameter above. + MainActor.assumeIsolated { + listener(auth, auth._currentUser) + } } } objc_sync_enter(Auth.self) @@ -1443,7 +1456,7 @@ extension Auth: AuthInterop { /// - Parameter completion: (Optional) the block invoked when the request to revoke the token is /// complete, or fails. Invoked asynchronously on the main thread in the future. @objc open func revokeToken(withAuthorizationCode authorizationCode: String, - completion: ((Error?) -> Void)? = nil) { + completion: (@MainActor (Error?) -> Void)? = nil) { _currentUser?.internalGetToken(backend: backend) { idToken, error in if let error { Auth.wrapMainAsync(completion, error) @@ -1605,13 +1618,11 @@ extension Auth: AuthInterop { /// so the caller should ignore the URL from further processing, and `false` means the /// the URL is for the app (or another library) so the caller should continue handling /// this URL as usual. - @objc(canHandleURL:) open func canHandle(_ url: URL) -> Bool { - kAuthGlobalWorkQueue.sync { - guard let authURLPresenter = self.authURLPresenter as? AuthURLPresenter else { - return false - } - return authURLPresenter.canHandle(url: url) + @MainActor @objc(canHandleURL:) open func canHandle(_ url: URL) -> Bool { + guard let authURLPresenter = authURLPresenter as? AuthURLPresenter else { + return false } + return authURLPresenter.canHandle(url: url) } #endif @@ -1819,22 +1830,23 @@ extension Auth: AuthInterop { /// This map is needed for looking up the keychain service name after the FirebaseApp instance /// is deleted, to remove the associated keychain item. Accessing should occur within a /// @synchronized([FIRAuth class]) context. - fileprivate static var gKeychainServiceNameForAppName: [String: String] = [:] + fileprivate static let gKeychainServiceNameForAppName = + FIRAllocatedUnfairLock<[String: String]>(initialState: [:]) /// Gets the keychain service name global data for the particular app by /// name, creating an entry for one if it does not exist. /// - Parameter app: The Firebase app to get the keychain service name for. /// - Returns: The keychain service name for the given app. static func keychainServiceName(for app: FirebaseApp) -> String { - objc_sync_enter(Auth.self) - defer { objc_sync_exit(Auth.self) } - let appName = app.name - if let serviceName = gKeychainServiceNameForAppName[appName] { - return serviceName - } else { - let serviceName = "firebase_auth_\(app.options.googleAppID)" - gKeychainServiceNameForAppName[appName] = serviceName - return serviceName + return gKeychainServiceNameForAppName.withLock { map in + let appName = app.name + if let serviceName = map[appName] { + return serviceName + } else { + let serviceName = "firebase_auth_\(app.options.googleAppID)" + map[appName] = serviceName + return serviceName + } } } @@ -1842,13 +1854,13 @@ extension Auth: AuthInterop { /// - Parameter appName: The name of the Firebase app to delete keychain service name for. /// - Returns: The deleted keychain service name, if any. static func deleteKeychainServiceNameForAppName(_ appName: String) -> String? { - objc_sync_enter(Auth.self) - defer { objc_sync_exit(Auth.self) } - guard let serviceName = gKeychainServiceNameForAppName[appName] else { - return nil + return gKeychainServiceNameForAppName.withLock { map in + guard let serviceName = map[appName] else { + return nil + } + map.removeValue(forKey: appName) + return serviceName } - gKeychainServiceNameForAppName.removeValue(forKey: appName) - return serviceName } func signOutByForce(withUserID userID: String) throws { @@ -1872,17 +1884,18 @@ extension Auth: AuthInterop { // Schedule new refresh task after successful attempt. scheduleAutoTokenRefresh() } - var internalNotificationParameters: [String: Any] = [:] - if let app = app { - internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationAppKey] = app - } - if let token, token.count > 0 { - internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationTokenKey] = token - } - internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationUIDKey] = _currentUser? - .uid - let notifications = NotificationCenter.default DispatchQueue.main.async { + var internalNotificationParameters: [String: Any] = [:] + if let app = self.app { + internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationAppKey] = app + } + if let token, token.count > 0 { + internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationTokenKey] = token + } + internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationUIDKey] = self + ._currentUser? + .uid + let notifications = NotificationCenter.default notifications.post(name: NSNotification.Name.FIRAuthStateDidChangeInternal, object: self, userInfo: internalNotificationParameters) @@ -2242,7 +2255,8 @@ extension Auth: AuthInterop { /// Invoked asynchronously on the main thread in the future. /// - Returns: Returns a block that updates the current user. func signInFlowAuthDataResultCallback(byDecorating callback: - ((AuthDataResult?, Error?) -> Void)?) -> (Result) -> Void { + (@MainActor (AuthDataResult?, Error?) -> Void)?) + -> @Sendable (Result) -> Void { return { result in switch result { case let .success(authResult): @@ -2258,7 +2272,8 @@ extension Auth: AuthInterop { } } - private func wrapAsyncRPCTask(_ request: any AuthRPCRequest, _ callback: ((Error?) -> Void)?) { + private func wrapAsyncRPCTask(_ request: any AuthRPCRequest, + _ callback: (@MainActor (Error?) -> Void)?) { Task { do { let _ = try await self.backend.call(with: request) @@ -2269,7 +2284,7 @@ extension Auth: AuthInterop { } } - class func wrapMainAsync(_ callback: ((Error?) -> Void)?, _ error: Error?) { + class func wrapMainAsync(_ callback: (@MainActor (Error?) -> Void)?, _ error: Error?) { if let callback { DispatchQueue.main.async { callback(error) @@ -2277,8 +2292,8 @@ extension Auth: AuthInterop { } } - class func wrapMainAsync(callback: ((T?, Error?) -> Void)?, - with result: Result) -> Void { + class func wrapMainAsync(callback: (@MainActor (T?, Error?) -> Void)?, + with result: Result) -> Void { guard let callback else { return } DispatchQueue.main.async { switch result { @@ -2291,7 +2306,7 @@ extension Auth: AuthInterop { #if os(iOS) private func wrapInjectRecaptcha(request: T, action: AuthRecaptchaAction, - _ callback: @escaping ( + _ callback: @escaping @Sendable ( (T.Response?, Error?) -> Void )) { Task { diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthComponent.swift b/FirebaseAuth/Sources/Swift/Auth/AuthComponent.swift index 649d274c294..36bb008d06c 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthComponent.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthComponent.swift @@ -18,6 +18,7 @@ import FirebaseAppCheckInterop import FirebaseAuthInterop import FirebaseCore import FirebaseCoreExtension +import FirebaseCoreInternal @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRAuthComponent) @@ -33,7 +34,7 @@ class AuthComponent: NSObject, Library, ComponentLifecycleMaintainer { private var instances: [String: Auth] = [:] /// Lock to manage access to the instances array to avoid race conditions. - private var instancesLock: os_unfair_lock = .init() + private let instancesLock: FirebaseCoreInternal.FIRAllocatedUnfairLock = .init() // MARK: - Initializers @@ -58,10 +59,10 @@ class AuthComponent: NSObject, Library, ComponentLifecycleMaintainer { // MARK: - AuthProvider conformance @discardableResult func auth() -> Auth { - os_unfair_lock_lock(&instancesLock) + instancesLock.lock() // Unlock before the function returns. - defer { os_unfair_lock_unlock(&instancesLock) } + defer { instancesLock.unlock() } if let instance = instances[app.name] { return instance diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift b/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift index 9d6dacd2687..bd4bdc73af9 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift @@ -22,7 +22,8 @@ extension AuthDataResult: NSSecureCoding {} /// /// It contains references to a `User` instance and an `AdditionalUserInfo` instance. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRAuthDataResult) open class AuthDataResult: NSObject { +@objc(FIRAuthDataResult) open class AuthDataResult: NSObject, + @unchecked Sendable /* TODO: sendable */ { /// The signed in user. @objc public let user: User diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift b/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift index 90be2649ef6..496cc6b2c98 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift @@ -20,7 +20,8 @@ extension AuthTokenResult: NSSecureCoding {} /// A data class containing the ID token JWT string and other properties associated with the /// token including the decoded payload claims. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRAuthTokenResult) open class AuthTokenResult: NSObject { +@objc(FIRAuthTokenResult) open class AuthTokenResult: NSObject, + @unchecked Sendable /* TODO: sendable */ { /// Stores the JWT string of the ID token. @objc open var token: String diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/FederatedAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/FederatedAuthProvider.swift index 0399b0584ec..76894d52c27 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/FederatedAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/FederatedAuthProvider.swift @@ -16,7 +16,7 @@ import Foundation /// Utility type for constructing federated auth provider credentials. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRFederatedAuthProvider) public protocol FederatedAuthProvider: NSObjectProtocol { +@objc(FIRFederatedAuthProvider) public protocol FederatedAuthProvider: NSObjectProtocol, Sendable { #if os(iOS) /// Used to obtain an auth credential via a mobile web flow. diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift index b8cca1f5fca..d397fdca48c 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift @@ -17,7 +17,8 @@ import Foundation /// Utility class for constructing OAuth Sign In credentials. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIROAuthProvider) open class OAuthProvider: NSObject, FederatedAuthProvider { +@objc(FIROAuthProvider) open class OAuthProvider: NSObject, FederatedAuthProvider, + @unchecked Sendable /* TODO: sendable */ { @objc public static let id = "OAuth" /// Array used to configure the OAuth scopes. @@ -268,7 +269,7 @@ import Foundation /// - Parameter completion: Optionally; a block which is invoked asynchronously on the main /// thread when the mobile web flow is completed. open func getCredentialWith(_ uiDelegate: AuthUIDelegate?, - completion: ((AuthCredential?, Error?) -> Void)? = nil) { + completion: (@MainActor (AuthCredential?, Error?) -> Void)? = nil) { guard let urlTypes = auth.mainBundleUrlTypes, AuthWebUtils.isCallbackSchemeRegistered(forCustomURLScheme: callbackScheme, urlTypes: urlTypes) else { @@ -281,13 +282,12 @@ import Foundation let eventID = AuthWebUtils.randomString(withLength: 10) let sessionID = AuthWebUtils.randomString(withLength: 10) - let callbackOnMainThread: ((AuthCredential?, Error?) -> Void) = { credential, error in - if let completion { - DispatchQueue.main.async { + let callbackOnMainThread: (@MainActor (AuthCredential?, Error?) -> Void) = + { credential, error in + if let completion { completion(credential, error) } } - } Task { do { guard let headfulLiteURL = try await self.getHeadfulLiteUrl(eventID: eventID, @@ -296,15 +296,15 @@ import Foundation "FirebaseAuth Internal Error: Both error and headfulLiteURL return are nil" ) } - let callbackMatcher: (URL?) -> Bool = { callbackURL in + let callbackMatcher: @Sendable (URL?) -> Bool = { callbackURL in AuthWebUtils.isExpectedCallbackURL(callbackURL, eventID: eventID, authType: "signInWithRedirect", callbackScheme: self.callbackScheme) } - self.auth.authURLPresenter.present(headfulLiteURL, - uiDelegate: uiDelegate, - callbackMatcher: callbackMatcher) { callbackURL, error in + await self.auth.authURLPresenter.present(headfulLiteURL, + uiDelegate: uiDelegate, + callbackMatcher: callbackMatcher) { callbackURL, error in if let error { callbackOnMainThread(nil, error) return @@ -328,7 +328,7 @@ import Foundation callbackOnMainThread(credential, nil) } } catch { - callbackOnMainThread(nil, error) + await callbackOnMainThread(nil, error) } } } diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index 61e5693f374..3d9a4ac9e34 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -19,7 +19,8 @@ import Foundation /// /// This class is available on iOS only. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRPhoneAuthProvider) open class PhoneAuthProvider: NSObject { +@objc(FIRPhoneAuthProvider) open class PhoneAuthProvider: NSObject, + @unchecked Sendable /* TODO: sendable */ { /// A string constant identifying the phone identity provider. @objc public static let id = "phone" private static let recaptchaVersion = "RECAPTCHA_ENTERPRISE" @@ -56,7 +57,7 @@ import Foundation @objc(verifyPhoneNumber:UIDelegate:completion:) open func verifyPhoneNumber(_ phoneNumber: String, uiDelegate: AuthUIDelegate? = nil, - completion: ((_: String?, _: Error?) -> Void)?) { + completion: (@Sendable (_: String?, _: Error?) -> Void)?) { verifyPhoneNumber(phoneNumber, uiDelegate: uiDelegate, multiFactorSession: nil, @@ -75,7 +76,7 @@ import Foundation open func verifyPhoneNumber(_ phoneNumber: String, uiDelegate: AuthUIDelegate? = nil, multiFactorSession: MultiFactorSession? = nil, - completion: ((_: String?, _: Error?) -> Void)?) { + completion: (@Sendable (String?, Error?) -> Void)?) { Task { do { let verificationID = try await verifyPhoneNumber( @@ -135,7 +136,7 @@ import Foundation open func verifyPhoneNumber(with multiFactorInfo: PhoneMultiFactorInfo, uiDelegate: AuthUIDelegate? = nil, multiFactorSession: MultiFactorSession?, - completion: ((_: String?, _: Error?) -> Void)?) { + completion: (@Sendable (String?, Error?) -> Void)?) { Task { do { let verificationID = try await verifyPhoneNumber( @@ -531,7 +532,7 @@ import Foundation "Internal error: reCAPTCHAURL returned neither a value nor an error. Report issue" ) } - let callbackMatcher: (URL?) -> Bool = { callbackURL in + let callbackMatcher: @Sendable (URL?) -> Bool = { callbackURL in AuthWebUtils.isExpectedCallbackURL( callbackURL, eventID: eventID, @@ -540,17 +541,19 @@ import Foundation ) } - return try await withUnsafeThrowingContinuation { continuation in - self.auth.authURLPresenter.present(url, - uiDelegate: uiDelegate, - callbackMatcher: callbackMatcher) { callbackURL, error in - if let error { - continuation.resume(throwing: error) - } else { - do { - try continuation.resume(returning: self.reCAPTCHAToken(forURL: callbackURL)) - } catch { + return try await withCheckedThrowingContinuation { continuation in + DispatchQueue.main.async { + self.auth.authURLPresenter.present(url, + uiDelegate: uiDelegate, + callbackMatcher: callbackMatcher) { callbackURL, error in + if let error { continuation.resume(throwing: error) + } else { + do { + try continuation.resume(returning: self.reCAPTCHAToken(forURL: callbackURL)) + } catch { + continuation.resume(throwing: error) + } } } } diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 7a0c39340ae..5232e5afa4c 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -23,7 +23,7 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) protocol AuthBackendProtocol: Sendable { - func call(with request: T) async throws -> T.Response + func call(with request: sending T) async throws -> T.Response } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @@ -48,7 +48,7 @@ final class AuthBackend: AuthBackendProtocol { /// * See FIRAuthInternalErrorCodeRPCResponseDecodingError /// - Parameter request: The request. /// - Returns: The response. - func call(with request: T) async throws -> T.Response { + func call(with request: sending T) async throws -> T.Response { let response = try await callInternal(with: request) if let auth = request.requestConfiguration().auth, let mfaError = Self.generateMFAError(response: response, auth: auth) { diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthRPCRequest.swift b/FirebaseAuth/Sources/Swift/Backend/AuthRPCRequest.swift index eeacfbea897..de253b8d56d 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthRPCRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthRPCRequest.swift @@ -16,7 +16,7 @@ import Foundation /// The generic interface for an RPC request needed by AuthBackend. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -protocol AuthRPCRequest { +protocol AuthRPCRequest: Sendable { associatedtype Response: AuthRPCResponse /// Gets the request's full URL. diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift b/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift index 91f99c266f8..1a98aa66333 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift @@ -19,7 +19,7 @@ import FirebaseCoreExtension /// Defines configurations to be added to a request to Firebase Auth's backend. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -final class AuthRequestConfiguration { +final class AuthRequestConfiguration: @unchecked Sendable /* TODO: sendable */ { /// The Firebase Auth API key used in the request. let apiKey: String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift index 987b7d28fe6..d9a1656b1a6 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift @@ -44,7 +44,8 @@ private let kTenantIDKey = "tenantId" /// Represents the parameters for the createAuthUri endpoint. /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/createAuthUri @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class CreateAuthURIRequest: IdentityToolkitRequest, AuthRPCRequest { +class CreateAuthURIRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = CreateAuthURIResponse /// The email or federated ID of the user. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountRequest.swift index 543d5af3b8f..62343c6726b 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountRequest.swift @@ -26,7 +26,8 @@ private let kIDTokenKey = "idToken" private let kLocalIDKey = "localId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class DeleteAccountRequest: IdentityToolkitRequest, AuthRPCRequest { +class DeleteAccountRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = DeleteAccountResponse /// The STS Access Token of the authenticated user. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInRequest.swift index 72a23517985..7ac70918036 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInRequest.swift @@ -33,7 +33,7 @@ private let kPostBodyKey = "postBody" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class EmailLinkSignInRequest: IdentityToolkitRequest, AuthRPCRequest { +class EmailLinkSignInRequest: IdentityToolkitRequest, AuthRPCRequest, @unchecked Sendable { typealias Response = EmailLinkSignInResponse let email: String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoRequest.swift index 5dba854b172..d824a617769 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoRequest.swift @@ -24,7 +24,8 @@ private let kIDTokenKey = "idToken" /// Represents the parameters for the getAccountInfo endpoint. /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class GetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest { +class GetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = GetAccountInfoResponse /// The STS Access Token for the authenticated user. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift index 3dd4a07b59b..2241b9cb768 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift @@ -112,7 +112,8 @@ protocol SuppressWarning { extension ActionCodeSettings: SuppressWarning {} @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class GetOOBConfirmationCodeRequest: IdentityToolkitRequest, AuthRPCRequest { +class GetOOBConfirmationCodeRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = GetOOBConfirmationCodeResponse /// The types of OOB Confirmation Code to request. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigRequest.swift index 1a1b011ff01..f4169a98d56 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigRequest.swift @@ -18,7 +18,8 @@ import Foundation private let kGetProjectConfigEndPoint = "getProjectConfig" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class GetProjectConfigRequest: IdentityToolkitRequest, AuthRPCRequest { +class GetProjectConfigRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = GetProjectConfigResponse init(requestConfiguration: AuthRequestConfiguration) { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigRequest.swift index 075d8273a15..3c9fc7e5af1 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigRequest.swift @@ -44,7 +44,8 @@ private let kVersionKey = "version" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class GetRecaptchaConfigRequest: IdentityToolkitRequest, AuthRPCRequest { +class GetRecaptchaConfigRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = GetRecaptchaConfigResponse required init(requestConfiguration: AuthRequestConfiguration) { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift index 0c1154d5a68..9430391ba58 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift @@ -20,7 +20,8 @@ private let kFinalizeMFAEnrollmentEndPoint = "accounts/mfaEnrollment:finalize" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class FinalizeMFAEnrollmentRequest: IdentityToolkitRequest, AuthRPCRequest { +class FinalizeMFAEnrollmentRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = FinalizeMFAEnrollmentResponse let idToken: String? diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift index a36c7bb3bc6..e6a1844a96d 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift @@ -32,7 +32,11 @@ private let kRecaptchaVersion = "recaptchaVersion" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class StartMFAEnrollmentRequest: IdentityToolkitRequest, AuthRPCRequest { +final class StartMFAEnrollmentRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { + // This and the other request classes could be made into structs, which would make + // Sendable conformance easier. + typealias Response = StartMFAEnrollmentResponse let idToken: String? diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift index 7e8b67eca96..a2808af9d30 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift @@ -20,7 +20,8 @@ private let kFinalizeMFASignInEndPoint = "accounts/mfaSignIn:finalize" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class FinalizeMFASignInRequest: IdentityToolkitRequest, AuthRPCRequest { +class FinalizeMFASignInRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = FinalizeMFAEnrollmentResponse let mfaPendingCredential: String? diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift index ae28451fb9e..b460a69594b 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift @@ -21,7 +21,8 @@ private let kStartMFASignInEndPoint = "accounts/mfaSignIn:start" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class StartMFASignInRequest: IdentityToolkitRequest, AuthRPCRequest { +class StartMFASignInRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = StartMFASignInResponse let MFAPendingCredential: String? diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift index f2324db3ca2..c82f4972b3b 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift @@ -20,7 +20,8 @@ private let kWithdrawMFAEndPoint = "accounts/mfaEnrollment:withdraw" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class WithdrawMFARequest: IdentityToolkitRequest, AuthRPCRequest { +class WithdrawMFARequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = WithdrawMFAResponse let idToken: String? diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordRequest.swift index c0b2629683d..3f85e471a11 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordRequest.swift @@ -27,7 +27,8 @@ private let kCurrentPasswordKey = "newPassword" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class ResetPasswordRequest: IdentityToolkitRequest, AuthRPCRequest { +class ResetPasswordRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = ResetPasswordResponse /// The oobCode sent in the request. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift index 94e6deeeabb..8bbcc3dd18c 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift @@ -33,7 +33,8 @@ private let kIDTokenKey = "idToken" /// /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class RevokeTokenRequest: IdentityToolkitRequest, AuthRPCRequest { +class RevokeTokenRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = RevokeTokenResponse /// The provider that issued the token to revoke. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift index cd26caa83f5..a6449895f9f 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift @@ -60,7 +60,7 @@ private nonisolated(unsafe) var gAPIHost = "securetoken.googleapis.com" /// Represents the parameters for the token endpoint. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class SecureTokenRequest: AuthRPCRequest { +class SecureTokenRequest: AuthRPCRequest, @unchecked Sendable /* TODO: sendable */ { typealias Response = SecureTokenResponse /// The type of grant requested. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift index 729cf1d200d..953769618e0 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift @@ -49,7 +49,8 @@ enum CodeIdentity: Equatable { } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class SendVerificationCodeRequest: IdentityToolkitRequest, AuthRPCRequest { +class SendVerificationCodeRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = SendVerificationCodeResponse /// The phone number to which the verification code should be sent. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift index 5e310d4a656..4ceedd5ca50 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift @@ -79,7 +79,8 @@ private let kTenantIDKey = "tenantId" /// Represents the parameters for the setAccountInfo endpoint. /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class SetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest { +class SetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = SetAccountInfoResponse /// The STS Access Token of the authenticated user. @@ -89,19 +90,19 @@ class SetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest { var displayName: String? /// The local ID of the user. - var localID: String? = nil + var localID: String? /// The email of the user. - var email: String? = nil + var email: String? /// The photoURL of the user. var photoURL: URL? /// The new password of the user. - var password: String? = nil + var password: String? /// The associated identity providers of the user. - var providers: [String]? = nil + var providers: [String]? /// The out-of-band code of the change email request. var oobCode: String? @@ -113,16 +114,16 @@ class SetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest { var upgradeToFederatedLogin: Bool = false /// The captcha challenge. - var captchaChallenge: String? = nil + var captchaChallenge: String? /// Response to the captcha. - var captchaResponse: String? = nil + var captchaResponse: String? /// The list of user attributes to delete. /// /// Every element of the list must be one of the predefined constant starts with /// `SetAccountInfoUserAttribute`. - var deleteAttributes: [String]? = nil + var deleteAttributes: [String]? /// The list of identity providers to delete. var deleteProviders: [String]? diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterRequest.swift index 34f94f87489..ba7684fbaad 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterRequest.swift @@ -18,7 +18,7 @@ private let kSignInWithGameCenterEndPoint = "signInWithGameCenter" /// The request to sign in with Game Center account @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class SignInWithGameCenterRequest: IdentityToolkitRequest, AuthRPCRequest { +class SignInWithGameCenterRequest: IdentityToolkitRequest, AuthRPCRequest, @unchecked Sendable { typealias Response = SignInWithGameCenterResponse /// The playerID to verify. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserRequest.swift index 68cfe6b5e38..8f2e7301803 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserRequest.swift @@ -45,7 +45,8 @@ private let kReturnSecureTokenKey = "returnSecureToken" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class SignUpNewUserRequest: IdentityToolkitRequest, AuthRPCRequest { +class SignUpNewUserRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = SignUpNewUserResponse /// The email of the user. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionRequest.swift index 30c924ff403..3c86fa38463 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionRequest.swift @@ -80,7 +80,8 @@ private let kLastNameKey = "lastName" /// Represents the parameters for the verifyAssertion endpoint. /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class VerifyAssertionRequest: IdentityToolkitRequest, AuthRPCRequest { +class VerifyAssertionRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = VerifyAssertionResponse /// The URI to which the IDP redirects the user back. It may contain federated login result diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenRequest.swift index b8a97278908..61d4a746185 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenRequest.swift @@ -27,7 +27,8 @@ private let kReturnSecureTokenKey = "returnSecureToken" private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class VerifyCustomTokenRequest: IdentityToolkitRequest, AuthRPCRequest { +class VerifyCustomTokenRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = VerifyCustomTokenResponse let token: String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordRequest.swift index 4c1096d5fbe..45eeca9f888 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordRequest.swift @@ -47,7 +47,8 @@ private let kTenantIDKey = "tenantId" /// Represents the parameters for the verifyPassword endpoint. /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class VerifyPasswordRequest: IdentityToolkitRequest, AuthRPCRequest { +class VerifyPasswordRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = VerifyPasswordResponse /// The email of the user. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift index 07ddf167527..acbf03adc39 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift @@ -57,7 +57,8 @@ extension AuthOperationType { } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class VerifyPhoneNumberRequest: IdentityToolkitRequest, AuthRPCRequest { +class VerifyPhoneNumberRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = VerifyPhoneNumberResponse /// The verification ID obtained from the response of `sendVerificationCode`. diff --git a/FirebaseAuth/Sources/Swift/Backend/VerifyClientRequest.swift b/FirebaseAuth/Sources/Swift/Backend/VerifyClientRequest.swift index 5821fc55c7e..e909c65f26b 100644 --- a/FirebaseAuth/Sources/Swift/Backend/VerifyClientRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/VerifyClientRequest.swift @@ -15,7 +15,8 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class VerifyClientRequest: IdentityToolkitRequest, AuthRPCRequest { +class VerifyClientRequest: IdentityToolkitRequest, AuthRPCRequest, + @unchecked Sendable /* TODO: sendable */ { typealias Response = VerifyClientResponse /// The endpoint for the verifyClient request. diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift index 17ec6b18731..250212d13af 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift @@ -33,7 +33,8 @@ import Foundation /// - Parameter completion: A block with the session identifier for a second factor enrollment /// operation. @objc(getSessionWithCompletion:) - open func getSessionWithCompletion(_ completion: ((MultiFactorSession?, Error?) -> Void)?) { + open func getSessionWithCompletion(_ completion: ((sending MultiFactorSession?, Error?) + -> Void)?) { let session = MultiFactorSession.session(for: user) if let completion { completion(session, nil) @@ -65,7 +66,7 @@ import Foundation @objc(enrollWithAssertion:displayName:completion:) open func enroll(with assertion: MultiFactorAssertion, displayName: String?, - completion: ((Error?) -> Void)?) { + completion: (@Sendable (Error?) -> Void)?) { // TODO: Refactor classes so this duplicated code isn't necessary for phone and totp. guard @@ -185,7 +186,7 @@ import Foundation /// complete, or fails. @objc(unenrollWithInfo:completion:) open func unenroll(with factorInfo: MultiFactorInfo, - completion: ((Error?) -> Void)?) { + completion: (@Sendable (Error?) -> Void)?) { unenroll(withFactorUID: factorInfo.uid, completion: completion) } @@ -202,7 +203,7 @@ import Foundation /// complete, or fails. @objc(unenrollWithFactorUID:completion:) open func unenroll(withFactorUID factorUID: String, - completion: ((Error?) -> Void)?) { + completion: (@Sendable (Error?) -> Void)?) { guard let user = user, let auth = user.auth else { fatalError("Internal Auth error: failed to get user unenrolling in MultiFactor") } diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift index 223c1f9f5f5..2a3fe152943 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift @@ -22,7 +22,7 @@ import Foundation /// This class is available on iOS only. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRMultiFactorResolver) - open class MultiFactorResolver: NSObject { + open class MultiFactorResolver: NSObject, @unchecked Sendable /* TODO: sendable */ { /// The opaque session identifier for the current sign-in flow. @objc public let session: MultiFactorSession @@ -39,7 +39,7 @@ import Foundation /// - Parameter completion: The block invoked when the request is complete, or fails. @objc(resolveSignInWithAssertion:completion:) open func resolveSignIn(with assertion: MultiFactorAssertion, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)? = nil) { var finalizedMFARequestInfo: AuthProto? if let totpAssertion = assertion as? TOTPMultiFactorAssertion { switch totpAssertion.secretOrID { diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift index c4bda3eba36..889207eb22c 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift @@ -25,7 +25,8 @@ import Foundation /// /// This class is available on iOS only. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) - @objc(FIRMultiFactorSession) open class MultiFactorSession: NSObject { + @objc(FIRMultiFactorSession) open class MultiFactorSession: NSObject, + @unchecked Sendable /* TODO: Sendable */ { /// The ID token for an enroll flow. This has to be retrieved after recent authentication. var idToken: String? diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift index 58e5fc5fb8b..be209725797 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift @@ -21,7 +21,12 @@ import Foundation /// The identifier of this second factor is "phone". /// /// This class is available on iOS only. - @objc(FIRPhoneMultiFactorInfo) open class PhoneMultiFactorInfo: MultiFactorInfo { + @objc(FIRPhoneMultiFactorInfo) public final class PhoneMultiFactorInfo: MultiFactorInfo, + @unchecked Sendable { + // In order for this class to remain safely Sendable, all of its parameters must be immutable + // Sendable types. If you add a parameter here that is either mutable or not Sendable, please + // update the unchecked Sendable above. + /// The string identifier for using phone as a second factor. @objc(FIRPhoneMultiFactorID) public static let PhoneMultiFactorID = "phone" @@ -29,7 +34,7 @@ import Foundation @objc(FIRTOTPMultiFactorID) public static let TOTPMultiFactorID = "totp" /// This is the phone number associated with the current second factor. - @objc open var phoneNumber: String + @objc final let phoneNumber: String init(proto: AuthProtoMFAEnrollment) { guard let phoneInfo = proto.phoneInfo else { diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift index bf3b07634ca..69e6d4ffb9b 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift @@ -30,7 +30,8 @@ import Foundation /// - Parameter completion: Completion block @objc(generateSecretWithMultiFactorSession:completion:) open class func generateSecret(with session: MultiFactorSession, - completion: @escaping (TOTPSecret?, Error?) -> Void) { + completion: @escaping @Sendable (sending TOTPSecret?, Error?) + -> Void) { guard let currentUser = session.currentUser, let auth = currentUser.auth else { let error = AuthErrorUtils.error(code: AuthErrorCode.internalError, userInfo: [NSLocalizedDescriptionKey: diff --git a/FirebaseAuth/Sources/Swift/User/User.swift b/FirebaseAuth/Sources/Swift/User/User.swift index 857dc5060a2..bfdd8e4f2af 100644 --- a/FirebaseAuth/Sources/Swift/User/User.swift +++ b/FirebaseAuth/Sources/Swift/User/User.swift @@ -27,7 +27,7 @@ extension User: NSSecureCoding {} /// /// This class is thread-safe. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRUser) open class User: NSObject, UserInfo { +@objc(FIRUser) open class User: NSObject, UserInfo, @unchecked Sendable /* TODO: unchecked */ { /// Indicates the user represents an anonymous user. @objc public internal(set) var isAnonymous: Bool @@ -101,7 +101,7 @@ extension User: NSSecureCoding {} ) #endif // !FIREBASE_CI @objc(updateEmail:completion:) - open func updateEmail(to email: String, completion: ((Error?) -> Void)? = nil) { + open func updateEmail(to email: String, completion: (@Sendable (Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.updateEmail(email: email, password: nil) { error in User.callInMainThreadWithError(callback: completion, error: error) @@ -173,7 +173,7 @@ extension User: NSSecureCoding {} /// - Parameter completion: Optionally; the block invoked when the user profile change has /// finished. @objc(updatePassword:completion:) - open func updatePassword(to password: String, completion: ((Error?) -> Void)? = nil) { + open func updatePassword(to password: String, completion: (@Sendable (Error?) -> Void)? = nil) { guard password.count > 0 else { if let completion { completion(AuthErrorUtils.weakPasswordError(serverResponseReason: "Missing Password")) @@ -234,7 +234,7 @@ extension User: NSSecureCoding {} /// finished. @objc(updatePhoneNumberCredential:completion:) open func updatePhoneNumber(_ credential: PhoneAuthCredential, - completion: ((Error?) -> Void)? = nil) { + completion: (@Sendable (Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.internalUpdateOrLinkPhoneNumber(credential: credential, isLinkOperation: false) { error in @@ -303,7 +303,7 @@ extension User: NSSecureCoding {} /// `updateEmail(to:)`. /// - Parameter completion: Optionally; the block invoked when the reload has finished. Invoked /// asynchronously on the main thread in the future. - @objc open func reload(completion: ((Error?) -> Void)? = nil) { + @objc open func reload(completion: (@Sendable (Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.getAccountInfoRefreshingCache { user, error in User.callInMainThreadWithError(callback: completion, error: error) @@ -361,7 +361,7 @@ extension User: NSSecureCoding {} /// finished. Invoked asynchronously on the main thread in the future. @objc(reauthenticateWithCredential:completion:) open func reauthenticate(with credential: AuthCredential, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { Task { do { @@ -463,7 +463,7 @@ extension User: NSSecureCoding {} @objc(reauthenticateWithProvider:UIDelegate:completion:) open func reauthenticate(with provider: FederatedAuthProvider, uiDelegate: AuthUIDelegate?, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { Task { do { @@ -507,7 +507,7 @@ extension User: NSSecureCoding {} /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked /// asynchronously on the main thread in the future. @objc(getIDTokenWithCompletion:) - open func getIDToken(completion: ((String?, Error?) -> Void)?) { + open func getIDToken(completion: (@Sendable (String?, Error?) -> Void)?) { // |getIDTokenForcingRefresh:completion:| is also a public API so there is no need to dispatch to // global work queue here. getIDTokenForcingRefresh(false, completion: completion) @@ -523,7 +523,7 @@ extension User: NSSecureCoding {} /// asynchronously on the main thread in the future. @objc(getIDTokenForcingRefresh:completion:) open func getIDTokenForcingRefresh(_ forceRefresh: Bool, - completion: ((String?, Error?) -> Void)?) { + completion: (@Sendable (String?, Error?) -> Void)?) { getIDTokenResult(forcingRefresh: forceRefresh) { tokenResult, error in if let completion { DispatchQueue.main.async { @@ -563,7 +563,7 @@ extension User: NSSecureCoding {} /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked /// asynchronously on the main thread in the future. @objc(getIDTokenResultWithCompletion:) - open func getIDTokenResult(completion: ((AuthTokenResult?, Error?) -> Void)?) { + open func getIDTokenResult(completion: (@Sendable (AuthTokenResult?, Error?) -> Void)?) { getIDTokenResult(forcingRefresh: false) { tokenResult, error in if let completion { DispatchQueue.main.async { @@ -584,7 +584,7 @@ extension User: NSSecureCoding {} /// asynchronously on the main thread in the future. @objc(getIDTokenResultForcingRefresh:completion:) open func getIDTokenResult(forcingRefresh: Bool, - completion: ((AuthTokenResult?, Error?) -> Void)?) { + completion: (@Sendable (AuthTokenResult?, Error?) -> Void)?) { kAuthGlobalWorkQueue.async { self.internalGetToken(forceRefresh: forcingRefresh, backend: self.backend) { token, error in var tokenResult: AuthTokenResult? @@ -660,7 +660,7 @@ extension User: NSSecureCoding {} /// fails. @objc(linkWithCredential:completion:) open func link(with credential: AuthCredential, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { if self.providerDataRaw[credential.provider] != nil { User.callInMainThreadWithAuthDataResultAndError( @@ -747,7 +747,7 @@ extension User: NSSecureCoding {} @objc(linkWithProvider:UIDelegate:completion:) open func link(with provider: FederatedAuthProvider, uiDelegate: AuthUIDelegate?, - completion: ((AuthDataResult?, Error?) -> Void)? = nil) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { Task { do { @@ -801,7 +801,7 @@ extension User: NSSecureCoding {} /// - Parameter completion: Optionally; the block invoked when the unlinking is complete, or /// fails. @objc open func unlink(fromProvider provider: String, - completion: ((User?, Error?) -> Void)? = nil) { + completion: (@Sendable (User?, Error?) -> Void)? = nil) { Task { do { let user = try await unlink(fromProvider: provider) @@ -847,7 +847,7 @@ extension User: NSSecureCoding {} /// - Parameter completion: Optionally; the block invoked when the request to send an email /// verification is complete, or fails. Invoked asynchronously on the main thread in the future. @objc(sendEmailVerificationWithCompletion:) - open func __sendEmailVerification(withCompletion completion: ((Error?) -> Void)?) { + open func __sendEmailVerification(withCompletion completion: (@Sendable (Error?) -> Void)?) { sendEmailVerification(completion: completion) } @@ -867,7 +867,7 @@ extension User: NSSecureCoding {} /// verification is complete, or fails. Invoked asynchronously on the main thread in the future. @objc(sendEmailVerificationWithActionCodeSettings:completion:) open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil, - completion: ((Error?) -> Void)? = nil) { + completion: (@Sendable (Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.internalGetToken(backend: self.backend) { accessToken, error in if let error { @@ -932,7 +932,7 @@ extension User: NSSecureCoding {} /// `reauthenticate(with:)`. /// - Parameter completion: Optionally; the block invoked when the request to delete the account /// is complete, or fails. Invoked asynchronously on the main thread in the future. - @objc open func delete(completion: ((Error?) -> Void)? = nil) { + @objc open func delete(completion: (@Sendable (Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.internalGetToken(backend: self.backend) { accessToken, error in if let error { @@ -985,7 +985,8 @@ extension User: NSSecureCoding {} /// - Parameter completion: Optionally; the block invoked when the request to send the /// verification email is complete, or fails. @objc(sendEmailVerificationBeforeUpdatingEmail:completion:) - open func __sendEmailVerificationBeforeUpdating(email: String, completion: ((Error?) -> Void)?) { + open func __sendEmailVerificationBeforeUpdating(email: String, + completion: (@Sendable (Error?) -> Void)?) { sendEmailVerification(beforeUpdatingEmail: email, completion: completion) } @@ -997,7 +998,7 @@ extension User: NSSecureCoding {} /// verification email is complete, or fails. @objc open func sendEmailVerification(beforeUpdatingEmail email: String, actionCodeSettings: ActionCodeSettings? = nil, - completion: ((Error?) -> Void)? = nil) { + completion: (@Sendable (Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.internalGetToken(backend: self.backend) { accessToken, error in if let error { @@ -1157,7 +1158,7 @@ extension User: NSSecureCoding {} private func updateEmail(email: String?, password: String?, - callback: @escaping (Error?) -> Void) { + callback: @escaping @Sendable (Error?) -> Void) { let hadEmailPasswordCredential = hasEmailPasswordCredential executeUserUpdateWithChanges(changeBlock: { user, request in if let email { @@ -1233,9 +1234,10 @@ extension User: NSSecureCoding {} /// - Parameter changeBlock: A block responsible for mutating a template `SetAccountInfoRequest` /// - Parameter callback: A block to invoke when the change is complete. Invoked asynchronously on /// the auth global work queue in the future. - func executeUserUpdateWithChanges(changeBlock: @escaping (GetAccountInfoResponse.User, - SetAccountInfoRequest) -> Void, - callback: @escaping (Error?) -> Void) { + func executeUserUpdateWithChanges(changeBlock: @escaping @Sendable (GetAccountInfoResponse.User, + SetAccountInfoRequest) + -> Void, + callback: @escaping @Sendable (Error?) -> Void) { Task { do { try await userProfileUpdate.executeUserUpdateWithChanges(user: self, @@ -1254,8 +1256,8 @@ extension User: NSSecureCoding {} /// Gets the users' account data from the server, updating our local values. /// - Parameter callback: Invoked when the request to getAccountInfo has completed, or when an /// error has been detected. Invoked asynchronously on the auth global work queue in the future. - func getAccountInfoRefreshingCache(callback: @escaping (GetAccountInfoResponse.User?, - Error?) -> Void) { + func getAccountInfoRefreshingCache(callback: @escaping @Sendable (GetAccountInfoResponse.User?, + Error?) -> Void) { Task { do { let responseUser = try await userProfileUpdate.getAccountInfoRefreshingCache(self) @@ -1318,7 +1320,7 @@ extension User: NSSecureCoding {} /// finished. private func internalUpdateOrLinkPhoneNumber(credential: PhoneAuthCredential, isLinkOperation: Bool, - completion: @escaping (Error?) -> Void) { + completion: @escaping @Sendable (Error?) -> Void) { internalGetToken(backend: backend) { accessToken, error in if let error { completion(error) @@ -1380,7 +1382,7 @@ extension User: NSSecureCoding {} private func link(withEmail email: String, password: String, authResult: AuthDataResult, - _ completion: ((AuthDataResult?, Error?) -> Void)?) { + _ completion: (@Sendable (AuthDataResult?, Error?) -> Void)?) { internalGetToken(backend: backend) { accessToken, error in guard let requestConfiguration = self.auth?.requestConfiguration else { fatalError("Internal auth error: missing auth on User") @@ -1428,7 +1430,7 @@ extension User: NSSecureCoding {} } private func link(withEmailCredential emailCredential: EmailAuthCredential, - completion: ((AuthDataResult?, Error?) -> Void)?) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)?) { if hasEmailPasswordCredential { User.callInMainThreadWithAuthDataResultAndError( callback: completion, @@ -1489,7 +1491,7 @@ extension User: NSSecureCoding {} #if !os(watchOS) private func link(withGameCenterCredential gameCenterCredential: GameCenterAuthCredential, - completion: ((AuthDataResult?, Error?) -> Void)?) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)?) { internalGetToken(backend: backend) { accessToken, error in guard let requestConfiguration = self.auth?.requestConfiguration, let publicKeyURL = gameCenterCredential.publicKeyURL, @@ -1537,7 +1539,7 @@ extension User: NSSecureCoding {} #if os(iOS) private func link(withPhoneCredential phoneCredential: PhoneAuthCredential, - completion: ((AuthDataResult?, Error?) -> Void)?) { + completion: (@Sendable (AuthDataResult?, Error?) -> Void)?) { internalUpdateOrLinkPhoneNumber(credential: phoneCredential, isLinkOperation: true) { error in if let error { @@ -1591,7 +1593,7 @@ extension User: NSSecureCoding {} /// on the global work thread in the future. func internalGetToken(forceRefresh: Bool = false, backend: AuthBackend, - callback: @escaping (String?, Error?) -> Void) { + callback: @escaping @Sendable (String?, Error?) -> Void) { Task { do { let token = try await internalGetTokenAsync(forceRefresh: forceRefresh, backend: backend) @@ -1636,7 +1638,7 @@ extension User: NSSecureCoding {} /// - Parameter callback: The callback to be called in main thread. /// - Parameter error: The error to pass to callback. - class func callInMainThreadWithError(callback: ((Error?) -> Void)?, error: Error?) { + class func callInMainThreadWithError(callback: (@MainActor (Error?) -> Void)?, error: Error?) { if let callback { DispatchQueue.main.async { callback(error) @@ -1648,9 +1650,10 @@ extension User: NSSecureCoding {} /// - Parameter callback: The callback to be called in main thread. /// - Parameter user: The user to pass to callback if there is no error. /// - Parameter error: The error to pass to callback. - private class func callInMainThreadWithUserAndError(callback: ((User?, Error?) -> Void)?, - user: User, - error: Error?) { + private class func callInMainThreadWithUserAndError(callback: (@MainActor (User?, Error?) + -> Void)?, + user: User, + error: Error?) { if let callback { DispatchQueue.main.async { callback((error != nil) ? nil : user, error) @@ -1661,7 +1664,7 @@ extension User: NSSecureCoding {} /// Calls a callback in main thread with user and error. /// - Parameter callback: The callback to be called in main thread. private class func callInMainThreadWithAuthDataResultAndError(callback: ( - (AuthDataResult?, Error?) -> Void + @MainActor (AuthDataResult?, Error?) -> Void )?, result: AuthDataResult? = nil, error: Error? = nil) { diff --git a/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift b/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift index 493f3d80f92..3270d124cb1 100644 --- a/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift +++ b/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift @@ -19,7 +19,8 @@ import Foundation /// Properties are marked as being part of a profile update when they are set. Setting a /// property value to nil is not the same as leaving the property unassigned. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRUserProfileChangeRequest) open class UserProfileChangeRequest: NSObject { +@objc(FIRUserProfileChangeRequest) open class UserProfileChangeRequest: NSObject, + @unchecked Sendable /* TODO: sendable */ { /// The name of the user. @objc open var displayName: String? { get { return _displayName } @@ -59,7 +60,7 @@ import Foundation /// This method should only be called once.Once called, property values should not be changed. /// - Parameter completion: Optionally; the block invoked when the user profile change has been /// applied. - @objc open func commitChanges(completion: ((Error?) -> Void)? = nil) { + @objc open func commitChanges(completion: (@Sendable (Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { if self.consumed { fatalError("Internal Auth Error: commitChanges should only be called once.") diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthDefaultUIDelegate.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthDefaultUIDelegate.swift index 4106977a5e1..767e5d22cc1 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthDefaultUIDelegate.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthDefaultUIDelegate.swift @@ -26,7 +26,7 @@ /// /// This class should be used in the case that a UIDelegate was expected and necessary to /// continue a given flow, but none was provided. - final class AuthDefaultUIDelegate: NSObject, AuthUIDelegate { + final class AuthDefaultUIDelegate: NSObject, AuthUIDelegate, Sendable { /// Returns a default AuthUIDelegate object. /// - Returns: The default AuthUIDelegate object. @MainActor static func defaultUIDelegate() -> AuthUIDelegate? { @@ -76,12 +76,12 @@ self.viewController = viewController } - func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, - completion: (() -> Void)? = nil) { + @MainActor func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, + completion: (() -> Void)? = nil) { viewController?.present(viewControllerToPresent, animated: flag, completion: completion) } - func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + @MainActor func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { viewController?.dismiss(animated: flag, completion: completion) } diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift index b0c61e4dfb4..7a89cbd5e2a 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift @@ -72,7 +72,7 @@ private(set) var agentConfig: AuthRecaptchaConfig? private(set) var tenantConfigs: [String: AuthRecaptchaConfig] = [:] private(set) var recaptchaClient: RCARecaptchaClientProtocol? - private static var _shared = AuthRecaptchaVerifier() + private nonisolated(unsafe) static var _shared = AuthRecaptchaVerifier() private let kRecaptchaVersion = "RECAPTCHA_ENTERPRISE" init() {} @@ -154,7 +154,7 @@ #endif // !(COCOAPODS || SWIFT_PACKAGE) } - private static var recaptchaClient: (any RCARecaptchaClientProtocol)? + private nonisolated(unsafe) static var recaptchaClient: (any RCARecaptchaClientProtocol)? #if COCOAPODS || SWIFT_PACKAGE // No recaptcha on internal build system. private func recaptchaToken(siteKey: String, diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthUIDelegate.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthUIDelegate.swift index 94025574ab4..444978f3de7 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthUIDelegate.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthUIDelegate.swift @@ -20,14 +20,14 @@ /// A protocol to handle user interface interactions for Firebase Auth. /// /// This protocol is available on iOS, macOS Catalyst, and tvOS only. - @objc(FIRAuthUIDelegate) public protocol AuthUIDelegate: NSObjectProtocol { + @objc(FIRAuthUIDelegate) public protocol AuthUIDelegate: NSObjectProtocol, Sendable { /// If implemented, this method will be invoked when Firebase Auth needs to display a view /// controller. /// - Parameter viewControllerToPresent: The view controller to be presented. /// - Parameter flag: Decides whether the view controller presentation should be animated. /// - Parameter completion: The block to execute after the presentation finishes. /// This block has no return value and takes no parameters. - @objc(presentViewController:animated:completion:) + @MainActor @objc(presentViewController:animated:completion:) func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) @@ -37,19 +37,19 @@ /// - Parameter flag: Decides whether removing the view controller should be animated or not. /// - Parameter completion: The block to execute after the presentation finishes. /// This block has no return value and takes no parameters. - @objc(dismissViewControllerAnimated:completion:) + @MainActor @objc(dismissViewControllerAnimated:completion:) func dismiss(animated flag: Bool, completion: (() -> Void)?) } // Extension to support default argument variations. extension AuthUIDelegate { - func present(_ viewControllerToPresent: UIViewController, - animated flag: Bool, - completion: (() -> Void)? = nil) { + @MainActor func present(_ viewControllerToPresent: UIViewController, + animated flag: Bool, + completion: (() -> Void)? = nil) { return present(viewControllerToPresent, animated: flag, completion: nil) } - func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + @MainActor func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { return dismiss(animated: flag, completion: nil) } } diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift index c59ced1cfe1..5745350dabc 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift @@ -22,17 +22,18 @@ /// A Class responsible for presenting URL via SFSafariViewController or WKWebView. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class AuthURLPresenter: NSObject, - SFSafariViewControllerDelegate, AuthWebViewControllerDelegate { + @preconcurrency SFSafariViewControllerDelegate, + AuthWebViewControllerDelegate { /// Presents an URL to interact with user. /// - Parameter url: The URL to present. /// - Parameter uiDelegate: The UI delegate to present view controller. /// - Parameter completion: A block to be called either synchronously if the presentation fails /// to start, or asynchronously in future on an unspecified thread once the presentation /// finishes. - func present(_ url: URL, - uiDelegate: AuthUIDelegate?, - callbackMatcher: @escaping (URL?) -> Bool, - completion: @escaping (URL?, Error?) -> Void) { + @MainActor func present(_ url: URL, + uiDelegate: AuthUIDelegate?, + callbackMatcher: @Sendable @escaping (URL?) -> Bool, + completion: @MainActor @escaping (URL?, Error?) -> Void) { if isPresenting { // Unable to start a new presentation on top of another. // Invoke the new completion closure and leave the old one as-is @@ -74,7 +75,7 @@ /// Determines if a URL was produced by the currently presented URL. /// - Parameter url: The URL to handle. /// - Returns: Whether the URL could be handled or not. - func canHandle(url: URL) -> Bool { + @MainActor func canHandle(url: URL) -> Bool { if isPresenting, let callbackMatcher = callbackMatcher, callbackMatcher(url) { @@ -86,45 +87,38 @@ // MARK: SFSafariViewControllerDelegate - func safariViewControllerDidFinish(_ controller: SFSafariViewController) { - kAuthGlobalWorkQueue.async { - if controller == self.safariViewController { - self.safariViewController = nil - // TODO: Ensure that the SFSafariViewController is actually removed from the screen - // before invoking finishPresentation - self.finishPresentation(withURL: nil, - error: AuthErrorUtils.webContextCancelledError(message: nil)) - } + @MainActor func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + if controller == safariViewController { + safariViewController = nil + // TODO: Ensure that the SFSafariViewController is actually removed from the screen + // before invoking finishPresentation + finishPresentation(withURL: nil, + error: AuthErrorUtils.webContextCancelledError(message: nil)) } } // MARK: AuthWebViewControllerDelegate - func webViewControllerDidCancel(_ controller: AuthWebViewController) { - kAuthGlobalWorkQueue.async { - if self.webViewController == controller { - self.finishPresentation(withURL: nil, - error: AuthErrorUtils.webContextCancelledError(message: nil)) - } + @MainActor func webViewControllerDidCancel(_ controller: AuthWebViewController) { + if webViewController == controller { + finishPresentation(withURL: nil, + error: AuthErrorUtils.webContextCancelledError(message: nil)) } } - func webViewController(_ controller: AuthWebViewController, canHandle url: URL) -> Bool { + @MainActor func webViewController(_ controller: AuthWebViewController, + canHandle url: URL) -> Bool { var result = false - kAuthGlobalWorkQueue.sync { - if self.webViewController == controller { - result = self.canHandle(url: url) - } + if webViewController == controller { + result = canHandle(url: url) } return result } - func webViewController(_ controller: AuthWebViewController, - didFailWithError error: Error) { - kAuthGlobalWorkQueue.async { - if self.webViewController == controller { - self.finishPresentation(withURL: nil, error: error) - } + @MainActor func webViewController(_ controller: AuthWebViewController, + didFailWithError error: Error) { + if webViewController == controller { + finishPresentation(withURL: nil, error: error) } } @@ -159,7 +153,7 @@ // MARK: Private methods - private func finishPresentation(withURL url: URL?, error: Error?) { + @MainActor private func finishPresentation(withURL url: URL?, error: Error?) { callbackMatcher = nil let uiDelegate = self.uiDelegate self.uiDelegate = nil @@ -170,12 +164,10 @@ let webViewController = self.webViewController self.webViewController = nil if safariViewController != nil || webViewController != nil { - DispatchQueue.main.async { - uiDelegate?.dismiss(animated: true) { - self.isPresenting = false - if let completion { - completion(url, error) - } + uiDelegate?.dismiss(animated: true) { + self.isPresenting = false + if let completion { + completion(url, error) } } } else { diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift index ee954b0029f..7fc50c82226 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift @@ -23,17 +23,19 @@ protocol AuthWebViewControllerDelegate: AnyObject { /// Notifies the delegate that the web view controller is being cancelled by the user. /// - Parameter webViewController: The web view controller in question. - func webViewControllerDidCancel(_ controller: AuthWebViewController) + @MainActor func webViewControllerDidCancel(_ controller: AuthWebViewController) /// Determines if a URL should be handled by the delegate. /// - Parameter url: The URL to handle. /// - Returns: Whether the URL could be handled or not. - func webViewController(_ controller: AuthWebViewController, canHandle url: URL) -> Bool + @MainActor func webViewController(_ controller: AuthWebViewController, canHandle url: URL) + -> Bool /// Notifies the delegate that the web view controller failed to load a page. /// - Parameter webViewController: The web view controller in question. /// - Parameter error: The error that has occurred. - func webViewController(_ controller: AuthWebViewController, didFailWithError error: Error) + @MainActor func webViewController(_ controller: AuthWebViewController, + didFailWithError error: Error) /// Presents an URL to interact with user. /// - Parameter url: The URL to present. @@ -41,10 +43,10 @@ /// - Parameter completion: A block to be called either synchronously if the presentation fails /// to start, or asynchronously in future on an unspecified thread once the presentation /// finishes. - func present(_ url: URL, - uiDelegate: AuthUIDelegate?, - callbackMatcher: @escaping (URL?) -> Bool, - completion: @escaping (URL?, Error?) -> Void) + @MainActor func present(_ url: URL, + uiDelegate: AuthUIDelegate?, + callbackMatcher: @Sendable @escaping (URL?) -> Bool, + completion: @MainActor @escaping (URL?, Error?) -> Void) } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuthTestingSupport.podspec b/FirebaseAuthTestingSupport.podspec index 3cf9b5128b5..34e00912a14 100644 --- a/FirebaseAuthTestingSupport.podspec +++ b/FirebaseAuthTestingSupport.podspec @@ -22,7 +22,7 @@ Pod::Spec.new do |s| tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseCombineSwift.podspec b/FirebaseCombineSwift.podspec index dc198ef47e0..e3897512ae6 100644 --- a/FirebaseCombineSwift.podspec +++ b/FirebaseCombineSwift.podspec @@ -19,7 +19,7 @@ for internal testing only. It should not be published. s.social_media_url = 'https://twitter.com/Firebase' - s.swift_version = '5.9' + s.swift_version = '6.0' ios_deployment_target = '13.0' osx_deployment_target = '10.15' diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index e0a86f26df0..cf730563819 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -40,7 +40,7 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration "#{s.module_name}_Privacy" => 'FirebaseCore/Sources/Resources/PrivacyInfo.xcprivacy' } - s.swift_version = '5.9' + s.swift_version = '6.0' s.public_header_files = 'FirebaseCore/Sources/Public/FirebaseCore/*.h' diff --git a/FirebaseCore/Internal/Sources/FIRAllocatedUnfairLock.swift b/FirebaseCore/Internal/Sources/FIRAllocatedUnfairLock.swift new file mode 100644 index 00000000000..ae52faefce6 --- /dev/null +++ b/FirebaseCore/Internal/Sources/FIRAllocatedUnfairLock.swift @@ -0,0 +1,66 @@ +// 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. + +import Foundation +import os.lock + +/// A reference wrapper around `os_unfair_lock`. Replace this class with +/// `OSAllocatedUnfairLock` once we support only iOS 16+. For an explanation +/// on why this is necessary, see the docs: +/// https://developer.apple.com/documentation/os/osallocatedunfairlock +public final class FIRAllocatedUnfairLock: @unchecked Sendable { + private var lockPointer: UnsafeMutablePointer + private var state: State + + public init(initialState: sending State) { + lockPointer = UnsafeMutablePointer + .allocate(capacity: 1) + lockPointer.initialize(to: os_unfair_lock()) + state = initialState + } + + public convenience init() where State == Void { + self.init(initialState: ()) + } + + public func lock() { + os_unfair_lock_lock(lockPointer) + } + + public func unlock() { + os_unfair_lock_unlock(lockPointer) + } + + @discardableResult + public func withLock(_ body: (inout State) throws -> R) rethrows -> R { + let value: R + lock() + defer { unlock() } + value = try body(&state) + return value + } + + @discardableResult + public func withLock(_ body: () throws -> R) rethrows -> R { + let value: R + lock() + defer { unlock() } + value = try body() + return value + } + + deinit { + lockPointer.deallocate() + } +} diff --git a/FirebaseCoreExtension.podspec b/FirebaseCoreExtension.podspec index 480568da10c..641e73fec87 100644 --- a/FirebaseCoreExtension.podspec +++ b/FirebaseCoreExtension.podspec @@ -20,7 +20,7 @@ Pod::Spec.new do |s| } s.social_media_url = 'https://twitter.com/Firebase' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = '12.0' s.osx.deployment_target = '10.15' diff --git a/FirebaseCoreInternal.podspec b/FirebaseCoreInternal.podspec index 18c528b5e39..6382dfbf32c 100644 --- a/FirebaseCoreInternal.podspec +++ b/FirebaseCoreInternal.podspec @@ -36,7 +36,7 @@ Pod::Spec.new do |s| "#{s.module_name}_Privacy" => 'FirebaseCore/Internal/Sources/Resources/PrivacyInfo.xcprivacy' } - s.swift_version = '5.9' + s.swift_version = '6.0' s.dependency 'GoogleUtilities/NSData+zlib', '~> 8.1' diff --git a/FirebaseCrashlytics.podspec b/FirebaseCrashlytics.podspec index 9e1c2cbcc70..8ebb67356df 100644 --- a/FirebaseCrashlytics.podspec +++ b/FirebaseCrashlytics.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec index 3a2a9b899f2..df134fad8d9 100644 --- a/FirebaseDatabase.podspec +++ b/FirebaseDatabase.podspec @@ -22,7 +22,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseDynamicLinks.podspec b/FirebaseDynamicLinks.podspec index bd7db3429db..cf9a691ad5a 100644 --- a/FirebaseDynamicLinks.podspec +++ b/FirebaseDynamicLinks.podspec @@ -18,7 +18,7 @@ Firebase Dynamic Links are deep links that enhance user experience and increase s.social_media_url = 'https://twitter.com/Firebase' s.ios.deployment_target = '13.0' - s.swift_version = '5.9' + s.swift_version = '6.0' # See https://firebase.google.com/support/dynamic-links-faq s.deprecated = true diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index a582e1c1aaa..83edebfc7ce 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -17,7 +17,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.osx.deployment_target = '10.15' s.tvos.deployment_target = '13.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.weak_framework = 'FirebaseFirestoreInternal' diff --git a/FirebaseFirestoreInternal.podspec b/FirebaseFirestoreInternal.podspec index 8567f74fe7d..ec3b59e6796 100644 --- a/FirebaseFirestoreInternal.podspec +++ b/FirebaseFirestoreInternal.podspec @@ -20,7 +20,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.osx.deployment_target = '10.15' s.tvos.deployment_target = '13.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false diff --git a/FirebaseFirestoreTestingSupport.podspec b/FirebaseFirestoreTestingSupport.podspec index a28ab450c3f..23764501713 100644 --- a/FirebaseFirestoreTestingSupport.podspec +++ b/FirebaseFirestoreTestingSupport.podspec @@ -22,7 +22,7 @@ Pod::Spec.new do |s| tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index 5bd8f2c5990..3f547ac2991 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -16,7 +16,7 @@ Cloud Functions for Firebase. :tag => 'CocoaPods-' + s.version.to_s } - s.swift_version = '5.9' + s.swift_version = '6.0' ios_deployment_target = '13.0' osx_deployment_target = '10.15' diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index 52b9b0b79cd..0a491c2794e 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -20,7 +20,7 @@ See more product details at https://firebase.google.com/products/in-app-messagin s.ios.deployment_target = '13.0' s.tvos.deployment_target = '13.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false diff --git a/FirebaseInstallations.podspec b/FirebaseInstallations.podspec index 328da56178e..07e8b67711a 100644 --- a/FirebaseInstallations.podspec +++ b/FirebaseInstallations.podspec @@ -22,7 +22,7 @@ Pod::Spec.new do |s| tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseMLModelDownloader.podspec b/FirebaseMLModelDownloader.podspec index 38b3563d84b..490b3f741ab 100644 --- a/FirebaseMLModelDownloader.podspec +++ b/FirebaseMLModelDownloader.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| :tag => 'CocoaPods-' + s.version.to_s } s.social_media_url = 'https://twitter.com/Firebase' - s.swift_version = '5.9' + s.swift_version = '6.0' ios_deployment_target = '13.0' osx_deployment_target = '10.15' diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index 79eabe7f453..22d900576e7 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -25,7 +25,7 @@ device, and it is completely free. tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseMessaging/Sources/FIRMessagingExtensionHelper.m b/FirebaseMessaging/Sources/FIRMessagingExtensionHelper.m index 7c987279e28..bacc2a3684a 100644 --- a/FirebaseMessaging/Sources/FIRMessagingExtensionHelper.m +++ b/FirebaseMessaging/Sources/FIRMessagingExtensionHelper.m @@ -97,24 +97,18 @@ - (NSData *)transportBytes { @end -@interface FIRMessagingExtensionHelper () -@property(nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); -@property(nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; - -@end - @implementation FIRMessagingExtensionHelper - (void)populateNotificationContent:(UNMutableNotificationContent *)content withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler { - self.contentHandler = [contentHandler copy]; - self.bestAttemptContent = content; + __block void (^handler)(UNNotificationContent *_Nonnull) = [contentHandler copy]; + __block UNMutableNotificationContent *bestAttemptContent = [content mutableCopy]; // The `userInfo` property isn't available on newer versions of tvOS. #if !TARGET_OS_TV NSObject *currentImageURL = content.userInfo[kPayloadOptionsName][kPayloadOptionsImageURLName]; if (!currentImageURL || currentImageURL == [NSNull null]) { - [self deliverNotification]; + [self deliverNotificationWithContent:bestAttemptContent handler:handler]; return; } NSURL *attachmentURL = [NSURL URLWithString:(NSString *)currentImageURL]; @@ -122,14 +116,14 @@ - (void)populateNotificationContent:(UNMutableNotificationContent *)content [self loadAttachmentForURL:attachmentURL completionHandler:^(UNNotificationAttachment *attachment) { if (attachment != nil) { - self.bestAttemptContent.attachments = @[ attachment ]; + bestAttemptContent.attachments = @[ attachment ]; } - [self deliverNotification]; + [self deliverNotificationWithContent:bestAttemptContent handler:handler]; }]; } else { FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageInvalidURL, @"The Image URL provided is invalid %@.", currentImageURL); - [self deliverNotification]; + [self deliverNotificationWithContent:bestAttemptContent handler:handler]; } #else // !TARGET_OS_TV [self deliverNotification]; @@ -150,14 +144,15 @@ - (NSString *)fileExtensionForResponse:(NSURLResponse *)response { } - (void)loadAttachmentForURL:(NSURL *)attachmentURL - completionHandler:(void (^)(UNNotificationAttachment *))completionHandler { - __block UNNotificationAttachment *attachment = nil; - + completionHandler: + (void (^NS_SWIFT_SENDABLE)(UNNotificationAttachment *))completionHandler + NS_SWIFT_SENDABLE { NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; [[session downloadTaskWithURL:attachmentURL completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) { + UNNotificationAttachment *attachment = nil; if (error != nil) { FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotDownloaded, @"Failed to download image given URL %@, error: %@\n", @@ -196,9 +191,10 @@ - (void)loadAttachmentForURL:(NSURL *)attachmentURL } #endif // !TARGET_OS_TV -- (void)deliverNotification { - if (self.contentHandler) { - self.contentHandler(self.bestAttemptContent); +- (void)deliverNotificationWithContent:(UNNotificationContent *)content + handler:(void (^_Nullable)(UNNotificationContent *_Nonnull))handler { + if (handler) { + handler(content); } } diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging+ExtensionHelper.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging+ExtensionHelper.h index 7f0fe7c7688..b4a2d7254f8 100644 --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging+ExtensionHelper.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging+ExtensionHelper.h @@ -32,7 +32,8 @@ NS_ASSUME_NONNULL_BEGIN * * @return An instance of MessagingExtensionHelper that handles the extensions API. */ -+ (FIRMessagingExtensionHelper *)extensionHelper NS_SWIFT_NAME(serviceExtension()); ++ (FIRMessagingExtensionHelper *)extensionHelper NS_SWIFT_NAME(serviceExtension()) + NS_SWIFT_SENDABLE; @end diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h index fded673f5fb..1c2e5e746d5 100644 --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h @@ -154,7 +154,7 @@ NS_SWIFT_NAME(MessagingDelegate) /// * Subscribing to any topics. - (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(nullable NSString *)fcmToken - NS_SWIFT_NAME(messaging(_:didReceiveRegistrationToken:)); + NS_SWIFT_NAME(messaging(_:didReceiveRegistrationToken:)) NS_SWIFT_UI_ACTOR; @end /** @@ -174,7 +174,7 @@ NS_SWIFT_NAME(Messaging) /** * Delegate to handle FCM token refreshes, and remote data messages received via FCM direct channel. */ -@property(nonatomic, weak, nullable) id delegate; +@property(nonatomic, weak, nullable) id delegate NS_SWIFT_UI_ACTOR; /** * FIRMessaging @@ -203,7 +203,7 @@ NS_SWIFT_NAME(Messaging) * If you would like to set the type of the APNs token, rather than relying on * automatic detection, see `setAPNSToken(_:type:)`. */ -@property(nonatomic, copy, nullable) NSData *APNSToken NS_SWIFT_NAME(apnsToken); +@property(nonatomic, copy, nullable) NSData *APNSToken NS_SWIFT_NAME(apnsToken) NS_SWIFT_UI_ACTOR; /** * Set the APNs token for the application. This token will be used to register @@ -216,7 +216,7 @@ NS_SWIFT_NAME(Messaging) * `MessagingAPNSTokenTypeUnknown` to have the type automatically * detected based on your provisioning profile. */ -- (void)setAPNSToken:(NSData *)apnsToken type:(FIRMessagingAPNSTokenType)type; +- (void)setAPNSToken:(NSData *)apnsToken type:(FIRMessagingAPNSTokenType)type NS_SWIFT_UI_ACTOR; #pragma mark - FCM Tokens @@ -236,7 +236,7 @@ NS_SWIFT_NAME(Messaging) * default (for example, because you want to prompt the user before getting a token), * set `FirebaseMessagingAutoInitEnabled` to NO in your application's Info.plist. */ -@property(nonatomic, assign, getter=isAutoInitEnabled) BOOL autoInitEnabled; +@property(nonatomic, assign, getter=isAutoInitEnabled) BOOL autoInitEnabled NS_SWIFT_UI_ACTOR; /** * The FCM registration token is used to identify this device so that FCM can send notifications to @@ -251,7 +251,8 @@ NS_SWIFT_NAME(Messaging) * Once you have an FCM registration token, you should send it to your application server, where * it can be used to send notifications to your device. */ -@property(nonatomic, readonly, nullable) NSString *FCMToken NS_SWIFT_NAME(fcmToken); +@property(nonatomic, readonly, nullable) NSString *FCMToken NS_SWIFT_NAME(fcmToken) + NS_SWIFT_UI_ACTOR; /** * Asynchronously gets the default FCM registration token. @@ -263,9 +264,9 @@ NS_SWIFT_NAME(Messaging) * * @param completion The completion handler to handle the token request. */ - -- (void)tokenWithCompletion:(void (^)(NSString *_Nullable token, - NSError *_Nullable error))completion; +- (void)tokenWithCompletion: + (void (^NS_SWIFT_UI_ACTOR)(NSString *_Nullable token, NSError *_Nullable error))completion + NS_SWIFT_UI_ACTOR; /** * Asynchronously deletes the default FCM registration token. @@ -275,8 +276,8 @@ NS_SWIFT_NAME(Messaging) * * @param completion The completion handler to handle the token deletion. */ - -- (void)deleteTokenWithCompletion:(void (^)(NSError *_Nullable error))completion; +- (void)deleteTokenWithCompletion:(void (^NS_SWIFT_UI_ACTOR)(NSError *_Nullable error))completion + NS_SWIFT_UI_ACTOR; /** * Retrieves an FCM registration token for a particular Sender ID. This can be used to allow @@ -298,9 +299,9 @@ NS_SWIFT_NAME(Messaging) * @param completion The completion handler to handle the token request. */ - (void)retrieveFCMTokenForSenderID:(NSString *)senderID - completion:(void (^)(NSString *_Nullable FCMToken, - NSError *_Nullable error))completion - NS_SWIFT_NAME(retrieveFCMToken(forSenderID:completion:)); + completion:(void (^NS_SWIFT_UI_ACTOR)(NSString *_Nullable FCMToken, + NSError *_Nullable error))completion + NS_SWIFT_NAME(retrieveFCMToken(forSenderID:completion:)) NS_SWIFT_UI_ACTOR; /** * Invalidates an FCM token for a particular Sender ID. That Sender ID cannot no longer send @@ -311,8 +312,8 @@ NS_SWIFT_NAME(Messaging) * @param completion The completion handler to handle the token deletion. */ - (void)deleteFCMTokenForSenderID:(NSString *)senderID - completion:(void (^)(NSError *_Nullable error))completion - NS_SWIFT_NAME(deleteFCMToken(forSenderID:completion:)); + completion:(void (^NS_SWIFT_UI_ACTOR)(NSError *_Nullable error))completion + NS_SWIFT_NAME(deleteFCMToken(forSenderID:completion:)) NS_SWIFT_UI_ACTOR; #pragma mark - Topics @@ -323,7 +324,7 @@ NS_SWIFT_NAME(Messaging) * * @param topic The name of the topic, for example, @"sports". */ -- (void)subscribeToTopic:(NSString *)topic NS_SWIFT_NAME(subscribe(toTopic:)); +- (void)subscribeToTopic:(NSString *)topic NS_SWIFT_NAME(subscribe(toTopic:)) NS_SWIFT_UI_ACTOR; /** * Asynchronously subscribe to the provided topic, retrying on failure. This uses the default FCM @@ -337,7 +338,8 @@ NS_SWIFT_NAME(Messaging) * appropriate error object is returned. */ - (void)subscribeToTopic:(nonnull NSString *)topic - completion:(void (^_Nullable)(NSError *_Nullable error))completion; + completion:(void (^_Nullable NS_SWIFT_UI_ACTOR)(NSError *_Nullable error))completion + NS_SWIFT_UI_ACTOR; /** * Asynchronously unsubscribe from a topic. This uses a FCM Token @@ -346,7 +348,8 @@ NS_SWIFT_NAME(Messaging) * * @param topic The name of the topic, for example @"sports". */ -- (void)unsubscribeFromTopic:(NSString *)topic NS_SWIFT_NAME(unsubscribe(fromTopic:)); +- (void)unsubscribeFromTopic:(NSString *)topic + NS_SWIFT_NAME(unsubscribe(fromTopic:)) NS_SWIFT_UI_ACTOR; /** * Asynchronously unsubscribe from the provided topic, retrying on failure. This uses a FCM Token @@ -359,7 +362,9 @@ NS_SWIFT_NAME(Messaging) * appropriate error object is returned. */ - (void)unsubscribeFromTopic:(nonnull NSString *)topic - completion:(void (^_Nullable)(NSError *_Nullable error))completion; + completion: + (void (^_Nullable NS_SWIFT_UI_ACTOR)(NSError *_Nullable error))completion + NS_SWIFT_UI_ACTOR; #pragma mark - Analytics @@ -374,7 +379,7 @@ NS_SWIFT_NAME(Messaging) * * @return Information about the downstream message. */ -- (FIRMessagingMessageInfo *)appDidReceiveMessage:(NSDictionary *)message; +- (FIRMessagingMessageInfo *)appDidReceiveMessage:(NSDictionary *)message NS_SWIFT_UI_ACTOR; #pragma mark - GDPR /** @@ -387,7 +392,8 @@ NS_SWIFT_NAME(Messaging) * @param completion A completion handler which is invoked when the operation completes. `error == * nil` indicates success. */ -- (void)deleteDataWithCompletion:(void (^)(NSError *__nullable error))completion; +- (void)deleteDataWithCompletion:(void (^NS_SWIFT_UI_ACTOR)(NSError *__nullable error))completion + NS_SWIFT_UI_ACTOR; @end diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h index eb25919ced3..ac24cc19e6c 100644 --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h @@ -30,14 +30,17 @@ NS_ASSUME_NONNULL_BEGIN /// specified in the notification body via the `image` parameter. Images and other /// rich content can be populated manually without the use of this class. See the /// `UNNotificationServiceExtension` type for more details. -__OSX_AVAILABLE(10.14) @interface FIRMessagingExtensionHelper : NSObject +__OSX_AVAILABLE(10.14) NS_SWIFT_SENDABLE @interface FIRMessagingExtensionHelper : NSObject /// Call this API to complete your notification content modification. If you like to /// overwrite some properties of the content instead of using the default payload, -/// make sure to make your customized motification to the content before passing it to -/// this call. -- (void)populateNotificationContent:(UNMutableNotificationContent *)content - withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler; +/// make sure to make your customized modification to the content before passing it to +/// this call. The content returned in the content handler after populating will be a +/// different instance than the value passed in to `content`. +- (void)populateNotificationContent:(UNNotificationContent *)content + withContentHandler: + (void (^NS_SWIFT_SENDABLE)(UNNotificationContent *_Nonnull))contentHandler + NS_SWIFT_SENDABLE; /// Exports delivery metrics to BigQuery. Call this API to enable logging delivery of alert /// notification or background notification and export to BigQuery. diff --git a/FirebasePerformance.podspec b/FirebasePerformance.podspec index b6ce91be3b4..3e340732a40 100644 --- a/FirebasePerformance.podspec +++ b/FirebasePerformance.podspec @@ -20,7 +20,7 @@ Firebase Performance library to measure performance of Mobile and Web Apps. ios_deployment_target = '13.0' tvos_deployment_target = '13.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 7866379e8ae..283b25d36a5 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -23,7 +23,7 @@ app update. tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseRemoteConfigInterop.podspec b/FirebaseRemoteConfigInterop.podspec index e1eb447a4db..03fa5527141 100644 --- a/FirebaseRemoteConfigInterop.podspec +++ b/FirebaseRemoteConfigInterop.podspec @@ -20,7 +20,7 @@ Pod::Spec.new do |s| :tag => 'CocoaPods-' + s.version.to_s } - s.swift_version = '5.9' + s.swift_version = '6.0' s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false diff --git a/FirebaseSessions.podspec b/FirebaseSessions.podspec index 9f7729162dc..ce92eced680 100644 --- a/FirebaseSessions.podspec +++ b/FirebaseSessions.podspec @@ -23,7 +23,7 @@ Pod::Spec.new do |s| tvos_deployment_target = '13.0' watchos_deployment_target = '7.0' - s.swift_version = '5.9' + s.swift_version = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target diff --git a/FirebaseSharedSwift.podspec b/FirebaseSharedSwift.podspec index fd5ded5eafd..4f483f7165a 100644 --- a/FirebaseSharedSwift.podspec +++ b/FirebaseSharedSwift.podspec @@ -18,7 +18,7 @@ Firebase products. FirebaseSharedSwift is not supported for non-Firebase usage. :tag => 'CocoaPods-' + s.version.to_s } - s.swift_version = '5.9' + s.swift_version = '6.0' ios_deployment_target = '13.0' osx_deployment_target = '10.15' diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index d8563d48502..2b9e520256e 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -27,7 +27,7 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.swift_version = '5.9' + s.swift_version = '6.0' s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false diff --git a/FirebaseStorage/CHANGELOG.md b/FirebaseStorage/CHANGELOG.md index 9e940e34310..43ad424d1e2 100644 --- a/FirebaseStorage/CHANGELOG.md +++ b/FirebaseStorage/CHANGELOG.md @@ -2,6 +2,7 @@ - [fixed] `putFile` now works in App Clips. Similarly to app extensions, background session configurations are not used in App Clips (#14794). + # 11.1.0 - [fixed] Fix a potential data race in Storage initialization. (#13369) diff --git a/FirebaseStorage/Sources/Internal/StorageFetcherService.swift b/FirebaseStorage/Sources/Internal/StorageFetcherService.swift index 809d258f87d..267f4e15933 100644 --- a/FirebaseStorage/Sources/Internal/StorageFetcherService.swift +++ b/FirebaseStorage/Sources/Internal/StorageFetcherService.swift @@ -15,9 +15,9 @@ import Foundation #if COCOAPODS - import GTMSessionFetcher + @preconcurrency import GTMSessionFetcher #else - import GTMSessionFetcherCore + @preconcurrency import GTMSessionFetcherCore #endif /// Manage Storage's fetcherService diff --git a/FirebaseStorage/Sources/Internal/StorageGetDownloadURLTask.swift b/FirebaseStorage/Sources/Internal/StorageGetDownloadURLTask.swift index be4e99f4066..abe66e7376e 100644 --- a/FirebaseStorage/Sources/Internal/StorageGetDownloadURLTask.swift +++ b/FirebaseStorage/Sources/Internal/StorageGetDownloadURLTask.swift @@ -19,7 +19,7 @@ import Foundation enum StorageGetDownloadURLTask { static func getDownloadURLTask(reference: StorageReference, queue: DispatchQueue, - completion: ((_: URL?, _: Error?) -> Void)?) { + completion: (@Sendable (_: URL?, _: Error?) -> Void)?) { StorageInternalTask(reference: reference, queue: queue, httpMethod: "GET", diff --git a/FirebaseStorage/Sources/Internal/StorageGetMetadataTask.swift b/FirebaseStorage/Sources/Internal/StorageGetMetadataTask.swift index dffbe26c46c..85348b0aebc 100644 --- a/FirebaseStorage/Sources/Internal/StorageGetMetadataTask.swift +++ b/FirebaseStorage/Sources/Internal/StorageGetMetadataTask.swift @@ -19,7 +19,7 @@ import Foundation enum StorageGetMetadataTask { static func getMetadataTask(reference: StorageReference, queue: DispatchQueue, - completion: ((_: StorageMetadata?, _: Error?) -> Void)?) { + completion: (@Sendable (_: StorageMetadata?, _: Error?) -> Void)?) { StorageInternalTask(reference: reference, queue: queue, httpMethod: "GET", diff --git a/FirebaseStorage/Sources/Internal/StorageInternalTask.swift b/FirebaseStorage/Sources/Internal/StorageInternalTask.swift index aac100bb5e1..6cdc47f7cbb 100644 --- a/FirebaseStorage/Sources/Internal/StorageInternalTask.swift +++ b/FirebaseStorage/Sources/Internal/StorageInternalTask.swift @@ -15,15 +15,15 @@ import Foundation #if COCOAPODS - import GTMSessionFetcher + @preconcurrency import GTMSessionFetcher #else - import GTMSessionFetcherCore + @preconcurrency import GTMSessionFetcherCore #endif /// Implement StorageTasks that are not directly exposed via the public API. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @preconcurrency -class StorageInternalTask: StorageTask { +class StorageInternalTask: StorageTask, @unchecked Sendable /* TODO: sendable */ { private var fetcher: GTMSessionFetcher? @discardableResult @@ -32,7 +32,7 @@ class StorageInternalTask: StorageTask { request: URLRequest? = nil, httpMethod: String, fetcherComment: String, - completion: ((_: Data?, _: Error?) -> Void)?) { + completion: (@Sendable (_: Data?, _: Error?) -> Void)?) { super.init(reference: reference, queue: queue) // Prepare a task and begins execution. diff --git a/FirebaseStorage/Sources/Internal/StorageTokenAuthorizer.swift b/FirebaseStorage/Sources/Internal/StorageTokenAuthorizer.swift index 3dec3a3872a..e35fb678eee 100644 --- a/FirebaseStorage/Sources/Internal/StorageTokenAuthorizer.swift +++ b/FirebaseStorage/Sources/Internal/StorageTokenAuthorizer.swift @@ -14,63 +14,85 @@ import Foundation -import FirebaseAppCheckInterop +@preconcurrency import FirebaseAppCheckInterop /* TODO: sendable */ import FirebaseAuthInterop import FirebaseCore internal import FirebaseCoreExtension #if COCOAPODS - import GTMSessionFetcher + @preconcurrency import GTMSessionFetcher #else - import GTMSessionFetcherCore + @preconcurrency import GTMSessionFetcherCore #endif @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class StorageTokenAuthorizer: NSObject, GTMSessionFetcherAuthorizer { +final class StorageTokenAuthorizer: NSObject, GTMSessionFetcherAuthorizer, Sendable { func authorizeRequest(_ request: NSMutableURLRequest?, - completionHandler handler: @escaping (Error?) -> Void) { + completionHandler handler: @escaping @Sendable (Error?) -> Void) { + if let request = request { + Task { + do { + try await self._authorizeRequest(request) + handler(nil) + } catch { + handler(error) + } + } + } + } + + private func _authorizeRequest(_ request: NSMutableURLRequest) async throws { // Set version header on each request let versionString = "ios/\(FirebaseVersion())" - request?.setValue(versionString, forHTTPHeaderField: "x-firebase-storage-version") + request.setValue(versionString, forHTTPHeaderField: "x-firebase-storage-version") // Set GMP ID on each request - request?.setValue(googleAppID, forHTTPHeaderField: "x-firebase-gmpid") + request.setValue(googleAppID, forHTTPHeaderField: "x-firebase-gmpid") - var tokenError: NSError? - let fetchTokenGroup = DispatchGroup() if let auth { - fetchTokenGroup.enter() - auth.getToken(forcingRefresh: false) { token, error in - if let error = error as? NSError { - var errorDictionary = error.userInfo - errorDictionary["ResponseErrorDomain"] = error.domain - errorDictionary["ResponseErrorCode"] = error.code - tokenError = StorageError.unauthenticated(serverError: errorDictionary) as NSError - } else if let token { - let firebaseToken = "Firebase \(token)" - request?.setValue(firebaseToken, forHTTPHeaderField: "Authorization") + let token: String = try await withCheckedThrowingContinuation { continuation in + auth.getToken(forcingRefresh: false) { token, error in + if let error = error as? NSError { + var errorDictionary = error.userInfo + errorDictionary["ResponseErrorDomain"] = error.domain + errorDictionary["ResponseErrorCode"] = error.code + let wrappedError = StorageError.unauthenticated(serverError: errorDictionary) as Error + continuation.resume(throwing: wrappedError) + } else if let token { + let firebaseToken = "Firebase \(token)" + continuation.resume(returning: firebaseToken) + } else { + let underlyingError: [String: Any] + if let error = error { + underlyingError = [NSUnderlyingErrorKey: error] + } else { + underlyingError = [:] + } + let unknownError = StorageError.unknown( + message: "Auth token fetch returned no token or error: \(token ?? "nil")", + serverError: underlyingError + ) as Error + continuation.resume(throwing: unknownError) + } } - fetchTokenGroup.leave() } + request.setValue(token, forHTTPHeaderField: "Authorization") } if let appCheck { - fetchTokenGroup.enter() - appCheck.getToken(forcingRefresh: false) { tokenResult in - request?.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck") - - if let error = tokenResult.error { - FirebaseLogger.log( - level: .debug, - service: "[FirebaseStorage]", - code: "I-STR000001", - message: "Failed to fetch AppCheck token. Error: \(error)" - ) + let token = await withCheckedContinuation { continuation in + appCheck.getToken(forcingRefresh: false) { tokenResult in + if let error = tokenResult.error { + FirebaseLogger.log( + level: .debug, + service: "[FirebaseStorage]", + code: "I-STR000001", + message: "Failed to fetch AppCheck token. Error: \(error)" + ) + } + continuation.resume(returning: tokenResult.token) } - fetchTokenGroup.leave() } - } - fetchTokenGroup.notify(queue: callbackQueue) { - handler(tokenError) + request.setValue(token, forHTTPHeaderField: "X-Firebase-AppCheck") } } @@ -98,7 +120,7 @@ class StorageTokenAuthorizer: NSObject, GTMSessionFetcherAuthorizer { return authHeader.hasPrefix("Firebase") } - var userEmail: String? + let userEmail: String? let callbackQueue: DispatchQueue private let googleAppID: String diff --git a/FirebaseStorage/Sources/Internal/StorageUpdateMetadataTask.swift b/FirebaseStorage/Sources/Internal/StorageUpdateMetadataTask.swift index 12f4924576f..d4fed476f23 100644 --- a/FirebaseStorage/Sources/Internal/StorageUpdateMetadataTask.swift +++ b/FirebaseStorage/Sources/Internal/StorageUpdateMetadataTask.swift @@ -20,7 +20,8 @@ enum StorageUpdateMetadataTask { static func updateMetadataTask(reference: StorageReference, queue: DispatchQueue, metadata: StorageMetadata, - completion: ((_: StorageMetadata?, _: Error?) -> Void)?) { + completion: (@Sendable (_: StorageMetadata?, _: Error?) + -> Void)?) { var request = StorageUtils.defaultRequestForReference(reference: reference) let updateData = try? JSONSerialization.data(withJSONObject: metadata.updatedMetadata()) request.httpBody = updateData diff --git a/FirebaseStorage/Sources/Result.swift b/FirebaseStorage/Sources/Result.swift index af2f11961a8..12250f76961 100644 --- a/FirebaseStorage/Sources/Result.swift +++ b/FirebaseStorage/Sources/Result.swift @@ -23,8 +23,9 @@ import Foundation /// - Returns: A closure parameterized with an optional generic and optional `Error` to match /// Objective-C APIs. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -private func getResultCallback(completion: @escaping (Result) -> Void) -> (_: T?, - _: Error?) +private func getResultCallback(completion: @escaping @Sendable (Result) -> Void) + -> @Sendable (_: T?, + _: Error?) -> Void { return { (value: T?, error: Error?) in if let value { @@ -48,7 +49,7 @@ public extension StorageReference { /// /// - Parameters: /// - completion: A completion block returning a `Result` enum with either a URL or an `Error`. - func downloadURL(completion: @escaping (Result) -> Void) { + func downloadURL(completion: @escaping @Sendable (Result) -> Void) { downloadURL(completion: getResultCallback(completion: completion)) } @@ -64,7 +65,7 @@ public extension StorageReference { /// /// - Returns: A StorageDownloadTask that can be used to monitor or manage the download. @discardableResult - func getData(maxSize: Int64, completion: @escaping (Result) -> Void) + func getData(maxSize: Int64, completion: @escaping @Sendable (Result) -> Void) -> StorageDownloadTask { return getData(maxSize: maxSize, completion: getResultCallback(completion: completion)) } @@ -74,7 +75,7 @@ public extension StorageReference { /// - Parameters: /// - completion: A completion block which returns a `Result` enum with either the /// object metadata or an `Error`. - func getMetadata(completion: @escaping (Result) -> Void) { + func getMetadata(completion: @escaping @Sendable (Result) -> Void) { getMetadata(completion: getResultCallback(completion: completion)) } @@ -97,7 +98,7 @@ public extension StorageReference { /// with either the list or an `Error`. func list(maxResults: Int64, pageToken: String, - completion: @escaping (Result) -> Void) { + completion: @escaping @Sendable (Result) -> Void) { list(maxResults: maxResults, pageToken: pageToken, completion: getResultCallback(completion: completion)) @@ -118,7 +119,7 @@ public extension StorageReference { /// prefixes under the current `StorageReference`. It returns a `Result` enum /// with either the list or an `Error`. func list(maxResults: Int64, - completion: @escaping (Result) -> Void) { + completion: @escaping @Sendable (Result) -> Void) { list(maxResults: maxResults, completion: getResultCallback(completion: completion)) } @@ -135,7 +136,7 @@ public extension StorageReference { /// - completion: A completion handler that will be invoked with all items and prefixes /// under the current StorageReference. It returns a `Result` enum with either the /// list or an `Error`. - func listAll(completion: @escaping (Result) -> Void) { + func listAll(completion: @escaping @Sendable (Result) -> Void) { listAll(completion: getResultCallback(completion: completion)) } @@ -154,7 +155,7 @@ public extension StorageReference { @discardableResult func putData(_ uploadData: Data, metadata: StorageMetadata? = nil, - completion: @escaping (Result) -> Void) + completion: @escaping @Sendable (Result) -> Void) -> StorageUploadTask { return putData(uploadData, metadata: metadata, @@ -175,7 +176,7 @@ public extension StorageReference { @discardableResult func putFile(from: URL, metadata: StorageMetadata? = nil, - completion: @escaping (Result) -> Void) + completion: @escaping @Sendable (Result) -> Void) -> StorageUploadTask { return putFile(from: from, metadata: metadata, @@ -189,7 +190,7 @@ public extension StorageReference { /// - completion: A completion block which returns a `Result` enum with either the /// object metadata or an `Error`. func updateMetadata(_ metadata: StorageMetadata, - completion: @escaping (Result) -> Void) { + completion: @escaping @Sendable (Result) -> Void) { updateMetadata(metadata, completion: getResultCallback(completion: completion)) } @@ -203,7 +204,7 @@ public extension StorageReference { /// /// - Returns: A `StorageDownloadTask` that can be used to monitor or manage the download. @discardableResult - func write(toFile: URL, completion: @escaping (Result) + func write(toFile: URL, completion: @escaping @Sendable (Result) -> Void) -> StorageDownloadTask { return write(toFile: toFile, completion: getResultCallback(completion: completion)) } diff --git a/FirebaseStorage/Sources/Storage.swift b/FirebaseStorage/Sources/Storage.swift index b4eefff1ff1..a46c0fec9b4 100644 --- a/FirebaseStorage/Sources/Storage.swift +++ b/FirebaseStorage/Sources/Storage.swift @@ -17,6 +17,7 @@ import Foundation import FirebaseAppCheckInterop import FirebaseAuthInterop import FirebaseCore +import FirebaseCoreInternal // Avoids exposing internal FirebaseCore APIs to Swift users. internal import FirebaseCoreExtension @@ -249,13 +250,13 @@ internal import FirebaseCoreExtension private var instances: [String: Storage] = [:] /// Lock to manage access to the instances array to avoid race conditions. - private var instancesLock: os_unfair_lock = .init() + private let instancesLock = FIRAllocatedUnfairLock() private init() {} func storage(app: FirebaseApp, bucket: String) -> Storage { - os_unfair_lock_lock(&instancesLock) - defer { os_unfair_lock_unlock(&instancesLock) } + instancesLock.lock() + defer { instancesLock.unlock() } if let instance = instances[bucket] { return instance diff --git a/FirebaseStorage/Sources/StorageDownloadTask.swift b/FirebaseStorage/Sources/StorageDownloadTask.swift index 70a2e64629e..8bfb663ab0e 100644 --- a/FirebaseStorage/Sources/StorageDownloadTask.swift +++ b/FirebaseStorage/Sources/StorageDownloadTask.swift @@ -15,9 +15,9 @@ import Foundation #if COCOAPODS - import GTMSessionFetcher + @preconcurrency import GTMSessionFetcher #else - import GTMSessionFetcherCore + @preconcurrency import GTMSessionFetcherCore #endif /** @@ -34,7 +34,8 @@ import Foundation */ @objc(FIRStorageDownloadTask) @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -open class StorageDownloadTask: StorageObservableTask, StorageTaskManagement { +open class StorageDownloadTask: StorageObservableTask, StorageTaskManagement, + @unchecked Sendable /* TODO: sendable */ { /** * Prepares a task and begins execution. */ diff --git a/FirebaseStorage/Sources/StorageError.swift b/FirebaseStorage/Sources/StorageError.swift index fb863afa14d..22c52d66d12 100644 --- a/FirebaseStorage/Sources/StorageError.swift +++ b/FirebaseStorage/Sources/StorageError.swift @@ -104,7 +104,11 @@ public let StorageErrorDomain: String = "FIRStorageErrorDomain" /// Firebase Storage errors @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -public enum StorageError: Error, CustomNSError { +public enum StorageError: Error, CustomNSError, @unchecked Sendable { + // It would be nice to make this type a checked sendable, + // but the Any params we get from NSErrors are not easily + // castable to Sendable even though they're all Foundation types + // and should be Sendable. case unknown(message: String, serverError: [String: Any]) case objectNotFound(object: String, serverError: [String: Any]) case bucketNotFound(bucket: String) diff --git a/FirebaseStorage/Sources/StorageListResult.swift b/FirebaseStorage/Sources/StorageListResult.swift index 418b3c25f20..e69efead4a2 100644 --- a/FirebaseStorage/Sources/StorageListResult.swift +++ b/FirebaseStorage/Sources/StorageListResult.swift @@ -16,7 +16,7 @@ import Foundation /** Contains the prefixes and items returned by a `StorageReference.list()` call. */ @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRStorageListResult) open class StorageListResult: NSObject { +@objc(FIRStorageListResult) public final class StorageListResult: NSObject, Sendable { /** * The prefixes (folders) returned by a `list()` operation. */ @@ -35,7 +35,7 @@ import Foundation // MARK: - NSObject overrides - @objc override open func copy() -> Any { + @objc override public func copy() -> Any { return StorageListResult(withPrefixes: prefixes, items: items, pageToken: pageToken) diff --git a/FirebaseStorage/Sources/StorageMetadata.swift b/FirebaseStorage/Sources/StorageMetadata.swift index d0e9d43df4b..3e8b8a779b1 100644 --- a/FirebaseStorage/Sources/StorageMetadata.swift +++ b/FirebaseStorage/Sources/StorageMetadata.swift @@ -23,7 +23,8 @@ import Foundation * [GCS documentation](https://cloud.google.com/storage/docs/json_api/v1/objects#resource) */ @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRStorageMetadata) open class StorageMetadata: NSObject { +@objc(FIRStorageMetadata) open class StorageMetadata: NSObject, + @unchecked Sendable /* TODO: sendable */ { // MARK: - Public APIs /** diff --git a/FirebaseStorage/Sources/StorageReference.swift b/FirebaseStorage/Sources/StorageReference.swift index 26d4b27ee5a..4d3a9409360 100644 --- a/FirebaseStorage/Sources/StorageReference.swift +++ b/FirebaseStorage/Sources/StorageReference.swift @@ -18,7 +18,8 @@ import Foundation /// upload and download objects, as well as get/set object metadata, and delete an object at the /// path. See the [Cloud docs](https://cloud.google.com/storage/) for more details. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRStorageReference) open class StorageReference: NSObject { +@objc(FIRStorageReference) open class StorageReference: NSObject, + @unchecked Sendable /* TODO: sendable */ { // MARK: - Public APIs /// The `Storage` service object which created this reference. @@ -235,7 +236,7 @@ import Foundation /// - Parameter completion: A completion block that either returns the URL on success, /// or an error on failure. @objc(downloadURLWithCompletion:) - open func downloadURL(completion: @escaping ((_: URL?, _: Error?) -> Void)) { + open func downloadURL(completion: @escaping (@Sendable (_: URL?, _: Error?) -> Void)) { StorageGetDownloadURLTask.getDownloadURLTask(reference: self, queue: storage.dispatchQueue, completion: completion) @@ -435,7 +436,7 @@ import Foundation /// - Parameter completion: A completion block which returns the object metadata on success, /// or an error on failure. @objc(metadataWithCompletion:) - open func getMetadata(completion: @escaping ((_: StorageMetadata?, _: Error?) -> Void)) { + open func getMetadata(completion: @escaping @Sendable (_: StorageMetadata?, _: Error?) -> Void) { StorageGetMetadataTask.getMetadataTask(reference: self, queue: storage.dispatchQueue, completion: completion) @@ -459,7 +460,7 @@ import Foundation /// or an error on failure. @objc(updateMetadata:completion:) open func updateMetadata(_ metadata: StorageMetadata, - completion: ((_: StorageMetadata?, _: Error?) -> Void)?) { + completion: (@Sendable (_: StorageMetadata?, _: Error?) -> Void)?) { StorageUpdateMetadataTask.updateMetadataTask(reference: self, queue: storage.dispatchQueue, metadata: metadata, diff --git a/FirebaseStorage/Sources/StorageUploadTask.swift b/FirebaseStorage/Sources/StorageUploadTask.swift index 80a25209b4e..d420028f5a4 100644 --- a/FirebaseStorage/Sources/StorageUploadTask.swift +++ b/FirebaseStorage/Sources/StorageUploadTask.swift @@ -21,9 +21,9 @@ import Foundation #endif // COCOAPODS #if COCOAPODS - import GTMSessionFetcher + @preconcurrency import GTMSessionFetcher #else - import GTMSessionFetcherCore + @preconcurrency import GTMSessionFetcherCore #endif /** @@ -40,7 +40,7 @@ import Foundation */ @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRStorageUploadTask) open class StorageUploadTask: StorageObservableTask, - StorageTaskManagement { + StorageTaskManagement, @unchecked Sendable /* TODO: sendable */ { /** * Prepares a task and begins execution. */ diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index 4305c116ad5..c3f26477dc0 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -36,7 +36,7 @@ Firebase SDK. 'FirebaseVertexAI/Sources/**/*.swift', ] - s.swift_version = '5.9' + s.swift_version = '6.0' s.framework = 'Foundation' s.ios.framework = 'UIKit' diff --git a/scripts/test_catalyst.sh b/scripts/test_catalyst.sh index ae28da6875c..ea955c48b69 100755 --- a/scripts/test_catalyst.sh +++ b/scripts/test_catalyst.sh @@ -53,8 +53,8 @@ args=( "SUPPORTS_MACCATALYST=YES" # Run on macOS. "-sdk" "macosx" "-destination platform=\"OS X\"" "TARGETED_DEVICE_FAMILY=2" - # Disable signing. - "CODE_SIGN_IDENTITY=-" "CODE_SIGNING_REQUIRED=NO" "CODE_SIGNING_ALLOWED=NO" + # Use ad-hoc signing for macOS tests. + "CODE_SIGN_IDENTITY=-" # GHA is still running 10.15. "MACOSX_DEPLOYMENT_TARGET=10.15" ) diff --git a/scripts/xcresult_logs.py b/scripts/xcresult_logs.py index 207f513e96b..34f8ef1efe7 100755 --- a/scripts/xcresult_logs.py +++ b/scripts/xcresult_logs.py @@ -245,7 +245,9 @@ def export_log(xcresult_path, log_id): Returns: The logged output, as a string. """ - contents = xcresulttool_json('get', '--path', xcresult_path, '--id', log_id) + # Note: --legacy is required for Xcode 16. + contents = xcresulttool_json( + 'get', '--path', xcresult_path, '--id', log_id, '--legacy') result = [] collect_log_output(contents, result)