From a037987e6d0a7a0477a5f18b91600e56637073cf Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:07:20 +0000 Subject: [PATCH] feat(apple): realtime heartbeat --- .../Sources/Services/Realtime.swift.twig | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/templates/swift/Sources/Services/Realtime.swift.twig b/templates/swift/Sources/Services/Realtime.swift.twig index 7838fc9fa..baf6c931c 100644 --- a/templates/swift/Sources/Services/Realtime.swift.twig +++ b/templates/swift/Sources/Services/Realtime.swift.twig @@ -7,11 +7,14 @@ open class Realtime : Service { private let TYPE_ERROR = "error" private let TYPE_EVENT = "event" + private let TYPE_PONG = "pong" private let DEBOUNCE_NANOS = 1_000_000 + private let HEARTBEAT_INTERVAL: UInt64 = 20_000_000_000 // 20 seconds in nanoseconds private var socketClient: WebSocketClient? = nil private var activeChannels = Set() private var activeSubscriptions = [Int: RealtimeCallback]() + private var heartbeatTask: Task? = nil let connectSync = DispatchQueue(label: "ConnectSync") @@ -20,6 +23,33 @@ open class Realtime : Service { private var subscriptionsCounter = 0 private var reconnect = true + private func startHeartbeat() { + stopHeartbeat() + heartbeatTask = Task { + do { + while !Task.isCancelled { + if let client = socketClient, client.isConnected { + let pingMessage = ["type": "ping"] + if let jsonData = try JSONSerialization.data(withJSONObject: pingMessage), + let jsonString = String(data: jsonData, encoding: .utf8) { + client.send(text: jsonString) + } + } + try await Task.sleep(nanoseconds: HEARTBEAT_INTERVAL) + } + } catch { + if !Task.isCancelled { + print("Heartbeat task failed: \(error.localizedDescription)") + } + } + } + } + + private func stopHeartbeat() { + heartbeatTask?.cancel() + heartbeatTask = nil + } + private func createSocket() async throws { guard activeChannels.count > 0 else { reconnect = false @@ -50,6 +80,8 @@ open class Realtime : Service { } private func closeSocket() async throws { + stopHeartbeat() + guard let client = socketClient, let group = client.threadGroup else { return @@ -163,6 +195,7 @@ extension Realtime: WebSocketClientDelegate { public func onOpen(channel: Channel) { self.reconnectAttempts = 0 + startHeartbeat() } public func onMessage(text: String) { @@ -172,6 +205,7 @@ extension Realtime: WebSocketClientDelegate { switch type { case TYPE_ERROR: try! handleResponseError(from: json) case TYPE_EVENT: handleResponseEvent(from: json) + case TYPE_PONG: break // Handle pong response if needed default: break } } @@ -179,6 +213,8 @@ extension Realtime: WebSocketClientDelegate { } public func onClose(channel: Channel, data: Data) async throws { + stopHeartbeat() + if (!reconnect) { reconnect = true return @@ -196,6 +232,7 @@ extension Realtime: WebSocketClientDelegate { } public func onError(error: Swift.Error?, status: HTTPResponseStatus?) { + stopHeartbeat() print(error?.localizedDescription ?? "Unknown error") }