diff --git a/README.md b/README.md index ddd28c2..7c6f2ba 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Carthage ----------------- Add this line to your `Cartfile`: ``` -github "socketio/socket.io-client-swift" ~> 3.1.2 # Or latest version +github "socketio/socket.io-client-swift" ~> 3.1.4 # Or latest version ``` Run `carthage update --platform ios,macosx`. @@ -83,7 +83,7 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' use_frameworks! -pod 'Socket.IO-Client-Swift', '~> 3.1.2' # Or latest version +pod 'Socket.IO-Client-Swift', '~> 3.1.4' # Or latest version ``` Install pods: diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 168d637..f09d17b 100644 --- a/Socket.IO-Client-Swift.podspec +++ b/Socket.IO-Client-Swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Socket.IO-Client-Swift" - s.version = "3.1.2" + s.version = "3.1.4" s.summary = "Socket.IO-client for iOS and OS X" s.description = <<-DESC Socket.IO-client for iOS and OS X. @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.author = { "Erik" => "nuclear.ace@gmail.com" } s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.10' - s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v3.1.2' } + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v3.1.4' } s.source_files = "SocketIOClientSwift/**/*.swift" s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files diff --git a/SocketIO-iOSTests/SocketParserTest.swift b/SocketIO-iOSTests/SocketParserTest.swift index 0eff01d..0e42f6f 100644 --- a/SocketIO-iOSTests/SocketParserTest.swift +++ b/SocketIO-iOSTests/SocketParserTest.swift @@ -19,7 +19,9 @@ class SocketParserTest: XCTestCase { "61-/swift,19[[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]": ("/swift", [ [1, 2], ["test": "bob"], 25, "polo", "~~0"], [], 19), "4/swift,": ("/swift", [], [], -1), "0/swift": ("/swift", [], [], -1), - "1/swift": ("/swift", [], [], -1)] + "1/swift": ("/swift", [], [], -1), + "4\"ERROR\"": ("/", ["ERROR"], [], -1), + "41": ("/", [1], [], -1)] func testDisconnect() { let message = "1" @@ -66,6 +68,16 @@ class SocketParserTest: XCTestCase { validateParseResult(message) } + func testErrorTypeString() { + let message = "4\"ERROR\"" + validateParseResult(message) + } + + func testErrorTypeInt() { + let message = "41" + validateParseResult(message) + } + func testInvalidInput() { let message = "8" switch SocketParser.parseString(message) { diff --git a/SocketIOClientSwift/SocketEngine.swift b/SocketIOClientSwift/SocketEngine.swift index dc3234c..57f58b0 100644 --- a/SocketIOClientSwift/SocketEngine.swift +++ b/SocketIOClientSwift/SocketEngine.swift @@ -29,10 +29,10 @@ public final class SocketEngine: NSObject, WebSocketDelegate { private typealias ProbeWaitQueue = [Probe] private let allowedCharacterSet = NSCharacterSet(charactersInString: "!*'();:@&=+$,/?%#[]\" {}").invertedSet - private let emitQueue = dispatch_queue_create("engineEmitQueue", DISPATCH_QUEUE_SERIAL) - private let handleQueue = dispatch_queue_create("engineHandleQueue", DISPATCH_QUEUE_SERIAL) + private let emitQueue = dispatch_queue_create("com.socketio.engineEmitQueue", DISPATCH_QUEUE_SERIAL) + private let handleQueue = dispatch_queue_create("com.socketio.engineHandleQueue", DISPATCH_QUEUE_SERIAL) private let logType = "SocketEngine" - private let parseQueue = dispatch_queue_create("engineParseQueue", DISPATCH_QUEUE_SERIAL) + private let parseQueue = dispatch_queue_create("com.socketio.engineParseQueue", DISPATCH_QUEUE_SERIAL) private let session: NSURLSession! private let workQueue = NSOperationQueue() @@ -41,6 +41,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate { private var fastUpgrade = false private var forcePolling = false private var forceWebsockets = false + private var invalidated = false private var pingInterval: Double? private var pingTimer: NSTimer? private var pingTimeout = 0.0 { @@ -69,7 +70,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate { var urlWebSocket = "" var ws: WebSocket? - + @objc public enum PacketType: Int { case Open, Close, Ping, Pong, Message, Upgrade, Noop @@ -99,14 +100,16 @@ public final class SocketEngine: NSObject, WebSocketDelegate { deinit { Logger.log("Engine is being deinit", type: logType) + closed = true + stopPolling() } - + private func checkIfMessageIsBase64Binary(var message: String) { if message.hasPrefix("b4") { // binary in base64 string message.removeRange(Range(start: message.startIndex, end: message.startIndex.advancedBy(2))) - + if let data = NSData(base64EncodedString: message, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { client?.parseBinaryData(data) @@ -147,7 +150,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate { return .Right(str) } } - + private func createURLs(params: [String: AnyObject]?) -> (String, String) { if client == nil { return ("", "") @@ -190,22 +193,22 @@ public final class SocketEngine: NSObject, WebSocketDelegate { private func createWebsocketAndConnect(connect: Bool) { let wsUrl = urlWebSocket + (sid == "" ? "" : "&sid=\(sid)") - + ws = WebSocket(url: NSURL(string: wsUrl)!) - + if cookies != nil { let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) for (key, value) in headers { ws?.headers[key] = value } } - + if extraHeaders != nil { for (headerName, value) in extraHeaders! { ws?.headers[headerName] = value } } - + ws?.queue = handleQueue ws?.delegate = self @@ -240,26 +243,30 @@ public final class SocketEngine: NSObject, WebSocketDelegate { let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) req.allHTTPHeaderFields = headers } - + if extraHeaders != nil { for (headerName, value) in extraHeaders! { req.setValue(value, forHTTPHeaderField: headerName) } } - - doRequest(req) + + doLongPoll(req) } - private func doRequest(req: NSMutableURLRequest) { - if !polling || closed { - return - } + private func doRequest(req: NSMutableURLRequest, + withCallback callback: (NSData?, NSURLResponse?, NSError?) -> Void) { + if !polling || closed || invalidated { + return + } - req.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData + Logger.log("Doing polling request", type: logType) - Logger.log("Doing polling request", type: logType) + req.cachePolicy = .ReloadIgnoringLocalAndRemoteCacheData + session.dataTaskWithRequest(req, completionHandler: callback).resume() + } - session.dataTaskWithRequest(req) {[weak self] data, res, err in + private func doLongPoll(req: NSMutableURLRequest) { + doRequest(req) {[weak self] data, res, err in if let this = self { if err != nil || data == nil { if this.polling { @@ -285,7 +292,8 @@ public final class SocketEngine: NSObject, WebSocketDelegate { } else if !this.closed && this.polling { this.doPoll() } - }}.resume() + } + } } private func flushProbeWait() { @@ -344,13 +352,13 @@ public final class SocketEngine: NSObject, WebSocketDelegate { Logger.log("POSTing: %@", type: logType, args: postStr) - session.dataTaskWithRequest(req) {[weak self] data, res, err in + doRequest(req) {[weak self] data, res, err in if let this = self { if err != nil && this.polling { this.handlePollingFailed(err?.localizedDescription ?? "Error") return } else if err != nil { - NSLog(err?.localizedDescription ?? "Error") + Logger.error(err?.localizedDescription ?? "Error", type: this.logType) return } @@ -362,14 +370,15 @@ public final class SocketEngine: NSObject, WebSocketDelegate { this?.doPoll() } } - }}.resume() + } + } } // We had packets waiting for send when we upgraded // Send them raw private func flushWaitingForPostToWebSocket() { guard let ws = self.ws else {return} - + for msg in postWait { ws.writeString(msg) } @@ -401,18 +410,18 @@ public final class SocketEngine: NSObject, WebSocketDelegate { self.sid = sid connected = true - + if let upgrades = json?["upgrades"] as? [String] { upgradeWs = upgrades.filter {$0 == "websocket"}.count != 0 } else { upgradeWs = false } - + if let pingInterval = json?["pingInterval"] as? Double, pingTimeout = json?["pingTimeout"] as? Double { self.pingInterval = pingInterval / 1000.0 self.pingTimeout = pingTimeout / 1000.0 } - + if !forcePolling && !forceWebsockets && upgradeWs { createWebsocketAndConnect(true) } @@ -456,7 +465,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate { if connected { Logger.error("Tried to open while connected", type: logType) client?.didError("Tried to open while connected") - + return } @@ -480,27 +489,27 @@ public final class SocketEngine: NSObject, WebSocketDelegate { let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) reqPolling.allHTTPHeaderFields = headers } - + if let extraHeaders = extraHeaders { for (headerName, value) in extraHeaders { reqPolling.setValue(value, forHTTPHeaderField: headerName) } } - - doRequest(reqPolling) + + doLongPoll(reqPolling) } private func parsePollingMessage(str: String) { guard str.characters.count != 1 else { return } - + var reader = SocketStringReader(message: str) - + while reader.hasNext { if let n = Int(reader.readUntilStringOccurence(":")) { let str = reader.read(n) - + dispatch_async(handleQueue) { self.parseEngineMessage(str, fromPolling: true) } @@ -626,6 +635,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate { } func stopPolling() { + invalidated = true session.finishTasksAndInvalidate() } @@ -692,7 +702,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate { if error != nil { client?.didError(reason) } - + client?.engineDidClose(reason) } else { flushProbeWait() diff --git a/SocketIOClientSwift/SocketIOClient.swift b/SocketIOClientSwift/SocketIOClient.swift index 3c716b9..f787864 100644 --- a/SocketIOClientSwift/SocketIOClient.swift +++ b/SocketIOClientSwift/SocketIOClient.swift @@ -25,7 +25,7 @@ import Foundation public final class SocketIOClient: NSObject, SocketEngineClient { - private let emitQueue = dispatch_queue_create("emitQueue", DISPATCH_QUEUE_SERIAL) + private let emitQueue = dispatch_queue_create("com.socketio.emitQueue", DISPATCH_QUEUE_SERIAL) private let handleQueue: dispatch_queue_t! public let socketURL: String @@ -111,7 +111,6 @@ public final class SocketIOClient: NSObject, SocketEngineClient { deinit { Logger.log("Client is being deinit", type: logType) - engine?.close(fast: true) } private func addEngine() -> SocketEngine { @@ -172,10 +171,10 @@ public final class SocketIOClient: NSObject, SocketEngineClient { let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) - dispatch_after(time, dispatch_get_main_queue()) { - if self.status != .Connected { - self.status = .Closed - self.engine?.close(fast: true) + dispatch_after(time, dispatch_get_main_queue()) {[weak self] in + if self != nil && self?.status != .Connected { + self?.status = .Closed + self?.engine?.close(fast: true) handler?() } diff --git a/SocketIOClientSwift/SocketParser.swift b/SocketIOClientSwift/SocketParser.swift index 64d359d..b25d8d0 100644 --- a/SocketIOClientSwift/SocketParser.swift +++ b/SocketIOClientSwift/SocketParser.swift @@ -54,7 +54,7 @@ class SocketParser { case .Disconnect: socket.didDisconnect("Got Disconnect") case .Error: - socket.didError("Error: \(pack.data)") + socket.didError(pack.data) default: Logger.log("Got invalid packet: %@", type: "SocketParser", args: pack.description) } @@ -93,7 +93,11 @@ class SocketParser { var idString = "" - while parser.hasNext { + if type == .Error { + parser.advanceIndexBy(-1) + } + + while parser.hasNext && type != .Error { if let int = Int(parser.read(1)) { idString += String(int) } else { @@ -107,7 +111,13 @@ class SocketParser { switch parseData(noPlaceholders) { case .Left(let err): - return .Left(err) + // If first you don't succeed, try again + if case let .Right(data) = parseData("\([noPlaceholders as AnyObject])") { + return .Right(SocketPacket(type: type, data: data, id: Int(idString) ?? -1, + nsp: namespace ?? "/", placeholders: placeholders)) + } else { + return .Left(err) + } case .Right(let data): return .Right(SocketPacket(type: type, data: data, id: Int(idString) ?? -1, nsp: namespace ?? "/", placeholders: placeholders)) diff --git a/SocketIOClientSwift/WebSocket.swift b/SocketIOClientSwift/WebSocket.swift index 7e4fc7d..21245cb 100644 --- a/SocketIOClientSwift/WebSocket.swift +++ b/SocketIOClientSwift/WebSocket.swift @@ -319,9 +319,12 @@ public class WebSocket : NSObject, NSStreamDelegate { let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) if length > 0 { if !connected { - let status = processHTTP(buffer, bufferLen: length) - if !status { - doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: 1)) + connected = processHTTP(buffer, bufferLen: length) + if !connected { + let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() + CFHTTPMessageAppendBytes(response, buffer, length) + let code = CFHTTPMessageGetResponseStatusCode(response) + doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code))) } } else { var process = false @@ -372,7 +375,6 @@ public class WebSocket : NSObject, NSStreamDelegate { if validateResponse(buffer, bufferLen: totalSize) { dispatch_async(queue,{ [weak self] in guard let s = self else { return } - s.connected = true if let connectBlock = s.onConnect { connectBlock() }