Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ let package = Package(
.library(name: "PusherSwift", targets: ["PusherSwift"])
],
dependencies: [
.package(url: "https://github.com/pusher/NWWebSocket.git", .upToNextMajor(from: "0.5.4")),
.package(url: "https://github.com/bitmark-inc/tweetnacl-swiftwrap", .upToNextMajor(from: "1.0.0")),
],
targets: [
.target(
name: "PusherSwift",
dependencies: [
"NWWebSocket",
"TweetNacl",
],
path: "Sources"
Expand Down
40 changes: 13 additions & 27 deletions Sources/Extensions/PusherConnection+WebsocketDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Foundation
import Network
import NWWebSocket

extension PusherConnection: WebSocketConnectionDelegate {

Expand Down Expand Up @@ -48,7 +47,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
- parameter reason: Optional further information on the connection closure.
*/
public func webSocketDidDisconnect(connection: WebSocketConnection,
closeCode: NWProtocolWebSocket.CloseCode,
closeCode: URLSessionWebSocketTask.CloseCode,
reason: Data?) {
resetConnection()

Expand All @@ -64,7 +63,8 @@ extension PusherConnection: WebSocketConnectionDelegate {
// Attempt reconnect if possible

// `autoReconnect` option is ignored if the closure code is within the 4000-4999 range
if case .privateCode = closeCode {} else {

if (4000...4999).contains(closeCode.rawValue) {} else {
guard self.options.autoReconnect else {
return
}
Expand All @@ -86,15 +86,15 @@ extension PusherConnection: WebSocketConnectionDelegate {
}
}

public func webSocketDidAttemptBetterPathMigration(result: Result<WebSocketConnection, NWError>) {
public func webSocketDidAttemptBetterPathMigration(result: Result<WebSocketConnection, Error>) {
switch result {
case .success:
updateConnectionState(to: .reconnecting)

case .failure(let error):
Logger.shared.debug(for: .errorReceived,
context: """
Path migration error: \(error.debugDescription)
Path migration error: \(error)
""")
}
}
Expand All @@ -106,7 +106,7 @@ extension PusherConnection: WebSocketConnectionDelegate {
`PusherChannelsProtocolCloseCode.ReconnectionStrategy`.
- Parameter closeCode: The closure code received by the WebSocket connection.
*/
func attemptReconnect(closeCode: NWProtocolWebSocket.CloseCode = .protocolCode(.normalClosure)) {
func attemptReconnect(closeCode: URLSessionWebSocketTask.CloseCode = .normalClosure) {
guard connectionState != .connected else {
return
}
Expand All @@ -118,8 +118,8 @@ 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: ChannelsProtocolCloseCode?
if case let .privateCode(code) = closeCode {
channelsCloseCode = ChannelsProtocolCloseCode(rawValue: code)
if (4000...4999).contains(closeCode.rawValue) {
channelsCloseCode = ChannelsProtocolCloseCode(rawValue: UInt16(closeCode.rawValue))
}
let strategy = channelsCloseCode?.reconnectionStrategy ?? .reconnectAfterBackingOff

Expand Down Expand Up @@ -186,22 +186,8 @@ extension PusherConnection: WebSocketConnectionDelegate {
/// - Parameters:
/// - closeCode: The closure code for the websocket connection.
/// - reason: Optional further information on the connection closure.
func logDisconnection(closeCode: NWProtocolWebSocket.CloseCode, reason: Data?) {
var rawCode: UInt16!
switch closeCode {
case .protocolCode(let definedCode):
rawCode = definedCode.rawValue

case .applicationCode(let applicationCode):
rawCode = applicationCode

case .privateCode(let protocolCode):
rawCode = protocolCode
@unknown default:
fatalError()
}

var closeMessage: String = "Close code: \(String(describing: rawCode))."
func logDisconnection(closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
var closeMessage: String = "Close code: \(String(describing: closeCode.rawValue))."
if let reason = reason,
let reasonString = String(data: reason, encoding: .utf8) {
closeMessage += " Reason: \(reasonString)."
Expand All @@ -224,15 +210,15 @@ extension PusherConnection: WebSocketConnectionDelegate {
//
}

public func webSocketDidReceiveError(connection: WebSocketConnection, error: NWError) {
public func webSocketDidReceiveError(connection: WebSocketConnection, error: Error) {
Logger.shared.debug(for: .errorReceived,
context: """
Error: \(error.debugDescription)
Error: \(error)
""")

// Resetting connection if we receive another POSIXError
// than ENOTCONN (57 - Socket is not connected)
if case .posix(let code) = error, code != .ENOTCONN {
if let urlError = error as? URLError, urlError.code.rawValue != POSIXError.ENOTCONN.rawValue {
resetConnection()

guard !intentionalDisconnect else {
Expand Down
93 changes: 93 additions & 0 deletions Sources/Protocols/WebSocketConnection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import Foundation

/// Defines a WebSocket connection.
public protocol WebSocketConnection {
/// Connect to the WebSocket.
func connect()

/// Send a UTF-8 formatted `String` over the WebSocket.
/// - Parameter string: The `String` that will be sent.
func send(string: String)

/// Send some `Data` over the WebSocket.
/// - Parameter data: The `Data` that will be sent.
func send(data: Data)

/// Start listening for messages over the WebSocket.
func listen()

/// Ping the WebSocket periodically.
/// - Parameter interval: The `TimeInterval` (in seconds) with which to ping the server.
func ping(interval: TimeInterval)

/// Ping the WebSocket once.
func ping()

/// Disconnect from the WebSocket.
/// - Parameter closeCode: The code to use when closing the WebSocket connection.
func disconnect(closeCode: URLSessionWebSocketTask.CloseCode)

/// The WebSocket connection delegate.
var delegate: WebSocketConnectionDelegate? { get set }
}

/// Defines a delegate for a WebSocket connection.
public protocol WebSocketConnectionDelegate: AnyObject {
/// Tells the delegate that the WebSocket did connect successfully.
/// - Parameter connection: The active `WebSocketConnection`.
func webSocketDidConnect(connection: WebSocketConnection)

/// Tells the delegate that the WebSocket did disconnect.
/// - Parameters:
/// - connection: The `WebSocketConnection` that disconnected.
/// - closeCode: A `URLSessionWebSocketTask.CloseCode` describing how the connection closed.
/// - reason: Optional extra information explaining the disconnection. (Formatted as UTF-8 encoded `Data`).
func webSocketDidDisconnect(connection: WebSocketConnection,
closeCode: URLSessionWebSocketTask.CloseCode,
reason: Data?)

/// Tells the delegate that the WebSocket connection viability has changed.
///
/// An example scenario of when this method would be called is a Wi-Fi connection being lost due to a device
/// moving out of signal range, and then the method would be called again once the device moved back in range.
/// - Parameters:
/// - connection: The `WebSocketConnection` whose viability has changed.
/// - isViable: A `Bool` indicating if the connection is viable or not.
func webSocketViabilityDidChange(connection: WebSocketConnection,
isViable: Bool)

/// Tells the delegate that the WebSocket has attempted a migration based on a better network path becoming available.
///
/// An example of when this method would be called is if a device is using a cellular connection, and a Wi-Fi connection
/// becomes available. This method will also be called if a device loses a Wi-Fi connection, and a cellular connection is available.
/// - Parameter result: A `Result` containing the `WebSocketConnection` if the migration was successful, or a
/// `NWError` if the migration failed for some reason.
func webSocketDidAttemptBetterPathMigration(result: Result<WebSocketConnection, Error>)

/// Tells the delegate that the WebSocket received an error.
///
/// An error received by a WebSocket is not necessarily fatal.
/// - Parameters:
/// - connection: The `WebSocketConnection` that received an error.
/// - error: The `Error` that was received.
func webSocketDidReceiveError(connection: WebSocketConnection,
error: Error)

/// Tells the delegate that the WebSocket received a 'pong' from the server.
/// - Parameter connection: The active `WebSocketConnection`.
func webSocketDidReceivePong(connection: WebSocketConnection)

/// Tells the delegate that the WebSocket received a `String` message.
/// - Parameters:
/// - connection: The active `WebSocketConnection`.
/// - string: The UTF-8 formatted `String` that was received.
func webSocketDidReceiveMessage(connection: WebSocketConnection,
string: String)

/// Tells the delegate that the WebSocket received a binary `Data` message.
/// - Parameters:
/// - connection: The active `WebSocketConnection`.
/// - data: The `Data` that was received.
func webSocketDidReceiveMessage(connection: WebSocketConnection,
data: Data)
}
9 changes: 5 additions & 4 deletions Sources/PusherSwift.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Foundation
import NWWebSocket

let PROTOCOL = 7
let VERSION = "10.1.5"
Expand Down Expand Up @@ -27,9 +26,11 @@ let CLIENT_NAME = "pusher-websocket-swift"
public init(key: String, options: PusherClientOptions = PusherClientOptions()) {
self.key = key
let urlString = URL.channelsSocketUrl(key: key, options: options)
let wsOptions = NWWebSocket.defaultOptions
wsOptions.setSubprotocols(["pusher-channels-protocol-\(PROTOCOL)"])
let ws = NWWebSocket(url: URL(string: urlString)!, options: wsOptions)
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = [
"Sec-WebSocket-Protocol": "pusher-channels-protocol-\(PROTOCOL)"
]
let ws = WebSocketClient(url: URL(string: urlString)!, options: config)
connection = PusherConnection(key: key, socket: ws, url: urlString, options: options)
connection.createGlobalChannel()
}
Expand Down
5 changes: 2 additions & 3 deletions Sources/Services/PusherConnection.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Foundation
import NWWebSocket

// swiftlint:disable file_length type_body_length

Expand All @@ -12,7 +11,7 @@ import NWWebSocket
open var socketId: String?
open var connectionState = ConnectionState.disconnected
open var channels = PusherChannels()
open var socket: NWWebSocket!
open var socket: WebSocketClient!
open var URLSession: Foundation.URLSession
open var userDataFetcher: (() -> PusherPresenceChannelMember)?
open var reconnectAttemptsMax: Int?
Expand Down Expand Up @@ -60,7 +59,7 @@ import NWWebSocket
*/
public init(
key: String,
socket: NWWebSocket,
socket: WebSocketClient,
url: String,
options: PusherClientOptions,
URLSession: Foundation.URLSession = Foundation.URLSession.shared
Expand Down
Loading