@@ -5,10 +5,29 @@ import Foundation
55 import FoundationNetworking
66#endif
77
8+ public final class AuthStateChangeListenerHandle {
9+ var onCancel : ( @Sendable ( ) -> Void ) ?
10+
11+ public func cancel( ) {
12+ onCancel ? ( )
13+ onCancel = nil
14+ }
15+
16+ deinit {
17+ cancel ( )
18+ }
19+ }
20+
21+ public typealias AuthStateChangeListener = @Sendable (
22+ _ event: AuthChangeEvent ,
23+ _ session: Session ?
24+ ) -> Void
25+
826public actor AuthClient {
927 /// FetchHandler is a type alias for asynchronous network request handling.
10- public typealias FetchHandler =
11- @Sendable ( _ request: URLRequest ) async throws -> ( Data , URLResponse )
28+ public typealias FetchHandler = @Sendable (
29+ _ request: URLRequest
30+ ) async throws -> ( Data , URLResponse )
1231
1332 /// Configuration struct represents the client configuration.
1433 public struct Configuration : Sendable {
@@ -150,7 +169,7 @@ public actor AuthClient {
150169 sessionManager: . live,
151170 codeVerifierStorage: . live,
152171 api: api,
153- eventEmitter: . live ,
172+ eventEmitter: EventEmitter ( ) ,
154173 sessionStorage: . live,
155174 logger: configuration. logger
156175 )
@@ -187,19 +206,38 @@ public actor AuthClient {
187206 )
188207 }
189208
209+ /// Listen for auth state changes.
210+ ///
211+ /// An `.initialSession` is always emitted when this method is called.
212+ @discardableResult
213+ public func onAuthStateChange(
214+ _ listener: @escaping AuthStateChangeListener
215+ ) -> AuthStateChangeListenerHandle {
216+ let handle = eventEmitter. attachListener ( listener)
217+ Task {
218+ await emitInitialSession ( forHandle: handle)
219+ }
220+ return handle
221+ }
222+
190223 /// Listen for auth state changes.
191224 ///
192225 /// An `.initialSession` is always emitted when this method is called.
193226 public var authStateChanges : AsyncStream < (
194227 event: AuthChangeEvent ,
195228 session: Session ?
196229 ) > {
197- let ( id, stream) = eventEmitter. attachListener ( )
198- logger? . debug ( " auth state change listener with id ' \( id. uuidString) ' attached. " )
230+ let ( stream, continuation) = AsyncStream< (
231+ event: AuthChangeEvent,
232+ session: Session?
233+ ) > . makeStream( )
234+
235+ let handle = onAuthStateChange { event, session in
236+ continuation. yield ( ( event, session) )
237+ }
199238
200- Task { [ id] in
201- await emitInitialSession ( forStreamWithID: id)
202- logger? . debug ( " initial session for listener with id ' \( id. uuidString) ' emitted. " )
239+ continuation. onTermination = { _ in
240+ handle. cancel ( )
203241 }
204242
205243 return stream
@@ -884,9 +922,9 @@ public actor AuthClient {
884922 return session
885923 }
886924
887- private func emitInitialSession( forStreamWithID id : UUID ) async {
925+ private func emitInitialSession( forHandle handle : AuthStateChangeListenerHandle ) async {
888926 let session = try ? await session
889- eventEmitter. emit ( . initialSession, session, id )
927+ eventEmitter. emit ( . initialSession, session: session , handle : handle )
890928 }
891929
892930 private func prepareForPKCE( ) -> ( codeChallenge: String ? , codeChallengeMethod: String ? ) {
0 commit comments