From 3ec23dd38fd97cd99b83ec308c4a6699f4b389bf Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Thu, 8 May 2025 11:20:19 -0400 Subject: [PATCH 1/2] Copy over Morgan's work --- FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift | 2 +- FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift index 3cfa35909f6..03e38336d00 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift @@ -15,7 +15,7 @@ import Foundation /// Manages information regarding action codes. -@objc(FIRActionCodeInfo) open class ActionCodeInfo: NSObject { +@objc(FIRActionCodeInfo) public final class ActionCodeInfo: NSObject, Sendable { /// The operation being performed. @objc public let operation: ActionCodeOperation diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift index 409b836545a..5cf512621b3 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift @@ -15,7 +15,8 @@ import Foundation /// Used to set and retrieve settings related to handling action codes. -@objc(FIRActionCodeSettings) open class ActionCodeSettings: NSObject { +@objc(FIRActionCodeSettings) open class ActionCodeSettings: NSObject, + @unchecked Sendable /* TODO: sendable */ { /// This URL represents the state/Continue URL in the form of a universal link. /// /// This URL can should be constructed as a universal link that would either directly open From 5ef7eb5ca26c6417a382b913fc8605d53b8e95b2 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 8 May 2025 11:34:11 -0400 Subject: [PATCH 2/2] Finishing touches --- .../Swift/ActionCode/ActionCodeInfo.swift | 5 +- .../ActionCode/ActionCodeOperation.swift | 2 +- .../Swift/ActionCode/ActionCodeSettings.swift | 105 +++++++++++++++--- .../Swift/ActionCode/ActionCodeURL.swift | 5 +- .../Utilities/FIRAllocatedUnfairLock.swift | 6 + 5 files changed, 106 insertions(+), 17 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift index 03e38336d00..abc783276ae 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift @@ -14,8 +14,11 @@ import Foundation +// TODO(Swift 6 Breaking): This type is immutable. Consider removing `open` at +// breaking change so checked Sendable can be used. + /// Manages information regarding action codes. -@objc(FIRActionCodeInfo) public final class ActionCodeInfo: NSObject, Sendable { +@objc(FIRActionCodeInfo) open class ActionCodeInfo: NSObject, @unchecked Sendable { /// The operation being performed. @objc public let operation: ActionCodeOperation diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift index e345b6e91e8..f916e283267 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift @@ -15,7 +15,7 @@ import Foundation /// Operations which can be performed with action codes. -@objc(FIRActionCodeOperation) public enum ActionCodeOperation: Int, @unchecked Sendable { +@objc(FIRActionCodeOperation) public enum ActionCodeOperation: Int, Sendable { /// Action code for unknown operation. case unknown = 0 diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift index 5cf512621b3..2f98f08b332 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift @@ -12,33 +12,55 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseCoreInternal import Foundation +// TODO(Swift 6 Breaking): Consider breaking up into a checked Sendable Swift +// type and unchecked Sendable ObjC wrapper class. + /// Used to set and retrieve settings related to handling action codes. @objc(FIRActionCodeSettings) open class ActionCodeSettings: NSObject, - @unchecked Sendable /* TODO: sendable */ { + @unchecked Sendable { /// This URL represents the state/Continue URL in the form of a universal link. /// /// This URL can should be constructed as a universal link that would either directly open /// the app where the action code would be handled or continue to the app after the action code /// is handled by Firebase. - @objc(URL) open var url: URL? + @objc(URL) open var url: URL? { + get { impl.url.value() } + set { impl.url.withLock { $0 = newValue } } + } /// Indicates whether the action code link will open the app directly or after being /// redirected from a Firebase owned web widget. - @objc open var handleCodeInApp: Bool = false + @objc open var handleCodeInApp: Bool { + get { impl.handleCodeInApp.value() } + set { impl.handleCodeInApp.withLock { $0 = newValue } } + } /// The iOS bundle ID, if available. The default value is the current app's bundle ID. - @objc open var iOSBundleID: String? + @objc open var iOSBundleID: String? { + get { impl.iOSBundleID.value() } + set { impl.iOSBundleID.withLock { $0 = newValue } } + } /// The Android package name, if available. - @objc open var androidPackageName: String? + @objc open var androidPackageName: String? { + get { impl.androidPackageName.value() } + set { impl.androidPackageName.withLock { $0 = newValue } } + } /// The minimum Android version supported, if available. - @objc open var androidMinimumVersion: String? + @objc open var androidMinimumVersion: String? { + get { impl.androidMinimumVersion.value() } + set { impl.androidMinimumVersion.withLock { $0 = newValue } } + } /// Indicates whether the Android app should be installed on a device where it is not available. - @objc open var androidInstallIfNotAvailable: Bool = false + @objc open var androidInstallIfNotAvailable: Bool { + get { impl.androidInstallIfNotAvailable.value() } + set { impl.androidInstallIfNotAvailable.withLock { $0 = newValue } } + } /// The Firebase Dynamic Link domain used for out of band code flow. #if !FIREBASE_CI @@ -48,14 +70,22 @@ import Foundation message: "Firebase Dynamic Links is deprecated. Migrate to use Firebase Hosting link and use `linkDomain` to set a custom domain instead." ) #endif // !FIREBASE_CI - @objc open var dynamicLinkDomain: String? + @objc open var dynamicLinkDomain: String? { + get { impl.dynamicLinkDomain.value() } + set { impl.dynamicLinkDomain.withLock { $0 = newValue } } + } /// The out of band custom domain for handling code in app. - @objc public var linkDomain: String? + @objc public var linkDomain: String? { + get { impl.linkDomain.value() } + set { impl.linkDomain.withLock { $0 = newValue } } + } + + private let impl: SendableActionCodeSettings /// Sets the iOS bundle ID. @objc override public init() { - iOSBundleID = Bundle.main.bundleIdentifier + impl = .init() } /// Sets the Android package name, the flag to indicate whether or not to install the app, @@ -71,13 +101,60 @@ import Foundation @objc open func setAndroidPackageName(_ androidPackageName: String, installIfNotAvailable: Bool, minimumVersion: String?) { - self.androidPackageName = androidPackageName - androidInstallIfNotAvailable = installIfNotAvailable - androidMinimumVersion = minimumVersion + impl + .setAndroidPackageName( + androidPackageName, + installIfNotAvailable: installIfNotAvailable, + minimumVersion: minimumVersion + ) } /// Sets the iOS bundle ID. open func setIOSBundleID(_ bundleID: String) { - iOSBundleID = bundleID + impl.setIOSBundleID(bundleID) + } +} + +private extension ActionCodeSettings { + /// Checked Sendable implementation of `ActionCodeSettings`. + final class SendableActionCodeSettings: Sendable { + let url = FIRAllocatedUnfairLock(initialState: nil) + + let handleCodeInApp = FIRAllocatedUnfairLock(initialState: false) + + let iOSBundleID: FIRAllocatedUnfairLock + + let androidPackageName = FIRAllocatedUnfairLock(initialState: nil) + + let androidMinimumVersion = FIRAllocatedUnfairLock(initialState: nil) + + let androidInstallIfNotAvailable = FIRAllocatedUnfairLock(initialState: false) + + #if !FIREBASE_CI + @available( + *, + deprecated, + message: "Firebase Dynamic Links is deprecated. Migrate to use Firebase Hosting link and use `linkDomain` to set a custom domain instead." + ) + #endif // !FIREBASE_CI + let dynamicLinkDomain = FIRAllocatedUnfairLock(initialState: nil) + + let linkDomain = FIRAllocatedUnfairLock(initialState: nil) + + init() { + iOSBundleID = FIRAllocatedUnfairLock(initialState: Bundle.main.bundleIdentifier) + } + + func setAndroidPackageName(_ androidPackageName: String, + installIfNotAvailable: Bool, + minimumVersion: String?) { + self.androidPackageName.withLock { $0 = androidPackageName } + androidInstallIfNotAvailable.withLock { $0 = installIfNotAvailable } + androidMinimumVersion.withLock { $0 = minimumVersion } + } + + func setIOSBundleID(_ bundleID: String) { + iOSBundleID.withLock { $0 = bundleID } + } } } diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift index 16c28437f4f..dbed003f05a 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift @@ -14,8 +14,11 @@ import Foundation +// TODO(Swift 6 Breaking): This type is immutable. Consider removing `open` at +// breaking change so checked Sendable can be used. + /// This class will allow developers to easily extract information about out of band links. -@objc(FIRActionCodeURL) open class ActionCodeURL: NSObject { +@objc(FIRActionCodeURL) open class ActionCodeURL: NSObject, @unchecked Sendable { /// Returns the API key from the link. nil, if not provided. @objc(APIKey) public let apiKey: String? diff --git a/FirebaseCore/Internal/Sources/Utilities/FIRAllocatedUnfairLock.swift b/FirebaseCore/Internal/Sources/Utilities/FIRAllocatedUnfairLock.swift index ae52faefce6..c94f3153db9 100644 --- a/FirebaseCore/Internal/Sources/Utilities/FIRAllocatedUnfairLock.swift +++ b/FirebaseCore/Internal/Sources/Utilities/FIRAllocatedUnfairLock.swift @@ -42,6 +42,12 @@ public final class FIRAllocatedUnfairLock: @unchecked Sendable { os_unfair_lock_unlock(lockPointer) } + public func value() -> State { + lock() + defer { unlock() } + return state + } + @discardableResult public func withLock(_ body: (inout State) throws -> R) rethrows -> R { let value: R