mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-11 22:55:59 +05:45
This PR adds native Swift `AsyncSequence` support for monitoring authentication state and ID token changes in Firebase Auth. It provides a modern `for await` syntax alternative to traditional callback listeners. * **`authStateChanges`:** Added a new property on `Auth` returning `AuthStateChangesSequence` to monitor user sign-in/sign-out state. * **`idTokenChanges`:** Added a new property on `Auth` returning `IDTokenChangesSequence` to monitor underlying token changes for the current user.
216 lines
8.2 KiB
Swift
216 lines
8.2 KiB
Swift
// 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
|
|
|
|
public extension Auth {
|
|
/// An asynchronous sequence of authentication state changes.
|
|
///
|
|
/// This sequence provides a modern, `async/await`-compatible way to monitor the authentication
|
|
/// state of the current user. It emits a new `User?` value whenever the user signs in or
|
|
/// out.
|
|
///
|
|
/// The sequence's underlying listener is automatically managed. It is added to the `Auth`
|
|
/// instance when you begin iterating over the sequence and is removed when the iteration
|
|
/// is cancelled or terminates.
|
|
///
|
|
/// - Important: The first value emitted by this sequence is always the *current* authentication
|
|
/// state, which may be `nil` if no user is signed in.
|
|
///
|
|
/// ### Example Usage
|
|
///
|
|
/// You can use a `for await` loop to handle authentication changes:
|
|
///
|
|
/// ```swift
|
|
/// func monitorAuthState() async {
|
|
/// for await user in Auth.auth().authStateChanges {
|
|
/// if let user {
|
|
/// print("User signed in: \(user.uid)")
|
|
/// // Update UI or perform actions for a signed-in user.
|
|
/// } else {
|
|
/// print("User signed out.")
|
|
/// // Update UI or perform actions for a signed-out state.
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
var authStateChanges: AuthStateChangesSequence {
|
|
AuthStateChangesSequence(self)
|
|
}
|
|
|
|
/// An `AsyncSequence` that emits `User?` values whenever the authentication state changes.
|
|
///
|
|
/// This struct is the concrete type returned by the `Auth.authStateChanges` property.
|
|
///
|
|
/// - Important: This type is marked `@unchecked Sendable` because the underlying `Auth` object
|
|
/// is not explicitly marked `Sendable` by the framework. However, the operations performed
|
|
/// (adding and removing listeners) are known to be thread-safe.
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
struct AuthStateChangesSequence: AsyncSequence, @unchecked Sendable {
|
|
public typealias Element = User?
|
|
public typealias Failure = Never
|
|
public typealias AsyncIterator = Iterator
|
|
|
|
private let auth: Auth
|
|
|
|
/// Creates a new sequence for monitoring authentication state changes.
|
|
/// - Parameter auth: The `Auth` instance to monitor.
|
|
public init(_ auth: Auth) {
|
|
self.auth = auth
|
|
}
|
|
|
|
/// Creates and returns an iterator for this asynchronous sequence.
|
|
/// - Returns: An `Iterator` for `AuthStateChangesSequence`.
|
|
public func makeAsyncIterator() -> Iterator {
|
|
Iterator(auth: auth)
|
|
}
|
|
|
|
/// The asynchronous iterator for `AuthStateChangesSequence`.
|
|
public struct Iterator: AsyncIteratorProtocol {
|
|
private let stream: AsyncStream<User?>
|
|
private var streamIterator: AsyncStream<User?>.Iterator
|
|
|
|
/// Initializes the iterator with the provided `Auth` instance.
|
|
/// This sets up the `AsyncStream` and registers the necessary listener.
|
|
/// - Parameter auth: The `Auth` instance to monitor.
|
|
init(auth: Auth) {
|
|
stream = AsyncStream<User?> { continuation in
|
|
let handle = auth.addStateDidChangeListener { _, user in
|
|
continuation.yield(user)
|
|
}
|
|
|
|
continuation.onTermination = { @Sendable _ in
|
|
auth.removeStateDidChangeListener(handle)
|
|
}
|
|
}
|
|
|
|
streamIterator = stream.makeAsyncIterator()
|
|
}
|
|
|
|
/// Produces the next element in the asynchronous sequence.
|
|
///
|
|
/// Returns a `User?` value (where `nil` indicates no signed-in user) or `nil` if the
|
|
/// sequence has terminated.
|
|
/// - Returns: An optional `User?` object, wrapped in another optional to indicate the end of
|
|
/// the sequence.
|
|
public mutating func next() async -> User?? {
|
|
await streamIterator.next()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An asynchronous sequence of ID token changes.
|
|
///
|
|
/// This sequence provides a modern, `async/await`-compatible way to monitor changes to the
|
|
/// current user's ID token. It emits a new `User?` value whenever the ID token changes.
|
|
///
|
|
/// The sequence's underlying listener is automatically managed. It is added to the `Auth`
|
|
/// instance when you begin iterating over the sequence and is removed when the iteration
|
|
/// is cancelled or terminates.
|
|
///
|
|
/// - Important: The first value emitted by this sequence is always the *current* authentication
|
|
/// state, which may be `nil` if no user is signed in.
|
|
///
|
|
/// ### Example Usage
|
|
///
|
|
/// You can use a `for await` loop to handle ID token changes:
|
|
///
|
|
/// ```swift
|
|
/// func monitorIDTokenChanges() async {
|
|
/// for await user in Auth.auth().idTokenChanges {
|
|
/// if let user {
|
|
/// print("ID token changed for user: \(user.uid)")
|
|
/// // Update UI or perform actions for a signed-in user.
|
|
/// } else {
|
|
/// print("User signed out.")
|
|
/// // Update UI or perform actions for a signed-out state.
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
var idTokenChanges: IDTokenChangesSequence {
|
|
IDTokenChangesSequence(self)
|
|
}
|
|
|
|
/// An `AsyncSequence` that emits `User?` values whenever the ID token changes.
|
|
///
|
|
/// This struct is the concrete type returned by the `Auth.idTokenChanges` property.
|
|
///
|
|
/// - Important: This type is marked `@unchecked Sendable` because the underlying `Auth` object
|
|
/// is not explicitly marked `Sendable` by the framework. However, the operations performed
|
|
/// (adding and removing listeners) are known to be thread-safe.
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
struct IDTokenChangesSequence: AsyncSequence, @unchecked Sendable {
|
|
public typealias Element = User?
|
|
public typealias Failure = Never
|
|
public typealias AsyncIterator = Iterator
|
|
|
|
private let auth: Auth
|
|
|
|
/// Creates a new sequence for monitoring ID token changes.
|
|
/// - Parameter auth: The `Auth` instance to monitor.
|
|
public init(_ auth: Auth) {
|
|
self.auth = auth
|
|
}
|
|
|
|
/// Creates and returns an iterator for this asynchronous sequence.
|
|
/// - Returns: An `Iterator` for `IDTokenChangesSequence`.
|
|
public func makeAsyncIterator() -> Iterator {
|
|
Iterator(auth: auth)
|
|
}
|
|
|
|
/// The asynchronous iterator for `IDTokenChangesSequence`.
|
|
public struct Iterator: AsyncIteratorProtocol {
|
|
private let stream: AsyncStream<User?>
|
|
private var streamIterator: AsyncStream<User?>.Iterator
|
|
|
|
/// Initializes the iterator with the provided `Auth` instance.
|
|
/// This sets up the `AsyncStream` and registers the necessary listener.
|
|
/// - Parameter auth: The `Auth` instance to monitor.
|
|
init(auth: Auth) {
|
|
stream = AsyncStream<User?> { continuation in
|
|
let handle = auth.addIDTokenDidChangeListener { _, user in
|
|
continuation.yield(user)
|
|
}
|
|
|
|
continuation.onTermination = { @Sendable _ in
|
|
auth.removeIDTokenDidChangeListener(handle)
|
|
}
|
|
}
|
|
|
|
streamIterator = stream.makeAsyncIterator()
|
|
}
|
|
|
|
/// Produces the next element in the asynchronous sequence.
|
|
///
|
|
/// Returns a `User?` value (where `nil` indicates no signed-in user) or `nil` if the
|
|
/// sequence has terminated.
|
|
/// - Returns: An optional `User?` object, wrapped in another optional to indicate the end of
|
|
/// the sequence.
|
|
public mutating func next() async -> User?? {
|
|
await streamIterator.next()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Explicitly mark the Iterators as unavailable for Sendable conformance
|
|
@available(*, unavailable)
|
|
extension Auth.AuthStateChangesSequence.Iterator: Sendable {}
|
|
|
|
@available(*, unavailable)
|
|
extension Auth.IDTokenChangesSequence.Iterator: Sendable {}
|