Skip to content

Commit

Permalink
Feature/add websocket client (#76)
Browse files Browse the repository at this point in the history
* feat: add WebSocketClient

Resolves: none.

* Update ConnectingToWebSocket.md
  • Loading branch information
loay-ashraf authored May 17, 2024
1 parent e1512dc commit 3fdaeba
Show file tree
Hide file tree
Showing 16 changed files with 130 additions and 59 deletions.
36 changes: 17 additions & 19 deletions Docs.docc/Articles/ConnectingToWebSocket.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Connecting To WebSocket Server

Connect to a web socket server with **RxNetworkKit**
Connect to a WebSocket server with **RxNetworkKit**

## Overview

In this article we will walk you through on how to connect to a web socket server and how to send/receive messages.
In this article we will walk you through on how to connect to a WebSocket server and how to send/receive messages.

### Creating a http client

In this section, you will create a ``HTTPClient`` using a ``Session``.
In this section, you will create a ``WebSocketClient`` using a ``Session``.

- First, go to *ViewController.swift* file.

- Second, create a ``HTTPClient`` using a ``Session`` in the `viewDidLoad` method as done below:
- Second, create a ``WebSocketClient`` using a ``Session`` in the `viewDidLoad` method as done below:

```swift
import UIKit
Expand All @@ -28,20 +28,19 @@ class ViewController: UIViewController {
// Do any additional setup after loading the view.
let sessionConfiguration = SessionConfiguration.default
let session = Session(configuration: sessionConfiguration)
let requestInterceptor = RequestInterceptor()
let httpClient = HTTPClient(session: session, requestInterceptor: requestInterceptor)
let webSocketClient = WebSocketClient(session: session)
}

}
```

- Now, you are ready to connect to a web socket server.
- Now, you are ready to connect to a WebSocket server.

### Connecting to a websocket server
### Connecting to a WebSocket server

In this section, you will create a ``WebSocket`` and send/receive messages to/from the server.

- First, call the `HTTPClient.websocket` method and pass the server url, protocols and close handler as arguments.
- First, call the `WebSocketClient.webSocket` method and pass the server url, protocols and close handler as arguments.

- Second, Subscribe to the output `Observable`s and call `WebSocket.connect` method as done below:

Expand All @@ -60,13 +59,12 @@ class ViewController: UIViewController {
// Do any additional setup after loading the view.
let sessionConfiguration = SessionConfiguration.default
let session = Session(configuration: sessionConfiguration)
let requestInterceptor = RequestInterceptor()
let httpClient = HTTPClient(session: session, requestInterceptor: requestInterceptor)
let webSocketClient = WebSocketClient(session: session)
// Replace with your web socket server url
let webSocket: WebSocket<Model> = httpClient.webSocket(URL(string: "wss://example")!,
["ts1"],
.init(code: { _ in .normalClosure },
reason: { _ in nil }))
let webSocket: WebSocket<Model> = webSocketClient.webSocket(URL(string: "wss://example")!,
["ts1"],
.init(code: { _ in .normalClosure },
reason: { _ in nil }))
webSocket.text
.subscribe(onNext: { text in
// print incoming text message
Expand All @@ -93,14 +91,14 @@ class ViewController: UIViewController {

- Optionally, you can send messages to the server via the `WebSocket.send(_:)` method.

- That's it, you are connected to a web socket server and ready to send/receive messages.
- That's it, you are connected to a WebSocket server and ready to send/receive messages.

- Tip: You can disconnect from the web socket server at any time by calling the `WebSocket.disconnect` method.
- Tip: You can disconnect from the WebSocket server at any time by calling the `WebSocket.disconnect` method.

- Note: Connection to the web socket server is terminated when the ``WebSocket`` instance is deallocated.
- Note: Connection to the WebSocket server is terminated when the ``WebSocket`` instance is deallocated.

- Warning: If you intend to make updates to the UI, you must use the `observe(on: MainScheduler.instance)` operator to avoid updating the UI on a background thread (which may lead to unexpected behavior or crashes).

## Conclusion

Now, you can use **RxNetworkKit** to connect to a web socket server.
Now, you can use **RxNetworkKit** to connect to a WebSocket server.
2 changes: 1 addition & 1 deletion Docs.docc/Articles/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class ViewController: UIViewController {
// Do any additional setup after loading the view.
let sessionConfiguration = SessionConfiguration(urlSessionConfiguration: .default)
sessionConfiguration.setUserAgentHeader = false
sessionConfiguration.setUserAgentHeader = false
sessionConfiguration.logRequests = false
let session = Session(configuration: sessionConfiguration)
let requestInterceptor = RequestInterceptor()
let restClient = RESTClient(session: session, requestInterceptor: requestInterceptor)
Expand Down
9 changes: 0 additions & 9 deletions Docs.docc/Pages/HTTPClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,3 @@
- ``HTTPUploadRequestFormData``
- ``HTTPClient/upload(_:_:_:_:)-cavg``
- ``HTTPClient/upload(_:_:_:_:)-2m1kr``

### Connecting to a WebSocket

- ``WebSocket``
- ``WebSocketMessage``
- ``WebSocketCloseCode``
- ``WebSocketCloseHandler``
- ``WebSocketError``
- ``HTTPClient/webSocket(_:_:_:)``
1 change: 1 addition & 0 deletions Docs.docc/Pages/RxNetworkKit.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ RxNetworkKit is a generic reactive networking framework that leverages the stabi
- ``TLSTrustEvaluationPolicy``
- ``RESTClient``
- ``HTTPClient``
- ``WebSocketClient``

### HTTP

Expand Down
16 changes: 16 additions & 0 deletions Docs.docc/Pages/WebSocketClient.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# ``WebSocketClient``

## Topics

### Creating a WebSocket Client

- ``WebSocketClient/init(session:)``

### Connecting to a WebSocket

- ``WebSocket``
- ``WebSocketMessage``
- ``WebSocketCloseCode``
- ``WebSocketCloseHandler``
- ``WebSocketError``
- ``WebSocketClient/webSocket(_:_:_:)``
68 changes: 52 additions & 16 deletions RxNetworkKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
C61A7E322B62885F00407C38 /* URLSession+LogRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C61A7E312B62885F00407C38 /* URLSession+LogRequests.swift */; };
C61CB5642AF2CFDD006A203A /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = C61CB5632AF2CFDD006A203A /* Session.swift */; };
C623E7B42AD6262A00A20A0A /* CoreHTTP in Frameworks */ = {isa = PBXBuildFile; productRef = C623E7B32AD6262A00A20A0A /* CoreHTTP */; };
C6513B812BF76C7000A19EBC /* WebSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6513B802BF76C7000A19EBC /* WebSocketClient.swift */; };
C6554A2B2AD5BBB60090DD3A /* RESTClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6554A2A2AD5BBB60090DD3A /* RESTClient.swift */; };
C6554A2D2AD5C1560090DD3A /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6554A2C2AD5C1560090DD3A /* HTTPClient.swift */; };
C658CF652AD5F648009E561D /* RxNetworkKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C658CF642AD5F648009E561D /* RxNetworkKit.swift */; };
Expand Down Expand Up @@ -118,6 +119,7 @@
C61A7E2F2B627B3000407C38 /* SessionConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionConfiguration.swift; sourceTree = "<group>"; };
C61A7E312B62885F00407C38 /* URLSession+LogRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+LogRequests.swift"; sourceTree = "<group>"; };
C61CB5632AF2CFDD006A203A /* Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = "<group>"; };
C6513B802BF76C7000A19EBC /* WebSocketClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketClient.swift; sourceTree = "<group>"; };
C6554A2A2AD5BBB60090DD3A /* RESTClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTClient.swift; sourceTree = "<group>"; };
C6554A2C2AD5C1560090DD3A /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = "<group>"; };
C658CF642AD5F648009E561D /* RxNetworkKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxNetworkKit.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -186,6 +188,7 @@
C6C642FF2BE6C78E0071C2CC /* TLS */,
C6554A292AD5B5600090DD3A /* REST */,
0B77E06129D965D30077FBC0 /* HTTP */,
C6513B7B2BF76BDC00A19EBC /* WebSocket */,
0B77E06729D965D30077FBC0 /* Reachability */,
C6049B152A95307800E5727E /* RxNetworkKit.h */,
C658CF642AD5F648009E561D /* RxNetworkKit.swift */,
Expand Down Expand Up @@ -251,6 +254,54 @@
path = Session;
sourceTree = "<group>";
};
C6513B7B2BF76BDC00A19EBC /* WebSocket */ = {
isa = PBXGroup;
children = (
C6513B7C2BF76BF000A19EBC /* Extensions */,
C6513B7D2BF76BF700A19EBC /* Types */,
);
path = WebSocket;
sourceTree = "<group>";
};
C6513B7C2BF76BF000A19EBC /* Extensions */ = {
isa = PBXGroup;
children = (
C6BDFFEB2ACDF4100022F675 /* Reactive+WebSocketPing.swift */,
C6BDFFE72ACDF3830022F675 /* Reactive+WebSocketReceive.swift */,
C6BDFFE92ACDF3D90022F675 /* Reactive+WebSocketSend.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
C6513B7D2BF76BF700A19EBC /* Types */ = {
isa = PBXGroup;
children = (
C6513B7E2BF76C3A00A19EBC /* Client */,
C6513B7F2BF76C4800A19EBC /* Connection */,
);
path = Types;
sourceTree = "<group>";
};
C6513B7E2BF76C3A00A19EBC /* Client */ = {
isa = PBXGroup;
children = (
C6513B802BF76C7000A19EBC /* WebSocketClient.swift */,
);
path = Client;
sourceTree = "<group>";
};
C6513B7F2BF76C4800A19EBC /* Connection */ = {
isa = PBXGroup;
children = (
C6BDFFED2ACDF46A0022F675 /* WebSocket.swift */,
C6BDFFF42ACDF5250022F675 /* WebSocketCloseCode.swift */,
C6BDFFEF2ACDF4AB0022F675 /* WebSocketCloseHandler.swift */,
C6B4B4C32AD47A2F009073ED /* WebSocketError.swift */,
C6BDFFF22ACDF50F0022F675 /* WebSocketMessage.swift */,
);
path = Connection;
sourceTree = "<group>";
};
C6554A292AD5B5600090DD3A /* REST */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -319,9 +370,6 @@
0B77E05629D965D30077FBC0 /* Reactive+URLSessionAdaptedUploadResponse.swift */,
0B77E04D29D965D30077FBC0 /* Reactive+URLSessionDownloadResponse.swift */,
0B77E05829D965D30077FBC0 /* Reactive+URLSessionUploadResponse.swift */,
C6BDFFEB2ACDF4100022F675 /* Reactive+WebSocketPing.swift */,
C6BDFFE72ACDF3830022F675 /* Reactive+WebSocketReceive.swift */,
C6BDFFE92ACDF3D90022F675 /* Reactive+WebSocketSend.swift */,
C61A7E2B2B62798800407C38 /* URLRequest+CURLCommand.swift */,
0B77E04F29D965D30077FBC0 /* URLSession+DownloadTask.swift */,
C61A7E312B62885F00407C38 /* URLSession+LogRequests.swift */,
Expand Down Expand Up @@ -350,7 +398,6 @@
children = (
C6554A352AD5C6950090DD3A /* Client */,
C6554A322AD5C64D0090DD3A /* Request */,
C6BDFFE62ACDF3260022F675 /* Web Socket */,
);
path = Types;
sourceTree = "<group>";
Expand All @@ -371,18 +418,6 @@
path = Client;
sourceTree = "<group>";
};
C6BDFFE62ACDF3260022F675 /* Web Socket */ = {
isa = PBXGroup;
children = (
C6BDFFED2ACDF46A0022F675 /* WebSocket.swift */,
C6BDFFF42ACDF5250022F675 /* WebSocketCloseCode.swift */,
C6BDFFEF2ACDF4AB0022F675 /* WebSocketCloseHandler.swift */,
C6B4B4C32AD47A2F009073ED /* WebSocketError.swift */,
C6BDFFF22ACDF50F0022F675 /* WebSocketMessage.swift */,
);
path = "Web Socket";
sourceTree = "<group>";
};
C6C642FF2BE6C78E0071C2CC /* TLS */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -530,6 +565,7 @@
0B77E0B129D965D30077FBC0 /* Observable+Decodable.swift in Sources */,
C6C6430F2BE6C9A40071C2CC /* SecKey+Bundle.swift in Sources */,
C6C643152BE6CA830071C2CC /* Data+SecCertificate.swift in Sources */,
C6513B812BF76C7000A19EBC /* WebSocketClient.swift in Sources */,
0B77E08A29D965D30077FBC0 /* HTTPDownloadRequestEvent.swift in Sources */,
C6A9BEFD2A93FAF100459E32 /* URLSessionConfiguration+setUserAgentHTTPHeader.swift in Sources */,
C6554A2B2AD5BBB60090DD3A /* RESTClient.swift in Sources */,
Expand Down
14 changes: 0 additions & 14 deletions Source/HTTP/Types/Client/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,4 @@ public class HTTPClient {
return observable
}

/// Creates websocket object and establishes connection using provided url and protocols.
///
/// - Parameters:
/// - url: `URL` of websocket server.
/// - protocols: `[String]` websocket connection protocols.
/// - closeHandler: `WebSocketCloseHandler` used to provide close coda and reason upon connection closure.
///
/// - Returns: `WebSocket` object that represents the connection.
public func webSocket<T: Decodable>(_ url: URL, _ protocols: [String], _ closeHandler: WebSocketCloseHandler) -> WebSocket<T> {
let task = urlSession.webSocketTask(with: url, protocols: protocols)
let webSocket = WebSocket<T>(task: task, closeHandler: closeHandler)
return webSocket
}

}
43 changes: 43 additions & 0 deletions Source/WebSocket/Types/Client/WebSocketClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// WebSocketClient.swift
// RxNetworkKit
//
// Created by Loay Ashraf on 17/05/2024.
//

import Foundation

/// Entry point for connecting to WebSocket servers.
public class WebSocketClient {

/// Principal `Session` object that encapsulates `URLSession` instance.
private let session: Session
/// Principal `URLSession` object used to establish connections to WebSocket servers.
private var urlSession: URLSession {
session.urlSession
}

/// Creates a `WebSocketClient` instance.
///
/// - Parameters:
/// - session: `Session` object that encapsulates `URLSession` instance.
public init(session: Session) {
// Initialize manager's properties.
self.session = session
}

/// Creates a WebSocket object and establishes connection using provided url and protocols.
///
/// - Parameters:
/// - url: `URL` of websocket server.
/// - protocols: `[String]` websocket connection protocols.
/// - closeHandler: `WebSocketCloseHandler` used to provide close coda and reason upon connection closure.
///
/// - Returns: `WebSocket` object that represents the connection.
public func webSocket<T: Decodable>(_ url: URL, _ protocols: [String], _ closeHandler: WebSocketCloseHandler) -> WebSocket<T> {
let task = urlSession.webSocketTask(with: url, protocols: protocols)
let webSocket = WebSocket<T>(task: task, closeHandler: closeHandler)
return webSocket
}

}

0 comments on commit 3fdaeba

Please sign in to comment.