@@ -50,9 +50,13 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
5050 @Published private( set) var rootLayout : LiveViewNativeCore . Document ?
5151 @Published private( set) var stylesheet : Stylesheet < R > ?
5252
53- // Socket connection
54- var liveSocket : LiveViewNativeCore . LiveSocket ?
55- var socket : LiveViewNativeCore . Socket ?
53+ private var persistence : SimplePersistentStore
54+ private var eventHandler : SimpleEventHandler
55+ private var patchHandler : SimplePatchHandler
56+ private var navHandler : SimpleNavHandler
57+
58+ private var liveviewClient : LiveViewClient ?
59+ private var builder : LiveViewClientBuilder
5660
5761 private var liveReloadChannel : LiveViewNativeCore . LiveChannel ?
5862 private var liveReloadListenerLoop : Task < ( ) , any Error > ?
@@ -85,6 +89,14 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
8589 public convenience init ( _ host: some LiveViewHost , config: LiveSessionConfiguration = . init( ) , customRegistryType: R . Type = R . self) {
8690 self . init ( host. url, config: config, customRegistryType: customRegistryType)
8791 }
92+
93+ public func clientChannel( ) -> LiveViewClientChannel ? {
94+ self . liveviewClient? . channel ( )
95+ }
96+
97+ public func status( ) -> SocketStatus {
98+ ( try ? self . liveviewClient? . status ( ) ) ?? . disconnected
99+ }
88100
89101 /// Creates a new coordinator with a custom registry.
90102 /// - Parameter url: The URL of the page to establish the connection to.
@@ -95,6 +107,29 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
95107
96108 self . configuration = config
97109
110+ self . patchHandler = SimplePatchHandler ( )
111+ self . eventHandler = SimpleEventHandler ( )
112+ self . navHandler = SimpleNavHandler ( )
113+ self . persistence = SimplePersistentStore ( )
114+
115+ self . builder = LiveViewClientBuilder ( ) ;
116+
117+ self . builder. setPatchHandler ( patchHandler)
118+ self . builder. setNavigationHandler ( navHandler)
119+ self . builder. setPersistenceProvider ( persistence)
120+ self . builder. setLiveChannelEventHandler ( eventHandler)
121+ self . builder. setLogLevel ( . debug)
122+
123+ self . eventHandler. viewReloadSubject
124+ . receive ( on: DispatchQueue . main)
125+ . sink { [ weak self] newView in
126+ guard let self else { return }
127+ guard let last = self . navigationPath. last else { return }
128+ if let client = self . liveviewClient {
129+ last. coordinator. join ( client, self . eventHandler, self . patchHandler)
130+ }
131+ } . store ( in: & cancellables)
132+
98133 // load cookies into core
99134 for cookie in HTTPCookieStorage . shared. cookies ( for: url) ?? [ ] {
100135 try ? LiveViewNativeCore . storeSessionCookie ( " \( cookie. name) = \( cookie. value) " , self . url. absoluteString)
@@ -111,38 +146,27 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
111146
112147 $navigationPath. scan ( ( [ LiveNavigationEntry < R > ] ( ) , [ LiveNavigationEntry < R > ] ( ) ) , { ( $0. 1 , $1) } ) . sink { [ weak self] prev, next in
113148 guard let self else { return }
149+ guard let client = liveviewClient else { return }
114150 Task {
115- try await prev. last? . coordinator. disconnect ( )
151+ prev. last? . coordinator. disconnect ( )
116152 if prev. count > next. count {
117- let targetEntry = self . liveSocket!. getEntries ( ) [ next. count - 1 ]
118- next. last? . coordinator. join (
119- try await self . liveSocket!. traverseTo ( targetEntry. id,
120- . some( [
121- " _format " : . str( string: LiveSessionParameters . platform) ,
122- " _interface " : . object( object: LiveSessionParameters . platformParams)
123- ] ) ,
124- nil )
125- )
153+
154+ var opts = NavActionOptions ( )
155+ opts. joinParams = . some( [ " _interface " : . object( object: LiveSessionParameters . platformParams) ] )
156+ let targetEntry = client. getEntries ( ) [ next. count - 1 ]
157+ let _ = try await client. traverseTo ( targetEntry. id, opts)
158+
126159 } else if next. count > prev. count && prev. count > 0 {
127160 // forward navigation (from `redirect` or `<NavigationLink>`)
128- next. last? . coordinator. join (
129- try await self . liveSocket!. navigate ( next. last!. url. absoluteString,
130- . some( [
131- " _format " : . str( string: LiveSessionParameters . platform) ,
132- " _interface " : . object( object: LiveSessionParameters . platformParams)
133- ] ) ,
134- NavOptions ( action: . push) )
135- )
161+ var opts = NavOptions ( )
162+ opts. joinParams = . some( [ " _interface " : . object( object: LiveSessionParameters . platformParams) ] )
163+ opts. action = . push
164+ let _ = try await client. navigate ( next. last!. url. absoluteString, opts)
136165 } else if next. count == prev. count {
137- guard let liveChannel =
138- try await self . liveSocket? . navigate ( next. last!. url. absoluteString,
139- . some( [
140- " _format " : . str( string: LiveSessionParameters . platform) ,
141- " _interface " : . object( object: LiveSessionParameters . platformParams)
142- ] ) ,
143- NavOptions ( action: . replace) )
144- else { return }
145- next. last? . coordinator. join ( liveChannel)
166+ var opts = NavOptions ( )
167+ opts. joinParams = . some( [ " _interface " : . object( object: LiveSessionParameters . platformParams) ] )
168+ opts. action = . replace
169+ let _ = try await client. navigate ( next. last!. url. absoluteString, opts)
146170 }
147171 }
148172 } . store ( in: & cancellables)
@@ -172,12 +196,8 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
172196 }
173197
174198 deinit {
175- let socket = socket
176199 let liveReloadChannel = liveReloadChannel
177200 Task {
178- do {
179- try await socket? . shutdown ( )
180- }
181201 do {
182202 try await liveReloadChannel? . shutdownParentSocket ( )
183203 }
@@ -195,6 +215,7 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
195215 /// - Parameter httpMethod: The HTTP method to use for the dead render. Defaults to `GET`.
196216 /// - Parameter httpBody: The HTTP body to send when requesting the dead render.
197217 public func connect( httpMethod: String ? = nil , httpBody: Data ? = nil , additionalHeaders: [ String : String ] ? = nil ) async {
218+
198219 do {
199220 switch state {
200221 case . setup, . disconnected, . connectionFailed:
@@ -212,37 +233,25 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
212233 let headers = ( configuration. headers ?? [ : ] )
213234 . merging ( additionalHeaders ?? [ : ] ) { $1 }
214235
215- if let socket {
216- try await socket. shutdown ( )
217- }
218-
219236 let adapter = ReconnectStrategyAdapter ( self . configuration. reconnectBehavior)
220237
221- self . liveSocket = try await LiveSocket (
222- originalURL. absoluteString,
223- LiveSessionParameters . platform,
224- ConnectOpts (
225- headers: headers,
226- body: httpBody,
227- method: httpMethod. flatMap ( Method . init ( _: ) ) ,
228- timeoutMs: 10_000
229- ) ,
230- adapter
231- )
232-
233- // save cookies to storage
234- HTTPCookieStorage . shared. setCookies (
235- ( self . liveSocket!. joinHeaders ( ) [ " set-cookie " ] ?? [ ] ) . flatMap {
236- HTTPCookie . cookies ( withResponseHeaderFields: [ " Set-Cookie " : $0] , for: URL ( string: self . liveSocket!. joinUrl ( ) ) !)
237- } ,
238- for: self . url,
239- mainDocumentURL: nil
238+ let opts = ClientConnectOpts (
239+ joinParams: . some( [ " _interface " : . object( object: LiveSessionParameters . platformParams) ] ) ,
240+ headers: . some( headers) ,
241+ method: Method . init ( httpMethod ?? " Get " ) ,
242+ requestBody: httpBody
240243 )
244+
245+ if let client = self . liveviewClient {
246+ try await client. reconnect ( originalURL. absoluteString, opts)
247+ } else {
248+ self . liveviewClient = try await self . builder. connect ( originalURL. absoluteString, opts)
249+ self . navigationPath. last!. coordinator. join ( self . liveviewClient!, self . eventHandler, self . patchHandler)
250+ }
241251
242- self . socket = self . liveSocket? . socket ( )
243252
244- self . rootLayout = self . liveSocket !. deadRender ( )
245- let styleURLs = self . liveSocket !. styleUrls ( )
253+ self . rootLayout = try self . liveviewClient !. deadRender ( )
254+ let styleURLs = try self . liveviewClient !. styleUrls ( )
246255
247256 self . stylesheet = try await withThrowingTaskGroup ( of: Stylesheet< R> . self ) { @Sendable group in
248257 for style in styleURLs {
@@ -266,34 +275,15 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
266275 }
267276 }
268277
269- let liveChannel = try await self . liveSocket!. joinLiveviewChannel (
270- . some( [
271- " _format " : . str( string: LiveSessionParameters . platform) ,
272- " _interface " : . object( object: LiveSessionParameters . platformParams)
273- ] ) ,
274- nil
275- )
276-
277- self . navigationPath. last!. coordinator. join ( liveChannel)
278-
279278 self . state = . connected
280279
281- if let liveReloadChannel {
282- try await liveReloadChannel. shutdownParentSocket ( )
283- self . liveReloadChannel = nil
284- }
285-
286- if self . liveSocket!. hasLiveReload ( ) {
287- self . liveReloadChannel = try await self . liveSocket!. joinLivereloadChannel ( )
288- bindLiveReloadListener ( )
289- }
290280 } catch {
291281 self . state = . connectionFailed( error)
292282 }
293283 }
294-
284+
285+ // TODO: move this error handlign into core
295286 func overrideLiveReloadChannel( channel: LiveChannel ) async throws {
296-
297287 if let liveReloadChannel {
298288 try await liveReloadChannel. shutdownParentSocket ( )
299289 self . liveReloadChannel = nil
@@ -332,7 +322,7 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
332322 private func disconnect( preserveNavigationPath: Bool = false ) async {
333323 do {
334324 for entry in self . navigationPath {
335- try await entry. coordinator. disconnect ( )
325+ entry. coordinator. disconnect ( )
336326 if !preserveNavigationPath {
337327 entry. coordinator. document = nil
338328 }
@@ -356,9 +346,10 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
356346
357347 self . liveReloadChannel = nil
358348
359- try await self . socket? . shutdown ( )
360- self . socket = nil
361- self . liveSocket = nil
349+ if let client = self . liveviewClient {
350+ try await client. disconnect ( )
351+ }
352+
362353 self . state = . disconnected
363354 } catch {
364355 self . state = . connectionFailed( error)
@@ -377,22 +368,6 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
377368 self . navigationPath = [ . init( url: self . url, coordinator: self . navigationPath. first!. coordinator, navigationTransition: nil , pendingView: nil ) ]
378369 }
379370 await self . connect ( httpMethod: httpMethod, httpBody: httpBody, additionalHeaders: headers)
380- // do {
381- // if let url {
382- // try await self.disconnect(preserveNavigationPath: false)
383- // self.url = url
384- // self.navigationPath = [.init(url: self.url, coordinator: self.navigationPath.first!.coordinator, navigationTransition: nil, pendingView: nil)]
385- // } else {
386- // // preserve the navigation path, but still clear the stale documents, since they're being completely replaced.
387- // try await self.disconnect(preserveNavigationPath: true)
388- // for entry in self.navigationPath {
389- // entry.coordinator.document = nil
390- // }
391- // }
392- // try await self.connect(httpMethod: httpMethod, httpBody: httpBody, additionalHeaders: headers)
393- // } catch {
394- // self.state = .connectionFailed(error)
395- // }
396371 }
397372
398373 /// Creates a publisher that can be used to listen for server-sent LiveView events.
@@ -432,6 +407,18 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
432407 . store ( in: & eventHandlers)
433408 }
434409
410+ public func postFormData(
411+ url: Url ,
412+ formData: [ String : String ]
413+ ) async throws {
414+ if let client = self . liveviewClient {
415+ try await client. postForm ( url. absoluteString,
416+ formData,
417+ . some( [ " _interface " : . object( object: LiveSessionParameters . platformParams) ] ) ,
418+ nil )
419+ }
420+ }
421+
435422 func redirect(
436423 _ redirect: LiveRedirect ,
437424 navigationTransition: Any ? = nil ,
@@ -615,3 +602,5 @@ fileprivate extension URL {
615602
616603extension Socket : @unchecked Sendable { }
617604extension Channel : @unchecked Sendable { }
605+ extension LiveViewClient : @unchecked Sendable { }
606+ extension LiveViewClientBuilder : @unchecked Sendable { }
0 commit comments