Skip to content

Commit 243d14b

Browse files
committed
add rdm + fix public api
1 parent 41dd829 commit 243d14b

File tree

5 files changed

+223
-19
lines changed

5 files changed

+223
-19
lines changed

README.md

+190-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,192 @@
11
# ActionCableSwift
2+
[![SPM](https://img.shields.io/badge/swift-package%20manager-green)](https://swift.org/package-manager/)
3+
[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.
4+
5+
### Also web sockets client are now separate from the client.
6+
7+
## Installation
8+
9+
To install, simply:
10+
11+
#### Swift Package Manager
12+
13+
Add the following line to your `Package.swift`
14+
15+
```swift
16+
// ...
17+
.package(url: "https://github.com/nerzh/Action-Cable-Swift.git", from: "0.1.0")
18+
// ...
19+
dependencies: ["ActionCableSwift"]
20+
// ...
21+
```
22+
23+
and you can import ActionCableSwift
24+
25+
```swift
26+
import ActionCableSwift
27+
```
28+
## Usage
29+
30+
### You will need to implement the `ACWebSocketProtocol` protocol.
31+
32+
### If you use "Starscream", you can take this code or to write your own web socket client:
33+
34+
```swift
35+
import Foundation
36+
import Starscream
37+
38+
class WSS: ACWebSocketProtocol, WebSocketDelegate {
39+
40+
var url: URL
41+
var ws: WebSocket
42+
43+
init(stringURL: String) {
44+
url = URL(string: stringURL)!
45+
ws = WebSocket(request: URLRequest(url: url))
46+
ws.delegate = self
47+
}
48+
49+
var onConnected: ((_ headers: [String : String]?) -> Void)?
50+
var onDisconnected: ((_ reason: String?) -> Void)?
51+
var onCancelled: (() -> Void)?
52+
var onText: ((_ text: String) -> Void)?
53+
var onBinary: ((_ data: Data) -> Void)?
54+
var onPing: (() -> Void)?
55+
var onPong: (() -> Void)?
56+
57+
func connect(headers: [String : String]?) {
58+
ws.request.allHTTPHeaderFields = headers
59+
ws.connect()
60+
}
61+
62+
func disconnect() {
63+
ws.disconnect()
64+
}
65+
66+
func send(data: Data) {
67+
ws.write(data: data)
68+
}
69+
70+
func send(data: Data, _ completion: (() -> Void)?) {
71+
ws.write(data: data, completion: completion)
72+
}
73+
74+
func send(text: String) {
75+
ws.write(string: text)
76+
}
77+
78+
func send(text: String, _ completion: (() -> Void)?) {
79+
ws.write(string: text, completion: completion)
80+
}
81+
82+
func didReceive(event: WebSocketEvent, client: WebSocket) {
83+
switch event {
84+
case .connected(let headers):
85+
onConnected?(headers)
86+
case .disconnected(let reason, let code):
87+
onDisconnected?(reason)
88+
case .text(let string):
89+
onText?(string)
90+
case .binary(let data):
91+
onBinary?(data)
92+
case .ping(_):
93+
onPing?()
94+
case .pong(_):
95+
onPong?()
96+
case .cancelled:
97+
onCancelled?()
98+
default: break
99+
}
100+
}
101+
}
102+
103+
```
104+
105+
106+
```swift
107+
import ActionCableSwift
108+
109+
/// web socket client
110+
let ws = WSS(stringURL: "ws://localhost:3334/cable")
111+
112+
/// action cable client
113+
var client = ACClient(ws: ws)
114+
115+
/// pass headers to connect
116+
client.headers = ["COOKIE": "Value"]
117+
118+
/// make channel
119+
/// buffering - buffering messages if disconnect and flush after reconnect
120+
var options = ACChannelOptions(buffering: true, autoSubscribe: true)
121+
let channel = client.makeChannel(name: "RoomChannel", options: options)
122+
123+
channel.addOnSubscribe { (channel, optionalMessage) in
124+
print(optionalMessage)
125+
}
126+
channel.addOnMessage { (channel, optionalMessage) in
127+
print(optionalMessage)
128+
}
129+
channel.addOnPing { (channel, optionalMessage) in
130+
print("ping")
131+
}
132+
133+
/// Connect
134+
client.connect()
135+
```
136+
137+
### Manual Subscribe to a Channel with Params
138+
139+
```swift
140+
client.addOnConnected { (h) in
141+
/// without params
142+
try? channel.subscribe()
143+
144+
/// with params
145+
try? channel.subscribe(params: ["Key": "Value"])
146+
}
147+
```
148+
149+
### Channel Callbacks
150+
151+
```swift
152+
153+
func addOnMessage(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
154+
155+
func addOnSubscribe(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
156+
157+
func addOnUnsubscribe(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
158+
159+
func addOnRejectSubscription(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
160+
161+
func addOnPing(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
162+
```
163+
164+
### Perform an Action on a Channel
165+
166+
```swift
167+
// Send an action
168+
channel.addOnSubscribe { (channel, optionalMessage) in
169+
try? ch.sendMessage(actionName: "speak", params: ["test": 10101010101])
170+
}
171+
```
172+
173+
### Authorization & Headers
174+
175+
```swift
176+
client.headers = [
177+
"Authorization": "sometoken"
178+
]
179+
```
180+
181+
## Requirements
182+
183+
Any Web Socket Library, e.g. [Starscream](https://github.com/daltoniam/Starscream)
184+
185+
## Author
186+
187+
Me
188+
189+
## License
190+
191+
ActionCableSwift is available under the MIT license. See the LICENSE file for more info.
2192

3-
A description of this package.

Sources/ActionCableSwift/ACChannel.swift

+20-6
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ public class ACChannel {
1919
weak var client: ACClient?
2020
public var isSubscribed = false
2121
public var bufferingIfDisconnected = false
22+
public var subscriptionParams: [String: Any]
2223

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

2627
/// callbacks
27-
var onMessage: [ACResponseCallback] = []
28+
private var onMessage: [ACResponseCallback] = []
2829
private var onSubscribe: [ACResponseCallbackWithOptionalMessage] = []
2930
private var onUnsubscribe: [ACResponseCallbackWithOptionalMessage] = []
3031
private var onRejectSubscription: [ACResponseCallbackWithOptionalMessage] = []
32+
private var onPing: [ACResponseCallbackWithOptionalMessage] = []
3133
private var actionsBuffer: [ACAction] = []
3234

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

49-
public func addAction(_ action: @escaping ACAction) {
51+
public func addOnPing(_ handler: @escaping ACResponseCallbackWithOptionalMessage) {
52+
onPing.append(handler)
53+
}
54+
55+
private func addAction(_ action: @escaping ACAction) {
5056
actionsBuffer.insert(action, at: 0)
5157
}
5258

53-
public init(channelName: String, client: ACClient, options: ACChannelOptions? = nil) {
59+
public init(channelName: String,
60+
client: ACClient,
61+
subscriptionParams: [String: Any] = [:],
62+
options: ACChannelOptions? = nil
63+
) {
5464
self.channelName = channelName
65+
self.subscriptionParams = subscriptionParams
5566
self.client = client
5667
self.options = options ?? ACChannelOptions()
5768
setupAutoSubscribe()
5869
setupOntextCallbacks()
5970
}
6071

61-
public func subscribe() throws {
62-
let data: Data = try ACSerializer.requestFrom(command: .subscribe, channelName: channelName)
72+
public func subscribe(params: [String: Any] = [:]) throws {
73+
let data: Data = try ACSerializer.requestFrom(command: .subscribe, channelName: channelName, identifier: params)
6374
client?.send(data: data)
6475
}
6576

@@ -99,7 +110,7 @@ public class ACChannel {
99110
if client?.isConnected ?? false { try? subscribe() }
100111
client?.addOnConnected { [weak self] (headers) in
101112
guard let self = self else { return }
102-
try? self.subscribe()
113+
try? self.subscribe(params: self.subscriptionParams)
103114
}
104115
}
105116
}
@@ -112,6 +123,7 @@ public class ACChannel {
112123
case .confirmSubscription:
113124
self.isSubscribed = true
114125
self.executeCallback(callbacks: self.onSubscribe, message: message)
126+
self.flushBuffer()
115127
case .rejectSubscription:
116128
self.isSubscribed = false
117129
self.executeCallback(callbacks: self.onRejectSubscription, message: message)
@@ -120,6 +132,8 @@ public class ACChannel {
120132
self.executeCallback(callbacks: self.onUnsubscribe, message: message)
121133
case .message:
122134
self.executeCallback(callbacks: self.onMessage, message: message)
135+
case .ping:
136+
self.executeCallback(callbacks: self.onPing)
123137
default: break
124138
}
125139
}

Sources/ActionCableSwift/ACSerializer.swift

+1-3
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ public class ACSerializer {
5656
if let identifier = dict["identifier"] as? String {
5757
message.identifier = try? identifier.toDictionary()
5858
}
59-
if let mess = dict["message"] as? [String: Any] {
60-
message.message = mess
61-
}
59+
message.message = dict["message"] as? [String: Any]
6260
return message
6361
}
6462
}

Sources/ActionCableSwift/ActionCableSwift.swift

+10-9
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ public final class ACClient {
55

66
public var ws: ACWebSocketProtocol
77
public var isConnected: Bool = false
8-
public let headers: [String: String]?
9-
public let options: ACClientOptions
8+
public var headers: [String: String]?
9+
public var options: ACClientOptions
10+
1011
private var channels: [String: ACChannel] = [:]
1112
private let clientConcurrentQueue = DispatchQueue(label: "com.ACClient.Conccurent", attributes: .concurrent)
1213

@@ -92,7 +93,7 @@ public final class ACClient {
9293
guard let self = self else { return }
9394
self.isConnected = true
9495
self.clientConcurrentQueue.async {
95-
while let closure = self.onConnected.popLast() {
96+
for closure in self.onConnected {
9697
self.clientConcurrentQueue.async {
9798
closure(headers)
9899
}
@@ -103,7 +104,7 @@ public final class ACClient {
103104
guard let self = self else { return }
104105
self.isConnected = false
105106
self.clientConcurrentQueue.async {
106-
while let closure = self.onDisconnected.popLast() {
107+
for closure in self.onDisconnected {
107108
self.clientConcurrentQueue.async {
108109
closure(reason)
109110
}
@@ -114,7 +115,7 @@ public final class ACClient {
114115
guard let self = self else { return }
115116
self.isConnected = false
116117
self.clientConcurrentQueue.async {
117-
while let closure = self.onCancelled.popLast() {
118+
for closure in self.onCancelled {
118119
self.clientConcurrentQueue.async {
119120
closure()
120121
}
@@ -124,7 +125,7 @@ public final class ACClient {
124125
ws.onText = { [weak self] text in
125126
guard let self = self else { return }
126127
self.clientConcurrentQueue.async {
127-
while let closure = self.onText.popLast() {
128+
for closure in self.onText {
128129
self.clientConcurrentQueue.async {
129130
closure(text)
130131
}
@@ -134,7 +135,7 @@ public final class ACClient {
134135
ws.onBinary = { [weak self] data in
135136
guard let self = self else { return }
136137
self.clientConcurrentQueue.async {
137-
while let closure = self.onBinary.popLast() {
138+
for closure in self.onBinary {
138139
self.clientConcurrentQueue.async {
139140
closure(data)
140141
}
@@ -144,7 +145,7 @@ public final class ACClient {
144145
ws.onPing = { [weak self] in
145146
guard let self = self else { return }
146147
self.clientConcurrentQueue.async {
147-
while let closure = self.onPing.popLast() {
148+
for closure in self.onPing {
148149
self.clientConcurrentQueue.async {
149150
closure()
150151
}
@@ -154,7 +155,7 @@ public final class ACClient {
154155
ws.onPong = { [weak self] in
155156
guard let self = self else { return }
156157
self.clientConcurrentQueue.async {
157-
while let closure = self.onPong.popLast() {
158+
for closure in self.onPong {
158159
self.clientConcurrentQueue.async {
159160
closure()
160161
}

Sources/ActionCableSwift/Helpers.swift

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public struct ACChannelOptions {
3131
public var buffering = false
3232
public var autoSubscribe = false
3333

34+
public init() {}
35+
3436
public init(buffering: Bool, autoSubscribe: Bool) {
3537
self.buffering = buffering
3638
self.autoSubscribe = autoSubscribe

0 commit comments

Comments
 (0)