@@ -61,6 +61,10 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
6161
6262 private var cancellables = Set < AnyCancellable > ( )
6363
64+ private var mergedEventSubjects : AnyCancellable ?
65+ private var eventSubject = PassthroughSubject < ( LiveViewCoordinator < R > , ( String , Payload ) ) , Never > ( )
66+ private var eventHandlers = Set < AnyCancellable > ( )
67+
6468 var isMounted : Bool = false
6569
6670 public convenience init ( _ host: some LiveViewHost , config: LiveSessionConfiguration = . init( ) , customRegistryType: R . Type = R . self) {
@@ -75,6 +79,12 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
7579 self . url = url. appending ( path: " " ) . absoluteURL
7680 self . config = config
7781 self . rootCoordinator = . init( session: self , url: self . url)
82+ self . mergedEventSubjects = self . rootCoordinator. eventSubject. compactMap ( {
83+ [ weak self] value in self . map ( { ( $0. rootCoordinator, value) } )
84+ } )
85+ . sink ( receiveValue: { [ weak self] value in
86+ self ? . eventSubject. send ( value)
87+ } )
7888 self . $internalState. sink { state in
7989 if case . connected = state {
8090 Task { [ weak self] in
@@ -93,7 +103,21 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
93103 }
94104 }
95105 } . store ( in: & cancellables)
96- $navigationPath. sink { entries in
106+ $navigationPath. sink { [ weak self] entries in
107+ // Receive events from all live views.
108+ if let self {
109+ let allCoordinators = ( [ self . rootCoordinator] + entries. map ( \. coordinator) )
110+ . reduce ( into: [ LiveViewCoordinator < R > ] ( ) ) { result, next in
111+ guard !result. contains ( where: { $0 === next } ) else { return }
112+ result. append ( next)
113+ }
114+ self . mergedEventSubjects = Publishers . MergeMany ( allCoordinators. map ( { coordinator in
115+ coordinator. eventSubject. map ( { ( coordinator, $0) } )
116+ } ) )
117+ . sink ( receiveValue: { [ weak self] value in
118+ self ? . eventSubject. send ( value)
119+ } )
120+ }
97121 guard let entry = entries. last else { return }
98122 Task {
99123 // If the coordinator is not connected to the right URL, update it.
@@ -190,6 +214,34 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
190214 await self . rootCoordinator. reconnect ( )
191215 }
192216
217+ /// Creates a publisher that can be used to listen for server-sent LiveView events.
218+ ///
219+ /// - Parameter event: The event name that is being listened for.
220+ /// - Returns: A publisher that emits event payloads.
221+ ///
222+ /// This event will be received from every LiveView handled by this session coordinator.
223+ ///
224+ /// See ``LiveViewCoordinator/receiveEvent(_:)`` for more details.
225+ public func receiveEvent( _ event: String ) -> some Publisher < ( LiveViewCoordinator < R > , Payload ) , Never > {
226+ eventSubject
227+ . filter { $0. 1 . 0 == event }
228+ . map ( { ( $0. 0 , $0. 1 . 1 ) } )
229+ }
230+
231+ /// Permanently registers a handler for a server-sent LiveView event.
232+ ///
233+ /// - Parameter event: The event name that is being listened for.
234+ /// - Parameter handler: A closure to invoke when the coordinator receives an event. The event value is provided as the closure's parameter.
235+ ///
236+ /// This event handler will be added to every LiveView handled by this session coordinator.
237+ ///
238+ /// See ``LiveViewCoordinator/handleEvent(_:handler:)`` for more details.
239+ public func handleEvent( _ event: String , handler: @escaping ( LiveViewCoordinator < R > , Payload ) -> Void ) {
240+ receiveEvent ( event)
241+ . sink ( receiveValue: handler)
242+ . store ( in: & eventHandlers)
243+ }
244+
193245 func fetchDOM( url: URL ) async throws -> String {
194246 let data : Data
195247 let resp : URLResponse
0 commit comments