Skip to content

Commit ecf3968

Browse files
compiling, two connection leaks
1 parent 7f591ca commit ecf3968

File tree

4 files changed

+217
-289
lines changed

4 files changed

+217
-289
lines changed

Sources/LiveViewNative/Coordinators/LiveSessionCoordinator.swift

Lines changed: 90 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -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

616603
extension Socket: @unchecked Sendable {}
617604
extension Channel: @unchecked Sendable {}
605+
extension LiveViewClient: @unchecked Sendable {}
606+
extension LiveViewClientBuilder: @unchecked Sendable {}

0 commit comments

Comments
 (0)