Skip to content

Commit

Permalink
Merge pull request #339 from pusher/tech/refactor-private-api
Browse files Browse the repository at this point in the history
Refactoring the private API
  • Loading branch information
danielrbrowne authored Mar 1, 2021
2 parents bdb08d8 + f6eb619 commit 13fb1b6
Show file tree
Hide file tree
Showing 38 changed files with 425 additions and 412 deletions.
140 changes: 76 additions & 64 deletions PusherSwift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions Sources/Extensions/PusherChannel+EncryptionHelpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

extension PusherChannel {

/// Determines whether or not a message should be decrypted, based on channel and event attributes.
/// - Parameters:
/// - name: The name of the channel.
/// - eventName: The name of the event received on the channel.
/// - Returns: A `Bool` indicating whether the message should be decrypted or not.
static func decryptsMessage(name: String?, eventName: String) -> Bool {
return isEncrypted(name: name) && !isSystemEvent(eventName: eventName)
}

/// Determines if a channel is a private encrypted or not, based on its name.
/// - Parameter name: The name of the channel.
/// - Returns: A `Bool` indicating whether the channel is encrypted or not.
static func isEncrypted(name: String?) -> Bool {
return name?.starts(with: "\(Constants.ChannelTypes.privateEncrypted)-") ?? false
}

/// Determines if an event is a system event or not, based on its name.
/// - Parameter eventName: The name of the event.
/// - Returns: A `Bool` indicating whether the event is a system event or not.
private static func isSystemEvent(eventName: String) -> Bool {
return eventName.starts(with: "\(Constants.EventTypes.pusher):")
|| eventName.starts(with: "\(Constants.EventTypes.pusherInternal):")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ extension PusherConnection: WebSocketConnectionDelegate {
- parameter string: The message received over the websocket
*/
public func webSocketDidReceiveMessage(connection: WebSocketConnection, string: String) {
PusherLogger.shared.debug(for: .receivedMessage, context: string)
Logger.shared.debug(for: .receivedMessage, context: string)

guard let payload = PusherParser.getPusherEventJSON(from: string),
guard let payload = EventParser.getPusherEventJSON(from: string),
let event = payload[Constants.JSONKeys.event] as? String
else {
PusherLogger.shared.debug(for: .unableToHandleIncomingMessage,
context: string)
Logger.shared.debug(for: .unableToHandleIncomingMessage,
context: string)
return
}

if event == Constants.Events.Pusher.error {
guard let error = PusherError(jsonObject: payload) else {
PusherLogger.shared.debug(for: .unableToHandleIncomingError,
context: string)
Logger.shared.debug(for: .unableToHandleIncomingError,
context: string)
return
}
self.handleError(error: error)
Expand All @@ -36,7 +36,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
/// Delegate method called when a pong is received over a websocket
/// - Parameter connection: The websocket that has received the pong
public func webSocketDidReceivePong(connection: WebSocketConnection) {
PusherLogger.shared.debug(for: .pongReceived)
Logger.shared.debug(for: .pongReceived)
resetActivityTimeoutTimer()
}

Expand Down Expand Up @@ -64,7 +64,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
updateConnectionState(to: .disconnected)

guard !intentionalDisconnect else {
PusherLogger.shared.debug(for: .intentionalDisconnection)
Logger.shared.debug(for: .intentionalDisconnection)
return
}

Expand All @@ -82,7 +82,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
}

guard reconnectAttemptsMax == nil || reconnectAttempts < reconnectAttemptsMax! else {
PusherLogger.shared.debug(for: .maxReconnectAttemptsLimitReached)
Logger.shared.debug(for: .maxReconnectAttemptsLimitReached)
return
}

Expand All @@ -91,9 +91,9 @@ extension PusherConnection: WebSocketConnectionDelegate {

public func webSocketViabilityDidChange(connection: WebSocketConnection, isViable: Bool) {
if isViable {
PusherLogger.shared.debug(for: .networkConnectionViable)
Logger.shared.debug(for: .networkConnectionViable)
} else {
PusherLogger.shared.debug(for: .networkConnectionUnviable)
Logger.shared.debug(for: .networkConnectionUnviable)
}
}

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

case .failure(let error):
PusherLogger.shared.debug(for: .errorReceived,
context: """
Logger.shared.debug(for: .errorReceived,
context: """
Path migration error: \(error.debugDescription)
""")
}
Expand All @@ -128,9 +128,9 @@ extension PusherConnection: WebSocketConnectionDelegate {

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

Expand Down Expand Up @@ -161,7 +161,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
/// Returns a `TimeInterval` appropriate for a reconnection attempt after some delay.
/// - Parameter strategy: The reconnection strategy for the reconnection attempt.
/// - Returns: An appropriate `TimeInterval`. (0.0 if `strategy == .reconnectImmediately`).
func reconnectionAttemptTimeInterval(strategy: PusherChannelsProtocolCloseCode.ReconnectionStrategy) -> TimeInterval {
func reconnectionAttemptTimeInterval(strategy: ChannelsProtocolCloseCode.ReconnectionStrategy) -> TimeInterval {
if case .reconnectImmediately = strategy {
return 0.0
}
Expand All @@ -174,10 +174,10 @@ extension PusherConnection: WebSocketConnectionDelegate {

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

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

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

PusherLogger.shared.debug(for: loggingEvent,
context: context)
Logger.shared.debug(for: loggingEvent,
context: context)
}

/// Logs the websocket disconnection event.
Expand Down Expand Up @@ -218,8 +218,8 @@ extension PusherConnection: WebSocketConnectionDelegate {
closeMessage += " Reason: \(reasonString)."
}

PusherLogger.shared.debug(for: .disconnectionWithoutError,
context: closeMessage)
Logger.shared.debug(for: .disconnectionWithoutError,
context: closeMessage)
}

/**
Expand All @@ -236,8 +236,8 @@ extension PusherConnection: WebSocketConnectionDelegate {
}

public func webSocketDidReceiveError(connection: WebSocketConnection, error: NWError) {
PusherLogger.shared.debug(for: .errorReceived,
context: """
Logger.shared.debug(for: .errorReceived,
context: """
Error: \(error.debugDescription)
""")
}
Expand Down
22 changes: 22 additions & 0 deletions Sources/Extensions/URL+Pusher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation

extension URL {

/// Creates a valid URL string that can be used in a connection attempt.
/// - Parameters:
/// - key: The app key to be inserted into the URL.
/// - options: The collection of options needed to correctly construct the URL.
/// - Returns: The constructed URL string, ready to use in a connection attempt.
static func channelsSocketUrl(key: String, options: PusherClientOptions) -> String {
var url = ""
let additionalPathComponents = options.path ?? ""

if options.useTLS {
url = "wss://\(options.host):\(options.port)\(additionalPathComponents)/app/\(key)"
} else {
url = "ws://\(options.host):\(options.port)\(additionalPathComponents)/app/\(key)"
}

return "\(url)?client=\(CLIENT_NAME)&version=\(VERSION)&protocol=\(PROTOCOL)"
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,43 @@
import CryptoKit
import Foundation
import TweetNacl

class PusherDecryptor {
struct Crypto {

private struct EncryptedData: Decodable {
let nonce: String
let ciphertext: String
}

// MARK: - Public methods

/// Generates a SHA256 HMAC digest of the message using the secret.
/// - Parameters:
/// - secret: The secret key.
/// - message: The message.
/// - Returns: The hex-encoded MAC string.
static func generateSHA256HMAC(secret: String, message: String) -> String {
let key = SymmetricKey(data: Data(secret.utf8))
let signature = HMAC<SHA256>.authenticationCode(for: Data(message.utf8), using: key)

return signature
.map { String(format: "%02hhx", $0) }
.joined()
}

/// Decrypts some data `String` using a key, according to the NaCl secret box algorithm.
/// - Parameters:
/// - data: A JSON-encoded `String` of base64-encoded nonce and cypher text strings.
/// - decryptionKey: A base64-encoded decryption key `String`.
/// - Throws: An `EventError` if the decryption operation fails for some reason.
/// - Returns: The decrypted data `String`.
static func decrypt(data: String?, decryptionKey: String?) throws -> String? {
guard let data = data else {
return nil
}

guard let decryptionKey = decryptionKey else {
throw PusherEventError.invalidDecryptionKey
throw EventError.invalidDecryptionKey
}

let encryptedData = try self.encryptedData(fromData: data)
Expand All @@ -25,41 +48,43 @@ class PusherDecryptor {
guard let decryptedData = try? NaclSecretBox.open(box: cipherText,
nonce: nonce,
key: secretKey),
let decryptedString = String(bytes: decryptedData, encoding: .utf8) else {
throw PusherEventError.invalidDecryptionKey
let decryptedString = String(bytes: decryptedData, encoding: .utf8) else {
throw EventError.invalidDecryptionKey
}

return decryptedString
}

// MARK: - Private methods

private static func encryptedData(fromData data: String) throws -> EncryptedData {
guard let encodedData = data.data(using: .utf8),
let encryptedData = try? JSONDecoder().decode(EncryptedData.self, from: encodedData) else {
throw PusherEventError.invalidEncryptedData
let encryptedData = try? JSONDecoder().decode(EncryptedData.self, from: encodedData) else {
throw EventError.invalidEncryptedData
}

return encryptedData
}

private static func decodedCipherText(fromEncryptedData encryptedData: EncryptedData) throws -> Data {
guard let decodedCipherText = Data(base64Encoded: encryptedData.ciphertext) else {
throw PusherEventError.invalidEncryptedData
throw EventError.invalidEncryptedData
}

return decodedCipherText
}

private static func decodedNonce(fromEncryptedData encryptedData: EncryptedData) throws -> Data {
guard let decodedNonce = Data(base64Encoded: encryptedData.nonce) else {
throw PusherEventError.invalidEncryptedData
throw EventError.invalidEncryptedData
}

return decodedNonce
}

private static func decodedDecryptionKey(fromDecryptionKey decryptionKey: String) throws -> Data {
guard let decodedDecryptionKey = Data(base64Encoded: decryptionKey) else {
throw PusherEventError.invalidDecryptionKey
throw EventError.invalidDecryptionKey
}

return decodedDecryptionKey
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

struct PusherParser {
struct EventParser {

/**
Parse a string to extract Pusher event information from it
Expand All @@ -20,12 +20,12 @@ struct PusherParser {
options: []) as? [String: AnyObject] {
return jsonObject
} else {
PusherLogger.shared.debug(for: .unableToParseStringAsJSON,
context: string)
Logger.shared.debug(for: .unableToParseStringAsJSON,
context: string)
}
} catch let error as NSError {
PusherLogger.shared.error(for: .genericError,
context: error.localizedDescription)
Logger.shared.error(for: .genericError,
context: error.localizedDescription)
}
return nil
}
Expand All @@ -44,8 +44,8 @@ struct PusherParser {
if let jsonData = data, let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: []) {
return jsonObject
} else {
PusherLogger.shared.debug(for: .unableToParseStringAsJSON,
context: string)
Logger.shared.debug(for: .unableToParseStringAsJSON,
context: string)
}
}
return nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Used for logging events for informational purposes
class PusherLogger {
class Logger {

// MARK: - Enum definitions

Expand Down Expand Up @@ -68,7 +68,7 @@ class PusherLogger {
case error = "[PUSHER ERROR]"
}

static let shared = PusherLogger()
static let shared = Logger()

weak var delegate: PusherDelegate?

Expand Down
40 changes: 0 additions & 40 deletions Sources/Helpers/PusherCrypto.swift

This file was deleted.

Loading

0 comments on commit 13fb1b6

Please sign in to comment.