Skip to content

Commit

Permalink
add rdm + fix public api
Browse files Browse the repository at this point in the history
  • Loading branch information
nerzh committed Mar 17, 2020
1 parent 41dd829 commit 243d14b
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 19 deletions.
191 changes: 190 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,192 @@
# ActionCableSwift
[![SPM](https://img.shields.io/badge/swift-package%20manager-green)](https://swift.org/package-manager/)
[Action Cable Swift](https://github.com/nerzh/Action-Cable-Swift) is a WebSocket server being released with Rails 5 which makes it easy to add real-time features to your app. This Swift client inspired by "Swift-ActionCableClient", but it not support now and I created Action-Cable-Swift.

### Also web sockets client are now separate from the client.

## Installation

To install, simply:

#### Swift Package Manager

Add the following line to your `Package.swift`

```swift
// ...
.package(url: "https://github.com/nerzh/Action-Cable-Swift.git", from: "0.1.0")
// ...
dependencies: ["ActionCableSwift"]
// ...
```

and you can import ActionCableSwift

```swift
import ActionCableSwift
```
## Usage

### You will need to implement the `ACWebSocketProtocol` protocol.

### If you use "Starscream", you can take this code or to write your own web socket client:

```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
}
}
}

```


```swift
import ActionCableSwift

/// web socket client
let ws = WSS(stringURL: "ws://localhost:3334/cable")

/// action cable client
var client = ACClient(ws: ws)

/// pass headers to connect
client.headers = ["COOKIE": "Value"]

/// make channel
/// buffering - buffering messages if disconnect and flush after reconnect
var options = ACChannelOptions(buffering: true, autoSubscribe: true)
let channel = client.makeChannel(name: "RoomChannel", options: options)

channel.addOnSubscribe { (channel, optionalMessage) in
print(optionalMessage)
}
channel.addOnMessage { (channel, optionalMessage) in
print(optionalMessage)
}
channel.addOnPing { (channel, optionalMessage) in
print("ping")
}

/// Connect
client.connect()
```

### Manual Subscribe to a Channel with Params

```swift
client.addOnConnected { (h) in
/// without params
try? channel.subscribe()

/// with params
try? channel.subscribe(params: ["Key": "Value"])
}
```

### Channel Callbacks

```swift

func addOnMessage(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)

func addOnSubscribe(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)

func addOnUnsubscribe(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)

func addOnRejectSubscription(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)

func addOnPing(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
```

### Perform an Action on a Channel

```swift
// Send an action
channel.addOnSubscribe { (channel, optionalMessage) in
try? ch.sendMessage(actionName: "speak", params: ["test": 10101010101])
}
```

### Authorization & Headers

```swift
client.headers = [
"Authorization": "sometoken"
]
```

## Requirements

Any Web Socket Library, e.g. [Starscream](https://github.com/daltoniam/Starscream)

## Author

Me

## License

ActionCableSwift is available under the MIT license. See the LICENSE file for more info.

A description of this package.
26 changes: 20 additions & 6 deletions Sources/ActionCableSwift/ACChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ public class ACChannel {
weak var client: ACClient?
public var isSubscribed = false
public var bufferingIfDisconnected = false
public var subscriptionParams: [String: Any]

private let channelConcurrentQueue = DispatchQueue(label: "com.ACChannel.Conccurent", attributes: .concurrent)
private let channelSerialQueue = DispatchQueue(label: "com.ACChannel.SerialQueue")

/// callbacks
var onMessage: [ACResponseCallback] = []
private var onMessage: [ACResponseCallback] = []
private var onSubscribe: [ACResponseCallbackWithOptionalMessage] = []
private var onUnsubscribe: [ACResponseCallbackWithOptionalMessage] = []
private var onRejectSubscription: [ACResponseCallbackWithOptionalMessage] = []
private var onPing: [ACResponseCallbackWithOptionalMessage] = []
private var actionsBuffer: [ACAction] = []

public func addOnMessage(_ handler: @escaping ACResponseCallback) {
Expand All @@ -46,20 +48,29 @@ public class ACChannel {
onRejectSubscription.append(handler)
}

public func addAction(_ action: @escaping ACAction) {
public func addOnPing(_ handler: @escaping ACResponseCallbackWithOptionalMessage) {
onPing.append(handler)
}

private func addAction(_ action: @escaping ACAction) {
actionsBuffer.insert(action, at: 0)
}

public init(channelName: String, client: ACClient, options: ACChannelOptions? = nil) {
public init(channelName: String,
client: ACClient,
subscriptionParams: [String: Any] = [:],
options: ACChannelOptions? = nil
) {
self.channelName = channelName
self.subscriptionParams = subscriptionParams
self.client = client
self.options = options ?? ACChannelOptions()
setupAutoSubscribe()
setupOntextCallbacks()
}

public func subscribe() throws {
let data: Data = try ACSerializer.requestFrom(command: .subscribe, channelName: channelName)
public func subscribe(params: [String: Any] = [:]) throws {
let data: Data = try ACSerializer.requestFrom(command: .subscribe, channelName: channelName, identifier: params)
client?.send(data: data)
}

Expand Down Expand Up @@ -99,7 +110,7 @@ public class ACChannel {
if client?.isConnected ?? false { try? subscribe() }
client?.addOnConnected { [weak self] (headers) in
guard let self = self else { return }
try? self.subscribe()
try? self.subscribe(params: self.subscriptionParams)
}
}
}
Expand All @@ -112,6 +123,7 @@ public class ACChannel {
case .confirmSubscription:
self.isSubscribed = true
self.executeCallback(callbacks: self.onSubscribe, message: message)
self.flushBuffer()
case .rejectSubscription:
self.isSubscribed = false
self.executeCallback(callbacks: self.onRejectSubscription, message: message)
Expand All @@ -120,6 +132,8 @@ public class ACChannel {
self.executeCallback(callbacks: self.onUnsubscribe, message: message)
case .message:
self.executeCallback(callbacks: self.onMessage, message: message)
case .ping:
self.executeCallback(callbacks: self.onPing)
default: break
}
}
Expand Down
4 changes: 1 addition & 3 deletions Sources/ActionCableSwift/ACSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ public class ACSerializer {
if let identifier = dict["identifier"] as? String {
message.identifier = try? identifier.toDictionary()
}
if let mess = dict["message"] as? [String: Any] {
message.message = mess
}
message.message = dict["message"] as? [String: Any]
return message
}
}
Expand Down
19 changes: 10 additions & 9 deletions Sources/ActionCableSwift/ActionCableSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ public final class ACClient {

public var ws: ACWebSocketProtocol
public var isConnected: Bool = false
public let headers: [String: String]?
public let options: ACClientOptions
public var headers: [String: String]?
public var options: ACClientOptions

private var channels: [String: ACChannel] = [:]
private let clientConcurrentQueue = DispatchQueue(label: "com.ACClient.Conccurent", attributes: .concurrent)

Expand Down Expand Up @@ -92,7 +93,7 @@ public final class ACClient {
guard let self = self else { return }
self.isConnected = true
self.clientConcurrentQueue.async {
while let closure = self.onConnected.popLast() {
for closure in self.onConnected {
self.clientConcurrentQueue.async {
closure(headers)
}
Expand All @@ -103,7 +104,7 @@ public final class ACClient {
guard let self = self else { return }
self.isConnected = false
self.clientConcurrentQueue.async {
while let closure = self.onDisconnected.popLast() {
for closure in self.onDisconnected {
self.clientConcurrentQueue.async {
closure(reason)
}
Expand All @@ -114,7 +115,7 @@ public final class ACClient {
guard let self = self else { return }
self.isConnected = false
self.clientConcurrentQueue.async {
while let closure = self.onCancelled.popLast() {
for closure in self.onCancelled {
self.clientConcurrentQueue.async {
closure()
}
Expand All @@ -124,7 +125,7 @@ public final class ACClient {
ws.onText = { [weak self] text in
guard let self = self else { return }
self.clientConcurrentQueue.async {
while let closure = self.onText.popLast() {
for closure in self.onText {
self.clientConcurrentQueue.async {
closure(text)
}
Expand All @@ -134,7 +135,7 @@ public final class ACClient {
ws.onBinary = { [weak self] data in
guard let self = self else { return }
self.clientConcurrentQueue.async {
while let closure = self.onBinary.popLast() {
for closure in self.onBinary {
self.clientConcurrentQueue.async {
closure(data)
}
Expand All @@ -144,7 +145,7 @@ public final class ACClient {
ws.onPing = { [weak self] in
guard let self = self else { return }
self.clientConcurrentQueue.async {
while let closure = self.onPing.popLast() {
for closure in self.onPing {
self.clientConcurrentQueue.async {
closure()
}
Expand All @@ -154,7 +155,7 @@ public final class ACClient {
ws.onPong = { [weak self] in
guard let self = self else { return }
self.clientConcurrentQueue.async {
while let closure = self.onPong.popLast() {
for closure in self.onPong {
self.clientConcurrentQueue.async {
closure()
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/ActionCableSwift/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public struct ACChannelOptions {
public var buffering = false
public var autoSubscribe = false

public init() {}

public init(buffering: Bool, autoSubscribe: Bool) {
self.buffering = buffering
self.autoSubscribe = autoSubscribe
Expand Down

0 comments on commit 243d14b

Please sign in to comment.