Auth+Async.swift 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // Copyright 2025 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. public extension Auth {
  16. /// An asynchronous sequence of authentication state changes.
  17. ///
  18. /// This sequence provides a modern, `async/await`-compatible way to monitor the authentication
  19. /// state of the current user. It emits a new `User?` value whenever the user signs in or
  20. /// out.
  21. ///
  22. /// The sequence's underlying listener is automatically managed. It is added to the `Auth`
  23. /// instance when you begin iterating over the sequence and is removed when the iteration
  24. /// is cancelled or terminates.
  25. ///
  26. /// - Important: The first value emitted by this sequence is always the *current* authentication
  27. /// state, which may be `nil` if no user is signed in.
  28. ///
  29. /// ### Example Usage
  30. ///
  31. /// You can use a `for await` loop to handle authentication changes:
  32. ///
  33. /// ```swift
  34. /// func monitorAuthState() async {
  35. /// for await user in Auth.auth().authStateChanges {
  36. /// if let user = user {
  37. /// print("User signed in: \(user.uid)")
  38. /// // Update UI or perform actions for a signed-in user.
  39. /// } else {
  40. /// print("User signed out.")
  41. /// // Update UI or perform actions for a signed-out state.
  42. /// }
  43. /// }
  44. /// }
  45. /// ```
  46. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  47. var authStateChanges: AuthStateChangesSequence {
  48. AuthStateChangesSequence(self)
  49. }
  50. /// An `AsyncSequence` that emits `User?` values whenever the authentication state changes.
  51. ///
  52. /// This struct is the concrete type returned by the `Auth.authStateChanges` property.
  53. ///
  54. /// - Important: This type is marked `@unchecked Sendable` because the underlying `Auth` object
  55. /// is not explicitly marked `Sendable` by the framework. However, the operations performed
  56. /// (adding and removing listeners) are known to be thread-safe.
  57. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  58. struct AuthStateChangesSequence: AsyncSequence, @unchecked Sendable {
  59. public typealias Element = User?
  60. public typealias Failure = Never
  61. public typealias AsyncIterator = Iterator
  62. private let auth: Auth
  63. /// Creates a new sequence for monitoring authentication state changes.
  64. /// - Parameter auth: The `Auth` instance to monitor.
  65. public init(_ auth: Auth) {
  66. self.auth = auth
  67. }
  68. /// Creates and returns an iterator for this asynchronous sequence.
  69. /// - Returns: An `Iterator` for `AuthStateChangesSequence`.
  70. public func makeAsyncIterator() -> Iterator {
  71. Iterator(auth: auth)
  72. }
  73. /// The asynchronous iterator for `AuthStateChangesSequence`.
  74. ///
  75. /// - Important: This type is marked `@unchecked Sendable` for the same reasons as its parent
  76. /// `AuthStateChangesSequence`.
  77. public struct Iterator: AsyncIteratorProtocol, @unchecked Sendable {
  78. private let stream: AsyncStream<User?>
  79. private var streamIterator: AsyncStream<User?>.Iterator
  80. /// Initializes the iterator with the provided `Auth` instance.
  81. /// This sets up the `AsyncStream` and registers the necessary listener.
  82. /// - Parameter auth: The `Auth` instance to monitor.
  83. init(auth: Auth) {
  84. stream = AsyncStream<User?> { continuation in
  85. let handle = auth.addStateDidChangeListener { _, user in
  86. continuation.yield(user)
  87. }
  88. continuation.onTermination = { @Sendable _ in
  89. auth.removeStateDidChangeListener(handle)
  90. }
  91. }
  92. streamIterator = stream.makeAsyncIterator()
  93. }
  94. /// Produces the next element in the asynchronous sequence.
  95. ///
  96. /// Returns a `User?` value (where `nil` indicates no signed-in user) or `nil` if the
  97. /// sequence has terminated.
  98. /// - Returns: An optional `User?` object, wrapped in another optional to indicate the end of
  99. /// the sequence.
  100. public mutating func next() async -> User?? {
  101. await streamIterator.next()
  102. }
  103. }
  104. }
  105. /// An asynchronous sequence of ID token changes.
  106. ///
  107. /// This sequence provides a modern, `async/await`-compatible way to monitor changes to the
  108. /// current user's ID token. It emits a new `User?` value whenever the ID token changes.
  109. ///
  110. /// The sequence's underlying listener is automatically managed. It is added to the `Auth`
  111. /// instance when you begin iterating over the sequence and is removed when the iteration
  112. /// is cancelled or terminates.
  113. ///
  114. /// - Important: The first value emitted by this sequence is always the *current* authentication
  115. /// state, which may be `nil` if no user is signed in.
  116. ///
  117. /// ### Example Usage
  118. ///
  119. /// You can use a `for await` loop to handle ID token changes:
  120. ///
  121. /// ```swift
  122. /// func monitorIDTokenChanges() async {
  123. /// for await user in Auth.auth().idTokenChanges {
  124. /// if let user = user {
  125. /// print("ID token changed for user: \(user.uid)")
  126. /// // Update UI or perform actions for a signed-in user.
  127. /// } else {
  128. /// print("User signed out.")
  129. /// // Update UI or perform actions for a signed-out state.
  130. /// }
  131. /// }
  132. /// }
  133. /// ```
  134. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  135. var idTokenChanges: IDTokenChangesSequence {
  136. IDTokenChangesSequence(self)
  137. }
  138. /// An `AsyncSequence` that emits `User?` values whenever the ID token changes.
  139. ///
  140. /// This struct is the concrete type returned by the `Auth.idTokenChanges` property.
  141. ///
  142. /// - Important: This type is marked `@unchecked Sendable` because the underlying `Auth` object
  143. /// is not explicitly marked `Sendable` by the framework. However, the operations performed
  144. /// (adding and removing listeners) are known to be thread-safe.
  145. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  146. struct IDTokenChangesSequence: AsyncSequence, @unchecked Sendable {
  147. public typealias Element = User?
  148. public typealias Failure = Never
  149. public typealias AsyncIterator = Iterator
  150. private let auth: Auth
  151. /// Creates a new sequence for monitoring ID token changes.
  152. /// - Parameter auth: The `Auth` instance to monitor.
  153. public init(_ auth: Auth) {
  154. self.auth = auth
  155. }
  156. /// Creates and returns an iterator for this asynchronous sequence.
  157. /// - Returns: An `Iterator` for `IDTokenChangesSequence`.
  158. public func makeAsyncIterator() -> Iterator {
  159. Iterator(auth: auth)
  160. }
  161. /// The asynchronous iterator for `IDTokenChangesSequence`.
  162. ///
  163. /// - Important: This type is marked `@unchecked Sendable` for the same reasons as its parent
  164. /// `IDTokenChangesSequence`.
  165. public struct Iterator: AsyncIteratorProtocol, @unchecked Sendable {
  166. private let stream: AsyncStream<User?>
  167. private var streamIterator: AsyncStream<User?>.Iterator
  168. /// Initializes the iterator with the provided `Auth` instance.
  169. /// This sets up the `AsyncStream` and registers the necessary listener.
  170. /// - Parameter auth: The `Auth` instance to monitor.
  171. init(auth: Auth) {
  172. stream = AsyncStream<User?> { continuation in
  173. let handle = auth.addIDTokenDidChangeListener { _, user in
  174. continuation.yield(user)
  175. }
  176. continuation.onTermination = { @Sendable _ in
  177. auth.removeIDTokenDidChangeListener(handle)
  178. }
  179. }
  180. streamIterator = stream.makeAsyncIterator()
  181. }
  182. /// Produces the next element in the asynchronous sequence.
  183. ///
  184. /// Returns a `User?` value (where `nil` indicates no signed-in user) or `nil` if the
  185. /// sequence has terminated.
  186. /// - Returns: An optional `User?` object, wrapped in another optional to indicate the end of
  187. /// the sequence.
  188. public mutating func next() async -> User?? {
  189. await streamIterator.next()
  190. }
  191. }
  192. }
  193. }