Skip to content

Commit 13fb1b6

Browse files
Merge pull request #339 from pusher/tech/refactor-private-api
Refactoring the private API
2 parents bdb08d8 + f6eb619 commit 13fb1b6

38 files changed

+425
-412
lines changed

PusherSwift.xcodeproj/project.pbxproj

Lines changed: 76 additions & 64 deletions
Large diffs are not rendered by default.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
3+
extension PusherChannel {
4+
5+
/// Determines whether or not a message should be decrypted, based on channel and event attributes.
6+
/// - Parameters:
7+
/// - name: The name of the channel.
8+
/// - eventName: The name of the event received on the channel.
9+
/// - Returns: A `Bool` indicating whether the message should be decrypted or not.
10+
static func decryptsMessage(name: String?, eventName: String) -> Bool {
11+
return isEncrypted(name: name) && !isSystemEvent(eventName: eventName)
12+
}
13+
14+
/// Determines if a channel is a private encrypted or not, based on its name.
15+
/// - Parameter name: The name of the channel.
16+
/// - Returns: A `Bool` indicating whether the channel is encrypted or not.
17+
static func isEncrypted(name: String?) -> Bool {
18+
return name?.starts(with: "\(Constants.ChannelTypes.privateEncrypted)-") ?? false
19+
}
20+
21+
/// Determines if an event is a system event or not, based on its name.
22+
/// - Parameter eventName: The name of the event.
23+
/// - Returns: A `Bool` indicating whether the event is a system event or not.
24+
private static func isSystemEvent(eventName: String) -> Bool {
25+
return eventName.starts(with: "\(Constants.EventTypes.pusher):")
26+
|| eventName.starts(with: "\(Constants.EventTypes.pusherInternal):")
27+
}
28+
}

Sources/Extensions/PusherWebsocketDelegate.swift renamed to Sources/Extensions/PusherConnection+WebsocketDelegate.swift

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ extension PusherConnection: WebSocketConnectionDelegate {
1111
- parameter string: The message received over the websocket
1212
*/
1313
public func webSocketDidReceiveMessage(connection: WebSocketConnection, string: String) {
14-
PusherLogger.shared.debug(for: .receivedMessage, context: string)
14+
Logger.shared.debug(for: .receivedMessage, context: string)
1515

16-
guard let payload = PusherParser.getPusherEventJSON(from: string),
16+
guard let payload = EventParser.getPusherEventJSON(from: string),
1717
let event = payload[Constants.JSONKeys.event] as? String
1818
else {
19-
PusherLogger.shared.debug(for: .unableToHandleIncomingMessage,
20-
context: string)
19+
Logger.shared.debug(for: .unableToHandleIncomingMessage,
20+
context: string)
2121
return
2222
}
2323

2424
if event == Constants.Events.Pusher.error {
2525
guard let error = PusherError(jsonObject: payload) else {
26-
PusherLogger.shared.debug(for: .unableToHandleIncomingError,
27-
context: string)
26+
Logger.shared.debug(for: .unableToHandleIncomingError,
27+
context: string)
2828
return
2929
}
3030
self.handleError(error: error)
@@ -36,7 +36,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
3636
/// Delegate method called when a pong is received over a websocket
3737
/// - Parameter connection: The websocket that has received the pong
3838
public func webSocketDidReceivePong(connection: WebSocketConnection) {
39-
PusherLogger.shared.debug(for: .pongReceived)
39+
Logger.shared.debug(for: .pongReceived)
4040
resetActivityTimeoutTimer()
4141
}
4242

@@ -64,7 +64,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
6464
updateConnectionState(to: .disconnected)
6565

6666
guard !intentionalDisconnect else {
67-
PusherLogger.shared.debug(for: .intentionalDisconnection)
67+
Logger.shared.debug(for: .intentionalDisconnection)
6868
return
6969
}
7070

@@ -82,7 +82,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
8282
}
8383

8484
guard reconnectAttemptsMax == nil || reconnectAttempts < reconnectAttemptsMax! else {
85-
PusherLogger.shared.debug(for: .maxReconnectAttemptsLimitReached)
85+
Logger.shared.debug(for: .maxReconnectAttemptsLimitReached)
8686
return
8787
}
8888

@@ -91,9 +91,9 @@ extension PusherConnection: WebSocketConnectionDelegate {
9191

9292
public func webSocketViabilityDidChange(connection: WebSocketConnection, isViable: Bool) {
9393
if isViable {
94-
PusherLogger.shared.debug(for: .networkConnectionViable)
94+
Logger.shared.debug(for: .networkConnectionViable)
9595
} else {
96-
PusherLogger.shared.debug(for: .networkConnectionUnviable)
96+
Logger.shared.debug(for: .networkConnectionUnviable)
9797
}
9898
}
9999

@@ -103,8 +103,8 @@ extension PusherConnection: WebSocketConnectionDelegate {
103103
updateConnectionState(to: .reconnecting)
104104

105105
case .failure(let error):
106-
PusherLogger.shared.debug(for: .errorReceived,
107-
context: """
106+
Logger.shared.debug(for: .errorReceived,
107+
context: """
108108
Path migration error: \(error.debugDescription)
109109
""")
110110
}
@@ -128,9 +128,9 @@ extension PusherConnection: WebSocketConnectionDelegate {
128128

129129
// Reconnect attempt according to Pusher Channels Protocol close code (if present).
130130
// (Otherwise, the default behavior is to attempt reconnection after backing off).
131-
var channelsCloseCode: PusherChannelsProtocolCloseCode?
131+
var channelsCloseCode: ChannelsProtocolCloseCode?
132132
if case let .privateCode(code) = closeCode {
133-
channelsCloseCode = PusherChannelsProtocolCloseCode(rawValue: code)
133+
channelsCloseCode = ChannelsProtocolCloseCode(rawValue: code)
134134
}
135135
let strategy = channelsCloseCode?.reconnectionStrategy ?? .reconnectAfterBackingOff
136136

@@ -161,7 +161,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
161161
/// Returns a `TimeInterval` appropriate for a reconnection attempt after some delay.
162162
/// - Parameter strategy: The reconnection strategy for the reconnection attempt.
163163
/// - Returns: An appropriate `TimeInterval`. (0.0 if `strategy == .reconnectImmediately`).
164-
func reconnectionAttemptTimeInterval(strategy: PusherChannelsProtocolCloseCode.ReconnectionStrategy) -> TimeInterval {
164+
func reconnectionAttemptTimeInterval(strategy: ChannelsProtocolCloseCode.ReconnectionStrategy) -> TimeInterval {
165165
if case .reconnectImmediately = strategy {
166166
return 0.0
167167
}
@@ -174,10 +174,10 @@ extension PusherConnection: WebSocketConnectionDelegate {
174174

175175
/// Logs the websocket reconnection attempt.
176176
/// - Parameter strategy: The reconnection strategy for the reconnection attempt.
177-
func logReconnectionAttempt(strategy: PusherChannelsProtocolCloseCode.ReconnectionStrategy) {
177+
func logReconnectionAttempt(strategy: ChannelsProtocolCloseCode.ReconnectionStrategy) {
178178

179179
var context = "(attempt \(reconnectAttempts + 1))"
180-
var loggingEvent = PusherLogger.LoggingEvent.attemptReconnectionImmediately
180+
var loggingEvent = Logger.LoggingEvent.attemptReconnectionImmediately
181181

182182
if reconnectAttemptsMax != nil {
183183
context.insert(contentsOf: " of \(reconnectAttemptsMax!)", at: context.index(before: context.endIndex))
@@ -189,8 +189,8 @@ extension PusherConnection: WebSocketConnectionDelegate {
189189
context = "\(timeInterval) seconds " + context
190190
}
191191

192-
PusherLogger.shared.debug(for: loggingEvent,
193-
context: context)
192+
Logger.shared.debug(for: loggingEvent,
193+
context: context)
194194
}
195195

196196
/// Logs the websocket disconnection event.
@@ -218,8 +218,8 @@ extension PusherConnection: WebSocketConnectionDelegate {
218218
closeMessage += " Reason: \(reasonString)."
219219
}
220220

221-
PusherLogger.shared.debug(for: .disconnectionWithoutError,
222-
context: closeMessage)
221+
Logger.shared.debug(for: .disconnectionWithoutError,
222+
context: closeMessage)
223223
}
224224

225225
/**
@@ -236,8 +236,8 @@ extension PusherConnection: WebSocketConnectionDelegate {
236236
}
237237

238238
public func webSocketDidReceiveError(connection: WebSocketConnection, error: NWError) {
239-
PusherLogger.shared.debug(for: .errorReceived,
240-
context: """
239+
Logger.shared.debug(for: .errorReceived,
240+
context: """
241241
Error: \(error.debugDescription)
242242
""")
243243
}

Sources/Extensions/URL+Pusher.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Foundation
2+
3+
extension URL {
4+
5+
/// Creates a valid URL string that can be used in a connection attempt.
6+
/// - Parameters:
7+
/// - key: The app key to be inserted into the URL.
8+
/// - options: The collection of options needed to correctly construct the URL.
9+
/// - Returns: The constructed URL string, ready to use in a connection attempt.
10+
static func channelsSocketUrl(key: String, options: PusherClientOptions) -> String {
11+
var url = ""
12+
let additionalPathComponents = options.path ?? ""
13+
14+
if options.useTLS {
15+
url = "wss://\(options.host):\(options.port)\(additionalPathComponents)/app/\(key)"
16+
} else {
17+
url = "ws://\(options.host):\(options.port)\(additionalPathComponents)/app/\(key)"
18+
}
19+
20+
return "\(url)?client=\(CLIENT_NAME)&version=\(VERSION)&protocol=\(PROTOCOL)"
21+
}
22+
}

Sources/Services/PusherDecryptor.swift renamed to Sources/Helpers/Crypto.swift

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,43 @@
1+
import CryptoKit
12
import Foundation
23
import TweetNacl
34

4-
class PusherDecryptor {
5+
struct Crypto {
56

67
private struct EncryptedData: Decodable {
78
let nonce: String
89
let ciphertext: String
910
}
1011

12+
// MARK: - Public methods
13+
14+
/// Generates a SHA256 HMAC digest of the message using the secret.
15+
/// - Parameters:
16+
/// - secret: The secret key.
17+
/// - message: The message.
18+
/// - Returns: The hex-encoded MAC string.
19+
static func generateSHA256HMAC(secret: String, message: String) -> String {
20+
let key = SymmetricKey(data: Data(secret.utf8))
21+
let signature = HMAC<SHA256>.authenticationCode(for: Data(message.utf8), using: key)
22+
23+
return signature
24+
.map { String(format: "%02hhx", $0) }
25+
.joined()
26+
}
27+
28+
/// Decrypts some data `String` using a key, according to the NaCl secret box algorithm.
29+
/// - Parameters:
30+
/// - data: A JSON-encoded `String` of base64-encoded nonce and cypher text strings.
31+
/// - decryptionKey: A base64-encoded decryption key `String`.
32+
/// - Throws: An `EventError` if the decryption operation fails for some reason.
33+
/// - Returns: The decrypted data `String`.
1134
static func decrypt(data: String?, decryptionKey: String?) throws -> String? {
1235
guard let data = data else {
1336
return nil
1437
}
1538

1639
guard let decryptionKey = decryptionKey else {
17-
throw PusherEventError.invalidDecryptionKey
40+
throw EventError.invalidDecryptionKey
1841
}
1942

2043
let encryptedData = try self.encryptedData(fromData: data)
@@ -25,41 +48,43 @@ class PusherDecryptor {
2548
guard let decryptedData = try? NaclSecretBox.open(box: cipherText,
2649
nonce: nonce,
2750
key: secretKey),
28-
let decryptedString = String(bytes: decryptedData, encoding: .utf8) else {
29-
throw PusherEventError.invalidDecryptionKey
51+
let decryptedString = String(bytes: decryptedData, encoding: .utf8) else {
52+
throw EventError.invalidDecryptionKey
3053
}
3154

3255
return decryptedString
3356
}
3457

58+
// MARK: - Private methods
59+
3560
private static func encryptedData(fromData data: String) throws -> EncryptedData {
3661
guard let encodedData = data.data(using: .utf8),
37-
let encryptedData = try? JSONDecoder().decode(EncryptedData.self, from: encodedData) else {
38-
throw PusherEventError.invalidEncryptedData
62+
let encryptedData = try? JSONDecoder().decode(EncryptedData.self, from: encodedData) else {
63+
throw EventError.invalidEncryptedData
3964
}
4065

4166
return encryptedData
4267
}
4368

4469
private static func decodedCipherText(fromEncryptedData encryptedData: EncryptedData) throws -> Data {
4570
guard let decodedCipherText = Data(base64Encoded: encryptedData.ciphertext) else {
46-
throw PusherEventError.invalidEncryptedData
71+
throw EventError.invalidEncryptedData
4772
}
4873

4974
return decodedCipherText
5075
}
5176

5277
private static func decodedNonce(fromEncryptedData encryptedData: EncryptedData) throws -> Data {
5378
guard let decodedNonce = Data(base64Encoded: encryptedData.nonce) else {
54-
throw PusherEventError.invalidEncryptedData
79+
throw EventError.invalidEncryptedData
5580
}
5681

5782
return decodedNonce
5883
}
5984

6085
private static func decodedDecryptionKey(fromDecryptionKey decryptionKey: String) throws -> Data {
6186
guard let decodedDecryptionKey = Data(base64Encoded: decryptionKey) else {
62-
throw PusherEventError.invalidDecryptionKey
87+
throw EventError.invalidDecryptionKey
6388
}
6489

6590
return decodedDecryptionKey

Sources/Helpers/PusherParser.swift renamed to Sources/Helpers/EventParser.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
22

3-
struct PusherParser {
3+
struct EventParser {
44

55
/**
66
Parse a string to extract Pusher event information from it
@@ -20,12 +20,12 @@ struct PusherParser {
2020
options: []) as? [String: AnyObject] {
2121
return jsonObject
2222
} else {
23-
PusherLogger.shared.debug(for: .unableToParseStringAsJSON,
24-
context: string)
23+
Logger.shared.debug(for: .unableToParseStringAsJSON,
24+
context: string)
2525
}
2626
} catch let error as NSError {
27-
PusherLogger.shared.error(for: .genericError,
28-
context: error.localizedDescription)
27+
Logger.shared.error(for: .genericError,
28+
context: error.localizedDescription)
2929
}
3030
return nil
3131
}
@@ -44,8 +44,8 @@ struct PusherParser {
4444
if let jsonData = data, let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: []) {
4545
return jsonObject
4646
} else {
47-
PusherLogger.shared.debug(for: .unableToParseStringAsJSON,
48-
context: string)
47+
Logger.shared.debug(for: .unableToParseStringAsJSON,
48+
context: string)
4949
}
5050
}
5151
return nil

Sources/Helpers/PusherLogger.swift renamed to Sources/Helpers/Logger.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Foundation
22

33
/// Used for logging events for informational purposes
4-
class PusherLogger {
4+
class Logger {
55

66
// MARK: - Enum definitions
77

@@ -68,7 +68,7 @@ class PusherLogger {
6868
case error = "[PUSHER ERROR]"
6969
}
7070

71-
static let shared = PusherLogger()
71+
static let shared = Logger()
7272

7373
weak var delegate: PusherDelegate?
7474

Sources/Helpers/PusherCrypto.swift

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

0 commit comments

Comments
 (0)