Skip to content

Commit 16eabe4

Browse files
committed
General fixes and upgrades.
- General fixes so that it compiles under XCode 12.0.1 - Applied fix for unique channel identifiers from here: danielrhodes/Swift-ActionCableClient#56 - Use sorted keys in JSON serialization for consistent serialization of identifier field. - Use Websocket delegate callbacks instead of blocks. - Adapted to new version of Starscream 4.0.4.
1 parent dfbe39c commit 16eabe4

File tree

5 files changed

+83
-77
lines changed

5 files changed

+83
-77
lines changed

Package.swift

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,26 @@
44
import PackageDescription
55

66
let package = Package(
7-
name: "ActionCableClient2",
8-
products: [
9-
// Products define the executables and libraries a package produces, and make them visible to other packages.
10-
.library(
11-
name: "ActionCableClient2",
12-
targets: ["ActionCableClient2"]),
13-
],
14-
dependencies: [
15-
// Dependencies declare other packages that this package depends on.
16-
.package(url: "https://github.com/daltoniam/Starscream", from: "4.0.4"),
17-
],
18-
targets: [
19-
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
20-
// Targets can depend on other targets in this package, and on products in packages this package depends on.
21-
.target(
22-
name: "ActionCableClient2",
23-
dependencies: ["Starscream"]),
24-
]
7+
name: "ActionCableClient2",
8+
platforms: [
9+
// Require iOS 11 for JSON writing options.
10+
.iOS("11.0"),
11+
],
12+
products: [
13+
// Products define the executables and libraries a package produces, and make them visible to other packages.
14+
.library(
15+
name: "ActionCableClient2",
16+
targets: ["ActionCableClient2"]),
17+
],
18+
dependencies: [
19+
// Dependencies declare other packages that this package depends on.
20+
.package(url: "https://github.com/daltoniam/Starscream", from: "4.0.4"),
21+
],
22+
targets: [
23+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
24+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
25+
.target(
26+
name: "ActionCableClient2",
27+
dependencies: ["Starscream"]),
28+
]
2529
)

Sources/ActionCableClient2/ActionCableClient.swift

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ open class ActionCableClient {
5656
/// On Rejected
5757
///
5858
/// Called when the client has been rejected from connecting.
59+
//
60+
// I don't think this actually works yet. Can't find where this actually
61+
// gets called.
5962
open var onRejected: (() -> Void)?
6063
/// On Ping
6164
///
@@ -68,13 +71,13 @@ open class ActionCableClient {
6871
open var onChannelRejected: ((Channel) -> (Void))?
6972
open var onChannelReceive: ((Channel, Any?, Swift.Error?) -> Void)?
7073

71-
//MARK: Properties
74+
// MARK: Properties
7275
open var isConnected: Bool = false
7376
open var url: Foundation.URL? { return socket.request.url }
7477
open var headers : [String: String]? {
7578
get { return socket.request.allHTTPHeaderFields }
7679
set {
77-
for (field, value) in headers ?? [:] {
80+
for (field, value) in newValue ?? [:] {
7881
socket.request.setValue(value, forHTTPHeaderField: field)
7982
}
8083
}
@@ -95,6 +98,7 @@ open class ActionCableClient {
9598
let request = URLRequest(url: url)
9699
/// Setup Initialize Socket
97100
socket = WebSocket(request: request)
101+
socket.delegate = self
98102
}
99103

100104
public required init(url: URL, headers: [String: String]? = nil, origin : String? = nil) {
@@ -115,18 +119,17 @@ open class ActionCableClient {
115119
/// Connect with the server
116120
@discardableResult
117121
open func connect() -> ActionCableClient {
118-
DispatchQueue.main.async {
119-
if let callback = self.willConnect {
120-
callback()
121-
}
122-
123-
ActionCableConcurrentQueue.async {
124-
self.socket.connect()
125-
self.reconnectionState = nil
126-
}
122+
DispatchQueue.main.async {
123+
if let callback = self.willConnect {
124+
callback()
125+
}
126+
ActionCableConcurrentQueue.async {
127+
self.socket.connect()
128+
self.reconnectionState = nil
127129
}
130+
}
128131

129-
return self
132+
return self
130133
}
131134

132135
/// Disconnect from the server.
@@ -177,7 +180,7 @@ open class ActionCableClient {
177180
}
178181

179182
// Let's check if we are connected.
180-
//guard isConnected else { throw TransmitError.notConnected }
183+
guard isConnected else { throw TransmitError.notConnected }
181184

182185
socket.write(string: JSONString) {
183186
//FINISHED!
@@ -486,16 +489,10 @@ extension ActionCableClient {
486489

487490
extension ActionCableClient : CustomDebugStringConvertible {
488491
public var debugDescription : String {
489-
return "ActionCableClient(url: \"\(url)\" connected: \(isConnected) id: \(Unmanaged.passUnretained(self).toOpaque()))"
492+
return "ActionCableClient(url: \"\(String(describing: url))\" connected: \(isConnected) id: \(Unmanaged.passUnretained(self).toOpaque()))"
490493
}
491494
}
492495

493-
//extension ActionCableClient : CustomPlaygroundQuickLookable {
494-
// public var customPlaygroundQuickLook: PlaygroundQuickLook {
495-
// return PlaygroundQuickLook.url(url?.absoluteString)
496-
// }
497-
//}
498-
499496
extension ActionCableClient {
500497
func copyWithZone(_ zone: NSZone?) -> AnyObject! {
501498
assert(false, "This class doesn't implement NSCopying. ")
@@ -514,28 +511,32 @@ extension ActionCableClient: WebSocketDelegate {
514511
switch event {
515512
case .connected(let headers):
516513
isConnected = true
514+
didConnect()
517515
print("websocket is connected: \(headers)")
518516
case .disconnected(let reason, let code):
519517
isConnected = false
518+
didDisconnect(nil)
520519
print("websocket is disconnected: \(reason) with code: \(code)")
521520
case .text(let string):
522521
print("Received text: \(string)")
522+
onText(string)
523523
case .binary(let data):
524524
print("Received data: \(data.count)")
525+
onData(data)
525526
case .ping(_):
526527
break
527528
case .pong(_):
528-
break
529+
didPong()
529530
case .viabilityChanged(_):
530531
break
531532
case .reconnectSuggested(_):
532533
break
533534
case .cancelled:
534535
isConnected = false
535-
case .error(let error):
536-
isConnected = false
537-
// handleError(error)
538-
536+
break
537+
case .error(let _):
538+
// handleError(error)
539+
break
539540
}
540541
}
541542
}

Sources/ActionCableClient2/ActionCableClient2.swift

Lines changed: 0 additions & 3 deletions
This file was deleted.

Sources/ActionCableClient2/Channel.swift

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
import Foundation
2424

25-
public typealias ChannelIdentifier = ActionPayload
25+
public typealias ChannelIdentifier = [String: String]
2626
public typealias OnReceiveClosure = ((Any?, Swift.Error?) -> (Void))
2727

2828
/// A particular channel on the server.
@@ -32,7 +32,7 @@ open class Channel: Hashable, Equatable {
3232
open var name : String
3333

3434
/// Identifier
35-
open var identifier: Dictionary<String, Any>?
35+
open var identifier: ChannelIdentifier?
3636

3737
/// Auto-Subscribe to channel on initialization and re-connect?
3838
open var autoSubscribe : Bool
@@ -51,12 +51,16 @@ open class Channel: Hashable, Equatable {
5151
get {
5252
//defaults to channel name
5353
var channelUID = name
54-
54+
5555
//if identifier isn't empty, fetch the first value as the channel unique identifier
56-
if let dictionary = identifier?.first {
57-
channelUID = dictionary.value as! String
56+
if let dictionary = identifier, dictionary.count > 1 {
57+
var identifier = name
58+
for key in dictionary.keys.sorted() {
59+
identifier += "-\(key):\(dictionary[key] as? String ?? "")"
60+
}
61+
channelUID = identifier
5862
}
59-
63+
6064
return channelUID
6165
}
6266
}
@@ -193,7 +197,7 @@ open class Channel: Hashable, Equatable {
193197
internal var onReceiveActionHooks: Dictionary<String, OnReceiveClosure> = Dictionary()
194198
internal unowned var client: ActionCableClient
195199
internal var actionBuffer: Array<Action> = Array()
196-
open let hashValue: Int = Int(arc4random_uniform(UInt32(Int32.max)))
200+
public let hashValue: Int = Int(arc4random_uniform(UInt32(Int32.max)))
197201
}
198202

199203
public func ==(lhs: Channel, rhs: Channel) -> Bool {
@@ -259,12 +263,3 @@ extension Channel: CustomDebugStringConvertible {
259263
}
260264
}
261265

262-
extension Channel: CustomPlaygroundQuickLookable {
263-
/// A custom playground quick look for this instance.
264-
///
265-
/// If this type has value semantics, the `PlaygroundQuickLook` instance
266-
/// should be unaffected by subsequent mutations.
267-
public var customPlaygroundQuickLook: PlaygroundQuickLook {
268-
return PlaygroundQuickLook.text(self.name)
269-
}
270-
}

Sources/ActionCableClient2/JSONSerializer.swift

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@ internal class JSONSerializer {
3737
}
3838

3939
identifierDict["channel"] = "\(channel.name)"
40-
41-
let JSONData = try JSONSerialization.data(withJSONObject: identifierDict, options: JSONSerialization.WritingOptions(rawValue: 0))
40+
41+
// Must use .sortedKeys to keep the identifier string constant
42+
// across requests.
43+
let JSONData = try JSONSerialization.data(withJSONObject: identifierDict, options: [.sortedKeys])
4244
guard let identifierString = NSString(data: JSONData, encoding: String.Encoding.utf8.rawValue)
4345
else { throw SerializationError.json }
44-
46+
4547
var commandDict = [
46-
"command" : command.string,
47-
"identifier" : identifierString
48+
"command" : command.string,
49+
"identifier" : identifierString
4850
] as [String : Any]
4951

5052
if let _ = data {
@@ -80,6 +82,7 @@ internal class JSONSerializer {
8082

8183
var channelName: String?
8284
var channelIdentifier: String?
85+
// idObj is a hash with channel and room_id keys
8386
if let idObj = JSONObj["identifier"] {
8487
var idJSON: Dictionary<String, AnyObject>
8588
if let idString = idObj as? String {
@@ -96,18 +99,24 @@ internal class JSONSerializer {
9699
} else {
97100
throw SerializationError.protocolViolation
98101
}
99-
100-
if let item = idJSON.first {
101-
channelIdentifier = item.value as? String
102-
}
103-
102+
104103
if let nameStr = idJSON["channel"], let name = nameStr as? String {
105-
channelName = name
106-
}
107-
108-
if channelIdentifier != nil {
109-
channelName = channelIdentifier
104+
channelName = name
105+
106+
if idJSON.count > 1 {
107+
var identifier = name
108+
for key in idJSON.keys.filter({ $0 != "channel" }).sorted() {
109+
identifier += "-\(key):\(idJSON[key] as? String ?? "")"
110+
}
111+
channelIdentifier = identifier
112+
}
110113
}
114+
// Why do we set the channel name to the identifier here?
115+
// As far as I know the chanell name is distinct from the identifier.
116+
// This breaks the client when uncommented.
117+
// if let channelIdentifier = channelIdentifier {
118+
// channelName = channelIdentifier
119+
// }
111120
}
112121

113122
switch messageType {

0 commit comments

Comments
 (0)