Skip to content

Commit

Permalink
Merge branch 'master' of github.com:nerzh/Action-Cable-Swift
Browse files Browse the repository at this point in the history
  • Loading branch information
nerzh committed Jan 18, 2024
2 parents 6aea8b3 + 1de9223 commit bba38b5
Showing 1 changed file with 18 additions and 293 deletions.
311 changes: 18 additions & 293 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ To install, simply:

#### Swift Package Manager

#### ⚠️ For iOS before 13 version, please use 0.4.0

Add the following line to your `Package.swift`

```swift
// ...
.package(name: "ActionCableSwift", url: "https://github.com/nerzh/Action-Cable-Swift.git", from: "0.3.2"),
.package(name: "ActionCableSwift", url: "https://github.com/nerzh/Action-Cable-Swift.git", from: "1.0.0"),
targets: [
.target(
name: "YourPackageName",
Expand All @@ -30,301 +32,14 @@ Add the following line to your `Package.swift`
// ...
```

#### Cocoa Pods

Add the following line to your `Podfile`

```ruby
pod 'ActionCableSwift'
```

and you can import ActionCableSwift

```swift
import ActionCableSwift
```
# Usage

---

## Your WebSocketService should to implement the `ACWebSocketProtocol` protocol.

---

### Use with [Websocket-kit](https://github.com/vapor/websocket-kit)

#### I highly recommend not using Starscream to implement a WebSocket, because they have a strange implementation that does not allow conveniently reconnecting to a remote server after disconnecting. There is also a cool and fast alternative from the [Swift Server Work Group (SSWG)](https://swift.org/server/), package named [Websocket-kit](https://github.com/vapor/websocket-kit).

[Websocket-kit](https://github.com/vapor/websocket-kit) is SPM(Swift Package Manager) client library built on [Swift-NIO](https://github.com/apple/swift-nio)

Package.swift
```swift
// ...
dependencies: [
.package(name: "ActionCableSwift", url: "https://github.com/nerzh/Action-Cable-Swift.git", from: "0.3.0"),
.package(name: "websocket-kit", url: "https://github.com/vapor/websocket-kit.git", .upToNextMinor(from: "2.0.0"))
],
targets: [
.target(
name: "YourPackageName",
dependencies: [
.product(name: "ActionCableSwift", package: "ActionCableSwift"),
.product(name: "WebSocketKit", package: "websocket-kit")
])
// ...
```


or inside xcode


<img width="733" alt="Снимок экрана 2020-08-28 в 14 05 21" src="https://user-images.githubusercontent.com/10519803/91554329-975aba00-e937-11ea-9a98-eaff35191197.png">


<details>
<summary>SPOILER: Recommended implementation WSS based on Websocket-kit(Swift-NIO)</summary>


This is propertyWrapper for threadsafe access to webSocket instance

```swift
import Foundation

@propertyWrapper
struct Atomic<Value> {

private var value: Value
private let lock = NSLock()

init(wrappedValue value: Value) {
self.value = value
}

var wrappedValue: Value {
get { return load() }
set { store(newValue: newValue) }
}

func load() -> Value {
lock.lock()
defer { lock.unlock() }
return value
}

mutating func store(newValue: Value) {
lock.lock()
defer { lock.unlock() }
value = newValue
}
}

```

This is implementation WSS

```swift
import NIO
import NIOHTTP1
import NIOWebSocket
import WebSocketKit

final class WSS: ACWebSocketProtocol {

var url: URL
private var eventLoopGroup: EventLoopGroup
@Atomic var ws: WebSocket?

init(stringURL: String, coreCount: Int = System.coreCount) {
url = URL(string: stringURL)!
eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: coreCount)
}

var onConnected: ((_ headers: [String : String]?) -> Void)?
var onDisconnected: ((_ reason: String?) -> Void)?
var onCancelled: (() -> Void)?
var onText: ((_ text: String) -> Void)?
var onBinary: ((_ data: Data) -> Void)?
var onPing: (() -> Void)?
var onPong: (() -> Void)?

func connect(headers: [String : String]?) {

var httpHeaders: HTTPHeaders = .init()
headers?.forEach({ (name, value) in
httpHeaders.add(name: name, value: value)
})
let promise: EventLoopPromise<Void> = eventLoopGroup.next().makePromise(of: Void.self)

WebSocket.connect(to: url.absoluteString,
headers: httpHeaders,
on: eventLoopGroup
) { ws in
self.ws = ws

ws.onPing { [weak self] (ws) in
self?.onPing?()
}

ws.onPong { [weak self] (ws) in
self?.onPong?()
}

ws.onClose.whenComplete { [weak self] (result) in
switch result {
case .success:
self?.onDisconnected?(nil)
self?.onCancelled?()
case let .failure(error):
self?.onDisconnected?(error.localizedDescription)
self?.onCancelled?()
}
}

ws.onText { (ws, text) in
self.onText?(text)
}

ws.onBinary { (ws, buffer) in
var data: Data = Data()
data.append(contentsOf: buffer.readableBytesView)
self.onBinary?(data)
}

}.cascade(to: promise)

promise.futureResult.whenSuccess { [weak self] (_) in
guard let self = self else { return }
self.onConnected?(nil)
}
}

func disconnect() {
ws?.close(promise: nil)
}

func send(data: Data) {
ws?.send([UInt8](data))
}

func send(data: Data, _ completion: (() -> Void)?) {
let promise: EventLoopPromise<Void>? = ws?.eventLoop.next().makePromise(of: Void.self)
ws?.send([UInt8](data), promise: promise)
promise?.futureResult.whenComplete { (_) in
completion?()
}
}

func send(text: String) {
ws?.send(text)
}

func send(text: String, _ completion: (() -> Void)?) {
let promise: EventLoopPromise<Void>? = ws?.eventLoop.next().makePromise(of: Void.self)
ws?.send(text, promise: promise)
promise?.futureResult.whenComplete { (_) in
completion?()
}
}
}
```
</details>

---

### Use with [Starscream](https://github.com/daltoniam/Starscream)

```ruby
pod 'Starscream', '~> 4.0.0'
```
<details>
<summary>SPOILER: If you still want to use "Starscream", then you can to copy this code for websocket client</summary>

```swift
import Foundation
import Starscream

class WSS: ACWebSocketProtocol, WebSocketDelegate {

var url: URL
var ws: WebSocket

init(stringURL: String) {
url = URL(string: stringURL)!
ws = WebSocket(request: URLRequest(url: url))
ws.delegate = self
}

var onConnected: ((_ headers: [String : String]?) -> Void)?
var onDisconnected: ((_ reason: String?) -> Void)?
var onCancelled: (() -> Void)?
var onText: ((_ text: String) -> Void)?
var onBinary: ((_ data: Data) -> Void)?
var onPing: (() -> Void)?
var onPong: (() -> Void)?

func connect(headers: [String : String]?) {
ws.request.allHTTPHeaderFields = headers
ws.connect()
}

func disconnect() {
ws.disconnect()
}

func send(data: Data) {
ws.write(data: data)
}

func send(data: Data, _ completion: (() -> Void)?) {
ws.write(data: data, completion: completion)
}

func send(text: String) {
ws.write(string: text)
}

func send(text: String, _ completion: (() -> Void)?) {
ws.write(string: text, completion: completion)
}

func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(let headers):
onConnected?(headers)
case .disconnected(let reason, let code):
onDisconnected?(reason)
case .text(let string):
onText?(string)
case .binary(let data):
onBinary?(data)
case .ping(_):
onPing?()
case .pong(_):
onPong?()
case .cancelled:
onCancelled?()
default: break
}
}
}

```
</details>

---

### Next step to use ActionCableSwift


```swift
import ActionCableSwift

/// web socket client
let ws: WSS = .init(stringURL: "ws://localhost:3001/cable")

/// action cable client
let clientOptions: ACClientOptions = .init(debug: false, reconnect: true)
let client: ACClient = .init(ws: ws, options: clientOptions)
let client: ACClient = .init(stringURL: "ws://localhost:3001/cable", options: clientOptions)
/// pass headers to connect
/// on server you can get this with env['HTTP_COOKIE']
client.headers = ["COOKIE": "Value"]
Expand Down Expand Up @@ -402,13 +117,23 @@ client.headers = [

---

## Requirements
# If you want to implement your own WebSocket Provider, you should to implement the `ACWebSocketProtocol` protocol and use another initializator for ACClient

Any Web Socket Library, e.g.
```swift
import ActionCableSwift

[Websocket-kit](https://github.com/vapor/websocket-kit)
/// web socket client
let ws: YourWSS = .init(stringURL: "ws://localhost:3001/cable")

/// action cable client
let clientOptions: ACClientOptions = .init(debug: false, reconnect: true)
let client: ACClient = .init(ws: ws, options: clientOptions)
```

[Starscream](https://github.com/daltoniam/Starscream)
---
## Requirements

[Websocket-kit](https://github.com/vapor/websocket-kit)

## Author

Expand Down

0 comments on commit bba38b5

Please sign in to comment.