diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 53353f0..b2e4519 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 = "2.3.5" + s.version = "2.3.6" 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 => 'v2.3.5' } + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v2.3.6' } s.source_files = "SocketIOClientSwift/**/*.swift" s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files diff --git a/SocketIOClientSwift/SocketEngine.swift b/SocketIOClientSwift/SocketEngine.swift index 384cf95..ab390b5 100644 --- a/SocketIOClientSwift/SocketEngine.swift +++ b/SocketIOClientSwift/SocketEngine.swift @@ -27,24 +27,24 @@ import Foundation public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { private typealias Probe = (msg:String, type:PacketType, data:[NSData]?) private typealias ProbeWaitQueue = [Probe] - + private let allowedCharacterSet = NSCharacterSet(charactersInString: "!*'();:@&=+$,/?%#[]\" {}").invertedSet private let workQueue = NSOperationQueue() private let emitQueue = dispatch_queue_create("engineEmitQueue", DISPATCH_QUEUE_SERIAL) private let parseQueue = dispatch_queue_create("engineParseQueue", DISPATCH_QUEUE_SERIAL) private let handleQueue = dispatch_queue_create("engineHandleQueue", DISPATCH_QUEUE_SERIAL) private let session:NSURLSession! - + private var closed = false private var _connected = false private var fastUpgrade = false private var forcePolling = false private var forceWebsockets = false - private var pingInterval:Int? + private var pingInterval:Double? private var pingTimer:NSTimer? - private var pingTimeout = 0 { + private var pingTimeout = 0.0 { didSet { - pongsMissedMax = pingTimeout / (pingInterval ?? 25) + pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25)) } } private var pongsMissed = 0 @@ -57,9 +57,9 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { private var waitingForPost = false private var _websocket = false private var websocketConnected = false - + let logType = "SocketEngine" - + var connected:Bool { return _connected } @@ -77,7 +77,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { return _websocket } var ws:WebSocket? - + public enum PacketType:Int { case OPEN = 0 case CLOSE = 1 @@ -86,7 +86,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { case MESSAGE = 4 case UPGRADE = 5 case NOOP = 6 - + init?(str:String?) { if let value = Int(str ?? ""), raw = PacketType(rawValue: value) { self = raw @@ -95,13 +95,13 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } } } - + public init(client:SocketEngineClient, sessionDelegate:NSURLSessionDelegate?) { self.client = client self.session = NSURLSession(configuration: NSURLSessionConfiguration.ephemeralSessionConfiguration(), delegate: sessionDelegate, delegateQueue: workQueue) } - + public convenience init(client:SocketEngineClient, opts:NSDictionary?) { self.init(client: client, sessionDelegate: opts?["sessionDelegate"] as? NSURLSessionDelegate) forceWebsockets = opts?["forceWebsockets"] as? Bool ?? false @@ -110,56 +110,56 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { log = opts?["log"] as? Bool ?? false socketPath = opts?["path"] as? String ?? "" } - + deinit { SocketLogger.log("Engine is being deinit", client: self) } - + public func close(fast fast:Bool) { SocketLogger.log("Engine is being closed. Fast: %@", client: self, args: fast) - + pingTimer?.invalidate() closed = true - + ws?.disconnect() - + if fast || polling { write("", withType: PacketType.CLOSE, withData: nil) client?.engineDidClose("Disconnect") } - + stopPolling() } - + private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { if websocket { var byteArray = [UInt8](count: 1, repeatedValue: 0x0) byteArray[0] = 4 let mutData = NSMutableData(bytes: &byteArray, length: 1) - + mutData.appendData(data) - + return (mutData, nil) } else { var str = "b4" str += data.base64EncodedStringWithOptions( NSDataBase64EncodingOptions.Encoding64CharacterLineLength) - + return (nil, str) } } - + private func createURLs(params:[String: AnyObject]?) -> (String?, String?) { if client == nil { return (nil, nil) } - + let path = socketPath == "" ? "/socket.io" : socketPath - + let url = "\(client!.socketURL)\(path)/?transport=" var urlPolling:String var urlWebSocket:String - + if client!.secure { urlPolling = "https://" + url + "polling" urlWebSocket = "wss://" + url + "websocket" @@ -167,15 +167,15 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { urlPolling = "http://" + url + "polling" urlWebSocket = "ws://" + url + "websocket" } - + if params != nil { - + for (key, value) in params! { let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters( allowedCharacterSet)! urlPolling += "&\(keyEsc)=" urlWebSocket += "&\(keyEsc)=" - + if value is String { let valueEsc = (value as! String).stringByAddingPercentEncodingWithAllowedCharacters( allowedCharacterSet)! @@ -187,27 +187,27 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } } } - + return (urlPolling, urlWebSocket) } - + private func createWebsocket(andConnect connect:Bool) { ws = WebSocket(url: NSURL(string: urlWebSocket! + "&sid=\(sid)")!, cookies: cookies) ws?.queue = handleQueue ws?.delegate = self - + if connect { ws?.connect() } } - + private func doFastUpgrade() { if waitingForPoll { SocketLogger.err("Outstanding poll when switched to WebSockets," + "we'll probably disconnect soon. You should report this.", client: self) } - + sendWebSocketMessage("", withType: PacketType.UPGRADE, datas: nil) _websocket = true _polling = false @@ -215,32 +215,32 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { probing = false flushProbeWait() } - + private func doPoll() { if websocket || waitingForPoll || !connected { return } - + waitingForPoll = true let req = NSMutableURLRequest(URL: NSURL(string: urlPolling! + "&sid=\(sid)&b64=1")!) - + if cookies != nil { let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) req.allHTTPHeaderFields = headers } - + doRequest(req) } - + private func doRequest(req:NSMutableURLRequest) { if !polling { return } - + req.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData - + SocketLogger.log("Doing polling request", client: self) - + session.dataTaskWithRequest(req) {[weak self] data, res, err in if let this = self { if err != nil || data == nil { @@ -251,17 +251,17 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } return } - + SocketLogger.log("Got polling response", client: this) - + if let str = NSString(data: data!, encoding: NSUTF8StringEncoding) as? String { dispatch_async(this.parseQueue) {[weak this] in this?.parsePollingMessage(str) } } - + this.waitingForPoll = false - + if this.fastUpgrade { this.doFastUpgrade() } else if !this.closed && this.polling { @@ -269,25 +269,25 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } }}?.resume() } - + private func flushProbeWait() { SocketLogger.log("Flushing probe wait", client: self) - + dispatch_async(emitQueue) {[weak self] in if let this = self { for waiter in this.probeWait { this.write(waiter.msg, withType: waiter.type, withData: waiter.data) } - + this.probeWait.removeAll(keepCapacity: false) - + if this.postWait.count != 0 { this.flushWaitingForPostToWebSocket() } } } } - + private func flushWaitingForPost() { if postWait.count == 0 || !connected { return @@ -295,37 +295,37 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { flushWaitingForPostToWebSocket() return } - + var postStr = "" - + for packet in postWait { let len = packet.characters.count - + postStr += "\(len):\(packet)" } - + postWait.removeAll(keepCapacity: false) - + let req = NSMutableURLRequest(URL: NSURL(string: urlPolling! + "&sid=\(sid)")!) - + if cookies != nil { let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) req.allHTTPHeaderFields = headers } - + req.HTTPMethod = "POST" req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") - + let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - + req.HTTPBody = postData req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") - + waitingForPost = true - + SocketLogger.log("POSTing: %@", client: self, args: postStr) - + session.dataTaskWithRequest(req) {[weak self] data, res, err in if let this = self { if err != nil && this.polling { @@ -335,9 +335,9 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { NSLog(err?.localizedDescription ?? "Error") return } - + this.waitingForPost = false - + dispatch_async(this.emitQueue) {[weak this] in if !(this?.fastUpgrade ?? true) { this?.flushWaitingForPost() @@ -346,29 +346,29 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } }}?.resume() } - + // We had packets waiting for send when we upgraded // Send them raw private func flushWaitingForPostToWebSocket() { for msg in postWait { ws?.writeString(msg) } - + postWait.removeAll(keepCapacity: true) } - + private func handleClose() { if polling { client?.engineDidClose("Disconnect") } } - + private func checkIfMessageIsBase64Binary(var message:String) { if message.hasPrefix("b4") { // binary in base64 string message.removeRange(Range(start: message.startIndex, end: advance(message.startIndex, 2))) - + if let data = NSData(base64EncodedString: message, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters), client = client { dispatch_async(client.handleQueue) {[weak self] in @@ -377,7 +377,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } } } - + private func handleMessage(message:String) { if let client = client { dispatch_async(client.handleQueue) {[weak client] in @@ -385,50 +385,50 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } } } - + private func handleNOOP() { doPoll() } - + private func handleOpen(openData:String) { let mesData = openData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - + do { let json = try NSJSONSerialization.JSONObjectWithData(mesData, options: NSJSONReadingOptions.AllowFragments) as? NSDictionary if let sid = json?["sid"] as? String { self.sid = sid _connected = true - + if !forcePolling && !forceWebsockets { createWebsocket(andConnect: true) } - if let pingInterval = json?["pingInterval"] as? Int, pingTimeout = json?["pingTimeout"] as? Int { - self.pingInterval = pingInterval / 1000 - self.pingTimeout = pingTimeout / 1000 + if let pingInterval = json?["pingInterval"] as? Double, pingTimeout = json?["pingTimeout"] as? Double { + self.pingInterval = pingInterval / 1000.0 + self.pingTimeout = pingTimeout / 1000.0 } } } catch { SocketLogger.err("Error parsing open packet", client: self) } - + startPingTimer() - + if !forceWebsockets { doPoll() } } - + private func handlePong(pongMessage:String) { pongsMissed = 0 - + // We should upgrade if pongMessage == "3probe" { upgradeTransport() } } - + // A poll failed, tell the client about it private func handlePollingFailed(reason:String) { _connected = false @@ -436,63 +436,63 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { pingTimer?.invalidate() waitingForPoll = false waitingForPost = false - + // If cancelled we were already closing if client == nil || reason == "cancelled" { return } - + if !closed { client?.didError(reason) client?.engineDidClose(reason) } } - + public func open(opts:[String: AnyObject]? = nil) { if connected { SocketLogger.err("Tried to open while connected", client: self) - + client?.didError("Tried to open while connected") return } - + SocketLogger.log("Starting engine", client: self) SocketLogger.log("Handshaking", client: self) - + closed = false - + (urlPolling, urlWebSocket) = createURLs(opts) - + if forceWebsockets { _polling = false _websocket = true createWebsocket(andConnect: true) return } - + let reqPolling = NSMutableURLRequest(URL: NSURL(string: urlPolling! + "&b64=1")!) - + if cookies != nil { let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) reqPolling.allHTTPHeaderFields = headers } - + doRequest(reqPolling) } - + // Translatation of engine.io-parser#decodePayload private func parsePollingMessage(str:String) { if str.characters.count == 1 { return } - + // println(str) - + let strArray = Array(str.characters) var length = "" var n = 0 var msg = "" - + func testLength(length:String, inout n:Int) -> Bool { if let num = Int(length) { n = num @@ -501,10 +501,10 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { return true } } - + for var i = 0, l = str.characters.count; i < l; i++ { let chr = String(strArray[i]) - + if chr != ":" { length += chr } else { @@ -513,27 +513,27 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { handlePollingFailed("Error parsing XHR message") return } - + msg = String(strArray[i+1...i+n]) - + if let lengthInt = Int(length) where lengthInt != msg.characters.count { SocketLogger.err("Parsing error: %@", client: self, args: str) return } - + if msg.characters.count != 0 { // Be sure to capture the value of the msg dispatch_async(handleQueue) {[weak self, msg] in self?.parseEngineMessage(msg, fromPolling: true) } } - + i += n length = "" } } } - + private func parseEngineData(data:NSData) { if let client = client { dispatch_async(client.handleQueue) {[weak self] in @@ -541,19 +541,19 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } } } - + private func parseEngineMessage(var message:String, fromPolling:Bool) { SocketLogger.log("Got message: %@", client: self, args: message) - + if fromPolling { fixDoubleUTF8(&message) } - + let type = PacketType(str: (message["^(\\d)"].groups()?[1])) ?? { self.checkIfMessageIsBase64Binary(message) return PacketType.NOOP }() - + switch type { case PacketType.MESSAGE: message.removeAtIndex(message.startIndex) @@ -571,13 +571,13 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { SocketLogger.log("Got unknown packet type", client: self) } } - + private func probeWebSocket() { if websocketConnected { sendWebSocketMessage("probe", withType: PacketType.PING) } } - + /// Send an engine message (4) public func send(msg:String, withData datas:[NSData]?) { if probing { @@ -586,7 +586,7 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { write(msg, withType: PacketType.MESSAGE, withData: datas) } } - + @objc private func sendPing() { //Server is not responding if pongsMissed > pongsMissedMax { @@ -594,43 +594,43 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { client?.engineDidClose("Ping timeout") return } - + ++pongsMissed write("", withType: PacketType.PING, withData: nil) } - + /// Send polling message. /// Only call on emitQueue private func sendPollMessage(var msg:String, withType type:PacketType, datas:[NSData]? = nil) { SocketLogger.log("Sending poll: %@ as type: %@", client: self, args: msg, type.rawValue) - + doubleEncodeUTF8(&msg) let strMsg = "\(type.rawValue)\(msg)" - + postWait.append(strMsg) - + if datas != nil { for data in datas! { let (_, b64Data) = createBinaryDataForSend(data) - + postWait.append(b64Data!) } } - + if !waitingForPost { flushWaitingForPost() } } - + /// Send message on WebSockets /// Only call on emitQueue private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { SocketLogger.log("Sending ws: %@ as type: %@", client: self, args: str, type.rawValue) - + ws?.writeString("\(type.rawValue)\(str)") - + if datas != nil { for data in datas! { let (data, _) = createBinaryDataForSend(data) @@ -640,37 +640,36 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } } } - + // Starts the ping timer private func startPingTimer() { if pingInterval == nil { return } - + pingTimer?.invalidate() dispatch_async(dispatch_get_main_queue()) {[weak self] in if let this = self { - this.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(this.pingInterval!), - target: this, + this.pingTimer = NSTimer.scheduledTimerWithTimeInterval(this.pingInterval!, target: this, selector: Selector("sendPing"), userInfo: nil, repeats: true) } } } - + func stopPolling() { session.invalidateAndCancel() } - + private func upgradeTransport() { if websocketConnected { SocketLogger.log("Upgrading transport to WebSockets", client: self) - + fastUpgrade = true sendPollMessage("", withType: PacketType.NOOP) // After this point, we should not send anymore polling messages } } - + /** Write a message, independent of transport. */ @@ -689,29 +688,29 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { } } } - + /** Write a message, independent of transport. For Objective-C. withData should be an NSArray of NSData */ public func writeObjc(msg:String, withType type:Int, withData data:NSArray?) { if let pType = PacketType(rawValue: type) { var arr = [NSData]() - + if data != nil { for d in data! { arr.append(d as! NSData) } } - + write(msg, withType: pType, withData: arr) } } - + // Delagate methods - + public func websocketDidConnect(socket:WebSocket) { websocketConnected = true - + if !forceWebsockets { probing = true probeWebSocket() @@ -721,37 +720,37 @@ public final class SocketEngine: NSObject, WebSocketDelegate, SocketLogClient { _polling = false } } - + public func websocketDidDisconnect(socket:WebSocket, error:NSError?) { websocketConnected = false probing = false - + if closed { client?.engineDidClose("Disconnect") return } - + if websocket { pingTimer?.invalidate() _connected = false _websocket = false - + let reason = error?.localizedDescription ?? "Socket Disconnected" - + if error != nil { client?.didError(reason) } - + client?.engineDidClose(reason) } else { flushProbeWait() } } - + public func websocketDidReceiveMessage(socket:WebSocket, text:String) { parseEngineMessage(text, fromPolling: false) } - + public func websocketDidReceiveData(socket:WebSocket, data:NSData) { parseEngineData(data) }