Skip to content

Commit 0cd62e1

Browse files
Merge pull request #289 from pusher/feature/native-websockets
Manage WebSocket connections natively
2 parents 98f0897 + bb38c0f commit 0cd62e1

23 files changed

+419
-1830
lines changed

.swiftlint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
excluded:
22
- Carthage
33
- Package.swift
4+
- Consumption-Tests/*/Carthage
5+
- Consumption-Tests/*/Pods
46

57
identifier_name:
68
excluded:

Cartfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
github "ashleymills/Reachability.swift" ~> 5.0.0
2-
github "daltoniam/Starscream" ~> 3.1.0
32
github "jedisct1/swift-sodium" == 0.8.0

Cartfile.resolved

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
github "ashleymills/Reachability.swift" "v5.0.0"
2-
github "daltoniam/Starscream" "3.1.1"
32
github "jedisct1/swift-sodium" "0.8.0"

PusherSwift.xcodeproj/project.pbxproj

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,16 @@
3030
33BB997C1D21230100B25C2A /* PusherTopLevelAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BB99661D21226C00B25C2A /* PusherTopLevelAPITests.swift */; };
3131
33C1FD6F1D81BFC300921AD7 /* ObjectiveC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33C1FD6E1D81BFC300921AD7 /* ObjectiveC.swift */; };
3232
33C40CB91C1DFC9C008A54E3 /* PusherSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = 33831CD61A9CFFF200B124F1 /* PusherSwift.h */; settings = {ATTRIBUTES = (Public, ); }; };
33+
53140355250A8F7600F3D7BF /* PusherChannelsProtocolCloseCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53140354250A8F7600F3D7BF /* PusherChannelsProtocolCloseCode.swift */; };
34+
53140356250A8F7600F3D7BF /* PusherChannelsProtocolCloseCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53140354250A8F7600F3D7BF /* PusherChannelsProtocolCloseCode.swift */; };
3335
539D9AFC2507F67300B5765A /* PusherLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539D9AFB2507F67300B5765A /* PusherLogger.swift */; };
3436
539D9AFE2507F68400B5765A /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539D9AFD2507F68400B5765A /* Constants.swift */; };
3537
539D9AFF2507F69400B5765A /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539D9AFD2507F68400B5765A /* Constants.swift */; };
3638
539D9B002507F69B00B5765A /* PusherLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539D9AFB2507F67300B5765A /* PusherLogger.swift */; };
39+
539D9B022509101E00B5765A /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539D9B012509101E00B5765A /* WebSocket.swift */; };
40+
539D9B032509101E00B5765A /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539D9B012509101E00B5765A /* WebSocket.swift */; };
41+
539D9B05250913B300B5765A /* WebSocketConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539D9B04250913B300B5765A /* WebSocketConnection.swift */; };
42+
539D9B06250913B300B5765A /* WebSocketConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539D9B04250913B300B5765A /* WebSocketConnection.swift */; };
3743
736E53F5242A378B0052CC1B /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736E53F3242A35D90052CC1B /* String+Extensions.swift */; };
3844
736E53F7242A45AC0052CC1B /* XCTest+Assertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736E53F6242A45AC0052CC1B /* XCTest+Assertions.swift */; };
3945
73D8A1E72435E5F3001FDE05 /* PusherDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BA541F1D9035BD00CD853B /* PusherDelegate.swift */; };
@@ -139,8 +145,11 @@
139145
33BB99661D21226C00B25C2A /* PusherTopLevelAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PusherTopLevelAPITests.swift; sourceTree = "<group>"; };
140146
33BB99671D21226C00B25C2A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Tests/Info.plist; sourceTree = "<group>"; };
141147
33C1FD6E1D81BFC300921AD7 /* ObjectiveC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveC.swift; sourceTree = "<group>"; };
148+
53140354250A8F7600F3D7BF /* PusherChannelsProtocolCloseCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PusherChannelsProtocolCloseCode.swift; sourceTree = "<group>"; };
142149
539D9AFB2507F67300B5765A /* PusherLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PusherLogger.swift; sourceTree = "<group>"; };
143150
539D9AFD2507F68400B5765A /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
151+
539D9B012509101E00B5765A /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; };
152+
539D9B04250913B300B5765A /* WebSocketConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketConnection.swift; sourceTree = "<group>"; };
144153
736E53F3242A35D90052CC1B /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
145154
736E53F6242A45AC0052CC1B /* XCTest+Assertions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+Assertions.swift"; sourceTree = "<group>"; };
146155
73D8A1FE2435E5F3001FDE05 /* PusherSwiftWithEncryption.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PusherSwiftWithEncryption.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -263,6 +272,7 @@
263272
3390D1E71F054D1E00E1944D /* Authorizer.swift */,
264273
3390D1E51F054D0400E1944D /* AuthRequestBuilderProtocol.swift */,
265274
33BA541F1D9035BD00CD853B /* PusherDelegate.swift */,
275+
539D9B04250913B300B5765A /* WebSocketConnection.swift */,
266276
);
267277
path = Protocols;
268278
sourceTree = "<group>";
@@ -273,11 +283,13 @@
273283
539D9AFD2507F68400B5765A /* Constants.swift */,
274284
3384C1B81CAECD9C00F10796 /* PusherChannel.swift */,
275285
3389F5711CAEDDF300563F49 /* PusherChannels.swift */,
286+
53140354250A8F7600F3D7BF /* PusherChannelsProtocolCloseCode.swift */,
276287
3389F5791CAEDEC800563F49 /* PusherClientOptions.swift */,
277288
E2498292231E612700CFBBD6 /* PusherError.swift */,
278289
E2B21F06243F5B860049A35B /* PusherEvent.swift */,
279290
3389F5751CAEDE2800563F49 /* PusherGlobalChannel.swift */,
280291
3389F56D1CAEDDD800563F49 /* PusherPresenceChannel.swift */,
292+
539D9B012509101E00B5765A /* WebSocket.swift */,
281293
);
282294
path = Models;
283295
sourceTree = "<group>";
@@ -634,7 +646,6 @@
634646
files = (
635647
);
636648
inputPaths = (
637-
Starscream,
638649
Reachability,
639650
);
640651
name = "Copy Carthage Frameworks";
@@ -722,7 +733,6 @@
722733
files = (
723734
);
724735
inputPaths = (
725-
Starscream,
726736
Reachability,
727737
Sodium,
728738
);
@@ -740,9 +750,11 @@
740750
isa = PBXSourcesBuildPhase;
741751
buildActionMask = 2147483647;
742752
files = (
753+
53140355250A8F7600F3D7BF /* PusherChannelsProtocolCloseCode.swift in Sources */,
743754
E26B8606244A079E00735172 /* PusherEncryptionHelpers.swift in Sources */,
744755
539D9AFE2507F68400B5765A /* Constants.swift in Sources */,
745756
E2B21F0A243F5BB50049A35B /* PusherConnection.swift in Sources */,
757+
539D9B05250913B300B5765A /* WebSocketConnection.swift in Sources */,
746758
33BA54201D9035BD00CD853B /* PusherDelegate.swift in Sources */,
747759
E2498293231E612700CFBBD6 /* PusherError.swift in Sources */,
748760
73D8A2282435F325001FDE05 /* PusherDecryptor.swift in Sources */,
@@ -753,6 +765,7 @@
753765
3389F5721CAEDDF300563F49 /* PusherChannels.swift in Sources */,
754766
E2CFE43122D79CA7004187C3 /* PusherParser.swift in Sources */,
755767
3389F5761CAEDE2800563F49 /* PusherGlobalChannel.swift in Sources */,
768+
539D9B022509101E00B5765A /* WebSocket.swift in Sources */,
756769
E2B21F01243F5B1E0049A35B /* PusherEventFactory.swift in Sources */,
757770
3389F57A1CAEDEC800563F49 /* PusherClientOptions.swift in Sources */,
758771
E2B21F07243F5B860049A35B /* PusherEvent.swift in Sources */,
@@ -794,9 +807,11 @@
794807
isa = PBXSourcesBuildPhase;
795808
buildActionMask = 2147483647;
796809
files = (
810+
53140356250A8F7600F3D7BF /* PusherChannelsProtocolCloseCode.swift in Sources */,
797811
E26B8607244A079E00735172 /* PusherEncryptionHelpers.swift in Sources */,
798812
E2B21F0B243F5BB50049A35B /* PusherConnection.swift in Sources */,
799813
73D8A1E72435E5F3001FDE05 /* PusherDelegate.swift in Sources */,
814+
539D9B06250913B300B5765A /* WebSocketConnection.swift in Sources */,
800815
73D8A1E82435E5F3001FDE05 /* PusherError.swift in Sources */,
801816
73D8A2292435F329001FDE05 /* PusherDecryptor.swift in Sources */,
802817
73D8A1EB2435E5F3001FDE05 /* PusherCrypto.swift in Sources */,
@@ -807,6 +822,7 @@
807822
73D8A1EE2435E5F3001FDE05 /* PusherChannels.swift in Sources */,
808823
73D8A1EF2435E5F3001FDE05 /* PusherParser.swift in Sources */,
809824
539D9AFF2507F69400B5765A /* Constants.swift in Sources */,
825+
539D9B032509101E00B5765A /* WebSocket.swift in Sources */,
810826
73D8A1F02435E5F3001FDE05 /* PusherGlobalChannel.swift in Sources */,
811827
E2B21F02243F5B1E0049A35B /* PusherEventFactory.swift in Sources */,
812828
73D8A1F12435E5F3001FDE05 /* PusherClientOptions.swift in Sources */,
@@ -892,13 +908,13 @@
892908
GCC_WARN_UNINITIALIZED_AUTOS = YES;
893909
GCC_WARN_UNUSED_FUNCTION = YES;
894910
GCC_WARN_UNUSED_VARIABLE = YES;
895-
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
896-
MACOSX_DEPLOYMENT_TARGET = 10.11;
911+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
912+
MACOSX_DEPLOYMENT_TARGET = 10.15;
897913
ONLY_ACTIVE_ARCH = YES;
898914
SDKROOT = "";
899915
SUPPORTED_PLATFORMS = "macosx appletvos iphoneos appletvsimulator iphonesimulator";
900916
SWIFT_VERSION = 5.0;
901-
TVOS_DEPLOYMENT_TARGET = 9.0;
917+
TVOS_DEPLOYMENT_TARGET = 13.0;
902918
};
903919
name = Debug;
904920
};
@@ -932,13 +948,13 @@
932948
GCC_WARN_UNINITIALIZED_AUTOS = YES;
933949
GCC_WARN_UNUSED_FUNCTION = YES;
934950
GCC_WARN_UNUSED_VARIABLE = YES;
935-
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
936-
MACOSX_DEPLOYMENT_TARGET = 10.11;
951+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
952+
MACOSX_DEPLOYMENT_TARGET = 10.15;
937953
SDKROOT = "";
938954
SUPPORTED_PLATFORMS = "macosx appletvos iphoneos appletvsimulator iphonesimulator";
939955
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
940956
SWIFT_VERSION = 5.0;
941-
TVOS_DEPLOYMENT_TARGET = 9.0;
957+
TVOS_DEPLOYMENT_TARGET = 13.0;
942958
};
943959
name = Release;
944960
};

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ For tutorials and more in-depth information about Pusher Channels, visit our [of
1919
- Can be used with Objective-C
2020

2121
### Deployment targets
22-
- iOS 8.0 and above
23-
- macOS (OS X) 10.11 and above
24-
- tvOS 9.0 and above
22+
- iOS 13.0 and above
23+
- macOS (OS X) 10.15 and above
24+
- tvOS 13.0 and above
2525
- Not currently compatible with watchOS
2626

27+
### Legacy OS support
28+
29+
If you need support for older versions of iOS, macOS or tvOS, please use the latest v8.x release of the SDK.
30+
2731
## I just want to copy and paste some code to get me started
2832

2933
What else would you want? Head over to one of our example apps:

Sources/Extensions/PusherWebsocketDelegate.swift

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
import Foundation
2-
import Starscream
2+
import Network
33

4-
extension PusherConnection: WebSocketDelegate {
4+
extension PusherConnection: WebSocketConnectionDelegate {
55

66
/**
77
Delegate method called when a message is received over a websocket
88

9-
- parameter ws: The websocket that has received the message
10-
- parameter text: The message received over the websocket
9+
- parameter connection: The websocket that has received the message
10+
- parameter string: The message received over the websocket
1111
*/
12-
public func websocketDidReceiveMessage(socket ws: WebSocketClient, text: String) {
13-
self.delegate?.debugLog?(message: PusherLogger.debug(for: .receivedMessage, context: text))
12+
func webSocketDidReceiveMessage(connection: WebSocketConnection, string: String) {
13+
self.delegate?.debugLog?(message: PusherLogger.debug(for: .receivedMessage, context: string))
1414

15-
guard let payload = PusherParser.getPusherEventJSON(from: text),
15+
guard let payload = PusherParser.getPusherEventJSON(from: string),
1616
let event = payload[Constants.JSONKeys.event] as? String
1717
else {
18-
self.delegate?.debugLog?(message: PusherLogger.debug(for: .unableToHandleIncomingMessage, context: text))
18+
self.delegate?.debugLog?(message: PusherLogger.debug(for: .unableToHandleIncomingMessage, context: string))
1919
return
2020
}
2121

2222
if event == Constants.Events.Pusher.error {
2323
guard let error = PusherError(jsonObject: payload) else {
24-
self.delegate?.debugLog?(message: PusherLogger.debug(for: .unableToHandleIncomingError, context: text))
24+
self.delegate?.debugLog?(message: PusherLogger.debug(for: .unableToHandleIncomingError,
25+
context: string))
2526
return
2627
}
2728
self.handleError(error: error)
@@ -30,13 +31,23 @@ extension PusherConnection: WebSocketDelegate {
3031
}
3132
}
3233

33-
/**
34-
Delegate method called when a websocket disconnected
34+
/// Delegate method called when a pong is received over a websocket
35+
/// - Parameter connection: The websocket that has received the pong
36+
func webSocketDidReceivePong(connection: WebSocketConnection) {
37+
self.delegate?.debugLog?(message: PusherLogger.debug(for: .pongReceived))
38+
resetActivityTimeoutTimer()
39+
}
3540

36-
- parameter ws: The websocket that disconnected
37-
- parameter error: The error, if one exists, when disconnected
38-
*/
39-
public func websocketDidDisconnect(socket ws: WebSocketClient, error: Error?) {
41+
/**
42+
Delegate method called when a websocket disconnected
43+
44+
- parameter connection: The websocket that disconnected
45+
- parameter closeCode: The closure code for the websocket connection.
46+
- parameter reason: Optional further information on the connection closure.
47+
*/
48+
func webSocketDidDisconnect(connection: WebSocketConnection,
49+
closeCode: NWProtocolWebSocket.CloseCode,
50+
reason: Data?) {
4051
// Handles setting channel subscriptions to unsubscribed wheter disconnection
4152
// is intentional or not
4253
if connectionState == .disconnecting || connectionState == .connected {
@@ -55,14 +66,9 @@ extension PusherConnection: WebSocketDelegate {
5566
return
5667
}
5768

58-
// Handle error (if any)
69+
// Log the disconnection
5970

60-
if let error = error {
61-
self.delegate?.debugLog?(message: PusherLogger.debug(for: .disconnectionWithError,
62-
context: "Error (code: \((error as NSError).code)): \(error.localizedDescription)"))
63-
} else {
64-
self.delegate?.debugLog?(message: PusherLogger.debug(for: .disconnectionWithoutError))
65-
}
71+
self.delegate?.debugLog?(message: PusherLogger.debug(for: .disconnectionWithoutError))
6672

6773
// Attempt reconnect if possible
6874

@@ -126,20 +132,18 @@ extension PusherConnection: WebSocketDelegate {
126132
/**
127133
Delegate method called when a websocket connected
128134

129-
- parameter ws: The websocket that connected
135+
- parameter connection: The websocket that connected
130136
*/
131-
public func websocketDidConnect(socket ws: WebSocketClient) {
137+
func webSocketDidConnect(connection: WebSocketConnection) {
132138
self.socketConnected = true
133139
}
134140

135-
public func websocketDidReceiveData(socket ws: WebSocketClient, data: Data) {}
136-
}
137-
138-
extension PusherConnection: WebSocketPongDelegate {
139-
140-
public func websocketDidReceivePong(socket: WebSocketClient, data: Data?) {
141-
self.delegate?.debugLog?(message: PusherLogger.debug(for: .pongReceived))
142-
resetActivityTimeoutTimer()
141+
func webSocketDidReceiveMessage(connection: WebSocketConnection, data: Data) {
142+
//
143143
}
144144

145+
func webSocketDidReceiveError(connection: WebSocketConnection, error: Error) {
146+
self.delegate?.debugLog?(message: PusherLogger.debug(for: .errorReceived,
147+
context: "Error (code: \((error as NSError).code)): \(error.localizedDescription)"))
148+
}
145149
}

Sources/Helpers/PusherLogger.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ internal class PusherLogger {
3232
"Connection state is 'connected' but received network reachability change so going to call attemptReconnect"
3333
case attemptReconnectionAfterWaiting = "Attempting to reconnect after waiting"
3434
case connectionEstablished = "Socket established with socket ID:"
35-
case disconnectionWithError = "Websocket is disconnected."
3635
case disconnectionWithoutError = "Websocket is disconnected but no error received"
36+
case errorReceived = "Websocket received error."
3737
case intentionalDisconnection = "Deliberate disconnection - skipping reconnect attempts"
3838
case maxReconnectAttemptsLimitReached = "Max reconnect attempts reached"
3939
case reconnectionFailureLikely = "Network unreachable so reconnect likely to fail"
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import Foundation
2+
3+
// MARK: - Pusher Channels Protocol close codes
4+
5+
/// Describes closure codes as specified by the Pusher Channels Protocol.
6+
///
7+
/// These closure codes fall in the 4000 - 4999 range, i.e. the `privateCode` case of `NWProtocolWebSocket.CloseCode`.
8+
///
9+
/// Reference: https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol#error-codes
10+
internal enum PusherChannelsProtocolCloseCode: UInt16 {
11+
12+
// MARK: - Pusher Channels Protocol reconnection strategies
13+
14+
/// Describes the reconnection strategy for a given `PusherChannelsProtocolCloseCode`.
15+
///
16+
/// Reference: https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol#connection-closure
17+
internal enum ReconnectionStrategy: UInt16 {
18+
19+
/// Indicates an error resulting in the connection being closed by Pusher Channels,
20+
/// and that attempting to reconnect using the same parameters will not succeed.
21+
case doNotReconnectUnchanged
22+
23+
/// Indicates an error resulting in the connection being closed by Pusher Channels,
24+
/// and that the client may reconnect after 1s or more.
25+
case reconnectAfterBackingOff
26+
27+
/// Indicates an error resulting in the connection being closed by Pusher Channels,
28+
/// and that the client may reconnect immediately.
29+
case reconnectImmediately
30+
31+
/// Indicates that the reconnection strategy is unknown due to the closure code being
32+
/// outside of the expected range as specified by the Pusher Channels Protocol.
33+
case unknown
34+
35+
// MARK: - Initialization
36+
37+
init(rawValue: UInt16) {
38+
switch rawValue {
39+
case 4000...4099:
40+
self = .doNotReconnectUnchanged
41+
case 4100...4199:
42+
self = .reconnectAfterBackingOff
43+
case 4200...4299:
44+
self = .reconnectImmediately
45+
default:
46+
self = .unknown
47+
}
48+
}
49+
}
50+
51+
// 4000 - 4099
52+
case applicationOnlyAcceptsSSLConnections = 4000
53+
case applicationDoesNotExist = 4001
54+
case applicationDisabled = 4003
55+
case applicationIsOverConnectionQuota = 4004
56+
case pathNotFound = 4005
57+
case invalidVersionStringFormat = 4006
58+
case unsupportedProtocolVersion = 4007
59+
case noProtocolVersionSupplied = 4008
60+
case connectionIsUnauthorized = 4009
61+
62+
// 4100 - 4199
63+
case overCapacity = 4100
64+
65+
// 4200 - 4299
66+
case genericReconnectImmediately = 4200
67+
68+
/// Ping was sent to the client, but no reply was received
69+
case pongReplyNotReceived = 4201
70+
71+
/// Client has been inactive for a long time (currently 24 hours)
72+
/// and client does not support ping.
73+
case closedAfterInactivity = 4202
74+
75+
// MARK: - Public properties
76+
77+
var reconnectionStrategy: ReconnectionStrategy {
78+
return ReconnectionStrategy(rawValue: self.rawValue)
79+
}
80+
}

0 commit comments

Comments
 (0)