Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
76 changes: 70 additions & 6 deletions Source/SocketIO/Client/SocketIOClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,28 @@ open class SocketIOClient: NSObject, SocketIOClientSpec {

/// The id of this socket.io connect. This is different from the sid of the engine.io connection.
public private(set) var sid: String?
/// The id of this socket.io connect for connection state recovery.
public private(set) var pid: String?

/// Offset of last socket.io event for connection state recovery.
public private(set) var lastEventOffset: String?
/// Boolean setted after connection to know if socket state is recovered or not.
public private(set) var recovered: Bool = false

/// Array of events (or binary events) to handle when socket is connected and recover packets from server
public private(set) var savedEvents = [SocketPacket]()

let ackHandlers = SocketAckManager()
var connectPayload: [String: Any]?

private var _connectPayload: [String: Any]?
var connectPayload: [String: Any]? {
get {
getConnectionStateRecoveryPayload(with: _connectPayload)
}
set {
_connectPayload = newValue
}
}

private(set) var currentAck = -1

Expand Down Expand Up @@ -131,7 +150,6 @@ open class SocketIOClient: NSObject, SocketIOClientSpec {
}

status = .connecting

joinNamespace(withPayload: payload)

switch manager.version {
Expand Down Expand Up @@ -159,6 +177,16 @@ open class SocketIOClient: NSObject, SocketIOClientSpec {
}
}

func getConnectionStateRecoveryPayload(with payload: [String: Any]?) -> [String: Any]? {
guard let pid else { return payload }
var recoveryPayload = payload ?? [:]
recoveryPayload["pid"] = pid
if let lastEventOffset {
recoveryPayload["offset"] = lastEventOffset
}
return recoveryPayload
}

func createOnAck(_ items: [Any], binary: Bool = true) -> OnAckCallback {
currentAck += 1

Expand All @@ -171,12 +199,16 @@ open class SocketIOClient: NSObject, SocketIOClientSpec {
/// - parameter toNamespace: The namespace that was connected to.
open func didConnect(toNamespace namespace: String, payload: [String: Any]?) {
guard status != .connected else { return }
let pid = payload?["pid"] as? String
recovered = self.pid != nil && self.pid == pid
self.pid = pid
sid = payload?["sid"] as? String

DefaultSocketLogger.Logger.log("Socket connected", type: logType)

status = .connected
sid = payload?["sid"] as? String

handleSavedEventPackets()
handleClientEvent(.connect, data: payload == nil ? [namespace] : [namespace, payload!])
}

Expand Down Expand Up @@ -356,8 +388,10 @@ open class SocketIOClient: NSObject, SocketIOClientSpec {
/// - parameter data: The data that was sent with this event.
/// - parameter isInternalMessage: Whether this event was sent internally. If `true` it is always sent to handlers.
/// - parameter ack: If > 0 then this event expects to get an ack back from the client.
open func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) {
guard status == .connected || isInternalMessage else { return }
/// - returns: true if event is handled, false if event need to be saved (not handled)
@discardableResult
open func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) -> Bool {
guard status == .connected || isInternalMessage else { return false }

DefaultSocketLogger.Logger.log("Handling event: \(event) with data: \(data)", type: logType)

Expand All @@ -366,6 +400,12 @@ open class SocketIOClient: NSObject, SocketIOClientSpec {
for handler in handlers where handler.event == event {
handler.executeCallback(with: data, withAck: ack, withSocket: self)
}

if !isInternalMessage && ack < 0 && pid != nil,
let eventOffset = data.last as? String {
self.lastEventOffset = eventOffset
}
return true
}

/// Causes a client to handle a socket.io packet. The namespace for the packet must match the namespace of the
Expand All @@ -377,7 +417,9 @@ open class SocketIOClient: NSObject, SocketIOClientSpec {

switch packet.type {
case .event, .binaryEvent:
handleEvent(packet.event, data: packet.args, isInternalMessage: false, withAck: packet.id)
if !handleEvent(packet.event, data: packet.args, isInternalMessage: false, withAck: packet.id) {
saveEventPacketIfNeeded(packet: packet, isInternalMessage: false)
}
case .ack, .binaryAck:
handleAck(packet.id, data: packet.data)
case .connect:
Expand All @@ -389,6 +431,28 @@ open class SocketIOClient: NSObject, SocketIOClientSpec {
}
}

/// Called when we get an event from socket.io
/// Save it to event array if an event is sent before socket is set to connected status.
/// Do not save event if pid is nil (cannot recover events from server)
///
/// - parameter packet: The packet to handle.
/// - parameter isInternalMessage: Whether this event was sent internally. If `true` ignore it.
open func saveEventPacketIfNeeded(packet: SocketPacket, isInternalMessage: Bool) {
guard !isInternalMessage && pid != nil else { return }
savedEvents.append(packet)
}

/// Called when socket pass to connected state, handle events if socket recover data from server
open func handleSavedEventPackets() {
if recovered {
savedEvents.removeAll { packet in
return handleEvent(packet.event, data: packet.args, isInternalMessage: false)
}
} else {
savedEvents.removeAll()
}
}

/// Call when you wish to leave a namespace and disconnect this socket.
open func leaveNamespace() {
manager?.disconnectSocket(self)
Expand Down
24 changes: 23 additions & 1 deletion Source/SocketIO/Client/SocketIOClientSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ public protocol SocketIOClientSpec : AnyObject {

/// The id of this socket.io connect. This is different from the sid of the engine.io connection.
var sid: String? { get }
/// The id of this socket.io connect for connection state recovery.
var pid: String? { get }

/// Offset of last socket.io event for connection state recovery.
var lastEventOffset: String? { get }
/// Boolean setted after connection to know if socket state is recovered or not.
var recovered: Bool { get }

/// Array of events (or binary events) to handle when socket is connected and recover packets from server
var savedEvents: [SocketPacket] { get }

/// The status of this client.
var status: SocketIOStatus { get }
Expand Down Expand Up @@ -184,14 +194,26 @@ public protocol SocketIOClientSpec : AnyObject {
/// - parameter data: The data that was sent with this event.
/// - parameter isInternalMessage: Whether this event was sent internally. If `true` it is always sent to handlers.
/// - parameter ack: If > 0 then this event expects to get an ack back from the client.
func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int)
/// - returns: true if event is handled, false if event need to be saved (not handled)
func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int) -> Bool

/// Causes a client to handle a socket.io packet. The namespace for the packet must match the namespace of the
/// socket.
///
/// - parameter packet: The packet to handle.
func handlePacket(_ packet: SocketPacket)

/// Called when we get an event from socket.io
/// Save it to event array if an event is sent before socket is set to connected status.
/// Do not save event if pid is nil (cannot recover events from server)
///
/// - parameter packet: The packet to handle.
/// - parameter isInternalMessage: Whether this event was sent internally. If `true` ignore it.
func saveEventPacketIfNeeded(packet: SocketPacket, isInternalMessage: Bool)

/// Called when socket pass to connected state, handle events if socket recover data from server
func handleSavedEventPackets()

/// Call when you wish to leave a namespace and disconnect this socket.
func leaveNamespace()

Expand Down
17 changes: 17 additions & 0 deletions Source/SocketIO/Engine/SocketEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,15 @@ open class SocketEngine: NSObject, WebSocketDelegate, URLSessionDelegate,
}
}

/// Force close engine.
///
/// - parameter reason: The reason for the disconnection. This is communicated up to the client.
open func close(reason: String) {
guard connected && !closed else { return closeOutEngine(reason: reason) }
DefaultSocketLogger.Logger.log("Engine is being force closed.", type: SocketEngine.logType)
closeOutEngine(reason: reason)
}

private func _disconnect(reason: String) {
guard connected && !closed else { return closeOutEngine(reason: reason) }

Expand Down Expand Up @@ -563,6 +572,7 @@ open class SocketEngine: NSObject, WebSocketDelegate, URLSessionDelegate,
polling = true
probing = false
invalidated = false
session?.invalidateAndCancel()
session = Foundation.URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: queue)
sid = ""
waitingForPoll = false
Expand Down Expand Up @@ -714,6 +724,13 @@ open class SocketEngine: NSObject, WebSocketDelegate, URLSessionDelegate,

if let error = error as? WSError {
didError(reason: "\(error.message). code=\(error.code), type=\(error.type)")
} else if let error = error as? HTTPUpgradeError {
switch error {
case let .notAnUpgrade(int, _):
didError(reason: "notAnUpgrade. code=\(int)")
case .invalidData:
didError(reason: "invalidData")
}
} else if let reason = error?.localizedDescription {
didError(reason: reason)
} else {
Expand Down
5 changes: 5 additions & 0 deletions Source/SocketIO/Engine/SocketEngineSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ public protocol SocketEngineSpec: AnyObject {
/// - parameter reason: The reason for the disconnection. This is communicated up to the client.
func disconnect(reason: String)

/// Force close engine.
///
/// - parameter reason: The reason for the disconnection. This is communicated up to the client.
func close(reason: String)

/// Called to switch from HTTP long-polling to WebSockets. After calling this method the engine will be in
/// WebSocket mode.
///
Expand Down
14 changes: 12 additions & 2 deletions Source/SocketIO/Manager/SocketManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ open class SocketManager: NSObject, SocketManagerSpec, SocketParsable, SocketDat
public var randomizationFactor = 0.5

/// The status of this manager.
public private(set) var status: SocketIOStatus = .notConnected {
public var status: SocketIOStatus = .notConnected {
didSet {
switch status {
case .connected:
Expand Down Expand Up @@ -242,6 +242,16 @@ open class SocketManager: NSObject, SocketManagerSpec, SocketParsable, SocketDat
engine?.disconnect(reason: "Disconnect")
}

/// Force disconnects the manager and all associated sockets.
open func close() {
DefaultSocketLogger.Logger.log("Manager closing", type: SocketManager.logType)

status = .disconnected

engine?.close(reason: "Disconnect")
}


/// Disconnects the given socket.
///
/// This will remove the socket for the manager's control, and make the socket instance useless and ready for
Expand Down Expand Up @@ -565,7 +575,7 @@ open class SocketManager: NSObject, SocketManagerSpec, SocketParsable, SocketDat

_config = config

if socketURL.absoluteString.hasPrefix("https://") {
if socketURL.absoluteString.hasPrefix("https://") || socketURL.absoluteString.hasPrefix("wss://") {
_config.insert(.secure(true))
}

Expand Down
3 changes: 3 additions & 0 deletions Source/SocketIO/Manager/SocketManagerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ public protocol SocketManagerSpec : SocketEngineClient {
/// Disconnects the manager and all associated sockets.
func disconnect()

/// Force disconnects the manager and all associated sockets.
func close()

/// Disconnects the given socket.
///
/// - parameter socket: The socket to disconnect.
Expand Down