@@ -25,6 +25,11 @@ public final class ObservableWebSocketClient: Identifiable, Equatable, Codable,
2525
2626 public let websocketURL : URL
2727
28+ private var pingTimer : Timer ?
29+ private let pingTimerInterval : TimeInterval ?
30+ private let pingMessage : String ?
31+ private let pingMessageWithGeneratedId : ( ( String ) -> String ) ?
32+
2833 private( set) var service : ObservableWebSocketService
2934
3035 var cancellables = Set < AnyCancellable > ( )
@@ -34,23 +39,77 @@ public final class ObservableWebSocketClient: Identifiable, Equatable, Codable,
3439 /// Creates an `ObservableWebSocketClient` instance.
3540 ///
3641 /// - Parameters:
42+ /// - id: Optional unique ID of the instance. If absent, an instance of `UUID` will be used instead.
3743 /// - websocketURL: The WebSocket `URL` to connect to, starting with `wss`.
38- /// E.g., `wss://endpoint.com`
39- public convenience init ( websocketURL: URL ) {
40- self . init ( websocketURL: websocketURL,
41- message: nil ,
42- error: nil )
43- }
44-
44+ /// For example:
45+ /// ```
46+ /// wss://endpoint.com
47+ /// ```
48+ /// - message: Optional `CodableWebSocketMessage`. Useful for mocking the instance's state.
49+ /// - error: Optional `ObservableWebSocketClientError`. Useful for mocking the instance's state.
50+ /// - pingTimerInterval: The value passed in (`TimeInterval`) will cause a timer to
51+ /// continuously send ping-type messages to the WS server, keeping the connection alive.
52+ /// - pingMessage: The ping-type `String` message.
53+ /// For example:
54+ /// ```
55+ /// "{\"id\": \"\(myId)\", \"type\": \"ping\"}"
56+ /// ```
57+ /// - pingMessageWithGeneratedId: The ping-type `String` message, including
58+ /// a dynamically generated ID. The closure (`(String) -> String`) takes a `String`
59+ /// (the generated ID) and returns a modified message string incorporating that ID.
60+ /// Notice that if `pingMessage` is also passed in, `pingMessage` will be used instead
61+ /// (causing `pingMessageWithGeneratedId` to be ignored). Usage Example:
62+ /// ```
63+ /// webSocketClient = .init(
64+ /// websocketURL: someWebSocketURL,
65+ /// pingTimerInterval: 18,
66+ /// pingMessageWithGeneratedId: { generatedId in
67+ /// "{\"id\": \"\(generatedId)\", \"type\": \"ping\"}"
68+ /// }
69+ /// )
70+ /// // The above will send the WS server (every 18 seconds)
71+ /// // a message like this:
72+ /// // {"id": "some-random-uuid", "type": "ping"}
73+ /// ```
4574 public init ( id: UUID = . init( ) ,
4675 websocketURL: URL ,
4776 message: CodableWebSocketMessage ? = nil ,
48- error: ObservableWebSocketClientError ? = nil ) {
77+ error: ObservableWebSocketClientError ? = nil ,
78+ pingTimerInterval: TimeInterval ? = nil ,
79+ pingMessage: String ? = nil ,
80+ pingMessageWithGeneratedId: ( ( String ) -> String ) ? = nil ) {
4981 self . id = id
5082 self . websocketURL = websocketURL
5183 self . codableMessage = message
5284 self . error = error
85+ self . pingTimerInterval = pingTimerInterval
86+ self . pingMessage = pingMessage
87+ self . pingMessageWithGeneratedId = pingMessageWithGeneratedId
5388 self . service = ObservableWebSocketService ( url: websocketURL)
89+
5490 observeWebSocketConnection ( )
91+ startPingTimer ( )
92+ }
93+
94+ deinit {
95+ pingTimer? . invalidate ( )
96+ }
97+ }
98+
99+ // MARK: - Private
100+
101+ private extension ObservableWebSocketClient {
102+ func startPingTimer( ) {
103+ if let pingTimerInterval,
104+ pingMessage? . isEmpty == false || pingMessageWithGeneratedId != nil {
105+ pingTimer = Timer . scheduledTimer (
106+ withTimeInterval: pingTimerInterval, repeats: true ) { [ weak self] _ in
107+ if let message = self ? . pingMessage {
108+ self ? . sendMessage ( message)
109+ } else if let messageWithGeneratedId = self ? . pingMessageWithGeneratedId {
110+ self ? . sendMessageWithGeneratedId ( messageWithGeneratedId)
111+ }
112+ }
113+ }
55114 }
56115}
0 commit comments