diff --git a/SocketIOClientSwift/SocketEngine.swift b/SocketIOClientSwift/SocketEngine.swift index f714059..d570d21 100644 --- a/SocketIOClientSwift/SocketEngine.swift +++ b/SocketIOClientSwift/SocketEngine.swift @@ -33,6 +33,8 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { public private(set) var ws: WebSocket? public weak var client: SocketEngineClient? + + private weak var sessionDelegate: NSURLSessionDelegate? private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) private typealias ProbeWaitQueue = [Probe] @@ -45,6 +47,7 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { private let url: String private let workQueue = NSOperationQueue() + private var connectParams: [String: AnyObject]? private var closed = false private var extraHeaders: [String: String]? private var fastUpgrade = false @@ -64,12 +67,11 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { private var probing = false private var probeWait = ProbeWaitQueue() private var secure = false - private var session: NSURLSession! + private var session: NSURLSession? private var voipEnabled = false private var waitingForPoll = false private var waitingForPost = false private var websocketConnected = false - private(set) var connected = false private(set) var polling = true private(set) var websocket = false @@ -81,9 +83,7 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { for option in options { switch option { case .SessionDelegate(let delegate): - session = NSURLSession(configuration: .defaultSessionConfiguration(), - delegate: delegate, - delegateQueue: workQueue) + sessionDelegate = delegate case .ForcePolling(let force): forcePolling = force case .ForceWebsockets(let force): @@ -102,12 +102,6 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { continue } } - - if session == nil { - session = NSURLSession(configuration: .defaultSessionConfiguration(), - delegate: nil, - delegateQueue: workQueue) - } } public convenience init(client: SocketEngineClient, url: String, options: NSDictionary?) { @@ -120,6 +114,35 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { closed = true stopPolling() } + + private func checkAndHandleEngineError(msg: String) { + guard let stringData = msg.dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false) else { return } + + do { + if let dict = try NSJSONSerialization.JSONObjectWithData(stringData, + options: NSJSONReadingOptions.MutableContainers) as? NSDictionary { + guard let code = dict["code"] as? Int else { return } + guard let error = dict["message"] as? String else { return } + + switch code { + case 0: // Unknown transport + logAndError(error) + case 1: // Unknown sid. clear and retry connect + sid = "" + open(connectParams) + case 2: // Bad handshake request + logAndError(error) + case 3: // Bad request + logAndError(error) + default: + logAndError(error) + } + } + } catch { + logAndError("Got unknown error from server") + } + } private func checkIfMessageIsBase64Binary(var message: String) -> Bool { if message.hasPrefix("b4") { @@ -143,6 +166,7 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { pingTimer?.invalidate() closed = true + connected = false if websocket { sendWebSocketMessage("", withType: .Close) @@ -341,11 +365,18 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { client?.engineDidClose(reason) } } + + private func logAndError(error: String) { + DefaultSocketLogger.Logger.error(error, type: logType) + client?.didError(error) + } public func open(opts: [String: AnyObject]? = nil) { + connectParams = opts + if connected { DefaultSocketLogger.Logger.error("Tried to open while connected", type: logType) - client?.didError("Tried to open while connected") + client?.didError("Tried to open engine while connected") return } @@ -353,7 +384,7 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { DefaultSocketLogger.Logger.log("Starting engine", type: logType) DefaultSocketLogger.Logger.log("Handshaking", type: logType) - closed = false + resetEngine() (urlPolling, urlWebSocket) = createURLs(opts) @@ -388,19 +419,15 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { private func parseEngineMessage(var message: String, fromPolling: Bool) { DefaultSocketLogger.Logger.log("Got message: %@", type: logType, args: message) - func handleOther(msg: String) -> SocketEnginePacketType { - if checkIfMessageIsBase64Binary(msg) { - return .Noop - } else { - DefaultSocketLogger.Logger.error("Got message: %@", type: logType, args: msg) - return .Close - } - } - let reader = SocketStringReader(message: message) - let type = SocketEnginePacketType(rawValue: Int(reader.currentCharacter) ?? -1) - ?? handleOther(message) + guard let type = SocketEnginePacketType(rawValue: Int(reader.currentCharacter) ?? -1) else { + if !checkIfMessageIsBase64Binary(message) { + checkAndHandleEngineError(message) + } + + return + } if fromPolling && type != .Noop { fixDoubleUTF8(&message) @@ -429,7 +456,25 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate { sendWebSocketMessage("probe", withType: .Ping) } } - + + + private func resetEngine() { + closed = false + connected = false + fastUpgrade = false + polling = true + probing = false + invalidated = false + session = NSURLSession(configuration: .defaultSessionConfiguration(), + delegate: sessionDelegate, + delegateQueue: workQueue) + sid = "" + waitingForPoll = false + waitingForPost = false + websocket = false + websocketConnected = false + } + /// Send an engine message (4) public func send(msg: String, withData datas: [NSData]) { if probing { @@ -525,13 +570,14 @@ extension SocketEngine { private func doRequest(req: NSMutableURLRequest, withCallback callback: (NSData?, NSURLResponse?, NSError?) -> Void) { if !polling || closed || invalidated { + DefaultSocketLogger.Logger.error("Tried to do polling request when not supposed to", type: logType) return } DefaultSocketLogger.Logger.log("Doing polling request", type: logType) req.cachePolicy = .ReloadIgnoringLocalAndRemoteCacheData - session.dataTaskWithRequest(req, completionHandler: callback).resume() + session?.dataTaskWithRequest(req, completionHandler: callback).resume() } private func doLongPoll(req: NSMutableURLRequest) { @@ -684,7 +730,7 @@ extension SocketEngine { private func stopPolling() { invalidated = true - session.finishTasksAndInvalidate() + session?.finishTasksAndInvalidate() } } diff --git a/SocketIOClientSwift/SocketIOClient.swift b/SocketIOClientSwift/SocketIOClient.swift index 4213061..51dcb3f 100644 --- a/SocketIOClientSwift/SocketIOClient.swift +++ b/SocketIOClientSwift/SocketIOClient.swift @@ -30,6 +30,7 @@ public final class SocketIOClient: NSObject, SocketEngineClient { public private(set) var engine: SocketEngineSpec? public private(set) var status = SocketIOClientStatus.NotConnected + public var forceNew = false public var nsp = "/" public var options: Set public var reconnects = true @@ -88,6 +89,8 @@ public final class SocketIOClient: NSObject, SocketEngineClient { DefaultSocketLogger.Logger = logger case .HandleQueue(let queue): handleQueue = queue + case .ForceNew(let force): + forceNew = force default: continue } @@ -109,7 +112,6 @@ public final class SocketIOClient: NSObject, SocketEngineClient { deinit { DefaultSocketLogger.Logger.log("Client is being deinit", type: logType) - DefaultSocketLogger.Logger.log = false engine?.close() } @@ -154,21 +156,21 @@ public final class SocketIOClient: NSObject, SocketEngineClient { assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") guard status != .Connected else { - return - } - - if status == .Closed { - DefaultSocketLogger.Logger.log("Warning! This socket was previously closed. This might be dangerous!", + DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", type: logType) - } - - status = SocketIOClientStatus.Connecting - addEngine().open(connectParams) - - guard timeoutAfter != 0 else { return } + status = .Connecting + + if engine == nil || forceNew { + addEngine().open(connectParams) + } else { + engine?.open(connectParams) + } + + guard timeoutAfter != 0 else { return } + let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) dispatch_after(time, handleQueue) {[weak self] in diff --git a/SocketIOClientSwift/SocketIOClientOption.swift b/SocketIOClientSwift/SocketIOClientOption.swift index 6036486..784a06b 100644 --- a/SocketIOClientSwift/SocketIOClientOption.swift +++ b/SocketIOClientSwift/SocketIOClientOption.swift @@ -32,6 +32,7 @@ public enum SocketIOClientOption: ClientOption { case ConnectParams([String: AnyObject]) case Cookies([NSHTTPCookie]) case ExtraHeaders([String: String]) + case ForceNew(Bool) case ForcePolling(Bool) case ForceWebsockets(Bool) case HandleQueue(dispatch_queue_t) @@ -68,6 +69,8 @@ public enum SocketIOClientOption: ClientOption { return .ReconnectAttempts(value as! Int) case "reconnectWait" where value is Int: return .ReconnectWait(value as! Int) + case "forceNew" where value is Bool: + return .ForceNew(value as! Bool) case "forcePolling" where value is Bool: return .ForcePolling(value as! Bool) case "forceWebsockets" where value is Bool: