@@ -110,32 +110,48 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
110
110
default :
111
111
false
112
112
}
113
+ print ( next. last!. coordinator. url)
114
+ print ( next. last!. url)
115
+ print ( isDisconnected)
113
116
if next. last!. coordinator. url != next. last!. url || isDisconnected {
114
117
Task {
115
- if prev. count > next. count {
116
- // back navigation
117
- print ( " Back navigate " , next. last!. url)
118
- try await prev. last? . coordinator. disconnect ( )
119
- let liveChannel = try await self . liveSocket!. joinLiveviewChannel (
120
- . some( [
121
- " _format " : . str( string: LiveSessionParameters . platform) ,
122
- " _interface " : . object( object: LiveSessionParameters . platformParams)
123
- ] ) ,
124
- next. last!. url. absoluteString
125
- )
126
- try await next. last!. coordinator. join ( liveChannel)
127
- } else if next. count > prev. count && prev. count > 0 {
128
- print ( " Forward navigation to \( next. last!. url) " )
129
- // forward navigation (from `redirect` or `<NavigationLink>`)
130
- try await prev. last? . coordinator. disconnect ( )
131
- let liveChannel = try await self . liveSocket!. joinLiveviewChannel (
132
- . some( [
133
- " _format " : . str( string: LiveSessionParameters . platform) ,
134
- " _interface " : . object( object: LiveSessionParameters . platformParams)
135
- ] ) ,
136
- next. last!. url. absoluteString
137
- )
138
- try await next. last? . coordinator. join ( liveChannel)
118
+ do {
119
+ if prev. count > next. count {
120
+ // back navigation
121
+ try await prev. last? . coordinator. disconnect ( )
122
+ let liveChannel = try await self . liveSocket!. joinLiveviewChannel (
123
+ . some( [
124
+ " _format " : . str( string: LiveSessionParameters . platform) ,
125
+ " _interface " : . object( object: LiveSessionParameters . platformParams)
126
+ ] ) ,
127
+ next. last!. url. absoluteString
128
+ )
129
+ try await next. last!. coordinator. join ( liveChannel)
130
+ } else if next. count > prev. count && prev. count > 0 {
131
+ // forward navigation (from `redirect` or `<NavigationLink>`)
132
+ try await prev. last? . coordinator. disconnect ( )
133
+ let liveChannel = try await self . liveSocket!. joinLiveviewChannel (
134
+ . some( [
135
+ " _format " : . str( string: LiveSessionParameters . platform) ,
136
+ " _interface " : . object( object: LiveSessionParameters . platformParams)
137
+ ] ) ,
138
+ next. last!. url. absoluteString
139
+ )
140
+ try await next. last? . coordinator. join ( liveChannel)
141
+ }
142
+ } catch let error as LiveSocketError {
143
+ switch error {
144
+ case . Phoenix( error: " server rejected join { \" reason \" : \" unauthorized \" } " ) ,
145
+ . Phoenix( error: " server rejected join { \" reason \" : \" stale \" } " ) :
146
+ try await self . disconnect ( preserveNavigationPath: true )
147
+ let originalURL = self . url
148
+ self . url = next. last!. url
149
+ try await self . connect ( httpMethod: nil , httpBody: nil )
150
+ self . url = originalURL
151
+ default :
152
+ logger. error ( " \( error) " )
153
+ throw error
154
+ }
139
155
}
140
156
}
141
157
}
@@ -323,184 +339,6 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
323
339
. sink ( receiveValue: handler)
324
340
. store ( in: & eventHandlers)
325
341
}
326
-
327
- /// Request the dead render with the given `request`.
328
- ///
329
- /// Returns the dead render HTML and the HTTP response information (including the final URL after redirects).
330
- func deadRender(
331
- for request: URLRequest ,
332
- domValues: DOMValues ?
333
- ) async throws -> ( String , HTTPURLResponse ) {
334
-
335
- var request = request
336
- request. url = request. url!. appendingLiveViewItems ( )
337
- request. allHTTPHeaderFields = configuration. headers
338
-
339
- if let domValues {
340
- request. setValue ( domValues. phxCSRFToken, forHTTPHeaderField: " x-csrf-token " )
341
- }
342
-
343
- let data : Data
344
- let response : URLResponse
345
- do {
346
- ( data, response) = try await urlSession. data ( for: request)
347
- } catch {
348
- throw LiveConnectionError . initialFetchError ( error)
349
- }
350
-
351
- guard let response = response as? HTTPURLResponse ,
352
- response. statusCode == 200 ,
353
- let html = String ( data: data, encoding: . utf8)
354
- else {
355
- if let html = String ( data: data, encoding: . utf8)
356
- {
357
- if try extractLiveReloadFrame ( SwiftSoup . parse ( html) ) {
358
- await connectLiveReloadSocket ( urlSessionConfiguration: urlSession. configuration)
359
- }
360
- throw LiveConnectionError . initialFetchUnexpectedResponse ( response, html)
361
- } else {
362
- throw LiveConnectionError . initialFetchUnexpectedResponse ( response)
363
- }
364
- }
365
- return ( html, response)
366
- }
367
-
368
- struct DOMValues {
369
- let phxCSRFToken : String
370
- let phxSession : String
371
- let phxStatic : String
372
- let phxView : String
373
- let phxID : String
374
- let liveReloadEnabled : Bool
375
- }
376
-
377
- nonisolated private func extractLiveReloadFrame( _ doc: SwiftSoup . Document ) throws -> Bool {
378
- !( try doc. select ( " iframe[src= \" /phoenix/live_reload/frame \" ] " ) . isEmpty ( ) )
379
- }
380
-
381
- private func extractDOMValues( _ doc: SwiftSoup . Document ) throws -> DOMValues {
382
- let csrfToken = try doc. select ( " csrf-token " )
383
- guard !csrfToken. isEmpty ( ) else {
384
- throw LiveConnectionError . initialParseError ( missingOrInvalid: . csrfToken)
385
- }
386
-
387
- let mainDivRes = try doc. select ( " div[data-phx-main] " )
388
- guard !mainDivRes. isEmpty ( ) else {
389
- throw LiveConnectionError . initialParseError ( missingOrInvalid: . phxMain)
390
- }
391
- let mainDiv = mainDivRes [ 0 ]
392
- return . init(
393
- phxCSRFToken: try csrfToken [ 0 ] . attr ( " value " ) ,
394
- phxSession: try mainDiv. attr ( " data-phx-session " ) ,
395
- phxStatic: try mainDiv. attr ( " data-phx-static " ) ,
396
- phxView: try mainDiv. attr ( " data-phx-view " ) ,
397
- phxID: try mainDiv. attr ( " id " ) ,
398
- liveReloadEnabled: try extractLiveReloadFrame ( doc)
399
- )
400
- }
401
-
402
- @MainActor
403
- private func connectSocket( _ domValues: DOMValues ) async throws {
404
- var wsEndpoint = await URLComponents ( url: self . url, resolvingAgainstBaseURL: true ) !
405
- wsEndpoint. scheme = await self . url. scheme == " https " ? " wss " : " ws "
406
- wsEndpoint. path = " /live/websocket "
407
- let configuration = await self . urlSession. configuration
408
- let socket = Socket (
409
- endPoint: wsEndpoint. string!,
410
- transport: {
411
- URLSessionTransport ( url: $0, configuration: configuration)
412
- } ,
413
- paramsClosure: {
414
- [
415
- " _csrf_token " : domValues. phxCSRFToken,
416
- " _format " : " swiftui "
417
- ]
418
- }
419
- )
420
-
421
- socket. onClose { @Sendable in logger. debug ( " [Socket] Closed " ) }
422
- socket. logger = { @Sendable message in logger. debug ( " [Socket] \( message) " ) }
423
-
424
- try await withCheckedThrowingContinuation { [ weak self] ( continuation: CheckedContinuation < Void , any Error > ) in
425
- guard let self else {
426
- return continuation. resume ( throwing: LiveConnectionError . sessionCoordinatorReleased)
427
- }
428
-
429
- // set to `reconnecting` when the socket asks for the delay duration.
430
- socket. reconnectAfter = { @Sendable [ weak self] tries in
431
- Task { @MainActor [ weak self] in
432
- self ? . state = . reconnecting
433
- }
434
- return Defaults . reconnectSteppedBackOff ( tries)
435
- }
436
- socket. onOpen { [ weak self] in
437
- Task { @MainActor [ weak self] in
438
- guard case . reconnecting = await self ? . state else { return }
439
- self ? . state = . connected
440
- }
441
- }
442
-
443
- var refs = [ String] ( )
444
-
445
- refs. append ( socket. onOpen { [ weak self, weak socket] in
446
- guard let socket else { return }
447
- guard self != nil else {
448
- socket. disconnect ( )
449
- return
450
- }
451
- logger. debug ( " [Socket] Opened " )
452
- socket. off ( refs)
453
- continuation. resume ( )
454
- } )
455
- refs. append ( socket. onError { [ weak self, weak socket, refs] ( error, response) in
456
- guard let socket else { return }
457
- guard self != nil else {
458
- socket. disconnect ( )
459
- return
460
- }
461
- logger. error ( " [Socket] Error: \( String ( describing: error) ) " )
462
- socket. off ( refs)
463
- } )
464
- }
465
- self . socket? . onClose { logger. debug ( " [Socket] Closed " ) }
466
- self . socket? . logger = { message in logger. debug ( " [Socket] \( message) " ) }
467
-
468
- self . state = . connected
469
-
470
- if domValues. liveReloadEnabled {
471
- await self . connectLiveReloadSocket ( urlSessionConfiguration: urlSession. configuration)
472
- }
473
- }
474
-
475
- private nonisolated func connectLiveReloadSocket( urlSessionConfiguration: URLSessionConfiguration ) async {
476
- await MainActor . run {
477
- if let liveReloadSocket = self . liveReloadSocket {
478
- liveReloadSocket. disconnect ( )
479
- self . liveReloadSocket = nil
480
- }
481
-
482
- var liveReloadEndpoint = URLComponents ( url: self . url, resolvingAgainstBaseURL: true ) !
483
- liveReloadEndpoint. scheme = self . url. scheme == " https " ? " wss " : " ws "
484
- liveReloadEndpoint. path = " /phoenix/live_reload/socket "
485
- self . liveReloadSocket = Socket ( endPoint: liveReloadEndpoint. string!, transport: {
486
- URLSessionTransport ( url: $0, configuration: urlSessionConfiguration)
487
- } )
488
- liveReloadSocket!. connect ( )
489
- self . liveReloadChannel = liveReloadSocket!. channel ( " phoenix:live_reload " )
490
- self . liveReloadChannel!. join ( ) . receive ( " ok " ) { msg in
491
- logger. debug ( " [LiveReload] connected to channel " )
492
- } . receive ( " error " ) { msg in
493
- logger. debug ( " [LiveReload] error connecting to channel: \( msg. payload) " )
494
- }
495
- self . liveReloadChannel!. on ( " assets_change " ) { [ weak self] _ in
496
- logger. debug ( " [LiveReload] assets changed, reloading " )
497
- Task {
498
- await StylesheetCache . shared. removeAll ( )
499
- // need to fully reconnect (rather than just re-join channel) because the elixir code reloader only triggers on http reqs
500
- await self ? . reconnect ( )
501
- }
502
- }
503
- }
504
342
505
343
func redirect(
506
344
_ redirect: LiveRedirect ,
@@ -520,8 +358,16 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
520
358
self . url = redirect. to
521
359
}
522
360
coordinator. document = navigationPath. last!. coordinator. document
523
- // await navigationPath.last?. coordinator.disconnect()
361
+ try await coordinator. disconnect ( )
524
362
navigationPath [ navigationPath. count - 1 ] = entry
363
+ let liveChannel = try await self . liveSocket!. joinLiveviewChannel (
364
+ . some( [
365
+ " _format " : . str( string: LiveSessionParameters . platform) ,
366
+ " _interface " : . object( object: LiveSessionParameters . platformParams)
367
+ ] ) ,
368
+ entry. url. absoluteString
369
+ )
370
+ try await coordinator. join ( liveChannel)
525
371
// try await coordinator.connect(domValues: self.domValues, redirect: true)
526
372
}
527
373
}
0 commit comments